@sema-agent/server 7.125.1 → 7.127.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 (275) hide show
  1. package/MIGRATION.md +229 -2
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/USAGE.md +346 -145
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
  7. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
  8. package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
  9. package/deploy/sema-up/chart/templates/server.yaml +2 -1
  10. package/deploy/sema-up/chart/values.yaml +22 -0
  11. package/deploy/sema-up/cluster-up.sh +10 -10
  12. package/deploy/sema-up/kube-up.sh +20 -13
  13. package/deploy/sema-up/lib/dataroot.sh +1 -1
  14. package/deploy/sema-up/lib/envf.sh +5 -5
  15. package/deploy/sema-up/lib/flagshape.sh +5 -5
  16. package/deploy/sema-up/lib/lanes.py +1 -1
  17. package/deploy/sema-up/lib/node-addrs.sh +42 -0
  18. package/deploy/sema-up/lib/parse-machines.py +2 -2
  19. package/deploy/sema-up/lib/shell-access.sh +1 -1
  20. package/deploy/sema-up/preflight.sh +4 -4
  21. package/deploy/sema-up/sema-up.sh +33 -33
  22. package/deploy/sema-up/smoke.sh +3 -3
  23. package/deploy/sema-up/stack/compose.yaml +1 -1
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
  25. package/dist/approval-card.d.ts +20 -4
  26. package/dist/approval-content-kind.d.ts +38 -22
  27. package/dist/approval-content-kind.js +20 -4
  28. package/dist/audit.js +3 -0
  29. package/dist/bench/s1/arms.d.ts +9 -37
  30. package/dist/bench/s1/arms.js +4 -61
  31. package/dist/bench/s1/live-deps.d.ts +4 -10
  32. package/dist/bench/s1/live-deps.js +1 -222
  33. package/dist/bench/s1/oracle.d.ts +1 -1
  34. package/dist/bench/s1/reviewer.d.ts +1 -1
  35. package/dist/bench/s1/row.d.ts +5 -7
  36. package/dist/bench/s1/row.js +0 -2
  37. package/dist/bench/s1/run-firm.d.ts +2 -2
  38. package/dist/bench/s1/run-firm.js +1 -4
  39. package/dist/bench/s1/runner-ctx.d.ts +17 -29
  40. package/dist/bench/s1/runner-ctx.js +3 -7
  41. package/dist/bench/s1/tasks.d.ts +9 -46
  42. package/dist/bench/s1/tasks.js +4 -63
  43. package/dist/boot/execution-env.d.ts +38 -26
  44. package/dist/boot/execution-env.js +87 -41
  45. package/dist/boot/leader.d.ts +2 -1
  46. package/dist/boot/memory-boundary.d.ts +1 -1
  47. package/dist/boot/memory-consolidation.d.ts +37 -3
  48. package/dist/boot/memory-consolidation.js +18 -2
  49. package/dist/boot/parked-revive-gate.d.ts +0 -3
  50. package/dist/boot/parked-revive-gate.js +2 -2
  51. package/dist/boot/resolve-spec.d.ts +0 -2
  52. package/dist/boot/resolve-spec.js +12 -17
  53. package/dist/boot/retention-lane.d.ts +12 -1
  54. package/dist/boot/retention-lane.js +2 -0
  55. package/dist/boot/runner-deps.d.ts +7 -6
  56. package/dist/boot/runner-deps.js +5 -16
  57. package/dist/boot/session-faces.d.ts +7 -2
  58. package/dist/boot/session-faces.js +37 -1
  59. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  60. package/dist/boot/stage-03-store-backend.js +2 -2
  61. package/dist/boot/stage-04-stores.d.ts +1 -0
  62. package/dist/boot/stage-05-execution-env.d.ts +2 -2
  63. package/dist/boot/stage-05-execution-env.js +7 -4
  64. package/dist/boot/stage-06-runners.d.ts +2 -3
  65. package/dist/boot/stage-06-runners.js +13 -9
  66. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  67. package/dist/boot/stage-07-capability-layer.js +3 -4
  68. package/dist/boot/stage-08-reapers.d.ts +2 -3
  69. package/dist/boot/stage-08-reapers.js +2 -2
  70. package/dist/boot/stage-09-leader.d.ts +2 -3
  71. package/dist/boot/stage-09-leader.js +2 -1
  72. package/dist/boot/stage-10-http-server.d.ts +2 -3
  73. package/dist/boot/stage-10-http-server.js +2 -2
  74. package/dist/boot/stores.d.ts +1 -0
  75. package/dist/boot/stores.js +4 -3
  76. package/dist/boot/web-admin.d.ts +5 -0
  77. package/dist/boot/web-admin.js +28 -14
  78. package/dist/brain.d.ts +2 -2
  79. package/dist/brain.js +1 -1
  80. package/dist/capabilities/builtin-tools.d.ts +2 -1
  81. package/dist/capabilities/composite-members.d.ts +1 -1
  82. package/dist/capabilities/composite-members.js +1 -1
  83. package/dist/capabilities/hands-lane.d.ts +9 -6
  84. package/dist/capabilities/hands-lane.js +2 -2
  85. package/dist/capabilities/memory-notice.d.ts +27 -22
  86. package/dist/capabilities/memory-notice.js +15 -17
  87. package/dist/capabilities/scenarios.d.ts +9 -14
  88. package/dist/capabilities/scenarios.js +5 -50
  89. package/dist/capability-facts.d.ts +22 -11
  90. package/dist/capability-facts.js +13 -6
  91. package/dist/config-catalog.js +14 -5
  92. package/dist/config-provider.d.ts +5 -1
  93. package/dist/config-provider.js +23 -4
  94. package/dist/config-types.d.ts +19 -2
  95. package/dist/config.js +41 -6
  96. package/dist/deployment-governance.d.ts +19 -21
  97. package/dist/deployment-governance.js +7 -8
  98. package/dist/env-name-allowlist-knobs.d.ts +5 -5
  99. package/dist/env-name-allowlist-knobs.js +1 -0
  100. package/dist/execution-lane-caps.d.ts +12 -0
  101. package/dist/execution-lane-caps.js +18 -4
  102. package/dist/fleet/subagent-tail-bus.js +15 -5
  103. package/dist/hooks/hook-runner.d.ts +84 -30
  104. package/dist/hooks/hook-runner.js +74 -41
  105. package/dist/http/admission.js +17 -10
  106. package/dist/http/durable-run-leg.d.ts +5 -4
  107. package/dist/http/durable-run-leg.js +2 -4
  108. package/dist/http/idempotency.d.ts +28 -6
  109. package/dist/http/idempotency.js +10 -2
  110. package/dist/http/principal-gate.d.ts +14 -0
  111. package/dist/http/principal-gate.js +7 -0
  112. package/dist/http/request-guards.d.ts +2 -1
  113. package/dist/http/request-guards.js +14 -14
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +20 -10
  116. package/dist/http/route-ctx.d.ts +15 -7
  117. package/dist/http/routes/a2a-serve.js +11 -7
  118. package/dist/http/routes/approvals-assistant.js +18 -3
  119. package/dist/http/routes/background.d.ts +1 -1
  120. package/dist/http/routes/background.js +1 -1
  121. package/dist/http/routes/capabilities.js +10 -2
  122. package/dist/http/routes/mcp-reconnect.js +1 -1
  123. package/dist/http/routes/runs.d.ts +4 -3
  124. package/dist/http/routes/runs.js +35 -14
  125. package/dist/http/routes/session-sync.js +14 -11
  126. package/dist/http/routes/sessions.js +9 -9
  127. package/dist/http/routes/side-query.js +6 -19
  128. package/dist/http/routes/tasks.js +29 -19
  129. package/dist/http/routes/workflows.js +8 -0
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/server.js +3 -5
  132. package/dist/http/submit-replay.d.ts +15 -8
  133. package/dist/http/submit-replay.js +6 -0
  134. package/dist/http/wire-types.d.ts +7 -9
  135. package/dist/index.d.ts +1 -1
  136. package/dist/index.js +1 -1
  137. package/dist/internal-run-env.d.ts +30 -0
  138. package/dist/internal-run-env.js +11 -0
  139. package/dist/leader/wire.js +3 -2
  140. package/dist/lsp/manager.js +1 -1
  141. package/dist/memory-scope.d.ts +19 -7
  142. package/dist/memory-scope.js +25 -8
  143. package/dist/observability/fail-open.d.ts +31 -3
  144. package/dist/observability/fail-open.js +31 -3
  145. package/dist/observability/metrics.d.ts +0 -1
  146. package/dist/observability/metrics.js +1 -4
  147. package/dist/observability/run-terminal.js +72 -4
  148. package/dist/permission-rule-vocab.d.ts +3 -2
  149. package/dist/permission-rule-vocab.js +1 -1
  150. package/dist/plan-cache-probe.d.ts +3 -2
  151. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  152. package/dist/plugins/caching-session-store.d.ts +42 -22
  153. package/dist/plugins/caching-session-store.js +19 -9
  154. package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
  155. package/dist/plugins/checkpoint-store-sql.js +57 -11
  156. package/dist/plugins/file-run-store.d.ts +25 -21
  157. package/dist/plugins/file-run-store.js +70 -34
  158. package/dist/plugins/k8s-exec-protocol.js +3 -0
  159. package/dist/plugins/local-checkpoint-store.d.ts +8 -5
  160. package/dist/plugins/local-checkpoint-store.js +5 -15
  161. package/dist/plugins/local-session-store.d.ts +66 -7
  162. package/dist/plugins/local-session-store.js +168 -101
  163. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
  164. package/dist/plugins/memory-run-store.d.ts +19 -3
  165. package/dist/plugins/memory-run-store.js +38 -3
  166. package/dist/plugins/one-time-migrations.d.ts +165 -39
  167. package/dist/plugins/one-time-migrations.js +51 -144
  168. package/dist/plugins/pg-pool.js +11 -1
  169. package/dist/plugins/pg-session-storage.d.ts +2 -0
  170. package/dist/plugins/pg-session-storage.js +9 -5
  171. package/dist/plugins/remote-env-adb.d.ts +3 -0
  172. package/dist/plugins/remote-env-adb.js +1 -0
  173. package/dist/plugins/remote-env-device.d.ts +3 -0
  174. package/dist/plugins/remote-env-device.js +1 -0
  175. package/dist/plugins/remote-env-e2b.d.ts +4 -0
  176. package/dist/plugins/remote-env-e2b.js +2 -1
  177. package/dist/plugins/remote-env-host.d.ts +23 -1
  178. package/dist/plugins/remote-env-host.js +20 -2
  179. package/dist/plugins/remote-env-k8s.d.ts +264 -41
  180. package/dist/plugins/remote-env-k8s.js +551 -82
  181. package/dist/plugins/remote-env-local-docker.d.ts +4 -0
  182. package/dist/plugins/remote-env-local-docker.js +2 -1
  183. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  184. package/dist/plugins/remote-env-ssh.js +1 -0
  185. package/dist/plugins/retention-store-sql.d.ts +4 -1
  186. package/dist/plugins/retention-store-sql.js +11 -9
  187. package/dist/plugins/run-store-sql.d.ts +44 -6
  188. package/dist/plugins/run-store-sql.js +92 -27
  189. package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
  190. package/dist/plugins/session-store.d.ts +4 -3
  191. package/dist/plugins/session-store.js +1 -1
  192. package/dist/plugins/sql-driver.d.ts +2 -1
  193. package/dist/plugins/store-backend.d.ts +12 -5
  194. package/dist/plugins/store-backend.js +13 -4
  195. package/dist/plugins/store-contracts.d.ts +20 -3
  196. package/dist/plugins/store-open-wait.js +3 -0
  197. package/dist/plugins/tidb-pool.js +15 -3
  198. package/dist/plugins/tidb-session-storage.js +3 -2
  199. package/dist/plugins/tidb-session-store.d.ts +2 -0
  200. package/dist/plugins/tidb-session-store.js +6 -2
  201. package/dist/request-key-closure.d.ts +32 -7
  202. package/dist/request-key-closure.js +25 -9
  203. package/dist/router/route-orchestration.d.ts +3 -11
  204. package/dist/router/route-orchestration.js +0 -4
  205. package/dist/rule-entry-list.js +0 -4
  206. package/dist/rules-consent.js +3 -0
  207. package/dist/run-acceptance.d.ts +51 -0
  208. package/dist/run-acceptance.js +27 -0
  209. package/dist/run-local.d.ts +5 -4
  210. package/dist/run-local.js +23 -17
  211. package/dist/runs.d.ts +7 -11
  212. package/dist/runs.js +6 -16
  213. package/dist/sandbox-egress-policy.d.ts +61 -0
  214. package/dist/sandbox-egress-policy.js +235 -0
  215. package/dist/security.d.ts +7 -0
  216. package/dist/security.js +5 -0
  217. package/dist/server-secret-env.d.ts +10 -4
  218. package/dist/server-secret-env.js +4 -1
  219. package/dist/session-error-wire.d.ts +2 -2
  220. package/dist/session-incarnation.d.ts +46 -2
  221. package/dist/session-incarnation.js +14 -3
  222. package/dist/session-sync-kernel.d.ts +10 -3
  223. package/dist/session-titler.d.ts +5 -3
  224. package/dist/spec-fields.d.ts +24 -8
  225. package/dist/spec-fields.js +21 -2
  226. package/dist/task-workflow.d.ts +6 -4
  227. package/dist/task-workflow.js +3 -2
  228. package/dist/tool-approval.d.ts +78 -6
  229. package/dist/tool-approval.js +7 -3
  230. package/dist/trace/core-keyset-guard.d.ts +6 -5
  231. package/dist/trace/engine-notice-wire.d.ts +26 -1
  232. package/dist/trace/engine-notice-wire.js +7 -2
  233. package/dist/trace/ledger-events.d.ts +9 -3
  234. package/dist/trace/ledger-sink.d.ts +7 -0
  235. package/dist/trace/ledger-sink.js +14 -4
  236. package/dist/trace/project.d.ts +57 -11
  237. package/dist/trace/project.js +54 -8
  238. package/dist/trace/self-minted-wire.d.ts +94 -2
  239. package/dist/trace/self-minted-wire.js +28 -0
  240. package/dist/trace/tool-output-media.d.ts +26 -0
  241. package/dist/trace/tool-output-media.js +57 -0
  242. package/dist/trace/wire-projection-faces.d.ts +7 -1
  243. package/dist/trace/wire-projection-faces.js +2 -1
  244. package/dist/web-admin-client/apply-effective.d.ts +13 -7
  245. package/dist/web-admin-client/apply-effective.js +11 -26
  246. package/dist/web-admin-client/apply-ledger.d.ts +2 -2
  247. package/dist/web-admin-client/candidate-gate.d.ts +39 -4
  248. package/dist/web-admin-client/candidate-gate.js +9 -2
  249. package/dist/web-admin-client/facade.d.ts +2 -2
  250. package/dist/web-admin-client/http-client.d.ts +3 -1
  251. package/dist/web-admin-client/http-client.js +4 -3
  252. package/dist/web-admin-client/read-warnings.d.ts +33 -5
  253. package/dist/web-admin-client/read-warnings.js +41 -6
  254. package/dist/web-admin-client/types.d.ts +11 -5
  255. package/dist/workspace-cache.d.ts +45 -0
  256. package/dist/workspace-cache.js +30 -0
  257. package/dist/workspace-space-commit.d.ts +116 -0
  258. package/dist/workspace-space-commit.js +117 -0
  259. package/dist/workspace-space.d.ts +75 -9
  260. package/dist/workspace-space.js +52 -1
  261. package/package.json +4 -4
  262. package/dist/boot/session-incarnation-seat.d.ts +0 -41
  263. package/dist/boot/session-incarnation-seat.js +0 -69
  264. package/dist/capabilities/code-review-council.d.ts +0 -49
  265. package/dist/capabilities/code-review-council.js +0 -111
  266. package/dist/capabilities/collab-wire.d.ts +0 -36
  267. package/dist/capabilities/collab-wire.js +0 -2
  268. package/dist/capabilities/collab-workflows.d.ts +0 -47
  269. package/dist/capabilities/collab-workflows.js +0 -90
  270. package/dist/capabilities/prompts/code-review.d.ts +0 -25
  271. package/dist/capabilities/prompts/code-review.js +0 -39
  272. package/dist/capabilities/prompts/team.d.ts +0 -10
  273. package/dist/capabilities/prompts/team.js +0 -6
  274. package/dist/capabilities/team.d.ts +0 -113
  275. package/dist/capabilities/team.js +0 -70
@@ -9,7 +9,7 @@ import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.
9
9
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
10
10
  import { readWritableSessionPermissionRules, describeSessionPermissionRulesProblem } from "../../session-policy-wire.js";
11
11
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders } from "../send.js";
12
- import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
12
+ import { gatedPrincipal, explicitOperatorOk, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
13
13
  import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
14
14
  import * as facts from "../../capability-facts.js";
15
15
  import { rosterAgentId } from "./agents-roster.js";
@@ -54,7 +54,7 @@ async function readLastLegMcp(deps, sessionId, principal) {
54
54
  const latest = await runStore.latestRunForSession(sessionId);
55
55
  if (latest === undefined)
56
56
  return undefined;
57
- if (!runOwnerReadable(principal, latest.owner, deps.config) && !explicitOperatorOk(principal, deps.config.operatorPrincipals))
57
+ if (!sessionOwnerReadable(principal, latest.owner, deps.config) && !explicitOperatorOk(principal, deps.config.operatorPrincipals))
58
58
  return undefined;
59
59
  const events = await runStore.getEvents(latest.taskId, 0);
60
60
  for (let i = events.length - 1; i >= 0; i--) {
@@ -70,7 +70,7 @@ async function readLastLegMcp(deps, sessionId, principal) {
70
70
  }
71
71
  export async function handleSessions(req, res, match, ctx) {
72
72
  const { deps } = ctx;
73
- const { readJson, safeDecode, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite } = ctx.helpers;
73
+ const { readJson, safeDecode, sessionOwnerOk, sessionOwnerScopeForWrite } = ctx.helpers;
74
74
  const readOwnedSessionView = async (sessionAudit, sessionId, readOpts) => {
75
75
  const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
76
76
  if (ownerOf !== undefined) {
@@ -79,7 +79,7 @@ export async function handleSessions(req, res, match, ctx) {
79
79
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
80
80
  return undefined;
81
81
  }
82
- if (!runOwnerOk(req, res, owner, "session"))
82
+ if (!sessionOwnerOk(req, res, owner))
83
83
  return undefined;
84
84
  }
85
85
  let audit;
@@ -95,7 +95,7 @@ export async function handleSessions(req, res, match, ctx) {
95
95
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
96
96
  return undefined;
97
97
  }
98
- if (!runOwnerOk(req, res, audit.owner, "session"))
98
+ if (!sessionOwnerOk(req, res, audit.owner))
99
99
  return undefined;
100
100
  return audit;
101
101
  };
@@ -265,7 +265,7 @@ export async function handleSessions(req, res, match, ctx) {
265
265
  return;
266
266
  }
267
267
  owner = head.owner;
268
- if (!runOwnerOk(req, res, owner ?? null, "session"))
268
+ if (!sessionOwnerOk(req, res, owner ?? null))
269
269
  return;
270
270
  leafId = head.leafId;
271
271
  }
@@ -275,7 +275,7 @@ export async function handleSessions(req, res, match, ctx) {
275
275
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
276
276
  return;
277
277
  }
278
- if (!runOwnerOk(req, res, owner ?? null, "session"))
278
+ if (!sessionOwnerOk(req, res, owner ?? null))
279
279
  return;
280
280
  leafId = await getLeafId(headSession);
281
281
  }
@@ -359,7 +359,7 @@ export async function handleSessions(req, res, match, ctx) {
359
359
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
360
360
  return;
361
361
  }
362
- if (!runOwnerOk(req, res, owner ?? null, "session")) {
362
+ if (!sessionOwnerOk(req, res, owner ?? null)) {
363
363
  cleanup();
364
364
  return;
365
365
  }
@@ -666,7 +666,7 @@ export async function handleSessions(req, res, match, ctx) {
666
666
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
667
667
  return;
668
668
  }
669
- if (!runOwnerOk(req, res, face.owner, "session"))
669
+ if (!sessionOwnerOk(req, res, face.owner))
670
670
  return;
671
671
  const read = readSessionRowPatch(await readJson(req));
672
672
  if ("problem" in read) {
@@ -1,4 +1,4 @@
1
- import { expandTiers, isThinkingLevel } from "@sema-agent/core";
1
+ import { MODEL_ROLES, expandTiers, isThinkingLevel } from "@sema-agent/core";
2
2
  import { HttpError } from "../../security.js";
3
3
  import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../../model-select.js";
4
4
  import { modelUnavailableMessage } from "../../model-entry-refusal.js";
@@ -12,22 +12,8 @@ export function isCorePreflightValidationError(message) {
12
12
  return CORE_PREFLIGHT_VALIDATION_PREFIXES.some((prefix) => message.startsWith(prefix));
13
13
  }
14
14
  export const CORE_PREFLIGHT_VALIDATION_ANCHORS = CORE_PREFLIGHT_VALIDATION_PREFIXES;
15
- const MODEL_ROLE_TABLE = {
16
- default: true,
17
- summarize: true,
18
- subagent: true,
19
- team: true,
20
- synthesize: true,
21
- advisor: true,
22
- verifier: true,
23
- classifier: true,
24
- consolidate: true,
25
- };
26
15
  function isModelRole(value) {
27
- return typeof value === "string" && Object.hasOwn(MODEL_ROLE_TABLE, value);
28
- }
29
- function passthroughModelRole(value) {
30
- return value;
16
+ return typeof value === "string" && MODEL_ROLES.includes(value);
31
17
  }
32
18
  function shapeSideQueryEcho(result) {
33
19
  if (result.responseModel === undefined || admitProviderEcho(result.responseModel, "side-query.responseModel"))
@@ -76,8 +62,9 @@ export async function handleSideQuery(req, res, match, ctx) {
76
62
  sendError(res, 400, "request.field_invalid", "invalid thinking level");
77
63
  return;
78
64
  }
79
- if (typeof body.model !== "string" && body.modelRole !== undefined && !isModelRole(body.modelRole)) {
80
- sendError(res, 400, "request.field_invalid", `invalid modelRole (expected one of: ${Object.keys(MODEL_ROLE_TABLE).join(", ")}) — a role only resolves a model when 'model' is absent`);
65
+ if (body.modelRole !== undefined && !isModelRole(body.modelRole)) {
66
+ const shown = (typeof body.modelRole === "string" ? JSON.stringify(body.modelRole.slice(0, 80)) : `of type ${body.modelRole === null ? "null" : Array.isArray(body.modelRole) ? "array" : typeof body.modelRole}`);
67
+ sendError(res, 400, "request.field_invalid", `invalid modelRole ${shown} — must be a string, one of ${MODEL_ROLES.join(" / ")} (the engine's role vocabulary); a value outside it is refused before any model call`);
81
68
  return;
82
69
  }
83
70
  if (typeof body.model === "string" && body.model.length > 0) {
@@ -98,7 +85,7 @@ export async function handleSideQuery(req, res, match, ctx) {
98
85
  const spec = {
99
86
  messages: body.messages,
100
87
  ...(typeof body.model === "string" ? { model: body.model } : {}),
101
- ...(typeof body.modelRole === "string" ? { modelRole: passthroughModelRole(body.modelRole) } : {}),
88
+ ...(isModelRole(body.modelRole) ? { modelRole: body.modelRole } : {}),
102
89
  ...(isThinkingLevel(body.thinking) ? { thinking: body.thinking } : {}),
103
90
  ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}),
104
91
  ...(Array.isArray(body.tools) ? { tools: body.tools } : {}),
@@ -13,9 +13,10 @@ import { clearTurnActivity, readTurnActivityMs, recordTurnActivity } from "../..
13
13
  import { buildCancelledLedgerRow, createRunAbort, mintCancelledResult } from "../../run-cancel-context.js";
14
14
  import { recordResourceWindow } from "../../resource-window.js";
15
15
  import { redactSecrets } from "../../trace/redact.js";
16
- import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData, liveIdentityFields } from "../../trace/project.js";
16
+ import { liveToolEndForBacklog } from "../../trace/tool-output-media.js";
17
+ import { contextUsageEventData, turnEndEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskStartedEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestFaceEventData, toolRosterDeltaEventData, toolProgressEventData, toolDisclosureEventData, humanInputEventData, appendModelUsageDelta, attachModelUsage, textEndEventData, liveIdentityFields } from "../../trace/project.js";
17
18
  import { cascadeConfig, runMeta } from "../run-meta.js";
18
- import { idemCallerOf, idemKeyOf, inflightKeyOf } from "../idempotency.js";
19
+ import { idemCallerOf, inflightKeyOf, submissionIdemOf } from "../idempotency.js";
19
20
  import { replayOfExistingRun } from "../submit-replay.js";
20
21
  import { sendJson, sendError, sseHeaders } from "../send.js";
21
22
  import { collectSsePreamble } from "../sse-log.js";
@@ -37,7 +38,8 @@ const DISCONNECT_PARK_GRACE_MS = 10_000;
37
38
  function sseData(res, payload) {
38
39
  const anchor = payload.eventId;
39
40
  const idLine = typeof anchor === "string" && anchor.length > 0 ? `id: ${anchor}\n` : "";
40
- res.write(`${idLine}data: ${JSON.stringify(payload)}\n\n`);
41
+ const frame = payload.type === "tool_end" ? liveToolEndForBacklog(payload, res.writableLength) : payload;
42
+ res.write(`${idLine}data: ${JSON.stringify(frame)}\n\n`);
41
43
  }
42
44
  async function mintSubmitExitResult(result, checkpoints) {
43
45
  return redactTerminalResult(sealResultForExit(await graftParkToolCallId(result, checkpoints)));
@@ -74,7 +76,7 @@ const MULTI_ATTEMPT_NO_EVENT_CHANNEL_NOTICE = "verify / cascade ran with their n
74
76
  export async function handleTasks(req, res, match, ctx) {
75
77
  const { deps } = ctx;
76
78
  const { idemCache, inflightRuns, steerableRuns, counters } = ctx.registry;
77
- const { rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
79
+ const { readJson, rateLimited, quotaExceeded, leaseDenied, usageWindowDenied } = ctx.helpers;
78
80
  const { prepareSpec, finalizeTaskResult } = ctx.legs;
79
81
  const reqState = ctx.req;
80
82
  const source = ctx.req.source;
@@ -97,12 +99,13 @@ export async function handleTasks(req, res, match, ctx) {
97
99
  {
98
100
  const rawIdem = headerStr(req.headers["idempotency-key"]);
99
101
  const submitter = gatedPrincipal(req, deps.config);
100
- const idemKey = rawIdem ? idemKeyOf(rawIdem, idemCallerOf(req, ctx), "run") : undefined;
101
- const inflightKey = idemKey ? inflightKeyOf(idemKey, "sync") : undefined;
102
+ const keyedBody = rawIdem ? await readJson(req) : undefined;
103
+ const idem = rawIdem ? submissionIdemOf(rawIdem, idemCallerOf(req, ctx), keyedBody) : undefined;
104
+ const inflightKey = idem ? inflightKeyOf(idem, "sync") : undefined;
102
105
  const cached = inflightKey ? idemCache.peek(inflightKey) : undefined;
103
- const keyedRow = idemKey && !cached && deps.runStore ? await deps.runStore.getRunByIdem(idemKey) : undefined;
106
+ const keyedRow = idem && !cached && deps.runStore ? await deps.runStore.getRunByIdem(idem.key) : undefined;
104
107
  if (keyedRow && deps.runStore) {
105
- const replay = await replayOfExistingRun(keyedRow, { principal: submitter }, "sync", deps, deps.runStore);
108
+ const replay = await replayOfExistingRun(keyedRow, { principal: submitter, idem }, "sync", deps, deps.runStore);
106
109
  if (liveStream && replay.status === 200) {
107
110
  sseHeaders(res, { "x-task-id": keyedRow.taskId });
108
111
  writeMetaFrame(res, keyedRow.taskId, keyedRow.sessionId);
@@ -141,7 +144,7 @@ export async function handleTasks(req, res, match, ctx) {
141
144
  }
142
145
  if (rateLimited(req, res) || quotaExceeded(req, res) || (await leaseDenied(req, res)) || (await usageWindowDenied(req, res)))
143
146
  return;
144
- const prepared = await prepareSpec(req, res, undefined, undefined, { liveStream });
147
+ const prepared = await prepareSpec(req, res, keyedBody, undefined, { liveStream });
145
148
  if (!prepared)
146
149
  return;
147
150
  const principal = prepared.auth?.principal;
@@ -257,15 +260,13 @@ export async function handleTasks(req, res, match, ctx) {
257
260
  };
258
261
  if (deps.runStore && prepared.spec.sessionId) {
259
262
  const tid = earlyDurableTid ?? uuidv7();
260
- const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idemKey: idemKey ?? null });
263
+ const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idem: idem ?? null });
261
264
  if (created.ok)
262
265
  clearTurnActivity(tid);
263
266
  if (created.ok)
264
267
  recordResourceWindow(tid, prepared.spec.resourceSuspend);
265
- if (created.ok)
266
- deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
267
268
  if (created.existing) {
268
- const replay = await replayOfExistingRun(created.existing, { principal: submitter }, "sync", deps, deps.runStore);
269
+ const replay = await replayOfExistingRun(created.existing, { principal: submitter, idem }, "sync", deps, deps.runStore);
269
270
  sseData(res, { type: "done", result: doneResultOfOutcome(replay), replay: true });
270
271
  return replay;
271
272
  }
@@ -389,6 +390,9 @@ export async function handleTasks(req, res, match, ctx) {
389
390
  if (t === "task_progress") {
390
391
  sseData(res, { type: "task_progress", ...taskProgressEventData(e) });
391
392
  }
393
+ else if (t === "task_started") {
394
+ sseData(res, { type: "task_started", ...taskStartedEventData(e) });
395
+ }
392
396
  else if (t === "tool_start") {
393
397
  const tsv = e;
394
398
  sseData(res, { type: "tool_start", ...toolStartEventData(tsv) });
@@ -457,6 +461,7 @@ export async function handleTasks(req, res, match, ctx) {
457
461
  userMsgEntryId = ev.entryId;
458
462
  const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
459
463
  await deps.userInputLedger?.observeLegEvent(liveStream, ev);
464
+ prepared.acceptance.observe(ev);
460
465
  if (ev.type === "done") {
461
466
  finalResult = await mintSubmitExitResult(ev.result, deps.checkpointStore);
462
467
  if (durableTaskId && finalResult)
@@ -464,7 +469,7 @@ export async function handleTasks(req, res, match, ctx) {
464
469
  if (causeAtDone !== undefined && finalResult !== undefined) {
465
470
  finalResult = mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId ?? "", startedAtMs: streamLegStartedAt, message: "cancelled by user", cause: causeAtDone, engineResult: finalResult });
466
471
  }
467
- finalizeTaskResult(ev.result, principal, prepared.spec.objective, prepared.spec.sessionId);
472
+ finalizeTaskResult(ev.result, principal, prepared.spec.objective);
468
473
  noteRunOutcome(finalResult);
469
474
  putRewindAnchor();
470
475
  settleFleet(outwardPlane(finalResult.terminal).status, fleetRunResiduals(finalResult));
@@ -516,6 +521,9 @@ export async function handleTasks(req, res, match, ctx) {
516
521
  else if (ev.type === "task_progress") {
517
522
  sseData(res, { type: "task_progress", ...taskProgressEventData(ev) });
518
523
  }
524
+ else if (ev.type === "task_started") {
525
+ sseData(res, { type: "task_started", ...taskStartedEventData(ev) });
526
+ }
519
527
  else if (ev.type === "compacted") {
520
528
  sseData(res, { type: "compacted", ...compactedEventData(ev) });
521
529
  }
@@ -830,19 +838,18 @@ export async function handleTasks(req, res, match, ctx) {
830
838
  let durableHeartbeat;
831
839
  if (deps.runStore && prepared.spec.sessionId) {
832
840
  const tid = uuidv7();
833
- const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idemKey: idemKey ?? null });
841
+ const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), idem: idem ?? null });
834
842
  if (created.ok)
835
843
  clearTurnActivity(tid);
836
844
  if (created.ok)
837
845
  recordResourceWindow(tid, prepared.spec.resourceSuspend);
838
846
  if (created.existing)
839
- return await replayOfExistingRun(created.existing, { principal: submitter }, "sync", deps, deps.runStore);
847
+ return await replayOfExistingRun(created.existing, { principal: submitter, idem }, "sync", deps, deps.runStore);
840
848
  if (!created.ok)
841
849
  return { status: 409, body: await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, prepared.spec.sessionId, created.activeTaskId) };
842
850
  const placementReject = await commitPlacement(deps, deps.runStore, tid, prepared.bindingIntent);
843
851
  if (placementReject)
844
852
  return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
845
- deps.sessionTitler?.maybeTitle(prepared.spec.sessionId, prepared.spec.objective, typeof prepared.spec.model === "string" ? prepared.spec.model : prepared.spec.model?.id);
846
853
  durableTaskId = tid;
847
854
  }
848
855
  commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
@@ -885,8 +892,10 @@ export async function handleTasks(req, res, match, ctx) {
885
892
  const cascadeLeg = () => runCascade(prepared.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd));
886
893
  const plainLeg = async () => {
887
894
  const stream = prepared.runner.runTaskStream(specWithSignal);
888
- for await (const ev of stream)
895
+ for await (const ev of stream) {
889
896
  observeSyncLegEvent(ev);
897
+ prepared.acceptance.observe(ev);
898
+ }
890
899
  return stream.result();
891
900
  };
892
901
  result = await instructionLeg.run(prepared.spec.sessionId, () => withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg()));
@@ -933,7 +942,8 @@ export async function handleTasks(req, res, match, ctx) {
933
942
  }
934
943
  if (durableTaskId && deps.runStore)
935
944
  await deps.runStore.setTerminal(durableTaskId, syncPlane.status, exitResult, syncPlane.errorMessage ?? null);
936
- finalizeTaskResult(result, principal, prepared.spec.objective, prepared.spec.sessionId);
945
+ prepared.acceptance.settle(result);
946
+ finalizeTaskResult(result, principal, prepared.spec.objective);
937
947
  noteRunOutcome(result);
938
948
  return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult)) };
939
949
  };
@@ -13,6 +13,7 @@ import { sendJson, sendError, sseHeaders } from "../send.js";
13
13
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
14
14
  import { assertNever } from "../route-ctx.js";
15
15
  import * as facts from "../../capability-facts.js";
16
+ import { steerBodyKeyIssue } from "../../request-key-closure.js";
16
17
  export async function handleWorkflows(req, res, match, ctx) {
17
18
  const { deps } = ctx;
18
19
  const { runSessionAcceptOk } = ctx.helpers;
@@ -188,6 +189,13 @@ export async function handleWorkflowAgentSteer(req, res, match, ctx) {
188
189
  sendError(res, 400, "request.body_shape", "body must be { content: string (non-empty) }");
189
190
  return;
190
191
  }
192
+ {
193
+ const keyIssue = steerBodyKeyIssue(body, "content");
194
+ if (keyIssue) {
195
+ sendError(res, 400, "request.body_shape", keyIssue.message, { unknownKeys: keyIssue.unknownKeys, unsupportedKeys: keyIssue.unsupportedKeys });
196
+ return;
197
+ }
198
+ }
191
199
  if (body.content.length > STEER_IN_MAX_REQUEST_CHARS) {
192
200
  sendError(res, 413, "steer.content_too_large", `content exceeds ${STEER_IN_MAX_REQUEST_CHARS} characters (got ${body.content.length}); note only the first ${STEER_IN_MAX_CHARS} are delivered anyway`);
193
201
  return;
@@ -718,6 +718,10 @@ export interface ServiceDeploymentDeps {
718
718
  * (`boot/stage-10-http-server.ts` → `modelRefusals: () => deployment.modelRefusalsState?.()`)—— 一处铸、两出口。缺席 / 返回 undefined =
719
719
  * 没有可报的拒或没有配置管道 ⇒ `/health` 形状逐字不变(additive)。 */
720
720
  modelRefusalsState?: () => import("@sema-agent/settings-schema").ModelRefusalsSignal | undefined;
721
+ /** S-1457:`GET /v1/models.candidateRejected` 的取值口 —— 最近一次配置候选被判官整拒的结论(`boot/stage-10-http-server.ts` →
722
+ * `candidateRejectionState: () => webAdmin.candidateRejectionState()`;座 = web-admin 的 `candidateRejection`,`config_candidate_rejected` 日志行的去重键读同一份)。
723
+ * present-iff 当前生效配置是因候选被拒而回落的那一份;缺席 / 返回 undefined = 没有被拒的候选或没有配置管道 ⇒ `GET /v1/models` 不出该键(additive)。 */
724
+ candidateRejectionState?: () => import("../web-admin-client/candidate-gate.js").CandidateRejection | undefined;
721
725
  /** `/health.configHandoff`(只上 `/health`):在场 ⇔ 部署声明了耐久接力 ∧ 应用着的那一代没被确认(web-admin 接力账的 `configHandoffState()`;合成律
722
726
  * `fleet/engine-signal.ts` 的 `buildConfigHandoff`)。缺席 / 返回 undefined = 有接力、没有中心或本地腿 ⇒ 键缺席。 */
723
727
  configHandoffState?: () => import("../fleet/engine-signal.js").ConfigHandoff | undefined;
@@ -5,7 +5,6 @@ import { validatePendingSteer } from "@sema-agent/core";
5
5
  import {} from "../web-admin-client/facade.js";
6
6
  import { HttpError } from "../security.js";
7
7
  import {} from "../memory-sync.js";
8
- import { evictIfConflict } from "../runs.js";
9
8
  import {} from "../orchestration/workflow-agent-steer.js";
10
9
  import {} from "../orchestration/workflow-completion-inbox.js";
11
10
  import {} from "../fleet/fleet-bus.js";
@@ -113,9 +112,9 @@ export function createHttpServer(rawDeps) {
113
112
  }
114
113
  };
115
114
  }
116
- const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, isFleetWide, sessionOwnerScope, sessionOwnerScopeForWrite, runOwnerOk, runSessionAcceptOk, readJson, readRawBody } = createRequestGuards({ deps });
115
+ const { quotaExceeded, ownerCostQuotaDenied, ownerFleetLeaseDenied, ownerLeaseDenied, ownerQuotaDenied, leaseDenied, usageWindowDenialFor, usageWindowDenied, rateLimited, safeDecode, isFleetWide, sessionOwnerScope, sessionOwnerScopeForWrite, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, readJson, readRawBody } = createRequestGuards({ deps });
117
116
  const { prepareSpec } = createAdmission({ deps, readJson, runnerFor });
118
- const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
117
+ const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, mayDiscloseRowError, resumePreempted, startFreshHostTurn, resumeWake } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, isFleetWide, runOwnerOk, sessionOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
119
118
  const { denyExpiredApprovals, redeemParkedAsk } = createApprovalSweeps({ deps, resumeCheckpoint, mayDiscloseRowError });
120
119
  const { handle } = createDispatch({ deps, noteActivity, routeCtxBase, counters });
121
120
  const server = http.createServer((req, res) => {
@@ -162,10 +161,9 @@ export function createHttpServer(rawDeps) {
162
161
  deps.metrics?.inc("cascade_total", { outcome: cascadeOutcome });
163
162
  deps.instrumentDegenerate?.(result);
164
163
  }
165
- function finalizeTaskResult(result, principal, objective, sessionId) {
164
+ function finalizeTaskResult(result, principal, objective) {
166
165
  recordTaskResult(result);
167
166
  deps.planCacheProbe?.record(principal, objective, result.terminal.kind === "completed");
168
- evictIfConflict(deps.runner, sessionId, result);
169
167
  }
170
168
  const isRunLiveOnThisReplica = (taskId) => inflightRuns.has(taskId) || steerableRuns.has(taskId);
171
169
  return Object.assign(server, { denyExpiredApprovals, isRunLiveOnThisReplica, redeemParkedAsk });
@@ -2,15 +2,19 @@
2
2
  * 提交幂等的**唯一回执函数** —— 一把提交钥匙命中了一条既有 run 行之后,这次提交拿到什么。
3
3
  *
4
4
  * ## 律(一条)
5
- * 幂等 = 资源行上的唯一键。`task_run` 上有两把:`idem_key`(`Idempotency-Key`,唯一铸法 `idemKeyOf(raw, caller, "run")`)
6
- * 与 `task_id`(调用方自铸的 `body.taskId`;A2A 由 messageId 派生)。哪一把命中、在哪条提交腿上命中、是请求进门时
7
- * 读行命中还是建行撞唯一键时观察到,一律经**本函数**作答 —— 提交面不再有第二个判官(进程内缓存只做在飞合流,
8
- * 见 `http/idempotency.ts`)。
5
+ * 幂等 = 同一调用方、同一键、**同一请求**,执法点 = 资源行上的唯一键。`task_run` 上有两把:`idem_key`(`Idempotency-Key`,唯一铸法
6
+ * `idemKeyOf(raw, caller, "run")`)与 `task_id`(调用方自铸的 `body.taskId`;A2A 由 messageId 派生);`idem_key` 旁边记着绑定它的那次
7
+ * 请求的体指纹(`idem_body_fingerprint`,`RunIdemBinding`)。哪一把命中、在哪条提交腿上命中、是请求进门时读行命中还是建行撞唯一键时
8
+ * 观察到,一律经**本函数**作答 —— 提交面不再有第二个判官(进程内缓存只做在飞合流,且只合同一请求,见 `http/idempotency.ts`)。
9
9
  *
10
- * ## 判定(两条,别的一律不放)
10
+ * ## 判定(三条,按序;别的一律不放)
11
11
  * · 这次提交自带 taskId 而命中的行**不是**那一条(`Idempotency-Key` 指向别的行)⇒ 冲突;
12
- * · 调用方读不到这一行(属主门 = `runOwnerReadable`,「这个调用方读得到这一行 run 吗」的唯一谓词)⇒ 冲突。
13
- * 冲突 ⇒ `409 conflict.run_exists`,体里**不回显**那条行的任何标识(不给跨租户预言机)。
12
+ * · 调用方读不到这一行(属主门 = `runOwnerReadable`,「这个调用方读得到这一行 run 吗」的唯一谓词)⇒ 冲突;
13
+ * 冲突 ⇒ `409 conflict.run_exists`,体里**不回显**那条行的任何标识(不给跨租户预言机)。先于下一条判:读不到的行不比体。
14
+ * · 这次提交带 `Idempotency-Key`(`caller.idem` 在场)、那一行绑的**就是这把键**,而它记着的请求体指纹与这次不同 ⇒
15
+ * `409 conflict.idempotency_payload_mismatch`(同形 `{ error, errorCode }`,不回显键与那一行);**不**回放首发、不新建、那一行不动。
16
+ * 行上没有指纹(列之前绑的键)⇒ 这一只绑上(同键先到者绑,`RunStore.bindIdemBodyFingerprint`)并放行。指纹属于绑定它的那把键:
17
+ * 按 taskId 命中而键是另一把(或行是无键提交建的)⇒ 不比、不写;只凭 `body.taskId` 的提交不带绑定,不比体。
14
18
  *
15
19
  * ## 回执两形(按提交腿)
16
20
  * · `durable`(`POST /v1/runs`、A2A `message/send`)—— `202 { taskId, sessionId, status }`(这条腿首答的既有形);
@@ -18,7 +22,7 @@
18
22
  * (读面 = `GET /v1/runs/:id` 的 200 体,同一只投影口 `readRunFace`)。同步腿的首答是引擎结果,而重放时手里只有行;
19
23
  * `replay: true` 是这一形的判别位。
20
24
  */
21
- import type { RunRecord } from "../plugins/store-contracts.js";
25
+ import type { RunIdemBinding, RunRecord } from "../plugins/store-contracts.js";
22
26
  import type { RunStore } from "../plugins/store-backend.js";
23
27
  import type { ServiceConfig } from "../config-types.js";
24
28
  import type { SubmitOutcome } from "./idempotency.js";
@@ -30,6 +34,9 @@ export interface ReplayCaller {
30
34
  principal: string | undefined;
31
35
  /** 这次提交自带的 taskId(`body.taskId`;A2A 派生值)。缺席 = 只凭 `Idempotency-Key` 命中。 */
32
36
  clientTaskId?: string;
37
+ /** 这次提交的账本绑定(键 + 请求体指纹;`RunIdemBinding`)。`undefined` = 这次提交没带 `Idempotency-Key`(只凭 `body.taskId`)⇒ 不比体。
38
+ * 必填位(值可为 `undefined`):每个调用点都得当面说出它有没有绑定,漏传是编译错误,不是静默不比。 */
39
+ idem: RunIdemBinding | undefined;
33
40
  }
34
41
  /** 一份提交回执是不是 {@link replayOfExistingRun} 铸的**冲突**体(判别位 = 机器码;铸点就在下面,码与它同文件)—— 把回执渲成别的协议形的
35
42
  * 调用方(A2A)按它分句,不另抄码。 */
@@ -8,6 +8,12 @@ export async function replayOfExistingRun(row, caller, face, deps, runStore) {
8
8
  if (!sameRun || !runOwnerReadable(caller.principal, row.owner, deps.config)) {
9
9
  return { status: 409, body: { error: "a run already exists for this submission's taskId or Idempotency-Key", errorCode: "conflict.run_exists" } };
10
10
  }
11
+ if (caller.idem !== undefined) {
12
+ const held = await runStore.bindIdemBodyFingerprint(row.taskId, caller.idem);
13
+ if (held !== null && held !== caller.idem.bodyFingerprint) {
14
+ return { status: 409, body: { error: "this Idempotency-Key was already used for a different request body — a retry must resend the same body; send a different request with a new Idempotency-Key", errorCode: "conflict.idempotency_payload_mismatch" } };
15
+ }
16
+ }
11
17
  switch (face) {
12
18
  case "durable":
13
19
  return { status: 202, body: { taskId: row.taskId, sessionId: row.sessionId, status: row.status } };
@@ -13,7 +13,7 @@ export interface TaskRequestBody {
13
13
  objective: string;
14
14
  sessionId?: string;
15
15
  /** Which deployment SCENARIO assembles this task's tools/prompt/skills (the keys of the worker's scenario
16
- * table — e.g. "default"/"code-review"/"scan"/"discuss"). An INTENT bounded by the deployment's own table AND,
16
+ * table — e.g. "default"/"code"/"code-review"/"scan"). An INTENT bounded by the deployment's own table AND,
17
17
  * on a governed worker, by the principal's center-resolved allowlist: an unknown name is a typed 400
18
18
  * `scenario_unknown`, a known-but-not-allowed one a typed 400 `scenario_not_allowed`; absent ⇒ the ruling's
19
19
  * assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`). A name
@@ -94,14 +94,6 @@ export interface TaskRequestBody {
94
94
  * a rung fails the gate (default = it didn't complete). Default off; mutually exclusive with `verify`;
95
95
  * not on /v1/tasks/stream. ⚠️ each rung is a cold re-run — use for read-only / idempotent tasks. */
96
96
  cascade?: boolean;
97
- /** Multi-lens review council (the `code-review` scenario's expensive tier) — `council:true` fans the review out
98
- * to N parallel lenses + an arbiter instead of the lead reviewing in-line; `debate:true` additionally runs the
99
- * L2 peer-debate rounds. Read on TWO seams and both matter: the scenario builder mounts `run_council`
100
- * (`capabilities/scenarios.ts` `codeReview`), and `boot/resolve-spec.ts` treats either flag as an EXPLICIT team
101
- * declaration — it widens the task's wall-clock tenancy budget (`resolveTaskLimits`) and suppresses the value
102
- * router's auto-escalation (`explicitTeam`). Only literal `true` counts on both seams. */
103
- council?: boolean;
104
- debate?: boolean;
105
97
  /** Work-view correlation id (optional): groups the runs of one logical task across both client doors
106
98
  * (MCP façade / portal) so a fragmented set of runs reads as one task. Opaque to the engine — only
107
99
  * persisted on the run row + filterable via `GET /v1/tasks?jobId=`. ≤64 chars (matches the column). */
@@ -317,6 +309,12 @@ export interface TaskRequestBody {
317
309
  /** [ref]② (core 1.314 工具面控制批): roster 真卸载清单(schema 不上 wire,manifest 收窄)。wire 工具名;
318
310
  * tighten-only(core 继承不变量 union 进子 spawn)。数组-of-string fail-loud at submit。 */
319
311
  excludeTools?: string[];
312
+ /** S-1448(core 7.49.0 #1623,[ref]):引擎的**可用性选择器** —— 这一 run 只用列出的工具(wire 名、别名、`Tool(content)` 整只、
313
+ * `mcp__srv` / `mcp__srv__*` / 工具段通配;`"*"` = 本层不设名单;`[]` = 什么都不给)。宿主**原样透传**进 `TaskSpec.allowTools`:
314
+ * 不算补集、不过滤、不与 `settings.permissions.allow`(免问)混词;没有任何工具答应的名字由引擎开跑前拒(`config.tool_face_control`,同步腿 400)。
315
+ * `excludeTools` 是同一只运算的减项(deny 赢)。值形:字符串数组(受理门 400 `request.field_invalid`,与 `excludeTools` 同一只门);
316
+ * 装着的引擎不认该座 ⇒ 400 `request.field_invalid` 点名所需引擎版本(不静默丢)。无租户门(收窄)。 */
317
+ allowTools?: string[];
320
318
  /** [ref]② (core 1.314): [ref] 延迟披露清单(占位上 wire,ToolSearch 激活)——cli「Workflow 默认开
321
319
  * 不暴露」的承载位。同 excludeTools 的校验/继承姿势。 */
322
320
  deferTools?: string[];
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { composeExecutionLane, assertExecutionLaneInvariants, type ExecutionLane
3
3
  export { createBrain } from "./brain.js";
4
4
  export { createSessionStore } from "./plugins/session-store.js";
5
5
  export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
6
+ export { RetiredSchemaPresentError } from "./plugins/one-time-migrations.js";
6
7
  export { CachingSessionStore, type CachingOptions } from "./plugins/caching-session-store.js";
7
8
  export type { RunRecord, RunEvent } from "./plugins/store-contracts.js";
8
9
  export { runInBackground } from "./runs.js";
@@ -15,7 +16,6 @@ export { Metrics, createMetrics, type Labels } from "./observability/metrics.js"
15
16
  export { RateLimiter, type RateDecision } from "./observability/rate-limit.js";
16
17
  export { GiteaClient, GitHubClient, createRepoClient, GIT_API_KINDS, isGitApiKind, repoToolsFor, parseRepo, type GitApiKind, type RepoCoords, type RepoReadClient } from "./capabilities/repo-tools.js";
17
18
  export { nowTool } from "./capabilities/builtin-tools.js";
18
- export { createCouncilTool } from "./capabilities/code-review-council.js";
19
19
  export { loadSkills, skillsForScenario, type LoadedSkill } from "./capabilities/skills.js";
20
20
  export { buildScenarios, selectScenario, type Scenario, type ScenarioBundle, type ScenarioContext, type ScenarioDeps, type ScenarioRequest, } from "./capabilities/scenarios.js";
21
21
  export { createTaskListLane, taskListKeyFor, MAX_TASK_LIST_SESSIONS, type TaskListLane, type TaskListLaneBackend, type TaskListLaneOpts } from "./boot/task-list-lane.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/exec
3
3
  export { createBrain } from "./brain.js";
4
4
  export { createSessionStore } from "./plugins/session-store.js";
5
5
  export { createStoreBackend } from "./plugins/store-backend.js";
6
+ export { RetiredSchemaPresentError } from "./plugins/one-time-migrations.js";
6
7
  export { CachingSessionStore } from "./plugins/caching-session-store.js";
7
8
  export { runInBackground } from "./runs.js";
8
9
  export { createHardenedVmRunner, runInHardenedVm } from "./orchestration/hardened-vm-runner.js";
@@ -13,7 +14,6 @@ export { Metrics, createMetrics } from "./observability/metrics.js";
13
14
  export { RateLimiter } from "./observability/rate-limit.js";
14
15
  export { GiteaClient, GitHubClient, createRepoClient, GIT_API_KINDS, isGitApiKind, repoToolsFor, parseRepo } from "./capabilities/repo-tools.js";
15
16
  export { nowTool } from "./capabilities/builtin-tools.js";
16
- export { createCouncilTool } from "./capabilities/code-review-council.js";
17
17
  export { loadSkills, skillsForScenario } from "./capabilities/skills.js";
18
18
  export { buildScenarios, selectScenario, } from "./capabilities/scenarios.js";
19
19
  export { createTaskListLane, taskListKeyFor, MAX_TASK_LIST_SESSIONS } from "./boot/task-list-lane.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * **服务自己为自己起的 run**(agent 钩子子代理 / leader 的集成沙盒与 grader 沙盒)的执行环境上下文 —— 它们不属于任何用户会话:
3
+ * 会话 id 是服务现铸的(`uuidv7()` / `leader-integ-<ts>`),会话店里没有这一行,也不该有。
4
+ *
5
+ * ── 为什么要在这里表态 ───────────────────────────────────────────────────────────────────────────
6
+ * k8s 车道的工作区空间按**会话住客**分区(`boot/execution-env.ts` `createK8sRunEnvFactory`):建环境时按 id 读会话店的住客,读不到(= 没有这只会话)
7
+ * 就报一行 `workspace_space_unavailable`、开着缓存卷时再计一次 fail-open。对用户的 run 这是真信号(会话被删了);对服务自己的 run 它是**恒真的噪声**
8
+ * —— 每只碰了手的内部 env 一行、计数被内部 runner 抬高。
9
+ *
10
+ * ── 怎么表态:不是新开关,是同一条法 ───────────────────────────────────────────────────────────────
11
+ * 工厂里已有的判别 `placementRootSessionId ≠ sessionId` 读作「这条 run 不拥有自己的空间」(委派子代 / 后台代理与父并发,共用一个空间 = 两个写者,
12
+ * 按法 off:不读住客、不出该行、不挂缓存卷,停驻车道照旧)。内部 run 同样没有自己的空间 —— 它的放置根不是某个用户会话,而是**服务自己**
13
+ * ({@link INTERNAL_RUN_PLACEMENT_ROOT})。所以内部 run 只需如实报它的放置根,工厂那一行判别原样把它读成「无空间」;本模块不加任何第二个判据。
14
+ * 放置根的另一个读者是 device 车道(按根会话找绑定):服务自己的根上没有任何绑定,与修前(根 = 它自己的现铸会话 id,同样没有绑定)同答 —— 拒。
15
+ */
16
+ import type { ExecutionEnvFactory, ExecutionEnvFactoryContext } from "@sema-agent/core";
17
+ /** 内部 run 的放置根:不是用户会话 id —— 提交口的会话 id 形律只收规范 uuid(`security.ts` `sessionIdShapeIssue`),这个词不是 uuid 形,调用方登记不出它。 */
18
+ export declare const INTERNAL_RUN_PLACEMENT_ROOT = "service:internal-run";
19
+ /** 服务为内部 run 直接调工厂时递的上下文(leader 的集成沙盒 / grader 沙盒):自己的现铸会话 id,放置根 = 服务自己。 */
20
+ export declare function buildInternalRunCtx(sessionId: string): ExecutionEnvFactoryContext;
21
+ /**
22
+ * 内部 Runner(agent 钩子子代理)的工厂:把 core 递来的上下文的放置根改报为服务自己 —— core 对这类 run 只知道「根 run,根 = 自己」,不知道它是服务
23
+ * 为自己起的。其余字段(`sessionId` / `taskId` …)原样过境。
24
+ */
25
+ export declare function createInternalRunEnvFactory(factory: ExecutionEnvFactory): ExecutionEnvFactory;
26
+ /** 内部 Runner 的 deps:`executionEnvFactory` 在场就按 {@link createInternalRunEnvFactory} 换装,其余键原样(缺席仍缺席)。 */
27
+ export declare function withInternalRunEnvs<D extends {
28
+ readonly executionEnvFactory?: ExecutionEnvFactory;
29
+ }>(deps: D): D;
30
+ //# sourceMappingURL=internal-run-env.d.ts.map
@@ -0,0 +1,11 @@
1
+ export const INTERNAL_RUN_PLACEMENT_ROOT = "service:internal-run";
2
+ export function buildInternalRunCtx(sessionId) {
3
+ return { sessionId, placementRootSessionId: INTERNAL_RUN_PLACEMENT_ROOT };
4
+ }
5
+ export function createInternalRunEnvFactory(factory) {
6
+ return (ctx) => factory({ ...ctx, placementRootSessionId: INTERNAL_RUN_PLACEMENT_ROOT });
7
+ }
8
+ export function withInternalRunEnvs(deps) {
9
+ return deps.executionEnvFactory === undefined ? deps : { ...deps, executionEnvFactory: createInternalRunEnvFactory(deps.executionEnvFactory) };
10
+ }
11
+ //# sourceMappingURL=internal-run-env.js.map
@@ -19,6 +19,7 @@ import { runLeaderTask, withLeaderLeg } from "./leader.js";
19
19
  import { attachRepairLoopDeps } from "./repair-wire.js";
20
20
  import { routePlanWithFallback, validateSubtasks } from "./planner.js";
21
21
  import { createEngineNoticeSeat } from "../boot/runner-deps.js";
22
+ import { buildInternalRunCtx } from "../internal-run-env.js";
22
23
  import { currentPrincipal } from "../observability/principal-context.js";
23
24
  import { redactHead } from "../observability/run-terminal.js";
24
25
  const REPAIR_LOOP_WIRED = true;
@@ -379,7 +380,7 @@ export function createLeaderRunner(cfg) {
379
380
  const provisionIntegrationSandbox = async () => {
380
381
  if (cfg.envFactory) {
381
382
  const integSessionId = `leader-integ-${Date.now()}`;
382
- const env = await cfg.envFactory({ sessionId: integSessionId, placementRootSessionId: integSessionId });
383
+ const env = await cfg.envFactory(buildInternalRunCtx(integSessionId));
383
384
  const reap = createEnvReaper(env, "leader integration sandbox");
384
385
  try {
385
386
  await sh(env)(body.seedCmd);
@@ -431,7 +432,7 @@ export function createLeaderRunner(cfg) {
431
432
  let env;
432
433
  if (cfg.envFactory) {
433
434
  const graderSessionId = `leader-grader-${Date.now()}`;
434
- env = await cfg.envFactory({ sessionId: graderSessionId, placementRootSessionId: graderSessionId });
435
+ env = await cfg.envFactory(buildInternalRunCtx(graderSessionId));
435
436
  }
436
437
  else {
437
438
  if (!cfg.e2bApiKey)
@@ -79,7 +79,7 @@ export class E2bLspManager {
79
79
  return undefined;
80
80
  }
81
81
  if (heal)
82
- await session.warmOpen(heal.carry).catch(() => ({ opened: 0, failed: heal.carry.length }));
82
+ await session.warmOpen(heal.carry).catch(() => ({ opened: 0, failed: heal.carry.length, deferred: 0 }));
83
83
  if (sessions.get(language) !== job) {
84
84
  await session.close().catch(() => undefined);
85
85
  return undefined;