AgentRuntime:端口、依赖与方法装配
一个会话对应一个 AgentRuntime:它身上的八十多个状态字段、构造时注入的四十九项依赖,近百个方法文件怎样用声明合并加原型安装装到同一个类上,AgentRuntimeInternal 的作用,trace 上下文如何从根一路传到模型请求,以及 core 包对外导出了什么。
ZCode 的 Agent 运行时住在 apps/zcode-cli/packages/core/src/runtime:163 个文件、约 3.7 万行,全部围绕一个类 AgentRuntime。终端 TUI、-p 无头模式、桌面端与 Web 背后的协议服务,最终都是拿到一个 AgentRuntime 实例,调它的方法、订阅它的事件。它自己不碰具体实现:文件、子进程、网络、存储与模型大多经“端口”注入,端口的接口声明在 apps/zcode-cli/packages/contracts(src/interfaces 下有 25 个 *.port.ts),实现在 adapters,由 bootstrap 组装。
这一篇只讲这个类本身:一个实例对应什么、身上挂着哪些状态、要哪些依赖、近百个方法文件怎样装到同一个类上,以及 trace 上下文怎么一路传下去。回合怎么跑、输入怎么受理、事件怎么落库,分别见回合循环、输入受理与会话事件流。
目录分工
位置(相对 core/src/runtime) | 内容 |
|---|---|
agent-runtime.ts | 类本体:82 个私有字段、构造器、两个关闭方法,以及同名 interface 声明的公开方法 |
methods/ | 97 个文件、约 2.56 万行;每个文件导出若干以 this 为首参的函数,由 methods/index.ts 安装到原型上 |
helpers/ | 53 个文件、约 7900 行;显式接收 runtime 参数或完全无状态的辅助函数 |
internal.ts、internal-methods.ts、internal-turn-methods.ts、internal-hook-methods.ts | AgentRuntimeInternal:给方法文件用的内部视图 |
types.ts | AgentRuntimeConfig、AgentRuntimeDeps 与各方法的入参、出参类型 |
deps.ts | 统一依赖出口:从 @zcode/contracts 与 core 其他目录再导出,外加两个 trace 包装函数 |
execution-state.ts、session-mode-port.ts、model-selection.ts | 权限模式与 Plan 开关、给工具用的模式端口、模型选择的防御性拷贝 |
command-queue.ts | 运行时命令队列,见输入受理 |
permission-full-access.ts、permission-grant-recovery.ts | 审批时一键切换完全访问,见权限模式与规则 |
一个会话,一个实例
构造器签名是 constructor(sessionId, config, deps)(apps/zcode-cli/packages/core/src/runtime/agent-runtime.ts:228)。sessionId 只在构造时赋值一次(agent-runtime.ts:230),类上没有任何改写它的方法,getSessionId() 原样返回它(apps/zcode-cli/packages/core/src/runtime/methods/config.ts:255),所以一个实例从生到死只服务一个会话。bootstrap 里有一段注释把这层关系写得很直白:ZCodeApp、AgentRuntime、sessionId 两两 1:1(apps/zcode-cli/packages/bootstrap/src/app/dynamic-workflow-run-progress-sink.ts:27)。
整个仓库里 new AgentRuntime( 只出现在四处:
| 位置 | 创建的是 | 事件存储 |
|---|---|---|
apps/zcode-cli/packages/bootstrap/src/app/create-app.ts:726 | 每个 ZCodeApp 的主会话 | 宿主传入,缺省新建内存存储(create-app.ts:730) |
apps/zcode-cli/packages/core/src/runtime/methods/subagent.ts:239 | 子 Agent 的子会话 | 与父共用(subagent.ts:296) |
apps/zcode-cli/packages/bootstrap/src/app/script-workflow-child-runtime.ts:111 | 动态工作流 actor 与旧脚本工作流的子会话 | 与父共用(script-workflow-child-runtime.ts:191) |
apps/zcode-cli/packages/bootstrap/src/app/workflow-facade.ts:283 | 专家工作流每个活动的子会话 | 另建一个内存存储(workflow-facade.ts:299) |
一个进程里可以同时存在多个实例。TUI 同一时刻只持有一个 ZCodeApp,换会话时新建、再关掉旧的(apps/zcode-cli/packages/cli/src/tui-prompt-handler.ts:63、tui-prompt-handler.ts:123);桌面端与 Web 拉起的协议服务则用一张 Map 管着多条会话记录(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/server.ts:260),每条记录各有自己的事件存储、ZCodeApp 与 runtime(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/server-operations.ts:3300、server-operations.ts:3323),空闲的由常驻池回收,见会话事件流的“会话驻留”一节。
它持有什么
82 个字段全部声明为 private(agent-runtime.ts:133 到 agent-runtime.ts:226),都只在内存里;需要跨进程存活的部分写进 SessionStorePort,冷启动时由 resumeFromStore 读回,见 SQLite 会话库。按用途归纳:
| 类别 | 代表字段 |
|---|---|
| 身份与配置 | sessionId、turnNumber、config、appVersion、workingDirectory(Bash cd 会改)、workspaceRoot(不随 cd 漂移)、sessionModelSelection |
| 端口与协作对象 | permissionService、permissionBroker、toolScheduler、registry、executor、hookRunner、modelFactory、executionPort、fileSystemPort、mcpPort、subagentPort 等二十余个 |
| 对话与上下文 | messageHistory、readFileState、cachedTools、contextBuilder、contextInitialized、contextSourceSnapshot、memoryRoot |
| 事件与观测 | eventReducer、eventStore、eventSinks(一个 Set)、rootTraceContext、logger、agentTelemetry |
| 调度与回合 | runtimeCommandQueue、runtimeCommandDrainActive、activeForegroundExecution、foregroundPromotionLease、activeTurn、activeTurnStartReservation、pendingInputReservations、queueAutoDrain |
| 持久化与投影标记 | sessionPersisted、latestConversationMessageId、latestAssistantTurnId、pendingModelChangeTimeline、sessionTitleGenerationAttempted |
| 计数与保护 | mainTurnCacheHitAggregate、currentTurnFileChanges、autoCompactConsecutiveFailures、branchGeneration(回退后丢弃旧分支的后台结果) |
| 生命周期 | mcpStartupPromise、residencyBlockingWorkCount、shuttingDown、backgroundTaskNotificationsSealed |
依赖哪些 Port
AgentRuntimeDeps 共 49 项(apps/zcode-cli/packages/core/src/runtime/types.ts:309),只有事件存储 eventStore 与模型工厂 modelFactory 必填(types.ts:314、types.ts:317),其余缺席就意味着对应能力不存在。下表中端口接口所在文件都在 apps/zcode-cli/packages/contracts/src/interfaces/ 下,另注明的除外:
| 类别 | 依赖 | 接口定义 |
|---|---|---|
| 会话与事件 | eventStore、eventSink、sessionStore、sessionMailboxPort | session.port.ts:57、session.port.ts:76、session-store.port.ts:1089、session-mailbox.port.ts:12 |
| 模型 | modelFactory、providerRuntimeHeadersPort、modelRequestAdmission、modelCatalogPort、resolveEffectiveModelSelection、modelIoDir | types.ts:386、types.ts:395(core);apps/zcode-cli/packages/contracts/src/model/index.ts:74;model-catalog.port.ts:29 |
| 本地 I/O | executionPort、fileSystemPort、httpClientPort、imageProcessorPort、pdfDocumentPort、artifactStore、browserControlPort | execution.port.ts:278、file-system.port.ts:280、http-client.port.ts:89、image-processor.port.ts:78、pdf-document.port.ts:41、tool-artifact-store.port.ts:106、browser-control.port.ts:537 |
| 上下文与扩展 | contextSourcePort、skillPort、mcpPort、contextBuilder、memoryRoot | context-source.port.ts:92、apps/zcode-cli/packages/contracts/src/skills/index.ts:108、mcp.port.ts:290 |
| 权限与钩子 | permissionService、permissionBroker、hookRunner、workspaceHookAdmission、workspaceHookSnapshot | permission.port.ts:95;其余是 core 自己的类型 |
| 工具装配 | toolRegistry、toolExecutor、toolScheduler、runtimeTaskRegistry | core 内部类型,缺省在构造器里新建 |
| 多 Agent 与工作流 | subagentPort、coordinatorResponsePort、workflowPort、workflowSubmitPort、workflowSubmitSchema、workflowEscalatePort、dynamicWorkflowRunPort、dynamicWorkflowSnippetPort | subagent.port.ts:113、coordinator-response.port.ts:18、workflow.port.ts:36、workflow-submit.port.ts:45、workflow-escalate.port.ts:47、dynamic-workflow-run.port.ts:600、dynamic-workflow-snippet.port.ts:49 |
| 自动化 | automationPort、offPeakPort | automation.port.ts:43、off-peak.port.ts:47 |
| 观测与环境 | agentTelemetry 及其两项因果配置、logger、traceContext、appVersion、now、isRemoteWorkspace | apps/zcode-cli/packages/contracts/src/telemetry/agent-execution.ts:277 |
有一部分端口 runtime 自己并不保存,只在构造时转交给工具执行器,例如 httpClientPort、automationPort、offPeakPort、三个工作流端口(apps/zcode-cli/packages/core/src/runtime/helpers/runtime-tools.ts:154)。端口在不在场,也直接决定注册哪些内置工具(runtime-tools.ts:49):
registerBuiltInTools(runtime.registry, {
bashTimeoutPolicy: runtime.config.bashTimeoutPolicy,
includeSkill: Boolean(runtime.skillPort),
includeAgent: Boolean(runtime.subagentPort),
includeSendMessage: runtime.subagentPort?.sendMessage !== undefined,
includeRespondToCoordinator:
runtime.config.taskType === "subagent_child" && Boolean(deps.coordinatorResponsePort),
// submit_result 只在注入了 workflowSubmitPort 的 workflow actor 会话注册。以端口存在为门,
// 与 taskType 无关:workflow actor 是 workflow_child,其 runtimeScope 目前是 "main"。
includeSubmitResult: Boolean(deps.workflowSubmitPort),
// ...
includeWorkflow: Boolean(deps.workflowPort),
includeAutomation: Boolean(deps.automationPort) && runtime.config.taskType !== "subagent_child",例外写在注释里:十个动态工作流工具的端口在任何 CLI 里都装配齐全,灰度由宿主决定,所以不看端口,看 config.dynamicWorkflowEnabled(runtime-tools.ts:70);node_repl 与浏览器控制也不因宿主给了 browserControlPort 就暴露,而是看官方插件推导出的 runtimeFeatures(runtime-tools.ts:74)。
端口的边界并不绝对。apps/zcode-cli/AGENTS.md 要求业务模块不直接调用 fs、child_process、process.env 等底层 API(apps/zcode-cli/AGENTS.md:53),但 core/src 里仍有 13 个文件直接 import 了 node:fs(直接起子进程的一个也没有),例如 apps/zcode-cli/packages/core/src/runtime/methods/bash-shell-snapshot.ts:1,多数是 Bash、任务输出与工作流相关的工具,另有浏览器客户端、钩子与子 Agent 的几处。端口的实现从哪来、怎样按配置组装,见下一篇 bootstrap。
近百个方法文件怎样装到一个类上
类文件只有 664 行,因为方法体都不在里面。agent-runtime.ts 先声明一个只有字段和构造器的 class,再声明一个同名的 interface 列出公开方法,两者被 TypeScript 合并成同一个类型;文件末尾一行把实现装上原型(agent-runtime.ts:131):
// oxlint-disable typescript-eslint/no-unsafe-declaration-merging
export class AgentRuntime {
private sessionId: SessionId;
// ...
}
export interface AgentRuntime {
lastPermissionGrantId?: string;
beginShutdown(): void;
// ...
getMode(): CollaborationMode;
// ...
}
installAgentRuntimeMethods(AgentRuntime);安装函数就是一长串赋值(apps/zcode-cli/packages/core/src/runtime/methods/index.ts:197):
type AgentRuntimeConstructor = { prototype: object };
export function installAgentRuntimeMethods(ctor: AgentRuntimeConstructor): void {
const proto = ctor.prototype as Record<string, unknown>;
proto.updateConfig = updateConfig;
proto.setExecutionState = setExecutionState;
proto.grantPermissionFullAccess = grantPermissionFullAccess;
proto.initializeSessionShellEnvironmentIfNeeded = initializeSessionShellEnvironmentIfNeeded;
proto.getSessionShellSelection = getSessionShellSelection;
proto.getMode = getMode;
// ...
proto.isProjectMemoryEnabled = isProjectMemoryEnabled;
}被安装的都是普通函数,用 TypeScript 的 this 参数声明调用者类型,例如 export function getMode(this: AgentRuntimeInternal)(config.ts:86)。一共装了 187 个方法,来自 46 个方法模块和另外两个文件(permission-full-access.ts 与 helpers/project-memory-extraction.ts)。其中 87 个出现在公开 interface 里,再加上类体里的 beginShutdown 与 closeBrowserSession,公开方法共 89 个;另外 100 个只给内部互相调用。方法文件里还有两种不安装的写法:只在模块内用的 this 函数经 .call(this, …) 调用,如 runRuntimeCommand.call(this, firstCommand)(apps/zcode-cli/packages/core/src/runtime/methods/runtime-command-queue.ts:61);显式收 runtime 参数的函数,如 applyRuntimeExecutionState(runtime, input, cause)(apps/zcode-cli/packages/core/src/runtime/execution-state.ts:43)。
为什么这么拆?CLI 的 AGENTS.md 规定单个源文件默认不超过 400 行(apps/zcode-cli/AGENTS.md:12);internal-hook-methods.ts 开头的注释说,它是为了让 internal-methods.ts 不越过 400 行、通过一个名为 runtime-module-boundary 的测试而单独拆出来的(apps/zcode-cli/packages/core/src/runtime/internal-hook-methods.ts:5)。这个测试不在开源仓库里,全仓只有 4 个测试文件,都不在 apps/zcode-cli。规则也没有被严格执行:97 个方法文件里有 22 个超过 400 行,最大的 session-fork.ts 有 1487 行、steering.ts 有 1403 行,types.ts 则干脆关掉了 max-lines 检查(types.ts:2)。
代价是编译器不再替你核对“原型上真的有这个方法”,这正是被关掉的那条 lint 规则要防的事。从代码看,新增一个方法要改三处:方法文件、methods/index.ts 的安装行、内部或公开的方法声明,漏掉安装行只会在运行时以“不是函数”的形式暴露。
AgentRuntimeInternal:内部视图
字段是 private 的,类体之外的函数按理读不到。AgentRuntimeInternal(apps/zcode-cli/packages/core/src/runtime/internal.ts:60)把同样的字段以公开属性重新声明一遍,并继承三组方法声明:AgentRuntimeCoreMethods、AgentRuntimeTurnMethods、AgentRuntimeHookMethods,分别在 internal-methods.ts、internal-turn-methods.ts、internal-hook-methods.ts。方法文件一律把 this 声明成它,于是可以随意读写状态、互相调用内部方法;bootstrap 与协议层拿到的仍是合并后的 AgentRuntime 类型,看不到私有字段,也看不到那 100 个内部方法。
由于同名字段一边是 private、一边是公开属性,两个类型互不兼容,构造器只能先做一次 this as unknown as AgentRuntimeInternal 的双重断言(agent-runtime.ts:229),再调用 createDefaultSubagentPort、startMcpStartup 等内部方法。内部视图比类多出 4 个字段:sessionMailboxPort、permissionFullAccessPending、pendingInputDrains、lastPermissionGrantId(internal.ts:117、internal.ts:136)。后三个是运行中动态挂上的,例如 permissionFullAccessPending 在 apps/zcode-cli/packages/core/src/runtime/permission-full-access.ts:34 置位;sessionMailboxPort 则只有声明、从未被赋值,邮箱端口实际直接从 deps 读(runtime-tools.ts:109)。还有些状态干脆不挂在实例上,而是放进以实例为键的 WeakMap,例如“事务已提交、事件尚未发布”的权限授予(apps/zcode-cli/packages/core/src/runtime/permission-grant-recovery.ts:4)。
构造时做了什么
构造器依次做配置投影、建缺省对象、派生根 trace 与子 logger、保存端口、新建消息历史与命令队列、装配工具,最后提前启动 MCP,主干如下(agent-runtime.ts:228):
constructor(sessionId: SessionId, config: AgentRuntimeConfig, deps: AgentRuntimeDeps) {
const runtime = this as unknown as AgentRuntimeInternal;
this.sessionId = sessionId;
this.turnNumber = 0;
// ...
this.permissionBroker = deps.permissionBroker ?? createDenyPermissionBroker();
// ...
this.eventReducer = new EventReducer();
this.eventStore = deps.eventStore;
this.sessionStore = deps.sessionStore;
this.rootTraceContext = deps.traceContext ?? createRootTraceContext({ sessionId });
// ...
this.subagentPort = deps.subagentPort ?? runtime.createDefaultSubagentPort(deps);
// ...
const tooling = initializeRuntimeTooling(runtime, deps, sessionId);
this.hookRunner = tooling.hookRunner;
this.workspaceHookAdmission = deps.workspaceHookAdmission;
this.executor = tooling.executor;
this.contextBuilder = deps.contextBuilder ?? null;
if (this.contextBuilder) {
runtime.initializeMessageHistoryFromContext(this.contextBuilder, this.rootTraceContext);
this.contextInitialized = true;
}
runtime.startMcpStartup(this.rootTraceContext);
}几个值得注意的缺省:
- 审批缺省拒绝。宿主不注入
permissionBroker时用DenyPermissionBroker,任何需要问人的工具调用都会以“No permission client configured”被拒(apps/zcode-cli/packages/core/src/permission/broker.ts:24)。 - 上下文预算策略被强制改写。
AgentRuntimeConfig允许传"legacy"或"preflight-v1"(types.ts:130),但构造器无论传什么都覆盖成共享默认值(agent-runtime.ts:232),即"preflight-v1"(packages/shared/src/zcode-protocol/index.ts:1696);注释说 3.12.2 仍接受旧宿主传入legacy,但运行时、日志与子 Agent 只用 preflight。 - 模型选择可以缺席。旧会话恢复时可能没有完整选择,构造器不替它挑默认模型(
agent-runtime.ts:274),真正开跑时才由createRuntimeModel拒绝(apps/zcode-cli/packages/core/src/runtime/methods/runtime-model.ts:21)。 - MCP 在构造时就开始连。
startMcpStartup立即发起配置里各服务器的连接,并登记为阻止会话回收的在飞工作(apps/zcode-cli/packages/core/src/runtime/methods/mcp.ts:111),首轮回合开始时多半已经连好,细节见 MCP。
trace 上下文怎么传下去
CLI 的 AGENTS.md 对可观测性的要求很硬:所有任务执行都携带可传播的 traceId,它默认对应一次顶层会话的完整任务链,子会话、子 Agent、重试与后台任务都归属同一个 traceId;sessionId、turnId、spanId 等是它之下的结构化子标识(apps/zcode-cli/AGENTS.md:74、apps/zcode-cli/AGENTS.md:75)。上下文对象定义在 apps/zcode-cli/packages/contracts/src/tracing/tracer.ts:14,字段是 traceId、queryId、spanId、parentSpanId、parentId、sessionId、turnId 与自由属性 attributes。派生子上下文沿用 traceId、生成新的 spanId、把父 spanId 记成 parentSpanId,并合并属性(tracer.ts:212):
export function createChildTraceContext(
parent: TraceContext,
options: {
queryId?: QueryId;
sessionId?: SessionId;
turnId?: TurnId;
attributes?: Record<string, string | number | boolean>;
} = {},
): TraceContext {
return {
traceId: parent.traceId,
queryId: options.queryId ?? parent.queryId,
spanId: generateSpanId(),
parentSpanId: parent.spanId,
parentId: parent.spanId,
sessionId: options.sessionId ?? parent.sessionId,
turnId: options.turnId ?? parent.turnId,
attributes: {
...parent.attributes,
...options.attributes,
},
};
}deps.ts 在这个函数和 traceContextToLogContext 外面又各包了一层,显式把 queryId 带上(apps/zcode-cli/packages/core/src/runtime/deps.ts:18、deps.ts:34),runtime 内部一律用包装后的版本。一条消息的 trace 链路是这样串起来的:
- 根。协议层建会话记录时,如果宿主请求带了
traceId与spanId就沿用,否则新生成一个 UUID(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/server-types.ts:274);进程内的 TUI 由createZCodeApp自己新建(create-app.ts:153)。runtime 拿到后存进rootTraceContext,并用它派生带module: "core.runtime"的子 logger(agent-runtime.ts:258、agent-runtime.ts:260)。 - 回合。受理输入时派生回合上下文,写入新的
turnId、queryId与属性turnNumber(apps/zcode-cli/packages/core/src/runtime/methods/prompt-admission.ts:88),整个回合在runWithContextAsync里执行(apps/zcode-cli/packages/core/src/runtime/methods/turn.ts:184),底层是AsyncLocalStorage(tracer.ts:166)。 - 模型步。每次模型请求再派生一层,带上
providerId、modelId、iteration与querySource(apps/zcode-cli/packages/core/src/runtime/methods/turn-model-step.ts:158);标题生成这类旁路请求同样从回合上下文派生。回合中途注入一条 guide 后,后续请求改挂在那条输入的queryId上(apps/zcode-cli/packages/core/src/runtime/methods/turn-guide-drain.ts:52)。 - 隐式取用。工具执行器、钩子运行器发事件时没有显式上下文,一律取
getCurrentTraceContext() ?? runtime.rootTraceContext(runtime-tools.ts:99、runtime-tools.ts:161)。 - 落到事件与子会话。事件本身只记
traceId与turnId(apps/zcode-cli/packages/core/src/runtime/methods/events.ts:69),spanId这一层只进日志;子 Agent 的 runtime 直接拿请求里的 trace 作为自己的根(subagent.ts:366),与父会话共享traceId。
执行状态、模式端口与模型选择
执行状态只有两个字段:权限模式 mode(build、edit、yolo 与内部用的 auto;plan 只在读旧格式时接受)和 Plan 开关 planEnabled(packages/shared/src/execution-state.ts:3)。修改统一走 core 的 applyRuntimeExecutionState(apps/zcode-cli/packages/core/src/runtime/execution-state.ts:43):完全访问的切换进行中则拒绝;Plan 与进行中的 Goal 不能同时生效;先把状态写成会话条目 runtime/execution_state,成功后才改内存,最后发 SessionModeChanged 事件,注释写明“保存失败不发布成功快照,也不提前改内存”(runtime/execution-state.ts:42)。
模式端口 createRuntimeSessionModePort(apps/zcode-cli/packages/core/src/runtime/session-mode-port.ts:5)把进出 Plan 模式包装成端口交给工具执行器(runtime-tools.ts:193),Plan 相关工具经它切换状态,不直接碰 runtime 字段;工具一侧的行为见 Todo、提问与 Plan 模式。
模型选择 cloneModelSelection 只做一件事:逐字段拷贝 providerId、modelId 与 options(apps/zcode-cli/packages/core/src/runtime/model-selection.ts:3)。读写 sessionModelSelection 时两头都拷贝(config.ts:94),外部拿到的对象改了也不会串回 runtime。模型怎样按选择创建,见模型适配层。
对外公开的方法
89 个公开方法按用途归纳:
| 类别 | 个数 | 主要方法 |
|---|---|---|
| 输入与回合 | 10 | admitPrompt、executeTurn、steerTurn、enqueueDeferredInput、stopActiveForegroundExecution、recordExternalUserPrompt |
| 队列管理 | 10 | removePendingInputById、reservePendingInputById、editPendingInputById、reorderPendingInput、setQueueAutoDrain、setFollowupMode |
| 配置与模式 | 12 | updateConfig、setExecutionState、getMode、grantPermissionFullAccess、setSessionModelSelection、emitModelSelected |
| 事件与投影 | 7 | subscribeEvents、appendEvent、getSessionEventStore、getProjection、isSessionPersisted |
| 会话标题 | 3 | setCustomSessionTitle、maybeStartSessionTitleGenerationFromExternalInput |
| 工具与权限 | 9 | getToolRegistry、invalidateToolCache、scheduleTools、executeTools、resolvePermission、createChildClientPorts |
| 目标 | 5 | recordTargetChanged、continueActiveTargetIfIdle、continueActiveTargetLoop |
| 后台任务与工作流 | 10 | readBackgroundBashOutput、stopBackgroundTask、sealBackgroundTaskNotifications、startSavedWorkflowRun、amendWorkflowRunSettings |
| 恢复、回退与分叉 | 9 | resumeFromStore、rewindConversationToMessage、forkStableConversationAtMessage、previewWorkspaceFileRewind |
| 驻留与生命周期 | 5 | hasActiveOrQueuedTurnWork、hasResidencyBlockingWork、trackResidencyBlockingWork、beginShutdown、closeBrowserSession |
| 其他 | 9 | getSessionId、getProjectId、getSkillCatalog、generateWorkspaceText、testModelConnectivity、drainMemoryExtractions |
createChildClientPorts、getSessionEventStore、notifyExternalChildSessionEvent 三个方法是给“在类外构造子 runtime”的 bootstrap 留的接缝:子会话必须共用父的事件存储,否则 transcript 会是一片空白;审批与请求头端口必须路由回父会话,注释记录了曾经因为身份错位让子代理在首个模型请求前挂起 80 分钟的事故(config.ts:177、config.ts:191、config.ts:216)。
RuntimeFactory 与 core 的导出面
@zcode/core 的 package.json 声明了四个入口:主入口、./repl、./browser-client 与 ./create-workflow-graph-bounds(apps/zcode-cli/packages/core/package.json:8)。主入口把 agent、context、compact、tool、hooks、mcp、subagent、runtime-task、workflow、permission 等子目录整体再导出,运行时部分在 apps/zcode-cli/packages/core/src/index.ts:132 导出 AgentRuntime,另外点名导出几个 bootstrap 也要用的函数,例如 fork 与 dwf 截断共用的消息克隆器,注释解释了为什么不让 bootstrap 再抄一份(core/src/index.ts:135)。
core/src/runtime.ts 是运行时的二级出口,除了 AgentRuntime 与一批类型,还声明了一个工厂接口 RuntimeFactory,只有一个方法 create(config),返回 Promise<AgentRuntime>(apps/zcode-cli/packages/core/src/runtime.ts:38)。它经 index.ts 以类型导出,但在整个仓库里找不到任何实现或使用方,四处创建实例都直接 new AgentRuntime(...)。从签名看它也装不下今天的构造器:只收 config,既没有 sessionId 也没有 deps,更像早期设计留下的契约。
下一篇:bootstrap:把运行时拼起来——这四十九项依赖从哪里来:配置分层、数据目录、模型工厂,以及会话怎样创建与恢复。