@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
@@ -1,15 +1,19 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
3
  import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf } from "./session-ownership.js";
4
- import { FileSessionRepo, SessionError, StoredSession, settledForkOptions, validateEntriesForImport } from "@sema-agent/core";
4
+ import { FileSessionRepo, SessionError, StoredSession, sessionLogMarkOf, settledForkOptions, validateEntriesForImport } from "@sema-agent/core";
5
5
  import { z } from "zod";
6
6
  import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
7
+ import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "../session-row.js";
7
8
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
8
9
  import { createZodColumnReader, decodeJsonColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
9
10
  import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./one-time-migrations.js";
10
11
  import { createLogger } from "../observability/logger.js";
11
12
  import { UNMANAGED_RETENTION } from "./retention-store-sql.js";
12
- import { UnreadableLeaf, carriedIncarnation, headFromRow, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
13
+ import { UnreadableLeaf, carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
14
+ import { isExecutionLane } from "../execution-lane-caps.js";
15
+ import { SessionForkInProgressError, SessionWriterPresentError, WRITABLE, assertPlaceableLane, isUnusableArchive, settledMirrorOutcome, settledPlaceOutcome, } from "../execution-placement.js";
16
+ import { classifyForkTarget, forkByFreshId } from "./session-fork.js";
13
17
  import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
14
18
  import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
15
19
  import { noteReplayInSessionOpenScope, openSessionForItself } from "../session-records-skipped-notice.js";
@@ -18,6 +22,35 @@ import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.
18
22
  import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
19
23
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
20
24
  const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
25
+ function archiveFaultWord(code) {
26
+ switch (code) {
27
+ case "not_found":
28
+ return "absent";
29
+ case "corrupt_session":
30
+ case "invalid_session":
31
+ return code;
32
+ case "invalid_entry":
33
+ case "invalid_fork_target":
34
+ case "storage":
35
+ case "conflict":
36
+ case "placement_refused":
37
+ case "unknown":
38
+ return "storage";
39
+ default: {
40
+ const unknownCode = code;
41
+ throw new Error(`LocalSessionStore: no archive state for session error code ${JSON.stringify(unknownCode)}`);
42
+ }
43
+ }
44
+ }
45
+ function unusableArchiveOf(word, fault) {
46
+ const code = fault instanceof SessionError ? undefined : fault?.code;
47
+ return { state: "unusable", word, fault, ...(word === "storage" && typeof code === "string" ? { errno: code } : {}) };
48
+ }
49
+ function importRowRefused(sessionId, landing) {
50
+ return new SessionError("conflict", landing === "create"
51
+ ? `session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`
52
+ : `session ${sessionId} is not the importer's row — an import replaces only a log whose row the importer owns (or an anonymous row, by an anonymous import); nothing was written`);
53
+ }
21
54
  const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
22
55
  function userText(entry) {
23
56
  if (entry.type !== "message")
@@ -38,11 +71,24 @@ function userText(entry) {
38
71
  }
39
72
  const SIDECAR_DEGRADES = { lastActivity: "recency", title: "title", pinned: "pin state", "instruction-source": "instruction-source baseline", workspace: "each session's last recorded place and project (the next frame records it again)", "session-meta": "session owner + incarnation (the write that needed it failed loudly)" };
40
73
  const readPinnedSidecar = createZodColumnReader(z.record(z.string(), z.literal(true)));
74
+ const MIRROR_CELL = { role: "mirror" };
41
75
  function readSessionMetaCell(cell) {
42
- return typeof cell === "string" ? { incarnation: cell, owner: undefined } : cell;
76
+ return typeof cell === "string" ? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined } : { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey };
43
77
  }
44
78
  const ownerRecorded = (cell) => cell !== undefined && readSessionMetaCell(cell).owner !== undefined;
45
- const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), z.object({ incarnation: z.string(), owner: z.string().nullable() }).strict()]));
79
+ function cellWith(before, bits) {
80
+ const base = typeof before === "string" ? { incarnation: before } : before;
81
+ return { ...base, ...("owner" in bits ? { owner: bits.owner } : {}), ...(bits.execution !== undefined ? { execution: bits.execution } : {}) };
82
+ }
83
+ const isForkCreatedCell = (meta) => meta !== undefined && meta.forkKey !== undefined && meta.execution === undefined;
84
+ function readingOfCell(sessionId, meta, forking) {
85
+ return { occupant: sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation), cell: forking ? { role: "forking" } : (meta.execution ?? { role: "absent" }) };
86
+ }
87
+ const LOCAL_EXECUTION_CELL = z.discriminatedUnion("role", [
88
+ z.object({ role: z.literal("writer"), lane: z.custom((v) => typeof v === "string" && isExecutionLane(v), { message: "not an execution lane" }) }).strict(),
89
+ z.object({ role: z.literal("mirror") }).strict(),
90
+ ]);
91
+ const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), z.object({ incarnation: z.string(), owner: z.string().nullable().optional(), execution: LOCAL_EXECUTION_CELL.optional(), forkKey: z.string().optional() }).strict()]));
46
92
  const readSessionMetaSidecar = createZodColumnReader(SESSION_META_SIDECAR);
47
93
  function ownerClaimWindowOpen() {
48
94
  const window = ONE_TIME_MIGRATIONS.find((w) => w.id === "M10");
@@ -185,6 +231,8 @@ export class LocalSessionStore {
185
231
  meta;
186
232
  log;
187
233
  sessionWriteLocks;
234
+ executionPlacement;
235
+ executionPlacementBackfill;
188
236
  constructor(repo, runs, dataDir, opts) {
189
237
  this.repo = repo;
190
238
  this.runs = runs;
@@ -200,6 +248,86 @@ export class LocalSessionStore {
200
248
  if (root !== undefined && !SESSION_WRITE_LOCKS_BY_ROOT.has(root))
201
249
  SESSION_WRITE_LOCKS_BY_ROOT.set(root, new Map());
202
250
  this.sessionWriteLocks = root !== undefined ? SESSION_WRITE_LOCKS_BY_ROOT.get(root) : new Map();
251
+ this.executionPlacement = {
252
+ read: (sessionId) => this.readExecutionPlacement(sessionId),
253
+ place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
254
+ markMirror: (sessionId, occupant) => this.markMirrorExecution(sessionId, occupant),
255
+ };
256
+ this.executionPlacementBackfill = {
257
+ read: (sessionId) => this.placementRead(sessionId),
258
+ place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
259
+ };
260
+ }
261
+ async forkingRead(sessionId, meta) {
262
+ if (!isForkCreatedCell(meta))
263
+ return false;
264
+ const probe = await this.probeArchive(sessionId);
265
+ if (probe.state === "unusable" && probe.word === "storage")
266
+ return probe;
267
+ return probe.state === "absent";
268
+ }
269
+ async forkingOf(sessionId, meta) {
270
+ const forking = await this.forkingRead(sessionId, meta);
271
+ if (typeof forking !== "boolean")
272
+ throw forking.fault;
273
+ return forking;
274
+ }
275
+ async placementRead(sessionId) {
276
+ const read = await this.metaRead(sessionId);
277
+ if (read.state === "unusable")
278
+ return read;
279
+ if (read.state === "absent")
280
+ return undefined;
281
+ const forking = await this.forkingRead(sessionId, read.meta);
282
+ return typeof forking === "boolean" ? readingOfCell(sessionId, read.meta, forking) : forking;
283
+ }
284
+ async readExecutionPlacement(sessionId) {
285
+ const reading = await this.placementRead(sessionId);
286
+ if (isUnusableArchive(reading))
287
+ throw reading.fault;
288
+ return reading;
289
+ }
290
+ async placeExecution(sessionId, occupant, cell) {
291
+ assertPlaceableLane(sessionId, cell);
292
+ return this.withSessionWriteLock(sessionId, () => this.settleExecutionHeld(sessionId, (reading) => {
293
+ const settled = settledPlaceOutcome(sessionId, reading, occupant, cell);
294
+ return settled === WRITABLE ? { outcome: { outcome: "placed" }, execution: { role: "writer", lane: cell.lane } } : { outcome: settled };
295
+ }));
296
+ }
297
+ async markMirrorExecution(sessionId, occupant) {
298
+ return this.withSessionWriteLock(sessionId, async () => {
299
+ await this.meta.ensureLoaded();
300
+ const claim = this.metaCellOf(sessionId) === undefined ? undefined : await this.runs.getActiveTaskId(sessionId);
301
+ return this.settleExecutionHeld(sessionId, (reading) => {
302
+ const settled = settledMirrorOutcome(sessionId, reading, occupant);
303
+ if (settled !== WRITABLE)
304
+ return { outcome: settled };
305
+ return claim === undefined ? { outcome: { outcome: "mirrored" }, execution: MIRROR_CELL } : { outcome: { outcome: "active", activeTaskId: claim } };
306
+ });
307
+ });
308
+ }
309
+ async settleExecutionHeld(sessionId, judge) {
310
+ await this.meta.ensureLoaded();
311
+ const forking = await this.forkingOf(sessionId, this.metaCellOf(sessionId));
312
+ let settled;
313
+ let fault;
314
+ await this.writeMeta(sessionId, (before) => {
315
+ try {
316
+ settled = judge(before === undefined ? undefined : readingOfCell(sessionId, readSessionMetaCell(before), forking));
317
+ }
318
+ catch (e) {
319
+ fault = e;
320
+ return undefined;
321
+ }
322
+ return settled.execution === undefined || before === undefined ? undefined : cellWith(before, { execution: settled.execution });
323
+ });
324
+ if (fault !== undefined)
325
+ throw fault;
326
+ return settled.outcome;
327
+ }
328
+ metaCellOf(sessionId) {
329
+ const cell = this.meta.state.entries.get(sessionId);
330
+ return cell === undefined ? undefined : readSessionMetaCell(cell);
203
331
  }
204
332
  async writeMeta(sessionId, next) {
205
333
  await this.meta.ensureLoaded();
@@ -234,29 +362,44 @@ export class LocalSessionStore {
234
362
  locks.delete(sessionId);
235
363
  }
236
364
  }
237
- async existsInRepo(sessionId) {
365
+ async probeArchive(sessionId) {
238
366
  try {
239
367
  await this.repo.open({ id: sessionId, createdAt: "" });
240
- return true;
368
+ return { state: "readable" };
241
369
  }
242
370
  catch (e) {
243
- if (isNotFound(e))
244
- return false;
245
- throw e;
371
+ const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
372
+ return word === "absent" ? { state: "absent" } : unusableArchiveOf(word, e);
246
373
  }
247
374
  }
375
+ async existsInRepo(sessionId) {
376
+ const probe = await this.probeArchive(sessionId);
377
+ if (probe.state === "unusable")
378
+ throw probe.fault;
379
+ return probe.state === "readable";
380
+ }
248
381
  async metaOf(sessionId) {
382
+ const read = await this.metaRead(sessionId);
383
+ if (read.state === "unusable")
384
+ throw read.fault;
385
+ return read.state === "found" ? read.meta : undefined;
386
+ }
387
+ async metaRead(sessionId) {
249
388
  await this.meta.ensureLoaded();
250
389
  const cell = this.meta.state.entries.get(sessionId);
251
390
  if (cell !== undefined)
252
- return readSessionMetaCell(cell);
253
- if (!(await this.existsInRepo(sessionId)))
254
- return undefined;
255
- return this.cellOfPresent(sessionId);
391
+ return { state: "found", meta: readSessionMetaCell(cell) };
392
+ const probe = await this.probeArchive(sessionId);
393
+ if (probe.state === "unusable")
394
+ return probe;
395
+ if (probe.state === "absent")
396
+ return { state: "absent" };
397
+ const meta = await this.cellOfPresent(sessionId);
398
+ return meta === undefined ? { state: "absent" } : { state: "found", meta };
256
399
  }
257
400
  async cellOfPresent(sessionId) {
258
401
  if (!this.meta.state.entries.has(sessionId))
259
- await this.writeMeta(sessionId, (before) => (before === undefined ? mintSessionIncarnation() : undefined));
402
+ await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation: mintSessionIncarnation() } : undefined));
260
403
  const cell = this.meta.state.entries.get(sessionId);
261
404
  return cell === undefined ? undefined : readSessionMetaCell(cell);
262
405
  }
@@ -282,55 +425,86 @@ export class LocalSessionStore {
282
425
  return this.titles.state.entries.has(sessionId) ? "titled" : "untitled";
283
426
  }
284
427
  async setTitleIfNull(sessionId, title) {
285
- await this.titles.ensureLoaded();
286
- const entries = this.titles.state.entries;
287
- if (entries.has(sessionId))
288
- return false;
289
- entries.set(sessionId, title);
290
- await this.titles.persist();
291
- return true;
428
+ return this.withSessionWriteLock(sessionId, async () => {
429
+ await this.titles.ensureLoaded();
430
+ const entries = this.titles.state.entries;
431
+ if (entries.has(sessionId))
432
+ return false;
433
+ entries.set(sessionId, title);
434
+ await this.titles.persist();
435
+ return true;
436
+ });
292
437
  }
293
- async patchSessionMeta(sessionId, patch, face) {
438
+ async writeSessionRowCells(sessionId, cells, how) {
294
439
  return this.withSessionWriteLock(sessionId, async () => {
295
- await this.meta.ensureLoaded();
296
- if (!this.occupiedBy(sessionId, face) || (await this.listedMeta(sessionId)) === undefined)
297
- return undefined;
298
- const { title, pinned } = patch;
299
- if (title !== undefined) {
300
- await this.titles.ensureLoaded();
301
- const ok = await this.titles.commit((draft) => {
302
- if (title === null)
303
- return draft.delete(sessionId);
304
- if (draft.get(sessionId) === title)
305
- return false;
306
- draft.set(sessionId, title);
307
- return true;
308
- });
309
- if (!ok)
310
- throw new SessionError("storage", `LocalSessionStore: title sidecar write failed for session ${sessionId} — the title was not recorded`);
311
- }
312
- if (pinned !== undefined) {
313
- await this.pinned.ensureLoaded();
314
- const ok = await this.pinned.commit((draft) => {
315
- if (!pinned)
316
- return draft.delete(sessionId);
317
- if (draft.get(sessionId) === true)
318
- return false;
319
- draft.set(sessionId, true);
320
- return true;
321
- });
322
- if (!ok)
323
- throw new SessionError("storage", `LocalSessionStore: pin sidecar write failed for session ${sessionId} — the pin was not recorded`);
324
- }
325
- const rollups = await this.runRollups();
326
440
  await this.loadRowSidecars();
327
- const meta = await this.listedMeta(sessionId);
328
- return meta === undefined ? undefined : this.summaryOf(meta, rollups);
441
+ const locked = await this.lockedRowOf(sessionId);
442
+ const mark = how.row.log === undefined || locked === undefined ? undefined : sessionLogMarkOf((await this.exportEntries(sessionId)) ?? []);
443
+ const verdict = judgeSessionRowWrite(sessionId, how, locked, () => {
444
+ if (mark === undefined)
445
+ throw new Error(`LocalSessionStore.writeSessionRowCells: the judge asked session ${sessionId}'s log mark, but the expectation carries none`);
446
+ return mark;
447
+ });
448
+ switch (verdict) {
449
+ case "absent":
450
+ return { kind: "absent" };
451
+ case "mirror":
452
+ return { kind: "mirror" };
453
+ case "write":
454
+ await this.writeRowCellsHeld(sessionId, cells);
455
+ return settleSessionRowWrite(how, verdict, await this.listRowOf(sessionId));
456
+ case "changed":
457
+ return settleSessionRowWrite(how, verdict, await this.listRowOf(sessionId));
458
+ default: {
459
+ const unknownVerdict = verdict;
460
+ throw new Error(`LocalSessionStore.writeSessionRowCells: no verdict ${JSON.stringify(unknownVerdict)}`);
461
+ }
462
+ }
329
463
  });
330
464
  }
331
- occupiedBy(sessionId, face) {
332
- const cell = this.meta.state.entries.get(sessionId);
333
- return cell !== undefined && readSessionMetaCell(cell).incarnation === face.incarnation;
465
+ async lockedRowOf(sessionId) {
466
+ const meta = this.metaCellOf(sessionId);
467
+ if (meta === undefined || (await this.listedMeta(sessionId)) === undefined)
468
+ return undefined;
469
+ return { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), placed: false, mirror: meta.execution?.role === "mirror", cells: this.rowCellsOf(sessionId) };
470
+ }
471
+ rowCellsOf(sessionId) {
472
+ return { title: this.titles.state.entries.get(sessionId) ?? null, pinned: this.pinned.state.entries.get(sessionId) === true };
473
+ }
474
+ async writeRowCellsHeld(sessionId, cells) {
475
+ const { title, pinned } = cells;
476
+ if (title !== undefined) {
477
+ const ok = await this.titles.commit((draft) => {
478
+ if (title === null)
479
+ return draft.delete(sessionId);
480
+ if (draft.get(sessionId) === title)
481
+ return false;
482
+ draft.set(sessionId, title);
483
+ return true;
484
+ });
485
+ if (!ok)
486
+ throw new SessionError("storage", `LocalSessionStore: title sidecar write failed for session ${sessionId} — the title was not recorded`);
487
+ }
488
+ if (pinned !== undefined) {
489
+ const ok = await this.pinned.commit((draft) => {
490
+ if (!pinned)
491
+ return draft.delete(sessionId);
492
+ if (draft.get(sessionId) === true)
493
+ return false;
494
+ draft.set(sessionId, true);
495
+ return true;
496
+ });
497
+ if (!ok)
498
+ throw new SessionError("storage", `LocalSessionStore: pin sidecar write failed for session ${sessionId} — the pin was not recorded`);
499
+ }
500
+ }
501
+ async listRowOf(sessionId) {
502
+ await this.loadRowSidecars();
503
+ const listed = await this.listedMeta(sessionId);
504
+ const meta = this.metaCellOf(sessionId);
505
+ if (listed === undefined || meta === undefined)
506
+ return undefined;
507
+ return { row: await this.summaryOf(listed, await this.runRollups()), occupant: { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent") } };
334
508
  }
335
509
  async sessionSummaryOf(sessionId) {
336
510
  await this.loadRowSidecars();
@@ -472,6 +646,8 @@ export class LocalSessionStore {
472
646
  throw e;
473
647
  }
474
648
  await this.meta.ensureLoaded();
649
+ if (isForkCreatedCell(this.metaCellOf(sessionId)))
650
+ throw new SessionForkInProgressError(sessionId);
475
651
  const session = await this.repo.create({ id: sessionId });
476
652
  await this.writeMeta(sessionId, (before) => ({ incarnation: mintSessionIncarnation(), owner: before !== undefined && ownerRecorded(before) ? (readSessionMetaCell(before).owner ?? null) : null }));
477
653
  return this.handOut(sessionId, session);
@@ -489,13 +665,17 @@ export class LocalSessionStore {
489
665
  return;
490
666
  assertFirstOwnerWritable(sessionId, owner);
491
667
  const existed = await this.existsInRepo(sessionId);
668
+ if (!existed && isForkCreatedCell(this.metaCellOf(sessionId)))
669
+ throw new SessionForkInProgressError(sessionId);
492
670
  const claim = existed && owner !== null && ownerClaimWindowOpen();
493
671
  const wrote = await this.writeMeta(sessionId, (before) => {
494
672
  if (ownerRecorded(before))
495
673
  return undefined;
496
- if (!existed || claim)
497
- return { incarnation: existed && before !== undefined ? readSessionMetaCell(before).incarnation : mintSessionIncarnation(), owner };
498
- return before === undefined ? mintSessionIncarnation() : undefined;
674
+ if (claim)
675
+ return before === undefined ? { incarnation: mintSessionIncarnation(), owner } : cellWith(before, { owner });
676
+ if (!existed)
677
+ return { incarnation: mintSessionIncarnation(), owner };
678
+ return before === undefined ? { incarnation: mintSessionIncarnation() } : undefined;
499
679
  });
500
680
  if (!existed && wrote)
501
681
  await this.repo.create({ id: sessionId });
@@ -544,17 +724,29 @@ export class LocalSessionStore {
544
724
  }
545
725
  async summaryOf(meta, rollups) {
546
726
  const firstActivityAt = meta.createdAt || new Date(0).toISOString();
727
+ const owner = this.ownerCellOf(meta.id);
728
+ const cells = this.rowCellsOf(meta.id);
729
+ const rowTag = this.rowTagKeyOf(meta.id, owner, cells);
730
+ const lastActivityAt = this.lastActivity.get(meta.id) ?? firstActivityAt;
731
+ const objectivePreview = await this.previewOf(meta);
547
732
  return {
548
733
  sessionId: meta.id,
549
- owner: this.ownerCellOf(meta.id),
550
- lastActivityAt: this.lastActivity.get(meta.id) ?? firstActivityAt,
734
+ owner,
735
+ lastActivityAt,
551
736
  firstActivityAt,
552
- objectivePreview: await this.previewOf(meta),
737
+ objectivePreview,
553
738
  ...this.runProjectionOf(meta.id, rollups),
554
- title: this.titles.state.entries.get(meta.id) ?? null,
555
- pinned: this.pinned.state.entries.get(meta.id) === true,
739
+ title: cells.title,
740
+ pinned: cells.pinned,
741
+ ...rowTag,
556
742
  };
557
743
  }
744
+ rowTagKeyOf(sessionId, owner, cells) {
745
+ const meta = this.metaCellOf(sessionId);
746
+ if (meta === undefined)
747
+ return {};
748
+ return sessionRowTagKey({ sessionId, owner, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), cells });
749
+ }
558
750
  async runRollups() {
559
751
  const rows = await this.runs.listSessions({ limit: Number.MAX_SAFE_INTEGER });
560
752
  return new Map(rows.map((r) => [r.sessionId, { runCount: r.runCount, lastStatus: r.lastStatus, lastRunId: r.lastRunId, ...(r.lastTaskRunId !== undefined ? { lastTaskRunId: r.lastTaskRunId } : {}) }]));
@@ -579,24 +771,52 @@ export class LocalSessionStore {
579
771
  }
580
772
  }
581
773
  async fork(sourceId, owner) {
774
+ return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner);
775
+ }
776
+ async forkTo(sourceId, owner, target) {
582
777
  await this.meta.ensureLoaded();
583
- return this.withSessionWriteLock(sourceId, async () => {
584
- const source = this.meta.state.entries.get(sourceId);
585
- const forkOwner = forkOwnerOf(owner, source === undefined ? undefined : readSessionMetaCell(source).owner);
778
+ if (target.id === sourceId)
779
+ return this.withSessionWriteLock(sourceId, () => this.forkToHeld(sourceId, owner, target));
780
+ const [first, second] = sourceId < target.id ? [sourceId, target.id] : [target.id, sourceId];
781
+ return this.withSessionWriteLock(first, () => this.withSessionWriteLock(second, () => this.forkToHeld(sourceId, owner, target)));
782
+ }
783
+ async forkToHeld(sourceId, owner, target) {
784
+ const sourceMeta = { id: sourceId, createdAt: "" };
785
+ let forkOptions;
786
+ try {
787
+ forkOptions = await settledForkOptions(await this.repo.open(sourceMeta));
788
+ }
789
+ catch (e) {
790
+ if (isNotFound(e))
791
+ return null;
792
+ throw e;
793
+ }
794
+ const forkOwner = forkOwnerOf(owner, this.metaCellOf(sourceId)?.owner);
795
+ const existing = this.metaCellOf(target.id);
796
+ const targetArchived = await this.existsInRepo(target.id);
797
+ const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id });
798
+ if (existing === undefined && !targetArchived) {
799
+ const created = await this.writeMeta(target.id, (before) => before !== undefined ? undefined : { incarnation: mintSessionIncarnation(), ...(forkOwner !== undefined ? { owner: forkOwner } : {}), ...(target.key !== undefined ? { forkKey: target.key } : {}) });
800
+ if (!created)
801
+ throw new SessionError("storage", `LocalSessionStore: fork target ${target.id} gained a session-meta cell under its own write lock — refusing to guess`);
586
802
  try {
587
- const sourceMeta = { id: sourceId, createdAt: "" };
588
- const forkOptions = await settledForkOptions(await this.repo.open(sourceMeta));
589
- const session = await this.repo.fork(sourceMeta, forkOptions);
590
- const id = (await session.getMetadata()).id;
591
- await this.writeMeta(id, () => (forkOwner === undefined ? mintSessionIncarnation() : { incarnation: mintSessionIncarnation(), owner: forkOwner }));
592
- return id;
803
+ await copy();
593
804
  }
594
- catch (e) {
595
- if (isNotFound(e))
596
- return null;
597
- throw e;
805
+ catch (err) {
806
+ try {
807
+ await this.dropMeta(target.id);
808
+ }
809
+ catch (dropErr) {
810
+ throw new SessionError("storage", `LocalSessionStore: fork into ${target.id} failed while copying (${err instanceof Error ? err.message : String(err)}) and its target cell could not be withdrawn (${dropErr instanceof Error ? dropErr.message : String(dropErr)}) — the target stays in the fork-in-progress state; retry the same fork or delete the session`, err instanceof Error ? err : undefined);
811
+ }
812
+ throw err;
598
813
  }
599
- });
814
+ return { outcome: "created", id: target.id };
815
+ }
816
+ const out = classifyForkTarget(target, { owner: existing?.owner, forkKey: existing?.forkKey }, forkOwner);
817
+ if (out.outcome === "replayed" && !targetArchived)
818
+ await copy();
819
+ return out;
600
820
  }
601
821
  async exportEntries(sessionId) {
602
822
  if (!this.repo.exportEntries)
@@ -627,43 +847,91 @@ export class LocalSessionStore {
627
847
  return gen();
628
848
  }
629
849
  async importEntries(sessionId, owner, entries) {
630
- await this.withImportLock(sessionId, owner, (importer) => this.importHeld(sessionId, importer, entries, "in_place"));
850
+ await this.withImportLock(sessionId, owner, (importer) => this.importHeld(sessionId, importer, entries, "create"));
631
851
  }
632
852
  async replaceEntries(sessionId, owner, entries) {
633
- await this.withImportLock(sessionId, owner, (importer) => this.importHeld(sessionId, importer, entries, "purge_first"));
853
+ await this.withImportLock(sessionId, owner, (importer) => this.importHeld(sessionId, importer, entries, "replace"));
634
854
  }
635
855
  async withImportLock(sessionId, owner, body) {
636
856
  const importer = importerOwnerOf(owner);
637
857
  return this.withSessionWriteLock(sessionId, () => body(importer));
638
858
  }
639
- async importHeld(sessionId, owner, entries, swap) {
640
- const row = await this.metaOf(sessionId);
641
- if (row !== undefined && !ownerEq(row.owner ?? null, owner)) {
642
- throw new SessionError("conflict", `session ${sessionId} is not the importer's row (its owner cell differs) — nothing was written`);
643
- }
644
- await this.swapLogHeld(sessionId, entries, swap);
645
- await this.finishSwap(sessionId, owner);
646
- }
647
- async swapLogHeld(sessionId, entries, swap) {
859
+ async importHeld(sessionId, owner, entries, landing) {
648
860
  if (!this.repo.importEntries)
649
861
  throw new SessionError("storage", "session import is not supported by this local backend");
650
- const archive = await this.archiveToSwap(sessionId, entries, swap);
651
- await this.repo.importEntries(sessionId, archive?.owner ?? undefined, entries, { expect: { row: archive } });
652
- }
653
- async archiveToSwap(sessionId, entries, swap) {
654
- switch (swap) {
655
- case "in_place":
656
- return (await this.repo.headOf(sessionId)) ?? null;
657
- case "purge_first":
658
- validateEntriesForImport(entries);
659
- await this.repo.delete({ id: sessionId, createdAt: "" });
660
- return null;
862
+ await this.meta.ensureLoaded();
863
+ validateEntriesForImport(entries);
864
+ const current = this.metaCellOf(sessionId);
865
+ if (current?.execution?.role === "writer")
866
+ throw new SessionWriterPresentError(sessionId, current.execution.lane);
867
+ if (await this.forkingOf(sessionId, current))
868
+ throw new SessionForkInProgressError(sessionId);
869
+ if (!(await this.importMayLand(sessionId, current, owner, landing)))
870
+ throw importRowRefused(sessionId, landing);
871
+ const prior = await this.landImportCell(sessionId, owner);
872
+ switch (landing) {
873
+ case "create":
874
+ case "judged":
875
+ try {
876
+ const archive = (await this.repo.headOf(sessionId)) ?? null;
877
+ await this.repo.importEntries(sessionId, archive?.owner ?? undefined, entries, { expect: { row: archive } });
878
+ }
879
+ catch (err) {
880
+ await this.withdrawImportCell(sessionId, prior, err);
881
+ throw err;
882
+ }
883
+ break;
884
+ case "replace":
885
+ try {
886
+ await this.repo.delete({ id: sessionId, createdAt: "" });
887
+ }
888
+ catch (e) {
889
+ if (!isNotFound(e))
890
+ throw e;
891
+ }
892
+ await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: null } });
893
+ break;
894
+ default: {
895
+ const unknownLanding = landing;
896
+ throw new Error(`LocalSessionStore: no import landing ${JSON.stringify(unknownLanding)}`);
897
+ }
661
898
  }
662
- }
663
- async finishSwap(sessionId, owner) {
664
- await this.writeMeta(sessionId, (before) => ({ incarnation: carriedIncarnation(sessionId, before === undefined ? undefined : readSessionMetaCell(before)), owner }));
665
899
  await this.touch(sessionId);
666
900
  }
901
+ async importMayLand(sessionId, current, owner, landing) {
902
+ switch (landing) {
903
+ case "create":
904
+ return current === undefined && !(await this.existsInRepo(sessionId));
905
+ case "replace":
906
+ return current !== undefined ? ownerEq(current.owner ?? null, owner) : ownerEq(null, owner) || !(await this.existsInRepo(sessionId));
907
+ case "judged":
908
+ return true;
909
+ default: {
910
+ const unknownLanding = landing;
911
+ throw new Error(`LocalSessionStore: no import landing ${JSON.stringify(unknownLanding)}`);
912
+ }
913
+ }
914
+ }
915
+ async landImportCell(sessionId, owner) {
916
+ let prior;
917
+ await this.writeMeta(sessionId, (before) => {
918
+ prior = before;
919
+ const cells = carriedRowCells(sessionId, before === undefined ? undefined : readSessionMetaCell(before));
920
+ return { incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}) };
921
+ });
922
+ return prior;
923
+ }
924
+ async withdrawImportCell(sessionId, prior, cause) {
925
+ try {
926
+ if (prior === undefined)
927
+ await this.dropMeta(sessionId);
928
+ else
929
+ await this.writeMeta(sessionId, () => prior);
930
+ }
931
+ catch (withdrawErr) {
932
+ throw new SessionError("storage", `LocalSessionStore: import into ${sessionId} failed (${cause instanceof Error ? cause.message : String(cause)}) and its session-meta cell could not be put back (${withdrawErr instanceof Error ? withdrawErr.message : String(withdrawErr)}) — the session reads as a mirror until the next push`, cause instanceof Error ? cause : undefined);
933
+ }
934
+ }
667
935
  beginImportStaging(realSessionId, token) {
668
936
  const h = new LocalStagingHandle(this, realSessionId, stagingIdFor(realSessionId, token));
669
937
  this.stagings.set(h.stagingId, h);
@@ -680,6 +948,8 @@ export class LocalSessionStore {
680
948
  const current = await this.metaOf(sessionId);
681
949
  if (!ownerCellIs(opts.expectedOwner, current === undefined ? undefined : (current.owner ?? null)))
682
950
  throw new StagedCommitOwnerChangedError(sessionId);
951
+ if (await this.forkingOf(sessionId, current))
952
+ throw new SessionForkInProgressError(sessionId);
683
953
  const dstEntries = await this.exportEntries(sessionId);
684
954
  const rel = classifySyncRelationshipByIds(staged.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
685
955
  if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
@@ -692,10 +962,15 @@ export class LocalSessionStore {
692
962
  throw new SyncConflictError(sessionId, contentForkRelation(diverged, rel.newEntryIds));
693
963
  }
694
964
  const contentEqual = rel.relation === "identical" && identicalIdsAlsoIdenticalContent(staged, dstEntries);
965
+ if (current?.execution?.role === "writer")
966
+ throw new SessionWriterPresentError(sessionId, current.execution.lane);
695
967
  if (rel.relation !== "identical" || !contentEqual) {
696
- await this.swapLogHeld(sessionId, staged, "in_place");
968
+ await this.importHeld(sessionId, importer, staged, "judged");
969
+ }
970
+ else {
971
+ await this.landImportCell(sessionId, importer);
972
+ await this.touch(sessionId);
697
973
  }
698
- await this.finishSwap(sessionId, importer);
699
974
  const meta = await this.metaOf(sessionId);
700
975
  if (meta === undefined)
701
976
  throw new SessionError("storage", `LocalSessionStore: staged commit for session ${sessionId} left no session face — nothing was settled`);
@@ -15,6 +15,10 @@ export declare const MEMORY_SLUG_COLUMN_CHARS = 512;
15
15
  * sync_cursor/push_queue/history)。`formatUserScope`/`formatProjScope` 的段编码(百分号转义)会
16
16
  * **膨胀**——非 ASCII principal 编码后可超列宽,故产出处与写口两道都要判。 */
17
17
  export declare const MEMORY_SCOPE_COLUMN_CHARS = 190;
18
+ /** memory-sync 店 `sync_cursor.peer` 列宽(两方言 VARCHAR(190) 同宽)—— 中心侧请求的 `peer` 落这一列。两个读者:中心侧验型
19
+ * (`memory-sync.ts` `parseMemorySyncRequest`,超宽 400)与客户端的身份铸点(`memory-sync-client.ts` `memorySyncPeerOf`,超宽拒启:
20
+ * 身份就是请求的 `peer`,装不进这一列的身份每一轮都会被中心拒)。 */
21
+ export declare const MEMORY_SYNC_PEER_COLUMN_CHARS = 190;
18
22
  export declare function assertSlugWidth(slug: string): void;
19
23
  export declare function assertMemoryScopeWidth(scope: string): void;
20
24
  /**
@@ -1,5 +1,6 @@
1
1
  export const MEMORY_SLUG_COLUMN_CHARS = 512;
2
2
  export const MEMORY_SCOPE_COLUMN_CHARS = 190;
3
+ export const MEMORY_SYNC_PEER_COLUMN_CHARS = 190;
3
4
  function widthChars(v) {
4
5
  return [...v].length;
5
6
  }