@sema-agent/server 7.138.0 → 7.139.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 (215) hide show
  1. package/MIGRATION.md +276 -7
  2. package/README.md +7 -4
  3. package/README.zh-CN.md +6 -4
  4. package/USAGE.md +73 -29
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +20 -20
  9. package/dist/bench/s1/arms.d.ts +1 -1
  10. package/dist/bench/s1/arms.js +1 -3
  11. package/dist/bench/s1/run-firm.js +2 -2
  12. package/dist/boot/device-lane.d.ts +8 -3
  13. package/dist/boot/device-lane.js +7 -2
  14. package/dist/boot/execution-env.d.ts +4 -2
  15. package/dist/boot/execution-env.js +16 -24
  16. package/dist/boot/execution-lane.d.ts +1 -1
  17. package/dist/boot/governance-seams.d.ts +3 -2
  18. package/dist/boot/governance-seams.js +9 -11
  19. package/dist/boot/memory-consolidation.js +2 -2
  20. package/dist/boot/resolve-spec.d.ts +1 -1
  21. package/dist/boot/resolve-spec.js +8 -9
  22. package/dist/boot/retention-lane.d.ts +5 -5
  23. package/dist/boot/retention-lane.js +0 -6
  24. package/dist/boot/runner-deps.d.ts +3 -1
  25. package/dist/boot/runner-deps.js +2 -1
  26. package/dist/boot/session-faces.d.ts +0 -3
  27. package/dist/boot/session-faces.js +15 -8
  28. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  29. package/dist/boot/stage-05-execution-env.js +2 -2
  30. package/dist/boot/stage-06-runners.js +0 -1
  31. package/dist/boot/stage-09-leader.js +1 -2
  32. package/dist/boot/stage-10-http-server.js +2 -2
  33. package/dist/boot/stores.js +4 -4
  34. package/dist/boot/web-admin.js +2 -2
  35. package/dist/capabilities/mcp-probe.js +1 -1
  36. package/dist/capabilities/scenarios.d.ts +1 -1
  37. package/dist/capability-facts.d.ts +15 -7
  38. package/dist/capability-facts.js +8 -3
  39. package/dist/config-catalog.js +7 -7
  40. package/dist/config-invariants.d.ts +23 -3
  41. package/dist/config-invariants.js +42 -0
  42. package/dist/config-provider.d.ts +19 -24
  43. package/dist/config-provider.js +27 -12
  44. package/dist/config-types.d.ts +10 -5
  45. package/dist/config.d.ts +33 -0
  46. package/dist/config.js +44 -15
  47. package/dist/delegated-session-owner.d.ts +2 -2
  48. package/dist/deployment-shape.d.ts +1 -1
  49. package/dist/device-enrollment.d.ts +41 -2
  50. package/dist/device-enrollment.js +27 -12
  51. package/dist/device-store.d.ts +58 -85
  52. package/dist/device-store.js +19 -49
  53. package/dist/device-ws-hub.js +2 -1
  54. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  55. package/dist/exec-cut.d.ts +60 -0
  56. package/dist/exec-cut.js +36 -0
  57. package/dist/execution-lane-caps.d.ts +19 -0
  58. package/dist/execution-lane-caps.js +10 -0
  59. package/dist/execution-placement.d.ts +259 -0
  60. package/dist/execution-placement.js +199 -0
  61. package/dist/field-conflict.d.ts +3 -2
  62. package/dist/field-conflict.js +1 -0
  63. package/dist/hosted-posture.d.ts +2 -2
  64. package/dist/http/admission.js +8 -19
  65. package/dist/http/checkpoint-ctx.d.ts +21 -12
  66. package/dist/http/checkpoint-ctx.js +4 -2
  67. package/dist/http/claimed-run.d.ts +1 -1
  68. package/dist/http/durable-run-leg.js +9 -5
  69. package/dist/http/execution-context.d.ts +218 -0
  70. package/dist/http/execution-context.js +330 -0
  71. package/dist/http/resume-legs.js +50 -20
  72. package/dist/http/route-ctx.d.ts +14 -8
  73. package/dist/http/routes/background.d.ts +29 -1
  74. package/dist/http/routes/background.js +29 -2
  75. package/dist/http/routes/devices.d.ts +13 -19
  76. package/dist/http/routes/devices.js +21 -26
  77. package/dist/http/routes/session-sync.js +110 -15
  78. package/dist/http/routes/sessions.d.ts +6 -0
  79. package/dist/http/routes/sessions.js +115 -11
  80. package/dist/http/routes/side-query.d.ts +12 -4
  81. package/dist/http/routes/side-query.js +4 -5
  82. package/dist/http/routes/tasks.js +37 -31
  83. package/dist/http/server.d.ts +35 -19
  84. package/dist/http/server.js +11 -0
  85. package/dist/http/session-inherit-env.d.ts +3 -4
  86. package/dist/http/session-inherit-env.js +1 -1
  87. package/dist/http/wire-types.d.ts +8 -12
  88. package/dist/index.d.ts +1 -0
  89. package/dist/memory-sync-client.d.ts +53 -14
  90. package/dist/memory-sync-client.js +196 -116
  91. package/dist/memory-sync.d.ts +1 -1
  92. package/dist/memory-sync.js +3 -2
  93. package/dist/observability/cost-taxonomy.d.ts +2 -2
  94. package/dist/observability/fail-open.d.ts +12 -0
  95. package/dist/observability/fail-open.js +12 -0
  96. package/dist/observability/run-terminal.js +0 -1
  97. package/dist/orchestration/workflow-notify-journal.js +4 -4
  98. package/dist/org-memory-admission.d.ts +4 -3
  99. package/dist/org-memory-admission.js +6 -6
  100. package/dist/parked-decide.d.ts +34 -9
  101. package/dist/parked-decide.js +12 -19
  102. package/dist/parked-revive-surface.d.ts +12 -15
  103. package/dist/parked-revive-surface.js +3 -6
  104. package/dist/peer-seat.d.ts +13 -7
  105. package/dist/peer-seat.js +37 -3
  106. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  107. package/dist/plugins/approval-ask-store-file.js +11 -0
  108. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  109. package/dist/plugins/approval-ask-store-memory.js +12 -0
  110. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  111. package/dist/plugins/approval-ask-store-sql.js +30 -12
  112. package/dist/plugins/caching-session-store.d.ts +6 -2
  113. package/dist/plugins/caching-session-store.js +9 -3
  114. package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
  115. package/dist/plugins/checkpoint-store-sql.js +5 -7
  116. package/dist/plugins/device-store-sql.d.ts +25 -51
  117. package/dist/plugins/device-store-sql.js +49 -103
  118. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  119. package/dist/plugins/execution-placement-sql.js +223 -0
  120. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  121. package/dist/plugins/file-history-store-sql.js +5 -5
  122. package/dist/plugins/file-run-store.d.ts +6 -1
  123. package/dist/plugins/file-run-store.js +4 -0
  124. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  125. package/dist/plugins/fork-routing-session-store.js +16 -0
  126. package/dist/plugins/local-session-store.d.ts +149 -50
  127. package/dist/plugins/local-session-store.js +382 -107
  128. package/dist/plugins/memory-key-guards.d.ts +4 -0
  129. package/dist/plugins/memory-key-guards.js +1 -0
  130. package/dist/plugins/memory-run-store.d.ts +1 -1
  131. package/dist/plugins/one-time-migrations.d.ts +111 -3
  132. package/dist/plugins/one-time-migrations.js +380 -10
  133. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  134. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  135. package/dist/plugins/permission-rule-store-sql.js +13 -16
  136. package/dist/plugins/pg-pool.d.ts +7 -3
  137. package/dist/plugins/pg-session-storage.d.ts +31 -20
  138. package/dist/plugins/pg-session-storage.js +120 -52
  139. package/dist/plugins/remote-env-adb.d.ts +20 -4
  140. package/dist/plugins/remote-env-adb.js +70 -62
  141. package/dist/plugins/remote-env-device.d.ts +30 -12
  142. package/dist/plugins/remote-env-device.js +18 -17
  143. package/dist/plugins/remote-env-e2b.d.ts +18 -4
  144. package/dist/plugins/remote-env-e2b.js +41 -42
  145. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  146. package/dist/plugins/remote-env-file-error.js +1 -1
  147. package/dist/plugins/remote-env-host.d.ts +21 -8
  148. package/dist/plugins/remote-env-host.js +66 -70
  149. package/dist/plugins/remote-env-k8s.d.ts +11 -16
  150. package/dist/plugins/remote-env-k8s.js +61 -54
  151. package/dist/plugins/remote-env-local-docker.d.ts +29 -6
  152. package/dist/plugins/remote-env-local-docker.js +46 -59
  153. package/dist/plugins/remote-env-ssh.d.ts +16 -4
  154. package/dist/plugins/remote-env-ssh.js +72 -56
  155. package/dist/plugins/remote-shell.d.ts +100 -3
  156. package/dist/plugins/remote-shell.js +166 -2
  157. package/dist/plugins/retention-store-sql.d.ts +9 -6
  158. package/dist/plugins/retention-store-sql.js +7 -1
  159. package/dist/plugins/run-store-sql.d.ts +13 -3
  160. package/dist/plugins/run-store-sql.js +5 -0
  161. package/dist/plugins/schema-consistency.d.ts +15 -5
  162. package/dist/plugins/schema-consistency.js +14 -8
  163. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  164. package/dist/plugins/schema-drift-sentence.js +38 -0
  165. package/dist/plugins/session-fork.d.ts +54 -0
  166. package/dist/plugins/session-fork.js +38 -1
  167. package/dist/plugins/session-row-sql.d.ts +22 -0
  168. package/dist/plugins/session-row-sql.js +81 -0
  169. package/dist/plugins/session-store.d.ts +1 -1
  170. package/dist/plugins/store-backend.d.ts +1 -1
  171. package/dist/plugins/store-backend.js +19 -7
  172. package/dist/plugins/store-contracts.d.ts +25 -5
  173. package/dist/plugins/tidb-pool.d.ts +12 -7
  174. package/dist/plugins/tidb-pool.js +9 -3
  175. package/dist/plugins/tidb-session-store.d.ts +57 -26
  176. package/dist/plugins/tidb-session-store.js +118 -50
  177. package/dist/plugins/tool-result-store-sql.d.ts +3 -2
  178. package/dist/plugins/tool-result-store-sql.js +18 -10
  179. package/dist/project-memory.d.ts +9 -15
  180. package/dist/project-memory.js +5 -4
  181. package/dist/request-key-closure.js +1 -1
  182. package/dist/request-value-closure.d.ts +6 -7
  183. package/dist/request-value-closure.js +1 -3
  184. package/dist/run-local.d.ts +6 -0
  185. package/dist/run-local.js +24 -20
  186. package/dist/runs.d.ts +2 -1
  187. package/dist/runs.js +3 -1
  188. package/dist/security.d.ts +38 -12
  189. package/dist/security.js +8 -2
  190. package/dist/server-secret-env.d.ts +9 -5
  191. package/dist/session-children-retire.d.ts +27 -9
  192. package/dist/session-children-retire.js +9 -5
  193. package/dist/session-incarnation.d.ts +19 -9
  194. package/dist/session-incarnation.js +4 -2
  195. package/dist/session-row.d.ts +114 -5
  196. package/dist/session-row.js +55 -5
  197. package/dist/session-titler.d.ts +2 -2
  198. package/dist/task-a2a.d.ts +1 -1
  199. package/dist/task-cwd.d.ts +6 -1
  200. package/dist/task-mcp.d.ts +3 -3
  201. package/dist/tool-approval.d.ts +6 -0
  202. package/dist/tool-approval.js +8 -1
  203. package/dist/trace/project.d.ts +1 -0
  204. package/dist/trace/project.js +1 -0
  205. package/dist/trace/self-minted-wire.d.ts +111 -2
  206. package/dist/trace/self-minted-wire.js +38 -1
  207. package/dist/url-refusal.d.ts +1 -1
  208. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  209. package/dist/web-admin-client/apply-effective.js +20 -10
  210. package/dist/web-admin-client/skills-mcp.js +3 -2
  211. package/dist/web-admin-client/types.d.ts +6 -0
  212. package/package.json +3 -3
  213. package/wire-changes/server@7.139.0.json +348 -0
  214. package/dist/http/device-placement.d.ts +0 -175
  215. package/dist/http/device-placement.js +0 -145
@@ -6,7 +6,9 @@ import { escapeLike } from "./sql-escape.js";
6
6
  import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
7
7
  import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
8
8
  import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
9
- import { carriedIncarnation, headFromRow, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
9
+ import { carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
10
+ import { sessionRowTagKey } from "../session-row.js";
11
+ import { writeSessionRowCellsSql } from "./session-row-sql.js";
10
12
  import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
11
13
  import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, createPlacedSessionRow } from "../delegated-session-owner.js";
12
14
  import { createLogger } from "../observability/logger.js";
@@ -14,17 +16,26 @@ import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, pla
14
16
  import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
15
17
  import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue } from "./session-ownership.js";
16
18
  import { selectForkRows } from "@sema-agent/core";
17
- import { FORK_LOG_ROW_SHAPE, forkPointOf } from "./session-fork.js";
19
+ import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "./session-fork.js";
20
+ import { isMysqlDupKeyError } from "./sql-errors.js";
21
+ import { mysqlDriver } from "./sql-driver.js";
22
+ import { MIRROR_ON_COMMIT_GUARD, MIRROR_ON_COMMIT_SET, SESSION_META_PLACEMENT_READ_COLUMNS, createSqlExecutionPlacementSeat, importedRowCells, lockRootAndReadClaim, placementCellFromRow, readPlacementRowStatement } from "./execution-placement-sql.js";
23
+ import { SessionActiveRunError, SessionWriterPresentError } from "../execution-placement.js";
24
+ const rowsOnConnection = (conn) => async (sql, values) => (await conn.query(sql, [...values]))[0];
18
25
  export class TiDBSessionStore {
19
26
  pool;
20
27
  retention;
21
28
  placements = SQL_SESSION_PLACEMENTS;
22
29
  pending = new Map();
23
30
  log;
31
+ executionPlacement;
32
+ db;
24
33
  constructor(pool, opts = {}) {
25
34
  this.pool = pool;
26
35
  this.log = opts.log ?? createLogger();
27
36
  this.retention = opts.retention ?? UNMANAGED_RETENTION;
37
+ this.db = mysqlDriver(pool);
38
+ this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
28
39
  }
29
40
  async acquire(sessionId, opts) {
30
41
  if (!sessionId) {
@@ -172,34 +183,21 @@ export class TiDBSessionStore {
172
183
  where.push("(sm.updated_at < ? OR (sm.updated_at = ? AND sm.session_id < ?))");
173
184
  params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
174
185
  }
175
- return this.selectSummaries({ where, params, limit: opts.limit });
186
+ return (await this.selectListRows({ where, params, limit: opts.limit })).map((a) => a.row);
176
187
  }
177
188
  async sessionSummaryOf(sessionId) {
178
- const rows = await this.selectSummaries({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?"], params: [sessionId], limit: 1, runSessionId: sessionId });
179
- return rows[0];
189
+ return (await this.listRowOf(sessionId))?.row;
180
190
  }
181
- async patchSessionMeta(sessionId, patch, face) {
182
- const sets = [];
183
- const params = [];
184
- if (patch.title !== undefined) {
185
- sets.push("title = ?");
186
- params.push(patch.title);
187
- }
188
- if (patch.pinned !== undefined) {
189
- sets.push("pinned = ?");
190
- params.push(patch.pinned);
191
- }
192
- if (sets.length > 0) {
193
- params.push(sessionId, face.incarnation);
194
- await this.pool.query(`UPDATE session_meta SET ${sets.join(", ")} WHERE session_id = ? AND incarnation = ? AND placement_kind IS NULL`, params);
195
- }
196
- const rows = await this.selectSummaries({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?", "sm.incarnation = ?"], params: [sessionId, face.incarnation], limit: 1, runSessionId: sessionId });
197
- return rows[0];
191
+ async listRowOf(sessionId) {
192
+ return (await this.selectListRows({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?"], params: [sessionId], limit: 1, runSessionId: sessionId }))[0];
198
193
  }
199
- async selectSummaries(q) {
194
+ async writeSessionRowCells(sessionId, cells, how) {
195
+ return writeSessionRowCellsSql(this.db, sessionId, cells, how, (id) => this.listRowOf(id));
196
+ }
197
+ async selectListRows(q) {
200
198
  const runWhere = q.runSessionId !== undefined ? " WHERE session_id = ?" : "";
201
199
  const runParams = q.runSessionId !== undefined ? [q.runSessionId, q.runSessionId] : [];
202
- const sql = "SELECT sm.session_id, sm.owner, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
200
+ const sql = "SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
203
201
  " COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id, latest.last_task_run_id " +
204
202
  "FROM session_meta sm " +
205
203
  "LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run" + runWhere + " GROUP BY session_id) r ON r.session_id = sm.session_id " +
@@ -212,9 +210,14 @@ export class TiDBSessionStore {
212
210
  const [rows] = await this.pool.query(sql, [...runParams, ...q.params, q.limit]);
213
211
  return rows.map((r) => {
214
212
  const runId = r.last_task_run_id;
215
- return {
216
- sessionId: String(r.session_id),
217
- owner: r.owner ?? null,
213
+ const sessionId = String(r.session_id);
214
+ const owner = r.owner ?? null;
215
+ const incarnation = incarnationFromCell(sessionId, r.incarnation, "absent");
216
+ const title = r.title ?? null;
217
+ const pinned = Number(r.pinned) === 1;
218
+ const row = {
219
+ sessionId,
220
+ owner,
218
221
  lastActivityAt: r.last_activity instanceof Date ? r.last_activity.toISOString() : String(r.last_activity),
219
222
  firstActivityAt: r.first_activity instanceof Date ? r.first_activity.toISOString() : String(r.first_activity),
220
223
  runCount: Number(r.run_count),
@@ -222,9 +225,11 @@ export class TiDBSessionStore {
222
225
  lastStatus: r.status == null ? "" : String(r.status),
223
226
  lastRunId: r.last_run_id ?? null,
224
227
  ...(typeof runId === "string" && runId !== "" ? { lastTaskRunId: runId } : {}),
225
- title: r.title ?? null,
226
- pinned: Number(r.pinned) === 1,
228
+ title,
229
+ pinned,
230
+ ...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
227
231
  };
232
+ return { row, occupant: { owner, incarnation } };
228
233
  });
229
234
  }
230
235
  async probeTitle(sessionId) {
@@ -259,6 +264,17 @@ export class TiDBSessionStore {
259
264
  await this.pool.query("UPDATE session_meta SET workspace = ? WHERE session_id = ? AND incarnation = ?", [encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId), sessionId, incarnation]);
260
265
  }
261
266
  async fork(sourceId, owner) {
267
+ return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner);
268
+ }
269
+ async forkTo(sourceId, owner, target) {
270
+ for (let attempt = 0; attempt < 2; attempt++) {
271
+ const out = await this.forkToOnce(sourceId, owner, target);
272
+ if (out !== "unsettled")
273
+ return out;
274
+ }
275
+ throw new ForkTargetUnsettledError(target.id);
276
+ }
277
+ async forkToOnce(sourceId, owner, target) {
262
278
  const [metaRows] = await this.pool.query("SELECT leaf_id, owner FROM session_meta WHERE session_id = ?", [sourceId]);
263
279
  if (!metaRows[0])
264
280
  return null;
@@ -270,19 +286,29 @@ export class TiDBSessionStore {
270
286
  const point = await forkPointOf(storage);
271
287
  const [allRows] = await this.pool.query(`SELECT entry_id, parent_id, type, ${jsonColumnText("tidb", "payload")}, ts FROM session_event WHERE session_id = ? ORDER BY seq ASC`, [sourceId]);
272
288
  const { rows: eventRows, leafId: forkLeafId } = selectForkRows(allRows, leafId, point, FORK_LOG_ROW_SHAPE);
273
- const newId = uuidv7();
274
289
  const now = new Date();
275
290
  const conn = await this.pool.getConnection();
291
+ let collided = false;
276
292
  try {
277
293
  await conn.beginTransaction();
278
- for (let seq = 0; seq < eventRows.length; seq++) {
279
- const r = eventRows[seq];
280
- const payload = String(r.payload);
281
- await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [newId, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
294
+ try {
295
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, fork_key) VALUES (?,?,?,?,?,?,?,?)", [target.id, forkOwner, now, now, forkLeafId, eventRows.length, mintSessionIncarnation(), target.key ?? null]);
282
296
  }
283
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,?,?,?)", [newId, forkOwner, now, now, forkLeafId, eventRows.length, mintSessionIncarnation()]);
284
- await conn.commit();
285
- return newId;
297
+ catch (err) {
298
+ if (!isMysqlDupKeyError(err))
299
+ throw err;
300
+ collided = true;
301
+ }
302
+ if (!collided) {
303
+ for (let seq = 0; seq < eventRows.length; seq++) {
304
+ const r = eventRows[seq];
305
+ const payload = String(r.payload);
306
+ await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [target.id, seq, r.entry_id, r.parent_id ?? null, r.type, payload, r.ts]);
307
+ }
308
+ await conn.commit();
309
+ return { outcome: "created", id: target.id };
310
+ }
311
+ await conn.rollback();
286
312
  }
287
313
  catch (err) {
288
314
  try {
@@ -295,6 +321,9 @@ export class TiDBSessionStore {
295
321
  finally {
296
322
  conn.release();
297
323
  }
324
+ const [existing] = await this.pool.query("SELECT owner, fork_key FROM session_meta WHERE session_id = ?", [target.id]);
325
+ const row = existing[0];
326
+ return row === undefined ? "unsettled" : classifyForkTarget(target, { owner: row.owner ?? null, forkKey: row.fork_key ?? null }, forkOwner);
298
327
  }
299
328
  async exportEntries(sessionId) {
300
329
  const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
@@ -334,7 +363,7 @@ export class TiDBSessionStore {
334
363
  const conn = await this.pool.getConnection();
335
364
  try {
336
365
  await conn.beginTransaction();
337
- await this.insertValidatedLog(conn, sessionId, owner, validated, mintSessionIncarnation());
366
+ await this.writeImportedLog(conn, sessionId, owner, validated, { incarnation: mintSessionIncarnation(), forkKey: null }, "create");
338
367
  await conn.commit();
339
368
  }
340
369
  catch (err) {
@@ -343,7 +372,7 @@ export class TiDBSessionStore {
343
372
  }
344
373
  catch {
345
374
  }
346
- if (err instanceof SessionError)
375
+ if (err instanceof SessionError || err instanceof SessionActiveRunError)
347
376
  throw err;
348
377
  throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
349
378
  }
@@ -357,14 +386,21 @@ export class TiDBSessionStore {
357
386
  const conn = await this.pool.getConnection();
358
387
  try {
359
388
  await conn.beginTransaction();
360
- const [placedRows] = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
361
- if (placedRows[0]?.placement_kind != null) {
389
+ const [placedRows] = await conn.query(`SELECT ${SESSION_META_PLACEMENT_READ_COLUMNS}, fork_key FROM session_meta WHERE session_id = ? FOR UPDATE`, [sessionId]);
390
+ const prior = placedRows[0];
391
+ if (prior?.placement_kind != null) {
362
392
  await conn.rollback();
363
- throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(placedRows[0].placement_kind)} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
393
+ throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(prior.placement_kind)} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
364
394
  }
365
- await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
366
- await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ? AND placement_kind IS NULL", [sessionId, owner]);
367
- await this.insertValidatedLog(conn, sessionId, owner, validated, carriedIncarnation(sessionId, placedRows[0] === undefined ? undefined : { incarnation: placedRows[0].incarnation }));
395
+ const [purged] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ? AND placement_kind IS NULL", [sessionId, owner]);
396
+ const swapped = purged.affectedRows === 1 ? prior : undefined;
397
+ const cells = carriedRowCells(sessionId, swapped === undefined ? undefined : { incarnation: swapped.incarnation, forkKey: swapped.fork_key });
398
+ const priorCell = swapped === undefined ? undefined : placementCellFromRow(sessionId, swapped);
399
+ if (priorCell?.role === "writer") {
400
+ await conn.rollback();
401
+ throw new SessionWriterPresentError(sessionId, priorCell.lane);
402
+ }
403
+ await this.writeImportedLog(conn, sessionId, owner, validated, cells, "replace");
368
404
  await conn.commit();
369
405
  }
370
406
  catch (err) {
@@ -373,7 +409,7 @@ export class TiDBSessionStore {
373
409
  }
374
410
  catch {
375
411
  }
376
- if (err instanceof SessionError)
412
+ if (err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
377
413
  throw err;
378
414
  throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
379
415
  }
@@ -381,16 +417,29 @@ export class TiDBSessionStore {
381
417
  conn.release();
382
418
  }
383
419
  }
384
- async insertValidatedLog(conn, sessionId, owner, validated, incarnation) {
420
+ async writeImportedLog(conn, sessionId, owner, validated, cells, mode) {
385
421
  let leafId = null;
386
422
  for (const e of validated)
387
423
  leafId = leafIdAfterEntry(e);
388
424
  const now = new Date();
425
+ const imported = importedRowCells(cells.forkKey);
426
+ await conn.query(`INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, ${imported.columns}) VALUES (?,?,?,?,?,?,?,?,?)` +
427
+ (mode === "create" ? " ON DUPLICATE KEY UPDATE session_id = session_id" : ""), [sessionId, owner, now, now, leafId, validated.length, cells.incarnation, ...imported.values]);
428
+ if (mode === "create") {
429
+ const [held] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
430
+ if (String(held[0]?.incarnation) !== cells.incarnation) {
431
+ throw new SessionError("storage", `import of session ${sessionId} failed`, new Error(`session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`));
432
+ }
433
+ }
434
+ const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", sessionId);
435
+ if (claim.activeTaskId !== null)
436
+ throw new SessionActiveRunError(sessionId, claim.activeTaskId);
437
+ if (mode === "replace")
438
+ await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
389
439
  for (let seq = 0; seq < validated.length; seq++) {
390
440
  const e = validated[seq];
391
441
  await conn.query("INSERT INTO session_event (session_id, seq, entry_id, parent_id, type, payload, ts) VALUES (?,?,?,?,?,?,?)", [sessionId, seq, e.id, e.parentId ?? null, e.type, JSON.stringify(e), new Date(e.timestamp)]);
392
442
  }
393
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,?,?,?)", [sessionId, owner, now, now, leafId, validated.length, incarnation]);
394
443
  }
395
444
  beginImportStaging(realSessionId, token) {
396
445
  return new TiDBStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
@@ -421,6 +470,11 @@ export class TiDBSessionStore {
421
470
  const conn = await this.pool.getConnection();
422
471
  try {
423
472
  await conn.beginTransaction();
473
+ const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", sessionId);
474
+ if (claim.activeTaskId !== null) {
475
+ await conn.rollback();
476
+ throw new SessionActiveRunError(sessionId, claim.activeTaskId);
477
+ }
424
478
  const [metaRes] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ?", [sessionId, owner]);
425
479
  if (metaRes.affectedRows > 0)
426
480
  await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
@@ -499,6 +553,11 @@ class TiDBStagingHandle {
499
553
  throw new StagedCommitOwnerChangedError(this.realSessionId);
500
554
  }
501
555
  }
556
+ const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "tidb", this.realSessionId);
557
+ if (claim.activeTaskId !== null) {
558
+ await conn.rollback();
559
+ throw new SessionActiveRunError(this.realSessionId, claim.activeTaskId);
560
+ }
502
561
  const [stagedRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
503
562
  const [dstRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
504
563
  const [dstMeta] = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = ?", [this.realSessionId]);
@@ -533,7 +592,16 @@ class TiDBStagingHandle {
533
592
  let leafId = null;
534
593
  if (lastRow[0])
535
594
  leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRow[0].payload, this.realSessionId, "last staged row"));
536
- await conn.query("UPDATE session_meta SET owner = ?, updated_at = ?, leaf_id = ?, leaf_seq = ? WHERE session_id = ?", [owner, now, leafId, srcIds.length, this.realSessionId]);
595
+ const [settle] = await conn.query(`UPDATE session_meta SET owner = ?, updated_at = ?, leaf_id = ?, leaf_seq = ?, ${MIRROR_ON_COMMIT_SET} WHERE session_id = ? AND ${MIRROR_ON_COMMIT_GUARD}`, [owner, now, leafId, srcIds.length, this.realSessionId]);
596
+ if (settle.affectedRows !== 1) {
597
+ const locked = readPlacementRowStatement("tidb", this.realSessionId, { lock: true });
598
+ const [lockedRows] = await conn.query(locked.text, [...locked.values]);
599
+ const cell = lockedRows[0] === undefined ? undefined : placementCellFromRow(this.realSessionId, lockedRows[0]);
600
+ await conn.rollback();
601
+ if (cell?.role === "writer")
602
+ throw new SessionWriterPresentError(this.realSessionId, cell.lane);
603
+ throw new SessionError("storage", `staged commit for ${this.realSessionId}: the commit point matched no row under a held row lock and the row is not a writer — refusing to guess`);
604
+ }
537
605
  const [afterMeta] = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = ?", [this.realSessionId]);
538
606
  if (afterMeta[0]?.placement_kind != null) {
539
607
  await conn.rollback();
@@ -548,7 +616,7 @@ class TiDBStagingHandle {
548
616
  await conn.rollback();
549
617
  }
550
618
  catch { }
551
- if (err instanceof SyncConflictError || err instanceof SessionError)
619
+ if (err instanceof SyncConflictError || err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
552
620
  throw err;
553
621
  throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
554
622
  }
@@ -15,14 +15,15 @@ export declare function ensureSchema(pool: PgPool | PgPoolClient): Promise<void>
15
15
  /**
16
16
  * [ref] 升级前置断言 —— **拒启**,不是 warn(codex 复审 [high],已核真)。
17
17
  *
18
- * 病:本仓不发 `ALTER TABLE` 迁移(标准裁定:改列就改 CREATE + 删库重建)。于是一台**没删表**就升上来的
18
+ * 病:本仓不发 `ALTER TABLE` 迁移(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升上来的
19
19
  * 部署,`CREATE TABLE IF NOT EXISTS` 对它是空操作,旧表既没有出处两列也只有 `VARCHAR(190)` 的 ref 列。
20
20
  * 那样跑起来的后果不是「少个功能」:每一次 offload 写都会撞 unknown column 报错,而 core 的 offload
21
21
  * 失败臂会把错误吞成一条内联占位("[offload LOST at write time…]")—— 服务照跑,工具产物全丢,日志里只有
22
22
  * 一句看不出根因的话。这正是「响亮或 fail-closed,禁静默降级」那条要挡的形态。
23
23
  *
24
24
  * 判据用**能力探测**而不是版本号/information_schema:发一条恒空的 `WHERE 1=0` 读,列不在就报错。
25
- * 探不通即拒启,错误文案直接给出两条方言的动作(删表重建),不让运维去猜。
25
+ * 探针读抛且是**可证**的缺列 ⇒ 拒启(其余错误原样上抛,与兄弟探针同一条规则)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift} / {@link schemaWaysOut}),没有销毁性的一条:
26
+ * 缺的出处两列由对账同一套件逐条打印 `ALTER TABLE tool_result ADD COLUMN …`;键宽不够则第 (2) 条是部署者自己把 `ref` 加宽到本构建的定义。
26
27
  */
27
28
  export declare function assertToolResultProvenanceSchema(query: (sql: string) => Promise<{
28
29
  rows: Record<string, unknown>[];
@@ -3,6 +3,8 @@ import { pgSanitizeText } from "./pg-safe-json.js";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
4
  import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
5
5
  import { ensureIndexes } from "./ensure-index.js";
6
+ import { describeProbedTableDrift, schemaWaysOut } from "./schema-drift-sentence.js";
7
+ import { isMissingColumnError } from "./sql-errors.js";
6
8
  export const PG_TOOL_RESULT_SCHEMA = [
7
9
  `CREATE TABLE IF NOT EXISTS tool_result (
8
10
  ref VARCHAR(518) COLLATE "C" NOT NULL,
@@ -28,23 +30,29 @@ export async function assertToolResultProvenanceSchema(query, dialect) {
28
30
  const width = Number(widthRow.rows[0]?.len ?? NaN);
29
31
  if (Number.isFinite(width) && width < MAX_MINTED_TOOL_RESULT_REF_CHARS) {
30
32
  throw new Error(`tool_result.ref is VARCHAR(${width}) but core 5.26.0 mints refs up to ${MAX_MINTED_TOOL_RESULT_REF_CHARS} characters ` +
31
- `(#119 made the ref injective: four \`~\`-separated segments). A truncating primary key aliases two distinct refs onto ` +
32
- `one row — one run's tool output reads back from another run's ref. This repository ships no ALTER TABLE migrations: ` +
33
- `run \`DROP TABLE tool_result;\` on the ${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} backend and restart ` +
34
- `(the schema is recreated at boot; offloaded results are a recoverable-window cache, inline previews are unaffected).`);
33
+ `(#119 made the ref injective: four \`~\`-separated segments) — refusing to start. A truncating primary key aliases two distinct refs onto ` +
34
+ `one row — one run's tool output reads back from another run's ref. ` +
35
+ `The ${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} database is behind this build on that key column; offloaded results are a recoverable-window cache, ` +
36
+ `inline previews in transcripts are unaffected.\n\n` +
37
+ ` table tool_result: column ref is VARCHAR(${width}); this build needs at least VARCHAR(${MAX_MINTED_TOOL_RESULT_REF_CHARS}) — the database is OLDER than this build.\n\n` +
38
+ schemaWaysOut({
39
+ when: "a key column is narrower than this build declares",
40
+ action: "run an ALTER TABLE that widens tool_result.ref to this build's definition yourself",
41
+ }).join("\n"));
35
42
  }
36
43
  try {
37
44
  await query("SELECT owner_session_id, owner_task_id FROM tool_result WHERE 1=0");
38
45
  }
39
46
  catch (err) {
47
+ if (!isMissingColumnError(err, dialect))
48
+ throw err;
40
49
  throw new Error(`tool_result is missing the #119 provenance columns (owner_session_id / owner_task_id) — refusing to start. ` +
41
50
  `This release picks up @sema-agent/core 5.26.0, which changed how an offloaded tool result is addressed ` +
42
- `(the ref grew past the old VARCHAR(190) key) and made the store record who wrote each row. This repository ` +
43
- `ships no ALTER TABLE migrations, so the table must be recreated: run \`DROP TABLE tool_result;\` on the ` +
44
- `${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} backend and restart — the schema is recreated at boot. ` +
45
- `Offloaded tool results are a recoverable-window cache (the inline previews in transcripts are unaffected), ` +
46
- `so the cost is at most the full text of results from runs still in flight. ` +
47
- `Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
51
+ `(the ref grew past the old VARCHAR(190) key) and made the store record who wrote each row. Without the columns every offload write ` +
52
+ `fails with unknown column and core's failure arm turns it into an inline placeholder — the service keeps running and the tool output is lost. ` +
53
+ `Offloaded tool results are a recoverable-window cache (the inline previews in transcripts are unaffected).\n` +
54
+ `Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
55
+ (await describeProbedTableDrift(query, dialect, ["tool_result"])));
48
56
  }
49
57
  }
50
58
  export class SqlToolResultStore {
@@ -1,14 +1,8 @@
1
1
  import { type PlatformPath } from "node:path";
2
- import type { ProjectMemoryLoad } from "@sema-agent/core";
2
+ import type { ProjectMemoryLoad, RunnerDeps } from "@sema-agent/core";
3
3
  import { type InstructionSourceSeat } from "./instruction-source-notice.js";
4
- interface ProjectMemoryCtx {
5
- cwd: string;
6
- handsEnabled: boolean;
7
- isSubagent: boolean;
8
- agentName?: string;
9
- sessionId?: string;
10
- phase?: "fresh" | "resume" | "post-compact" | "post-clear";
11
- }
4
+ /** The engine's loader ctx, read from its seat type (one source: a hand-copied twin kept a phase word the engine had retired). */
5
+ type ProjectMemoryCtx = Parameters<NonNullable<RunnerDeps["loadProjectMemory"]>>[0];
12
6
  /** The instruction-file name chain: the CLAUDE family first, the AGENTS family only when no CLAUDE file is
13
7
  * present. The FIRST hit is the single declared instruction source.
14
8
  *
@@ -37,17 +31,17 @@ export declare function instructionChainCoordinates(absPath: string, p?: Platfor
37
31
  rel: InstructionChainMember;
38
32
  } | null;
39
33
  /**
40
- * Build a `loadProjectMemory` for the host lane. Returns null (skip) unless hands are enabled AND cwd is a local
41
- * directory. Reads the instruction-file chain (CLAUDE.md → .claude/CLAUDE.md → AGENTS.md → .claude/AGENTS.md, [ref]) + (non-subagent)
34
+ * Build a `loadProjectMemory` for the host lane. Answers `{ content: null }` (skip) unless hands are enabled AND cwd is a
35
+ * local directory — every answer is a `ProjectMemoryLoad`, the one form the engine reads. Reads the instruction-file chain (CLAUDE.md → .claude/CLAUDE.md → AGENTS.md → .claude/AGENTS.md, [ref]) + (non-subagent)
42
36
  * `git log -5` / `git status`, redacts secrets, and caps the total. A subagent gets a trimmed instruction file and
43
- * no git narrative (cost control, H7). When an instruction file is hit the return is a `ProjectMemoryLoad`
44
- * declaring it in `instructionSources` (path + sha256 over the raw bounded-read window) so core's
37
+ * no git narrative (cost control, H7). When an instruction file is hit the answer also declares it in
38
+ * `instructionSources` (path + sha256 over the raw bounded-read window) so core's
45
39
  * instructions-change lane can probe it mid-run.
46
40
  *
47
41
  * Session snapshot cache (S-2): keyed on ctx.sessionId — a session keeps ONE stable instruction snapshot
48
42
  * (mid-run file edits surface via the probe as tail attachments, never a silently re-rendered prefix).
49
43
  * `phase: "post-compact"` forces a re-read (core re-renders the prefix there anyway). No entry for
50
- * sessionless one-shot runs. (post-clear never arrives from core — [ref] — so it is not a cache key here.)
44
+ * sessionless one-shot runs.
51
45
  *
52
46
  * [ref] — source-change notice: with `instructionSource` wired, every prepare of a non-subagent session that WALKED the
53
47
  * instruction chain hands the chain member it selected (`null` = none) to {@link announceInstructionSource}, which
@@ -72,7 +66,7 @@ export declare function makeLoadProjectMemory(opts: {
72
66
  cacheMax?: number;
73
67
  /** [ref]: the baseline store + notice exit. Absent ⇒ no source-change notice (the session store has no baseline face). */
74
68
  instructionSource?: InstructionSourceSeat;
75
- }): (ctx: ProjectMemoryCtx) => Promise<string | null | ProjectMemoryLoad>;
69
+ }): (ctx: ProjectMemoryCtx) => Promise<ProjectMemoryLoad>;
76
70
  /**
77
71
  * ② The `RunnerDeps.probeInstructionSources` seam: re-fingerprint the declared instruction files mid-run so
78
72
  * core's instructions-change lane can append a tail attachment when one changes. Same read posture as the
@@ -42,7 +42,8 @@ function instructionHash(raw) {
42
42
  return `hmac-sha256:${createHmac("sha256", INSTRUCTION_HASH_KEY).update(raw).digest("hex")}`;
43
43
  }
44
44
  const ABSENT_HASH = null;
45
- const NOT_APPLICABLE = { walked: false, answer: null };
45
+ const NOTHING_TO_INJECT = Object.freeze({ content: null });
46
+ const NOT_APPLICABLE = { walked: false, answer: NOTHING_TO_INJECT };
46
47
  export function instructionChainCoordinates(absPath, p = hostPath) {
47
48
  const candidates = INSTRUCTION_FILE_CHAIN.filter((rel) => absPath.endsWith(p.sep + rel.split("/").join(p.sep))).sort((a, b) => b.length - a.length);
48
49
  for (const rel of candidates) {
@@ -218,12 +219,12 @@ export function makeLoadProjectMemory(opts) {
218
219
  if (gitNarrative)
219
220
  sections.push(gitNarrative);
220
221
  if (sections.length === 0)
221
- return { walked: true, answer: null, instructionFile };
222
+ return { walked: true, answer: NOTHING_TO_INJECT, instructionFile };
222
223
  const redacted = redactSecrets(sections.join("\n\n"));
223
224
  const capped = capUtf8(redacted, totalCap);
224
225
  opts.logger?.debug?.("project_memory_loaded", { cwd: root, bytes: Buffer.byteLength(capped, "utf8"), isSubagent: ctx.isSubagent, gitOk, ...(gitDropReason ? { gitDropReason } : {}), claudeTruncated, ...(hitFile ? { instructionFile: hitFile } : {}), ...(ctx.phase ? { phase: ctx.phase } : {}), cacheSize: cache.size });
225
226
  if (!capped)
226
- return { walked: true, answer: null, instructionFile };
227
+ return { walked: true, answer: NOTHING_TO_INJECT, instructionFile };
227
228
  if (hitFile && hitHash) {
228
229
  return {
229
230
  walked: true,
@@ -237,7 +238,7 @@ export function makeLoadProjectMemory(opts) {
237
238
  instructionFile,
238
239
  };
239
240
  }
240
- return { walked: true, answer: capped, instructionFile };
241
+ return { walked: true, answer: { content: capped }, instructionFile };
241
242
  }
242
243
  }
243
244
  export function makeProbeInstructionSources() {
@@ -28,7 +28,7 @@ const TASK_BODY_DECLARED_KEYS = [
28
28
  "maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries", "outputStyle",
29
29
  "outputSchema", "permissionMode", "projectId", "projectMemory", "promptLean", "reasoningEffort", "requireExistingSession",
30
30
  "resilience", "restoreFiles", "resumeAt", "resumeAtMode", "retainBackgroundProcesses", "retainSubagentSessions",
31
- "rewindFiles", "rewindFilesTo", "sandboxImageProfile", "scenario", "scratchpadDir", "selfOrchestration",
31
+ "rewindFilesTo", "sandboxImageProfile", "scenario", "scratchpadDir", "selfOrchestration",
32
32
  "sessionId", "settings", "skills", "suggestNextPrompts", "systemPrompt", "taskId", "toolMaterializeStrategy",
33
33
  "userAttachments", "userInvokedSkills", "verify", "verifyRounds",
34
34
  ];
@@ -67,18 +67,17 @@ export declare const PROJECT_MEMORY_OFF_SPELLING_MESSAGE = "projectMemory must b
67
67
  * 判据:{@link fileHistoryRestoreConflict}。**只在 fresh 受理门**(`http/admission.ts`);续跑腿不过这道门(RESUME 重放不过受理门,
68
68
  * 同律 —— 且续跑腿重建时整族剥掉一次性历史动作,矛盾在盘上的体里本就留不下来)。wire 可见、字节冻结。
69
69
  */
70
- export declare const FILE_HISTORY_DISABLED_NO_RESTORE = "fileHistory: 'disabled' cannot ride with a file restore (restoreFiles, rewindFilesTo, or the retired rewindFiles) \u2014 the request turns this run's file history off and asks to restore files from it in the same breath; the engine would refuse it (rewind.capture_disabled) only after the session had already been branched at resumeAt, which it does not undo, so it is refused here, before any side effect. Drop fileHistory to restore, or drop the restore to run without file history";
70
+ export declare const FILE_HISTORY_DISABLED_NO_RESTORE = "fileHistory: 'disabled' cannot ride with a file restore (restoreFiles or rewindFilesTo) \u2014 the request turns this run's file history off and asks to restore files from it in the same breath; the engine would refuse it (rewind.capture_disabled) only after the session had already been branched at resumeAt, which it does not undo, so it is refused here, before any side effect. Drop fileHistory to restore, or drop the restore to run without file history";
71
71
  /**
72
- * 「关断词 ∧ 还原」这一对的判据(纯函数,只看体)。还原 = 三个键里任一个在请求:`restoreFiles: true`、非空串 `rewindFilesTo`
73
- * (与 `boot/resolve-spec.ts` 的读法同:空串 / 非串按缺席)、退役拼法 `rewindFiles: true`(restore 义要一次还原;capture 义要的
74
- * 正是这个词关掉的记录 —— 两义都与关断词矛盾)。调用前值形已由上面的单成员表与既有布尔门判过。
72
+ * 「关断词 ∧ 还原」这一对的判据(纯函数,只看体)。还原 = 两个键里任一个在请求:`restoreFiles: true`、非空串 `rewindFilesTo`
73
+ * (与 `boot/resolve-spec.ts` 的读法同:空串 / 非串按缺席)。调用前值形已由上面的单成员表与既有布尔门判过。
75
74
  */
76
- export declare function fileHistoryRestoreConflict(body: Pick<TaskRequestBody, "fileHistory" | "restoreFiles" | "rewindFiles" | "rewindFilesTo">): boolean;
75
+ export declare function fileHistoryRestoreConflict(body: Pick<TaskRequestBody, "fileHistory" | "restoreFiles" | "rewindFilesTo">): boolean;
77
76
  /**
78
- * 还原键里请求**真带了的**那几个(顺序固定:`restoreFiles` · `rewindFilesTo` · 退役的 `rewindFiles`)。{@link fileHistoryRestoreConflict} 的「还原」一半就是它非空 ——
77
+ * 还原键里请求**真带了的**那几个(顺序固定:`restoreFiles` · `rewindFilesTo`)。{@link fileHistoryRestoreConflict} 的「还原」一半就是它非空 ——
79
78
  * 判据只有这一份,拒体的 `detail.fields` 点名的也是它(判与点名不会各读各的)。
80
79
  */
81
- export declare function fileHistoryRestoreKeysInBody(body: Pick<TaskRequestBody, "restoreFiles" | "rewindFiles" | "rewindFilesTo">): Array<"restoreFiles" | "rewindFilesTo" | "rewindFiles">;
80
+ export declare function fileHistoryRestoreKeysInBody(body: Pick<TaskRequestBody, "restoreFiles" | "rewindFilesTo">): Array<"restoreFiles" | "rewindFilesTo">;
82
81
  /**
83
82
  * **单成员闭集键表** —— 每键合法值只有它自己那一个关断词(`word`),缺席=照常,没有「开」的拼法。
84
83
  *
@@ -2,7 +2,7 @@ export const MEMORY_CAPTURE_SPELLING_MESSAGE = "memoryCapture must be the string
2
2
  export const MEMORY_OFF_SPELLING_MESSAGE = "memory must be the string 'off' when present (the request-level declaration that this run mounts no memory face at all; absent = memory as usual — there is no 'on' spelling; to keep memory mounted but stop this session's content from entering long-term memory, send memoryCapture: 'off' instead)";
3
3
  export const FILE_HISTORY_OFF_SPELLING_MESSAGE = "fileHistory must be the string 'disabled' when present (the request-level declaration that this run records no file history — no backup of an edited file, and no file restore in the same request; absent = the deployment's setting — there is no 'enabled' spelling)";
4
4
  export const PROJECT_MEMORY_OFF_SPELLING_MESSAGE = "projectMemory must be the string 'disabled' when present (the request-level declaration that this run reads none of the deployment's project context — no project instruction files and no project layer; absent = the deployment's project context as usual — there is no 'on' spelling)";
5
- export const FILE_HISTORY_DISABLED_NO_RESTORE = "fileHistory: 'disabled' cannot ride with a file restore (restoreFiles, rewindFilesTo, or the retired rewindFiles) — the request turns this run's file history off and asks to restore files from it in the same breath; the engine would refuse it (rewind.capture_disabled) only after the session had already been branched at resumeAt, which it does not undo, so it is refused here, before any side effect. Drop fileHistory to restore, or drop the restore to run without file history";
5
+ export const FILE_HISTORY_DISABLED_NO_RESTORE = "fileHistory: 'disabled' cannot ride with a file restore (restoreFiles or rewindFilesTo) — the request turns this run's file history off and asks to restore files from it in the same breath; the engine would refuse it (rewind.capture_disabled) only after the session had already been branched at resumeAt, which it does not undo, so it is refused here, before any side effect. Drop fileHistory to restore, or drop the restore to run without file history";
6
6
  export function fileHistoryRestoreConflict(body) {
7
7
  return body.fileHistory === "disabled" && fileHistoryRestoreKeysInBody(body).length > 0;
8
8
  }
@@ -12,8 +12,6 @@ export function fileHistoryRestoreKeysInBody(body) {
12
12
  out.push("restoreFiles");
13
13
  if (typeof body.rewindFilesTo === "string" && body.rewindFilesTo.length > 0)
14
14
  out.push("rewindFilesTo");
15
- if (body.rewindFiles === true)
16
- out.push("rewindFiles");
17
15
  return out;
18
16
  }
19
17
  const SINGLE_MEMBER_OFF_FIELDS = [
@@ -196,6 +196,12 @@ export declare function promptYesNoOnTty(prompt: string, signal?: AbortSignal, s
196
196
  * or whose approval no person could answer, see {@link unfinishedStopOf} — / a fatal error, 2 = bad usage / unmet env). Pure of
197
197
  * `process.exit` so a test can assert the code; `main()` applies it.
198
198
  */
199
+ /**
200
+ * 本入口拒 `CONFIG_PROVIDER=remote` 的那一句(配置源的词要么兑现、要么拒)。本入口只装本地配置源:中心拉取的那一套 —— 凭据续签、托管模型钥解封、
201
+ * 名册门、LKG —— 只在 HTTP 服务里装配。修前它在 remote 的词下照读本地 `config.d`(判官对「没递中心」回落 local),零句。
202
+ * 配没配中心地址都是这一句:出路(不设词 / 写 `local` / 改跑 HTTP 服务)在本入口照做都兑现得了。
203
+ */
204
+ export declare const RUN_LOCAL_CONFIG_SOURCE_REFUSAL: string;
199
205
  export declare function runLocal(argv: string[], deps?: RunLocalDeps): Promise<number>;
200
206
  /** {@link installRunLocalExitBoundary} 的把手(同进程内反复装配的调用方=测试必须用它收尾)。 */
201
207
  export interface RunLocalExitBoundary {