@epoch-agent/core 2.7.0 → 3.0.0

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 +114 -5
  2. package/dist/index.d.ts +962 -168
  3. package/dist/index.js +1686 -674
  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, BackgroundTaskInfo, EpochToolCall, Telemetry, HookManager as HookManager$1, Lang, PermissionManager as PermissionManager$1, ApprovalRequest, ApprovalReply, AgentRoleScope, AgentRoleSource, EpochUserContent, EpochContentPart, AgentEvent, AgentRole, PermissionLevel, PlanProposal, ScheduleRunnerSpec, ScheduleBackendKind, ScheduleTrigger, OperationType, ScheduleAllowlist, ScheduleDefinition, ScheduleRunStatus, ScheduleRun, SchedulePendingApproval, DiagnosticSink, CustomCommandDef, ModelRef, ExpandedCommand, CustomCommandSource, ContextBreakdown, WireMcpConnector, 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, 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
  * 摘要调用的产出。
@@ -2834,6 +2930,28 @@ interface AgentConfig {
2834
2930
  * 而它们同名的那半个词已经让人查错过一次方向。
2835
2931
  */
2836
2932
  maxOutputTokens?: number;
2933
+ /**
2934
+ * 单次请求里**所有图片 base64 的合计**上限(字节,2026-10-28)。
2935
+ *
2936
+ * **缺省 = `DEFAULT_MAX_REQUEST_IMAGE_BYTES`(8 MiB,猜的)**,判据全文
2937
+ * (含「为什么这个数没有权威出处」)在 `provider/body-limit-cache.ts` 的文件头。
2938
+ *
2939
+ * ⚠️ 它是**字节**这条线,和 `contextLength`(token)是两个量纲 ——
2940
+ * 一张 2 MB 的截图在 token 账上只记一千出头,在字节账上是 2.7 MB。
2941
+ * 两条线都要有,因为只有文本那一半时 token 会先抓住,而只有图片时
2942
+ * token 那条线**永远抓不住**。
2943
+ *
2944
+ * 撞上一次之后,学到的值会在比它更小时顶替它(`effectiveImageBudget`)。
2945
+ */
2946
+ maxRequestImageBytes?: number;
2947
+ /**
2948
+ * 数据目录(`~/.epoch` 那一层),学到的字节上限存进它的 `cache/` 下。
2949
+ *
2950
+ * 不给就**不学、也不读**历史学习值 —— 退回只用配置值/缺省值。理由同
2951
+ * `MultimodalPolicy.homeDir`:这一层够不着「这台机器的数据目录在哪」,
2952
+ * 自己猜一个 `homedir()` 在测试和多 profile 场景下会写错地方。
2953
+ */
2954
+ homeDir?: string;
2837
2955
  /**
2838
2956
  * 预先算好的项目上下文(含工作区信任判定和已加载的指令文件)。
2839
2957
  *
@@ -3225,6 +3343,15 @@ interface HistoryEntry {
3225
3343
  role: 'user' | 'assistant';
3226
3344
  content: string;
3227
3345
  parts?: EpochContentPart[];
3346
+ /**
3347
+ * 这一轮的思考原文(2026-10-28)。
3348
+ *
3349
+ * ⚠️ **必须能跟着历史穿过来。** 原来它只落在会话库的 `messages.reasoning` 列上
3350
+ * 给人看,`loadHistory()` 一个字都不带 —— 于是刷新页面 / `--resume` 之后下一条
3351
+ * 消息发出去,DeepSeek 看不见上一轮的 reasoning_content,当场 400。
3352
+ * 判据在 registry 的 `CARRIES_REASONING_BACK` 上。
3353
+ */
3354
+ reasoning?: string;
3228
3355
  }
3229
3356
  interface RunInput {
3230
3357
  /** 纯文本或多模态部件数组。两种形态 AI SDK 都原生接受,不需要在这里分支 */
@@ -3272,6 +3399,26 @@ interface RunInput {
3272
3399
  * 不给就是今天的行为:这一轮跑完才轮到下一条消息。
3273
3400
  */
3274
3401
  steer?: SteerSource;
3402
+ /**
3403
+ * 这一轮每产出一个事件就旁路回调一次(2026-10-27)。
3404
+ *
3405
+ * ## 为什么是在 `run()` 上开个口,而不是改成让调用方用 `stream()`
3406
+ *
3407
+ * `run()` 和 `stream()` 走的是同一个 `runTurns()`,但 `stream()` 不回
3408
+ * `RunResult`(它的 `turns` 是内部数组,调用方拿不到)。子 agent 那一路
3409
+ * 既需要完整结果(它的汇总要交给父 agent),又需要知道「跑到哪一步了」
3410
+ * (后台作业的 `task_output` 一直只写着「还没跑完」)。改 `stream()` 的返回
3411
+ * 等于把两个生成器合并,而那是另一笔账。
3412
+ *
3413
+ * ## 默认什么都不做
3414
+ *
3415
+ * 不给这一格 = 改造前的行为,引擎一处都不多花。
3416
+ *
3417
+ * ## ⚠️ 回调里不要抛
3418
+ *
3419
+ * 它住在引擎的事件循环里,抛出来会把整轮掀翻。要防护自己在回调里做完。
3420
+ */
3421
+ onEvent?: (ev: AgentEvent) => void;
3275
3422
  }
3276
3423
  /**
3277
3424
  * 「正在跑的这一轮里,用户又说话了吗」——**由宿主实现,引擎只来拉**。
@@ -3347,37 +3494,39 @@ interface AbortLike {
3347
3494
  *
3348
3495
  * LLM 调用 delegate_task 工具,派发子任务给独立 Agent 并行执行。
3349
3496
  *
3350
- * ## 后台子 agent:接口留着了,**这一轮没实现**(方案 49 PR-3)
3497
+ * ## 两种派法:同步等(默认)和后台作业(`background: true`)
3351
3498
  *
3352
- * 今天 `delegate_task` 是**同步等**的:`delegate()` 一路 `await` 到所有子任务
3353
- * 结束才返回,于是父 agent 那一轮就卡在这儿。方案 49 §3.1 把这一格列成
3354
- * 「作业种类之三:后台子 agent —— **没有**(`delegate_task` 是同步等的)」,
3355
- * 而 §3.3 的判据是「现在统一,不等它」:等到真做后台子 agent 时再统一,
3356
- * 代价是那时要同时改三处已经发出去的工具。
3499
+ * 默认仍然是**同步等**:`delegate()` 一路 `await` 到所有子任务结束才返回,
3500
+ * 这一条一个字没改 —— 大多数委托十几次工具调用就回来了,模型拿到结论直接接着
3501
+ * 往下干,是它要的形状。
3357
3502
  *
3358
- * PR-3 把作业表下沉到了 [infra/jobs.ts](../../../infra/src/jobs.ts),
3359
- * 那张表的 `JobKind` 里**已经有 `'agent'` 这一档**,接法因此是确定的:
3503
+ * `background: true` 是 2026-10-26 接上的第三类作业(`JobKind = 'agent'`):
3504
+
3505
+ * - 派完**立刻返回作业 id**(`a1`),父回合接着干别的
3506
+ * - 作业登记在**派活的那个会话**名下(`task_*` 那道按会话分区的墙靠它)
3507
+ * - 子任务结束 / 失败 / 被杀时,`task_output` 读得到汇总,下一轮开头
3508
+ * `TaskNoticeTracker` 会挂一条「a1 已结束」的 `user` note
3509
+ * - **父回合的终止不再打到它身上**:用户按停只停父回合,后台子任务继续跑
3510
+ * (四家竞品一致:Claude Code 在后台 agent 的 abort controller 上专门留了
3511
+ * 一句注释说明它不挂父链)。要收掉它只有两条路 —— `task_stop a1`,或者
3512
+ * 进程退出
3360
3513
  *
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
- * ```
3514
+ * ### 为什么不学另外三家「一律后台」
3515
+ *
3516
+ * 那三家(codex / hermes-agent / deepseek-harness)没有这个开关,它们的
3517
+ * 委托入口本来就**只有后台一种**。我们保留默认同步,判据是我们的宿主没有
3518
+ * 它们那套推送:`epoch -p` 一轮结束就是结束,TUI 空转时也不会自己开口。
3519
+ * 一轮里「派出去然后等结论」仍然是最常见的形态,把它也改成异步,模型得
3520
+ * 每派一次就 `task_wait` 一次,等于白多一轮往返。
3372
3521
  *
3373
- * 之后 `task_list` / `task_output` / `task_wait` / `task_stop`
3374
- * **一行都不用改**就认得它(那四个已经是 kind 无关的了),`epoch doctor` 的
3375
- * 作业一节同理。要补的只有 `job-notice.ts` 的 `KIND_MARK` ——
3376
- * 那是一张 `Record<JobKind, string>`,漏了会在编译期报缺键。
3522
+ * ### 作业的 `stop` 收的是控制器,不是进程
3377
3523
  *
3378
- * ⚠️ **刻意不在这里放一个空壳**(没有 `startBackground()` 占位):一个没有调用方
3379
- * 的入口会被下一个人当成「已经能用了」,而它的权限口径(后台派活要不要重新过
3380
- * `delegate_task` 那一档 `command`)还没判过。真做那一轮要答的就是这个问题。
3524
+ * `JobSpec.stop` 收的是一个 `AbortController.abort()` —— 子 agent 不是子进程,
3525
+ * 退出时那张按 pid 记的表摸不到它。所以「进程退出时收干净」这件事在这一族上
3526
+ * 是靠**进程一起没了**实现的,不是靠 `release()`。
3527
+ *
3528
+ * ⚠️ **`detail` 里只放 controller**:`task_*` 那四个工具不读它,`jobSandbox()`
3529
+ * 读的那一格(`sandbox`)在这一族上不存在,于是它照旧返回 `undefined`。
3381
3530
  */
3382
3531
 
3383
3532
  /** 子任务定义 */
@@ -3399,6 +3548,11 @@ interface DelegateArgs {
3399
3548
  max_concurrent?: number;
3400
3549
  /** `goal` 简写形式的角色名 */
3401
3550
  role?: string;
3551
+ /**
3552
+ * `true` = 派到后台立刻返回(作业 id 形如 `a1`)。缺省 `false` = 同步等,
3553
+ * 与改造前逐字节相同。判据与取舍见文件头「两种派法」那一节。
3554
+ */
3555
+ background?: boolean;
3402
3556
  }
3403
3557
  /** 委托结果 */
3404
3558
  interface DelegateTaskResult {
@@ -3413,6 +3567,16 @@ interface DelegateTaskResult {
3413
3567
  }>;
3414
3568
  total_duration: number;
3415
3569
  }
3570
+ /** `delegate()` 的返回值。后台那一档多两个字段,同步那一档不受影响 */
3571
+ interface DelegateOutcome {
3572
+ success: boolean;
3573
+ output: string;
3574
+ duration: number;
3575
+ /** 只有后台派活才有 —— 模型拿它去 `task_output` / `task_wait` / `task_stop` */
3576
+ jobId?: string;
3577
+ /** 父回合是不是被放走了(后台那一档)。**不是**「有没有作业」 */
3578
+ background?: boolean;
3579
+ }
3416
3580
  /**
3417
3581
  * 子 Agent 被禁止的工具 [Hermes]。
3418
3582
  *
@@ -3426,6 +3590,12 @@ declare const BLOCKED_TOOLS: readonly string[];
3426
3590
  interface DelegateConfig {
3427
3591
  max_concurrent_children: number;
3428
3592
  max_spawn_depth: number;
3593
+ /**
3594
+ * 一个子任务最多跑多久(毫秒)。**`0` = 不限时**。
3595
+ *
3596
+ * 真源是 `config.delegate.taskTimeoutSec`(秒),在 `build.ts` 里折成毫秒;
3597
+ * 构造函数里那个 `900_000` 只给不经过配置的调用方(用例、嵌入宿主)。
3598
+ */
3429
3599
  task_timeout: number;
3430
3600
  }
3431
3601
  declare class DelegateManager {
@@ -3438,29 +3608,43 @@ declare class DelegateManager {
3438
3608
  *
3439
3609
  * @param parentSessionId 派活的那个会话(`ToolContext.sessionId`)。一路带到
3440
3610
  * `runner` 上,让子任务的花费和文件快照记到**它**头上而不是装配时那个会话
3611
+ * @param signal 父回合的终止信号。**只有同步那一档会挂到子任务上** ——
3612
+ * 后台派活的判据见文件头「两种派法」
3441
3613
  */
3442
- delegate(args: DelegateArgs, parentSessionId: string, signal?: AbortLike): Promise<{
3443
- success: boolean;
3444
- output: string;
3445
- duration: number;
3446
- }>;
3614
+ delegate(args: DelegateArgs, parentSessionId: string, signal?: AbortLike): Promise<DelegateOutcome>;
3615
+ /**
3616
+ * 把这一批登记成一格 `agent` 作业。
3617
+ *
3618
+ * 归**派活的那个会话**(不是子任务自己那个 `delegate-xxxx`):`task_*` 那道
3619
+ * 按会话分区的墙靠这一格 —— 记到子任务名下的话,父会话一结束就谁都看不见、
3620
+ * 也停不掉了。
3621
+ */
3622
+ private registerJob;
3447
3623
  private normalizeTasks;
3624
+ /**
3625
+ * 分批跑,每批最多 `maxConcurrent` 个。
3626
+ *
3627
+ * @param signal **整批**那个控制器的信号(不是父回合的)—— 每一格再各自
3628
+ * 派生一个,判据在 {@link runSingleTask} 上
3629
+ * @param onDone 每收一格回调一次。后台那一档用它往作业缓冲里写进度;
3630
+ * 同步那一档不传(写进去也没有人读 —— 那一档没有作业)
3631
+ */
3448
3632
  private runWithConcurrency;
3449
3633
  /**
3450
3634
  * 跑一个子任务。
3451
3635
  *
3452
3636
  * ## 每个子任务一个自己的 controller(2026-09-24)
3453
3637
  *
3454
- * 父回合的终止和这一格的超时**都打到它上面**,子 agent 拿到的是它的 signal。
3455
- * 原来两样都没有接:`sub.run()` 不带 signal,于是父会话按了终止,父回合要干等
3456
- * 所有子任务跑完(每个最多 `task_timeout`,一批一批串着等);而超时只是
3457
- * `Promise.race` 抢答 —— 父回合往下走了,子 agent 在后台接着跑、接着花钱,
3458
- * 屏幕上一个字都看不见。
3638
+ * 进来的 `signal` 是**整批**那个控制器的(`delegate()` 建的),每格再链一个
3639
+ * 自己的:这一格的超时只掐它自己,而 `task_stop` / 父回合终止打的是整批那一个,
3640
+ * 于是两件事都成立 —— 超时不会误伤兄弟,收批也不会漏掉哪一格。
3459
3641
  *
3460
- * 父 signal 是鸭子类型(`ToolContext.signal` 只保证有 `aborted`)时接不上监听,
3461
- * 只在开跑前判一次 —— 真宿主递下来的都是真 `AbortSignal`。
3642
+ * 父回合的终止是不是真的会打下来,取决于派法:同步那一档 `delegate()` 把
3643
+ * 父 signal 链到整批控制器上,后台那一档**不链**(判据在文件头)。
3462
3644
  */
3463
3645
  private runSingleTask;
3646
+ /** 进度那一行里的「谁、干了什么、多久」—— 和汇总标题同一套写法 */
3647
+ private describeResult;
3464
3648
  private buildSummary;
3465
3649
  }
3466
3650
 
@@ -3496,7 +3680,18 @@ interface SubAgentRunner {
3496
3680
  * 实现方要把它交给子 agent 的 `run()` —— 不交的话按了终止它照跑,判据在
3497
3681
  * `DelegateManager.runSingleTask` 上。可选只是为了不拆已有的调用方
3498
3682
  */
3499
- signal?: AbortSignal): Promise<{
3683
+ signal?: AbortSignal,
3684
+ /**
3685
+ * 子任务跑的过程中每完成一步回调一次(2026-10-27)。
3686
+ *
3687
+ * 收的是一行已经拼好的人话(`读文件 `src/a.ts``),不是事件对象 ——
3688
+ * 两个调用方(作业缓冲 / 未来的其他通道)要的是同一句话,而把事件形状
3689
+ * 漏上去等于让每个调用方重写一遍折行逻辑。
3690
+ *
3691
+ * **同步那一档目前不给**:父回合正卡在这个工具调用上,没有地方显示它。
3692
+ * 后台那一档接了 —— 那里的 `task_output` 在此之前只能看到「还没跑完」。
3693
+ */
3694
+ onProgress?: (line: string) => void): Promise<{
3500
3695
  text: string;
3501
3696
  error?: string;
3502
3697
  }>;
@@ -4019,13 +4214,20 @@ declare function createMemoryTool(memory: MemoryManager$1): EpochTool;
4019
4214
  *
4020
4215
  * ⚠️ **这一段 2026-08-28 之前写的是「`run_code` 的边界在权限管线,不在进程隔离」,
4021
4216
  * 那句话被 PR-3 判为假**(程序里 `import('node:fs')` 直通,权限管线整个被绕过)。
4022
- * 今天的口径是「一次 `run_code` = 一次任意本机代码执行,边界是审批那一下」,
4217
+ * 那时改成了「一次 `run_code` = 一次任意本机代码执行,边界是审批那一下」,
4023
4218
  * 全文在 [PR-3 记录 §二](../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr3.md)。
4024
4219
  *
4025
- * **但这个文件的立意不受影响**,而这一点值得说清:它跑在我们自己的进程里的一个
4026
- * worker 线程上,所以这四道上限守的是**「别把宿主拖死」**(死循环、吃内存、
4027
- * 一个写错的 `for` 调一万次工具),不是「别越权」。越权那一半今天由审批兜着。
4028
- * 也就是说下面这张表**仍然成立**,只是它列的不再是「多出来的全部能力」:
4220
+ * **2026-10-27 又收窄了一层**:`runner.ts` 给 worker 挂了 `--permission` +
4221
+ * 清空的 env(判据在 [policy.ts](./policy.ts) 的文件头)。所以今天的口径是
4222
+ * 「审批是边界,权限策略把程序限制在工作目录内、起不了子进程、看不见 env」。
4223
+ * ⚠️ **但下面的表仍然成立,它守的东西也没变** —— 这个文件管的是「别把宿主拖死」,
4224
+ * 不是「别越权」。权限模型不管死循环、不管吃内存、更不管
4225
+ * 「一个写错的 `for` 调一万次工具」,而这四格里没有一格能被 `--permission` 代替。
4226
+ *
4227
+ * 它跑在我们自己的进程里的一个 worker 线程上,所以这四道上限守的是
4228
+ * **「别把宿主拖死」**(死循环、吃内存、一个写错的 `for` 调一万次工具),
4229
+ * 不是「别越权」。越权那一半今天由审批 + 权限策略两层兜着 ——
4230
+ * 而下面这张表列的是**权限策略一格都盖不到**的那部分:
4029
4231
  *
4030
4232
  * | 多出来的能力 | 这里的哪一格 |
4031
4233
  * | --------------------------------- | ----------------------------------------- |
@@ -4114,14 +4316,14 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4114
4316
  * | ------------ | ---------------------------------------------- | ---------------------------------------- |
4115
4317
  * | 干什么 | 算东西、处理数据 | **编排工具调用** |
4116
4318
  * | 能不能碰宿主 | **不能** —— 写不出沙箱、默认断网、读不到 `$HOME` | **必须能** —— 它的全部意义就是回调工具 |
4117
- * | 隔离级别 | macOS 上 OS 强制(Seatbelt) | **一点都没有**,跑在我们自己的 worker 线程里 |
4118
- * | 安全模型 | 沙箱是边界 | **审批是边界**(见下一节,2026-08-28 拍板)|
4319
+ * | 隔离级别 | macOS 上 OS 强制(Seatbelt) | worker 线程 + Node 权限模型(`code-mode/policy.ts`) |
4320
+ * | 安全模型 | 沙箱是边界 | **审批是边界**;权限策略只收窄「程序能碰什么」 |
4119
4321
  *
4120
4322
  * 所以 `sandbox/sandbox.ts` 的那整套设计(沙箱目录在 tmpdir、`~/.epoch` 在读黑名单
4121
4323
  * 里)对这个工具**一条都不适用** —— 一个连 `~/.epoch` 都读不到的程序怎么调
4122
4324
  * `file_read`。**别把两个工具的措辞「统一」**,它们说的是两条不同的路。
4123
4325
  *
4124
- * ## ⚠️ 它的边界是**审批**,不是隔离(2026-08-28 拍板,方案 51 PR-3)
4326
+ * ## ⚠️ 边界是**审批**;权限策略只是让那次审批的语义变准
4125
4327
  *
4126
4328
  * 这一段 2026-08-28 之前写的是「一个 `run_code` 程序能做的事**逐字等于**模型
4127
4329
  * 一个个调那些工具能做的事」。**那句话是假的**,而且是被实测推翻的:程序跑在
@@ -4132,16 +4334,36 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4132
4334
  * [PR-4 记录](../../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr4.md) §三 第 3 / 4 条,
4133
4335
  * 那不是理论上的洞,是模型自己找到并用上的路。
4134
4336
  *
4135
- * PR-3 **判定不做**引擎隔离(三个选项和判据在
4337
+ * PR-3 那轮**判定不做**引擎隔离(三个选项和判据在
4136
4338
  * [PR-3 记录](../../../../../docs/verify/VERIFY_RECORD-51-code-mode-pr3.md) §二),
4137
4339
  * 改成把边界说准:
4138
4340
  *
4139
4341
  * > **一次 `run_code` = 一次任意本机代码执行,和 `terminal` 同一档。
4140
4342
  * > 边界是审批那一下 —— 用户看到程序全文,批的是「这段程序可以跑」。**
4141
4343
  *
4142
- * 于是这个工具的 `operation: 'command'` 和 `describeTarget` = 程序全文
4143
- * **从实现细节升级成了承重墙**,各有一条会红的用例钉着(`tool-executor.test.ts`
4144
- * 的「run_code 的审批对象是程序全文」、tui 的「多行 / 超宽 target」)。
4344
+ * ## 2026-10-27:那句话里「一点隔离都没有」的一半补上了
4345
+ *
4346
+ * PR-3 当时只有两个选项:preload 拦 `node:fs`(实测无效,`Module._load` 只盖 CJS),
4347
+ * 或者上 OS 隔离(要改成子进程,动整个进程模型)。两个都不成立,所以「不做」
4348
+ * 在那时是对的。
4349
+ *
4350
+ * 第三条路是 worker 自己的 `execArgv` 挂 `--permission`:进程模型一个字不动,
4351
+ * 策略只作用在那一个线程上。实测数据和它**没**拦住什么(网络、env)全部在
4352
+ * [policy.ts](../../code-mode/policy.ts) 的文件头。
4353
+ *
4354
+ * 于是口径变成两句:
4355
+ *
4356
+ * > **审批仍然是边界**;权限策略把「一次任意本机代码执行」收窄成
4357
+ * > 「工作目录内读写、起不了子进程、看不见环境变量」。
4358
+ *
4359
+ * 剩下那个没被收窄的是**网络**,而这里不假装它变了:权限模型不管网络,
4360
+ * 所以一段恶意程序照样能把读到的内容发出去。想在 `bypass` 档 / headless
4361
+ * 预授权下开着它,判断照旧同 `docs/TOOLS.md` 那句 —— 「那一眼是唯一的边界」。
4362
+ *
4363
+ * `operation: 'command'` 和 `describeTarget` = 程序全文仍然是承重墙,
4364
+ * 各有一条会红的用例钉着(`tool-executor.test.ts` 的「run_code 的审批对象是程序全文」、
4365
+ * tui 的「多行 / 超宽 target」)。加权限策略**不改**这一格:策略算错时
4366
+ * 程序起不来,而审批框里看到的仍然是全文。
4145
4367
  * 落进 git 的 `permissions.allow` 里也一条 `run_code` 规则都不许有
4146
4368
  * (`permission/rule-validate.ts` 的 `NEVER_ALLOW_TOOL_NAMES`)。
4147
4369
  *
@@ -4184,6 +4406,9 @@ declare const CODE_MODE_LIMITS: CodeModeLimits;
4184
4406
  * 断网要么靠子进程 + 沙箱(PR-3 记录 §二 选项 C,重开条件在 §五末尾),要么不做。
4185
4407
  * 原来的理由是「它想联网就调 `web_fetch`,那条路有 SSRF 三层防护」——
4186
4408
  * 那句话仍然成立,只是它**描述不了今天的现状**。
4409
+ * ⚠️ **2026-10-27 加权限策略之后再确认一遍:它管不了网络。** 实测带着
4410
+ * `--permission` 跑,`fetch` 照样通 —— 权限模型只管文件系统、子进程、worker、
4411
+ * 原生插件这四类。别把「加了策略」读成「断网了」。
4187
4412
  *
4188
4413
  * ## ⚠️ 三条边界,别在读这个文件时以为方案已经全落了
4189
4414
  *
@@ -4761,8 +4986,6 @@ type ScheduleIssueCode =
4761
4986
  | 'permission-unknown'
4762
4987
  /** 工作目录不存在 */
4763
4988
  | 'work-dir-missing'
4764
- /** `maxBudgetUsd` 必填且必须为正。**这是刻意的摩擦**,见协议上那段 */
4765
- | 'budget-missing'
4766
4989
  /** `maxTurns` 必须是正整数 */
4767
4990
  | 'max-turns-invalid'
4768
4991
  /** 墙钟超时必须是正数 */
@@ -4793,18 +5016,18 @@ type ScheduleIssueCode =
4793
5016
  /** 清单里有一条规则写错了(`detail` 是 `rule-validate` 给的位置与原因) */
4794
5017
  | 'rule-invalid'
4795
5018
  /**
4796
- * 「跳过全部检查」+ 空工作区。
5019
+ * 「完全访问」+ 空工作区。
4797
5020
  *
4798
5021
  * ⚠️ **2026-09-18 起没有产出点了** —— 空着落到的是 `~/.epoch/automation/<id>/`,
4799
5022
  * 一个确定、专属、隔离的目录,不是「不确定的 cwd」。判据全文在
4800
5023
  * `validate.ts` 的 `bypassIssues()` 第 1 条上。保留这条 code 是为了不破坏协议。
4801
5024
  */
4802
5025
  | 'bypass-needs-workdir'
4803
- /** 「跳过全部检查」的工作区指到了家目录本身 —— 那和不绑定没有区别 */
5026
+ /** 「完全访问」的工作区指到了家目录本身 —— 那和不绑定没有区别 */
4804
5027
  | 'bypass-workdir-too-broad'
4805
- /** 「跳过全部检查」缺三把尺子中的某一把 */
5028
+ /** 「完全访问」缺尺子 —— 今天只剩超时那一把 */
4806
5029
  | 'bypass-needs-limits'
4807
- /** 「跳过全部检查」没有拿到那次二次确认 */
5030
+ /** 「完全访问」没有拿到那次二次确认 */
4808
5031
  | 'bypass-not-confirmed';
4809
5032
  /** 全集。CLI 侧靠它穷举文案,少一条编译不过 */
4810
5033
  declare const SCHEDULE_ISSUE_CODES: readonly ScheduleIssueCode[];
@@ -4843,7 +5066,6 @@ interface ScheduleImportInput {
4843
5066
  allowOperations: readonly OperationType[];
4844
5067
  allowRules: readonly string[];
4845
5068
  maxTurns: number;
4846
- maxBudgetUsd: number;
4847
5069
  timeoutMs: number;
4848
5070
  showInSessionList?: boolean;
4849
5071
  trigger: ScheduleTrigger;
@@ -4874,8 +5096,6 @@ interface CreateScheduleInput {
4874
5096
  allowOperations?: readonly OperationType[];
4875
5097
  allowRules?: readonly string[];
4876
5098
  maxTurns?: number;
4877
- /** **必填**,没有默认值 */
4878
- maxBudgetUsd: number;
4879
5099
  timeoutMs?: number;
4880
5100
  /**
4881
5101
  * 跑完的会话直接进会话列表吗。缺省 `false` = 收起来(归档),
@@ -4922,8 +5142,7 @@ interface CreateScheduleInput {
4922
5142
  * `ScheduleControl.update` 解析掉;**`ScheduleStore.update` 的入参不收它**
4923
5143
  * (同 `create(input & { workDir: string })` 那条契约,收窄在编译期)。
4924
5144
  */
4925
- type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'maxBudgetUsd' | 'workDir' | 'model' | 'role' | 'startDate' | 'endDate'>> & {
4926
- maxBudgetUsd?: number;
5145
+ type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'workDir' | 'model' | 'role' | 'startDate' | 'endDate'>> & {
4927
5146
  /** `null` = 不绑工作区了 → 落回 `~/.epoch/automation/<id>/`,**不是**删掉这一格 */
4928
5147
  workDir?: string | null;
4929
5148
  model?: string | null;
@@ -4933,74 +5152,57 @@ type UpdateScheduleInput = Partial<Omit<CreateScheduleInput, 'maxBudgetUsd' | 'w
4933
5152
  endDate?: string | null;
4934
5153
  };
4935
5154
  /**
4936
- * 三把尺子的缺省值 —— **「调 API / CLI 没给这一格时引擎用什么」**。
5155
+ * 两把尺子的缺省值 —— **「调 API / CLI 没给这一格时引擎用什么」**。
4937
5156
  *
4938
- * ## ⚠️ `maxBudgetUsd` **不在这里**,而 2026-09-18 之后这句话更要紧了
5157
+ * ## ⚠️ 2026-10-29:只剩轮次和超时(预算那一把整个撤了)
4939
5158
  *
4940
- * 它没有引擎兜底:没给就是一条 `budget-required`(网线上 400,CLI 上必给 `--budget`)。
5159
+ * 这里原来还挂着一段「预算不在这份缺省里,因为一个假默认会让『一觉醒来 40
5160
+ * 美元』变成我们的错」。那次撤掉的不只是那一段注释,而是**那一格本身**:
5161
+ * `maxBudgetUsd` 已经从契约、库、网线、CLI、表单上全部退场,判据在
5162
+ * `validate.ts` 的 `bypassIssues()` 上。
4941
5163
  *
4942
- * 那一天新建**表单**上它开始预填 200 了,而那是**另一份缺省**
4943
- * (protocol 的 `WireScheduleDefaults.maxBudgetUsd`,判据全文在那个接口上):
4944
- * 一份说「表单打开时格子里印什么」,一份说「没给值时引擎用什么」。
4945
- * 把 200 抄进这一份的表现是**网线那一道闸整个消失** —— 任何一个不经表单的调用方
4946
- * 从此都能建出一条没人想过预算的任务,而那正是这一格的缺席拦着的事。
5164
+ * 于是这一份和界面预填那一份(protocol 的 `WireScheduleDefaults`)**不再有
5165
+ * 任何区别** —— 两份都只是「轮次 + 超时」。
4947
5166
  */
4948
5167
  declare const SCHEDULE_DEFAULTS: {
4949
5168
  /**
4950
- * 和交互缺省**同一个常量**(2026-09-04,原来这儿是自己的一个 `20`)。
5169
+ * 轮次上限。**`0` = 不限**(`UNLIMITED_TURNS`)。
4951
5170
  *
4952
- * 那个 20 的理由写的是「无人看着的一轮不该比有人看着的更长」。它不成立了 ——
4953
- * 它当初拦的是「跑飞了没人按 Esc」,而这一层真正拦住那件事的是 `maxBudgetUsd`:
4954
- * 它在这套功能里**必填**(所以它不在这份缺省里),而钱是比轮次准的尺子。
4955
- * 用轮次替它把无人值守的活压到 20,拦掉的绝大多数不是跑飞,是「这个任务本来
4956
- * 就要 60 轮」—— 而那种失败要等到第二天 9 点才看得见(同下面
5171
+ * ## 为什么缺省是「不限」,而不是一个保守的数
5172
+ *
5173
+ * 这一格原来是自己的一个 `20`,理由写的是「无人看着的一轮不该比有人看着的
5174
+ * 更长」。它不成立了 —— 它当初拦的是「跑飞了没人按 Esc」,而用轮次替它把
5175
+ * 无人值守的活压到 20,拦掉的绝大多数不是跑飞,是「这个任务本来就要 60 轮」
5176
+ * —— 而那种失败要等到第二天 9 点才看得见(同下面
4957
5177
  * `SCHEDULE_DEFAULT_ALLOWLIST` 那节的判据)。
4958
5178
  *
4959
5179
  * ⚠️ 这一份是**没有配置在手时的兜底**。真正拿着用户那格 `maxTurns` 的地方是
4960
5180
  * `server/src/schedule/handlers.ts` 的 `defaultsOf()` —— 表单预填走它,
4961
5181
  * 所以用户把设置里那个数改成 500,新建任务那一格就预填 500。
4962
5182
  *
4963
- * ⚠️ **2026-09-06 起这个缺省是「不设上限」**(`UNLIMITED_TURNS`),因为上面那条
4964
- * 判据跟着交互缺省一起走到了头:既然轮次拦掉的绝大多数不是跑飞,那把它当无人
4965
- * 值守的兜底闸门就是**拿错了尺子**。这条路上兜住跑飞的仍然是 `maxBudgetUsd`
4966
- * (必填,所以它不在这份缺省里)和 `timeoutMs`。要一道轮次闸门的人在新建任务
5183
+ * ⚠️ **2026-09-06 起这个缺省是「不设上限」**(`UNLIMITED_TURNS`),判据同上。
5184
+ * 🔴 **2026-10-29 起这条路真正兜得住「跑飞」的只剩下面那一个 `timeoutMs`**
5185
+ * —— 预算那把尺子没了,而这一格缺省是 0。要一道轮次闸门的人在新建任务
4967
5186
  * 那一格自己填一个数。
4968
5187
  */
4969
5188
  readonly maxTurns: 0;
4970
5189
  /**
4971
- * 墙钟超时。唯一能拦住「一条命令挂住了」的那把尺子 —— 前两把在那种情形下
4972
- * 都不增长(轮次不动、花费不动)。
5190
+ * 墙钟超时。**无人值守路上唯一兜得住「跑飞」的那把尺子**(2026-10-29 起)。
5191
+ *
5192
+ * 它要拦两种前一把拦不住的东西:一条 `terminal` 命令挂住了(那种情况下轮次
5193
+ * 不增长),以及一个每轮都很便宜、但一轮接一轮跑下去的循环。
4973
5194
  *
4974
5195
  * ⚠️ **2026-09-18 从 15 分钟抬到 60 分钟**(用户定的)。15 分钟那条线当初是
4975
5196
  * 照着「一次巡检」估的,而它砍掉的不是跑飞,是**本来就要跑久的那种任务**:
4976
5197
  * 一条被这把尺子掐断的运行和跑飞长得一模一样(都是 `timeout`),
4977
5198
  * 而定时任务的反馈回路按天算 —— 第二天早上看到的只有「超时了」三个字。
4978
5199
  *
4979
- * 兜住真正跑飞的是必填的 `maxBudgetUsd`(钱是比时间准的尺子),
4980
- * 判据同上面 `maxTurns` 那一段。
4981
- *
4982
5200
  * ⚠️ 这个数**两条路共用**(CLI 不给 `--timeout`、表单预填,都读它)——
4983
5201
  * 在服务端那侧另写一份的表现,`runtime/schedule/control.ts` 那段注释记着:
4984
5202
  * 「表单预填 200、直接调这个 API 却得到 20」。
4985
5203
  */
4986
5204
  readonly timeoutMs: number;
4987
5205
  };
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
5206
  /**
5005
5207
  * 新建任务时预填的最小清单(方案 45 §3.2 的落点)。
5006
5208
  *
@@ -5326,8 +5528,10 @@ declare class ScheduleRegistrar {
5326
5528
  *
5327
5529
  * 1. `validateSchedule()` 照旧跑,红了就**一个字都不写**(半条任务比没有更糟);
5328
5530
  * 2. 「无人值守下跑不出结果」的那两族措辞照旧**拒收**(`unattendedIssues`);
5329
- * 3. `bypass` 档照旧要 `SCHEDULE_FORM_BUDGET_USD` 的预算和正的超时
5330
- * (`bypassIssues` 的 `bypass-needs-limits`),而工作目录不许宽到等于没绑。
5531
+ * 3. `bypass` 档照旧要**正的超时**(`bypassIssues` 的 `bypass-needs-limits`),
5532
+ * 而工作目录不许宽到等于没绑。
5533
+ * ⚠️ **2026-10-29 起这条尺子只剩超时**:那一刻那条第 2 条里的「三把尺子」
5534
+ * 改成了「超时 + 轮次(而轮次缺省不限)」—— 判据在 `validate.ts` 上。
5331
5535
  *
5332
5536
  * ## ⚠️ 有一档仍然降级成提案:`plan`(只读调研模式)
5333
5537
  *
@@ -5563,12 +5767,24 @@ declare function resolveSessionVisibility(input: SessionVisibilityInput): Sessio
5563
5767
  * ## 为什么这值得一个工具
5564
5768
  *
5565
5769
  * `delegate_task` 派出去的子 agent 有**独立的 sessionId**,`fork()` 出来的会话
5566
- * 也有 `parent_id`。于是「刚才那个子 agent 到底干了什么」今天无从查起:
5567
- * 它的结论回来了(一段 summary),但它中间读了哪些文件、试过哪些路、
5568
- * 在哪一步拐弯,全都在另一段会话里 —— 而模型手上连那段会话的 id 都没有。
5770
+ * 也有 `parent_id`。于是「刚才那个子 agent 到底干了什么」可查:它的结论回来了
5771
+ * (一段 summary),而它中间读了哪些文件、试过哪些路、在哪一步拐弯,
5772
+ * 全在另一段会话里。
5569
5773
  *
5570
5774
  * 这一份给的就是那些 id:拿到之后接 `session_messages_search` 就能进去看。
5571
5775
  *
5776
+ * ## ⚠️ 2026-10-27 之前这段说明是假话
5777
+ *
5778
+ * 那时子 agent 的过程**从来没落过盘**:`delegate-xxxxxxxx` 只是个临时字符串,
5779
+ * `sessions` 表里根本没有它那一行。于是上面那句话(以及 `docs/TOOLS.md` 里
5780
+ * 同一句)描述的是一个不存在的能力 —— 模型照着描述去查,查回来是空的,
5781
+ * 而它没有办法分辨「真没有」和「没存下来」。
5782
+ *
5783
+ * 修法不是改文案:`subRunner` 跑完之后把这一轮按步折下来落进库,并写上
5784
+ * `parent_id`(判据在 `runtime/src/session-factory.ts` 的 `persistDelegateTurn` 上)。
5785
+ * 那段会话的 `source` 是 `delegate`,所以它不会出现在「接着哪一段聊」的列表里
5786
+ * —— 列表那一边有 `includeDelegate` 这个开关,判据在 `DELEGATE_SESSION_SOURCE` 上。
5787
+ *
5572
5788
  * ## 为什么不长在 [authorization.ts](./authorization.js) 里
5573
5789
  *
5574
5790
  * 那边的 `lineageOf()` 也走 `parent_id`,但它要的是**一串 id**(拼进 WHERE),
@@ -5879,9 +6095,11 @@ declare class SessionQueries {
5879
6095
  * 在这一步只需要判断「哪段会话值得进去看」。真要看细节,下一步是
5880
6096
  * `session_messages_search`。
5881
6097
  *
5882
- * 两张 FTS 索引的分工照搬 `SessionDB.search()`:先走 `porter unicode61`
5883
- * 那张,一条都没有再退到 trigram。**中文靠的就是 trigram** —— unicode61
5884
- * 会把一整串中文切成一个 token,「前导」搜不到「前导导致的」。
6098
+ * 两张 FTS 索引的分工照搬 `SessionDB.search()`:**合并,不是 fallback**
6099
+ * (2026-10-22 起;判据在 [fts.ts](./fts.ts) 的 `mergeRankedRows`)。
6100
+ * **中文靠的就是 trigram** —— unicode61 会把一整串中文切成一个 token,
6101
+ * 「前导」搜不到「前导导致的」;而只要同一批候选里有一处中文被标点切成独立整词,
6102
+ * porter 就非空,旧写法于是把 trigram 的答案整个丢掉。
5885
6103
  */
5886
6104
  searchSessions(input: SearchSessionsInput): SessionHit[];
5887
6105
  /**
@@ -5950,6 +6168,18 @@ declare class SessionQueries {
5950
6168
  * ⚠️ **没有 `AND m.active = 1`,而且不许加**(见文件头)。
5951
6169
  */
5952
6170
  private contentRows;
6171
+ /**
6172
+ * 两张 FTS 索引的结果**合并**成一份候选 —— 这一族里唯一一处「先查哪张、怎么拼」。
6173
+ *
6174
+ * 抽出来是因为跨会话({@link contentRows})与会话内({@link messageRows})那两条
6175
+ * SQL 除了 `WHERE` 和参数之外逐字相同,而「是不是 fallback」这件事**必须两边一样**
6176
+ * —— 只改一边,模型会看到「同一次检索里跨会话找得到、钻进那段会话反而找不到」。
6177
+ *
6178
+ * 顺序与去重的判据在 [fts.ts](./fts.ts) 的 `mergeRankedRows`,这里只负责三件事:
6179
+ * 拼 MATCH 表达式、在 {@link belowTrigramFloor} 时干脆不查 trigram(那张索引对
6180
+ * <3 字符的词恒空)、把 `limit` 一起递进去。
6181
+ */
6182
+ private rankedRows;
5953
6183
  /**
5954
6184
  * 标题命中。走 `LIKE` 而不是 FTS —— 标题不在任何全文索引里
5955
6185
  * (两张 FTS 表的内容源都是 `messages.content`)。
@@ -6826,6 +7056,22 @@ declare class TrackerDB {
6826
7056
  countOpen(sessionId: string): number;
6827
7057
  /** 全量替换会话的 TODO 列表(事务:DELETE + INSERT 批量) */
6828
7058
  replaceAll(sessionId: string, todos: TodoItem[]): void;
7059
+ /**
7060
+ * 删掉一段会话的全部 TODO(2026-10-27)。返回删了几条。
7061
+ *
7062
+ * ## 什么时候需要它
7063
+ *
7064
+ * `delegate_task` 派出去的子任务用**自己那个** `sessionId` 写 TODO
7065
+ * (判据在 `AgentConfig.pendingTodos` 上:闸门要读的是「它自己那几件活」)。
7066
+ * 而那个 id 对应的 sessions 行是**跑完之后才补建的** —— 建行失败或压根没落盘时,
7067
+ * 这几行 TODO 就成了谁也删不掉的孤儿:`epoch sessions` 列不到它,
7068
+ * 会话保留期清理也扫不到它(那一支按 `sessions` 表扫)。
7069
+ *
7070
+ * 所以那一条失败路径上要主动收一下。正常路径**不在这儿删**:子会话已经有
7071
+ * 自己的 sessions 行了,`SessionDB.deleteSession` 会连带清掉
7072
+ * (判据在 db.ts 的 `DELETE FROM todos WHERE session_id = ?` 那一行)。
7073
+ */
7074
+ deleteBySession(sessionId: string): number;
6829
7075
  /** 释放连接(引用计数归零才真正关闭) */
6830
7076
  close(): void;
6831
7077
  }
@@ -8794,7 +9040,15 @@ interface PluginInventory {
8794
9040
  issues: ParseIssue[];
8795
9041
  }
8796
9042
  /**
8797
- * **六类**扩展物的总数。0 表示这个插件装了等于没装,值得在预览里说一句。
9043
+ * **六类**扩展物的总数。
9044
+ *
9045
+ * ⚠️ **0 不等于「这个插件什么都没带」**(2026-10-23 改)。它只说明六类
9046
+ * (命令 / 角色 / 技能 / hook / deny / MCP)里一条都没有 —— 而清单里合法的还有
9047
+ * `panel`(插件自带一块嵌进输入框上方的页)和 `appearance`(外观预设)这两格,
9048
+ * 它们不在这六类里。所以 0 的用法只有两种:当作**六类这一面的**空判据,
9049
+ * 或者判两套扫描路的口径是否一致(见下)。**别拿它推「装了等于没装」** ——
9050
+ * 那句话曾经出现在 web 的卡片和 CLI 的 `plugin list` 上,2026-10-23 拿掉了,
9051
+ * 起因是一份只带面板的主题插件被它说成了空的。
8798
9052
  *
8799
9053
  * ## ⚠️ 这个数必须等于 `LoadedPlugin.counts` 六格之和
8800
9054
  *
@@ -9540,6 +9794,31 @@ type LifecycleOutcome = {
9540
9794
  ok: false;
9541
9795
  reason: string;
9542
9796
  };
9797
+ /**
9798
+ * 启用 / 停用的结局。**比 {@link LifecycleOutcome} 多一格 `changed`**(2026-10-23)。
9799
+ *
9800
+ * ## 为什么要这一格
9801
+ *
9802
+ * 停用 / 启用**只有真变了才该让上层立「重启后生效」那格闩**。点一个本来就停用的
9803
+ * 插件,`epoch plugin disable` 会回一句「本来就是停用状态」,盘上一个字节都没动
9804
+ * —— 那时要是照样把 `pendingRestart` 立起来,宿主会弹一条「插件改动还没生效」
9805
+ * 的重启通知,而实际上什么都没变。
9806
+ *
9807
+ * 判据在 `setPluginEnabled` 的 `hit.enabled !== enabled`:那一格本来就算出来了,
9808
+ * 只是以前只拿它选文案。这一轮把它递出去,让「要不要立闩」和「该说哪句话」
9809
+ * 用**同一个**答案 —— 分成两处判的话,它们分叉的那天屏幕上是一句真话配一个假动作。
9810
+ *
9811
+ * ⚠️ **uninstall / update 照旧用 `LifecycleOutcome`**:它们没有「本来就是这个状态」
9812
+ * 这一档,多一格 `changed` 恒为 true,等于给调用方多一个永远不需要看的字段。
9813
+ */
9814
+ type ToggleOutcome = {
9815
+ ok: true;
9816
+ message: string;
9817
+ changed: boolean;
9818
+ } | {
9819
+ ok: false;
9820
+ reason: string;
9821
+ };
9543
9822
  /**
9544
9823
  * 卸载:删记录,**软链只删链、不删用户的源码目录**(验收 #15)。
9545
9824
  *
@@ -9556,7 +9835,7 @@ declare function uninstallPlugin(name: string, opts: LifecycleOptions): Promise<
9556
9835
  * 停用之后扩展物全部消失、目录还在 —— 这正是它和 `uninstall` 的分工:
9557
9836
  * 「这个插件今天碍事」和「我不要它了」是两件事,前者不该付出重装的代价。
9558
9837
  */
9559
- declare function setPluginEnabled(name: string, enabled: boolean, opts: LifecycleOptions): Promise<LifecycleOutcome>;
9838
+ declare function setPluginEnabled(name: string, enabled: boolean, opts: LifecycleOptions): Promise<ToggleOutcome>;
9560
9839
  /**
9561
9840
  * 更新:按记录里的 `source` 重来一遍。
9562
9841
  *
@@ -10383,6 +10662,15 @@ interface BreakdownInput {
10383
10662
  tool: EpochTool;
10384
10663
  source: string;
10385
10664
  }>;
10665
+ /**
10666
+ * 上一次请求的用量(2026-10-26)。**只用来算缓存那一格**,不给就整个缺席 ——
10667
+ * 「没跑过任何一轮」和「跑了一轮但一次没命中」是两件事,前者不该显示成 0%。
10668
+ *
10669
+ * 口径是**最近一次**而不是累计:累计值会随着高命中的历史越来越好看,
10670
+ * 从而掩盖「刚改的这一处根本没生效」—— 而这正是 {@link ContextBreakdown.cache}
10671
+ * 存在的理由。
10672
+ */
10673
+ lastUsage?: TokenUsage;
10386
10674
  }
10387
10675
  declare function buildContextBreakdown(input: BreakdownInput): ContextBreakdown;
10388
10676
 
@@ -11108,6 +11396,34 @@ declare class AgentLoop {
11108
11396
  private learnedSkills;
11109
11397
  constructor(config: AgentConfig);
11110
11398
  abort(): void;
11399
+ /**
11400
+ * 撞上「请求体太大」之后的第一段救援:**摘掉最老的几张图**,返回摘过的那份。
11401
+ *
11402
+ * ## 为什么它排在压缩之前(这是全套改动的判据所在)
11403
+ *
11404
+ * 1. **代价差一个量级**:摘图是纯本地操作(把 base64 换成一行占位),压缩是
11405
+ * 一发真实的摘要请求(花钱、要等几秒)。而这一档的现场绝大多数是图撑的
11406
+ * (`request-bytes.ts` 文件头那张量纲表),先试便宜的那个。
11407
+ * 2. **压缩的代价高得多,而且它动的是整段历史**:`compress()` 收到的是文本投影
11408
+ * (`message-codec.ts` 的 `flattenMessage` 明确把 `parts` 丢掉),所以图片
11409
+ * 它确实能拿掉。但拿掉图只是它的副作用 —— 它真正干的是把中段**整段换成一段
11410
+ * 摘要**,而那是不可逆的。仅仅为了几张老图就这么干,不值当。
11411
+ *
11412
+ * ## ⚠️ 目标**不能用 {@link imageBudget}**(实测踩到过)
11413
+ *
11414
+ * 走到这儿说明那一次**真的超了**,而 `imageBudget()` 正是撞墙前用的那个数。
11415
+ * 拿它当「摘到多少为止」,在「配置值给得太宽松」和「没有 `homeDir`、学不到
11416
+ * 值」这两档上都会得出「一张都不用摘」(图总量本来就小于那个宽松预算),
11417
+ * 于是救援原地空转、重发同一份东西。
11418
+ *
11419
+ * 撞墙这个事实本身就说明「必须比刚才小」,所以目标是**实发量打八折**
11420
+ * (同 `LEARN_SHRINK` 的口径:留两成余量)。学到的那个值只是让**下一次**
11421
+ * 不必先撞一次。
11422
+ *
11423
+ * @returns 摘动了就回新的 messages + 摘了几张;**一张都摘不掉时回 `null`**
11424
+ * —— 调用方据此知道「这一招不管用」,别拿一模一样的东西再撞一次墙
11425
+ */
11426
+ private tryOffloadForBodyLimit;
11111
11427
  /**
11112
11428
  * 多模态策略的**现读出口**:尺寸上限来自 config,`supportsImages` 来自
11113
11429
  * provider 自己(见 {@link AgentProvider.capabilities})。
@@ -11126,6 +11442,47 @@ declare class AgentLoop {
11126
11442
  * 磁盘缓存)。相对一次模型调用,这个量级不值得为它留一个会说谎的快照。
11127
11443
  */
11128
11444
  private multimodal;
11445
+ /**
11446
+ * 这一发该按多大的图片预算走 —— **配置/缺省值和学习值取小的那个**。
11447
+ *
11448
+ * ## 为什么每轮现算,不构造时算一次
11449
+ *
11450
+ * 判据逐字同 {@link multimodal}:**学习值会在一次 run 中途变化** ——
11451
+ * 撞上 1051 的那一刻 {@link learnBodyLimit} 刚把它写下去,而同一轮接下来的
11452
+ * 重发就该按新值(更紧的那个)走。构造时算一次的话,那一次重发用的还是撞墙
11453
+ * 前那个宽松的预算,于是再摘不掉、再撞一次。
11454
+ *
11455
+ * ## 没有 `homeDir` 时
11456
+ *
11457
+ * 不读历史学习值(盘上那份在哪都不知道),只用配置值/缺省值 —— 于是「学」
11458
+ * 这件事在这种宿主上不发生。这是一个**明确的降级**,不是 bug:没给数据目录的
11459
+ * 嵌入宿主不该被我们写文件。
11460
+ */
11461
+ private imageBudget;
11462
+ /**
11463
+ * 记一笔「这个端点收不下这么大的图片」。best-effort:写不进去只是下次再撞一遍。
11464
+ *
11465
+ * ⚠️ 传进来的是**这一次真的发出去的字节数**(不是预算值)。两者的区别就是这条线
11466
+ * 的全部意义:预算可能比它小(那我们不该撞),撞上了说明预算偏大,而偏大多少
11467
+ * 只看这一次的实发量。
11468
+ */
11469
+ private learnBodyLimit;
11470
+ /**
11471
+ * 学习缓存的三元组 `[homeDir, provider, model]` —— 没有 `homeDir` 时回
11472
+ * `undefined`,于是「学」和「读」两条路一起关掉。
11473
+ *
11474
+ * ⚠️ **`provider` 那一格只能退而求其次。** `body-limit-cache.ts` 的文件头要求
11475
+ * 缓存键带 provider(同一个裸名在两家下面可以是两个模型、端点更是各家自己的),
11476
+ * 但 `AgentLoop` 手上**没有** provider 类型:`AgentProvider` 只给 `modelId`
11477
+ * (一个裸名,`agent/types.ts`),provider 身份在 router 里,core 这一层够不着。
11478
+ *
11479
+ * 所以这里 provider 给空串,也就是**本机所有 provider 共享同一条学习值**。
11480
+ * 这是已知的精度损失,方向是**安全的那一侧**:两个端点里更保守的那个值被两边
11481
+ * 共用,代价是最慢那家让我们摘得比别家早一点(可逆、不花钱),而不是在宽松
11482
+ * 那家上撞墙。要修成真带 provider,得让装配层把它递进 `AgentConfig` ——
11483
+ * 那是一次跨包改契约,不在这一笔的范围里。
11484
+ */
11485
+ private imageLimitKey;
11129
11486
  /**
11130
11487
  * 「这一轮会用哪个模型、它认不认图」——**给 system prompt 用的那一份**
11131
11488
  * (2026-09-02)。判据全文在 `AgentProvider.modelId` 上。
@@ -11206,6 +11563,12 @@ declare class AgentLoop {
11206
11563
  contextBreakdown(opts: {
11207
11564
  historyText: string;
11208
11565
  toolSource?: (name: string) => string | undefined;
11566
+ /**
11567
+ * 上一次请求的用量(2026-10-26)。只用来算缓存那一格 —— `/context` 要回答
11568
+ * 「我说了缓存到底有没有生效」,而这句话只能由 provider 报的真数回答。
11569
+ * 不给就是「这个宿主量不到」,那一格整个缺席(不是 0%)。
11570
+ */
11571
+ lastUsage?: TokenUsage;
11209
11572
  }): ContextBreakdown;
11210
11573
  /** 执行一次对话,收敛成最终结果 */
11211
11574
  run(input: RunInput): Promise<RunResult>;
@@ -12120,7 +12483,7 @@ declare function describeJsonError(err: unknown, text: string): string;
12120
12483
  * | -------------------------------- | ------------------------------------------ |
12121
12484
  * | `allowManagedPermissionRulesOnly` | 其余各层的 allow / ask / deny 全部忽略 |
12122
12485
  * | `allowManagedHooksOnly` | 用户级 hook 一律不加载 |
12123
- * | `disableBypassPermissionsMode` | `permission: bypass` 拒绝启动 |
12486
+ * | `disableBypassPermissionsMode` | 审批轴 = `never` 的那一档拒绝启动(今天就是 `auto`) |
12124
12487
  *
12125
12488
  * ## 这一层**不过信任闸门**,而且不该过
12126
12489
  *
@@ -12199,12 +12562,17 @@ declare class ManagedPolicyError extends Error {
12199
12562
  *
12200
12563
  * ## 为什么是**拒绝启动**而不是静默降级到 default
12201
12564
  *
12202
- * 降级是「更安全」的那一侧,但它会让用户以为自己在 bypass 里 —— 然后每一次
12565
+ * 降级是「更安全」的那一侧,但它会让用户以为自己在那一档里 —— 然后每一次
12203
12566
  * 审批弹窗都成了怪现象,而真正的原因(IT 禁掉了这个级别)一个字都看不见。
12204
- * 权限级别是用户**显式**要的东西(`--permission bypass` 或 config.yaml 里那一行),
12567
+ * 权限级别是用户**显式**要的东西(`--permission auto` 或 config.yaml 里那一行),
12205
12568
  * 显式请求被策略挡下必须当面说,这跟「配了个不认识的级别回退到 default」
12206
12569
  * 那种笔误不是一回事。
12207
12570
  *
12571
+ * ⚠️ **2026-10-27 起它挡的是「审批轴 = `never`」这个组合,不再是一个档位名。**
12572
+ * 合并 `auto` + `bypass` 之后,「不问」这件事不再由某个档位名独有 ——
12573
+ * 用一个 `level === 'auto'` 判断也能对得上今天的代码,但那是第二份知识,
12574
+ * 下一次加一档 `never` 的档位时它会静默漏掉。
12575
+ *
12208
12576
  * 挡的是级别本身,不区分它从哪一层来 —— `--permission`、`EPOCH_PERMISSION`、
12209
12577
  * `~/.epoch/config.yaml` 最后都汇进同一个 `config.permission`,
12210
12578
  * 在这里分叉等于把「哪个来源算数」这个问题又答一遍。
@@ -13553,13 +13921,7 @@ declare const EpochConfigSchema: z.ZodObject<{
13553
13921
  maxOutputTokens: z.ZodOptional<z.ZodInt>;
13554
13922
  homeDir: z.ZodDefault<z.ZodString>;
13555
13923
  dbPath: z.ZodDefault<z.ZodString>;
13556
- permission: z.ZodDefault<z.ZodEnum<{
13557
- default: "default";
13558
- acceptEdits: "acceptEdits";
13559
- plan: "plan";
13560
- auto: "auto";
13561
- bypass: "bypass";
13562
- }>>;
13924
+ permission: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<"default" | "acceptEdits" | "plan" | "auto", string>>;
13563
13925
  permissions: z.ZodOptional<z.ZodObject<{
13564
13926
  allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
13565
13927
  ask: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -13587,6 +13949,9 @@ declare const EpochConfigSchema: z.ZodObject<{
13587
13949
  sandbox: z.ZodPrefault<z.ZodObject<{
13588
13950
  terminal: z.ZodDefault<z.ZodBoolean>;
13589
13951
  }, z.core.$strip>>;
13952
+ delegate: z.ZodPrefault<z.ZodObject<{
13953
+ taskTimeoutSec: z.ZodDefault<z.ZodInt>;
13954
+ }, z.core.$strip>>;
13590
13955
  tools: z.ZodPrefault<z.ZodObject<{
13591
13956
  deferMcp: z.ZodDefault<z.ZodBoolean>;
13592
13957
  mode: z.ZodDefault<z.ZodEnum<{
@@ -13602,6 +13967,9 @@ declare const EpochConfigSchema: z.ZodObject<{
13602
13967
  enabled: z.ZodDefault<z.ZodBoolean>;
13603
13968
  threshold: z.ZodDefault<z.ZodNumber>;
13604
13969
  }, z.core.$strip>>;
13970
+ limits: z.ZodPrefault<z.ZodObject<{
13971
+ maxRequestImageBytes: z.ZodOptional<z.ZodInt>;
13972
+ }, z.core.$strip>>;
13605
13973
  memory: z.ZodPrefault<z.ZodObject<{
13606
13974
  memoryCharLimit: z.ZodDefault<z.ZodInt>;
13607
13975
  userCharLimit: z.ZodDefault<z.ZodInt>;
@@ -13749,9 +14117,14 @@ declare const EpochConfigSchema: z.ZodObject<{
13749
14117
  }> & z.core.$partial, z.ZodString>>;
13750
14118
  hidden: z.ZodOptional<z.ZodBoolean>;
13751
14119
  }, z.core.$strip>>;
14120
+ scheduleRemoveConfirm: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
14121
+ zh: "zh";
14122
+ en: "en";
14123
+ }> & z.core.$partial, z.ZodString>>;
13752
14124
  user: z.ZodOptional<z.ZodObject<{
13753
14125
  label: z.ZodString;
13754
14126
  icon: z.ZodOptional<z.ZodString>;
14127
+ avatar: z.ZodOptional<z.ZodString>;
13755
14128
  }, z.core.$strip>>;
13756
14129
  settingsHidden: z.ZodOptional<z.ZodArray<z.ZodString>>;
13757
14130
  settingsPanes: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -14330,6 +14703,177 @@ interface SchemaExport {
14330
14703
  */
14331
14704
  declare function buildSchemaExports(): SchemaExport[];
14332
14705
 
14706
+ /**
14707
+ * 配置键的**全量**表 —— 从 `EpochConfigSchema` 派生,一个字都不手抄。
14708
+ *
14709
+ * ## 它存在的原因
14710
+ *
14711
+ * `epoch config set` 原来认 24 个键,而那 24 个是**手抄**在三张 `Record` 里的
14712
+ * (`SCALAR_FIELDS` / `SECTION_FIELDS` / `BUDGET_FIELDS`)。schema 有 45 个顶层键 /
14713
+ * 83 个叶子,于是「知道的」和「能改的」差着五十多个 —— 而那个差距在任何一屏上都
14714
+ * 看不见:用户(或模型)照着 schema / 文档敲一个键,收到的是「未知配置项」,
14715
+ * 它读起来和「这个功能没做」一模一样。
14716
+ *
14717
+ * `cli/commands/config.ts` 的注释里记着这个坑的三次前科(`permission` / `shell` /
14718
+ * 设置页那一串),每一次都是手抄名单落下的。这一份把它从根上断掉:**清单只有
14719
+ * schema 一个真源**,`set` / `unset` / 报错提示 / `config list` 全部查这张表。
14720
+ *
14721
+ * ## 值域也是派生的
14722
+ *
14723
+ * {@link checkConfigValue} 按 JSON Schema 的 `type` / `enum` / `minimum` /
14724
+ * `exclusiveMinimum` / `maximum` / `minLength` 校验 —— 那几格正是 zod 的
14725
+ * `.int().positive().max(10000)` / `.enum([...])` / `.min(1)` 编译出来的东西
14726
+ * (实测:`automation.keepRuns` 拿到 `exclusiveMinimum: 0, maximum: 10000`,
14727
+ * `budget.onUnknownPricing` 拿到 `enum: ['block','warn']`)。
14728
+ *
14729
+ * 于是 `epoch config set` 的值域**永远等于 schema**,而不再是「另一份人肉对齐的
14730
+ * 近似」。这一层原来最容易出错的形态是「放行 `1.5`,而引擎那侧是 `z.int()`,
14731
+ * 于是整份配置退回默认值」—— `sessions.retentionDays` 与 `automation.*` 两处
14732
+ * 的注释各记了一次。
14733
+ *
14734
+ * ## 三档可写性,判据写在每一档旁边
14735
+ *
14736
+ * | 档 | 判据 | 今天几个 |
14737
+ * | --- | --- | --- |
14738
+ * | `scalar` | 顶层标量,或**一层** `段.字段`,且不是 {@link BLOCKED} 里那三个 | 37 |
14739
+ * | `manual` | 两层以上的路径,或数组 / 对象 | 40 |
14740
+ * | `blocked` | 「位置 / 身份」类的三个键 | 3 |
14741
+ *
14742
+ * ### 为什么两层以上的路径不开放
14743
+ *
14744
+ * `setSectionField` 的就地改写只做一层(`infra/src/config-yaml.ts` 文件头那句
14745
+ * 「代价是只能改标量和一层嵌套——够用」)。把它扩成正则任意深度,要处理每一层
14746
+ * 的缩进、兄弟字段和注释归属,而那个手术碰坏用户注释的形态是静默的。
14747
+ *
14748
+ * 更硬的一条在 `display.currency` 上:它的 `code` / `rate` 在 schema 里是
14749
+ * **required 成对**的(JSON Schema 那句 `required: ['code','rate']`)。只写一个,
14750
+ * 整份配置过不了 `EpochConfigSchema`,而 loader 的语义是「不逐字段抢救」——
14751
+ * 结果是**整份配置连同模型和 key 一起退回默认值**。为一个货币单位付这个代价
14752
+ * 不值得,所以这一族留给人手改文件(手改时他看得见旁边那一行)。
14753
+ *
14754
+ * ### 为什么 {@link BLOCKED} 那三个键连手改都算「不是设置」
14755
+ *
14756
+ * 它们不是「某个开关的值」,是「数据放在哪 / 我是哪个档」。而这三个键今天的
14757
+ * 真源**不在 config.yaml 里**:
14758
+ *
14759
+ * - `profile`:唯一真源是环境变量 `EPOCH_PROFILE`(`infra/src/paths.ts` 的
14760
+ * `resolveHomeDir()` / `resolveProfile()` 都只读环境变量)。写进 yaml 一个字都
14761
+ * 不会切目录,只会让「关于」那一屏(`WireAbout.profile`)报一个假的当前档。
14762
+ * - `homeDir`:有两套解析真源 —— `paths.ts` 那一家只看 `EPOCH_HOME` /
14763
+ * `EPOCH_PROFILE`,而 runtime 到处读 `config.homeDir`。写它不会让配置文件搬家
14764
+ * (`configPath()` 照样在旧家),只会让**数据**跑去新家,于是同一个进程里
14765
+ * 配置文件、会话库、凭据分散在两个目录。
14766
+ * - `dbPath`:同族,而且切得更碎 —— `runtime/src/services.ts` 读
14767
+ * `config.dbPath`,而 `runtime/src/schedule/control.ts` 用的是
14768
+ * `dbPath(homeDir)`。写它只切走一半的库。
14769
+ *
14770
+ * 换句话说:把这三个键放行不会「让配置更灵活」,只会造出一台**看起来改了、
14771
+ * 其实只改了一半**的机器。要搬家的正路是环境变量(`EPOCH_HOME` /
14772
+ * `EPOCH_PROFILE`),那是它们本来的入口。
14773
+ *
14774
+ * ### 另外三个键的单独判据(写在这一层,是因为它们各自的原因不一样)
14775
+ *
14776
+ * - **`modelEntries`**:schema 上自己写着「写在 config.yaml 里不生效」——
14777
+ * 真源是 `models.json`,loader 读到这一格会报一条 `legacy_ignored`。
14778
+ * - **`hooks`**:旧格式,已搬去 `hooks.json`。这一格只保证「是个对象」,
14779
+ * 内部结构由 hook 自己的 schema 校验(这里给它写一个字段表就等于维护第二份)。
14780
+ * - **`display.currency.code` / `.rate`**:两格在 schema 里是 **required 成对**的
14781
+ * (`required: ['code','rate']`)。只写一个,整份配置过不了校验、按 loader
14782
+ * 一贯的「不逐字段抢救」退回默认值 —— 模型和 key 一起没。
14783
+ */
14784
+
14785
+ /** 一个叶子能是的那几种标量。数组 / 对象不在这里 */
14786
+ type ConfigKeyKind = 'string' | 'integer' | 'number' | 'boolean';
14787
+ /**
14788
+ * 这个键能不能通过 `epoch config set` 写。
14789
+ *
14790
+ * 三档的含义和判据见文件头那张表 —— **别照着名字猜**,
14791
+ * `blocked` 不是「危险」,是「它不是设置」。
14792
+ */
14793
+ type ConfigKeyWrite = 'scalar' | 'manual' | 'blocked';
14794
+ /**
14795
+ * 不能 `config set` 的**原因码**。
14796
+ *
14797
+ * ⚠️ 这一格是给 CLI 翻译用的,**不是给人读的** —— 人读的是 catalog 里那几句
14798
+ * (`cli.config.manual_nested` / `manual_structured` / `not_a_setting`)。
14799
+ *
14800
+ * 为什么不在表里存一句「为什么不能写」的中文描述:`config set` / `config list`
14801
+ * 会把它拼进输出,而英文界面上就长出一句英文夹中文的话。存原因码、
14802
+ * 让文案走 catalog,两边各自才是对的。
14803
+ */
14804
+ type ConfigKeyReason = 'position' | 'structured' | 'nested';
14805
+ /** 键表里的一行 */
14806
+ interface ConfigKeyEntry {
14807
+ /** 点分路径,和 `config.yaml` 里的写法逐字一致(`budget.maxCostUsd`) */
14808
+ path: string;
14809
+ /** 段名;顶层键没有这一格 */
14810
+ section?: string;
14811
+ /** 段内的字段名;顶层键没有这一格 */
14812
+ field?: string;
14813
+ kind: ConfigKeyKind;
14814
+ write: ConfigKeyWrite;
14815
+ /** `write !== 'scalar'` 时**必有**:不能 `config set` 的原因码(CLI 据此选文案) */
14816
+ reason?: ConfigKeyReason;
14817
+ /** 枚举型键的可选值 */
14818
+ choices?: readonly string[];
14819
+ minimum?: number;
14820
+ exclusiveMinimum?: number;
14821
+ maximum?: number;
14822
+ exclusiveMaximum?: number;
14823
+ minLength?: number;
14824
+ /** schema 里写的内置默认值。没有默认值的键(`model` / `budget.maxCostUsd` …)不带这格 */
14825
+ builtin?: SettingValue;
14826
+ }
14827
+ /**
14828
+ * 全部配置键,按 schema 里的声明顺序排。
14829
+ *
14830
+ * 调用方**不许**改它(`readonly`),也不许在别处再抄一份 —— 报错提示、
14831
+ * `config list`、`config set` / `unset` 认的键必须来自这一个数组。
14832
+ */
14833
+ declare function configKeyTable(): readonly ConfigKeyEntry[];
14834
+ /** 按点分路径查一行;不认识的键返回 `undefined`(调用方据此报「未知配置项」) */
14835
+ declare function configKeyByPath(path: string): ConfigKeyEntry | undefined;
14836
+ /**
14837
+ * 全部能 `config set` 的键。报错提示和 `config list --all` 都从这里取。
14838
+ *
14839
+ * 顺序就是 schema 的顺序 —— 分段天然相邻,不需要再排一次。
14840
+ */
14841
+ declare function writableConfigKeys(): readonly ConfigKeyEntry[];
14842
+ /** 校验失败的原因。带格子是因为 CLI 要把它拼进一句人话(catalog 里那几条) */
14843
+ type ConfigValueProblem = {
14844
+ code: 'not-a-kind';
14845
+ kind: ConfigKeyKind;
14846
+ } | {
14847
+ code: 'not-in-choices';
14848
+ choices: readonly string[];
14849
+ } | {
14850
+ code: 'below-minimum';
14851
+ bound: number;
14852
+ exclusive: boolean;
14853
+ } | {
14854
+ code: 'above-maximum';
14855
+ bound: number;
14856
+ exclusive: boolean;
14857
+ } | {
14858
+ code: 'too-short';
14859
+ minLength: number;
14860
+ };
14861
+ /** {@link checkConfigValue} 的结论。成功时带上**该写进 yaml 的字面量** */
14862
+ type ConfigValueCheck = {
14863
+ ok: true;
14864
+ literal: string;
14865
+ } | {
14866
+ ok: false;
14867
+ problem: ConfigValueProblem;
14868
+ };
14869
+ /**
14870
+ * 一个字符串能不能当这个键的值,以及它该以什么字面量写进 yaml。
14871
+ *
14872
+ * 校验顺序是固定的:类型 → 枚举 → 边界 → 长度。顺序有意义 —— 反过来的话
14873
+ * `budget.warnAtPercent: abc` 会先撞上「不在 0~100 之间」,而真正的问题是它不是个数。
14874
+ */
14875
+ declare function checkConfigValue(entry: ConfigKeyEntry, raw: string): ConfigValueCheck;
14876
+
14333
14877
  /**
14334
14878
  * `~/.epoch/keybindings.json` 的加载(方案 31 PR-1 / PR-2)。
14335
14879
  *
@@ -15632,6 +16176,30 @@ interface Session {
15632
16176
  /** 获取上下文窗口内的消息 [OpenCode] */
15633
16177
  getContext(maxTokens?: number): Message[];
15634
16178
  }
16179
+ /**
16180
+ * `delegate_task` 派出去的子任务,在 `sessions.source` 上记的那一档。
16181
+ *
16182
+ * ## 为什么要有一个自己的 source,而不是沿用 `'cli'`
16183
+ *
16184
+ * 子会话从 2026-10-27 起**真的会落盘**(在那之前只有一段临时 id,库里的
16185
+ * `sessions` 表根本没有它那一行 —— 判据在 `runtime` 的 `subRunner` 上)。
16186
+ * 落盘之后它就和用户自己的对话长得一模一样了,而这是两件事:
16187
+ *
16188
+ * - **它不是「用户的一段对话」**:`epoch -r`(最近一段)、`epoch -c`
16189
+ * (这个目录下最近一段)接上去都接错了对象 —— 用户想接着聊的是他自己那段,
16190
+ * 不是十分钟前派出去的那个子任务。
16191
+ * - **但它的过程必须读得到**:`session_trace` 从父会话沿着 `parent_id` 往下走,
16192
+ * 要能列出它;`session_messages_read` 要能读回它每一步调了什么工具。
16193
+ *
16194
+ * 所以这两条路被刻意分开了:**列表 / `-r` / `-c` 默认排除它**(判据在
16195
+ * `SessionDB.listSessions` 的 `includeDelegate` 上),**血缘和消息读回不排除**
16196
+ * (那两条走的是各自的 SQL,不经过列表)。
16197
+ *
16198
+ * ⚠️ 这是一个**跨包的名字**:`core`(写行 / 列表过滤)和 `runtime`(建行)各用
16199
+ * 一次,所以真源只在这儿一份。字符串抄两遍的下场是有人改了其中一处,
16200
+ * 而表现只是「子会话忽然出现在列表里」—— 没有一处会红。
16201
+ */
16202
+ declare const DELEGATE_SESSION_SOURCE = "delegate";
15635
16203
  interface CreateSessionInput {
15636
16204
  id?: string;
15637
16205
  title?: string;
@@ -15725,6 +16293,17 @@ interface ListSessionsInput {
15725
16293
  * 再传进来(我们这边的同步层退 2s),代价只是重复拉几行、而那是幂等的。
15726
16294
  */
15727
16295
  updatedSince?: number;
16296
+ /**
16297
+ * 要不要把 `delegate_task` 派出去的子任务会话一起列出来。**默认不要。**
16298
+ *
16299
+ * 判据在 {@link DELEGATE_SESSION_SOURCE} 上:子会话不是用户的一段对话,
16300
+ * 而 `epoch -r` / `epoch -c` / 侧栏列表问的都是「接着哪一段聊」。
16301
+ * 它落盘是为了 `session_trace` + `session_messages_read` 读得回过程,
16302
+ * 那条路上不经过这里。
16303
+ *
16304
+ * 只有「这个库里一共有哪些行」这一类调用方(增量备份、诊断)才该传 `true`。
16305
+ */
16306
+ includeDelegate?: boolean;
15728
16307
  }
15729
16308
  interface ListSessionsOutput {
15730
16309
  sessions: SessionMeta[];
@@ -16342,6 +16921,11 @@ declare class SessionManager implements SessionManager$1 {
16342
16921
  * 那不是摘要,是残缺的原文,句子往往截在一半,喂回模型比不给更糟。
16343
16922
  */
16344
16923
  compact(sessionId: string, summarize?: (transcript: string) => Promise<string | null>): Promise<CompactionResult>;
16924
+ /**
16925
+ * 把库里那几张图片下放成一行占位(2026-10-28)。判据全文在
16926
+ * {@link SessionDB.offloadImageParts} 上 —— 这一层只是转发,**一个数都不算**。
16927
+ */
16928
+ offloadImageParts(sessionId: string): number;
16345
16929
  /**
16346
16930
  * 把引擎刚压完的那一次落进库里(方案 64 PR-1)。判据全文在
16347
16931
  * {@link SessionDB.archiveAndCompact} 上 —— 这一层只是转发,**一个数都不算**。
@@ -17320,18 +17904,25 @@ declare const MAX_AUDIT_ENTRIES = 500;
17320
17904
  * 拆的直接原因是 manager.ts 越过了 500 行硬线,但拆得动本身说明这一档确实
17321
17905
  * 独立:它是「没有任何规则命中时的兜底」,语义上不该和规则匹配混在一个类里。
17322
17906
  *
17323
- * 五档的意图(对标 Hermes `approval.py`):
17907
+ * 四档的意图(对标 Hermes `approval.py` + deepseek-harness 的两根轴):
17908
+ *
17909
+ * | 级别 | 审批 | 沙箱 | 意图 |
17910
+ * | ------------- | ---- | -------------------- | --------------------------------------------- |
17911
+ * | `auto` | never| `danger-full-access` | 全放行,**但底线那一层照旧**:灾难级拒、tier-2 拒、判不了放行 |
17912
+ * | `plan` | ask | `read-only` | 只读:写入 / 网络 / 执行代码一律拒,**只读命令放行**;区外**读**要问 |
17913
+ * | `acceptEdits` | ask | `workspace-write` | 同 `auto`,但**工作区外的文件**要确认 |
17914
+ * | `default` | ask | `workspace-write` | 只读操作免确认(**工作区外的读除外**),其余一律确认 |
17324
17915
  *
17325
- * | 级别 | 意图 |
17326
- * | ------------- | -------------------------------------------------- |
17327
- * | `bypass` | 全放行 —— 但**挡不住第 1 层那两条 deny**,那层在前面 |
17328
- * | `auto` | 信任 agent:这一档自己不拦,边界全在第 1 层 |
17329
- * | `plan` | 只读:写入 / 命令 / 网络 / 执行代码一律拒;区外**读**要问 |
17330
- * | `acceptEdits` | 同 `auto`,但**工作区外的文件**要确认 |
17331
- * | `default` | 只读操作免确认(**工作区外的读除外**),其余一律确认 |
17916
+ * ## ⚠️ 2026-10-27:五档并成四档,`bypass` 并进 `auto`
17917
+ *
17918
+ * 实测(真 `PermissionManager` 跑 15 项日常操作)合并前的弹窗数是
17919
+ * **11 / 5 / 2 / 1**(default / acceptEdits / auto / bypass),而 `auto` 与
17920
+ * `bypass` 只差「判不了」那一格外加规则表 `ask` 那一族 —— 两档说不出
17921
+ * 「用户凭什么选它而不是另一个」。合并方向取宽的({@link ruleAskActive} 现在
17922
+ * 只对 `auto` 为假),老名字 `bypass` 走 protocol 的别名表静默映射过来。
17332
17923
  *
17333
17924
  * ⚠️ **「工作区外」那一格是 `plan` / `default` / `acceptEdits` 三档共用的**
17334
- * (2026-10-08):`auto` / `bypass` 放行,其余三档问一句,判据在
17925
+ * (2026-10-08):`auto` 放行,其余三档问一句,判据在
17335
17926
  * {@link outsideWorkspace} 上。**档位管的是「问了之后算不算数」,不是「要不要问」**
17336
17927
  * —— 而「算不算数」由权限层算出来的 `grantedPaths` 兑现,工具层照它放行
17337
17928
  * (判据在 protocol 的 `ToolContext.grantedPaths` 上)。
@@ -17341,7 +17932,7 @@ declare const MAX_AUDIT_ENTRIES = 500;
17341
17932
  *
17342
17933
  * ## ⚠️ 这一档哪天要用「沙箱模式」,调 `sandboxModeForLevel()`,别在这儿长第二张表
17343
17934
  *
17344
- * 上面那张五档表还有一个孪生兄弟:**同样五档 → 沙箱模式**(`read-only` /
17935
+ * 上面那张四档表还有一个孪生兄弟:**同样四档 → 沙箱模式**(`read-only` /
17345
17936
  * `workspace-write` / `danger-full-access`),它今天住在
17346
17937
  * [infra/src/sandbox/policy.ts](../../../infra/src/sandbox/policy.ts)。
17347
17938
  * 方案 46 §2.2 原文说那段推导本该住在这个文件旁边(「那是权限系统的知识,
@@ -17752,29 +18343,52 @@ declare class PermissionManager implements PermissionManager$1 {
17752
18343
  * plan 模式下一条判不了的命令会弹给用户,用户点「允许」它就真跑了 ——
17753
18344
  * 而 `checkPlan()` 那句「plan 模式禁止写入和命令执行」压根没机会说话。
17754
18345
  *
17755
- * 2. **`bypass` + `obfuscated` → 放行。** 排在缓存前面,于是「`bypass` 整段不查
18346
+ * 2. **`never` 档下要人确认的底线分成两小支,落点相反**(2026-10-27 定的第二支)。
18347
+ *
18348
+ * 2a. **`obfuscated` → 放行。** 排在缓存前面,于是「`never` 整段不查
17756
18349
  * 审批缓存」({@link checkRulesAndCache} 那一节)对**规则那条路**逐字仍然成立。
18350
+ * 塌向「放行」的理由:这条判定的原话是「静态检查无法判定其真实行为,需人工确认」
18351
+ * —— 它是一句「我判不了」,不是一句「这危险」,而这一档的字面意思正是
18352
+ * 「别拿你判不了的事来问我」。逐字就是 Hermes 的 yolo。
17757
18353
  *
17758
- * ⚠️ **认码,不是认「所有 ask 形状的底线」**(2026-09-10 用户拍的):
17759
- * `dangerous-command`(tier-2,危险但可恢复)在这一档下**照旧问**。
17760
- * 用户的原话是「除了那种真的非常危险的操作,和无法判断的才会……停下来找
17761
- * 用户」,而他随后选定的两档形状是:**危险的问一句(点了就能干活)、
17762
- * 判不了的不问**。所以这一支只塌 `obfuscated`。
18354
+ * ⚠️ **代价说清楚**:这一档 + 提示注入 + `$(…)` 拼命令名这条路由此打通。
18355
+ * Hermes 在 `tools/approval.py` 那句 `# this and bypass every approval check
18356
+ * (prompt-injection escalation path)` 上承认的是同一个敞口。不接受的人该做的
18357
+ * 是**别用这一档**,而不是让这一档偷偷弹框 —— 一个说了「不问」却还在问的
18358
+ * 开关,会让用户学会闭眼点「允许」,那比敞口更坏。
17763
18359
  *
17764
- * ⚠️ 塌向「放行」而不是 Codex 的「拒绝」:这条判定的原话是「静态检查无法
17765
- * 判定其真实行为,需人工确认」—— 它是一句「我判不了」,不是一句「这危险」,
17766
- * 而 `bypass` 的字面意思正是「别拿你判不了的事来问我」。这一档下**逐字就是
17767
- * Hermes 的 yolo**(它的 floor 只有 hardline,判不了那一族住在 Tier-2、
17768
- * 被 yolo 整段跳过)。
18360
+ * 2b. **`dangerous-command`(tier-2,危险但可恢复)→ 拒绝。**
17769
18361
  *
17770
- * ⚠️ **代价说清楚**:`bypass` + 提示注入 + `$(…)` 拼命令名这条路由此打通。
17771
- * Hermes 在 `tools/approval.py` 那句 `# this and bypass every approval check
17772
- * (prompt-injection escalation path)` 上承认的是同一个敞口。不接受的人该做的是
17773
- * **别用这一档**,而不是让这一档偷偷弹框 —— 一个说了「不问」却还在问的开关,
17774
- * 会让用户学会闭眼点「允许」,那比敞口更坏。
18362
+ * ## ⚠️ 2026-10-27:这一支从「问一句」翻成「拒绝」(用户拍的)
18363
+ *
18364
+ * 原判据(2026-09-10)是「危险的问一句(点了就能干活)、判不了的不问」。
18365
+ * 它当时是对的 —— 那一版把「危险」和「判不了」一起塌成放行,而最高档下
18366
+ * `sudo apt install` 永远做不了是一句更坏的话。
18367
+ *
18368
+ * 翻过来的理由是**两家参照实现在这一点上一致,而只有我们不同**:
18369
+ *
18370
+ * - Codex `exec_policy.rs:812`:`AskForApproval::Never => Decision::Forbidden`,
18371
+ * 注释原文「we prefer to prompt the user rather than outright forbid the
18372
+ * command, but if the user has explicitly disabled prompts, we must forbid」;
18373
+ * - deepseek-harness `user-approval/src/index.ts` 的 `ApprovalPolicy`:`'never'`
18374
+ * 的定义是「every ask resolves `'rejected'` deterministically」。
18375
+ *
18376
+ * 两家都是**拒绝**,而这一档承诺的是「不问」—— 承诺兑现之后必须有一个落点,
18377
+ * 两家一致选了拒绝(fail closed),我们照此对齐。
18378
+ *
18379
+ * ⚠️ **落到用户身上的代价**:`auto` 档下 `sudo apt install` /
18380
+ * `git push --force` / `chmod 777` 这类**做不了**,模型会收到一句拒绝而不是
18381
+ * 一个可以点的出口。要干这些事得换回 `default` 或 `acceptEdits` 档 ——
18382
+ * 那两档照旧弹窗,点了就能干活。这条差别是刻意的:**这一档不再提供
18383
+ * 「点一下就放行」这个出口**,它只提供「不问」。
18384
+ *
18385
+ * ⚠️ **灾难级(`rm -rf /`)不受影响**:它在 `hard-limits.ts` 里直接
18386
+ * 不带 `requireApproval`,压根走不到这个方法。
18387
+ *
18388
+ * ⚠️ **`plan` 档不受影响**:第 1 条在它前面就 return 了。
17775
18389
  *
17776
18390
  * 3. **用户以前对这一条明确答过 → 照他的答复。** 这是另一个缺陷:审批缓存躺在
17777
- * 这一层**后面**,所以一条判不了 / tier-2 危险的命令上点一百次「永久允许」,
18391
+ * 这一层**后面**,所以一条 tier-2 危险的命令上点一百次「永久允许」,
17778
18392
  * 第一百零一次照样弹 —— 那个出口在这一族上是**画着但不通**的。
17779
18393
  *
17780
18394
  * ⚠️ 这里查缓存**不受 `approvalCacheActive()` 约束**(那条只管规则那条路):
@@ -17782,6 +18396,11 @@ declare class PermissionManager implements PermissionManager$1 {
17782
18396
  * 2026-08-17 那条判据(`bypass` 下缓存的 deny 不该拦住任何东西)没有被翻 ——
17783
18397
  * 它说的是**档位本来就放行**的操作,而这一支下面的操作档位从来不放行。
17784
18398
  *
18399
+ * ⚠️ **2026-10-27 起它在「不问」那一档的 tier-2 拒绝之前**,这是刻意的:
18400
+ * 用户在 `default` / `acceptEdits` 档下点过「永久允许」的 `sudo apt install`,
18401
+ * 换到 `auto` 档之后**照旧放行** —— 那是一个真实的授权,`never` 的意思是
18402
+ * 「别拿没授权的事来问我」,不是「我批过的也不作数」。要撤销去审批面板撤。
18403
+ *
17785
18404
  * 这不是「让缓存绕过黑名单」:绕不过的是 deny 形状的那两条(灾难级危险命令、
17786
18405
  * 敏感路径),它们在 `hard.requireApproval` 为假时压根走不到这儿。
17787
18406
  *
@@ -17790,12 +18409,16 @@ declare class PermissionManager implements PermissionManager$1 {
17790
18409
  */
17791
18410
  private settleUnaskable;
17792
18411
  /**
17793
- * **规则那条路**上会不会查审批缓存(2026-08-17 的判据:`bypass` 整段不查,
18412
+ * **规则那条路**上会不会查审批缓存(2026-08-17 的判据:`never` 那一档整段不查,
17794
18413
  * 全文在 {@link checkRulesAndCache} 上)。
17795
18414
  *
17796
18415
  * ⚠️ **它不是「这个档位记不记得住」**:安全底线那条路({@link settleUnaskable})
17797
18416
  * 在任何档位下都查缓存。宿主要知道的是**这一次问的这道题**记不记得住,
17798
18417
  * 那个答案在 `PermissionDecision.rememberable` 上,由 {@link rememberFlag} 打。
18418
+ *
18419
+ * ⚠️ 2026-10-27 起它和 {@link ruleAskActive} 走同一个判据(审批轴),但**仍是
18420
+ * 两个方法**:一个问「规则表说要问,这一档还问不问」,一个问「弹窗答案的记忆
18421
+ * 这一档查不查」。合并成一处的话,哪天其中一条改了判据,另一条会**无声地**跟着改。
17799
18422
  */
17800
18423
  approvalCacheActive(): boolean;
17801
18424
  /**
@@ -18503,7 +19126,7 @@ declare function nextRunAt(trigger: ScheduleTrigger, from?: number, startDate?:
18503
19126
  * 那是「这个任务每天都失败,而你要三天后才发现」。所以保存时校验、
18504
19127
  * 校验不过不许存 —— 定时任务的反馈回路按天算,能提前的每一步都值钱。
18505
19128
  *
18506
- * ## 「定时任务 + 跳过全部检查」的三条硬约束**挂在组合上**
19129
+ * ## 「定时任务 + 完全访问」的三条硬约束**挂在组合上**
18507
19130
  *
18508
19131
  * `bypass` 在对话里是一档现成的权限档,用户自己看着屏幕时选它是他的事;
18509
19132
  * 但一个**无人值守、每天自己跑一次**的 `bypass` 是另一回事 —— 那正是
@@ -18521,7 +19144,6 @@ interface ValidateScheduleInput {
18521
19144
  /** 绝对路径。空串 = 用户没绑工作区(`bypass` 下这是硬伤) */
18522
19145
  workDir: string;
18523
19146
  maxTurns: number;
18524
- maxBudgetUsd: number;
18525
19147
  timeoutMs: number;
18526
19148
  trigger: ScheduleTrigger;
18527
19149
  startDate?: string;
@@ -18613,8 +19235,22 @@ declare function parseScheduleDraft(raw: unknown): DraftParse;
18613
19235
  * 这次提案**放宽**了什么。
18614
19236
  *
18615
19237
  * 三类都是授权面变大的方向(判据在 `ScheduleWideningKind` 上)。新建那一支的
18616
- * 基线是**引擎那份缺省档**(`default`)而不是「空」—— 拿空当基线的话每一条新任务
18617
- * 都会被标成「放宽了」,而一枚恒亮的警示标等于没有警示标。
19238
+ * 基线是**引擎那份缺省档**(`SCHEDULE_DEFAULT_PERMISSION`)而不是「空」——
19239
+ * 拿空当基线的话每一条新任务都会被标成「放宽了」,而一枚恒亮的警示标等于没有警示标。
19240
+ *
19241
+ * ## 🔴 2026-10-27:基线从 `default` 抬到了 `auto`,于是「新建」那一支再也标不出来了
19242
+ *
19243
+ * 这不是失手,是抬档的必然结果:基线既然已经是**最宽那一档**,没有任何一档
19244
+ * 在它右边 —— `indexOf(level) > indexOf(base)` 对新建立永远不成立。
19245
+ *
19246
+ * 那一支的警示因此**没有消费者了**。留着它而不是删掉,是因为 `update` 那一支
19247
+ * 靠的正是同一个比较(拿那条任务现在的档当基线),而那一条照旧有效;
19248
+ * 把两支拆开会让「放宽」这个概念在两处各有一个定义。
19249
+ *
19250
+ * ⚠️ **新建那条路的警示不是丢了,是换了一个地方说**:权限档现在默认就是最宽
19251
+ * 那一档,于是「这条新任务将不经确认地做这些事」这句话由**卡片上那份清单** +
19252
+ * `validate.ts` 那三道网(工作目录不许宽得等于没绑 / 尺子必须有值 / 二次确认)
19253
+ * 接住。用户在卡片上看见的是它的绝对值,而不是一个「相对刚才变宽了」的相对值。
18618
19254
  */
18619
19255
  declare function draftWidening(draft: ScheduleDraft, current?: ScheduleDefinition): ScheduleWideningKind[];
18620
19256
  /**
@@ -18626,10 +19262,9 @@ declare function draftWidening(draft: ScheduleDraft, current?: ScheduleDefinitio
18626
19262
  * 看见「未知命令」。那一支的真话是「改一条已有任务今天只有界面那条路」,
18627
19263
  * 由工具输出说出来。
18628
19264
  *
18629
- * ⚠️ **`--budget` 那一格现在是真数了。** 2026-10-20 之前它是 `<你定一个上限>` 的
18630
- * 占位符(草稿没有预算,而 `epoch schedule add` 缺 `--budget` 会当场拒绝),
18631
- * 于是这条命令行和表单那条路落在同一处摩擦上。今天草稿带着预算,这条命令行
18632
- * 因此真的可以直接粘贴跑起来 —— 打印的就是**已经落库的那个数**。
19265
+ * ⚠️ **命令里已经没有 `--budget` 了**(2026-10-29 撤掉了预算那把尺子,
19266
+ * `epoch schedule add` 也不再认那个旗子)。轮次和超时那两格走缺省,所以这一行
19267
+ * 拼出来的就是一份完整可用的 `add`。
18633
19268
  */
18634
19269
  declare function draftCommandLine(draft: ScheduleDraft): string | undefined;
18635
19270
 
@@ -19291,6 +19926,30 @@ declare const DEFAULT_COMPLIANCE_SETTINGS: ComplianceSettings;
19291
19926
  *
19292
19927
  * 通过 shell 命令执行 hook 脚本,JSON 通过 stdin/stdout 通信。
19293
19928
  * 退出码 2 = 关键阻止,其他非零 = 警告。
19929
+ *
19930
+ * ## 沙箱(2026-10-26 补上,方案 65 §2.3)
19931
+ *
19932
+ * 这条子进程以前是**完全无边界**的 `spawn(command, { shell: true })`,而同一个
19933
+ * 仓库里 `terminal` 是包在 `confine()` 里的 —— 同一能力两套边界。而 hook 的口子
19934
+ * 并不小:它是**任意 shell**,来源是七份配置文件(项目级那三份要过工作区信任闸门,
19935
+ * 通过之后就是永久授权),并且 `tool:before` / `verify:stop` 是**自动执行**的,
19936
+ * 没有人会在那一刻再回答一次「要不要跑」。
19937
+ *
19938
+ * 现在它在 macOS / Linux 上走和 `terminal` 同一条 `confine()`,边界见
19939
+ * {@link hookSandboxPolicy}。Windows 没有后端(判据在 infra 的
19940
+ * `sandbox/backend.ts` 文件头),所以那一支**逐字节保持改之前的样子**。
19941
+ *
19942
+ * ## 一条不许漏的:沙箱拒绝**不进表决**
19943
+ *
19944
+ * 被挡住时退出码是普通的非零(seatbelt 是 1),`blocked` 照旧只有 `exitCode === 2`
19945
+ * 才成立。这不是没顺手改 —— 是**不能**改:`verify:stop` 上「阻止」等于
19946
+ * 「不许收工」,而一条被沙箱挡住的 `verify:stop` 下一轮还是被挡住,于是
19947
+ * 模型永远收不了工(确定性死循环,不是概率问题)。沙箱拒绝那条路由
19948
+ * [manager.ts](./manager.ts) 的 `reportIfNotRun()` **如实说出口**,说清
19949
+ * 「这条 hook 没跑成」而不是「它通过了」。
19950
+ *
19951
+ * 「放行」这个方向本身沿用的是 2026-08-08 定下的取舍(hook 不是安全边界),
19952
+ * 判据在 `manager.ts` 的 `reportIfNotRun()` 上 —— 别在这里另开一套。
19294
19953
  */
19295
19954
 
19296
19955
  /** Command hook 配置 */
@@ -19324,6 +19983,40 @@ interface CommandHookResult {
19324
19983
  rejectReason?: string;
19325
19984
  /** stdout 解析为 JSON 时的结构化输出 */
19326
19985
  output?: Record<string, unknown>;
19986
+ /**
19987
+ * 这一次的沙箱账。**永远有值** —— 「没包上」也是一个要说出口的结论
19988
+ * (同 infra 的 `Confinement`:`confine()` 不返回 null 就是为了这个)。
19989
+ */
19990
+ sandbox: CommandHookSandbox;
19991
+ /**
19992
+ * 非零退出时这次失败属于哪一种(判据在 infra 的 `sandbox/classify.ts` 文件头)。
19993
+ *
19994
+ * ⚠️ **只在包上了沙箱时才有值**:没有沙箱就没有「被沙箱挡了」和
19995
+ * 「沙箱没起来」这两种可能,硬跑一遍分类器只会拿当前平台压根不产生的方言
19996
+ * 去匹配(`terminal` 那条路也是这么分的,见 `plugin-terminal/src/sandbox.ts`
19997
+ * 的 `ConfinedShell.classify`)。
19998
+ */
19999
+ failure?: FailureVerdict;
20000
+ }
20001
+ /** hook 这一次跑在什么边界里 —— 形状对齐 `plugin-terminal` 的 `SandboxOutcome` */
20002
+ interface CommandHookSandbox {
20003
+ /** 真包上了没有。`false` 时看 {@link skipped} */
20004
+ confined: boolean;
20005
+ /** 按哪一档包的。**恒为 `workspace-write`**,判据在 {@link hookSandboxPolicy} 上 */
20006
+ mode: SandboxMode;
20007
+ backend: IsolationBackend;
20008
+ /**
20009
+ * 没包上的原因。三档是**三句不同的话**(同 infra 的 `Confinement.reason`,
20010
+ * 但少了 `config-disabled` 一档 —— hook 这一侧今天没有自己的开关,
20011
+ * 判据在 {@link hookSandboxPolicy}):
20012
+ * `mode-disabled` 走不到(mode 是钉死的),留着它是为了形状对齐。
20013
+ *
20014
+ * ⚠️ `Confinement.reason` 2026-10-27 多了一档(`escalated`;
20015
+ * 方案 65 §2.1),**它在这里刻意不存在**:那一档的唯一产地
20016
+ * 是 `SandboxPolicy.bypass`,而 hook 的 policy 从来不设那一格(见
20017
+ * {@link hookSandboxPolicy})。收窄在 {@link hookSkipReason} 里。
20018
+ */
20019
+ skipped?: 'config-disabled' | 'mode-disabled' | 'no-backend';
19327
20020
  }
19328
20021
 
19329
20022
  /**
@@ -21077,6 +21770,105 @@ declare function imagePartTokens(image: string): number | undefined;
21077
21770
  */
21078
21771
  declare function pendingImageTokens(messages: readonly EpochMessage[]): number;
21079
21772
 
21773
+ /**
21774
+ * 「这一次请求里图片一共占多少**字节**」—— 和那条 token 预算**是两个量纲**。
21775
+ *
21776
+ * ## 为什么非要有这一条线(2026-10-28)
21777
+ *
21778
+ * 仓库里已经有一条按容量的预算:`isPromptOversize`(protocol 的 `usage.ts:202`),
21779
+ * 它判 token 占窗口的几成。那条线**看不见图片的字节**,而这两件事在图片上会
21780
+ * 彻底分家:
21781
+ *
21782
+ * | 量纲 | 谁在看 | 一张 2 MB 的截图记多少 |
21783
+ * | --- | --- | --- |
21784
+ * | token | `isPromptOversize` / `shouldCompress` / `pendingImageTokens` | **约一千出头**(按宽高估,`image-tokens.ts:48`) |
21785
+ * | 字节 | **本文件** | 2.7 MB(base64 是原文的 4/3) |
21786
+ *
21787
+ * 而 chat completions 是**无状态**的:每一轮都把整段历史重发一遍,历史里每张图
21788
+ * 都以 base64 再传一次。于是「几个 2 MB 的截图」在 token 账上还空着九成,请求体
21789
+ * 已经撞穿了网关那条上限 —— 表现是 `PROVIDER_BODY_TOO_LARGE`(1051)或者说
21790
+ * 「请求体过大」,而**两条 token 线一个都不会动**。
21791
+ *
21792
+ * ## 为什么只数图片,不数整份请求体
21793
+ *
21794
+ * 因为**只有图片是「token 少、字节大」的那一个**。文本这两个量纲是单调同向的
21795
+ * (一段一 MB 的日志在 token 账上就是几十万),它太大时 `isPromptOversize` 那条
21796
+ * 线早就先抓住了,轮不到这里。
21797
+ *
21798
+ * 反过来做的代价很大:要数整份请求体就得 `JSON.stringify(system + tools + messages)`
21799
+ * 一遍 —— 那是每轮几 MB 的字符串拼接,而且它会把「这一轮该不该发」这个判断
21800
+ * 绑在 SDK 的消息形状上(`toModelMessages` 是唯一认那个形状的地方,
21801
+ * 见 `provider/sdk-adapter.ts` 的文件头)。
21802
+ *
21803
+ * ## 三类图里只有一类有字节可数
21804
+ *
21805
+ * `EpochImagePart.image` 有三种形态(判据在 protocol 的 `message.ts:60`):
21806
+ *
21807
+ * | 形态 | 数不数 | 为什么 |
21808
+ * | --- | --- | --- |
21809
+ * | 裸 base64 / `data:` URL | 数 | 它就摆在请求体里 |
21810
+ * | `http(s)` URL | **不数** | provider 自己去取,我们连字节都没有 —— 猜一个数去拦人是最坏的选择(判据同 `image-tokens.ts` 的那两条取舍) |
21811
+ *
21812
+ * `data:` 前缀(`data:image/png;base64,`,约 22 字节)**剥掉再数**:它是个常数、
21813
+ * 量级无关,但剥掉之后数出来的就是真正的载荷,和小票上那两个数对得上账
21814
+ * (`artifact.bytes` 是原始字节,base64 是它的 4/3)。
21815
+ */
21816
+
21817
+ /**
21818
+ * 这一组消息里**所有图片**的 base64 字节合计。
21819
+ *
21820
+ * ## 必须扫两处,不是一处
21821
+ *
21822
+ * 图有两条进请求体的路,而它们**恰好对应两种撑爆的现场**:
21823
+ *
21824
+ * | 从哪来 | 形状 | 对应哪种现场 |
21825
+ * | --- | --- | --- |
21826
+ * | 用户贴的图 | `message.parts[].type === 'image'` | 历史累积(第二轮那种,每条消息都发不出去) |
21827
+ * | **工具产物** | `toolResults[].artifacts[].data` | 一轮内累积(第一轮那种,工具跑几次就撑爆) |
21828
+ *
21829
+ * 只扫第一处的话,`stripHistoryImages`(`agent/loop.ts:269`)那种**只看 user 行
21830
+ * 的 `parts`** 的老盲区会原地复现一次:工具截图(`plugin-file` 的 `file_read` 读
21831
+ * png、`page_shot` 那类)全部漏掉,而它们恰恰是体积最大的一族。
21832
+ *
21833
+ * ## `artifacts[].data` 才是判据,不是 `artifacts` 非空
21834
+ *
21835
+ * 有没有 `data` 是闸门的结论(`context/artifact-gate.ts:183` 那段:有 `data` 就
21836
+ * 进上下文、没有就只是一句 summary)。被闸门拦下的图**一个字节都不进请求体**,
21837
+ * 把它算进预算会让下放发生在根本不需要的时候 —— 而那种误操作是有损的。
21838
+ */
21839
+ declare function requestImageBytes(messages: readonly EpochMessage[]): number;
21840
+ /**
21841
+ * 把**最老的**几张图换成占位文本,直到剩下的字节数落在预算内。
21842
+ *
21843
+ * ## `keepFresh` 是这一整个特性的安全性所在
21844
+ *
21845
+ * 它说的是「最新的 N 条消息里的图一张都不摘」。**必须 ≥ 1**,理由是这一轮用户
21846
+ * 刚贴的那张图:他正等着模型看图回答,把它摘掉等于把这一轮的活直接干废 ——
21847
+ * 而模型还会一本正经地照着一句「图片已下放」去答,用户完全看不出发生了什么。
21848
+ *
21849
+ * 所以下放只从**旧**的一头开始。代价是「一张巨图 + 预算很小」这一档下放不动
21850
+ * (最老的那张就是最新的那张),那时回 `offloaded: 0`,调用方据此知道
21851
+ * 「这一招不管用」—— 别在那儿硬摘一张正在用的图。
21852
+ *
21853
+ * ## 为什么不是「摘到刚好够」而是「按张为单位」
21854
+ *
21855
+ * 一张图是不可分的:摘一半的 base64 是坏数据。所以这里逐张摘,直到总量进预算。
21856
+ * 最坏情况是超摘一点点(最后那一张其实可以留着),那个代价可以接受 ——
21857
+ * 反过来「差一点没摘够」会让这次救援白跑一趟,而重发的那一发是要么成功要么
21858
+ * 再撞一次墙。
21859
+ *
21860
+ * @param keepFresh 最新的多少条消息里的图不碰。**0 是不安全的**(见上),
21861
+ * 调用方给 `undefined` 时按 1 处理
21862
+ */
21863
+ declare function offloadOldestImages(messages: readonly EpochMessage[], opts?: {
21864
+ budgetBytes: number;
21865
+ keepFresh?: number;
21866
+ }): {
21867
+ messages: EpochMessage[];
21868
+ offloaded: number;
21869
+ remainingBytes: number;
21870
+ };
21871
+
21080
21872
  /**
21081
21873
  * 上下文压缩器 — 默认 ContextEngine 实现。
21082
21874
  * 4 阶段算法(Hermes agent/context_compressor.py),**中间多了一道闸**
@@ -21224,7 +22016,7 @@ declare class ContextCompressor implements ContextEngine {
21224
22016
  */
21225
22017
  private cleanupOrphans;
21226
22018
  /**
21227
- * 一组消息的 token 总量。逐条过 {@link estimateTokens},不另立口径。
22019
+ * 一组消息的 token 总量。逐条过 {@link messageTokens},不另立口径。
21228
22020
  *
21229
22021
  * 这里原来是三个私有函数(`estimateTokens` / `estimateTokenCount` /
21230
22022
  * `tokensForChars`),各自写着 `chars / 4`,和 `model-metadata.ts` 里那份
@@ -21814,7 +22606,9 @@ interface RunCodeOutcome {
21814
22606
  * 失败的分类。**给模型看的措辞按这个分叉**,因为每一档的正确下一步都不一样:
21815
22607
  * 触顶要少调几次、超时要提 `timeout`、OOM 要少占内存、`throw` 要改程序。
21816
22608
  */
21817
- type RunCodeFailure = 'timeout' | 'oom' | 'cap' | 'throw' | 'aborted' | 'worker-missing';
22609
+ type RunCodeFailure = 'timeout' | 'oom' | 'cap' | 'throw' | 'aborted' | 'worker-missing'
22610
+ /** 权限策略把 worker 自己拦在门外 —— 判据在 `policy.ts` 的文件头 */
22611
+ | 'policy';
21818
22612
  declare function runCode(input: RunCodeInput): Promise<RunCodeOutcome>;
21819
22613
 
21820
22614
  /**
@@ -21859,4 +22653,4 @@ declare function resolveCodeModeWorker(baseDir: string, exists?: typeof existsSy
21859
22653
  */
21860
22654
  declare function codeModeWorkerPath(): string | undefined;
21861
22655
 
21862
- 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 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 };
22656
+ 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 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_MAX_HOLD_CHARS, DELEGATE_SESSION_SOURCE, 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_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, checkConfigValue, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, configKeyByPath, configKeyTable, 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, 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 };