@sema-agent/server 7.50.0 → 7.52.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 (222) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +83 -20
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/device-lane.d.ts +79 -0
  21. package/dist/boot/device-lane.js +63 -0
  22. package/dist/boot/engine-lease.d.ts +1 -1
  23. package/dist/boot/execution-env.d.ts +48 -2
  24. package/dist/boot/execution-env.js +57 -5
  25. package/dist/boot/governance-seams.d.ts +2 -2
  26. package/dist/boot/leader.d.ts +1 -1
  27. package/dist/boot/memory-boundary.d.ts +2 -2
  28. package/dist/boot/memory-consolidation.d.ts +6 -6
  29. package/dist/boot/org-memory.d.ts +1 -1
  30. package/dist/boot/parked-revive-gate.d.ts +3 -3
  31. package/dist/boot/reapers.d.ts +2 -2
  32. package/dist/boot/reapers.js +17 -4
  33. package/dist/boot/resolve-spec.d.ts +15 -0
  34. package/dist/boot/resolve-spec.js +40 -15
  35. package/dist/boot/retention-lane.d.ts +2 -2
  36. package/dist/boot/runner-deps.d.ts +17 -7
  37. package/dist/boot/runner-deps.js +4 -2
  38. package/dist/boot/runtime-caps.d.ts +23 -0
  39. package/dist/boot/runtime-caps.js +24 -2
  40. package/dist/boot/session-faces.d.ts +6 -1
  41. package/dist/boot/session-faces.js +17 -3
  42. package/dist/boot/shutdown.d.ts +8 -0
  43. package/dist/boot/shutdown.js +3 -1
  44. package/dist/boot/stores.js +40 -1
  45. package/dist/boot/task-list-lane.d.ts +2 -2
  46. package/dist/brain.js +51 -31
  47. package/dist/budget.d.ts +2 -2
  48. package/dist/capabilities/center-plugins.d.ts +1 -1
  49. package/dist/capabilities/collab-workflows.d.ts +1 -1
  50. package/dist/capabilities/hands-lane.d.ts +3 -3
  51. package/dist/capabilities/scenarios.d.ts +3 -3
  52. package/dist/capabilities/skills.d.ts +1 -1
  53. package/dist/config-center/hot-keys-registry.js +5 -0
  54. package/dist/config-center/http-client.d.ts +2 -2
  55. package/dist/config-center/read-face.d.ts +1 -1
  56. package/dist/config-center/restart-signal.d.ts +1 -1
  57. package/dist/config-center/types.d.ts +10 -3
  58. package/dist/config-invariants.d.ts +2 -2
  59. package/dist/config-invariants.js +18 -0
  60. package/dist/config-types.d.ts +90 -18
  61. package/dist/config.js +91 -19
  62. package/dist/deployment-governance.d.ts +1 -1
  63. package/dist/device-enrollment.d.ts +125 -0
  64. package/dist/device-enrollment.js +156 -0
  65. package/dist/device-store.d.ts +385 -0
  66. package/dist/device-store.js +407 -0
  67. package/dist/device-ws-hub.d.ts +182 -0
  68. package/dist/device-ws-hub.js +1012 -0
  69. package/dist/device-ws-protocol.d.ts +429 -0
  70. package/dist/device-ws-protocol.js +464 -0
  71. package/dist/env-facts.d.ts +5 -2
  72. package/dist/env-facts.js +1 -0
  73. package/dist/execution-lane-caps.d.ts +152 -0
  74. package/dist/execution-lane-caps.js +166 -0
  75. package/dist/fleet/fleet-bus.d.ts +1 -1
  76. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  77. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  78. package/dist/http/active-run-conflict.d.ts +2 -2
  79. package/dist/http/principal-gate.d.ts +1 -1
  80. package/dist/http/route-ctx.d.ts +24 -4
  81. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  82. package/dist/http/routes/admin-memory-optout.js +75 -0
  83. package/dist/http/routes/agents-roster.d.ts +1 -1
  84. package/dist/http/routes/approvals-assistant.js +21 -21
  85. package/dist/http/routes/capabilities.js +27 -11
  86. package/dist/http/routes/diagnostics.d.ts +1 -1
  87. package/dist/http/routes/leader.d.ts +1 -1
  88. package/dist/http/routes/memory-bundle.d.ts +1 -1
  89. package/dist/http/routes/memory-bundle.js +3 -1
  90. package/dist/http/routes/memory-compliance.d.ts +4 -4
  91. package/dist/http/routes/memory-origin.d.ts +4 -4
  92. package/dist/http/routes/memory-origin.js +3 -1
  93. package/dist/http/routes/memory-policy.js +6 -2
  94. package/dist/http/routes/retention-ops.d.ts +1 -1
  95. package/dist/http/routes/rules.d.ts +5 -2
  96. package/dist/http/routes/rules.js +84 -5
  97. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  98. package/dist/http/routes/run-memory-capture.js +97 -0
  99. package/dist/http/routes/runs.d.ts +1 -0
  100. package/dist/http/routes/runs.js +212 -6
  101. package/dist/http/routes/session-sync.d.ts +4 -6
  102. package/dist/http/routes/session-sync.js +60 -47
  103. package/dist/http/routes/sessions.js +61 -188
  104. package/dist/http/routes/shared-memory.d.ts +1 -1
  105. package/dist/http/routes/tasks.js +1 -1
  106. package/dist/http/routes/workflows.js +19 -0
  107. package/dist/http/server.d.ts +49 -13
  108. package/dist/http/server.js +109 -13
  109. package/dist/http/verify-rounds.d.ts +1 -1
  110. package/dist/http/wire-types.d.ts +41 -8
  111. package/dist/leader/endpoint.d.ts +1 -1
  112. package/dist/leader/leader.d.ts +1 -1
  113. package/dist/leader/repair-oracle.d.ts +2 -2
  114. package/dist/leader/repair-oracle.js +1 -1
  115. package/dist/leader/wire.js +4 -2
  116. package/dist/main.js +23 -9
  117. package/dist/memory-bundle-engine.d.ts +1 -1
  118. package/dist/memory-operator-faces.d.ts +5 -5
  119. package/dist/memory-origin-fence.d.ts +50 -0
  120. package/dist/memory-origin-fence.js +15 -0
  121. package/dist/memory-posture.d.ts +13 -1
  122. package/dist/memory-posture.js +1 -0
  123. package/dist/memory-scope.d.ts +6 -1
  124. package/dist/memory-scope.js +3 -2
  125. package/dist/model-select.d.ts +2 -2
  126. package/dist/observability/fail-open.d.ts +17 -5
  127. package/dist/observability/fail-open.js +17 -5
  128. package/dist/orchestration/hardened-vm-runner.d.ts +9 -2
  129. package/dist/orchestration/hardened-vm-runner.js +11 -1
  130. package/dist/orchestration/hardened-vm-worker-runner.js +2 -2
  131. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  132. package/dist/plugins/background-shell-support.d.ts +2 -2
  133. package/dist/plugins/blob-backend.d.ts +7 -2
  134. package/dist/plugins/blob-backend.js +22 -18
  135. package/dist/plugins/caching-session-store.d.ts +7 -3
  136. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  137. package/dist/plugins/device-store-sql.d.ts +130 -0
  138. package/dist/plugins/device-store-sql.js +574 -0
  139. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  140. package/dist/plugins/file-history-store-sql.js +827 -0
  141. package/dist/plugins/file-run-store.d.ts +44 -5
  142. package/dist/plugins/file-run-store.js +85 -13
  143. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  144. package/dist/plugins/fork-routing-session-store.js +2 -2
  145. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  146. package/dist/plugins/local-session-store.d.ts +9 -1
  147. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  148. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  149. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  150. package/dist/plugins/memory-origin-law.d.ts +2 -2
  151. package/dist/plugins/memory-run-store.d.ts +4 -3
  152. package/dist/plugins/memory-run-store.js +2 -1
  153. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  154. package/dist/plugins/permission-rule-store-file.js +77 -13
  155. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  156. package/dist/plugins/permission-rule-store-sql.js +123 -21
  157. package/dist/plugins/pg-pool.js +59 -0
  158. package/dist/plugins/pg-safe-json.d.ts +1 -1
  159. package/dist/plugins/remote-env-device.d.ts +271 -0
  160. package/dist/plugins/remote-env-device.js +727 -0
  161. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  162. package/dist/plugins/remote-scratchpad.js +1 -1
  163. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  164. package/dist/plugins/retention-store-sql.d.ts +7 -7
  165. package/dist/plugins/retention-store-sql.js +6 -0
  166. package/dist/plugins/run-store-sql.d.ts +9 -3
  167. package/dist/plugins/run-store-sql.js +2 -2
  168. package/dist/plugins/send-user-file.d.ts +1 -1
  169. package/dist/plugins/session-placement.d.ts +1 -1
  170. package/dist/plugins/sql-errors.d.ts +1 -1
  171. package/dist/plugins/store-backend.d.ts +68 -29
  172. package/dist/plugins/store-backend.js +78 -15
  173. package/dist/plugins/store-contracts.d.ts +5 -3
  174. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  175. package/dist/plugins/tidb-pool.js +59 -0
  176. package/dist/plugins/web-search.d.ts +1 -1
  177. package/dist/prompts-domain-validate.js +1 -1
  178. package/dist/resource-suspend.d.ts +1 -1
  179. package/dist/resource-window.d.ts +3 -3
  180. package/dist/rules-consent.d.ts +231 -9
  181. package/dist/rules-consent.js +411 -13
  182. package/dist/run-local.js +1 -0
  183. package/dist/runs.d.ts +15 -1
  184. package/dist/runs.js +19 -0
  185. package/dist/runtime-caps-resolver.d.ts +36 -1
  186. package/dist/runtime-caps-resolver.js +25 -0
  187. package/dist/runtime-governance.d.ts +1 -1
  188. package/dist/security.d.ts +5 -5
  189. package/dist/security.js +1 -1
  190. package/dist/session-sync.d.ts +52 -37
  191. package/dist/session-sync.js +19 -16
  192. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  193. package/dist/sighup-idle.d.ts +1 -1
  194. package/dist/spec-fields.d.ts +22 -1
  195. package/dist/spec-fields.js +11 -0
  196. package/dist/store-live-probe.d.ts +2 -2
  197. package/dist/task-a2a.d.ts +1 -1
  198. package/dist/task-cwd.d.ts +25 -0
  199. package/dist/task-cwd.js +3 -0
  200. package/dist/task-mcp.d.ts +3 -3
  201. package/dist/task-settings.d.ts +3 -3
  202. package/dist/task-settings.js +3 -1
  203. package/dist/task-workflow.d.ts +1 -1
  204. package/dist/text-bidi.d.ts +2 -2
  205. package/dist/tool-approval.d.ts +191 -9
  206. package/dist/tool-approval.js +248 -29
  207. package/dist/trace/core-keyset-guard.d.ts +1 -1
  208. package/dist/trace/engine-notice-wire.d.ts +6 -4
  209. package/dist/trace/engine-notice-wire.js +2 -0
  210. package/dist/trace/ledger-events.d.ts +1 -1
  211. package/dist/trace/ledger-sink.d.ts +2 -2
  212. package/dist/trace/project.d.ts +3 -2
  213. package/dist/trace/project.js +1 -0
  214. package/dist/turn-activity.d.ts +1 -1
  215. package/dist/wall-clock-jump-guard.d.ts +3 -3
  216. package/package.json +2 -2
  217. package/dist/http/tar.d.ts +0 -17
  218. package/dist/http/tar.js +0 -53
  219. package/dist/http/workspace-content.d.ts +0 -11
  220. package/dist/http/workspace-content.js +0 -20
  221. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  222. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -111,7 +111,7 @@ export declare function autonomyOverrides(autonomy: Autonomy | undefined): Parti
111
111
  * `autonomyOverrides(autonomy).shellGate`(`AUTONOMY=ask`)与 `MANUAL_MODE_SHELL_GATE` 旋钮,取「有一个
112
112
  * 是 always」。`commandPolicy` 不入判据:它产的是 `toolPolicy` 的 ask,不抬 shellGate 档,与本格无关。
113
113
  *
114
- * ⚠️ **已知残留(codex 对抗复审 2026-08-11 逮到,如实登记而不是掩盖)**:本判据读的是**当下**的
114
+ * ⚠️ **已知残留(codex 交叉复审 2026-08-11 逮到,如实登记而不是掩盖)**:本判据读的是**当下**的
115
115
  * config,而 `config.autonomy` 是**热改**的(`config-center/apply-effective.ts` 就地写 `config.autonomy`)。
116
116
  * 于是失真是**双向**的,不是我原先写的「只往缺席方向」:
117
117
  * · 缺席向(常见):mint 之后运维把 `AUTONOMY=ask` 撤了 ⇒ 老 park 行归不出治理出身,少标一个;
@@ -25,9 +25,9 @@ export interface SessionListItem {
25
25
  * E6 fail-closed 守卫的辖域判据:**这个请求是不是一次「破坏性会话写」**。
26
26
  *
27
27
  * 🔴 2026-08-01 复审逮到:守卫原本是**逐条路由白名单**(`PUT …/policy`、`POST …/sync/import`),
28
- * 而 `DELETE /v1/sessions/:id`(删整条会话)不在其中 —— 同一威胁形、爆炸半径**更大**,却没门。
28
+ * 而 `DELETE /v1/sessions/:id`(删整条会话)不在其中 —— 同一风险形、爆炸半径**更大**,却没门。
29
29
  *
30
- * 威胁形逐字同源(守卫自己的注释):「无 service token + 未开 `ALLOW_UNAUTHED_WRITES`」这一形下
30
+ * 风险形逐字同源(守卫自己的注释):「无 service token + 未开 `ALLOW_UNAUTHED_WRITES`」这一形下
31
31
  * `sessionOwnerScopeForWrite` 判 fleet-wide ⇒ 集群内**任意**调用方可对**任意** session 动手。
32
32
  * policy 腿担心的是「forged principal 能 tighten/DoS 一个会话的工具」;DELETE 直接把会话删掉。
33
33
  *
@@ -55,7 +55,7 @@ export declare const LEGACY_UNOWNED_AMNESTY_TENANT = "default";
55
55
  * ① 单机形 `!requirePrincipal` —— 多租部署本臂**死路**(header 必到、键必具名);
56
56
  * ② 存量行恰为无主 `owner === null` —— 任何具名 owner 的行不受影响;
57
57
  * ③ 调用方恰为 `default` 主 —— 非 default 的具名主对无主行仍走原门(404 / deleted:false)。
58
- * 反向风险有界:①限定的单机形本就单用户,把无主行判给 default 主不产生跨租泄露。
58
+ * 反向风险有界:①限定的单机形本就单用户,把无主行判给 default 主不产生跨租外泄。
59
59
  *
60
60
  * 🪦 墓碑注(与第一臂同源):这是两个表示法之间的**过渡承接**。存量随会话生命周期消亡后,本臂
61
61
  * 连同 {@link LEGACY_UNOWNED_AMNESTY_TENANT} 与几处调用点可**整删**,不留兼容层。
@@ -235,7 +235,7 @@ export declare function createAuthorizer(config: ServiceConfig, sessionStore: Ow
235
235
  export declare const PRINCIPAL_MAX_LENGTH = 190;
236
236
  /** 修1 — the SINGLE registry of internal sentinel/scope identifiers a tenant principal must never equal.
237
237
  * 🔴 新增内部 sentinel scope 必须在此登记(精确值加 RESERVED_PRINCIPALS,前缀族加
238
- * RESERVED_PRINCIPAL_PREFIXES),否则 assertPrincipalShape 会放行同名租户 = 与内部 scope 行合流的越权面。
238
+ * RESERVED_PRINCIPAL_PREFIXES),否则 assertPrincipalShape 会放行同名租户 = 与内部 scope 行合流的超范围面。
239
239
  * Current entries (where each is minted): `"_"` = single-user/anonymous sentinel (checkpoint/sendfile/
240
240
  * resource-suspend scope family); `"__none__"` = missing-principal placeholder on the decide/pending gates
241
241
  * (http/server.ts `principal ?? "__none__"`); `"_leader"` = leader durable-approval scope
@@ -281,7 +281,7 @@ export declare const decodeCheckpointScope: (scope: string) => string | undefine
281
281
  /** Read the authenticated principal from the trusted header. Never from the body. */
282
282
  export declare function principalFrom(req: IncomingMessage, config: ServiceConfig): string | undefined;
283
283
  /** Header carrying the crypto-verified principal JWT on a direct-door worker (the issuer signs it; the worker only
284
- * verifies). The trusted twin of `config.principalHeader` (the latter is client-spoofable on a direct door). */
284
+ * verifies). The trusted twin of `config.principalHeader` (the latter is client-self-asserted on a direct door). */
285
285
  export declare const PRINCIPAL_TOKEN_HEADER = "x-approval-principal-token";
286
286
  /**
287
287
  * direct-door 决策证明三件套的另外两个头名(JWT 在上面那个常量里)。
package/dist/security.js CHANGED
@@ -7,7 +7,7 @@ export function isDestructiveSessionWrite(method, url) {
7
7
  if (method === "PUT")
8
8
  return /^\/v1\/sessions\/[^/]+\/policy$/.test(url);
9
9
  if (method === "POST")
10
- return /^\/v1\/sessions\/[^/]+\/(sync\/import|fork)$/.test(url);
10
+ return /^\/v1\/sessions\/[^/]+\/(sync\/import|fork|memory\/erase)$/.test(url);
11
11
  return false;
12
12
  }
13
13
  export const LEGACY_UNOWNED_AMNESTY_TENANT = "default";
@@ -45,23 +45,40 @@
45
45
  */
46
46
  import type { StoreBackend } from "./plugins/store-backend.js";
47
47
  import { type SyncRelation } from "./session-sync-kernel.js";
48
- import { type SessionTreeEntry, type SessionRulesRecord, type FileSnapshotResult, type SessionPermissionRules, type StoredSessionRules, type SessionPolicyStore } from "@sema-agent/core";
49
- import type { ServiceFileSnapshotStore, ServiceSessionPolicyStore, ResumeAnchorStore } from "./plugins/store-backend.js";
48
+ import { type SessionTreeEntry, type SessionRulesRecord, type FileHistoryExport, type FileHistoryResult, type SessionPermissionRules, type StoredSessionRules, type SessionPolicyStore } from "@sema-agent/core";
49
+ import type { ServiceFileHistoryStore, ServiceSessionPolicyStore, ResumeAnchorStore } from "./plugins/store-backend.js";
50
50
  /** A blob getter: content-addressed bytes for a sha256 hash, or `undefined` if the source can't supply them.
51
51
  * Returned by {@link exportSessionManifest} (closes over the source store); consumed wherever a caller pulls
52
- * content-addressed snapshot bytes lazily (the live PUSH route's `importManifest` replay). */
52
+ * content-addressed history bytes lazily (the live PUSH route's `importHistory` replay). */
53
53
  export type BlobGetter = (hash: string) => Promise<Uint8Array | undefined>;
54
- /** The snapshot-import capability face of a `FileSnapshotStore` signature verbatim = the authoritative
55
- * implementation, `FileSnapshotStoreSql.importManifest` (src/plugins/file-snapshot-store-sql.ts:201). */
56
- export interface FileSnapshotImportFace {
57
- importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: BlobGetter): Promise<FileSnapshotResult>;
54
+ /** The history-import capability face of a `FileHistoryStore` (`importHistory` is OPTIONAL on the core
55
+ * contract a store without it cannot be a 2c history sink). */
56
+ export interface FileHistoryImportFace {
57
+ importHistory(scope: string, data: FileHistoryExport, srcGetBlob: BlobGetter): Promise<FileHistoryResult>;
58
58
  }
59
- /** Single-point capability probe (design/158 R7): does `fs` carry the optional `importManifest` snapshot-import
60
- * seam? Returns the bound method (never a naked function reference — `this` inside `importManifest` must resolve
61
- * to `fs`, exactly like `asServiceDeps`'s other capability-probe-then-bind authorities) or `undefined` when the
62
- * seam is absent (an older/in-memory dev store). Callers gate on the RETURN VALUE, not a separate `typeof` check,
63
- * so there is exactly one place that knows the shape of the probe. */
64
- export declare function fileSnapshotImportFace(fs: unknown): FileSnapshotImportFace | undefined;
59
+ /** Single-point capability probe (design/158 R7): does `fs` carry the optional `importHistory` seam?
60
+ * Returns the bound method (never a naked function reference — `this` inside must resolve to `fs`) or
61
+ * `undefined` when absent. Callers gate on the RETURN VALUE, not a separate `typeof` check, so there is
62
+ * exactly one place that knows the shape of the probe. */
63
+ export declare function fileHistoryImportFace(fs: unknown): FileHistoryImportFace | undefined;
64
+ /**
65
+ * The 2c PUSH replay of a session's file-history graph — keep-SOURCE convergence over core's
66
+ * non-destructive-target law. `importHistory` refuses a target that already holds a DIFFERENT graph
67
+ * (typed `conflict`; the identical envelope re-publishes idempotently). On that arm, when the store has
68
+ * the purge seam, the sync's documented semantic (the destination converges to the source's state, same
69
+ * posture as the policy/anchor replays) is applied: wipe the scope, then import once more. A store
70
+ * WITHOUT `deleteBySession` (the local-lane core file store) surfaces the typed conflict unchanged —
71
+ * an honest capability limit, never a silent half-state.
72
+ *
73
+ * Honest residual (recorded, not hidden): if the RE-import after the wipe fails (a blob corrupted in
74
+ * transit past the Phase-A presence check), the destination's old graph is already gone and the target
75
+ * is left EMPTY — worse than the moment before this call, but strictly self-healing: the client re-PUTs
76
+ * the blob and re-POSTs, and the retry lands on an empty target (importHistory's first-import arm).
77
+ * The alternative (keeping the old graph) would wedge every retry on the same typed conflict forever.
78
+ */
79
+ export declare function replayFileHistory(store: ServiceFileHistoryStore, scope: string, envelope: FileHistoryExport, srcGetBlob: BlobGetter, opts: {
80
+ allowReplace: boolean;
81
+ }): Promise<FileHistoryResult>;
65
82
  /**
66
83
  * The portable state of ONE session, ready to replay into another backend. Blobs are deliberately NOT inlined
67
84
  * (§5: a turn's working-tree blobs can be tens of MiB → OOM/413 if buffered); each snapshot carries only its
@@ -71,11 +88,10 @@ export interface SessionBundle {
71
88
  sessionId: string;
72
89
  /** The FULL durable conversation log (compaction floor bypassed), verbatim ids/parents/payload — `exportEntries`. */
73
90
  entries: SessionTreeEntry[];
74
- /** One per E19 file snapshot (keyed by `SessionTreeEntry.id` = `key`); manifest = [relPath, blobHash] tuples. */
75
- snapshots: Array<{
76
- key: string;
77
- manifest: Array<[string, string]>;
78
- }>;
91
+ /** The session's per-edited-file rewind history graph (core's versioned `FileHistoryExport` envelope
92
+ * tracked set + version chains + boundaries; blob BYTES travel separately, content-addressed). `null` =
93
+ * no history / the source store cannot export. Replaces the retired E19 `snapshots` manifests (S-15). */
94
+ fileHistory: FileHistoryExport | null;
79
95
  /** Every (principal, rules) record for the session, across ALL principals (E6 `listBySession`). */
80
96
  policy: SessionRulesRecord[];
81
97
  /** Every E18 resume-at anchor (eventId→entryId + its source owner; owner is RE-KEYED on import, §9). */
@@ -101,11 +117,8 @@ export interface SessionManifest {
101
117
  entryCount: number;
102
118
  /** The session's current leaf `SessionTreeEntry.id` (session_meta.leaf_id), or null if it has none. */
103
119
  leafId: string | null;
104
- /** One per E19 file snapshot (keyed by entry id); manifest = [relPath, blobHash] tuples. EXACTLY as the bundle. */
105
- snapshots: Array<{
106
- key: string;
107
- manifest: Array<[string, string]>;
108
- }>;
120
+ /** The per-edited-file rewind history envelope (S-15; see {@link SessionBundle.fileHistory}). EXACTLY as the bundle. */
121
+ fileHistory: FileHistoryExport | null;
109
122
  /** Every (principal, rules) record across ALL principals (E6 `listBySession`). EXACTLY as the bundle. */
110
123
  policy: SessionRulesRecord[];
111
124
  /** Every E18 resume-at anchor (eventId→entryId + source owner; RE-KEYED on import, §9). EXACTLY as the bundle. */
@@ -125,7 +138,7 @@ export interface SessionManifest {
125
138
  *
126
139
  * The OWNER is NOT exported (import re-stamps it, §9). The owner-scope GATE is the CALLER's (route P1d): this returns
127
140
  * `null` ONLY for "session does not exist". (Named exportSessionManifest, NOT exportManifest, to avoid the collision
128
- * with `FileSnapshotStore.exportManifest`.)
141
+ * with the history store's own export verb.)
129
142
  */
130
143
  export declare function exportSessionManifest(sessionId: string, srcBackend: StoreBackend): Promise<{
131
144
  manifest: SessionManifest;
@@ -152,24 +165,26 @@ export declare function replayPolicyRecords(store: Pick<SessionPolicyStore, "get
152
165
  /** #137 ② 能力预检(fail-loud 方向,[2195] 判据:保护型能力缺席=fail-closed):consented
153
166
  * overwrite-dst 需要 policy 店的 `deleteBySession` purge seam 才能不留混合态;缺席=true,路由在
154
167
  * commit **之前**用它干净中止(此时零写入落地),wire 拒因字面量归路由所有(api-error-text-freeze
155
- * 锚字面站点)。anchor 店的 deleteBySession 是类型必填、快照店有 core 必填的 `reap` 兜底,都不构成
156
- * 缺席面;唯一可缺的就是 policy 这条(core 接口上是可选方法) */
168
+ * 锚字面站点)。anchor 店的 deleteBySession 是类型必填、history 店有 core 必填的 `reap` 兜底(+
169
+ * {@link replayFileHistory} conflict 收敛臂),都不构成缺席面;唯一可缺的就是 policy 这条
170
+ * (core 接口上是可选方法)。 */
157
171
  export declare function overwriteWipeIncapable(policyStore: ServiceSessionPolicyStore | undefined): boolean;
158
172
  /** #137 ②(死码 ⓪ 擦除步的移植):consented overwrite-dst = keep-source,结果必须是**源端那
159
- * 一份**,不得混成「源端对话 + 目的端遗留 policy/anchor/快照」(混合态比丢历史更危险:悬空
160
- * anchor 指向已 purge 的 entry、被放弃分支的 policy 行继续 gating、共享 key 快照因
161
- * importManifest create-once 永远留着目的端字节)。调用时点=commit(原子 swap)成功**之后**、
162
- * 附随物重放**之前**:死码在任何写入前擦(其 entries 写本身非原子);活路由有真 commit point,
163
- * 擦在 commit 前会在「commit 失败」时留下反向混合态(目的端对话完好、附随物已毁)——方向更坏。
173
+ * 一份**,不得混成「源端对话 + 目的端遗留 policy/anchor/历史」(混合态比丢历史更危险:悬空
174
+ * anchor 指向已 purge 的 entry、被放弃分支的 policy 行继续 gating、目的端旧历史图挡住源端图的
175
+ * importHistory 空目标法则)。调用时点=commit(原子 swap)成功**之后**、附随物重放**之前**:
176
+ * 死码在任何写入前擦(其 entries 写本身非原子);活路由有真 commit point,擦在 commit 前会在
177
+ * commit 失败」时留下反向混合态(目的端对话完好、附随物已毁)——方向更坏。
164
178
  * 诚实残余:进程在 commit 与本函数之间崩溃时,重试落 Phase A identical 支(无 wipe)⇒ 遗留行
165
- * 可存活;该窗口窄于修前世界(修前=遗留行**恒**存活),与 §8「快照重放失败 ⇒ 422 重试自愈」
179
+ * 可存活;该窗口窄于修前世界(修前=遗留行**恒**存活),与 §8「历史重放失败 ⇒ 422 重试自愈」
166
180
  * 同款接受,记录于此不装没有。
167
- * 各店姿势(死码 verbatim 语义):快照=deleteBySession(SQL 孪生:manifest 即删、blob 走异步
168
- * 孤儿 GC)缺席则 core 必填 `reap(scope, [])`(内容寻址,他 scope 仍引用的 blob 存活);
169
- * anchor=直调(类型必填);policy=deleteBySession(路由已用 {@link overwriteWipeIncapable}
170
- * 预检过,此处缺席=类型外的 JS 层残缺实现,直调抛 TypeError 即 fail-loud)。 */
181
+ * 各店姿势:file history=deleteBySession( scope 清空;blob 走异步孤儿 GC)缺席则 core 必填
182
+ * `reap(scope, [])`(丢全部 boundary;v1 基线按 core 契约留存 —— 随后 {@link replayFileHistory}
183
+ * 的 conflict 臂在有 purge 缝的店上收敛,无缝的店如实 422);anchor=直调(类型必填);
184
+ * policy=deleteBySession(路由已用 {@link overwriteWipeIncapable} 预检过,此处缺席=类型外的
185
+ * JS 层残缺实现,直调抛 TypeError 即 fail-loud)。 */
171
186
  export declare function wipeSessionAttendants(stores: {
172
- fileSnapshotStore?: ServiceFileSnapshotStore | undefined;
187
+ fileHistoryStore?: ServiceFileHistoryStore | undefined;
173
188
  sessionPolicyStore?: ServiceSessionPolicyStore | undefined;
174
189
  resumeAnchorStore?: Pick<ResumeAnchorStore, "deleteBySession"> | undefined;
175
190
  }, sessionId: string): Promise<void>;
@@ -1,10 +1,22 @@
1
1
  import { classifySyncRelationship } from "./session-sync-kernel.js";
2
2
  import { normalizeRules, stripRev } from "@sema-agent/core";
3
- export function fileSnapshotImportFace(fs) {
4
- const candidate = fs?.importManifest;
3
+ export function fileHistoryImportFace(fs) {
4
+ const candidate = fs?.importHistory;
5
5
  if (typeof candidate !== "function")
6
6
  return undefined;
7
- return { importManifest: candidate.bind(fs) };
7
+ return { importHistory: candidate.bind(fs) };
8
+ }
9
+ export async function replayFileHistory(store, scope, envelope, srcGetBlob, opts) {
10
+ const face = fileHistoryImportFace(store);
11
+ if (face === undefined) {
12
+ return { ok: false, error: { code: "unsupported_format", message: "this deployment's file-history store has no importHistory seam" } };
13
+ }
14
+ let r = await face.importHistory(scope, envelope, srcGetBlob);
15
+ if (!r.ok && r.error.code === "conflict" && opts.allowReplace && typeof store.deleteBySession === "function") {
16
+ await store.deleteBySession(scope);
17
+ r = await face.importHistory(scope, envelope, srcGetBlob);
18
+ }
19
+ return r;
8
20
  }
9
21
  function ownerAware(backend) {
10
22
  return backend.session();
@@ -19,17 +31,8 @@ export async function exportSessionManifest(sessionId, srcBackend) {
19
31
  return null;
20
32
  const getLeafId = session.getLeafId?.bind(session);
21
33
  const leafId = getLeafId ? await getLeafId(sessionId) : null;
22
- const fs = srcBackend.fileSnapshot();
23
- const snapshots = [];
24
- if (typeof fs.exportManifest === "function") {
25
- const keys = await fs.listKeys(sessionId);
26
- for (const key of keys) {
27
- const manifest = await fs.exportManifest(sessionId, key);
28
- if (manifest === null)
29
- continue;
30
- snapshots.push({ key, manifest: [...manifest] });
31
- }
32
- }
34
+ const fs = srcBackend.fileHistory();
35
+ const fileHistory = typeof fs.exportHistory === "function" ? await fs.exportHistory(sessionId) : null;
33
36
  const policyStore = srcBackend.sessionPolicy();
34
37
  const policy = typeof policyStore.listBySession === "function"
35
38
  ? await policyStore.listBySession(sessionId)
@@ -39,7 +42,7 @@ export async function exportSessionManifest(sessionId, srcBackend) {
39
42
  const getBlobFn = fs.getBlob?.bind(fs);
40
43
  const getBlob = getBlobFn ? (hash) => getBlobFn(hash) : async () => undefined;
41
44
  return {
42
- manifest: { sessionId, entryIds, entryCount: entryIds.length, leafId, snapshots, policy, anchors },
45
+ manifest: { sessionId, entryIds, entryCount: entryIds.length, leafId, fileHistory, policy, anchors },
43
46
  getBlob,
44
47
  };
45
48
  }
@@ -68,7 +71,7 @@ export function overwriteWipeIncapable(policyStore) {
68
71
  return policyStore !== undefined && typeof policyStore.deleteBySession !== "function";
69
72
  }
70
73
  export async function wipeSessionAttendants(stores, sessionId) {
71
- const fs = stores.fileSnapshotStore;
74
+ const fs = stores.fileHistoryStore;
72
75
  if (fs !== undefined) {
73
76
  if (typeof fs.deleteBySession === "function")
74
77
  await fs.deleteBySession(sessionId);
@@ -45,7 +45,7 @@ export interface SharedMemoryScopeAuthorizerOptions {
45
45
  /**
46
46
  * 活对象(闭包持有目录与取值口)⇒ `create*`(CLAUDE.md 工厂命名律)。
47
47
  *
48
- * **代价与它的边界**(codex 对抗复审 R1-[medium] 的一半采纳,一半具名保留):取值口按调用现算,
48
+ * **代价与它的边界**(codex 交叉复审 R1-[medium] 的一半采纳,一半具名保留):取值口按调用现算,
49
49
  * 意味着每次 `resolve` 都要扫一遍 `config.projects`(O(项目数 × 每项 scope 数)的纯 CPU)。
50
50
  * · **已采纳**:principal 缺席那支不再二次建 Set —— 生产取值口(`collectDeploymentOrgScopes`)本来就用
51
51
  * Set 建、交回来已去重,这里再包一层是纯浪费。**去重责任因此上移到取值口**(契约见下面那个字段的注),
@@ -39,7 +39,7 @@ export interface SighupIdleDeps {
39
39
  /**
40
40
  * SIGHUP 处理器 + 它自己那只空闲窗的**停表口**。
41
41
  *
42
- * 🔴 `stop()` 不是可选装饰(codex 对抗复审 F2,已复现):armed 之后这只 interval **刻意不 unref**
42
+ * 🔴 `stop()` 不是可选装饰(codex 交叉复审 F2,已复现):armed 之后这只 interval **刻意不 unref**
43
43
  * (孤儿形下它就是关停驱动),而它握着的 `shutdown`/`escalate` 是**装配那一刻**的闭包。只摘 SIGHUP 监听、
44
44
  * 不停表 = 一个已经退役的装配仍会在 grace 到期时按旧 ctx 关停进程(实测:arm 后立刻摘监听,1.1s 后旧
45
45
  * ctx 的 `server.close` 照样被调)。注册/摘除配对必须覆盖它。
@@ -60,6 +60,27 @@ export declare function validateTaskAgents(v: unknown): string | null;
60
60
  * `task_agent_dropped`(绝不 throw 砖 resume);重名后到者 DROP;超上限截断。幸存字段按白名单拷贝
61
61
  * (不透传未知键 —— 非法项已整条 drop,这里只是让持久化 body 的杂键不上 spec)。全空 ⇒ undefined。 */
62
62
  export declare function taskAgentsFromBody(raw: unknown, requirePrincipal: boolean | undefined, warn: (event: string, fields: Record<string, unknown>) => void): AgentDefinition[] | undefined;
63
+ /** [5673]②/[5697](cli 认领消费)—— fresh-submit 的 agents[].model **目录成员资格**门(形状门
64
+ * {@link validateTaskAgents} 通过之后跑;调用方铸 400 `request.unknown_reference` + `available` 枚举)。
65
+ * 病灶=报错太晚太远:server 持目录却不查,错到 core prepare 才炸 `config.agent.unknown_model`
66
+ * (dist/agents/subagent.js:1258),以「run failed」形出现而非 400;cli resolver 只挡词法形,目录
67
+ * 成员资格只有 server 持有。
68
+ * 三条判定纪律(全部 core dist 亲读):
69
+ * - **只查 string 形**:core 只对 `typeof a.model === "string"` 走 `resolveModel`(subagent.js:1253);
70
+ * Model 对象形 core 原样信任不查目录([879]④ 契约:自带完整 Model 配置,其 id 本就不必在目录)——
71
+ * 对象形没有「晚失败」病灶,这里同拒会是射程外的行为收紧,不做。
72
+ * - **`catalog` 必须是 tier 展开形**(调用方传 `expandTiers(config.models, config.tiers) ?? config.models`,
73
+ * resolve-spec.ts wireCatalog 同源纪律):core Runner 构造期把 deps.models 换成展开目录
74
+ * (runtask.js:1472),agentCatalog.models 即它 —— 裸 config.models 会把合法档位词/CC 别名误杀 400。
75
+ * - **判定=`Object.hasOwn` 纯键查**,刻意不用 `matchCatalogModel`:那是 body.model 门的四形判
76
+ * (name/tier/alias/**id**),而 core 的 `resolveModel` 是纯键查(roles.js:`models?.[ref]`)——
77
+ * 多认 id 形会放行一个 core prepare 必炸的值(门假绿,病灶照旧)。hasOwn 顺带挡原型链键。
78
+ * RESUME 重放不过此门({@link taskAgentsFromBody} 维持 defensive drop 现状;重放无 400 可回,
79
+ * 模型事后被移出目录时由 core prepare 拒)。 */
80
+ export declare function unknownTaskAgentModelRef(agents: readonly unknown[], catalog: Record<string, unknown>): {
81
+ index: number;
82
+ ref: string;
83
+ } | undefined;
63
84
  /** [876] main.ts resolveSpec 的 agents 装配片段(可 import 真 fold,[1.211 codex L]:此前 main.ts 用内联
64
85
  * 闭包,测试只能 SOURCE PIN 文本+重写同构表达式——文本锚可被「移出 resolveSpec 但保留同文」静默骗过;
65
86
  * 现在行为测试直接调这个真出口,main.ts 只剩一次调用点可锚)。语义=taskAgentsFromBody 的条件 spread:
@@ -106,7 +127,7 @@ export interface TaskLimitCaps {
106
127
  /**
107
128
  * [854]④ — normalize `body.limits` → 请求方配速意图 {maxWalltimeMs?, maxOutputTokens?, maxTurns?}。DEFENSIVE
108
129
  * (resume 重放持久化 body 不过 HTTP 400 门):仅正的有限数字存活(floor 取整),0/负/垃圾按键 DROP —— core 侧
109
- * 墙钟 > 0 才生效(0=不设墙,会绕过运营方封顶)、maxTurns/maxOutputTokens 的 0 无意义。每键各自被
130
+ * 墙钟 > 0 才生效(0=不设墙,会跳过运营方封顶)、maxTurns/maxOutputTokens 的 0 无意义。每键各自被
110
131
  * 运营方上限旋钮 clamp(Math.min;旋钮缺省不设=不封顶)。全空 ⇒ undefined。
111
132
  */
112
133
  export declare function normalizeLimits(v: unknown, caps: TaskLimitCaps, warn?: (event: string, fields: Record<string, unknown>) => void): {
@@ -231,6 +231,17 @@ export function taskAgentsFromBody(raw, requirePrincipal, warn) {
231
231
  }
232
232
  return out.length > 0 ? out : undefined;
233
233
  }
234
+ export function unknownTaskAgentModelRef(agents, catalog) {
235
+ for (let i = 0; i < agents.length; i++) {
236
+ const item = agents[i];
237
+ if (item === null || typeof item !== "object")
238
+ continue;
239
+ const model = item.model;
240
+ if (typeof model === "string" && !Object.hasOwn(catalog, model))
241
+ return { index: i, ref: model };
242
+ }
243
+ return undefined;
244
+ }
234
245
  export function taskAgentsSpecFragment(raw, requirePrincipal, warn) {
235
246
  const agents = taskAgentsFromBody(raw, requirePrincipal, warn);
236
247
  return agents ? { agents } : {};
@@ -7,7 +7,7 @@
7
7
  * 键形钉在 test/health-store-live.test.ts,本环判据钉在 test/store-live-probe.test.ts)。
8
8
  *
9
9
  * 判据:
10
- * - 有界:单拍探针经 STORE_PROBE_TIMEOUT_MS race——挂死的 pool(半开连接)不得钉死环;
10
+ * - 有界:单拍探针经 STORE_PROBE_TIMEOUT_MS race——挂死的 pool(半开连接)不得卡住环;
11
11
  * - 翻转披露(§M):live→dead warn `store_probe_dead`、dead→live info `store_probe_recovered`,
12
12
  * 只在翻转拍记(连续死不逐拍刷日志——down 的 DB 不该制造日志风暴);
13
13
  * - 诚实缺席:首拍落地前 state() = undefined(/health 键缺席=「还没探过」,不冒充「活」)。
@@ -50,7 +50,7 @@ export type StoreProbePublicPhrase = (typeof STORE_PROBE_PUBLIC_PHRASES)[number]
50
50
  * 原始探针异常 message → 免鉴权面可披露的闭集词。**返回值恒是 {@link STORE_PROBE_PUBLIC_PHRASES} 的
51
51
  * 一个字面量**(返回类型即闭集,写错一个词是编译错误),永不回声入参。
52
52
  *
53
- * 误分类的代价是「运维读到的分类不精确」,**不是泄露** —— 所以这里的判别用宽松的子串包含即可,不必为
53
+ * 误分类的代价是「运维读到的分类不精确」,**不是外泄** —— 所以这里的判别用宽松的子串包含即可,不必为
54
54
  * 精确性把原文碎片带出来。臂序有意义:自家的 race 超时先于泛化的 timeout 臂。
55
55
  */
56
56
  export declare function publicStoreProbeError(raw: string): StoreProbePublicPhrase;
@@ -35,7 +35,7 @@
35
35
  * 判据一到货就换 core 真源、钉子逐字不用改。
36
36
  * 🔴 **今天这两票在 env 通道上还打不响**,而这是刻意如实记下的、不是被忽略的缺口:`LOCKED_CONFIG_KEYS`
37
37
  * 经 core 的 `resolveLockedKeys` 校验(未知键拒启)、`COMPLIANCE_ADDITIONAL_DENIES` 经 `COMPLIANCE_CAPABILITIES`
38
- * 校验(同拒),所以运维今天**写不进** `a2a` / `a2a_peers`。而且合规那一票还有第二层:即便有人绕过 boot 期
38
+ * 校验(同拒),所以运维今天**写不进** `a2a` / `a2a_peers`。而且合规那一票还有第二层:即便有人跳过 boot 期
39
39
  * 校验把 `a2a_peers` 塞进档位,`resolveComplianceDenies` 对闭集外的词是**抛**(亲测 core 5.36),于是这条
40
40
  * 请求腿的失败方向是**响亮**(500)而不是安静放行 —— 方向正确,故本模块不加任何兜底。两票的代码先在场是
41
41
  * 纵深:core 词表到货那一拍这条腿自己就活了,不需要有人记得回来补;反过来(等词表到了再写判据)才是
@@ -17,6 +17,31 @@ export declare function cwdHonored(config: {
17
17
  };
18
18
  requirePrincipal?: boolean;
19
19
  }): boolean;
20
+ /**
21
+ * device lane 的**平行**判别式(device-executor-lane-v2 §4.3.3,车A-4)——「员工在自己设备的项目目录
22
+ * 里干活」是这条车道的存在理由,所以 caller cwd 必须被尊重。
23
+ *
24
+ * 🔴 **为什么不合并进 {@link cwdHonored}**:两闸语义不同,合并会把 host 的单用户约束**放松**。
25
+ * · `cwdHonored` 的 `requirePrincipal !== true` 防的是「一个租户的输入把 agent 指向 **worker 宿主**上
26
+ * 另一个租户/运维的目录」—— 那是一台共享机器上的跨租户穿越;
27
+ * · device lane 上那个穿越面**结构性不存在**:路径落在**发起者自己的设备**上,而设备归属已由
28
+ * `device_session` 绑定行(提交期用验证过的 principal 写下)+ 准入链的 owner 谓词验过。所以本闸
29
+ * **不要求**单用户 —— 多租户部署里每个员工各连各的设备,正是这条车道的正常形态。
30
+ * 合并两者 = 让 host 车道在多租户下也开始尊重 cwd,那是一条真实的超范围面。
31
+ *
32
+ * 消费者:写侧 = `boot/resolve-spec.ts`(**单写者**,与 cwdHonored 同一处);读侧 =
33
+ * `plugins/remote-env-device.ts` 的工厂(`effectiveDeviceCwd`)。两侧必须同批落 —— 只开写侧会留下一个
34
+ * 没有消费者的登记项(这正是 A-1 把它登记进 `PLANNED_LANE_DIVERGENCES` 而不是当场开的理由)。
35
+ *
36
+ * ⚠️ 刻意**不**连带放开 `additionalDirectories`:那一位在 device v1 上诚实缺席(v2 §4.1 capability
37
+ * projectContext 行:「拆精确字段,device v1 对 unsupported 项显式 false,不打包沿用」)。
38
+ */
39
+ export declare function deviceCwdHonored(config: {
40
+ remoteExec?: {
41
+ provider?: string;
42
+ };
43
+ requirePrincipal?: boolean;
44
+ }): boolean;
20
45
  /**
21
46
  * [851]P3a — the OTHER host-semantics lane: provider UNSET = the in-process single-user lane. It has NO shell
22
47
  * hands (core prepare-task: no factory/executionEnv ⇒ StubExecutionEnv), so the perSession cwd/env registries
package/dist/task-cwd.js CHANGED
@@ -11,6 +11,9 @@ export function isValidCwd(cwd) {
11
11
  export function cwdHonored(config) {
12
12
  return config.remoteExec?.provider === "host" && config.requirePrincipal !== true;
13
13
  }
14
+ export function deviceCwdHonored(config) {
15
+ return config.remoteExec?.provider === "device";
16
+ }
14
17
  export function inProcessSingleUserLane(config) {
15
18
  return config.remoteExec === undefined && config.requirePrincipal !== true;
16
19
  }
@@ -17,7 +17,7 @@
17
17
  * MCP honored seamlessly (TOC↔cloud is a spectrum, not a wall — clay 2026-06-29: TOB = single-user + people-mgmt).
18
18
  * The caller being the trust root, `toolAxes` (authoritative effect overrides) + `elicitation` are their own choices.
19
19
  * - MULTI-TENANT deployment (`requirePrincipal===true`): a tenant must NOT make the SHARED worker run an arbitrary
20
- * command (RCE) or connect to a body-chosen URL (SSRF). Gate CLOSED → body MCP ignored; the fleet gets MCP only
20
+ * command (unrestricted exec) or connect to a body-chosen URL (SSRF). Gate CLOSED → body MCP ignored; the fleet gets MCP only
21
21
  * through center config refs. Per-principal http-remote injection (a center `allowMcpInjection` entitlement via
22
22
  * `runtimeCapsResolver`) is a DEFERRED v2.
23
23
  * When the gate is closed, a sent `mcpServers` is IGNORED (advertised via `capabilities.mcpInjection`).
@@ -83,7 +83,7 @@ export declare function assertRequestMcpUnlocked(bodyMcp: unknown, lockedKeys: R
83
83
  * (`requirePrincipal===true`)整只 `mcpServers` 被忽略,租户**结构上**够不着这个键(钉见
84
84
  * `test/task-mcp.test.ts` 的多租户格)。同一条腿今天已经透传 `toolAxes`(喂**审批门**,权限比本键**高**
85
85
  * 一档 —— core 明写 contentOrigin「no gate/policy/roster effect」)与 stdio 的**任意命令**(worker 宿主
86
- * 上的真 RCE);在那两样之后,「我声明我自己这台 server 的内容不算外来」不构成任何新的暴露面。
86
+ * 上的真无限制执行);在那两样之后,「我声明我自己这台 server 的内容不算外来」不构成任何新的暴露面。
87
87
  * 名字撞车时 baseline 恒赢(`mergeRequestMcp`)⇒ 调用方也改写不了一台**已配置** server 的声明。
88
88
  *
89
89
  * ## 为什么 fresh 腿是 400 而不是静默丢键
@@ -97,7 +97,7 @@ export declare function assertRequestMcpUnlocked(bodyMcp: unknown, lockedKeys: R
97
97
  * (`normalizeServer` / `resolveMcpServers`),core 根本看不到它,也就永远不会拒。
98
98
  * 与此同时 `checkpoint_ctx` 存的是**原始** `TaskRequestBody`,耐久 resume 逐字重放它,从本门逃出去的
99
99
  * `HttpError` 会被 `src/http/server.ts` 折成 `409 resume_blocked_by_policy` ⇒ 一条**升级前** park、body 里
100
- * 带着当时被静默忽略的坏值的任务,升级后**永久**恢复不了(codex 对抗复审 R1-[high],验真后采纳)。
100
+ * 带着当时被静默忽略的坏值的任务,升级后**永久**恢复不了(codex 交叉复审 R1-[high],验真后采纳)。
101
101
  * ⇒ 照本仓成文的三处 `opts.leg` 先例(`appendSystemPrompt` 案 / `@model` 名单 #233 终裁 (a) /
102
102
  * `compactionModel` 案,都逐字写着「砖死 resume 比降级糟」):fresh fail-loud,resume 降级 + 留声
103
103
  * (留声点 = {@link resolveRequestMcp} 的 `mcp_content_class_dropped`)。挂点判据在 `boot/resolve-spec.ts`。
@@ -25,7 +25,7 @@
25
25
  * `combinePolicies` / `tightenTaskSpec`), so the deny-wins ENFORCEMENT is core-single-source. Only the COMPOSITION
26
26
  * is duplicated for the remote path; a proposed follow-up extracts it to `@sema-agent/sdk` for true
27
27
  * single-source. Because the core backstops (deny-wins + tighten-throws-on-loosen + this bypass-reject) hold either
28
- * way, a composition drift would be a local/remote CONSISTENCY gap, never a privilege escalation.
28
+ * way, a composition drift would be a local/remote CONSISTENCY gap, never a privilege-scope widening.
29
29
  *
30
30
  * v1 scope: `permissions` + `permissions.defaultMode` + `model` + `outputStyle` + `env` (shipped 1.26.0). DEFERRED
31
31
  * (need a seam, flagged to the team + advertised in capabilities so a caller never silently loses a setting):
@@ -97,7 +97,7 @@ export declare function effectivePermissionMode(body: {
97
97
  * §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
98
98
  *
99
99
  * ⚠️ **`auto` 这一行有一条待裁的开口(design/201 §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
100
- * 表驱动一行」)。施工期对抗复审给出了那条新披露,已亲读安装包核实**:
100
+ * 表驱动一行」)。施工期交叉复审给出了那条新披露,已亲读安装包核实**:
101
101
  * · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
102
102
  * decision.action === "ask" …)`);
103
103
  * · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
@@ -155,7 +155,7 @@ export interface ParseTaskSettingsResult {
155
155
  deferred: string[];
156
156
  }
157
157
  /** Cap on `outputStyle` length — it lands in the system prompt, so it shares the `MAX_SYSTEM_PROMPT_CHARS` (16384)
158
- * posture (an uncapped prompt body is a per-turn token-cost hole; adversarial review). Exported so the HTTP layer
158
+ * posture (an uncapped prompt body is a per-turn token-cost hole; cross review). Exported so the HTTP layer
159
159
  * (prepareSpec) can 400 fail-loud on submit with the SAME bound this defensive parse enforces on every path. */
160
160
  export declare const MAX_SETTINGS_OUTPUT_STYLE_CHARS = 16384;
161
161
  /** Cap on the element count of each permission list (allow/deny/ask) — bounds a pathological rule blast. */
@@ -1,5 +1,6 @@
1
1
  import { combinePolicies, createAllowDenyPolicy, createFsWriteGatePolicy, createPermissionRulePolicy, tightenTaskSpec, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, } from "@sema-agent/core";
2
2
  import { findCcRuleFormNames, findUnmatchableToolNames, formatUnmatchableToolNames } from "./config.js";
3
+ import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
3
4
  import { parseHooksConfig } from "./hooks/hook-runner.js";
4
5
  export function coercePermissionMode(raw) {
5
6
  if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
@@ -160,7 +161,8 @@ function createAskListPolicy(askNames) {
160
161
  });
161
162
  }
162
163
  catch (e) {
163
- throw new Error(`permissions.ask has rule(s) core cannot compile — ${e instanceof Error ? e.message : String(e)}`, { cause: e });
164
+ const coreMsg = (e instanceof Error ? e.message : String(e)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
165
+ throw new Error(`permissions.ask has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
164
166
  }
165
167
  }
166
168
  const WORKFLOW_ASK_NAMES = [RUN_WORKFLOW_TOOL_NAME];
@@ -78,7 +78,7 @@ export declare function normalizeRetainSubagentSessions(raw: unknown): boolean |
78
78
  max?: number;
79
79
  } | undefined;
80
80
  /**
81
- * Per-task wall clock by TENANCY POSTURE (clay 2026-07-04, make-real 教训①:600s 默认墙钟砍掉了健康推进的
81
+ * Per-task wall clock by TENANCY POSTURE (clay 2026-07-04, make-real 教训①:600s 默认墙钟截停了健康推进的
82
82
  * 长任务/chaos 恢复 run):
83
83
  * - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
84
84
  * (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
@@ -27,8 +27,8 @@
27
27
  * · U+061C = ALM(Arabic Letter Mark);U+200E/200F = LRM/RLM;
28
28
  * · U+202A-202E = 嵌入/覆写族(含 RLO);U+2066-2069 = 隔离符族。
29
29
  *
30
- * 🔴 **U+061C 是 codex 对抗复审 [medium] 验真后补的**:首版漏了它 —— 而漏一个码点在**检测**面上不是
31
- * 「覆盖率差一点」,是一条完整的绕过路(只用 ALM 构造的输入让在场位缺席,壳于是照常按干净渲染)。
30
+ * 🔴 **U+061C 是 codex 交叉复审 [medium] 验真后补的**:首版漏了它 —— 而漏一个码点在**检测**面上不是
31
+ * 「覆盖率差一点」,是一条完整的跳过路(只用 ALM 构造的输入让在场位缺席,壳于是照常按干净渲染)。
32
32
  * 交叉锚:core 自己的 `APPROVER_REFUSED_CHARS_RE`(`dist/core/tool-policy.js`)同样含 `\\u061c`。
33
33
  *
34
34
  * ⚠️ 与 core 那只**更宽**的集合的差额如实登记(刻意,不是漏):core 还含 C1(`\\u0080-\\u009f`)与