@sema-agent/server 7.151.0 → 7.152.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 (286) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +15 -3
  3. package/README.zh-CN.md +10 -2
  4. package/USAGE.md +93 -64
  5. package/deploy/sema-up/README.md +5 -4
  6. package/deploy/sema-up/cluster-up.sh +6 -6
  7. package/deploy/sema-up/kube-up.sh +5 -5
  8. package/deploy/sema-up/lib/flagshape.sh +36 -8
  9. package/deploy/sema-up/machines.example.yaml +3 -1
  10. package/deploy/sema-up/sema-up.sh +22 -16
  11. package/dist/approval-ask-audit-store.d.ts +1 -1
  12. package/dist/approval-ask-machine.d.ts +4 -10
  13. package/dist/approval-ask-retention.d.ts +1 -1
  14. package/dist/approval-card.d.ts +10 -12
  15. package/dist/approval-gate-currency.d.ts +8 -8
  16. package/dist/approval-gate-currency.js +3 -4
  17. package/dist/approval-hmac.d.ts +17 -1
  18. package/dist/approval-hmac.js +7 -1
  19. package/dist/approval-queue-kind.d.ts +1 -1
  20. package/dist/approval.d.ts +2 -2
  21. package/dist/approval.js +2 -2
  22. package/dist/bind-host.d.ts +1 -3
  23. package/dist/bind-host.js +2 -3
  24. package/dist/boot/coordinators.d.ts +1 -1
  25. package/dist/boot/coordinators.js +9 -10
  26. package/dist/boot/device-lane.js +2 -0
  27. package/dist/boot/execution-env.d.ts +1 -0
  28. package/dist/boot/execution-env.js +31 -5
  29. package/dist/boot/leader.d.ts +31 -30
  30. package/dist/boot/leader.js +38 -28
  31. package/dist/boot/leg-path-adjudication.d.ts +40 -0
  32. package/dist/boot/leg-path-adjudication.js +18 -0
  33. package/dist/boot/parked-revive-gate.js +3 -15
  34. package/dist/boot/reapers.d.ts +1 -1
  35. package/dist/boot/reapers.js +1 -1
  36. package/dist/boot/resolve-spec.js +12 -26
  37. package/dist/boot/runner-deps.d.ts +39 -35
  38. package/dist/boot/runner-deps.js +92 -89
  39. package/dist/boot/shutdown.d.ts +6 -0
  40. package/dist/boot/shutdown.js +10 -4
  41. package/dist/boot/stage-02-web-admin-pull.js +4 -5
  42. package/dist/boot/stage-05-execution-env.js +2 -1
  43. package/dist/boot/stage-06-runners.d.ts +2 -0
  44. package/dist/boot/stage-06-runners.js +10 -4
  45. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  46. package/dist/boot/stage-07-capability-layer.js +1 -1
  47. package/dist/boot/stage-08-reapers.d.ts +1 -0
  48. package/dist/boot/stage-09-leader.d.ts +1 -0
  49. package/dist/boot/stage-09-leader.js +2 -2
  50. package/dist/boot/stage-10-http-server.d.ts +8 -0
  51. package/dist/boot/stage-10-http-server.js +11 -15
  52. package/dist/boot/stores.js +7 -4
  53. package/dist/boot/web-admin.js +2 -7
  54. package/dist/capabilities/approvals-push.d.ts +3 -3
  55. package/dist/capabilities/scenarios.d.ts +28 -13
  56. package/dist/capabilities/scenarios.js +2 -2
  57. package/dist/capability-facts.d.ts +22 -20
  58. package/dist/capability-facts.js +18 -34
  59. package/dist/card-rule-offers.d.ts +32 -0
  60. package/dist/card-rule-offers.js +130 -0
  61. package/dist/config-catalog.js +12 -11
  62. package/dist/config-dir-trust.js +4 -2
  63. package/dist/config-invariants.d.ts +18 -7
  64. package/dist/config-invariants.js +51 -8
  65. package/dist/config-lkg.d.ts +5 -19
  66. package/dist/config-lkg.js +8 -62
  67. package/dist/config-provider.d.ts +1 -1
  68. package/dist/config-root-migration.d.ts +2 -2
  69. package/dist/config-types.d.ts +28 -38
  70. package/dist/config.d.ts +7 -5
  71. package/dist/config.js +55 -16
  72. package/dist/control-socket.d.ts +51 -0
  73. package/dist/control-socket.js +115 -0
  74. package/dist/deployment-governance.d.ts +2 -1
  75. package/dist/device-ws-hub.d.ts +7 -0
  76. package/dist/device-ws-hub.js +7 -1
  77. package/dist/elicitation.js +2 -2
  78. package/dist/entry-problems.d.ts +40 -3
  79. package/dist/entry-problems.js +3 -1
  80. package/dist/execution-env-refusal.d.ts +8 -2
  81. package/dist/execution-env-refusal.js +3 -0
  82. package/dist/fleet/engine-signal.d.ts +12 -29
  83. package/dist/fleet/engine-signal.js +1 -7
  84. package/dist/fleet-client.js +2 -5
  85. package/dist/gate-settlement.d.ts +1 -8
  86. package/dist/gate-settlement.js +0 -4
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +2 -3
  89. package/dist/hitl-delivery.js +0 -2
  90. package/dist/hooks/hook-llm.js +4 -2
  91. package/dist/hooks/hook-runner.d.ts +3 -2
  92. package/dist/hooks/hook-runner.js +16 -1
  93. package/dist/host-decision.d.ts +11 -11
  94. package/dist/http/active-run-conflict.d.ts +35 -2
  95. package/dist/http/active-run-conflict.js +48 -19
  96. package/dist/http/approval-sweeps.d.ts +1 -1
  97. package/dist/http/approval-sweeps.js +5 -2
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +6 -2
  101. package/dist/http/dispatch.js +25 -5
  102. package/dist/http/durable-run-leg.js +0 -1
  103. package/dist/http/health-reader.d.ts +16 -9
  104. package/dist/http/health-reader.js +2 -1
  105. package/dist/http/health-self-report.js +1 -1
  106. package/dist/http/owner-access.d.ts +2 -3
  107. package/dist/http/owner-access.js +3 -4
  108. package/dist/http/principal-gate.d.ts +49 -21
  109. package/dist/http/principal-gate.js +61 -11
  110. package/dist/http/request-channel.d.ts +23 -0
  111. package/dist/http/request-channel.js +9 -0
  112. package/dist/http/request-guards.js +17 -2
  113. package/dist/http/resume-ctx-read.js +20 -3
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +111 -41
  116. package/dist/http/route-ctx.d.ts +73 -7
  117. package/dist/http/route-ctx.js +17 -0
  118. package/dist/http/route-table.d.ts +4 -0
  119. package/dist/http/route-table.js +4 -0
  120. package/dist/http/routes/admin-config-refresh.js +3 -3
  121. package/dist/http/routes/admin-drain.d.ts +3 -0
  122. package/dist/http/routes/admin-drain.js +7 -7
  123. package/dist/http/routes/admin-memory-optout.js +3 -3
  124. package/dist/http/routes/adoption.js +3 -5
  125. package/dist/http/routes/approvals-assistant.d.ts +2 -1
  126. package/dist/http/routes/approvals-assistant.js +95 -48
  127. package/dist/http/routes/capabilities.d.ts +4 -0
  128. package/dist/http/routes/capabilities.js +30 -5
  129. package/dist/http/routes/config-catalog.js +2 -2
  130. package/dist/http/routes/devices.js +1 -1
  131. package/dist/http/routes/diagnostics.d.ts +3 -3
  132. package/dist/http/routes/diagnostics.js +19 -7
  133. package/dist/http/routes/images.js +10 -10
  134. package/dist/http/routes/memory-bundle.js +3 -3
  135. package/dist/http/routes/memory-compliance.js +3 -3
  136. package/dist/http/routes/memory-consolidation.js +2 -2
  137. package/dist/http/routes/memory-origin.js +3 -3
  138. package/dist/http/routes/memory-policy.js +2 -2
  139. package/dist/http/routes/observability.js +3 -3
  140. package/dist/http/routes/retention-ops.js +3 -3
  141. package/dist/http/routes/rules.js +5 -5
  142. package/dist/http/routes/run-permission-mode.d.ts +2 -1
  143. package/dist/http/routes/run-permission-mode.js +2 -2
  144. package/dist/http/routes/runs.d.ts +4 -9
  145. package/dist/http/routes/runs.js +73 -33
  146. package/dist/http/routes/session-sync.d.ts +18 -6
  147. package/dist/http/routes/session-sync.js +38 -36
  148. package/dist/http/routes/sessions.js +20 -9
  149. package/dist/http/routes/tasks.js +28 -37
  150. package/dist/http/routes/workflows.js +1 -1
  151. package/dist/http/server.d.ts +9 -3
  152. package/dist/http/server.js +15 -3
  153. package/dist/http/submit-ctx.d.ts +1 -1
  154. package/dist/http/submit-ctx.js +2 -0
  155. package/dist/human-facing-args.d.ts +33 -0
  156. package/dist/human-facing-args.js +33 -0
  157. package/dist/index.d.ts +1 -1
  158. package/dist/k8s-api-credentials.d.ts +29 -0
  159. package/dist/k8s-api-credentials.js +25 -0
  160. package/dist/lane-home-probe.d.ts +1 -1
  161. package/dist/lane-home-probe.js +2 -1
  162. package/dist/leader/diffup.d.ts +19 -0
  163. package/dist/leader/diffup.js +7 -0
  164. package/dist/leader/leader.d.ts +114 -10
  165. package/dist/leader/leader.js +69 -5
  166. package/dist/leader/repair-wire.js +2 -2
  167. package/dist/leader/run-entry.d.ts +2 -1
  168. package/dist/leader/run-entry.js +1 -1
  169. package/dist/leader/wire.d.ts +42 -132
  170. package/dist/leader/wire.js +81 -67
  171. package/dist/memory-scope.d.ts +20 -9
  172. package/dist/memory-scope.js +15 -3
  173. package/dist/model-entry-headers.d.ts +9 -10
  174. package/dist/model-entry-headers.js +0 -4
  175. package/dist/model-entry-refusal.d.ts +7 -4
  176. package/dist/model-entry-refusal.js +11 -5
  177. package/dist/model-plane-face.d.ts +1 -1
  178. package/dist/model-plane-face.js +3 -1
  179. package/dist/model-route-endpoint.d.ts +13 -7
  180. package/dist/model-route-endpoint.js +14 -11
  181. package/dist/model-select.d.ts +22 -0
  182. package/dist/model-select.js +9 -0
  183. package/dist/model-unreachable-notice.d.ts +11 -0
  184. package/dist/model-unreachable-notice.js +4 -4
  185. package/dist/object-store-buckets.d.ts +640 -6
  186. package/dist/object-store-buckets.js +166 -0
  187. package/dist/observability/fail-open.d.ts +20 -16
  188. package/dist/observability/fail-open.js +20 -16
  189. package/dist/observability/logger.js +3 -1
  190. package/dist/operator-roster.d.ts +68 -0
  191. package/dist/operator-roster.js +32 -0
  192. package/dist/orchestration/workflow-park-decision.d.ts +1 -1
  193. package/dist/outbound-dispatcher.d.ts +71 -8
  194. package/dist/outbound-dispatcher.js +48 -7
  195. package/dist/owner-faces.d.ts +1 -1
  196. package/dist/parked-decide.d.ts +2 -2
  197. package/dist/permission-mode-admission.d.ts +59 -26
  198. package/dist/permission-mode-admission.js +37 -10
  199. package/dist/plugins/approval-ask-store-file.d.ts +4 -0
  200. package/dist/plugins/approval-ask-store-file.js +3 -0
  201. package/dist/plugins/approval-ask-store-memory.d.ts +4 -0
  202. package/dist/plugins/approval-ask-store-memory.js +7 -1
  203. package/dist/plugins/approval-ask-store-sql.d.ts +26 -2
  204. package/dist/plugins/approval-ask-store-sql.js +12 -1
  205. package/dist/plugins/blob-backend.d.ts +83 -7
  206. package/dist/plugins/blob-backend.js +57 -6
  207. package/dist/plugins/checkpoint-index-columns.d.ts +20 -16
  208. package/dist/plugins/checkpoint-index-columns.js +3 -6
  209. package/dist/plugins/checkpoint-store-face.d.ts +119 -61
  210. package/dist/plugins/checkpoint-store-face.js +134 -55
  211. package/dist/plugins/landed-attendants.d.ts +2 -2
  212. package/dist/plugins/local-checkpoint-store.d.ts +13 -3
  213. package/dist/plugins/local-checkpoint-store.js +38 -49
  214. package/dist/plugins/one-time-migrations.d.ts +51 -21
  215. package/dist/plugins/one-time-migrations.js +67 -45
  216. package/dist/plugins/pg-pool.js +11 -9
  217. package/dist/plugins/remote-env-host.d.ts +29 -10
  218. package/dist/plugins/remote-env-host.js +99 -19
  219. package/dist/plugins/remote-env-k8s.d.ts +68 -2
  220. package/dist/plugins/remote-env-k8s.js +107 -24
  221. package/dist/plugins/replica-registration.d.ts +1 -1
  222. package/dist/plugins/replica-registration.js +1 -1
  223. package/dist/plugins/s3-presign.d.ts +23 -0
  224. package/dist/plugins/s3-presign.js +37 -3
  225. package/dist/plugins/scheduler-support.d.ts +2 -2
  226. package/dist/plugins/scheduler-support.js +0 -3
  227. package/dist/plugins/schema-indexes.js +2 -0
  228. package/dist/plugins/send-file-ledger.d.ts +2 -2
  229. package/dist/plugins/send-file-ledger.js +2 -2
  230. package/dist/plugins/send-user-file.d.ts +3 -4
  231. package/dist/plugins/send-user-file.js +56 -52
  232. package/dist/plugins/session-blob-claim-token.d.ts +15 -0
  233. package/dist/plugins/session-blob-claim-token.js +20 -0
  234. package/dist/plugins/session-blob-namespace.d.ts +46 -0
  235. package/dist/plugins/session-blob-namespace.js +116 -0
  236. package/dist/plugins/snapshot-bucket-presence.d.ts +25 -2
  237. package/dist/plugins/snapshot-bucket-presence.js +8 -2
  238. package/dist/plugins/sql-json-column.d.ts +1 -1
  239. package/dist/plugins/store-backend.d.ts +44 -2
  240. package/dist/plugins/store-backend.js +75 -53
  241. package/dist/plugins/task-attachment-store.d.ts +2 -1
  242. package/dist/plugins/task-attachment-store.js +5 -1
  243. package/dist/plugins/tidb-pool.js +19 -24
  244. package/dist/plugins/workspace-custody.d.ts +4 -3
  245. package/dist/question.d.ts +2 -1
  246. package/dist/question.js +5 -13
  247. package/dist/request-form.d.ts +6 -1
  248. package/dist/request-form.js +4 -1
  249. package/dist/rule-issue-reading.d.ts +1 -1
  250. package/dist/rules-consent.d.ts +1 -1
  251. package/dist/run-local.js +8 -9
  252. package/dist/runs.d.ts +17 -8
  253. package/dist/runs.js +15 -2
  254. package/dist/runtime-governance.d.ts +2 -2
  255. package/dist/sema-config-dir.d.ts +2 -1
  256. package/dist/session-sync.d.ts +27 -11
  257. package/dist/session-sync.js +15 -7
  258. package/dist/stores-sql/blob-index-sql.d.ts +30 -9
  259. package/dist/stores-sql/blob-index-sql.js +40 -21
  260. package/dist/stores-sql/checkpoint-store-sql.d.ts +54 -27
  261. package/dist/stores-sql/checkpoint-store-sql.js +70 -146
  262. package/dist/stores-sql/file-history-store-sql.d.ts +64 -16
  263. package/dist/stores-sql/file-history-store-sql.js +100 -28
  264. package/dist/stores-sql/retention-store-sql.js +3 -9
  265. package/dist/task-settings.d.ts +6 -5
  266. package/dist/task-settings.js +3 -3
  267. package/dist/tool-approval.d.ts +342 -518
  268. package/dist/tool-approval.js +498 -1207
  269. package/dist/trace/core-keyset-guard.d.ts +1 -1
  270. package/dist/trace/ledger-events.d.ts +3 -3
  271. package/dist/trace/project.d.ts +2 -3
  272. package/dist/trace/redact.js +3 -2
  273. package/dist/trace/self-minted-wire.d.ts +287 -157
  274. package/dist/trace/self-minted-wire.js +93 -48
  275. package/dist/web-admin-client/apply-effective.js +2 -2
  276. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  277. package/dist/web-admin-client/read-warnings.js +1 -1
  278. package/dist/web-admin-client/types.d.ts +1 -1
  279. package/dist/workspace-space.d.ts +10 -4
  280. package/dist/workspace-space.js +4 -1
  281. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +1 -1
  282. package/package.json +6 -2
  283. package/scripts/embed-minimal.mjs +3 -3
  284. package/wire-changes/server@7.152.0.json +900 -0
  285. package/dist/config-d-migration.d.ts +0 -37
  286. package/dist/config-d-migration.js +0 -60
@@ -9,8 +9,11 @@
9
9
  */
10
10
  import type { IncomingMessage, ServerResponse } from "node:http";
11
11
  import { type SessionRulesRecord, type FileHistoryExport } from "@sema-agent/core";
12
+ import { type VisibleBlobs } from "../../session-sync.js";
12
13
  import { type ImportAnchor, type StagingHandle } from "../../session-sync-kernel.js";
14
+ import { type ListFilter } from "../../owner-faces.js";
13
15
  import type { DurableOwnerKey } from "../../owner-brands.js";
16
+ import type { SyncLanding } from "../../plugins/store-backend.js";
14
17
  import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
15
18
  import { type SessionRowPatch } from "../../session-row.js";
16
19
  import { type SessionFace } from "../../session-incarnation.js";
@@ -39,15 +42,24 @@ export type PendingStaging = {
39
42
  expectedOccupant: SessionFace | undefined;
40
43
  /** 开这张暂存时占下的导入租约的键({@link importLeaseKey});续期与放掉都按它,不再从会话 id 重算。 */
41
44
  leaseKey: string;
45
+ /** 第一阶段那一次可见性读的答案(落点 = 开暂存时门判的那一格):提交臂的文件历史重放只经它的取值口取字节 —— 许可集在第一阶段定,覆盖写的擦除不改它。 */
46
+ visible: VisibleBlobs;
42
47
  };
43
48
  /**
44
- * 导入租约的键 = 这次导入**落在哪一格属主上**(与提交的属主 CAS 同一格)+ 会话 id。一条读法:
45
- * · id 被占着(活行 ∪ 在删行 —— 「被占着」的唯一读法,门读的 `occupant`)⇒ 落点是住客的属主格(`null` = 无主):所有碰得到这一行的调用方(属主、fleet、存量无主互认)共一把 —— 同一份会话上第二份在飞导入,谁发起的都 409;
46
- * · id 空闲 ⇒ 还没有谁的会话,落点是这位调用方自己将成的属主(`importingPrincipal`):各占各的,先提交者得(提交的属主 CAS 要求那一格仍空着,输的那一份
47
- * 在提交前复判被拒)。一个主体的在飞导入既不挡、也不透给另一个主体 —— 租约键不含主体时,空闲 id 上任一主体都能占位,并从 409 读到「此 id 此刻有人在导」。
48
- * 键的主体只来自门读的 `occupant` 与 `importingPrincipal`(两者都派生自属主存取器的 `listFilterOf` 与门读的会话面,不另读调用方),且占租约在 `pushGateOk` 之后。
49
+ * 这次推送 / 拉取**落在哪一格**(落点)的唯一读法 —— 导入租约的键、blob 授予(`PUT …/sync/blobs/:hash`)、blob 可见性(第一阶段预检 · 重放取值 · `GET …/sync/history/blobs/:hash`)同读它:
50
+ * · id 被占着(活行 ∪ 在删行 —— 「被占着」的唯一读法,门读的 `occupant`)⇒ 落点是住客的属主格(`null` = 无主):所有碰得到这一行的调用方(属主、fleet、存量无主互认)落在同一格;
51
+ * · id 空闲 ⇒ 还没有谁的会话,落点是这位调用方自己将成的属主(fleet ⇒ 无主格;主体 ⇒ 主体):各落各的,先提交者得(提交的属主 CAS 要求那一格仍空着)。
52
+ * `occupied` 随落点走:只有被占着的 id,它的历史图才是 blob 的来历(空闲 id 上残留的图不是任何一位调用方持有过字节的证明)。
53
+ * 落点的主体只来自门读的 `occupant` 与 `listFilterOf` 的派生,不另读调用方;拉取口只在活行上放行(`pullGateOk`),那时 `occupant` 就是活行。
49
54
  */
50
- export declare function importLeaseKey(landingOwner: string | null, sessionId: string): string;
55
+ export declare function landingOf(gate: {
56
+ readonly occupant: SessionFace | undefined;
57
+ }, scope: ListFilter, sessionId: string): SyncLanding;
58
+ /**
59
+ * 导入租约的键 = 这次导入的落点({@link landingOf}):落点属主格 + 会话 id。同一份会话上第二份在飞导入,谁发起的都 409(同一格);空闲 id 上一个主体的在飞导入既不挡、也不透给
60
+ * 另一个主体 —— 租约键不含落点属主格时,空闲 id 上任一主体都能占位,并从 409 读到「此 id 此刻有人在导」。占租约在 `pushGateOk` 之后。
61
+ */
62
+ export declare function importLeaseKey(landing: SyncLanding): string;
51
63
  /** 一把导入租约:最后活动时刻 + 持有它的暂存 + 它守的会话 id(清扫与运维日志用,不从键里反解)。 */
52
64
  export type ImportLease = {
53
65
  at: number;
@@ -1,7 +1,7 @@
1
1
  import { once } from "node:events";
2
2
  import { sessionLogDigest, sessionLogDigestsComparable, validateFileHistoryExport } from "@sema-agent/core";
3
3
  import { StringDecoder } from "node:string_decoder";
4
- import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory } from "../../session-sync.js";
4
+ import { FileHistoryUnreadableError, NO_VISIBLE_BLOBS, exportSessionManifest } from "../../session-sync.js";
5
5
  import { fileHistoryImportFace } from "../../plugins/landed-attendants.js";
6
6
  import * as facts from "../../capability-facts.js";
7
7
  import { classifySyncRelationshipByIds, createStagedLogFeed, MalformedImportEntryError, newStagingToken, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
@@ -23,8 +23,13 @@ import { defaultTaskRegistry } from "@sema-agent/core";
23
23
  import { SessionActiveRunError, SessionForkInProgressError, SessionWriterPresentError } from "../../execution-placement.js";
24
24
  import { probeSessionChildren, sessionTenantScope } from "../../session-children-retire.js";
25
25
  import { liveChildrenRefusal } from "./background.js";
26
- export function importLeaseKey(landingOwner, sessionId) {
27
- return JSON.stringify([landingOwner, sessionId]);
26
+ export function landingOf(gate, scope, sessionId) {
27
+ return gate.occupant !== undefined
28
+ ? { sessionId, owner: gate.occupant.owner, occupied: true }
29
+ : { sessionId, owner: scope.fleetWide ? null : scope.gateOwner, occupied: false };
30
+ }
31
+ export function importLeaseKey(landing) {
32
+ return JSON.stringify([landing.owner, landing.sessionId]);
28
33
  }
29
34
  export function createSessionSyncLocal() {
30
35
  const pendingStagings = new Map();
@@ -108,6 +113,7 @@ export async function handleSessionSync(req, res, match, ctx) {
108
113
  }
109
114
  const gateFace = gate.face;
110
115
  const owner = gateFace === undefined ? undefined : gateFace.owner;
116
+ const landing = landingOf(gate, scope, sessionId);
111
117
  const sendSessionAbsent = (reason) => refuse(syncEntry, res, reason);
112
118
  const pullGateOk = () => {
113
119
  if (!gate.mayPull) {
@@ -199,7 +205,7 @@ export async function handleSessionSync(req, res, match, ctx) {
199
205
  if (plan.wipe && !(await attendants.wipe(sessionId, landed)).landed)
200
206
  return { kind: "gone", leg: "wipe" };
201
207
  if (plan.fileHistory !== null && canReplayHistory) {
202
- const h = await attendants.replayHistory(sessionId, landed, plan.fileHistory, blobGetterOf(historyStore), { allowReplace: plan.allowReplace });
208
+ const h = await attendants.replayHistory(sessionId, landed, plan.fileHistory, plan.getBlob, { allowReplace: plan.allowReplace });
203
209
  if (!h.landed)
204
210
  return { kind: "gone", leg: "file_history" };
205
211
  if (!h.value.ok)
@@ -221,20 +227,20 @@ export async function handleSessionSync(req, res, match, ctx) {
221
227
  if (match.decl.id === "session-sync-manifest") {
222
228
  if (!pullGateOk())
223
229
  return;
224
- let out;
230
+ let manifest;
225
231
  try {
226
- out = await exportSessionManifest(sessionId, backend, historyStore);
232
+ manifest = await exportSessionManifest(sessionId, backend, historyStore);
227
233
  }
228
234
  catch (e) {
229
235
  if (sendSessionPolicyError(res, e) || sendPullUnreadable(req, res, deps.logger, e))
230
236
  return;
231
237
  throw e;
232
238
  }
233
- if (out === null) {
239
+ if (manifest === null) {
234
240
  sendSessionAbsent("missing");
235
241
  return;
236
242
  }
237
- await pullAnswer(() => sendJson(res, 200, { manifest: out.manifest }));
243
+ await pullAnswer(() => sendJson(res, 200, { manifest }));
238
244
  return;
239
245
  }
240
246
  if (match.decl.id === "session-sync-entries") {
@@ -301,9 +307,9 @@ export async function handleSessionSync(req, res, match, ctx) {
301
307
  return;
302
308
  }
303
309
  if (match.decl.id === "session-sync-history-blob") {
304
- const blobs = facts.sessionSyncFileHistory(deps);
305
- if (!blobs.available) {
306
- sendSessionSyncUnavailable(res, blobs.why);
310
+ const faces = facts.sessionSyncFileHistory(deps);
311
+ if (!faces.available) {
312
+ sendSessionSyncUnavailable(res, faces.why);
307
313
  return;
308
314
  }
309
315
  if (!pullGateOk())
@@ -317,25 +323,22 @@ export async function handleSessionSync(req, res, match, ctx) {
317
323
  sendError(res, 400, "request.id_invalid", "blob hash must be 64 lowercase-hex chars");
318
324
  return;
319
325
  }
320
- const fs = blobs.historyStore;
321
- const exchange = blobs.blobExchange;
322
- let envelope;
326
+ let visible;
323
327
  try {
324
- envelope = await exportedFileHistory(fs, sessionId);
328
+ visible = await faces.blobs.visible(landing, [hash]);
325
329
  }
326
330
  catch (e) {
327
331
  if (sendPullUnreadable(req, res, deps.logger, e))
328
332
  return;
329
333
  throw e;
330
334
  }
331
- const referenced = envelope !== null && envelope.tracked.some((t) => t.versions.some((v) => v.blobHash === hash));
332
- if (!referenced) {
335
+ if (!visible.hashes.has(hash)) {
333
336
  await pullAnswer(() => sendError(res, 404, "not_found.blob", "blob not found"));
334
337
  return;
335
338
  }
336
339
  let bytes;
337
340
  try {
338
- bytes = await exchange.get(hash);
341
+ bytes = await visible.get(hash);
339
342
  }
340
343
  catch (e) {
341
344
  if (sendPullUnreadable(req, res, deps.logger, e))
@@ -356,9 +359,9 @@ export async function handleSessionSync(req, res, match, ctx) {
356
359
  if (match.decl.id === "session-sync-blob-put") {
357
360
  if (rateLimited(req, res))
358
361
  return;
359
- const blobs = facts.sessionSyncFileHistory(deps);
360
- if (!blobs.available) {
361
- sendSessionSyncUnavailable(res, blobs.why);
362
+ const faces = facts.sessionSyncFileHistory(deps);
363
+ if (!faces.available) {
364
+ sendSessionSyncUnavailable(res, faces.why);
362
365
  return;
363
366
  }
364
367
  if (!pushGateOk())
@@ -369,7 +372,7 @@ export async function handleSessionSync(req, res, match, ctx) {
369
372
  return;
370
373
  }
371
374
  const body = await readRawBody(req, SYNC_BLOB_MAX_BYTES);
372
- const put = await blobs.blobExchange.put(hash, new Uint8Array(body));
375
+ const put = await faces.blobs.put(landing, hash, new Uint8Array(body));
373
376
  if (!put.ok) {
374
377
  if (put.error.blobCap !== undefined) {
375
378
  sendError(res, 413, "blob_too_large_for_sql", put.error.message, { wallSource: put.error.blobCap.wallSource });
@@ -471,15 +474,21 @@ export async function handleSessionSync(req, res, match, ctx) {
471
474
  sendError(res, 409, "conflict", "sync conflict", { relation: relA });
472
475
  return;
473
476
  }
477
+ let visible = NO_VISIBLE_BLOBS;
474
478
  if (fileHistory !== null) {
475
479
  const distinct = new Set();
476
480
  for (const t of fileHistory.tracked)
477
481
  for (const v of t.versions)
478
482
  if (v.blobHash !== null)
479
483
  distinct.add(v.blobHash);
480
- if (await firstMissingBlob(facts.sessionSyncFileHistory(deps), [...distinct]) !== undefined) {
481
- await pushAnswer(() => sendMissingBlob(res));
482
- return;
484
+ if (distinct.size > 0) {
485
+ const faces = facts.sessionSyncFileHistory(deps);
486
+ if (faces.available)
487
+ visible = await faces.blobs.visible(landing, [...distinct]);
488
+ if ([...distinct].some((h) => !visible.hashes.has(h))) {
489
+ await pushAnswer(() => sendMissingBlob(res));
490
+ return;
491
+ }
483
492
  }
484
493
  }
485
494
  let payloadVerified = false;
@@ -554,7 +563,7 @@ export async function handleSessionSync(req, res, match, ctx) {
554
563
  }
555
564
  let landedA;
556
565
  try {
557
- landedA = await landAttendants(attendantsA, gateFace, { wipe: false, fileHistory, allowReplace: resolution === "overwrite-dst", policy, operator: operatorOk, anchors, owner: importingPrincipal, row });
566
+ landedA = await landAttendants(attendantsA, gateFace, { wipe: false, fileHistory, getBlob: visible.get, allowReplace: resolution === "overwrite-dst", policy, operator: operatorOk, anchors, owner: importingPrincipal, row });
558
567
  }
559
568
  catch (e) {
560
569
  if (sendSessionPolicyError(res, e))
@@ -582,7 +591,7 @@ export async function handleSessionSync(req, res, match, ctx) {
582
591
  }));
583
592
  return;
584
593
  }
585
- const leaseKey = importLeaseKey(gate.occupant !== undefined ? gate.occupant.owner : importingPrincipal, sessionId);
594
+ const leaseKey = importLeaseKey(landing);
586
595
  const staleSecRaw = deps.config.syncImportLeaseStaleSec;
587
596
  const staleSec = typeof staleSecRaw === "number" && Number.isFinite(staleSecRaw) ? staleSecRaw : 600;
588
597
  const staleMs = staleSec * 1000;
@@ -605,7 +614,7 @@ export async function handleSessionSync(req, res, match, ctx) {
605
614
  importLeases.set(leaseKey, { at: Date.now(), stagingId, sessionId });
606
615
  pendingStagings.set(stagingId, {
607
616
  handle, realSessionId: sessionId, owner: importingPrincipal, ...(resolution ? { resolution } : {}), operatorOk,
608
- fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOccupant: gateFace, leaseKey,
617
+ fileHistory, policy, anchors, row, declaredEntryIds: entryIds, openedBy: ownerKeyOf(scope.gateOwner), expectedOccupant: gateFace, leaseKey, visible,
609
618
  });
610
619
  sendJson(res, 200, { stagingId, relation: relA.relation });
611
620
  return;
@@ -729,6 +738,7 @@ export async function handleSessionSync(req, res, match, ctx) {
729
738
  const landedB = await landAttendants(attendantsB, committed.face, {
730
739
  wipe: pending.resolution === "overwrite-dst" && (committed.relation === "fork" || committed.relation === "stale"),
731
740
  fileHistory: pending.fileHistory,
741
+ getBlob: pending.visible.get,
732
742
  allowReplace: pending.resolution === "overwrite-dst",
733
743
  policy: pending.policy,
734
744
  operator: pending.operatorOk,
@@ -869,14 +879,6 @@ function sendFileHistoryReplayFailure(res, failure) {
869
879
  const { code, permanence } = failure.error;
870
880
  sendError(res, 422, code, `file history import failed (${code})`, permanence === undefined ? undefined : { permanence });
871
881
  }
872
- async function firstMissingBlob(blobFaces, referenced) {
873
- if (referenced.length === 0)
874
- return undefined;
875
- if (!blobFaces.available)
876
- return referenced[0];
877
- const present = await blobFaces.blobExchange.has(referenced);
878
- return referenced.find((h) => !present.has(h));
879
- }
880
882
  function sendSessionSyncUnavailable(res, why) {
881
883
  switch (why) {
882
884
  case "store_backend_absent":
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { SessionError, materializeA2aTools, materializeMcpTools, sanitizePathComponent } from "@sema-agent/core";
2
3
  import { a2aForScenario, mcpForScenario } from "../../web-admin-client/facade.js";
3
4
  import { HttpError, isUuidShape } from "../../security.js";
@@ -14,6 +15,7 @@ import { readWritableSessionPermissionRules } from "../../session-policy-wire.js
14
15
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
15
16
  import { gatedPerson, gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
16
17
  import { personOf } from "../../owner-brands.js";
18
+ import { scenarioRequestedBySubject } from "../../request-form.js";
17
19
  import { leafOfAdmittedHead, sessionFaceOfHead } from "../../session-incarnation.js";
18
20
  import { admitSessionById, admitSessionForDeletion, admitSessionRow, admitSessionView, admittedAsFleet, callerEntry, callerOf, deliverVerdict, enter, judgeSessionById, protocolEntry, readOwnerCell, readVisibleLatestRun, refuse, sessionOccupantUnchanged } from "../owner-access.js";
19
21
  import { assertNever } from "../route-ctx.js";
@@ -23,6 +25,7 @@ import { projectSessionListRow } from "./sessions-list.js";
23
25
  import { readSessionRowPatch } from "../../session-row.js";
24
26
  import { forkPathOf, resolveExecutionContext } from "../execution-context.js";
25
27
  import { executionPlacementView } from "../../execution-placement.js";
28
+ import { readSessionOccupant, refusalOccupantOf } from "../active-run-conflict.js";
26
29
  import { anchorReaderOf, resolveHistoryAnchor } from "../../history-anchor.js";
27
30
  import { engineRefusalAnswerOf } from "../engine-refusal.js";
28
31
  import { buildRewindFilesReply } from "../rewind-files-reply.js";
@@ -135,7 +138,7 @@ export async function handleSessions(req, res, match, ctx) {
135
138
  const ledgerHalf = lastLegMcp === undefined ? {} : { lastLegMcp };
136
139
  let scenarioName;
137
140
  try {
138
- scenarioName = await deps.scenarioGate(principal, undefined);
141
+ ({ scenario: scenarioName } = await deps.scenarioGate(principal, scenarioRequestedBySubject(caller.subject, undefined)));
139
142
  }
140
143
  catch (e) {
141
144
  if (!(e instanceof HttpError))
@@ -192,7 +195,7 @@ export async function handleSessions(req, res, match, ctx) {
192
195
  sendPrincipalRefusal(res, req, deps.config);
193
196
  return;
194
197
  }
195
- const scenarioName = await deps.scenarioGate(principal, undefined);
198
+ const { scenario: scenarioName } = await deps.scenarioGate(principal, scenarioRequestedBySubject(gatedSubject(req, deps.config), undefined));
196
199
  const specs = a2aForScenario(deps.config.a2aPeers, scenarioName);
197
200
  if (!specs || specs.length === 0) {
198
201
  sendJson(res, 200, { peers: [], asOf: new Date().toISOString() });
@@ -256,13 +259,21 @@ export async function handleSessions(req, res, match, ctx) {
256
259
  if (admittedHead === undefined)
257
260
  return;
258
261
  const leafId = leafOfAdmittedHead(admittedHead.row);
259
- const etag = `"${leafId ?? "empty"}"`;
262
+ const runs = facts.durableRuns(deps);
263
+ const occupant = runs.available ? await readSessionOccupant(runs.runStore, headSession) : undefined;
264
+ if (occupant !== undefined && occupant.activeTaskId !== null && !(await sessionOccupantUnchanged(headEntry, headFact.sessionStorage, headSession, admittedHead.row))) {
265
+ refuse(headEntry, res, "missing");
266
+ return;
267
+ }
268
+ const body = { sessionId: headSession, leafId, ...occupant };
269
+ const bytes = JSON.stringify(body);
270
+ const etag = `"${createHash("sha256").update(bytes).digest("base64url")}"`;
260
271
  if (req.headers["if-none-match"] === etag) {
261
272
  res.writeHead(304, { etag }).end();
262
273
  return;
263
274
  }
264
- res.setHeader("etag", etag);
265
- sendJson(res, 200, { sessionId: headSession, leafId });
275
+ res.writeHead(200, { "content-type": "application/json", etag });
276
+ res.end(bytes);
266
277
  return;
267
278
  }
268
279
  }
@@ -767,9 +778,9 @@ export async function handleSessions(req, res, match, ctx) {
767
778
  return;
768
779
  }
769
780
  const entryId = await resolveHistoryAnchor("rewindFilesTo", anchors, anchorReaderOf(req, deps, rewindEntry.caller), sessionId, read.to, sessionOwner, "to");
770
- const activeTaskId = await deps.runStore?.getActiveTaskId(sessionId);
771
- if (activeTaskId) {
772
- sendError(res, 409, "conflict.session_active_run", `session has an active run (${activeTaskId}); its files cannot be rewound under it — wait for that run to end (or cancel it), then rewind`, { activeTaskId });
781
+ const holder = await deps.runStore?.getActiveTaskId(sessionId);
782
+ if (holder) {
783
+ sendError(res, 409, "conflict.session_active_run", `session has an active run (${holder}); its files cannot be rewound under it — wait for that run to end (or cancel it), then rewind`, await refusalOccupantOf(deps.runStore, holder));
773
784
  return;
774
785
  }
775
786
  let result;
@@ -883,7 +894,7 @@ export async function handleSessions(req, res, match, ctx) {
883
894
  const owner = deleteAdmitted.row.owner;
884
895
  const result = await purge(sessionId, owner);
885
896
  if ("active" in result) {
886
- sendError(res, 409, "conflict.session_active_run", "session has an active run; cancel it before deleting", { activeTaskId: result.active });
897
+ sendError(res, 409, "conflict.session_active_run", "session has an active run; cancel it before deleting", await refusalOccupantOf(deps.runStore, result.active));
887
898
  return;
888
899
  }
889
900
  if ("inProgress" in result) {
@@ -1,7 +1,7 @@
1
1
  import { redactErrorMessage, redactHead, redactTerminalResult } from "../../observability/run-terminal.js";
2
2
  import { runWithVerification, runCascade, taskNotificationDedupKey, uuidv7 } from "@sema-agent/core";
3
3
  import { heldFor } from "../../peer-seat.js";
4
- import { beatRunHeartbeat, createRunAnchorWriter, graftParkToolCallId, heldParkOf, markChildrenStoppedByUserOnAbort, syncRefusalHttpStatus, zeroWorkRefusalFacts, releaseKeyIfZeroWorkRefusal, createPromptCommitReader, settleResumeAnchor, TurnAnchorCapture, HEARTBEAT_MS } from "../../runs.js";
4
+ import { beatRunHeartbeat, createRunAnchorWriter, graftParkToolCallId, heldParkOf, markChildrenStoppedByUserOnAbort, syncRefusalHttpStatus, zeroWorkRefusalFacts, releaseKeyIfZeroWorkRefusal, TurnAnchorCapture, HEARTBEAT_MS } from "../../runs.js";
5
5
  import { commitParkRow } from "../../park-row-commit.js";
6
6
  import { atHostBoundary, failureCodeOf, sealResultForExit, outwardPlane } from "../../terminal.js";
7
7
  import { voidVerifierPauseCard } from "../../verifier-pause-card.js";
@@ -172,7 +172,7 @@ export async function handleTasks(req, res, match, ctx) {
172
172
  };
173
173
  const assembleApprovalLeg = (streamApprovalOn) => resolveApprovalLeg({ ...approvalLegInput(streamApprovalOn), nowMonotonicMs: performance.now() });
174
174
  const principal = prepared.auth?.subject;
175
- const wiringOperatorFace = explicitOperatorOk(gatedPerson(req, deps.config), deps.config);
175
+ const wiringOperatorFace = explicitOperatorOk(gatedPerson(req, deps.config), deps.config, "operate");
176
176
  let wiringReceipt;
177
177
  const instructionLeg = createInstructionSelectionLeg();
178
178
  const legWiringSections = () => ({ instructions: instructionLeg.segment(), skills: prepared.legFacts?.skills });
@@ -236,7 +236,6 @@ export async function handleTasks(req, res, match, ctx) {
236
236
  onDisconnect();
237
237
  const replayGate = resolveStreamApprovalGate({
238
238
  toolApprovalEnabled: Boolean(deps.toolApproval),
239
- streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
240
239
  backend: deps.backend,
241
240
  checkpointStore: deps.checkpointStore,
242
241
  });
@@ -291,25 +290,6 @@ export async function handleTasks(req, res, match, ctx) {
291
290
  let durableLegId;
292
291
  let durableHeartbeat;
293
292
  const cancelledResult = (errorMessage, cause) => mintCancelledResult({ taskId: durableTaskId ?? "", sessionId: prepared.spec.sessionId, startedAtMs: streamLegStartedAt, message: errorMessage, cause });
294
- let userMsgEntryId;
295
- const promptCommit = createPromptCommitReader();
296
- let anchorPut = false;
297
- const putRewindAnchor = async () => {
298
- const entryId = userMsgEntryId;
299
- const write = createRunAnchorWriter({
300
- seat: deps.backend?.landedAttendants(),
301
- sessionId: prepared.spec.sessionId,
302
- owner: principal ?? null,
303
- promptKey: durableTaskId,
304
- witness: prepared.legFacts?.sessionWitness,
305
- occupantOf: sessionOccupantReadOf(deps.sessionStorage),
306
- leafOf: undefined,
307
- }).prompt;
308
- if (anchorPut || !entryId || write === undefined)
309
- return;
310
- anchorPut = true;
311
- await settleResumeAnchor((held) => write(entryId, held), { leg: "stream", kind: "prompt" });
312
- };
313
293
  try {
314
294
  if (deps.runStore) {
315
295
  const tid = earlyDurableTid ?? uuidv7();
@@ -526,15 +506,32 @@ export async function handleTasks(req, res, match, ctx) {
526
506
  steerableRuns.set(durableTaskId, liveStream);
527
507
  prepared.acceptance.attachStream({ reachable: () => !closed && !res.writableEnded && !res.destroyed, write: (frame) => writeLiveFrame(res, { ...frame }) });
528
508
  const liveWorkspace = createSessionWorkspaceRecorder(sessionWorkspaceStoreOf(deps.sessionStorage), prepared.legFacts?.sessionWitness);
509
+ const anchor = new TurnAnchorCapture(undefined, "stream", createRunAnchorWriter({
510
+ seat: deps.backend?.landedAttendants(),
511
+ sessionId: prepared.spec.sessionId,
512
+ owner: principal ?? null,
513
+ promptKey: durableTaskId,
514
+ witness: prepared.legFacts?.sessionWitness,
515
+ occupantOf: sessionOccupantReadOf(deps.sessionStorage),
516
+ leafOf: undefined,
517
+ }).prompt);
529
518
  for await (const ev of liveStream) {
530
519
  if (closed)
531
520
  break;
532
521
  for (const f of ev.type === "done" ? [...childText.cut(), ...topText.cut()] : topText.onEvent(ev))
533
522
  writeLiveFrame(res, f);
534
- const promptEntryId = promptCommit(ev);
535
- if (promptEntryId !== undefined)
536
- userMsgEntryId = promptEntryId;
537
523
  const causeAtDone = ev.type === "done" ? ac.cause() : undefined;
524
+ switch (ev.type) {
525
+ case "human_input":
526
+ anchor.onHumanInput(ev);
527
+ break;
528
+ case "message_committed":
529
+ await anchor.onMessageCommitted(ev.role, ev.entryId);
530
+ break;
531
+ case "turn_end":
532
+ await anchor.onTurnEnd();
533
+ break;
534
+ }
538
535
  await deps.userInputLedger?.observeLegEvent(liveStream, ev);
539
536
  await prepared.acceptance.observe(ev);
540
537
  if (ev.type === "done")
@@ -552,7 +549,6 @@ export async function handleTasks(req, res, match, ctx) {
552
549
  }
553
550
  finalizeTaskResult(ev.result, principal, prepared.spec.objective);
554
551
  noteRunOutcome(finalResult);
555
- await putRewindAnchor();
556
552
  if (!(durableTaskId && deps.runStore && deps.checkpointStore && heldCard !== undefined))
557
553
  settleFleet(outwardPlane(finalResult.terminal).status, fleetRunResiduals(finalResult, failureCodeOf(finalResult.terminal)));
558
554
  if (ledgerSink && finalResult) {
@@ -694,7 +690,6 @@ export async function handleTasks(req, res, match, ctx) {
694
690
  : withElicit();
695
691
  const streamApprovalOn = resolveStreamApprovalGate({
696
692
  toolApprovalEnabled: Boolean(deps.toolApproval),
697
- streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
698
693
  backend: deps.backend,
699
694
  checkpointStore: deps.checkpointStore,
700
695
  }).active;
@@ -708,14 +703,10 @@ export async function handleTasks(req, res, match, ctx) {
708
703
  abortSignal: ac.signal,
709
704
  sessionId: prepared.spec.sessionId,
710
705
  ...approvalLegOriginOf(prepared.execution),
711
- ...(streamApprovalOn
712
- ? {
713
- family: "approval_request",
714
- transport: hitlTransport,
715
- legKey: "",
716
- ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}),
717
- }
718
- : { family: "tool_approval", transport: hitlTransport }),
706
+ family: "approval_request",
707
+ transport: hitlTransport,
708
+ legKey: "",
709
+ ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}),
719
710
  }
720
711
  : undefined;
721
712
  if (deps.toolApproval) {
@@ -727,7 +718,8 @@ export async function handleTasks(req, res, match, ctx) {
727
718
  taskId: askTaskId,
728
719
  sessionId: prepared.spec.sessionId,
729
720
  ...approvalLegOriginOf(prepared.execution),
730
- ...(streamApprovalOn ? { legKey: "", ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}) } : {}),
721
+ legKey: "",
722
+ ...(legDeadlineMonotonic !== undefined ? { legDeadlineMonotonic } : {}),
731
723
  });
732
724
  }
733
725
  const withApproval = () => approvalCtx && deps.toolApproval ? deps.toolApproval.runWithContext(approvalCtx, withQuestion) : withQuestion();
@@ -755,7 +747,6 @@ export async function handleTasks(req, res, match, ctx) {
755
747
  await releaseKeyIfZeroWorkRefusal(deps.runStore, durableTaskId, finalResult);
756
748
  }
757
749
  else {
758
- await putRewindAnchor();
759
750
  const pending = await deps.checkpointStore?.findPendingTokenBySession(prepared.spec.sessionId, undefined, { axis: "run" });
760
751
  const dcCause = ac.cause();
761
752
  if (pending) {
@@ -192,7 +192,7 @@ export async function handleWorkflowAgentSteer(req, res, match, ctx) {
192
192
  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
193
  return;
194
194
  }
195
- const trusted = explicitOperatorOk(principal, deps.config);
195
+ const trusted = explicitOperatorOk(principal, deps.config, "operate");
196
196
  const wfVerdict = await judgeWorkflowRun(steerEntry, runs.store, wfRunId);
197
197
  if (wfVerdict === "session_mismatch") {
198
198
  refuse(steerEntry, res, "session_mismatch");
@@ -169,11 +169,12 @@ export interface ServiceCoreDeps {
169
169
  * `GET /v1/sessions/:id/mcp` 与 `…/a2a` 两张会话面板 —— 的场景名都由它给,**点名与缺席都问**:点名 ⇒ 判在册 / allowlist,
170
170
  * 拒体(400 `scenario_unknown` 附在册词表 / 400 `scenario_not_allowed` 附 allowlist)与任务提交腿逐字同;缺席 ⇒ 指派链(中心 ruling 胜者,
171
171
  * 否则部署缺省),与这位主体 run 时同一读法(指派悬空 ⇒ 503 `state.scenario_assignment_unknown`,同提交腿)。中心配了却答不出这位主体的裁定 ⇒
172
- * 点名与缺席同答 503 `state.scenario_assignment_unavailable`(同提交腿)。
172
+ * 点名与缺席同答 503 `state.scenario_assignment_unavailable`(同提交腿)。门的答是 `{ scenario, source }`(`ScenarioChoice`):上面三张面只取名字,
173
+ * `GET /v1/capabilities/scenario` 把整只答原样交给客户端(这次请求会生效哪只场景、它从哪一层来)—— 名字与来层只有门这一处铸。
173
174
  * 读面上这只门走**只读腿**(`createScenarioGate(…, "peek")`):问中心裁定时 center 抖动的那一拍,不替这个主体的真提交留下 fail-closed 降级项
174
175
  * (提交腿自己是 `"enforce"`)。
175
176
  * 🔴 **必填**:读口的场景名只有这一个来源,没有「缺接缝 ⇒ 读部署缺省 / 点名 404」这类第二种读法 —— 漏接是**编译错**(嵌入方自己装配时,工厂与它的型从包根导出:
176
- * `createScenarioGate` / `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources`)。
177
+ * `createScenarioGate` / `ScenarioGate` / `ScenarioGateLeg` / `ScenarioGateSources` / `ScenarioChoice` / `ScenarioSource`)。
177
178
  */
178
179
  scenarioGate: import("../capabilities/scenarios.js").ScenarioGate;
179
180
  /**
@@ -321,7 +322,7 @@ export interface ServiceCoordinatorDeps {
321
322
  question?: QuestionCoordinator;
322
323
  /** [ref]/[ref]②: live tool-approval coordinator (core onAsk → CC three-choice card). Set when
323
324
  * TOOL_APPROVAL_ENABLED. Wired onto the runner's onAsk seam AND consumed here by
324
- * `POST /v1/tool-approvals/:id/respond` + the per-run ALS wrap on the SYNC streaming leg (live-only by design —
325
+ * the ask decision door (`POST /v1/tasks/:taskId/asks/:askId/decision`) + the per-run ALS wrap on the SYNC streaming leg (live-only by design —
325
326
  * every other leg keeps core's fail-closed headless auto-deny, [ref]⑤/[ref]④). */
326
327
  toolApproval?: ToolApprovalCoordinator;
327
328
  /** [ref] 裁 (c) 审计半场:local 车道的 File ask **审计**读面(`GET /v1/approvals` 的 additive 键
@@ -944,6 +945,11 @@ export declare function createHttpServer(rawDeps: ServiceDeps): http.Server & {
944
945
  sessionNotifyDoor: SessionNotifyDoor;
945
946
  /** run 收场送达的转交(装配层把它接进 workflow 编排面完成送达的晚绑座;律见 `resume-legs.ts` 的 `handOverRunEndedDecisions`)。 */
946
947
  handOverRunEndedDecisions: (runId: string) => Promise<void>;
948
+ /**
949
+ * 本机控制 socket 那只 `http.Server`(建而不听):与本 server 同一只请求监听器,进门打通道标 `control-socket`(`request-channel.ts`),不挂设备 WS 的 upgrade 监听器。
950
+ * 开口(判 + 听)只经 `control-socket.ts` 的 `openControlSocket`;起服第 10 段在 TCP listen 之后开、收尾链与本 server 一起关。嵌入方不开 ⇒ 面缺席。
951
+ */
952
+ controlSocketServer: http.Server;
947
953
  };
948
954
  /** SSE: replay durable events after Last-Event-ID, then tail until the run is terminal (or stale). */
949
955
  /**
@@ -1,5 +1,6 @@
1
1
  import http from "node:http";
2
2
  import { outwardPlane } from "../terminal.js";
3
+ import { markControlSocketRequest } from "./request-channel.js";
3
4
  import { validatePendingSteer } from "@sema-agent/core";
4
5
  import {} from "../web-admin-client/facade.js";
5
6
  import { describeValueShape } from "@sema-agent/settings-schema";
@@ -28,7 +29,7 @@ import { parkDecisionStoreRefusal } from "../orchestration/workflow-park-decisio
28
29
  import { CHECKPOINT_CTX_SEALING_REQUIRED } from "./checkpoint-ctx.js";
29
30
  import { SERVING_LEASE_MEMBERSHIP_REQUIRED } from "../fleet/engine-signal.js";
30
31
  import { PARKED_REVIVE_EXECUTION_SEAT_REQUIRED } from "../parked-decide.js";
31
- import { a2aAudienceVerifiedSourceRefusal, operatorDeclarationRefusal } from "../config-invariants.js";
32
+ import { a2aAudienceVerifiedSourceRefusal, operatorDeclarationRefusal, operatorRosterWellFormedRefusal } from "../config-invariants.js";
32
33
  import { retiredServiceDepsRefusal } from "./retired-service-deps.js";
33
34
  import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
34
35
  import { createResumeLegs } from "./resume-legs.js";
@@ -94,6 +95,12 @@ export function createHttpServer(rawDeps) {
94
95
  throw new Error(operatorDeclarationFailure);
95
96
  }
96
97
  }
98
+ {
99
+ const rosterFailure = operatorRosterWellFormedRefusal(deps.config);
100
+ if (rosterFailure !== undefined) {
101
+ throw new Error(rosterFailure);
102
+ }
103
+ }
97
104
  {
98
105
  const bridgeDeclared = configuredIdentitySources(deps.config).includes("sso");
99
106
  const verifierWired = deps.webAdminJwtVerifier !== undefined;
@@ -229,7 +236,7 @@ export function createHttpServer(rawDeps) {
229
236
  const { routeCtxBase, resumeCheckpoint, driveResumeIntoRunLog, driveResumeLeg, decisionRowAdmits, resumePreempted, startFreshHostTurn, resumeWake, sessionNotifyDoor, handOverRunEndedDecisions } = createResumeLegs({ deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, admitRun, safeDecode, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor });
230
237
  const { denyExpiredApprovals, redeemParkedAsk } = createApprovalSweeps({ deps, resumeCheckpoint, decisionRowAdmits });
231
238
  const { handle } = createDispatch({ deps, noteActivity, routeCtxBase, counters });
232
- const server = http.createServer((req, res) => {
239
+ const onRequest = (req, res) => {
233
240
  attachResponseLogger(res, deps.logger);
234
241
  void handle(req, res).catch((err) => {
235
242
  if (err instanceof HttpError) {
@@ -258,6 +265,11 @@ export function createHttpServer(rawDeps) {
258
265
  }
259
266
  sendOpenFaceFault(res, err);
260
267
  });
268
+ };
269
+ const server = http.createServer(onRequest);
270
+ const controlSocketServer = http.createServer((req, res) => {
271
+ markControlSocketRequest(req);
272
+ onRequest(req, res);
261
273
  });
262
274
  const deviceLane = facts.deviceLane(deps);
263
275
  if (deviceLane.available)
@@ -289,6 +301,6 @@ export function createHttpServer(rawDeps) {
289
301
  deps.planCacheProbe?.record(principal, objective, result.terminal.kind === "completed");
290
302
  }
291
303
  const isRunLiveOnThisReplica = (taskId) => liveRunIdsOf(inflightRuns, steerableRuns).has(taskId);
292
- return Object.assign(server, { denyExpiredApprovals, isRunLiveOnThisReplica, redeemParkedAsk, sessionNotifyDoor, handOverRunEndedDecisions });
304
+ return Object.assign(server, { denyExpiredApprovals, isRunLiveOnThisReplica, redeemParkedAsk, sessionNotifyDoor, handOverRunEndedDecisions, controlSocketServer });
293
305
  }
294
306
  //# sourceMappingURL=server.js.map
@@ -40,7 +40,7 @@ export declare function persistSubmitCtx(store: SubmitCtxStore, deps: {
40
40
  * 提交腿写续跑输入的入口(三个调用点同调这一只):体与标记按受理座提交时交出的那一份定(见文件头「体里的目标按哪一份写」),只写这一笔。
41
41
  * 写不进的处置与答复 = {@link persistSubmitCtx}(本函数只定体、不另判)。
42
42
  */
43
- export declare function persistSubmissionCtx(store: SubmitCtxStore, deps: Parameters<typeof persistSubmitCtx>[1], claim: Parameters<typeof persistSubmitCtx>[2], prepared: Pick<PreparedTaskSubmission, "body" | "auth" | "verify" | "acceptance" | "bodyFromScreenedCtx"> & Partial<Pick<PreparedTaskSubmission, "legFacts">> & {
43
+ export declare function persistSubmissionCtx(store: SubmitCtxStore, deps: Parameters<typeof persistSubmitCtx>[1], claim: Parameters<typeof persistSubmitCtx>[2], prepared: Pick<PreparedTaskSubmission, "body" | "auth" | "verify" | "acceptance" | "bodyFromScreenedCtx" | "bodyPermissionModeWrite"> & Partial<Pick<PreparedTaskSubmission, "legFacts">> & {
44
44
  readonly spec: Pick<PreparedTaskSubmission["spec"], "sessionId" | "model">;
45
45
  }): Promise<SubmitCtxRefusal | typeof SUBMIT_CTX_CLAIM_LOST | undefined>;
46
46
  //# sourceMappingURL=submit-ctx.d.ts.map
@@ -7,6 +7,7 @@ import { limitFactsUnmeasured } from "../limit-refusal.js";
7
7
  import { serviceVersion } from "../service-version.js";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { DEFAULT_MODEL_REF } from "../model-select.js";
10
+ import { submissionPermissionModeWrite } from "../permission-mode-admission.js";
10
11
  const WITHHELD_OBJECTIVE = "";
11
12
  export function objectiveWithheldIn(ctx) {
12
13
  return ctx.promptScreened === true;
@@ -79,6 +80,7 @@ export async function persistSubmissionCtx(store, deps, claim, prepared) {
79
80
  ...(prepared.legFacts?.projectContext !== undefined ? { projectContext: prepared.legFacts.projectContext } : {}),
80
81
  ...(prepared.legFacts?.memorySeed !== undefined ? { memorySeed: prepared.legFacts.memorySeed } : {}),
81
82
  writerVersion: serviceVersion(),
83
+ permissionModeWrite: prepared.bodyPermissionModeWrite !== undefined ? prepared.bodyPermissionModeWrite.stored : submissionPermissionModeWrite(prepared.body),
82
84
  });
83
85
  }
84
86
  //# sourceMappingURL=submit-ctx.js.map