@sema-agent/server 7.103.2 → 7.105.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 (204) hide show
  1. package/MIGRATION.md +378 -2
  2. package/README.md +6 -1
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +320 -16
  5. package/deploy/sema-up/README.md +57 -7
  6. package/deploy/sema-up/cluster-up.sh +18 -4
  7. package/deploy/sema-up/kube-up.sh +16 -0
  8. package/deploy/sema-up/lib/dataroot.sh +142 -0
  9. package/deploy/sema-up/lib/flagshape.sh +69 -0
  10. package/deploy/sema-up/lib/lanes.py +14 -4
  11. package/deploy/sema-up/lib/shell-access.sh +41 -0
  12. package/deploy/sema-up/preflight.sh +14 -1
  13. package/deploy/sema-up/sema-up.sh +134 -37
  14. package/deploy/sema-up/smoke.sh +5 -0
  15. package/dist/auto-mode-face.js +3 -4
  16. package/dist/boot/budget-tracing.js +1 -1
  17. package/dist/boot/config-center.d.ts +6 -2
  18. package/dist/boot/config-center.js +5 -59
  19. package/dist/boot/execution-env.d.ts +11 -0
  20. package/dist/boot/execution-env.js +14 -0
  21. package/dist/boot/execution-lane.d.ts +25 -0
  22. package/dist/boot/execution-lane.js +95 -0
  23. package/dist/boot/memory-consolidation.d.ts +185 -9
  24. package/dist/boot/memory-consolidation.js +192 -32
  25. package/dist/boot/resolve-spec.d.ts +2 -0
  26. package/dist/boot/resolve-spec.js +28 -24
  27. package/dist/boot/runner-deps.d.ts +6 -2
  28. package/dist/boot/runner-deps.js +6 -1
  29. package/dist/boot/session-faces.d.ts +5 -0
  30. package/dist/boot/session-faces.js +18 -2
  31. package/dist/boot/session-incarnation-seat.d.ts +41 -0
  32. package/dist/boot/session-incarnation-seat.js +69 -0
  33. package/dist/boot/stage-01-config.js +2 -2
  34. package/dist/boot/stage-02-registry-pull.js +3 -1
  35. package/dist/boot/stage-04-stores.d.ts +2 -0
  36. package/dist/boot/stage-04-stores.js +3 -1
  37. package/dist/boot/stage-05-execution-env.d.ts +4 -0
  38. package/dist/boot/stage-05-execution-env.js +2 -2
  39. package/dist/boot/stage-06-runners.d.ts +4 -1
  40. package/dist/boot/stage-06-runners.js +7 -14
  41. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  42. package/dist/boot/stage-08-reapers.d.ts +4 -1
  43. package/dist/boot/stage-09-leader.d.ts +6 -1
  44. package/dist/boot/stage-09-leader.js +2 -1
  45. package/dist/boot/stage-10-http-server.d.ts +6 -1
  46. package/dist/boot/stage-10-http-server.js +4 -1
  47. package/dist/brain.d.ts +1 -1
  48. package/dist/config-catalog.d.ts +16 -1
  49. package/dist/config-catalog.js +9 -9
  50. package/dist/config-center/restart-signal.d.ts +4 -4
  51. package/dist/config-invariants.d.ts +7 -1
  52. package/dist/config-invariants.js +37 -6
  53. package/dist/config-provider.d.ts +42 -10
  54. package/dist/config-provider.js +18 -16
  55. package/dist/config-types.d.ts +37 -16
  56. package/dist/config-types.js +1 -0
  57. package/dist/config.d.ts +28 -30
  58. package/dist/config.js +95 -77
  59. package/dist/env-facts.d.ts +13 -3
  60. package/dist/env-facts.js +15 -3
  61. package/dist/execution-lane-caps.d.ts +15 -0
  62. package/dist/execution-lane-caps.js +8 -0
  63. package/dist/file-history-capture.d.ts +42 -35
  64. package/dist/file-history-capture.js +15 -10
  65. package/dist/file-history-disclosure.d.ts +7 -31
  66. package/dist/file-history-disclosure.js +12 -0
  67. package/dist/fleet/fleet-bus.d.ts +5 -1
  68. package/dist/http/admission.d.ts +12 -5
  69. package/dist/http/admission.js +5 -1
  70. package/dist/http/dispatch.d.ts +18 -0
  71. package/dist/http/dispatch.js +19 -6
  72. package/dist/http/principal-gate.d.ts +22 -0
  73. package/dist/http/principal-gate.js +20 -0
  74. package/dist/http/resume-legs.d.ts +1 -1
  75. package/dist/http/resume-legs.js +9 -5
  76. package/dist/http/route-table.d.ts +15 -4
  77. package/dist/http/route-table.js +6 -5
  78. package/dist/http/routes/agents-roster.d.ts +7 -0
  79. package/dist/http/routes/agents-roster.js +6 -2
  80. package/dist/http/routes/approvals-assistant.d.ts +48 -19
  81. package/dist/http/routes/approvals-assistant.js +117 -50
  82. package/dist/http/routes/capabilities.js +3 -2
  83. package/dist/http/routes/config-catalog.js +1 -0
  84. package/dist/http/routes/fleet.d.ts +3 -2
  85. package/dist/http/routes/images.js +0 -1
  86. package/dist/http/routes/memory-consolidation.d.ts +3 -2
  87. package/dist/http/routes/memory-policy.js +3 -3
  88. package/dist/http/routes/rules.js +2 -2
  89. package/dist/http/routes/runs.js +29 -10
  90. package/dist/http/routes/session-sync.js +14 -15
  91. package/dist/http/routes/sessions-list.js +3 -2
  92. package/dist/http/routes/sessions.js +49 -25
  93. package/dist/http/routes/shared-memory.js +2 -2
  94. package/dist/http/routes/side-query.js +8 -1
  95. package/dist/http/routes/tasks.js +22 -19
  96. package/dist/http/routes/trace-usage.d.ts +2 -0
  97. package/dist/http/routes/trace-usage.js +8 -13
  98. package/dist/http/send.d.ts +12 -0
  99. package/dist/http/send.js +12 -1
  100. package/dist/http/server.d.ts +30 -8
  101. package/dist/http/server.js +11 -5
  102. package/dist/http/sse-lifecycle.d.ts +1 -1
  103. package/dist/http/sse-log.d.ts +12 -9
  104. package/dist/http/sse-log.js +1 -8
  105. package/dist/http/sync-submit-body.d.ts +43 -0
  106. package/dist/http/sync-submit-body.js +4 -0
  107. package/dist/http/wire-types.d.ts +23 -5
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.js +1 -0
  110. package/dist/leader/merge.d.ts +8 -3
  111. package/dist/memory-incident-sink.js +2 -1
  112. package/dist/model-select.d.ts +9 -0
  113. package/dist/model-select.js +5 -1
  114. package/dist/observability/corrupt-read-seat.d.ts +151 -16
  115. package/dist/observability/corrupt-read-seat.js +158 -15
  116. package/dist/observability/fail-open.d.ts +17 -1
  117. package/dist/observability/fail-open.js +17 -1
  118. package/dist/observability/metrics.js +1 -1
  119. package/dist/observability/run-terminal.d.ts +60 -10
  120. package/dist/observability/run-terminal.js +119 -9
  121. package/dist/parked-decide.d.ts +8 -3
  122. package/dist/parked-decide.js +56 -20
  123. package/dist/parked-revive-surface.d.ts +14 -7
  124. package/dist/parked-revive-surface.js +5 -1
  125. package/dist/plugins/approval-ask-store-file.d.ts +4 -1
  126. package/dist/plugins/approval-ask-store-file.js +9 -12
  127. package/dist/plugins/approval-ask-store-sql.d.ts +4 -0
  128. package/dist/plugins/approval-ask-store-sql.js +4 -1
  129. package/dist/plugins/approval-exemption-store.d.ts +21 -1
  130. package/dist/plugins/approval-exemption-store.js +36 -7
  131. package/dist/plugins/approval-nonce-store.d.ts +16 -2
  132. package/dist/plugins/approval-nonce-store.js +17 -7
  133. package/dist/plugins/caching-session-store.d.ts +5 -0
  134. package/dist/plugins/caching-session-store.js +3 -0
  135. package/dist/plugins/file-resume-anchor-store.d.ts +7 -2
  136. package/dist/plugins/file-resume-anchor-store.js +19 -5
  137. package/dist/plugins/file-run-store.d.ts +21 -8
  138. package/dist/plugins/file-run-store.js +60 -38
  139. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  140. package/dist/plugins/image-bake-store-sql.js +0 -5
  141. package/dist/plugins/local-session-store.d.ts +22 -0
  142. package/dist/plugins/local-session-store.js +74 -11
  143. package/dist/plugins/memory-run-store.d.ts +7 -5
  144. package/dist/plugins/memory-run-store.js +11 -4
  145. package/dist/plugins/one-time-migrations.d.ts +38 -1
  146. package/dist/plugins/one-time-migrations.js +143 -15
  147. package/dist/plugins/permission-rule-store-file.d.ts +9 -2
  148. package/dist/plugins/permission-rule-store-file.js +37 -51
  149. package/dist/plugins/pg-session-storage.d.ts +3 -0
  150. package/dist/plugins/pg-session-storage.js +19 -10
  151. package/dist/plugins/retention-store-sql.js +1 -0
  152. package/dist/plugins/run-store-sql.d.ts +15 -6
  153. package/dist/plugins/run-store-sql.js +15 -13
  154. package/dist/plugins/schema-indexes.js +1 -0
  155. package/dist/plugins/send-file-ledger.d.ts +5 -1
  156. package/dist/plugins/send-file-ledger.js +11 -6
  157. package/dist/plugins/session-event-payload.d.ts +14 -4
  158. package/dist/plugins/session-event-payload.js +5 -2
  159. package/dist/plugins/session-policy-store-sql.js +18 -5
  160. package/dist/plugins/store-backend.d.ts +13 -7
  161. package/dist/plugins/store-backend.js +10 -6
  162. package/dist/plugins/store-contracts.d.ts +7 -2
  163. package/dist/plugins/tidb-pool.js +12 -5
  164. package/dist/plugins/tidb-session-store.d.ts +6 -1
  165. package/dist/plugins/tidb-session-store.js +17 -10
  166. package/dist/project-identity.d.ts +4 -6
  167. package/dist/read-face-posture.d.ts +21 -0
  168. package/dist/read-face-posture.js +9 -0
  169. package/dist/request-key-closure.js +1 -1
  170. package/dist/request-value-closure.d.ts +72 -3
  171. package/dist/request-value-closure.js +12 -4
  172. package/dist/roster-bound-http-status.d.ts +6 -0
  173. package/dist/roster-bound-http-status.js +6 -0
  174. package/dist/rules-consent.js +0 -11
  175. package/dist/run-local.js +22 -27
  176. package/dist/runs.d.ts +22 -2
  177. package/dist/runs.js +29 -7
  178. package/dist/sealed-key.d.ts +31 -4
  179. package/dist/sealed-key.js +9 -0
  180. package/dist/security.d.ts +5 -1
  181. package/dist/session-children-retire.d.ts +92 -0
  182. package/dist/session-children-retire.js +85 -0
  183. package/dist/session-error-wire.d.ts +58 -0
  184. package/dist/session-error-wire.js +38 -0
  185. package/dist/session-incarnation.d.ts +61 -0
  186. package/dist/session-incarnation.js +43 -0
  187. package/dist/session-policy-wire.d.ts +98 -12
  188. package/dist/session-policy-wire.js +71 -8
  189. package/dist/session-records-skipped-notice.d.ts +20 -0
  190. package/dist/session-records-skipped-notice.js +27 -0
  191. package/dist/tool-approval.d.ts +65 -0
  192. package/dist/tool-approval.js +36 -9
  193. package/dist/trace/core-keyset-guard.d.ts +8 -7
  194. package/dist/trace/engine-notice-wire.d.ts +3 -1
  195. package/dist/trace/engine-notice-wire.js +2 -0
  196. package/dist/trace/project.d.ts +11 -6
  197. package/dist/trace/project.js +12 -3
  198. package/dist/trace/redact.d.ts +7 -0
  199. package/dist/trace/redact.js +153 -61
  200. package/dist/trace/wire-projection-faces.d.ts +10 -3
  201. package/dist/trace/wire-projection-faces.js +8 -2
  202. package/package.json +2 -2
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
@@ -28,7 +28,7 @@ import type { QuotaTracker } from "../observability/cost-quota.js";
28
28
  import type { ModelUsageTracker, PromptManifestTracker } from "../budget.js";
29
29
  import { scopedIdempotencyKey } from "./idempotency.js";
30
30
  export { scopedIdempotencyKey };
31
- import type { ConsolidationSeatLabel } from "../boot/memory-consolidation.js";
31
+ import type { ConsolidationSeatLabel, ConsolidationValve } from "../boot/memory-consolidation.js";
32
32
  import { streamApprovals, checkQuestionAnswer } from "./routes/approvals-assistant.js";
33
33
  export { streamApprovals, checkQuestionAnswer };
34
34
  import { cascadeConfig } from "./run-meta.js";
@@ -112,10 +112,15 @@ export interface ServiceCoreDeps {
112
112
  }) => TaskSpec | Promise<TaskSpec>;
113
113
  /**
114
114
  * [ref] 场景 hands lane 选路:按 `resolveSpec` 刚产出的 TaskSpec 取本任务的执行 Runner。
115
- * 装配点(main.ts)把「场景判别位 → Runner 对」的映射整只闭包传进来 —— 本层不解释场景,只取用。
116
- * 缺席 ⇒ 恒 {@link ServiceDeps.runner}(单 Runner 部署形 = [ref] 之前的行为,逐字不变;测试桩即此形)。
115
+ * 装配点(`boot/stage-10-http-server.ts`)传的是 `createHandsRunnerSelector` —— 选路规则只有那一份实现,
116
+ * 本层不解释场景,只取用。
117
+ * 🔴 **必填**(S-175,7.105.0):修前这一席可选,缺席 ⇒ 静默落 {@link ServiceDeps.runner} —— 最宽的那只
118
+ * (带全量手的主 Runner)。而同一轴上「车道**字段**缺席」早已是拒(S-149,`handsLaneOf` 抛
119
+ * `HandsLaneUnstampedError`),席缺席回落最宽 = 同一件事两条律。现在缺席是**编译错**;绕过类型
120
+ * (JS 嵌入方 / `as` 硬转)造的 deps 在 {@link createHttpServer} 入口**拒启**。单 Runner 的嵌入方显式传
121
+ * `runnerFor: () => runner` —— 那是一句表态,不是一个回落。
117
122
  */
118
- runnerFor?: (spec: TaskSpec) => Runner;
123
+ runnerFor: (spec: TaskSpec) => Runner;
119
124
  }
120
125
  /** **durable 持久面**:每个键 present ⇔ 对应路由/能力位在场,absent ⇒ 501(诚实缺席),
121
126
  * 绝不静默降级。装配点 = main.ts 的 store backend 开箱段。 */
@@ -148,8 +153,9 @@ export interface ServiceStoreDeps {
148
153
  /** [ref] per-edited-file rewind history store. Present ⇒ the rewind capabilities are advertised; core uses it
149
154
  * via RunnerDeps.fileHistoryStore (first-touch tracking + per-turn boundaries + tracked-set restore, ANY
150
155
  * env). Also the 2c history-transfer store (exportHistory/importHistory + blob negotiation faces).
151
- * 🔴 S-589:这是装配单点 `assembleFileHistory` 的**捕获脸**(后端 ∧ `FILE_HISTORY_CAPTURE`),不是「后端有没有这只店」
152
- * —— 旋钮关着时它缺席,rewind 族四位、能力词 `fileHistoryCapture` 与 2c sync 路由族一起按「店不在」走。
156
+ * 🔴 S-724:这是装配单点 `assembleFileHistory` 的**店**(后端在即在,与 `FILE_HISTORY_CAPTURE` 无关 —— 旋钮只关捕获,
157
+ * 落在 core 的 `RunnerDeps.fileHistoryCapture` 上)。rewind 族四位与 2c sync 路由族跟它走;能力词 `fileHistoryCapture`
158
+ * = `fileHistoryCaptureOf(本键, config.fileHistoryCapture)`(与 core 捕获位同一只判定)。
153
159
  * 擦除(E21 / 孤儿 blob GC)不读本键,读 boot 侧的擦除脸。 */
154
160
  fileHistoryStore?: ServiceFileHistoryStore;
155
161
  /** D-1 通用文件上传(clay 拍 2026-07-27:独立附件 store,不骑 snapshot blob 面):enables
@@ -341,10 +347,14 @@ export interface ServiceSeamDeps {
341
347
  * run-ledger/checkpoint/tool-result rows, owner-guarded, in one call. Returns `{ deleted }` (idempotent) — or
342
348
  * `{ active }` when the runs-ledger TX re-asserted a still-live run (a concurrent createRun raced the route's
343
349
  * pre-check), which the route maps to 409. Enables DELETE /v1/sessions/:id. Wired by main.ts (all the stores). */
350
+ /** E21 删除协调器。三种结局:删了 / 有活跑(`{active}`,409 `conflict.session_active_run`)/ S-790:以本会话为根的后台子代没停干净
351
+ * (`{childrenAlive}` = 仍活着的子代句柄,409 `conflict.session_children_alive` 的 `remaining[]`;会话记录不动)。 */
344
352
  purgeSession?: (sessionId: string, owner: string | null) => Promise<{
345
353
  deleted: boolean;
346
354
  } | {
347
355
  active: string;
356
+ } | {
357
+ childrenAlive: readonly string[];
348
358
  }>;
349
359
  /** [ref]③(b): read a BACKGROUND agent-handle child's final report / current status out of the core
350
360
  * TaskRegistry (the same face the TaskOutput tool reads — agent handles are NEVER in the run store).
@@ -460,6 +470,12 @@ export interface ServiceSeamDeps {
460
470
  * 装配漏接 ⇒ tsc 红(本行就是通知);测试桩在 `test/helpers/boot.ts` 显式给 false。
461
471
  */
462
472
  memoryAutoConsolidationArmed: boolean;
473
+ /**
474
+ * S-824 / [ref] + 车PI 修复轮 ④ —— 记忆整理阀门的**生效判决**(boot 第 4 段 `createConsolidationValve`:显式词,或出厂缺省按
475
+ * 装配结果派生)。配置目录那一行(`GET /v1/config/catalog` 的 `MEMORY_CONSOLIDATION_DRIVER` `effectiveValue`)读它 —— 那一列的契约是
476
+ * 「本进程实际生效值」,而这根旋钮的生效值只有装配期才知道。🔴 **必填**:装配漏接 ⇒ tsc 红;测试桩在 `test/helpers/boot.ts` 显式给。
477
+ */
478
+ memoryConsolidationValve: Pick<ConsolidationValve, "enabled" | "source">;
463
479
  /** [ref] 件②(core 5.59.0 `MemoryEngine.listExternalOriginEntries` / `listOriginClearances` /
464
480
  * `clearEntryOrigin`):**外源标记人面**。启用 `GET /v1/memory/origin/external`、
465
481
  * `GET /v1/memory/origin/clearances`、`POST /v1/memory/origin/entries/:entryId/clear`(三口 operator-only)。
@@ -493,7 +509,8 @@ export interface ServiceSeamDeps {
493
509
  /** [ref](core 5.58.0 `runMemoryConsolidationDriver`):记忆 **consolidation 阀门**的操作面。
494
510
  * 启用 `POST /v1/admin/memory/consolidation/run` 与 `GET /v1/admin/memory/consolidation`(两口 operator-only)。
495
511
  * main.ts 把它接到 `createMemoryConsolidationFaces` 的产物上。
496
- * 🔴 缺席的**唯一**成因是「阀门没开」(`MEMORY_CONSOLIDATION_DRIVER` 不是 `on` —— 出厂缺省):
512
+ * 🔴 缺席的**唯一**成因是「阀门没开」(显式 `MEMORY_CONSOLIDATION_DRIVER=off`,或未设而出厂缺省派生成关 ——
513
+ * S-824 / [ref]:整理座落主模型 / 结构上跑不起来;判决属主 `boot/memory-consolidation.ts` `resolveConsolidationValve`):
497
514
  * 阀门开着却结构上跑不了的三种形(引擎没接线 / 后端没有控制面归属 / `MEMORY_PROVENANCE=off`)在
498
515
  * **启动期**就被拒了(`boot/memory-consolidation.ts`),那些机器根本起不来。⇒ 缺席形是 **404 族**
499
516
  * (整域不挂),刻意**不是** 501:501 说「换部署形态」,这里的动作是「把旋钮打开」。全文见
@@ -755,6 +772,11 @@ export interface ServiceDeploymentDeps {
755
772
  * ⚠️ 席位只承载**部署默认**:单用户车道上每请求的 `settings.webSearch` 压过它,但那条腿**不经过本席**
756
773
  * (它在场景装配里每请求现算),所以本位不会随调用方漂。 */
757
774
  webSearchProvider?: import("../plugins/web-search.js").WebSearchProvider;
775
+ /** S-737:本进程模型钥匙保管店的**公钥面** —— `GET /v1/capabilities` 的 `sealedKey` 段唯一席位。值**逐字**取自
776
+ * config-center 运行时(`boot/config-center.ts` 的 `sealedKeyPublic`,boot 期建的那一只店),路由里不再建店、不读全局。
777
+ * 类型只有最新那把公钥(`sealed-key.ts` `SealedKeyPublicFace`):私钥、保管路径、持有的旧 id 列表从这一层结构上够不着。
778
+ * 缺席 = 本进程没有保管店(boot 建店失败,或不是由 composition root 装起来的测试夹具形)⇒ caps 的 `sealedKey` 键缺席。 */
779
+ sealedKeyPublic?: import("../sealed-key.js").SealedKeyPublicFace;
758
780
  /** ① core ruling — SPLIT: `capabilities.workflows` = the ENGINE-CAN axis, boot-computed from core's own
759
781
  * `workflowsCapability(deps)` (hardened script runner ∧ governance), NOT the `Boolean(workflowRunStore)` store
760
782
  * proxy. Orthogonal to `workflowsList` (the durable-list axis = `workflowRunStore`). Falls back to the store
@@ -839,7 +861,7 @@ export declare function createHttpServer(rawDeps: ServiceDeps): http.Server & {
839
861
  /** SSE: replay durable events after Last-Event-ID, then tail until the run is terminal (or stale). */
840
862
  /**
841
863
  * The differences a concrete log (task_run | image_bake) feeds the ONE resumable SSE reader (P2.8). Everything
842
- * the reader does — Last-Event-ID/`?from=` resume, the 416 retention boundary, the per-poll concurrent
864
+ * the reader does — Last-Event-ID/`?from=` resume (every row with seq > from; no eviction answer, S-211 ①), the per-poll concurrent
843
865
  * status+events read, the terminal re-fetch (the terminal event lands in the gap before the status flips), the
844
866
  * stale fallback, the 15-min cap, the 15s idle heartbeat — is provider-agnostic and lives in `streamSseLog`.
845
867
  * 🔴 The task_run provider MUST keep the existing wire bytes EXACTLY (center's relay + 730+ tests depend on it).
@@ -19,7 +19,7 @@ import { cascadeConfig } from "./run-meta.js";
19
19
  export { cascadeConfig };
20
20
  import { coarseStatusForState, errorCodeForExit } from "./routes/images.js";
21
21
  export { coarseStatusForState, errorCodeForExit };
22
- import { sendError, httpErrorCode, msg } from "./send.js";
22
+ import { sendError, sendSessionError, httpErrorCode, msg } from "./send.js";
23
23
  import { createRequestGuards } from "./request-guards.js";
24
24
  import { createAdmission, MAX_SKILL_CONTENT_CHARS, MAX_SYSTEM_PROMPT_CHARS, MAX_OUTPUT_SCHEMA_CHARS, validateUserSkills } from "./admission.js";
25
25
  import { createResumeLegs } from "./resume-legs.js";
@@ -38,6 +38,9 @@ export function flattenServiceDeps(deps) {
38
38
  }
39
39
  export function createHttpServer(rawDeps) {
40
40
  const deps = flattenServiceDeps(rawDeps);
41
+ if (typeof deps.runnerFor !== "function") {
42
+ throw new Error("createHttpServer: the runnerFor seat is required (it picks each task's Runner from its execution lane) — pass the hands-lane selector, or `runnerFor: () => runner` for a single-Runner embedding; refusing to start rather than run every task on the widest Runner");
43
+ }
41
44
  const idemCache = new IdempotencyCache();
42
45
  const inflightRuns = new Map();
43
46
  const cancelledViaVerb = new Set();
@@ -107,15 +110,18 @@ export function createHttpServer(rawDeps) {
107
110
  return;
108
111
  }
109
112
  deps.logger?.error("request_error", { method: req.method, url: req.url, err: redactHead(msg(err), 2000) });
110
- if (!res.headersSent)
111
- sendError(res, 500, "internal.error", "internal error");
112
- else
113
+ if (res.headersSent) {
113
114
  res.end();
115
+ return;
116
+ }
117
+ if (sendSessionError(res, err, "open"))
118
+ return;
119
+ sendError(res, 500, "internal.error", "internal error");
114
120
  });
115
121
  });
116
122
  deps.deviceHub?.attach(server);
117
123
  function runnerFor(spec) {
118
- return deps.runnerFor?.(spec) ?? deps.runner;
124
+ return deps.runnerFor(spec);
119
125
  }
120
126
  function recordTaskResult(result) {
121
127
  const plane = outwardPlane(result.terminal);
@@ -33,7 +33,7 @@
33
33
  * ## 为什么注册完还要回看一眼状态
34
34
  *
35
35
  * 光「订阅未来事件」不够:`'close'` **只发一次**,而四条腿的接线全都排在若干次 await 之后
36
- * (trace 是 `getRun`/`retainedFrom`,runs 是 subagent probe,sse-log 是 `retainedFrom`)。客户端
36
+ * (trace 是 `getRun`,runs 是 subagent probe;sse-log 自 S-211 ① 起写头前已无 await,此前是 `retainedFrom`)。客户端
37
37
  * 在那段慢查询里断开 ⇒ 两个 close 都在监听器注册**之前**烧完(实测:后装的监听器不会被补发)⇒
38
38
  * 清理整条不发生,泵对着死连接轮询到 15 分钟帽。这不是假想:`sse-log.ts` 的 preamble 注里记着的
39
39
  * 同一个坑,当时只把 preamble 挪到了接线之后,而接线本身前面还有别的 await。
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * [ref] A9 seam —— 通用可恢复 SSE 泵(P2.8),从 `http/server.ts` 整段搬出的原文。
3
3
  *
4
- * task_run 与 image_bake 两条日志共用同一套 liveness/heartbeat/416/终态复取/15 分钟帽逻辑;域模块
4
+ * task_run 与 image_bake 两条日志共用同一套 liveness/heartbeat/终态复取/15 分钟帽逻辑;域模块
5
5
  * (routes/images.ts)与 server.ts 都要用它,所以它必须住在 http/ 叶子里(routes/* 值 import server.ts
6
6
  * 会闭合运行时装载环)。
7
7
  */
@@ -17,8 +17,6 @@ export interface SseLogProvider<E extends {
17
17
  } | null | undefined>;
18
18
  /** Events with seq strictly greater than `after`, ascending. */
19
19
  getEvents(id: string, after: number): Promise<E[]>;
20
- /** Earliest retained seq → the 416 resume boundary. */
21
- retainedFrom(id: string): Promise<number>;
22
20
  /** Map ONE event to its SSE frame (the `id:`/`event:`/`data:` lines differ per log). */
23
21
  formatEvent(ev: E): {
24
22
  id: number;
@@ -31,13 +29,12 @@ export interface SseLogProvider<E extends {
31
29
  data: unknown;
32
30
  };
33
31
  /**
34
- * [ref] 车3 §5:**开流 preamble** —— 在 416 判定与 `sseHeaders` **之后**、日志 tail **之前**写的
32
+ * [ref] 车3 §5:**开流 preamble** —— 在 `sseHeaders` **之后**、日志 tail **之前**写的
35
33
  * 一批**非账本**帧(今天唯一的用户 = 审批未决卡的全量对账基准)。
36
34
  *
37
- * 🔴 为什么必须是这里的内部 hook,而不是调用方在 `streamSseLog` **之前**自己写:本 helper 的判序是
38
- * ①读 `last-event-id`/`?from=` → ②保留期比对、超界直接 `sendError(416)` → ③`sseHeaders` → ④tail。
39
- * 在 ① 之前写任何字节 = 提前提交 200 头 ⇒ ② 的 416 分支**结构上不可能再触发**(或触发
40
- * headers-already-sent),而调用方自己先调 `sseHeaders` 又会被本 helper 重复写头。
35
+ * 为什么是这里的内部 hook,而不是调用方在 `streamSseLog` **之前**自己写:写头是本 helper 的事,调用方
36
+ * 先调 `sseHeaders` 会被这里重复写头。(S-211 ① 之前本 helper 在写头之前还有一道 416 判定,那是这条
37
+ * 理由的另一半 —— 判定随「账本不存在局部头部裁剪」一起删了,见 {@link streamSseLog} 头注。)
41
38
  *
42
39
  * 返回帧**不带 `id:`** —— 它不是账本行,不参与 `Last-Event-ID` 游标(壳的规则:preamble 给的是
43
40
  * 权威全量卡集,账本重放里的同 id 帧只用于时间线渲染)。
@@ -83,7 +80,13 @@ export declare function collectSsePreamble(preamble: (signal: AbortSignal) => Pr
83
80
  data: unknown;
84
81
  }>>;
85
82
  /** The shared resumable SSE pump (P2.8) — provider supplies the log-specific differences; the liveness/
86
- * heartbeat/416/terminal-re-fetch/15-min-cap logic is identical for task_run + image_bake. */
83
+ * heartbeat/terminal-re-fetch/15-min-cap logic is identical for task_run + image_bake.
84
+ *
85
+ * 🔴 S-211 ①:**没有「续读点已被淘汰」这一答**。两条日志都**没有局部头部裁剪腿** —— `task_event` 唯一的删除者
86
+ * 是整棵会话树连删(会话删除 / 托管留存,`task_run` 行同批消失 ⇒ 调用方开流前 404),`image_bake_event` 库里一条
87
+ * 删除语句都没有。此前这里按 `retainedFrom > from + 1` 答 416 `limit.retention_evicted`:真淘汰永远走不到它,
88
+ * 唯一走得到的形是「写失败耗掉了号」留下的**头部缺行**(ledger-sink 账本留洞)—— 那一形里什么都没被淘汰,416
89
+ * 是一句谎。续读语义因此只有一句:投 `seq > from` 的每一行(缺的行就是缺的,不补、不猜)。 */
87
90
  export declare function streamSseLog<E extends {
88
91
  seq: number;
89
92
  }>(req: IncomingMessage, res: ServerResponse, provider: SseLogProvider<E>, id: string, staleMs: number): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { bindSseLifecycle } from "./sse-lifecycle.js";
2
- import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "./send.js";
2
+ import { sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "./send.js";
3
3
  export const SSE_PREAMBLE_TIMEOUT_MS = 2_000;
4
4
  export async function collectSsePreamble(preamble, onFailure, timeoutMs = SSE_PREAMBLE_TIMEOUT_MS) {
5
5
  const timedOut = Symbol("preamble-timeout");
@@ -34,13 +34,6 @@ export async function collectSsePreamble(preamble, onFailure, timeoutMs = SSE_PR
34
34
  export async function streamSseLog(req, res, provider, id, staleMs) {
35
35
  const lastId = Number(req.headers["last-event-id"] ?? new URL(req.url ?? "", "http://x").searchParams.get("from") ?? 0);
36
36
  let from = Number.isFinite(lastId) ? lastId : 0;
37
- if (from > 0) {
38
- const retainedFrom = await provider.retainedFrom(id);
39
- if (retainedFrom > from + 1) {
40
- sendError(res, 416, "limit.retention_evicted", "resume point evicted past retention", { retainedFrom });
41
- return;
42
- }
43
- }
44
37
  sseHeaders(res);
45
38
  let closed = false;
46
39
  bindSseLifecycle(req, res, () => {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * S-173 —— 非流式 `POST /v1/tasks` 的 200 体的**唯一铸点**。
3
+ *
4
+ * ## 一条规则
5
+ * 同步 200 体恒 = **「出口封口 + 脱敏后的引擎结果」**(调用方先 `redactTerminalResult(sealResultForExit(result))`
6
+ * 铸**一次**,同一份对象既落 run 行、又进本体 —— 两个载体同一只来源);run 还停在门上(park)⇒ 再带
7
+ * **poll 回执三键** {@link SyncParkReceipt}。park 不是例外:三个 200 出口(终局 / suspended / needs_review)都经本函数。
8
+ *
9
+ * ## 修前的病
10
+ * park 两臂自造 `{ taskId, sessionId, status }`,**没有** `terminal`;同一次 park 在 `/v1/tasks/stream` 的
11
+ * `done.result` 上是 `terminal.kind === "paused"`,在本腿上是一个状态词 —— 同一事实两形。
12
+ *
13
+ * ## 回执三键为什么还在(additive,不是 BREAKING)
14
+ * 修前的消费方按这三键判读(`status` 词 → 转去 `GET /v1/runs/:taskId` poll)。三键**原值原样**叠在结果之上:
15
+ * · `taskId` = 同步腿提交时铸的 durable run id(= 结果自带的 `taskId`:同步腿把它经 `spec.taskId` 交给引擎);
16
+ * · `sessionId` = 提交的会话(= 结果自带的 `sessionId`);
17
+ * · `status` = **对外词**,即 `GET /v1/runs/:id` 行上的那个词(`outwardPlane(result.terminal).status`,词由 core
18
+ * pause registry 的 `taskStatus` 列给)。引擎结果自 core 7.6.0 起**没有** `status` 键(D-8),所以不存在「两个
19
+ * status 打架」;这一键只在 park 时在场 —— 终局体上它从 7.64.0 起就不在,本件不把它加回去(那会给终局体
20
+ * 重新造一个 `terminal.kind` 之外的第二真源)。
21
+ * 回执叠在结果**之后**:三键的值恒等于修前,消费方读到的字节与修前逐字相同(R5 正控)。
22
+ *
23
+ * ## 安全轴
24
+ * 本函数**不**做封口 / 脱敏 —— 那两件事的属主是 `sealResultForExit`(`terminal.ts`)与 `redactTerminalResult`
25
+ * (`observability/run-terminal.ts`),调用方铸一次交进来;本函数只负责「体长什么样」。凭据(`paused.token` /
26
+ * `failed.nestedPause.token`)因此在进本函数之前就已摘掉,幂等缓存存的正是本函数的产物,重放体同体同脱。
27
+ */
28
+ import type { ParkedRunStatus, PersistedTaskResult } from "../plugins/store-contracts.js";
29
+ /** park 中的 run 在同步 200 体上带的 poll 回执三键(修前 park 体的全部内容,值逐字不变)。 */
30
+ export interface SyncParkReceipt {
31
+ /** 同步腿提交时铸的 durable run id —— `GET /v1/runs/:taskId` 的 poll 键。 */
32
+ taskId: string;
33
+ /** 提交的会话。 */
34
+ sessionId: string;
35
+ /** 对外词 = `GET /v1/runs/:id` 行上的 `status`(park 两词之一,闭集从 `RUN_STATUS_CLASS` 派生)。 */
36
+ status: ParkedRunStatus;
37
+ }
38
+ /**
39
+ * 铸同步 200 体。`exitResult` = 已封口已脱敏的引擎结果(与 run 行落的是同一份对象);`parkReceipt` 在场 ⇔ run 停在门上。
40
+ * 返回的是纯数据(无方法、无闭包)⇒ `build*`。
41
+ */
42
+ export declare function buildSyncSubmitBody<R extends PersistedTaskResult>(exitResult: R, parkReceipt?: SyncParkReceipt): R | (R & SyncParkReceipt);
43
+ //# sourceMappingURL=sync-submit-body.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function buildSyncSubmitBody(exitResult, parkReceipt) {
2
+ return parkReceipt === undefined ? exitResult : { ...exitResult, ...parkReceipt };
3
+ }
4
+ //# sourceMappingURL=sync-submit-body.js.map
@@ -9,6 +9,7 @@
9
9
  import type { A2aServerSpec, AgentDefinition, ApproverPosture, McpServerSpec, SkillSpec } from "@sema-agent/core";
10
10
  import type { SettingsPermissionMode } from "../task-settings.js";
11
11
  import type { wiringManifestFaceEventData } from "../trace/project.js";
12
+ import type { buildSyncSubmitBody } from "./sync-submit-body.js";
12
13
  export interface TaskRequestBody {
13
14
  objective: string;
14
15
  sessionId?: string;
@@ -173,8 +174,10 @@ export interface TaskRequestBody {
173
174
  };
174
175
  /** [ref] file restore ([ref]): with `resumeAt`, ALSO converge the working tree's **agent-edited files**
175
176
  * (the tracked set — nothing else is visible to the history, so a user's parallel work is structurally
176
- * untouchable) to that entry's history boundary. Capture needs no request anymore: first-touch tracking is
177
- * always on when the deployment wires a history store. Requires a FileHistoryStore. */
177
+ * untouchable) to that entry's history boundary. Capture needs no request: first-touch tracking is on
178
+ * whenever the deployment wires a history store, unless `FILE_HISTORY_CAPTURE=false` (the deployment's capture
179
+ * switch) or this request's {@link fileHistory} turns it off — restore reads what the store already holds
180
+ * either way. Requires a FileHistoryStore. */
178
181
  restoreFiles?: boolean;
179
182
  /** DV-15: opt-in tolerance for a PARTIAL file restore (default = loud terminal failure carrying the per-file
180
183
  * ledger; `true` = keep going and disclose the same ledger on the result's rewindNotes). */
@@ -189,6 +192,17 @@ export interface TaskRequestBody {
189
192
  * value is the same handle as `resumeAt` (the prompt's taskId); the service resolves it → the entry id. Honored ONLY
190
193
  * when `resumeAt` is ABSENT (the two modes are mutually exclusive). Requires the anchor map + a FileHistoryStore. */
191
194
  rewindFilesTo?: string;
195
+ /** S-724(core 7.31.0 #1048 `TaskSpec.fileHistory`)—— 请求级的**文件历史关断词**,唯一词 `"disabled"`(core 的拼法,
196
+ * 与 `checkpointStore` 同;没有 `"enabled"` —— 缺席就是「部署说了算」的唯一形,声明只能单向收紧)。`"disabled"` ⇒
197
+ * 这条 run 零历史店调用:编辑照常落盘,但不做首次触碰备份、不铸回合边界、不做 fork 携带;委派出去的子代继承
198
+ * (core `ParentSpecSeat` 从本位铸,本仓零接线)。`wiring_manifest.fileHistory` 报 `"disabled"`。
199
+ * 同一个提交里再要还原(`restoreFiles` / `rewindFilesTo` / 退役 `rewindFiles`)= 体内矛盾 ⇒ **受理时** 400
200
+ * `request.field_conflict`(`request-value-closure.ts` 的 `FILE_HISTORY_DISABLED_NO_RESTORE`):core 的 `rewind.capture_disabled`
201
+ * 落在会话按 `resumeAt` 分叉之后、拒后不回滚,所以本层在副作用之前拒。续跑腿不过这道门。
202
+ * ⚠️ 拼写门**两腿同判、响亮拒**:fresh 400 `request.field_invalid`;resume 重放同判(与 `memoryCapture` / `memory`
203
+ * 同一张单成员闭集表,`request-value-closure.ts` 的 `singleMemberOffFieldIssue`)。
204
+ * Rides the persisted body onto resume legs —— 它是这条 run 的策略,不是一次性历史动作(与 `rewindFilesTo` 不同,续跑不剥)。 */
205
+ fileHistory?: "disabled";
192
206
  /** [ref] (core 1.246): per-task resilience INTENT flags. allowDegrade/allowFailover are caller-facing;
193
207
  * bypassBreaker is operator-only (resolveSpec drops it for non-operators — a permission downgrade, not a 400). */
194
208
  resilience?: {
@@ -238,8 +252,8 @@ export interface TaskRequestBody {
238
252
  * behavior). Purely a render channel — core never merges the child stream into the parent's model context. */
239
253
  forwardSubagentEvents?: boolean;
240
254
  /** G3([ref] 壳侧承诺③/[ref]③):壳供 per-session scratchpad 路径(壳 vendored getScratchpadDir 的
241
- * 约定路径)。接受后优先于 server 自算,同源喂 envFacts.scratchpadDir 事实 + fs-write gate exemptDirs +
242
- * additionalDirectories 根围栏——壳提示词与 server 写门指同一个目录。验收 fail-closed(单用户 +
255
+ * 约定路径)。接受后优先于 server 自算,同源喂 envFacts.scratchpadDir 宣告(core 据它开根围栏的洞,
256
+ * S-842 起是唯一写者)+ fs-write gate exemptDirs——壳提示词与 server 写门指同一个目录。验收 fail-closed(单用户 +
243
257
  * host-semantics lane + 绝对路径/深度/长度门,规则在 env-facts.acceptShellScratchpadDir),拒收形
244
258
  * warn 后回落自算——「没到货引擎忽略 = 无害 additive」的对偶:异形键忽略,任务照跑。 */
245
259
  scratchpadDir?: string;
@@ -546,7 +560,11 @@ export type ApprovalStaleCurrentPending = {
546
560
  * `unknown`,产物按段 present-iff 铸)。所以这一行**不**声称「wire 上的每一段都有静态型」;它声称的是
547
561
  * 「这一键的值恒等于那只投影器的产物」,而那正是消费端唯一需要的那句话。段级判据的属主在 core。 */
548
562
  export type WiringManifestReceipt = ReturnType<typeof wiringManifestFaceEventData>;
549
- /** S-528 —— `POST /v1/tasks`(**非流式**)200 体在 core `TaskResult` / park 回执之外带的**附加键**。
563
+ /** S-173 —— `POST /v1/tasks`(**非流式**)200 体的值形(不含下方 {@link SyncSubmitExtraKeys}),**从铸点返回型派生**:
564
+ * 「封口脱敏后的引擎结果」,park 时再叠 poll 回执三键 `{taskId, sessionId, status}`。键表的唯一属主是
565
+ * `http/sync-submit-body.ts` 的 `buildSyncSubmitBody`(结果的键集属主是 core `TaskResult`),这里不手抄一张。 */
566
+ export type SyncSubmitBody = ReturnType<typeof buildSyncSubmitBody>;
567
+ /** S-528 —— `POST /v1/tasks`(**非流式**)200 体在 {@link SyncSubmitBody}(引擎结果 / park 回执)之外带的**附加键**。
550
568
  *
551
569
  * 两键都是 **additive + present-iff**,同一只装饰点铸(`routes/tasks.ts` 的 `withSyncSubmitKeys`),
552
570
  * 所以三处 200 出口(终局 / suspended / needs_review)与幂等重放腿自动同形:
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { loadConfig, type ServiceConfig } from "./config.js";
2
+ export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/execution-lane.js";
2
3
  export { createBrain } from "./brain.js";
3
4
  export { createSessionStore } from "./plugins/session-store.js";
4
5
  export { createStoreBackend, type StoreBackend, type SessionCaptureRecordSource } from "./plugins/store-backend.js";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { loadConfig } from "./config.js";
2
+ export { composeExecutionLane, assertExecutionLaneInvariants } from "./boot/execution-lane.js";
2
3
  export { createBrain } from "./brain.js";
3
4
  export { createSessionStore } from "./plugins/session-store.js";
4
5
  export { createStoreBackend } from "./plugins/store-backend.js";
@@ -60,9 +60,14 @@ export interface IntegrationRepair {
60
60
  /**
61
61
  * S-113 / clay [ref] ④ — **合不上 = 交给用户**(对齐 CC 形)。`git apply --3way` 冲突时的产出:冲突**路径**
62
62
  * (不含内容)+ 哪几只 worker 的补丁已经进树,由 leader 铸成 `LeaderResult.conflict` 段交到用户手上。
63
- * 本段刻意**只有路径与身份**:CC 259 语料对 collapse / conflict resolution 零命中,CC 的并行是同机 teammates
64
- * 各 worktree、结束由 git 重放到用户 HEAD、冲突列给用户(cli259.js @2303113)——没有任何模型去读别的 worker
65
- * 的 diff。7.83.0 删掉的那条「冲突解决模型」通道(它的类型面与 `LEADER_CONFLICT_ROUNDS` 旋钮)正是此意。
63
+ * 本段刻意**只有路径与身份**,不把任何 worker 的 diff 交给模型。CC 对照(2.1.280 语料):`conflict resolution` 零命中,
64
+ * `collapse` 的 169 处命中里没有一处与「把并行 worker 收成一个」相关(UI 折叠 / context collapse 一类);teammates 各一个
65
+ * worktree,团队收尾对每个成员的 worktree 只做移除(移不掉就保留),这条路径上没有 merge / 重放回用户 HEAD(cli280.js:214129
66
+ * `swarm_team_cleanup` / `le`);`/batch` 是每个隔离 worktree agent 各开一个 PR(cli280.js:224700)。旧引的
67
+ * `cli259.js @2303113` 不是 teammates,而是 Directory sync:agent 未发布的 commit 重建到用户 HEAD 上,重放冲突时 commit
68
+ * 停到一个 ref、改动以未提交编辑留在工作树且用户的行胜出 —— 冲突是告诉**模型**的(路径 + blob id,不含 diff 正文;
69
+ * cli280.js:247957)。「CC 里没有模型读别的 worker 的 diff」只有缺席证据,未闭环(车LZ X2)。
70
+ * 7.83.0 删掉的那条「冲突解决模型」通道(它的类型面与 `LEADER_CONFLICT_ROUNDS` 旋钮)正是此意。
66
71
  */
67
72
  export interface MergeConflict {
68
73
  /** 有冲突标记或 `.rej` 的路径(**不含内容**;`.rej` 后缀已剥,指向源文件)。 */
@@ -1,3 +1,4 @@
1
+ import { redactIncarnations } from "./session-incarnation.js";
1
2
  export function createMemoryEngineIncidentSink(deps) {
2
3
  return (err, ctx) => {
3
4
  const code = err?.code;
@@ -5,7 +6,7 @@ export function createMemoryEngineIncidentSink(deps) {
5
6
  deps.logger.warn("memory_engine_incident", {
6
7
  ...(ctx?.sessionId !== undefined ? { sessionId: ctx.sessionId } : {}),
7
8
  code: codeText ?? null,
8
- detail: String(err),
9
+ detail: redactIncarnations(String(err)),
9
10
  });
10
11
  deps.metrics?.inc("memory_engine_incident_total", { code: codeText ?? "none" });
11
12
  };
@@ -1,3 +1,4 @@
1
+ import { type Model, type RunnerDeps } from "@sema-agent/core";
1
2
  import type { ModelEntryRefusal } from "./model-entry-refusal.js";
2
3
  /**
3
4
  * Resolve the model ref for a task spec from the request (CLI `/model` picker + `run --model`).
@@ -28,6 +29,14 @@ import type { ModelEntryRefusal } from "./model-entry-refusal.js";
28
29
  * `resolveModel("default", catalog)` 解它)。**单点**:读面要说「这台部署的主模型是谁」时读同一个词
29
30
  * (`auto-mode-face.ts` 的席解析器入参),两处各写一个 `"default"` 字面串正是漂移的成因。 */
30
31
  export declare const DEFAULT_MODEL_REF = "default";
32
+ /**
33
+ * 本仓给一条**没选模型**的 task 铸的主模型 —— {@link DEFAULT_MODEL_REF} 在**展开**目录上(与引擎构造期 `expandTiers(models, tiers)`
34
+ * 之后再解逐字同源)解到的那只**目录条目**(对象引用)。`boot/resolve-spec.ts` 每条 spec 都盖 `spec.model`,所以引擎眼里的 main
35
+ * 恒是它,**不是** core 的 `default` **角色**链。解不出 ⇒ 抛(core 的拒句);调用方决定披露形。
36
+ * 读它的两处(`auto-mode-face.ts` 的分类器席 / `boot/memory-consolidation.ts` 的整理座是否落主模型)调这同一只 —— 车PI 修复轮 ⑦(h)
37
+ * 起不再各抄一份「展开 + 解」。
38
+ */
39
+ export declare function resolveMainModel(plane: Pick<RunnerDeps, "models" | "tiers">): Model;
31
40
  export declare function resolveTaskModel(bodyModel: unknown, objective: string, catalog: Record<string, unknown>, allowlist?: readonly string[], refusals?: Readonly<Record<string, ModelEntryRefusal>>): {
32
41
  model: string;
33
42
  cleanedObjective: string;
@@ -1,5 +1,9 @@
1
- import { expandTiers, parseModelMention } from "@sema-agent/core";
1
+ import { expandTiers, parseModelMention, resolveTaskModel as coreResolveTaskModel } from "@sema-agent/core";
2
2
  export const DEFAULT_MODEL_REF = "default";
3
+ export function resolveMainModel(plane) {
4
+ const expanded = expandTiers(plane.models, plane.tiers ?? {}) ?? plane.models;
5
+ return coreResolveTaskModel({ model: DEFAULT_MODEL_REF }, { ...(expanded !== undefined ? { models: expanded } : {}) }).model;
6
+ }
3
7
  export function resolveTaskModel(bodyModel, objective, catalog, allowlist, refusals) {
4
8
  const mention = parseModelMention(objective, [...Object.keys(refusals ?? {}), ...Object.keys(catalog)]);
5
9
  const mentionUsable = mention.model !== undefined && Object.hasOwn(catalog, mention.model) ? mention.model : undefined;
@@ -31,12 +31,15 @@
31
31
  * · **不接 `recordFailOpen`**:那条通道是给 F 类(装饰/缓存)fail-open 的计数与探针用的;这里的 fail-open
32
32
  * 是 core 的裁定、本仓不改它,server 的职责只有一条 —— **不许无声**([ref] 安全轴纪律)。响亮 warn 就是
33
33
  * 那条职责的兑现;要不要再进 fail-open 台账是另一件事、另一台车的设计。
34
+ * (⚠️ 这一条只说 core 自带 File 店的座。**本仓自有**的 JSONL 店是另一回事 —— 失败方向归本仓裁,见文件
35
+ * 后半的 S-582 重放属主:fail-open 的座**必须**进 F 类台账,fail-closed 的座不进台账而是拒用。)
34
36
  *
35
37
  * ⚠️ core 那只 guard 的纪律同样适用:sink 是**无绑定**调用的(`this` 是 undefined)。本属主返回的是箭头
36
38
  * 闭包,天然满足;但**不要**把某个对象的裸方法引用直接塞进座 —— 读 `this` 会抛,而那个抛会被 core 的
37
39
  * guard 吞掉,于是披露**静默丢失**(比没接座更难查)。
38
40
  */
39
- import type { FileStorageCorruptReadInfo, readJsonlRecords } from "@sema-agent/core";
41
+ import { type FileStorageCorruptReadInfo } from "@sema-agent/core";
42
+ import { type FailOpenTag } from "./fail-open.js";
40
43
  import type { Logger } from "./logger.js";
41
44
  /**
42
45
  * 铸一只腐读披露座。
@@ -46,28 +49,160 @@ import type { Logger } from "./logger.js";
46
49
  * @param event warn 事件名。**留在调用点**:它是那只店的身份,接线锚(test/roster-corrupt-read.test.ts)
47
50
  * 正是靠「构造实参里能读到这个名字」来证明座不是个空回调。
48
51
  * @param note 这张脸的后果句(可选)。缺席 ⇒ 不写这个键。
52
+ * @param leg 与披露**同拍**的一条附加腿(可选;S-612 加):座拿到一次披露时先交给它、再写日志。唯一的用户是
53
+ * `LocalSessionStore` 那只 `FileSessionRepo` 的座 —— 会话打开作用域的计数腿(`noteReplayInSessionOpenScope`:
54
+ * 作用域在场时把 `replay` 计数记到那次打开上,由打开方铸 `session.records_skipped`)。腿**必须不抛**(那一只是纯内存
55
+ * 赋值);先腿后日志,是因为日志座可能坏(core 的 guard 会吞掉它的抛),而一次日志故障不许吃掉这一记。
56
+ * 座本身不变:载荷投影、事件名、后果句仍由本属主铸,腿只多拿一份同一个 `info`。
49
57
  */
50
- export declare function createCorruptReadSeat(warn: Logger["warn"], event: string, note?: string): (info: FileStorageCorruptReadInfo) => void;
58
+ export declare function createCorruptReadSeat(warn: Logger["warn"], event: string, note?: string, leg?: (info: FileStorageCorruptReadInfo) => void): (info: FileStorageCorruptReadInfo) => void;
51
59
  /**
52
60
  * `FileStorageBackend` 那两只座共用的后果句 —— 一个座覆盖 core 转发的三张脸,所以共同事实只有一句,
53
61
  * 差别只在第三张脸叫什么(`run-local` 腿看到的是 file-snapshot,HTTP 服务腿看到的是 file-history)。
54
62
  * 这一句从前在两处各抄一份、只差一个词;现在词从参数来,句子只有一份。
55
63
  */
56
64
  export declare function buildDurableAbsenceNote(thirdFace: string): string;
57
- /** core `readJsonlRecords` 的坏行回调入参(core 不从包根导出这两级的型名 ⇒ 从那只函数的签名派生,不手抄)。 */
58
- export type JsonlCorruptInfo = Parameters<NonNullable<Parameters<typeof readJsonlRecords>[1]>>[0];
59
- /** 其中「一条被跳过的记录」那一级。 */
60
- export type JsonlSkippedRecord = Extract<JsonlCorruptInfo, {
61
- kind: "record";
62
- }>;
63
65
  /**
64
- * S-616(core 7.30.0 #1041 提货)—— 本仓直接调 `readJsonlRecords` 的店(approval-ask 日志、permission-rule 两本日志)
65
- * 的坏行回调**唯一**形。core 自这一版起对一次坏读报两级:每条被跳过的记录一发 `record`,这次重放的计数一发
66
- * `replay`(`dropped` = 同一批 `record` 的条数)。这些店按**记录**计数、按记录告警 / 跳闸 —— 手写回调不分级就把同一条
67
- * 坏行记两次(k3s8 实测:approval-ask 的「1 条解析不出」读成 2 条,permission-rule 的 onError 一行变两行)。
68
- * 规则一句:**只按记录计,计数级是同一事实的汇总,不再计一次**;穷举 switch,core 加第三级 ⇒ 编译红。
69
- * 与 {@link createCorruptReadSeat} 的分工:那只是 core 自带 File 店的**披露座**(两级都转发,日志按 `kind` 分桶);
70
- * 本函数是本仓自有店**消费**坏行事实的口子(计数 / 跳闸只要记录级)。
66
+ * 一只店对自己记录的**形判据 + 投影**。`read` 认得 ⇒ 返回店的记录;认不得 ⇒ `undefined`。
67
+ *
68
+ * 判据写**最小必填键**:本店重放真读的那几格必须在、且形对(一格读错形会被重放读成另一件事 —— 例如豁免店的
69
+ * `revoked` 若是字符串 `"false"`,真值判断会把它当成撤销)。不写「是对象就行」:那一层 core 已经判过了。
70
+ * 🔴 `read` 必须是记录的**纯函数** —— 属主对 core 留下的每条记录会再读一次取投影(core 的判据座只回布尔)。
71
+ */
72
+ export interface JsonlRecordShape<T> {
73
+ /** 被拒那一行在披露句里的名字(core `JsonlRecordJudge.what`)。 */
74
+ readonly what: string;
75
+ read(record: object): T | undefined;
76
+ }
77
+ type JsonlReplaySeatEntry = {
78
+ readonly direction: "fail-closed";
79
+ /** 腐读座的 warn 事件名。 */
80
+ readonly event: string;
81
+ /** 这只面在拒绝句里的名字。 */
82
+ readonly face: string;
83
+ /** 进每一条披露的后果句。 */
84
+ readonly note: string;
85
+ /** 拒绝句的出路段:人该做什么、移开文件会丢什么。 */
86
+ readonly remedy: string;
87
+ } | {
88
+ readonly direction: "fail-open";
89
+ readonly event: string;
90
+ /** 逐条计数的 F 类 tag(`fail-open.ts` 闭集词表;普查表 §1 有登记行)。 */
91
+ readonly tag: FailOpenTag;
92
+ readonly note: string;
93
+ };
94
+ /**
95
+ * JSONL 座的**登记表**(闭集:未登记的座名传不进属主 ⇒ 编译红)。每一座在 `docs/FAIL-OPEN-CENSUS.md` §1 有登记行。
96
+ * · `fail-closed` —— 权限 / 安全轴:一行读不出 = 一次授权事实缺了一块,拿残缺的账判决会**放行**不该放行的
97
+ * (nonce 重放 / 已撤销的豁免复活 / 已认领的票复活 / 审批记录连同 rev 回滚)。
98
+ * · `fail-open` —— 可用性轴:丢一行 = 丢一段进度 / 一条列表项 / 一个事件,放行合法但**必留痕**(F 类)。
99
+ */
100
+ export declare const JSONL_REPLAY_SEATS: {
101
+ readonly "approval-nonce": {
102
+ readonly direction: "fail-closed";
103
+ readonly event: "approval_nonce_corrupt_read";
104
+ readonly face: "approval-nonce ledger";
105
+ readonly note: string;
106
+ readonly remedy: string;
107
+ };
108
+ readonly "approval-exemption": {
109
+ readonly direction: "fail-closed";
110
+ readonly event: "approval_exemption_corrupt_read";
111
+ readonly face: "approval-exemption log";
112
+ readonly note: string;
113
+ readonly remedy: string;
114
+ };
115
+ readonly "permission-rule-approvals": {
116
+ readonly direction: "fail-closed";
117
+ readonly event: "permission_rule_approvals_corrupt_read";
118
+ readonly face: "permission-rule approval log";
119
+ readonly note: "S-582 fail-closed: an unreadable snapshot would roll a rule-approval record (and its rev) back to an earlier state. The face refuses every read and write until a person inspects the file.";
120
+ readonly remedy: "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
121
+ };
122
+ readonly "permission-rule-tickets": {
123
+ readonly direction: "fail-closed";
124
+ readonly event: "permission_rule_tickets_corrupt_read";
125
+ readonly face: "permission-rule import-ticket log";
126
+ readonly note: "S-582 fail-closed: an unreadable consume/release line would resurrect a claimed import ticket as unclaimed (one human consent redeemed twice). The face refuses every read and write until a person inspects the file.";
127
+ readonly remedy: "the permission-rule lane is fail-closed until a person inspects the file (moving it aside restarts the lane with an empty log)";
128
+ };
129
+ readonly "approval-ask-journal": {
130
+ readonly direction: "fail-open";
131
+ readonly event: "approval_ask_journal_corrupt_read";
132
+ readonly tag: "server.approval-ask-journal.corrupt-record-skipped";
133
+ readonly note: "F: a skipped ask-journal record loses an ask transition's PROGRESS, never a gate (every write is CAS-guarded); the original is quarantined next to the journal.";
134
+ };
135
+ readonly "run-store": {
136
+ readonly direction: "fail-open";
137
+ readonly event: "run_store_corrupt_read";
138
+ readonly tag: "server.run-store.corrupt-record-skipped";
139
+ readonly note: string;
140
+ };
141
+ readonly "resume-anchor": {
142
+ readonly direction: "fail-open";
143
+ readonly event: "resume_anchor_corrupt_read";
144
+ readonly tag: "server.resume-anchor.corrupt-record-skipped";
145
+ readonly note: "F: a skipped anchor means that eventId no longer resolves (resumeAt / rewindFilesTo answer 404 unknown_event for it; the next put re-records it).";
146
+ };
147
+ readonly "send-file-ledger": {
148
+ readonly direction: "fail-open";
149
+ readonly event: "send_file_ledger_corrupt_read";
150
+ readonly tag: "server.send-file-ledger.corrupt-record-skipped";
151
+ readonly note: "F: a skipped ledger row means that issued link is missing from its tenant's listing (the stored object itself is untouched).";
152
+ };
153
+ };
154
+ /** 登记名(闭集)。 */
155
+ export type JsonlReplaySeat = keyof typeof JSONL_REPLAY_SEATS;
156
+ type SeatsWhere<D extends JsonlReplaySeatEntry["direction"]> = {
157
+ [K in JsonlReplaySeat]: (typeof JSONL_REPLAY_SEATS)[K]["direction"] extends D ? K : never;
158
+ }[JsonlReplaySeat];
159
+ /** 登记为 fail-closed 的座(只能交给 {@link replayJsonlGuarded})。 */
160
+ export type FailClosedJsonlSeat = SeatsWhere<"fail-closed">;
161
+ /** 登记为 fail-open 的座(只能交给 {@link replayJsonlSkipping})。 */
162
+ export type FailOpenJsonlSeat = SeatsWhere<"fail-open">;
163
+ /**
164
+ * fail-closed 座的**带码拒绝**。`kind` 与店层拒绝契约同字(`StoreCorruptError.kind`),`seat` 说是哪一只面。
165
+ * 调用方各按自己的既有纪律接:豁免探针被拒 ⇒ 当未豁免(照问人);nonce 消费被拒 ⇒ 外层 500(证明不受理)。
166
+ */
167
+ export declare class JsonlStoreCorruptError extends Error {
168
+ readonly kind: "corrupt";
169
+ readonly seat: FailClosedJsonlSeat;
170
+ readonly path: string;
171
+ readonly reason: string;
172
+ constructor(seat: FailClosedJsonlSeat, path: string, reason: string);
173
+ }
174
+ /**
175
+ * 一个 fail-closed 文件的**损坏闸**(原 `permission-rule-store-file.ts` 的 `CorruptionGate`,S-582 收进属主)。
176
+ * 置位之后,店的每一个判决 / 追加方法第一行 {@link assertUsable} 即响亮拒 —— 一份不完整的授权账既不能用来
177
+ * 判决,也不能在它上面继续追加(追加只会让下一次重启读到同样残缺的历史)。
178
+ * 只是一只闩:披露由属主在跳闸那一刻经腐读座发出(逐条记录 + 计数),闸本身不再说第二遍。
179
+ */
180
+ export declare class JsonlStoreGate {
181
+ private readonly seat;
182
+ private readonly path;
183
+ private reason;
184
+ constructor(seat: FailClosedJsonlSeat, path: string);
185
+ /** 记一次损坏(幂等:第一条原因就是拒绝句里报的那条)。 */
186
+ trip(reason: string): void;
187
+ get tripped(): boolean;
188
+ assertUsable(): void;
189
+ }
190
+ /**
191
+ * **fail-closed** 座的重放:坏行 ⇒ 逐条披露 + 这只文件的闸合上(交回的 {@link JsonlStoreGate});好行照常交回
192
+ * (闸合上时店不再用它们判决,但删除类动作 —— 例如 E21 删会话 —— 仍可按店的规矩收掉它们)。
193
+ * 读错(非 ENOENT)照 core 原样抛。
194
+ */
195
+ export declare function replayJsonlGuarded<T>(path: string, seat: FailClosedJsonlSeat, shape: JsonlRecordShape<T>, warn?: Logger["warn"]): {
196
+ records: T[];
197
+ gate: JsonlStoreGate;
198
+ };
199
+ /**
200
+ * **fail-open** 座的重放:坏行 ⇒ 跳过 + 逐条披露 + 逐条 `recordFailOpen(<登记的 F tag>)`;`skipped` = 跳过的条数
201
+ * (撕尾不计 —— core 按设计不报它)。读错(非 ENOENT)照 core 原样抛。
71
202
  */
72
- export declare function createSkippedRecordSink(onRecord: (info: JsonlSkippedRecord) => void): (info: JsonlCorruptInfo) => void;
203
+ export declare function replayJsonlSkipping<T>(path: string, seat: FailOpenJsonlSeat, shape: JsonlRecordShape<T>, warn?: Logger["warn"]): {
204
+ records: T[];
205
+ skipped: number;
206
+ };
207
+ export {};
73
208
  //# sourceMappingURL=corrupt-read-seat.d.ts.map