@sema-agent/server 7.117.0 → 7.119.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 (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
@@ -21,7 +21,7 @@
21
21
  * 没有方法、没有捕获的行为。
22
22
  */
23
23
  import { z } from "zod";
24
- import { ALLOW_RULE_MEMBERS, READ_ROOT_CANDIDATE_DIR_MAX, type AskClass, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
24
+ import { ALLOW_RULE_MEMBERS, READ_ROOT_CANDIDATE_DIR_MAX, type AskClass, type AskEvidenceAbsence, type AskRequest as CoreAskRequest, type PendingAction as CorePendingAction, type ReadRootGrantCandidate, type RuleOffer, type SteerReplayFacet } from "@sema-agent/core";
25
25
  import type { AskRow } from "./plugins/approval-ask-store-sql.js";
26
26
  /** 模型自由文本(`sourceAgentName` / `delegation.agentName`)的限长(设计稿 §6.2)。设计 §3.1 把
27
27
  * 「限长 + 脱敏」写成 **server 新增责任**(引擎无此层):spawning model 挑的名字是自由文本,
@@ -81,11 +81,13 @@ export declare const RuleOfferSchema: z.ZodUnion<readonly [z.ZodObject<{
81
81
  kind: z.ZodLiteral<"single">;
82
82
  rule: z.ZodString;
83
83
  match: z.ZodEnum<{
84
+ name: "name";
84
85
  path: "path";
85
86
  exact: "exact";
86
87
  prefix: "prefix";
87
88
  wildcard: "wildcard";
88
89
  subpath: "subpath";
90
+ name_prefix: "name_prefix";
89
91
  }>;
90
92
  command: z.ZodString;
91
93
  }, z.core.$strict>, z.ZodObject<{
@@ -94,11 +96,13 @@ export declare const RuleOfferSchema: z.ZodUnion<readonly [z.ZodObject<{
94
96
  kind: z.ZodLiteral<"command">;
95
97
  rule: z.ZodString;
96
98
  match: z.ZodEnum<{
99
+ name: "name";
97
100
  path: "path";
98
101
  exact: "exact";
99
102
  prefix: "prefix";
100
103
  wildcard: "wildcard";
101
104
  subpath: "subpath";
105
+ name_prefix: "name_prefix";
102
106
  }>;
103
107
  command: z.ZodString;
104
108
  segment: z.ZodString;
@@ -158,11 +162,13 @@ export declare const RuleOfferRawSchema: z.ZodUnion<readonly [z.ZodObject<{
158
162
  kind: z.ZodLiteral<"single">;
159
163
  rule: z.ZodString;
160
164
  match: z.ZodEnum<{
165
+ name: "name";
161
166
  path: "path";
162
167
  exact: "exact";
163
168
  prefix: "prefix";
164
169
  wildcard: "wildcard";
165
170
  subpath: "subpath";
171
+ name_prefix: "name_prefix";
166
172
  }>;
167
173
  command: z.ZodString;
168
174
  }, z.core.$strip>, z.ZodObject<{
@@ -171,11 +177,13 @@ export declare const RuleOfferRawSchema: z.ZodUnion<readonly [z.ZodObject<{
171
177
  kind: z.ZodLiteral<"command">;
172
178
  rule: z.ZodString;
173
179
  match: z.ZodEnum<{
180
+ name: "name";
174
181
  path: "path";
175
182
  exact: "exact";
176
183
  prefix: "prefix";
177
184
  wildcard: "wildcard";
178
185
  subpath: "subpath";
186
+ name_prefix: "name_prefix";
179
187
  }>;
180
188
  command: z.ZodString;
181
189
  segment: z.ZodString;
@@ -437,7 +445,7 @@ type ToolApprovalPark = Extract<CorePendingAction, {
437
445
  kind: "tool_approval";
438
446
  }>;
439
447
  /**
440
- * 🔴 **闭集词座的唯一窄读**(S-721 起 `mandate`;S-755 起 `origin`)—— 一座一只 core 成员筛,**名字是参数**(与
448
+ * 🔴 **闭集词座的唯一窄读**(S-721 起 `mandate`;S-755 起 `origin`;S-1170 起 `classifierNoRuling` / `differs`)—— 一座一只成员筛,**名字是参数**(与
441
449
  * {@link readParkRowTrueSeat} 同一条:一处解析,不是两只各写一遍的函数)。
442
450
  * · `mandate`(core 7.32.0 #1092)—— 一问**站在哪一个 mandate 词上**(`mandated` 说「站在强制上」,本座说是 core
443
451
  * `PERSISTED_RULE_MANDATES` 六词里的哪一个;core 只在强制位的**整因**是那一个词时铸 —— 治理 / hook / 祖先,或不止一个理由
@@ -446,6 +454,11 @@ type ToolApprovalPark = Extract<CorePendingAction, {
446
454
  * · `origin`(core 7.5.0 [ref] 的 park 孪生座;S-755)—— **谁提的这一问**,core `ASK_ORIGINS` 闭集一词。读者:运维队列行读寄存行
447
455
  * 孪生座 `PendingAction.tool_approval.origin`({@link parkRowFacts})。活卡帧**不经**本函数:它读引擎在门上刚盖好的
448
456
  * `AskRequest.origin`(盖章点即 core 的筛点),照旧只过「非空串」形门(`tool-approval.ts` 帧键 `origin` 顶注)。
457
+ * · `classifierNoRuling`(core 7.43.0 #1353)—— 分类器没给判词的成因词,core `AUTO_MODE_FALLBACK_CAUSES` 闭集一词(筛 = `isAutoModeFallbackCause`)。
458
+ * 读者:活卡帧读 `AskRequest.classifierNoRuling`(`tool-approval.ts`);inbox 行读 core `CheckpointSummary` 已筛过的同名位、直投不经本函数。
459
+ * · `differs`(core 7.44.0 #1374)—— **载体是引擎拒 steer 重放那只错的 `detail`**(不是 ask 的三个载体之一),值是 core `STEER_REPLAY_FACETS` 的**非空子集**
460
+ * (core:「恒非空」;筛 = 本文件 {@link isSteerReplayDiffers},成员表取 core 常量)。读者:`runs.ts` 的 409 `steering.duplicate_input_id` 体(`detail.differs`)。
461
+ * 集外成员 / 空数组 / 非数组 ⇒ 按缺席(不铸 `detail`,码仍是契约)+ 计一次;detail 只报形名(`differs=<array>` —— 数组从不展开成员,见 {@link outOfSetDetail})。
449
462
  *
450
463
  * 什么时候经本函数 —— 一句话:**筛点在 core;server 只在「读的是持久层原字节、中间没有 core 投影」的地方亲手调 core 的那一只筛**。
451
464
  * inbox 行读 core `CheckpointSummary.{mandate,origin}`(core `summarizeCheckpoint` 已用同一只 `isPersistedRuleMandate` / `isAskOrigin`
@@ -459,10 +472,12 @@ type ToolApprovalPark = Extract<CorePendingAction, {
459
472
  * 只读值不调用值)。
460
473
  */
461
474
  export declare function readClosedWordSeat<S extends ClosedWordSeat>(carrier: object, seat: S): ClosedWordOf<S> | undefined;
462
- /** 闭集词座的名:三个 core 载体上**同名**(`AskRequest` / 寄存行 `tool_approval` 臂 / `CheckpointSummary`)—— 任一处改名 / 退役 ⇒ 下面的围栏编译红。 */
463
- type ClosedWordSeat = "mandate" | "origin";
464
- /** 一座的值型 = core 寄存行上那一座的型(去掉可选位的 `undefined`)。 */
465
- type ClosedWordOf<S extends ClosedWordSeat> = NonNullable<ToolApprovalPark[S]>;
475
+ /** ask 载体上的闭集词座名:三个 core 载体上**同名**(`AskRequest` / 寄存行 `tool_approval` 臂 / `CheckpointSummary`)—— 任一处改名 / 退役 ⇒ 下面的围栏编译红。 */
476
+ type AskClosedWordSeat = "mandate" | "origin" | "classifierNoRuling";
477
+ /** 闭集词座的名 = ask 载体上的三座 + 引擎拒 steer 重放那只错的 `detail.differs`(载体不同、读法同一只)。 */
478
+ type ClosedWordSeat = AskClosedWordSeat | "differs";
479
+ /** 一座的值型:ask 三座 = core 寄存行上那一座的型(去掉可选位的 `undefined`);`differs` = core 闭集 `SteerReplayFacet` 的数组(core `detail.differs` 的型)。 */
480
+ type ClosedWordOf<S extends ClosedWordSeat> = S extends AskClosedWordSeat ? NonNullable<ToolApprovalPark[S]> : readonly SteerReplayFacet[];
466
481
  /**
467
482
  * 🔴 **S-934 ②(core 7.37.0 #1214)—— ask 类对的唯一窄读**:`askClass`(一问的类 = `classes` 的首员)与 `classes`(这一问属于的全部类,core
468
483
  * `ASK_CLASSES` 序)。三个载体上同名:`AskRequest`(活卡帧 / 呈卡帧的卡读它)、寄存行 `PendingAction.tool_approval`(运维队列行经
@@ -600,11 +615,13 @@ export declare const ApprovalCardSchema: z.ZodObject<{
600
615
  kind: z.ZodLiteral<"single">;
601
616
  rule: z.ZodString;
602
617
  match: z.ZodEnum<{
618
+ name: "name";
603
619
  path: "path";
604
620
  exact: "exact";
605
621
  prefix: "prefix";
606
622
  wildcard: "wildcard";
607
623
  subpath: "subpath";
624
+ name_prefix: "name_prefix";
608
625
  }>;
609
626
  command: z.ZodString;
610
627
  }, z.core.$strict>, z.ZodObject<{
@@ -613,11 +630,13 @@ export declare const ApprovalCardSchema: z.ZodObject<{
613
630
  kind: z.ZodLiteral<"command">;
614
631
  rule: z.ZodString;
615
632
  match: z.ZodEnum<{
633
+ name: "name";
616
634
  path: "path";
617
635
  exact: "exact";
618
636
  prefix: "prefix";
619
637
  wildcard: "wildcard";
620
638
  subpath: "subpath";
639
+ name_prefix: "name_prefix";
621
640
  }>;
622
641
  command: z.ZodString;
623
642
  segment: z.ZodString;
@@ -676,8 +695,8 @@ export declare const ApprovalCardSchema: z.ZodObject<{
676
695
  }, z.core.$strict>>>;
677
696
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
678
697
  }, z.core.$strict>>;
679
- askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
680
- classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
698
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
699
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
681
700
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
682
701
  sourceTaskId: z.ZodOptional<z.ZodString>;
683
702
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -726,11 +745,13 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
726
745
  kind: z.ZodLiteral<"single">;
727
746
  rule: z.ZodString;
728
747
  match: z.ZodEnum<{
748
+ name: "name";
729
749
  path: "path";
730
750
  exact: "exact";
731
751
  prefix: "prefix";
732
752
  wildcard: "wildcard";
733
753
  subpath: "subpath";
754
+ name_prefix: "name_prefix";
734
755
  }>;
735
756
  command: z.ZodString;
736
757
  }, z.core.$strict>, z.ZodObject<{
@@ -739,11 +760,13 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
739
760
  kind: z.ZodLiteral<"command">;
740
761
  rule: z.ZodString;
741
762
  match: z.ZodEnum<{
763
+ name: "name";
742
764
  path: "path";
743
765
  exact: "exact";
744
766
  prefix: "prefix";
745
767
  wildcard: "wildcard";
746
768
  subpath: "subpath";
769
+ name_prefix: "name_prefix";
747
770
  }>;
748
771
  command: z.ZodString;
749
772
  segment: z.ZodString;
@@ -802,8 +825,8 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
802
825
  }, z.core.$strict>>>;
803
826
  personalRuleDotsAbsent: z.ZodOptional<z.ZodCustom<AskEvidenceAbsence, AskEvidenceAbsence>>;
804
827
  }, z.core.$strict>>;
805
- askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
806
- classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
828
+ askClass: z.ZodOptional<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>;
829
+ classes: z.ZodOptional<z.ZodArray<z.ZodCustom<"mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary", "mode" | "hook" | "org_rule" | "write_protection" | "write_protection_unresolved" | "content_question" | "ask_rule" | "denial_limit_fallback" | "ancestor" | "dangerous_delete" | "unverifiable_delete" | "transcript_write" | "governance_unavailable" | "rule_unreadable" | "classifier_no_ruling" | "requires_user_interaction" | "operator_forced" | "shell_floor" | "probe" | "tool_marks_mcp" | "tool_marks_a2a" | "policy_rra" | "artifact_shared_read" | "unstated" | "ordinary">>>;
807
830
  fromSubagent: z.ZodOptional<z.ZodLiteral<true>>;
808
831
  sourceTaskId: z.ZodOptional<z.ZodString>;
809
832
  sourceAgentName: z.ZodOptional<z.ZodString>;
@@ -976,6 +999,13 @@ export declare function buildRevokeFrame(batchId: string, askIds: readonly strin
976
999
  * 竞争者;收敛是车2 的窗到期竞争者与车5 恢复扫描的职责)。
977
1000
  */
978
1001
  export declare function buildReplayFrame(row: AskRow, nowMs: number): ApprovalRequestFrame | undefined;
1002
+ /**
1003
+ * S-1154 —— 持久行上那张卡**要不要人**:卡面 `risk.requiresRealApproval`(铸卡时取自引擎在 `AskRequest` 上盖的同一位,
1004
+ * {@link buildApprovalCard} 第三参)。读者 = 耐久 ask 回决口在裁决之前交给引擎判官 `policyCannotAnswer` 的那一问(`host-decision.ts` 的 `heldAskOf`)。
1005
+ * 🔴 读不出卡(`card_json` 形不合)⇒ **按要人读**(fail-closed):这一位只用来**拒**策略的放行,读坏的行上拒一次可以改成人答 / 策略拒
1006
+ * 再来;反方向(读坏当不要人)会让一次引擎终将拒掉的放行先落成一条宣称 approve 的行。
1007
+ */
1008
+ export declare function storedCardDemandsAPerson(cardJson: unknown): boolean;
979
1009
  /** {@link buildApprovalPreamble} 的返回形——帧 + 两个**计数**(调用方按自己的 logger 记 warn;本模块
980
1010
  * 保持纯函数,不吃 logger:那会让一个 `build*` 捕获行为)。 */
981
1011
  export interface ApprovalPreamble {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { ALLOW_RULE_MEMBERS, ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, isAskClass, isAskOrigin, isPersistedRuleMandate, screenAskClassMembers } from "@sema-agent/core";
2
+ import { ALLOW_RULE_MEMBERS, ASK_EVIDENCE_ABSENCE_VALUES, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, STEER_REPLAY_FACETS, isAskClass, isAskOrigin, isAutoModeFallbackCause, isPersistedRuleMandate, screenAskClassMembers } from "@sema-agent/core";
3
3
  import { redactSecrets } from "./trace/redact.js";
4
4
  import { recordFailOpen } from "./observability/fail-open.js";
5
5
  import { PERSISTED_RULE_MATCHES, UNCOVERED_SEGMENT_REASONS } from "./permission-rule-vocab.js";
@@ -314,9 +314,15 @@ export function readClosedWordSeat(carrier, seat) {
314
314
  recordFailOpen("server.approval-card.closed-word-out-of-set", `${seat}=${outOfSetDetail(raw)}`);
315
315
  return undefined;
316
316
  }
317
+ function isSteerReplayDiffers(v) {
318
+ const facets = STEER_REPLAY_FACETS;
319
+ return Array.isArray(v) && v.length > 0 && v.every((f) => facets.includes(f));
320
+ }
317
321
  const CLOSED_WORD_SEAT_GUARDS = {
318
322
  mandate: isPersistedRuleMandate,
319
323
  origin: isAskOrigin,
324
+ classifierNoRuling: isAutoModeFallbackCause,
325
+ differs: isSteerReplayDiffers,
320
326
  };
321
327
  export function readAskClassPair(carrier) {
322
328
  const askClass = "askClass" in carrier ? carrier.askClass : undefined;
@@ -520,6 +526,10 @@ export function buildReplayFrame(row, nowMs) {
520
526
  approvalId: parsed.data.approvalId,
521
527
  };
522
528
  }
529
+ export function storedCardDemandsAPerson(cardJson) {
530
+ const parsed = ApprovalCardEnvelopeSchema.safeParse(cardJson);
531
+ return !parsed.success || parsed.data.card.risk.requiresRealApproval;
532
+ }
523
533
  export function buildApprovalPreamble(rows, nowMs, replayMax) {
524
534
  const frames = [];
525
535
  let skipped = 0;
@@ -3,9 +3,10 @@
3
3
  * (createOaApprovalPolicy + ApprovalStore pending 行轮询)整体退役;唯一门形 = durable checkpoint
4
4
  * suspend/resume,见 createDurableAskPolicy 与 /v1/approvals 的 checkpointStore 腿)。
5
5
  */
6
- import { type ToolPolicy } from "@sema-agent/core";
6
+ import { type ToolCallRequest, type ToolPolicy } from "@sema-agent/core";
7
7
  import { type PostureSource } from "./posture-source.js";
8
8
  import { type ExecutionLane } from "./execution-lane-caps.js";
9
+ import { type ApprovalExemptionStore } from "./plugins/approval-exemption-store.js";
9
10
  /**
10
11
  * 门意图判据读的五个量(`ServiceConfig` 结构满足)。单独成型是为了让**热路径**能拿「候选视图」来判
11
12
  * (S-668 波 2:`{ ...config, approvalRequire: 候选名单 }`)—— 判据只有一份,输入可以是活配置也可以是候选。
@@ -52,7 +53,7 @@ export declare function singleUserAutoAcceptBaselineApplies(config: GateIntentVi
52
53
  readonly requirePrincipal: boolean;
53
54
  }): boolean;
54
55
  /**
55
- * 门意图**不可服务**的判据 —— 返回拒因(可服务 ⇒ `undefined`)。**全仓唯一一份**,两处调用、处置不同:
56
+ * 门意图**不可服务**的判据 —— 返回拒因(可服务 ⇒ `undefined`)。**全仓唯一一份**,三处调用、处置不同:
56
57
  * · **boot**(`boot/stage-07-capability-layer.ts`):对装配完的整份 config 判,拒因 ⇒ **拒启**(全窗复审 D3-F1,
57
58
  * HIGH;与 env 墓碑族同谱系)。v4.3.0 的轮询门形(APPROVAL_REQUIRE + DB backend + DURABLE_APPROVAL 未设)在
58
59
  * 5.0.0 只剩 core 每任务 warn——**原先拦、现在放**(方向反转)。「留 undefined 让 core warn」对新配置是
@@ -60,6 +61,9 @@ export declare function singleUserAutoAcceptBaselineApplies(config: GateIntentVi
60
61
  * · **热路径**(S-668 波 2 ④,`config-center/apply-effective.ts` 的审批组 stage):对**候选视图**
61
62
  * (活配置 + 候选名单)判,拒因 ⇒ 审批组**整组拒**、活配置零变化、世代账停旧代。进程在跑,拒启不是选项;
62
63
  * 而放行一份「没有 checkpoint 店可停车」的名单 = 单用户 auto-accept 基线仍在、名单静默不生效。
64
+ * · **每个任务的装配**(S-1144,`boot/resolve-spec.ts` 的 `resolveSpec` 头一道):对**活配置**判,拒因 ⇒ 请求 501
65
+ * `capability.checkpoint_store_required`。部署形到不了(boot 已拒启);可达的是嵌入方装配 / 直调 `resolveSpec`(自己递 ctx、没经 stage-07)。
66
+ * 修前这一形把审批基线静默铸成 `undefined`,请求的 allow 清「普通问」后任务零卡落盘 —— 同一条规则在起服层拒、在装配层静默 = 一事两读。
63
67
  * `checkpointStorePresent` 是**部署事实**(本进程建没建出 checkpoint 店),不在 `ServiceConfig` 里 ⇒ 调用方显式递。
64
68
  * 放这文件与 hasOperatorGateIntent/inv#2 同源(意图判据再漂要同时改两段)。
65
69
  */
@@ -129,22 +133,56 @@ export declare function buildForceDurableGateInertNotice(input: {
129
133
  * replica. (The former poll-lane
130
134
  * twin, createOaApprovalPolicy, was retired in 5.0.0 — durable is the only gate.)
131
135
  */
132
- export interface DurableAskOptions {
133
- requireApproval: string[];
134
- deny?: string[];
136
+ export interface DurableAskOptions extends ApprovalListsView {
135
137
  autoBudget?: number;
136
- neverAuto?: string[];
137
- /** Per-session approval-exemption probe ("本会话不再询问"): consulted ONLY for require-listed
138
- * tools, AFTER deny/neverAuto (both always win) and BEFORE the budget path. Canonical-space toolName.
139
- * A rejected promise is treated as NOT exempt (fail-closed → ask) at the call site below.
140
- * 🔴 S-877 候裁 16:会话授权**两个读位、一只店、按问句的作者分**(收车档 ⑩ 16):本探针兑现**本仓自己的问**(名单门)
141
- * —— 在它自己的层上「不问」,于是委派子代经父约束链继承到的同一问也不问;引擎自装的问(模式写门 / 形状层)由审批席
142
- * (`ToolApprovalCoordinator.standingGrantSettles`)兑现。本探针**不能**退役:子代的继承问到达祖先审批席时 core 盖
143
- * `mandated`(`ruleClearanceOf` 的 inheritedUnresolved / ancestorResolved 门),席的判据依约不退役它 —— 只有本层的
144
- * 「不问」够得着继承链(钉 test/parked-decide-leg.test.ts「[候裁② 修]remember 全链行为」)。 */
145
- exempt?: (canonicalToolName: string) => Promise<boolean>;
146
- /** Audit hook — fired when an exemption short-circuits the ask (the `approval_exempted` log line). */
147
- onExempted?: (canonicalToolName: string, rawToolName: string) => void;
138
+ /** 本会话的「本会话放行」读面(server 的会话豁免店,键 = 会话 × wire 工具名)。缺席 = 这条腿没有会话 / 没有店 ⇒ 名单门照问。 */
139
+ exemption?: DeploymentListExemption;
148
140
  }
141
+ /** 三张运维名单(一只调用由哪一张说话)的入参 —— 名单门与「问的属主」读法同读这一份。 */
142
+ export interface ApprovalListsView {
143
+ readonly requireApproval: readonly string[];
144
+ readonly deny?: readonly string[];
145
+ readonly neverAuto?: readonly string[];
146
+ }
147
+ /**
148
+ * 三张运维名单对**一只调用**的判词(deny 先于 never-auto 先于 require),`hit` = 命中的那个名(规则归因写运维名单里的那个)。
149
+ * 名单门按它出 deny / 恒问人 / 名单的问;「这张卡上有没有名单自己的问」也按它读(见 {@link deploymentListAsks})—— 一份读法,两个读者。
150
+ */
151
+ export type ApprovalListVerdict = {
152
+ readonly list: "deny";
153
+ } | {
154
+ readonly list: "never_auto" | "require";
155
+ readonly hit: string;
156
+ } | {
157
+ readonly list: "none";
158
+ };
159
+ /** 铸一只判词函数(名单的命中集与 covering 规则只解一次)。名字读法 = {@link heldRuleName}(收紧臂读调用答得上的全部名字)。 */
160
+ export declare function createApprovalListVerdict(lists: ApprovalListsView): (call: Pick<ToolCallRequest, "toolName" | "face">) => ApprovalListVerdict;
161
+ /**
162
+ * **「问的属主」的读法**:部署名单门对这只调用出不出它**自己的**那一问(`APPROVAL_REQUIRE` 臂)—— 读的是铸点本身(同一只判词),
163
+ * 不另造一张表。读者:审批席(活卡的提示位 / 回决写侧)、耐久 `/decide` 腿与运维队列行(停驻行上只有工具名,按 wire 名读)。
164
+ * 名单是热换的(中心下发),读者每次现读活配置。答 `true` 只说「名单门问这只调用」;这张卡是不是名单的那一问,还要看卡上的类
165
+ * (`tool-approval.ts` 的 `StandingAnswerFacts.deploymentListQuestion`)。
166
+ */
167
+ export declare function deploymentListAsks(config: Pick<GateIntentView, "approvalRequire" | "approvalDeny" | "approvalNeverAuto">, call: Pick<ToolCallRequest, "toolName" | "face">): boolean;
168
+ /**
169
+ * 名单门的「本会话放行」读面:本会话的豁免店读一次 + server 自己的审计行。名单的问是 server 铸的,它的站着的答案记在 server 的
170
+ * 会话豁免店(活卡的 `allow_session` 与耐久 `/decide` 的 `remember:"session"` 写同一只店),名单门在出问**之前**读它。
171
+ */
172
+ export interface DeploymentListExemption {
173
+ /** 这只工具(wire 名)在本会话有没有豁免行。有界、fail-closed:读抛 / 超时 ⇒ `false`(照问,人多看一张卡)。 */
174
+ readonly has: (toolName: string) => Promise<boolean>;
175
+ /** 名单门因为豁免行没出问 —— server 自己的审计行(宿主豁免;不是人答的、不是策略代答的、没有记住任何东西)。 */
176
+ readonly onExempted: (toolName: string) => void;
177
+ }
178
+ /**
179
+ * 名单门豁免读面的**唯一**铸点(HTTP 腿 `resolve-spec` 与 park 赎回腿 `parked-revive-gate` 两处装配都经它):键 = 会话 × wire 工具名
180
+ * (与活卡 `allow_session` / `/decide` remember 写下的键同形)。审计事件名 {@link APPROVAL_EXEMPTED_EVENT},字段只有会话、工具与读位
181
+ * `seat: "deployment_list"`(审批席自己那一行是 `seat: "approval_seat"`)。读的期限与审批席读同一只店的期限同值。
182
+ */
183
+ export declare function createDeploymentListExemption(store: Pick<ApprovalExemptionStore, "has">, sessionId: string, logger: {
184
+ info(event: string, fields?: Record<string, unknown>): void;
185
+ warn(event: string, fields?: Record<string, unknown>): void;
186
+ }, leg?: "parked_revive"): DeploymentListExemption;
149
187
  export declare function createDurableAskPolicy(opts: DurableAskOptions): ToolPolicy;
150
188
  //# sourceMappingURL=approval.d.ts.map
package/dist/approval.js CHANGED
@@ -4,7 +4,8 @@ import { ASK_USER_QUESTION_TOOL_NAME } from "./approval-content-kind.js";
4
4
  import { isExplicitPosture } from "./posture-source.js";
5
5
  import { handsOnWorkerFilePlane } from "./execution-lane-caps.js";
6
6
  import { heldRuleName } from "./tool-rule-names.js";
7
- import { answerGrantsBeyondCall } from "./standing-answer-grant.js";
7
+ import { DURABLE_CONVERGE_READ_TIMEOUT_MS, policyAskStandingAnswerFacts, standingAnswerDurablyRecordable } from "./tool-approval.js";
8
+ import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
8
9
  export function hasApprovalListIntent(config) {
9
10
  return (config.approvalRequire.length > 0 ||
10
11
  config.approvalDeny.length > 0 ||
@@ -68,6 +69,49 @@ export function buildForceDurableGateInertNotice(input) {
68
69
  },
69
70
  };
70
71
  }
72
+ export function createApprovalListVerdict(lists) {
73
+ const require = createNameSetMatcher(lists.requireApproval);
74
+ const deny = createNameSetMatcher(lists.deny ?? []);
75
+ const neverAuto = createNameSetMatcher(lists.neverAuto ?? []);
76
+ return (call) => {
77
+ if (heldRuleName(call, deny) !== undefined)
78
+ return { list: "deny" };
79
+ const neverAutoHit = heldRuleName(call, neverAuto);
80
+ if (neverAutoHit !== undefined)
81
+ return { list: "never_auto", hit: neverAutoHit };
82
+ const requireHit = heldRuleName(call, require);
83
+ if (requireHit !== undefined)
84
+ return { list: "require", hit: requireHit };
85
+ return { list: "none" };
86
+ };
87
+ }
88
+ export function deploymentListAsks(config, call) {
89
+ return createApprovalListVerdict({ requireApproval: config.approvalRequire, deny: config.approvalDeny, neverAuto: config.approvalNeverAuto })(call).list === "require";
90
+ }
91
+ export function createDeploymentListExemption(store, sessionId, logger, leg) {
92
+ const where = { sessionId, ...(leg !== undefined ? { leg } : {}) };
93
+ return {
94
+ has: async (toolName) => {
95
+ let timer;
96
+ try {
97
+ const deadline = new Promise((_, reject) => {
98
+ timer = setTimeout(() => reject(new Error(`exemption read timed out after ${DURABLE_CONVERGE_READ_TIMEOUT_MS}ms`)), DURABLE_CONVERGE_READ_TIMEOUT_MS);
99
+ timer.unref?.();
100
+ });
101
+ return (await Promise.race([store.has(sessionId, toolName), deadline])) === true;
102
+ }
103
+ catch (err) {
104
+ logger.warn("session_grant_read_failed", { ...where, toolName, seat: "deployment_list", error: String(err).slice(0, 200) });
105
+ return false;
106
+ }
107
+ finally {
108
+ if (timer !== undefined)
109
+ clearTimeout(timer);
110
+ }
111
+ },
112
+ onExempted: (toolName) => logger.info(APPROVAL_EXEMPTED_EVENT, { ...where, toolName, seat: "deployment_list" }),
113
+ };
114
+ }
71
115
  function createNameSetMatcher(names) {
72
116
  const exact = new Set(names);
73
117
  const covering = names.filter(isNamespacedCoveringRuleName);
@@ -79,44 +123,27 @@ function grantedByRule() {
79
123
  return { action: "allow", decisionReason: "rule" };
80
124
  }
81
125
  export function createDurableAskPolicy(opts) {
82
- const require = createNameSetMatcher(opts.requireApproval);
83
- const deny = createNameSetMatcher(opts.deny ?? []);
84
- const neverAuto = createNameSetMatcher(opts.neverAuto ?? []);
126
+ const verdictOf = createApprovalListVerdict(opts);
85
127
  const budget = Math.max(0, Math.floor(opts.autoBudget ?? 0));
86
128
  let autoApproved = 0;
87
129
  const isQuestionTool = (name) => name === ASK_USER_QUESTION_TOOL_NAME;
88
130
  return {
89
131
  check: (req) => {
90
- if (heldRuleName(req, deny) !== undefined)
91
- return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
92
- const neverAutoHit = heldRuleName(req, neverAuto);
93
- if (neverAutoHit !== undefined)
94
- return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${neverAutoHit})`, `human approval required to run "${req.toolName}"`);
95
- const requireHit = heldRuleName(req, require);
96
- if (requireHit !== undefined) {
97
- const remEarly = req.budget?.resourceRemainingMicroUsd;
98
- if (remEarly !== undefined && remEarly <= 0) {
99
- return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
100
- }
101
- if (opts.exempt && heldRuleName(req, isQuestionTool) === undefined && !answerGrantsBeyondCall(req)) {
102
- return (async () => {
103
- let exempted = false;
104
- try {
105
- exempted = (await opts.exempt(req.toolName)) === true;
106
- }
107
- catch {
108
- exempted = false;
109
- }
110
- if (exempted) {
111
- opts.onExempted?.(req.toolName, req.toolName);
112
- return grantedByRule();
113
- }
114
- return decideRequired(req, requireHit);
115
- })();
132
+ const verdict = verdictOf(req);
133
+ switch (verdict.list) {
134
+ case "deny":
135
+ return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
136
+ case "never_auto":
137
+ return operatorAlwaysHumanAsk(`APPROVAL_NEVER_AUTO(${verdict.hit})`, `human approval required to run "${req.toolName}"`);
138
+ case "require":
139
+ return decideRequired(req, verdict.hit);
140
+ case "none":
141
+ return { action: "allow" };
142
+ default: {
143
+ const unreachable = verdict;
144
+ throw new Error(`unhandled approval-list verdict: ${JSON.stringify(unreachable)}`);
116
145
  }
117
- return decideRequired(req, requireHit);
118
146
  }
119
- return { action: "allow" };
120
147
  },
121
148
  };
122
149
  function decideRequired(req, requireHit) {
@@ -124,11 +151,25 @@ export function createDurableAskPolicy(opts) {
124
151
  if (rem !== undefined && rem <= 0) {
125
152
  return operatorAlwaysHumanAsk("RESOURCE_SUSPEND budget exhausted", `durable resource budget exhausted — human approval required to run "${req.toolName}"`);
126
153
  }
154
+ const ask = operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`);
155
+ if (opts.exemption !== undefined && standingAnswerDurablyRecordable(policyAskStandingAnswerFacts(req, ask, true))) {
156
+ const exemption = opts.exemption;
157
+ return (async () => {
158
+ if (await exemption.has(req.toolName)) {
159
+ exemption.onExempted(req.toolName);
160
+ return { action: "allow" };
161
+ }
162
+ return budgetOrAsk(req, ask);
163
+ })();
164
+ }
165
+ return budgetOrAsk(req, ask);
166
+ }
167
+ function budgetOrAsk(req, ask) {
127
168
  if (heldRuleName(req, isQuestionTool) === undefined && budget > 0 && autoApproved < budget) {
128
169
  autoApproved++;
129
170
  return grantedByRule();
130
171
  }
131
- return operatorAsk(`APPROVAL_REQUIRE(${requireHit})`, `approval required to run "${req.toolName}"`);
172
+ return ask;
132
173
  }
133
174
  }
134
175
  //# sourceMappingURL=approval.js.map
package/dist/audit.d.ts CHANGED
@@ -16,7 +16,9 @@ export interface SessionAudit {
16
16
  provider: string;
17
17
  modelId: string;
18
18
  } | null;
19
- /** Current model context: prior messages with older history collapsed into a summary message. */
19
+ /** Current model context: prior messages with older history collapsed into a summary message.
20
+ * 🔴 **存档原形**(store 里怎么写就怎么回)—— 不是 wire 形。HTTP 读面一律过 {@link redactSessionViewForRead}(次序 = 选窗 → 脱敏 → 截断)
21
+ * 再回给调用方;直接消费本视图的宿主(`buildSessionAudit` 是包根导出)自己负责同一道脱敏。 */
20
22
  messages: unknown[];
21
23
  /** [ref]① web 观测切片 B: the session's prompt-epoch pin, core contract verbatim (core 1.304
22
24
  * `session.getPromptEpoch()` — first-class `prompt_epoch` entry, compaction restatement, [ref] anchor
@@ -36,10 +38,19 @@ export declare function buildSessionAudit(pool: Pool, sessionId: string): Promis
36
38
  * (its storage seam has no wake-equivalent yet — may turn out to be a core gap). */
37
39
  export declare function buildSessionAuditPg(pool: PgPool, sessionId: string): Promise<SessionAudit | undefined>;
38
40
  /** The LOCAL (File) leg — web session area on DB_BACKEND=local.
41
+ * repo 取自 `createLocalSessionRepo`(与 `LocalSessionStore` 同一只工厂):坏记录的披露座(F 类 `server.session-repo.corrupt-record-skipped`)
42
+ * 因此在视图口同样在场 —— 视图照常返回、跳过的记录有计数 + 探针行(修前自建一只不带座的 repo,零披露)。
39
43
  * 实现姿势:`FileSessionRepo.exportEntries`(纯读、torn-tail 恢复、missing→not_found)→
40
44
  * `boundedTail`(纯函数切 floor)→ InMemorySessionStorage → 同一 auditFromStorage 内核。零写面、
41
45
  * 零新 seam;createdAt 从 repo.list() 的 metadata 取(File 形态 meta 与 entries 分储)。 */
42
46
  export declare function buildSessionAuditLocal(root: string, sessionId: string): Promise<SessionAudit | undefined>;
47
+ /**
48
+ * 🔴 **会话视图的读面口 —— `GET /v1/sessions/:id` 的全部取法(整读 / `?tail` / `?before` / `?blobCap` / `?message=N`)共用这一只**。
49
+ * 次序 = **选 → 脱 → 截**(路由 `http/routes/sessions.ts`):整条消息的选窗不切字符串,先选窗不改任何一条的脱敏读数、只把脱敏的量降到选中的那几条;
50
+ * `blobCap` 的截断会切字符串(预览切在密钥中间,半截密钥认不出来)⇒ 截断恒在脱敏之后。
51
+ * `messages` 缺席 ⇒ 不铸键;非数组 ⇒ 深扫;其余键(`owner` / `promptEpoch` / `model` …)逐字。字节没变 ⇒ **同一引用**。
52
+ */
53
+ export declare function redactSessionViewForRead<V extends object>(view: V): V;
43
54
  /** Window `messages` — `tail=N` (last N) or `before=<offset>&limit=N` ([max(0,before-N), before)).
44
55
  * Returns the window + its {offset,total} so the web can page upward. */
45
56
  export declare function windowMessages(messages: unknown[], opts: {
package/dist/audit.js CHANGED
@@ -1,6 +1,9 @@
1
- import { StoredSession, FileSessionRepo, InMemorySessionStorage, boundedTail } from "@sema-agent/core";
1
+ import { StoredSession, InMemorySessionStorage, boundedTail } from "@sema-agent/core";
2
2
  import { TiDBSessionStorage } from "./plugins/tidb-session-storage.js";
3
3
  import { PgSessionStorage } from "./plugins/pg-session-storage.js";
4
+ import { createLocalSessionRepo } from "./plugins/local-session-store.js";
5
+ import { redactModelText } from "./observability/run-terminal.js";
6
+ import { redactDeepWith } from "./trace/redact.js";
4
7
  export async function buildSessionAudit(pool, sessionId) {
5
8
  return auditFromStorage(await TiDBSessionStorage.wake(pool, sessionId), sessionId);
6
9
  }
@@ -8,7 +11,7 @@ export async function buildSessionAuditPg(pool, sessionId) {
8
11
  return auditFromStorage(await PgSessionStorage.wake(pool, sessionId), sessionId);
9
12
  }
10
13
  export async function buildSessionAuditLocal(root, sessionId) {
11
- const repo = new FileSessionRepo(root);
14
+ const repo = createLocalSessionRepo(root);
12
15
  let entries;
13
16
  try {
14
17
  entries = await repo.exportEntries(sessionId);
@@ -40,6 +43,63 @@ async function auditFromStorage(storage, sessionId) {
40
43
  ...(promptEpoch ? { promptEpoch } : {}),
41
44
  };
42
45
  }
46
+ const deepScrub = (v) => redactDeepWith(v, redactModelText);
47
+ const textSlot = (v) => (typeof v === "string" ? redactModelText(v) : deepScrub(v));
48
+ function scrubSlot(p, key, scan) {
49
+ const v = p[key];
50
+ if (v === undefined)
51
+ return p;
52
+ const next = scan(v);
53
+ return next === v ? p : { ...p, [key]: next };
54
+ }
55
+ const PART_READ_TREATMENT = {
56
+ text: (p) => scrubSlot(p, "text", textSlot),
57
+ thinking: (p) => scrubSlot(p, "thinking", textSlot),
58
+ toolCall: (p) => scrubSlot(p, "arguments", deepScrub),
59
+ image: (p) => p,
60
+ document: (p) => p,
61
+ };
62
+ function readParts(content) {
63
+ if (!Array.isArray(content))
64
+ return textSlot(content);
65
+ const next = content.map((part) => {
66
+ if (part !== null && typeof part === "object" && !Array.isArray(part)) {
67
+ const type = part.type;
68
+ if (typeof type === "string" && Object.hasOwn(PART_READ_TREATMENT, type)) {
69
+ return PART_READ_TREATMENT[type](part);
70
+ }
71
+ }
72
+ return deepScrub(part);
73
+ });
74
+ return next.every((x, i) => x === content[i]) ? content : next;
75
+ }
76
+ const readContent = (m) => scrubSlot(m, "content", readParts);
77
+ const MESSAGE_READ_TREATMENT = {
78
+ user: (m) => m,
79
+ custom: (m) => m,
80
+ assistant: (m) => readContent(m),
81
+ toolResult: (m) => scrubSlot(readContent(m), "details", deepScrub),
82
+ bashExecution: (m) => scrubSlot(scrubSlot(m, "command", textSlot), "output", textSlot),
83
+ compactionSummary: (m) => scrubSlot(m, "summary", textSlot),
84
+ };
85
+ function readMessage(m) {
86
+ if (m !== null && typeof m === "object" && !Array.isArray(m)) {
87
+ const role = m.role;
88
+ if (typeof role === "string" && Object.hasOwn(MESSAGE_READ_TREATMENT, role)) {
89
+ return MESSAGE_READ_TREATMENT[role](m);
90
+ }
91
+ }
92
+ return deepScrub(m);
93
+ }
94
+ export function redactSessionViewForRead(view) {
95
+ const { messages } = view;
96
+ if (messages === undefined)
97
+ return view;
98
+ const next = Array.isArray(messages)
99
+ ? (() => { const out = messages.map(readMessage); return out.every((x, i) => x === messages[i]) ? messages : out; })()
100
+ : deepScrub(messages);
101
+ return next === messages ? view : { ...view, messages: next };
102
+ }
43
103
  export function windowMessages(messages, opts) {
44
104
  const total = messages.length;
45
105
  if (opts.before !== undefined) {
@@ -42,8 +42,7 @@
42
42
  * `entitlementSource`(既有位)说的是「center 源在不在」;`armed` 说的是「按三态谓词此刻会不会武装」。
43
43
  * 两者刻意并存:前者告诉壳有没有组织面,后者告诉壳这台机器上 auto 到底是不是 default 的别名。
44
44
  */
45
- import { type RunnerDeps, type RuntimeCaps } from "@sema-agent/core";
46
- import type { SettingsPermissionMode } from "./task-settings.js";
45
+ import { type PermissionMode, type RunnerDeps, type RuntimeCaps } from "@sema-agent/core";
47
46
  /**
48
47
  * 分类器席的模型 —— **调 core 的单一解析器** `classifierSeatModel({deps, main})`(core 7.20.0 / [ref],
49
48
  * 本仓 [ref] 的跨仓诉求兑现;契约句 `classifier.seat.one_resolver` 逐字:引擎的武装点与**显示这个席**
@@ -123,7 +122,7 @@ export type AutoModeVerdict = {
123
122
  * 走到这里还在抛 = 装配缺陷,读面 500 比一句编出来的 reason 诚实。
124
123
  */
125
124
  export declare function judgeAutoModeArming(face: AutoModeFaceDeps | undefined, input: {
126
- readonly requestedMode: SettingsPermissionMode | undefined;
125
+ readonly requestedMode: PermissionMode | undefined;
127
126
  readonly principal: string | undefined;
128
127
  /** 本地 `PERMISSIONS_DISABLE_AUTO_MODE`(= catalog/settings 键 `permissions.disableAutoMode`)是否置 true —— caps false 时分词
129
128
  * `local_denied` / `org_denied`(seam ⑤;两者对壳的指路不同:改本机 env vs 找组织)。 */