@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -4,49 +4,21 @@ import { usageSummary, usageSeries, usageBreakdown } from "../../usage-analytics
4
4
  import { sleep } from "../sse-log.js";
5
5
  import { bindSseLifecycle } from "../sse-lifecycle.js";
6
6
  import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
7
+ import { filterFingerprint } from "../cursor-fingerprint.js";
7
8
  import { gatedPrincipal } from "../principal-gate.js";
8
- // [3321] tool-results 读面**共用这一段守卫**([3318] 的字面承诺):新 sub 进的是同一个 `sub` 捕获组,
9
- // 于是下方 fleetWide / gateOwner / per-id owner-404 三道门原样罩住它,不存在第二套门可以漂。
10
- // ⚠️ 「both-or-neither」结构不可破:`:id` 与 `sub` 同在一个可选组里,所以 TRACE_RE 从不匹配裸
11
- // `/v1/tasks/<seg>` —— 那正是 `POST /v1/tasks/stream` 不被本读面遮蔽的原因(见下方 source-summary 注)。
12
- // 新臂写成 `sub` 的一个 alternation 分支(而不是新开捕获组),组号与既有两个捕获保持逐字不变。
13
9
  const TRACE_RE = /^\/v1\/tasks(?:\/([^/]+)(\/turns|\/stream|\/artifacts|\/tool-results\/[^/]+))?$/;
14
- /** tool-results 读面的 sub 前缀(路由判别 + 段切分单源)。 */
15
10
  const TOOL_RESULTS_SUB = "/tool-results/";
16
- /** `limit` 的 server 权威上限(chars)。缺席 = 不传给 store,用 store 自己的默认;超上限 = 400(不静默夹取:
17
- * 夹取会让「我要 1M」与「我要 256K」返回同一段内容,调用方无从知道自己被截了)。 */
18
11
  const TOOL_RESULT_LIMIT_CAP = 262_144;
19
- /** `offset` 的上限 = **最窄后端**能表达的值(codex 复审 R1 medium,已核真)。PG 那半边发的是
20
- * `SUBSTRING(content FROM $1::int …)` 且绑的是 `offset + 1`(`plugins/tool-result-store-sql.ts`),
21
- * 于是 `offset = 2147483647` 过得了「非负整数」这一关,却在库里变成 `2147483648::int` ⇒ 范围错 ⇒ 500。
22
- * 「坏输入回 400」是本面的承诺,承诺不能挑后端兑现,所以上限按 int32 - 1 收(留出 +1 的余量)。 */
23
12
  const TOOL_RESULT_OFFSET_CAP = 2_147_483_646;
24
13
  export async function handleTraceUsage(req, res, url, ctx) {
25
14
  const miss = { fell: false };
26
15
  await handleTraceUsageBody(req, res, url, ctx, miss);
27
16
  return !miss.fell;
28
17
  }
29
- /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
30
- * = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
31
18
  async function handleTraceUsageBody(req, res, url, ctx, miss) {
32
19
  const { deps } = ctx;
33
20
  const { isFleetWide, safeDecode } = ctx.helpers;
34
- // Task Trace API (S1, read-only): GET /v1/tasks[/:id/turns|/stream]. Authorized by EITHER the full authToken
35
- // OR a read-only traceToken (sema-registry pulls traces fleet-wide with one read-only token). Handled BEFORE
36
- // the global gate (like /metrics) so a traceToken-only caller isn't 401'd.
37
- // ⚠️ Blast radius (council DESIGN-1): fleet-wide by design — traces are NOT per-run-owner-scoped here (the
38
- // trace token is a deployment-held read-only credential, like metricsToken). Per-tenant/roster scoping is the
39
- // sema-registry relay's job (admin-session + roster); the kill-switch is leaving TRACE_TOKEN unset.
40
- // Per-door usage rollup (center systems-UI, SERVICE-PROPOSAL-SYSTEMS-DOMAIN): COUNT by (source,status)
41
- // since ?sinceSec ago (default 24h, max 30d). Own route — TRACE_RE's :id group is both-or-neither (it
42
- // deliberately never matches a bare /v1/tasks/<seg>, keeping POST /v1/tasks/stream unshadowed), so this
43
- // CANNOT ride that block (caught live: the in-block branch was dead code → 404).
44
21
  if (req.method === "GET" && url === "/v1/tasks/source-summary") {
45
- // source-summary is a CROSS-TENANT aggregate (count-by-source/status across ALL tenants) with NO owner-scoped
46
- // variant — so it is ops-only: fleet-wide callers (system/trace token) on any deployment, or dev-open on a
47
- // single-user deployment. A multi-tenant deployment without a token must NOT see it (isFleetWide fail-closes —
48
- // the SAME HIGH tenant-isolation fix as the trace/session gates; this endpoint had the un-migrated fail-open
49
- // clause, caught by the systematic-audit review's dimension-2 sweep).
50
22
  if (!isFleetWide(req)) {
51
23
  sendError(res, 401, "auth.unauthorized", "unauthorized");
52
24
  return;
@@ -62,11 +34,8 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
62
34
  }
63
35
  const traceMatch = req.method === "GET" ? TRACE_RE.exec(url) : null;
64
36
  if (traceMatch) {
65
- // FLEET-WIDE (sees ALL tenants): a system authToken or the read-only traceToken (sema-registry ops
66
- // relay), or dev-open on a SINGLE-USER deployment. A multi-tenant deployment without a token is owner-gated,
67
- // NEVER fleet-wide — see isFleetWide (HIGH tenant-isolation audit fix). Otherwise a principal caller is owner-gated.
68
37
  const fleetWide = isFleetWide(req);
69
- const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null); // direct-door safe; `|| null`: an absent principal ("" / undefined) → null
38
+ const gateOwner = fleetWide ? null : (gatedPrincipal(req, deps.config) || null);
70
39
  if (!fleetWide && gateOwner === null) {
71
40
  sendError(res, 401, "auth.unauthorized", "unauthorized");
72
41
  return;
@@ -77,11 +46,7 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
77
46
  }
78
47
  const query = new URL(req.url ?? "", "http://x").searchParams;
79
48
  const taskId = traceMatch[1];
80
- const sub = traceMatch[2]; // undefined (list) | "/turns" | "/stream" | "/artifacts" | "/tool-results/<ref>"
81
- // per-id owner gate for the principal path (404 for a non-owner — no existence oracle, parity with runOwnerOk).
82
- // 🔴 该行**只在 principal 路径**被取(fleet 路径按设计不做 per-id 归属判定)——tool-results 面还要用
83
- // `run.sessionId` 算 ref 前缀,所以这里把取到的行留住(`ownedRun`),fleet 路径由该面自己补一次取行:
84
- // 少了那次取行,fleet caller 就能拿任意 taskId 配任意 ref 读走**别的 session** 的产物。
49
+ const sub = traceMatch[2];
85
50
  let ownedRun;
86
51
  if (taskId && gateOwner !== null) {
87
52
  const run = await deps.runStore.getRun(taskId);
@@ -92,7 +57,7 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
92
57
  ownedRun = run;
93
58
  }
94
59
  if (!taskId)
95
- await handleTaskList(res, deps.runStore, query, gateOwner); // gateOwner (when set) forces the owner filter
60
+ await handleTaskList(res, deps.runStore, query, gateOwner);
96
61
  else if (sub === "/turns")
97
62
  await handleTaskTurns(res, deps.runStore, taskId, query);
98
63
  else if (sub === "/stream")
@@ -106,16 +71,6 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
106
71
  sendError(res, 404, "not_found.route", "not found");
107
72
  return;
108
73
  }
109
- // ── 用量统计查询面(web P4,registry AnalyticsSource "service-usage" 数据源)────────────────
110
- // GET /v1/usage/{summary|series|breakdown}?from&to&…(ISO 或 epoch-ms)。鉴权双支持(回帖契约):
111
- // - fleet 凭证(authToken/traceToken)= 全量视角,可用 principal 参数过滤任意主体;
112
- // - registry JWT(1.141 鉴权桥,verifiedPrincipal)= 强制 owner=本人(参数里的 principal 被忽略);
113
- // - 都没有 → 401。scope 参数 v1 不实装(principal 即租户键;registry 侧按需把 scope→principal 集合映射)。
114
- // 聚合=usage-analytics 纯函数;store 扫窗触顶 → truncated:true 诚实上抛(绝不静默截断)。
115
- // 🔴 位置(1.148 修):必须在全局 service-credential 门【之前】(与 trace 门/metrics 同款待遇)——
116
- // 门函数 isFleetWide 认只读 traceToken,但配了 authToken 的部署里全局门只认 service token/SSO-JWT,
117
- // usage 挂门后时 traceToken-only 调用(registry 用量中继的姿势)在全局门就 401=门内 traceToken 支持成死代码
118
- // (a2 实机抓获)。本块所有命中路径自带 401/501 fail-closed 再 return,未命中 sub 落回后续路由(含全局门)。
119
74
  if (url.startsWith("/v1/usage/") && req.method === "GET") {
120
75
  const sub = url.slice("/v1/usage/".length).split("?")[0];
121
76
  if (sub === "summary" || sub === "series" || sub === "breakdown") {
@@ -124,12 +79,12 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
124
79
  return;
125
80
  }
126
81
  const fleetWide = isFleetWide(req);
127
- const jwtOwner = fleetWide ? undefined : gatedPrincipal(req, deps.config); // 经 gatedPrincipal:未验 Bearer 不回退头
82
+ const jwtOwner = fleetWide ? undefined : gatedPrincipal(req, deps.config);
128
83
  if (!fleetWide && !jwtOwner) {
129
84
  sendError(res, 401, "auth.unauthorized", "unauthorized");
130
85
  return;
131
86
  }
132
- const q = new URL(req.url ?? "", "http://x").searchParams; // NB: `url` 已剥 query,参数必须读 req.url
87
+ const q = new URL(req.url ?? "", "http://x").searchParams;
133
88
  const parseT = (v, dflt) => {
134
89
  if (!v)
135
90
  return dflt;
@@ -137,13 +92,12 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
137
92
  return Number.isFinite(n) ? n : NaN;
138
93
  };
139
94
  const now = Date.now();
140
- const from = parseT(q.get("from"), now - 7 * 86_400_000); // 默认近 7 天
95
+ const from = parseT(q.get("from"), now - 7 * 86_400_000);
141
96
  const to = parseT(q.get("to"), now);
142
97
  if (Number.isNaN(from) || Number.isNaN(to) || from >= to) {
143
98
  sendError(res, 400, "request.query_invalid", "invalid from/to (ISO-8601 or epoch-ms; from < to)");
144
99
  return;
145
100
  }
146
- // owner 过滤:JWT 主体强制=本人;fleet 凭证可选 principal 参数。
147
101
  const owner = jwtOwner ?? (q.get("principal") ?? undefined);
148
102
  try {
149
103
  const scan = await deps.runStore.usageScan(from, to, owner !== undefined ? { owner } : undefined);
@@ -166,7 +120,6 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
166
120
  sendError(res, 400, "request.query_invalid", "dimension=principal|model");
167
121
  return;
168
122
  }
169
- // JWT 主体查 dimension=principal 只会看到自己一桶(owner 已强制)——语义自然成立,无泄漏面。
170
123
  sendJson(res, 200, { ...base, dimension, breakdown: usageBreakdown(scan.rows, dimension) });
171
124
  }
172
125
  }
@@ -179,44 +132,42 @@ async function handleTraceUsageBody(req, res, url, ctx, miss) {
179
132
  }
180
133
  miss.fell = true;
181
134
  }
182
- // ─────────── Task Trace API handlers (S1) ───────────
183
- function encodeCursor(c) {
184
- return Buffer.from(JSON.stringify(c)).toString("base64url");
135
+ function encodeCursor(c, f) {
136
+ return Buffer.from(JSON.stringify({ ...c, f })).toString("base64url");
185
137
  }
186
- function decodeCursor(s) {
138
+ function decodeCursor(s, expectF) {
187
139
  if (!s)
188
140
  return undefined;
189
141
  try {
190
142
  const o = JSON.parse(Buffer.from(s, "base64url").toString());
191
- // Same date-validity guard as decodeSessionCursor: a tampered non-date createdAt would reach `new Date()` in the
192
- // store keyset predicate and 500 (same latent issue as the sibling cursor).
143
+ if (o.f !== undefined && (typeof o.f !== "string" || o.f !== expectF))
144
+ return "mismatch";
193
145
  return o.createdAt && o.taskId && !Number.isNaN(Date.parse(o.createdAt)) ? { createdAt: o.createdAt, taskId: o.taskId } : undefined;
194
146
  }
195
147
  catch {
196
148
  return undefined;
197
149
  }
198
150
  }
199
- /** GET /v1/tasks — cheap list summary (contract 2.1), keyset-paginated newest-first. */
200
151
  async function handleTaskList(res, runStore, query, forceOwner) {
201
152
  const status = query.get("status") ?? undefined;
202
- const jobId = query.get("jobId") ?? undefined; // work-view group filter — all runs of one logical task
203
- const source = query.get("source") ?? undefined; // credential-derived door filter (oa/cc-mcp/portal)
204
- // forceOwner (set on the principal path) PINS the listing to the caller's own runs — it overrides any ?owner
205
- // so a principal can't list another tenant. null/undefined (fleet-wide path) → honor the ?owner query (ops view).
153
+ const jobId = query.get("jobId") ?? undefined;
154
+ const source = query.get("source") ?? undefined;
206
155
  const owner = forceOwner != null ? forceOwner : (query.get("owner") ?? undefined);
207
156
  const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 50) || 50));
208
- const cursor = decodeCursor(query.get("cursor"));
157
+ const f = filterFingerprint([status, jobId, source, owner]);
158
+ const cursor = decodeCursor(query.get("cursor"), f);
159
+ if (cursor === "mismatch") {
160
+ sendError(res, 400, "request.cursor_filter_mismatch", "cursor was issued under a different filter (status/jobId/source/owner) — restart from the first page without a cursor");
161
+ return;
162
+ }
209
163
  const rows = await runStore.listRuns({ ...(status ? { status } : {}), ...(jobId ? { jobId } : {}), ...(source ? { source } : {}), ...(owner ? { owner } : {}), ...(cursor ? { cursor } : {}), limit: limit + 1 });
210
164
  const hasMore = rows.length > limit;
211
165
  const page = hasMore ? rows.slice(0, limit) : rows;
212
- const tasks = page.map((r) => taskSummary(r)); // lastStep from status; latest-event lastStep = a later refinement
166
+ const tasks = page.map((r) => taskSummary(r));
213
167
  const last = page[page.length - 1];
214
- const nextCursor = hasMore && last ? encodeCursor({ createdAt: last.createdAt, taskId: last.taskId }) : undefined;
168
+ const nextCursor = hasMore && last ? encodeCursor({ createdAt: last.createdAt, taskId: last.taskId }, f) : undefined;
215
169
  sendJson(res, 200, { tasks, ...(nextCursor ? { nextCursor } : {}) });
216
170
  }
217
- /** GET /v1/tasks/:id/turns — lazy history (contract 2.2), backward-paginated by turn seq. */
218
- /** GET /v1/tasks/:id/artifacts — what the agent produced (M2). A pure projection of the
219
- * durable log (no new write path), so it works for every historical run. */
220
171
  async function handleTaskArtifacts(res, runStore, taskId) {
221
172
  const run = await runStore.getRun(taskId);
222
173
  if (!run) {
@@ -226,85 +177,11 @@ async function handleTaskArtifacts(res, runStore, taskId) {
226
177
  const events = await runStore.getEvents(taskId, 0);
227
178
  sendJson(res, 200, { artifacts: projectArtifacts(events, { taskId, jobId: run.jobId }) });
228
179
  }
229
- /**
230
- * GET /v1/tasks/:id/tool-results/:ref?offset&limit —— core `ToolResultStore.get` 的 wire 镜像([3321])。
231
- *
232
- * 三件事,顺序本身就是契约:
233
- *
234
- * ① **query 先判**。坏 offset/limit ⇒ 400,且这一判**先于**任何 ref/归属/store 在场性判定 —— 否则
235
- * 「坏 query + 已知 ref = 400」而「坏 query + 未知 ref = 404」,状态码之差立刻成了存在性 oracle。
236
- * query 的合法性只取决于 query 自身,与调用方看不看得见这个 ref 无关,所以先判是安全的。
237
- *
238
- * ② **归属绑定 = 店里记的出处(`ownerOf`)与本 run 的 session/task 精确相等**(实现见 ②-a),不解析 ref、
239
- * 不复制段转义规则,**且没有第二条腿**。
240
- *
241
- * 下面这一大段是这条判据的**病历**(前缀形 → 本 task 日志作证形 → 出处形),留着是因为「为什么不
242
- * 照 ref 的字面去判」每隔一阵就被重新问一次;当前生效的判据只有 🟡 段之后的那一条。
243
- *
244
- * 先说为什么**不是**前缀判定([3321]② 的字面形、[3322] 的指定形):core 的 ref 形是
245
- * `tr_<seg(session)>_<seg(toolCallId)>`,而 `_` 在两段里都合法 ⇒ session `team` 的前缀 `tr_team_`
246
- * 同样命中 session `team_blue` 的 ref(`tr_team_blue_toolu_x`)。本仓的 sessionId 是**调用方自选的
247
- * 任意 ≤64 字符串**(`http/server.ts` 的 submit 面只查长度,刻意宽松),所以租户 A 可以把自己的
248
- * sessionId 取成租户 B 的前缀,拿自己的 taskId 配 B 的 ref 读走 B 的产物(codex 复审 R1 high,已核真)。
249
- * 这在字符串层面**无解**:把余段当 toolCallId 重铸一遍会逐字相等(拼接本身有歧义),而按「更长的
250
- * session 是否存在」去探,既堵不住折成 base+sha 的 sessionId(候选串 ≠ 真 id),又反手造出一个新的
251
- * 跨租户**拒绝**面(别人注册一个 `<你的 session>_toolu` 就能让你自己的 ref 永远 404)——R2 复审两条
252
- * 中标,已撤回该启发式。
253
- *
254
- * 当年的改判据(**已被 ②-a 取代,见下**):ref **必须由本 task 自己的耐久事件日志作证**。日志里每条 tool_start/tool_end 都带
255
- * `toolCallId`(`trace/project.ts` 的 toolStartEventData / toolEndEventData 是唯一铸造点),用 core
256
- * 的**同一个** `buildToolResultRef` 把它们铸成 ref 集合,再要求 `ref` 是其中一员 —— 精确相等,
257
- * 零解析、零转义规则复制,歧义面结构上不存在(A 的 taskId 只作证 A 自己的 toolCallId)。
258
- * 辖域(诚实写明,也正是本路由的语义):本面服务的是**这条 task 的 trace 里看得见的那些产物** ——
259
- * trace 的 tool-result 块与本判据读的是同一份日志,所以「trace 上看得见 ⇒ 这里读得到」成立;
260
- * 同 session 里**别的 task** 的 ref(或日志已过保留窗被逐出的)按 404 同形拒,请到那条 task 上读。
261
- *
262
- * 🟡 **两条残余的现状(7.14.0 合并窗复扫改口,下面原文保留作病历)**。core 按当年这里写的方向动了刀:
263
- * `put` 带出处落库 + `ownerOf` 读回,读面改用出处判定(实现见 ②-a)。逐条对账:
264
- * · (b) 拼接非单射的越权面**已彻底消失** —— ref 铸法换成 `~` 多段,`~` 不在段的合法字符集里。
265
- * · (a) 合成 id 铸的 ref **只销了一半**:offload / budget / compaction 三个写点的出处只声明
266
- * session ⇒ 现在读得到;而 `task-registry-monitor` 的 spill(`<stream>_seg<n>`)与
267
- * `task-registry-agent` 的 spill(`c<cycle>`)在出处里**点名了 taskId,填的是 core TaskRegistry
268
- * 的 handle id**(`b/w/a/m` 前缀 + 16 hex,见 core `task-registry.js`),与本路由段里的 server
269
- * run id(uuidv7)是两个命名空间,恒不相等 ⇒ 这两族 ref 在本面**恒 404**(改前也 404,不是回归)。
270
- * 而它们恰恰是 core 在成品文本里明写「call ReadToolResult with ref …」露给调用方的那批。
271
- * 真解仍在 core 侧(出处该声明的是**调用方认得的** task 坐标,或干脆只声明 session);本仓不自行
272
- * 放宽 taskId 那条腿 —— 那是单方面改判定语义,归属判据会与上游各漂一份。
273
- * 日志重铸那条腿**已删**(见 ②-b:它对任何真实行都不可达,且每次未命中还要付一次全量日志读)。
274
- *
275
- * ⚠️ 两处**已知残余**,都要 core 侧动刀才能消,写在明处而不是让下一个人自己撞(codex 复审 R3):
276
- *
277
- * (a) **覆盖面**:core 除了「按工具调用 id 卸载」这条主路,还从**合成 id** 铸 ref ——
278
- * `task-registry-monitor` 的 `<handle>_<stream>_seg<n>`、`task-registry-agent` 的 `<handle>_c<n>`、
279
- * `runner/compaction-call-options` 的 `<toolCallId>_s<sha>`、budget 的 `idx<k>`。这些 id **不进**
280
- * 耐久事件日志(日志记的是外层调用 id),所以那几类 ref 在本面一律 404 同形拒。
281
- * 方向明确:**不**在本仓照抄那几条命名规则去反推(复制上游规则 = 两处各自漂,宪法禁),
282
- * 真解是 core 在写入时带上 (session/task) 出处、或给出 core 属主的精确成员判定。
283
- *
284
- * (b) **注入性**:`buildToolResultRef` 对 `(session, toolCallId)` **不是单射**(`_` 两段都合法):
285
- * `("team","blue_toolu_1")` 与 `("team_blue","toolu_1")` 铸出同一枚 ref。所以「本 task 日志作证过
286
- * 一个恰好长成 `<受害 session 尾巴>_<受害 toolCallId>` 的 toolCallId」时,判据仍会放行。
287
- * 与被撤掉的前缀形的差别是**门槛**:前缀形只要知道 ref 就能读,本形还要求攻击者能让**模型网关**
288
- * 铸出那一枚精确的 toolCallId(provider 铸,本服务不控也不产)。同样只有 core 换成单射编码才真消。
289
- * 这里**不留特征化测试**去钉这条通路 —— 把缺陷钉成契约是另一种病。
290
- *
291
- * ③ **同形 404**(unknown ref / 出处判不过(含无属主行)/ 本部署没有 durable store)。任何可区分的响应都是
292
- * 缺陷:前两者之差会告诉调用方「这枚 ref 存在,只是不是你的」;第三者若回 501 则会把部署形告诉一个
293
- * 连自己的 ref 都读不到的 caller。文案恒定且**不回显 ref**(回显=把输入原样反射进错误面)。
294
- * 残余面(诚实写明):响应字节同形,**时序**并不同形 —— 三条臂到达的后端调用数不同。要抹平时序得让
295
- * 每条臂都跑满全部后端调用,那是另一档代价/收益裁定,不在本批;本批的判据是响应本身不给 oracle。
296
- */
297
- async function handleToolResult(res, store, runStore, taskId, ownedRun,
298
- /** 已过 `safeDecode` 的 ref;`null` = %-序列坏(客户端错,但同形 404 —— 它同样不该成为一条可区分的信道)。
299
- * 解码用调用点传下来的 helper 而不是本地 try/catch:那条规则(坏序列 ⇒ null,绝不 500)在本仓只有
300
- * `safeDecode` 一个属主,复制一份等于把它变成两个。 */
301
- ref, query) {
302
- // ① query 校验(先于一切存在性判定 —— 见头注)。非负十进制整数,`Number()` 的宽松形(空串/前后空白/
303
- // "1e3"/"0x10"/"1.5")一律不认:宽松解析会让两个不同的请求串静默映射到同一段内容。
180
+ async function handleToolResult(res, store, runStore, taskId, ownedRun, ref, query) {
304
181
  const intParam = (name, cap) => {
305
182
  const raw = query.get(name);
306
183
  if (raw === null)
307
- return undefined; // 缺席 = 用 store 默认
184
+ return undefined;
308
185
  if (!/^\d+$/.test(raw))
309
186
  return "bad";
310
187
  const n = Number(raw);
@@ -324,46 +201,18 @@ ref, query) {
324
201
  sendError(res, 400, "request.query_invalid", "limit must be a non-negative integer of at most 262144");
325
202
  return;
326
203
  }
327
- // ③ 同形 404 的**唯一**下发点:每条臂都必须走这里,不许任何一条自己写一句(文案分家=oracle 复活)。
328
204
  const notFound = () => sendError(res, 404, "not_found.tool_result", "tool result not found");
329
205
  if (ref === null)
330
- return notFound(); // 坏 %-序列(见参数注)
331
- // fleet 路径没做 per-id 取行(见调用点注),这里补齐 —— 归属绑定要 run.sessionId,没有行就没有绑定面。
206
+ return notFound();
332
207
  const run = ownedRun ?? (await runStore.getRun(taskId));
333
208
  if (!run)
334
209
  return notFound();
335
- // ②-a 出处判定(#119,core 5.26.0 —— 上面 (a)/(b) 两条残余的**正解到货**)。
336
- // 店里现在自己记着「这枚 ref 属于谁」(`put` 的第三参落库,`ownerOf` 读回),读面拿**自己已知的字段**
337
- // 比对即可:零解析、零段转义规则复制,拼接歧义那条残余彻底消失,合成 id 那条**消了一半**
338
- // (monitor/agent spill 两族的出处点名的 taskId 是 core handle id,与本函数收到的 server run id 不同
339
- // 命名空间 ⇒ 恒判假;详见头注 🟡 段)。
340
- //
341
- // 🔴 为什么必须改而不是"锦上添花":5.26.0 起 offload ref 是多段单射形(内容坐标进 ref),服务端手里
342
- // 没有内容 ⇒ 原来那条重铸判据**重铸不出来** ⇒ 每一枚真 offload ref 都会 404。读面整死。
343
- //
344
- // ⚠️ 辖域(有意的口径变化,写在明处):出处的粒度由 core 定 —— offload/budget/projection 三个写点只声明
345
- // session,把 taskId 钉在那里会把「一个 session 的两条 task 共用一枚 ref」这个**设计内**的共享形变成拒绝
346
- // (core d.ts 逐字裁过)。所以有出处时的判据是「同 session,且出处**若点名了** task 则必须是本 task」——
347
- // 相对旧的「本 task 日志作证过」是**放宽到同 session**。放宽的边界仍在 owner 之内(run 归属门在本函数
348
- // 之前已过,session 的属主就是这个 caller),换来的是这条读面在 5.26.0 上还活着。
349
210
  const owner = store?.ownerOf ? await store.ownerOf(ref) : undefined;
350
211
  const ownedByThisCaller = owner !== undefined && owner.sessionId === run.sessionId && (owner.taskId === undefined || owner.taskId === taskId);
351
- // ②-b **没有**第二条腿:出处判不过就是 404(unowned 行也在内 —— core 的口径就是「读面对 unowned
352
- // fail-closed」)。这里曾有一条「用本 task 耐久日志作证过的 toolCallId 经 `buildToolResultRef` 重铸再
353
- // 精确相等」的兜底,7.14.0 合并窗复扫判定为**结构性死支**并整条删除,证据链写在明处:
354
- // · 重铸只喂得起两个参数(服务端手里没有内容)⇒ 铸出的是**两段**形 `tr_<sid>~<callId>`;
355
- // · core 5.27.0 的**每一个**落库写点铸的都是 ≥3 段(offload/budget/compaction 三段带内容摘要、
356
- // detail 卸载四段、monitor/agent spill 三段带 `<stream>_seg<n>`/`c<cycle>`),而 `~` 不在段的合法
357
- // 字符集里 ⇒ **段数不同的 ref 恒不相等** ⇒ 该判据对库里任何真实行都不可能成立;
358
- // · 旧引擎(≤5.25.0)的 `tr_<sid>_<call>` 同样重铸不出来(分隔符换了),而按旧规则手写一个 legacy
359
- // 编码器 = 复制上游规则(本文件头注自己禁掉的那件事);存量行的真处置是**删表重建**(见 CHANGELOG)。
360
- // 删的另一半理由是**代价**:走到这条腿的前提是「出处缺席」,而任意乱写的未知 ref 恰好也落在这里,
361
- // 于是每个必然 404 的请求都要付一次整份耐久事件日志读(已鉴权调用方可用随机 ref 放大后端读),
362
- // 换不到任何放行。行为面只收窄不放宽:能放行的集合原本就是空集。
363
212
  if (!ownedByThisCaller)
364
213
  return notFound();
365
214
  if (!store)
366
- return notFound(); // durable store 缺席:404 同形,不 501(不泄露部署形)
215
+ return notFound();
367
216
  const slice = await store.get(ref, { ...(offset !== undefined ? { offset } : {}), ...(limit !== undefined ? { limit } : {}) });
368
217
  if (!slice)
369
218
  return notFound();
@@ -376,20 +225,15 @@ async function handleTaskTurns(res, runStore, taskId, query) {
376
225
  return;
377
226
  }
378
227
  const limit = Math.min(100, Math.max(1, Number(query.get("limit") ?? 20) || 20));
379
- const cursorSeq = Number(query.get("cursor") ?? 0) || 0; // turns with seq < cursor (older); 0 = newest page
228
+ const cursorSeq = Number(query.get("cursor") ?? 0) || 0;
380
229
  const [events, retainedFrom] = await Promise.all([runStore.getEvents(taskId, 0), runStore.retainedFrom(taskId)]);
381
- const all = projectEvents(events); // the objective (user turn) isn't on the run row → omitted for MVP (refinement)
230
+ const all = projectEvents(events);
382
231
  const eligible = cursorSeq > 0 ? all.filter((t) => t.seq < cursorSeq) : all;
383
- const turns = eligible.slice(Math.max(0, eligible.length - limit)); // backward = the newest of the eligible
232
+ const turns = eligible.slice(Math.max(0, eligible.length - limit));
384
233
  const oldest = turns[0];
385
- // The turns cursor is a plain turn-seq (turns are seq-ordered); it is opaque to the client per the contract
386
- // (2.2), so it needn't match the list endpoint's base64url(createdAt,taskId) keyset cursor (council).
387
234
  const nextCursor = eligible.length > limit && oldest ? String(oldest.seq) : undefined;
388
235
  sendJson(res, 200, { turns, ...(nextCursor ? { nextCursor } : {}), retainedFrom });
389
236
  }
390
- /** GET /v1/tasks/:id/stream — resumable SSE (contract 2.3). Maps the durable event log to the contract event
391
- * names, declares mode in the first event, 416s a resume point evicted past retention, heartbeats, and emits an
392
- * explicit `error` on stall so the client never hangs. */
393
237
  async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
394
238
  const run0 = await runStore.getRun(taskId);
395
239
  if (!run0) {
@@ -397,22 +241,17 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
397
241
  return;
398
242
  }
399
243
  const lastId = Number(req.headers["last-event-id"] ?? new URL(req.url ?? "", "http://x").searchParams.get("from") ?? 0);
400
- let from = Number.isFinite(lastId) ? lastId : 0; // unified with streamRunEvents (council DESIGN-4); a <0 from just replays all
244
+ let from = Number.isFinite(lastId) ? lastId : 0;
401
245
  if (from > 0) {
402
246
  const retainedFrom = await runStore.retainedFrom(taskId);
403
247
  if (retainedFrom > from + 1) {
404
- // The events the client wants to resume past were evicted → it must re-sync via GET /turns (contract 2.3).
405
248
  sendError(res, 416, "limit.retention_evicted", "resume point evicted past retention", { retainedFrom });
406
249
  return;
407
250
  }
408
251
  }
409
252
  sseHeaders(res);
410
- // First event declares the stream contract: mode + a version (council lets the relay detect a worker whose
411
- // SSE shape changed, e.g. when P1 adds token-delta granularity). resumeFrom echoes the honored Last-Event-ID.
412
- res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "delta", resumeFrom: from })}\n\n`); // [2373]C-12:data.type 双发与内容帧一致
253
+ res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, mode: "delta", resumeFrom: from })}\n\n`);
413
254
  let closed = false;
414
- // design/245 件1:断连清理走共享助手(req+res 双挂 + 一次性闸)。只挂 `req` 时,请求流被读干的
415
- // 形下这条旗永远翻不过来,泵会对着死 socket 一路轮询到 15 分钟帽 —— 理由全文在 `sse-lifecycle.ts` 顶注。
416
255
  bindSseLifecycle(req, res, () => {
417
256
  closed = true;
418
257
  });
@@ -432,7 +271,7 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
432
271
  if (!run)
433
272
  break;
434
273
  if (run.status !== "running") {
435
- write(await runStore.getEvents(taskId, from)); // the terminal event can land in the gap above
274
+ write(await runStore.getEvents(taskId, from));
436
275
  break;
437
276
  }
438
277
  const stale = Date.now() - new Date(run.updatedAt).getTime() > staleMs;
@@ -441,8 +280,6 @@ async function streamTaskTrace(req, res, runStore, taskId, staleMs) {
441
280
  break;
442
281
  }
443
282
  if (Date.now() - start > SSE_MAX_STREAM_MS) {
444
- // BL-16: the stream hit its 15-min cap but the RUN is still going — emit a terminal event so the client
445
- // can tell "reconnect to continue" from "task completed" (a silent close looked like completion).
446
283
  res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "STREAM_MAX_DURATION", message: "trace stream reached its 15-minute cap — reconnect with Last-Event-ID to continue (the run is still active)" })}\n\n`);
447
284
  break;
448
285
  }