代理¶
本地脚本 API¶
src/main/automation-server.ts 是一个回环 HTTP 监听器,本地脚本通过它向已启用的代理发送消息、运行例程,并回答待处理的问题和审批。它还会读取投递结果、更改代理的模型和账户,以及开始新的聊天。docs/automation.md 包含相关路由和命令。监听器仅在至少一个代理启用了 allowAutomation 时运行,并在空闲端口上绑定 127.0.0.1。它将 URL 和新的 bearer Token 写入 <userData>/automation/(文件夹 0700,文件 0600),并在停止时删除它们。在检查 Token 之前,它会拒绝任何带有 Origin 头或非本机 Host 的请求,因此网页无法访问它。
此处的一次运行是指携带指令后的载荷进行的手动例程运行,因此 Team API 协议、运行类型或发送方均不改变。载荷保存在运行行中,崩溃后 resumePendingRuns 会再次发送它。该标志位于 agent_json 中,无需迁移;没有该标志的配置即为关闭。只有用户能更改它:常规 Team API 代理解析器和代理配置工具不接受它,常规远程 IPC 更新会拒绝它,复制也不会拷贝它。当该标志开启时,开发者指令中会写明这两个文件路径,而绝不会提及 Token。
所有者或管理员也可以通过现有的 agent-host-settings-v1 路由更改该标志。桌面端、Web 端和移动端客户端使用这些路由。常规代理更新路由仍会拒绝它,而没有此能力的主机则保持隐藏远程控制。远程客户端从主机设置响应中读取该标志,因为已发布的代理摘要不包含它。
关于 #1720 的权限决定:开发者明确批准将现有的本地脚本授权扩展到消息和问题/审批响应,包括已经启用该授权的代理。没有新的选择加入机制或能力。这是对 agent-host-settings-v1 首次发布时仅限例程这一含义的具体例外。较旧的客户端仍然可以更改同一标志;其旧文本可能只描述例程。线上载荷以及所有者/管理员检查保持不变。
消息使用 AgentService.sendMessage 及其持久化的重试键。注意力注册表为每个待处理对象分配一个不透明的本地句柄。响应会同时匹配该句柄和代理;过期的句柄无法应答后续的提供方请求。待处理详情不会为了 UI 快照预算而裁剪。HTTP 边界会对传出值进行脱敏。浏览器接管和机密问题仅是通知,并引导用户前往 OpenBot。消息和例程运行共享同一速率限制。
计划例程更改¶
代理调度器每轮读取一次到期列表。在开始该次例程之前,它会重新读取每个例程,因为将较早的运行排队时可能会让出执行。暂停或删除的例程、被替换的触发器或变更的到期时间,都会使旧列表不再启动任何工作。如果编辑保留了相同的触发器和到期时间,则使用当前的指令和策略。运行行仍然在触发器推进或完成之前提交,因此重启恢复会保留相同的运行和邮箱投递。
静默例程运行¶
代理例程的定时、脚本或 webhook 运行可以通过两种方式不产生任何消息地结束。没有人在等待这种运行的答案(quietRoutineRun)。运行历史会在 run_source 中存储启动手动运行的方式(test、script 或 webhook);来自迁移 35 之前的手动运行没有该字段,会被视为 Test 运行。代理会调用运行提示中提到的 routine_no_update 工具,或者用户会在例程任务中要求仅返回 [[no-update]] 作为答案。模型常常会写一个短句而不是精确的 token,因此该工具是主要路径。没有相关设置。该工具和标记不依赖于答案的语言(src/backend/agent/routine-quiet-runs.ts)。工具调用会保存在内存中,直到回合完成;历史导入会通过工具名称再次找到它,就像通过文本来找到标记一样。当仅运行了静默例程运行的回合完成,且每个答案都是标记,或代理调用了该工具且回合没有给出文件、图像或问题时,该回合会从会话中丢弃其标记、思考和计划。其他每个文本答案都会以条目类型 routine_quiet_answer 保留。静默答案不计为未读,不改变代理预览,任何 Team API 客户端都不会收到它;桌面聊天会在其运行条目下显示它,且该条目初始为关闭状态。该回合会恢复运行前的代理预览(运行开始时会在那里显示任务;仅保存在内存中,因此重启后任务仍会保留),并且其 turn-completed 事件带有 quiet: true,从而阻止桌面通知和完成提示音。运行标记和运行历史都会保留。较长答案中的标记属于报告,会被显示。Test 运行永远不会静默。
quiet 也会通过可选的 quiet-turn-v1 能力(packages/contracts/src/team-protocol/quiet-turn-v1.ts)到达远程客户端。主机向具备该能力的客户端发送 quiet-turn-completed 事件,代替 turn-completed。客户端传输层会将其改回带有 quiet: true 的 turn-completed(optional-events.ts)。随后,浏览器客户端、手机以及连接到远程服务器的桌面端都不会显示通知、播放完成提示音,Dynamic Island 也不会显示新回复。不具备该能力的客户端会像以前一样收到已发布的 turn-completed 事件,并将运行显示为已完成。格式错误的 quiet-turn-completed 事件会被视为错误。
例程运行失败¶
一次运行的状态来自其队列投递(routineStatusForDelivery),因此已完成的回合就是 succeeded 运行,即使其中的任务失败也是如此。每个运行提示都会提到 routine_report_failure 工具,Test 运行和流程交接也一样。当任务无法完成时,代理会用简短的 reason 调用该工具。该原因会保存在内存中,直到回合完成。回合完成后,该回合的每个例程投递都会以 completed 结束,原因写入其 error 列(像其他错误一样被脱敏)。代理确实给出了回答,因此回答、聊天摘要和通知与之前一样。routineStatusForDelivery 将带有错误的已完成投递映射为带有该错误的 failed 运行,因此运行会一步从 running 变为 failed,之后的每次对账都会使其保持失败。没有新增列。失败的运行永远不会静默,即使在调用 routine_no_update 之后也是如此:该调用随后会回答 shown,历史导入会保留这些答案。提供方错误或中断仍然优先:投递随后会以自身错误结束为 failed 或 interrupted。运行历史会显示失败运行的错误。结果目标会收到投递已完成的失败运行的答案;提供方失败则没有答案,也不会发送任何内容。在例程运行之外,该工具会返回与 routine_no_update 相同的错误。
若例行任务运行的最后一条回答只是标记,它就不会把标记放入预览;测试运行会在聊天中显示该标记,但同样不会放入预览。提供方历史导入(src/backend/provider-history-import.ts)仅根据暂存项来判断:在例行任务投递启动的回合中,它会跳过每个仅含标记的回答;当所有回答都是标记时,它也会跳过该回合的思考,因此之后的导入不会让一个安静的回合重新出现。
例行任务会话¶
在智能体自己的聊天中运行的例行任务(不涉及任务、频道或消息投递)不会恢复该聊天的提供方会话。DrainScheduler.startDelivery 会通过 ThreadLifecycle.startRoutineThread 为其启动一个新会话:使用聊天的线程参数,并设置 ephemeral: true 和 persistSession: false。ConversationRuntime 将会话绑定到智能体的公共线程,因此运行的项目会显示在聊天中,并由 registerRoutineSession 标记。第一条消息会读取最新聊天消息的简短交接内容(renderRoutineHandoff,8,000 个字符)。在运行期间,中断和转向操作会进入例行任务会话。该运行的用量记录使用会话 ID routine:<external id>;其来源保持为 routine。
回合结束时,TurnLifecycle 会关闭会话(closeRoutineThread)。非安静运行会将其回答的简短摘要添加到聊天会话的待处理交接中(appendRoutineDigest:每次运行 1,500 个字符,总共 6,000 个字符),以便下一次聊天回合读取。聊天会话保留其 ID 和上下文。运行期间重启会将运行标记为已中断:会话不会被保存。
例行任务日历源¶
src/main/routine-feed-server.ts 是一个回环 HTTP 监听器,用于将本机的例行任务作为 iCalendar 源(src/main/routine-feed-ics.ts)提供给服务器设置 > 例行任务。它只在用户创建源 URL 后运行,并绑定 127.0.0.1。URL 是 /routines/<token>.ics,可选 ?agent=<id>:日历应用无法发送标头,因此令牌放在路径中。<userData>/openbot-routine-feed-v1.json 保存令牌(使用密钥存储加密器加密)和端口,因此重启后 URL 保持不变。当端口不可用时,监听器会选取一个新端口并保存它。新 URL 会替换令牌,关闭 会删除该文件。错误令牌会得到 404。带有 Origin 标头或外部 Host 的请求会在检查令牌之前得到 403。日志中永远不会包含该 URL。
该源将接下来 30 天活跃例行任务的每次运行列为一条 UTC 事件,放置逻辑与触发该运行的调度代码相同,因此不需要 RRULE 或时区规则。没有任何内容离开本机:从自己的服务器获取源的日历服务(例如 Google Calendar 或 iCloud)无法读取它。
例行任务流¶
例行任务流会将智能体例行任务的回答转交给其他智能体。侧边栏的“例行任务”视图为每个智能体显示一个画布:包括运行到达该画布的每个例行任务、其链接所到达的智能体,以及每个例行任务的最近一次运行。主机保存这些流。已加入的桌面端和 Web 客户端通过可选的 routine-flows-v1 能力读取和更改它们(参见例行任务流);没有该能力的主机会显示通知而不是画布。智能体事件 routine-flows-changed 会指明画布发生变化的智能体,无论在本机还是已加入的主机上。
-
src/renderer/src/features/routine-flows/RoutineFlowsCanvas.tsx是画布。它不读取任何应用上下文:它接收一个RoutineFlowsHost(routine-flows-port.ts)、打开的智能体和智能体列表。WorkspaceRoutineFlows.tsx通过 main 为它提供活动服务器,web-client/web-routine-flows.ts通过 Team API 为它提供已连接的主机。 -
src/backend/routine-flows/拥有迁移 31 的三个表:links、node positions 和 steps。这些行是直接写入的,而不是通过dispatch,因为一个 step 保存着一个智能体给另一个智能体的文本,且事件日志中的内容永远不会被删除。hardDeleteAgent会删除已删除智能体的行;被删除的例行任务或运行会通过外键带走其行。 RoutineFlows(routine-flows.ts)是在其自身托管运行时上运行的 Effect 服务。它从不监听提供方。在回合、队列或例行任务事件时,它会进行清扫:记录已结束运行的回答,结算每个投递已结束的步骤,并发送后续消息。清扫会逐个运行;如果清扫期间有事件到达,则在该清扫之后再运行一次。启动时会清扫一次,因此因重启而停止的流会继续。- 一个链接属于一个例行任务,并且只适用于在其之后启动的运行。有多个输入的智能体会等待所有输入,并在一条消息中获取它们。所有输入都失败的智能体会被跳过,因此失败不会让流一直等待。
- 交接是从例行任务进行的邮箱投递(
RoutineScheduler.enqueueHandoff)。它指明相同的例行任务和运行,但reconcileDelivery只通过运行自身的投递来查找运行,因此运行状态不会改变。交接回合中的routine_report_failure调用只会通过其已完成投递的错误使该步骤失败,并且其后的智能体会被跳过。 - 画布显示每种触发器类型的例行任务(
routine-flow-routines.ts)。Webhook 例行任务带有其端点、事件类型和过滤器;详情面板会保存它们、轮换密钥并通过 events API 运行测试,就像例行任务设置所做的那样。在已加入的主机上,只有所有者或管理员拥有该 API(events-v1);成员看不到 Webhook 配置,也无法从画布更改或运行 Webhook 例行任务。 - 智能体使用
openbot工具list_routine_flows、connect_routine_agents和disconnect_routine_agents读取和更改流(routine-flow-tools.ts)。RoutineFlows在智能体服务之后构建,因此工具路由器通过 getter 读取它。画布聊天面板会将用户请求发送到打开的智能体自己的对话,并显示读取该请求的回合的回答。
工作流¶
工作流是用户在“工作流”页面(页面侧栏的 Waypoints 按钮)的画布上绘制的步骤图。它不属于某个代理:一个图可以命名多个代理、模型、条件、审批和输出。主机保存这些工作流。已关联的桌面端和 Web 客户端通过可选的 workflows-v1 能力使用它们(参见 工作流)。代理事件 workflows-changed 会指明本机和已关联主机上发生变化的工作流。
packages/ui/src/features/workflows/是画布。它不读取任何应用上下文:它接收文档、目录、运行状态和回调作为 props。编辑保留在本地,以保证拖拽和撤销的流畅性,画布会在每个历史步骤之后给出新的文档。src/renderer/src/features/workflows/包含WorkflowsPage.tsx、workflows-port.ts(在window.openbot.workflows之上的端口)和workflows-controller.ts。控制器在页面和 stories 中是同一个。它会在最后一次编辑后 400 毫秒保存图,在其基础修订版上一次只保存一次。保存失败时,编辑内容会保留到下次保存。来自其他设备的更改、冲突或还原会重新加载图,画布上的撤销可以找回这些编辑。src/backend/workflows/拥有迁移 46 的表:workflows、nodes、edges、versions、triggers、webhook triggers、receipts、runs、steps 和 output posts。这些行是直接写入的,不通过dispatch,与例程流程一样。workflow-graph.ts检查图规则(分支边只能来自条件、端口类型、限制)。图可以包含循环。使用旧修订版保存时,会以当前图作为冲突应答。存储最多保留 50 个版本:当最新版本超过 5 分钟时保留一个,还原时保留一个,以及当运行在没有版本号的图修订版上启动时保留一个。Workflows(workflows.ts)是一个运行在自身受管运行时上的 Effect 服务。一次运行会复制图。每一轮会将已结束的步骤收尾,并启动所有输入已就绪的节点(workflow-walk.ts)。轮次逐一运行;一轮会在一次回合或队列事件之后、每次用户操作之后以及启动时运行。当某个节点之前的节点有了更新的步骤时,该节点会再次运行,例如重试之后。重试使用节点当前的设置,因此失败后所做的修复会生效;当节点已不存在或已变成另一种类型时,则使用运行时的副本。指向更早节点的边(契约中的workflowLoopEdges)就是循环:当其源节点已结束且更新时,更早的节点会再次运行,并将该结果作为额外输入。循环没有轮次限制。当条件走向另一个分支、某一步骤失败或被拒绝,或用户停止运行时,循环结束。画布会将没有条件的循环标记为无限循环。- 代理步骤是例程交接(
AgentService.enqueueRoutineHandoff,将工作流作为例程),并在其投递结束时结束。其令牌计入该代理名下。模型步骤和条件在没有工具的进程中运行;条件必须回答“是”或“否”,并且只有该分支的边保持活跃。该进程(warm-generations.ts)会空闲保留 5 分钟,因此下一步骤不会重新启动提供方。每个步骤仍会启动一个新的临时线程,因此没有步骤会看到另一个步骤的文本。当提供方的主客户端停止或更换、某一步骤失败后以及关闭时,该进程会停止。这些步骤的令牌计入一个保留的用量行WORKFLOWS_USAGE_ID,与代理的用量并列。审批会等待用户;拒绝会停止运行。输出会显示桌面通知或发布到消息目标。 WorkflowTriggers(workflow-triggers.ts)将调度触发器作为额外的到期来源提供给例程定时器。当工作流有正在进行的运行时,一次触发事件会被跳过,错过的触发事件会合并为一次运行。Webhook 触发器具有自己的所有者类型路由(workflow);参见 路由生命周期。- 重启会保留审批和代理步骤,因为它们的状态在数据库和邮箱中。第一轮会使正在运行的模型步骤失败,并结束正在停止的运行。当代理被删除时,代理步骤会以
agentMissing失败。尚未开始发送的发布项会被发送;正在发送途中的发布项会以不确定(uncertain)状态失败,用户需要重试该步骤。 - 图像、资产、文件输出和消息触发器节点会保留在图中,但尚不运行;运行到达这些节点时会以
nodeUnsupported失败。
隐藏代理¶
用户可以通过代理的侧边栏菜单隐藏代理,而不是删除它。代理会保留其线程、文件、记忆、设置、固定和分区。AgentSummary.hiddenAt 位于 projection_agents.agent_json 中,因此该状态不需要 SQL 迁移,且重复的代理不会复制它。主机拒绝向隐藏代理发送新消息,返回 error.backend.recipientHidden,涉及 IPC、频道、MCP send_message、自动化服务器和外部消息。其例程不会运行,其排队中的消息会等待在 "hidden" 排空保持之后。隐藏时正在运行的回合会执行完毕。隐藏代理不会出现在 list_agents、队友提示词、提及选择器、计数或 "Needs you" 中。隐藏的线索不会路由频道请求,ChannelService.releaseAgent 会将该代理排队中和正在启动的频道分配归还给各自的频道。
恢复操作会在清除该标志之前,将错过的例程运行标记为已跳过,这样定时器就不会运行积压的任务。然后它会重新启用例程并排空队列。IPC agent:hidden:list 和 agent:hidden:set 改变状态,事件 hidden-agents-changed 携带整个列表。已关联的桌面端、Web 客户端和移动应用使用可选的 agent-hidden-v1 能力(packages/contracts/src/team-protocol/agent-hidden-v1.ts)。任何成员都可以读取列表;只有所有者或管理员才能在主机上隐藏或恢复代理。/v1/agents 和已发布的代理编解码器不携带 hiddenAt。
浏览器配置文件¶
在代理设置 > 高级中,可以选择代理的浏览器配置文件:Shared 或命名配置文件,其他代理也可以使用。对于已加入的服务器,当主机提供 browser-profiles-v1 时,所有者或管理员可以看到该选择器;代理摘要不携带配置文件,因此面板从主机读取它。“仅此代理”仅对已使用该配置文件的代理显示。更改会关闭代理已打开的标签页。“管理”会打开代理所在服务器的设置中的浏览器配置文件部分(ServerBrowserProfilesPanel),该部分可以添加、重命名、更改命名配置文件的颜色、清除和删除这些配置文件。它首先列出 Shared,显示其代理数量且没有操作。没有该能力的已加入服务器没有此部分。选择器、浏览器工具栏和标签页显示命名配置文件的颜色。删除会将使用该配置文件的代理移到 Shared。代理没有更改其配置文件的工具,工具结果也不会提及该配置文件。关于存储和分区,请参见 浏览器会话。
代理线程¶
侧线程(在代码中和传输时仍称为任务)是一个代理的独立执行线程。代理保持一个身份、工作区和提供程序设置;每个侧线程有自己的提供程序线程、对话、队列、进度和结果。代理自身的聊天是其主线程,没有侧线程行。projection_agent_threads(src/backend/agent-threads/agent-thread-schema.ts,迁移 47)为每个侧线程保存一行,包括其标题和标题来源(auto、agent 或 user)。迁移 47 将 projection_agent_tasks(迁移 38)的每个任务移到那里:已关闭的任务成为归档线程,任务 ID 保持为 legacy_task_id。侧线程的任务 ID 是 legacy_task_id ?? thread_id,因此存储的邮箱消息和已发布的客户端只要提到该任务仍能找到它。带有 taskId 的邮箱消息属于该侧线程,DrainScheduler 将每个任务作为自己的通道进行排空。主聊天通道不计入任务限制。一个代理最多同时运行 limit 个任务轮次。agentTaskLimit 给出限制:代理的本地 taskLimit 设置(1 到 AGENT_TASK_LIMIT_MAX,在 agent_json 中),否则为 AGENT_TASK_LIMIT_DEFAULT(2)。已加入服务器的所有者或管理员通过 agent-task-limit-v1 读取和设置它。描述符中 concurrentTurns: false 的提供程序(自定义 ACP 代理)将获得 1,其聊天和任务一次只运行一个轮次。其他任务消息会以 hold 原因等待:task-limit、agent-restarting、usage-limit、server-busy、agent-busy 或 one-turn。托管服务器的服务器级 TurnSlots 限制是独立的。
聊天头部显示主线程的标签页、每个打开的侧线程的标签页、一个“+”按钮和一个包含“已归档”分组的所有线程菜单(packages/ui/src/features/conversation/threads/)。用户通过“+”启动侧线程(一个草稿标签页,在发送第一条消息后成为线程),或将排队的消息移到侧线程(“在新线程中运行”)。哪些标签页打开、它们的顺序和当前打开的标签页按设备保存在 localStorage 中(openbot:thread-tabs:v1,服务器 ID → 代理 ID);关闭标签页不会改变线程。归档即任务关闭;打开归档线程或向其中发送消息会将其恢复。重命名设置 user 标题,后续消息不会更改它。删除会立即隐藏线程并显示“撤销”提示;当提示结束时,AgentService.deleteTask 取消排队的消息,并删除线程及其对话、附件、事件历史记录和提供程序会话。它拒绝删除正在运行轮次的线程。主线程没有归档、重命名或删除功能。
代理通过 start_task OpenBot 工具启动侧线程,最多 8 个打开的任务。在桌面端和 Web 端,它以代理的一个标签页形式打开,不会抢占焦点。一个代理最多有 50 个打开的任务(INPUT_LIMITS.agentTasks,即任务列表的大小),因此每个打开的任务都保持可见。发送到正在运行的任务的消息会在任务中等待,或者当代理的忙碌消息模式为“steer”且提供程序原生支持引导时,引导该轮次,与主聊天类似。停止会中断任务轮次;当没有其消息在等待、启动或运行时,关闭在手机上会将任务移到“已关闭”,在桌面端和 Web 端移到“已归档”,重新打开则将其移回。启动恢复会注册任务线程并重新启动排队的任务。当任务的提供程序会话结束时,下一个会话会获得该任务自身转录的交接,这与主聊天的做法相同;频道线程仍然不会获得任何转录。
已加入的桌面端、Web 客户端和移动应用通过可选的 agent-tasks-v1 能力访问服务器的任务(packages/contracts/src/team-protocol/agent-tasks-v1.ts,主机路由 src/main/team-api/route-agent-tasks.ts)。其路由为 POST /v1/agent-tasks/{list,read,start,send,stop,close,reopen},ID 在请求体中。可以向代理发送消息的成员可以使用它们;不检查角色,成员是启动和消息的发送者。隐藏的代理或对端无法看到的代理会返回 404。启动和消息只携带文本,“在新线程中运行”保持在本地。文件通过下面的 agent-thread-files-v1 传输。主机以本地形式发送任务事件(agent-tasks-changed、agent-task-conversation、agent-task-conversation-delta),仅发送给具有该能力的对端,并且不针对隐藏的代理。可选的 agent-threads-v1 能力(packages/contracts/src/team-protocol/agent-threads-v1.ts,主机路由 src/main/team-api/route-agent-threads.ts)增加了 POST /v1/agent-threads/{rename,delete},代理和任务 ID 在请求体中。它们需要 chat 代理级别;隐藏的代理返回 404。只有 agent-tasks-v1 的服务器显示线程但没有重命名或删除功能。可选的 agent-thread-reads-v1 能力(packages/contracts/src/team-protocol/agent-thread-reads-v1.ts,主机路由 routeAgentThreadReads,位于 src/main/team-api/route-agent-threads.ts)增加了 POST /v1/agent-threads/{reads,read}:成员对每个打开的侧线程的已读标记,以及单个标记的移动。这些标记是 projection_thread_reads 中位于侧线程自身 threadId 下的行,因此成员的设备共享这些标记,无需迁移。桌面端在窗口获得焦点时将打开的线程标记为已读(通过 IPC 调用 markTaskRead,对于已加入的服务器则使用路由);手机在线程屏幕位于前台时将其标记为已读,并在代理名称上显示一个圆点,表示该打开的线程有未读的代理消息。标记移动时主机不发送事件;客户端在收到 agent-tasks-changed 或打开显示这些标记的屏幕时重新读取标记。可选的 agent-thread-files-v1 能力(packages/contracts/src/team-protocol/agent-thread-files-v1.ts,主机路由 routeAgentThreadFiles,位于 src/main/team-api/route-agent-threads.ts)增加了 POST /v1/agent-threads/{start-with-files,send-with-files}:启动或消息最多可包含 10 个附件草稿,由成员通过 POST /v1/attachments 上传,文本可以为空。返回结果是冻结的 agent-tasks-v1 任务和回执。团队客户端仅在带文件的消息中使用这些路由,因此不带文件的消息在旧主机上仍然有效。已加入的桌面端对于没有该能力的服务器会以 error.backend.threadRemoteUnsupported 拒绝文件;手机仅在具有该能力的服务器上在线程编辑器中显示加号。两者都没有的服务器只显示主线程,任务控制在其中会以 error.backend.taskRemoteUnsupported 失败。任务轮次的提示、审批和警告显示在主聊天中。在桌面端和 Web 端,侧线程轮次的审批也显示在该线程中。任务轮次的提供程序错误会归入该任务:AgentTasks 将脱敏后的文本存储在任务的 error 列中,任务的下一个轮次会清除它。重启后线程也会显示该错误。
在桌面端和 Web 端,任务会以聊天头部中的线程形式打开。手机端使用 @openbot/team-client/agent-tasks 中的分区和标题规则。在手机端,当主机提供该能力时,服务器抽屉会打开 Tasks 屏幕(apps/mobile/src/app/(app)/server-tasks.tsx)。任务会以全屏形式打开(apps/mobile/src/app/(app)/task/[taskId].tsx),包含代理聊天的消息列表和头部,任务操作取代浏览器按钮,以及不带文件的输入框。任务事件会写入缓存的任务列表和会话(apps/mobile/src/features/workspace/model/task-cache.ts)。手机端回答任务轮次中的问题(POST /v1/prompts/respond,它会通过请求在任何线程上查找问题);它不显示任务附件。任务的轮次、问题、审批和接管事件携带任务的线程,因此手机端会将它们排除在代理聊天的活动行之外(withoutTaskTurns)。与桌面端一样,聊天会在代理活动下方为每个打开的任务显示一行:已完成的任务会保留其结果,直到用户关闭它,点击即可打开任务。
共享写入:每个任务线程拥有自己的浏览器标签页和标签页队列。Computer Use 是提供商启动的 MCP 服务器,因此 OpenBot 无法对其调用进行排队;ThreadLifecycle 启动任务会话时不会包含它,指令会将桌面工作发送到代理自己的聊天中。任务共享工作区,OpenBot 不会锁定文件;start_task 指南会就此警告代理。
当轮次、任务或排队消息等待时,用户或代理请求的提供商、账户或自定义代理变更会以 pendingRuntime 的形式存储在代理上。正在运行的工作会保留其会话。当排空即将开始下一条消息且尚未有工作启动时,AgentService 会先应用排队中的运行时(next-turn 移动),然后回收聊天和每个任务线程的提供商会话,以便每个会话都能在新提供商上完成交接。如果排队的变更在空闲代理上失败,例如针对现已关闭的提供商,则该变更会被移除。同时更改其他字段的更新、切换到备份模型、Team API 请求(其发布协议没有 pendingRuntime),以及自动的使用限制、提供商错误和端点移动,仍然会等待空闲工作(error.agent.waitForTasksBeforeProviderChange / error.agent.waitBeforeProviderChange)。Team API 的模型或努力程度变更会保留排队中的变更,因为远程客户端无法看到它。代理的频道线程(ChannelStore.contextThreadsForAgent)也会被回收,其工作步骤会被保存。频道轮次也携带其自包含的上下文数据包。实例传输文件会保留 taskLimit。
代理通信策略¶
共享的开发人员指令默认将常规队友交流保持在内部。代理应直接开始或继续工作,无需叙述设置、上下文加载、发现或就绪状态。进度更新应聚焦于有意义的结果、已完成的工作、重大变更、阻塞项、失败,以及所需的用户输入或审批。委派的工作仍需要向发出请求的队友明确回复;确认信息不得形成循环。相关发现应归入任务结果中,用户可以要求提供详细的协作报告。
代理在尝试每条路径之前,不会拒绝任务,也不会声称自己无法访问某项服务。当任务超出其配置文件范围,或提到某个可能由队友拥有的服务时,它会先调用 openbot.list_agents,并委派给名称、标题或描述涵盖该工作的队友。在频道任务中,它只委派给频道成员。MCP 服务器是主机全局的,因此专家队友仅在其配置文件、技能和记忆上有所不同。否则,代理使用其已连接的 MCP 服务器、技能、嵌入式浏览器和 Computer Use。
登录页面不会阻止它:它会调用 openbot_browser.request_takeover。该请求会显示在代理的对话中;当代理的通知开启时,也会显示在灵动岛中,对于委派的工作也是如此。常规运行则会报告登录情况,因为可能无人应答;常规运行中的请求者会在委派消息中说明这一点。该工具仅在用户应答后返回,这可能需要几分钟。轮次结束会取消该请求,因此代理会继续等待结果,即使在调用运行期间提供商交还控制权也是如此。在冒烟运行中,Codex exec 大约每 30 秒执行一次此操作。在出错或取消后,代理不会指向接管窗口。当代理在浏览器中访问某项服务,但该服务的插件不在其工具中时,回答会以固定的句子结束:在 Marketplace 的 Apps 选项卡中安装插件,或在 MCP 服务器中启用它。该句子同时提及两种操作,因为 read_agent 和代理的工具只显示已启用的服务器,因此代理无法区分已禁用的插件和不存在的插件。
当用户提出要求时,代理会使用 MCP 服务器工具(src/backend/agent/mcp-tools.ts,issue #2673)添加、更改、启用或禁用、测试和移除 MCP 服务器。每次更改都会等待 AttentionRegistry.awaitToolApproval 的审批,该审批会说明服务器上的每个代理都会获得这些工具。与其他内置工具审批一样,完全访问权限会批准该审批。save_mcp_server 在保存之前先进行连接(inspectMcpServer,即静默测试):不响应的服务器不会被保存;需要 OAuth 登录或密钥的服务器会被保存。这些工具绝不会获取或返回标头或环境值,并且显示的地址不带其查询参数:用户在 Server settings → MCP 中输入密钥。编辑会保留该行的标头和环境;如果编辑会将它们发送到其他传输、源、命令、参数或文件夹,则会被拒绝。审批会屏蔽命令行中已知的机密,并指明 stdio 服务器启动所在的文件夹。登录会在对话中显示 mcp-sign-in:<id> 卡片。其“Sign in”会打开 Server settings → MCP,并启动该行正常的登录流程,因此已加入的服务器会通过其中继或实时页面进行登录。“Continue”会发送一条消息,告诉代理重新测试。保存的更改会在代理的下一个轮次时生效。
当自有工具失败时,代理会检查其队友。当队友报告自己受阻或正在等待用户时,请求方不会重复该工作或再次发送相同的请求;它会将阻塞因素和解阻操作告知用户。受阻任务的回复会在 Status、Result 和 Evidence 之后增加第四行 Unblock:。该回复的投递文本(src/backend/agent/delivery-content.ts)重复了报告规则,因为请求方在作答时会阅读它。此前的失败不会阻止代理向该队友提出新请求。代理绝不会把任务退回给交给它的队友。返回委派结果的回答以队友的名字开头,并且除非工具结果或回复显示某服务已被检查,否则代理不会声称检查过该服务。当一切都不奏效时,代理会说明已尝试的措施以及能够解阻的那一个操作;对于带插件的服务,该操作就是安装或启用插件。它可以提议创建一个专家队友,但只有在用户同意后才会创建。
委派工作的代理可以跟踪并停止这些工作。openbot.list_agents 以 300 个字符为限报告每个代理的描述(read_agent 返回完整描述),包括其 status(开始投递和上下文压缩均计为 working)、queuedMessages(频道工作除外)、turnStartedAt 和 lastActivityAt。后两者来自 TurnLifecycle 所看到的提供商通知;它们仅存在于内存中,因此重启后 lastActivityAt 会回退为该代理最新消息的时间。OpenBot 不记录某轮次更改了哪些文件。openbot.interrupt_agent(src/backend/agent/agent-interrupt-tool.ts)拒绝调用者自身、频道轮次以及由除调用者外的任何投递启动的轮次,因此代理无法停止来自用户、例程或其他代理的工作。它会在发送 turn/interrupt 之前取消调用者排队发往目标代理的请求,因为被中断的轮次会在完成时排空队列。它保留调用者的回答,因为目标代理可能持有这些回答(见下文)。然后它将来自调用者的一条 expectsReply 为 false 的通知加入队列。该通知会在空闲目标代理上启动一个短轮次,因此提供商线程会记录工作停止的原因。
发送给两个或更多队友的请求,其回答会在请求方的队列中等待,与此同时该请求的另一个队友副本可能正在排队、启动或运行(MailboxStore.nextQueued)。当最后一个副本结束时,等待中的回答会在一个轮次中启动,并且提示会点名那些副本结束但未产生回答的队友。来自用户的消息不等待:它会立即启动并取走已在队列中的回答。每个副本的结束都会为请求方安排一次队列排空。
来自另一个代理的消息遵循接收方的忙碌消息模式,就像来自用户的消息一样。当模式为 "steer" 时,提供商原生地进行引导,接收方运行一个聊天轮次,send_message 和结果中继会将该消息带入该轮次(AgentService.#steerAgentMessage)。频道、消息和任务工作,以及发送给两个或更多队友的请求的每个回答,都保持排队状态(MailboxStore.steersChatTurn),因此这些回答仍然会一起启动。无法引导的代理所发送的消息,其所在行不会显示回退原因。用户可以在桌面端、Web 端和移动端的等待区块中将任何排队的回答引导到正在运行的轮次中;该等待区块没有其他队列操作。这是用户的选择,因此它可以在一组回答中先取走其中一个。
此策略位于 src/backend/agent/developer-instructions.ts 中,在线程启动和恢复时都会提供。Codex 接收 developerInstructions;Claude 和 Pi 将其附加到系统提示中;Grok 和其他 ACP 代理在常规轮次输入中将其作为带标签的指令块接收;Muse 将其放在轮次输入的最前面。ACP 和 Muse 仅在会话缺少这些指令时发送:新会话或重新打开会话的第一个轮次、变更之后、压缩或失败轮次之后,以及对于报告无上下文大小的 ACP 代理,每 10 个轮次发送一次。没有特定于模型的冗长度设置或响应过滤器。投递经过测试,但合规性取决于提供商、模型和现有会话上下文;Grok 的输入块不是专用的系统消息。重启应用会重新应用当前策略,且不会删除对话历史。该策略不会在桌面端或移动端客户端中隐藏邮箱记录、工具活动、审批或失败。
模型评估场景¶
在包含两个代理的隔离测试配置文件中运行这些场景,分别针对 Codex、Claude 和 Grok 进行。记录提供商/模型版本、提示词和观察到的响应。在应用重启后使用同一对话重复协作测试,包括一次带有早期冗长协调的对话。这些是手动模型评估,独立于伪提供商的生命周期回归测试。
| 场景 | 预期行为 |
|---|---|
| 在新对话中,让一个代理与一个队友共同研究某个主题。 | 工作立即开始,没有设置、发现或就绪独白。 |
| 在该任务期间交换常规的范围澄清和确认。 | 没有面向用户的逐条消息回顾或确认循环。 |
| 让队友完成研究并发送回发现结果。 | 请求代理接收结果;用户收到简洁有用的综合总结。 |
| 让队友报告失败的步骤、阻塞因素或改变建议的发现。 | 用户看到后果以及任何需要做出的决定。 |
| 包含需要审批或澄清的步骤。 | 现有的审批/提问流程保持可见,且代理等待回答。 |
| 重启应用,然后让代理继续同一任务。 | 工作以相同策略继续进行,且没有加载上下文的回顾。 |
| 明确要求详细说明队友协调情况。 | 代理提供所要求的详细信息。 |
| 创建一个描述中拥有 Notion 的队友,然后向通用代理询问某个 Notion 页面。 | 通用代理会将任务委托给该 Notion 队友。其回答以该队友的名字开头。 |
| 在未安装 Notion 插件的情况下询问同样的问题。 | Notion 队友会在浏览器中打开 Notion,并请求接管以进行登录。当登录失败时,回答会说明已尝试的操作,并建议安装 Notion 插件。 |
Prompt 驱动的代理配置文件¶
用户可以通过在普通桌面或移动对话中请求某个代理来创建和编辑代理配置文件。openbot.create_agent 会创建一个持久的队友,并附有说明和第一个任务;openbot.update_profile 会更改现有代理的名称、标题、说明、生成的或自定义头像、提供方、模型、推理强度、账户、备用模型、访问权限、Computer Use 或通知。avatarPath 接受本地 PNG、JPEG 或 WebP 文件,最大 512 KB,相对路径根据调用代理的工作区进行解析。代理会在需要时使用其可用工具来调整或压缩副本。OpenBot 会在配置文件更改前验证准备好的文件,并将其复制到受管理的头像存储中。生成的头像设置会移除自定义图像。这两者都通过现有的 AgentService 运行,并在更改状态前验证参数。
openbot.create_agent 还接受可选的 provider、model 和 reasoningEffort。只读的 openbot.list_models 返回模型选择器中显示的每个提供方的模型,及其推理强度,以及仅指定提供方时请求所使用的默认模型。没有 query 时,提供方会列出其默认模型、本地代理用作其主模型或备用模型的模型,然后是其目录中的前几个模型,总计最多 8 个模型;omittedModels 表示其余模型的数量。query 会按模型 id 或名称中的单词搜索所有模型,最多返回 20 个。create_agent 和 update_profile 仍然接受每个目录模型,未知模型错误会列出同样的简短清单。一个可以容纳多个账户的提供方还会列出其账户,提供方自己的登录(default)排在首位:包括 id、用户为其指定的名称(或 null)以及状态。它从不列出账户的电子邮件、凭据或账户文件夹。update_profile 接受代理的 providerAccountId 及其 backupModels(automatic 以及 provider、model 和 account 的 order),与设置页面提供的选项相同。OpenBot 在写入之前会对照清单检查每个备用模型和账户,AgentService 会检查主账户;账户更改与提供方更改一样,会等到代理空闲时才执行。这些更改与配置文件的其他字段一样会立即生效。read_agent 和 update_profile 的结果会返回账户、备用模型和 usageLimitReturn,即代理在遇到使用限制或提供方错误(cause: "provider-error")时退回的模型。未知模型或不支持的推理强度会返回一个错误,其中列出有效值;OpenBot 会在创建代理之前检查它们。如果没有提供方和模型,新代理将使用调用代理的提供方、模型和推理强度启动,因此一个代理创建的团队会在该代理运行的地方运行。当调用者的提供方不再列出其模型时,新代理将以用户的默认设置启动。创建过程保持如此轻量。随后,调用代理可以使用作用于自身的相同工具来配置新代理或任何其他本地代理。openbot.read_agent 返回某个代理的设置:配置文件、运行时、访问权限、Computer Use、通知、自动批准、已安装的技能、例程,以及它可用的 MCP 服务器的名称和传输方式。它不会返回 MCP 命令、环境变量值、URL 或标头,因为这些可能包含机密。install_local_skill、set_skill_enabled、uninstall_skill 以及例程工具接受可选的 agentId;如果不提供,则作用于调用者。uninstall_skill 绝不会删除用户修改过的技能文件。openbot.update_agents 通过一次调用即可为多个代理设置相同的提供方、模型、推理强度、访问权限、Computer Use 或通知设置。每个代理都会依次获得 update_profile 的检查以及自身的 updateAgent 写入。某个代理的失败不会停止或撤销其他代理,结果会针对每个 agent id 报告 ok(含新设置)或错误。未列出的提供方或模型会在任何写入之前导致整个调用失败。
代理只能限制自身或队友的访问权限和 Computer Use。路由器只写入限制,因此用户在其检查与写入之间所做的更改永远不会被撤销。只有用户才能再次放宽这些限制,也只有用户才能将代理在 Ask first 和 Full access 之间移动,以及更改本地脚本运行。代理只能在用户请求并批准后,通过 MCP 服务器工具更改 MCP 服务器。新代理会继承创建它的代理的访问权限和 Computer Use 限制,因此仅限工作区的代理无法通过队友绕过其沙箱。在 UI 中没有单独的技能或例程创建步骤。
其他提供方会收到动态工具定义;Claude 通过其 SDK MCP 桥公开相同的操作。src/backend/openbot-tools.ts 负责两个声明共用的工具名称、描述和 Zod 参数结构。它复用了配置文件、分区和例程的 schema。Claude 使用 SDK 的 AskUserQuestion 流程,而不是 ask_user MCP 工具。没有单独的 prompt 生成按钮或审阅对话框。
代理可以通过 list_sections、create_section、rename_section、delete_section 和 assign_agent_section 将队友组织到扁平化的侧边栏分区中。赋值操作接受 null 分区以取消对代理的分组;删除分区也会取消对其代理的分组,但不会删除这些代理。这些工具与手动侧边栏编辑使用相同的 SidebarLayoutStore,包括持久化、验证,以及发送到桌面端和已连接客户端的事件。
html_render 发布一个视觉回复:一个显示在 agent 回复上方的 HTML 页面。路由器将该页面存储为生成的 text/html 附件,并添加一条助手消息,其项目类型为 visual-reply:<height>,文本为页面标题(@openbot/contracts/chat-visual)。不了解该项目类型的客户端会显示标题和文件。视觉消息不计为已读:未读数、最新消息预览和移动端已读状态都会跳过它。桌面端在 openbot-visual:(远程主机为 openbot-remote-visual:)上提供该页面,带有 Content-Security-Policy: sandbox allow-scripts allow-forms 和框架脚本,而框架使用相同的沙箱但不含 allow-same-origin。页面运行其脚本并可从网络加载文件,但它具有不透明来源,不获得任何权限,并且只能通过 parseChatVisualMessage 的经过检查的消息与应用通信:其高度、主题、显示模式、链接、消息、上下文、图像和应用快捷方式。框架脚本通过 MCP Apps 方法 ui/message、ui/update-model-context 和 ui/request-display-mode 赋予页面 window.openbotPage(sendMessage、setContext、requestFullscreen、exitFullscreen、displayMode)。链接、消息、上下文、图像、全屏和快捷方式都需要框架获得焦点和用户激活,因此页面只在其中发生点击或按键后才行动。消息放在草稿末尾,由用户发送。上下文是草稿开头视觉消息的引用标记,因此它显示为引用块,并随草稿一起保存和发送;页面的下一个上下文会替换它。全屏使用 Popover API 将框架的盒子放入顶层,因此框架不会重新加载;在应用为页面打开对话框之前,它会退出。只有渲染器的快捷方式允许列表(chat-visual-shortcuts.ts)以框架元素上的按键事件形式离开页面。移动端在独立的 react-native-webview WebView(html-page-view.native.tsx)中运行页面,在聊天中以 agent 的高度显示且不响应触摸,也显示在预览屏幕上。它从 about:blank 处的字符串加载,从 chatVisualDocument 获取基础样式和应用主题(bootstrap: false),并带有 form-action 'none' 策略。该视图没有 onMessage 且不注入脚本,因此 react-native-webview 不会添加消息处理器或 JavaScript 接口,页面无法访问应用;切勿在 Expo DOM 组件中运行页面,因为它有通往应用的桥接。点击的 Web 链接在浏览器中打开,页面不会转到其他地址。链接仅在触摸后的一秒内打开:Android WebView 只向应用询问主框架 GET 加载且不提供原因,而 WebKit 在点击时会对链接调用脚本 click()。iOS 将页面存储保存在内存中(incognito);Android 关闭 DOM 存储和第三方 cookie,因为其 incognito 会清除应用的每个 WebView。没有原生模块的 Android 构建会显示无脚本的页面。
html_preview 让 agent 在发布前查看页面:主进程在带有自己内存会话的隐藏窗口中绘制它(ChatVisualPreviewer),并返回 PNG、内容高度和控制台行。文件预览在同一框架中显示 HTML 文件。在桌面端,共享或工作区 HTML 文件预览会在其字节旁边给客户端一个临时的 openbot-visual://preview/<token> 页面 URL。FilePreviewPages 仅保留已通过现有文件访问检查的本地 HTML,位于有界的内存注册表中。页面协议只接受不透明 token,绝不接受文件路径,并使用与 HTML 附件相同的沙箱响应。服务器持有的预览状态在替换、关闭或处置时释放其页面;过期的预览响应释放其未使用的页面。主进程在应用控制的渲染器加载之前、主框架文档导航提交之后、渲染器退出时或其 Web 内容被销毁时清除注册表。取消的导航保留活动渲染器的页面和深度链接就绪状态。在文件解析之前捕获的代次可防止旧请求在清除后注册页面。已接受的地址在释放或清除之前保持有效,包括用户切换到源码再切回时。最多可持有八个页面。注册表已满或页面超过现有的 8 MB 限制时,回退到源码。只有本地文件预览使用此 HTML MIME 覆盖;附件导入保留其现有 MIME 类型。下载的远程文件保留其现有预览路径,不在此注册。FilePreview 是本地 IPC 数据;此可选字段不会跨越 Team API 或更改其冻结的适配器。
Codex 在 provider 会话创建时固定动态工具;恢复不会更新它们。本地 provider-toolsets 清单记录每个新 Codex 会话的工具指纹。指纹缺失或过期的会话在下一轮之前被替换,使用现有历史交接,同时保留公共线程、agent 身份、工作区和存储的对话。未更改的指纹恢复现有会话。待处理的历史交接在替换绑定之前写入,重启后重新加载,并在回合接受交接后移除。
相同的交接在 provider 切换后将聊天带到另一个 provider。它仅保留最后一个上下文重置标记之后的用户和助手消息,且仅含附件名称。OpenBot 不存储工具步骤,因此工作日志来自 provider。在切换时,旧会话退役之前,ThreadLifecycle.readWorkSteps 通过持有每个活动会话的客户端读取 provider 历史,限时 10 秒。切换然后再次检查没有回合开始。仅在切换存储后,saveWorkSteps 将最近 60 个回合的渲染步骤写入 provider-work-steps/v2/<sha256(session id)>(模式 0600),对于没有步骤的会话写入 {}。会话在写入之前退役,因此在此期间开始的回合保持其新会话并改为读取 provider。该文件被删除并与其他会话文件协调,无法解析的文件视为没有捕获。交接不读取 provider。没有捕获的会话(例如没有客户端持有的会话)不提供步骤。readCapturedSteps(src/backend/agent/handoff-tool-steps.ts)渲染工作日志:带退出码和输出尾部的命令、更改的文件路径、工具调用、搜索和进度说明。交接添加每个与转录消息匹配的回合的步骤。每个字段在截断前都会脱敏,每个回合也有大小限制。推理、差异、图像和其他 provider 私有状态保留在生成它们的 provider 中。失败的捕获会将该会话的步骤排除在外。Codex 从其存储的 rollout 返回工具步骤。Claude 只返回说明。ACP agent 只保留其回合的文本和思考,因此它也仅提供说明,并且仅在其进程持有会话期间。
可选的 agent-profile-generation Team API 端点仍然可用。它们使用独立的提供方客户端,工具受限,并在返回草稿之前进行验证。其保存路径仍保留恢复与重试保证:
在创建工作区或 agent 行之前,会先写入一个资料创建标记。启动时会在邮箱初始化和队列排空之前移除未提交的创建;而已提交的重试回执会保留该智能体及其介绍。现有的侧边栏协调逻辑会为已恢复的不完整智能体移除分配。
已审阅的指令沿用现有资料的描述。资料保存会将 SQLite 与单独存储的侧边栏布局协调一致,失败时回滚版块分配。更新现有资料及其重试回执共享同一个 SQLite 事务。创建过程沿用现有的 workspace/initial-message 流程,失败时进行清理。有了回执,响应丢失后的重试会返回已保存的智能体。这不会引入 schema 迁移,也不会更改已发布的协议编解码器。
导入智能体¶
服务器设置 > 导入会将 .zip 导出中的智能体移动到本地主机,或移动到提供 agent-import-v1 的远程主机(参见 从已加入的服务器导入智能体)。格式为 openbot-import.json 加上 agents/<key>/ 文件夹。resources/agent-import/grok-bot/SKILL.md 负责写入它,src/main/agent-import-manifest.ts 负责读取它;两者是产品契约,因此只能添加可选字段;若含义发生变化,应提高 version。渲染进程从不指定路径:agent-import:choose 在主进程中打开对话框,AgentImportService 将经过校验的导出及其 SHA-256 保存在单次使用令牌下;apply 会拒绝已更改的文件。stage 会测量条目而不解压它们,并拒绝 skill-package.ts 中 isUnsafeArchivePath 拒绝的路径,或指定 Windows 驱动器名的路径。apply 会检查智能体的所有技能,通过 AgentService 创建该智能体,并通过本地技能库发布技能;如果某个智能体的步骤失败,该智能体会连同其已发布的技能修订一起被删除,其余智能体继续处理。可选的 channels 列表携带 Grok Bot 群聊。在智能体之后,每个选中的频道通过 ChannelService.command 以本地用户身份(host.channelActor())创建,成员和负责人映射到新的 agent id;频道需要至少一个已导入的成员,如果某个频道的记忆步骤失败,该频道会被删除。步骤 1 提供两种方式添加导出的智能体:通过其 Grok Bot 链接,或通过手动配置的技能。agent-import:read-skill 和 agent-import:save-skill 从应用的资源中提供该技能(electron-builder.yml 中的 extraResources);主进程打开保存对话框,因此渲染进程无需指定路径。无需更改 schema。
实例转移¶
服务器设置 > 转移会通过一个 .zip 文件将服务器的智能体和服务器设置移动到另一台 OpenBot 服务器。它与 Grok Bot 智能体导入是两种不同格式。第一个屏幕提供“从此服务器导出”和“导入到此服务器”两个选项。该版块会为此电脑的服务器显示;对于已加入的服务器,当用户是所有者或管理员时,会通过 instance-transfer-v1 显示(参见 与已加入的服务器进行实例转移)。Web 客户端和移动端不显示该功能。应用偏好设置、默认模型以及已关闭的提供方都属于应用而非服务器,因此不会出现在文件中。
文件根目录包含 openbot-transfer.json、agents/<key>/ 文件夹(avatar.<ext>、skills/<slug>/ 和 workspace/),以及用于 ~/OpenBot/Shared 的 shared/。清单为 { format: "openbot-instance-transfer", version: 1, source, agents, settings, shared, signedIn }。src/main/instance-transfer-manifest.ts 使用 Effect Schema 写入和读取该文件;它是应用版本之间的契约,因此只能添加可选字段;若含义发生变化,应提高 version。version 更高的文件会被拒绝。无效的智能体会被跳过并附带警告,其余智能体仍会被提供;无效的设置也以同样方式被跳过。
每个智能体都携带其资料(名称、标题、指令、头像种子和图像)、提供方、模型和推理力度、通知和忙碌消息模式、其例程(时间表、限制和错失策略;一次性例程位于可选字段 oneTimeRoutines 中,因此较旧的应用会跳过它们但保留该智能体)、其记忆(包括用户选择的重要或可搜索类型),以及 OpenBot 不管理的 .agents/skills 和 .claude/skills 中的技能文件夹。工作区文件是可选的,并且会省略 .openbot/、技能文件夹、链接,以及 isUnsafeArchivePath 拒绝的所有路径(.git、node_modules、.env*、私钥、嵌套压缩包)。服务器设置是可选的:MCP 服务器、自定义模型提供方、自定义 ACP 智能体(此项会给出警告,见下文)、侧边栏版块以及导出智能体之间的例程流程。自定义提供方模型会保留其可选的 contextWindow;不认识该字段的应用会忽略它。
文件中不包含任何机密:MCP 变量和标头值为空,自定义提供方没有 key 或标头,自定义智能体只保留其变量的名称,signedIn 列出的是提供方 id,绝不会是令牌。参数和 URL 也可能包含令牌,因此导出会清空名称表明其为机密的标志参数或 name=value 参数的值,并移除 URL 中的 user、password、query 和 fragment;对于参数字符串(如连接字符串)中的 URL,也同样处理。MCP 地址只保留其 origin,因为某些服务将访问密钥放在路径中;cleared 会列出它更改的每一项,导入时会再次要求提供这些值。聊天历史、消息连接、连接器、团队和账户会话、浏览器数据、审批自动化,以及每个智能体的访问、Computer Use 和自动化设置都不会导出,因此导入的智能体会在新计算机上获得新智能体的默认设置。共享表(Shared/Data/agent-data.db)会被排除,因为其所有者是 agent id。
InstanceTransferService(src/main/instance-transfer-service.ts)一次一个文件地写入归档,不压缩地存储到 <name>.zip.part,完成后重命名。导入使用与智能体导入(src/main/agent-archive.ts)相同的检查:文件保留在一次性 Token 下并带有其 SHA-256,小于 2 GB 且不超过 50,000 个文件。apply 首先添加缺失的自定义提供方。它从不保存自定义 ACP 智能体:已保存的自定义智能体会运行其命令来列出模型,因此警告会指明该智能体及其命令,用户检查后再重新添加它。新添加的提供方的模型尚未列出:保存不会等待模型发现,而且提供方在用户输入密钥之前没有密钥。因此,使用此类模型的智能体会在默认模型上启动,并提供警告,要求用户在提供方就绪后重新选择它。然后通过 AgentService 和本地技能库创建每个选定的智能体。文件进入工作区根目录;如果名称已存在,则保存在旁边。与导出一样,导入从不写入 .openbot/ 或技能文件夹。步骤失败的智能体将被删除(连同其发布的技能修订版),其他智能体继续。此计算机不提供、或其提供方已关闭的模型,会让智能体保留在默认模型上并给出警告。最后,它添加名称是新的 MCP 服务器(关闭,因为文件可以指定任意命令),按名称创建缺失的侧边栏分区并分配新智能体,将例程流程连接到新的例程 ID,并复制尚不存在的 Shared 文件。结果会列出在另一台计算机上已登录但此处未登录的提供方,每个提供方都有一个登录按钮。
智能体模板¶
智能体模板是单个本地智能体的仅链接副本:名称、标题和说明,头像,例程,市场技能作为已批准版本的引用,本地技能作为其 SKILL.md 文本。它没有工作区文件,也没有记忆。聊天标头中的“发布”按钮会打开 PublishAgentDialog;src/main/agent-template-service.ts 构建快照,当文本字段看起来像机密时停止,并将其提交到 Account Worker。Worker 在 D1 agent_templates 中为每个账户和本地智能体保留一行,因此第二次发布会更新同一个链接。取消发布会清除行的内容和图像并设置 unpublished_at,但保留该行,因此再次发布同一个智能体会获得相同的链接。一个账户最多可以发布 5 个智能体;Worker 在写入该行的语句中检查这一点,因此一个客户端或同时发出的两个请求都无法绕过它,未发布的行不计入。
没有审核,也没有市场列表。发布前,渲染器绘制一张 1200×630 的分享卡片(agent-template-card.tsx),包含智能体的头像和文本。它在那里绘制是因为头像及其字体就在那里,并且它使用 data: URL,因为内容安全策略拒绝 blob: 图像。Worker 只接受该尺寸的 PNG,将其存储在 R2 中,并作为页面的 og:image 提供,因此 X 上的帖子会显示该智能体。openbot.run/agents/<id> 是一个以头像颜色着色的公开卡片页面。Bloub 库在其模块加载时使用仅限浏览器的 API,因此页面在水合后于浏览器中加载头像及其颜色。其按钮打开 openbot://agents/<id>,这是 src/main/deep-link-router.ts 中的第三种渲染器链接类型。应用随后在 AgentTemplateInstallDialog 中显示该模板;与插件链接一样,链接本身不安装任何内容。页面的后备行还链接到 /app?agent=<id>,浏览器客户端在其中显示相同预览。同样,邀请页面将四个邀请字段链接到 /app,插件页面链接到 /app?plugin=<slug>。浏览器客户端在读取这些字段后将其移除,并打开加入对话框或市场列表。没有点击,它绝不会加入或安装。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 openbo/openbot