@sema-agent/server 7.139.0 → 7.140.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 (194) hide show
  1. package/MIGRATION.md +181 -0
  2. package/USAGE.md +19 -13
  3. package/dist/adoption/runner.js +1 -1
  4. package/dist/adoption/sql.js +1 -1
  5. package/dist/approval-card.d.ts +6 -6
  6. package/dist/bench/s1/live-deps.js +3 -2
  7. package/dist/bench/s1/row.d.ts +5 -2
  8. package/dist/bench/s1/row.js +2 -22
  9. package/dist/boot/adoption.js +1 -1
  10. package/dist/boot/execution-env.js +12 -10
  11. package/dist/boot/execution-lane.d.ts +8 -0
  12. package/dist/boot/execution-lane.js +6 -1
  13. package/dist/boot/memory-boundary.d.ts +14 -51
  14. package/dist/boot/memory-boundary.js +10 -20
  15. package/dist/boot/memory-consolidation.d.ts +15 -11
  16. package/dist/boot/memory-consolidation.js +23 -12
  17. package/dist/boot/resolve-spec.d.ts +3 -5
  18. package/dist/boot/resolve-spec.js +2 -4
  19. package/dist/boot/runner-deps.d.ts +3 -3
  20. package/dist/boot/runner-deps.js +1 -0
  21. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  22. package/dist/boot/stage-03-store-backend.js +2 -3
  23. package/dist/boot/stage-04-stores.d.ts +2 -6
  24. package/dist/boot/stage-04-stores.js +5 -5
  25. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  26. package/dist/boot/stage-06-runners.d.ts +2 -6
  27. package/dist/boot/stage-06-runners.js +2 -2
  28. package/dist/boot/stage-07-capability-layer.d.ts +2 -6
  29. package/dist/boot/stage-08-reapers.d.ts +2 -6
  30. package/dist/boot/stage-09-leader.d.ts +2 -6
  31. package/dist/boot/stage-10-http-server.d.ts +2 -6
  32. package/dist/boot/stage-10-http-server.js +5 -4
  33. package/dist/boot/stores.d.ts +3 -6
  34. package/dist/boot/stores.js +27 -40
  35. package/dist/boot/task-list-lane.d.ts +1 -1
  36. package/dist/boot/task-list-lane.js +1 -1
  37. package/dist/boot/web-admin.js +35 -9
  38. package/dist/capabilities/execution-lane.d.ts +2 -2
  39. package/dist/config-catalog.js +2 -1
  40. package/dist/config-provider.d.ts +14 -13
  41. package/dist/config-provider.js +24 -18
  42. package/dist/config-types.d.ts +11 -2
  43. package/dist/config.js +2 -1
  44. package/dist/delegated-session-owner.d.ts +2 -1
  45. package/dist/delegated-session-owner.js +1 -1
  46. package/dist/deployment-shape.d.ts +9 -8
  47. package/dist/deployment-shape.js +4 -4
  48. package/dist/execution-lane-caps.d.ts +97 -106
  49. package/dist/execution-lane-caps.js +67 -83
  50. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  51. package/dist/http/memory-compliance-failure-reply.js +5 -0
  52. package/dist/http/memory-layer-reply.d.ts +30 -0
  53. package/dist/http/memory-layer-reply.js +24 -0
  54. package/dist/http/resume-legs.js +13 -16
  55. package/dist/http/retired-service-deps.d.ts +1 -1
  56. package/dist/http/retired-service-deps.js +1 -1
  57. package/dist/http/routes/adoption.js +1 -1
  58. package/dist/http/routes/memory-bundle.js +3 -0
  59. package/dist/http/routes/memory-compliance.js +8 -1
  60. package/dist/http/routes/memory-consolidation.js +12 -1
  61. package/dist/http/routes/memory-origin.js +14 -2
  62. package/dist/http/routes/memory-policy.js +5 -0
  63. package/dist/http/routes/sessions.js +28 -11
  64. package/dist/http/routes/tasks.js +4 -2
  65. package/dist/http/server.d.ts +5 -25
  66. package/dist/http/server.js +2 -2
  67. package/dist/identity-source.d.ts +1 -1
  68. package/dist/identity-source.js +1 -1
  69. package/dist/lane-home-probe.d.ts +68 -42
  70. package/dist/lane-home-probe.js +53 -30
  71. package/dist/leader/wire.js +5 -4
  72. package/dist/memory-bundle-engine.d.ts +16 -9
  73. package/dist/memory-bundle-engine.js +29 -6
  74. package/dist/memory-operator-faces.d.ts +85 -30
  75. package/dist/memory-operator-faces.js +136 -30
  76. package/dist/memory-project-layer.d.ts +3 -21
  77. package/dist/memory-project-layer.js +11 -8
  78. package/dist/memory-scope-stores.d.ts +73 -0
  79. package/dist/memory-scope-stores.js +86 -0
  80. package/dist/memory-scope.d.ts +29 -5
  81. package/dist/memory-scope.js +16 -4
  82. package/dist/memory-sync-client.d.ts +20 -7
  83. package/dist/memory-sync-client.js +31 -13
  84. package/dist/plugins/adoption-log-sql.js +4 -4
  85. package/dist/plugins/approval-ask-store-sql.js +14 -14
  86. package/dist/plugins/approval-nonce-store.js +4 -4
  87. package/dist/plugins/blob-backend.d.ts +4 -4
  88. package/dist/plugins/blob-backend.js +7 -7
  89. package/dist/plugins/breaker-state-sql.js +2 -2
  90. package/dist/plugins/checkpoint-index-columns.js +2 -2
  91. package/dist/plugins/checkpoint-store-sql.js +16 -16
  92. package/dist/plugins/device-store-sql.js +19 -19
  93. package/dist/plugins/ensure-index.d.ts +1 -2
  94. package/dist/plugins/execution-placement-sql.d.ts +8 -8
  95. package/dist/plugins/execution-placement-sql.js +8 -8
  96. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  97. package/dist/plugins/file-history-store-sql.js +4 -4
  98. package/dist/plugins/image-bake-store-sql.js +9 -9
  99. package/dist/plugins/image-index-sql.js +11 -11
  100. package/dist/plugins/leader-run-store-sql.js +4 -4
  101. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  102. package/dist/plugins/mailbox-store-sql.js +4 -4
  103. package/dist/plugins/memory-engine-tidb.js +5 -5
  104. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  105. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  106. package/dist/plugins/one-time-migrations.d.ts +10 -9
  107. package/dist/plugins/one-time-migrations.js +36 -36
  108. package/dist/plugins/org-delivery-quota-store.js +2 -2
  109. package/dist/plugins/outcome-ledger-sql.js +2 -2
  110. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  111. package/dist/plugins/permission-rule-store-sql.js +16 -16
  112. package/dist/plugins/remote-env-adb.d.ts +7 -24
  113. package/dist/plugins/remote-env-adb.js +22 -5
  114. package/dist/plugins/remote-env-device.d.ts +2 -28
  115. package/dist/plugins/remote-env-device.js +2 -1
  116. package/dist/plugins/remote-env-e2b.d.ts +18 -26
  117. package/dist/plugins/remote-env-e2b.js +51 -4
  118. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  119. package/dist/plugins/remote-env-file-error.js +8 -2
  120. package/dist/plugins/remote-env-host.d.ts +19 -35
  121. package/dist/plugins/remote-env-host.js +26 -3
  122. package/dist/plugins/remote-env-k8s.d.ts +3 -14
  123. package/dist/plugins/remote-env-k8s.js +3 -2
  124. package/dist/plugins/remote-env-local-docker.d.ts +4 -15
  125. package/dist/plugins/remote-env-local-docker.js +5 -4
  126. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  127. package/dist/plugins/remote-env-ssh.js +4 -3
  128. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  129. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  130. package/dist/plugins/retention-lane-store-sql.js +2 -2
  131. package/dist/plugins/retention-store-sql.d.ts +1 -1
  132. package/dist/plugins/retention-store-sql.js +6 -6
  133. package/dist/plugins/run-store-sql.js +15 -15
  134. package/dist/plugins/schema-consistency.d.ts +6 -6
  135. package/dist/plugins/schema-consistency.js +1 -1
  136. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  137. package/dist/plugins/schema-ensure-legs.js +2 -2
  138. package/dist/plugins/schema-live-columns.d.ts +1 -1
  139. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  140. package/dist/plugins/session-rules-column.d.ts +1 -1
  141. package/dist/plugins/session-rules-column.js +1 -1
  142. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  143. package/dist/plugins/session-workspace-cell.js +1 -1
  144. package/dist/plugins/shared-memory-store-sql.js +2 -2
  145. package/dist/plugins/sql-driver.d.ts +10 -5
  146. package/dist/plugins/sql-driver.js +6 -6
  147. package/dist/plugins/sql-errors.js +1 -1
  148. package/dist/plugins/store-backend.d.ts +4 -6
  149. package/dist/plugins/store-backend.js +6 -6
  150. package/dist/plugins/task-list-store-sql.js +11 -11
  151. package/dist/plugins/tidb-pool.js +5 -5
  152. package/dist/plugins/tidb-session-storage.js +4 -4
  153. package/dist/plugins/tidb-session-store.js +13 -13
  154. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  155. package/dist/plugins/tool-result-store-sql.js +3 -3
  156. package/dist/plugins/user-input-ledger-sql.js +2 -2
  157. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  158. package/dist/plugins/workflow-run-store-sql.js +19 -19
  159. package/dist/plugins/write-behind-counter.d.ts +2 -3
  160. package/dist/plugins/write-behind-counter.js +5 -5
  161. package/dist/resource-suspend.d.ts +14 -12
  162. package/dist/resource-suspend.js +1 -1
  163. package/dist/resource-window.d.ts +4 -3
  164. package/dist/run-local.js +14 -19
  165. package/dist/runs.d.ts +8 -4
  166. package/dist/runs.js +49 -65
  167. package/dist/session-incarnation.d.ts +1 -1
  168. package/dist/session-incarnation.js +1 -1
  169. package/dist/session-policy-wire.d.ts +1 -1
  170. package/dist/sql-engine-posture.d.ts +1 -1
  171. package/dist/sql-engine-posture.js +1 -1
  172. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  173. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  174. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  175. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  176. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  177. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  178. package/dist/task-a2a.js +5 -6
  179. package/dist/task-mcp.d.ts +20 -7
  180. package/dist/task-mcp.js +17 -9
  181. package/dist/terminal.d.ts +27 -0
  182. package/dist/terminal.js +6 -0
  183. package/dist/trace/redact.d.ts +3 -1
  184. package/dist/trace/redact.js +80 -28
  185. package/dist/trace/self-minted-wire.d.ts +58 -0
  186. package/dist/trace/self-minted-wire.js +16 -0
  187. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  188. package/dist/web-admin-client/candidate-gate.js +1 -1
  189. package/dist/web-admin-client/http-client.d.ts +5 -4
  190. package/dist/web-admin-client/http-client.js +2 -2
  191. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  192. package/dist/web-admin-client/read-warnings.js +5 -9
  193. package/package.json +2 -2
  194. package/wire-changes/server@7.140.0.json +184 -0
@@ -24,7 +24,7 @@ export async function ensureSchema(pool) {
24
24
  await ensureIndexes((t, p) => pool.query(t, p), "pg", TOOL_RESULT_INDEXES);
25
25
  }
26
26
  export async function assertToolResultProvenanceSchema(query, dialect) {
27
- const widthRow = await query(dialect === "tidb"
27
+ const widthRow = await query(dialect === "mysql"
28
28
  ? "SELECT CHARACTER_MAXIMUM_LENGTH AS len FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'tool_result' AND column_name = 'ref'"
29
29
  : "SELECT character_maximum_length AS len FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = 'tool_result' AND column_name = 'ref'").catch(() => ({ rows: [] }));
30
30
  const width = Number(widthRow.rows[0]?.len ?? NaN);
@@ -62,8 +62,8 @@ export class SqlToolResultStore {
62
62
  this.db = db;
63
63
  this.retention = opts.retention ?? UNMANAGED_RETENTION;
64
64
  }
65
- q(tidb, pg) {
66
- return this.db.dialect === "tidb" ? tidb : pg;
65
+ q(mysql, pg) {
66
+ return this.db.dialect === "mysql" ? mysql : pg;
67
67
  }
68
68
  isUnsafeRef(ref) {
69
69
  try {
@@ -69,8 +69,8 @@ export class SqlUserInputLedgerStore {
69
69
  constructor(db) {
70
70
  this.db = db;
71
71
  }
72
- q(tidb, pg) {
73
- return this.db.dialect === "tidb" ? tidb : pg;
72
+ q(mysql, pg) {
73
+ return this.db.dialect === "mysql" ? mysql : pg;
74
74
  }
75
75
  async accept(a) {
76
76
  try {
@@ -15,8 +15,8 @@ export class SqlWorkflowJournalStore {
15
15
  this.db = db;
16
16
  this.resumeClaimTtlMs = opts?.resumeClaimTtlMs ?? DEFAULT_RESUME_CLAIM_TTL_MS;
17
17
  }
18
- q(tidb, pg) {
19
- return this.db.dialect === "tidb" ? tidb : pg;
18
+ q(mysql, pg) {
19
+ return this.db.dialect === "mysql" ? mysql : pg;
20
20
  }
21
21
  async append(runId, scope, entry) {
22
22
  assertJournalEntryFits(entry);
@@ -35,7 +35,7 @@ export class SqlWorkflowJournalStore {
35
35
  }
36
36
  }
37
37
  async load(runId, scope) {
38
- const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("tidb", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
38
+ const { rows } = await this.db.query(this.q(`SELECT call_key, ${jsonColumnText("mysql", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC`, `SELECT call_key, ${jsonColumnText("pg", "result")} FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC`), [runId, scope]);
39
39
  return rows.map((r) => {
40
40
  const callKey = String(r.call_key);
41
41
  return journalEntryOfCell(runId, callKey, r.result, `workflow_journal.result for run ${runId} entry ${callKey}`);
@@ -69,10 +69,10 @@ export class SqlWorkflowJournalStore {
69
69
  return row !== undefined && row.ordinal === ordinal && row.resultJson !== null ? row.resultJson : undefined;
70
70
  }
71
71
  async readRows(runId, scope, from, limit, inlineBytes) {
72
- const params = this.db.dialect === "tidb"
72
+ const params = this.db.dialect === "mysql"
73
73
  ? [inlineBytes, runId, scope, from, limit]
74
74
  : [runId, scope, inlineBytes, from, limit];
75
- const { rows } = await this.db.query(this.q(`SELECT ordinal, call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("tidb", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? AND ordinal >= ? ORDER BY ordinal ASC LIMIT ?`, `SELECT ordinal, call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 AND ordinal >= $4 ORDER BY ordinal ASC LIMIT $5`), params);
75
+ const { rows } = await this.db.query(this.q(`SELECT ordinal, call_key, CASE WHEN LENGTH(result) > ? THEN NULL ELSE ${jsonColumnTextExpr("mysql", "result")} END AS result_json, LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = ? AND scope = ? AND ordinal >= ? ORDER BY ordinal ASC LIMIT ?`, `SELECT ordinal, call_key, CASE WHEN OCTET_LENGTH(result) > $3 THEN NULL ELSE ${jsonColumnTextExpr("pg", "result")} END AS result_json, OCTET_LENGTH(result) AS result_bytes FROM workflow_journal JOIN workflow_journal_owner USING (run_id, incarnation) WHERE run_id = $1 AND scope = $2 AND ordinal >= $4 ORDER BY ordinal ASC LIMIT $5`), params);
76
76
  return rows.map((r) => ({ ordinal: Number(r.ordinal), callKey: String(r.call_key), resultJson: r.result_json === null ? null : String(r.result_json), resultBytes: Number(r.result_bytes) }));
77
77
  }
78
78
  async deleteByRun(runId) {
@@ -57,8 +57,8 @@ export class SqlWorkflowRunStore {
57
57
  this.db = db;
58
58
  this.onWarn = onWarn;
59
59
  }
60
- q(tidb, pg) {
61
- return this.db.dialect === "tidb" ? tidb : pg;
60
+ q(mysql, pg) {
61
+ return this.db.dialect === "mysql" ? mysql : pg;
62
62
  }
63
63
  async put(id, run) {
64
64
  const stored = { ...withRedactedError(run), id, rev: run.rev ?? 0 };
@@ -73,7 +73,7 @@ export class SqlWorkflowRunStore {
73
73
  }
74
74
  }
75
75
  async get(id) {
76
- const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "run")}, rev FROM workflow_run WHERE id = ?`, `SELECT ${jsonColumnText("pg", "run")}, rev FROM workflow_run WHERE id = $1`), [id]);
76
+ const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "run")}, rev FROM workflow_run WHERE id = ?`, `SELECT ${jsonColumnText("pg", "run")}, rev FROM workflow_run WHERE id = $1`), [id]);
77
77
  const r = rows[0];
78
78
  if (!r)
79
79
  return null;
@@ -100,7 +100,7 @@ export class SqlWorkflowRunStore {
100
100
  const params = [scope];
101
101
  let where;
102
102
  let limit = "";
103
- if (this.db.dialect === "tidb") {
103
+ if (this.db.dialect === "mysql") {
104
104
  where = "scope = ?";
105
105
  if (opts?.status !== undefined) {
106
106
  where += " AND status = ?";
@@ -160,9 +160,9 @@ export class SqlWorkflowRunStore {
160
160
  }
161
161
  if (doomed.length === 0)
162
162
  return 0;
163
- const res = await this.db.query(this.q(`DELETE FROM workflow_run WHERE scope = ? AND id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
164
- await this.db.query(this.q(`DELETE FROM workflow_agent_session WHERE scope = ? AND run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_agent_session WHERE scope = $1 AND run_id = ANY($2::text[])"), this.db.dialect === "tidb" ? [scope, ...doomed] : [scope, doomed]);
165
- await this.db.query(this.q(`DELETE FROM workflow_park_decision WHERE run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_park_decision WHERE run_id = ANY($1::text[])"), this.db.dialect === "tidb" ? [...doomed] : [doomed]);
163
+ const res = await this.db.query(this.q(`DELETE FROM workflow_run WHERE scope = ? AND id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])"), this.db.dialect === "mysql" ? [scope, ...doomed] : [scope, doomed]);
164
+ await this.db.query(this.q(`DELETE FROM workflow_agent_session WHERE scope = ? AND run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_agent_session WHERE scope = $1 AND run_id = ANY($2::text[])"), this.db.dialect === "mysql" ? [scope, ...doomed] : [scope, doomed]);
165
+ await this.db.query(this.q(`DELETE FROM workflow_park_decision WHERE run_id IN (${doomed.map(() => "?").join(",")})`, "DELETE FROM workflow_park_decision WHERE run_id = ANY($1::text[])"), this.db.dialect === "mysql" ? [...doomed] : [doomed]);
166
166
  return res.affected;
167
167
  }
168
168
  }
@@ -175,8 +175,8 @@ export class SqlWorkflowCompletionInbox {
175
175
  this.db = db;
176
176
  this.onWarn = onWarn;
177
177
  }
178
- q(tidb, pg) {
179
- return this.db.dialect === "tidb" ? tidb : pg;
178
+ q(mysql, pg) {
179
+ return this.db.dialect === "mysql" ? mysql : pg;
180
180
  }
181
181
  async sweepFences(now) {
182
182
  await this.db.query(this.q("DELETE FROM workflow_inbox_fence WHERE until_ms < ?", "DELETE FROM workflow_inbox_fence WHERE until_ms < $1"), [now]);
@@ -213,7 +213,7 @@ export class SqlWorkflowCompletionInbox {
213
213
  }
214
214
  const over = await this.db.query(this.q("SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = ? ORDER BY seq DESC LIMIT 100000 OFFSET ?", "SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = $1 ORDER BY seq DESC OFFSET $2"), [entry.sessionId, MAX_PENDING_PER_SESSION]);
215
215
  if (over.rows.length > 0) {
216
- await this.db.query(this.q(`DELETE FROM workflow_completion_inbox WHERE session_id = ? AND seq IN (${over.rows.map(() => "?").join(",")})`, "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])"), this.db.dialect === "tidb" ? [entry.sessionId, ...over.rows.map((r) => r.seq)] : [entry.sessionId, over.rows.map((r) => r.seq)]);
216
+ await this.db.query(this.q(`DELETE FROM workflow_completion_inbox WHERE session_id = ? AND seq IN (${over.rows.map(() => "?").join(",")})`, "DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])"), this.db.dialect === "mysql" ? [entry.sessionId, ...over.rows.map((r) => r.seq)] : [entry.sessionId, over.rows.map((r) => r.seq)]);
217
217
  for (const r of over.rows)
218
218
  this.onWarn?.("workflow_completion_inbox_overflow", { sessionId: entry.sessionId, droppedRunId: String(r.run_id) });
219
219
  }
@@ -243,7 +243,7 @@ export class SqlWorkflowCompletionInbox {
243
243
  await this.sweepFences(now);
244
244
  await this.db.query(this.q("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES (?,?,?,?,?,?) " +
245
245
  "ON DUPLICATE KEY UPDATE owner = VALUES(owner), owner_known = VALUES(owner_known), until_ms = VALUES(until_ms)", "INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,'',$3,$4,$5) " +
246
- "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms"), this.db.dialect === "tidb"
246
+ "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms"), this.db.dialect === "mysql"
247
247
  ? [sessionId, KIND_PURGE, "", purgedOwner ?? null, purgedOwner !== undefined ? 1 : 0, now + PURGE_FENCE_MS]
248
248
  : [sessionId, KIND_PURGE, purgedOwner ?? null, purgedOwner !== undefined ? 1 : 0, now + PURGE_FENCE_MS]);
249
249
  await this.db.query(this.q("DELETE FROM workflow_completion_inbox WHERE session_id = ?", "DELETE FROM workflow_completion_inbox WHERE session_id = $1"), [sessionId]);
@@ -263,8 +263,8 @@ export class SqlWorkflowNotifyJournalStore {
263
263
  constructor(db) {
264
264
  this.db = db;
265
265
  }
266
- q(tidb, pg) {
267
- return this.db.dialect === "tidb" ? tidb : pg;
266
+ q(mysql, pg) {
267
+ return this.db.dialect === "mysql" ? mysql : pg;
268
268
  }
269
269
  async record(input) {
270
270
  await this.db.query(this.q("INSERT IGNORE INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at_ms) VALUES (?,?,0,?,?,?)", "INSERT INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at_ms) VALUES ($1,$2,0,$3,$4,$5) ON CONFLICT (run_id) DO NOTHING"), [input.runId, input.scope, input.sourceTaskId ?? null, input.principal ?? null, input.createdAt]);
@@ -308,8 +308,8 @@ export class SqlWorkflowAgentSessionIndex {
308
308
  constructor(db) {
309
309
  this.db = db;
310
310
  }
311
- q(tidb, pg) {
312
- return this.db.dialect === "tidb" ? tidb : pg;
311
+ q(mysql, pg) {
312
+ return this.db.dialect === "mysql" ? mysql : pg;
313
313
  }
314
314
  async ensurePointers(_runId, rows) {
315
315
  for (const r of rows) {
@@ -335,8 +335,8 @@ export class SqlWorkflowParkDecisionStore {
335
335
  constructor(claims) {
336
336
  this.claims = claims;
337
337
  }
338
- q(tidb, pg) {
339
- return this.claims.db.dialect === "tidb" ? tidb : pg;
338
+ q(mysql, pg) {
339
+ return this.claims.db.dialect === "mysql" ? mysql : pg;
340
340
  }
341
341
  async record(d) {
342
342
  const fenced = await this.claims.whileHoldingClaim(d.hostSessionId, d.deliveredTaskId, async (tx) => {
@@ -347,12 +347,12 @@ export class SqlWorkflowParkDecisionStore {
347
347
  return fenced.held;
348
348
  }
349
349
  async get(token) {
350
- const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("tidb", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = $1`), [token]);
350
+ const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("mysql", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE token = $1`), [token]);
351
351
  const r = rows[0];
352
352
  return r ? rowToParkDecision(r) : null;
353
353
  }
354
354
  async listByHostSession(hostSessionId) {
355
- const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("tidb", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = $1`), [hostSessionId]);
355
+ const { rows } = await this.claims.db.query(this.q(`SELECT token, run_id, host_session_id, ${jsonColumnText("mysql", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = ?`, `SELECT token, run_id, host_session_id, ${jsonColumnText("pg", "outcome")}, fingerprint, delivered_task_id, accepted_at_ms FROM workflow_park_decision WHERE host_session_id = $1`), [hostSessionId]);
356
356
  return rows.map(rowToParkDecision);
357
357
  }
358
358
  }
@@ -34,6 +34,7 @@
34
34
  import type { Pool as MySqlPool } from "mysql2/promise";
35
35
  import type { Pool as PgPool } from "pg";
36
36
  import { type AlignedWindow } from "../observability/cost-quota.js";
37
+ import type { SqlDialect } from "./sql-driver.js";
37
38
  export interface CounterTable {
38
39
  /** Table name; must have columns (<keyCol>, window_bucket BIGINT, <valCol>, updated_at). */
39
40
  table: string;
@@ -56,7 +57,6 @@ export type CounterDegradeHook = (info: CounterDegradeInfo) => void;
56
57
  * flush round forever; the stalled watchdog can then only report, not recover). The rejection lands in
57
58
  * the existing flush/refresh catch → the S9 streak telemetry counts it; the round ENDS and retries. */
58
59
  export declare const DEFAULT_COUNTER_QUERY_TIMEOUT_MS = 30000;
59
- type CounterDialect = "tidb" | "pg";
60
60
  /** Dual-dialect write-behind aggregating counter. See the file header for the dialect-delta ledger. */
61
61
  export declare class SqlWriteBehindCounter {
62
62
  private readonly dialect;
@@ -73,7 +73,7 @@ export declare class SqlWriteBehindCounter {
73
73
  private flushing;
74
74
  private flushStartedAt;
75
75
  private failStreak;
76
- constructor(dialect: CounterDialect, pool: MySqlPool | PgPool, t: CounterTable,
76
+ constructor(dialect: SqlDialect, pool: MySqlPool | PgPool, t: CounterTable,
77
77
  /** 🔴 不再 `readonly`([ref]):`costQuotaWindowSec` 热更新经 {@link setWindowMs} 换代,语义见那里。 */
78
78
  windowMs: number, now?: () => number, onDegraded?: CounterDegradeHook | undefined, // S9: observability tap — never affects counting
79
79
  queryTimeoutMs?: number);
@@ -135,5 +135,4 @@ export declare class WriteBehindCounter extends SqlWriteBehindCounter {
135
135
  export declare class PgWriteBehindCounter extends SqlWriteBehindCounter {
136
136
  constructor(pool: PgPool, t: CounterTable, windowMs: number, now?: () => number, onDegraded?: CounterDegradeHook, queryTimeoutMs?: number);
137
137
  }
138
- export {};
139
138
  //# sourceMappingURL=write-behind-counter.d.ts.map
@@ -25,7 +25,7 @@ export class SqlWriteBehindCounter {
25
25
  this.queryTimeoutMs = queryTimeoutMs;
26
26
  }
27
27
  async exec(sql, params) {
28
- if (this.dialect === "tidb") {
28
+ if (this.dialect === "mysql") {
29
29
  const [r] = (await this.pool.query({ sql, timeout: this.queryTimeoutMs }, params));
30
30
  if (Array.isArray(r))
31
31
  return { rows: r, affected: 0 };
@@ -92,7 +92,7 @@ export class SqlWriteBehindCounter {
92
92
  }
93
93
  async flushOnce() {
94
94
  const { table, keyCol, valCol } = this.t;
95
- const sql = this.dialect === "tidb"
95
+ const sql = this.dialect === "mysql"
96
96
  ? `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES (?,?,?,?) ` +
97
97
  `ON DUPLICATE KEY UPDATE ${valCol} = ${valCol} + VALUES(${valCol}), updated_at = VALUES(updated_at)`
98
98
  : `INSERT INTO ${table} (${keyCol}, window_bucket, ${valCol}, updated_at) VALUES ($1,$2,$3,$4) ` +
@@ -129,7 +129,7 @@ export class SqlWriteBehindCounter {
129
129
  const { table, keyCol, valCol } = this.t;
130
130
  const b = this.bucket();
131
131
  try {
132
- const { rows } = await this.exec(this.dialect === "tidb"
132
+ const { rows } = await this.exec(this.dialect === "mysql"
133
133
  ? `SELECT ${keyCol}, ${valCol} FROM ${table} WHERE window_bucket = ?`
134
134
  : `SELECT ${keyCol}, ${valCol} FROM ${table} WHERE window_bucket = $1`, [b]);
135
135
  const next = new Map();
@@ -145,7 +145,7 @@ export class SqlWriteBehindCounter {
145
145
  }
146
146
  async reap() {
147
147
  try {
148
- const { affected } = await this.exec(this.dialect === "tidb"
148
+ const { affected } = await this.exec(this.dialect === "mysql"
149
149
  ? `DELETE FROM ${this.t.table} WHERE window_bucket < ?`
150
150
  : `DELETE FROM ${this.t.table} WHERE window_bucket < $1`, [this.bucket()]);
151
151
  this.bump("reap", false);
@@ -175,7 +175,7 @@ export class SqlWriteBehindCounter {
175
175
  }
176
176
  export class WriteBehindCounter extends SqlWriteBehindCounter {
177
177
  constructor(pool, t, windowMs, now, onDegraded, queryTimeoutMs) {
178
- super("tidb", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
178
+ super("mysql", pool, t, windowMs, now, onDegraded, queryTimeoutMs);
179
179
  }
180
180
  }
181
181
  export class PgWriteBehindCounter extends SqlWriteBehindCounter {
@@ -5,24 +5,26 @@
5
5
  * task opts in, a budget/turns/walltime limit OR a scheduler preempt durably SUSPENDS the task (resumable via
6
6
  * `POST /v1/assistant/tasks/:id/resume`) instead of FAILING; absent ⇒ a limit still FAILS.
7
7
  *
8
- * 🔴 EXCLUDE verify/cascade tasks. Core runs their implementation via `runWithVerification`/`runCascade`, which
9
- * map an inner durable suspend to a FAILED result (`errorCode: "unexpected.suspended"`,内层 status
10
- * "needs_review" 时铸 `"unexpected.needs_review"` —— 真源是 core `agents/suspend-guard.js` 的
11
- * `mapNestedSuspend`;`suspended.*` 三码是 bg 子代/委派报告面的词,不落在这条 TaskResult 腿上). The service would then
12
- * `setTerminal(failed)` while core had already minted a pending `resource_limit` checkpoint + suspended the
13
- * remote VM — ORPHANING both (the run row is terminal, so nothing resumes them). So a resource-limit hit on a
14
- * verify/cascade task must FAIL CLEANLY (no suspend), exactly as before the flag. This exclusion is the SAME
15
- * boundary the `preemptSignal` wiring enforces in runs.ts (stream-path only), so eligibility and the signal stay
16
- * aligned. (Cross review wtyc7ist3 HIGH finding: without this, enabling RESOURCE_SUSPEND leaks a suspended
17
- * VM + checkpoint on every verify/cascade task that hits a resource limit.)
8
+ * 🔴 EXCLUDE cascade tasks. Core runs a cascade through `runCascade`, which maps a rung's durable suspend to a FAILED
9
+ * result carrying the pause (`errorCode: "unexpected.suspended"`, 内层 status "needs_review" 时铸 `"unexpected.needs_review"`,
10
+ * 停驻因由在 `terminal.nestedPause` —— 真源是 core `agents/suspend-guard.js` 的 `mapNestedSuspend`;`suspended.*` 三码是 bg
11
+ * 子代/委派报告面的词,不落在这条 TaskResult 腿上). A rung's pause lands on the rung's own engine-minted session, which the
12
+ * host-boundary judge (`atHostBoundary`, src/terminal.ts) leaves terminal: the service would `setTerminal(failed)` while core
13
+ * had already minted a pending `resource_limit` checkpoint + suspended the remote VM — ORPHANING both (the run row is
14
+ * terminal, so nothing resumes them). A resource-limit hit on a cascade task must FAIL CLEANLY (no suspend).
15
+ * (Cross review wtyc7ist3 HIGH finding: without this, enabling RESOURCE_SUSPEND leaks a suspended VM + checkpoint on every
16
+ * cascade task that hits a resource limit.) The arm is the judge's session cell seen from the opt-in side: the day the engine
17
+ * lets a rung's pause land on a session the host's resume chain reaches (core [ref]), the judge's answer flips and this arm goes.
18
+ * Every other task — a verify task included — opts in alike. A verify task's implementation leg and fix rounds run on the
19
+ * run's own session, so the judge lifts their pause into a park of the run row (resumable through
20
+ * `POST /v1/assistant/tasks/:id/resume`); the verifier's own leg is built by the engine without an opt-in, so a limit there
21
+ * still fails that leg.
18
22
  */
19
23
  export declare function resourceSuspendOptIn(opts: {
20
24
  /** `RESOURCE_SUSPEND=true` (the worker-level switch). */
21
25
  enabled: boolean;
22
26
  /** `RESOURCE_SUSPEND_TTL_SEC` — 0 ⇒ core default (30d) by omitting `ttlMs`. */
23
27
  ttlSec: number;
24
- /** This task requested the developer-mode verify gate (`body.verify === true`). */
25
- isVerify: boolean;
26
28
  /** This task requested the cascade ladder (`body.cascade === true`). */
27
29
  isCascade: boolean;
28
30
  /** Multi-tenant isolation key — the SAME key durableApproval uses (the principal; "_" when auth is off). */
@@ -1,5 +1,5 @@
1
1
  export function resourceSuspendOptIn(opts) {
2
- if (!opts.enabled || opts.isVerify || opts.isCascade)
2
+ if (!opts.enabled || opts.isCascade)
3
3
  return undefined;
4
4
  return {
5
5
  scope: opts.scope,
@@ -9,7 +9,7 @@
9
9
  * 【两半各自的真源(刻意不自建第三本账)】
10
10
  * · **上限半**:本模块的进程内登记册 —— 记的是**这条 run 真正铸给 core 的那只 `resourceSuspend`**
11
11
  * (装配点 `boot/resolve-spec.ts` 的 `resourceSuspendOptIn`),不是读时现取 env。差别是承重的:
12
- * ① verify/cascade 腿被 `resourceSuspendOptIn` **整只排除**(opt-in 不成立)⇒ 登记册里没有它 ⇒
12
+ * ① cascade 腿被 `resourceSuspendOptIn` **整只排除**(opt-in 不成立)⇒ 登记册里没有它 ⇒
13
13
  * 整键缺席,绝不显示一个没人执行的天花板;② 滚动改 env 之后,已在跑的 run 报的仍是它自己那份。
14
14
  * · **已耗半**:run 自己的 durable 账本行 `model_usage`(`trace/project.ts` 的 `aggregateModelUsage`)——
15
15
  * 与 `TaskStats.modelUsage` 回声**同一份账**,append-only、跨 slice、跨进程(每条腿只追加自己的
@@ -21,7 +21,8 @@
21
21
  * core 的 `spentTokens` 轴 = 每片 `TaskStats.tokens`(= Σ 每次调用 `usage.totalTokens`,**不含**委派子代,
22
22
  * 子代在 `stats.nested`),而 `model_usage` 行正是同一批 `brain.call` 的逐 turn delta、同样只收顶层 run。
23
23
  * 已知差:① **滞后至多一个 turn**(delta 在 turn 边界 drain,进行中的那一 turn 还没入账);② 账本行
24
- * 写失败(F 类)会少计。⇒ 本投影是**分诊/预警**读面,**不是**任何门/CAS/resume 判定的输入。
24
+ * 写失败(F 类)会少计;③ 只回结果的组合腿(verify)没有逐 turn 落账的流,`model_usage` 行只在停驻 / 终局那一拍写 ⇒
25
+ * running 期读到的是上一次停驻时的量,滞后**一整片**(钉:`test/resource-suspend.test.ts` 的 🚧 格)。⇒ 本投影是**分诊/预警**读面,**不是**任何门/CAS/resume 判定的输入。
25
26
  *
26
27
  * 【登记册的语义边界(与 `turn-activity.ts` 的 `msSinceLastActivity` 逐字同族)】
27
28
  * · **同副本 best-effort**:只活在本进程。跨副本 poll(run 跑在别的副本)或本副本重启后 ⇒ 读不到 ⇒
@@ -34,7 +35,7 @@
34
35
  * 【已声明的代价:每次 poll 一次全量账本读(codex 交叉复审 R2-[medium],如实登记、本批不修)】
35
36
  * {@link buildCrossSliceUsage} 要对**全部** `model_usage` 行求和 ⇒ 调用点(`routes/runs.ts` 的 poll)在
36
37
  * 窗已登记时每拍读一次 `getEvents(taskId, 0)`,工作量随「轮询次数 × 已积累事件数」增长。三条限幅是**结构性**
37
- * 的,不是「应该很少见」:① **只有登记过窗的 run 才读**(没开三旋钮的部署、以及 verify/cascade 腿,一次都不读);
38
+ * 的,不是「应该很少见」:① **只有登记过窗的 run 才读**(没开三旋钮的部署、以及 cascade 腿,一次都不读);
38
39
  * ② 只在 `running` 且非 stale 时读;③ 同一条路上早已存在同形读(终局 + 配了 infra 价目表时的 `needCost` 臂,
39
40
  * 同样是 `getEvents(taskId, 0)`)—— 本批加的是**跑动期**这一档,不是新病种。
40
41
  * 🔴 真正的收口在**存储面**(不在本模块):给 run store 加一条「累计用量 + resource_limit park 计数」的
package/dist/run-local.js CHANGED
@@ -25,13 +25,13 @@ import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
25
25
  import { configLocalRoot, createConfigProvider } from "./config-provider.js";
26
26
  import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
27
27
  import { buildUnreadConfigFileFields, buildUnreadConfigKeyFields } from "./web-admin-client/read-warnings.js";
28
- import { composeExecutionLane, settleHostLaneShell } from "./boot/execution-lane.js";
28
+ import { composeExecutionLane, settleHostLaneShell, warnReadDenyFloorUnkept } from "./boot/execution-lane.js";
29
29
  import { judgeConsolidationValve } from "./boot/memory-consolidation.js";
30
30
  import { logConfigDirMigration, migrateLocalConfigDirectory } from "./config-d-migration.js";
31
31
  import { assertLocalConfigDirTrusted, ConfigDirUntrustedError } from "./config-dir-trust.js";
32
32
  import { applyEffective, applyCenterReadFace, applySkillsManifest, resolveMcpServers, mcpForScenario, resolveA2aPeers, a2aForScenario } from "./web-admin-client/facade.js";
33
33
  import { hostExecutionEnvFactory } from "./plugins/remote-env-host.js";
34
- import { executionLaneHomeDir, executionLaneOf } from "./execution-lane-caps.js";
34
+ import { buildLaneHomeClaim, executionLaneOf } from "./execution-lane-caps.js";
35
35
  import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-memory.js";
36
36
  import { createInstructionSourceSeat } from "./instruction-source-notice.js";
37
37
  import { loadDeploymentSkills } from "./capabilities/skills.js";
@@ -42,12 +42,13 @@ import { createTaskListLane } from "./boot/task-list-lane.js";
42
42
  import { pickHandsRunner, withoutHands } from "./capabilities/hands-lane.js";
43
43
  import { HttpError, sessionIdShapeIssue } from "./security.js";
44
44
  import { createMemoryEngineIncidentSink } from "./memory-incident-sink.js";
45
- import { fileMemoryEngineRoot, memoryEngineBackendFor, memorySpecForRequest } from "./memory-scope.js";
45
+ import { fileMemoryEngineRoot, memoryEngineBackendFor, memoryLayersNamedBy, memorySpecForRequest } from "./memory-scope.js";
46
+ import { createMemoryScopeStores } from "./memory-scope-stores.js";
46
47
  import { createMemoryProjectLayers } from "./memory-project-layer.js";
47
48
  import { assertProjectLayerOwnable } from "./project-identity.js";
48
49
  import { declaredCredential } from "./model-route-gap.js";
49
50
  import { judgeServedModelRoute } from "./model-route-endpoint.js";
50
- import { buildMemorySyncEnabledFields, createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
51
+ import { startMemorySyncRunner } from "./memory-sync-client.js";
51
52
  import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
52
53
  import { createSharedRunnerDeps } from "./boot/runner-deps.js";
53
54
  import { createPeerDesk } from "./peer-seat.js";
@@ -472,17 +473,22 @@ export async function runLocal(argv, deps = {}) {
472
473
  const hostRx = config.remoteExec?.provider === "host" ? config.remoteExec : undefined;
473
474
  const workspaceDir = args.workspace ?? hostRx?.workspaceBase ?? process.cwd();
474
475
  await settleHostLaneShell(config, logger);
476
+ warnReadDenyFloorUnkept(config, logger);
477
+ const homeDirSeat = buildLaneHomeClaim(config);
475
478
  const executionEnvFactory = hostRx
476
479
  ? hostExecutionEnvFactory({
477
480
  workspaceDir,
478
- ...((h) => (h === undefined ? {} : { homeDir: h }))(executionLaneHomeDir(config)),
481
+ ...homeDirSeat,
479
482
  backgroundShell: config.requirePrincipal !== true,
480
483
  ...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
481
484
  })
482
485
  : undefined;
483
486
  const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? createHostLspManager(config, (event, fields) => logger.info(event, fields)) : undefined;
484
- const memoryEngine = memoryEngineBackendFor(config, root, memoryEngineIncident);
485
487
  const engineDataRoot = fileMemoryEngineRoot(config, root);
488
+ const fileEngine = memoryEngineBackendFor(config, root, memoryEngineIncident);
489
+ const memoryEngine = fileEngine !== undefined
490
+ ? createMemoryScopeStores({ backend: fileEngine.backend, root: engineDataRoot, namedLayers: () => memoryLayersNamedBy({ ...config, memoryScope: args.user ?? "local" }), onIncident: memoryEngineIncident })
491
+ : undefined;
486
492
  try {
487
493
  judgeConsolidationValve({ config, memoryEngine });
488
494
  }
@@ -494,18 +500,7 @@ export async function runLocal(argv, deps = {}) {
494
500
  }
495
501
  let memorySync;
496
502
  if (config.memorySync && memoryEngine) {
497
- const transport = createMemorySyncTransport({ url: config.memorySync.url, token: config.memorySync.token });
498
- memorySync = createMemorySyncRunner({
499
- backend: memoryEngine.backend,
500
- scope: config.memorySync.scope,
501
- memoryRoot: memoryEngine.root,
502
- transport,
503
- log: logger,
504
- ...(config.memorySync.maxPushEntries !== undefined ? { maxPushEntries: config.memorySync.maxPushEntries } : {}),
505
- ...(config.memorySync.maxPullEntries !== undefined ? { maxPullEntries: config.memorySync.maxPullEntries } : {}),
506
- });
507
- logger.info("memory_sync_enabled", buildMemorySyncEnabledFields(transport.peer, config.memorySync.scope, memorySync.cursorPathFor(config.memorySync.scope)));
508
- memorySync.trigger("boot");
503
+ memorySync = startMemorySyncRunner({ sync: config.memorySync, stores: memoryEngine, log: logger });
509
504
  }
510
505
  else if (config.memorySync) {
511
506
  logger.warn("memory_sync_configured_but_memory_dark", { note: "MEMORY_SYNC_URL is set but the file memory engine is dark (MEMORY_ENGINE=off / remote exec lane) — no sync rounds will run" });
@@ -687,7 +682,7 @@ export async function runLocal(argv, deps = {}) {
687
682
  }
688
683
  }
689
684
  const projectKey = memoryEngine
690
- ? await createMemoryProjectLayers({ backend: memoryEngine.backend, syncRunner: memorySync, log: logger }).prepare(config, scope, workspaceDir)
685
+ ? await createMemoryProjectLayers({ stores: memoryEngine, syncRunner: memorySync, log: logger }).prepare(config, scope, workspaceDir)
691
686
  : undefined;
692
687
  const taskTimeoutSec = Math.floor(parseNumOrFailNonNegative("TASK_TIMEOUT_SEC", process.env.TASK_TIMEOUT_SEC || "0"));
693
688
  const timeoutSec = taskWallClockSec(taskTimeoutSec, false);
package/dist/runs.d.ts CHANGED
@@ -263,10 +263,14 @@ export declare function graftParkToolCallId<T>(result: T, cs: ParkCheckpointRead
263
263
  * Preemption (POST /v1/assistant/tasks/:id/preempt, [ref] seam #2): a SECOND, INDEPENDENT controller
264
264
  * (`preemptCtrl`, registered in `preemptable`) carries the scheduler's graceful "yield this task" — when raised,
265
265
  * core durably SUSPENDS the task at the next clean turn boundary (gate resource_limit, reason "preempt") so it is
266
- * RESUMABLE, the opposite of cancel's kill. It is wired ONLY on the plain-stream path: the verify/cascade
267
- * branches finalize with `setTerminal` unconditionally (no `setSuspended`), so a preempt-suspend there would write
268
- * a non-terminal "suspended" as terminal and release the session lock — and preempt's contract is graceful, not a
269
- * failure. Cross-replica via the `preempt_requested` flag (same heartbeat tick). Eligibility (resourceSuspend +
266
+ * RESUMABLE, the opposite of cancel's kill. It is wired ONLY on the plain-stream path. A cascade cannot honor it: a
267
+ * rung's pause lands on the rung's own engine-minted session, which the host's resume chain cannot reach (the
268
+ * host-boundary judge settles it terminal). A verify task carries the resource-suspend opt-in (a budget / turns /
269
+ * walltime limit parks its implementation leg or fix round), but its FIRST slice here is not wired to the preempt
270
+ * signal (`preemptEligible` below is false for verify, so the preempt endpoint answers a no-op for a verify run this
271
+ * replica owns); a RESUMED verify leg is wired (`resume-legs.ts` registers every resume leg in `preemptableRuns` and
272
+ * threads `preemptSignal` into `resumeWithVerification`). Cross-replica via the `preempt_requested` flag (same heartbeat
273
+ * tick). Eligibility (resourceSuspend +
270
274
  * durable store + durable tool-results + remote env) is core's call: an ineligible task's preemptSignal no-ops.
271
275
  *
272
276
  * Steering (POST /v1/runs/:id/steer, [ref]): the live core `TaskStream` is registered in `steerable` so the