@epoch-agent/core 2.8.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +133 -29
  2. package/dist/index.d.ts +1613 -166
  3. package/dist/index.js +4913 -2254
  4. package/package.json +3 -3
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { ToolProvider, EpochTool, EpochPlugin, ToolExposure, EpochSkill, HookType, EpochHook, PluginContext, QuestionRequest, QuestionAnswer, ComplianceCategory, ComplianceAction, ToolArtifactInput, ToolArtifact, TrustLevel, TrustManager as TrustManager$1, TokenUsage, EpochConfig, BudgetBreach, NoticeCode, ProviderType, EpochMessage, JsonSchema, ModelSelection, BackgroundTaskInfo, EpochToolCall, Telemetry, HookManager as HookManager$1, Lang, PermissionManager as PermissionManager$1, ApprovalRequest, ApprovalReply, AgentRoleScope, AgentRoleSource, EpochUserContent, EpochContentPart, AgentRole, PermissionLevel, PlanProposal, ScheduleRunnerSpec, ScheduleBackendKind, ScheduleTrigger, OperationType, ScheduleAllowlist, ScheduleDefinition, ScheduleRunStatus, ScheduleRun, SchedulePendingApproval, DiagnosticSink, CustomCommandDef, ModelRef, ExpandedCommand, CustomCommandSource, ContextBreakdown, WireMcpConnector, AgentEvent, SelectionContext, SetSelectionResult, ModelEntry, ReasoningState, ReasoningChoice, PermissionRuleLists, MoneyDigits, CurrencyDisplay, KeybindingTable, Diagnostic, KeybindingReport, FileOmitReason, SessionOmitReason, EpochToolResult, EpochUserCommand, EpochTurnFacts, LocalizedDetail, ApprovalScope, RuleValue, Operation, ApprovalOutcome, PermissionDecision, ScheduleDraft, ScheduleWideningKind, WireAgentEvent, WireAgentEnvelope, HookEvent, LogFn, HookRegistration, HookHandler, HookContext, VerifyResult, EpochFilePart, EpochSessionPart, MentionSet, EpochImagePart, TrustExecGrant, DecidedTrustLevel, TrustScope, TrustGrant, TrustRecord } from '@epoch-agent/protocol';
2
- export { API_KEY_ENV_VARS, AgentEvent, AgentEventOf, AgentEventType, ApprovalAnswer, ApprovalOutcome, ApprovalReply, ApprovalRequest, ApprovalScope, BudgetBreach, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, ComplianceAction, ComplianceCategory, DecidedTrustLevel, EpochConfig, EpochHook, EpochPlugin, EpochSkill, EpochTool, FinishReason, HookContext, HookEvent, HookHandler, HookRegistration, HookType, HookManager as IHookManager, PermissionManager as IPermissionManager, TrustManager as ITrustManager, JsonSchema, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, Operation, OperationType, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, PermissionDecision, PermissionLevel, PlanApprovalOutcome, PlanProposal, PluginContext, ProviderInfo, ProviderType, TokenUsage, ToolContext, ToolDefinition, ToolExposure, ToolResult, TrustExecGrant, TrustGrant, TrustLevel, TrustRecord, TrustScope, TrustTier, VerifyResult, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
3
- import { IsolationBackend, SqliteDatabase, ParseIssue, ShellFlavor } from '@epoch-agent/infra';
1
+ import { ToolProvider, EpochTool, EpochPlugin, ToolExposure, EpochSkill, HookType, EpochHook, PluginContext, QuestionRequest, QuestionAnswer, ComplianceCategory, ComplianceAction, ToolArtifactInput, ToolArtifact, TrustLevel, TrustManager as TrustManager$1, TokenUsage, EpochConfig, BudgetBreach, NoticeCode, ProviderType, EpochMessage, JsonSchema, ModelSelection, AgentMessage, EpochUserContent, TeamTask, TeamTaskStatus, TeamTaskPatch, AgentMemberInfo, AgentMemberStatus, BackgroundTaskInfo, EpochToolCall, Telemetry, HookManager as HookManager$1, Lang, PermissionManager as PermissionManager$1, ApprovalRequest, ApprovalReply, AgentRoleScope, AgentRoleSource, EpochContentPart, AgentEvent, AgentRole, PermissionLevel, PlanProposal, ScheduleRunnerSpec, ScheduleBackendKind, ScheduleTrigger, OperationType, ScheduleAllowlist, ScheduleDefinition, ScheduleRunStatus, ScheduleRun, SchedulePendingApproval, BoardChannel, BoardThread, BoardPost, DiagnosticSink, CustomCommandDef, ModelRef, ExpandedCommand, CustomCommandSource, ContextBreakdown, WireMcpConnector, SelectionContext, SetSelectionResult, ModelEntry, ReasoningState, ReasoningChoice, PermissionRuleLists, MoneyDigits, CurrencyDisplay, KeybindingTable, Diagnostic, KeybindingReport, FileOmitReason, SessionOmitReason, EpochToolResult, EpochUserCommand, EpochTurnFacts, EpochDiffSnapshot, LocalizedDetail, ApprovalScope, RuleValue, Operation, ApprovalOutcome, PermissionDecision, ScheduleDraft, ScheduleWideningKind, WireAgentEvent, WireAgentEnvelope, HookEvent, LogFn, HookRegistration, HookHandler, HookContext, VerifyResult, EpochFilePart, EpochSessionPart, MentionSet, EpochImagePart, TrustExecGrant, DecidedTrustLevel, TrustScope, TrustGrant, TrustRecord } from '@epoch-agent/protocol';
2
+ export { API_KEY_ENV_VARS, AgentEvent, AgentEventOf, AgentEventType, ApprovalAnswer, ApprovalOutcome, ApprovalReply, ApprovalRequest, ApprovalScope, BudgetBreach, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, ComplianceAction, ComplianceCategory, DecidedTrustLevel, EpochConfig, EpochHook, EpochPlugin, EpochSkill, EpochTool, FinishReason, HookContext, HookEvent, HookHandler, HookRegistration, HookType, HookManager as IHookManager, PermissionManager as IPermissionManager, TrustManager as ITrustManager, JsonSchema, LEGACY_LEVEL_ALIASES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, Operation, OperationType, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, PermissionDecision, PermissionLevel, PlanApprovalOutcome, PlanProposal, PluginContext, ProviderInfo, ProviderType, TokenUsage, ToolContext, ToolDefinition, ToolExposure, ToolResult, TrustExecGrant, TrustGrant, TrustLevel, TrustRecord, TrustScope, TrustTier, VerifyResult, apiKeyEnvVar, approvalPolicyForLevel, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval, resolvePermissionLevel } from '@epoch-agent/protocol';
3
+ import { IsolationBackend, SqliteDatabase, ParseIssue, ShellFlavor, SandboxMode, FailureVerdict } from '@epoch-agent/infra';
4
4
  export { BubblewrapOptions, DangerMatch, IS_WINDOWS, IsolationBackend, IsolationOptions, LogLevel, SeatbeltOptions, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
5
5
  import { z } from 'zod';
6
6
  import { existsSync } from 'node:fs';
@@ -182,12 +182,47 @@ declare class ToolRegistry {
182
182
  /** 批量注册独立工具 */
183
183
  registerTools(tools: EpochTool[], source?: string): void;
184
184
  private addTool;
185
+ /**
186
+ * 把**某一个来源**的工具表整个换成新的一份,**保住已有工具的位置**。
187
+ *
188
+ * ## 为什么不能「撤干净再注册一遍」(2026-10-26)
189
+ *
190
+ * 撤回再注册在功能上等价,但**顺序会变**:`this.tools` 是 `Map`,删掉再插
191
+ * 等于让这个来源的工具全部挪到表尾。而工具表的顺序是**请求前缀的一部分** ——
192
+ * OpenAI / DeepSeek 的前缀缓存要求「两次请求从第 0 个 token 起完全相同」,
193
+ * 顺序一动,整段历史按全价重新预填。装了几台 MCP 的会话里,任意一台
194
+ * `list_changed` 或重连都会触发这件事,而用户看到的是账单,不是报错。
195
+ *
196
+ * 所以这里逐个 `set`:`Map` 对**已存在的键**不改插入顺序,于是已发出去的那些
197
+ * 工具逐字节不动,只有真正新增的才 append 到表尾。hermes 那边把同一条判据写成
198
+ * `preserve_prefix=True`,docstring 逐字是「existing tools keep their slot and
199
+ * the schema bytes already sent, new tools append at the tail」。
200
+ *
201
+ * ## 和 {@link unregisterBySource} 的分工
202
+ *
203
+ * 那个是「**全部**撤掉」(删除一个来源、整表销毁),这个是「**换成这一批**」。
204
+ * MCP 那条全量刷新的路走这个 —— 它一次 `tools/list` 拿到的是那台 server 的
205
+ * 完整现状,包括「哪些没了」。
206
+ *
207
+ * ## 三条语义
208
+ *
209
+ * 1. **新一批里没有的** → 真删(那是 server 撤掉的工具);
210
+ * 2. **两边都有** → 就地覆盖(位置不变,description / schema 可以变);
211
+ * 3. **只有新一批有** → append 到表尾。
212
+ *
213
+ * 跨来源撞名仍然是「先注册的赢」(同 `addTool`),**同批内重名则是先到的赢**
214
+ * —— 与逐条 `addTool` 的行为一致,只是不再记一条 `keptFrom === droppedFrom`
215
+ * 那样的自我冲突(那条记录在启动诊断里读起来像 bug)。
216
+ *
217
+ * @returns 真被删掉(新一批不再提供)的条数
218
+ */
219
+ replaceBySource(tools: readonly EpochTool[], source?: string): number;
185
220
  /**
186
221
  * 撤掉某个来源的全部工具。
187
222
  *
188
- * 给「按 server 整体替换」用:MCP 的 `tools/list` 本来就是全量返回,
189
- * 逐个 diff 只是自找麻烦 —— 撤干净再重新注册一遍
190
- * (对齐 gemini-cli `ToolRegistry.removeMcpToolsByServer()` 的粒度)。
223
+ * 给「整个来源消失」用(删掉一台 server、销毁注册表)。**热更新不要用它** ——
224
+ * 撤干净再注册会让这个来源的工具全部挪到表尾,把请求前缀缓存整个废掉,
225
+ * 判据见 {@link replaceBySource}。
191
226
  *
192
227
  * 连带清掉与这个来源相关的冲突记录:不清的话每刷新一次就多攒一条,
193
228
  * 启动诊断里会出现同一个撞名重复十遍。
@@ -690,8 +725,10 @@ declare class CodeSandbox {
690
725
  * 模型压根拿不到这个能力。
691
726
  *
692
727
  * 工具输出会**如实附上本次的隔离级别**。这不是啰嗦:
693
- * macOS 上是 OS 强制的(写不出沙箱、默认断网),Linux/Windows 上只有进程隔离,
694
- * 两者的风险差一个量级,不能让人以为都一样安全。
728
+ * macOS 上是 OS 强制的(写不出沙箱、默认断网),Windows 上没有 OS 级后端 ——
729
+ * 但 **JavaScript 那档从 2026-10-27 起挂着 Node 自己的权限模型**
730
+ * (`sandbox/node-permission.ts`,Node ≥ 22.5 才挂),Python 那档仍然只有进程隔离。
731
+ * 几档的风险差一个量级,不能让人以为都一样安全。
695
732
  *
696
733
  * ## ⚠️ 2026-08-16 起 `terminal` 也走隔离层了,但**边界不一样**
697
734
  *
@@ -1709,6 +1746,44 @@ interface CompressConfig {
1709
1746
  * 不给(或空串)时摘要提示词**逐字**保持原样,理由同 `instruction` / `plan`。
1710
1747
  */
1711
1748
  goal?: string;
1749
+ /**
1750
+ * **撞墙之后**那一次压缩:不按尾部 token 预算留,只保最小的那几条
1751
+ * (2026-10-28)。
1752
+ *
1753
+ * ## 它修的是哪一种救不回来
1754
+ *
1755
+ * `determineBoundaries` 的尾部预算 = `threshold × contextLength × targetRatio`
1756
+ * ([compressor.ts](./compressor.ts) 的 Phase 2)。窗口设得越大,这一份预算越宽
1757
+ * ——**宽到能把整段历史都吃进去**,于是中段为空、`compress()` 在 Phase 2
1758
+ * 之后直接返回、`shrank()` 判为没变小,最后原样抛。
1759
+ *
1760
+ * 而这不是假想的边角:撞墙**恰恰常常发生在 token 侧还宽松的时候**。
1761
+ * 图片的 token 按宽高估(一张 2 MB 截图只记一千出头),字节却是几 MB ——
1762
+ * 于是「窗口 200K、历史才 10K token」这种看起来毫无压力的会话,请求体已经
1763
+ * 撞穿了网关。那时正常那一套边界计算一条都不愿松手,而我们要的正是
1764
+ * 「能拿多少拿多少」。
1765
+ *
1766
+ * ## 落到哪一档(判据是一次明确的选择,不是顺手)
1767
+ *
1768
+ * `true` 时尾部预算按 **0** 算,于是尾部循环只受那两条下限约束:
1769
+ * `protectLastN` 条、`minTailUserMessages` 条 user 消息。
1770
+ *
1771
+ * - **头部的 `protectFirstN` 不动。** 保住的那一条是会话开头的第一条用户消息,
1772
+ * 摘要模板的 `## Goal` 正是对着它写的 —— 连它一起压掉,摘要就失去了
1773
+ * 「这一摊到底要干什么」的锚点,压出来的是一段不知道自己为了什么而写的转述。
1774
+ * - **那两条下限也不动。** 它们守的是「刚才在干什么」,压掉之后模型会拿一份
1775
+ * 摘要接着干一件它已经做到一半的事,而它不知道做到哪儿了。
1776
+ *
1777
+ * 也就是说:`force` 松的是**按 token 摊开的那份预算**,不是任何一条保护。
1778
+ *
1779
+ * ## 谁给、谁不给
1780
+ *
1781
+ * **只有 `AgentLoop` 撞墙后的那一次给**(`compressNow` 的 `force`)。
1782
+ * 自动压缩的两条线(轮初硬线、轮末软线)和手动 `/compact` 都不给 ——
1783
+ * 那几处上一发是**正常结束**的,没有「这一发根本发不出去」这个前提,
1784
+ * 按正常预算留够近期内容才是对的。
1785
+ */
1786
+ force?: boolean;
1712
1787
  }
1713
1788
  /**
1714
1789
  * 压缩用的消息形状。
@@ -1725,6 +1800,27 @@ interface CompressMessage {
1725
1800
  toolCallIds?: string[];
1726
1801
  /** tool 消息回应的 tool call id */
1727
1802
  toolCallId?: string;
1803
+ /**
1804
+ * 这一步模型的思考原文(2026-10-28)。
1805
+ *
1806
+ * ## 它为什么必须**穿过**压缩器,而不是在这里被丢掉
1807
+ *
1808
+ * 两件事,缺一件就是 bug:
1809
+ *
1810
+ * 1. **head / tail 原样保留那一段,思考不能掉。** 压缩的形状是
1811
+ * `head + 摘要 + tail`,两头是逐字带过去的。而 DeepSeek 思考模式 + `tools`
1812
+ * 时要求**每一条**历史 assistant 都带 `reasoning_content`(判据在
1813
+ * `EpochMessage.reasoning` 上)。这里不带上,压一次就把两头的思考洗掉了 ——
1814
+ * 不是因为「模型变笨」,是下一次请求直接 400。
1815
+ * 2. **中间段进摘要时,思考是素材的一部分。** 摘要是唯一活下来的那一段,
1816
+ * 而「为什么这么做」几乎全在思考里(正文常常只有一句「我查一下」)。
1817
+ * 只喂 `content` 等于让摘要模型对着一堆动作猜动机。
1818
+ *
1819
+ * ⚠️ **它是 `content` 之外的一格,不是并进 `content`**:并进去的话
1820
+ * `unflattenMessage` 还原时分不出哪半是正文、哪半是思考,回灌给模型的
1821
+ * `reasoning_content` 会连正文一起塞进去 —— 而模型会读到「自己说过两遍」。
1822
+ */
1823
+ reasoning?: string;
1728
1824
  }
1729
1825
  /**
1730
1826
  * 摘要调用的产出。
@@ -2448,6 +2544,324 @@ interface UtilityModelInfo {
2448
2544
  issue?: string;
2449
2545
  }
2450
2546
 
2547
+ /**
2548
+ * 一个成员的收件箱 —— 子 agent 那一侧的投递口。
2549
+ *
2550
+ * ## 它实现的是 `SteerSource`
2551
+ *
2552
+ * 引擎(`loop.ts:723`)在每个**步骤边界**调一次 `take()`,把拿到的东西当成
2553
+ * 一条 `steer` 事件插进当前这一轮。所以「子任务跑到第 3 步收到一条消息、
2554
+ * 当轮改变行为」这件事,**不需要新机制** —— 主会话的插话走的就是这条路
2555
+ * (`agent-session.ts:174` 把宿主的队列原样透给 `RunInput.steer`)。
2556
+ *
2557
+ * 我们做的只是:给子 agent 也接一个源,而那个源的另一头是 `send_message`。
2558
+ *
2559
+ * ## 满了丢最老的,但要**说一声**
2560
+ *
2561
+ * 计划里的风险一是上下文膨胀:成员互发消息时历史会一直长,而子 agent 的
2562
+ * 上下文刻意是小的。所以给收件箱一个硬容量。
2563
+ *
2564
+ * 丢最老的而不是拒收新的:拒收会让**最新的纠正**进不来,而那正是这个功能
2565
+ * 存在的理由。代价是老消息没了 —— 所以 `take()` 时如果丢过,前面补一行
2566
+ * 说明。静默丢话是最坏的一种:模型不知道自己漏看了什么。
2567
+ *
2568
+ * ## 为什么 `take()` 一次取干净
2569
+ *
2570
+ * 判据和 `SteerSource.take()` 上那段逐字相同:两条消息之间没有模型的回答,
2571
+ * 分两轮送进去等于让它对着第一句先答一遍 —— 而这两句本来是**同一批**
2572
+ * 纠正。这里 additionally 把几条消息拼成同一条 user 内容,省一次角色切换。
2573
+ */
2574
+
2575
+ /** 一个成员收件箱最多攒多少条还没被读走的消息 */
2576
+ declare const DEFAULT_INBOX_CAPACITY = 20;
2577
+ declare class AgentInbox implements SteerSource {
2578
+ private readonly capacity;
2579
+ private queue;
2580
+ private dropped;
2581
+ constructor(capacity?: number);
2582
+ /**
2583
+ * 投一条。返回 `false` = 因为满了挤掉了一条更老的。
2584
+ *
2585
+ * 返回值不是「成功与否」:**这条总是进去了**。它只用来让调用方在工具结果
2586
+ * 里说一句「顺带挤掉了 N 条」,而不是让模型以为一切照旧。
2587
+ */
2588
+ push(msg: AgentMessage): boolean;
2589
+ pending(): boolean;
2590
+ /**
2591
+ * 取走全部待读消息,折成**一条** user 内容。
2592
+ *
2593
+ * 前缀由 protocol 的 `agentMessageLine` 拼 —— 那是两个消费方(这里和父会话的
2594
+ * `TaskNoticeTracker`)逐字相同的唯一保证,见那个函数的注释。
2595
+ */
2596
+ take(): EpochUserContent[];
2597
+ /** 还没被读走的条数。`list_agents` 和排障用 */
2598
+ get size(): number;
2599
+ }
2600
+
2601
+ /**
2602
+ * 团队共享任务板(阶段 2)—— 一组带依赖和归属的任务,多个成员看得见同一份。
2603
+ *
2604
+ * ## 为什么是内存、不是 SQLite
2605
+ *
2606
+ * 判据和 `registry.ts` 文件头那条**逐字相同**:这份东西的读者是**同一个进程里
2607
+ * 的那几个成员**。别的进程既不该看见你的任务、也不该往里写 —— 那是两拨互不
2608
+ * 相干的活,混在一块板上只会互相干扰。要跨进程共享的是**讨论板**(阶段 3),
2609
+ * 它才走 SQLite。
2610
+ *
2611
+ * ## `expectedRevision` 是 compare-and-set,不是「乐观锁的装饰」
2612
+ *
2613
+ * 两个成员同时看到「任务 3 还是 pending」,各自都决定去认领它。没有这道闸门
2614
+ * 的话后写的那个会静默盖掉前一个,而**两边都收到成功**—— 于是两个人干同一件
2615
+ * 事,而板上写着只有一个 owner。
2616
+ *
2617
+ * 所以 `update()` 收一个调用方**上一次读到的** revision:对不上就整笔拒绝,
2618
+ * 一个字段都不改,并告诉它现在是多少(让它可以重读再决定,而不是瞎猜)。
2619
+ * 不传 = 不检查,那条路留给「我确实是唯一写者」的场景。
2620
+ *
2621
+ * 这和 harness 的 `team_task_update`、codex 的 DAG 板是同一条规矩。
2622
+ */
2623
+
2624
+ /** 一次写入的结果。失败时**一个字段都没改** */
2625
+ type TaskWriteResult = {
2626
+ ok: true;
2627
+ task: TeamTask;
2628
+ } | {
2629
+ ok: false;
2630
+ reason: string;
2631
+ };
2632
+ declare class TeamTaskBoard {
2633
+ private readonly capacity;
2634
+ private readonly tasks;
2635
+ /** 插一个变更回调。registry 拿它把「板子变了」也算进「团队变了」 */
2636
+ private readonly listeners;
2637
+ constructor(capacity?: number);
2638
+ onChange(cb: () => void): () => void;
2639
+ private notify;
2640
+ /**
2641
+ * 建一条。
2642
+ *
2643
+ * `dependsOn` 里出现不存在的 id 是**硬失败**:那种依赖永远不会被满足,
2644
+ * 而任务会一直卡在「等一个不存在的东西」上,看起来像「它还没开始」。
2645
+ */
2646
+ create(input: {
2647
+ title: string;
2648
+ description?: string;
2649
+ owner?: string;
2650
+ dependsOn?: string[];
2651
+ }): TaskWriteResult;
2652
+ get(id: string): TeamTask | undefined;
2653
+ list(filter?: {
2654
+ status?: TeamTaskStatus;
2655
+ owner?: string;
2656
+ }): TeamTask[];
2657
+ /**
2658
+ * 改一条。
2659
+ *
2660
+ * `expectedRevision` 给了就必须**正好等于**当前值 —— 这是两个成员同时认领
2661
+ * 同一件事时唯一能拦住「后写的静默盖掉前一个」的地方。
2662
+ */
2663
+ update(id: string, patch: TeamTaskPatch, expectedRevision?: number): TaskWriteResult;
2664
+ }
2665
+
2666
+ /**
2667
+ * 进程内的成员表 —— 谁在、它现在什么状态、它的收件箱在哪。
2668
+ *
2669
+ * ## 为什么**不持久化**
2670
+ *
2671
+ * 一个成员就是一次 `new AgentLoop(...).run()`(`session-factory.ts:1306`),
2672
+ * 跑完那个实例就没人持有了。它能被「记住」的唯一载体是会话库里那一行
2673
+ * (`persistDelegateTurn`,带 `parent_id`)—— 而那是**事后回看**用的,
2674
+ * 不是「它还活着」的证据。
2675
+ *
2676
+ * 所以这张表每个进程一套,进程退出即消失。这和阶段 3 的讨论板是**两回事**:
2677
+ * 板子要跨进程共享(那是它存在的理由),所以它走 SQLite;roster 跨进程没有
2678
+ * 意义 —— 别的进程里的成员不在你这个进程里,你既看不到它的收件箱也打不断它。
2679
+ * 混在一起做会让「板上有一条消息,但收件人是另一个进程里早就结束的成员」
2680
+ * 变成一种没法处理的中间态。
2681
+ *
2682
+ * ## 为什么收件箱住在表里而不是调用方手里
2683
+ *
2684
+ * 两个不相干的调用方要往同一个成员塞话:父 agent 的 `send_message`
2685
+ * (`core/src/tools/builtin/send-message.ts`)和阶段 2 的成员之间互发。
2686
+ * 收件箱跟着成员走,这两条路就都只需要一个 `agentId`。
2687
+ *
2688
+ * 另外子 agent 那一侧(`runtime/src/session-factory.ts`)拿到收件箱的方式
2689
+ * 是**按 `subSessionId` 反查** —— 它手里只有这个,没有 agentId。所以表同时
2690
+ * 按两个键索引,见 `bySession()`。
2691
+ */
2692
+
2693
+ /**
2694
+ * 一次投递的结果。
2695
+ *
2696
+ * `ok: false` 时消息**没有进任何收件箱** —— 调用方(那个工具)要把 `reason`
2697
+ * 原样交给模型,别自己改写。原因是这条路的失败模式全是「模型以为送到了」:
2698
+ * 发给一个已经收工的成员,它下一轮永远不来,而父 agent 会干等着。
2699
+ */
2700
+ type DeliveryResult = {
2701
+ ok: true;
2702
+ to: string;
2703
+ status: AgentMemberStatus;
2704
+ } | {
2705
+ ok: false;
2706
+ to: string;
2707
+ reason: string;
2708
+ };
2709
+ declare class TeamRegistry {
2710
+ private readonly inboxCapacity;
2711
+ private readonly members;
2712
+ /** 按子会话 id 反查 —— 子 agent 那一侧只有这个键 */
2713
+ private readonly bySessionId;
2714
+ /**
2715
+ * 主会话的收件箱们 —— 按 sessionId 索引。
2716
+ *
2717
+ * 不再只有一个 `lead` 收件箱:每个主会话有自己的身份(用 sessionId 做 agentId),
2718
+ * 这样不同会话派出的成员不会串话,且会话之间可以互相通信。
2719
+ */
2720
+ private readonly mainInboxes;
2721
+ /** 登记过的主会话 id(判据在 `registerSession` 上) */
2722
+ private readonly sessions;
2723
+ /**
2724
+ * 打断用的回调(`interrupt_agent` 拉的就是它们)。
2725
+ *
2726
+ * 和 `members` 分开存:那个控制器住在 `DelegateManager.runSingleTask` 里,
2727
+ * 而成员登记**早于**控制器诞生(先登记才能早点收到消息)。所以这条线是
2728
+ * 「后补的」,判据在 `setAbort` 上。
2729
+ */
2730
+ private readonly aborters;
2731
+ /** 「团队变了」的订阅者(`wait_agent` 挂在这里) */
2732
+ private readonly listeners;
2733
+ /** 共享任务板(阶段 2)。它也变动时也会叫醒 `wait_agent` */
2734
+ readonly tasks: TeamTaskBoard;
2735
+ constructor(inboxCapacity?: number);
2736
+ /** 订阅「团队变了」。返回取消订阅 */
2737
+ onChange(cb: () => void): () => void;
2738
+ /**
2739
+ * 叫醒所有等着的(`wait_agent` 的底座)。
2740
+ *
2741
+ * ⚠️ **`setActivity` 刻意不叫**:它每完成一步就变一次,接上去的话
2742
+ * `wait_agent` 会在一个子 agent 跑得正欢时每一步都返回一次,而调用方要的是
2743
+ * 「有实质变化」(新人 / 新消息 / 任务动了)。
2744
+ */
2745
+ private notify;
2746
+ /**
2747
+ * 等下一次团队变化。
2748
+ *
2749
+ * @returns `true` = 真的变了;`false` = 等到超时或被中止。
2750
+ * 两种情况都只报「有没有变」,**不带变了什么** —— 调用方应该自己去
2751
+ * `list_agents` / `team_task_list` 读一遍,而不是相信一个可能已经过时的摘要。
2752
+ */
2753
+ waitForChange(ms: number, signal?: {
2754
+ readonly aborted: boolean;
2755
+ }): Promise<boolean>;
2756
+ /**
2757
+ * 派出去一个成员。返回它的 `AgentMemberInfo`(含 `agentId`)。
2758
+ *
2759
+ * `agentId` 今天就是子会话 id:一次派活的子会话本来就唯一,再造一个短名
2760
+ * 只是多一个要同步的映射。阶段 2 的 `spawn_teammate` 要「具名」时再让
2761
+ * 调用方传 `agentId` 进来。
2762
+ */
2763
+ createMember(args: {
2764
+ sessionId: string;
2765
+ parentSessionId: string;
2766
+ /** 具名成员(阶段 2 的 `spawn_teammate`)。缺省 = 子会话 id */
2767
+ agentId?: string;
2768
+ /** 角色名。纯展示,进 `list_agents` */
2769
+ role?: string;
2770
+ }): AgentMemberInfo;
2771
+ /**
2772
+ * 补上「怎么打断这个成员」。
2773
+ *
2774
+ * 控制器诞生在 `DelegateManager.runSingleTask` 里,而成员登记比它早 ——
2775
+ * 所以那条线只能事后接进来。没接上的成员 `interrupt()` 返回 `false`,
2776
+ * 而不是假装打断了。
2777
+ */
2778
+ setAbort(agentId: string, fn: () => void): void;
2779
+ /** 打断一个成员。返回 `false` = 没有可打断的(已经收工,或者从没接上) */
2780
+ interrupt(agentId: string): boolean;
2781
+ /** 按 `agentId` 取。返回的是**快照**,改它不会影响表 */
2782
+ get(agentId: string): AgentMemberInfo | undefined;
2783
+ /** 按子会话 id 取 —— 子 agent 那一侧唯一的入口 */
2784
+ bySession(sessionId: string): AgentMemberInfo | undefined;
2785
+ /** 全部成员,按派出去的先后 */
2786
+ list(): AgentMemberInfo[];
2787
+ /** 把一个成员的收件箱交给子 agent 那一侧(`sub.run({ steer })`) */
2788
+ inboxOf(agentId: string): AgentInbox | undefined;
2789
+ /** 同上,按子会话 id 查 */
2790
+ inboxBySession(sessionId: string): AgentInbox | undefined;
2791
+ /**
2792
+ * 登记一段主会话 —— 「这个 id 是活的会话」的唯一凭据。
2793
+ *
2794
+ * ## 为什么需要它
2795
+ *
2796
+ * `deliver` 现在要同时接受两种收件人:成员(在 `members` 里)和**别的主会话**
2797
+ * (不在表里)。只看「表里没有」就当成主会话的话,一个拼错的成员名会被静默
2798
+ * 投进一个永远没人读的箱子 —— 那正是 `send_message` 从一开始就要防的
2799
+ * 「调用方以为送到了」。
2800
+ *
2801
+ * 所以主会话先登记再寻址,剩下的未知 id 仍然报错。登记点只有一处:
2802
+ * `AgentLoop.runTurns()` 开跑时登记自己(判据在那儿)。
2803
+ */
2804
+ registerSession(sessionId: string): void;
2805
+ /** 这个 id 是不是一段登记过的主会话(诊断和 `list_agents --all` 用) */
2806
+ hasSession(sessionId: string): boolean;
2807
+ /** 登记过的全部主会话 id,按登记先后 */
2808
+ sessionIds(): string[];
2809
+ /**
2810
+ * 取主会话的收件箱。**不存在时会新建** —— 这样成员给一个还没调用过团队工具的
2811
+ * 主会话发消息时,它下一轮能收到,而不是因为「收件箱不存在」失败。
2812
+ */
2813
+ mainInbox(sessionId: string): AgentInbox;
2814
+ /**
2815
+ * 记它此刻在干什么。
2816
+ *
2817
+ * 来源是 `SubAgentRunner.onProgress` 那条通道。**没有就不叫**,别填占位符 ——
2818
+ * `list_agents` 会把它原样印出来,一句话占位会让模型以为那是真进度。
2819
+ */
2820
+ setActivity(agentId: string, line: string): void;
2821
+ /**
2822
+ * 把它重新标成「在跑」(阶段 2 的 `followup_task`)。
2823
+ *
2824
+ * ## 为什么不是再 `createMember` 一次
2825
+ *
2826
+ * 成员已经在表里了 —— 再建一次撞名,而那个错会以「`followup_task` 明明成了、
2827
+ * 却报成员名被占用」的形态出现。这一条只动状态,不动身份:`sessionId`、
2828
+ * `parentSessionId`、`role`、`startedAt` 全部保留 —— 续跑的是**同一个**成员,
2829
+ * 不是新派出去的一个。
2830
+ *
2831
+ * ## `finishedAt` 必须清掉
2832
+ *
2833
+ * `markFinished` 有一道「`finishedAt` 只写一次」的闸。不清的话这一轮跑完
2834
+ * 那道闸会把新的收工挡掉 —— 成员会永远停在 `running`,而 `list_agents`
2835
+ * 上那一行看着像还在干活。
2836
+ *
2837
+ * `activity` 也清:它上一轮最后那句话(「读完了 src/a.ts」)说的是**上一次**
2838
+ * 的收尾,留着会让 `list_agents` 在它刚醒来时报告一句过期的话。
2839
+ */
2840
+ markRunning(agentId: string): void;
2841
+ /** 收工(或挂掉)。`finishedAt` 只写一次 —— 重复调用是空操作 */
2842
+ markFinished(agentId: string, status: Extract<AgentMemberStatus, 'idle' | 'finalized' | 'failed'>): void;
2843
+ /**
2844
+ * 投一条消息。
2845
+ *
2846
+ * ## `finalized` / `failed` 一律失败,消息**不进收件箱**
2847
+ *
2848
+ * 计划里那条规矩(照 codex 的 `api.rs:35-36`):通知不唤醒已终结的 agent。
2849
+ * 收进一个永远不会有下一轮读它的箱子,等于给调用方一个假成功 —— 而父 agent
2850
+ * 接下来会照常等回音。
2851
+ *
2852
+ * 阶段 2 有了 `followup_task`(2026-10-30)之后,唤醒走的是**另一条路**:
2853
+ * 用 `resumeSessionId` 另起一轮 `run()`,而不是往这个箱子里塞一条「等下一轮」
2854
+ * 的消息。两件事刻意分开 —— 收进一个不会有人读的箱子却报成功,正是这一条要防的。
2855
+ */
2856
+ deliver(msg: AgentMessage): DeliveryResult;
2857
+ /** 造一条消息。`id` 现在没有消费方(阶段 2 的重试去重要用),但形状先定下 */
2858
+ static message(args: {
2859
+ from: string;
2860
+ to: string;
2861
+ text: string;
2862
+ }): AgentMessage;
2863
+ }
2864
+
2451
2865
  /**
2452
2866
  * 给人看的路径:工作区内走相对路径,分隔符统一成 `/`。
2453
2867
  *
@@ -2505,15 +2919,74 @@ type BackgroundTaskSource = () => readonly BackgroundTaskInfo[];
2505
2919
  */
2506
2920
  declare class TaskNoticeTracker {
2507
2921
  private readonly source;
2922
+ /**
2923
+ * 成员表(阶段 1)。给了才把成员发来的话拌进这条 note。
2924
+ *
2925
+ * 不给 = 行为和改造前逐字节相同 —— 用例、嵌入宿主不必为了跑一轮
2926
+ * 对话去建一张成员表。
2927
+ */
2928
+ private readonly team?;
2929
+ /**
2930
+ * 讨论板的订阅拉取点(阶段 3)。`pending` 纯读,`markRead` 推到最新。
2931
+ *
2932
+ * ⚠️ **`markRead` 只在确定要返回 note 时调**(见下面 `notice()` 里那一段):
2933
+ * 提前推游标等于把还没进上下文的帖子标记成「看过了」。
2934
+ */
2935
+ private readonly board?;
2508
2936
  private readonly seen;
2509
- constructor(source: BackgroundTaskSource | undefined);
2937
+ constructor(source: BackgroundTaskSource | undefined,
2938
+ /**
2939
+ * 成员表(阶段 1)。给了才把成员发来的话拌进这条 note。
2940
+ *
2941
+ * 不给 = 行为和改造前逐字节相同 —— 用例、嵌入宿主不必为了跑一轮
2942
+ * 对话去建一张成员表。
2943
+ */
2944
+ team?: TeamRegistry | undefined,
2945
+ /**
2946
+ * 讨论板的订阅拉取点(阶段 3)。`pending` 纯读,`markRead` 推到最新。
2947
+ *
2948
+ * ⚠️ **`markRead` 只在确定要返回 note 时调**(见下面 `notice()` 里那一段):
2949
+ * 提前推游标等于把还没进上下文的帖子标记成「看过了」。
2950
+ */
2951
+ board?: {
2952
+ pending: () => Array<{
2953
+ target: string;
2954
+ count: number;
2955
+ }>;
2956
+ markRead: () => void;
2957
+ } | undefined);
2958
+ /**
2959
+ * 取走父亲收件箱里的成员消息,折成 notice 里的那几行。
2960
+ *
2961
+ * ⚠️ **只能在确定要返回一条 note 时调**(`take()` 会清空队列)。
2962
+ * 现在的调用点(`loop.ts:1367`)拿到什么就往 `messages` 里推什么,
2963
+ * 所以那里是安全;哪天有人把它改成「先问一句再决定」,这个函数就要跟着改。
2964
+ */
2965
+ private takeAgentMessages;
2510
2966
  /**
2511
2967
  * 算出这一轮要报的话;没有变化时返回 `null`。
2512
2968
  *
2513
2969
  * 返回 `null` 而不是空串很重要 —— 调用方据此决定**要不要往 messages 里塞
2514
2970
  * 一条消息**。塞一条空消息会白白多一轮 role 切换,还可能打断 prompt cache。
2515
2971
  */
2516
- notice(): string | null;
2972
+ notice(sessionId?: string): string | null;
2973
+ /**
2974
+ * 和 {@link notice} 同一次计算,但把**成员发来的那几行**单独交出来
2975
+ * (2026-11-02)。
2976
+ *
2977
+ * 判据是它有两类消费方:模型那一侧要整段(后台任务 + 成员消息 + 讨论板),
2978
+ * 而**界面和会话历史只要成员消息那一半** —— 后台任务状态每轮都可能变,
2979
+ * 把它落进库等于往对话里灌噪音。拆开之后两个消费方各取所需,
2980
+ * 而不是让落盘那一侧拿字符串前缀去猜哪几行是消息。
2981
+ *
2982
+ * ⚠️ **取走就没了**(`take()` 清空队列),所以 `notice()` 和它
2983
+ * **不能对同一个箱子里调两次** —— 调一次拿 `text` 和 `agent` 两样。
2984
+ * 两个方法都留着的理由是旧调用方只要一句话。
2985
+ */
2986
+ noticeDetailed(sessionId?: string): {
2987
+ text: string;
2988
+ agent: string[];
2989
+ } | null;
2517
2990
  }
2518
2991
  /**
2519
2992
  * 一条变化的措辞。**不带输出、不带 pid**。
@@ -2834,6 +3307,28 @@ interface AgentConfig {
2834
3307
  * 而它们同名的那半个词已经让人查错过一次方向。
2835
3308
  */
2836
3309
  maxOutputTokens?: number;
3310
+ /**
3311
+ * 单次请求里**所有图片 base64 的合计**上限(字节,2026-10-28)。
3312
+ *
3313
+ * **缺省 = `DEFAULT_MAX_REQUEST_IMAGE_BYTES`(8 MiB,猜的)**,判据全文
3314
+ * (含「为什么这个数没有权威出处」)在 `provider/body-limit-cache.ts` 的文件头。
3315
+ *
3316
+ * ⚠️ 它是**字节**这条线,和 `contextLength`(token)是两个量纲 ——
3317
+ * 一张 2 MB 的截图在 token 账上只记一千出头,在字节账上是 2.7 MB。
3318
+ * 两条线都要有,因为只有文本那一半时 token 会先抓住,而只有图片时
3319
+ * token 那条线**永远抓不住**。
3320
+ *
3321
+ * 撞上一次之后,学到的值会在比它更小时顶替它(`effectiveImageBudget`)。
3322
+ */
3323
+ maxRequestImageBytes?: number;
3324
+ /**
3325
+ * 数据目录(`~/.epoch` 那一层),学到的字节上限存进它的 `cache/` 下。
3326
+ *
3327
+ * 不给就**不学、也不读**历史学习值 —— 退回只用配置值/缺省值。理由同
3328
+ * `MultimodalPolicy.homeDir`:这一层够不着「这台机器的数据目录在哪」,
3329
+ * 自己猜一个 `homedir()` 在测试和多 profile 场景下会写错地方。
3330
+ */
3331
+ homeDir?: string;
2837
3332
  /**
2838
3333
  * 预先算好的项目上下文(含工作区信任判定和已加载的指令文件)。
2839
3334
  *
@@ -3177,6 +3672,35 @@ interface AgentConfig {
3177
3672
  * core 不 import plugin-terminal(分层不允许),所以这条线由装配层接。
3178
3673
  */
3179
3674
  backgroundTasks?: BackgroundTaskSource;
3675
+ /**
3676
+ * 成员表(阶斵 1)。给了就在每轮 LLM 请求前把**成员发来的话**拌成一条 note
3677
+ * (与上面 `backgroundTasks` 那条并排,但各自一条)。
3678
+ *
3679
+ * 不给 = 行为与改造前逐字节相同。子 agent 那条**不要填**:它没有可寻址的
3680
+ * 成员表,它的收件箱是另一回事(`RunInput.steer`,由 `subRunner` 接)。
3681
+ *
3682
+ * ⚠️ 只取类型(编译后擦掉):`TeamRegistry` 反过来依赖 `SteerSource`,
3683
+ * 运行期走 type-only 才不会成环。
3684
+ */
3685
+ team?: TeamRegistry;
3686
+ /**
3687
+ * 讨论板的订阅拉取点(阶段 3)。给了就在每轮请求前把「你订阅的格子有新帖」
3688
+ * 拌成一条 note(与上面两条并排,各自一条)。
3689
+ *
3690
+ * 两个回调而不是一个对象:`markRead` 必须**只在确定要返回 note 时**调
3691
+ * (它会把游标推到最新),而 `pending()` 是纯读。把它们合成一个
3692
+ * `pendingAndMark()` 会让「这次没返回 note」变成一次静默漏看。
3693
+ *
3694
+ * 不给 = 行为与改造前逐字节相同。子 agent 那条**不要填** ——
3695
+ * 它的收件箱是 `RunInput.steer`,和板子无关。
3696
+ */
3697
+ board?: {
3698
+ pending: () => Array<{
3699
+ target: string;
3700
+ count: number;
3701
+ }>;
3702
+ markRead: () => void;
3703
+ };
3180
3704
  /**
3181
3705
  * 内容合规闸门(core 的 `compliance/`)。给了就在两处生效:
3182
3706
  * 模型输出(`agent/turn.ts` 那个唯一出口)和用户输入(发请求之前)。
@@ -3225,6 +3749,15 @@ interface HistoryEntry {
3225
3749
  role: 'user' | 'assistant';
3226
3750
  content: string;
3227
3751
  parts?: EpochContentPart[];
3752
+ /**
3753
+ * 这一轮的思考原文(2026-10-28)。
3754
+ *
3755
+ * ⚠️ **必须能跟着历史穿过来。** 原来它只落在会话库的 `messages.reasoning` 列上
3756
+ * 给人看,`loadHistory()` 一个字都不带 —— 于是刷新页面 / `--resume` 之后下一条
3757
+ * 消息发出去,DeepSeek 看不见上一轮的 reasoning_content,当场 400。
3758
+ * 判据在 registry 的 `CARRIES_REASONING_BACK` 上。
3759
+ */
3760
+ reasoning?: string;
3228
3761
  }
3229
3762
  interface RunInput {
3230
3763
  /** 纯文本或多模态部件数组。两种形态 AI SDK 都原生接受,不需要在这里分支 */
@@ -3272,6 +3805,26 @@ interface RunInput {
3272
3805
  * 不给就是今天的行为:这一轮跑完才轮到下一条消息。
3273
3806
  */
3274
3807
  steer?: SteerSource;
3808
+ /**
3809
+ * 这一轮每产出一个事件就旁路回调一次(2026-10-27)。
3810
+ *
3811
+ * ## 为什么是在 `run()` 上开个口,而不是改成让调用方用 `stream()`
3812
+ *
3813
+ * `run()` 和 `stream()` 走的是同一个 `runTurns()`,但 `stream()` 不回
3814
+ * `RunResult`(它的 `turns` 是内部数组,调用方拿不到)。子 agent 那一路
3815
+ * 既需要完整结果(它的汇总要交给父 agent),又需要知道「跑到哪一步了」
3816
+ * (后台作业的 `task_output` 一直只写着「还没跑完」)。改 `stream()` 的返回
3817
+ * 等于把两个生成器合并,而那是另一笔账。
3818
+ *
3819
+ * ## 默认什么都不做
3820
+ *
3821
+ * 不给这一格 = 改造前的行为,引擎一处都不多花。
3822
+ *
3823
+ * ## ⚠️ 回调里不要抛
3824
+ *
3825
+ * 它住在引擎的事件循环里,抛出来会把整轮掀翻。要防护自己在回调里做完。
3826
+ */
3827
+ onEvent?: (ev: AgentEvent) => void;
3275
3828
  }
3276
3829
  /**
3277
3830
  * 「正在跑的这一轮里,用户又说话了吗」——**由宿主实现,引擎只来拉**。
@@ -3336,6 +3889,16 @@ interface TurnRecord {
3336
3889
  result: string;
3337
3890
  }>;
3338
3891
  duration: number;
3892
+ /**
3893
+ * 在这**一次请求之前**插进来的消息(2026-11-02)。今天只有一种来源:
3894
+ * 子 agent 的收件箱(`drainSteer` 在步骤边界取走的 `send_message`)。
3895
+ *
3896
+ * 它进这一步的判据是**顺序**:这些消息是在本步那次模型请求之前进上下文的,
3897
+ * 所以落盘时它们应该排在本步 assistant 行**前面**(`persistDelegateTurn`
3898
+ * 那一侧就是这么用的)。不带它的话,子会话的历史里只有它自己说过的话,
3899
+ * 「谁跟它说了什么」查不到 —— 而子 agent 恰恰是靠这些话才改的行为。
3900
+ */
3901
+ received?: string[];
3339
3902
  }
3340
3903
  /** 中止信号的最小接口(AbortSignal 也满足) */
3341
3904
  interface AbortLike {
@@ -3347,37 +3910,39 @@ interface AbortLike {
3347
3910
  *
3348
3911
  * LLM 调用 delegate_task 工具,派发子任务给独立 Agent 并行执行。
3349
3912
  *
3350
- * ## 后台子 agent:接口留着了,**这一轮没实现**(方案 49 PR-3)
3913
+ * ## 两种派法:同步等(默认)和后台作业(`background: true`)
3914
+ *
3915
+ * 默认仍然是**同步等**:`delegate()` 一路 `await` 到所有子任务结束才返回,
3916
+ * 这一条一个字没改 —— 大多数委托十几次工具调用就回来了,模型拿到结论直接接着
3917
+ * 往下干,是它要的形状。
3351
3918
  *
3352
- * 今天 `delegate_task` 是**同步等**的:`delegate()` 一路 `await` 到所有子任务
3353
- * 结束才返回,于是父 agent 那一轮就卡在这儿。方案 49 §3.1 把这一格列成
3354
- * 「作业种类之三:后台子 agent —— **没有**(`delegate_task` 是同步等的)」,
3355
- * 而 §3.3 的判据是「现在统一,不等它」:等到真做后台子 agent 时再统一,
3356
- * 代价是那时要同时改三处已经发出去的工具。
3919
+ * `background: true` 是 2026-10-26 接上的第三类作业(`JobKind = 'agent'`):
3920
+
3921
+ * - 派完**立刻返回作业 id**(`a1`),父回合接着干别的
3922
+ * - 作业登记在**派活的那个会话**名下(`task_*` 那道按会话分区的墙靠它)
3923
+ * - 子任务结束 / 失败 / 被杀时,`task_output` 读得到汇总,下一轮开头
3924
+ * `TaskNoticeTracker` 会挂一条「a1 已结束」的 `user` note
3925
+ * - **父回合的终止不再打到它身上**:用户按停只停父回合,后台子任务继续跑
3926
+ * (四家竞品一致:Claude Code 在后台 agent 的 abort controller 上专门留了
3927
+ * 一句注释说明它不挂父链)。要收掉它只有两条路 —— `task_stop a1`,或者
3928
+ * 进程退出
3357
3929
  *
3358
- * PR-3 把作业表下沉到了 [infra/jobs.ts](../../../infra/src/jobs.ts),
3359
- * 那张表的 `JobKind` 里**已经有 `'agent'` 这一档**,接法因此是确定的:
3930
+ * ### 为什么不学另外三家「一律后台」
3360
3931
  *
3361
- * ```ts
3362
- * const job = registerJob<{ abort: AbortController }>({
3363
- * sessionId: parentSessionId, // 归派活的那个会话 —— task_* 那道墙靠它
3364
- * kind: 'agent',
3365
- * label: task.goal, // 一行标签就是它的目标(见 JobInfo.label)
3366
- * maxOutput: …, artifactsDir: …, artifactLabel: 'agent',
3367
- * stop: async () => controller.abort(), // 「怎么收」由产出方给
3368
- * terminates: true, // 子 agent 有终点,task_wait 对它有意义
3369
- * detail: { abort: controller },
3370
- * });
3371
- * ```
3932
+ * 那三家(codex / hermes-agent / deepseek-harness)没有这个开关,它们的
3933
+ * 委托入口本来就**只有后台一种**。我们保留默认同步,判据是我们的宿主没有
3934
+ * 它们那套推送:`epoch -p` 一轮结束就是结束,TUI 空转时也不会自己开口。
3935
+ * 一轮里「派出去然后等结论」仍然是最常见的形态,把它也改成异步,模型得
3936
+ * 每派一次就 `task_wait` 一次,等于白多一轮往返。
3372
3937
  *
3373
- * 之后 `task_list` / `task_output` / `task_wait` / `task_stop`
3374
- * **一行都不用改**就认得它(那四个已经是 kind 无关的了),`epoch doctor` 的
3375
- * 作业一节同理。要补的只有 `job-notice.ts` 的 `KIND_MARK` ——
3376
- * 那是一张 `Record<JobKind, string>`,漏了会在编译期报缺键。
3938
+ * ### 作业的 `stop` 收的是控制器,不是进程
3377
3939
  *
3378
- * ⚠️ **刻意不在这里放一个空壳**(没有 `startBackground()` 占位):一个没有调用方
3379
- * 的入口会被下一个人当成「已经能用了」,而它的权限口径(后台派活要不要重新过
3380
- * `delegate_task` 那一档 `command`)还没判过。真做那一轮要答的就是这个问题。
3940
+ * `JobSpec.stop` 收的是一个 `AbortController.abort()` —— 子 agent 不是子进程,
3941
+ * 退出时那张按 pid 记的表摸不到它。所以「进程退出时收干净」这件事在这一族上
3942
+ * 是靠**进程一起没了**实现的,不是靠 `release()`。
3943
+ *
3944
+ * ⚠️ **`detail` 里只放 controller**:`task_*` 那四个工具不读它,`jobSandbox()`
3945
+ * 读的那一格(`sandbox`)在这一族上不存在,于是它照旧返回 `undefined`。
3381
3946
  */
3382
3947
 
3383
3948
  /** 子任务定义 */
@@ -3391,6 +3956,26 @@ interface SubTask {
3391
3956
  * 是常见形态,强制整批同角色没道理。
3392
3957
  */
3393
3958
  role?: string;
3959
+ /**
3960
+ * 具名成员(阶段 2 的 `spawn_teammate`)。缺省 = 用子会话 id。
3961
+ *
3962
+ * 给了之后 `send_message` / `interrupt_agent` 都能按这个名字找到它 ——
3963
+ * 而 `delegate_task` 派出去的匿名成员只能靠 `list_agents` 看到那个
3964
+ * `delegate-xxxx`。重名由 registry 直接抛(不会静默改名)。
3965
+ */
3966
+ agentId?: string;
3967
+ /**
3968
+ * 接着这个**子会话**再跑一轮(阶段 2 的 `followup_task`)。
3969
+ *
3970
+ * 给了之后:
3971
+ * - 会话 id 就用它(不现生成 `delegate-xxxx`)—— 而“用同一个 id”就是这整
3972
+ * 条路的实现:runner 会按它把库里那段历史装回来;
3973
+ * - **不重新登记成员**(那个成员已经在了,重登记会撞名),改走 `markRunning`。
3974
+ *
3975
+ * ⚠️ 它和 `agentId` **不绑定**:匿名成员(`delegate_task` 派出去的)也有一行
3976
+ * 子会话,也能被续。只是它们没有名字,得用 `list_agents` 看到的那个 id 寻址。
3977
+ */
3978
+ resumeSessionId?: string;
3394
3979
  }
3395
3980
  /** 委托参数(工具接收) */
3396
3981
  interface DelegateArgs {
@@ -3399,6 +3984,11 @@ interface DelegateArgs {
3399
3984
  max_concurrent?: number;
3400
3985
  /** `goal` 简写形式的角色名 */
3401
3986
  role?: string;
3987
+ /**
3988
+ * `true` = 派到后台立刻返回(作业 id 形如 `a1`)。缺省 `false` = 同步等,
3989
+ * 与改造前逐字节相同。判据与取舍见文件头「两种派法」那一节。
3990
+ */
3991
+ background?: boolean;
3402
3992
  }
3403
3993
  /** 委托结果 */
3404
3994
  interface DelegateTaskResult {
@@ -3413,9 +4003,23 @@ interface DelegateTaskResult {
3413
4003
  }>;
3414
4004
  total_duration: number;
3415
4005
  }
4006
+ /** `delegate()` 的返回值。后台那一档多两个字段,同步那一档不受影响 */
4007
+ interface DelegateOutcome {
4008
+ success: boolean;
4009
+ output: string;
4010
+ duration: number;
4011
+ /** 只有后台派活才有 —— 模型拿它去 `task_output` / `task_wait` / `task_stop` */
4012
+ jobId?: string;
4013
+ /** 父回合是不是被放走了(后台那一档)。**不是**「有没有作业」 */
4014
+ background?: boolean;
4015
+ }
3416
4016
  /**
3417
4017
  * 子 Agent 被禁止的工具 [Hermes]。
3418
4018
  *
4019
+ * ⚠️ `send_message` **不在**这个名单里(2026-10-30 起,阶段 1 的父子单通道)。
4020
+ * 它曾经在(继承自 Hermes 的名单),但那时候全仓也没有这个工具的实现 ——
4021
+ * 那是一个**预防性的封禁**,不是做过又删掉的东西。放行的判据写在下面那一条。
4022
+ *
3419
4023
  * plan 模式那两个(方案 35)在这里不是「子任务用不上」,是**会串台**:
3420
4024
  * 子 agent 和父 agent 共用同一个 `PermissionManager`(见 build.ts 的 runner),
3421
4025
  * 子 agent 调一次 `enter_plan_mode` 就把**父会话**的权限级别压成了只读,
@@ -3426,7 +4030,21 @@ declare const BLOCKED_TOOLS: readonly string[];
3426
4030
  interface DelegateConfig {
3427
4031
  max_concurrent_children: number;
3428
4032
  max_spawn_depth: number;
4033
+ /**
4034
+ * 一个子任务最多跑多久(毫秒)。**`0` = 不限时**。
4035
+ *
4036
+ * 真源是 `config.delegate.taskTimeoutSec`(秒),在 `build.ts` 里折成毫秒;
4037
+ * 构造函数里那个 `900_000` 只给不经过配置的调用方(用例、嵌入宿主)。
4038
+ */
3429
4039
  task_timeout: number;
4040
+ /**
4041
+ * 成员表(阶段 1)。给了才登记成员、才能被 `send_message` 寻址到。
4042
+ *
4043
+ * 不给 = 行为和改造前逐字节相同(用例、嵌入宿主不必为了跑一次子任务去建表)。
4044
+ * 它和上面两个数是不同类别的东西(一个是实例、两个是数量),但都从同一个
4045
+ * `createDelegateTool` 的配置面进来 —— 拆成两个参数得多改一打调用方。
4046
+ */
4047
+ team?: TeamRegistry;
3430
4048
  }
3431
4049
  declare class DelegateManager {
3432
4050
  private config;
@@ -3438,29 +4056,43 @@ declare class DelegateManager {
3438
4056
  *
3439
4057
  * @param parentSessionId 派活的那个会话(`ToolContext.sessionId`)。一路带到
3440
4058
  * `runner` 上,让子任务的花费和文件快照记到**它**头上而不是装配时那个会话
4059
+ * @param signal 父回合的终止信号。**只有同步那一档会挂到子任务上** ——
4060
+ * 后台派活的判据见文件头「两种派法」
3441
4061
  */
3442
- delegate(args: DelegateArgs, parentSessionId: string, signal?: AbortLike): Promise<{
3443
- success: boolean;
3444
- output: string;
3445
- duration: number;
3446
- }>;
4062
+ delegate(args: DelegateArgs, parentSessionId: string, signal?: AbortLike): Promise<DelegateOutcome>;
4063
+ /**
4064
+ * 把这一批登记成一格 `agent` 作业。
4065
+ *
4066
+ * 归**派活的那个会话**(不是子任务自己那个 `delegate-xxxx`):`task_*` 那道
4067
+ * 按会话分区的墙靠这一格 —— 记到子任务名下的话,父会话一结束就谁都看不见、
4068
+ * 也停不掉了。
4069
+ */
4070
+ private registerJob;
3447
4071
  private normalizeTasks;
4072
+ /**
4073
+ * 分批跑,每批最多 `maxConcurrent` 个。
4074
+ *
4075
+ * @param signal **整批**那个控制器的信号(不是父回合的)—— 每一格再各自
4076
+ * 派生一个,判据在 {@link runSingleTask} 上
4077
+ * @param onDone 每收一格回调一次。后台那一档用它往作业缓冲里写进度;
4078
+ * 同步那一档不传(写进去也没有人读 —— 那一档没有作业)
4079
+ */
3448
4080
  private runWithConcurrency;
3449
4081
  /**
3450
4082
  * 跑一个子任务。
3451
4083
  *
3452
4084
  * ## 每个子任务一个自己的 controller(2026-09-24)
3453
4085
  *
3454
- * 父回合的终止和这一格的超时**都打到它上面**,子 agent 拿到的是它的 signal。
3455
- * 原来两样都没有接:`sub.run()` 不带 signal,于是父会话按了终止,父回合要干等
3456
- * 所有子任务跑完(每个最多 `task_timeout`,一批一批串着等);而超时只是
3457
- * `Promise.race` 抢答 —— 父回合往下走了,子 agent 在后台接着跑、接着花钱,
3458
- * 屏幕上一个字都看不见。
4086
+ * 进来的 `signal` 是**整批**那个控制器的(`delegate()` 建的),每格再链一个
4087
+ * 自己的:这一格的超时只掐它自己,而 `task_stop` / 父回合终止打的是整批那一个,
4088
+ * 于是两件事都成立 —— 超时不会误伤兄弟,收批也不会漏掉哪一格。
3459
4089
  *
3460
- * 父 signal 是鸭子类型(`ToolContext.signal` 只保证有 `aborted`)时接不上监听,
3461
- * 只在开跑前判一次 —— 真宿主递下来的都是真 `AbortSignal`。
4090
+ * 父回合的终止是不是真的会打下来,取决于派法:同步那一档 `delegate()` 把
4091
+ * 父 signal 链到整批控制器上,后台那一档**不链**(判据在文件头)。
3462
4092
  */
3463
4093
  private runSingleTask;
4094
+ /** 进度那一行里的「谁、干了什么、多久」—— 和汇总标题同一套写法 */
4095
+ private describeResult;
3464
4096
  private buildSummary;
3465
4097
  }
3466
4098
 
@@ -3496,18 +4128,60 @@ interface SubAgentRunner {
3496
4128
  * 实现方要把它交给子 agent 的 `run()` —— 不交的话按了终止它照跑,判据在
3497
4129
  * `DelegateManager.runSingleTask` 上。可选只是为了不拆已有的调用方
3498
4130
  */
3499
- signal?: AbortSignal): Promise<{
4131
+ signal?: AbortSignal,
4132
+ /**
4133
+ * 子任务跑的过程中每完成一步回调一次(2026-10-27)。
4134
+ *
4135
+ * 收的是一行已经拼好的人话(`读文件 `src/a.ts``),不是事件对象 ——
4136
+ * 两个调用方(作业缓冲 / 未来的其他通道)要的是同一句话,而把事件形状
4137
+ * 漏上去等于让每个调用方重写一遍折行逻辑。
4138
+ *
4139
+ * **同步那一档目前不给**:父回合正卡在这个工具调用上,没有地方显示它。
4140
+ * 后台那一档接了 —— 那里的 `task_output` 在此之前只能看到「还没跑完」。
4141
+ */
4142
+ onProgress?: (line: string) => void,
4143
+ /**
4144
+ * 这一次是不是**接着一个已有的子会话**跑(阶段 2 的 `followup_task`)。
4145
+ *
4146
+ * ⚠️ 收一个对象而不是第 8 个位置参数:这个接口已经有七格了,再加一格
4147
+ * 位置参数之后,每处调用都变成一串「第四个是 role、第六个是 signal」的
4148
+ * 排列谜题。而对象只增不减,下一个开关加进去也不用改签名。
4149
+ *
4150
+ * 实现方看到 `resume` 为真时应该把 `sessionId` 那段**已有历史**装进去
4151
+ * (`session-store.loadHistory()`)—— 不装的话第二次跑就是一段全新的对话,
4152
+ * 而「再跑一轮」的全部意义就是它还记得上一次。
4153
+ */
4154
+ runOptions?: SubAgentRunOptions): Promise<{
3500
4155
  text: string;
3501
4156
  error?: string;
3502
4157
  }>;
3503
4158
  }
4159
+ /** {@link SubAgentRunner} 第 8 个参数 */
4160
+ interface SubAgentRunOptions {
4161
+ /**
4162
+ * 接着这个子会话的**已有历史**跑。
4163
+ *
4164
+ * 缺省(`undefined` / `false`)= 这是一次全新的派活,`sessionId` 那一格
4165
+ * 是个刚生成的 id,库里没有它那一行。
4166
+ */
4167
+ resume?: boolean;
4168
+ }
3504
4169
  /**
3505
4170
  * @param roles 可派的角色表(方案 18)。**函数而不是数组** —— 角色表理论上
3506
4171
  * 可以在运行期变,而工具描述是每次 `list()` 时读的,给数组就把它钉死在
3507
4172
  * 装配那一刻。这跟 `toolProvider` 是同一条规矩(方案 15)。
3508
4173
  * 不给 = 没有角色系统,`role` 入参一律报错,其余行为与改造前一致。
3509
4174
  */
3510
- declare function createDelegateTool(runner: SubAgentRunner, config?: Partial<DelegateConfig>, roles?: () => readonly AgentRole[]): EpochTool;
4175
+ declare function createDelegateTool(runner: SubAgentRunner, config?: Partial<DelegateConfig>, roles?: () => readonly AgentRole[],
4176
+ /**
4177
+ * 拿到这里面的那个 manager(阶段 2 的 `spawn_teammate` 要复用同一个)。
4178
+ *
4179
+ * 为什么要它:`spawn_teammate` 和 `delegate_task` 走的是**同一套派活逻辑**
4180
+ * (并发、超时、成员登记、收尾),差只差在参数。各建一个 manager 也能跑,
4181
+ * 但那样两边会给同一个 team 建两份互不相干的调度状态 —— 而那正是将来
4182
+ * 「为什么超时时间不一样」这类问题的温床
4183
+ */
4184
+ onManager?: (manager: DelegateManager) => void): EpochTool;
3511
4185
 
3512
4186
  /**
3513
4187
  * goal 的类型([方案 52](../../../../docs/verify/VERIFY_RECORD-52-goal.md))——
@@ -4019,13 +4693,20 @@ declare function createMemoryTool(memory: MemoryManager$1): EpochTool;
4019
4693
  *
4020
4694
  * ⚠️ **这一段 2026-08-28 之前写的是「`run_code` 的边界在权限管线,不在进程隔离」,
4021
4695
  * 那句话被 PR-3 判为假**(程序里 `import('node:fs')` 直通,权限管线整个被绕过)。
4022
- * 今天的口径是「一次 `run_code` = 一次任意本机代码执行,边界是审批那一下」,
4696
+ * 那时改成了「一次 `run_code` = 一次任意本机代码执行,边界是审批那一下」,
4023
4697
  * 全文在 [PR-3 记录 §二](../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr3.md)。
4024
4698
  *
4025
- * **但这个文件的立意不受影响**,而这一点值得说清:它跑在我们自己的进程里的一个
4026
- * worker 线程上,所以这四道上限守的是**「别把宿主拖死」**(死循环、吃内存、
4027
- * 一个写错的 `for` 调一万次工具),不是「别越权」。越权那一半今天由审批兜着。
4028
- * 也就是说下面这张表**仍然成立**,只是它列的不再是「多出来的全部能力」:
4699
+ * **2026-10-27 又收窄了一层**:`runner.ts` 给 worker 挂了 `--permission` +
4700
+ * 清空的 env(判据在 [policy.ts](./policy.ts) 的文件头)。所以今天的口径是
4701
+ * 「审批是边界,权限策略把程序限制在工作目录内、起不了子进程、看不见 env」。
4702
+ * ⚠️ **但下面的表仍然成立,它守的东西也没变** —— 这个文件管的是「别把宿主拖死」,
4703
+ * 不是「别越权」。权限模型不管死循环、不管吃内存、更不管
4704
+ * 「一个写错的 `for` 调一万次工具」,而这四格里没有一格能被 `--permission` 代替。
4705
+ *
4706
+ * 它跑在我们自己的进程里的一个 worker 线程上,所以这四道上限守的是
4707
+ * **「别把宿主拖死」**(死循环、吃内存、一个写错的 `for` 调一万次工具),
4708
+ * 不是「别越权」。越权那一半今天由审批 + 权限策略两层兜着 ——
4709
+ * 而下面这张表列的是**权限策略一格都盖不到**的那部分:
4029
4710
  *
4030
4711
  * | 多出来的能力 | 这里的哪一格 |
4031
4712
  * | --------------------------------- | ----------------------------------------- |
@@ -4114,14 +4795,14 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4114
4795
  * | ------------ | ---------------------------------------------- | ---------------------------------------- |
4115
4796
  * | 干什么 | 算东西、处理数据 | **编排工具调用** |
4116
4797
  * | 能不能碰宿主 | **不能** —— 写不出沙箱、默认断网、读不到 `$HOME` | **必须能** —— 它的全部意义就是回调工具 |
4117
- * | 隔离级别 | macOS 上 OS 强制(Seatbelt) | **一点都没有**,跑在我们自己的 worker 线程里 |
4118
- * | 安全模型 | 沙箱是边界 | **审批是边界**(见下一节,2026-08-28 拍板)|
4798
+ * | 隔离级别 | macOS 上 OS 强制(Seatbelt) | worker 线程 + Node 权限模型(`code-mode/policy.ts`) |
4799
+ * | 安全模型 | 沙箱是边界 | **审批是边界**;权限策略只收窄「程序能碰什么」 |
4119
4800
  *
4120
4801
  * 所以 `sandbox/sandbox.ts` 的那整套设计(沙箱目录在 tmpdir、`~/.epoch` 在读黑名单
4121
4802
  * 里)对这个工具**一条都不适用** —— 一个连 `~/.epoch` 都读不到的程序怎么调
4122
4803
  * `file_read`。**别把两个工具的措辞「统一」**,它们说的是两条不同的路。
4123
4804
  *
4124
- * ## ⚠️ 它的边界是**审批**,不是隔离(2026-08-28 拍板,方案 51 PR-3)
4805
+ * ## ⚠️ 边界是**审批**;权限策略只是让那次审批的语义变准
4125
4806
  *
4126
4807
  * 这一段 2026-08-28 之前写的是「一个 `run_code` 程序能做的事**逐字等于**模型
4127
4808
  * 一个个调那些工具能做的事」。**那句话是假的**,而且是被实测推翻的:程序跑在
@@ -4132,16 +4813,36 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4132
4813
  * [PR-4 记录](../../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr4.md) §三 第 3 / 4 条,
4133
4814
  * 那不是理论上的洞,是模型自己找到并用上的路。
4134
4815
  *
4135
- * PR-3 **判定不做**引擎隔离(三个选项和判据在
4816
+ * PR-3 那轮**判定不做**引擎隔离(三个选项和判据在
4136
4817
  * [PR-3 记录](../../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr3.md) §二),
4137
4818
  * 改成把边界说准:
4138
4819
  *
4139
4820
  * > **一次 `run_code` = 一次任意本机代码执行,和 `terminal` 同一档。
4140
4821
  * > 边界是审批那一下 —— 用户看到程序全文,批的是「这段程序可以跑」。**
4141
4822
  *
4142
- * 于是这个工具的 `operation: 'command'` 和 `describeTarget` = 程序全文
4143
- * **从实现细节升级成了承重墙**,各有一条会红的用例钉着(`tool-executor.test.ts`
4144
- * 的「run_code 的审批对象是程序全文」、tui 的「多行 / 超宽 target」)。
4823
+ * ## 2026-10-27:那句话里「一点隔离都没有」的一半补上了
4824
+ *
4825
+ * PR-3 当时只有两个选项:preload 拦 `node:fs`(实测无效,`Module._load` 只盖 CJS),
4826
+ * 或者上 OS 隔离(要改成子进程,动整个进程模型)。两个都不成立,所以「不做」
4827
+ * 在那时是对的。
4828
+ *
4829
+ * 第三条路是 worker 自己的 `execArgv` 挂 `--permission`:进程模型一个字不动,
4830
+ * 策略只作用在那一个线程上。实测数据和它**没**拦住什么(网络、env)全部在
4831
+ * [policy.ts](../../code-mode/policy.ts) 的文件头。
4832
+ *
4833
+ * 于是口径变成两句:
4834
+ *
4835
+ * > **审批仍然是边界**;权限策略把「一次任意本机代码执行」收窄成
4836
+ * > 「工作目录内读写、起不了子进程、看不见环境变量」。
4837
+ *
4838
+ * 剩下那个没被收窄的是**网络**,而这里不假装它变了:权限模型不管网络,
4839
+ * 所以一段恶意程序照样能把读到的内容发出去。想在 `bypass` 档 / headless
4840
+ * 预授权下开着它,判断照旧同 `docs/TOOLS.md` 那句 —— 「那一眼是唯一的边界」。
4841
+ *
4842
+ * `operation: 'command'` 和 `describeTarget` = 程序全文仍然是承重墙,
4843
+ * 各有一条会红的用例钉着(`tool-executor.test.ts` 的「run_code 的审批对象是程序全文」、
4844
+ * tui 的「多行 / 超宽 target」)。加权限策略**不改**这一格:策略算错时
4845
+ * 程序起不来,而审批框里看到的仍然是全文。
4145
4846
  * 落进 git 的 `permissions.allow` 里也一条 `run_code` 规则都不许有
4146
4847
  * (`permission/rule-validate.ts` 的 `NEVER_ALLOW_TOOL_NAMES`)。
4147
4848
  *
@@ -4184,6 +4885,9 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4184
4885
  * 断网要么靠子进程 + 沙箱(PR-3 记录 §二 选项 C,重开条件在 §五末尾),要么不做。
4185
4886
  * 原来的理由是「它想联网就调 `web_fetch`,那条路有 SSRF 三层防护」——
4186
4887
  * 那句话仍然成立,只是它**描述不了今天的现状**。
4888
+ * ⚠️ **2026-10-27 加权限策略之后再确认一遍:它管不了网络。** 实测带着
4889
+ * `--permission` 跑,`fetch` 照样通 —— 权限模型只管文件系统、子进程、worker、
4890
+ * 原生插件这四类。别把「加了策略」读成「断网了」。
4187
4891
  *
4188
4892
  * ## ⚠️ 三条边界,别在读这个文件时以为方案已经全落了
4189
4893
  *
@@ -4761,8 +5465,6 @@ type ScheduleIssueCode =
4761
5465
  | 'permission-unknown'
4762
5466
  /** 工作目录不存在 */
4763
5467
  | 'work-dir-missing'
4764
- /** `maxBudgetUsd` 必填且必须为正。**这是刻意的摩擦**,见协议上那段 */
4765
- | 'budget-missing'
4766
5468
  /** `maxTurns` 必须是正整数 */
4767
5469
  | 'max-turns-invalid'
4768
5470
  /** 墙钟超时必须是正数 */
@@ -4793,18 +5495,18 @@ type ScheduleIssueCode =
4793
5495
  /** 清单里有一条规则写错了(`detail` 是 `rule-validate` 给的位置与原因) */
4794
5496
  | 'rule-invalid'
4795
5497
  /**
4796
- * 「跳过全部检查」+ 空工作区。
5498
+ * 「完全访问」+ 空工作区。
4797
5499
  *
4798
5500
  * ⚠️ **2026-09-18 起没有产出点了** —— 空着落到的是 `~/.epoch/automation/<id>/`,
4799
5501
  * 一个确定、专属、隔离的目录,不是「不确定的 cwd」。判据全文在
4800
5502
  * `validate.ts` 的 `bypassIssues()` 第 1 条上。保留这条 code 是为了不破坏协议。
4801
5503
  */
4802
5504
  | 'bypass-needs-workdir'
4803
- /** 「跳过全部检查」的工作区指到了家目录本身 —— 那和不绑定没有区别 */
5505
+ /** 「完全访问」的工作区指到了家目录本身 —— 那和不绑定没有区别 */
4804
5506
  | 'bypass-workdir-too-broad'
4805
- /** 「跳过全部检查」缺三把尺子中的某一把 */
5507
+ /** 「完全访问」缺尺子 —— 今天只剩超时那一把 */
4806
5508
  | 'bypass-needs-limits'
4807
- /** 「跳过全部检查」没有拿到那次二次确认 */
5509
+ /** 「完全访问」没有拿到那次二次确认 */
4808
5510
  | 'bypass-not-confirmed';
4809
5511
  /** 全集。CLI 侧靠它穷举文案,少一条编译不过 */
4810
5512
  declare const SCHEDULE_ISSUE_CODES: readonly ScheduleIssueCode[];
@@ -4843,7 +5545,6 @@ interface ScheduleImportInput {
4843
5545
  allowOperations: readonly OperationType[];
4844
5546
  allowRules: readonly string[];
4845
5547
  maxTurns: number;
4846
- maxBudgetUsd: number;
4847
5548
  timeoutMs: number;
4848
5549
  showInSessionList?: boolean;
4849
5550
  trigger: ScheduleTrigger;
@@ -4874,8 +5575,6 @@ interface CreateScheduleInput {
4874
5575
  allowOperations?: readonly OperationType[];
4875
5576
  allowRules?: readonly string[];
4876
5577
  maxTurns?: number;
4877
- /** **必填**,没有默认值 */
4878
- maxBudgetUsd: number;
4879
5578
  timeoutMs?: number;
4880
5579
  /**
4881
5580
  * 跑完的会话直接进会话列表吗。缺省 `false` = 收起来(归档),
@@ -4922,8 +5621,7 @@ interface CreateScheduleInput {
4922
5621
  * `ScheduleControl.update` 解析掉;**`ScheduleStore.update` 的入参不收它**
4923
5622
  * (同 `create(input & { workDir: string })` 那条契约,收窄在编译期)。
4924
5623
  */
4925
- type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'maxBudgetUsd' | 'workDir' | 'model' | 'role' | 'startDate' | 'endDate'>> & {
4926
- maxBudgetUsd?: number;
5624
+ type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'workDir' | 'model' | 'role' | 'startDate' | 'endDate'>> & {
4927
5625
  /** `null` = 不绑工作区了 → 落回 `~/.epoch/automation/<id>/`,**不是**删掉这一格 */
4928
5626
  workDir?: string | null;
4929
5627
  model?: string | null;
@@ -4933,74 +5631,57 @@ type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'maxBudgetUsd' | 'w
4933
5631
  endDate?: string | null;
4934
5632
  };
4935
5633
  /**
4936
- * 三把尺子的缺省值 —— **「调 API / CLI 没给这一格时引擎用什么」**。
5634
+ * 两把尺子的缺省值 —— **「调 API / CLI 没给这一格时引擎用什么」**。
4937
5635
  *
4938
- * ## ⚠️ `maxBudgetUsd` **不在这里**,而 2026-09-18 之后这句话更要紧了
5636
+ * ## ⚠️ 2026-10-29:只剩轮次和超时(预算那一把整个撤了)
4939
5637
  *
4940
- * 它没有引擎兜底:没给就是一条 `budget-required`(网线上 400,CLI 上必给 `--budget`)。
5638
+ * 这里原来还挂着一段「预算不在这份缺省里,因为一个假默认会让『一觉醒来 40
5639
+ * 美元』变成我们的错」。那次撤掉的不只是那一段注释,而是**那一格本身**:
5640
+ * `maxBudgetUsd` 已经从契约、库、网线、CLI、表单上全部退场,判据在
5641
+ * `validate.ts` 的 `bypassIssues()` 上。
4941
5642
  *
4942
- * 那一天新建**表单**上它开始预填 200 了,而那是**另一份缺省**
4943
- * (protocol 的 `WireScheduleDefaults.maxBudgetUsd`,判据全文在那个接口上):
4944
- * 一份说「表单打开时格子里印什么」,一份说「没给值时引擎用什么」。
4945
- * 把 200 抄进这一份的表现是**网线那一道闸整个消失** —— 任何一个不经表单的调用方
4946
- * 从此都能建出一条没人想过预算的任务,而那正是这一格的缺席拦着的事。
5643
+ * 于是这一份和界面预填那一份(protocol 的 `WireScheduleDefaults`)**不再有
5644
+ * 任何区别** —— 两份都只是「轮次 + 超时」。
4947
5645
  */
4948
5646
  declare const SCHEDULE_DEFAULTS: {
4949
5647
  /**
4950
- * 和交互缺省**同一个常量**(2026-09-04,原来这儿是自己的一个 `20`)。
5648
+ * 轮次上限。**`0` = 不限**(`UNLIMITED_TURNS`)。
5649
+ *
5650
+ * ## 为什么缺省是「不限」,而不是一个保守的数
4951
5651
  *
4952
- * 那个 20 的理由写的是「无人看着的一轮不该比有人看着的更长」。它不成立了 ——
4953
- * 它当初拦的是「跑飞了没人按 Esc」,而这一层真正拦住那件事的是 `maxBudgetUsd`:
4954
- * 它在这套功能里**必填**(所以它不在这份缺省里),而钱是比轮次准的尺子。
4955
- * 用轮次替它把无人值守的活压到 20,拦掉的绝大多数不是跑飞,是「这个任务本来
4956
- * 就要 60 轮」—— 而那种失败要等到第二天 9 点才看得见(同下面
5652
+ * 这一格原来是自己的一个 `20`,理由写的是「无人看着的一轮不该比有人看着的
5653
+ * 更长」。它不成立了 —— 它当初拦的是「跑飞了没人按 Esc」,而用轮次替它把
5654
+ * 无人值守的活压到 20,拦掉的绝大多数不是跑飞,是「这个任务本来就要 60 轮」
5655
+ * —— 而那种失败要等到第二天 9 点才看得见(同下面
4957
5656
  * `SCHEDULE_DEFAULT_ALLOWLIST` 那节的判据)。
4958
5657
  *
4959
5658
  * ⚠️ 这一份是**没有配置在手时的兜底**。真正拿着用户那格 `maxTurns` 的地方是
4960
5659
  * `server/src/schedule/handlers.ts` 的 `defaultsOf()` —— 表单预填走它,
4961
5660
  * 所以用户把设置里那个数改成 500,新建任务那一格就预填 500。
4962
5661
  *
4963
- * ⚠️ **2026-09-06 起这个缺省是「不设上限」**(`UNLIMITED_TURNS`),因为上面那条
4964
- * 判据跟着交互缺省一起走到了头:既然轮次拦掉的绝大多数不是跑飞,那把它当无人
4965
- * 值守的兜底闸门就是**拿错了尺子**。这条路上兜住跑飞的仍然是 `maxBudgetUsd`
4966
- * (必填,所以它不在这份缺省里)和 `timeoutMs`。要一道轮次闸门的人在新建任务
5662
+ * ⚠️ **2026-09-06 起这个缺省是「不设上限」**(`UNLIMITED_TURNS`),判据同上。
5663
+ * 🔴 **2026-10-29 起这条路真正兜得住「跑飞」的只剩下面那一个 `timeoutMs`**
5664
+ * —— 预算那把尺子没了,而这一格缺省是 0。要一道轮次闸门的人在新建任务
4967
5665
  * 那一格自己填一个数。
4968
5666
  */
4969
5667
  readonly maxTurns: 0;
4970
5668
  /**
4971
- * 墙钟超时。唯一能拦住「一条命令挂住了」的那把尺子 —— 前两把在那种情形下
4972
- * 都不增长(轮次不动、花费不动)。
5669
+ * 墙钟超时。**无人值守路上唯一兜得住「跑飞」的那把尺子**(2026-10-29 起)。
5670
+ *
5671
+ * 它要拦两种前一把拦不住的东西:一条 `terminal` 命令挂住了(那种情况下轮次
5672
+ * 不增长),以及一个每轮都很便宜、但一轮接一轮跑下去的循环。
4973
5673
  *
4974
5674
  * ⚠️ **2026-09-18 从 15 分钟抬到 60 分钟**(用户定的)。15 分钟那条线当初是
4975
5675
  * 照着「一次巡检」估的,而它砍掉的不是跑飞,是**本来就要跑久的那种任务**:
4976
5676
  * 一条被这把尺子掐断的运行和跑飞长得一模一样(都是 `timeout`),
4977
5677
  * 而定时任务的反馈回路按天算 —— 第二天早上看到的只有「超时了」三个字。
4978
5678
  *
4979
- * 兜住真正跑飞的是必填的 `maxBudgetUsd`(钱是比时间准的尺子),
4980
- * 判据同上面 `maxTurns` 那一段。
4981
- *
4982
5679
  * ⚠️ 这个数**两条路共用**(CLI 不给 `--timeout`、表单预填,都读它)——
4983
5680
  * 在服务端那侧另写一份的表现,`runtime/schedule/control.ts` 那段注释记着:
4984
5681
  * 「表单预填 200、直接调这个 API 却得到 20」。
4985
5682
  */
4986
5683
  readonly timeoutMs: number;
4987
5684
  };
4988
- /**
4989
- * 新建**表单**里预算那一格印什么(2026-09-18,用户定的 200 美元)。
4990
- *
4991
- * ## ⚠️ 它**刻意不在** {@link SCHEDULE_DEFAULTS} 里,这一条是承重的
4992
- *
4993
- * 那一份答的是「调 API / CLI 没给这一格时引擎用什么」,而这一格**没有**那种兜底:
4994
- * 网线上没给预算就是 400(`budget-required`),CLI 上 `--budget` 照旧必给。
4995
- *
4996
- * 这一份答的是另一个问题:「表单打开时那个格子里印着什么」。两者混成一份的表现是
4997
- * 网线那道闸整个消失 —— 任何一个不经表单的调用方从此都能建出一条没人想过预算的
4998
- * 任务,而定时任务是没人看着的(一觉醒来那笔账已经花完了)。
4999
- *
5000
- * 判据全文、以及这个数撤掉了方案 45 §3.5 的哪一半,写在 protocol 的
5001
- * `WireScheduleDefaults` 上。
5002
- */
5003
- declare const SCHEDULE_FORM_BUDGET_USD = 200;
5004
5685
  /**
5005
5686
  * 新建任务时预填的最小清单(方案 45 §3.2 的落点)。
5006
5687
  *
@@ -5326,8 +6007,10 @@ declare class ScheduleRegistrar {
5326
6007
  *
5327
6008
  * 1. `validateSchedule()` 照旧跑,红了就**一个字都不写**(半条任务比没有更糟);
5328
6009
  * 2. 「无人值守下跑不出结果」的那两族措辞照旧**拒收**(`unattendedIssues`);
5329
- * 3. `bypass` 档照旧要 `SCHEDULE_FORM_BUDGET_USD` 的预算和正的超时
5330
- * (`bypassIssues` 的 `bypass-needs-limits`),而工作目录不许宽到等于没绑。
6010
+ * 3. `bypass` 档照旧要**正的超时**(`bypassIssues` 的 `bypass-needs-limits`),
6011
+ * 而工作目录不许宽到等于没绑。
6012
+ * ⚠️ **2026-10-29 起这条尺子只剩超时**:那一刻那条第 2 条里的「三把尺子」
6013
+ * 改成了「超时 + 轮次(而轮次缺省不限)」—— 判据在 `validate.ts` 上。
5331
6014
  *
5332
6015
  * ## ⚠️ 有一档仍然降级成提案:`plan`(只读调研模式)
5333
6016
  *
@@ -5410,6 +6093,219 @@ interface ScheduleDraftDeps {
5410
6093
  }
5411
6094
  declare function createScheduleDraftTool(deps: ScheduleDraftDeps): EpochTool;
5412
6095
 
6096
+ /**
6097
+ * `send_message` —— agent 之间发一条消息(阶段 1:父子单通道)。
6098
+ *
6099
+ * ## 它和 `delegate_task` 不是一回事
6100
+ *
6101
+ * - `delegate_task` 是**派活**:开一个新成员、给它一段 goal、等它交结论
6102
+ * - 这个工具是**对已经在跑的人说一句**:不新开成员、不等结论、不改变谁在干活
6103
+ *
6104
+ * 所以它不能替代 `delegate_task`,也不该被用来「再派一个」。模型手上两个都有时
6105
+ * 最容易犯的错是拿它当派活用 —— 描述里那段「什么时候用它」就是为这个写的。
6106
+ *
6107
+ * ## 投递语义(两个方向不对称,这是刻意的)
6108
+ *
6109
+ * - **发给一个 running 成员**:进它的收件箱,引擎在**下一个步骤边界**取走
6110
+ * (`loop.ts:723` 的 `steer.take()`)。它当轮就会看到,不用等这一轮跑完
6111
+ * - **发给 `lead`**:进主会话的收件箱,下一轮 `TaskNoticeTracker` 在请求前
6112
+ * 拼成一条 note(`loop.ts:1367`)。**父会话没在跑时它待在那儿不动**
6113
+ * - **发给已经收工的成员**:直接失败。收进一个永远不会有下一轮读它的箱子
6114
+ * 等于给调用方一个假成功 —— 父 agent 会照着它继续等回音
6115
+ *
6116
+ * 第三条是 codex `api.rs:35-36` 那条规矩的本地版:通知**不唤醒**已终结的 agent。
6117
+ * 阶段 2 有了 `followup_task`,唤醒才会变成一条显式的路。
6118
+ */
6119
+
6120
+ /** 工具名。`BLOCKED_TOOLS` 那边按这个名字放行,别各写各的 */
6121
+ declare const SEND_MESSAGE_TOOL = "send_message";
6122
+ declare function createSendMessageTool(team: TeamRegistry): EpochTool;
6123
+
6124
+ /**
6125
+ * 讨论板(阶段 3)—— **跨进程**共享的一块板。
6126
+ *
6127
+ * ## 为什么它和 roster / 任务板不是一类东西
6128
+ *
6129
+ * `registry.ts` 和 `tasks.ts` 都是内存里的:那两样的读者是**同一个进程里的
6130
+ * 那几个成员**,别的进程既不该看见也不该写。而板子存在的**全部理由**就是
6131
+ * 「我开着两个 epoch 窗口,它们看到的是同一块板」。
6132
+ *
6133
+ * 所以它走 SQLite,并且挂在 `~/.epoch/sessions.db` 这个**本来就多进程共享**的
6134
+ * 文件上(判据:`session/db.ts:1193`、`fts-health.ts:297` 都写着「两个进程打开
6135
+ * 同一个 sessions.db」),用 `migrate()` 的 namespace 隔离,不另开一个文件。
6136
+ * 另开文件的话 `openDatabase` 的 refCount 要自己再管一份,而那边已经有一套了。
6137
+ *
6138
+ * ## 通知是**拉取式**的,不唤醒任何人
6139
+ *
6140
+ * 照 codex `api.rs:35-36` 那条规矩:通知不唤醒已终结的 agent。这里更彻底 ——
6141
+ * 板子自己**不投递任何东西**,它只记「每个订阅者看到哪一条了」(`last_seen_seq`)。
6142
+ * 谁想知道有没有新帖,谁自己来 `pendingFor()` 拉。
6143
+ *
6144
+ * 这样做的代价是「发帖的人不知道对方看没看」,而收益是不用维护投递队列、
6145
+ * 也不会有「消息发往一个早就退出的进程」那种没法处理的中间态。
6146
+ *
6147
+ * ## `append` 和 `post` 的差别
6148
+ *
6149
+ * - `post`:发帖,**会自动订阅作者并通知订阅者**
6150
+ * - `append`:静默写入,不订阅、不通知(贴证据、记流水用它)
6151
+ *
6152
+ * 这是 codex 的同一条分法(`PostBehavior::Discussion` / `SilentAppend`)。
6153
+ */
6154
+
6155
+ /** 单条帖子的字符上限。超了截断并明说 —— 不静默丢 */
6156
+ declare const MAX_POST_CHARS = 20000;
6157
+ /** 订阅目标:`channel:<名字>` 或 `thread:<id>` */
6158
+ type BoardTarget = {
6159
+ kind: 'channel';
6160
+ name: string;
6161
+ } | {
6162
+ kind: 'thread';
6163
+ id: string;
6164
+ };
6165
+ /** 一条「你有新帖」的摘要。**不带正文** —— 要看得自己去 read_thread */
6166
+ interface BoardPending {
6167
+ target: string;
6168
+ count: number;
6169
+ }
6170
+ declare class BoardStore {
6171
+ private readonly path;
6172
+ private readonly db;
6173
+ constructor(homeDir: string);
6174
+ /** 归还连接(引用计数 -1)。宿主 dispose 时调 */
6175
+ close(): void;
6176
+ createChannel(args: {
6177
+ name: string;
6178
+ description?: string;
6179
+ by: string;
6180
+ }): BoardChannel;
6181
+ /** 只改描述,**不碰帖子和订阅**。不存在的频道会被建出来(判据同 codex 的 upsert) */
6182
+ upsertChannel(args: {
6183
+ name: string;
6184
+ description: string;
6185
+ by: string;
6186
+ }): BoardChannel;
6187
+ channel(name: string): BoardChannel | undefined;
6188
+ listChannels(): BoardChannel[];
6189
+ listThreads(channel: string, limit?: number): BoardThread[];
6190
+ /**
6191
+ * 发帖。
6192
+ *
6193
+ * - 目标是**频道**:开一个新线程,这条是 root post,标题取正文第一行
6194
+ * - 目标是**线程**:回复进去
6195
+ *
6196
+ * 无论哪种,作者都会被**自动订阅**(判据同 codex:`Posting subscribes its author`),
6197
+ * 且不会收到自己这条的通知(`last_seen_seq` 直接推到它自己这条)。
6198
+ */
6199
+ post(args: {
6200
+ target: BoardTarget;
6201
+ author: string;
6202
+ text: string;
6203
+ }): {
6204
+ post: BoardPost;
6205
+ threadId: string;
6206
+ };
6207
+ /** 静默追加:不订阅、不通知 */
6208
+ append(args: {
6209
+ target: BoardTarget;
6210
+ author: string;
6211
+ text: string;
6212
+ }): {
6213
+ post: BoardPost;
6214
+ threadId: string;
6215
+ };
6216
+ private write;
6217
+ readThread(threadId: string, limit?: number): BoardPost[];
6218
+ readPost(id: string): BoardPost | undefined;
6219
+ searchPosts(query: string, channel?: string, limit?: number): BoardPost[];
6220
+ /**
6221
+ * 订阅。`seenSeq` 给了就把游标推到那儿(作者发帖时用)。
6222
+ *
6223
+ * 重复订阅是**幂等**的:只更新游标(如果新游标更小就不动 —— 别让一次
6224
+ * 重订阅把读者没看的帖子吞掉)。
6225
+ */
6226
+ subscribe(agent: string, target: BoardTarget, seenSeq?: number): void;
6227
+ /** 全板最大的 seq(空板是 0) */
6228
+ private maxSeq;
6229
+ unsubscribe(agent: string, target: BoardTarget): boolean;
6230
+ /**
6231
+ * 这个 agent 订阅的格子里,**自它上次看之后**新增了多少条。
6232
+ *
6233
+ * 只给条数,不给正文 —— 要读自己去 `read_thread`。判据同 `waitForChange`:
6234
+ * 一个会过期的摘要比没有更坏。
6235
+ */
6236
+ pendingFor(agent: string): BoardPending[];
6237
+ /** 把游标推到「现在」(读过之后调)。不带 target = 全部 */
6238
+ markRead(agent: string, target?: BoardTarget): void;
6239
+ private countSince;
6240
+ }
6241
+
6242
+ /**
6243
+ * 讨论板的 11 个工具(阶段 3)。
6244
+ *
6245
+ * ## 寻址:`target` 一个参数两种形态
6246
+ *
6247
+ * - `th-xxxxxxxx` → 一个线程(回复进去)
6248
+ * - 其他 → 频道名(**开一个新线程**,这条是 root post)
6249
+ *
6250
+ * 不做 `channel:name` 那种前缀:列表里 `th-` 开头的 id 和频道名永远不会撞,
6251
+ * 而少一层语法就是少一处模型会写错的地方。
6252
+ *
6253
+ * ## `post` 和 `append` 的区别要写进描述
6254
+ *
6255
+ * 前者会**订阅作者并通知订阅者**,后者静默。模型拿 `append` 去「在讨论里说一句」
6256
+ * 的话,别人永远不知道有新内容 —— 那是最容易发生的一种误用,所以两边的描述
6257
+ * 都把这一点写在第一句。
6258
+ *
6259
+ * ## 板子是**跨进程**的,所以这里没有任何「当前进程内」的假设
6260
+ *
6261
+ * 别人的窗口发的帖,你这边 `read_thread` 读得到;反过来也一样。这也是这几个
6262
+ * 工具和 `send_message` / `team_task_*` 的根本差别 —— 那两组只在进程内。
6263
+ */
6264
+
6265
+ /**
6266
+ * @param store 板子。`undefined` = 这个进程没开起来(比如 homeDir 不可写),
6267
+ * 那时**一个工具都不注册** —— 装了但每次调用都报错,模型会以为那是它用错了
6268
+ */
6269
+ declare function createBoardTools(store: BoardStore, deps: {
6270
+ agentOf: (sessionId: string) => string | undefined;
6271
+ }): EpochTool[];
6272
+ /** 工具名清单(门禁和 `docs/TOOLS.md` 按它们对名字) */
6273
+ declare const BOARD_TOOL_NAMES: readonly string[];
6274
+
6275
+ /**
6276
+ * 阶段 2 的团队工具:派具名成员、看名册、等变化、打断,以及共享任务板。
6277
+ *
6278
+ * ## 八个工具为什么在一个文件里
6279
+ *
6280
+ * 它们共享**同一份状态**(`TeamRegistry`,任务板挂在它下面),而且语义上是一套:
6281
+ * 名册 + 消息 + 任务。拆成三个文件的话,`wait_agent` 那种「等任意一种变化」
6282
+ * 的工具会横跨两边,反而要 import 两处。
6283
+ *
6284
+ * ## 曝光:只 `list_agents` 是 Direct
6285
+ *
6286
+ * 计划里定的:20 个新工具默认 `Deferred`(走 `tool_search` 按需发现),
6287
+ * 只留最常用的直接出现。`list_agents` 是每次想跟谁说话之前都要看一眼的那个,
6288
+ * 放 Deferred 会让模型先 `tool_search` 一次再 `list_agents` —— 两步换一步。
6289
+ *
6290
+ * ## `spawn_teammate` 和 `delegate_task` 的区别(描述里必须说清)
6291
+ *
6292
+ * - `delegate_task`:一次性,派完等结论,跑完即止
6293
+ * - `spawn_teammate`:具名、后台、**跑着的时候能收到消息**
6294
+ *
6295
+ * 合并它们会让模型两头都判断错:拿 `delegate_task` 去派一个「需要中途纠正」的活,
6296
+ * 那个成员根本收不到消息(它一开始就没名字)。
6297
+ */
6298
+
6299
+ /**
6300
+ * 全部阶段 2 工具。
6301
+ *
6302
+ * 顺序不影响行为(注册表按名字去重),但 `list_agents` 排前面:
6303
+ * 它是 `ToolExposure.Direct` 的那一个,而其余是 Deferred。
6304
+ */
6305
+ declare function createTeamTools(team: TeamRegistry, manager: DelegateManager): EpochTool[];
6306
+ /** 工具名清单。`BLOCKED_TOOLS` 和门禁按它们对名字,别各写各的 */
6307
+ declare const TEAM_TOOL_NAMES: readonly string[];
6308
+
5413
6309
  /**
5414
6310
  * 「哪些历史会话,这一段会话能读」—— **全仓唯一一份判定**(方案 48 §四)。
5415
6311
  *
@@ -5563,12 +6459,24 @@ declare function resolveSessionVisibility(input: SessionVisibilityInput): Sessio
5563
6459
  * ## 为什么这值得一个工具
5564
6460
  *
5565
6461
  * `delegate_task` 派出去的子 agent 有**独立的 sessionId**,`fork()` 出来的会话
5566
- * 也有 `parent_id`。于是「刚才那个子 agent 到底干了什么」今天无从查起:
5567
- * 它的结论回来了(一段 summary),但它中间读了哪些文件、试过哪些路、
5568
- * 在哪一步拐弯,全都在另一段会话里 —— 而模型手上连那段会话的 id 都没有。
6462
+ * 也有 `parent_id`。于是「刚才那个子 agent 到底干了什么」可查:它的结论回来了
6463
+ * (一段 summary),而它中间读了哪些文件、试过哪些路、在哪一步拐弯,
6464
+ * 全在另一段会话里。
5569
6465
  *
5570
6466
  * 这一份给的就是那些 id:拿到之后接 `session_messages_search` 就能进去看。
5571
6467
  *
6468
+ * ## ⚠️ 2026-10-27 之前这段说明是假话
6469
+ *
6470
+ * 那时子 agent 的过程**从来没落过盘**:`delegate-xxxxxxxx` 只是个临时字符串,
6471
+ * `sessions` 表里根本没有它那一行。于是上面那句话(以及 `docs/TOOLS.md` 里
6472
+ * 同一句)描述的是一个不存在的能力 —— 模型照着描述去查,查回来是空的,
6473
+ * 而它没有办法分辨「真没有」和「没存下来」。
6474
+ *
6475
+ * 修法不是改文案:`subRunner` 跑完之后把这一轮按步折下来落进库,并写上
6476
+ * `parent_id`(判据在 `runtime/src/session-factory.ts` 的 `persistDelegateTurn` 上)。
6477
+ * 那段会话的 `source` 是 `delegate`,所以它不会出现在「接着哪一段聊」的列表里
6478
+ * —— 列表那一边有 `includeDelegate` 这个开关,判据在 `DELEGATE_SESSION_SOURCE` 上。
6479
+ *
5572
6480
  * ## 为什么不长在 [authorization.ts](./authorization.js) 里
5573
6481
  *
5574
6482
  * 那边的 `lineageOf()` 也走 `parent_id`,但它要的是**一串 id**(拼进 WHERE),
@@ -6840,6 +7748,22 @@ declare class TrackerDB {
6840
7748
  countOpen(sessionId: string): number;
6841
7749
  /** 全量替换会话的 TODO 列表(事务:DELETE + INSERT 批量) */
6842
7750
  replaceAll(sessionId: string, todos: TodoItem[]): void;
7751
+ /**
7752
+ * 删掉一段会话的全部 TODO(2026-10-27)。返回删了几条。
7753
+ *
7754
+ * ## 什么时候需要它
7755
+ *
7756
+ * `delegate_task` 派出去的子任务用**自己那个** `sessionId` 写 TODO
7757
+ * (判据在 `AgentConfig.pendingTodos` 上:闸门要读的是「它自己那几件活」)。
7758
+ * 而那个 id 对应的 sessions 行是**跑完之后才补建的** —— 建行失败或压根没落盘时,
7759
+ * 这几行 TODO 就成了谁也删不掉的孤儿:`epoch sessions` 列不到它,
7760
+ * 会话保留期清理也扫不到它(那一支按 `sessions` 表扫)。
7761
+ *
7762
+ * 所以那一条失败路径上要主动收一下。正常路径**不在这儿删**:子会话已经有
7763
+ * 自己的 sessions 行了,`SessionDB.deleteSession` 会连带清掉
7764
+ * (判据在 db.ts 的 `DELETE FROM todos WHERE session_id = ?` 那一行)。
7765
+ */
7766
+ deleteBySession(sessionId: string): number;
6843
7767
  /** 释放连接(引用计数归零才真正关闭) */
6844
7768
  close(): void;
6845
7769
  }
@@ -10430,6 +11354,15 @@ interface BreakdownInput {
10430
11354
  tool: EpochTool;
10431
11355
  source: string;
10432
11356
  }>;
11357
+ /**
11358
+ * 上一次请求的用量(2026-10-26)。**只用来算缓存那一格**,不给就整个缺席 ——
11359
+ * 「没跑过任何一轮」和「跑了一轮但一次没命中」是两件事,前者不该显示成 0%。
11360
+ *
11361
+ * 口径是**最近一次**而不是累计:累计值会随着高命中的历史越来越好看,
11362
+ * 从而掩盖「刚改的这一处根本没生效」—— 而这正是 {@link ContextBreakdown.cache}
11363
+ * 存在的理由。
11364
+ */
11365
+ lastUsage?: TokenUsage;
10433
11366
  }
10434
11367
  declare function buildContextBreakdown(input: BreakdownInput): ContextBreakdown;
10435
11368
 
@@ -11155,6 +12088,34 @@ declare class AgentLoop {
11155
12088
  private learnedSkills;
11156
12089
  constructor(config: AgentConfig);
11157
12090
  abort(): void;
12091
+ /**
12092
+ * 撞上「请求体太大」之后的第一段救援:**摘掉最老的几张图**,返回摘过的那份。
12093
+ *
12094
+ * ## 为什么它排在压缩之前(这是全套改动的判据所在)
12095
+ *
12096
+ * 1. **代价差一个量级**:摘图是纯本地操作(把 base64 换成一行占位),压缩是
12097
+ * 一发真实的摘要请求(花钱、要等几秒)。而这一档的现场绝大多数是图撑的
12098
+ * (`request-bytes.ts` 文件头那张量纲表),先试便宜的那个。
12099
+ * 2. **压缩的代价高得多,而且它动的是整段历史**:`compress()` 收到的是文本投影
12100
+ * (`message-codec.ts` 的 `flattenMessage` 明确把 `parts` 丢掉),所以图片
12101
+ * 它确实能拿掉。但拿掉图只是它的副作用 —— 它真正干的是把中段**整段换成一段
12102
+ * 摘要**,而那是不可逆的。仅仅为了几张老图就这么干,不值当。
12103
+ *
12104
+ * ## ⚠️ 目标**不能用 {@link imageBudget}**(实测踩到过)
12105
+ *
12106
+ * 走到这儿说明那一次**真的超了**,而 `imageBudget()` 正是撞墙前用的那个数。
12107
+ * 拿它当「摘到多少为止」,在「配置值给得太宽松」和「没有 `homeDir`、学不到
12108
+ * 值」这两档上都会得出「一张都不用摘」(图总量本来就小于那个宽松预算),
12109
+ * 于是救援原地空转、重发同一份东西。
12110
+ *
12111
+ * 撞墙这个事实本身就说明「必须比刚才小」,所以目标是**实发量打八折**
12112
+ * (同 `LEARN_SHRINK` 的口径:留两成余量)。学到的那个值只是让**下一次**
12113
+ * 不必先撞一次。
12114
+ *
12115
+ * @returns 摘动了就回新的 messages + 摘了几张;**一张都摘不掉时回 `null`**
12116
+ * —— 调用方据此知道「这一招不管用」,别拿一模一样的东西再撞一次墙
12117
+ */
12118
+ private tryOffloadForBodyLimit;
11158
12119
  /**
11159
12120
  * 多模态策略的**现读出口**:尺寸上限来自 config,`supportsImages` 来自
11160
12121
  * provider 自己(见 {@link AgentProvider.capabilities})。
@@ -11173,6 +12134,47 @@ declare class AgentLoop {
11173
12134
  * 磁盘缓存)。相对一次模型调用,这个量级不值得为它留一个会说谎的快照。
11174
12135
  */
11175
12136
  private multimodal;
12137
+ /**
12138
+ * 这一发该按多大的图片预算走 —— **配置/缺省值和学习值取小的那个**。
12139
+ *
12140
+ * ## 为什么每轮现算,不构造时算一次
12141
+ *
12142
+ * 判据逐字同 {@link multimodal}:**学习值会在一次 run 中途变化** ——
12143
+ * 撞上 1051 的那一刻 {@link learnBodyLimit} 刚把它写下去,而同一轮接下来的
12144
+ * 重发就该按新值(更紧的那个)走。构造时算一次的话,那一次重发用的还是撞墙
12145
+ * 前那个宽松的预算,于是再摘不掉、再撞一次。
12146
+ *
12147
+ * ## 没有 `homeDir` 时
12148
+ *
12149
+ * 不读历史学习值(盘上那份在哪都不知道),只用配置值/缺省值 —— 于是「学」
12150
+ * 这件事在这种宿主上不发生。这是一个**明确的降级**,不是 bug:没给数据目录的
12151
+ * 嵌入宿主不该被我们写文件。
12152
+ */
12153
+ private imageBudget;
12154
+ /**
12155
+ * 记一笔「这个端点收不下这么大的图片」。best-effort:写不进去只是下次再撞一遍。
12156
+ *
12157
+ * ⚠️ 传进来的是**这一次真的发出去的字节数**(不是预算值)。两者的区别就是这条线
12158
+ * 的全部意义:预算可能比它小(那我们不该撞),撞上了说明预算偏大,而偏大多少
12159
+ * 只看这一次的实发量。
12160
+ */
12161
+ private learnBodyLimit;
12162
+ /**
12163
+ * 学习缓存的三元组 `[homeDir, provider, model]` —— 没有 `homeDir` 时回
12164
+ * `undefined`,于是「学」和「读」两条路一起关掉。
12165
+ *
12166
+ * ⚠️ **`provider` 那一格只能退而求其次。** `body-limit-cache.ts` 的文件头要求
12167
+ * 缓存键带 provider(同一个裸名在两家下面可以是两个模型、端点更是各家自己的),
12168
+ * 但 `AgentLoop` 手上**没有** provider 类型:`AgentProvider` 只给 `modelId`
12169
+ * (一个裸名,`agent/types.ts`),provider 身份在 router 里,core 这一层够不着。
12170
+ *
12171
+ * 所以这里 provider 给空串,也就是**本机所有 provider 共享同一条学习值**。
12172
+ * 这是已知的精度损失,方向是**安全的那一侧**:两个端点里更保守的那个值被两边
12173
+ * 共用,代价是最慢那家让我们摘得比别家早一点(可逆、不花钱),而不是在宽松
12174
+ * 那家上撞墙。要修成真带 provider,得让装配层把它递进 `AgentConfig` ——
12175
+ * 那是一次跨包改契约,不在这一笔的范围里。
12176
+ */
12177
+ private imageLimitKey;
11176
12178
  /**
11177
12179
  * 「这一轮会用哪个模型、它认不认图」——**给 system prompt 用的那一份**
11178
12180
  * (2026-09-02)。判据全文在 `AgentProvider.modelId` 上。
@@ -11253,6 +12255,12 @@ declare class AgentLoop {
11253
12255
  contextBreakdown(opts: {
11254
12256
  historyText: string;
11255
12257
  toolSource?: (name: string) => string | undefined;
12258
+ /**
12259
+ * 上一次请求的用量(2026-10-26)。只用来算缓存那一格 —— `/context` 要回答
12260
+ * 「我说了缓存到底有没有生效」,而这句话只能由 provider 报的真数回答。
12261
+ * 不给就是「这个宿主量不到」,那一格整个缺席(不是 0%)。
12262
+ */
12263
+ lastUsage?: TokenUsage;
11256
12264
  }): ContextBreakdown;
11257
12265
  /** 执行一次对话,收敛成最终结果 */
11258
12266
  run(input: RunInput): Promise<RunResult>;
@@ -12167,7 +13175,7 @@ declare function describeJsonError(err: unknown, text: string): string;
12167
13175
  * | -------------------------------- | ------------------------------------------ |
12168
13176
  * | `allowManagedPermissionRulesOnly` | 其余各层的 allow / ask / deny 全部忽略 |
12169
13177
  * | `allowManagedHooksOnly` | 用户级 hook 一律不加载 |
12170
- * | `disableBypassPermissionsMode` | `permission: bypass` 拒绝启动 |
13178
+ * | `disableBypassPermissionsMode` | 审批轴 = `never` 的那一档拒绝启动(今天就是 `auto`) |
12171
13179
  *
12172
13180
  * ## 这一层**不过信任闸门**,而且不该过
12173
13181
  *
@@ -12246,12 +13254,17 @@ declare class ManagedPolicyError extends Error {
12246
13254
  *
12247
13255
  * ## 为什么是**拒绝启动**而不是静默降级到 default
12248
13256
  *
12249
- * 降级是「更安全」的那一侧,但它会让用户以为自己在 bypass 里 —— 然后每一次
13257
+ * 降级是「更安全」的那一侧,但它会让用户以为自己在那一档里 —— 然后每一次
12250
13258
  * 审批弹窗都成了怪现象,而真正的原因(IT 禁掉了这个级别)一个字都看不见。
12251
- * 权限级别是用户**显式**要的东西(`--permission bypass` 或 config.yaml 里那一行),
13259
+ * 权限级别是用户**显式**要的东西(`--permission auto` 或 config.yaml 里那一行),
12252
13260
  * 显式请求被策略挡下必须当面说,这跟「配了个不认识的级别回退到 default」
12253
13261
  * 那种笔误不是一回事。
12254
13262
  *
13263
+ * ⚠️ **2026-10-27 起它挡的是「审批轴 = `never`」这个组合,不再是一个档位名。**
13264
+ * 合并 `auto` + `bypass` 之后,「不问」这件事不再由某个档位名独有 ——
13265
+ * 用一个 `level === 'auto'` 判断也能对得上今天的代码,但那是第二份知识,
13266
+ * 下一次加一档 `never` 的档位时它会静默漏掉。
13267
+ *
12255
13268
  * 挡的是级别本身,不区分它从哪一层来 —— `--permission`、`EPOCH_PERMISSION`、
12256
13269
  * `~/.epoch/config.yaml` 最后都汇进同一个 `config.permission`,
12257
13270
  * 在这里分叉等于把「哪个来源算数」这个问题又答一遍。
@@ -13600,13 +14613,7 @@ declare const EpochConfigSchema: z.ZodObject<{
13600
14613
  maxOutputTokens: z.ZodOptional<z.ZodInt>;
13601
14614
  homeDir: z.ZodDefault<z.ZodString>;
13602
14615
  dbPath: z.ZodDefault<z.ZodString>;
13603
- permission: z.ZodDefault<z.ZodEnum<{
13604
- default: "default";
13605
- acceptEdits: "acceptEdits";
13606
- plan: "plan";
13607
- auto: "auto";
13608
- bypass: "bypass";
13609
- }>>;
14616
+ permission: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<"default" | "acceptEdits" | "plan" | "auto", string>>;
13610
14617
  permissions: z.ZodOptional<z.ZodObject<{
13611
14618
  allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
13612
14619
  ask: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -13634,6 +14641,9 @@ declare const EpochConfigSchema: z.ZodObject<{
13634
14641
  sandbox: z.ZodPrefault<z.ZodObject<{
13635
14642
  terminal: z.ZodDefault<z.ZodBoolean>;
13636
14643
  }, z.core.$strip>>;
14644
+ delegate: z.ZodPrefault<z.ZodObject<{
14645
+ taskTimeoutSec: z.ZodDefault<z.ZodInt>;
14646
+ }, z.core.$strip>>;
13637
14647
  tools: z.ZodPrefault<z.ZodObject<{
13638
14648
  deferMcp: z.ZodDefault<z.ZodBoolean>;
13639
14649
  mode: z.ZodDefault<z.ZodEnum<{
@@ -13649,6 +14659,9 @@ declare const EpochConfigSchema: z.ZodObject<{
13649
14659
  enabled: z.ZodDefault<z.ZodBoolean>;
13650
14660
  threshold: z.ZodDefault<z.ZodNumber>;
13651
14661
  }, z.core.$strip>>;
14662
+ limits: z.ZodPrefault<z.ZodObject<{
14663
+ maxRequestImageBytes: z.ZodOptional<z.ZodInt>;
14664
+ }, z.core.$strip>>;
13652
14665
  memory: z.ZodPrefault<z.ZodObject<{
13653
14666
  memoryCharLimit: z.ZodDefault<z.ZodInt>;
13654
14667
  userCharLimit: z.ZodDefault<z.ZodInt>;
@@ -13796,6 +14809,10 @@ declare const EpochConfigSchema: z.ZodObject<{
13796
14809
  }> & z.core.$partial, z.ZodString>>;
13797
14810
  hidden: z.ZodOptional<z.ZodBoolean>;
13798
14811
  }, z.core.$strip>>;
14812
+ scheduleRemoveConfirm: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
14813
+ zh: "zh";
14814
+ en: "en";
14815
+ }> & z.core.$partial, z.ZodString>>;
13799
14816
  user: z.ZodOptional<z.ZodObject<{
13800
14817
  label: z.ZodString;
13801
14818
  icon: z.ZodOptional<z.ZodString>;
@@ -14378,6 +15395,177 @@ interface SchemaExport {
14378
15395
  */
14379
15396
  declare function buildSchemaExports(): SchemaExport[];
14380
15397
 
15398
+ /**
15399
+ * 配置键的**全量**表 —— 从 `EpochConfigSchema` 派生,一个字都不手抄。
15400
+ *
15401
+ * ## 它存在的原因
15402
+ *
15403
+ * `epoch config set` 原来认 24 个键,而那 24 个是**手抄**在三张 `Record` 里的
15404
+ * (`SCALAR_FIELDS` / `SECTION_FIELDS` / `BUDGET_FIELDS`)。schema 有 45 个顶层键 /
15405
+ * 83 个叶子,于是「知道的」和「能改的」差着五十多个 —— 而那个差距在任何一屏上都
15406
+ * 看不见:用户(或模型)照着 schema / 文档敲一个键,收到的是「未知配置项」,
15407
+ * 它读起来和「这个功能没做」一模一样。
15408
+ *
15409
+ * `cli/commands/config.ts` 的注释里记着这个坑的三次前科(`permission` / `shell` /
15410
+ * 设置页那一串),每一次都是手抄名单落下的。这一份把它从根上断掉:**清单只有
15411
+ * schema 一个真源**,`set` / `unset` / 报错提示 / `config list` 全部查这张表。
15412
+ *
15413
+ * ## 值域也是派生的
15414
+ *
15415
+ * {@link checkConfigValue} 按 JSON Schema 的 `type` / `enum` / `minimum` /
15416
+ * `exclusiveMinimum` / `maximum` / `minLength` 校验 —— 那几格正是 zod 的
15417
+ * `.int().positive().max(10000)` / `.enum([...])` / `.min(1)` 编译出来的东西
15418
+ * (实测:`automation.keepRuns` 拿到 `exclusiveMinimum: 0, maximum: 10000`,
15419
+ * `budget.onUnknownPricing` 拿到 `enum: ['block','warn']`)。
15420
+ *
15421
+ * 于是 `epoch config set` 的值域**永远等于 schema**,而不再是「另一份人肉对齐的
15422
+ * 近似」。这一层原来最容易出错的形态是「放行 `1.5`,而引擎那侧是 `z.int()`,
15423
+ * 于是整份配置退回默认值」—— `sessions.retentionDays` 与 `automation.*` 两处
15424
+ * 的注释各记了一次。
15425
+ *
15426
+ * ## 三档可写性,判据写在每一档旁边
15427
+ *
15428
+ * | 档 | 判据 | 今天几个 |
15429
+ * | --- | --- | --- |
15430
+ * | `scalar` | 顶层标量,或**一层** `段.字段`,且不是 {@link BLOCKED} 里那三个 | 37 |
15431
+ * | `manual` | 两层以上的路径,或数组 / 对象 | 40 |
15432
+ * | `blocked` | 「位置 / 身份」类的三个键 | 3 |
15433
+ *
15434
+ * ### 为什么两层以上的路径不开放
15435
+ *
15436
+ * `setSectionField` 的就地改写只做一层(`infra/src/config-yaml.ts` 文件头那句
15437
+ * 「代价是只能改标量和一层嵌套——够用」)。把它扩成正则任意深度,要处理每一层
15438
+ * 的缩进、兄弟字段和注释归属,而那个手术碰坏用户注释的形态是静默的。
15439
+ *
15440
+ * 更硬的一条在 `display.currency` 上:它的 `code` / `rate` 在 schema 里是
15441
+ * **required 成对**的(JSON Schema 那句 `required: ['code','rate']`)。只写一个,
15442
+ * 整份配置过不了 `EpochConfigSchema`,而 loader 的语义是「不逐字段抢救」——
15443
+ * 结果是**整份配置连同模型和 key 一起退回默认值**。为一个货币单位付这个代价
15444
+ * 不值得,所以这一族留给人手改文件(手改时他看得见旁边那一行)。
15445
+ *
15446
+ * ### 为什么 {@link BLOCKED} 那三个键连手改都算「不是设置」
15447
+ *
15448
+ * 它们不是「某个开关的值」,是「数据放在哪 / 我是哪个档」。而这三个键今天的
15449
+ * 真源**不在 config.yaml 里**:
15450
+ *
15451
+ * - `profile`:唯一真源是环境变量 `EPOCH_PROFILE`(`infra/src/paths.ts` 的
15452
+ * `resolveHomeDir()` / `resolveProfile()` 都只读环境变量)。写进 yaml 一个字都
15453
+ * 不会切目录,只会让「关于」那一屏(`WireAbout.profile`)报一个假的当前档。
15454
+ * - `homeDir`:有两套解析真源 —— `paths.ts` 那一家只看 `EPOCH_HOME` /
15455
+ * `EPOCH_PROFILE`,而 runtime 到处读 `config.homeDir`。写它不会让配置文件搬家
15456
+ * (`configPath()` 照样在旧家),只会让**数据**跑去新家,于是同一个进程里
15457
+ * 配置文件、会话库、凭据分散在两个目录。
15458
+ * - `dbPath`:同族,而且切得更碎 —— `runtime/src/services.ts` 读
15459
+ * `config.dbPath`,而 `runtime/src/schedule/control.ts` 用的是
15460
+ * `dbPath(homeDir)`。写它只切走一半的库。
15461
+ *
15462
+ * 换句话说:把这三个键放行不会「让配置更灵活」,只会造出一台**看起来改了、
15463
+ * 其实只改了一半**的机器。要搬家的正路是环境变量(`EPOCH_HOME` /
15464
+ * `EPOCH_PROFILE`),那是它们本来的入口。
15465
+ *
15466
+ * ### 另外三个键的单独判据(写在这一层,是因为它们各自的原因不一样)
15467
+ *
15468
+ * - **`modelEntries`**:schema 上自己写着「写在 config.yaml 里不生效」——
15469
+ * 真源是 `models.json`,loader 读到这一格会报一条 `legacy_ignored`。
15470
+ * - **`hooks`**:旧格式,已搬去 `hooks.json`。这一格只保证「是个对象」,
15471
+ * 内部结构由 hook 自己的 schema 校验(这里给它写一个字段表就等于维护第二份)。
15472
+ * - **`display.currency.code` / `.rate`**:两格在 schema 里是 **required 成对**的
15473
+ * (`required: ['code','rate']`)。只写一个,整份配置过不了校验、按 loader
15474
+ * 一贯的「不逐字段抢救」退回默认值 —— 模型和 key 一起没。
15475
+ */
15476
+
15477
+ /** 一个叶子能是的那几种标量。数组 / 对象不在这里 */
15478
+ type ConfigKeyKind = 'string' | 'integer' | 'number' | 'boolean';
15479
+ /**
15480
+ * 这个键能不能通过 `epoch config set` 写。
15481
+ *
15482
+ * 三档的含义和判据见文件头那张表 —— **别照着名字猜**,
15483
+ * `blocked` 不是「危险」,是「它不是设置」。
15484
+ */
15485
+ type ConfigKeyWrite = 'scalar' | 'manual' | 'blocked';
15486
+ /**
15487
+ * 不能 `config set` 的**原因码**。
15488
+ *
15489
+ * ⚠️ 这一格是给 CLI 翻译用的,**不是给人读的** —— 人读的是 catalog 里那几句
15490
+ * (`cli.config.manual_nested` / `manual_structured` / `not_a_setting`)。
15491
+ *
15492
+ * 为什么不在表里存一句「为什么不能写」的中文描述:`config set` / `config list`
15493
+ * 会把它拼进输出,而英文界面上就长出一句英文夹中文的话。存原因码、
15494
+ * 让文案走 catalog,两边各自才是对的。
15495
+ */
15496
+ type ConfigKeyReason = 'position' | 'structured' | 'nested';
15497
+ /** 键表里的一行 */
15498
+ interface ConfigKeyEntry {
15499
+ /** 点分路径,和 `config.yaml` 里的写法逐字一致(`budget.maxCostUsd`) */
15500
+ path: string;
15501
+ /** 段名;顶层键没有这一格 */
15502
+ section?: string;
15503
+ /** 段内的字段名;顶层键没有这一格 */
15504
+ field?: string;
15505
+ kind: ConfigKeyKind;
15506
+ write: ConfigKeyWrite;
15507
+ /** `write !== 'scalar'` 时**必有**:不能 `config set` 的原因码(CLI 据此选文案) */
15508
+ reason?: ConfigKeyReason;
15509
+ /** 枚举型键的可选值 */
15510
+ choices?: readonly string[];
15511
+ minimum?: number;
15512
+ exclusiveMinimum?: number;
15513
+ maximum?: number;
15514
+ exclusiveMaximum?: number;
15515
+ minLength?: number;
15516
+ /** schema 里写的内置默认值。没有默认值的键(`model` / `budget.maxCostUsd` …)不带这格 */
15517
+ builtin?: SettingValue;
15518
+ }
15519
+ /**
15520
+ * 全部配置键,按 schema 里的声明顺序排。
15521
+ *
15522
+ * 调用方**不许**改它(`readonly`),也不许在别处再抄一份 —— 报错提示、
15523
+ * `config list`、`config set` / `unset` 认的键必须来自这一个数组。
15524
+ */
15525
+ declare function configKeyTable(): readonly ConfigKeyEntry[];
15526
+ /** 按点分路径查一行;不认识的键返回 `undefined`(调用方据此报「未知配置项」) */
15527
+ declare function configKeyByPath(path: string): ConfigKeyEntry | undefined;
15528
+ /**
15529
+ * 全部能 `config set` 的键。报错提示和 `config list --all` 都从这里取。
15530
+ *
15531
+ * 顺序就是 schema 的顺序 —— 分段天然相邻,不需要再排一次。
15532
+ */
15533
+ declare function writableConfigKeys(): readonly ConfigKeyEntry[];
15534
+ /** 校验失败的原因。带格子是因为 CLI 要把它拼进一句人话(catalog 里那几条) */
15535
+ type ConfigValueProblem = {
15536
+ code: 'not-a-kind';
15537
+ kind: ConfigKeyKind;
15538
+ } | {
15539
+ code: 'not-in-choices';
15540
+ choices: readonly string[];
15541
+ } | {
15542
+ code: 'below-minimum';
15543
+ bound: number;
15544
+ exclusive: boolean;
15545
+ } | {
15546
+ code: 'above-maximum';
15547
+ bound: number;
15548
+ exclusive: boolean;
15549
+ } | {
15550
+ code: 'too-short';
15551
+ minLength: number;
15552
+ };
15553
+ /** {@link checkConfigValue} 的结论。成功时带上**该写进 yaml 的字面量** */
15554
+ type ConfigValueCheck = {
15555
+ ok: true;
15556
+ literal: string;
15557
+ } | {
15558
+ ok: false;
15559
+ problem: ConfigValueProblem;
15560
+ };
15561
+ /**
15562
+ * 一个字符串能不能当这个键的值,以及它该以什么字面量写进 yaml。
15563
+ *
15564
+ * 校验顺序是固定的:类型 → 枚举 → 边界 → 长度。顺序有意义 —— 反过来的话
15565
+ * `budget.warnAtPercent: abc` 会先撞上「不在 0~100 之间」,而真正的问题是它不是个数。
15566
+ */
15567
+ declare function checkConfigValue(entry: ConfigKeyEntry, raw: string): ConfigValueCheck;
15568
+
14381
15569
  /**
14382
15570
  * `~/.epoch/keybindings.json` 的加载(方案 31 PR-1 / PR-2)。
14383
15571
  *
@@ -15655,6 +16843,13 @@ interface Message {
15655
16843
  tokenCount?: number;
15656
16844
  finishReason?: string;
15657
16845
  reasoning?: string;
16846
+ /**
16847
+ * 这一步里工具改过的文件(补丁预览,v7)。和 `reasoning` 同一个位置:
16848
+ * 一条 assistant 行 = 一步,那一步可能改了多个文件。
16849
+ *
16850
+ * 老库里这一列是 NULL(迁移刚加的),读侧见到 `undefined` 就当没有补丁卡。
16851
+ */
16852
+ diffs?: EpochDiffSnapshot[];
15658
16853
  active: boolean;
15659
16854
  compacted: boolean;
15660
16855
  observed: boolean;
@@ -15680,6 +16875,30 @@ interface Session {
15680
16875
  /** 获取上下文窗口内的消息 [OpenCode] */
15681
16876
  getContext(maxTokens?: number): Message[];
15682
16877
  }
16878
+ /**
16879
+ * `delegate_task` 派出去的子任务,在 `sessions.source` 上记的那一档。
16880
+ *
16881
+ * ## 为什么要有一个自己的 source,而不是沿用 `'cli'`
16882
+ *
16883
+ * 子会话从 2026-10-27 起**真的会落盘**(在那之前只有一段临时 id,库里的
16884
+ * `sessions` 表根本没有它那一行 —— 判据在 `runtime` 的 `subRunner` 上)。
16885
+ * 落盘之后它就和用户自己的对话长得一模一样了,而这是两件事:
16886
+ *
16887
+ * - **它不是「用户的一段对话」**:`epoch -r`(最近一段)、`epoch -c`
16888
+ * (这个目录下最近一段)接上去都接错了对象 —— 用户想接着聊的是他自己那段,
16889
+ * 不是十分钟前派出去的那个子任务。
16890
+ * - **但它的过程必须读得到**:`session_trace` 从父会话沿着 `parent_id` 往下走,
16891
+ * 要能列出它;`session_messages_read` 要能读回它每一步调了什么工具。
16892
+ *
16893
+ * 所以这两条路被刻意分开了:**列表 / `-r` / `-c` 默认排除它**(判据在
16894
+ * `SessionDB.listSessions` 的 `includeDelegate` 上),**血缘和消息读回不排除**
16895
+ * (那两条走的是各自的 SQL,不经过列表)。
16896
+ *
16897
+ * ⚠️ 这是一个**跨包的名字**:`core`(写行 / 列表过滤)和 `runtime`(建行)各用
16898
+ * 一次,所以真源只在这儿一份。字符串抄两遍的下场是有人改了其中一处,
16899
+ * 而表现只是「子会话忽然出现在列表里」—— 没有一处会红。
16900
+ */
16901
+ declare const DELEGATE_SESSION_SOURCE = "delegate";
15683
16902
  interface CreateSessionInput {
15684
16903
  id?: string;
15685
16904
  title?: string;
@@ -15731,6 +16950,8 @@ interface AppendMessageInput {
15731
16950
  tokenCount?: number;
15732
16951
  finishReason?: string;
15733
16952
  reasoning?: string;
16953
+ /** 这一步里工具改过的文件(补丁预览,v7)。**只该传给 assistant 行** */
16954
+ diffs?: EpochDiffSnapshot[];
15734
16955
  }
15735
16956
  interface ListSessionsInput {
15736
16957
  /**
@@ -15773,6 +16994,17 @@ interface ListSessionsInput {
15773
16994
  * 再传进来(我们这边的同步层退 2s),代价只是重复拉几行、而那是幂等的。
15774
16995
  */
15775
16996
  updatedSince?: number;
16997
+ /**
16998
+ * 要不要把 `delegate_task` 派出去的子任务会话一起列出来。**默认不要。**
16999
+ *
17000
+ * 判据在 {@link DELEGATE_SESSION_SOURCE} 上:子会话不是用户的一段对话,
17001
+ * 而 `epoch -r` / `epoch -c` / 侧栏列表问的都是「接着哪一段聊」。
17002
+ * 它落盘是为了 `session_trace` + `session_messages_read` 读得回过程,
17003
+ * 那条路上不经过这里。
17004
+ *
17005
+ * 只有「这个库里一共有哪些行」这一类调用方(增量备份、诊断)才该传 `true`。
17006
+ */
17007
+ includeDelegate?: boolean;
15776
17008
  }
15777
17009
  interface ListSessionsOutput {
15778
17010
  sessions: SessionMeta[];
@@ -16390,6 +17622,11 @@ declare class SessionManager implements SessionManager$1 {
16390
17622
  * 那不是摘要,是残缺的原文,句子往往截在一半,喂回模型比不给更糟。
16391
17623
  */
16392
17624
  compact(sessionId: string, summarize?: (transcript: string) => Promise<string | null>): Promise<CompactionResult>;
17625
+ /**
17626
+ * 把库里那几张图片下放成一行占位(2026-10-28)。判据全文在
17627
+ * {@link SessionDB.offloadImageParts} 上 —— 这一层只是转发,**一个数都不算**。
17628
+ */
17629
+ offloadImageParts(sessionId: string): number;
16393
17630
  /**
16394
17631
  * 把引擎刚压完的那一次落进库里(方案 64 PR-1)。判据全文在
16395
17632
  * {@link SessionDB.archiveAndCompact} 上 —— 这一层只是转发,**一个数都不算**。
@@ -17368,18 +18605,25 @@ declare const MAX_AUDIT_ENTRIES = 500;
17368
18605
  * 拆的直接原因是 manager.ts 越过了 500 行硬线,但拆得动本身说明这一档确实
17369
18606
  * 独立:它是「没有任何规则命中时的兜底」,语义上不该和规则匹配混在一个类里。
17370
18607
  *
17371
- * 五档的意图(对标 Hermes `approval.py`):
18608
+ * 四档的意图(对标 Hermes `approval.py` + deepseek-harness 的两根轴):
18609
+ *
18610
+ * | 级别 | 审批 | 沙箱 | 意图 |
18611
+ * | ------------- | ---- | -------------------- | --------------------------------------------- |
18612
+ * | `auto` | never| `danger-full-access` | 全放行,**但底线那一层照旧**:灾难级拒、tier-2 拒、判不了放行 |
18613
+ * | `plan` | ask | `read-only` | 只读:写入 / 网络 / 执行代码一律拒,**只读命令放行**;区外**读**要问 |
18614
+ * | `acceptEdits` | ask | `workspace-write` | 同 `auto`,但**工作区外的文件**要确认 |
18615
+ * | `default` | ask | `workspace-write` | 只读操作免确认(**工作区外的读除外**),其余一律确认 |
17372
18616
  *
17373
- * | 级别 | 意图 |
17374
- * | ------------- | -------------------------------------------------- |
17375
- * | `bypass` | 全放行 —— 但**挡不住第 1 层那两条 deny**,那层在前面 |
17376
- * | `auto` | 信任 agent:这一档自己不拦,边界全在第 1 层 |
17377
- * | `plan` | 只读:写入 / 命令 / 网络 / 执行代码一律拒;区外**读**要问 |
17378
- * | `acceptEdits` | 同 `auto`,但**工作区外的文件**要确认 |
17379
- * | `default` | 只读操作免确认(**工作区外的读除外**),其余一律确认 |
18617
+ * ## ⚠️ 2026-10-27:五档并成四档,`bypass` 并进 `auto`
18618
+ *
18619
+ * 实测(真 `PermissionManager` 跑 15 项日常操作)合并前的弹窗数是
18620
+ * **11 / 5 / 2 / 1**(default / acceptEdits / auto / bypass),而 `auto` 与
18621
+ * `bypass` 只差「判不了」那一格外加规则表 `ask` 那一族 —— 两档说不出
18622
+ * 「用户凭什么选它而不是另一个」。合并方向取宽的({@link ruleAskActive} 现在
18623
+ * 只对 `auto` 为假),老名字 `bypass` 走 protocol 的别名表静默映射过来。
17380
18624
  *
17381
18625
  * ⚠️ **「工作区外」那一格是 `plan` / `default` / `acceptEdits` 三档共用的**
17382
- * (2026-10-08):`auto` / `bypass` 放行,其余三档问一句,判据在
18626
+ * (2026-10-08):`auto` 放行,其余三档问一句,判据在
17383
18627
  * {@link outsideWorkspace} 上。**档位管的是「问了之后算不算数」,不是「要不要问」**
17384
18628
  * —— 而「算不算数」由权限层算出来的 `grantedPaths` 兑现,工具层照它放行
17385
18629
  * (判据在 protocol 的 `ToolContext.grantedPaths` 上)。
@@ -17389,7 +18633,7 @@ declare const MAX_AUDIT_ENTRIES = 500;
17389
18633
  *
17390
18634
  * ## ⚠️ 这一档哪天要用「沙箱模式」,调 `sandboxModeForLevel()`,别在这儿长第二张表
17391
18635
  *
17392
- * 上面那张五档表还有一个孪生兄弟:**同样五档 → 沙箱模式**(`read-only` /
18636
+ * 上面那张四档表还有一个孪生兄弟:**同样四档 → 沙箱模式**(`read-only` /
17393
18637
  * `workspace-write` / `danger-full-access`),它今天住在
17394
18638
  * [infra/src/sandbox/policy.ts](../../../infra/src/sandbox/policy.ts)。
17395
18639
  * 方案 46 §2.2 原文说那段推导本该住在这个文件旁边(「那是权限系统的知识,
@@ -17800,29 +19044,52 @@ declare class PermissionManager implements PermissionManager$1 {
17800
19044
  * plan 模式下一条判不了的命令会弹给用户,用户点「允许」它就真跑了 ——
17801
19045
  * 而 `checkPlan()` 那句「plan 模式禁止写入和命令执行」压根没机会说话。
17802
19046
  *
17803
- * 2. **`bypass` + `obfuscated` → 放行。** 排在缓存前面,于是「`bypass` 整段不查
19047
+ * 2. **`never` 档下要人确认的底线分成两小支,落点相反**(2026-10-27 定的第二支)。
19048
+ *
19049
+ * 2a. **`obfuscated` → 放行。** 排在缓存前面,于是「`never` 整段不查
17804
19050
  * 审批缓存」({@link checkRulesAndCache} 那一节)对**规则那条路**逐字仍然成立。
19051
+ * 塌向「放行」的理由:这条判定的原话是「静态检查无法判定其真实行为,需人工确认」
19052
+ * —— 它是一句「我判不了」,不是一句「这危险」,而这一档的字面意思正是
19053
+ * 「别拿你判不了的事来问我」。逐字就是 Hermes 的 yolo。
17805
19054
  *
17806
- * ⚠️ **认码,不是认「所有 ask 形状的底线」**(2026-09-10 用户拍的):
17807
- * `dangerous-command`(tier-2,危险但可恢复)在这一档下**照旧问**。
17808
- * 用户的原话是「除了那种真的非常危险的操作,和无法判断的才会……停下来找
17809
- * 用户」,而他随后选定的两档形状是:**危险的问一句(点了就能干活)、
17810
- * 判不了的不问**。所以这一支只塌 `obfuscated`。
19055
+ * ⚠️ **代价说清楚**:这一档 + 提示注入 + `$(…)` 拼命令名这条路由此打通。
19056
+ * Hermes 在 `tools/approval.py` 那句 `# this and bypass every approval check
19057
+ * (prompt-injection escalation path)` 上承认的是同一个敞口。不接受的人该做的
19058
+ * 是**别用这一档**,而不是让这一档偷偷弹框 —— 一个说了「不问」却还在问的
19059
+ * 开关,会让用户学会闭眼点「允许」,那比敞口更坏。
17811
19060
  *
17812
- * ⚠️ 塌向「放行」而不是 Codex 的「拒绝」:这条判定的原话是「静态检查无法
17813
- * 判定其真实行为,需人工确认」—— 它是一句「我判不了」,不是一句「这危险」,
17814
- * 而 `bypass` 的字面意思正是「别拿你判不了的事来问我」。这一档下**逐字就是
17815
- * Hermes 的 yolo**(它的 floor 只有 hardline,判不了那一族住在 Tier-2、
17816
- * 被 yolo 整段跳过)。
19061
+ * 2b. **`dangerous-command`(tier-2,危险但可恢复)→ 拒绝。**
17817
19062
  *
17818
- * ⚠️ **代价说清楚**:`bypass` + 提示注入 + `$(…)` 拼命令名这条路由此打通。
17819
- * Hermes 在 `tools/approval.py` 那句 `# this and bypass every approval check
17820
- * (prompt-injection escalation path)` 上承认的是同一个敞口。不接受的人该做的是
17821
- * **别用这一档**,而不是让这一档偷偷弹框 —— 一个说了「不问」却还在问的开关,
17822
- * 会让用户学会闭眼点「允许」,那比敞口更坏。
19063
+ * ## ⚠️ 2026-10-27:这一支从「问一句」翻成「拒绝」(用户拍的)
19064
+ *
19065
+ * 原判据(2026-09-10)是「危险的问一句(点了就能干活)、判不了的不问」。
19066
+ * 它当时是对的 —— 那一版把「危险」和「判不了」一起塌成放行,而最高档下
19067
+ * `sudo apt install` 永远做不了是一句更坏的话。
19068
+ *
19069
+ * 翻过来的理由是**两家参照实现在这一点上一致,而只有我们不同**:
19070
+ *
19071
+ * - Codex `exec_policy.rs:812`:`AskForApproval::Never => Decision::Forbidden`,
19072
+ * 注释原文「we prefer to prompt the user rather than outright forbid the
19073
+ * command, but if the user has explicitly disabled prompts, we must forbid」;
19074
+ * - deepseek-harness `user-approval/src/index.ts` 的 `ApprovalPolicy`:`'never'`
19075
+ * 的定义是「every ask resolves `'rejected'` deterministically」。
19076
+ *
19077
+ * 两家都是**拒绝**,而这一档承诺的是「不问」—— 承诺兑现之后必须有一个落点,
19078
+ * 两家一致选了拒绝(fail closed),我们照此对齐。
19079
+ *
19080
+ * ⚠️ **落到用户身上的代价**:`auto` 档下 `sudo apt install` /
19081
+ * `git push --force` / `chmod 777` 这类**做不了**,模型会收到一句拒绝而不是
19082
+ * 一个可以点的出口。要干这些事得换回 `default` 或 `acceptEdits` 档 ——
19083
+ * 那两档照旧弹窗,点了就能干活。这条差别是刻意的:**这一档不再提供
19084
+ * 「点一下就放行」这个出口**,它只提供「不问」。
19085
+ *
19086
+ * ⚠️ **灾难级(`rm -rf /`)不受影响**:它在 `hard-limits.ts` 里直接
19087
+ * 不带 `requireApproval`,压根走不到这个方法。
19088
+ *
19089
+ * ⚠️ **`plan` 档不受影响**:第 1 条在它前面就 return 了。
17823
19090
  *
17824
19091
  * 3. **用户以前对这一条明确答过 → 照他的答复。** 这是另一个缺陷:审批缓存躺在
17825
- * 这一层**后面**,所以一条判不了 / tier-2 危险的命令上点一百次「永久允许」,
19092
+ * 这一层**后面**,所以一条 tier-2 危险的命令上点一百次「永久允许」,
17826
19093
  * 第一百零一次照样弹 —— 那个出口在这一族上是**画着但不通**的。
17827
19094
  *
17828
19095
  * ⚠️ 这里查缓存**不受 `approvalCacheActive()` 约束**(那条只管规则那条路):
@@ -17830,6 +19097,11 @@ declare class PermissionManager implements PermissionManager$1 {
17830
19097
  * 2026-08-17 那条判据(`bypass` 下缓存的 deny 不该拦住任何东西)没有被翻 ——
17831
19098
  * 它说的是**档位本来就放行**的操作,而这一支下面的操作档位从来不放行。
17832
19099
  *
19100
+ * ⚠️ **2026-10-27 起它在「不问」那一档的 tier-2 拒绝之前**,这是刻意的:
19101
+ * 用户在 `default` / `acceptEdits` 档下点过「永久允许」的 `sudo apt install`,
19102
+ * 换到 `auto` 档之后**照旧放行** —— 那是一个真实的授权,`never` 的意思是
19103
+ * 「别拿没授权的事来问我」,不是「我批过的也不作数」。要撤销去审批面板撤。
19104
+ *
17833
19105
  * 这不是「让缓存绕过黑名单」:绕不过的是 deny 形状的那两条(灾难级危险命令、
17834
19106
  * 敏感路径),它们在 `hard.requireApproval` 为假时压根走不到这儿。
17835
19107
  *
@@ -17838,12 +19110,16 @@ declare class PermissionManager implements PermissionManager$1 {
17838
19110
  */
17839
19111
  private settleUnaskable;
17840
19112
  /**
17841
- * **规则那条路**上会不会查审批缓存(2026-08-17 的判据:`bypass` 整段不查,
19113
+ * **规则那条路**上会不会查审批缓存(2026-08-17 的判据:`never` 那一档整段不查,
17842
19114
  * 全文在 {@link checkRulesAndCache} 上)。
17843
19115
  *
17844
19116
  * ⚠️ **它不是「这个档位记不记得住」**:安全底线那条路({@link settleUnaskable})
17845
19117
  * 在任何档位下都查缓存。宿主要知道的是**这一次问的这道题**记不记得住,
17846
19118
  * 那个答案在 `PermissionDecision.rememberable` 上,由 {@link rememberFlag} 打。
19119
+ *
19120
+ * ⚠️ 2026-10-27 起它和 {@link ruleAskActive} 走同一个判据(审批轴),但**仍是
19121
+ * 两个方法**:一个问「规则表说要问,这一档还问不问」,一个问「弹窗答案的记忆
19122
+ * 这一档查不查」。合并成一处的话,哪天其中一条改了判据,另一条会**无声地**跟着改。
17847
19123
  */
17848
19124
  approvalCacheActive(): boolean;
17849
19125
  /**
@@ -18551,7 +19827,7 @@ declare function nextRunAt(trigger: ScheduleTrigger, from?: number, startDate?:
18551
19827
  * 那是「这个任务每天都失败,而你要三天后才发现」。所以保存时校验、
18552
19828
  * 校验不过不许存 —— 定时任务的反馈回路按天算,能提前的每一步都值钱。
18553
19829
  *
18554
- * ## 「定时任务 + 跳过全部检查」的三条硬约束**挂在组合上**
19830
+ * ## 「定时任务 + 完全访问」的三条硬约束**挂在组合上**
18555
19831
  *
18556
19832
  * `bypass` 在对话里是一档现成的权限档,用户自己看着屏幕时选它是他的事;
18557
19833
  * 但一个**无人值守、每天自己跑一次**的 `bypass` 是另一回事 —— 那正是
@@ -18569,7 +19845,6 @@ interface ValidateScheduleInput {
18569
19845
  /** 绝对路径。空串 = 用户没绑工作区(`bypass` 下这是硬伤) */
18570
19846
  workDir: string;
18571
19847
  maxTurns: number;
18572
- maxBudgetUsd: number;
18573
19848
  timeoutMs: number;
18574
19849
  trigger: ScheduleTrigger;
18575
19850
  startDate?: string;
@@ -18661,8 +19936,22 @@ declare function parseScheduleDraft(raw: unknown): DraftParse;
18661
19936
  * 这次提案**放宽**了什么。
18662
19937
  *
18663
19938
  * 三类都是授权面变大的方向(判据在 `ScheduleWideningKind` 上)。新建那一支的
18664
- * 基线是**引擎那份缺省档**(`default`)而不是「空」—— 拿空当基线的话每一条新任务
18665
- * 都会被标成「放宽了」,而一枚恒亮的警示标等于没有警示标。
19939
+ * 基线是**引擎那份缺省档**(`SCHEDULE_DEFAULT_PERMISSION`)而不是「空」——
19940
+ * 拿空当基线的话每一条新任务都会被标成「放宽了」,而一枚恒亮的警示标等于没有警示标。
19941
+ *
19942
+ * ## 🔴 2026-10-27:基线从 `default` 抬到了 `auto`,于是「新建」那一支再也标不出来了
19943
+ *
19944
+ * 这不是失手,是抬档的必然结果:基线既然已经是**最宽那一档**,没有任何一档
19945
+ * 在它右边 —— `indexOf(level) > indexOf(base)` 对新建立永远不成立。
19946
+ *
19947
+ * 那一支的警示因此**没有消费者了**。留着它而不是删掉,是因为 `update` 那一支
19948
+ * 靠的正是同一个比较(拿那条任务现在的档当基线),而那一条照旧有效;
19949
+ * 把两支拆开会让「放宽」这个概念在两处各有一个定义。
19950
+ *
19951
+ * ⚠️ **新建那条路的警示不是丢了,是换了一个地方说**:权限档现在默认就是最宽
19952
+ * 那一档,于是「这条新任务将不经确认地做这些事」这句话由**卡片上那份清单** +
19953
+ * `validate.ts` 那三道网(工作目录不许宽得等于没绑 / 尺子必须有值 / 二次确认)
19954
+ * 接住。用户在卡片上看见的是它的绝对值,而不是一个「相对刚才变宽了」的相对值。
18666
19955
  */
18667
19956
  declare function draftWidening(draft: ScheduleDraft, current?: ScheduleDefinition): ScheduleWideningKind[];
18668
19957
  /**
@@ -18674,10 +19963,9 @@ declare function draftWidening(draft: ScheduleDraft, current?: ScheduleDefinitio
18674
19963
  * 看见「未知命令」。那一支的真话是「改一条已有任务今天只有界面那条路」,
18675
19964
  * 由工具输出说出来。
18676
19965
  *
18677
- * ⚠️ **`--budget` 那一格现在是真数了。** 2026-10-20 之前它是 `<你定一个上限>` 的
18678
- * 占位符(草稿没有预算,而 `epoch schedule add` 缺 `--budget` 会当场拒绝),
18679
- * 于是这条命令行和表单那条路落在同一处摩擦上。今天草稿带着预算,这条命令行
18680
- * 因此真的可以直接粘贴跑起来 —— 打印的就是**已经落库的那个数**。
19966
+ * ⚠️ **命令里已经没有 `--budget` 了**(2026-10-29 撤掉了预算那把尺子,
19967
+ * `epoch schedule add` 也不再认那个旗子)。轮次和超时那两格走缺省,所以这一行
19968
+ * 拼出来的就是一份完整可用的 `add`。
18681
19969
  */
18682
19970
  declare function draftCommandLine(draft: ScheduleDraft): string | undefined;
18683
19971
 
@@ -19339,6 +20627,30 @@ declare const DEFAULT_COMPLIANCE_SETTINGS: ComplianceSettings;
19339
20627
  *
19340
20628
  * 通过 shell 命令执行 hook 脚本,JSON 通过 stdin/stdout 通信。
19341
20629
  * 退出码 2 = 关键阻止,其他非零 = 警告。
20630
+ *
20631
+ * ## 沙箱(2026-10-26 补上,方案 65 §2.3)
20632
+ *
20633
+ * 这条子进程以前是**完全无边界**的 `spawn(command, { shell: true })`,而同一个
20634
+ * 仓库里 `terminal` 是包在 `confine()` 里的 —— 同一能力两套边界。而 hook 的口子
20635
+ * 并不小:它是**任意 shell**,来源是七份配置文件(项目级那三份要过工作区信任闸门,
20636
+ * 通过之后就是永久授权),并且 `tool:before` / `verify:stop` 是**自动执行**的,
20637
+ * 没有人会在那一刻再回答一次「要不要跑」。
20638
+ *
20639
+ * 现在它在 macOS / Linux 上走和 `terminal` 同一条 `confine()`,边界见
20640
+ * {@link hookSandboxPolicy}。Windows 没有后端(判据在 infra 的
20641
+ * `sandbox/backend.ts` 文件头),所以那一支**逐字节保持改之前的样子**。
20642
+ *
20643
+ * ## 一条不许漏的:沙箱拒绝**不进表决**
20644
+ *
20645
+ * 被挡住时退出码是普通的非零(seatbelt 是 1),`blocked` 照旧只有 `exitCode === 2`
20646
+ * 才成立。这不是没顺手改 —— 是**不能**改:`verify:stop` 上「阻止」等于
20647
+ * 「不许收工」,而一条被沙箱挡住的 `verify:stop` 下一轮还是被挡住,于是
20648
+ * 模型永远收不了工(确定性死循环,不是概率问题)。沙箱拒绝那条路由
20649
+ * [manager.ts](./manager.ts) 的 `reportIfNotRun()` **如实说出口**,说清
20650
+ * 「这条 hook 没跑成」而不是「它通过了」。
20651
+ *
20652
+ * 「放行」这个方向本身沿用的是 2026-08-08 定下的取舍(hook 不是安全边界),
20653
+ * 判据在 `manager.ts` 的 `reportIfNotRun()` 上 —— 别在这里另开一套。
19342
20654
  */
19343
20655
 
19344
20656
  /** Command hook 配置 */
@@ -19372,6 +20684,40 @@ interface CommandHookResult {
19372
20684
  rejectReason?: string;
19373
20685
  /** stdout 解析为 JSON 时的结构化输出 */
19374
20686
  output?: Record<string, unknown>;
20687
+ /**
20688
+ * 这一次的沙箱账。**永远有值** —— 「没包上」也是一个要说出口的结论
20689
+ * (同 infra 的 `Confinement`:`confine()` 不返回 null 就是为了这个)。
20690
+ */
20691
+ sandbox: CommandHookSandbox;
20692
+ /**
20693
+ * 非零退出时这次失败属于哪一种(判据在 infra 的 `sandbox/classify.ts` 文件头)。
20694
+ *
20695
+ * ⚠️ **只在包上了沙箱时才有值**:没有沙箱就没有「被沙箱挡了」和
20696
+ * 「沙箱没起来」这两种可能,硬跑一遍分类器只会拿当前平台压根不产生的方言
20697
+ * 去匹配(`terminal` 那条路也是这么分的,见 `plugin-terminal/src/sandbox.ts`
20698
+ * 的 `ConfinedShell.classify`)。
20699
+ */
20700
+ failure?: FailureVerdict;
20701
+ }
20702
+ /** hook 这一次跑在什么边界里 —— 形状对齐 `plugin-terminal` 的 `SandboxOutcome` */
20703
+ interface CommandHookSandbox {
20704
+ /** 真包上了没有。`false` 时看 {@link skipped} */
20705
+ confined: boolean;
20706
+ /** 按哪一档包的。**恒为 `workspace-write`**,判据在 {@link hookSandboxPolicy} 上 */
20707
+ mode: SandboxMode;
20708
+ backend: IsolationBackend;
20709
+ /**
20710
+ * 没包上的原因。三档是**三句不同的话**(同 infra 的 `Confinement.reason`,
20711
+ * 但少了 `config-disabled` 一档 —— hook 这一侧今天没有自己的开关,
20712
+ * 判据在 {@link hookSandboxPolicy}):
20713
+ * `mode-disabled` 走不到(mode 是钉死的),留着它是为了形状对齐。
20714
+ *
20715
+ * ⚠️ `Confinement.reason` 2026-10-27 多了一档(`escalated`;
20716
+ * 方案 65 §2.1),**它在这里刻意不存在**:那一档的唯一产地
20717
+ * 是 `SandboxPolicy.bypass`,而 hook 的 policy 从来不设那一格(见
20718
+ * {@link hookSandboxPolicy})。收窄在 {@link hookSkipReason} 里。
20719
+ */
20720
+ skipped?: 'config-disabled' | 'mode-disabled' | 'no-backend';
19375
20721
  }
19376
20722
 
19377
20723
  /**
@@ -21125,6 +22471,105 @@ declare function imagePartTokens(image: string): number | undefined;
21125
22471
  */
21126
22472
  declare function pendingImageTokens(messages: readonly EpochMessage[]): number;
21127
22473
 
22474
+ /**
22475
+ * 「这一次请求里图片一共占多少**字节**」—— 和那条 token 预算**是两个量纲**。
22476
+ *
22477
+ * ## 为什么非要有这一条线(2026-10-28)
22478
+ *
22479
+ * 仓库里已经有一条按容量的预算:`isPromptOversize`(protocol 的 `usage.ts:202`),
22480
+ * 它判 token 占窗口的几成。那条线**看不见图片的字节**,而这两件事在图片上会
22481
+ * 彻底分家:
22482
+ *
22483
+ * | 量纲 | 谁在看 | 一张 2 MB 的截图记多少 |
22484
+ * | --- | --- | --- |
22485
+ * | token | `isPromptOversize` / `shouldCompress` / `pendingImageTokens` | **约一千出头**(按宽高估,`image-tokens.ts:48`) |
22486
+ * | 字节 | **本文件** | 2.7 MB(base64 是原文的 4/3) |
22487
+ *
22488
+ * 而 chat completions 是**无状态**的:每一轮都把整段历史重发一遍,历史里每张图
22489
+ * 都以 base64 再传一次。于是「几个 2 MB 的截图」在 token 账上还空着九成,请求体
22490
+ * 已经撞穿了网关那条上限 —— 表现是 `PROVIDER_BODY_TOO_LARGE`(1051)或者说
22491
+ * 「请求体过大」,而**两条 token 线一个都不会动**。
22492
+ *
22493
+ * ## 为什么只数图片,不数整份请求体
22494
+ *
22495
+ * 因为**只有图片是「token 少、字节大」的那一个**。文本这两个量纲是单调同向的
22496
+ * (一段一 MB 的日志在 token 账上就是几十万),它太大时 `isPromptOversize` 那条
22497
+ * 线早就先抓住了,轮不到这里。
22498
+ *
22499
+ * 反过来做的代价很大:要数整份请求体就得 `JSON.stringify(system + tools + messages)`
22500
+ * 一遍 —— 那是每轮几 MB 的字符串拼接,而且它会把「这一轮该不该发」这个判断
22501
+ * 绑在 SDK 的消息形状上(`toModelMessages` 是唯一认那个形状的地方,
22502
+ * 见 `provider/sdk-adapter.ts` 的文件头)。
22503
+ *
22504
+ * ## 三类图里只有一类有字节可数
22505
+ *
22506
+ * `EpochImagePart.image` 有三种形态(判据在 protocol 的 `message.ts:60`):
22507
+ *
22508
+ * | 形态 | 数不数 | 为什么 |
22509
+ * | --- | --- | --- |
22510
+ * | 裸 base64 / `data:` URL | 数 | 它就摆在请求体里 |
22511
+ * | `http(s)` URL | **不数** | provider 自己去取,我们连字节都没有 —— 猜一个数去拦人是最坏的选择(判据同 `image-tokens.ts` 的那两条取舍) |
22512
+ *
22513
+ * `data:` 前缀(`data:image/png;base64,`,约 22 字节)**剥掉再数**:它是个常数、
22514
+ * 量级无关,但剥掉之后数出来的就是真正的载荷,和小票上那两个数对得上账
22515
+ * (`artifact.bytes` 是原始字节,base64 是它的 4/3)。
22516
+ */
22517
+
22518
+ /**
22519
+ * 这一组消息里**所有图片**的 base64 字节合计。
22520
+ *
22521
+ * ## 必须扫两处,不是一处
22522
+ *
22523
+ * 图有两条进请求体的路,而它们**恰好对应两种撑爆的现场**:
22524
+ *
22525
+ * | 从哪来 | 形状 | 对应哪种现场 |
22526
+ * | --- | --- | --- |
22527
+ * | 用户贴的图 | `message.parts[].type === 'image'` | 历史累积(第二轮那种,每条消息都发不出去) |
22528
+ * | **工具产物** | `toolResults[].artifacts[].data` | 一轮内累积(第一轮那种,工具跑几次就撑爆) |
22529
+ *
22530
+ * 只扫第一处的话,`stripHistoryImages`(`agent/loop.ts:269`)那种**只看 user 行
22531
+ * 的 `parts`** 的老盲区会原地复现一次:工具截图(`plugin-file` 的 `file_read` 读
22532
+ * png、`page_shot` 那类)全部漏掉,而它们恰恰是体积最大的一族。
22533
+ *
22534
+ * ## `artifacts[].data` 才是判据,不是 `artifacts` 非空
22535
+ *
22536
+ * 有没有 `data` 是闸门的结论(`context/artifact-gate.ts:183` 那段:有 `data` 就
22537
+ * 进上下文、没有就只是一句 summary)。被闸门拦下的图**一个字节都不进请求体**,
22538
+ * 把它算进预算会让下放发生在根本不需要的时候 —— 而那种误操作是有损的。
22539
+ */
22540
+ declare function requestImageBytes(messages: readonly EpochMessage[]): number;
22541
+ /**
22542
+ * 把**最老的**几张图换成占位文本,直到剩下的字节数落在预算内。
22543
+ *
22544
+ * ## `keepFresh` 是这一整个特性的安全性所在
22545
+ *
22546
+ * 它说的是「最新的 N 条消息里的图一张都不摘」。**必须 ≥ 1**,理由是这一轮用户
22547
+ * 刚贴的那张图:他正等着模型看图回答,把它摘掉等于把这一轮的活直接干废 ——
22548
+ * 而模型还会一本正经地照着一句「图片已下放」去答,用户完全看不出发生了什么。
22549
+ *
22550
+ * 所以下放只从**旧**的一头开始。代价是「一张巨图 + 预算很小」这一档下放不动
22551
+ * (最老的那张就是最新的那张),那时回 `offloaded: 0`,调用方据此知道
22552
+ * 「这一招不管用」—— 别在那儿硬摘一张正在用的图。
22553
+ *
22554
+ * ## 为什么不是「摘到刚好够」而是「按张为单位」
22555
+ *
22556
+ * 一张图是不可分的:摘一半的 base64 是坏数据。所以这里逐张摘,直到总量进预算。
22557
+ * 最坏情况是超摘一点点(最后那一张其实可以留着),那个代价可以接受 ——
22558
+ * 反过来「差一点没摘够」会让这次救援白跑一趟,而重发的那一发是要么成功要么
22559
+ * 再撞一次墙。
22560
+ *
22561
+ * @param keepFresh 最新的多少条消息里的图不碰。**0 是不安全的**(见上),
22562
+ * 调用方给 `undefined` 时按 1 处理
22563
+ */
22564
+ declare function offloadOldestImages(messages: readonly EpochMessage[], opts?: {
22565
+ budgetBytes: number;
22566
+ keepFresh?: number;
22567
+ }): {
22568
+ messages: EpochMessage[];
22569
+ offloaded: number;
22570
+ remainingBytes: number;
22571
+ };
22572
+
21128
22573
  /**
21129
22574
  * 上下文压缩器 — 默认 ContextEngine 实现。
21130
22575
  * 4 阶段算法(Hermes agent/context_compressor.py),**中间多了一道闸**
@@ -21272,7 +22717,7 @@ declare class ContextCompressor implements ContextEngine {
21272
22717
  */
21273
22718
  private cleanupOrphans;
21274
22719
  /**
21275
- * 一组消息的 token 总量。逐条过 {@link estimateTokens},不另立口径。
22720
+ * 一组消息的 token 总量。逐条过 {@link messageTokens},不另立口径。
21276
22721
  *
21277
22722
  * 这里原来是三个私有函数(`estimateTokens` / `estimateTokenCount` /
21278
22723
  * `tokensForChars`),各自写着 `chars / 4`,和 `model-metadata.ts` 里那份
@@ -21862,7 +23307,9 @@ interface RunCodeOutcome {
21862
23307
  * 失败的分类。**给模型看的措辞按这个分叉**,因为每一档的正确下一步都不一样:
21863
23308
  * 触顶要少调几次、超时要提 `timeout`、OOM 要少占内存、`throw` 要改程序。
21864
23309
  */
21865
- type RunCodeFailure = 'timeout' | 'oom' | 'cap' | 'throw' | 'aborted' | 'worker-missing';
23310
+ type RunCodeFailure = 'timeout' | 'oom' | 'cap' | 'throw' | 'aborted' | 'worker-missing'
23311
+ /** 权限策略把 worker 自己拦在门外 —— 判据在 `policy.ts` 的文件头 */
23312
+ | 'policy';
21866
23313
  declare function runCode(input: RunCodeInput): Promise<RunCodeOutcome>;
21867
23314
 
21868
23315
  /**
@@ -21907,4 +23354,4 @@ declare function resolveCodeModeWorker(baseDir: string, exists?: typeof existsSy
21907
23354
  */
21908
23355
  declare function codeModeWorkerPath(): string | undefined;
21909
23356
 
21910
- export { ABOUT_DESC_MAX_CHARS, ABOUT_README_MAX_CHARS, AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, type AcquireResult, type ActivePlugin, type AgentConfig, AgentLoop, type AgentProvider, type AgentSite, type AgentSiteRef, AppearanceStore, type AppendMessageInput, ApprovalCache, type ApprovalDecision, type ArtifactOpenRefusal, type ArtifactRetention, type AskQuestionDeps, type AttachmentRetention, BLOCKED_TOOLS, type BackendOptions, type BackendOutcome, type BackgroundTaskSource, type BaseOrigin, type BaseSlot, type BeginTurnInput, type BreakdownInput, BudgetGuard, type BudgetGuardOptions, type BudgetInfo, type BudgetLevel, type BudgetOptions, type BudgetStatus, BudgetStore, type BudgetStoreOptions, type BuildFireArgvInput, type BuildIndexOptions, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, type CachedApproval, type CachedDecision, type CheckpointCapture, type CheckpointFile, CheckpointManager, type CheckpointManagerOptions, type CheckpointManifest, CheckpointStore, type CheckpointStoreOptions, type CheckpointSummary, type ClaudeHooksParseOptions, type CodeModeLimits, type CodeRequest, type CodeResult, CodeSandbox, type CommandHookConfig, type CommandHookResult, type CommandLoadOutcome, type CommandLoadSummary, type CommandResult, type CommandRunner, type CompactionResult, type CompiledRules, type ComplianceDirSpec, type ComplianceDirsSummary, ComplianceEngine, type ComplianceEngineInput, type ComplianceGate, type ComplianceHit, type ComplianceRule, type ComplianceRuleSource, type ComplianceSettings, type ComplianceVerdict, type CompressConfig, type CompressResult, type ConfigMigration, type ConfigOrigins, ContextCompressor, type ContextEngine, type CreateScheduleInput, type CreateSessionInput, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, type DelegateArgs, type DelegateConfig, DelegateManager, type DelegateTaskResult, type DiagnoseOptions, type DiscoverOutcome, type DiscoveredFile, type DoctorReport, type DraftParse, EMPTY_RULES, EPOCH_DISK_LEDGER, type EntryProbeCode, type EntryProbeResult, EpochConfigSchema, type ExpandOptions, type ExtractOutcome, FTS_TABLES, FTS_TRIGGERS, type FallbackEvent, type FileSlots, type FileSourceKind, type ForkInput, type FrontmatterParse, type FrontmatterSplit, type FtsHealth, type FtsReclaim, type FtsSpace, type FtsTableHealth, GOAL_BLOCK_CODES, type GateAxis, type GateVerdict, type GenerateInput, type GenerateOutput, type Goal, type GoalBlockReason, type GoalClear, type GoalPhase, type GoalPromptView, type GoalRefusal, type GoalRoundResult, GoalService, GoalStore, type GoalToolDeps, type GoalWrite, HEADLESS_AUDIT_CODES, type HeadlessPolicy, type HookConfigEntry, HookManager, type HookManagerOptions, type HookMatcher, type HookSourceKind, type HookSourceStatus, type HookSourcesOptions, type HookSourcesSummary, type HooksParseOutcome, type MemoryManager$1 as IMemoryManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, type SessionManager$1 as ISessionManager, type SkillSystem$1 as ISkillSystem, ImageLoadError, type ImageSize, type ImportDecision, type ImportGate, type ImportGoal, type ImportMessage, type ImportNode, type ImportPartsResult, type ImportSessionInput, type ImportSessionMeta, type ImportSessionResult, type ImportSkipReason, type ImportTodo, ImportTrustStore, type InstallDependency, type InstallOptions, type InstallOutcome, type InstallPreview, type InstallStep, type InstructionScan, type InstructionSource, type IsolationReason, type IsolationReport, type JsonSchemaDocument, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, type LastOpenSession, LastOpenSessions, type LayerOutcome, type LayerScalars, type LayerStatus, type LearningContext, type LearningResult, type LearningSkipReason, type LexiconLoadOutcome, type LifecycleOutcome, type ListCandidatesInput, type ListSessionsInput, type ListSessionsOutput, type LoadCommandsOptions, type LoadConfigOptions, type LoadKeybindingsOptions, type LoadKeybindingsResult, type LoadRolesOptions, type LoadedPlugin, type LockHolder, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_ENTRIES_FILE, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, type ManagedPolicy, ManagedPolicyError, type ManagedSettings, ManagedSettingsSchema, type MarketplaceCatalog, type MarketplaceCategory, type MarketplaceDisplay, type MarketplaceDisplayField, type MarketplaceEntry, type MarketplaceOutcome, type MarketplaceRecord, type MarketplaceStatus, type MemoryConfig, type MemoryEntry, type MemoryImportRecord, type MemoryImportResult, MemoryManager, type MemoryManagerOptions, type MemoryNotice, type MemoryRemoval, type MemorySearchResult, type MemoryTarget, type MemoryWriteResult, type MentionPart, type MentionResolution, type Message, type MessageHit, type MessageRecord, type MessageSurface, type ModelCapability, ModelFailureTracker, type ModelMetaEntry, type ModelPricing, type MultimodalPolicy, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, type NonInteractiveProbe, type OsTaskSnapshot, type OutputGuard, PACK_EXCLUDES, PERMISSION_AUDIT_CODES, PLUGIN_ICON_MAX_BYTES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, type PackFile, type PackLimits, type PackOutcome, type PackPlan, type PackPlanOutcome, type PermissionAuditCode, type PermissionAuditEntry, type PermissionAuditOutcome, type PermissionAuditScope, type PermissionAuditSnapshot, PermissionManager, type PersistFailureKind, type PersistedBudget, type PersistedPart, type PlanApprovalFn, type PlanEnterResult, type PlanModeDeps, PlanModeState, type PlanSettlement, type PluginAbout, type PluginArtifacts, type PluginAuthor, PluginAuthorSchema, type PluginCommandDir, type PluginCounts, type PluginDoc, type PluginHookFile, type PluginInventory, type PluginLoadOptions, type PluginManifest, type PluginPathRef, type PluginRecord, type PluginScope, type PluginSettingsFile, type PluginSourceType, type PluginStateReadOutcome, PluginStore, type PluginVisibility, type PolicyDirSpec, type PolicyDirsSummary, type PolicyLoadOutcome, type PolicyRule, PolicyRuleSchema, type PricingAt, type PricingOverride, type ProjectContext, type ProjectExtensionInventory, ProviderError, ProviderRouter, type ProviderStreamChunk, type ProviderToolCall$1 as ProviderToolCall, type ProviderWarning, type PruneResult, type QuestionAskFn, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, type ReadMessagesInput, type ReadMessagesOutput, type RecentWorkspace, RecentWorkspaces, type RecordRunInput, type RecorderOptions, type RecoveryAction, type RegisterInput, type RegisterOutcome, type RegistrarOptions, type RegistrationPatch, type RepairOutcome, type ResolveMentionsOptions, type ResolveReferenceInput, type ResolveSettingsOptions, type ResolvedCompression, type ResolvedReference, type ResolvedSource, type ResponseFormat, type RestorePartsOptions, type RewindAction, type RewindDrift, type RewindFilePlan, type RewindOptions, type RewindOutcome, type RewindPreview, type RoleCreateFailure, type RoleCreateInput, type RoleCreateOptions, type RoleCreateOutcome, type RoleLoadOutcome, type RoleLoadSummary, type RoleMergeNotice, type RoleMergeResult, type RoleRemoveFailure, type RoleRemoveOptions, type RoleRemoveOutcome, type RoleScope, type RuleMatch, type RuleSuggestion, type RuleVerdict, type RunCodeFailure, type RunCodeInput, type RunCodeOutcome, type RunCodeToolDeps, type RunEstimateInput, type RunInput, type RunResult, type RunningSchedule, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_ASSET_DIRS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_FILE, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, type ScalarKey, type ScanInstructionsOptions, type ScheduleArgvRefusal, type ScheduleArgvResult, type ScheduleArgvWarning, type ScheduleBackend, type ScheduleCliEntry, type ScheduleDraftDeps, type ScheduleDrift, type ScheduleDriftKind, type ScheduleImportInput, type ScheduleInstallForm, type ScheduleIssue, type ScheduleIssueCode, type ScheduleLock, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, type ScheduleValidation, type SchemaExport, type SearchHit, type SearchInput, type SearchMessagesInput, type SearchMessagesOutput, type SearchOutput, type SearchSessionsInput, type SelectionDeps, type SelfConnector, type SelfIndexInput, type SelfSettings, type Session, type SessionCandidate, type SessionHit, SessionManager, type SessionMentionDeps, type SessionMeta, SessionQueries, SessionReferences, type SessionSearchDeps, type SessionStats, type SessionSurface, type SessionTrace, type SessionVisibility, type SessionVisibilityInput, type SettingApply, type SettingApplyFacts, type SettingChain, type SettingFutile, type SettingLayer, type SettingStep, type SettingValue, type SettingValueKind, type SettingWriteFailure, type SettingWriteKey, type SettingWriteLayer, type SettingWriteOutcome, type SettingWriteRequest, type SettingWriteTarget, type SettingWriteTargetsOptions, type SettingsFile, SettingsFileError, SettingsFileSchema, type SettingsLayer, type SettingsResolution, type ShadowFinding, type ShadowKind, type Skill, type SkillDirSource, type SkillDirSpec, type SkillDirsSummary, type SkillImportConflict, type SkillImportDone, type SkillImportEntry, type SkillImportFailure, type SkillImportForm, type SkillImportOptions, type SkillImportPreview, type SkillImportResult, type SkillIndexEntry, type SkillIndexOptions, type SkillIndexResidency, type SkillIndexStats, SkillLearner, type SkillLearningProvider, type SkillMeta, type SkillStats, SkillSystem, type SkillSystemOptions, type SkillViewDeps, type SkillWriteDeps, type SteerSource, type StoredAppearance, type SubAgentRunner, type SubTask, type SubToolCaller, type SummarizeOutput, type SurfaceMessage, type SystemPromptParams, type SystemPromptSegments, TOOL_SEARCH_TOOL, TaskNoticeTracker, type TodoItem, type ToggleOutcome, type ToolCall, type ToolConflict, type ToolGate, type ToolNameFidelity, type ToolNameLookup, ToolRegistry, type ToolRevealState, type ToolScope, type ToolSearchDeps, type ToolTableCost, type TraceInput, type TraceNode, TrackerDB, type TrustDecision, type TrustGate, type TrustGateOptions, TrustManager, type TurnRecord, type TurnTiming, UNSETTABLE_USER_KEYS, type UpdateScheduleInput, type UtilityModelInfo, type ValidateScheduleInput, type VersionCheck, Workspace, type WorkspaceFiles, type WorkspaceIssue, type WorkspaceIssueKind, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, approvalEntryFor, approvalRootFor, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, capabilityOfRef, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createPluginScope, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, discoverySpec, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, grantedPathsFor, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, hasUsableModel, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidInterval, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listEntryModels, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadModelEntries, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelEntriesPath, modelListCandidates, needsAllowlist, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, normalizeModelEntry, normalizeTrigger, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelEntry, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readEntryKey, readImageSize, readMarketplaces, readPluginAbout, readPluginIcon, readPluginManifest, readPluginRecords, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, saveModelEntries, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, testModelEntry, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, unsetUserSetting, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };
23357
+ export { ABOUT_DESC_MAX_CHARS, ABOUT_README_MAX_CHARS, AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, type AcquireResult, type ActivePlugin, type AgentConfig, AgentInbox, AgentLoop, type AgentProvider, type AgentSite, type AgentSiteRef, AppearanceStore, type AppendMessageInput, ApprovalCache, type ApprovalDecision, type ArtifactOpenRefusal, type ArtifactRetention, type AskQuestionDeps, type AttachmentRetention, BLOCKED_TOOLS, BOARD_TOOL_NAMES, type BackendOptions, type BackendOutcome, type BackgroundTaskSource, type BaseOrigin, type BaseSlot, type BeginTurnInput, type BoardPending, BoardStore, type BoardTarget, type BreakdownInput, BudgetGuard, type BudgetGuardOptions, type BudgetInfo, type BudgetLevel, type BudgetOptions, type BudgetStatus, BudgetStore, type BudgetStoreOptions, type BuildFireArgvInput, type BuildIndexOptions, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, type CachedApproval, type CachedDecision, type CheckpointCapture, type CheckpointFile, CheckpointManager, type CheckpointManagerOptions, type CheckpointManifest, CheckpointStore, type CheckpointStoreOptions, type CheckpointSummary, type ClaudeHooksParseOptions, type CodeModeLimits, type CodeRequest, type CodeResult, CodeSandbox, type CommandHookConfig, type CommandHookResult, type CommandLoadOutcome, type CommandLoadSummary, type CommandResult, type CommandRunner, type CompactionResult, type CompiledRules, type ComplianceDirSpec, type ComplianceDirsSummary, ComplianceEngine, type ComplianceEngineInput, type ComplianceGate, type ComplianceHit, type ComplianceRule, type ComplianceRuleSource, type ComplianceSettings, type ComplianceVerdict, type CompressConfig, type CompressResult, type ConfigKeyEntry, type ConfigKeyKind, type ConfigKeyReason, type ConfigKeyWrite, type ConfigMigration, type ConfigOrigins, type ConfigValueCheck, type ConfigValueProblem, ContextCompressor, type ContextEngine, type CreateScheduleInput, type CreateSessionInput, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_INBOX_CAPACITY, DEFAULT_MAX_HOLD_CHARS, DELEGATE_SESSION_SOURCE, type DelegateArgs, type DelegateConfig, DelegateManager, type DelegateTaskResult, type DeliveryResult, type DiagnoseOptions, type DiscoverOutcome, type DiscoveredFile, type DoctorReport, type DraftParse, EMPTY_RULES, EPOCH_DISK_LEDGER, type EntryProbeCode, type EntryProbeResult, EpochConfigSchema, type ExpandOptions, type ExtractOutcome, FTS_TABLES, FTS_TRIGGERS, type FallbackEvent, type FileSlots, type FileSourceKind, type ForkInput, type FrontmatterParse, type FrontmatterSplit, type FtsHealth, type FtsReclaim, type FtsSpace, type FtsTableHealth, GOAL_BLOCK_CODES, type GateAxis, type GateVerdict, type GenerateInput, type GenerateOutput, type Goal, type GoalBlockReason, type GoalClear, type GoalPhase, type GoalPromptView, type GoalRefusal, type GoalRoundResult, GoalService, GoalStore, type GoalToolDeps, type GoalWrite, HEADLESS_AUDIT_CODES, type HeadlessPolicy, type HookConfigEntry, HookManager, type HookManagerOptions, type HookMatcher, type HookSourceKind, type HookSourceStatus, type HookSourcesOptions, type HookSourcesSummary, type HooksParseOutcome, type MemoryManager$1 as IMemoryManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, type SessionManager$1 as ISessionManager, type SkillSystem$1 as ISkillSystem, ImageLoadError, type ImageSize, type ImportDecision, type ImportGate, type ImportGoal, type ImportMessage, type ImportNode, type ImportPartsResult, type ImportSessionInput, type ImportSessionMeta, type ImportSessionResult, type ImportSkipReason, type ImportTodo, ImportTrustStore, type InstallDependency, type InstallOptions, type InstallOutcome, type InstallPreview, type InstallStep, type InstructionScan, type InstructionSource, type IsolationReason, type IsolationReport, type JsonSchemaDocument, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, type LastOpenSession, LastOpenSessions, type LayerOutcome, type LayerScalars, type LayerStatus, type LearningContext, type LearningResult, type LearningSkipReason, type LexiconLoadOutcome, type LifecycleOutcome, type ListCandidatesInput, type ListSessionsInput, type ListSessionsOutput, type LoadCommandsOptions, type LoadConfigOptions, type LoadKeybindingsOptions, type LoadKeybindingsResult, type LoadRolesOptions, type LoadedPlugin, type LockHolder, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_POST_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_ENTRIES_FILE, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, type ManagedPolicy, ManagedPolicyError, type ManagedSettings, ManagedSettingsSchema, type MarketplaceCatalog, type MarketplaceCategory, type MarketplaceDisplay, type MarketplaceDisplayField, type MarketplaceEntry, type MarketplaceOutcome, type MarketplaceRecord, type MarketplaceStatus, type MemoryConfig, type MemoryEntry, type MemoryImportRecord, type MemoryImportResult, MemoryManager, type MemoryManagerOptions, type MemoryNotice, type MemoryRemoval, type MemorySearchResult, type MemoryTarget, type MemoryWriteResult, type MentionPart, type MentionResolution, type Message, type MessageHit, type MessageRecord, type MessageSurface, type ModelCapability, ModelFailureTracker, type ModelMetaEntry, type ModelPricing, type MultimodalPolicy, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, type NonInteractiveProbe, type OsTaskSnapshot, type OutputGuard, PACK_EXCLUDES, PERMISSION_AUDIT_CODES, PLUGIN_ICON_MAX_BYTES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, type PackFile, type PackLimits, type PackOutcome, type PackPlan, type PackPlanOutcome, type PermissionAuditCode, type PermissionAuditEntry, type PermissionAuditOutcome, type PermissionAuditScope, type PermissionAuditSnapshot, PermissionManager, type PersistFailureKind, type PersistedBudget, type PersistedPart, type PlanApprovalFn, type PlanEnterResult, type PlanModeDeps, PlanModeState, type PlanSettlement, type PluginAbout, type PluginArtifacts, type PluginAuthor, PluginAuthorSchema, type PluginCommandDir, type PluginCounts, type PluginDoc, type PluginHookFile, type PluginInventory, type PluginLoadOptions, type PluginManifest, type PluginPathRef, type PluginRecord, type PluginScope, type PluginSettingsFile, type PluginSourceType, type PluginStateReadOutcome, PluginStore, type PluginVisibility, type PolicyDirSpec, type PolicyDirsSummary, type PolicyLoadOutcome, type PolicyRule, PolicyRuleSchema, type PricingAt, type PricingOverride, type ProjectContext, type ProjectExtensionInventory, ProviderError, ProviderRouter, type ProviderStreamChunk, type ProviderToolCall$1 as ProviderToolCall, type ProviderWarning, type PruneResult, type QuestionAskFn, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, type ReadMessagesInput, type ReadMessagesOutput, type RecentWorkspace, RecentWorkspaces, type RecordRunInput, type RecorderOptions, type RecoveryAction, type RegisterInput, type RegisterOutcome, type RegistrarOptions, type RegistrationPatch, type RepairOutcome, type ResolveMentionsOptions, type ResolveReferenceInput, type ResolveSettingsOptions, type ResolvedCompression, type ResolvedReference, type ResolvedSource, type ResponseFormat, type RestorePartsOptions, type RewindAction, type RewindDrift, type RewindFilePlan, type RewindOptions, type RewindOutcome, type RewindPreview, type RoleCreateFailure, type RoleCreateInput, type RoleCreateOptions, type RoleCreateOutcome, type RoleLoadOutcome, type RoleLoadSummary, type RoleMergeNotice, type RoleMergeResult, type RoleRemoveFailure, type RoleRemoveOptions, type RoleRemoveOutcome, type RoleScope, type RuleMatch, type RuleSuggestion, type RuleVerdict, type RunCodeFailure, type RunCodeInput, type RunCodeOutcome, type RunCodeToolDeps, type RunEstimateInput, type RunInput, type RunResult, type RunningSchedule, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SEND_MESSAGE_TOOL, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_ASSET_DIRS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_FILE, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, type ScalarKey, type ScanInstructionsOptions, type ScheduleArgvRefusal, type ScheduleArgvResult, type ScheduleArgvWarning, type ScheduleBackend, type ScheduleCliEntry, type ScheduleDraftDeps, type ScheduleDrift, type ScheduleDriftKind, type ScheduleImportInput, type ScheduleInstallForm, type ScheduleIssue, type ScheduleIssueCode, type ScheduleLock, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, type ScheduleValidation, type SchemaExport, type SearchHit, type SearchInput, type SearchMessagesInput, type SearchMessagesOutput, type SearchOutput, type SearchSessionsInput, type SelectionDeps, type SelfConnector, type SelfIndexInput, type SelfSettings, type Session, type SessionCandidate, type SessionHit, SessionManager, type SessionMentionDeps, type SessionMeta, SessionQueries, SessionReferences, type SessionSearchDeps, type SessionStats, type SessionSurface, type SessionTrace, type SessionVisibility, type SessionVisibilityInput, type SettingApply, type SettingApplyFacts, type SettingChain, type SettingFutile, type SettingLayer, type SettingStep, type SettingValue, type SettingValueKind, type SettingWriteFailure, type SettingWriteKey, type SettingWriteLayer, type SettingWriteOutcome, type SettingWriteRequest, type SettingWriteTarget, type SettingWriteTargetsOptions, type SettingsFile, SettingsFileError, SettingsFileSchema, type SettingsLayer, type SettingsResolution, type ShadowFinding, type ShadowKind, type Skill, type SkillDirSource, type SkillDirSpec, type SkillDirsSummary, type SkillImportConflict, type SkillImportDone, type SkillImportEntry, type SkillImportFailure, type SkillImportForm, type SkillImportOptions, type SkillImportPreview, type SkillImportResult, type SkillIndexEntry, type SkillIndexOptions, type SkillIndexResidency, type SkillIndexStats, SkillLearner, type SkillLearningProvider, type SkillMeta, type SkillStats, SkillSystem, type SkillSystemOptions, type SkillViewDeps, type SkillWriteDeps, type SteerSource, type StoredAppearance, type SubAgentRunner, type SubTask, type SubToolCaller, type SummarizeOutput, type SurfaceMessage, type SystemPromptParams, type SystemPromptSegments, TEAM_TOOL_NAMES, TOOL_SEARCH_TOOL, TaskNoticeTracker, TeamRegistry, type TodoItem, type ToggleOutcome, type ToolCall, type ToolConflict, type ToolGate, type ToolNameFidelity, type ToolNameLookup, ToolRegistry, type ToolRevealState, type ToolScope, type ToolSearchDeps, type ToolTableCost, type TraceInput, type TraceNode, TrackerDB, type TrustDecision, type TrustGate, type TrustGateOptions, TrustManager, type TurnRecord, type TurnTiming, UNSETTABLE_USER_KEYS, type UpdateScheduleInput, type UtilityModelInfo, type ValidateScheduleInput, type VersionCheck, Workspace, type WorkspaceFiles, type WorkspaceIssue, type WorkspaceIssueKind, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, approvalEntryFor, approvalRootFor, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, capabilityOfRef, checkConfigValue, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, configKeyByPath, configKeyTable, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createBoardTools, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createPluginScope, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSendMessageTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTeamTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, discoverySpec, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, grantedPathsFor, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, hasUsableModel, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidInterval, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listEntryModels, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadModelEntries, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelEntriesPath, modelListCandidates, needsAllowlist, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, normalizeModelEntry, normalizeTrigger, offloadOldestImages, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelEntry, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readEntryKey, readImageSize, readMarketplaces, readPluginAbout, readPluginIcon, readPluginManifest, readPluginRecords, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, requestImageBytes, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, saveModelEntries, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, testModelEntry, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, unsetUserSetting, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writableConfigKeys, writeSettingValue };