@sema-agent/server 7.138.0 → 7.140.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 (332) hide show
  1. package/MIGRATION.md +457 -7
  2. package/README.md +7 -4
  3. package/README.zh-CN.md +6 -4
  4. package/USAGE.md +91 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/runner.js +1 -1
  8. package/dist/adoption/sql.js +1 -1
  9. package/dist/adoption/wire.d.ts +3 -3
  10. package/dist/approval-card.d.ts +26 -26
  11. package/dist/bench/s1/arms.d.ts +1 -1
  12. package/dist/bench/s1/arms.js +1 -3
  13. package/dist/bench/s1/live-deps.js +3 -2
  14. package/dist/bench/s1/row.d.ts +5 -2
  15. package/dist/bench/s1/row.js +2 -22
  16. package/dist/bench/s1/run-firm.js +2 -2
  17. package/dist/boot/adoption.js +1 -1
  18. package/dist/boot/device-lane.d.ts +8 -3
  19. package/dist/boot/device-lane.js +7 -2
  20. package/dist/boot/execution-env.d.ts +4 -2
  21. package/dist/boot/execution-env.js +28 -34
  22. package/dist/boot/execution-lane.d.ts +9 -1
  23. package/dist/boot/execution-lane.js +6 -1
  24. package/dist/boot/governance-seams.d.ts +3 -2
  25. package/dist/boot/governance-seams.js +9 -11
  26. package/dist/boot/memory-boundary.d.ts +14 -51
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +15 -11
  29. package/dist/boot/memory-consolidation.js +25 -14
  30. package/dist/boot/resolve-spec.d.ts +4 -6
  31. package/dist/boot/resolve-spec.js +10 -13
  32. package/dist/boot/retention-lane.d.ts +5 -5
  33. package/dist/boot/retention-lane.js +0 -6
  34. package/dist/boot/runner-deps.d.ts +6 -4
  35. package/dist/boot/runner-deps.js +3 -1
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  40. package/dist/boot/stage-03-store-backend.js +2 -3
  41. package/dist/boot/stage-04-stores.d.ts +2 -6
  42. package/dist/boot/stage-04-stores.js +5 -5
  43. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  44. package/dist/boot/stage-05-execution-env.js +2 -2
  45. package/dist/boot/stage-06-runners.d.ts +2 -6
  46. package/dist/boot/stage-06-runners.js +2 -3
  47. package/dist/boot/stage-07-capability-layer.d.ts +2 -6
  48. package/dist/boot/stage-08-reapers.d.ts +2 -6
  49. package/dist/boot/stage-09-leader.d.ts +2 -6
  50. package/dist/boot/stage-09-leader.js +1 -2
  51. package/dist/boot/stage-10-http-server.d.ts +2 -6
  52. package/dist/boot/stage-10-http-server.js +7 -6
  53. package/dist/boot/stores.d.ts +3 -6
  54. package/dist/boot/stores.js +27 -40
  55. package/dist/boot/task-list-lane.d.ts +1 -1
  56. package/dist/boot/task-list-lane.js +1 -1
  57. package/dist/boot/web-admin.js +37 -11
  58. package/dist/capabilities/execution-lane.d.ts +2 -2
  59. package/dist/capabilities/mcp-probe.js +1 -1
  60. package/dist/capabilities/scenarios.d.ts +1 -1
  61. package/dist/capability-facts.d.ts +15 -7
  62. package/dist/capability-facts.js +8 -3
  63. package/dist/config-catalog.js +9 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +33 -37
  67. package/dist/config-provider.js +51 -30
  68. package/dist/config-types.d.ts +21 -7
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +46 -16
  71. package/dist/delegated-session-owner.d.ts +4 -3
  72. package/dist/delegated-session-owner.js +1 -1
  73. package/dist/deployment-shape.d.ts +10 -9
  74. package/dist/deployment-shape.js +4 -4
  75. package/dist/device-enrollment.d.ts +41 -2
  76. package/dist/device-enrollment.js +27 -12
  77. package/dist/device-store.d.ts +58 -85
  78. package/dist/device-store.js +19 -49
  79. package/dist/device-ws-hub.js +2 -1
  80. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  81. package/dist/exec-cut.d.ts +60 -0
  82. package/dist/exec-cut.js +36 -0
  83. package/dist/execution-lane-caps.d.ts +115 -105
  84. package/dist/execution-lane-caps.js +76 -82
  85. package/dist/execution-placement.d.ts +259 -0
  86. package/dist/execution-placement.js +199 -0
  87. package/dist/field-conflict.d.ts +3 -2
  88. package/dist/field-conflict.js +1 -0
  89. package/dist/hosted-posture.d.ts +2 -2
  90. package/dist/http/admission.js +8 -19
  91. package/dist/http/checkpoint-ctx.d.ts +21 -12
  92. package/dist/http/checkpoint-ctx.js +4 -2
  93. package/dist/http/claimed-run.d.ts +1 -1
  94. package/dist/http/durable-run-leg.js +9 -5
  95. package/dist/http/execution-context.d.ts +218 -0
  96. package/dist/http/execution-context.js +330 -0
  97. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  98. package/dist/http/memory-compliance-failure-reply.js +5 -0
  99. package/dist/http/memory-layer-reply.d.ts +30 -0
  100. package/dist/http/memory-layer-reply.js +24 -0
  101. package/dist/http/resume-legs.js +63 -36
  102. package/dist/http/retired-service-deps.d.ts +1 -1
  103. package/dist/http/retired-service-deps.js +1 -1
  104. package/dist/http/route-ctx.d.ts +14 -8
  105. package/dist/http/routes/adoption.js +1 -1
  106. package/dist/http/routes/background.d.ts +29 -1
  107. package/dist/http/routes/background.js +29 -2
  108. package/dist/http/routes/devices.d.ts +13 -19
  109. package/dist/http/routes/devices.js +21 -26
  110. package/dist/http/routes/memory-bundle.js +3 -0
  111. package/dist/http/routes/memory-compliance.js +8 -1
  112. package/dist/http/routes/memory-consolidation.js +12 -1
  113. package/dist/http/routes/memory-origin.js +14 -2
  114. package/dist/http/routes/memory-policy.js +5 -0
  115. package/dist/http/routes/session-sync.js +110 -15
  116. package/dist/http/routes/sessions.d.ts +6 -0
  117. package/dist/http/routes/sessions.js +143 -22
  118. package/dist/http/routes/side-query.d.ts +12 -4
  119. package/dist/http/routes/side-query.js +4 -5
  120. package/dist/http/routes/tasks.js +41 -33
  121. package/dist/http/server.d.ts +40 -44
  122. package/dist/http/server.js +13 -2
  123. package/dist/http/session-inherit-env.d.ts +3 -4
  124. package/dist/http/session-inherit-env.js +1 -1
  125. package/dist/http/wire-types.d.ts +8 -12
  126. package/dist/identity-source.d.ts +1 -1
  127. package/dist/identity-source.js +1 -1
  128. package/dist/index.d.ts +1 -0
  129. package/dist/lane-home-probe.d.ts +68 -42
  130. package/dist/lane-home-probe.js +53 -30
  131. package/dist/leader/wire.js +5 -4
  132. package/dist/memory-bundle-engine.d.ts +16 -9
  133. package/dist/memory-bundle-engine.js +29 -6
  134. package/dist/memory-operator-faces.d.ts +85 -30
  135. package/dist/memory-operator-faces.js +136 -30
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +29 -5
  141. package/dist/memory-scope.js +16 -4
  142. package/dist/memory-sync-client.d.ts +70 -18
  143. package/dist/memory-sync-client.js +215 -117
  144. package/dist/memory-sync.d.ts +1 -1
  145. package/dist/memory-sync.js +3 -2
  146. package/dist/observability/cost-taxonomy.d.ts +2 -2
  147. package/dist/observability/fail-open.d.ts +12 -0
  148. package/dist/observability/fail-open.js +12 -0
  149. package/dist/observability/run-terminal.js +0 -1
  150. package/dist/orchestration/workflow-notify-journal.js +4 -4
  151. package/dist/org-memory-admission.d.ts +4 -3
  152. package/dist/org-memory-admission.js +6 -6
  153. package/dist/parked-decide.d.ts +34 -9
  154. package/dist/parked-decide.js +12 -19
  155. package/dist/parked-revive-surface.d.ts +12 -15
  156. package/dist/parked-revive-surface.js +3 -6
  157. package/dist/peer-seat.d.ts +13 -7
  158. package/dist/peer-seat.js +37 -3
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  161. package/dist/plugins/approval-ask-store-file.js +11 -0
  162. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  163. package/dist/plugins/approval-ask-store-memory.js +12 -0
  164. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  165. package/dist/plugins/approval-ask-store-sql.js +44 -26
  166. package/dist/plugins/approval-nonce-store.js +4 -4
  167. package/dist/plugins/blob-backend.d.ts +4 -4
  168. package/dist/plugins/blob-backend.js +7 -7
  169. package/dist/plugins/breaker-state-sql.js +2 -2
  170. package/dist/plugins/caching-session-store.d.ts +6 -2
  171. package/dist/plugins/caching-session-store.js +9 -3
  172. package/dist/plugins/checkpoint-index-columns.js +2 -2
  173. package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
  174. package/dist/plugins/checkpoint-store-sql.js +21 -23
  175. package/dist/plugins/device-store-sql.d.ts +25 -51
  176. package/dist/plugins/device-store-sql.js +68 -122
  177. package/dist/plugins/ensure-index.d.ts +1 -2
  178. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  179. package/dist/plugins/execution-placement-sql.js +223 -0
  180. package/dist/plugins/file-history-store-sql.d.ts +9 -8
  181. package/dist/plugins/file-history-store-sql.js +9 -9
  182. package/dist/plugins/file-run-store.d.ts +6 -1
  183. package/dist/plugins/file-run-store.js +4 -0
  184. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  185. package/dist/plugins/fork-routing-session-store.js +16 -0
  186. package/dist/plugins/image-bake-store-sql.js +9 -9
  187. package/dist/plugins/image-index-sql.js +11 -11
  188. package/dist/plugins/leader-run-store-sql.js +4 -4
  189. package/dist/plugins/local-session-store.d.ts +149 -50
  190. package/dist/plugins/local-session-store.js +382 -107
  191. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  192. package/dist/plugins/mailbox-store-sql.js +4 -4
  193. package/dist/plugins/memory-engine-tidb.js +5 -5
  194. package/dist/plugins/memory-key-guards.d.ts +4 -0
  195. package/dist/plugins/memory-key-guards.js +1 -0
  196. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  197. package/dist/plugins/memory-run-store.d.ts +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +117 -8
  200. package/dist/plugins/one-time-migrations.js +408 -38
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  203. package/dist/plugins/outcome-ledger-sql.js +2 -2
  204. package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
  205. package/dist/plugins/permission-rule-store-sql.js +29 -32
  206. package/dist/plugins/pg-pool.d.ts +7 -3
  207. package/dist/plugins/pg-session-storage.d.ts +31 -20
  208. package/dist/plugins/pg-session-storage.js +120 -52
  209. package/dist/plugins/remote-env-adb.d.ts +18 -19
  210. package/dist/plugins/remote-env-adb.js +91 -66
  211. package/dist/plugins/remote-env-device.d.ts +23 -31
  212. package/dist/plugins/remote-env-device.js +19 -17
  213. package/dist/plugins/remote-env-e2b.d.ts +27 -21
  214. package/dist/plugins/remote-env-e2b.js +91 -45
  215. package/dist/plugins/remote-env-file-error.d.ts +3 -1
  216. package/dist/plugins/remote-env-file-error.js +9 -3
  217. package/dist/plugins/remote-env-host.d.ts +31 -34
  218. package/dist/plugins/remote-env-host.js +91 -72
  219. package/dist/plugins/remote-env-k8s.d.ts +13 -29
  220. package/dist/plugins/remote-env-k8s.js +63 -55
  221. package/dist/plugins/remote-env-local-docker.d.ts +24 -12
  222. package/dist/plugins/remote-env-local-docker.js +49 -61
  223. package/dist/plugins/remote-env-ssh.d.ts +11 -19
  224. package/dist/plugins/remote-env-ssh.js +74 -57
  225. package/dist/plugins/remote-shell.d.ts +100 -3
  226. package/dist/plugins/remote-shell.js +166 -2
  227. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  228. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  229. package/dist/plugins/retention-lane-store-sql.js +2 -2
  230. package/dist/plugins/retention-store-sql.d.ts +10 -7
  231. package/dist/plugins/retention-store-sql.js +13 -7
  232. package/dist/plugins/run-store-sql.d.ts +13 -3
  233. package/dist/plugins/run-store-sql.js +20 -15
  234. package/dist/plugins/schema-consistency.d.ts +21 -11
  235. package/dist/plugins/schema-consistency.js +15 -9
  236. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  237. package/dist/plugins/schema-drift-sentence.js +38 -0
  238. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  239. package/dist/plugins/schema-ensure-legs.js +2 -2
  240. package/dist/plugins/schema-live-columns.d.ts +1 -1
  241. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  242. package/dist/plugins/session-fork.d.ts +54 -0
  243. package/dist/plugins/session-fork.js +38 -1
  244. package/dist/plugins/session-row-sql.d.ts +22 -0
  245. package/dist/plugins/session-row-sql.js +81 -0
  246. package/dist/plugins/session-rules-column.d.ts +1 -1
  247. package/dist/plugins/session-rules-column.js +1 -1
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  250. package/dist/plugins/session-workspace-cell.js +1 -1
  251. package/dist/plugins/shared-memory-store-sql.js +2 -2
  252. package/dist/plugins/sql-driver.d.ts +10 -5
  253. package/dist/plugins/sql-driver.js +6 -6
  254. package/dist/plugins/sql-errors.js +1 -1
  255. package/dist/plugins/store-backend.d.ts +5 -7
  256. package/dist/plugins/store-backend.js +23 -11
  257. package/dist/plugins/store-contracts.d.ts +25 -5
  258. package/dist/plugins/task-list-store-sql.js +11 -11
  259. package/dist/plugins/tidb-pool.d.ts +12 -7
  260. package/dist/plugins/tidb-pool.js +13 -7
  261. package/dist/plugins/tidb-session-storage.js +4 -4
  262. package/dist/plugins/tidb-session-store.d.ts +57 -26
  263. package/dist/plugins/tidb-session-store.js +127 -59
  264. package/dist/plugins/tool-result-store-sql.d.ts +5 -4
  265. package/dist/plugins/tool-result-store-sql.js +21 -13
  266. package/dist/plugins/user-input-ledger-sql.js +2 -2
  267. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  268. package/dist/plugins/workflow-run-store-sql.js +19 -19
  269. package/dist/plugins/write-behind-counter.d.ts +2 -3
  270. package/dist/plugins/write-behind-counter.js +5 -5
  271. package/dist/project-memory.d.ts +9 -15
  272. package/dist/project-memory.js +5 -4
  273. package/dist/request-key-closure.js +1 -1
  274. package/dist/request-value-closure.d.ts +6 -7
  275. package/dist/request-value-closure.js +1 -3
  276. package/dist/resource-suspend.d.ts +14 -12
  277. package/dist/resource-suspend.js +1 -1
  278. package/dist/resource-window.d.ts +4 -3
  279. package/dist/run-local.d.ts +6 -0
  280. package/dist/run-local.js +34 -35
  281. package/dist/runs.d.ts +10 -5
  282. package/dist/runs.js +52 -66
  283. package/dist/security.d.ts +38 -12
  284. package/dist/security.js +8 -2
  285. package/dist/server-secret-env.d.ts +9 -5
  286. package/dist/session-children-retire.d.ts +27 -9
  287. package/dist/session-children-retire.js +9 -5
  288. package/dist/session-incarnation.d.ts +20 -10
  289. package/dist/session-incarnation.js +5 -3
  290. package/dist/session-policy-wire.d.ts +1 -1
  291. package/dist/session-row.d.ts +114 -5
  292. package/dist/session-row.js +55 -5
  293. package/dist/session-titler.d.ts +2 -2
  294. package/dist/sql-engine-posture.d.ts +1 -1
  295. package/dist/sql-engine-posture.js +1 -1
  296. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  297. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  298. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  299. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  300. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  301. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  302. package/dist/task-a2a.d.ts +1 -1
  303. package/dist/task-a2a.js +5 -6
  304. package/dist/task-cwd.d.ts +6 -1
  305. package/dist/task-mcp.d.ts +22 -9
  306. package/dist/task-mcp.js +17 -9
  307. package/dist/terminal.d.ts +27 -0
  308. package/dist/terminal.js +6 -0
  309. package/dist/tool-approval.d.ts +6 -0
  310. package/dist/tool-approval.js +8 -1
  311. package/dist/trace/project.d.ts +1 -0
  312. package/dist/trace/project.js +1 -0
  313. package/dist/trace/redact.d.ts +3 -1
  314. package/dist/trace/redact.js +80 -28
  315. package/dist/trace/self-minted-wire.d.ts +169 -2
  316. package/dist/trace/self-minted-wire.js +54 -1
  317. package/dist/url-refusal.d.ts +1 -1
  318. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  319. package/dist/web-admin-client/apply-effective.js +20 -10
  320. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  321. package/dist/web-admin-client/candidate-gate.js +1 -1
  322. package/dist/web-admin-client/http-client.d.ts +5 -4
  323. package/dist/web-admin-client/http-client.js +2 -2
  324. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  325. package/dist/web-admin-client/read-warnings.js +5 -9
  326. package/dist/web-admin-client/skills-mcp.js +3 -2
  327. package/dist/web-admin-client/types.d.ts +6 -0
  328. package/package.json +3 -3
  329. package/wire-changes/server@7.139.0.json +348 -0
  330. package/wire-changes/server@7.140.0.json +184 -0
  331. package/dist/http/device-placement.d.ts +0 -175
  332. package/dist/http/device-placement.js +0 -145
@@ -7,6 +7,8 @@ import { seatText } from "../../trace/exit-seat.js";
7
7
  import { recordFailOpen } from "../../observability/fail-open.js";
8
8
  import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
9
9
  import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
10
+ import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
11
+ import { foldPlaneStatuses } from "../../memory-operator-faces.js";
10
12
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
11
13
  import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
12
14
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
@@ -18,6 +20,10 @@ import * as facts from "../../capability-facts.js";
18
20
  import { rosterAgentId } from "./agents-roster.js";
19
21
  import { projectSessionListRow } from "./sessions-list.js";
20
22
  import { readSessionRowPatch } from "../../session-row.js";
23
+ import { forkPathOf } from "../execution-context.js";
24
+ import { executionPlacementView } from "../../execution-placement.js";
25
+ import { ForkTargetUnsettledError, forkKeyOf } from "../../plugins/session-fork.js";
26
+ import { fieldConflictFacts } from "../../field-conflict.js";
21
27
  import { buildBoundedToolNames } from "../../panel-tool-names.js";
22
28
  import { projectMcpAuthorization } from "../../mcp-authorization-face.js";
23
29
  const MCP_STATUS_TTL_MS = 5_000;
@@ -172,7 +178,7 @@ export async function handleSessions(req, res, match, ctx) {
172
178
  mcpStatusCache.clear();
173
179
  const panel = (async () => {
174
180
  const stopDial = new AbortController();
175
- const matP = materializeMcpTools(specs, principal, undefined, undefined, undefined, deps.mcpRevocations, undefined, undefined, undefined, stopDial.signal, undefined, deps.peerDesk.seat.answer);
181
+ const matP = materializeMcpTools(specs, principal, undefined, undefined, undefined, deps.mcpRevocations, undefined, undefined, undefined, stopDial.signal, undefined, deps.peerDesk.seat);
176
182
  const mat = await raceDeadline(matP, MCP_DIAL_DEADLINE_MS).catch(() => null);
177
183
  if (!mat) {
178
184
  stopDial.abort();
@@ -221,7 +227,7 @@ export async function handleSessions(req, res, match, ctx) {
221
227
  if (a2aStatusCache.size > 1024)
222
228
  a2aStatusCache.clear();
223
229
  const panel = (async () => {
224
- const matP = materializeA2aTools(specs, principal, undefined, undefined, deps.peerDesk.seat.answer);
230
+ const matP = materializeA2aTools(specs, principal, undefined, undefined, deps.peerDesk.seat);
225
231
  let timer;
226
232
  const mat = await Promise.race([matP.catch(() => null), new Promise((r) => { timer = setTimeout(() => r(null), A2A_MATERIALIZE_TIMEOUT_MS); })]);
227
233
  if (timer)
@@ -286,6 +292,29 @@ export async function handleSessions(req, res, match, ctx) {
286
292
  return;
287
293
  }
288
294
  }
295
+ {
296
+ if (match.decl.id === "session-execution-placement") {
297
+ const placementFact = facts.executionPlacementRead(deps);
298
+ if (!placementFact.available) {
299
+ sendError(res, 501, "capability.session_store_required", "the execution placement read requires a durable session store (DB_BACKEND=mysql|pg|local)");
300
+ return;
301
+ }
302
+ const placementSession = safeDecode(match.params.sessionId);
303
+ if (placementSession === null) {
304
+ sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
305
+ return;
306
+ }
307
+ const reading = await placementFact.seat.read(placementSession);
308
+ if (reading === undefined) {
309
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
310
+ return;
311
+ }
312
+ if (!explicitOperatorOk(gatedPrincipal(req, deps.config), deps.config) && !sessionOwnerOk(req, res, await sessionOwnerCellOf(deps.sessionStorage, placementSession, reading.occupant.owner)))
313
+ return;
314
+ sendJson(res, 200, executionPlacementView(reading.cell));
315
+ return;
316
+ }
317
+ }
289
318
  {
290
319
  if (match.decl.id === "session-events") {
291
320
  const evFact = facts.sessionEventStream(deps);
@@ -524,11 +553,17 @@ export async function handleSessions(req, res, match, ctx) {
524
553
  sendError(res, 400, "request.field_invalid", "allowUnevidenced must be a boolean when present");
525
554
  return;
526
555
  }
556
+ const at = readLayerSeat(res, Object.prototype.hasOwnProperty.call(raw, "scope"), raw.scope);
557
+ if (at === null)
558
+ return;
559
+ const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
527
560
  try {
528
- sendJson(res, 200, await faces.erase({ requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) }));
561
+ const input = { requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) };
562
+ sendJson(res, 200, await (at !== undefined ? faces.erase(input, at) : faces.erase(input)));
529
563
  }
530
564
  catch (err) {
531
- const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
565
+ if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld }))
566
+ return;
532
567
  if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }, deps.logger))
533
568
  return;
534
569
  deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
@@ -560,20 +595,27 @@ export async function handleSessions(req, res, match, ctx) {
560
595
  sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
561
596
  return;
562
597
  }
598
+ const query = new URLSearchParams(match.search);
599
+ const at = readLayerSeat(res, query.has("scope"), query.get("scope"));
600
+ if (at === null)
601
+ return;
563
602
  try {
564
603
  const { memoryStatusCache } = ctx.local.sessions;
565
- let entry = memoryStatusCache.get(sessionId);
566
- if (!entry || Date.now() - entry.at > MEMORY_STATUS_TTL_MS) {
604
+ const statusOf = (store) => {
605
+ const cacheKey = JSON.stringify([sessionId, store.controlPlane]);
606
+ const cached = memoryStatusCache.get(cacheKey);
607
+ if (cached && Date.now() - cached.at <= MEMORY_STATUS_TTL_MS)
608
+ return cached.status;
567
609
  if (memoryStatusCache.size > 1024)
568
610
  memoryStatusCache.clear();
569
- const status = face.status(sessionId);
611
+ const status = store.status(sessionId);
570
612
  const fresh = { at: Date.now(), status };
571
- memoryStatusCache.set(sessionId, fresh);
572
- status.catch(() => { if (memoryStatusCache.get(sessionId) === fresh)
573
- memoryStatusCache.delete(sessionId); });
574
- entry = fresh;
575
- }
576
- const s = await entry.status;
613
+ memoryStatusCache.set(cacheKey, fresh);
614
+ status.catch(() => { if (memoryStatusCache.get(cacheKey) === fresh)
615
+ memoryStatusCache.delete(cacheKey); });
616
+ return status;
617
+ };
618
+ const s = foldPlaneStatuses(await Promise.all(face.storesAt(at).map(statusOf)));
577
619
  sendJson(res, 200, {
578
620
  sessionId,
579
621
  autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
@@ -585,6 +627,8 @@ export async function handleSessions(req, res, match, ctx) {
585
627
  });
586
628
  }
587
629
  catch (err) {
630
+ if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld: (message) => deps.logger?.warn?.("session_memory_status_refused", { sessionId, err: message }) }))
631
+ return;
588
632
  deps.logger?.warn?.("session_memory_status_failed", { sessionId, err: String(err) });
589
633
  sendError(res, 500, "internal.error", "session memory status read failed");
590
634
  }
@@ -675,13 +719,25 @@ export async function handleSessions(req, res, match, ctx) {
675
719
  sendError(res, 400, read.problem.code, read.problem.message);
676
720
  return;
677
721
  }
678
- const row = await store.patchSessionMeta(sessionId, read.patch, face);
679
- if (row === undefined) {
680
- sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
681
- return;
722
+ const outcome = await store.writeSessionRowCells(sessionId, read.patch, { writer: "client", row: { row: face }, ...(read.expectedRowTag !== undefined ? { rowTag: read.expectedRowTag } : {}) });
723
+ switch (outcome.kind) {
724
+ case "written":
725
+ sendJson(res, 200, projectSessionListRow(outcome.row));
726
+ return;
727
+ case "changed":
728
+ sendError(res, 409, "conflict.session_row_changed", "the session row changed since the rowTag you sent was read — nothing was written; currentRow is the row as it stands now: decide again, then send its rowTag", { currentRow: projectSessionListRow(outcome.row) });
729
+ return;
730
+ case "mirror":
731
+ sendError(res, 409, "conflict.session_mirror", "this session is a read-only mirror of a session whose writer is elsewhere — it cannot be renamed or pinned here; fork it (forkPath) to continue here; nothing was written", { forkPath: forkPathOf(sessionId) });
732
+ return;
733
+ case "absent":
734
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
735
+ return;
736
+ default: {
737
+ const unknownOutcome = outcome;
738
+ throw new Error(`PATCH /v1/sessions/:id: no answer for the row write outcome ${JSON.stringify(unknownOutcome)}`);
739
+ }
682
740
  }
683
- sendJson(res, 200, projectSessionListRow(row));
684
- return;
685
741
  }
686
742
  {
687
743
  if (match.decl.id === "session-fork") {
@@ -712,12 +768,57 @@ export async function handleSessions(req, res, match, ctx) {
712
768
  return;
713
769
  }
714
770
  const newOwner = scope.fleetWide ? (owner ?? null) : scope.gateOwner;
715
- const newId = await fork(sourceId, newOwner);
716
- if (newId === null) {
771
+ const keyed = readForkTarget(await readJson(req), sourceId);
772
+ if (keyed.kind === "refused") {
773
+ sendError(res, 400, keyed.errorCode, keyed.message, keyed.facts);
774
+ return;
775
+ }
776
+ if (keyed.kind === "none") {
777
+ const newId = await fork(sourceId, newOwner);
778
+ if (newId === null) {
779
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
780
+ return;
781
+ }
782
+ sendJson(res, 200, { sessionId: newId });
783
+ return;
784
+ }
785
+ const forkTo = forkFact.sessionStorage.forkTo?.bind(forkFact.sessionStorage);
786
+ if (forkTo === undefined) {
787
+ sendError(res, 501, "capability.session_store_required", "a keyed fork (forkId + forkNonce) requires a session store that creates the target row atomically (DB_BACKEND=mysql|pg|local)");
788
+ return;
789
+ }
790
+ let outcome;
791
+ try {
792
+ outcome = await forkTo(sourceId, newOwner, { id: keyed.forkId, key: forkKeyOf(sourceId, keyed.forkNonce) });
793
+ }
794
+ catch (e) {
795
+ if (e instanceof ForkTargetUnsettledError) {
796
+ sendError(res, 503, "state.fork_target_unsettled", e.message, { permanence: "transient" });
797
+ return;
798
+ }
799
+ throw e;
800
+ }
801
+ if (outcome === null) {
717
802
  sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
718
803
  return;
719
804
  }
720
- sendJson(res, 200, { sessionId: newId });
805
+ switch (outcome.outcome) {
806
+ case "created":
807
+ sendJson(res, 200, { sessionId: outcome.id });
808
+ return;
809
+ case "replayed":
810
+ sendJson(res, 200, { sessionId: outcome.id, replayed: true });
811
+ return;
812
+ case "occupied":
813
+ if (!outcome.sameOwner) {
814
+ sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
815
+ return;
816
+ }
817
+ sendError(res, 409, "conflict.fork_id_taken", "forkId already names another session of yours (a different fork, or a session that was not created by this fork request) — pick a new forkId and forkNonce; nothing was forked", { forkId: keyed.forkId });
818
+ return;
819
+ default:
820
+ assertNever(outcome);
821
+ }
721
822
  return;
722
823
  }
723
824
  }
@@ -853,6 +954,7 @@ export const SESSIONS_ROUTES = [
853
954
  { id: "session-mcp", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/mcp$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
854
955
  { id: "session-a2a", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/a2a$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
855
956
  { id: "session-head", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/head$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
957
+ { id: "session-execution-placement", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/execution-placement$/, label: "/v1/sessions/:id/execution-placement", credentialGated: ["GET"], methods: ["GET"] },
856
958
  { id: "session-events", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/events$/, label: "/v1/sessions/:id/$verb", methods: ["GET"], subscription: true },
857
959
  { id: "session-fork", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/fork$/, label: "/v1/sessions/:id/$verb", methods: ["POST"] },
858
960
  { id: "session-policy-get", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/policy$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
@@ -864,4 +966,23 @@ export const SESSIONS_ROUTES = [
864
966
  { id: "session-patch", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["PATCH"] },
865
967
  { id: "session-delete", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["DELETE"] },
866
968
  ];
969
+ function readForkTarget(raw, sourceId) {
970
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
971
+ return { kind: "refused", errorCode: "request.field_invalid", message: "the fork body must be an object { forkId?, forkNonce? }" };
972
+ const { forkId, forkNonce } = raw;
973
+ if (forkId === undefined && forkNonce === undefined)
974
+ return { kind: "none" };
975
+ if (forkId === undefined || forkNonce === undefined) {
976
+ return { kind: "refused", errorCode: "request.field_conflict", message: "forkId and forkNonce come together — send both (a keyed, retryable fork) or neither (a fork under a freshly minted id)", facts: fieldConflictFacts("fork_target", ["forkId", "forkNonce"]) };
977
+ }
978
+ if (typeof forkId !== "string" || !isUuidShape(forkId))
979
+ return { kind: "refused", errorCode: "request.id_invalid", message: "forkId must be a canonical uuid (the id the forked session will have)" };
980
+ if (forkId === sourceId)
981
+ return { kind: "refused", errorCode: "request.field_invalid", message: "forkId must name a new session — it equals the session being forked" };
982
+ if (typeof forkNonce !== "string" || forkNonce.length === 0 || forkNonce.length > FORK_NONCE_MAX_CHARS) {
983
+ return { kind: "refused", errorCode: "request.field_invalid", message: `forkNonce must be a non-empty string of at most ${FORK_NONCE_MAX_CHARS} characters (the caller's key for this fork request; a retry sends the same one)` };
984
+ }
985
+ return { kind: "keyed", forkId, forkNonce };
986
+ }
987
+ const FORK_NONCE_MAX_CHARS = 255;
867
988
  //# sourceMappingURL=sessions.js.map
@@ -8,10 +8,18 @@
8
8
  */
9
9
  import type { IncomingMessage, ServerResponse } from "node:http";
10
10
  import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
11
- /** 表内=core 的前置校验形(客户端可改请求)⇒ 400;表外一律 500。 */
12
- export declare function isCorePreflightValidationError(message: string): boolean;
13
- /** 供跨仓锚门读:门要拿这张表去装机的 core 里逐条对账(见上方 doc)。 */
14
- export declare const CORE_PREFLIGHT_VALIDATION_ANCHORS: readonly string[];
11
+ /**
12
+ * [ref] —— core 前置校验异常的分类(400 客户端可改 / 500 我方缺陷):**只读引擎的码**。
13
+ * 引擎角色族的拒(未知模型引用 / 不是模型引用 / 角色词出表 / 角色链给不出模型)一律带码 `config.unknown_role`(`detail.reason` 分臂;
14
+ * core 7.60.0 [ref] 起最后一臂也带码)—— 这是引擎写明的判别位,本路由的 400 只认它;句子不读(修前是一张按英文开头匹配的两行前缀表,
15
+ * 引擎改一个字就会把客户端错误静默翻成 500)。失败方向:别的码 / 没有码一律 500(响亮、可告警、可重试),绝不把不认识的异常猜成客户端错误。
16
+ * 跨仓锚:test/side-query-http.test.ts 调装机 core 的两个导出解析器真抛,拿真错误过本谓词。
17
+ *
18
+ * 🪦 原表还有第三臂 `requires a non-empty messages array`(core `core/side-query.js`)—— 不可达(本路由在调 core 之前就有
19
+ * `!Array.isArray(body.messages) || body.messages.length === 0 ⇒ 400 request.body_shape` 的前拦),已删;前拦一旦被摘,
20
+ * side-query-http 的「空/非数组 messages → 400 request.body_shape」两格先红。
21
+ */
22
+ export declare function isCorePreflightValidationError(err: unknown): boolean;
15
23
  /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。 */
16
24
  export declare function handleSideQuery(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SideQueryRouteId>, ctx: RouteCtx): Promise<void>;
17
25
  export declare const SIDE_QUERY_ROUTES: readonly [{
@@ -4,15 +4,14 @@ import { echoCallerText } from "../../caller-text-echo.js";
4
4
  import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../../model-select.js";
5
5
  import { echoModelRef, modelUnavailableMessage } from "../../model-entry-refusal.js";
6
6
  import { redactSecrets } from "../../trace/redact.js";
7
+ import { errorCodeOf } from "../../observability/err-text.js";
7
8
  import { admitProviderEcho, redactErrorMessage } from "../../observability/run-terminal.js";
8
9
  import { sendJson, sendError, sendHttpError, msg } from "../send.js";
9
10
  import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
10
11
  import { assertNever } from "../route-ctx.js";
11
- const CORE_PREFLIGHT_VALIDATION_PREFIXES = ["Unknown model ref ", "No model for role "];
12
- export function isCorePreflightValidationError(message) {
13
- return CORE_PREFLIGHT_VALIDATION_PREFIXES.some((prefix) => message.startsWith(prefix));
12
+ export function isCorePreflightValidationError(err) {
13
+ return errorCodeOf(err) === "config.unknown_role";
14
14
  }
15
- export const CORE_PREFLIGHT_VALIDATION_ANCHORS = CORE_PREFLIGHT_VALIDATION_PREFIXES;
16
15
  const MODEL_ROLE_ECHO_CHARS = 80;
17
16
  function isModelRole(value) {
18
17
  return typeof value === "string" && MODEL_ROLES.includes(value);
@@ -105,7 +104,7 @@ export async function handleSideQuery(req, res, match, ctx) {
105
104
  deps.metrics?.inc?.("side_query_total", { result: "error" });
106
105
  if (!res.writableEnded && !ac.signal.aborted) {
107
106
  const m = msg(e);
108
- const isValidation = isCorePreflightValidationError(m);
107
+ const isValidation = isCorePreflightValidationError(e);
109
108
  sendError(res, isValidation ? 400 : 500, isValidation ? "request.field_invalid" : "internal.error", redactSecrets(m));
110
109
  }
111
110
  }
@@ -2,7 +2,7 @@ import { redactErrorMessage, redactHead, redactTerminalResult } from "../../obse
2
2
  import { runWithVerification, runCascade, taskNotificationDedupKey, uuidv7 } from "@sema-agent/core";
3
3
  import { heldFor } from "../../peer-seat.js";
4
4
  import { graftParkToolCallId, markChildrenStoppedByUserOnAbort, syncRefusalHttpStatus, zeroWorkRefusalFacts, releaseKeyIfZeroWorkRefusal, createPromptCommitReader, HEARTBEAT_MS } from "../../runs.js";
5
- import { sealResultForExit, outwardPlane } from "../../terminal.js";
5
+ import { atHostBoundary, sealResultForExit, outwardPlane } from "../../terminal.js";
6
6
  import { withPrincipal } from "../../observability/principal-context.js";
7
7
  import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../../fleet/fleet-bus.js";
8
8
  import { defaultSubagentTailBus } from "../../fleet/subagent-tail-bus.js";
@@ -24,7 +24,8 @@ import { collectSsePreamble } from "../sse-log.js";
24
24
  import { buildApprovalPreamble, buildApprovalPreambleSseFrames } from "../../approval-card.js";
25
25
  import { approvalPreambleReplays, createApprovalCardEmitter, declaredApprovalFrameFamilies, resolveApprovalLeg, resolveStreamApprovalGate } from "../../tool-approval.js";
26
26
  import { buildActiveRunConflict, isActiveRunConflictBody, toDoneFrameResult } from "../active-run-conflict.js";
27
- import { commitPlacement, devicePlacementErrorBody } from "../device-placement.js";
27
+ import { commitExecutionContext } from "../execution-context.js";
28
+ import { rootSessionAbsentAnswer } from "../execution-context.js";
28
29
  import { commitSessionInheritEnv } from "../session-inherit-env.js";
29
30
  import { headerStr, gatedPrincipal, explicitOperatorOk, runIdentityOf } from "../principal-gate.js";
30
31
  import { failOpenTagForDroppedFrame, recordFailOpen } from "../../observability/fail-open.js";
@@ -177,7 +178,7 @@ export async function handleTasks(req, res, match, ctx) {
177
178
  sendError(res, 400, "request.field_conflict", "verify / cascade are not supported on /v1/tasks/stream (both are multi-attempt, not a single stream) — use /v1/tasks or /v1/runs", fieldConflictFacts("cascade_verify", prepared.verify ? (prepared.cascade ? ["verify", "cascade"] : ["verify"]) : ["cascade"]));
178
179
  return;
179
180
  }
180
- const earlyDurableTid = facts.durableRuns(deps).available && prepared.spec.sessionId ? uuidv7() : undefined;
181
+ const earlyDurableTid = facts.durableRuns(deps).available ? uuidv7() : undefined;
181
182
  const streamLegStartedAt = Date.now();
182
183
  const detachOnDisconnect = headerStr(req.headers["x-detach-on-disconnect"]) === "true";
183
184
  if (detachOnDisconnect && earlyDurableTid === undefined) {
@@ -289,19 +290,19 @@ export async function handleTasks(req, res, match, ctx) {
289
290
  let runRowSettled = false;
290
291
  let durableTaskId;
291
292
  let durableHeartbeat;
292
- const cancelledResult = (errorMessage, cause) => mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId ?? "", startedAtMs: streamLegStartedAt, message: errorMessage, cause });
293
+ const cancelledResult = (errorMessage, cause) => mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId, startedAtMs: streamLegStartedAt, message: errorMessage, cause });
293
294
  let userMsgEntryId;
294
295
  const promptCommit = createPromptCommitReader();
295
296
  let anchorPut = false;
296
297
  const putRewindAnchor = () => {
297
- if (anchorPut || !userMsgEntryId || !durableTaskId || !prepared.spec.sessionId || !deps.resumeAnchorStore)
298
+ if (anchorPut || !userMsgEntryId || !durableTaskId || !deps.resumeAnchorStore)
298
299
  return;
299
300
  anchorPut = true;
300
301
  void deps.resumeAnchorStore
301
302
  .put(prepared.spec.sessionId, durableTaskId, userMsgEntryId, principal ?? null)
302
303
  .catch(() => deps.metrics?.inc("resume_anchor_capture_failed"));
303
304
  };
304
- if (deps.runStore && prepared.spec.sessionId) {
305
+ if (deps.runStore) {
305
306
  const tid = earlyDurableTid ?? uuidv7();
306
307
  const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), identity: runIdentityOf(req, deps.config, principal ?? null), idem: idem ?? null });
307
308
  if (created.ok)
@@ -313,22 +314,25 @@ export async function handleTasks(req, res, match, ctx) {
313
314
  sseData(res, { type: "done", result: doneResultOfOutcome(replay), replay: true });
314
315
  return replay;
315
316
  }
317
+ if (created.sessionAbsent) {
318
+ const refused = rootSessionAbsentAnswer();
319
+ sseData(res, { type: "done", result: doneResultOfOutcome(refused) });
320
+ return refused;
321
+ }
316
322
  if (!created.ok) {
317
323
  const conflict = await buildActiveRunConflict({ runStore: deps.runStore, checkpointStore: deps.checkpointStore, governance: deps.config, runStaleSec: deps.config.runStaleSec, turnActivity: readTurnActivityMs }, prepared.spec.sessionId, created.activeTaskId);
318
324
  const refused = { status: 409, body: conflict };
319
325
  sseData(res, { type: "done", result: doneResultOfOutcome(refused) });
320
326
  return refused;
321
327
  }
322
- const placementReject = await commitPlacement(deps, deps.runStore, tid, prepared.bindingIntent);
328
+ const placementReject = await commitExecutionContext(deps, { kind: "run", runStore: deps.runStore, taskId: tid }, prepared.execution);
323
329
  if (placementReject) {
324
- const body = devicePlacementErrorBody(placementReject);
325
- const refused = { status: placementReject.status, body };
326
- sseData(res, { type: "done", result: doneResultOfOutcome(refused) });
327
- return refused;
330
+ sseData(res, { type: "done", result: doneResultOfOutcome(placementReject) });
331
+ return placementReject;
328
332
  }
329
333
  durableTaskId = tid;
330
334
  }
331
- if (deps.checkpointStore && prepared.spec.sessionId) {
335
+ if (deps.checkpointStore) {
332
336
  const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
333
337
  if (ctxRefusal) {
334
338
  sseData(res, { type: "done", result: doneResultOfOutcome(ctxRefusal) });
@@ -336,8 +340,7 @@ export async function handleTasks(req, res, match, ctx) {
336
340
  }
337
341
  }
338
342
  commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
339
- if (prepared.spec.sessionId)
340
- markChildrenStoppedByUserOnAbort(ac.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
343
+ markChildrenStoppedByUserOnAbort(ac.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
341
344
  let ledgerSink;
342
345
  let ledgerTerminal = false;
343
346
  if (durableTaskId && deps.runStore) {
@@ -412,7 +415,7 @@ export async function handleTasks(req, res, match, ctx) {
412
415
  }
413
416
  : {}),
414
417
  });
415
- if (deps.userInputLedger && durableTaskId && prepared.spec.sessionId)
418
+ if (deps.userInputLedger && durableTaskId)
416
419
  await deps.userInputLedger.announceUndrained({ sessionId: prepared.spec.sessionId, taskId: durableTaskId });
417
420
  try {
418
421
  const streamBody = async () => {
@@ -420,7 +423,7 @@ export async function handleTasks(req, res, match, ctx) {
420
423
  const tailContentMode = prepared.spec.forwardSubagentEvents === true ? "on" : "progress_only";
421
424
  const childReasoning = createReasoningSegments();
422
425
  const topReasoning = createReasoningSegments();
423
- const heldDecisions = prepared.spec.sessionId !== undefined ? await heldWorkflowParkDecisions(deps, prepared.spec.sessionId) : {};
426
+ const heldDecisions = await heldWorkflowParkDecisions(deps, prepared.spec.sessionId);
424
427
  const fwdInternals = {
425
428
  ...heldDecisions,
426
429
  onForwardEvent: (e) => {
@@ -476,7 +479,7 @@ export async function handleTasks(req, res, match, ctx) {
476
479
  }
477
480
  const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
478
481
  const deliverable = syncLegLive && !res.writableEnded && !res.destroyed;
479
- const parked = !deliverable && Boolean(deps.workflowCompletionInbox && prepared.spec.sessionId);
482
+ const parked = !deliverable && deps.workflowCompletionInbox !== undefined;
480
483
  deps.logger?.info?.("task_notification_observed", { route: "sync-stream", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: syncLegLive, parkedDurable: parked });
481
484
  if (parked) {
482
485
  void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(prepared.spec.sessionId, askOwner, n, Date.now(), durableTaskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "sync-stream", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
@@ -521,7 +524,7 @@ export async function handleTasks(req, res, match, ctx) {
521
524
  if (durableTaskId && finalResult)
522
525
  finalResult = { ...finalResult, taskId: durableTaskId };
523
526
  if (causeAtDone !== undefined && finalResult !== undefined) {
524
- finalResult = mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId ?? "", startedAtMs: streamLegStartedAt, message: "cancelled by user", cause: causeAtDone, engineResult: finalResult });
527
+ finalResult = mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId, startedAtMs: streamLegStartedAt, message: "cancelled by user", cause: causeAtDone, engineResult: finalResult });
525
528
  }
526
529
  finalizeTaskResult(ev.result, principal, prepared.spec.objective);
527
530
  noteRunOutcome(finalResult);
@@ -714,7 +717,8 @@ export async function handleTasks(req, res, match, ctx) {
714
717
  taskId: askTaskId,
715
718
  owner: askOwner,
716
719
  abortSignal: ac.signal,
717
- ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}),
720
+ sessionId: prepared.spec.sessionId,
721
+ executionLane: prepared.execution.lane,
718
722
  ...(streamApprovalOn
719
723
  ? {
720
724
  emitCard,
@@ -728,11 +732,12 @@ export async function handleTasks(req, res, match, ctx) {
728
732
  if (deps.toolApproval) {
729
733
  const toolApproval = deps.toolApproval;
730
734
  prepared.spec.onAsk = unattended
731
- ? toolApproval.boundUnattendedAsk({ owner: askOwner, taskId: askTaskId, ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}) }, unattended)
735
+ ? toolApproval.boundUnattendedAsk({ owner: askOwner, taskId: askTaskId, sessionId: prepared.spec.sessionId, executionLane: prepared.execution.lane }, unattended)
732
736
  : toolApproval.boundAsk({
733
737
  owner: askOwner,
734
738
  taskId: askTaskId,
735
- ...(prepared.spec.sessionId ? { sessionId: prepared.spec.sessionId } : {}),
739
+ sessionId: prepared.spec.sessionId,
740
+ executionLane: prepared.execution.lane,
736
741
  ...(streamApprovalOn ? { legKey: "", ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}) } : {}),
737
742
  });
738
743
  }
@@ -815,7 +820,7 @@ export async function handleTasks(req, res, match, ctx) {
815
820
  try {
816
821
  const row = await deps.runStore.getRun(durableTaskId);
817
822
  if (row?.status === "running") {
818
- const pending = prepared.spec.sessionId ? await deps.checkpointStore?.findPendingTokenBySession(prepared.spec.sessionId) : undefined;
823
+ const pending = await deps.checkpointStore?.findPendingTokenBySession(prepared.spec.sessionId);
819
824
  const backstopCause = ac.cause();
820
825
  if (pending) {
821
826
  if (ledgerSink && !ledgerTerminal) {
@@ -896,7 +901,7 @@ export async function handleTasks(req, res, match, ctx) {
896
901
  const syncLegStartedAt = Date.now();
897
902
  let durableTaskId;
898
903
  let durableHeartbeat;
899
- if (deps.runStore && prepared.spec.sessionId) {
904
+ if (deps.runStore) {
900
905
  const tid = uuidv7();
901
906
  const created = await deps.runStore.createRun(tid, prepared.spec.sessionId, principal ?? null, deps.instanceId ?? "default", { ...runMeta(prepared, source), identity: runIdentityOf(req, deps.config, principal ?? null), idem: idem ?? null });
902
907
  if (created.ok)
@@ -905,22 +910,23 @@ export async function handleTasks(req, res, match, ctx) {
905
910
  recordResourceWindow(tid, prepared.spec.resourceSuspend);
906
911
  if (created.existing)
907
912
  return await replayOfExistingRun(created.existing, { principal: submitter, idem }, "sync", deps, deps.runStore);
913
+ if (created.sessionAbsent)
914
+ return rootSessionAbsentAnswer();
908
915
  if (!created.ok)
909
916
  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) };
910
- const placementReject = await commitPlacement(deps, deps.runStore, tid, prepared.bindingIntent);
917
+ const placementReject = await commitExecutionContext(deps, { kind: "run", runStore: deps.runStore, taskId: tid }, prepared.execution);
911
918
  if (placementReject)
912
- return { status: placementReject.status, body: devicePlacementErrorBody(placementReject) };
919
+ return placementReject;
913
920
  durableTaskId = tid;
914
921
  }
915
- if (deps.checkpointStore && prepared.spec.sessionId) {
922
+ if (deps.checkpointStore) {
916
923
  const ctxRefusal = await persistSubmitCtx(deps.checkpointStore, deps, durableTaskId !== undefined && deps.runStore ? { runStore: deps.runStore, taskId: durableTaskId } : undefined, prepared.spec.sessionId, { body: prepared.body, memoryScope: prepared.auth?.memoryScope, writerVersion: serviceVersion() });
917
924
  if (ctxRefusal)
918
925
  return ctxRefusal;
919
926
  }
920
927
  commitSessionInheritEnv(deps, prepared.spec.sessionId, prepared.body);
921
928
  const syncCancelCtrl = createRunAbort();
922
- if (prepared.spec.sessionId)
923
- markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
929
+ markChildrenStoppedByUserOnAbort(syncCancelCtrl.signal, durableTaskId ?? prepared.spec.sessionId, prepared.auth?.principal);
924
930
  if (durableTaskId && deps.runStore) {
925
931
  const rs = deps.runStore;
926
932
  const tid = durableTaskId;
@@ -950,7 +956,7 @@ export async function handleTasks(req, res, match, ctx) {
950
956
  let result;
951
957
  counters.uncountedBillableInflight++;
952
958
  try {
953
- const heldDecisions = prepared.spec.sessionId !== undefined ? await heldWorkflowParkDecisions(deps, prepared.spec.sessionId) : {};
959
+ const heldDecisions = await heldWorkflowParkDecisions(deps, prepared.spec.sessionId);
954
960
  const verifyLeg = (v) => runWithVerification(prepared.runner, specWithSignal, v, heldDecisions);
955
961
  const cascadeLeg = () => runCascade(prepared.runner, specWithSignal, cascadeConfig(deps.config.cascadeLadder, prepared.spec.limits?.maxCostUsd), heldDecisions);
956
962
  const plainLeg = async () => {
@@ -963,7 +969,7 @@ export async function handleTasks(req, res, match, ctx) {
963
969
  }
964
970
  return stream.result();
965
971
  };
966
- result = await instructionLeg.run(prepared.spec.sessionId, () => withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg()));
972
+ result = atHostBoundary(await instructionLeg.run(prepared.spec.sessionId, () => withPrincipal(principal, () => prepared.verify ? verifyLeg(prepared.verify) : prepared.cascade ? cascadeLeg() : plainLeg())), prepared.spec.sessionId);
967
973
  }
968
974
  finally {
969
975
  counters.uncountedBillableInflight--;
@@ -976,7 +982,7 @@ export async function handleTasks(req, res, match, ctx) {
976
982
  if (syncAbortCause !== undefined) {
977
983
  result = mintCancelledResult({
978
984
  taskId: durableTaskId ?? "",
979
- sessionId: prepared.spec.sessionId ?? "",
985
+ sessionId: prepared.spec.sessionId,
980
986
  startedAtMs: syncLegStartedAt,
981
987
  message: "cancelled by user",
982
988
  cause: syncAbortCause,
@@ -998,12 +1004,14 @@ export async function handleTasks(req, res, match, ctx) {
998
1004
  }
999
1005
  const exitResult = await mintSubmitExitResult(result, deps.checkpointStore);
1000
1006
  const syncPlane = outwardPlane(result.terminal);
1001
- if (syncPlane.status === "suspended" && deps.checkpointStore && deps.runStore && durableTaskId && prepared.spec.sessionId) {
1007
+ if (syncPlane.status === "suspended" && deps.checkpointStore && deps.runStore && durableTaskId) {
1002
1008
  await deps.runStore.setSuspended(durableTaskId, exitResult);
1009
+ prepared.acceptance.settle(result);
1003
1010
  return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult, { taskId: durableTaskId, sessionId: prepared.spec.sessionId, status: "suspended" })) };
1004
1011
  }
1005
- if (syncPlane.status === "needs_review" && deps.checkpointStore && deps.runStore && durableTaskId && prepared.spec.sessionId) {
1012
+ if (syncPlane.status === "needs_review" && deps.checkpointStore && deps.runStore && durableTaskId) {
1006
1013
  await deps.runStore.setNeedsReview(durableTaskId, exitResult);
1014
+ prepared.acceptance.settle(result);
1007
1015
  return { status: 200, body: withSyncSubmitKeys(buildSyncSubmitBody(exitResult, { taskId: durableTaskId, sessionId: prepared.spec.sessionId, status: "needs_review" })) };
1008
1016
  }
1009
1017
  if (durableTaskId && deps.runStore)