@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
@@ -49,26 +49,28 @@ export declare const MAX_TASK_AGENTS = 32;
49
49
  *
50
50
  * 本谓词只判「字符串数组」(core 7.42.0 起与请求的 `settings.permissions.allow`、运维的 `APPROVAL_GRANT` **同一只**:`rule-entry-list.ts` 的
51
51
  * `ruleEntryListShapeOk`):引擎直接 `for (const entry of list)` —— 一个裸字符串 `"Bash"` 会被逐字符迭代成 `B` / `a` / `s` / `h` 四条
52
- * (deny 侧 = 意图静默落空,安全轴上的放宽方向)。条目的**文法**归引擎:允侧读不出的条目(`*` / 写路径 / 域名)引擎在开跑时发
53
- * `permission.allowed_tool_not_granted`(受众 = 用户);拒侧读成空的条目在 `resolveSpec` 拒(提交 400 / 续跑 409,`rule-entry-list.ts` 的
54
- * `skillDenyInertEntryOf`,两处调用:提交口拒点 + 存量体重放预判)。形对之后两道门再问 {@link skillToolListRefusal}(协议名的服务器段铸不出 / 缺段 ⇒ 400)——
55
- * 不是文法,是「写得出却永远不生效」,判据与请求的 allow 桶、运维的 allow 表同一份(`rule-entry-list.ts`)。
56
- * 条数帽 = 条目表那一只(`rule-entry-list.ts` `allowLayerEntryCountIssue`,准备开销的界 —— 引擎逐条播种是平方级;与请求 allow 桶、运维表同一只),
52
+ * (deny 侧 = 意图静默落空,安全轴上的放宽方向)。条目的**文法与名字**是引擎的读法:允侧读不出的条目(`*` / 写路径 / 域名 / 退役名 …)引擎在开跑时发
53
+ * `permission.allowed_tool_not_granted`(受众 = 用户);拒侧读成空的条目(文法或名字层)在 `resolveSpec` 拒(提交 400 / 续跑 409,`rule-entry-list.ts` 的
54
+ * `skillDenyInertEntryOf`,判据 = 引擎的条目读法)。形对之后两道门再问 {@link skillToolListRefusal}(条数帽 + 引擎读不到的那一形 ⇒ 400)。
55
+ * 条数帽 = 条目表那一只(`rule-entry-list.ts` `allowLayerEntryCountIssue`,准备开销的界;与请求 allow 桶、运维表同一只),
57
56
  * 在 {@link skillToolListRefusal} 里判;无长度帽([ref])。
58
57
  */
59
58
  export declare const skillToolListOk: (v: unknown) => v is readonly string[];
60
59
  /**
61
60
  * 一条技能(形已过两道门的字符串数组判)的两只清单在受理口的拒因 —— 都过 ⇒ `undefined`。两处受理口同调:顶层 `skills[]`
62
- * (`http/admission.ts` 的 `validateUserSkills`)、子代 `agents[].skills[]`({@link validateTaskAgents})。判据是 `rule-entry-list.ts` 那一份:
63
- * 任一只清单里「协议名写得出、却永远匹配不到」的条目(与请求的 allow 桶、配置表、会话规则写口同一只判官)。
64
- * 拒侧读成空的条目**不在这里判**:那一问在 `resolveSpec`(`rule-entry-list.ts` 的 `skillDenyInertEntryOf`;新提交与每条续跑腿都经过那一处)与存量体重放前的
65
- * 预判(`http/checkpoint-ctx.ts` `storedBodyReplayRefusal`)—— 一只判、两处调用。
61
+ * (`http/admission.ts` 的 `validateUserSkills`)、子代 `agents[].skills[]`({@link validateTaskAgents})。两问:条数帽;引擎读不到、却永远匹配不到的
62
+ * 条目(工具段为空的协议名,`rule-entry-list.ts` 那一份,与配置表、会话规则写口同一只)。退役名 / 对端段铸不出 / 读成空的条目是引擎的读法(见上):
63
+ * 允侧交引擎通告,拒侧由 `resolveSpec` 拒。
66
64
  */
67
65
  export declare function skillToolListRefusal(skill: Record<string, unknown>): string | undefined;
68
66
  /** [ref] 单条 agent 定义的形状裁决:null=合法;否则一句可 400 的英文错误(不带索引,调用方自己加)。
69
67
  * 白名单外键 fail-loud —— 与 resilience/limits 的「容忍未知键」相反:agents 是能力定义,静默丢字段 =
70
- * 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。⚠️ 已知残账(core 半场):
71
- * core 只对 allowTools 未知项 fail-loud,denyTools 未知项(如 typo)静默失效 —— 已上板请 core 镜像。
68
+ * 定义被悄悄削(一个丢掉的 isolation 就是放宽),比 400 更危险,故整条拒。`allowTools` / `denyTools` 本门只判形与条数(`toolList`),
69
+ * **名字归引擎一只判**(core 7.45.0 #1377 / #1385,开跑前,三来源同判):没有工具能是的拼法(带空白 / 括号形坏 / 不可见字符)、非字符串条目 ⇒ 整 run 拒
70
+ * `config.agent.unknown_tool`;无人挂的名 ⇒ 不拒、经 `onError`(phase `"config"`)告一次(本仓汇点 `runner_config_advisory`);大小写孪生 / 退役名(`bash`)
71
+ * **在本仓部署形上也只告不拒** —— 主车道的委派工具带 extraTools 工厂(`capabilities/builtin-tools.ts`),引擎开跑前列不全子代工具池(`agentToolPool` 缺席)就不把
72
+ * 像 `Bash` 的名字判成拼错;引擎给宿主「声明可注入名册」的座后本仓声明名册、这两形回到开跑前拒;
73
+ * `denyTools` 的 `Tool(content)` ⇒ 整只工具对该子代藏起并告。本仓不在受理口另做名字对照(那会是同一问的第二个判官)。
72
74
  * ⚠️ 键判断必须 Object.hasOwn:`in` 走原型链,JSON 里的 "toString"/"constructor"/"__proto__" 键会被
73
75
  * 误判为白名单字段,随后 SHAPES[k].ok 不是函数 ⇒ TypeError(fresh 500 / resume 砖;cross-review 复现)。 */
74
76
  export declare function taskAgentIssue(item: unknown): string | null;
@@ -1,7 +1,7 @@
1
1
  import { isThinkingLevel } from "@sema-agent/core";
2
2
  import { taskWallClockSec } from "./task-workflow.js";
3
3
  import { isValidCwd } from "./task-cwd.js";
4
- import { allowLayerEntryCountIssue, describeRetiredRuleEntry, describeUnreachableRuleEntry, retiredRuleEntryOf, ruleEntryListShapeOk, unreachableRuleEntryOf } from "./rule-entry-list.js";
4
+ import { allowLayerEntryCountIssue, describeEmptyToolSegmentEntry, emptyToolSegmentEntryOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
5
5
  export function normalizeSuggestNextPrompts(v) {
6
6
  if (v === true)
7
7
  return true;
@@ -76,12 +76,9 @@ export function skillToolListRefusal(skill) {
76
76
  const over = allowLayerEntryCountIssue(`skill.${key}`, list);
77
77
  if (over !== undefined)
78
78
  return over.message;
79
- const retired = retiredRuleEntryOf(list);
80
- if (retired !== undefined)
81
- return describeRetiredRuleEntry(`skill.${key}`, retired);
82
- const unreachable = unreachableRuleEntryOf(list);
83
- if (unreachable !== undefined)
84
- return describeUnreachableRuleEntry(`skill.${key}`, unreachable);
79
+ const empty = emptyToolSegmentEntryOf(list);
80
+ if (empty !== undefined)
81
+ return describeEmptyToolSegmentEntry(`skill.${key}`, empty);
85
82
  }
86
83
  return undefined;
87
84
  }
@@ -1,8 +1,20 @@
1
- /** 读口的入参:任何一种载着「这一问」的形(活的 `AskRequest`、`ToolCallRequest`、待决条目、停驻行的 `tool_approval` 臂)都结构上满足。 */
1
+ /** 读口的入参:任何一种载着「这一问」的形(活的 `AskRequest`、`ToolCallRequest`、停驻行的 `tool_approval` 臂)都结构上满足。 */
2
2
  export interface StandingAnswerGrantCarrier {
3
3
  readonly toolName?: unknown;
4
4
  readonly boundGrantDigest?: unknown;
5
5
  }
6
- /** 这一问的应答是否授予这一次调用之外的东西(读法见文件头注)。 */
6
+ /** 一问的授予绑定(闭集):`none` = 应答只覆盖这一次调用;`bound` = 授予、绑定可读;`unreadable` = 授予、绑定读不出。 */
7
+ export type GrantBinding = {
8
+ readonly kind: "none";
9
+ } | {
10
+ readonly kind: "bound";
11
+ readonly digest: string;
12
+ } | {
13
+ readonly kind: "unreadable";
14
+ };
15
+ /** 这一问的授予绑定(读法见文件头注)。 */
16
+ export declare function grantBindingOf(carrier: StandingAnswerGrantCarrier): GrantBinding;
17
+ /** 这一问的应答是否授予这一次调用之外的东西(= 绑定不是 `none`)。**按工具名记**的读者(耐久豁免店的读写口、豁免列表)据它不碰这类问:
18
+ * 它们的键没有绑定这一维,记下 / 代答都会越过绑定。 */
7
19
  export declare function answerGrantsBeyondCall(carrier: StandingAnswerGrantCarrier): boolean;
8
20
  //# sourceMappingURL=standing-answer-grant.d.ts.map
@@ -1,5 +1,12 @@
1
1
  import { SKILL_TOOL_NAME } from "@sema-agent/core";
2
+ export function grantBindingOf(carrier) {
3
+ if (Object.hasOwn(carrier, "boundGrantDigest")) {
4
+ const d = carrier.boundGrantDigest;
5
+ return typeof d === "string" && d.length > 0 ? { kind: "bound", digest: d } : { kind: "unreadable" };
6
+ }
7
+ return carrier.toolName === SKILL_TOOL_NAME ? { kind: "unreadable" } : { kind: "none" };
8
+ }
2
9
  export function answerGrantsBeyondCall(carrier) {
3
- return Object.hasOwn(carrier, "boundGrantDigest") || carrier.toolName === SKILL_TOOL_NAME;
10
+ return grantBindingOf(carrier).kind !== "none";
4
11
  }
5
12
  //# sourceMappingURL=standing-answer-grant.js.map
@@ -8,24 +8,32 @@
8
8
  *
9
9
  * ## 形
10
10
  * `declared` = 运维的声明(`DB_BACKEND` 词,含缺省推导);`backend` = **装配出的**后端 kind(没装出后端 = `memory`);两者不等是合法的
11
- * 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;auto 降级;缺省推导 local 起不来)—— 两位都上 wire,
11
+ * 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;缺省推导 local 起不来)—— 两位都上 wire,
12
12
  * 由调用方自己读出落差。`degradedReason` present-iff 降级臂走过(按后端族定词,{@link storeDegradedReasonFor})。
13
13
  * 顶层 `sql` 并入 `store.sql`(硬 breaking,零别名;对象逐字同形)。
14
14
  */
15
15
  import type { StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
16
+ import type { ServiceConfig } from "./config-types.js";
16
17
  import type { StoreBackend } from "./plugins/store-backend.js";
17
18
  import type { SqlEngineFacts } from "./plugins/sql-driver.js";
18
19
  import { type SqlEngineCapability } from "./sql-engine-posture.js";
19
20
  /**
20
- * 降级词闭集(**按后端族**,不按失败发生在哪一步 —— SQL 池懒连,连接失败第一次就出现在 `ensureSchema` 里,与 DDL 失败同一个调用,
21
- * 按调用位置分不出「连不上」与「建表失败」):
22
- * · `db_unreachable` —— SQL 族(`mysql` / `pg`)在 `SESSION_BACKEND=auto` 下打不开(连接或建表);
21
+ * 降级词闭集(**按后端族**,不按失败发生在哪一步):
23
22
  * · `local_unwritable` —— **缺省推导**的 local 文件店构造期失败(mkdir / 数据根锁 / 权限 / 只读挂载 / 磁盘满)。
23
+ *
24
+ * 🔴 SQL 族**没有**降级词:配了 DSN 的库当下打不开 ⇒ 窗内重试、到窗拒启(`plugins/store-open-wait.ts` / `openStoreBackendWithFallback`),
25
+ * 从来不退成内存 —— 修前这里有第二个词 `db_unreachable`(`SESSION_BACKEND=auto` 下 SQL 族打不开 ⇒ 内存 + 本位置位),那是 [ref] P 类的静默 fail-open,随降级臂一并退役。
24
26
  */
25
- export declare const STORE_DEGRADED_REASONS: readonly ["db_unreachable", "local_unwritable"];
27
+ export declare const STORE_DEGRADED_REASONS: readonly ["local_unwritable"];
26
28
  export type StoreDegradedReason = (typeof STORE_DEGRADED_REASONS)[number];
27
- /** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。`memory` 从不尝试打开后端,走不到降级臂 —— 到了就是装配缺陷,响亮抛。 */
29
+ /** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。只有缺省推导的 `local` 走得到降级臂;`mysql` / `pg` / `memory` 走到这里 = 装配缺陷,响亮抛。 */
28
30
  export declare function storeDegradedReasonFor(declared: StoreBackendWord): StoreDegradedReason;
31
+ /**
32
+ * `/health.sessionBackend` 与 `listening` 日志 `session` 位的**唯一读法**:装配后的 `config.sessionBackend`(`memory` | `tidb`;`tidb` = 「后端自己的 durable 店」的泛指标签)
33
+ * + 装配出的后端 kind ⇒ 对运维说人话的有效店 —— `durable(pg)` / `durable(mysql)` / `durable(local)` / `memory`。`sessionBackend` 缺 backend 的 `tidb`
34
+ * 不替它编一个后端名(那是装配缺陷,`createSessionStore` 会先拒)。部署侧的 smoke(`deploy/sema-up/lib/health.sh`)按这一形读同一位。
35
+ */
36
+ export declare function sessionStoreLabelOf(sessionBackend: ServiceConfig["sessionBackend"], backendKind: StoreBackend["kind"] | undefined): string;
29
37
  /** 装配值(纯数据;`boot/stores.ts` 铸一次,gauge / `/health` / caps 三面同读)。 */
30
38
  export interface StorePosture {
31
39
  readonly declared: StoreBackendWord;
@@ -1,12 +1,12 @@
1
1
  import { projectSqlEngineCapability } from "./sql-engine-posture.js";
2
- export const STORE_DEGRADED_REASONS = ["db_unreachable", "local_unwritable"];
2
+ export const STORE_DEGRADED_REASONS = ["local_unwritable"];
3
3
  export function storeDegradedReasonFor(declared) {
4
4
  switch (declared) {
5
- case "mysql":
6
- case "pg":
7
- return "db_unreachable";
8
5
  case "local":
9
6
  return "local_unwritable";
7
+ case "mysql":
8
+ case "pg":
9
+ throw new Error(`store posture: DB_BACKEND=${declared} never degrades to memory (a configured SQL store is retried then refused, never replaced) — this is a wiring defect`);
10
10
  case "memory":
11
11
  throw new Error("store posture: DB_BACKEND=memory never opens a backend, so it cannot degrade — this is a wiring defect");
12
12
  default: {
@@ -15,6 +15,9 @@ export function storeDegradedReasonFor(declared) {
15
15
  }
16
16
  }
17
17
  }
18
+ export function sessionStoreLabelOf(sessionBackend, backendKind) {
19
+ return sessionBackend === "tidb" && backendKind !== undefined ? `durable(${backendKind})` : sessionBackend;
20
+ }
18
21
  export function buildStorePosture(a) {
19
22
  return { declared: a.declared, backend: a.assembled ?? "memory", ...(a.degradedReason !== undefined ? { degradedReason: a.degradedReason } : {}) };
20
23
  }
@@ -27,8 +27,8 @@
27
27
  * `APPROVAL_REQUIRE` (an ask rule), `APPROVAL_NEVER_AUTO` (a person, mandated), `MANUAL_MODE_SHELL_GATE=always` (shell).
28
28
  * A request-carried skill's `allowedTools` is the same member's twin (same grammar, same predicate, same admission judge
29
29
  * `rule-entry-list.ts`). Pinned on the real assembly: `test/allow-layer-operator-knobs.test.ts`.
30
- * - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收**五**模式
31
- * (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
30
+ * - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收 core `PERMISSION_MODES` **全集**
31
+ * (default | acceptEdits | plan | auto | bypassPermissions | dontAsk),bypass/auto 被 HONOR 为 gate-shape 选择
32
32
  * ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
33
33
  * ask 照压);精化语义见 :2xx 行内注释。旧头注的「NEVER honored/三模式白名单」是 pre-[ref] 拍照。
34
34
  *
@@ -51,60 +51,32 @@ import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy,
51
51
  import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
52
52
  import { type KeyClosureIssue } from "./request-key-closure.js";
53
53
  /**
54
- * S-902(7.107.0,车QY 候裁 11 裁 A;[ref])—— core 的**每一个**权限模式词,本仓请求面收不收:`"request"` = 受理(顶层 `permissionMode` /
55
- * `settings.permissions.defaultMode` / `?permissionMode=` 查询串三处同读),`"excluded"` = 显式排除(不上请求面)。
56
- *
57
- * 🔴 这张表是**表态**,不是词表的第二份:词与词序只从 core `PERMISSION_MODES` 来(下面 {@link SETTINGS_PERMISSION_MODES} 按它过滤);
58
- * 表只对每个词说一句「收 / 不收」。`satisfies Record<PermissionMode, …>` 对 core 的词型**穷举**:core 加一个词 ⇒ 本表缺键 **编译红**
59
- * (新词必须被表态一次,不会被静默收进请求面、也不会被静默漏掉);core 删 / 改名一个词 ⇒ 本表多键编译红(残条目不留)。
60
- *
61
- * 今天的表态(与 7.106.0 逐字同行为):五词受理;`dontAsk`(CC 的「自动拒」档,core 7.34.0 起第六词)**排除** —— 它上不上请求面是一件
62
- * 新能力(未裁,本表不替它裁);不收它时提交面 400 `request.field_invalid`,续跑重放腿按 {@link coercePermissionMode} 的既有宽读折 `default`。
63
- * 语义沿革(为什么是这五个、`bypassPermissions` / `auto` 何时进来):[ref] / [ref] / [ref] —— CC 自己的全集是六词,CC 托管 / 远端形收四词
64
- * (= 本仓 − `bypassPermissions`),本仓 = {CC 远端四词} ∪ {`bypassPermissions`},登记过的偏离;生效模式原样过境 `TaskSpec.permissionMode`(S-877)。
54
+ * 请求面 `permissionMode` 的闭集 = core `PERMISSION_MODES` **全集**(顶层 `permissionMode` / `settings.permissions.defaultMode` /
55
+ * `?permissionMode=` 查询串三处同读)。本仓不持有第二份词表,也不持有「收 / 不收」的表态:判据 = core 的 `isPermissionMode`,型 = core 的
56
+ * `PermissionMode`,wire 句子里的词表只从这里取词({@link PERMISSION_MODE_WORDS})。core 加一个词 ⇒ 请求面自动收它,它在派生腿上的去向由
57
+ * {@link deriveSettingsPolicy} 的穷举 `switch` 逼出来(缺一臂 ⇒ 编译红);core 删 / 改名一个词 ⇒ 本文件里按名写出它的那一臂编译红。
58
+ * 生效模式原样过境 `TaskSpec.permissionMode`(S-877):引擎自己解释模式 —— 含 `dontAsk`(CC 的「自动拒」档:凡是要问的一律由模式拒,零卡,
59
+ * 门记录 `deniedBy:"mode"`),本仓不另写判。
65
60
  */
66
- export declare const PERMISSION_MODE_REQUEST_FACE: {
67
- readonly default: "request";
68
- readonly acceptEdits: "request";
69
- readonly plan: "request";
70
- readonly auto: "request";
71
- readonly bypassPermissions: "request";
72
- readonly dontAsk: "excluded";
73
- };
74
- /** 请求面受理的模式词的型形 —— 从表态表派生(`"request"` 那几行的键)。 */
75
- export type SettingsPermissionMode = {
76
- [M in PermissionMode]: (typeof PERMISSION_MODE_REQUEST_FACE)[M] extends "request" ? M : never;
77
- }[PermissionMode];
78
- /** 请求面受理的模式词(core 序)= core `PERMISSION_MODES` 过滤表态 `"request"`。wire 门、coerce 两族、两句 400 文案同读这一份。 */
79
- export declare const SETTINGS_PERMISSION_MODES: readonly SettingsPermissionMode[];
80
- /** 显式排除集(core 序)= core `PERMISSION_MODES` 过滤表态 `"excluded"`;今天恰是 `["dontAsk"]`。只作披露 / 钉读,不参与判定。 */
81
- export declare const SETTINGS_PERMISSION_MODES_EXCLUDED: readonly Exclude<PermissionMode, SettingsPermissionMode>[];
82
- /** 五词表的 wire 文案形(`"a" | "b" | …`)—— 两条 400 的句子从这里取词,不再各抄一份。 */
83
- export declare const SETTINGS_PERMISSION_MODE_WORDS: string;
84
- /** 闭集成员判定(`unknown` 入口用)—— 词表的**唯一**运行期读者:wire 门与 coerce 两族同调。 */
85
- export declare function isSettingsPermissionMode(raw: unknown): raw is SettingsPermissionMode;
61
+ /** 请求面闭集的 wire 文案形(`"a" | "b" | …`,core 序)—— 三处 400(顶层 / bundle / 查询串)的句子从这里取词,不再各抄一份。 */
62
+ export declare const PERMISSION_MODE_WORDS: string;
86
63
  /**
87
- * R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends the
88
- * CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
64
+ * R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends
65
+ * the CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
89
66
  * 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
90
67
  * gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
91
- * folds via tightenTaskSpec, deny-wins). Post-[ref] the honored set is the five-word table above (NOT "CC verbatim" —
92
- * see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
93
- * was [ref] P2-1's finding: no CC set has five members). `acceptEdits`,
94
- * `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
95
- * would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
96
- * coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
97
- * UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
98
- * [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-five-mode value
99
- * (server.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
68
+ * folds via tightenTaskSpec, deny-wins). The honored set is core's `PERMISSION_MODES` in full — every word passes
69
+ * through verbatim (`dontAsk` included: folding the auto-deny mode to `default`, the asking mode, would turn a refusal
70
+ * into a card). An UNKNOWN string coerces to `default` (fail-safe: the most-asking mode).
71
+ * [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-member value
72
+ * (admission.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
100
73
  * population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
101
74
  * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
102
75
  */
103
- export declare function coercePermissionMode(raw: unknown): SettingsPermissionMode | undefined;
104
- /** 五模式闭集的**严格**读法([ref] ④:`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,
105
- * 不走上面那条给 resume 重放留的 `default` 宽臂)。上面的宽读只是本函数外再包一层折叠。
106
- * S-496:词表单点已上移到 {@link SETTINGS_PERMISSION_MODES},本函数只是它的 `undefined` 形读者。 */
107
- export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
76
+ export declare function coercePermissionMode(raw: unknown): PermissionMode | undefined;
77
+ /** 闭集的**严格**读法(`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,不走上面那条给 resume 重放留的 `default` 宽臂)。
78
+ * 上面的宽读只是本函数外再包一层折叠;词表只有 core 的 `PERMISSION_MODES`(判据 `isPermissionMode`)。 */
79
+ export declare function coercePermissionModeStrict(raw: unknown): PermissionMode | undefined;
108
80
  /**
109
81
  * S-433 —— 一份**被批准的计划**之后,这条会话继续用的 permission mode(`plan_review` 决裁体的
110
82
  * `permissionModeAfter`)。core 的契约句是 `present-plan-tool.js`:「If the plan is approved, the run
@@ -116,8 +88,8 @@ export declare function coercePermissionModeStrict(raw: unknown): SettingsPermis
116
88
  * edits」/「Yes, and manually approve edits」)的对位;`plan` 自己(= 不松开)由 `edit`/`reject` 两个
117
89
  * 决裁表达,`bypassPermissions` / `auto` 不在这张卡上 —— 一个批准动作不该顺带把部署的 ask 面整个关掉。
118
90
  *
119
- * 🔴 **不手抄词表**:`satisfies readonly SettingsPermissionMode[]` 让「本表的成员必须仍是本仓受理的
120
- * 模式词」成为**编译期**事实 —— 五词表某天改名/删词,这里就是红的,而不是静默留一个没人认的词。
91
+ * 🔴 **不手抄词表**:`satisfies readonly PermissionMode[]` 让「本表的成员必须仍是 core 的模式词」成为**编译期**事实 ——
92
+ * core 某天改名/删词,这里就是红的,而不是静默留一个没人认的词。
121
93
  */
122
94
  export declare const PLAN_APPROVE_PERMISSION_MODES_AFTER: readonly ["default", "acceptEdits"];
123
95
  /** {@link PLAN_APPROVE_PERMISSION_MODES_AFTER} 的类型形(值表单点,类型从值派生 —— 两处各写一份就会漂)。 */
@@ -139,7 +111,7 @@ export declare function isPlanApprovePermissionModeAfter(raw: unknown): raw is P
139
111
  export declare function storedBodyPermissionMode(body: {
140
112
  permissionMode?: unknown;
141
113
  settings?: unknown;
142
- }): SettingsPermissionMode | undefined;
114
+ }): PermissionMode | undefined;
143
115
  /** L2 ultracode ([ref]): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
144
116
  * preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
145
117
  * (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
@@ -148,7 +120,7 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
148
120
  /** R4: fold a top-level `body.permissionMode` onto the parsed settings as `permissions.defaultMode` (the top-level
149
121
  * field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
150
122
  * no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
151
- export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: SettingsPermissionMode): ParsedTaskSettings;
123
+ export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: PermissionMode): ParsedTaskSettings;
152
124
  /**
153
125
  * [ref] §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
154
126
  * 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
@@ -161,7 +133,7 @@ export declare function withPermissionMode(settings: ParsedTaskSettings | undefi
161
133
  */
162
134
  export declare function effectivePermissionMode(body: {
163
135
  permissionMode?: unknown;
164
- }, settings: ParsedTaskSettings | undefined): SettingsPermissionMode | undefined;
136
+ }, settings: ParsedTaskSettings | undefined): PermissionMode | undefined;
165
137
  /**
166
138
  * S-346 —— [ref] 技能清单预算两旋钮的**值域谓词**(一对具名单铸点)。
167
139
  *
@@ -187,7 +159,7 @@ export interface ParsedTaskSettings {
187
159
  deny?: string[];
188
160
  /** Carried for completeness; v1 folds `ask` into the gate via the deny-wins base (see {@link applyTaskSettings}). */
189
161
  ask?: string[];
190
- defaultMode?: SettingsPermissionMode;
162
+ defaultMode?: PermissionMode;
191
163
  /** [ref] 件⑥([ref] §3.4,[ref];PM 裁 [ref]):CC settings 的 kill-switch `permissions.disableAutoMode`,cli 投影
192
164
  * (settingsRulesWire.ts toWireSettings)而此前本白名单丢弃 ⇒ 用户/managed 层关 auto 到不了引擎。语义 = 只能关
193
165
  * 不能开(tighten-only):把生效模式 auto 折 default(座不写 ⇒ 不武装)。折叠单点 = {@link effectivePermissionMode}。
@@ -1,25 +1,13 @@
1
- import { combinePolicies, createFsWriteGatePolicy, parseRuleText, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, PERMISSION_MODES, ruleToolGrammarOf, } from "@sema-agent/core";
1
+ import { combinePolicies, createFsWriteGatePolicy, parseRuleText, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, PERMISSION_MODES, isPermissionMode, ruleToolGrammarOf, } from "@sema-agent/core";
2
2
  import { unmatchableToolNamesRefusal } from "./config.js";
3
3
  import { MAX_PERMISSION_RULES_PER_LIST, createRuleListPolicy } from "./permission-rule-list.js";
4
- import { allowLayerEntryCountIssue, describeAllowLayerOverCap, ruleEntryListShapeOk } from "./rule-entry-list.js";
4
+ import { allowLayerEntryCountIssue, describeAllowLayerOverCap, describeEmptyToolSegmentEntry, emptyToolSegmentEntryOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
5
5
  import { tightenGoverned } from "./governance-overrides.js";
6
6
  import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-closure.js";
7
7
  import { parseHooksConfig } from "./hooks/hook-runner.js";
8
8
  import { heldRuleName } from "./tool-rule-names.js";
9
- export const PERMISSION_MODE_REQUEST_FACE = {
10
- default: "request",
11
- acceptEdits: "request",
12
- plan: "request",
13
- auto: "request",
14
- bypassPermissions: "request",
15
- dontAsk: "excluded",
16
- };
17
- export const SETTINGS_PERMISSION_MODES = PERMISSION_MODES.filter((m) => PERMISSION_MODE_REQUEST_FACE[m] === "request");
18
- export const SETTINGS_PERMISSION_MODES_EXCLUDED = PERMISSION_MODES.filter((m) => PERMISSION_MODE_REQUEST_FACE[m] === "excluded");
19
- export const SETTINGS_PERMISSION_MODE_WORDS = SETTINGS_PERMISSION_MODES.map((m) => `"${m}"`).join(" | ");
20
- export function isSettingsPermissionMode(raw) {
21
- return typeof raw === "string" && SETTINGS_PERMISSION_MODES.includes(raw);
22
- }
9
+ import { policyAskStandingAnswerFacts, standingAnswerDurablyRecordable } from "./tool-approval.js";
10
+ export const PERMISSION_MODE_WORDS = PERMISSION_MODES.map((m) => `"${m}"`).join(" | ");
23
11
  export function coercePermissionMode(raw) {
24
12
  const strict = coercePermissionModeStrict(raw);
25
13
  if (strict !== undefined)
@@ -29,7 +17,7 @@ export function coercePermissionMode(raw) {
29
17
  return undefined;
30
18
  }
31
19
  export function coercePermissionModeStrict(raw) {
32
- return isSettingsPermissionMode(raw) ? raw : undefined;
20
+ return isPermissionMode(raw) ? raw : undefined;
33
21
  }
34
22
  export const PLAN_APPROVE_PERMISSION_MODES_AFTER = ["default", "acceptEdits"];
35
23
  export function isPlanApprovePermissionModeAfter(raw) {
@@ -304,7 +292,8 @@ function createWorkflowAskPolicy(isExempt) {
304
292
  async check(req) {
305
293
  if (heldRuleName(req, isWorkflowName) === undefined)
306
294
  return { action: "allow" };
307
- if (isExempt) {
295
+ const ask = { action: "ask", message: `dynamic workflow "${req.toolName}" requires approval in this permission mode`, decisionReason: "rule" };
296
+ if (isExempt && standingAnswerDurablyRecordable(policyAskStandingAnswerFacts(req, ask, false))) {
308
297
  try {
309
298
  if (await isExempt(req.toolName))
310
299
  return { action: "allow", decisionReason: "rule" };
@@ -312,7 +301,7 @@ function createWorkflowAskPolicy(isExempt) {
312
301
  catch {
313
302
  }
314
303
  }
315
- return { action: "ask", message: `dynamic workflow "${req.toolName}" requires approval in this permission mode`, decisionReason: "rule" };
304
+ return ask;
316
305
  },
317
306
  nameSets: [{ ask: WORKFLOW_ASK_NAMES }],
318
307
  };
@@ -346,7 +335,6 @@ function rejectUnmatchableSettingsNames(perms, pathBases) {
346
335
  const judgments = [
347
336
  ["permissions.ask", perms.ask, { consumer: "rule-policy", behavior: "ask", ...(pathBases !== undefined ? { pathBases } : {}) }],
348
337
  ["permissions.deny", perms.deny, { consumer: "rule-policy", behavior: "deny", ...(pathBases !== undefined ? { pathBases } : {}) }],
349
- ["permissions.allow", perms.allow, { consumer: "allow-layer" }],
350
338
  ];
351
339
  for (const [field, names, judgment] of judgments) {
352
340
  if (!names)
@@ -355,6 +343,9 @@ function rejectUnmatchableSettingsNames(perms, pathBases) {
355
343
  if (unmatchable !== undefined)
356
344
  throw new Error(unmatchable.message);
357
345
  }
346
+ const emptyAllow = perms.allow !== undefined ? emptyToolSegmentEntryOf(perms.allow) : undefined;
347
+ if (emptyAllow !== undefined)
348
+ throw new Error(describeEmptyToolSegmentEntry("permissions.allow", emptyAllow));
358
349
  }
359
350
  export function deriveSettingsPolicy(settings, gate, workflowGate) {
360
351
  const perms = settings.permissions;
@@ -396,7 +387,8 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
396
387
  return p ? { toolPolicy: p } : {};
397
388
  }
398
389
  case "default":
399
- case "auto": {
390
+ case "auto":
391
+ case "dontAsk": {
400
392
  const p = compose(createWorkflowAskPolicy(workflowGate?.isExempt));
401
393
  return p ? { toolPolicy: p } : {};
402
394
  }