@sema-agent/server 7.131.0 → 7.132.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 (256) hide show
  1. package/MIGRATION.md +309 -0
  2. package/README.md +5 -1
  3. package/README.zh-CN.md +3 -1
  4. package/USAGE.md +163 -54
  5. package/dist/approval-card.d.ts +3 -1
  6. package/dist/approval-card.js +2 -3
  7. package/dist/audit.d.ts +7 -10
  8. package/dist/auth-bridge.d.ts +4 -0
  9. package/dist/auth-bridge.js +10 -11
  10. package/dist/boot/deployment-mods.d.ts +31 -0
  11. package/dist/boot/deployment-mods.js +26 -0
  12. package/dist/boot/execution-env.d.ts +1 -1
  13. package/dist/boot/execution-env.js +19 -6
  14. package/dist/boot/memory-boundary.d.ts +2 -2
  15. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  16. package/dist/boot/read-deny-anchor-audit.js +36 -0
  17. package/dist/boot/resolve-spec.d.ts +1 -5
  18. package/dist/boot/resolve-spec.js +19 -47
  19. package/dist/boot/runner-deps.d.ts +19 -8
  20. package/dist/boot/runner-deps.js +14 -2
  21. package/dist/boot/session-faces.d.ts +1 -7
  22. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  23. package/dist/boot/stage-03-store-backend.js +2 -2
  24. package/dist/boot/stage-04-stores.d.ts +1 -1
  25. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  26. package/dist/boot/stage-05-execution-env.js +7 -10
  27. package/dist/boot/stage-06-runners.d.ts +2 -2
  28. package/dist/boot/stage-06-runners.js +5 -4
  29. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  30. package/dist/boot/stage-07-capability-layer.js +3 -3
  31. package/dist/boot/stage-08-reapers.d.ts +2 -2
  32. package/dist/boot/stage-09-leader.d.ts +3 -9
  33. package/dist/boot/stage-10-http-server.d.ts +3 -9
  34. package/dist/boot/stage-10-http-server.js +6 -7
  35. package/dist/boot/stores.d.ts +1 -1
  36. package/dist/boot/stores.js +5 -6
  37. package/dist/boot/web-admin.d.ts +0 -3
  38. package/dist/boot/web-admin.js +35 -35
  39. package/dist/brain.js +17 -22
  40. package/dist/budget.js +5 -4
  41. package/dist/caller-text-echo.d.ts +20 -0
  42. package/dist/caller-text-echo.js +24 -0
  43. package/dist/capabilities/brand-identity.d.ts +26 -0
  44. package/dist/capabilities/brand-identity.js +27 -0
  45. package/dist/capabilities/center-plugins.js +1 -1
  46. package/dist/capabilities/center-prompts.d.ts +32 -59
  47. package/dist/capabilities/center-prompts.js +18 -32
  48. package/dist/capabilities/engine-capabilities.js +2 -1
  49. package/dist/capabilities/hands-lane.d.ts +9 -6
  50. package/dist/capabilities/hands-lane.js +1 -1
  51. package/dist/capabilities/mcp-probe.d.ts +14 -1
  52. package/dist/capabilities/mcp-probe.js +22 -133
  53. package/dist/capabilities/scenarios.d.ts +34 -40
  54. package/dist/capabilities/scenarios.js +16 -33
  55. package/dist/capabilities/skills.d.ts +19 -4
  56. package/dist/capabilities/skills.js +30 -10
  57. package/dist/config-catalog.js +6 -6
  58. package/dist/config-lkg.d.ts +9 -9
  59. package/dist/config-lkg.js +20 -4
  60. package/dist/config-provider.d.ts +3 -1
  61. package/dist/config-provider.js +12 -4
  62. package/dist/config-types.d.ts +7 -9
  63. package/dist/config.js +17 -4
  64. package/dist/device-ws-protocol.d.ts +1 -1
  65. package/dist/device-ws-protocol.js +1 -0
  66. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  67. package/dist/env-name-allowlist-knobs.js +5 -4
  68. package/dist/exec-bounds.d.ts +15 -3
  69. package/dist/exec-bounds.js +4 -0
  70. package/dist/execution-lane-caps.d.ts +9 -2
  71. package/dist/execution-lane-caps.js +11 -0
  72. package/dist/hooks/classic-stations.d.ts +86 -0
  73. package/dist/hooks/classic-stations.js +27 -0
  74. package/dist/hooks/hook-llm.d.ts +8 -5
  75. package/dist/hooks/hook-llm.js +78 -35
  76. package/dist/hooks/hook-runner.d.ts +232 -80
  77. package/dist/hooks/hook-runner.js +218 -312
  78. package/dist/http/admission.d.ts +8 -2
  79. package/dist/http/admission.js +101 -57
  80. package/dist/http/approval-sweeps.js +57 -16
  81. package/dist/http/checkpoint-ctx.d.ts +25 -8
  82. package/dist/http/checkpoint-ctx.js +11 -7
  83. package/dist/http/claimed-run.d.ts +13 -0
  84. package/dist/http/claimed-run.js +15 -0
  85. package/dist/http/device-placement.js +5 -10
  86. package/dist/http/dispatch.d.ts +0 -4
  87. package/dist/http/dispatch.js +5 -9
  88. package/dist/http/durable-run-leg.d.ts +2 -1
  89. package/dist/http/durable-run-leg.js +5 -3
  90. package/dist/http/health-self-report.d.ts +7 -5
  91. package/dist/http/health-self-report.js +4 -18
  92. package/dist/http/principal-gate.d.ts +23 -6
  93. package/dist/http/principal-gate.js +24 -5
  94. package/dist/http/refusal-body.d.ts +21 -0
  95. package/dist/http/refusal-body.js +4 -0
  96. package/dist/http/request-guards.d.ts +27 -0
  97. package/dist/http/request-guards.js +24 -3
  98. package/dist/http/resume-legs.d.ts +25 -0
  99. package/dist/http/resume-legs.js +43 -22
  100. package/dist/http/routes/capabilities.js +1 -1
  101. package/dist/http/routes/diagnostics.js +1 -1
  102. package/dist/http/routes/rules.js +2 -2
  103. package/dist/http/routes/runs.js +7 -7
  104. package/dist/http/routes/session-sync.js +4 -2
  105. package/dist/http/routes/sessions.js +1 -1
  106. package/dist/http/routes/shared-memory.js +2 -13
  107. package/dist/http/routes/side-query.js +2 -3
  108. package/dist/http/routes/tasks.js +19 -9
  109. package/dist/http/send.d.ts +9 -7
  110. package/dist/http/send.js +7 -2
  111. package/dist/http/server.d.ts +21 -11
  112. package/dist/http/server.js +28 -2
  113. package/dist/http/submit-ctx.d.ts +25 -0
  114. package/dist/http/submit-ctx.js +39 -0
  115. package/dist/http/wire-types.d.ts +1 -1
  116. package/dist/index.d.ts +1 -1
  117. package/dist/index.js +1 -1
  118. package/dist/lane-home-probe.d.ts +43 -0
  119. package/dist/lane-home-probe.js +35 -0
  120. package/dist/limit-refusal.d.ts +47 -0
  121. package/dist/limit-refusal.js +12 -0
  122. package/dist/main.js +1 -1
  123. package/dist/mcp-assertion-header.d.ts +1 -9
  124. package/dist/mcp-assertion-header.js +0 -6
  125. package/dist/mcp-tool-faces.d.ts +5 -0
  126. package/dist/mcp-tool-faces.js +5 -1
  127. package/dist/memory-scope.d.ts +1 -1
  128. package/dist/model-provider.d.ts +39 -12
  129. package/dist/model-provider.js +19 -2
  130. package/dist/model-route-endpoint.d.ts +1 -1
  131. package/dist/model-route-endpoint.js +5 -5
  132. package/dist/observability/cost-taxonomy.d.ts +7 -7
  133. package/dist/observability/fail-open.d.ts +42 -6
  134. package/dist/observability/fail-open.js +42 -6
  135. package/dist/observability/metrics.js +3 -1
  136. package/dist/observability/prompt-manifest.d.ts +47 -82
  137. package/dist/observability/prompt-manifest.js +119 -17
  138. package/dist/observability/run-terminal.js +14 -6
  139. package/dist/observability/secret-env-scrub.d.ts +13 -1
  140. package/dist/observability/secret-env-scrub.js +13 -1
  141. package/dist/observability/tool-trace.d.ts +16 -21
  142. package/dist/observability/tool-trace.js +51 -42
  143. package/dist/per-task-image.d.ts +7 -1
  144. package/dist/per-task-image.js +3 -3
  145. package/dist/plain-string-record.d.ts +25 -0
  146. package/dist/plain-string-record.js +32 -0
  147. package/dist/plugins/blob-backend.d.ts +68 -13
  148. package/dist/plugins/blob-backend.js +53 -10
  149. package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
  150. package/dist/plugins/checkpoint-store-sql.js +39 -26
  151. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  152. package/dist/plugins/k8s-bg-scripts.js +22 -0
  153. package/dist/plugins/local-checkpoint-store.js +5 -2
  154. package/dist/plugins/local-session-store.js +1 -1
  155. package/dist/plugins/one-time-migrations.d.ts +137 -90
  156. package/dist/plugins/one-time-migrations.js +356 -207
  157. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  158. package/dist/plugins/permission-rule-store-sql.js +4 -18
  159. package/dist/plugins/pg-session-storage.d.ts +4 -5
  160. package/dist/plugins/pg-session-storage.js +7 -17
  161. package/dist/plugins/remote-env-adb.d.ts +6 -0
  162. package/dist/plugins/remote-env-adb.js +9 -4
  163. package/dist/plugins/remote-env-device.js +3 -0
  164. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  165. package/dist/plugins/remote-env-e2b.js +21 -9
  166. package/dist/plugins/remote-env-host.d.ts +9 -9
  167. package/dist/plugins/remote-env-host.js +10 -32
  168. package/dist/plugins/remote-env-k8s.d.ts +95 -26
  169. package/dist/plugins/remote-env-k8s.js +265 -90
  170. package/dist/plugins/remote-env-local-docker.d.ts +20 -1
  171. package/dist/plugins/remote-env-local-docker.js +77 -9
  172. package/dist/plugins/remote-env-ssh.d.ts +15 -0
  173. package/dist/plugins/remote-env-ssh.js +109 -3
  174. package/dist/plugins/remote-shell.d.ts +0 -1
  175. package/dist/plugins/remote-shell.js +1 -2
  176. package/dist/plugins/retention-store-sql.js +6 -2
  177. package/dist/plugins/schema-consistency.d.ts +54 -0
  178. package/dist/plugins/schema-consistency.js +52 -0
  179. package/dist/plugins/session-epoch-anchor.d.ts +13 -0
  180. package/dist/plugins/session-epoch-anchor.js +23 -0
  181. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  182. package/dist/plugins/shared-memory-store-sql.js +5 -16
  183. package/dist/plugins/sql-driver.d.ts +141 -0
  184. package/dist/plugins/sql-driver.js +115 -1
  185. package/dist/plugins/sql-errors.d.ts +41 -0
  186. package/dist/plugins/sql-errors.js +36 -0
  187. package/dist/plugins/store-backend.js +2 -1
  188. package/dist/plugins/tidb-session-storage.d.ts +7 -11
  189. package/dist/plugins/tidb-session-storage.js +6 -16
  190. package/dist/plugins/tidb-session-store.js +1 -1
  191. package/dist/prompts-domain-validate.d.ts +17 -61
  192. package/dist/prompts-domain-validate.js +9 -119
  193. package/dist/request-key-closure.d.ts +2 -2
  194. package/dist/request-key-closure.js +2 -4
  195. package/dist/rule-entry-list.d.ts +12 -9
  196. package/dist/rule-entry-list.js +37 -21
  197. package/dist/run-local.js +19 -19
  198. package/dist/runs.d.ts +6 -0
  199. package/dist/runs.js +16 -0
  200. package/dist/server-secret-env.d.ts +44 -18
  201. package/dist/server-secret-env.js +49 -8
  202. package/dist/session-incarnation.d.ts +15 -5
  203. package/dist/session-incarnation.js +16 -9
  204. package/dist/session-titler.js +12 -2
  205. package/dist/spec-fields.d.ts +28 -16
  206. package/dist/spec-fields.js +46 -53
  207. package/dist/sql-engine-posture.d.ts +29 -4
  208. package/dist/sql-engine-posture.js +36 -1
  209. package/dist/stored-request-tool-axes-m19.d.ts +19 -4
  210. package/dist/stored-request-tool-axes-m19.js +30 -2
  211. package/dist/task-a2a.d.ts +9 -1
  212. package/dist/task-a2a.js +73 -47
  213. package/dist/task-mcp.d.ts +46 -58
  214. package/dist/task-mcp.js +99 -65
  215. package/dist/task-settings.d.ts +7 -21
  216. package/dist/task-settings.js +7 -36
  217. package/dist/text-bidi.d.ts +3 -46
  218. package/dist/text-bidi.js +3 -3
  219. package/dist/tool-approval.d.ts +1 -1
  220. package/dist/trace/core-keyset-guard.d.ts +24 -4
  221. package/dist/trace/engine-notice-wire.d.ts +11 -3
  222. package/dist/trace/engine-notice-wire.js +8 -1
  223. package/dist/trace/ledger-events.d.ts +3 -1
  224. package/dist/trace/ledger-sink.js +4 -1
  225. package/dist/trace/model-segment.js +1 -1
  226. package/dist/trace/project.d.ts +19 -6
  227. package/dist/trace/project.js +57 -22
  228. package/dist/trace/wire-projection-faces.d.ts +79 -1
  229. package/dist/trace/wire-projection-faces.js +22 -0
  230. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  231. package/dist/web-admin-client/candidate-gate.js +3 -3
  232. package/dist/web-admin-client/http-client.js +0 -6
  233. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  234. package/dist/web-admin-client/read-warnings.js +10 -0
  235. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  236. package/dist/web-admin-client/skills-mcp.js +9 -6
  237. package/dist/web-admin-client/types.d.ts +2 -3
  238. package/dist/workspace-cache.d.ts +35 -0
  239. package/dist/workspace-cache.js +36 -0
  240. package/dist/workspace-space.d.ts +3 -2
  241. package/dist/workspace-space.js +2 -0
  242. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
  243. package/package.json +4 -4
  244. package/scripts/drain-check-local.mjs +34 -1
  245. package/scripts/drain-check.mysql.sql +30 -2
  246. package/scripts/drain-check.pg.sql +29 -2
  247. package/dist/capabilities/memory-notice.d.ts +0 -138
  248. package/dist/capabilities/memory-notice.js +0 -64
  249. package/dist/capabilities/prompt.d.ts +0 -17
  250. package/dist/capabilities/prompt.js +0 -6
  251. package/dist/mcp-content-origin.d.ts +0 -34
  252. package/dist/mcp-content-origin.js +0 -10
  253. package/dist/tool-axis-words.d.ts +0 -27
  254. package/dist/tool-axis-words.js +0 -9
  255. package/dist/wire-name-bound.d.ts +0 -14
  256. package/dist/wire-name-bound.js +0 -13
@@ -1,122 +1,12 @@
1
- import { createHash } from "node:crypto";
2
- import { createRequire } from "node:module";
3
- import { readFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
1
  import { verifyPromptArtifact } from "@sema-agent/core";
6
- import { PROMPT_SLOTS } from "@sema-agent/settings-schema";
7
- import { SHA256_DIGEST_RE } from "./digest-form.js";
8
- const ID_RE = /^[a-z0-9][a-z0-9/._-]*$/;
9
- const SLOTS = PROMPT_SLOTS;
10
- function declarationIssue(d, where) {
11
- if (d === null || typeof d !== "object")
12
- return `${where}: declaration is not an object`;
13
- const o = d;
14
- if (typeof o.id !== "string" || o.id.length === 0 || o.id.length > 128 || !ID_RE.test(o.id))
15
- return `${where}: bad id`;
16
- if (o.id.startsWith("core/"))
17
- return `${where}: id in the reserved core/ namespace`;
18
- if (typeof o.slot !== "string" || !SLOTS.includes(o.slot))
19
- return `${where}: bad slot`;
20
- if (typeof o.text !== "string" || o.text.length === 0)
21
- return `${where}: text must be a non-empty string`;
22
- if (typeof o.contentHash !== "string" || !SHA256_DIGEST_RE.test(o.contentHash))
23
- return `${where}: contentHash is required (sha256:<64hex> — the epoch declaration axis needs it for byte-level change detection)`;
24
- const expected = `sha256:${createHash("sha256").update(o.text).digest("hex")}`;
25
- if (o.contentHash !== expected)
26
- return `${where}: contentHash does not match sha256(text) — stale or altered declaration`;
27
- return undefined;
28
- }
29
- export function validateCenterPrompts(raw) {
30
- if (raw === null || typeof raw !== "object")
31
- return { ok: false, error: "prompts is not an object" };
32
- const p = raw;
33
- if (typeof p.packId !== "string" || !/^center:[0-9a-f]{12}$/.test(p.packId))
34
- return { ok: false, error: "bad packId form (center:<digest12>)" };
35
- if (typeof p.contentDigest !== "string" || !SHA256_DIGEST_RE.test(p.contentDigest))
36
- return { ok: false, error: "bad contentDigest form" };
37
- const arrayIssue = (decls, where) => {
38
- if (!Array.isArray(decls))
39
- return `${where} is not an array`;
40
- if (decls.length === 0)
41
- return `${where} is empty (an empty replacement set is not a v1 semantic — omit the key instead)`;
42
- const seen = new Set();
43
- for (let i = 0; i < decls.length; i++) {
44
- const issue = declarationIssue(decls[i], `${where}[${i}]`);
45
- if (issue)
46
- return issue;
47
- const id = decls[i].id;
48
- if (seen.has(id))
49
- return `${where}[${i}]: duplicate id "${id}" (core composer throws on duplicates)`;
50
- seen.add(id);
51
- }
52
- return undefined;
53
- };
54
- const sectionsIssue = arrayIssue(p.sections, "sections");
55
- if (sectionsIssue)
56
- return { ok: false, error: sectionsIssue };
57
- if (p.scenarioOverrides !== undefined) {
58
- if (p.scenarioOverrides === null || typeof p.scenarioOverrides !== "object" || Array.isArray(p.scenarioOverrides)) {
59
- return { ok: false, error: "scenarioOverrides is not an object" };
60
- }
61
- for (const [name, decls] of Object.entries(p.scenarioOverrides)) {
62
- const issue = arrayIssue(decls, `scenarioOverrides[${name}]`);
63
- if (issue)
64
- return { ok: false, error: issue };
65
- }
66
- }
67
- return { ok: true, value: raw };
68
- }
69
- export const CORE_ENGINE_VERSION = (() => {
70
- try {
71
- const entry = createRequire(import.meta.url).resolve("@sema-agent/core");
72
- return JSON.parse(readFileSync(join(dirname(entry), "..", "package.json"), "utf8")).version ?? "0.0.0";
73
- }
74
- catch {
75
- return "0.0.0";
76
- }
77
- })();
78
- export function validatePromptsDomain(raw) {
79
- if (raw === null || typeof raw !== "object")
80
- return { ok: false, error: "prompts is not an object" };
81
- const w = raw;
82
- if (w.schemaVersion === undefined) {
83
- return { ok: false, error: "prompts payload has no schemaVersion — the legacy flat shape was retired in 5.0.0; wrap as {schemaVersion:1, declarations:{...}} (registry-core EffectivePromptsWire)" };
84
- }
85
- if (w.schemaVersion !== 1)
86
- return { ok: false, error: `unsupported prompts schemaVersion ${String(w.schemaVersion)}` };
87
- if (w.declarations === undefined && w.catalog === undefined)
88
- return { ok: false, error: "dual-axis prompts carries neither axis (at least one of declarations|catalog required)" };
89
- const value = { identity: "", axes: [] };
90
- if (w.declarations !== undefined) {
91
- const d = validateCenterPrompts(w.declarations);
92
- if (!d.ok)
93
- return { ok: false, error: `declarations axis: ${d.error}` };
94
- value.declarations = d.value;
95
- value.axes.push("declarations");
96
- }
97
- if (w.catalog !== undefined) {
98
- const c = verifyPromptArtifact(w.catalog, { engineVersion: CORE_ENGINE_VERSION });
99
- if (!c.ok)
100
- return { ok: false, error: `catalog axis: ${c.code}: ${c.reason}` };
101
- value.catalog = w.catalog;
102
- value.axes.push("catalog");
103
- }
104
- value.identity = `${value.declarations?.contentDigest ?? "-"}+${value.catalog?.artifact.artifactDigest ?? "-"}`;
105
- return { ok: true, value };
106
- }
107
- export function centerPromptsFromEffective(eff) {
108
- const prompts = eff?.prompts;
109
- if (prompts === undefined || prompts === null)
110
- return undefined;
111
- if (typeof prompts !== "object" || Array.isArray(prompts))
112
- return prompts;
113
- const o = prompts;
114
- if ("schemaVersion" in o)
115
- return prompts;
116
- if ("packId" in o && "contentDigest" in o && "sections" in o)
117
- return prompts;
118
- if (o.catalog !== undefined)
119
- return { schemaVersion: 1, catalog: o.catalog };
120
- return undefined;
2
+ import { EffectivePromptsWire, formatConfigPath, sanitizeZodError } from "@sema-agent/settings-schema";
3
+ export function judgePromptsWire(raw) {
4
+ const wire = EffectivePromptsWire.safeParse(raw);
5
+ if (!wire.success) {
6
+ const issues = sanitizeZodError(wire.error).issues.map((i) => `${i.path.length === 0 ? "prompts" : `prompts.${formatConfigPath(i.path.map((s) => (typeof s === "number" ? s : String(s))))}`}: ${i.message}`);
7
+ return { ok: false, error: issues.join("; ") };
8
+ }
9
+ const verified = verifyPromptArtifact(wire.data.artifact);
10
+ return verified.ok ? { ok: true, artifact: verified.artifact } : { ok: false, error: `prompts.artifact refused by the engine (${verified.code}): ${verified.reason}` };
121
11
  }
122
12
  //# sourceMappingURL=prompts-domain-validate.js.map
@@ -1,8 +1,8 @@
1
1
  /** 拒体里逐字回显的键名条数上限。键名原样回显(不猜语义),但**条数有界** —— 一个塞满键的体不许把 400 响应
2
2
  * 放大成流量面。超出部分只报数。 */
3
3
  export declare const MAX_KEY_ECHO = 20;
4
- /** 键名回显的清洗:剥控制/双向字符(键名进的是 JSON 错误体与运维日志,一个 RLO 能把两行字读反),
5
- * 再按 {@link MAX_KEY_ECHO_CHARS} 截断。清洗之外**逐字**,不做大小写/近似猜测。 */
4
+ /** 键名回显 = 前缀 + 调用方串的唯一回显口径(`caller-text-echo.ts`:剥控制 / 双向字符、截到 64 字 + …;键名进的是 JSON 错误体与运维日志,
5
+ * 一个 RLO 能把两行字读反)。清洗之外**逐字**,不做大小写/近似猜测。 */
6
6
  export declare function echoKeyName(prefix: string, key: string): string;
7
7
  /** 一次键闭集裁决的产物:一句 wire 文案 + 两张分类键名表(消费端可机读,不必解析文案)。 */
8
8
  export interface KeyClosureIssue {
@@ -1,9 +1,7 @@
1
- import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
1
+ import { echoCallerText } from "./caller-text-echo.js";
2
2
  export const MAX_KEY_ECHO = 20;
3
- const MAX_KEY_ECHO_CHARS = 64;
4
3
  export function echoKeyName(prefix, key) {
5
- const clean = key.replace(CONTROL_AND_BIDI_STRIP_RE, "");
6
- return `${prefix}${clean.length > MAX_KEY_ECHO_CHARS ? `${clean.slice(0, MAX_KEY_ECHO_CHARS)}…` : clean}`;
4
+ return `${prefix}${echoCallerText(key)}`;
7
5
  }
8
6
  export function scanKeyClosure(bag, prefix, accepted, tolerated, into) {
9
7
  for (const k of Object.keys(bag)) {
@@ -17,7 +17,7 @@
17
17
  * 运维 / 中心的审批名单读同一份,没有「这一处空名册、那一处带名册」的两种读法。
18
18
  * **读成空的哪些原因词是拒面,按方向定**:**限制侧**(读成空 = 静默放行:技能 `disallowedTools`、`excludeTools` / 子代 `denyTools`、`settings.permissions.deny` / `ask`、运维 / 中心的审批名单)四类全拒 ——
19
19
  * 近似名 · 退役名 · 文法读不出 · 不带内容形的工具带内容;**放行侧**(读成空 = 想放的什么也没放:`settings.permissions.allow`、根 / 子代 `allowTools`、技能 `allowedTools`)**只拒近似名**
20
- * ({@link allowSideReadsAsNothing}):近似名现名唯一、确定是笔误;退役名 / 内容形 / 文法读不出 / 协议名对端段通配照 7.130.0 受理、交引擎既有通告(引擎侧的终形是把退役名归一到现名并在用户面点名,到货前放行侧不拒 ——
20
+ * ({@link allowSideReadsAsNothing}):近似名现名唯一、确定是笔误;退役名 / 内容形 / 文法读不出 / 协议名对端段通配照 7.130.0 受理、交引擎既有通告(其中选择器清单 `allowTools` 里**有后继**的退役名〔`Task` ⇒ `Agent`、`bash` ⇒ `Bash`〕core 7.52.0 起由引擎开跑前拒,不在本仓拒面;规则表上的退役名引擎照旧告运维 ——
21
21
  * 壳转投的 CC 用户 settings 里这些极常见,尤其内容形里的 `$VAR`(`Bash(echo $HOME)`)读成 `grammar`,拒了 = 每次提交 400)。运维的 `APPROVAL_GRANT` 是运维自己的配置:任何读成空都拒启(写错了当场改)。
22
22
  * 技能拒侧那一拒与引擎 #1372(core 7.43.0:那只技能不激活)是同一件事的两处 —— 7.118.0 重估过、本版**不退**:本仓的续跑腿把引擎的
23
23
  * `checkpoint.config_refused`(`config.skill_deny_unreadable`)答得不一(资源停车续跑腿 500、`/decide` 先结了卡、`/wake` 先铸了 run 行),先统一那一层再退
@@ -29,6 +29,7 @@
29
29
  * `*` 在放行侧是「本层不设名单」、在限制侧读成空;限制侧 `mcp__*` / `a2a__*`(整个协议的拒)合法。
30
30
  */
31
31
  import { compileAllowList } from "@sema-agent/core";
32
+ import { type LimitRefusal } from "./limit-refusal.js";
32
33
  /** ① 形:字符串数组(空串也是字符串 —— 引擎把它读成 `grammar` 并通告 / 拒,见 ③)。 */
33
34
  export declare const ruleEntryListShapeOk: (v: unknown) => v is readonly string[];
34
35
  /**
@@ -42,14 +43,10 @@ export declare const ruleEntryListShapeOk: (v: unknown) => v is readonly string[
42
43
  * 没有真实用法需要上千条」。抬不抬这个数是产品判断,不随提货改。
43
44
  */
44
45
  export declare const MAX_ALLOW_LAYER_ENTRIES = 1000;
45
- /** 超帽的拒句(不截:截掉的尾项读起来像「在生效」)—— 句子的唯一铸点(请求 allow 桶的 wire 拒体按字段重铸也调它)。 */
46
+ /** 超帽的拒句({@link allowLayerOverCap} 的句子;请求 allow 桶的拒句按字段重铸时读它)。 */
46
47
  export declare function describeAllowLayerOverCap(field: string, count: number): string;
47
- /** 一张条目表超了 {@link MAX_ALLOW_LAYER_ENTRIES} ⇒ 拒句 + 机读两数;没超 ⇒ `undefined`。四张表同调这一只。 */
48
- export declare function allowLayerEntryCountIssue(field: string, list: readonly unknown[]): {
49
- readonly message: string;
50
- readonly count: number;
51
- readonly cap: number;
52
- } | undefined;
48
+ /** 一张条目表超了 {@link MAX_ALLOW_LAYER_ENTRIES} ⇒ 拒(句子 + 机读位 `field` / `reason: "over_cap"` / `count` / `cap`);没超 ⇒ `undefined`。四张表同调这一只。 */
49
+ export declare function allowLayerEntryCountIssue(field: string, list: readonly unknown[]): LimitRefusal | undefined;
53
50
  /** 一条引擎读成空的条目(引擎的形:原因词,名字层另带判词 / 1:1 现名;包根不导出这只具名型 ⇒ 从 `compileAllowList` 的返回型取,不手抄)。 */
54
51
  export type InertRuleEntry = ReturnType<typeof compileAllowList>["inert"][number];
55
52
  /**
@@ -150,6 +147,12 @@ export declare function replayInertEntry(layer: ToolListLayer | RulePolicyLayer,
150
147
  export interface SkillDenyInertEntry extends InertToolListEntry {
151
148
  readonly layer: SkillListLayer;
152
149
  }
150
+ /**
151
+ * 一条技能(顶层 `skills[k]` / 子代 `agents[i].skills[j]`,`at` = 它的体内坐标)两只清单的**条数帽**:先 `allowedTools` 后 `disallowedTools`(与 7.131.0 的判序同)。
152
+ * 判与句子就是 {@link overCapOfList} 那一只(拒点 `resolveSpec` 两腿同经的 {@link inertToolListEntryOf} 先过的也是它):受理口在技能的两道形门处调它
153
+ * (`http/admission.ts` 的 `validateUserSkills` 与 `spec-fields.ts` 的 `validateTaskAgents`:两道门都逐条先形、后帽 —— 与 7.131.0 的拒点与判序同,码与先后不变),句子与机读位点体内坐标。
154
+ */
155
+ export declare function skillToolListsOverCap(skill: unknown, at: string, scope: "top" | "agent"): InertToolListEntry | undefined;
153
156
  /**
154
157
  * ③ 的拒侧在**提交体**上的判,技能那两层(顶层 `skills[]` 与子代 `agents[].skills[]`;第一处命中即答)。全部工具名清单的判是 {@link inertToolListEntryOf}(调用点两处都调它);
155
158
  * 本函数单独导出是给随包排空脚本:那条判据(`skill-deny-inert`)只数技能这两层,SQL 车道的孪生也只看技能的清单。
@@ -167,7 +170,7 @@ export declare function skillDenyInertEntryOf(body: unknown): SkillDenyInertEntr
167
170
  * 本函数只是「在体的哪几处取清单」;句子只有一个有界的组句器({@link describeInertRuleEntries}),清单先过条数帽({@link allowLayerEntryCountIssue})。
168
171
  *
169
172
  * 为什么限制侧四类全拒、放行侧只拒近似名:引擎 7.51.x 把「没有任何工具答应的名字」改成条目原样保留 + 运维行告知一次。限制侧读成空 = **静默放行**(`denyTools:["BASH"]` 以为禁了 `Bash`,子代照用);
170
- * 放行侧读成空 = 想放的什么也没放 —— 其中**近似名**(现名唯一)是确定的笔误,拒并点名现名;**退役名**、**内容形**与**文法读不出**在放行侧照 7.130.0 受理、交引擎既有通告(见头注 ③)。
173
+ * 放行侧读成空 = 想放的什么也没放 —— 其中**近似名**(现名唯一)是确定的笔误,拒并点名现名;**退役名**(选择器清单里有后继的由引擎开跑前拒)、**内容形**与**文法读不出**在放行侧照 7.130.0 受理、交引擎既有通告(见头注 ③)。
171
174
  * **真未知名**(没有近似也没有退役对应)不在读成空之列,原样保留、告运维一次(之后挂上同名工具即生效)。请求的 `settings.permissions.deny` / `ask`
172
175
  * 与运维 / 中心的审批名单读同一张名册,走另一只入口({@link unmatchableToolNamesRefusal},`config.ts`),不在本函数(存体预判调同一只判据、home 同源于部署配置,见 `http/stored-body-replay.ts`)。
173
176
  *
@@ -1,16 +1,18 @@
1
1
  import { PROTOCOL_TABLE, compileAllowList, parsePermissionRule } from "@sema-agent/core";
2
2
  import { displayListEntry } from "./list-entry-display.js";
3
+ import { limitRefusal } from "./limit-refusal.js";
3
4
  import { TOOL_NAME_ROSTER } from "./namespaced-rule-name.js";
4
5
  export const ruleEntryListShapeOk = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
5
6
  export const MAX_ALLOW_LAYER_ENTRIES = 1_000;
7
+ const ALLOW_LAYER_OVER_CAP_TAIL = "Refused rather than truncated: a truncated list silently drops the entries past the cap. Merge entries into broader ones (mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**)) or trim the list";
8
+ function allowLayerOverCap(field, count) {
9
+ return limitRefusal(field, "entries", count, MAX_ALLOW_LAYER_ENTRIES, { tail: ALLOW_LAYER_OVER_CAP_TAIL });
10
+ }
6
11
  export function describeAllowLayerOverCap(field, count) {
7
- return (`${field} has ${count} entries — at most ${MAX_ALLOW_LAYER_ENTRIES} are accepted. Refused rather than truncated: a truncated list silently drops the entries past the cap. ` +
8
- `Merge entries into broader ones (mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**)) or trim the list`);
12
+ return allowLayerOverCap(field, count).message;
9
13
  }
10
14
  export function allowLayerEntryCountIssue(field, list) {
11
- if (list.length <= MAX_ALLOW_LAYER_ENTRIES)
12
- return undefined;
13
- return { message: describeAllowLayerOverCap(field, list.length), count: list.length, cap: MAX_ALLOW_LAYER_ENTRIES };
15
+ return list.length <= MAX_ALLOW_LAYER_ENTRIES ? undefined : allowLayerOverCap(field, list.length);
14
16
  }
15
17
  export function inertRuleEntriesOf(list, behavior) {
16
18
  return compileAllowList(list, behavior, TOOL_NAME_ROSTER).inert;
@@ -114,14 +116,16 @@ function inertEntriesOfList(list, layer) {
114
116
  function* toolListsOf(body) {
115
117
  const skills = body["skills"];
116
118
  if (Array.isArray(skills)) {
117
- for (const skill of skills) {
119
+ for (let k = 0; k < skills.length; k++) {
120
+ const skill = skills[k];
118
121
  if (!isPlainRecord(skill))
119
122
  continue;
120
- yield { layer: "skills", field: "skill.disallowedTools", prefix: "", list: skill["disallowedTools"] };
123
+ yield { layer: "skills", field: "skill.disallowedTools", prefix: "", at: `skills[${k}].disallowedTools`, list: skill["disallowedTools"] };
121
124
  }
122
- for (const skill of skills) {
125
+ for (let k = 0; k < skills.length; k++) {
126
+ const skill = skills[k];
123
127
  if (isPlainRecord(skill))
124
- yield { layer: "skills.allowedTools", field: "skill.allowedTools", prefix: "", list: skill["allowedTools"] };
128
+ yield { layer: "skills.allowedTools", field: "skill.allowedTools", prefix: "", at: `skills[${k}].allowedTools`, list: skill["allowedTools"] };
125
129
  }
126
130
  }
127
131
  const agents = body["agents"];
@@ -134,7 +138,7 @@ function* toolListsOf(body) {
134
138
  for (let j = 0; j < agentSkills.length; j++) {
135
139
  const skill = agentSkills[j];
136
140
  if (isPlainRecord(skill))
137
- yield { layer: "agents.skills", field: "skill.disallowedTools", prefix: `agents[${i}]: skills[${j}]: `, list: skill["disallowedTools"] };
141
+ yield { layer: "agents.skills", field: "skill.disallowedTools", prefix: `agents[${i}]: skills[${j}]: `, at: `agents[${i}].skills[${j}].disallowedTools`, list: skill["disallowedTools"] };
138
142
  }
139
143
  }
140
144
  for (let i = 0; i < agents.length; i++) {
@@ -145,31 +149,29 @@ function* toolListsOf(body) {
145
149
  for (let j = 0; j < agentSkills.length; j++) {
146
150
  const skill = agentSkills[j];
147
151
  if (isPlainRecord(skill))
148
- yield { layer: "agents.skills.allowedTools", field: "skill.allowedTools", prefix: `agents[${i}]: skills[${j}]: `, list: skill["allowedTools"] };
152
+ yield { layer: "agents.skills.allowedTools", field: "skill.allowedTools", prefix: `agents[${i}]: skills[${j}]: `, at: `agents[${i}].skills[${j}].allowedTools`, list: skill["allowedTools"] };
149
153
  }
150
154
  }
151
155
  for (let i = 0; i < agents.length; i++) {
152
156
  const agent = agents[i];
153
157
  if (!isPlainRecord(agent))
154
158
  continue;
155
- yield { layer: "agents.denyTools", field: `agents[${i}].denyTools`, prefix: "", list: agent["denyTools"] };
156
- yield { layer: "agents.allowTools", field: `agents[${i}].allowTools`, prefix: "", list: agent["allowTools"] };
159
+ yield { layer: "agents.denyTools", field: `agents[${i}].denyTools`, prefix: "", at: `agents[${i}].denyTools`, list: agent["denyTools"] };
160
+ yield { layer: "agents.allowTools", field: `agents[${i}].allowTools`, prefix: "", at: `agents[${i}].allowTools`, list: agent["allowTools"] };
157
161
  }
158
162
  }
159
- yield { layer: "excludeTools", field: "excludeTools", prefix: "", list: body["excludeTools"] };
160
- yield { layer: "allowTools", field: "allowTools", prefix: "", list: body["allowTools"] };
163
+ yield { layer: "excludeTools", field: "excludeTools", prefix: "", at: "excludeTools", list: body["excludeTools"] };
164
+ yield { layer: "allowTools", field: "allowTools", prefix: "", at: "allowTools", list: body["allowTools"] };
161
165
  const settings = body["settings"];
162
166
  const permissions = isPlainRecord(settings) ? settings["permissions"] : undefined;
163
167
  if (isPlainRecord(permissions))
164
- yield { layer: "settings.permissions.allow", field: "settings.permissions.allow", prefix: "", list: permissions["allow"] };
168
+ yield { layer: "settings.permissions.allow", field: "settings.permissions.allow", prefix: "", at: "settings.permissions.allow", list: permissions["allow"] };
165
169
  }
166
170
  function entryOfList(l) {
167
171
  const face = TOOL_LIST_FACES[l.layer];
168
- if (Array.isArray(l.list)) {
169
- const over = allowLayerEntryCountIssue(l.field, l.list);
170
- if (over !== undefined)
171
- return { layer: l.layer, side: face.side, code: LIST_OVER_CAP_CODE, message: `${l.prefix}${over.message}`, extra: { field: l.field, reason: "over_cap", count: over.count, cap: over.cap } };
172
- }
172
+ const over = overCapOfList(l);
173
+ if (over !== undefined)
174
+ return over;
173
175
  if (!ruleEntryListShapeOk(l.list))
174
176
  return undefined;
175
177
  const inert = inertEntriesOfList(l.list, l.layer);
@@ -177,6 +179,20 @@ function entryOfList(l) {
177
179
  return undefined;
178
180
  return { layer: l.layer, side: face.side, code: face.code, message: `${l.prefix}${describeInertRuleEntries(l.field, inert)} — ${REFUSAL_TAIL[face.side]}` };
179
181
  }
182
+ function overCapOfList(l) {
183
+ if (!Array.isArray(l.list))
184
+ return undefined;
185
+ const over = allowLayerEntryCountIssue(l.at, l.list);
186
+ return over === undefined ? undefined : { layer: l.layer, side: TOOL_LIST_FACES[l.layer].side, code: LIST_OVER_CAP_CODE, message: over.message, extra: over.facts };
187
+ }
188
+ export function skillToolListsOverCap(skill, at, scope) {
189
+ if (!isPlainRecord(skill))
190
+ return undefined;
191
+ const allowLayer = scope === "top" ? "skills.allowedTools" : "agents.skills.allowedTools";
192
+ const denyLayer = scope === "top" ? "skills" : "agents.skills";
193
+ return (overCapOfList({ layer: allowLayer, field: "skill.allowedTools", prefix: "", at: `${at}.allowedTools`, list: skill["allowedTools"] }) ??
194
+ overCapOfList({ layer: denyLayer, field: "skill.disallowedTools", prefix: "", at: `${at}.disallowedTools`, list: skill["disallowedTools"] }));
195
+ }
180
196
  function firstInertEntry(body, acceptLayer, acceptSide) {
181
197
  if (!isPlainRecord(body))
182
198
  return undefined;
package/dist/run-local.js CHANGED
@@ -8,7 +8,7 @@ import { join } from "node:path";
8
8
  import { realpathSync } from "node:fs";
9
9
  import { createInterface } from "node:readline/promises";
10
10
  import { fileURLToPath } from "node:url";
11
- import { Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
11
+ import { BIDI_CONTROL_CHARS, Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
12
12
  import { readAskClassPair } from "./approval-card.js";
13
13
  import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
14
14
  import { withUnattendedLeg } from "./unattended-permission-mode.js";
@@ -18,14 +18,13 @@ import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, buildAppr
18
18
  import { applyRuntimeGovernance, autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
19
19
  import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
20
20
  import { taskWallClockSec } from "./task-workflow.js";
21
- import { applyCatalogToSource } from "./capabilities/center-prompts.js";
22
- import { CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
21
+ import { applyCenterArtifact, centerSourceOnError } from "./capabilities/center-prompts.js";
23
22
  import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
24
23
  import { buildStoreOpenRefusalTail } from "./plugins/store-open-refusal.js";
25
24
  import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
26
25
  import { configLocalRoot, configProviderKind, createConfigProvider } from "./config-provider.js";
27
26
  import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
28
- import { buildUnreadConfigFileFields } from "./web-admin-client/read-warnings.js";
27
+ import { buildUnreadConfigFileFields, buildUnreadConfigKeyFields } from "./web-admin-client/read-warnings.js";
29
28
  import { composeExecutionLane, settleHostLaneShell } from "./boot/execution-lane.js";
30
29
  import { judgeConsolidationValve } from "./boot/memory-consolidation.js";
31
30
  import { logConfigDirMigration, migrateLocalConfigDirectory } from "./config-d-migration.js";
@@ -37,7 +36,7 @@ import { createInstructionSourceSeat } from "./instruction-source-notice.js";
37
36
  import { loadDeploymentSkills } from "./capabilities/skills.js";
38
37
  import { createRepoClient } from "./capabilities/repo-tools.js";
39
38
  import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
40
- import { buildScenarios, createScenarioGate, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
39
+ import { buildPromptSeats, buildScenarios, createScenarioGate, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
41
40
  import { createTaskListLane } from "./boot/task-list-lane.js";
42
41
  import { pickHandsRunner, withoutHands } from "./capabilities/hands-lane.js";
43
42
  import { HttpError, sessionIdShapeIssue } from "./security.js";
@@ -50,7 +49,7 @@ import { createSharedRunnerDeps } from "./boot/runner-deps.js";
50
49
  import { createGovernanceSeams } from "./boot/governance-seams.js";
51
50
  import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
52
51
  import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
53
- import { createPermissionDeniedMeter } from "./observability/tool-trace.js";
52
+ import { assertDeploymentModsAdmitted, createDeploymentMods } from "./boot/deployment-mods.js";
54
53
  import { createKeyResolver } from "./key-resolver.js";
55
54
  import { createLogger } from "./observability/logger.js";
56
55
  import { fatalExit } from "./boot/fatal-exit.js";
@@ -254,8 +253,9 @@ function argsDisplayOf(args) {
254
253
  function buildLocalAskPrompt(req, argsShown) {
255
254
  return `approve "${terminalSafeText(req.toolName)}"? ${terminalSafeText(req.message)}\n ${argsShown}\n[y/N] `;
256
255
  }
256
+ const TERMINAL_UNSAFE_RE = new RegExp(`[\\u0000-\\u001f\\u007f-\\u009f${BIDI_CONTROL_CHARS}\\u200b-\\u200d\\u2028\\u2029\\u2060-\\u2065\\ufeff]`, "g");
257
257
  function terminalSafeText(text) {
258
- return text.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028-\u202e\u2060-\u2069\ufeff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
258
+ return text.replace(TERMINAL_UNSAFE_RE, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
259
259
  }
260
260
  function inputAlreadyEnded(input) {
261
261
  const state = input;
@@ -366,6 +366,8 @@ export async function runLocal(argv, deps = {}) {
366
366
  else {
367
367
  for (const f of r.unreadFiles ?? [])
368
368
  logger.warn("config_unread_file", buildUnreadConfigFileFields(f, configProvider.kind === "local" ? root : undefined));
369
+ for (const k of r.unreadKeys ?? [])
370
+ logger.warn("config_unread_key", buildUnreadConfigKeyFields(k, configProvider.kind === "local" ? root : undefined));
369
371
  for (const d of r.droppedEntries ?? [])
370
372
  logger.warn("config_entry_dropped", { domain: d.domain, entry: d.entry, reasonCode: d.reasonCode, error: d.error, version: r.effective.version, phase: "boot", note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back" });
371
373
  applying = true;
@@ -488,25 +490,25 @@ export async function runLocal(argv, deps = {}) {
488
490
  return new CenterPromptSource({
489
491
  store: new FilePromptArtifactStore(promptEpochDir),
490
492
  stateStore: new FilePromptSourceStateStore(promptEpochDir),
491
- engineVersion: CORE_ENGINE_VERSION,
493
+ onError: centerSourceOnError(logger),
492
494
  });
493
495
  }
494
496
  catch (e) {
495
497
  logger.warn("center_prompts_store_unwritable", { dir: promptEpochDir, err: String(e), note: "file-backed prompt-epoch store unavailable — memory fallback; built-ins unaffected" });
496
- return new CenterPromptSource({ store: new MemoryPromptArtifactStore(), stateStore: new MemoryPromptSourceStateStore(), engineVersion: CORE_ENGINE_VERSION });
498
+ return new CenterPromptSource({ store: new MemoryPromptArtifactStore(), stateStore: new MemoryPromptSourceStateStore(), onError: centerSourceOnError(logger) });
497
499
  }
498
500
  })();
499
501
  try {
500
502
  await promptSource.restore();
501
503
  if (candidatePrompts !== undefined) {
502
- await applyCatalogToSource(promptSource, candidatePrompts, undefined, logger, "run-local");
504
+ await applyCenterArtifact(promptSource, candidatePrompts, undefined, logger, "run-local");
503
505
  }
504
506
  else {
505
- await applyCatalogToSource(promptSource, undefined, undefined, logger, "run-local");
507
+ await applyCenterArtifact(promptSource, undefined, undefined, logger, "run-local");
506
508
  }
507
509
  }
508
510
  catch (e) {
509
- logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "catalog candidate absent — built-ins serve" });
511
+ logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "center candidate absent — built-ins serve" });
510
512
  }
511
513
  let refusedWithoutPerson = 0;
512
514
  const onAsk = createLocalApprover({
@@ -559,6 +561,8 @@ export async function runLocal(argv, deps = {}) {
559
561
  { name: "toolResultStore", store: fileBackend.toolResultStore },
560
562
  ],
561
563
  });
564
+ const deploymentMods = createDeploymentMods({ metrics, brandIdentity: true });
565
+ await assertDeploymentModsAdmitted(deploymentMods);
562
566
  const sharedRunnerDeps = createSharedRunnerDeps({
563
567
  metrics,
564
568
  config,
@@ -575,10 +579,7 @@ export async function runLocal(argv, deps = {}) {
575
579
  backgroundAgentStore: fileBackend.backgroundAgentStore,
576
580
  mailboxStore: fileBackend.mailboxStore,
577
581
  rosterStore: fileBackend.rosterStore,
578
- deploymentHooks: {
579
- ...createPermissionDeniedMeter(metrics),
580
- ...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
581
- },
582
+ deploymentMods,
582
583
  toolResultStore: fileBackend.toolResultStore,
583
584
  checkpointStore: undefined,
584
585
  sessionPolicyStore: fileBackend.sessionPolicyStore,
@@ -604,7 +605,7 @@ export async function runLocal(argv, deps = {}) {
604
605
  const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
605
606
  const subRunner = new Runner(subRunnerDeps);
606
607
  const handslessRunner = new Runner(withoutHands(runnerDeps));
607
- const deploymentSkills = loadDeploymentSkills(config);
608
+ const deploymentSkills = loadDeploymentSkills(config, logger);
608
609
  const skills = effective?.skills ? await applySkillsManifest(deploymentSkills, effective.skills, configProvider, logger) : deploymentSkills;
609
610
  const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
610
611
  const webSearchCfg = webSearchConfigFromEnv();
@@ -615,7 +616,6 @@ export async function runLocal(argv, deps = {}) {
615
616
  ...(webSearch ? { webSearch } : {}),
616
617
  backgroundAgentStore: fileBackend.backgroundAgentStore,
617
618
  taskListStoreFor: taskListLane.storeFor,
618
- brandIdentity: true,
619
619
  };
620
620
  const scenarios = buildScenarios(scenarioDeps);
621
621
  if (effective?.scenarios) {
@@ -673,7 +673,7 @@ export async function runLocal(argv, deps = {}) {
673
673
  getApiKeyAndHeaders: keyResolver,
674
674
  tools: cap.tools,
675
675
  skills: cap.skills,
676
- ...(cap.promptProvider ? { promptProvider: cap.promptProvider } : {}),
676
+ ...buildPromptSeats(scenarioName, cap.prompt, undefined),
677
677
  ...(cap.finalVerification === true ? { finalVerification: true } : {}),
678
678
  ...(mcp ? { mcp } : {}),
679
679
  ...(a2a ? { a2a } : {}),
package/dist/runs.d.ts CHANGED
@@ -109,6 +109,12 @@ export declare function resumeAtHttpStatus(result: {
109
109
  * `resumeAtHttpStatus` 自己(单元测试直接喂码)。
110
110
  */
111
111
  export declare function syncRefusalHttpStatus(result: Pick<TaskResult, "terminal" | "stats">): number | undefined;
112
+ /**
113
+ * 同步腿零工作拒体的机读事实(交 `refusalBody` 的 `facts`)—— 唯一一处。入参是**出口封口 + 脱敏后**的终局(调用方递 `redactTerminalResult(sealResultForExit(result)).terminal`,
114
+ * 与 run 记录读面 / 流式 `done.result` 上的 `terminal` 同一只来源),本函数只按码取它的 `detail.detail`,不另读、不另脱。码不在登记表里、终局不是 `failed`、
115
+ * 引擎没给 `coded` 因由 ⇒ `{}`(缺席不造键)。
116
+ */
117
+ export declare function zeroWorkRefusalFacts(terminal: unknown): Readonly<Record<string, unknown>>;
112
118
  /**
113
119
  * 一条提交腿把引擎终局落行之后的**唯一**收尾:**零工作的拒不占提交幂等键**。判据就是上面这只 {@link syncRefusalHttpStatus}
114
120
  * (零 turn、零 token 且是 prepare 期的拒码)—— 三条提交腿(同步 / 流式 / durable)同调这一只,不另开判据。
package/dist/runs.js CHANGED
@@ -78,7 +78,10 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
78
78
  "config.image_invalid": 400,
79
79
  "config.user_attachment_invalid": 400,
80
80
  "config.tool_face_control": 400,
81
+ "config.agent.unknown_tool": 400,
82
+ "config.legacy_tool_name": 400,
81
83
  "config.memory_capture_unsupported": 409,
84
+ "prompt.assembled_role_refused": 400,
82
85
  };
83
86
  export function resumeAtHttpStatus(result) {
84
87
  const code = failureCodeOf(result.terminal);
@@ -106,6 +109,19 @@ export function syncRefusalHttpStatus(result) {
106
109
  return undefined;
107
110
  return resumeAtHttpStatus(result);
108
111
  }
112
+ const ZERO_WORK_REFUSAL_DETAIL_CODES = new Set(["config.user_attachment_invalid"]);
113
+ export function zeroWorkRefusalFacts(terminal) {
114
+ if (terminal === null || typeof terminal !== "object")
115
+ return {};
116
+ const t = terminal;
117
+ if (t.kind !== "failed" || typeof t.code !== "string" || !ZERO_WORK_REFUSAL_DETAIL_CODES.has(t.code))
118
+ return {};
119
+ const cause = t.detail;
120
+ if (cause === null || typeof cause !== "object")
121
+ return {};
122
+ const c = cause;
123
+ return c.kind === "coded" && c.detail !== undefined ? { detail: c.detail } : {};
124
+ }
109
125
  export async function releaseKeyIfZeroWorkRefusal(runStore, taskId, result) {
110
126
  if (syncRefusalHttpStatus(result) !== undefined)
111
127
  await runStore.releaseIdemKey(taskId);