@tencent-ai/agent-harness 0.2.0-beta.1 → 0.2.0-beta.2

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 (137) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/cli/CHANGELOG.md +71 -0
  4. package/cli/dist-server/shared.cjs +318 -314
  5. package/cli/dist-server/types/node/acp/acp-protocol.d.ts +40 -12
  6. package/cli/dist-server/types/node/acp/usage-by-category-calculator.d.ts +21 -0
  7. package/cli/dist-server/types/node/cli/stream-json-protocol.d.ts +15 -1
  8. package/cli/dist-server/types/node/data-fs/index.d.ts +13 -1
  9. package/cli/dist-server/types/node/hooks/hook-protocol.d.ts +2 -2
  10. package/cli/dist-server/types/node/server/sessions.d.ts +24 -0
  11. package/cli/dist-server/types/node/server/types.d.ts +6 -0
  12. package/cli/dist-server/types/node/settings/settings-protocol.d.ts +5 -0
  13. package/cli/dist-server/types/node/tools/tool-protocol.d.ts +112 -2
  14. package/cli/dist-server/types/vendor/cbc-core/node/agent/agent-protocol.d.ts +29 -0
  15. package/cli/dist-server/types/vendor/cbc-core/node/agent/agent-service.d.ts +2 -0
  16. package/cli/dist-server/types/vendor/cbc-core/node/agent/index.d.ts +1 -0
  17. package/cli/dist-server/types/vendor/cbc-core/node/agent/interceptors/input-interceptors.d.ts +1 -6
  18. package/cli/dist-server/types/vendor/cbc-core/node/agent/runner-factory.d.ts +31 -11
  19. package/cli/dist-server/types/vendor/cbc-core/node/agent/speaker-label.d.ts +44 -0
  20. package/cli/dist-server/types/vendor/cbc-core/node/agent/tool-call-loop-detector-service.d.ts +5 -4
  21. package/cli/dist-server/types/vendor/cbc-core/node/agent/tool-call-loop-guard-protocol.d.ts +1 -1
  22. package/cli/dist-server/types/vendor/cbc-core/node/agent/tool-call-loop-pattern.d.ts +15 -4
  23. package/cli/dist-server/types/vendor/cbc-core/node/hooks/agent-trigger.d.ts +6 -22
  24. package/cli/dist-server/types/vendor/cbc-core/node/hooks/hook-protocol.d.ts +4 -1
  25. package/cli/dist-server/types/vendor/cbc-core/node/session/idle-queue-drain-service.d.ts +4 -56
  26. package/cli/dist-server/types/vendor/cbc-core/node/session/index.d.ts +3 -0
  27. package/cli/dist-server/types/vendor/cbc-core/node/session/interruption-service.d.ts +55 -9
  28. package/cli/dist-server/types/vendor/cbc-core/node/session/model-response-interceptors.d.ts +18 -0
  29. package/cli/dist-server/types/vendor/cbc-core/node/session/recovery-budget.d.ts +55 -0
  30. package/cli/dist-server/types/vendor/cbc-core/node/session/session-hook-manager.d.ts +15 -0
  31. package/cli/dist-server/types/vendor/cbc-core/node/session/session-manager.d.ts +10 -1
  32. package/cli/dist-server/types/vendor/cbc-core/node/session/session-persistence-error.d.ts +43 -0
  33. package/cli/dist-server/types/vendor/cbc-core/node/session/session-protocol.d.ts +63 -4
  34. package/cli/dist-server/types/vendor/cbc-core/node/session/session-run-state-machine-protocol.d.ts +1 -0
  35. package/cli/dist-server/types/vendor/cbc-core/node/session/session-runtime-lifecycle.d.ts +5 -0
  36. package/cli/dist-server/types/vendor/cbc-core/node/session/session-store.d.ts +150 -4
  37. package/cli/dist-server/types/vendor/cbc-core/node/session/stream-timeout-retry-budget.d.ts +13 -1
  38. package/cli/dist-server/types/vendor/cbc-core/node/utils/history-utils.d.ts +23 -0
  39. package/cli/dist-server/types/vendor/cbc-core/node/utils/index.d.ts +1 -0
  40. package/cli/dist-server/types/vendor/cbc-core/node/utils/message-utils.d.ts +12 -0
  41. package/cli/dist-server/types/vendor/cbc-core/node/utils/model-utils.d.ts +7 -0
  42. package/cli/dist-server/types/vendor/cbc-core/node/utils/queue-dispatch-utils.d.ts +24 -2
  43. package/cli/dist-server/types/vendor/cbc-core/node/utils/session-utils.d.ts +4 -1
  44. package/cli/dist-server/types/vendor/cbc-core/node/utils/string-retention-utils.d.ts +4 -0
  45. package/cli/dist-server/types/vendor/cbc-shared/node/hooks/hook-types.d.ts +7 -1
  46. package/cli/dist-server/types/vendor/cbc-shared/node/index.d.ts +2 -0
  47. package/cli/dist-server/types/vendor/cbc-shared/node/model/model-errors.d.ts +7 -3
  48. package/cli/dist-server/types/vendor/cbc-shared/node/tools/tool-names.d.ts +1 -0
  49. package/cli/dist-server/types/vendor/cbc-shared/node/utils/retry-backoff.d.ts +30 -0
  50. package/cli/dist-server/types/vendor/cbc-shared/node/utils/retry-policy.d.ts +26 -0
  51. package/cli/dist-server/types/workspace/authentication/lib/common/account-scoped-external-link-authentication-provider.d.ts +2 -2
  52. package/cli/dist-server/types/workspace/authentication/lib/common/account-utils.d.ts +25 -0
  53. package/cli/dist-server/types/workspace/authentication/lib/common/authentication-manager.d.ts +2 -2
  54. package/cli/dist-server/types/workspace/authentication/lib/common/authentication-protocol.d.ts +19 -2
  55. package/cli/dist-server/types/workspace/authentication/lib/common/external-link-authentication-provider.d.ts +2 -2
  56. package/cli/dist-server/types/workspace/authentication/lib/common/wx-authentication-provider.d.ts +2 -2
  57. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/agent-protocol.d.ts +29 -0
  58. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/agent-service.d.ts +2 -0
  59. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/index.d.ts +1 -0
  60. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/interceptors/input-interceptors.d.ts +1 -6
  61. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/runner-factory.d.ts +31 -11
  62. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/speaker-label.d.ts +44 -0
  63. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/tool-call-loop-detector-service.d.ts +5 -4
  64. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/tool-call-loop-guard-protocol.d.ts +1 -1
  65. package/cli/dist-server/types/workspace/cbc-core/lib/node/agent/tool-call-loop-pattern.d.ts +15 -4
  66. package/cli/dist-server/types/workspace/cbc-core/lib/node/hooks/agent-trigger.d.ts +6 -22
  67. package/cli/dist-server/types/workspace/cbc-core/lib/node/hooks/hook-protocol.d.ts +4 -1
  68. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/idle-queue-drain-service.d.ts +4 -56
  69. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/index.d.ts +3 -0
  70. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/interruption-service.d.ts +55 -9
  71. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/model-response-interceptors.d.ts +18 -0
  72. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/recovery-budget.d.ts +55 -0
  73. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-hook-manager.d.ts +15 -0
  74. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-manager.d.ts +10 -1
  75. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-persistence-error.d.ts +43 -0
  76. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-protocol.d.ts +63 -4
  77. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-run-state-machine-protocol.d.ts +1 -0
  78. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-runtime-lifecycle.d.ts +5 -0
  79. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/session-store.d.ts +150 -4
  80. package/cli/dist-server/types/workspace/cbc-core/lib/node/session/stream-timeout-retry-budget.d.ts +13 -1
  81. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/history-utils.d.ts +23 -0
  82. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/index.d.ts +1 -0
  83. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/message-utils.d.ts +12 -0
  84. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/model-utils.d.ts +7 -0
  85. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/queue-dispatch-utils.d.ts +24 -2
  86. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/session-utils.d.ts +4 -1
  87. package/cli/dist-server/types/workspace/cbc-core/lib/node/utils/string-retention-utils.d.ts +4 -0
  88. package/cli/dist-server/types/workspace/cbc-shared/lib/node/hooks/hook-types.d.ts +7 -1
  89. package/cli/dist-server/types/workspace/cbc-shared/lib/node/index.d.ts +2 -0
  90. package/cli/dist-server/types/workspace/cbc-shared/lib/node/model/model-errors.d.ts +7 -3
  91. package/cli/dist-server/types/workspace/cbc-shared/lib/node/tools/tool-names.d.ts +1 -0
  92. package/cli/dist-server/types/workspace/cbc-shared/lib/node/utils/retry-backoff.d.ts +30 -0
  93. package/cli/dist-server/types/workspace/cbc-shared/lib/node/utils/retry-policy.d.ts +26 -0
  94. package/cli/dist-server/types/workspace/cbc-workflow/lib/node/workflow/workflow-execution.d.ts +14 -1
  95. package/cli/dist-server/types/workspace/cbc-workflow/lib/node/workflow/workflow-manager.d.ts +7 -0
  96. package/cli/dist-server/types/workspace/cbc-worktree/lib/node/worktree/worktree-session.d.ts +3 -0
  97. package/cli/dist-server/types/workspace/product/lib/common/cloud-product-provider.d.ts +9 -0
  98. package/cli/dist-server/types/workspace/product/lib/common/index.d.ts +1 -0
  99. package/cli/dist-server/types/workspace/product/lib/common/pick-market-skill-id.d.ts +13 -0
  100. package/cli/dist-server/types/workspace/product/lib/common/tool-control-catalog.d.ts +2 -2
  101. package/cli/dist-server/types/workspace/product/lib/common/types/features.d.ts +62 -2
  102. package/cli/dist-server/types/workspace/product/lib/common/types/language-model.d.ts +66 -0
  103. package/cli/dist-server/types/workspace/product/lib/common/types/product.d.ts +7 -1
  104. package/cli/dist-server/types/workspace/sandbox-cli/lib/common/backup-policy.d.ts +10 -1
  105. package/cli/dist-server/types/workspace/sandbox-cli/lib/common/center-protocol.generated.d.ts +370 -4
  106. package/cli/dist-server/types/workspace/sandbox-cli/lib/common/file-policy.d.ts +1 -1
  107. package/cli/dist-server/types/workspace/sandbox-cli/lib/node/sandbox-center-client.d.ts +216 -0
  108. package/cli/package.json +1 -1
  109. package/cli/product.cloudhosted.json +2 -2
  110. package/cli/product.internal.json +259 -40
  111. package/cli/product.ioa.json +333 -148
  112. package/cli/product.json +309 -351
  113. package/cli/product.selfhosted.json +2 -2
  114. package/docs/README.md +1 -0
  115. package/docs/in-process.md +16 -0
  116. package/lib/application/services.d.ts +3 -1
  117. package/lib/application/services.js +1 -1
  118. package/lib/components/sandbox-center.d.mts +2 -0
  119. package/lib/components/sandbox-center.mjs +1 -0
  120. package/lib/components/session-config.d.mts +2 -0
  121. package/lib/components/session-config.mjs +1 -1
  122. package/lib/harness/create-harness.js +1 -1
  123. package/lib/harness/runtime.js +1 -1
  124. package/lib/harness/types.d.ts +3 -0
  125. package/lib/integration/executor-supervisor.js +1 -1
  126. package/lib/public.d.ts +1 -0
  127. package/lib/runtime/sandbox-center.d.ts +21 -0
  128. package/lib/runtime/sandbox-center.js +1 -0
  129. package/lib/server/public.d.ts +1 -0
  130. package/lib/session/agent-backend.d.ts +2 -0
  131. package/lib/session/agent-backend.js +1 -1
  132. package/lib/session/local-fs.js +1 -1
  133. package/lib/session/server-config.js +1 -1
  134. package/lib/session/server-interactions.js +1 -1
  135. package/package.json +25 -15
  136. package/lib/session/local-options.d.ts +0 -50
  137. package/lib/session/local-options.js +0 -1
@@ -8,12 +8,44 @@ import { InterruptionService, type PendingApprovalView, Session, SessionHookMana
8
8
  import { SessionRunStateMachine } from './session-run-state-machine-protocol.js';
9
9
  type IIdleQueueDrainService = import('./session-protocol.js').IdleQueueDrainService;
10
10
  export declare class InterruptionServiceImpl implements InterruptionService {
11
+ private readonly resolvedSandboxItems;
11
12
  private pendingInterruptions;
12
13
  private itemToSessionMap;
13
14
  private interruptionQueues;
14
15
  private parentIdToMainSession;
15
16
  private childToMainSession;
16
17
  private waitForSubscriptions;
18
+ private readonly approvalSources;
19
+ private readonly handlingStates;
20
+ private readonly reportedStaleStates;
21
+ private readonly reportedDiscardedApprovals;
22
+ private readonly handledResults;
23
+ private readonly resumeProgress;
24
+ /**
25
+ * Terminal by design: a blocked continuation already proved it cannot make
26
+ * progress, and a later user request arrives on a *new* RunState, so the
27
+ * session is never stranded.
28
+ */
29
+ private readonly blockedResumeStates;
30
+ /**
31
+ * Deliberately not `session.meta.consecutiveEmptyDrainCount`: the idle
32
+ * empty-drain guard both increments that counter (tripping this
33
+ * continuation early, aborting a legitimate resume) and resets it
34
+ * (stretching the budget without bound).
35
+ */
36
+ private readonly resumeNoProgressCount;
37
+ private assertApprovalSource;
38
+ private approveSource;
39
+ private awaitPermissionReply;
40
+ /**
41
+ * A decision the user actually clicked must never disappear without a word.
42
+ *
43
+ * The card is dequeued and the deferred rejected, but the run that owned it
44
+ * is already gone — so nothing downstream surfaces the rejection and the
45
+ * user is left believing the tool was approved.
46
+ */
47
+ private reportDiscardedDecision;
48
+ private reportStaleSource;
17
49
  /**
18
50
  * 权限审批全局真相源(方案一:统一数据源)。
19
51
  *
@@ -61,6 +93,8 @@ export declare class InterruptionServiceImpl implements InterruptionService {
61
93
  protected readonly idleQueueDrainService?: IIdleQueueDrainService;
62
94
  protected readonly traceCollector?: SessionInterruptionTraceHost;
63
95
  protected get startupProfiler(): SessionInterruptionProfilerHost;
96
+ /** Claim before calling out: sandbox callbacks can throw or synchronously re-enter. */
97
+ private takeSandboxResolve;
64
98
  approve(item: RunToolApprovalItem, options?: {
65
99
  alwaysApprove?: boolean;
66
100
  scope?: 'session' | 'localSettings';
@@ -101,8 +135,9 @@ export declare class InterruptionServiceImpl implements InterruptionService {
101
135
  *
102
136
  * 幂等:未注册的 sessionId 为 no-op。
103
137
  */
104
- releaseSession(sessionId: string): void;
105
- protected handleInterruptions(session: Session, interruptions: RunToolApprovalItem[]): Promise<void>;
138
+ releaseSession(target: string | Session): void;
139
+ protected handleInterruptions(session: Session, interruptions: RunToolApprovalItem[], result?: RunResult<any, any> | StreamedRunResult<any, any> | undefined): Promise<void>;
140
+ private processInterruptions;
106
141
  protected executePendingManualPostCompactHook(session: Session, result: RunResult<any, any> | StreamedRunResult<any, any>): Promise<void>;
107
142
  waitFor(session: Session): Promise<void>;
108
143
  /**
@@ -334,15 +369,26 @@ export declare class InterruptionServiceImpl implements InterruptionService {
334
369
  private static sanitizeToolNotFoundMessage;
335
370
  private hasRecentModelErrorRetry;
336
371
  /**
337
- * 检测是否已有 stream timeout 重试记录
338
- * 避免反复重试导致无限循环
372
+ * 网络 / 流错误自动恢复的统一前置步骤:认领额度 → 可取消的指数退避 → 标记恢复轮。
373
+ *
374
+ * 认领放在退避之前、且晚于本 catch 里的第一个 await:session-manager 的 gate 在
375
+ * `result.completed` reject 后同步读取同一份预算判据,认领若早于 gate 读取,最后
376
+ * 一次重试会被 gate 误判为「预算已耗尽」而提前下发终态。
377
+ *
378
+ * 退避期间会话处于 RETRYING:失败的 run 与写入中断结果的 skipRun run 都已收尾回 idle,
379
+ * 这里重新置为 busy,TUI 继续显示进行中并响应 Esc,用户新消息进队列而不是直接起 run。
380
+ * 恢复 run 带 bypassMessageQueue 从 RETRYING 直接发起,中间不回 idle,不留让新 run
381
+ * 插入的空窗;恢复 run 收尾(AGENT_ENDED)时回 idle 并带出排队消息。
382
+ *
383
+ * 退避期间用户取消时退回额度、以 RUN_FAILED 收回 idle 并返回 `cancelled`,调用方不得
384
+ * 再发起。取消信号在等待前现取:写入中断结果的 skipRun run 会替换 `session.abortController`,
385
+ * 用户此后按下的 Stop 作用在新 controller 上;排队消息接管 owner 时,其 abort 会级联到这里。
339
386
  *
340
- * 首 token 超时:允许最多 2 次重试(含线性延迟 2s/4s),给网络波动更多恢复机会
341
- * 流数据超时:保持只重试 1 次
342
- * 旧的基于历史消息窗口的检测不可靠:长任务(如 8 个子任务)会产生大量消息,
343
- * 将 retry marker 推出 5 条消息的窗口,导致误判为"可以再次重试"
387
+ * 等待结束时若状态既不是 RETRYING 也不是 idle,说明 RETRYING 被迟到的收尾事件冲掉后
388
+ * 已有新 run 被接纳并在执行,返回 `superseded`,否则恢复 run 会与它同时写同一个会话。
389
+ * 此时不退回额度——新请求边界已经清零,再减一次会扣掉新链的额度。
344
390
  */
345
- private incrementStreamTimeoutRetryCount;
391
+ private prepareRecoveryAttempt;
346
392
  /**
347
393
  * CLI TUI 手打 `/compact` 裸跑成功后补发 SessionStart('compact')。
348
394
  *
@@ -29,6 +29,24 @@ export declare class finishReasonErrorInterceptor implements ModelResponseInterc
29
29
  protected readonly sessionStore: SessionStore;
30
30
  protected readonly telemetryService: TelemetryService;
31
31
  intercept(ctx: ModelResponseInterceptorContext): Promise<boolean | void>;
32
+ /**
33
+ * 处理「已经产出实质内容之后才 `finish_reason=error`」:自动续写,最多 3 次、1s 起步 2 倍退避。
34
+ *
35
+ * 没有任何实质输出就 error 的两种形态(仅 reasoning / 零 token)在模型层被转成可重试的
36
+ * EmptyStreamError,走 interruption-service 的流错误恢复;三者认领同一份 error 预算。
37
+ *
38
+ * 能走到这里说明这次 run 已经结束:SDK 只要本轮还有可执行的工具调用就会继续循环,
39
+ * 不会把控制权交回。因此不存在「参数完整但尚未执行」的工具调用;万一出现,每次构造
40
+ * 请求前的 `fillFunctionCallResultHistory` 也会补齐配对,不会因孤儿调用触发上游 400。
41
+ * 本期只统计孤儿数量用于验证这一推论,不改补齐逻辑。
42
+ *
43
+ * 预算用尽、退避期间被取消或发起失败时都**不返回 `true`**:没有重跑在路上,本轮必须
44
+ * 正常收敛给上层,否则 CLI 会一直等一个不存在的 run。
45
+ */
46
+ private handleErrorFinishReason;
47
+ private writeErrorTerminalMessage;
48
+ /** 统计本轮以 error 收尾时残留的无配对结果工具调用,用于验证「此处不应有孤儿」的推论。 */
49
+ private logOrphanToolCallsOnError;
32
50
  private shouldSkipExpectedSubagentToolCalls;
33
51
  /**
34
52
  * 处理 length 类型的完成原因:从断点续写(#119765)。
@@ -0,0 +1,55 @@
1
+ import type { Session } from './session-protocol.js';
2
+ /**
3
+ * `finish_reason=error` 三种表现(已有输出 / 仅 reasoning / 零 token)共用的预算键。
4
+ *
5
+ * 三者是同一次上游故障的不同形态,而且会在恢复过程中互相转化(第一次零 token、
6
+ * 重试后只吐 reasoning、再重试吐了内容后又 error)。各记各的会让一次故障的总轮数
7
+ * 远超约定上限,所以必须认领同一个键。
8
+ */
9
+ export declare const ERROR_RECOVERY_COUNT_META_KEY = "errorRecoveryCount";
10
+ /**
11
+ * 流开始之后的网络错误(错误仍带原始 errno)使用的预算键。
12
+ *
13
+ * 响应头之前的连接失败由模型层连接级重试处理,耗尽后被归一化成 HTTP 502 上抛,
14
+ * 不会再走到这里——两层不会为同一次失败接力。
15
+ */
16
+ export declare const NETWORK_RECOVERY_COUNT_META_KEY = "networkErrorRetryCount";
17
+ /**
18
+ * `finish_reason=error` 后注入给模型的续写提示。
19
+ *
20
+ * 与截断续写(TRUNCATION_CONTINUATION_PROMPT)的区别是刻意去掉了「把剩下的工作拆小」:
21
+ * 那句是在修「一次输出太长」的因果,上游内部错误与输出长度无关,带上它既是错误归因,
22
+ * 也可能让模型把任务切碎、多烧轮次。同样不告诉模型「是服务端出错」——那会给它道歉和
23
+ * 复述的由头,与 no apology / no recap 自相矛盾。
24
+ */
25
+ export declare const ERROR_RECOVERY_PROMPT: string;
26
+ /**
27
+ * 包成 error-recovery system-reminder:stream-json 等输出通道会把只含 system-reminder
28
+ * 的内部用户消息清空丢弃,避免恢复提示混进用户可见的对话。
29
+ */
30
+ export declare function buildErrorRecoveryReminder(): string;
31
+ export declare function readRecoveryBudgetCount(session: Session, key: string): number;
32
+ export declare function hasExhaustedRecoveryBudget(session: Session, key: string, limit?: number): boolean;
33
+ /**
34
+ * 同步完成「判定 + 认领」,返回本次恢复的序号(1-based);预算已用尽返回 `undefined`。
35
+ *
36
+ * 判定与消耗必须在同一个同步段里完成:分散到 `await` 两侧时,两处调用可能同时在
37
+ * `count = limit - 1` 通过检查,上限就不再成立。
38
+ */
39
+ export declare function claimRecoveryBudget(session: Session, key: string, limit?: number): number | undefined;
40
+ /** 认领后没有真正发起恢复(退避期间被取消、发起失败)时退回额度。 */
41
+ export declare function releaseRecoveryBudget(session: Session, key: string): void;
42
+ /** 清零并返回旧值,供日志使用。 */
43
+ export declare function resetRecoveryBudget(session: Session, key: string): number;
44
+ /**
45
+ * 「下一个模型请求是自动恢复轮」的一次性标记。
46
+ *
47
+ * 恢复轮的首 token 时限比首次请求短(见模型层)。这个标记在发起恢复 run 前由恢复
48
+ * 入口显式写入,由模型层在该 run 的第一个模型请求上读取并清除——之后同一 run 的工具
49
+ * 循环请求回到普通时限。不能改用重试计数反推「是否恢复轮」:计数跨多个模型请求累计,
50
+ * 反推会让恢复链之后的所有请求都被当成恢复轮。
51
+ */
52
+ export declare const RECOVERY_REQUEST_PENDING_META_KEY = "recoveryRequestPending";
53
+ export declare function markRecoveryRequestPending(session: Session): void;
54
+ export declare function consumeRecoveryRequestPending(session: Session | undefined): boolean;
55
+ export declare function clearRecoveryRequestPending(session: Session): void;
@@ -208,4 +208,19 @@ export declare class SessionHookManagerImpl implements SessionHookManager {
208
208
  * Does NOT support continueRequest.
209
209
  */
210
210
  executeFinalStopHooks(session: Session, reason?: FinalStopReason): Promise<void>;
211
+ /**
212
+ * UserPromptExpansion — Claude Code 官方事件:任何用户输入的命令/skill 展开成
213
+ * prompt、送进模型之前统一从这里触发,可阻断展开。`command_args` / `prompt` 按
214
+ * 官方 schema 是必填字段(无参数时发空串,而不是整个省略 key)。
215
+ */
216
+ executeUserPromptExpansionHooks(session: Session, payload: {
217
+ expansionType: 'slash_command' | 'mcp_prompt' | string;
218
+ commandName: string;
219
+ commandArgs: string;
220
+ commandSource?: string;
221
+ prompt: string;
222
+ }): Promise<{
223
+ allowed: boolean;
224
+ message?: string;
225
+ }>;
211
226
  }
@@ -33,6 +33,15 @@ export declare class SessionManagerImpl implements SessionManager, ApplicationLi
33
33
  private sessionStartTimes;
34
34
  private addHistoryLocks;
35
35
  private resultSubscriptions;
36
+ private readonly resultSubscriptionOwners;
37
+ /**
38
+ * 每个 sessionId 当前活着的会话对象。身份守卫不能只认 result 订阅的登记:
39
+ * 订阅由调用方事后 `initRunResultHandler` 挂上(teammate / agent-task),而
40
+ * drain / team-inbox / turn-metrics 在 create 时就按 id 注册了,中间那段
41
+ * 「已注册、无订阅」的窗口里 owner 读不到,旧任务的延迟释放会摘掉同 id 后继
42
+ * 会话的注册。WeakRef:登记不得延长会话寿命,deref 为空即无需保护的对象。
43
+ */
44
+ private readonly liveSessionsById;
36
45
  private overridePermissionModes;
37
46
  private overrideModels;
38
47
  /**
@@ -177,7 +186,7 @@ export declare class SessionManagerImpl implements SessionManager, ApplicationLi
177
186
  *
178
187
  * 幂等:未注册的 id 为 no-op;DI 容错:精简变体缺 service 时降级跳过。
179
188
  */
180
- releaseInMemory(id: string): void;
189
+ releaseInMemory(target: string | Session): void;
181
190
  rename(session: Session, name: string): Promise<void>;
182
191
  setOverridePermissionMode(sessionId: string, mode: PermissionMode): void;
183
192
  getOverridePermissionMode(sessionId: string): PermissionMode | undefined;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Typed local-transcript I/O failure (issue #119545).
3
+ *
4
+ * The only reason this type exists: `addHistory` must be able to tell "the
5
+ * local JSONL copy could not be written" apart from "this turn is broken".
6
+ * Windows `EBUSY` on the transcript is a third-party sharing violation, and a
7
+ * side-channel copy must never terminate a turn that is still executing tools.
8
+ *
9
+ * Everything else about persistence stays best-effort on purpose: there is no
10
+ * pending queue, no cross-process outbox and no durability barrier, so nothing
11
+ * else in the runtime waits on, or can be vetoed by, this file.
12
+ */
13
+ export type SessionPersistencePhase = 'open' | 'stat' | 'write' | 'close' | 'rollback';
14
+ export type SessionPersistenceRollbackOutcome = 'not-needed' | 'succeeded' | 'failed';
15
+ export interface SessionPersistenceErrorOptions {
16
+ phase: SessionPersistencePhase;
17
+ cause: unknown;
18
+ attempt?: number;
19
+ rollbackOutcome?: SessionPersistenceRollbackOutcome;
20
+ retryable?: boolean;
21
+ }
22
+ /**
23
+ * Windows sharing violations and their POSIX lookalikes. `EBUSY` is the one
24
+ * this ticket measured (25% of appends within 10ms of the previous write);
25
+ * `EPERM` / `EACCES` ride along because the same scanners produce them.
26
+ */
27
+ export declare function isRetryableSessionPersistenceError(error: unknown): boolean;
28
+ export declare class SessionPersistenceError extends Error {
29
+ readonly phase: SessionPersistencePhase;
30
+ readonly code?: string;
31
+ readonly attempt: number;
32
+ readonly rollbackOutcome: SessionPersistenceRollbackOutcome;
33
+ readonly retryable: boolean;
34
+ constructor(options: SessionPersistenceErrorOptions);
35
+ }
36
+ /**
37
+ * Duck-typed on purpose: core and the host package can each hold their own copy
38
+ * of this class, so `instanceof` is not reliable across the package boundary.
39
+ * The required fields keep a foreign error named `SessionPersistenceError` from
40
+ * being mistaken for one of ours — an unrecognized error still fails the run,
41
+ * which is the safe direction.
42
+ */
43
+ export declare function isSessionPersistenceError(error: unknown): error is SessionPersistenceError;
@@ -89,6 +89,7 @@ export type AgentPhase = 'preparing' | 'model_requesting' | 'model_streaming' |
89
89
  export interface AgentPhaseInfo {
90
90
  phase: AgentPhase;
91
91
  toolName?: string;
92
+ toolCallId?: string;
92
93
  streamingToolName?: string;
93
94
  startedAt: number;
94
95
  /**
@@ -177,6 +178,8 @@ export interface PreToolUseHookResult {
177
178
  permissionDecision?: 'allow' | 'deny' | 'ask';
178
179
  permissionDecisionReason?: string;
179
180
  runtimeSecurity?: Record<string, unknown>;
181
+ /** PreToolUse `continue:false` — stop the current turn after the deny result is recorded. */
182
+ preventContinuation?: boolean;
180
183
  }
181
184
  export interface Session {
182
185
  id: string;
@@ -206,6 +209,8 @@ export interface Session {
206
209
  */
207
210
  replSandboxDepth?: number;
208
211
  state?: RunState<any, any>;
212
+ /** Emitted after a physical run releases its lifecycle owner and abort links. */
213
+ runSettledSubject?: Subject<void>;
209
214
  createdAt: number;
210
215
  updatedAt: number;
211
216
  meta?: Record<string, any>;
@@ -638,6 +643,12 @@ export interface Session {
638
643
  * Context from UserPromptSubmit hook to be injected into current user message
639
644
  */
640
645
  userPromptSubmitContext?: string;
646
+ /**
647
+ * Context from UserPromptExpansion hook (hookSpecificOutput.additionalContext),
648
+ * to be injected alongside the expanded prompt (Claude Code parity: same
649
+ * injection point as UserPromptSubmit — "alongside the submitted prompt").
650
+ */
651
+ userPromptExpansionContext?: string;
641
652
  /**
642
653
  * Context from PostToolUse hook to be injected after tool execution
643
654
  */
@@ -915,6 +926,8 @@ export interface SessionOptions {
915
926
  debugToStderr?: boolean;
916
927
  verbose?: boolean;
917
928
  print?: boolean;
929
+ /** Host Bash output preference; omitted preserves the CLI's PTY selection. */
930
+ usePty?: boolean;
918
931
  outputFormat?: 'text' | 'json' | 'stream-json';
919
932
  inputFormat?: 'text' | 'stream-json';
920
933
  /** Whether to persist the session transcript. Defaults to true; false keeps history in memory only. */
@@ -1123,9 +1136,11 @@ export interface SessionManager {
1123
1136
  *
1124
1137
  * 幂等:未注册的 sessionId 为 no-op。
1125
1138
  *
1126
- * @param id 已完成 subagent 的 session id
1139
+ * @param target 传 Session 对象时,索引键与身份校验同源——只有注册表当前持有
1140
+ * 的正是这个对象才释放,复用同一 sessionId 的后继对象不会被误清。只拿得到
1141
+ * id 的调用方(RPC、`remove(id)`)传字符串,此时不做身份校验。
1127
1142
  */
1128
- releaseInMemory(id: string): void;
1143
+ releaseInMemory(target: string | Session): void;
1129
1144
  /**
1130
1145
  * 该 sessionId 是否已在本进程通过 get() 加载过(且尚未 remove()/releaseInMemory())。
1131
1146
  *
@@ -1216,8 +1231,31 @@ export interface SessionManager {
1216
1231
  */
1217
1232
  flushHistory(session: Session): Promise<void>;
1218
1233
  }
1234
+ /**
1235
+ * Cumulative transcript persistence counters (issue #119545 埋点建议).
1236
+ * A best-effort side channel, so every reader must treat the numbers as a
1237
+ * snapshot that may be sampled at any moment.
1238
+ */
1239
+ export interface TranscriptPersistenceMetrics {
1240
+ /** In-band retry attempts consumed. */
1241
+ retries: number;
1242
+ /** Batches that only landed because of a retry. */
1243
+ retrySucceeded: number;
1244
+ /** Batches given up on after the retry budget. */
1245
+ abandonedBatches: number;
1246
+ /** Lines lost with those batches. */
1247
+ abandonedLines: number;
1248
+ }
1219
1249
  export interface SessionStore {
1220
1250
  getSessionFilePath(id: string, parentSessionId?: string, cwd?: string): string;
1251
+ /**
1252
+ * Transcript persistence counters, when the implementation keeps any.
1253
+ * Optional so in-memory and host-delegating stores do not have to fake it.
1254
+ * Exposed so callers that observe a degradation can log the counters from
1255
+ * their own call site: the store's own abandon audit line is best-effort
1256
+ * and a lost line would otherwise take the only copy of the numbers.
1257
+ */
1258
+ transcriptPersistenceMetrics?(): Readonly<TranscriptPersistenceMetrics>;
1221
1259
  get(id: string, options?: SessionGetOptions): Promise<Session | undefined>;
1222
1260
  getAcrossProjects(id: string, options?: SessionGetOptions): Promise<Session | undefined>;
1223
1261
  save(session: Session, cwd?: string): Promise<void>;
@@ -1376,6 +1414,26 @@ export interface SessionHookManager {
1376
1414
  * Does NOT support continueRequest.
1377
1415
  */
1378
1416
  executeFinalStopHooks(session: Session, reason?: FinalStopReason): Promise<void>;
1417
+ /**
1418
+ * UserPromptExpansion — Claude Code 官方事件(非 cbc 专属扩展):任何用户输入的
1419
+ * 命令/skill 在展开成 prompt、送进模型之前统一从这里触发,可阻断展开。适用于
1420
+ * skill、自定义命令、Dynamic Workflow、MCP prompt 等一切"输入即展开"的路径,
1421
+ * 不是只服务某一个具体调用方(如统一资产门控)。
1422
+ */
1423
+ executeUserPromptExpansionHooks(session: Session, payload: {
1424
+ /** Claude Code spec: 'slash_command' for skill/custom commands, 'mcp_prompt' for MCP prompts. */
1425
+ expansionType: 'slash_command' | 'mcp_prompt' | string;
1426
+ commandName: string;
1427
+ /** Claude Code spec: required field (empty string when no args), always sent. */
1428
+ commandArgs: string;
1429
+ /** e.g. Skill.source / Command.scope ('project' | 'user' | 'plugin' | 'mcp' | ...). */
1430
+ commandSource?: string;
1431
+ /** Claude Code spec: required field — raw, pre-expansion prompt text as typed by the user. */
1432
+ prompt: string;
1433
+ }): Promise<{
1434
+ allowed: boolean;
1435
+ message?: string;
1436
+ }>;
1379
1437
  }
1380
1438
  export interface RunItemProcessor {
1381
1439
  process(runItem: RunItem): Promise<void>;
@@ -1494,9 +1552,10 @@ export interface InterruptionService {
1494
1552
  *
1495
1553
  * 幂等:session 未注册时为 no-op。只清内存映射,不动磁盘 jsonl。
1496
1554
  *
1497
- * @param sessionId 已完成 subagent 的 session id
1555
+ * @param target 传 Session 对象时按对象身份校验,同 id 的后继对象不会被误清;
1556
+ * 只拿得到 id 的调用方传字符串,此时不做身份校验。
1498
1557
  */
1499
- releaseSession(sessionId: string): void;
1558
+ releaseSession(target: string | Session): void;
1500
1559
  }
1501
1560
  export interface MessageQueueManager {
1502
1561
  enqueueMessage(session: Session, message: AgentInputItem): string;
@@ -114,6 +114,7 @@ export type SessionRunEvent = {
114
114
  } | {
115
115
  type: SessionRunEventType.TOOL_STARTED;
116
116
  toolName?: string;
117
+ toolCallId?: string;
117
118
  reason?: string;
118
119
  } | {
119
120
  type: SessionRunEventType.TOOL_ENDED;
@@ -0,0 +1,5 @@
1
+ import type { Session } from './session-protocol.js';
2
+ export declare function isSessionRuntimeClosed(session: Session): boolean;
3
+ export declare function closeSessionRuntime(session: Session): void;
4
+ export declare function sessionRuntimeSignal(session: Session): AbortSignal;
5
+ export declare function assertSessionRuntimeOpen(session: Session | undefined): void;
@@ -2,7 +2,7 @@ import { Logger } from '@celljs/core';
2
2
  import { type AgentInputItem } from '../../../../cbc-shared/lib/node/index.js';
3
3
  import * as fs from 'fs/promises';
4
4
  import { LocalJsonlSessionStore } from './local-jsonl-session-store.js';
5
- import { type Session, SessionFactory, type SessionForkMutationOptions, type SessionGetOptions, type SessionRewindMutationOptions, SessionStore } from './session-protocol.js';
5
+ import { type Session, SessionFactory, type SessionForkMutationOptions, type SessionGetOptions, type SessionRewindMutationOptions, SessionStore, type TranscriptPersistenceMetrics } from './session-protocol.js';
6
6
  import type { Lane, LanePointer, SessionKey, SessionStoreEntry } from './session-store-contract.js';
7
7
  import { SessionImageStorage as ImageBlobService, SessionRestoreHost, SessionStoragePaths, SessionToolOutputStorage as ToolResultBlobService } from './session-store-host-protocol.js';
8
8
  /**
@@ -10,6 +10,27 @@ import { SessionImageStorage as ImageBlobService, SessionRestoreHost, SessionSto
10
10
  * 达到此阈值且遇到 user message 时停止逆序读取,避免启动时全量读 JSONL。
11
11
  */
12
12
  export declare const DEFAULT_SESSION_MAX_ITEMS = 1000;
13
+ /**
14
+ * 每追加这么多条 history 行,就把当前 session-meta 快照重新补写到文件尾部。
15
+ *
16
+ * 读侧按 item 数封顶逆序读,只在窗口内找 session-meta;写侧按内容去重,身份
17
+ * 不变就只有创建时那一行(文件第 1 行)。会话一长,读窗口够不到它 → resume
18
+ * 静默丢 `codebuddy.ai/mainAgent`(PTC 掉回 cli、REPL 消失)/ `multitaskMode`
19
+ * / `codebuddy.ai/a2aContexts` / `codebuddy.ai/hostKind`(#128860)。
20
+ *
21
+ * 取值远小于 {@link DEFAULT_SESSION_MAX_ITEMS},读侧即使把上限调低也仍能覆盖;
22
+ * 代价是每 200 行多一条约 200 字节的行(≈0.1%)。
23
+ *
24
+ * **与 {@link SESSION_META_HEAD_SCAN_BYTES} 是配对约束,必须满足**
25
+ * `SESSION_META_TAIL_REFRESH_LINES × 单行平均字节 << SESSION_META_HEAD_SCAN_BYTES`:
26
+ * 写侧靠本行距把快照压在文件尾部,读侧只有 256KB 的文件头兜底窗口 —— 一旦尾部快照
27
+ * 滑出头部窗口,长会话 resume 丢键(#128860 本体)就会以另一种形式回归。
28
+ * 当前 200 × ~200B ≈ 40KB,对 256KB 有约 6× 余量。**调大本值或让单行显著变长
29
+ * (例如工具结果外置后仍很大的行)都必须同步核对另一侧**,spec 里有一条边界断言钉住。
30
+ */
31
+ export declare const SESSION_META_TAIL_REFRESH_LINES = 200;
32
+ /** 文件头 session-meta 兜底读取的字节窗口(存量会话用,不做全量回扫)。与上者配对,见其注释。导出以便 spec 钉住配对约束。 */
33
+ export declare const SESSION_META_HEAD_SCAN_BYTES: number;
13
34
  /**
14
35
  * Keys of session.meta that persist across processes (the off-tree
15
36
  * session-meta line; bin resume only — served sessions never write). A
@@ -22,6 +43,37 @@ export declare const DEFAULT_SESSION_MAX_ITEMS = 1000;
22
43
  * Role / locks / parentSessionId stay runtime-only.
23
44
  */
24
45
  export declare const PERSISTED_SESSION_META_KEYS: readonly ['codebuddy.ai/mainAgent', 'codebuddy.ai/hostKind', 'multitaskMode', 'codebuddy.ai/a2aContexts'];
46
+ /**
47
+ * 白名单里「缺席即删除」的键:只认最新那一行的存在性,不向更早的行按键继承。
48
+ *
49
+ * 其余键(mainAgent / hostKind / a2aContexts)只会被赋值、不会被 delete,所以
50
+ * 读侧按键继承——某个写方视野不全(只快照了自己关心的键)不能把别的键擦掉
51
+ * (#128860:`/multitask` 盖章写出只含 multitaskMode 的行,把 PTC 站立身份永久
52
+ * 抹掉,`/agent-mode` 变成 Standard(cli))。
53
+ *
54
+ * `multitaskMode` 是唯一会被真正 delete 的键(multitask/session-flags.ts 的
55
+ * clearMultitaskSession),所以必须留在这里,否则"关掉多任务"会被更早的
56
+ * `true` 复活。**新增白名单键时**:只要它的"缺席"承载语义,就必须登记到本表。
57
+ *
58
+ * 注意「缺席」要能写进文件才算数:子集被删空时 `persistSessionMeta` 会补一条
59
+ * `meta: {}` 的墓碑行,否则最新那条仍是删除前的快照,缺席永远不会出现。墓碑是
60
+ * **整份空快照**:本表登记第二个键之后,为删 A 写的墓碑会连带把 B 也判成缺席,
61
+ * 届时得改成按键墓碑,别直接往表里加。
62
+ */
63
+ export declare const SNAPSHOT_ONLY_SESSION_META_KEYS: ReadonlyArray<string>;
64
+ /**
65
+ * 参与合并的一条 session-meta 行。
66
+ *
67
+ * `id` 可能是空串:#94203 才给这行加上自身 UUIDv7,更早写下的存量行没有 id
68
+ * (CHANGELOG 明确「兼容旧的无 ID 记录」)。任何按 id 判等的地方都必须先处理
69
+ * 这个缺失,见 {@link SessionStoreImpl.metaLineIdentity}。
70
+ */
71
+ interface SessionMetaLine {
72
+ id: string;
73
+ meta: Record<string, unknown>;
74
+ /** 行原文(trim 后)。缺 id 的存量行靠它做判等。 */
75
+ raw: string;
76
+ }
25
77
  /**
26
78
  * 解析 session 历史回放的 maxItems。
27
79
  *
@@ -46,6 +98,7 @@ export declare class SessionStoreImpl implements SessionStore {
46
98
  protected readonly paths: SessionStoragePaths;
47
99
  protected readonly restoreHost: SessionRestoreHost;
48
100
  private writeLocks;
101
+ private readonly transcriptMetrics;
49
102
  private readonly transcriptAuditOnce;
50
103
  private transcriptAuditSequence;
51
104
  /**
@@ -113,6 +166,16 @@ export declare class SessionStoreImpl implements SessionStore {
113
166
  * behavioral loop: externalize → Read → externalize → Read …).
114
167
  */
115
168
  private externalizeToolResultIfNeeded;
169
+ /**
170
+ * Externalize the UI-only copy of a tool result. Tool implementations commonly store
171
+ * the same payload again in providerData.toolResult.content/renderer; keeping that copy
172
+ * defeats output externalization and can make a single JSONL row hundreds of KiB.
173
+ *
174
+ * The render payload is persisted as one JSON document. History keeps a text renderer
175
+ * pointing at that file, so clients always have a visible fallback instead of trying to
176
+ * parse a <persisted-output> marker as the original media/custom renderer payload.
177
+ */
178
+ private externalizeToolRenderDataIfNeeded;
116
179
  /**
117
180
  * Handle externalization for structured array output.
118
181
  * Iterates through array elements and externalizes any input_text items that exceed the threshold.
@@ -139,7 +202,25 @@ export declare class SessionStoreImpl implements SessionStore {
139
202
  */
140
203
  getSessionMetaFilePath(id: string, parentSessionId?: string, cwd?: string): string;
141
204
  private lastPersistedMeta;
205
+ /** 各转录文件「距上次 session-meta 行」的 history 行数(进程内,用于尾部刷新节流)。 */
206
+ private historyLinesSinceMeta;
207
+ /** 读侧靠文件头兜底才拿到 meta 的转录文件:下一批 append 立即把快照补到尾部。 */
208
+ private staleTailMeta;
209
+ /**
210
+ * 「据已知,该转录尾部最新那条 session-meta 还带着非空子集」——决定子集被删空时
211
+ * 要不要补墓碑。
212
+ *
213
+ * 判据必须是**文件事实**而不是"本进程写过什么":resume 只 `get()` 不 `save()`
214
+ * (agent-task.ts 的子代理恢复就是这样),进程内缓存恒空,冷启动后第一轮就关掉
215
+ * 多任务会写不出墓碑,"关掉"永远落不了盘。所以读侧 load 完也要维护它。
216
+ *
217
+ * 仍是进程内状态:同一转录被两个进程同时写时,另一方的非空写入本进程不可见
218
+ * (与 `lastPersistedMeta` 同一层限制)。
219
+ */
220
+ private tailMetaNonEmpty;
142
221
  private persistedMetaSubset;
222
+ /** 一条 off-tree session-meta 行的字节形式。空 `persisted` 即墓碑快照。 */
223
+ private metaSnapshotLine;
143
224
  /** The off-tree session-meta line, or undefined when there is nothing to persist. */
144
225
  protected sessionMetaLineFor(fileId: string, meta: Session['meta']): string | undefined;
145
226
  /**
@@ -150,7 +231,59 @@ export declare class SessionStoreImpl implements SessionStore {
150
231
  * .meta.json overwrite. Bin surface only — served sessions never reach
151
232
  * here (persistence gate; locked by spec).
152
233
  */
153
- protected persistSessionMeta(fileId: string, meta: Session['meta'], parentSessionId?: string, cwd?: string): Promise<void>;
234
+ protected persistSessionMeta(fileId: string, meta: Session['meta'], parentSessionId?: string, cwd?: string, options?: {
235
+ force?: boolean;
236
+ }): Promise<void>;
237
+ /**
238
+ * 尾部快照刚落盘后的进程内状态收尾 —— 四份状态必须一起动。
239
+ *
240
+ * `lastPersistedMeta` / `historyLinesSinceMeta` / `staleTailMeta` / `tailMetaNonEmpty`
241
+ * 都以 filePath 为键、都描述「这个转录尾部最新那条 meta 现在是什么」。任何往尾部
242
+ * 写快照的路径(persistSessionMeta / initializeForkHistory)都要走这里:在调用点
243
+ * 手写四行,下一次再加状态时必然漏一处 —— fork 初始化就是这么漏的(lastPersistedMeta
244
+ * 写了,另外三份没写,导致 fork 后关掉多任务写不出墓碑、行数计数带着旧值)。
245
+ */
246
+ private markTailMetaPersisted;
247
+ /**
248
+ * 让「最新 session-meta」始终留在读侧的逆序窗口内(#128860 根治的写入侧)。
249
+ *
250
+ * 只在 history 真正落盘之后调用:每 {@link SESSION_META_TAIL_REFRESH_LINES}
251
+ * 行补写一次快照;读侧走过文件头兜底的文件(存量长会话)立即补写一次,
252
+ * 这样同一个会话只会退化一次。
253
+ *
254
+ * 失败不能拖垮本轮 append —— 转录字节已经写成功了,meta 下一批还会重试。
255
+ */
256
+ private refreshTailSessionMeta;
257
+ /**
258
+ * 文件头的 session-meta 兜底读取。
259
+ *
260
+ * `session-meta` 创建行通常在文件靠前的位置(新建会话是第 1 行;子会话/迁移过的
261
+ * 会话可能更靠后),而 history 逆序读有 item 上限——长会话倒着读到不了那里
262
+ * (#128860)。这里只读文件头一小段,不做全量回扫:
263
+ * 29MB 的会话里末尾 1100 行只占 20% 字节,回扫到头会把 resume 的读 I/O 放大 5×。
264
+ *
265
+ * 返回窗口内所有 session-meta 的 meta(文件序,旧→新),由
266
+ * {@link composeSessionMeta} 统一合并。
267
+ */
268
+ protected readHeadSessionMeta(filePath: string): Promise<SessionMetaLine[] | undefined>;
269
+ /**
270
+ * 合并去重用的行身份。
271
+ *
272
+ * 不能直接用 `id`:#94203 之前的存量行没有 id,兜成空串会让「头部的创建行」
273
+ * 和「尾部的残缺行」撞成同一个键 —— 头部那条被当成"已经见过"丢掉,正好复现
274
+ * 本 issue 要修的丢键现场(本机实测 2607 条 meta 行里 1195 条没有 id)。
275
+ * 缺 id 时退回整行原文:内容相同的两行参与合并是幂等的,丢一条无害。
276
+ */
277
+ private metaLineIdentity;
278
+ /**
279
+ * 把若干条 session-meta(文件序,旧→新)合成一份 meta 事实。
280
+ *
281
+ * - {@link SNAPSHOT_ONLY_SESSION_META_KEYS}:只认最新那一行(缺席=已删除)。
282
+ * - 其余白名单键:按键向更早的行继承,最新的值优先。写方只快照了自己视野
283
+ * 里的键时,别人的键不会被顺带擦掉(#128860 的第二处:`/multitask` 写出
284
+ * 只含 multitaskMode 的行,PTC 站立身份被永久抹掉)。
285
+ */
286
+ protected composeSessionMeta(metaLinesOldestFirst: ReadonlyArray<SessionMetaLine>): Record<string, unknown> | undefined;
154
287
  private findSubagentSessionByStoreId;
155
288
  protected deserializeSessionFromJsonl(id: string, options?: SessionGetOptions): Promise<Session | undefined>;
156
289
  /**
@@ -225,13 +358,25 @@ export declare class SessionStoreImpl implements SessionStore {
225
358
  */
226
359
  protected isNonUserSession(session: Session): boolean;
227
360
  appendHistory(id: string, input: AgentInputItem[], parentSessionId?: string, session?: Session, cwd?: string): Promise<void>;
361
+ protected openJsonlFile(filePath: string, flags: string): ReturnType<typeof fs.open>;
362
+ /** Short, in-band only: this runs on the addHistory chain, so it is turn latency. */
363
+ protected transcriptRetryDelaysMs(): readonly number[];
364
+ protected sleepBeforeTranscriptRetry(delayMs: number): Promise<void>;
365
+ /** Cumulative transcript persistence counters (issue #119545 埋点). */
366
+ transcriptPersistenceMetrics(): Readonly<TranscriptPersistenceMetrics>;
228
367
  /**
229
- * The transcript-line writer, extracted verbatim from the previous
230
- * appendHistory body: exclusive-create or append open, chunked line
368
+ * The transcript-line writer: exclusive-create or append open, chunked line
231
369
  * writes, and on failure a rollback (unlink a file this batch created,
232
370
  * truncate to the original size otherwise) with forensic audit logs.
371
+ *
372
+ * A retryable failure gets a short in-band retry. When the budget runs out
373
+ * the batch is abandoned and reported as a typed error — the local copy is
374
+ * allowed to lose its tail, and `addHistory` keeps the turn running. Read
375
+ * paths repair the gap (unpaired tool calls are filtered out of model
376
+ * input), so the cost of a loss is a shorter history, not a broken one.
233
377
  */
234
378
  protected writeTranscriptLines(sessionFilePath: string, lines: string[], key?: SessionKey): Promise<void>;
379
+ private writeTranscriptLinesOnce;
235
380
  private readonly entriesStoreInstances;
236
381
  /**
237
382
  * The embedded SessionStore-contract backend — the single jsonl writer
@@ -312,3 +457,4 @@ export declare class SessionStoreImpl implements SessionStore {
312
457
  private cleanupFailedFork;
313
458
  rewindSession(source: Session, options: SessionRewindMutationOptions): Promise<void>;
314
459
  }
460
+ export {};