@sema-agent/server 7.130.0 → 7.132.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 (294) hide show
  1. package/MIGRATION.md +396 -0
  2. package/README.md +13 -6
  3. package/README.zh-CN.md +9 -5
  4. package/USAGE.md +187 -50
  5. package/dist/approval-card.d.ts +3 -1
  6. package/dist/approval-card.js +2 -3
  7. package/dist/audit.d.ts +7 -10
  8. package/dist/audit.js +4 -1
  9. package/dist/auth-bridge.d.ts +4 -0
  10. package/dist/auth-bridge.js +10 -11
  11. package/dist/bench/s1/live-deps.js +2 -0
  12. package/dist/boot/deployment-mods.d.ts +31 -0
  13. package/dist/boot/deployment-mods.js +26 -0
  14. package/dist/boot/execution-env.d.ts +1 -1
  15. package/dist/boot/execution-env.js +19 -6
  16. package/dist/boot/memory-boundary.d.ts +2 -2
  17. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  18. package/dist/boot/read-deny-anchor-audit.js +36 -0
  19. package/dist/boot/resolve-spec.d.ts +1 -5
  20. package/dist/boot/resolve-spec.js +56 -61
  21. package/dist/boot/runner-deps.d.ts +19 -8
  22. package/dist/boot/runner-deps.js +14 -2
  23. package/dist/boot/session-faces.d.ts +1 -7
  24. package/dist/boot/stage-02-web-admin-pull.js +9 -1
  25. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  26. package/dist/boot/stage-03-store-backend.js +2 -2
  27. package/dist/boot/stage-04-stores.d.ts +1 -1
  28. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  29. package/dist/boot/stage-05-execution-env.js +7 -10
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +5 -4
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  33. package/dist/boot/stage-07-capability-layer.js +3 -3
  34. package/dist/boot/stage-08-reapers.d.ts +2 -2
  35. package/dist/boot/stage-09-leader.d.ts +3 -9
  36. package/dist/boot/stage-10-http-server.d.ts +3 -9
  37. package/dist/boot/stage-10-http-server.js +12 -10
  38. package/dist/boot/stores.d.ts +1 -1
  39. package/dist/boot/stores.js +5 -6
  40. package/dist/boot/web-admin.d.ts +0 -3
  41. package/dist/boot/web-admin.js +35 -35
  42. package/dist/brain.js +17 -22
  43. package/dist/budget.js +5 -4
  44. package/dist/caller-text-echo.d.ts +20 -0
  45. package/dist/caller-text-echo.js +24 -0
  46. package/dist/capabilities/brand-identity.d.ts +26 -0
  47. package/dist/capabilities/brand-identity.js +27 -0
  48. package/dist/capabilities/builtin-tools.js +2 -1
  49. package/dist/capabilities/center-plugins.js +1 -1
  50. package/dist/capabilities/center-prompts.d.ts +32 -59
  51. package/dist/capabilities/center-prompts.js +18 -32
  52. package/dist/capabilities/composite-members.d.ts +1 -0
  53. package/dist/capabilities/composite-members.js +1 -0
  54. package/dist/capabilities/engine-capabilities.js +2 -1
  55. package/dist/capabilities/hands-lane.d.ts +9 -6
  56. package/dist/capabilities/hands-lane.js +3 -3
  57. package/dist/capabilities/host-tool-names.d.ts +17 -0
  58. package/dist/capabilities/host-tool-names.js +15 -0
  59. package/dist/capabilities/mcp-probe.d.ts +14 -1
  60. package/dist/capabilities/mcp-probe.js +22 -133
  61. package/dist/capabilities/repo-tools.js +4 -3
  62. package/dist/capabilities/scenarios.d.ts +42 -47
  63. package/dist/capabilities/scenarios.js +19 -35
  64. package/dist/capabilities/select-environment-tool.js +2 -1
  65. package/dist/capabilities/send-user-file-tool.d.ts +0 -1
  66. package/dist/capabilities/send-user-file-tool.js +2 -2
  67. package/dist/capabilities/skills.d.ts +19 -4
  68. package/dist/capabilities/skills.js +30 -10
  69. package/dist/capabilities/tool-defer.js +2 -1
  70. package/dist/config-catalog.js +22 -11
  71. package/dist/config-invariants.js +2 -2
  72. package/dist/config-lkg.d.ts +9 -9
  73. package/dist/config-lkg.js +20 -4
  74. package/dist/config-provider.d.ts +3 -1
  75. package/dist/config-provider.js +14 -6
  76. package/dist/config-types.d.ts +13 -9
  77. package/dist/config.d.ts +2 -2
  78. package/dist/config.js +34 -20
  79. package/dist/device-ws-protocol.d.ts +1 -1
  80. package/dist/device-ws-protocol.js +1 -0
  81. package/dist/elicitation.d.ts +7 -0
  82. package/dist/elicitation.js +20 -5
  83. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  84. package/dist/env-name-allowlist-knobs.js +5 -4
  85. package/dist/exec-bounds.d.ts +15 -3
  86. package/dist/exec-bounds.js +4 -0
  87. package/dist/execution-lane-caps.d.ts +9 -2
  88. package/dist/execution-lane-caps.js +11 -0
  89. package/dist/hooks/classic-stations.d.ts +86 -0
  90. package/dist/hooks/classic-stations.js +27 -0
  91. package/dist/hooks/hook-llm.d.ts +8 -5
  92. package/dist/hooks/hook-llm.js +80 -37
  93. package/dist/hooks/hook-runner.d.ts +273 -75
  94. package/dist/hooks/hook-runner.js +293 -317
  95. package/dist/http/admission.d.ts +8 -2
  96. package/dist/http/admission.js +103 -59
  97. package/dist/http/approval-sweeps.js +59 -18
  98. package/dist/http/checkpoint-ctx.d.ts +36 -18
  99. package/dist/http/checkpoint-ctx.js +14 -10
  100. package/dist/http/claimed-run.d.ts +13 -0
  101. package/dist/http/claimed-run.js +15 -0
  102. package/dist/http/device-placement.js +5 -10
  103. package/dist/http/dispatch.d.ts +0 -4
  104. package/dist/http/dispatch.js +6 -10
  105. package/dist/http/durable-run-leg.d.ts +2 -1
  106. package/dist/http/durable-run-leg.js +5 -3
  107. package/dist/http/health-self-report.d.ts +7 -5
  108. package/dist/http/health-self-report.js +4 -18
  109. package/dist/http/principal-gate.d.ts +23 -6
  110. package/dist/http/principal-gate.js +24 -5
  111. package/dist/http/refusal-body.d.ts +21 -0
  112. package/dist/http/refusal-body.js +4 -0
  113. package/dist/http/request-guards.d.ts +27 -0
  114. package/dist/http/request-guards.js +24 -3
  115. package/dist/http/resume-legs.d.ts +25 -0
  116. package/dist/http/resume-legs.js +48 -26
  117. package/dist/http/routes/approvals-assistant.js +1 -1
  118. package/dist/http/routes/capabilities.js +7 -17
  119. package/dist/http/routes/diagnostics.js +1 -1
  120. package/dist/http/routes/rules.js +2 -2
  121. package/dist/http/routes/runs.js +13 -17
  122. package/dist/http/routes/session-sync.js +4 -2
  123. package/dist/http/routes/sessions.js +23 -11
  124. package/dist/http/routes/shared-memory.js +2 -13
  125. package/dist/http/routes/side-query.js +2 -3
  126. package/dist/http/routes/tasks.js +34 -19
  127. package/dist/http/scenario-read-face.d.ts +14 -0
  128. package/dist/http/scenario-read-face.js +5 -0
  129. package/dist/http/send.d.ts +13 -1
  130. package/dist/http/send.js +12 -1
  131. package/dist/http/server.d.ts +37 -23
  132. package/dist/http/server.js +30 -4
  133. package/dist/http/stored-body-replay.d.ts +10 -3
  134. package/dist/http/stored-body-replay.js +27 -6
  135. package/dist/http/submit-ctx.d.ts +25 -0
  136. package/dist/http/submit-ctx.js +39 -0
  137. package/dist/http/wire-types.d.ts +18 -5
  138. package/dist/index.d.ts +3 -2
  139. package/dist/index.js +3 -2
  140. package/dist/lane-home-probe.d.ts +43 -0
  141. package/dist/lane-home-probe.js +35 -0
  142. package/dist/limit-refusal.d.ts +47 -0
  143. package/dist/limit-refusal.js +12 -0
  144. package/dist/main.js +1 -1
  145. package/dist/mcp-assertion-header.d.ts +3 -11
  146. package/dist/mcp-assertion-header.js +3 -9
  147. package/dist/mcp-tool-faces.d.ts +5 -0
  148. package/dist/mcp-tool-faces.js +5 -1
  149. package/dist/memory-scope.d.ts +1 -1
  150. package/dist/model-entry-refusal.d.ts +1 -7
  151. package/dist/model-provider.d.ts +39 -12
  152. package/dist/model-provider.js +19 -2
  153. package/dist/model-route-endpoint.d.ts +52 -12
  154. package/dist/model-route-endpoint.js +48 -26
  155. package/dist/model-route-gap.d.ts +5 -4
  156. package/dist/model-route-gap.js +1 -1
  157. package/dist/model-select.d.ts +58 -0
  158. package/dist/model-select.js +31 -0
  159. package/dist/namespaced-rule-name.d.ts +7 -4
  160. package/dist/namespaced-rule-name.js +4 -3
  161. package/dist/observability/cost-taxonomy.d.ts +7 -7
  162. package/dist/observability/fail-open.d.ts +53 -12
  163. package/dist/observability/fail-open.js +47 -7
  164. package/dist/observability/metrics.js +3 -1
  165. package/dist/observability/prompt-manifest.d.ts +47 -82
  166. package/dist/observability/prompt-manifest.js +119 -17
  167. package/dist/observability/run-terminal.js +14 -6
  168. package/dist/observability/secret-env-scrub.d.ts +13 -1
  169. package/dist/observability/secret-env-scrub.js +13 -1
  170. package/dist/observability/tool-trace.d.ts +16 -21
  171. package/dist/observability/tool-trace.js +51 -42
  172. package/dist/per-task-image.d.ts +7 -1
  173. package/dist/per-task-image.js +3 -3
  174. package/dist/plain-string-record.d.ts +25 -0
  175. package/dist/plain-string-record.js +32 -0
  176. package/dist/plugins/blob-backend.d.ts +68 -13
  177. package/dist/plugins/blob-backend.js +53 -10
  178. package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
  179. package/dist/plugins/checkpoint-store-sql.js +39 -26
  180. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  181. package/dist/plugins/k8s-bg-scripts.js +22 -0
  182. package/dist/plugins/local-checkpoint-store.js +5 -2
  183. package/dist/plugins/local-session-store.js +3 -3
  184. package/dist/plugins/one-time-migrations.d.ts +147 -91
  185. package/dist/plugins/one-time-migrations.js +372 -208
  186. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  187. package/dist/plugins/permission-rule-store-sql.js +4 -18
  188. package/dist/plugins/pg-session-storage.d.ts +4 -5
  189. package/dist/plugins/pg-session-storage.js +7 -17
  190. package/dist/plugins/remote-env-adb.d.ts +6 -0
  191. package/dist/plugins/remote-env-adb.js +9 -4
  192. package/dist/plugins/remote-env-device.js +3 -0
  193. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  194. package/dist/plugins/remote-env-e2b.js +21 -9
  195. package/dist/plugins/remote-env-host.d.ts +9 -9
  196. package/dist/plugins/remote-env-host.js +10 -32
  197. package/dist/plugins/remote-env-k8s.d.ts +95 -26
  198. package/dist/plugins/remote-env-k8s.js +265 -90
  199. package/dist/plugins/remote-env-local-docker.d.ts +20 -1
  200. package/dist/plugins/remote-env-local-docker.js +77 -9
  201. package/dist/plugins/remote-env-ssh.d.ts +15 -0
  202. package/dist/plugins/remote-env-ssh.js +109 -3
  203. package/dist/plugins/remote-shell.d.ts +0 -1
  204. package/dist/plugins/remote-shell.js +1 -2
  205. package/dist/plugins/retention-store-sql.js +6 -2
  206. package/dist/plugins/schema-consistency.d.ts +54 -0
  207. package/dist/plugins/schema-consistency.js +52 -0
  208. package/dist/plugins/session-epoch-anchor.d.ts +13 -0
  209. package/dist/plugins/session-epoch-anchor.js +23 -0
  210. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  211. package/dist/plugins/shared-memory-store-sql.js +5 -16
  212. package/dist/plugins/sql-driver.d.ts +141 -0
  213. package/dist/plugins/sql-driver.js +115 -1
  214. package/dist/plugins/sql-errors.d.ts +41 -0
  215. package/dist/plugins/sql-errors.js +36 -0
  216. package/dist/plugins/store-backend.js +2 -1
  217. package/dist/plugins/tidb-session-storage.d.ts +7 -11
  218. package/dist/plugins/tidb-session-storage.js +6 -16
  219. package/dist/plugins/tidb-session-store.js +1 -1
  220. package/dist/prompts-domain-validate.d.ts +17 -61
  221. package/dist/prompts-domain-validate.js +9 -119
  222. package/dist/question.d.ts +18 -9
  223. package/dist/question.js +21 -13
  224. package/dist/request-key-closure.d.ts +4 -4
  225. package/dist/request-key-closure.js +18 -13
  226. package/dist/rule-entry-list.d.ts +134 -24
  227. package/dist/rule-entry-list.js +162 -30
  228. package/dist/run-local.d.ts +3 -3
  229. package/dist/run-local.js +26 -25
  230. package/dist/runs.d.ts +48 -2
  231. package/dist/runs.js +61 -2
  232. package/dist/server-secret-env.d.ts +44 -18
  233. package/dist/server-secret-env.js +49 -8
  234. package/dist/session-incarnation.d.ts +15 -5
  235. package/dist/session-incarnation.js +16 -9
  236. package/dist/session-titler.js +12 -2
  237. package/dist/spec-fields.d.ts +34 -18
  238. package/dist/spec-fields.js +46 -53
  239. package/dist/sql-engine-posture.d.ts +29 -4
  240. package/dist/sql-engine-posture.js +36 -1
  241. package/dist/stored-request-hooks-m20.d.ts +23 -0
  242. package/dist/stored-request-hooks-m20.js +124 -0
  243. package/dist/stored-request-tool-axes-m19.d.ts +20 -5
  244. package/dist/stored-request-tool-axes-m19.js +30 -2
  245. package/dist/task-a2a.d.ts +9 -1
  246. package/dist/task-a2a.js +73 -47
  247. package/dist/task-mcp.d.ts +54 -102
  248. package/dist/task-mcp.js +194 -92
  249. package/dist/task-settings.d.ts +47 -25
  250. package/dist/task-settings.js +46 -61
  251. package/dist/text-bidi.d.ts +3 -46
  252. package/dist/text-bidi.js +3 -3
  253. package/dist/tool-approval.d.ts +1 -1
  254. package/dist/trace/core-keyset-guard.d.ts +24 -4
  255. package/dist/trace/engine-notice-wire.d.ts +11 -3
  256. package/dist/trace/engine-notice-wire.js +8 -1
  257. package/dist/trace/ledger-events.d.ts +3 -1
  258. package/dist/trace/ledger-sink.d.ts +29 -0
  259. package/dist/trace/ledger-sink.js +15 -1
  260. package/dist/trace/model-segment.js +1 -1
  261. package/dist/trace/project.d.ts +25 -6
  262. package/dist/trace/project.js +68 -22
  263. package/dist/trace/wire-projection-faces.d.ts +80 -2
  264. package/dist/trace/wire-projection-faces.js +23 -1
  265. package/dist/user-authored-text.d.ts +7 -0
  266. package/dist/user-authored-text.js +3 -0
  267. package/dist/web-admin-client/apply-effective.js +10 -5
  268. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  269. package/dist/web-admin-client/candidate-gate.js +3 -3
  270. package/dist/web-admin-client/http-client.js +0 -6
  271. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  272. package/dist/web-admin-client/read-warnings.js +10 -0
  273. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  274. package/dist/web-admin-client/skills-mcp.js +9 -6
  275. package/dist/web-admin-client/types.d.ts +3 -4
  276. package/dist/workspace-cache.d.ts +35 -0
  277. package/dist/workspace-cache.js +36 -0
  278. package/dist/workspace-space.d.ts +3 -2
  279. package/dist/workspace-space.js +2 -0
  280. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
  281. package/package.json +4 -4
  282. package/scripts/drain-check-local.mjs +37 -3
  283. package/scripts/drain-check.mysql.sql +30 -2
  284. package/scripts/drain-check.pg.sql +29 -2
  285. package/dist/capabilities/memory-notice.d.ts +0 -138
  286. package/dist/capabilities/memory-notice.js +0 -64
  287. package/dist/capabilities/prompt.d.ts +0 -17
  288. package/dist/capabilities/prompt.js +0 -6
  289. package/dist/mcp-content-origin.d.ts +0 -34
  290. package/dist/mcp-content-origin.js +0 -10
  291. package/dist/tool-axis-words.d.ts +0 -27
  292. package/dist/tool-axis-words.js +0 -9
  293. package/dist/wire-name-bound.d.ts +0 -14
  294. package/dist/wire-name-bound.js +0 -13
@@ -0,0 +1,25 @@
1
+ /** 一个没进副本的成员:名字(调用方写的,原样;回显前由调用点清洗)与它为什么不能交(只说种类,不回显值)。 */
2
+ export interface PlainStringRecordMemberFault {
3
+ readonly name: string;
4
+ readonly fault: string;
5
+ }
6
+ /**
7
+ * {@link readPlainStringRecord} 的读数(见模块头注的律):`ok:false` = 记录不交(`fault` 只说种类;坏在某个成员时 `member` 点名它);
8
+ * `ok:true` = 交出去的纯数据副本 + 被剔掉的 `__proto__` 名成员(按记录里的顺序;恒只有这一种成员会被剔)。
9
+ * `ok:false` 的 `fault` 是接在「its member "<名>" 」后面的原因句;成员名恰是 `__proto__` 而值也坏时,这一句同时说两件事(名不许、值必须是串)。
10
+ */
11
+ export type PlainStringRecordRead = {
12
+ readonly ok: true;
13
+ readonly record: Record<string, string>;
14
+ readonly stripped: readonly PlainStringRecordMemberFault[];
15
+ } | {
16
+ readonly ok: false;
17
+ readonly fault: string;
18
+ readonly member?: string;
19
+ };
20
+ /**
21
+ * 读一份「名 → 串」记录(见模块头注的律)。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算,与引擎收据同读法),值按属性描述读:
22
+ * 数据属性取值,访问器调一次。先判每个成员的值(`__proto__` 名的成员也判:它的值不是串 ⇒ 整只不交,与 7.131.x 同),再剔 `__proto__` 名。调用方的对象不被交出去、也不被改。
23
+ */
24
+ export declare function readPlainStringRecord(v: unknown): PlainStringRecordRead;
25
+ //# sourceMappingURL=plain-string-record.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { describeValueShape } from "@sema-agent/settings-schema";
2
+ const PROTO_MEMBER_FAULT = 'no member of a declaration may be named "__proto__"';
3
+ export function readPlainStringRecord(v) {
4
+ if (v === null || typeof v !== "object" || Array.isArray(v))
5
+ return { ok: false, fault: v === null ? "got null" : Array.isArray(v) ? "got an array" : `got a ${typeof v}` };
6
+ const proto = Object.getPrototypeOf(v);
7
+ if (proto !== Object.prototype && proto !== null)
8
+ return { ok: false, fault: "got an object that is not plain data (a class instance, a Map, process.env, or a record built on a prototype; copy it into a plain object)" };
9
+ const record = {};
10
+ const stripped = [];
11
+ for (const name of Object.getOwnPropertyNames(v)) {
12
+ const d = Object.getOwnPropertyDescriptor(v, name);
13
+ let value;
14
+ let unreadable = false;
15
+ try {
16
+ value = d === undefined ? undefined : Object.hasOwn(d, "value") ? d.value : d.get === undefined ? undefined : Reflect.apply(d.get, v, []);
17
+ }
18
+ catch {
19
+ unreadable = true;
20
+ }
21
+ if (unreadable || typeof value !== "string") {
22
+ const valueFault = unreadable ? "could not be read (its getter threw)" : `must be a string — got ${describeValueShape(value)}`;
23
+ return { ok: false, fault: name === "__proto__" ? `is not allowed (${PROTO_MEMBER_FAULT}) and its value ${valueFault}` : valueFault, member: name };
24
+ }
25
+ if (name === "__proto__")
26
+ stripped.push({ name, fault: PROTO_MEMBER_FAULT });
27
+ else
28
+ record[name] = value;
29
+ }
30
+ return { ok: true, record, stripped };
31
+ }
32
+ //# sourceMappingURL=plain-string-record.js.map
@@ -1,19 +1,69 @@
1
1
  import type { Pool as MysqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- /** (a)(clay 拍 a+c,2026-07-27)tidb 方言的默认 per-blob 帽 = **6 MiB,TiDB 自己的默认
4
- * `txn-entry-size-limit`**。两堵墙真库实测(k3s8 tidb-test v7.5.1 默认配置,2026-07-27 探针):
5
- * 7MiB=`entry too large`(entry 墙,默认 6291456)、31MiB=同、32MiB=`max_allowed_packet`(包墙,
6
- * 64MiB ÷ ~2× 文本协议膨胀)——矮墙是 entry 的 6MiB,不是先前推测的包墙 ~31MiB。帽=诚实拒绝:
7
- * 超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn 拿到可行动信息);**帽拒绝的
8
- * 在默认配置下本来也落不进去 ⇒ 零回归**。调大过 entry 限/packet 的部署用 SNAPSHOT_BLOB_SQL_MAX_BYTES
9
- * 放宽(两方言都生效)。**pg 方言默认不设帽**(扩展协议无这两堵墙,不人为降档)。D-1 附件战役同墙
10
- * 教训:task-attachment-store.ts:19。 */
11
- export declare const SQL_BLOB_DEFAULT_MAX_BYTES = 6291456;
3
+ import { type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
4
+ /**
5
+ * 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
6
+ * 包墙折成「能装下多大的 blob」只用这一只常量,推导里不手写 `/ 2`。
7
+ */
8
+ export declare const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
9
+ /**
10
+ * 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes},与提交体受理口读同一个折算)再按 {@link SQL_TEXT_PROTOCOL_BLOB_INFLATION} 折半。
11
+ * 提交体按原样进语句、没有膨胀,blob 以十六进制字面进语句、有 2× —— 膨胀律是 blob 腿自己的,余量不是。
12
+ */
13
+ export declare function packetWallBlobCap(maxPacketBytes: number): number;
14
+ /**
15
+ * **事实本身还没建立**时 MySQL 协议方言的缺省帽(池上还没借出过连接,或最近一次连接初始化被拒:`SqlDriver.facts === null`)= 事实缺席时的墙
16
+ * (`sql-driver.ts` `SQL_FACTS_ABSENT_WALLS`,判官 `sqlWallsOf`;提交受理口读同一只)走下面同一个推导 —— 不是第二份落点,只是那个推导在这组墙上的值
17
+ * (今天 = TiDB 缺省行墙走 blob 行的折算 6 MiB − 1 KiB:最矮的包墙缺省 16 MiB 扣语句余量再折半仍近 8 MiB,行腿更矮)。
18
+ */
19
+ export declare const SQL_BLOB_FACTS_ABSENT_CAP_BYTES: number;
20
+ /**
21
+ * SQL 快照 blob 的**缺省**帽(运维没设 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 时)的唯一推导 —— 入参是判官 `sqlWallsOf` 给的墙(不读事实、不判缺席、不看方言),**两堵墙取最小**:
22
+ * 行墙腿(`SqlEngineFacts.maxRowBytes`,TiDB:{@link rowWallPayloadBytes} 的 `blob` = 行墙 − blob 行的余量 1 KiB —— 按这一行的真实形算,不借 JSON 提交体行的 64 KiB)
23
+ * 与包墙腿(`SqlEngineFacts.maxPacketBytes`,InnoDB 与 TiDB 都有:{@link packetWallBlobCap} = ⌊(包墙 − 1 KiB 语句余量) ÷ 2⌋)。余量只在 `sql-driver.ts` 定义一次
24
+ * (墙 × 行的形各一只),提交体受理口(`http/request-guards.ts` `refuseSubmitBody`)读同一张表的 `json` 那一只,不各抄。
25
+ * 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG —— 扩展协议无这两堵墙,事实在不在都一样 —— 或没有 SQL 后端)⇒ `undefined` 不设帽。事实本身缺席时
26
+ * 墙由判官给(MySQL 协议按最保守的墙),走同一个推导 = {@link SQL_BLOB_FACTS_ABSENT_CAP_BYTES}。显式旋钮是运维的策略帽,在本推导之上(见 {@link sqlBlobCapBytes})。
27
+ *
28
+ * 两堵墙的真库读数(clay 拍 a+c,2026-07-27;k3s8 tidb-test v7.5.1 默认配置探针):7 MiB / 31 MiB = `entry too large`(行墙,默认 6291456)、
29
+ * 32 MiB = `max_allowed_packet`(包墙,64 MiB ÷ ~2× 文本协议膨胀)。帽=诚实拒绝:超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn
30
+ * 拿到可行动信息);帽与墙之间只差这一行自己的几百字节(blob 行余量),帽之下的 blob 在缺省配置下落得进。D-1 附件战役同墙教训:`task-attachment-store.ts` 头注。
31
+ */
32
+ export declare function sqlBlobDefaultCapBytes(walls: SqlWallsReading): number | undefined;
33
+ /**
34
+ * 一个 SQL 快照 blob 的帽 + 它**从哪来**。`operator` = 运维显式设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`(策略帽);`reported` / `assumed` = 缺省推导里**定下这个帽的那条腿**的墙数字出处
35
+ * ({@link SqlWallSource}:库自己报的 / 读失败或事实缺席而退的文档缺省)。机读位同 413 拒体的 `wallSource` 词,旋钮形多一个 `operator`。
36
+ */
37
+ export interface SqlBlobCapReading {
38
+ readonly capBytes: number;
39
+ readonly source: "operator" | SqlWallSource;
40
+ }
41
+ /**
42
+ * 缺省帽的推导(唯一一处,{@link sqlBlobDefaultCapBytes} 是它的字节投影):两条腿各带自己那堵墙的出处,取最小的那条;两条一样小取行墙腿。两堵墙都不存在 ⇒ `undefined`。
43
+ */
44
+ export declare function sqlBlobDefaultCapOf(walls: SqlWallsReading): SqlBlobCapReading | undefined;
45
+ /**
46
+ * 一个 SQL 快照 blob 此刻的帽 —— **显式旋钮 ?? 判官给的墙走缺省推导**的唯一一只函数。两个读者同调它、各传本池的**引擎读口**(方言与活事实同出一只驱动,
47
+ * 与提交受理口读的是同一种读口):店的写前帽({@link SqlBlobBackend},本池的驱动)与 `PUT /sync/blobs` 的前置 413(`boot/stage-10-http-server.ts` 装配的读口,
48
+ * 按请求调,读 `ServiceDeps.sqlEngine`)。同一池上两个读口读的是同一份池级事实。
49
+ */
50
+ export declare function sqlBlobCapOf(explicitCapBytes: number | undefined, engine: SqlEngineReading): SqlBlobCapReading | undefined;
51
+ /** {@link sqlBlobCapOf} 的字节投影(同一个推导,不另读)。 */
52
+ export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined, engine: SqlEngineReading): number | undefined;
53
+ /**
54
+ * 一次 blob 超帽的拒句 —— **唯一的组句器**(`PUT /sync/blobs` 的前置 413 与店的 typed {@link BlobTooLargeError} 同调它):句子按帽的出处({@link SqlBlobCapReading.source})
55
+ * 分三形,不把假定说成库的上限、也不把运维的旋钮说成库的上限。
56
+ * · `operator` —— 帽是运维设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`;
57
+ * · `reported` —— 帽由库报的两堵墙推出(单行上限与包墙÷文本协议膨胀的较小者);
58
+ * · `assumed` —— 库的上限没读到(事实还没建立,或这一堵读失败),帽按受支持引擎里最保守的缺省墙推出,稍后重试或由运维设旋钮。
59
+ */
60
+ export declare function sqlBlobCapRefusalText(byteLen: number, cap: SqlBlobCapReading): string;
12
61
  /** typed 判别错:blob 超过 SQL 店的承载帽。判别码供店侧结果映射/HTTP 面/日志使用——绝不能与
13
- * transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)。 */
62
+ * transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)。句子是 {@link sqlBlobCapRefusalText}(带帽的出处)。 */
14
63
  export declare class BlobTooLargeError extends Error {
64
+ readonly cap: SqlBlobCapReading;
15
65
  readonly code: "blob_too_large_for_sql";
16
- constructor(byteLen: number, capBytes: number);
66
+ constructor(byteLen: number, cap: SqlBlobCapReading);
17
67
  }
18
68
  /**
19
69
  * The content-addressed byte store. All four methods are keyed on the sha256 `hash` of the bytes:
@@ -47,11 +97,16 @@ export interface BlobBackend {
47
97
  export declare class SqlBlobBackend implements BlobBackend {
48
98
  private readonly dialect;
49
99
  private readonly pool;
50
- /** Effective per-blob cap: explicit ctor value wins; else tidb=SQL_BLOB_DEFAULT_MAX_BYTES, pg=uncapped. */
51
- private readonly capBytes;
100
+ /** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link sqlBlobCapBytes} over this pool's live engine facts. */
101
+ private readonly explicitCapBytes;
102
+ /** This pool's engine reading (dialect + live facts, from the pool's own driver) — the one source the cap's walls are read from. */
103
+ private readonly engine;
52
104
  /** Blob-domain table ([ref] blob 域隔离:file-history 域用自己的表,reap/GC 不跨域)。代码内字面量,非用户输入。 */
53
105
  private readonly table;
54
106
  constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string);
107
+ /** The cap in force for this write: the explicit one, else the default derived from the pool's engine facts as they
108
+ * stand now (the driver refreshes them on each connection initialization). */
109
+ private cap;
55
110
  /** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
56
111
  * created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
57
112
  * (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
@@ -1,14 +1,51 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
3
+ import { SQL_FACTS_ABSENT_WALLS, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
3
4
  const BLOB_GC_GRACE_MS = 3_600_000;
4
- export const SQL_BLOB_DEFAULT_MAX_BYTES = 6_291_456;
5
+ export const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
6
+ export function packetWallBlobCap(maxPacketBytes) {
7
+ return Math.floor(packetWallStatementBytes(maxPacketBytes) / SQL_TEXT_PROTOCOL_BLOB_INFLATION);
8
+ }
9
+ export const SQL_BLOB_FACTS_ABSENT_CAP_BYTES = sqlBlobDefaultCapBytes(SQL_FACTS_ABSENT_WALLS);
10
+ export function sqlBlobDefaultCapBytes(walls) {
11
+ return sqlBlobDefaultCapOf(walls)?.capBytes;
12
+ }
13
+ export function sqlBlobDefaultCapOf(walls) {
14
+ const legs = [];
15
+ if (walls.maxRowBytes !== null)
16
+ legs.push({ capBytes: rowWallPayloadBytes(walls.maxRowBytes, "blob"), source: walls.sources.maxRowBytes });
17
+ if (walls.maxPacketBytes !== null)
18
+ legs.push({ capBytes: packetWallBlobCap(walls.maxPacketBytes), source: walls.sources.maxPacketBytes });
19
+ return legs.reduce((best, leg) => (best === undefined || leg.capBytes < best.capBytes ? leg : best), undefined);
20
+ }
21
+ export function sqlBlobCapOf(explicitCapBytes, engine) {
22
+ return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(engine));
23
+ }
24
+ export function sqlBlobCapBytes(explicitCapBytes, engine) {
25
+ return sqlBlobCapOf(explicitCapBytes, engine)?.capBytes;
26
+ }
27
+ export function sqlBlobCapRefusalText(byteLen, cap) {
28
+ const objectStorage = "configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs";
29
+ const head = `blob of ${byteLen} bytes exceeds this deployment's SQL snapshot-blob cap (${cap.capBytes} bytes — `;
30
+ switch (cap.source) {
31
+ case "operator":
32
+ return `${head}set by the operator's SNAPSHOT_BLOB_SQL_MAX_BYTES) — ${objectStorage}, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if the database allows larger writes`;
33
+ case "reported":
34
+ return `${head}the smaller of the database's single-row limit (TiDB txn-entry-size-limit, 6MiB by default) and about half its max_allowed_packet (the text protocol doubles a blob inside the statement)) — ${objectStorage}, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment lifted those limits`;
35
+ case "assumed":
36
+ return `${head}the database's limits were not read — engine facts not read yet, or a limit could not be read — so the smallest documented default wall applies) — retry shortly, ${objectStorage}, or set SNAPSHOT_BLOB_SQL_MAX_BYTES to the limit your deployment actually has`;
37
+ default: {
38
+ const unhandled = cap.source;
39
+ throw new Error(`blob cap refusal without a sentence: ${String(unhandled)}`);
40
+ }
41
+ }
42
+ }
5
43
  export class BlobTooLargeError extends Error {
44
+ cap;
6
45
  code = "blob_too_large_for_sql";
7
- constructor(byteLen, capBytes) {
8
- super(`blob of ${byteLen} bytes exceeds the SQL snapshot-blob cap (${capBytes} bytes): a single-row INSERT this large ` +
9
- `hits TiDB's txn-entry-size-limit (default 6MiB) or the mysql-protocol max_allowed_packet (~2x text-protocol ` +
10
- `inflation). Configure object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot ` +
11
- `blobs, or override the cap with SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment raised those limits.`);
46
+ constructor(byteLen, cap) {
47
+ super(sqlBlobCapRefusalText(byteLen, cap));
48
+ this.cap = cap;
12
49
  this.name = "BlobTooLargeError";
13
50
  }
14
51
  }
@@ -26,17 +63,23 @@ async function mapBounded(items, limit, fn) {
26
63
  export class SqlBlobBackend {
27
64
  dialect;
28
65
  pool;
29
- capBytes;
66
+ explicitCapBytes;
67
+ engine;
30
68
  table;
31
69
  constructor(dialect, pool, maxBytes, table = "snapshot_blob") {
32
70
  this.dialect = dialect;
33
71
  this.pool = pool;
34
- this.capBytes = maxBytes ?? (dialect === "tidb" ? SQL_BLOB_DEFAULT_MAX_BYTES : Number.POSITIVE_INFINITY);
72
+ this.explicitCapBytes = maxBytes;
35
73
  this.table = table;
74
+ this.engine = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
75
+ }
76
+ cap() {
77
+ return sqlBlobCapOf(this.explicitCapBytes, this.engine);
36
78
  }
37
79
  async putBlob(hash, bytes) {
38
- if (bytes.byteLength > this.capBytes)
39
- throw new BlobTooLargeError(bytes.byteLength, this.capBytes);
80
+ const cap = this.cap();
81
+ if (cap !== undefined && bytes.byteLength > cap.capBytes)
82
+ throw new BlobTooLargeError(bytes.byteLength, cap);
40
83
  if (this.dialect === "tidb") {
41
84
  await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
42
85
  }
@@ -51,11 +51,12 @@ export interface PendingCheckpoint extends ParkRowFacts {
51
51
  * consumer to treat the key's PRESENCE as "there is a binding" and echo `null` back into /decide. */
52
52
  boundCallId?: string;
53
53
  boundInputHash?: string;
54
- /** 🔴 [ref]③ (additive): the checkpoint's gate kind — the operator queue mixes tool approvals with
54
+ /** 🔴 [ref]③: the checkpoint's gate kind — the operator queue mixes tool approvals with
55
55
  * plan_review / resource_limit / task_done parks (all of which carry `toolName: null`), so a durable-recovery
56
- * consumer needs this to ROUTE a row to the right wire (`/decide` vs `/plan_review` vs `/resume`). Absent
57
- * only for a legacy SQL row suspended before the `gate_kind` column existed. */
58
- gateKind?: CheckpointGate["kind"];
56
+ * consumer needs this to ROUTE a row to the right wire (`/decide` vs `/plan_review` vs `/resume`). ALWAYS present
57
+ * (S-1681, 7.132.0): every park stamps it, the one-time migration M21 filled any row without it from the row's gate,
58
+ * and a SQL row that still carries none is not listed by `listPending` (counted: `server.approvals.queue-row-gate-kind-missing`; no decide route binds it either). */
59
+ gateKind: CheckpointGate["kind"];
59
60
  /** The suspended run's taskId (task_active join) — the key to the run/trace ("view task context"). */
60
61
  taskId: string | null;
61
62
  /** The pending tool call's args (post-hook), redacted + bounded — for tool_approval the write payload, for a
@@ -278,6 +279,15 @@ export declare function assertCheckpointColumnSchema(query: (sql: string) => Pro
278
279
  export declare function boundedRuleOffers(raw: unknown, opts?: {
279
280
  countRedactions?: boolean;
280
281
  }): RuleOfferProjection[] | undefined;
282
+ /**
283
+ * S-1681 —— 运维队列行的门型:**两只孪生同一只判官**(SQL 孪生读 `gate_kind` 列 —— put 时从同一只门反范式下来的那一格,每次停驻都盖,一次性迁移 M21 把没有它的
284
+ * 存量行从门子树补齐;local 孪生读快照上的门 `cp.gate.kind`)。
285
+ * 读到的值是 NULL / 缺席 / 不是 core 登记的门型 ⇒ `undefined`:这一行**不列**(响亮:计一次 `server.approvals.queue-row-gate-kind-missing`)—— 与每一条决断口同一个读法:
286
+ * 按门型绑定取行的读口对这种行本来就不命中(`pendingRowQuery` 的 `gate_kind IN (…)`,证不出门型就不当它是这道门),队列不再把一行谁也决不了的行投出去,
287
+ * 也不按缺键投(消费方按 `gateKind` 路由、缺键即 fail-closed)。不整只拒:一行坏行不许让整个租户的队列(列表 500、SSE 心跳照常而队列永远停在旧帧)消失。
288
+ * 按构造不可达(写口恒盖、M21 补齐);计数非零 = 写腿或手改出了错,按 `SELECT … WHERE status = 'pending' AND gate_kind IS NULL` 查那一行。
289
+ */
290
+ export declare function queueRowGateKind(kind: unknown): CheckpointGate["kind"] | undefined;
281
291
  /** Redact + size-bound a pending tool call's args for persistence/serving. JSON-roundtrips so the stored shape
282
292
  * matches what the operator queue serves; oversized payloads collapse to a marker rather than bloating the row.
283
293
  * Exported for the LOCAL twin (local-checkpoint-store.ts) so the operator-queue projection can't drift. */
@@ -1,13 +1,13 @@
1
1
  import { parkedApprovalCard, parkedFacetOf } from "../approval-content-kind.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { APPROVAL_GATE_KINDS_SQL_IN, denySweepOwns } from "../tool-approval.js";
4
- import { CheckpointError, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, checkpointVersionOf, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
4
+ import { CheckpointError, isGateKind, MAX_RULE_TEXT_CHARS, validatePendingSteer, appendPendingSteer, checkpointVersionOf, winnerFromOutcome, summarizeCheckpoint, MAX_SUPPORTED_CHECKPOINT_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
5
5
  import { redactDeep, redactSecrets } from "../trace/redact.js";
6
6
  import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, RuleOfferRawSchema, backfillLegacyBatchMemberKind, parkRowFacts, screenUncoveredDetailSeat } from "../approval-card.js";
7
7
  import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
10
- import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
10
+ import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf } from "./sql-errors.js";
11
11
  import { MANAGED_RETENTION } from "./retention-store-sql.js";
12
12
  import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
13
13
  const REAP_CLASSIFY_BATCH = 200;
@@ -196,6 +196,12 @@ function readParkGateCell(cell) {
196
196
  }
197
197
  }
198
198
  const readParkGateMemberBody = createObjectColumnReader();
199
+ export function queueRowGateKind(kind) {
200
+ if (isGateKind(kind))
201
+ return kind;
202
+ recordFailOpen("server.approvals.queue-row-gate-kind-missing", `gate_kind=${kind === null || kind === undefined ? "NULL" : redactSecrets(String(kind)).slice(0, 40)}`);
203
+ return undefined;
204
+ }
199
205
  function parkedGateOfRow(r) {
200
206
  return { kind: r.gate_kind ?? undefined, approvalCard: readParkGateCell(r[PARK_ROW_GATE.as])?.approvalCard };
201
207
  }
@@ -418,7 +424,7 @@ export class SqlCheckpointStore {
418
424
  if (this.isDupKey(e)) {
419
425
  throw new CheckpointError("checkpoint.already_exists", `checkpoint token already exists`);
420
426
  }
421
- throw e;
427
+ throw rowTooLargeOrSelf(this.db.dialect, e, `the checkpoint row for session ${JSON.stringify(cp.sessionId)}`);
422
428
  }
423
429
  }
424
430
  async get(token) {
@@ -582,31 +588,33 @@ export class SqlCheckpointStore {
582
588
  const { rows } = scope
583
589
  ? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
584
590
  : await this.db.query(`${base} ORDER BY c.created_at_ms ASC`);
585
- const out = rows.map((r) => {
591
+ const out = rows.flatMap((r) => {
592
+ const gateKind = queueRowGateKind(r.gate_kind);
593
+ if (gateKind === undefined)
594
+ return [];
586
595
  const toolName = r.tool_name ?? null;
587
596
  const toolCallId = r.tool_call_id ?? null;
588
597
  const boundInputHash = r.bound_input_hash ?? null;
589
- const gateKind = r.gate_kind ?? null;
590
598
  const ruleOffers = readRuleOffersCell(r.rule_suggestions);
591
599
  const approvalCard = parkedApprovalCard(parkedGateOfRow(r));
592
- return {
593
- sessionId: String(r.session_id),
594
- scope: String(r.scope),
595
- toolName,
596
- toolCallId,
597
- ...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
598
- ...(boundInputHash !== null ? { boundInputHash } : {}),
599
- ...(gateKind !== null ? { gateKind } : {}),
600
- taskId: r.task_id ?? null,
601
- input: refusing("a tool input the operator would be approving unseen", () => decodeToolInputColumn(r.tool_input)) ?? null,
602
- createdAt: Number(r.created_at_ms),
603
- deadline: r.deadline == null ? null : Number(r.deadline),
604
- riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
605
- ...(ruleOffers !== undefined ? { ruleOffers } : {}),
606
- ...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
607
- ...(approvalCard !== undefined ? { approvalCard } : {}),
608
- ...readParkRowFactsCell(r[PARK_ROW_PENDING_ACTION.as]),
609
- };
600
+ return [{
601
+ sessionId: String(r.session_id),
602
+ scope: String(r.scope),
603
+ toolName,
604
+ toolCallId,
605
+ ...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
606
+ ...(boundInputHash !== null ? { boundInputHash } : {}),
607
+ gateKind,
608
+ taskId: r.task_id ?? null,
609
+ input: refusing("a tool input the operator would be approving unseen", () => decodeToolInputColumn(r.tool_input)) ?? null,
610
+ createdAt: Number(r.created_at_ms),
611
+ deadline: r.deadline == null ? null : Number(r.deadline),
612
+ riskDescriptor: readRiskDescriptorCell(r.risk_descriptor),
613
+ ...(ruleOffers !== undefined ? { ruleOffers } : {}),
614
+ ...(Number(r.has_bidi_controls) === 1 ? { hasBidiControls: true } : {}),
615
+ ...(approvalCard !== undefined ? { approvalCard } : {}),
616
+ ...readParkRowFactsCell(r[PARK_ROW_PENDING_ACTION.as]),
617
+ }];
610
618
  });
611
619
  return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
612
620
  }
@@ -716,9 +724,14 @@ export class SqlCheckpointStore {
716
724
  async putCtx(sessionId, ctx) {
717
725
  const text = this.json(ctx, "checkpoint ctx");
718
726
  assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (write leg)`);
719
- await this.db.query(this.q("INSERT INTO checkpoint_ctx (session_id, ctx, updated_at_ms) VALUES (?,?,?) " +
720
- "ON DUPLICATE KEY UPDATE ctx = VALUES(ctx), updated_at_ms = VALUES(updated_at_ms)", "INSERT INTO checkpoint_ctx (session_id, ctx, updated_at_ms) VALUES ($1,$2::jsonb,$3) " +
721
- "ON CONFLICT (session_id) DO UPDATE SET ctx = EXCLUDED.ctx, updated_at_ms = EXCLUDED.updated_at_ms"), [sessionId, text, Date.now()]);
727
+ try {
728
+ await this.db.query(this.q("INSERT INTO checkpoint_ctx (session_id, ctx, updated_at_ms) VALUES (?,?,?) " +
729
+ "ON DUPLICATE KEY UPDATE ctx = VALUES(ctx), updated_at_ms = VALUES(updated_at_ms)", "INSERT INTO checkpoint_ctx (session_id, ctx, updated_at_ms) VALUES ($1,$2::jsonb,$3) " +
730
+ "ON CONFLICT (session_id) DO UPDATE SET ctx = EXCLUDED.ctx, updated_at_ms = EXCLUDED.updated_at_ms"), [sessionId, text, Date.now()]);
731
+ }
732
+ catch (e) {
733
+ throw rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)}`);
734
+ }
722
735
  }
723
736
  async getCtx(sessionId) {
724
737
  const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
@@ -1,3 +1,14 @@
1
+ /** The longest the kill script itself may take beyond one control-plane wall: its re-check rounds (4 pauses of one second, plus slack). */
2
+ export declare const KILL_ALL_RECHECK_MS = 5000;
3
+ /**
4
+ * The pod-side "end all work" script: SIGKILL every LIVE process of the pod except pid 1, the container's keeper (`sleep infinity` — as pid 1, or as the child
5
+ * of the `sh` that is pid 1 when the pod's start command runs a hook first: killing it would end the container and with it the exec the snapshot needs) and this
6
+ * script's own shell — what a run-end snapshot needs of a command still running when its run is over (the pod is deleted right after; killing the exec socket would
7
+ * not stop the process, see the adapter's header). Exits 0 only when nothing it should have killed is alive any more: a killed process is re-checked (up to 5 rounds,
8
+ * one second apart, only while something is still alive) by its `State:` line — a zombie (`Z`, which a pid-1 `sleep` never reaps) is dead; a process that outlives the
9
+ * rounds makes the script exit 1, so the caller never reads a silent kill failure as a still tree. `procDir` is `/proc` (a test points it at a fake tree).
10
+ */
11
+ export declare function buildKillAllScript(procDir?: string): string;
1
12
  /** The detached runner (base64'd into the pod, run under `setsid`). Self-reports pgid; streams output to out/err. */
2
13
  export declare function buildBgRunnerScript(dir: string): string;
3
14
  /** Short foreground launcher: writes cmd.sh + runner.sh, then `setsid`s the runner detached and returns immediately. */
@@ -1,4 +1,26 @@
1
1
  import { shellQuote } from "./remote-shell.js";
2
+ export const KILL_ALL_RECHECK_MS = 5_000;
3
+ export function buildKillAllScript(procDir = "/proc") {
4
+ return [
5
+ 'alive() { st=; while read -r k v; do [ "$k" = "State:" ] && st=${v%% *}; done 2>/dev/null < "$1/status"; [ -n "$st" ] && [ "$st" != Z ] && [ "$st" != X ]; }',
6
+ `keeper() { [ "$(tr '\\0' ' ' 2>/dev/null < "$1/cmdline")" = "sleep infinity " ]; }`,
7
+ 'spare() { pid=${1##*/}; [ "$pid" = 1 ] || [ "$pid" = "$$" ] || keeper "$1"; }',
8
+ "round=0",
9
+ "while :; do",
10
+ "left=0",
11
+ `for p in ${shellQuote(procDir)}/[0-9]*; do`,
12
+ 'spare "$p" && continue',
13
+ 'alive "$p" || continue',
14
+ "left=1",
15
+ 'kill -9 "${p##*/}" 2>/dev/null',
16
+ "done",
17
+ '[ "$left" = 0 ] && exit 0',
18
+ "round=$((round + 1))",
19
+ '[ "$round" -ge 5 ] && exit 1',
20
+ "sleep 1",
21
+ "done",
22
+ ].join("\n");
23
+ }
2
24
  export function buildBgRunnerScript(dir) {
3
25
  const q = (name) => shellQuote(`${dir}/${name}`);
4
26
  return [
@@ -6,7 +6,7 @@ import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "no
6
6
  import { join } from "node:path";
7
7
  import { denySweepOwns } from "../tool-approval.js";
8
8
  import { FileCheckpointStore, atomicWriteFile, executionVerdict, sanitizePathComponent, } from "@sema-agent/core";
9
- import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, pendingActionToolCallId, pendingRowPrecedes, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
9
+ import { approvalDecisionOfWinner, approvalPayloadFingerprint, boundedRuleOffers, boundedToolInput, pendingActionToolCallId, pendingRowPrecedes, queueRowGateKind, TERMINAL_BACKSTOP_MS, TERMINAL_GRACE_MS } from "./checkpoint-store-sql.js";
10
10
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
11
11
  import { createZodColumnReader, decodeJsonColumn, foldJsonColumn, readStringArrayColumn } from "./sql-json-column.js";
12
12
  import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
@@ -165,6 +165,9 @@ export class LocalCheckpointStore {
165
165
  for (const { cp } of await this.pendings(scope)) {
166
166
  const pa = cp.pendingAction;
167
167
  const gate = cp.gate;
168
+ const gateKind = queueRowGateKind(gate?.kind);
169
+ if (gateKind === undefined)
170
+ continue;
168
171
  const toolCallId = pa?.toolCallId ?? null;
169
172
  rows.push({
170
173
  sessionId: cp.sessionId,
@@ -173,7 +176,7 @@ export class LocalCheckpointStore {
173
176
  toolCallId,
174
177
  ...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
175
178
  ...(pa?.boundInputHash !== undefined ? { boundInputHash: pa.boundInputHash } : {}),
176
- ...(gate?.kind !== undefined ? { gateKind: gate.kind } : {}),
179
+ gateKind,
177
180
  taskId: (await this.opts.getActiveTaskId?.(cp.sessionId)) ?? null,
178
181
  input: boundedToolInput(pa?.args),
179
182
  createdAt: cp.createdAt,
@@ -12,7 +12,7 @@ import { carriedIncarnation, mintSessionIncarnation, sessionFaceFromRow } from "
12
12
  import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
13
13
  import { noteReplayInSessionOpenScope, openSessionForItself } from "../session-records-skipped-notice.js";
14
14
  import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
15
- import { buildObjectivePreview } from "../user-authored-text.js";
15
+ import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
16
16
  import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, } from "../session-sync-kernel.js";
17
17
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
18
18
  const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
@@ -21,7 +21,7 @@ function userText(entry) {
21
21
  if (entry.type !== "message")
22
22
  return null;
23
23
  const msg = entry.message;
24
- if (msg.role !== "user")
24
+ if (msg.role !== "user" || isAttachmentFrame(msg))
25
25
  return null;
26
26
  const c = msg.content;
27
27
  if (typeof c === "string")
@@ -642,7 +642,7 @@ export class LocalSessionStore {
642
642
  const current = await this.metaOf(sessionId);
643
643
  const prior = current === undefined ? undefined : { owner: current.owner ?? null, incarnation: current.incarnation };
644
644
  await this.deleteSessionHeld(sessionId, owner);
645
- await this.importEntriesAs(sessionId, owner, entries, carriedIncarnation(prior, owner));
645
+ await this.importEntriesAs(sessionId, owner, entries, carriedIncarnation(sessionId, prior, owner));
646
646
  }
647
647
  beginImportStaging(realSessionId, token) {
648
648
  const h = new LocalStagingHandle(this, realSessionId, stagingIdFor(realSessionId, token));