@sema-agent/server 7.55.0 → 7.57.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 (129) hide show
  1. package/MIGRATION.md +3 -3
  2. package/README.md +14 -7
  3. package/README.zh-CN.md +10 -6
  4. package/USAGE.md +207 -59
  5. package/deploy/sema-up/chart/values.yaml +1 -1
  6. package/dist/approval-ask-audit-store.d.ts +100 -1
  7. package/dist/approval-ask-audit-store.js +103 -2
  8. package/dist/approval-card.d.ts +148 -13
  9. package/dist/approval-card.js +82 -13
  10. package/dist/approval-content-kind.d.ts +22 -0
  11. package/dist/approval-content-kind.js +5 -0
  12. package/dist/approval.d.ts +31 -0
  13. package/dist/approval.js +18 -0
  14. package/dist/auto-mode-face.d.ts +111 -0
  15. package/dist/auto-mode-face.js +99 -0
  16. package/dist/bench/s1/arms.js +5 -5
  17. package/dist/bench/s1/live-deps.js +11 -11
  18. package/dist/bench/s1/run-firm.js +3 -0
  19. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  20. package/dist/bench/s1/runner-ctx.js +7 -0
  21. package/dist/boot/config-center.js +23 -1
  22. package/dist/boot/coordinators.js +13 -1
  23. package/dist/boot/leader.d.ts +21 -0
  24. package/dist/boot/leader.js +6 -0
  25. package/dist/boot/parked-revive-gate.d.ts +7 -2
  26. package/dist/boot/parked-revive-gate.js +12 -1
  27. package/dist/boot/resolve-spec.d.ts +3 -0
  28. package/dist/boot/resolve-spec.js +3 -1
  29. package/dist/boot/runner-deps.d.ts +12 -0
  30. package/dist/boot/runner-deps.js +28 -3
  31. package/dist/boot/runtime-caps.d.ts +19 -8
  32. package/dist/boot/runtime-caps.js +50 -20
  33. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  34. package/dist/boot/session-shell-gate-registry.js +21 -0
  35. package/dist/config-catalog.js +14 -5
  36. package/dist/config-center/types.d.ts +2 -1
  37. package/dist/config-provider.js +1 -0
  38. package/dist/config-types.d.ts +41 -10
  39. package/dist/config.d.ts +21 -0
  40. package/dist/config.js +40 -5
  41. package/dist/http/route-ctx.d.ts +90 -3
  42. package/dist/http/routes/a2a-serve.js +5 -3
  43. package/dist/http/routes/approvals-assistant.js +21 -8
  44. package/dist/http/routes/capabilities.js +21 -4
  45. package/dist/http/routes/leader.js +2 -2
  46. package/dist/http/routes/memory-origin.d.ts +2 -2
  47. package/dist/http/routes/rules.js +3 -2
  48. package/dist/http/routes/runs.d.ts +0 -14
  49. package/dist/http/routes/runs.js +20 -8
  50. package/dist/http/routes/tasks.js +34 -8
  51. package/dist/http/routes/workflows.js +20 -5
  52. package/dist/http/server.d.ts +11 -1
  53. package/dist/http/server.js +220 -34
  54. package/dist/http/wire-types.d.ts +9 -4
  55. package/dist/leader/diffout.js +2 -1
  56. package/dist/leader/endpoint.js +49 -15
  57. package/dist/leader/fanout.js +6 -5
  58. package/dist/leader/leader.js +17 -14
  59. package/dist/leader/merge.js +17 -12
  60. package/dist/leader/planner.js +3 -2
  61. package/dist/leader/repair-oracle.js +6 -4
  62. package/dist/leader/repair-wire.js +5 -3
  63. package/dist/leader/wire.d.ts +80 -1
  64. package/dist/leader/wire.js +55 -26
  65. package/dist/main.js +44 -8
  66. package/dist/observability/err-text.d.ts +6 -0
  67. package/dist/observability/err-text.js +10 -0
  68. package/dist/observability/fail-open.d.ts +40 -0
  69. package/dist/observability/fail-open.js +19 -0
  70. package/dist/observability/metrics.js +1 -1
  71. package/dist/observability/run-terminal-log.d.ts +120 -0
  72. package/dist/observability/run-terminal-log.js +360 -0
  73. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  74. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  75. package/dist/permission-rule-vocab.d.ts +40 -0
  76. package/dist/permission-rule-vocab.js +17 -0
  77. package/dist/plugins/checkpoint-store-sql.d.ts +26 -0
  78. package/dist/plugins/checkpoint-store-sql.js +23 -4
  79. package/dist/plugins/file-run-store.d.ts +3 -34
  80. package/dist/plugins/file-run-store.js +21 -0
  81. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  82. package/dist/plugins/local-checkpoint-store.js +5 -0
  83. package/dist/plugins/local-session-store.d.ts +9 -9
  84. package/dist/plugins/local-session-store.js +5 -3
  85. package/dist/plugins/memory-run-store.d.ts +3 -15
  86. package/dist/plugins/memory-run-store.js +21 -0
  87. package/dist/plugins/permission-rule-store-file.d.ts +22 -4
  88. package/dist/plugins/permission-rule-store-file.js +25 -11
  89. package/dist/plugins/permission-rule-store-sql.d.ts +60 -30
  90. package/dist/plugins/permission-rule-store-sql.js +39 -23
  91. package/dist/plugins/pg-session-storage.js +17 -13
  92. package/dist/plugins/remote-scratchpad.js +3 -2
  93. package/dist/plugins/run-store-sql.d.ts +3 -42
  94. package/dist/plugins/run-store-sql.js +36 -5
  95. package/dist/plugins/store-backend.d.ts +1 -1
  96. package/dist/plugins/store-contracts.d.ts +70 -1
  97. package/dist/plugins/tidb-session-store.js +18 -14
  98. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  99. package/dist/plugins/workflow-run-store-sql.js +10 -2
  100. package/dist/rules-consent.d.ts +5 -4
  101. package/dist/rules-consent.js +25 -46
  102. package/dist/run-cancel-context.d.ts +13 -0
  103. package/dist/run-cancel-context.js +15 -0
  104. package/dist/run-local.js +2 -2
  105. package/dist/runs.d.ts +4 -1
  106. package/dist/runs.js +41 -10
  107. package/dist/runtime-caps-resolver.d.ts +133 -17
  108. package/dist/runtime-caps-resolver.js +44 -4
  109. package/dist/security.d.ts +7 -0
  110. package/dist/task-settings.d.ts +57 -3
  111. package/dist/task-settings.js +78 -5
  112. package/dist/task-workflow.d.ts +32 -2
  113. package/dist/task-workflow.js +8 -3
  114. package/dist/tool-approval.d.ts +38 -63
  115. package/dist/tool-approval.js +102 -74
  116. package/dist/trace/core-keyset-guard.d.ts +2 -2
  117. package/dist/trace/ledger-sink.d.ts +13 -4
  118. package/dist/trace/ledger-sink.js +14 -6
  119. package/dist/trace/project.d.ts +13 -0
  120. package/dist/trace/project.js +10 -1
  121. package/dist/trace/redact.d.ts +22 -11
  122. package/dist/trace/redact.js +655 -20
  123. package/dist/trace/sema-provenance.d.ts +26 -0
  124. package/dist/trace/sema-provenance.js +11 -0
  125. package/dist/turn-activity.d.ts +32 -2
  126. package/dist/turn-activity.js +29 -4
  127. package/package.json +3 -3
  128. package/skills/find-skills.md +1 -1
  129. package/skills/loop.md +1 -1
@@ -22,10 +22,10 @@ export declare const APPROVAL_GATE_KINDS_SQL_IN: string;
22
22
  * 因此续跑」。反面(真被消费)不铸键 —— additive 只记真,与 `updatedInputForwarded`/`decisionNote`
23
23
  * 的先例逐字同族。**缺席禁读作「有消费者」的证明**:老版本 server 从不发这个键。
24
24
  *
25
- * 成因三形([ref] 复审件3 补第三形):①持有悬挂 promise 的是**另一个副本**;②本副本重启后本地窗已空;
26
- * ③**弃单墓碑守卫把素批准翻成 park**({@link ToolApprovalCoordinator.guardRowDerivedApprove}:本地腿
27
- * 在场,但没有一条把这次 approve 当**放行**消费 —— 它们全部收成 park,人须经 durable 门再兑)。
28
- * 三形的处置同一句:壳读 run 面判续跑没有,绝不把 200 无键当成「run 已续跑」的证明。
25
+ * 成因两形([ref] 起;[ref] 复审件3 曾补的第三形 = 弃单墓碑守卫翻 park,随守卫整删退役。进程内仍可
26
+ * 短暂出现「腿在场而零交付」:编辑在飞让路 —— 标记放开后的下一拍按行兑现):①持有悬挂 promise 的是
27
+ * **另一个副本**;②本副本重启后本地窗已空。
28
+ * 两形的处置同一句:壳读 run 面判续跑没有,绝不把 200 无键当成「run 已续跑」的证明。
29
29
  *
30
30
  * 为什么不是 409/410(施工时的二选一,裁定写在这里):409/410 说的是「你的请求没有生效」,而这次决议
31
31
  * **确实生效了**(行是 DECIDED,幂等回放读得到、审计面记得住)——把它渲成错误会让壳把一次成功的
@@ -323,6 +323,10 @@ export interface LivePendingRow {
323
323
  * 子代 ask)上恒等成立。缺席 = 顶层 ask。`fromSubagent` 保留不动(byte-compat:先于本键的消费方仍按
324
324
  * 它判别)。 */
325
325
  originTaskId?: string;
326
+ /** [ref]([ref]②):内容问句分型 —— `"content_ask"` 当 `toolName` 为 AskUserQuestion(词属主
327
+ * `approval-content-kind.ts`,与 durable 两读面 / core summarize 面同词,OMIT 契约同族)。
328
+ * 缺席 = 普通工具 ask。展示/分诊分型用,永不参与决议路由。 */
329
+ contentKind?: "content_ask";
326
330
  }
327
331
  export interface ToolApprovalRunContext {
328
332
  taskId: string;
@@ -537,6 +541,10 @@ export declare function resolveStreamApprovalGate(input: StreamApprovalGateInput
537
541
  * `prepare-task.js` 的 durableMandate/contentMandate(:1415/:1417)与 durableQuestionFace(:2136)等),
538
542
  * 故不在本函数内。
539
543
  *
544
+ * 🔴 **[ref](半接线旋钮族)**:`windowZero` 的第一源(`STREAM_ASK_WINDOW_MS=0`)自此**不再挂在
545
+ * `streamApprovalOn` 后面** —— 那是部署级旋钮,它的语义不该因为这台机器有没有 durable ask 账而改变
546
+ * (`ApprovalLegAssembly.windowZero` 顶注写了那条病)。第三源(贴 deadline)照旧只在协议上场时参与。
547
+ *
540
548
  * **`windowZero` 的两件事必须同时做**(调用方契约):不包 ALS **且**注入 immediate-unavailable 闭包。
541
549
  * 只做一件另一条路仍会呈卡。⚠️ [ref] 起**三腿都有** per-task `spec.onAsk` 装配点(sync=routes/tasks.ts,
542
550
  * bg=runs.ts,resume=server.ts driveResumeLeg),windowZero 的第二件在三腿都是显式闭包 —— 旧文「bg/
@@ -550,7 +558,14 @@ export interface ApprovalLegAssembly {
550
558
  * 同 (owner, session) 活流呈活卡(sync 腿 [ref] 起本就如此;bg/resume 的这条行为差 = [ref] CHANGELOG
551
559
  * 披露的行为面变更)。旧文「本腿逐字保持协议之前的行为」只对协议件成立,别再把它读成「协议关 = 零呈卡」。 */
552
560
  active: boolean;
553
- /** §8.3 窗=0 恒 park。`active` 为假时恒 false(协议都没上场,谈不上关窗) */
561
+ /** §8.3 窗=0 恒 park。
562
+ * 🔴 **[ref] 起它与 `active` 解耦**(旧文「`active` 为假时恒 false —— 协议都没上场,谈不上关窗」已
563
+ * 作废):`STREAM_ASK_WINDOW_MS` 是**部署级**旋钮,`0` 的语义(运维显式关窗 ⇒ 恒走
564
+ * {@link ToolApprovalCoordinator.unattendedAskOutcome},连卡都不发)在两条车道上必须是同一个意思。
565
+ * 旧形下 local 车道(`volatile_ask_ledger`)/ 无 park 设施 / 显式关协议的部署配了 `0`,拿到的是一张
566
+ * 等满 5min 的活卡 —— 与它自己的域注正好相反,且没有任何一行说出来。
567
+ * ⚠️ 「窗**缺席**」(`windowMs: undefined` = 这份装配根本没有 streamApproval 段,只出现在 stub-harness)
568
+ * **不是**关窗:那时逐字保持活卡腿。 */
554
569
  windowZero: boolean;
555
570
  /** §7.3 本 leg 的 walltime deadline(`performance.now()` 单调基)。只在 `active ∧ ¬windowZero ∧
556
571
  * 有 walltime 墙` 时在场 —— 关着时供值会把既有 5min 活卡窗按三元公式缩短(§7.2 真行为变更)。 */
@@ -579,8 +594,13 @@ export declare function createApprovalCardEmitter(sinks: {
579
594
  export declare function resolveApprovalLeg(input: {
580
595
  /** = `resolveStreamApprovalGate(...).active`(协议在本 worker 上场没有)。 */
581
596
  streamApprovalOn: boolean;
582
- /** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。 */
583
- windowMs: number;
597
+ /** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。
598
+ * 🔴 [ref]:`undefined` = **这份装配没有 `streamApproval` 段**(只出现在只填被测键的 stub-harness ——
599
+ * 真实 boot 无条件铸该段,见 `config.ts`)。此前三个调用点各写 `?? 0` 把这一形折成「0」,在旧语义下
600
+ * 是死代码(`windowZero` 那时挂在 `streamApprovalOn` 后面,协议关就恒 false);[ref] 让 `0` 在协议关的
601
+ * 腿上也真生效之后,那个折算会把一台**什么都没配**的 harness 判成「运维显式关窗」。故本字段收成
602
+ * `number | undefined`,由每个调用点如实交出「有没有这个值」,不许再就地编一个。 */
603
+ windowMs: number | undefined;
584
604
  /** `config.streamAskWindowMarginMs`(`STREAM_ASK_WINDOW_MARGIN_MS`)。 */
585
605
  windowMarginMs: number;
586
606
  /** `spec.limits.maxWalltimeMs`。缺席 = 没有 walltime 墙 ⇒ 第三源不参与(**缺席 ≠ 0**)。 */
@@ -670,10 +690,6 @@ export declare class ToolApprovalCoordinator {
670
690
  /** [ref] + codex R3-[critical]:askId → **此刻正带着 `updatedInput` 决它的请求数**(见
671
691
  * {@link markEditedDecisionInFlight} 顶注)。只在 `decideAsk` 那一次调用期间非空,故天然有界。 */
672
692
  private readonly editedDecisionsInFlight;
673
- /** [ref] + codex R6-[critical]:askId → **弃单墓碑**到期时刻。看门狗放弃一枚在飞编辑标记时落一条,
674
- * 此后行派生的 approve 对这只 ask 改走 park 路由(理由见 {@link notifyExternalDecision} 里那段)。
675
- * 有界两道:条目自带 TTL(惰性过期)+ 表大小 FIFO 上限,同 park 墓碑表的纪律。 */
676
- private readonly abandonedEditedDecisions;
677
693
  /** [ref] 车2(D5 一次性 warn 节流):同实例只报第一次,后续只计数(避免 store 抖动期间刷屏)。 */
678
694
  private storeErrorWarned;
679
695
  private storeErrorTally;
@@ -802,6 +818,8 @@ export declare class ToolApprovalCoordinator {
802
818
  /** 两份候选是否**逐条逐键**相等(顺序即展示序 ⇒ 顺序敏感)。两侧都缺席 = 相等;一侧缺席 = 不等。
803
819
  * 用在幂等重入的「行 = 真源」对账上(见 `ensureAsk` 那段撤回臂)。 */
804
820
  private static ruleOffersEqual;
821
+ private static batchMemberEqual;
822
+ private static uncoveredDetailEqual;
805
823
  /**
806
824
  * [ref] / 5.58 复审 F-1 —— OFFER 的**文本座尺**(红先修)。
807
825
  *
@@ -824,6 +842,11 @@ export declare class ToolApprovalCoordinator {
824
842
  * 位置在**素材铸点**(与基数帽同一处):两族帧 + `card_json` + 回决口的等式左边从此拿的是**同一份**
825
843
  * 已截素材,而不是两处各截一遍。
826
844
  */
845
+ /** 兑付侧座(single 的 rule/command、command 成员的 rule/command、directoryRead 成员的 rule/directory)任一超尺?
846
+ * `segment` 与 `uncoveredDetail[].segment` 是纯渲染座,不在此列(截长是诚实省略)。 */
847
+ private static ruleOfferTextsOverSize;
848
+ /** codex r1 [high]([ref]):offer 的自称座与 `parseAllowRuleText(rule)` 派生的三元组逐字一致?(理由见调用点) */
849
+ private static ruleOfferDerivedFactsAgree;
827
850
  private static boundRuleOfferTexts;
828
851
  private buildRuleLaneMaterial;
829
852
  /** [ref]([ref]/[ref] 双属主裁定):把某 wire run 的**流内未决 ask** 立即转 durable park——断连支专用。
@@ -911,8 +934,7 @@ export declare class ToolApprovalCoordinator {
911
934
  * 争终局与读真相并成店的一次原子 claim,分类器只剩纯函数半场。[ref](行派生 approve 丢编辑)已随
912
935
  * [ref] 落列收口:`approved` 臂带行上的 `updatedInput`。
913
936
  *
914
- * 行派生 approve 的两道守卫**在这里**施加(五处消费一份判据,见 {@link guardRowDerivedApprove}):
915
- * · 弃单墓碑在场 ⇒ `parked`(park 路由,可逆可再批 —— 绝不把一个证不出载荷的放行交出去);
937
+ * 行派生 approve 的守卫只剩一道**在这里**施加([ref]:弃单墓碑守卫随 [ref] 落列整删,见文件头 🪦 注):
916
938
  * · 编辑在飞 ⇒ `edit-in-flight`(codex R4-[critical] 同族口:载荷还在别人手里的那一拍按行结算 = 用裸
917
939
  * `true` 抢在它前面;持有者随后必定自己结算)。
918
940
  */
@@ -1224,15 +1246,6 @@ export declare class ToolApprovalCoordinator {
1224
1246
  backlog: number;
1225
1247
  truncated: boolean;
1226
1248
  }>;
1227
- /**
1228
- * [ref](案A)+ codex [critical] 的**前置门读口**:本副本对这条持久 askId 还有几条**未结算**的活体登记。
1229
- *
1230
- * 唯一消费者 = durable 回决口(`routes/runs.ts`)在 CAS **之前**判「这次带来的 `updatedInput` 交得出去
1231
- * 吗」—— 交不出去就响亮拒,绝不静默把编辑降级成不带编辑的批准(判据与理由全文在那个调用点)。
1232
- * 返回 0 的两种成因(**刻意不分**):等待的 promise 在**别的副本**,或那条 run 已经死了(重启后本地窗空)。
1233
- * 两者的处置相同(这次编辑在这里都落不了地),而分辨它们需要读 run 面 —— 那不是本口能回答的问题。
1234
- */
1235
- liveConsumerCount(askId: string): number;
1236
1249
  /**
1237
1250
  * 🔴 [ref] + codex R3-[critical](真缺陷,红先复现器在 `test/case-a-reachability-queue.test.ts` 同名格)
1238
1251
  * —— **带编辑的决议在飞期间,观察者不结算**。
@@ -1260,48 +1273,10 @@ export declare class ToolApprovalCoordinator {
1260
1273
  editedDecisionInFlightCount(askId: string): number;
1261
1274
  /** {@link markEditedDecisionInFlight} 的读口:此刻有没有**别的**请求正带着编辑决这只 ask。 */
1262
1275
  editedDecisionInFlight(askId: string): boolean;
1263
- /**
1264
- * 弃单墓碑的读口(**惰性过期**:读到过期即删,不另起定时器 —— park 墓碑表的姿势)。
1265
- * 在场 = 这只 ask 曾有一次带编辑的决议在飞而看门狗放弃了它 ⇒ **行派生**的 approve 一律改走 park 路由。
1266
- *
1267
- * 🔴 **过期/逐出必须 fail-closed**(codex R7-[high],验真后采纳):首版是「到点就删」——而它保护的是
1268
- * 一条**仍然挂着**的本地等待腿。店退化到十分钟以上、或并发弃单超过表帽时,墓碑先消失、腿还在,下一次
1269
- * 行派生 approve 照样把原始命令放出去,正好复活它要防的那件事。判据改成:**丢墓碑之前先把它保护的
1270
- * 消费者收掉**(park 路由,可逆可再批)。收完之后再删就是安全的 —— 没有腿可被误放行了。
1271
- */
1272
- private abandonedEditedDecisionPending;
1273
- /**
1274
- * 🔴 **行派生 approve 的统一守卫**(codex R7-[critical],验真后修)。
1275
- *
1276
- * R6 把弃单墓碑的兑现点收在 {@link notifyExternalDecision} 上,并自称「公共咽喉」—— 那句话**不成立**:
1277
- * 树上还有三处**直接** `settle(true, …)` / 直接把 `true` 交回 core 的行派生回放(`convergeFromDurableState`
1278
- * 的收敛臂、`ensureAsk` 幂等命中终局行的回放、窗到期报错臂的 `readDecidedForRecovery` —— [ref] 起前者与
1279
- * 后者合并成 `readRowForRecovery` 一个读口、三臂共用;[ref] 起读口撤销,同一份判据长在 claim 败方读数的
1280
- * 分类器 `classifyClaimLoss` 里),它们跳过那个口。于是弃单之后从这三条路上仍能放出原始命令。
1281
- *
1282
- * 收口形 = **一个判据函数、五处消费**(上面三处 + 轮询 + HTTP 观察者):把「行说 approve」这件事
1283
- * 在弃单墓碑在场时翻译成 `"unavailable"`(park 路由,可逆可再批),其余取值逐字透传。
1284
- * 判据只认**不带编辑的行派生**放行(裸 `true`)—— 人的当次决议带着 `updatedInput` 走的是另一条路,不经
1285
- * 本函数;[ref] 之后行上带编辑的 approve 回放出来是对象臂 `{allow:true, updatedInput}`,载荷**在行上**,
1286
- * 墓碑要护的「证不出载荷」这件事对它不成立,同样原样透传。
1287
- * ⚠️ 既有边界如实记([ref] 随批复核):列在之后,一只 ask 的决议**要么**带编辑落列(回放恒对象臂,本
1288
- * 守卫永不命中)**要么**从没带过编辑(从没设过在飞标记,永无墓碑)⇒ 本守卫在新行上结构上不再可达,
1289
- * 留着只为老进程/老行的并存窗与既有判据格;拆除是独立件([ref] 登记),不搭本批。
1290
- */
1291
- private guardRowDerivedApprove;
1292
- /** 丢一枚弃单墓碑的**唯一**口(过期 / 容量逐出共用):先把它还在保护的本地消费者按 park 路由收掉,
1293
- * 再删表项 —— 顺序是硬的(先删表再收人 = 中间那一瞬正是可被误放行的窗口)。 */
1294
- private dropAbandonedEditTombstone;
1295
- /** 判据钩子:把「看门狗那一拍」显式地跑一次(留弃单墓碑 + 让在飞标记归零)。
1296
- * 存在理由 = 那条臂的真实触发要等 {@link EDITED_DECISION_MARK_CAP_MS} 的墙钟,而本仓禁固定睡眠钉
1297
- * (fixed-sleep 棘轮);把**语义**开一个口比让判据去睡 10 秒诚实得多。生产路径不调它。 */
1276
+ /** 判据钩子:把「看门狗那一拍」显式地跑一次(= 在飞标记被放弃归零;[ref] 起看门狗不再铸弃单墓碑,
1277
+ * 本钩子随之只剩这一半)。存在理由 = 那条臂的真实触发要等 {@link EDITED_DECISION_MARK_CAP_MS}
1278
+ * 墙钟,而本仓禁固定睡眠钉(fixed-sleep 棘轮)。生产路径不调它。 */
1298
1279
  expireEditedDecisionMarkForTest(askId: string): void;
1299
- /** 判据钩子:把一枚弃单墓碑**拨到已过期**并当场走它的丢弃路(= 真实 TTL 到点那一拍的语义)。
1300
- * 同上理由:真触发要等 {@link ABANDONED_EDIT_TOMBSTONE_TTL_MS} 的墙钟。生产路径不调它。 */
1301
- forceAbandonedEditTombstoneExpiryForTest(askId: string): void;
1302
- /** 判据钩子:把一枚弃单墓碑的到期时刻**改写**成给定值(不触发惰性过期口本身)。用来构造
1303
- * 「墓碑已过期、但还没有任何人来读过它」这一拍 —— 那正是「触发过期的那次调用还护不护」的判据点。 */
1304
- setAbandonedEditTombstoneDeadlineForTest(askId: string, atMs: number): void;
1305
1280
  /** Test/observability hooks. */
1306
1281
  pendingCount(): number;
1307
1282
  /** [ref]:当前挂着的**悬挂**条目数(零投递集合铸的那一类)——运维/判据读它证「悬挂队列有多长」。 */
@@ -1,5 +1,6 @@
1
+ import { contentKindOf } from "./approval-content-kind.js";
1
2
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { uuidv7, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
3
+ import { uuidv7, MAX_RULE_TEXT_CHARS, parseAllowRuleText } from "@sema-agent/core";
3
4
  import { precheckCardRuleText } from "./rules-consent.js";
4
5
  import { redactDeep, redactSecrets } from "./trace/redact.js";
5
6
  import { hasBidiControls } from "./text-bidi.js";
@@ -7,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
7
8
  import { createLogger } from "./observability/logger.js";
8
9
  import { recordFailOpen } from "./observability/fail-open.js";
9
10
  import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
10
- import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
11
+ import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
11
12
  import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
12
13
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
13
14
  const defaultLogger = createLogger();
@@ -22,8 +23,6 @@ const DEFAULT_UNREACHED_ASK_TTL_MS = 60 * 60_000;
22
23
  const SUSPENDED_POLL_MAX_PER_TICK = 64;
23
24
  const SUSPENDED_POLL_BUDGET_MS = 5_000;
24
25
  const EDITED_DECISION_MARK_CAP_MS = 10_000;
25
- const ABANDONED_EDIT_TOMBSTONE_TTL_MS = 10 * 60_000;
26
- const ABANDONED_EDIT_TOMBSTONE_MAX = 256;
27
26
  const DEFAULT_WINDOW_MARGIN_MS = 10_000;
28
27
  const MAX_ALLOW_SESSIONS = 4096;
29
28
  const DEFAULT_ADMIT_MAX_PER_TASK = 32;
@@ -196,7 +195,7 @@ export function createApprovalCardEmitter(sinks) {
196
195
  }
197
196
  export function resolveApprovalLeg(input) {
198
197
  if (!input.streamApprovalOn)
199
- return { active: false, windowZero: false };
198
+ return { active: false, windowZero: input.windowMs === 0 };
200
199
  const tightDeadline = input.legWalltimeMs !== undefined && input.legWalltimeMs - input.windowMarginMs <= 0;
201
200
  if (input.windowMs === 0 || tightDeadline)
202
201
  return { active: true, windowZero: true };
@@ -269,7 +268,6 @@ export class ToolApprovalCoordinator {
269
268
  admitByOwner = new Map();
270
269
  suspendedPollOffset = 0;
271
270
  editedDecisionsInFlight = new Map();
272
- abandonedEditedDecisions = new Map();
273
271
  storeErrorWarned = false;
274
272
  storeErrorTally = 0;
275
273
  governanceAskMarks;
@@ -346,32 +344,115 @@ export class ToolApprovalCoordinator {
346
344
  if (x.kind === "batch" && y.kind === "batch") {
347
345
  return (x.uncoveredSegments === y.uncoveredSegments &&
348
346
  x.rules.length === y.rules.length &&
349
- x.rules.every((r, j) => r.rule === y.rules[j].rule && r.match === y.rules[j].match && r.command === y.rules[j].command && r.segment === y.rules[j].segment));
347
+ x.rules.every((r, j) => ToolApprovalCoordinator.batchMemberEqual(r, y.rules[j])) &&
348
+ ToolApprovalCoordinator.uncoveredDetailEqual(x.uncoveredDetail, y.uncoveredDetail));
350
349
  }
351
350
  return false;
352
351
  });
353
352
  }
353
+ static batchMemberEqual(r, s) {
354
+ if (r.kind !== s.kind)
355
+ return false;
356
+ if (r.kind === "command" && s.kind === "command")
357
+ return r.rule === s.rule && r.match === s.match && r.command === s.command && r.segment === s.segment;
358
+ if (r.kind === "directoryRead" && s.kind === "directoryRead")
359
+ return r.rule === s.rule && r.directory === s.directory && r.segment === s.segment;
360
+ return false;
361
+ }
362
+ static uncoveredDetailEqual(a, b) {
363
+ if (a === undefined || b === undefined)
364
+ return a === b;
365
+ return a.length === b.length && a.every((d, i) => d.segment === b[i].segment && d.reason === b[i].reason);
366
+ }
367
+ static ruleOfferTextsOverSize(offer) {
368
+ const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
369
+ if (offer.kind === "single")
370
+ return over(offer.rule) || over(offer.command);
371
+ return offer.rules.some((r) => {
372
+ switch (r.kind) {
373
+ case "command":
374
+ return over(r.rule) || over(r.command);
375
+ case "directoryRead":
376
+ return over(r.rule) || over(r.directory);
377
+ default:
378
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
379
+ }
380
+ });
381
+ }
382
+ static ruleOfferDerivedFactsAgree(offer) {
383
+ const derive = (rule) => {
384
+ const p = parseAllowRuleText(rule);
385
+ return "rule" in p ? p.rule : undefined;
386
+ };
387
+ if (offer.kind === "single") {
388
+ const d = derive(offer.rule);
389
+ return d !== undefined && d.rule === offer.rule && d.tool === "Bash" && d.match === offer.match && d.command === offer.command;
390
+ }
391
+ return offer.rules.every((m) => {
392
+ const d = derive(m.rule);
393
+ if (d === undefined || d.rule !== m.rule)
394
+ return false;
395
+ switch (m.kind) {
396
+ case "command":
397
+ return d.tool === "Bash" && d.match === m.match && d.command === m.command;
398
+ case "directoryRead":
399
+ return d.tool === "Read" && d.match === "subpath" && d.command === m.directory;
400
+ default:
401
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(m)}`);
402
+ }
403
+ });
404
+ }
354
405
  static boundRuleOfferTexts(offers) {
355
406
  const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
407
+ const clipSegment = (s) => (over(s) ? s.slice(0, MAX_RULE_TEXT_CHARS) : s);
356
408
  const out = [];
357
- for (const offer of offers) {
409
+ for (const raw of offers) {
410
+ const parsed = RuleOfferRawSchema.safeParse(screenUncoveredDetailSeat(backfillLegacyBatchMemberKind(raw)));
411
+ if (!parsed.success) {
412
+ defaultLogger.warn("rule_offer_dropped_unreadable_shape", {
413
+ kind: typeof raw === "object" && raw !== null ? String(raw.kind) : typeof raw,
414
+ note: "this offer's shape is outside the closed vocabulary this build reads (member kind / match / reason) — dropped whole; sibling offers still render (core/dist skew?)",
415
+ });
416
+ continue;
417
+ }
418
+ const offer = parsed.data;
419
+ if (ToolApprovalCoordinator.ruleOfferTextsOverSize(offer))
420
+ return undefined;
421
+ if (!ToolApprovalCoordinator.ruleOfferDerivedFactsAgree(offer)) {
422
+ defaultLogger.warn("rule_offer_dropped_derived_facts_disagree", {
423
+ kind: offer.kind,
424
+ note: "an offer's kind/match/command/directory seats do not agree with what the shared validator derives from its rule text — dropped whole so the card never shows one grant while redeeming another (core/dist skew or forged material?)",
425
+ });
426
+ continue;
427
+ }
358
428
  switch (offer.kind) {
359
429
  case "single":
360
- if (over(offer.rule) || over(offer.command))
361
- return undefined;
362
- out.push(offer);
430
+ out.push({ kind: "single", rule: offer.rule, match: offer.match, command: offer.command });
363
431
  break;
364
- case "batch":
365
- if (offer.rules.some((r) => over(r.rule) || over(r.command)))
366
- return undefined;
432
+ case "batch": {
433
+ const rules = [];
434
+ for (const r of offer.rules) {
435
+ switch (r.kind) {
436
+ case "command":
437
+ rules.push({ kind: "command", rule: r.rule, match: r.match, command: r.command, segment: clipSegment(r.segment) });
438
+ break;
439
+ case "directoryRead":
440
+ rules.push({ kind: "directoryRead", rule: r.rule, directory: r.directory, segment: clipSegment(r.segment) });
441
+ break;
442
+ default:
443
+ throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
444
+ }
445
+ }
367
446
  out.push({
368
447
  kind: "batch",
369
- rules: offer.rules.map((r) => (over(r.segment) ? { ...r, segment: r.segment.slice(0, MAX_RULE_TEXT_CHARS) } : r)),
448
+ rules,
370
449
  uncoveredSegments: offer.uncoveredSegments,
450
+ ...(offer.uncoveredDetail !== undefined ? { uncoveredDetail: offer.uncoveredDetail.map((d) => ({ segment: clipSegment(d.segment), reason: d.reason })) } : {}),
371
451
  });
372
452
  break;
453
+ }
373
454
  default:
374
- return undefined;
455
+ throw new Error(`unknown rule-offer kind: ${JSON.stringify(offer)}`);
375
456
  }
376
457
  }
377
458
  return out;
@@ -504,10 +585,10 @@ export class ToolApprovalCoordinator {
504
585
  return { kind: "none" };
505
586
  return { kind: "row", current: projectAskTerminalClaimTruth(row) };
506
587
  }
507
- classifyClaimLoss(askId, current, where) {
588
+ classifyClaimLoss(askId, current, _where) {
508
589
  if (current.state === "absent")
509
590
  return { kind: "absent" };
510
- const replayed = settleArgsOf(this.guardRowDerivedApprove(askId, replayTerminalAskRow(current), where));
591
+ const replayed = settleArgsOf(replayTerminalAskRow(current));
511
592
  if (replayed === "unavailable")
512
593
  return { kind: "parked" };
513
594
  if (!replayed.allowed)
@@ -608,10 +689,6 @@ export class ToolApprovalCoordinator {
608
689
  const entries = this.pendingByAskId.get(askId);
609
690
  if (!entries)
610
691
  return { settled: 0 };
611
- if (allowed && updatedInput === undefined) {
612
- if (this.guardRowDerivedApprove(askId, true, "notify-external-decision") === "unavailable")
613
- return { settled: 0 };
614
- }
615
692
  let settled = 0;
616
693
  for (const entry of [...entries]) {
617
694
  entry.settle(allowed, allowed ? "allowed" : "denied", updatedInput);
@@ -820,7 +897,7 @@ export class ToolApprovalCoordinator {
820
897
  });
821
898
  if (existingOrCreated.row.state !== "STREAM_PENDING") {
822
899
  releaseAdmission();
823
- const replayed = this.guardRowDerivedApprove(askId, replayTerminalAskRow(existingOrCreated.row), "ensure-ask-idempotent-replay");
900
+ const replayed = replayTerminalAskRow(existingOrCreated.row);
824
901
  return replayed === "unavailable" ? this.unattendedOutcome() : replayed;
825
902
  }
826
903
  const persistedEnvelope = ApprovalCardEnvelopeSchema.safeParse(existingOrCreated.row.cardJson);
@@ -1859,6 +1936,7 @@ export class ToolApprovalCoordinator {
1859
1936
  for (const [id, e] of this.pending) {
1860
1937
  if (scope !== undefined && e.owner !== scope)
1861
1938
  continue;
1939
+ const contentKind = contentKindOf(e.toolName);
1862
1940
  rows.push({
1863
1941
  approvalId: id,
1864
1942
  toolName: e.toolName,
@@ -1868,6 +1946,7 @@ export class ToolApprovalCoordinator {
1868
1946
  ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
1869
1947
  ...(e.governanceForced ? { governanceForced: true } : {}),
1870
1948
  ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
1949
+ ...(contentKind !== undefined ? { contentKind } : {}),
1871
1950
  });
1872
1951
  }
1873
1952
  return rows;
@@ -1932,13 +2011,6 @@ export class ToolApprovalCoordinator {
1932
2011
  }
1933
2012
  return { polled, settled, backlog, truncated };
1934
2013
  }
1935
- liveConsumerCount(askId) {
1936
- let n = 0;
1937
- for (const e of this.pendingByAskId.get(askId) ?? [])
1938
- if (e.settledOutcome === undefined)
1939
- n++;
1940
- return n;
1941
- }
1942
2014
  markEditedDecisionInFlight(askId) {
1943
2015
  this.editedDecisionsInFlight.set(askId, (this.editedDecisionsInFlight.get(askId) ?? 0) + 1);
1944
2016
  let released = false;
@@ -1957,12 +2029,6 @@ export class ToolApprovalCoordinator {
1957
2029
  if (released)
1958
2030
  return;
1959
2031
  defaultLogger.warn("approval_edited_decision_mark_expired", { askId, capMs: EDITED_DECISION_MARK_CAP_MS });
1960
- this.abandonedEditedDecisions.set(askId, Date.now() + ABANDONED_EDIT_TOMBSTONE_TTL_MS);
1961
- if (this.abandonedEditedDecisions.size > ABANDONED_EDIT_TOMBSTONE_MAX) {
1962
- const oldest = this.abandonedEditedDecisions.keys().next();
1963
- if (!oldest.done)
1964
- this.dropAbandonedEditTombstone(oldest.value, "capacity");
1965
- }
1966
2032
  drop();
1967
2033
  }, EDITED_DECISION_MARK_CAP_MS);
1968
2034
  watchdog.unref?.();
@@ -1974,47 +2040,9 @@ export class ToolApprovalCoordinator {
1974
2040
  editedDecisionInFlight(askId) {
1975
2041
  return (this.editedDecisionsInFlight.get(askId) ?? 0) > 0;
1976
2042
  }
1977
- abandonedEditedDecisionPending(askId) {
1978
- const until = this.abandonedEditedDecisions.get(askId);
1979
- if (until === undefined)
1980
- return false;
1981
- if (Date.now() > until) {
1982
- this.dropAbandonedEditTombstone(askId, "ttl");
1983
- return true;
1984
- }
1985
- return true;
1986
- }
1987
- guardRowDerivedApprove(askId, outcome, where) {
1988
- if (outcome !== true || askId === undefined)
1989
- return outcome;
1990
- if (!this.abandonedEditedDecisionPending(askId))
1991
- return outcome;
1992
- defaultLogger.warn("approval_row_derived_approve_parked_after_abandoned_edit", { askId, where });
1993
- this.settleSameAskIdParkRoute(askId);
1994
- return "unavailable";
1995
- }
1996
- dropAbandonedEditTombstone(askId, why) {
1997
- const stillWaiting = this.liveConsumerCount(askId);
1998
- if (stillWaiting > 0) {
1999
- defaultLogger.warn("approval_abandoned_edit_tombstone_dropped_parking_consumers", { askId, why, parked: stillWaiting });
2000
- this.settleSameAskIdParkRoute(askId);
2001
- }
2002
- this.abandonedEditedDecisions.delete(askId);
2003
- }
2004
2043
  expireEditedDecisionMarkForTest(askId) {
2005
- this.abandonedEditedDecisions.set(askId, Date.now() + ABANDONED_EDIT_TOMBSTONE_TTL_MS);
2006
2044
  this.editedDecisionsInFlight.delete(askId);
2007
2045
  }
2008
- forceAbandonedEditTombstoneExpiryForTest(askId) {
2009
- if (!this.abandonedEditedDecisions.has(askId))
2010
- return;
2011
- this.abandonedEditedDecisions.set(askId, Date.now() - 1);
2012
- this.abandonedEditedDecisionPending(askId);
2013
- }
2014
- setAbandonedEditTombstoneDeadlineForTest(askId, atMs) {
2015
- if (this.abandonedEditedDecisions.has(askId))
2016
- this.abandonedEditedDecisions.set(askId, atMs);
2017
- }
2018
2046
  pendingCount() {
2019
2047
  return this.pending.size;
2020
2048
  }
@@ -20,7 +20,7 @@
20
20
  import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxStore, TaskSpec, TaskResult } from "@sema-agent/core";
21
21
  /** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
22
22
  type AssertAllKeysHandled<T extends never> = T;
23
- type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure";
23
+ type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance";
24
24
  type NotificationExcluded = "peer";
25
25
  type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
26
26
  type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
@@ -29,7 +29,7 @@ type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, Bg
29
29
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
30
30
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
31
31
  type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
32
- type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld";
32
+ type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
33
33
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
34
34
  type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest" | "text_end";
35
35
  type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
@@ -58,8 +58,14 @@ export interface LedgerSink {
58
58
  onForwardEvent(e: {
59
59
  type?: string;
60
60
  }): void;
61
- /** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the non-secret `checkpointGate` is
62
- * persisted — never the capability `checkpointToken` (search [18] Q1b). */
61
+ /** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the `checkpointGate` is persisted —
62
+ * never the capability `checkpointToken` (search [18] Q1b).
63
+ * ⚠️ **"non-secret" was the old wording and it is not true** ([ref] R3, 2026-09-03 — wording only, behaviour
64
+ * unchanged; [ref] decides the arm): the gate carries NO capability token, but its `riskDescriptor.summary` /
65
+ * `shadowedRule` are model-/human-controlled display text that [ref] measured carrying a PEM private-key
66
+ * body verbatim. This durable row replays on every `GET /v1/runs/:id/events` reconnect and is NOT backfilled
67
+ * by core [ref]'s mint-point fix — it is the third face of the `checkpointGate` sister item recorded in
68
+ * `http/routes/approvals-assistant.ts` (`redactPendingDisclosures` header). */
63
69
  appendParked(kind: "suspended" | "needs_review", result: unknown): Promise<void>;
64
70
  /** Terminal `done` event. `result` must already be checkpoint-token-stripped (and, on the bg leg,
65
71
  * model-usage-attached) by the caller. */
@@ -101,7 +107,10 @@ export declare function createLedgerSink(opts: {
101
107
  * ——**text_delta/reasoning_delta 只进内存缓冲、到 flush 边界才 append**(codex S1-F1,2026-08-13:
102
108
  * 只打 append 会让长时间流式输出被误读成「无进展」,方向与本键的设计目的正好相反);②`append`
103
109
  * 入口——兜住不经 onEvent 的直写路径(appendParked/appendDone/onForwardEvent)。分诊提示性回调:
104
- * 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。 */
105
- onActivity?: () => void;
110
+ * 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。
111
+ * 🔴 [ref] 件3:回调带**事件类型**(`kind`)——`cancelContext.lastEventKind` 的数据源。两个打点位
112
+ * 各传自己看得见的那个词(位①=流事件 `ev.type`,位②=账本行 `type`);拿不到类型的打点方
113
+ * (core 的 onActivity 第五席、resume 腿手动打点)省参,`kind` 诚实缺席(见 turn-activity.ts)。 */
114
+ onActivity?: (kind?: string, statusData?: Record<string, unknown>) => void;
106
115
  }): LedgerSink;
107
116
  //# sourceMappingURL=ledger-sink.d.ts.map
@@ -17,15 +17,15 @@ export function createLedgerSink(opts) {
17
17
  let text = "";
18
18
  let reasoning = "";
19
19
  let textEventId;
20
- const beat = () => {
20
+ const beat = (kind, statusData) => {
21
21
  try {
22
- opts.onActivity?.();
22
+ opts.onActivity?.(kind, statusData);
23
23
  }
24
24
  catch {
25
25
  }
26
26
  };
27
27
  const append = (type, data) => {
28
- beat();
28
+ beat(type);
29
29
  const step = chain.then(() => opts.appendEvent(++seq, type, data));
30
30
  chain = step.then(() => undefined, () => undefined);
31
31
  return step;
@@ -41,7 +41,7 @@ export function createLedgerSink(opts) {
41
41
  }
42
42
  };
43
43
  const onEvent = async (ev) => {
44
- beat();
44
+ beat(ev.type);
45
45
  switch (ev.type) {
46
46
  case "text_delta": {
47
47
  const td = ev;
@@ -63,7 +63,11 @@ export function createLedgerSink(opts) {
63
63
  break;
64
64
  case "status":
65
65
  await flush();
66
- await append("status", brainStatusEventData(ev));
66
+ {
67
+ const statusData = brainStatusEventData(ev);
68
+ beat("status", statusData);
69
+ await append("status", statusData);
70
+ }
67
71
  break;
68
72
  case "context_usage":
69
73
  await flush();
@@ -134,7 +138,11 @@ export function createLedgerSink(opts) {
134
138
  void append("tool_end", toolEndEventData(e)).catch(() => undefined);
135
139
  }
136
140
  };
137
- const appendStatus = (ev) => append("status", brainStatusEventData(ev));
141
+ const appendStatus = (ev) => {
142
+ const statusData = brainStatusEventData(ev);
143
+ beat("status", statusData);
144
+ return append("status", statusData);
145
+ };
138
146
  const appendParked = (kind, result) => append(kind, {
139
147
  gate: result?.checkpointGate ?? null,
140
148
  ...(typeof result?.toolCallId === "string" ? { toolCallId: result.toolCallId } : {}),
@@ -16,6 +16,7 @@ import type { ApprovalSettledBy, TaskResult, SystemInjectionPriority } from "@se
16
16
  import { type AskDenyResolution } from "@sema-agent/core";
17
17
  import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
18
18
  import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
19
+ import { type SemaProvenanceInput } from "./sema-provenance.js";
19
20
  import type { LedgerEventType } from "./ledger-events.js";
20
21
  /** 账本写口的统一签名 —— `type` 是**闭集**(`ledger-events.ts`):新型必须先登记,否则调用点编译红
21
22
  * (codex 交叉复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
@@ -214,6 +215,18 @@ export declare function taskNotificationEventData(ev: {
214
215
  status?: number;
215
216
  requestId?: string;
216
217
  };
218
+ /** [ref](a) [ref] 片6 d1(core 7.2.0):子代→主代 uplink 帧的**渲染判别**成员。在场 = 这一帧是
219
+ * `SendMessage("main")` 的 agent-message(cli 渲 agent-message 卡;父模型面读的是顶层 `<agent-message from=…>`
220
+ * 帧而非 task-notification 外壳);缺席 = 老形卡。`body` 是子代文本 = UNTRUSTED(与 `result` 同姿势 redact);
221
+ * `from` 是 core 铸的发送方标签(引擎值,verbatim)。铸造点唯一:core `agents/send-message-tool.ts` uplink 腿。 */
222
+ agentMessage?: {
223
+ from: string;
224
+ body: string;
225
+ };
226
+ /** 同批:typed 归因侧记(wire 键;core 契约「a host projects it, the model never sees it」)。成员逐个挑取,
227
+ * `kind` 过闭集(`trace/sema-provenance.ts` 单点,park 半场同一判据);`agentType` 本 mount 恒缺席(工具无
228
+ * agent-type 席),在场即透传(fleet spawn 帧按 taskId 可 join 时上游给)。 */
229
+ _sema_provenance?: SemaProvenanceInput;
217
230
  };
218
231
  eventId?: string;
219
232
  parentToolCallId?: string;
@@ -1,6 +1,8 @@
1
+ import { redactErrorMessage } from "../observability/run-terminal-log.js";
1
2
  import { isApprovalSettledBy, isAskDenyResolution } from "@sema-agent/core";
2
3
  import { redactSecrets, redactDeep } from "./redact.js";
3
4
  import { injectionTierOrAbsent } from "./injection-tier.js";
5
+ import { semaProvenanceOrAbsent } from "./sema-provenance.js";
4
6
  export async function appendModelUsageDelta(append, modelUsage, taskId) {
5
7
  const delta = modelUsage?.drain(taskId);
6
8
  if (delta) {
@@ -151,6 +153,13 @@ export function taskNotificationEventData(ev) {
151
153
  ...(n.result !== undefined ? { result: redactSecrets(n.result) } : {}),
152
154
  ...(n.output_file !== undefined ? { output_file: redactSecrets(n.output_file) } : {}),
153
155
  ...(n.usage !== undefined ? { usage: n.usage } : {}),
156
+ ...(n.agentMessage !== undefined ? { agentMessage: { from: n.agentMessage.from, body: redactSecrets(n.agentMessage.body) } } : {}),
157
+ ...(() => {
158
+ if (n._sema_provenance === undefined)
159
+ return {};
160
+ const p = semaProvenanceOrAbsent(n._sema_provenance);
161
+ return p !== undefined ? { _sema_provenance: p } : {};
162
+ })(),
154
163
  ...(() => {
155
164
  const tier = injectionTierOrAbsent(ev.priority);
156
165
  return tier !== undefined ? { priority: tier } : {};
@@ -522,7 +531,7 @@ function mapTraceEventInner(type, seq, data) {
522
531
  case "needs_review":
523
532
  return { event: "done", data: { seq, suspended: true } };
524
533
  case "failed":
525
- return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactSecrets(String(data.errorMessage ?? "run failed")) } };
534
+ return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactErrorMessage(String(data.errorMessage ?? "run failed")) ?? "run failed" } };
526
535
  case "context_usage":
527
536
  case "compaction_outcome":
528
537
  case "wiring_manifest":