@morlay/ui-conversation-message-actions 0.0.19-alpha.5 → 0.0.19-alpha.6

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.
package/dist/testing.mjs CHANGED
@@ -10,6 +10,7 @@ import SessionProjectionRegistry from "@deepseek-ai/dsh-session-projection";
10
10
  import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionHandleClosedError, SessionPersistence, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertVersion, materializeAppendBatch, materializeCreateHeader, validateStoredEvents } from "@deepseek-ai/dsh-session-persistence";
11
11
  import { dirname, isAbsolute, join, resolve } from "node:path";
12
12
  import { AssistantStreamAccumulator, HarnessError, MessageId, freezeMessage, isAgentLoopRequest } from "@deepseek-ai/dsh-llm";
13
+ import { isDeepStrictEqual } from "node:util";
13
14
  import { mkdir, open, readdir, stat } from "node:fs/promises";
14
15
  import { fileURLToPath } from "node:url";
15
16
  import { DatabaseSync } from "node:sqlite";
@@ -63,7 +64,7 @@ function isNullable(value) {
63
64
  return value === null || value === void 0;
64
65
  }
65
66
  /** Return true for non-array object values. */
66
- function isPlainObject$2(data) {
67
+ function isPlainObject$1(data) {
67
68
  return data && typeof data === "object" && !Array.isArray(data);
68
69
  }
69
70
  /** Filter object entries and return a new object. */
@@ -81,6 +82,43 @@ function pick$1(source, keys, forced) {
81
82
  for (const key of keys) if (forced || source[key] !== void 0) result[key] = source[key];
82
83
  return result;
83
84
  }
85
+ /** Shared config references used by schema validators and plugin runtimes. */
86
+ const write = Symbol.for("cosmokit.volatile.write");
87
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
88
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
89
+ if (value === null || typeof value !== "object") return value;
90
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
91
+ ancestors.add(value);
92
+ try {
93
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
94
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
95
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
96
+ } finally {
97
+ ancestors.delete(value);
98
+ }
99
+ }
100
+ /**
101
+ * Create a detached reference containing an immutable copy of the supplied data.
102
+ * @param value - validated config data; class instances and functions are unsupported.
103
+ * @returns a reference whose value is updated only by its owning runtime.
104
+ */
105
+ function createVolatile(value) {
106
+ let current = snapshot(value);
107
+ return Object.freeze({
108
+ get: () => current,
109
+ [write]: (value) => {
110
+ current = value;
111
+ }
112
+ });
113
+ }
114
+ /**
115
+ * Identify references across ESM/CJS copies of the shared library.
116
+ * @param value - a parsed config value.
117
+ * @returns whether the value implements the shared reference protocol.
118
+ */
119
+ function isVolatile(value) {
120
+ return typeof value === "object" && value !== null && write in value;
121
+ }
84
122
  /** Test values using `instanceof` with a `toStringTag` fallback. */
85
123
  function is$1(type, value) {
86
124
  if (arguments.length === 1) return (value) => is$1(type, value);
@@ -161,26 +199,47 @@ function clone$1(source, refs = /* @__PURE__ */ new Map()) {
161
199
  }
162
200
  return result;
163
201
  }
164
- /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
202
+ /**
203
+ * Compare values recursively, treating two volatile references as equal regardless of value.
204
+ * Strict comparison distinguishes null/undefined, treats opaque objects by identity,
205
+ * compares URLs by normalized href, treats array holes as undefined, and considers distinct cyclic structures unequal.
206
+ * @param a - first value.
207
+ * @param b - second value.
208
+ * @param strict - whether to require strict data equality outside volatile references.
209
+ * @returns whether the values compare equal.
210
+ */
165
211
  function deepEqual(a, b, strict) {
166
- if (a === b) return true;
167
- if (!strict && isNullable(a) && isNullable(b)) return true;
168
- if (typeof a !== typeof b) return false;
169
- if (typeof a !== "object") return false;
170
- if (!a || !b) return false;
171
- function check(test, then) {
172
- return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
173
- }
174
- return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is$1("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is$1("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
175
- if (a.byteLength !== b.byteLength) return false;
176
- const viewA = new Uint8Array(a);
177
- const viewB = new Uint8Array(b);
178
- for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
179
- return true;
180
- }) ?? Object.keys({
181
- ...a,
182
- ...b
183
- }).every((key) => deepEqual(a[key], b[key], strict));
212
+ const ancestors = /* @__PURE__ */ new Set();
213
+ function compare(a, b) {
214
+ if (a === b) return true;
215
+ if (isVolatile(a) || isVolatile(b)) return isVolatile(a) && isVolatile(b);
216
+ if (!strict && isNullable(a) && isNullable(b)) return true;
217
+ if (typeof a !== typeof b || typeof a !== "object" || !a || !b) return false;
218
+ if (ancestors.has(a)) return false;
219
+ function check(test, then) {
220
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
221
+ }
222
+ ancestors.add(a);
223
+ try {
224
+ return check(Array.isArray, (a, b) => {
225
+ if (a.length !== b.length) return false;
226
+ for (let index = 0; index < a.length; index++) if (!compare(a[index], b[index])) return false;
227
+ return true;
228
+ }) ?? check(is$1("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is$1("URL"), (a, b) => a.href === b.href) ?? check(is$1("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
229
+ if (a.byteLength !== b.byteLength) return false;
230
+ const viewA = new Uint8Array(a);
231
+ const viewB = new Uint8Array(b);
232
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
233
+ return true;
234
+ }) ?? ((!strict || [a, b].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
235
+ ...a,
236
+ ...b
237
+ }).every((key) => compare(a[key], b[key])));
238
+ } finally {
239
+ ancestors.delete(a);
240
+ }
241
+ }
242
+ return compare(a, b);
184
243
  }
185
244
  /** Time constants plus parsing and formatting helpers. */
186
245
  var Time;
@@ -429,6 +488,7 @@ Schema.prototype.pattern = function pattern(regexp) {
429
488
  return schema;
430
489
  };
431
490
  Schema.prototype.simplify = function simplify(value) {
491
+ if (isVolatile(value)) value = value.get();
432
492
  if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
433
493
  if (isNullable(value)) return value;
434
494
  if (this.type === "object" || this.type === "dict") {
@@ -485,12 +545,49 @@ for (const key of [
485
545
  };
486
546
  return schema;
487
547
  } });
548
+ Schema.prototype.volatile = function volatile() {
549
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
550
+ return this.extra("volatile", true);
551
+ };
488
552
  const resolvers = {};
553
+ const checkedVolatile = Symbol("checked-volatile-schema");
554
+ function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
555
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
556
+ if (states.has(blocked)) return;
557
+ states.add(blocked);
558
+ seen.set(schema, states);
559
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
560
+ const nested = blocked || !!schema.meta?.volatile;
561
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
562
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
563
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
564
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
565
+ }
489
566
  Schema.extend = function extend(type, resolve) {
490
567
  resolvers[type] = resolve;
491
568
  };
492
569
  Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
493
570
  if (!schema) return [data];
571
+ if (!options[checkedVolatile]) {
572
+ validateVolatileSchema(schema, options.path);
573
+ options = {
574
+ ...options,
575
+ [checkedVolatile]: true
576
+ };
577
+ }
578
+ if (schema.meta?.volatile) {
579
+ const inner = Schema(schema);
580
+ inner.meta = {
581
+ ...schema.meta,
582
+ volatile: false
583
+ };
584
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
585
+ try {
586
+ return [createVolatile(value), adapted];
587
+ } catch (error) {
588
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
589
+ }
590
+ }
494
591
  if (options.ignore?.(data, schema)) return [data];
495
592
  if (isNullable(data) && schema.type !== "lazy") {
496
593
  if (schema.meta.required) throw new ValidationError(`missing required value`, options);
@@ -593,6 +690,7 @@ Schema.extend("lazy", (data, schema, options, strict) => {
593
690
  ...schema.meta,
594
691
  ...schema.inner.meta
595
692
  };
693
+ validateVolatileSchema(schema.inner, options.path, true);
596
694
  }
597
695
  return Schema.resolve(data, schema.inner, options, strict);
598
696
  });
@@ -692,7 +790,7 @@ function property(data, key, schema, options) {
692
790
  } catch (e) {
693
791
  if (!options?.autofix) throw e;
694
792
  delete data[key];
695
- return schema.meta.default;
793
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
696
794
  }
697
795
  }
698
796
  Schema.extend("array", (data, { inner, meta }, options) => {
@@ -701,7 +799,7 @@ Schema.extend("array", (data, { inner, meta }, options) => {
701
799
  return [data.map((_, index) => property(data, index, inner, options))];
702
800
  });
703
801
  Schema.extend("dict", (data, { inner, sKey }, options, strict) => {
704
- if (!isPlainObject$2(data)) throw new ValidationError(`expected object but got ${data}`, options);
802
+ if (!isPlainObject$1(data)) throw new ValidationError(`expected object but got ${data}`, options);
705
803
  const result = {};
706
804
  for (const key in data) {
707
805
  let rKey;
@@ -731,7 +829,7 @@ function merge$1(result, data) {
731
829
  }
732
830
  }
733
831
  Schema.extend("object", (data, { dict }, options, strict) => {
734
- if (!isPlainObject$2(data)) throw new ValidationError(`expected object but got ${data}`, options);
832
+ if (!isPlainObject$1(data)) throw new ValidationError(`expected object but got ${data}`, options);
735
833
  const result = {};
736
834
  for (const key in dict) {
737
835
  const value = property(data, key, dict[key], options);
@@ -760,7 +858,7 @@ Schema.extend("intersect", (data, { list, toString }, options, strict) => {
760
858
  else if (typeof value === "object") merge$1(result ??= {}, value);
761
859
  else if (result !== value) throw new ValidationError(`expected ${toString()} but got ${JSON.stringify(data)}`, options);
762
860
  }
763
- if (!strict && isPlainObject$2(data)) merge$1(result, data);
861
+ if (!strict && isPlainObject$1(data)) merge$1(result, data);
764
862
  return [result];
765
863
  });
766
864
  Schema.extend("transform", (data, { inner, callback, preserve }, options) => {
@@ -9082,7 +9180,7 @@ var PgText = class extends PgColumn {
9082
9180
  return "text";
9083
9181
  }
9084
9182
  };
9085
- function text$2(a, b = {}) {
9183
+ function text$3(a, b = {}) {
9086
9184
  const { name, config } = getColumnNameAndConfig(a, b);
9087
9185
  return new PgTextBuilder(name, config);
9088
9186
  }
@@ -9390,7 +9488,7 @@ function getPgColumnBuilders() {
9390
9488
  serial,
9391
9489
  smallint,
9392
9490
  smallserial,
9393
- text: text$2,
9491
+ text: text$3,
9394
9492
  time: time$1,
9395
9493
  timestamp,
9396
9494
  uuid: uuid$1,
@@ -14558,6 +14656,10 @@ var WriteGuard = class {
14558
14656
  confirmHead(id, head) {
14559
14657
  this.headSeqs.set(id, head);
14560
14658
  }
14659
+ /** 本实例有没有这条会话的 head 记录(迁移重写用它决定是"登记基准"还是"严格比较")。 */
14660
+ has(id) {
14661
+ return this.headSeqs.has(id);
14662
+ }
14561
14663
  assertNoConcurrentWriter(id, storedHead) {
14562
14664
  const known = this.headSeqs.get(id);
14563
14665
  if (known === void 0) {
@@ -14594,12 +14696,6 @@ function offloadMessageImages(message, indexes) {
14594
14696
  selected += 1;
14595
14697
  }
14596
14698
  imageIndex += 1;
14597
- } else if (block.type === "tool-result") {
14598
- const content = visit(block.content);
14599
- if (content !== block.content) projected = {
14600
- ...block,
14601
- content
14602
- };
14603
14699
  }
14604
14700
  if (projected !== block) next ??= blocks.slice(0, index);
14605
14701
  next?.push(projected);
@@ -14617,7 +14713,7 @@ function offloadMessageImages(message, indexes) {
14617
14713
  //#region ../../../vendor/deepseek-harness/packages/compaction/compaction-image-offload/lib/types/projection.js
14618
14714
  /** Browser-safe durable image selection declaration and pure replay definition. */
14619
14715
  /** Whether a durable value is a JSON object. */
14620
- function isRecord$1(value) {
14716
+ function isRecord(value) {
14621
14717
  return typeof value === "object" && value !== null && !Array.isArray(value);
14622
14718
  }
14623
14719
  /** Whether a durable occurrence index or sequence is canonical. */
@@ -14629,11 +14725,11 @@ const imageOffloadProjection = {
14629
14725
  type: "image/offload",
14630
14726
  project(event, context) {
14631
14727
  const data = event.data;
14632
- if (!isRecord$1(data) || Object.keys(data).length !== 1 || !Array.isArray(data["targets"]) || data["targets"].length === 0) throw new Error("image/offload: data must contain a nonempty targets array");
14728
+ if (!isRecord(data) || Object.keys(data).length !== 1 || !Array.isArray(data["targets"]) || data["targets"].length === 0) throw new Error("image/offload: data must contain a nonempty targets array");
14633
14729
  const messages = /* @__PURE__ */ new Map();
14634
14730
  const nodes = new Set(context.nodes);
14635
14731
  for (const target of data["targets"]) {
14636
- if (!isRecord$1(target) || Object.keys(target).length !== 2 || !isIndex(target["seq"]) || !Array.isArray(target["imageIndexes"]) || target["imageIndexes"].length === 0) throw new Error("image/offload: each target must contain a seq and nonempty imageIndexes");
14732
+ if (!isRecord(target) || Object.keys(target).length !== 2 || !isIndex(target["seq"]) || !Array.isArray(target["imageIndexes"]) || target["imageIndexes"].length === 0) throw new Error("image/offload: each target must contain a seq and nonempty imageIndexes");
14637
14733
  const seq = target["seq"];
14638
14734
  if (messages.has(seq)) throw new Error(`image/offload: duplicate target seq ${seq}`);
14639
14735
  if (!nodes.has(seq)) throw new Error(`image/offload: target seq ${seq} is not a current surface node`);
@@ -16828,7 +16924,7 @@ function legacyMessageId(sessionId, seq) {
16828
16924
  function malformedLegacy(sessionId, type, seq) {
16829
16925
  return new SessionFormatError(`session ${JSON.stringify(sessionId)} contains malformed pre-react-loop ${type} at seq ${seq}`);
16830
16926
  }
16831
- const SURFACE_TYPES$2 = /* @__PURE__ */ new Set([
16927
+ const SURFACE_TYPES$3 = /* @__PURE__ */ new Set([
16832
16928
  "user/message",
16833
16929
  "assistant/message",
16834
16930
  "tool/result"
@@ -16857,7 +16953,7 @@ function assertReleasedArtifactRelationships(artifact, extensions = {}) {
16857
16953
  const extensionStepEvent = extensions.stepEvents?.has(event.type) === true;
16858
16954
  if (RELEASED_V0_EVENT_DISPOSITIONS[event.type] === void 0 && !extensionStepEvent) continue;
16859
16955
  const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
16860
- if (SURFACE_TYPES$2.has(event.type)) surface = applySurface(surface, event);
16956
+ if (SURFACE_TYPES$3.has(event.type)) surface = applySurface(surface, event);
16861
16957
  if ((event.type === "turn/start" || event.type === "turn/end") && openCompaction !== void 0 && !staleCompactionStarts.has(openCompaction.startSeq)) throw new SessionFormatError(`${event.type} crosses an open compaction`);
16862
16958
  if (extensionStepEvent) {
16863
16959
  requireOpenStep(event, data, openTurn, openStep);
@@ -17190,7 +17286,7 @@ const EVENT_REQUIRED$1 = [
17190
17286
  "time",
17191
17287
  "data"
17192
17288
  ];
17193
- const SURFACE_TYPES$1 = /* @__PURE__ */ new Set([
17289
+ const SURFACE_TYPES$2 = /* @__PURE__ */ new Set([
17194
17290
  "user/message",
17195
17291
  "assistant/message",
17196
17292
  "tool/result"
@@ -17236,7 +17332,7 @@ function validateReleasedV2Artifact(artifact, mode, knownEventTypes, relationshi
17236
17332
  const installed = knownEventTypes?.has(type) === true;
17237
17333
  const ignorableUnknown = disposition === void 0 && record["ignorable"] === true;
17238
17334
  if (mode === "current" && disposition === void 0 && !installed && !ignorableUnknown) throw new SessionFormatUnsupportedMigrationError(`format v2 contains unknown event type ${JSON.stringify(type)} at seq ${index}`);
17239
- const surface = disposition !== void 0 && SURFACE_TYPES$1.has(type);
17335
+ const surface = disposition !== void 0 && SURFACE_TYPES$2.has(type);
17240
17336
  assertReleasedV2Keys(record, EVENT_REQUIRED$1, mode === "physical" || disposition === void 0 ? SURFACE_OPTIONAL : surface ? SURFACE_OPTIONAL : LOG_OPTIONAL, `format v2 event ${index}`);
17241
17337
  if (record["seq"] !== index) throw new SessionFormatError(`format v2 event ${index} is not dense`);
17242
17338
  sessionFormatSafeInteger(record["time"], `format v2 event ${index} time`);
@@ -17559,7 +17655,7 @@ function transformReleasedEvent(state, event, context) {
17559
17655
  if (event.type === "assistant/chunk") assertChunkEnvelope(event);
17560
17656
  if (RELEASED_V0_EVENT_DISPOSITIONS[event.type] === void 0) throw refusal(`format v1 contains unknown event type ${JSON.stringify(event.type)} at seq ${event.seq}`);
17561
17657
  const interrupted = legacyInterruptedTurn(state.legacyTurns, event);
17562
- if (event.type === "turn/start" && state.legacyTurns.openTurn !== null && interrupted === void 0) throw refusal(`turn/start ${JSON.stringify(record$1(event.data)["turn"])} does not close the prior turn`);
17658
+ if (event.type === "turn/start" && state.legacyTurns.openTurn !== null && interrupted === void 0) throw refusal(`turn/start ${JSON.stringify(record$3(event.data)["turn"])} does not close the prior turn`);
17563
17659
  assertSourceDeliveryMarker(state, event);
17564
17660
  observeLegacyTurn(state.legacyTurns, event);
17565
17661
  state.lastTime = event.time;
@@ -17601,7 +17697,7 @@ function assertChunkEnvelope(event) {
17601
17697
  }
17602
17698
  function assertSourceDeliveryMarker(state, event) {
17603
17699
  if (event.type !== "session-log-deepseek/delivery-accepted") return;
17604
- const data = record$1(event.data);
17700
+ const data = record$3(event.data);
17605
17701
  const inherited = state.sourceHeader.parentSession !== void 0 && event.seq < state.sourceCut;
17606
17702
  if (data["sessionFormatVersion"] === 1 && !inherited && data["sessionId"] !== state.sourceHeader.id) throw refusal("current-generation delivery marker names the wrong Session");
17607
17703
  }
@@ -17638,10 +17734,10 @@ function finishMigration(state, context) {
17638
17734
  return state.targetCut;
17639
17735
  }
17640
17736
  function transformChunk(state, event, context) {
17641
- const data = record$1(event.data);
17737
+ const data = record$3(event.data);
17642
17738
  const turn = coordinate(data["turn"]);
17643
17739
  const step = coordinate(data["step"]);
17644
- const chunk = record$1(data["chunk"]);
17740
+ const chunk = record$3(data["chunk"]);
17645
17741
  if (state.pending !== void 0 && (state.pending.group.terminal || state.pending.group.turn !== turn || state.pending.group.step !== step)) finishAttempt(state, context);
17646
17742
  else if (state.pending !== void 0) flushBuffered(state, state.pending, context);
17647
17743
  state.pending ??= {
@@ -17658,7 +17754,7 @@ function transformChunk(state, event, context) {
17658
17754
  if (chunk["type"] === "finish") state.pending.group.terminal = true;
17659
17755
  }
17660
17756
  function transformMessage(state, event, context) {
17661
- const data = record$1(event.data);
17757
+ const data = record$3(event.data);
17662
17758
  const turn = coordinate(data["turn"]);
17663
17759
  const step = coordinate(data["step"]);
17664
17760
  const sources = event.sourceEventSeqs;
@@ -17738,8 +17834,8 @@ function legacyTurnState() {
17738
17834
  };
17739
17835
  }
17740
17836
  function legacyInterruptedTurn(state, event) {
17741
- if (event.type !== "turn/start" || state.openTurn === null || state.openStep !== null || coordinate(record$1(event.data)["turn"]) !== state.openTurn + 1 || state.previous?.type !== "agent/inbox/spliced") return void 0;
17742
- const splice = record$1(state.previous.data);
17837
+ if (event.type !== "turn/start" || state.openTurn === null || state.openStep !== null || coordinate(record$3(event.data)["turn"]) !== state.openTurn + 1 || state.previous?.type !== "agent/inbox/spliced") return void 0;
17838
+ const splice = record$3(state.previous.data);
17743
17839
  if (splice["target"] !== "next-turn" || !Array.isArray(splice["inserted"]) || splice["inserted"].length === 0) return;
17744
17840
  return {
17745
17841
  type: "turn/end",
@@ -17752,7 +17848,7 @@ function legacyInterruptedTurn(state, event) {
17752
17848
  };
17753
17849
  }
17754
17850
  function observeLegacyTurn(state, event) {
17755
- const data = record$1(event.data);
17851
+ const data = record$3(event.data);
17756
17852
  if (event.type === "turn/start") {
17757
17853
  state.openTurn = coordinate(data["turn"]);
17758
17854
  state.openStep = null;
@@ -17765,8 +17861,8 @@ function observeLegacyTurn(state, event) {
17765
17861
  }
17766
17862
  function splitLegacyGoalChange(event) {
17767
17863
  if (event.type !== "user/message") return void 0;
17768
- const data = record$1(event.data);
17769
- const source = record$1(data["source"]);
17864
+ const data = record$3(event.data);
17865
+ const source = record$3(data["source"]);
17770
17866
  if (source["kind"] !== "goal" || source["change"] === void 0) return void 0;
17771
17867
  return {
17772
17868
  change: {
@@ -17885,7 +17981,7 @@ function streamOf(group) {
17885
17981
  return group.stream.map(({ record }) => record);
17886
17982
  }
17887
17983
  function messageEvent(source, group) {
17888
- const data = record$1(source.data);
17984
+ const data = record$3(source.data);
17889
17985
  const { sourceEventSeqs: _sourceEventSeqs, ...event } = source;
17890
17986
  return {
17891
17987
  ...event,
@@ -17912,7 +18008,7 @@ function remapReferences(source, targetSeq, mapping) {
17912
18008
  const sources = sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: mapList(numberArray(sourceEventSeqs), mapping, `${source.type} ${source.seq} sources`) };
17913
18009
  let operation = surfaceOp;
17914
18010
  if (surfaceOp !== void 0 && surfaceOp !== "append") {
17915
- const replacement = record$1(surfaceOp);
18011
+ const replacement = record$3(surfaceOp);
17916
18012
  operation = {
17917
18013
  op: "replace",
17918
18014
  start: mapOne(coordinate(replacement["start"]), mapping, `${source.type} ${source.seq} surface start`),
@@ -17928,7 +18024,7 @@ function remapReferences(source, targetSeq, mapping) {
17928
18024
  };
17929
18025
  }
17930
18026
  function remapPayloadReferences(event, mapping) {
17931
- const data = record$1(event.data);
18027
+ const data = record$3(event.data);
17932
18028
  switch (event.type) {
17933
18029
  case "command/done": return data["sourceEventSeq"] === void 0 ? data : {
17934
18030
  ...data,
@@ -17936,7 +18032,7 @@ function remapPayloadReferences(event, mapping) {
17936
18032
  };
17937
18033
  case "compaction/prune":
17938
18034
  case "compaction/summary": {
17939
- const range = record$1(data["shadowedRange"]);
18035
+ const range = record$3(data["shadowedRange"]);
17940
18036
  return {
17941
18037
  ...data,
17942
18038
  shadowedRange: {
@@ -17962,7 +18058,7 @@ function mapOne(value, mapping, label) {
17962
18058
  if (mapped === void 0) throw refusal(`${label} targets consumed assistant/chunk ${value}`);
17963
18059
  return mapped;
17964
18060
  }
17965
- function record$1(value) {
18061
+ function record$3(value) {
17966
18062
  return value;
17967
18063
  }
17968
18064
  function numberArray(value) {
@@ -17978,7 +18074,7 @@ function refusal(message) {
17978
18074
  //#region ../../../vendor/deepseek-harness/packages/session/session-format-v2-to-v3/lib/index.js
17979
18075
  /** Audited V2 migration admission and V3 payload validation, independent of installed core Session types. */
17980
18076
  /** Audited surface event names; all other admitted events are log-only. */
17981
- const SURFACE_TYPES = /* @__PURE__ */ new Set([
18077
+ const SURFACE_TYPES$1 = /* @__PURE__ */ new Set([
17982
18078
  "system/message",
17983
18079
  "user/message",
17984
18080
  "assistant/message",
@@ -18007,7 +18103,7 @@ const SOURCE_KINDS = /* @__PURE__ */ new Set([
18007
18103
  * @param label - diagnostic subject.
18008
18104
  * @returns the narrowed object.
18009
18105
  */
18010
- function record(value, label) {
18106
+ function record$2(value, label) {
18011
18107
  if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(label + " must be an object");
18012
18108
  return value;
18013
18109
  }
@@ -18037,7 +18133,7 @@ function assertEvent(event, version) {
18037
18133
  const disposition = RELEASED_V2_EVENT_DISPOSITIONS[event.type];
18038
18134
  const feedback = event.type === "feedback/message-put" || event.type === "feedback/message-delete";
18039
18135
  if (disposition === void 0 && !feedback) throw new SessionFormatUnsupportedMigrationError("format v2 to v3 cannot safely transform unclassified event " + event.type);
18040
- const surface = SURFACE_TYPES.has(event.type);
18136
+ const surface = SURFACE_TYPES$1.has(event.type);
18041
18137
  keys(event, [
18042
18138
  "type",
18043
18139
  "seq",
@@ -18055,7 +18151,7 @@ function assertEvent(event, version) {
18055
18151
  assertReleasedSurfaceMetadata(event, event.seq, event.type, "forbid-assistant");
18056
18152
  if (event["surfaceOp"] === void 0) throw new SessionFormatError(event.type + " requires surfaceOp");
18057
18153
  }
18058
- const data = record(event.data, event.type + " data");
18154
+ const data = record$2(event.data, event.type + " data");
18059
18155
  if (feedback) {
18060
18156
  assertFeedback(event.type, data);
18061
18157
  return;
@@ -18069,10 +18165,10 @@ function assertEvent(event, version) {
18069
18165
  }
18070
18166
  if (event.type === "session/end-seed" && data["inherited"] !== void 0 && data["inherited"] !== true) throw new SessionFormatError("session/end-seed inherited must be true");
18071
18167
  if (event.type === "user/message") assertSource(data);
18072
- if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record(data["message"], "message"));
18168
+ if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record$2(data["message"], "message"));
18073
18169
  if (event.type === "tool/result" && isSessionFormatJsonObject(data["error"]) && data["error"]["code"] === "TOOL_NOT_STARTED") {
18074
- const message = record(data["message"], "tool result message");
18075
- const source = record(message["source"], "tool result source");
18170
+ const message = record$2(data["message"], "tool result message");
18171
+ const source = record$2(message["source"], "tool result source");
18076
18172
  if (!isRepairIdentity(message["id"], source["callId"])) throw new SessionFormatError("TOOL_NOT_STARTED repair requires its canonical historical message id");
18077
18173
  }
18078
18174
  if (event.type === "agent/inbox/spliced" || event.type === "session/title-llm-request") {
@@ -18094,7 +18190,7 @@ function isRepairIdentity(id, callId) {
18094
18190
  return /^(0|[1-9]\d*)$/.test(suffix) && Number.isSafeInteger(Number(suffix));
18095
18191
  }
18096
18192
  function assertSource(message) {
18097
- const source = record(message["source"], "message source");
18193
+ const source = record$2(message["source"], "message source");
18098
18194
  if (typeof source["kind"] !== "string" || !SOURCE_KINDS.has(source["kind"])) throw new SessionFormatUnsupportedMigrationError("cannot safely transform unclassified message source");
18099
18195
  if (source["kind"] === "agent-message") {
18100
18196
  keys(source, [
@@ -18127,14 +18223,14 @@ function assertOwnedContent(event, data) {
18127
18223
  case "assistant/message":
18128
18224
  case "tool/result":
18129
18225
  case "team/message/queued":
18130
- assertContentKinds(record(data["message"], label + ".message")["content"], label + ".message.content");
18226
+ assertContentKinds(record$2(data["message"], label + ".message")["content"], label + ".message.content");
18131
18227
  break;
18132
18228
  case "agent/inbox/spliced":
18133
18229
  case "session/title-llm-request": {
18134
18230
  const field = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
18135
18231
  for (const [index, value] of contentArray(data[field], label + "." + field).entries()) {
18136
18232
  const path = label + "." + field + "[" + String(index) + "]";
18137
- assertContentKinds(record(value, path)["content"], path + ".content");
18233
+ assertContentKinds(record$2(value, path)["content"], path + ".content");
18138
18234
  }
18139
18235
  break;
18140
18236
  }
@@ -18144,9 +18240,9 @@ function assertOwnedContent(event, data) {
18144
18240
  }
18145
18241
  if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const [index, value] of contentArray(data["stream"], label + ".stream").entries()) {
18146
18242
  const path = label + ".stream[" + String(index) + "]";
18147
- const entry = record(value, path);
18243
+ const entry = record$2(value, path);
18148
18244
  if (entry["type"] !== "chunk") continue;
18149
- const chunk = record(entry["chunk"], path + ".chunk");
18245
+ const chunk = record$2(entry["chunk"], path + ".chunk");
18150
18246
  if (chunk["type"] === "block-end") assertContentBlock(chunk["block"], path + ".chunk.block");
18151
18247
  if (chunk["type"] === "block-start") assertContentKind(chunk["blockType"], path + ".chunk.blockType");
18152
18248
  }
@@ -18158,7 +18254,7 @@ function assertContentKinds(content, label) {
18158
18254
  for (const [index, value] of contentArray(content, label).entries()) assertContentBlock(value, label + "[" + String(index) + "]");
18159
18255
  }
18160
18256
  function assertContentBlock(value, label) {
18161
- const block = record(value, label);
18257
+ const block = record$2(value, label);
18162
18258
  assertContentKind(block["type"], label);
18163
18259
  if (block["type"] === "tool-result") {
18164
18260
  if (!Array.isArray(block["content"])) throw new SessionFormatError(label + ".content: invalid message content kind \"tool-result\": content must be an array");
@@ -18166,7 +18262,7 @@ function assertContentBlock(value, label) {
18166
18262
  }
18167
18263
  if (block["type"] === "file") {
18168
18264
  keys(block, ["type", "attachment"], [], label + " kind \"file\"");
18169
- const attachment = record(block["attachment"], label + " kind \"file\" attachment");
18265
+ const attachment = record$2(block["attachment"], label + " kind \"file\" attachment");
18170
18266
  keys(attachment, [
18171
18267
  "attachmentId",
18172
18268
  "name",
@@ -18204,10 +18300,10 @@ function assertV3StructuralRow(value) {
18204
18300
  if (typeof value !== "object" || value === null || Array.isArray(value)) return;
18205
18301
  const row = value;
18206
18302
  if (row["type"] === "request/header") {
18207
- const data = record(row["data"], "request/header data");
18208
- if (Object.hasOwn(record(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError("format v3 request/header rejects retired header.system");
18303
+ const data = record$2(row["data"], "request/header data");
18304
+ if (Object.hasOwn(record$2(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError("format v3 request/header rejects retired header.system");
18209
18305
  } else if (row["type"] === "system/message") {
18210
- const data = record(row["data"], "system/message data");
18306
+ const data = record$2(row["data"], "system/message data");
18211
18307
  assertSystem({
18212
18308
  type: "system/message",
18213
18309
  seq: 0,
@@ -18223,7 +18319,7 @@ function assertSystem(event, data) {
18223
18319
  "message"
18224
18320
  ], [], "system/message data");
18225
18321
  for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
18226
- const message = record(data["message"], "system message");
18322
+ const message = record$2(data["message"], "system message");
18227
18323
  keys(message, [
18228
18324
  "id",
18229
18325
  "role",
@@ -18231,7 +18327,7 @@ function assertSystem(event, data) {
18231
18327
  "content"
18232
18328
  ], [], "system message");
18233
18329
  if (typeof message["id"] !== "string" || message["id"].length === 0 || message["role"] !== "system") throw new SessionFormatError("system message requires an id and system role");
18234
- const source = record(message["source"], "system source");
18330
+ const source = record$2(message["source"], "system source");
18235
18331
  if (source["kind"] !== "plugin" || typeof source["plugin"] !== "string" || source["plugin"].length === 0) throw new SessionFormatError("system message requires plugin source");
18236
18332
  assertReleasedPayloadSemantics({
18237
18333
  ...event,
@@ -18249,7 +18345,7 @@ function assertFeedback(type, data) {
18249
18345
  if (typeof data["messageId"] !== "string") throw new SessionFormatError("feedback messageId must be a string");
18250
18346
  return;
18251
18347
  }
18252
- const item = record(data["item"], "feedback item");
18348
+ const item = record$2(data["item"], "feedback item");
18253
18349
  keys(item, [
18254
18350
  "messageId",
18255
18351
  "rating",
@@ -18270,15 +18366,15 @@ function assertFeedback(type, data) {
18270
18366
  * @param knownEventTypes - additional installed event types whose envelopes are interpreted.
18271
18367
  */
18272
18368
  function assertV3Event(event, knownEventTypes) {
18273
- const value = record(event, "format v3 event");
18369
+ const value = record$2(event, "format v3 event");
18274
18370
  const subject = `format v3 ${event.type} at seq ${event.seq}`;
18275
- const opaque = !(!(event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") && (SURFACE_TYPES.has(event.type) || RELEASED_V2_EVENT_DISPOSITIONS[event.type] !== void 0 || event.type === "tool/ptc-dispatch-start" || event.type === "tool/ptc-dispatch" || event.type === "feedback/message-put" || event.type === "feedback/message-delete" || knownEventTypes?.has(event.type) === true));
18371
+ const opaque = !(!(event.type === "tool/code-dispatch-start" || event.type === "tool/code-dispatch") && (SURFACE_TYPES$1.has(event.type) || RELEASED_V2_EVENT_DISPOSITIONS[event.type] !== void 0 || event.type === "tool/ptc-dispatch-start" || event.type === "tool/ptc-dispatch" || event.type === "feedback/message-put" || event.type === "feedback/message-delete" || knownEventTypes?.has(event.type) === true));
18276
18372
  keys(value, [
18277
18373
  "type",
18278
18374
  "seq",
18279
18375
  "time",
18280
18376
  "data"
18281
- ], SURFACE_TYPES.has(event.type) || opaque ? [
18377
+ ], SURFACE_TYPES$1.has(event.type) || opaque ? [
18282
18378
  "ignorable",
18283
18379
  "surfaceOp",
18284
18380
  "sourceEventSeqs"
@@ -18287,11 +18383,11 @@ function assertV3Event(event, knownEventTypes) {
18287
18383
  sessionFormatCount(event.seq, `${subject} seq`);
18288
18384
  sessionFormatSafeInteger(event.time, `${subject} time`);
18289
18385
  if (Object.hasOwn(value, "ignorable") && value["ignorable"] !== true) throw new SessionFormatError(`${subject} ignorable must be true when present`);
18290
- if (SURFACE_TYPES.has(event.type)) {
18386
+ if (SURFACE_TYPES$1.has(event.type)) {
18291
18387
  const operation = value["surfaceOp"];
18292
18388
  if (operation === void 0) throw new SessionFormatError(`${subject} requires a surfaceOp marker`);
18293
18389
  if (operation !== "append") {
18294
- const replace = record(operation, `${subject} surfaceOp`);
18390
+ const replace = record$2(operation, `${subject} surfaceOp`);
18295
18391
  if (Object.keys(replace).length !== 3 || replace["op"] !== "replace" || !Object.hasOwn(replace, "startSeq") || !Object.hasOwn(replace, "endSeq")) throw new SessionFormatError(`${subject} requires exact replace fields op/startSeq/endSeq`);
18296
18392
  for (const key of ["startSeq", "endSeq"]) if (sessionFormatCount(replace[key], `${subject} surfaceOp ${key}`) >= event.seq) throw new SessionFormatError(`${subject} replacement endpoints must reference earlier events`);
18297
18393
  }
@@ -18313,13 +18409,13 @@ function assertV3Event(event, knownEventTypes) {
18313
18409
  function assertCanonicalPayload(event) {
18314
18410
  const subject = `format v3 ${event.type} at seq ${event.seq}`;
18315
18411
  if (event.type === "request/header") {
18316
- const header = record(record(event.data, `${subject} data`)["header"], `${subject} header`);
18412
+ const header = record$2(record$2(event.data, `${subject} data`)["header"], `${subject} header`);
18317
18413
  if (Array.isArray(header["tools"]) && header["tools"].length === 0 || isSessionFormatJsonObject(header["adapterDefaults"]) && Object.keys(header["adapterDefaults"]).length === 0) throw new SessionFormatError(`${subject} empty optional header fields must be omitted`);
18318
18414
  }
18319
18415
  if (event.type !== "tool/result") return;
18320
- const data = record(event.data, `${subject} data`);
18416
+ const data = record$2(event.data, `${subject} data`);
18321
18417
  if (data["error"] === void 0) return;
18322
- const content = record(data["message"], `${subject} message`)["content"];
18418
+ const content = record$2(data["message"], `${subject} message`)["content"];
18323
18419
  if (!Array.isArray(content) || content.length !== 1 || !isSessionFormatJsonObject(content[0]) || content[0]["type"] !== "tool-result" || content[0]["isError"] !== true) throw new SessionFormatError(`${subject} carries error metadata for a non-error tool result`);
18324
18420
  }
18325
18421
  /**
@@ -18331,7 +18427,7 @@ function canonicalizeTransformedEvent(event) {
18331
18427
  let target = event;
18332
18428
  const operation = event["surfaceOp"];
18333
18429
  if (operation !== void 0 && operation !== "append") {
18334
- const replace = record(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
18430
+ const replace = record$2(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
18335
18431
  if (Object.keys(replace).length !== 3 || replace["op"] !== "replace" || !Object.hasOwn(replace, "start") || !Object.hasOwn(replace, "end")) throw new SessionFormatError(`format v2 ${event.type} at seq ${event.seq} requires exact replace fields op/start/end`);
18336
18432
  target = {
18337
18433
  ...event,
@@ -18343,8 +18439,8 @@ function canonicalizeTransformedEvent(event) {
18343
18439
  };
18344
18440
  }
18345
18441
  if (event.type === "request/header") {
18346
- const data = record(event.data, `format v2 request/header at seq ${event.seq} data`);
18347
- const header = record(data["header"], `format v2 request/header at seq ${event.seq} header`);
18442
+ const data = record$2(event.data, `format v2 request/header at seq ${event.seq} data`);
18443
+ const header = record$2(data["header"], `format v2 request/header at seq ${event.seq} header`);
18348
18444
  const empty = Object.keys(header).filter((key) => key === "tools" && Array.isArray(header[key]) && header[key].length === 0 || key === "adapterDefaults" && isSessionFormatJsonObject(header[key]) && Object.keys(header[key]).length === 0);
18349
18445
  if (empty.length > 0) {
18350
18446
  const canonical = Object.fromEntries(Object.entries(header).filter(([key]) => !empty.includes(key)));
@@ -18389,37 +18485,37 @@ function restoreReleasedV3Artifact(artifact, knownEventTypes) {
18389
18485
  assertV3Event(event, knownEventTypes);
18390
18486
  const system = event.type === "system/message";
18391
18487
  if (event.type === "step/start") {
18392
- const data = record(event.data, event.type);
18488
+ const data = record$2(event.data, event.type);
18393
18489
  step = {
18394
18490
  turn: data["turn"],
18395
18491
  step: data["step"]
18396
18492
  };
18397
18493
  } else if (event.type === "step/end" || event.type === "turn/end") step = void 0;
18398
18494
  if (system) {
18399
- const data = record(event.data, "system/message");
18495
+ const data = record$2(event.data, "system/message");
18400
18496
  if (step === void 0 || step.turn !== data["turn"] || step.step !== data["step"]) throw new SessionFormatError("system/message does not match an open step");
18401
18497
  const operation = event["surfaceOp"];
18402
18498
  if (hasSurface && head === void 0) throw new SessionFormatError("system/message requires a protected first surface head");
18403
18499
  if (operation === "append") {
18404
18500
  if (!hasSurface) head = event.seq;
18405
18501
  } else {
18406
- const replace = record(operation, "system replacement");
18502
+ const replace = record$2(operation, "system replacement");
18407
18503
  if (replace["startSeq"] === head || replace["endSeq"] === head) {
18408
18504
  if (replace["startSeq"] !== head || replace["endSeq"] !== head) throw new SessionFormatError("system/message must replace exactly the current system head");
18409
18505
  head = event.seq;
18410
18506
  }
18411
18507
  }
18412
- } else if (SURFACE_TYPES.has(event.type) && event["surfaceOp"] !== "append") {
18413
- const replace = record(event["surfaceOp"], "surface replacement");
18508
+ } else if (SURFACE_TYPES$1.has(event.type) && event["surfaceOp"] !== "append") {
18509
+ const replace = record$2(event["surfaceOp"], "surface replacement");
18414
18510
  if (replace["startSeq"] === head || replace["endSeq"] === head) throw new SessionFormatError("surface replacement cannot shadow the protected system head");
18415
18511
  }
18416
18512
  if (event.type === "compaction/prune" || event.type === "compaction/summary") {
18417
- const seqs = record(event.data, event.type)["shadowedSeqs"];
18513
+ const seqs = record$2(event.data, event.type)["shadowedSeqs"];
18418
18514
  if (Array.isArray(seqs) && seqs.some((seq) => seq === head)) throw new SessionFormatError("compaction cannot shadow the protected system head");
18419
18515
  }
18420
- if (SURFACE_TYPES.has(event.type)) hasSurface = true;
18516
+ if (SURFACE_TYPES$1.has(event.type)) hasSurface = true;
18421
18517
  const projected = relationshipEvent(event);
18422
- if (!SURFACE_TYPES.has(event.type) || event["surfaceOp"] === "append") return projected;
18518
+ if (!SURFACE_TYPES$1.has(event.type) || event["surfaceOp"] === "append") return projected;
18423
18519
  const replacement = event["surfaceOp"];
18424
18520
  return {
18425
18521
  ...projected,
@@ -18466,7 +18562,7 @@ function relationshipEvent(event) {
18466
18562
  };
18467
18563
  }
18468
18564
  if (event.type === "system/message") {
18469
- const message = record(record(event.data, "system data")["message"], "system message");
18565
+ const message = record$2(record$2(event.data, "system data")["message"], "system message");
18470
18566
  return {
18471
18567
  ...event,
18472
18568
  type: "user/message",
@@ -18477,11 +18573,11 @@ function relationshipEvent(event) {
18477
18573
  };
18478
18574
  }
18479
18575
  if (event.type !== "tool/result") return event;
18480
- const data = record(event.data, "tool result");
18576
+ const data = record$2(event.data, "tool result");
18481
18577
  if (data["error"] === void 0) return event;
18482
- if (record(data["error"], "tool error")["code"] !== "TOOL_NOT_STARTED") return event;
18483
- const message = record(data["message"], "tool message");
18484
- const callId = record(message["source"], "tool source")["callId"];
18578
+ if (record$2(data["error"], "tool error")["code"] !== "TOOL_NOT_STARTED") return event;
18579
+ const message = record$2(data["message"], "tool message");
18580
+ const callId = record$2(message["source"], "tool source")["callId"];
18485
18581
  const id = message["id"];
18486
18582
  if (!isRepairIdentity(id, callId)) return event;
18487
18583
  const prefix = "interrupted-tool-result-" + callId + "-";
@@ -18598,14 +18694,14 @@ function remapEvent(event, seq, mapping) {
18598
18694
  return value.map(one);
18599
18695
  };
18600
18696
  const range = (value) => {
18601
- const source = record(value, "sequence range");
18697
+ const source = record$2(value, "sequence range");
18602
18698
  return {
18603
18699
  ...source,
18604
18700
  start: one(source["start"]),
18605
18701
  end: one(source["end"])
18606
18702
  };
18607
18703
  };
18608
- let data = record(event.data, event.type);
18704
+ let data = record$2(event.data, event.type);
18609
18705
  switch (event.type) {
18610
18706
  case "command/done":
18611
18707
  if (data["sourceEventSeq"] !== void 0) data = {
@@ -18679,9 +18775,9 @@ var ReleasedV2ToV3Stage = class {
18679
18775
  assertEvent(event, 2);
18680
18776
  this.observeMessageIds(event);
18681
18777
  let source = event;
18682
- const data = record(event.data, event.type);
18778
+ const data = record$2(event.data, event.type);
18683
18779
  if (event.type === "request/header") {
18684
- const { system, ...header } = record(data["header"], "request header");
18780
+ const { system, ...header } = record$2(data["header"], "request header");
18685
18781
  const prompt = typeof system === "string" ? system : "";
18686
18782
  if (prompt !== this.prompt) this.emitSystem(prompt, event, context);
18687
18783
  source = {
@@ -18692,7 +18788,7 @@ var ReleasedV2ToV3Stage = class {
18692
18788
  }
18693
18789
  };
18694
18790
  }
18695
- if (SURFACE_TYPES.has(event.type) && this.head === void 0) throw new SessionFormatUnsupportedMigrationError("format v2 surface before first step cannot acquire a system head without changing chronology");
18791
+ if (SURFACE_TYPES$1.has(event.type) && this.head === void 0) throw new SessionFormatUnsupportedMigrationError("format v2 surface before first step cannot acquire a system head without changing chronology");
18696
18792
  if (event.type === "session/end-seed" && data["inherited"] === true) {
18697
18793
  if (!this.input.sourceHeader.isSeeded) throw new SessionFormatError("format v2 unseeded Session contains an inherited end-seed marker");
18698
18794
  this.sourceCut = event.seq;
@@ -18723,8 +18819,8 @@ var ReleasedV2ToV3Stage = class {
18723
18819
  return sessionFormatCount(this.targetCut, "format v3 inherited event count");
18724
18820
  }
18725
18821
  observeMessageIds(event) {
18726
- const data = record(event.data, event.type);
18727
- const messages = event.type === "user/message" ? [data] : event.type === "assistant/message" || event.type === "tool/result" ? [record(data["message"], "message")] : event.type === "agent/inbox/spliced" ? data["inserted"] : event.type === "session/title-llm-request" ? data["messages"] : [];
18822
+ const data = record$2(event.data, event.type);
18823
+ const messages = event.type === "user/message" ? [data] : event.type === "assistant/message" || event.type === "tool/result" ? [record$2(data["message"], "message")] : event.type === "agent/inbox/spliced" ? data["inserted"] : event.type === "session/title-llm-request" ? data["messages"] : [];
18728
18824
  for (const message of messages) {
18729
18825
  const id = message["id"];
18730
18826
  if (this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError("source message id collides with a generated system message id");
@@ -18829,6 +18925,1500 @@ function renameMessageSource(message) {
18829
18925
  };
18830
18926
  }
18831
18927
  //#endregion
18928
+ //#region ../../../vendor/deepseek-harness/packages/session/session-format-v3-to-v4/lib/index.js
18929
+ /** Released V3 plugin-source conversion and declared message traversal. */
18930
+ /**
18931
+ * Visit only messages carried by first-party event payloads.
18932
+ * @param event - source or target logical event.
18933
+ * @param transform - message transform preserving unchanged identity.
18934
+ * @returns the event sharing all untouched payloads.
18935
+ */
18936
+ function mapEventMessages(event, transform) {
18937
+ const data = event.data;
18938
+ if (!isSessionFormatJsonObject(data)) return event;
18939
+ if (event.type === "user/message") {
18940
+ const message = transform(data);
18941
+ return message === data ? event : {
18942
+ ...event,
18943
+ data: message
18944
+ };
18945
+ }
18946
+ if (event.type === "developer/message" || event.type === "system/message" || event.type === "assistant/message" || event.type === "tool/result") {
18947
+ if (!isSessionFormatJsonObject(data["message"])) throw new SessionFormatError(event.type + " requires a message");
18948
+ const message = transform(data["message"]);
18949
+ return message === data["message"] ? event : {
18950
+ ...event,
18951
+ data: {
18952
+ ...data,
18953
+ message
18954
+ }
18955
+ };
18956
+ }
18957
+ const key = event.type === "agent/inbox/spliced" ? "inserted" : event.type === "session/title-llm-request" ? "messages" : void 0;
18958
+ if (key === void 0) return event;
18959
+ const messages = data[key];
18960
+ if (!Array.isArray(messages)) throw new SessionFormatError(event.type + " requires message array");
18961
+ const mapped = messages.map((message) => {
18962
+ if (!isSessionFormatJsonObject(message)) throw new SessionFormatError(event.type + " requires message objects");
18963
+ return transform(message);
18964
+ });
18965
+ return mapped.every((message, index) => message === messages[index]) ? event : {
18966
+ ...event,
18967
+ data: {
18968
+ ...data,
18969
+ [key]: mapped
18970
+ }
18971
+ };
18972
+ }
18973
+ /** Released V3 plugin names whose current producer kind is not the plugin string. */
18974
+ const RENAMED_PRODUCERS = Object.freeze({
18975
+ "compact": "compact-checkpoint",
18976
+ "tools-code-mode": "ptc-mode",
18977
+ "tools-ptc": "ptc-mode",
18978
+ "dsh-compaction-basic": "compact-basic",
18979
+ "@deepseek-ai/dsh-system-prompt": "runtime-context"
18980
+ });
18981
+ /** First-party V3 plugin identities that intentionally keep their current kind. */
18982
+ const RELEASED_SAME_NAME_PRODUCERS = /* @__PURE__ */ new Set([
18983
+ "agent-instructions",
18984
+ "session-reference",
18985
+ "team-message",
18986
+ "goal",
18987
+ "skill-invocation",
18988
+ "skill-catalog",
18989
+ "coordinator",
18990
+ "subagent-report",
18991
+ "subagent-settled",
18992
+ "webhook",
18993
+ "agent-message",
18994
+ "model-selection",
18995
+ "plan-mode",
18996
+ "time-context",
18997
+ "tmux-context",
18998
+ "user-approval",
18999
+ "repeat-tool-reminder",
19000
+ "tool-cordis",
19001
+ "cordis-host-runner",
19002
+ "tool-goal",
19003
+ "tool-jobs",
19004
+ "hooks-codex",
19005
+ "hooks-claude-code",
19006
+ "schedule",
19007
+ "dsh-session-title-llm"
19008
+ ]);
19009
+ /** Resolve the current producer kind for one released V3 plugin string. */
19010
+ function producerKind(plugin, role) {
19011
+ if (plugin === "@deepseek-ai/dsh-system-prompt" && role === "system") return "system-prompt";
19012
+ const renamed = Object.hasOwn(RENAMED_PRODUCERS, plugin) ? RENAMED_PRODUCERS[plugin] : void 0;
19013
+ if (renamed !== void 0) return renamed;
19014
+ if (RELEASED_SAME_NAME_PRODUCERS.has(plugin)) return plugin;
19015
+ return `plugin:${plugin}`;
19016
+ }
19017
+ /**
19018
+ * Lift one released V3 plugin source into the current producer-owned shape.
19019
+ * @param source - source record whose `kind` is `'plugin'`.
19020
+ * @param seq - event seq for diagnostics.
19021
+ * @param role - carried message role for role-sensitive renames.
19022
+ * @returns a copy whose kind is the producer's own kind and whose `plugin` field is dropped.
19023
+ */
19024
+ function rewritePluginSource(source, seq, role) {
19025
+ const plugin = source["plugin"];
19026
+ if (typeof plugin !== "string") throw new SessionFormatError(`plugin source at seq ${seq} is not canonical: plugin requires a string`);
19027
+ const kind = producerKind(plugin, role);
19028
+ if (Object.keys(source).length === 2) return { kind };
19029
+ return Object.fromEntries(Object.entries(source).filter(([key]) => key !== "plugin").map(([key, item]) => [key, key === "kind" ? kind : item]));
19030
+ }
19031
+ /**
19032
+ * Convert released plugin wrappers while retaining every direct source kind and its metadata.
19033
+ * @param source - decoded V3 message source.
19034
+ * @param seq - event sequence for diagnostics.
19035
+ * @param role - enclosing message role for role-sensitive producer mappings.
19036
+ * @returns the converted source with every non-identity field preserved.
19037
+ */
19038
+ function rewriteV3MessageSource(source, seq, role) {
19039
+ const kind = source["kind"];
19040
+ if (typeof kind !== "string" || kind.length === 0) throw new SessionFormatError(`message source at seq ${seq} requires a nonempty kind`);
19041
+ if (kind === "plugin") return rewritePluginSource(source, seq, role);
19042
+ return source;
19043
+ }
19044
+ /** Native source admission preserves unknown attribution and refuses retired plugin wrappers. */
19045
+ function source(message) {
19046
+ const value = message["source"];
19047
+ if (!isSessionFormatJsonObject(value) || typeof value["kind"] !== "string" || value["kind"].length === 0 || value["kind"] === "plugin") throw new SessionFormatError("format v4 message requires a producer-owned source kind");
19048
+ }
19049
+ /**
19050
+ * Validate producer attribution in every declared durable message slot.
19051
+ * @param event - complete native V4 event before Session adoption.
19052
+ */
19053
+ function assertV4MessageSources(event) {
19054
+ mapEventMessages(event, (message) => {
19055
+ source(message);
19056
+ return message;
19057
+ });
19058
+ }
19059
+ /**
19060
+ * Refuse retired source syntax even after a malformed recoverable row; leave incomplete messages to decoding.
19061
+ * @param row - parsed row whose incomplete values have not been classified by the scanner.
19062
+ */
19063
+ function assertV4SourceRowAdmission(row) {
19064
+ if (!isSessionFormatJsonObject(row) || !isSessionFormatJsonObject(row["data"])) return;
19065
+ const data = row["data"];
19066
+ const messages = row["type"] === "user/message" ? [data] : row["type"] === "system/message" || row["type"] === "assistant/message" || row["type"] === "tool/result" ? [data["message"]] : row["type"] === "agent/inbox/spliced" ? data["inserted"] : row["type"] === "session/title-llm-request" ? data["messages"] : [];
19067
+ if (!Array.isArray(messages)) return;
19068
+ for (const message of messages) {
19069
+ if (!isSessionFormatJsonObject(message)) continue;
19070
+ const value = message["source"];
19071
+ if (isSessionFormatJsonObject(value) && value["kind"] === "plugin") source(message);
19072
+ }
19073
+ }
19074
+ /** Retired native syntax remains a hard refusal even after a recoverable physical-row failure. */
19075
+ function assertBlock(block, subject) {
19076
+ if (isSessionFormatJsonObject(block) && block["type"] === "tool-result") throw new SessionFormatError(`${subject} must not contain a released tool-result wrapper`);
19077
+ }
19078
+ function assertContent$1(content, subject) {
19079
+ if (Array.isArray(content)) content.forEach((block) => {
19080
+ assertBlock(block, subject);
19081
+ });
19082
+ }
19083
+ function assertMessageContent(message, subject) {
19084
+ if (isSessionFormatJsonObject(message)) assertContent$1(message["content"], subject);
19085
+ }
19086
+ /**
19087
+ * Refuse retired headers, PTC tags, and tool-result blocks in interpreted content slots.
19088
+ * Tool arguments, replay state, schema parameters, and nested extension values remain opaque.
19089
+ * @param row - parsed physical row or complete native logical event.
19090
+ */
19091
+ function assertV4RetiredSyntax(row) {
19092
+ if (!isSessionFormatJsonObject(row)) return;
19093
+ if ((row["type"] === "tool/code-dispatch-start" || row["type"] === "tool/code-dispatch") && row["ignorable"] !== true) throw new SessionFormatUnsupportedMigrationError(`format v4 rejects retired event type ${row["type"]}`);
19094
+ const data = row["data"];
19095
+ if (row["type"] === "request/header") {
19096
+ if (!isSessionFormatJsonObject(data) || !isSessionFormatJsonObject(data["header"])) throw new SessionFormatError("format v4 request/header requires data and header objects");
19097
+ if (Object.hasOwn(data["header"], "system")) throw new SessionFormatError("format v4 request/header rejects retired header.system");
19098
+ return;
19099
+ }
19100
+ if (!isSessionFormatJsonObject(data)) return;
19101
+ const subject = `format v4 ${String(row["type"])} at seq ${String(row["seq"])} content`;
19102
+ switch (row["type"]) {
19103
+ case "user/message":
19104
+ assertContent$1(data["content"], subject);
19105
+ break;
19106
+ case "developer/message":
19107
+ case "assistant/message":
19108
+ case "team/message/queued":
19109
+ assertMessageContent(data["message"], subject);
19110
+ break;
19111
+ case "agent/inbox/spliced":
19112
+ case "session/title-llm-request": {
19113
+ const messages = data[row["type"] === "agent/inbox/spliced" ? "inserted" : "messages"];
19114
+ if (Array.isArray(messages)) messages.forEach((message) => {
19115
+ assertMessageContent(message, subject);
19116
+ });
19117
+ break;
19118
+ }
19119
+ case "compaction/summary":
19120
+ assertContent$1(data["summary"], subject);
19121
+ assertContent$1(data["rawOutput"], subject);
19122
+ break;
19123
+ case "tool/ptc-dispatch": assertContent$1(data["content"], subject);
19124
+ }
19125
+ if ((row["type"] === "assistant/message" || row["type"] === "assistant/attempt") && Array.isArray(data["stream"])) for (const entry of data["stream"]) {
19126
+ if (!isSessionFormatJsonObject(entry) || entry["type"] !== "chunk") continue;
19127
+ const chunk = entry["chunk"];
19128
+ if (!isSessionFormatJsonObject(chunk)) continue;
19129
+ if (chunk["type"] === "block-end") assertBlock(chunk["block"], subject);
19130
+ if (chunk["type"] === "block-start" && chunk["blockType"] === "tool-result") throw new SessionFormatError(`${subject} must not contain a released tool-result wrapper`);
19131
+ }
19132
+ }
19133
+ /** Native system-message fields checked before physical recovery can discard a row. */
19134
+ function record$1(value, subject) {
19135
+ if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(`format v4 ${subject} requires an object`);
19136
+ return value;
19137
+ }
19138
+ function string$1(value, subject, nonempty = false) {
19139
+ if (typeof value !== "string" || nonempty && value.length === 0) throw new SessionFormatError(`format v4 ${subject} requires a string${nonempty ? " with content" : ""}`);
19140
+ }
19141
+ function positive(value, subject) {
19142
+ if (sessionFormatCount(value, subject) === 0) throw new SessionFormatError(`format v4 ${subject} must be positive`);
19143
+ }
19144
+ function image(value) {
19145
+ const attachment = record$1(value, "system image attachment");
19146
+ string$1(attachment["attachmentId"], "system image attachmentId", true);
19147
+ if (![
19148
+ "image/png",
19149
+ "image/jpeg",
19150
+ "image/webp",
19151
+ "image/gif"
19152
+ ].includes(attachment["mediaType"])) throw new SessionFormatError("format v4 system image requires a supported mediaType");
19153
+ sessionFormatCount(attachment["bytes"], "system image bytes");
19154
+ positive(attachment["width"], "system image width");
19155
+ positive(attachment["height"], "system image height");
19156
+ if (attachment["name"] !== void 0) string$1(attachment["name"], "system image name");
19157
+ if (attachment["originalDimensions"] !== void 0) {
19158
+ const original = record$1(attachment["originalDimensions"], "system image originalDimensions");
19159
+ positive(original["width"], "system image original width");
19160
+ positive(original["height"], "system image original height");
19161
+ }
19162
+ }
19163
+ /**
19164
+ * Validate system fields independently of historical message representations; preserve additional JSON fields.
19165
+ * @param row - complete logical event or parsed physical row before corruption recovery.
19166
+ */
19167
+ function assertV4SystemMessageFields(row) {
19168
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "system/message") return;
19169
+ const data = record$1(row["data"], "system/message data");
19170
+ positive(data["turn"], "system/message turn");
19171
+ positive(data["step"], "system/message step");
19172
+ const message = record$1(data["message"], "system message");
19173
+ string$1(message["id"], "system message id", true);
19174
+ if (message["role"] !== "system") throw new SessionFormatError("format v4 system message requires system role");
19175
+ const content = message["content"];
19176
+ if (!Array.isArray(content)) throw new SessionFormatError("format v4 system message requires content array");
19177
+ for (const value of content) {
19178
+ const block = record$1(value, "system content block");
19179
+ string$1(block["type"], "system content type", true);
19180
+ switch (block["type"]) {
19181
+ case "text":
19182
+ case "reasoning":
19183
+ string$1(block["text"], "system content text");
19184
+ break;
19185
+ case "tool-call":
19186
+ string$1(block["id"], "system tool-call id", true);
19187
+ string$1(block["name"], "system tool-call name", true);
19188
+ string$1(block["arguments"], "system tool-call arguments");
19189
+ break;
19190
+ case "image":
19191
+ image(block["attachment"]);
19192
+ break;
19193
+ case "tool-result": throw new SessionFormatError("format v4 system content rejects retired tool-result wrappers");
19194
+ }
19195
+ }
19196
+ }
19197
+ /** Native V4 developer-message and deferred-tool-schema validation. */
19198
+ function assertToolChange(block, developer) {
19199
+ if (!isSessionFormatJsonObject(block) || block["type"] !== "tool-addition" && block["type"] !== "tool-removal") return;
19200
+ if (!developer) throw new SessionFormatError(`format v4 ${block["type"]} requires developer role`);
19201
+ if (typeof block["toolName"] !== "string" || block["toolName"].length === 0) throw new SessionFormatError(`format v4 ${block["type"]} requires a nonempty toolName`);
19202
+ if (block["type"] === "tool-addition" && Object.hasOwn(block, "tool")) throw new SessionFormatError("format v4 tool-addition must omit inline tool definitions");
19203
+ }
19204
+ function assertContent(value, developer = false) {
19205
+ if (Array.isArray(value)) value.forEach((block) => {
19206
+ assertToolChange(block, developer);
19207
+ });
19208
+ }
19209
+ function assertDeveloperMessage(message) {
19210
+ assertContent(message["content"], true);
19211
+ const source = message["source"];
19212
+ if (typeof message["id"] !== "string" || message["id"].length === 0 || !Array.isArray(message["content"]) || !isSessionFormatJsonObject(source) || typeof source["kind"] !== "string" || source["kind"].length === 0 || source["kind"] === "plugin") throw new SessionFormatError("format v4 developer message requires id, role, content, and a producer-owned source");
19213
+ }
19214
+ /** Ordinary-message corruption belongs to the decoder; recognized V4-only values are refused here. */
19215
+ function assertOrdinaryMessage(value) {
19216
+ if (!isSessionFormatJsonObject(value)) return;
19217
+ if (value["role"] === "developer") throw new SessionFormatError("format v4 developer/message and developer role must occur together");
19218
+ assertContent(value["content"]);
19219
+ }
19220
+ /**
19221
+ * Validate native V4 developer messages, tool-change blocks, and deferred tool schemas.
19222
+ * Additional developer JSON fields are preserved. Malformed legacy message
19223
+ * slots remain available to recoverable decoding.
19224
+ * @param event - logical event or physical row before source-event range decoding.
19225
+ */
19226
+ function assertV4DeveloperData(event) {
19227
+ const data = event.data;
19228
+ if (!isSessionFormatJsonObject(data)) {
19229
+ if (event.type === "developer/message") throw new SessionFormatError("format v4 developer/message data must be an object");
19230
+ return;
19231
+ }
19232
+ if (event.type === "developer/message") {
19233
+ const message = data["message"];
19234
+ if (!isSessionFormatJsonObject(message) || message["role"] !== "developer") throw new SessionFormatError("format v4 developer/message requires turn, step, and a developer message");
19235
+ for (const field of ["turn", "step"]) if (sessionFormatCount(data[field], `developer/message ${field}`) === 0) throw new SessionFormatError(`developer/message ${field} must be positive`);
19236
+ assertDeveloperMessage(message);
19237
+ if (message["content"].some((block) => isSessionFormatJsonObject(block) && block["type"] === "tool-addition")) sessionFormatCount(data["headerSeq"], "developer/message headerSeq");
19238
+ else if (Object.hasOwn(data, "headerSeq")) throw new SessionFormatError("format v4 developer/message must omit headerSeq without tool additions");
19239
+ } else switch (event.type) {
19240
+ case "user/message":
19241
+ assertOrdinaryMessage(data);
19242
+ break;
19243
+ case "system/message":
19244
+ case "assistant/message":
19245
+ case "tool/result":
19246
+ assertOrdinaryMessage(data["message"]);
19247
+ break;
19248
+ case "agent/inbox/spliced":
19249
+ case "session/title-llm-request": {
19250
+ const messages = data[event.type === "agent/inbox/spliced" ? "inserted" : "messages"];
19251
+ if (Array.isArray(messages)) messages.forEach(assertOrdinaryMessage);
19252
+ break;
19253
+ }
19254
+ }
19255
+ if (event.type === "compaction/summary") {
19256
+ assertContent(data["summary"]);
19257
+ assertContent(data["rawOutput"]);
19258
+ }
19259
+ if ((event.type === "assistant/message" || event.type === "assistant/attempt") && Array.isArray(data["stream"])) for (const entry of data["stream"]) {
19260
+ if (!isSessionFormatJsonObject(entry) || entry["type"] !== "chunk") continue;
19261
+ const chunk = entry["chunk"];
19262
+ if (!isSessionFormatJsonObject(chunk)) continue;
19263
+ if (chunk["type"] === "block-end") assertToolChange(chunk["block"], false);
19264
+ if (chunk["type"] === "block-start" && (chunk["blockType"] === "tool-addition" || chunk["blockType"] === "tool-removal")) throw new SessionFormatError("format v4 tool-change blocks require developer role");
19265
+ }
19266
+ if (event.type === "request/header" && isSessionFormatJsonObject(data["header"])) {
19267
+ const tools = data["header"]["tools"];
19268
+ if (Array.isArray(tools)) for (const tool of tools) {
19269
+ if (!isSessionFormatJsonObject(tool) || !Object.hasOwn(tool, "deferLoading")) continue;
19270
+ if (tool["deferLoading"] !== true) throw new SessionFormatError("format v4 tool deferLoading must be true when present");
19271
+ }
19272
+ }
19273
+ }
19274
+ /** Native V4 fork-result identity and not-started error validation. */
19275
+ /**
19276
+ * Validate fork-generated not-started results without changing their identity or text.
19277
+ * Append results carry their own sequence; replacement results retain an earlier identity.
19278
+ * @param row - logical event or physical event row.
19279
+ */
19280
+ function assertV4ForkResult(row) {
19281
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "tool/result") return;
19282
+ const data = row["data"];
19283
+ if (!isSessionFormatJsonObject(data)) return;
19284
+ const error = data["error"];
19285
+ const message = data["message"];
19286
+ if (!isSessionFormatJsonObject(error) || error["code"] !== "TOOL_NOT_STARTED" || !isSessionFormatJsonObject(message) || typeof message["id"] !== "string" || !message["id"].startsWith("forked-tool-result-")) return;
19287
+ const source = message["source"];
19288
+ const callId = isSessionFormatJsonObject(source) ? source["callId"] : void 0;
19289
+ const prefix = `forked-tool-result-${String(callId)}-`;
19290
+ const suffix = message["id"].slice(prefix.length);
19291
+ const operation = row["surfaceOp"];
19292
+ const replacement = isSessionFormatJsonObject(operation) && operation["op"] === "replace";
19293
+ const sequence = Number(suffix);
19294
+ const content = message["content"];
19295
+ const text = Array.isArray(content) && content.length === 1 ? content[0] : void 0;
19296
+ const sourceEventSeqs = row["sourceEventSeqs"];
19297
+ if (typeof callId !== "string" || !message["id"].startsWith(prefix) || !/^(0|[1-9]\d*)$/.test(suffix) || !Number.isSafeInteger(sequence) || (replacement ? typeof row["seq"] !== "number" || sequence >= row["seq"] : sequence !== row["seq"]) || error["name"] !== "ToolNotStartedError" || !replacement && (sourceEventSeqs !== void 0 || operation !== "append") || replacement && (!Array.isArray(sourceEventSeqs) || sourceEventSeqs.length !== 1 || sourceEventSeqs[0] !== sequence) || message["role"] !== "tool" || message["isError"] !== true || message["toolCallId"] !== callId || !isSessionFormatJsonObject(source) || source["kind"] !== "tool" || !isSessionFormatJsonObject(text) || text["type"] !== "text" || typeof text["text"] !== "string") throw new SessionFormatError("invalid V4 not-started fork result");
19298
+ }
19299
+ /** First-class tool-role messages in the V4 representation. */
19300
+ const WRAPPER_FIELDS = /* @__PURE__ */ new Set([
19301
+ "type",
19302
+ "toolCallId",
19303
+ "content",
19304
+ "isError"
19305
+ ]);
19306
+ const MESSAGE_FIELDS = /* @__PURE__ */ new Set([
19307
+ "id",
19308
+ "role",
19309
+ "source",
19310
+ "content"
19311
+ ]);
19312
+ /** Preserve unknown fields without merging their original message and result owners. */
19313
+ function extensionFields(value, fields, owner) {
19314
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !fields.has(key)).map(([key, field]) => [`plugin:${owner}:${key}`, field]));
19315
+ }
19316
+ function resultContent(value, subject) {
19317
+ if (!Array.isArray(value)) throw new SessionFormatError(`${subject} tool-result content must be an array`);
19318
+ if (value.some((block) => isSessionFormatJsonObject(block) && block["type"] === "tool-result")) throw new SessionFormatUnsupportedMigrationError(`${subject} contains a nested tool-result unsupported by this converter`);
19319
+ return value;
19320
+ }
19321
+ /**
19322
+ * Lift one released-V3 wrapper tool/result row into the first-class V4 message.
19323
+ * Validate the wrapper before dropping it so malformed historical content is
19324
+ * rejected instead of being silently truncated.
19325
+ * @param event - released wrapper tool/result event.
19326
+ * @returns the same event with a first-class tool-role message.
19327
+ * @throws {SessionFormatError} when the wrapper, tool source, content, or error flag is malformed.
19328
+ * @throws {SessionFormatUnsupportedMigrationError} when the result contains another result.
19329
+ */
19330
+ function liftToolResult(event) {
19331
+ if (event.type !== "tool/result" || !isSessionFormatJsonObject(event.data)) return event;
19332
+ const data = event.data;
19333
+ const message = data["message"];
19334
+ if (!isSessionFormatJsonObject(message) || message["role"] !== "user") return event;
19335
+ const source = message["source"];
19336
+ const callId = isSessionFormatJsonObject(source) ? source["callId"] : void 0;
19337
+ const content = message["content"];
19338
+ const block = Array.isArray(content) && content.length === 1 ? content[0] : void 0;
19339
+ const wrapper = isSessionFormatJsonObject(block) ? block : void 0;
19340
+ const id = message["id"];
19341
+ if (typeof id !== "string" || id.length === 0 || !isSessionFormatJsonObject(source) || source["kind"] !== "tool" || typeof callId !== "string" || callId.length === 0 || wrapper === void 0 || wrapper["type"] !== "tool-result" || wrapper["toolCallId"] !== callId) throw new SessionFormatError(`format v3 ${event.type} at seq ${event.seq} requires exactly one tool-result wrapper matching its tool source`);
19342
+ const isError = wrapper["isError"];
19343
+ if (isError !== void 0 && typeof isError !== "boolean") throw new SessionFormatError(`format v3 ${event.type} at seq ${event.seq} tool-result isError must be boolean`);
19344
+ const targetMessage = {
19345
+ role: "tool",
19346
+ source,
19347
+ toolCallId: callId,
19348
+ content: resultContent(wrapper["content"], `format v3 ${event.type} at seq ${event.seq}`),
19349
+ ...isError === void 0 ? {} : { isError },
19350
+ id,
19351
+ ...extensionFields(message, MESSAGE_FIELDS, "message"),
19352
+ ...extensionFields(wrapper, WRAPPER_FIELDS, "result")
19353
+ };
19354
+ return {
19355
+ ...event,
19356
+ data: {
19357
+ ...data,
19358
+ message: targetMessage
19359
+ }
19360
+ };
19361
+ }
19362
+ /**
19363
+ * Validate the native V4 first-class tool-role message of one tool/result row.
19364
+ * @param event - canonical V4 event.
19365
+ * @throws {SessionFormatError} when the row is not an exact first-class tool result.
19366
+ */
19367
+ function assertV4ToolResultMessage(event) {
19368
+ if (event.type !== "tool/result") return;
19369
+ const subject = `format v4 ${event.type} at seq ${event.seq}`;
19370
+ const data = event.data;
19371
+ if (!isSessionFormatJsonObject(data)) throw new SessionFormatError(`${subject} data must be an object`);
19372
+ const message = data["message"];
19373
+ if (!isSessionFormatJsonObject(message)) throw new SessionFormatError(`${subject} message must be an object`);
19374
+ const id = message["id"];
19375
+ const role = message["role"];
19376
+ const toolCallId = message["toolCallId"];
19377
+ const source = message["source"];
19378
+ const isError = message["isError"];
19379
+ const content = message["content"];
19380
+ const sourceCallId = isSessionFormatJsonObject(source) ? source["callId"] : void 0;
19381
+ if (typeof id !== "string" || id.length === 0) throw new SessionFormatError(`${subject} requires a first-class message with a string id`);
19382
+ if (role !== "tool") throw new SessionFormatError(`${subject} requires a tool-role message`);
19383
+ if (typeof toolCallId !== "string" || toolCallId.length === 0 || sourceCallId !== toolCallId) throw new SessionFormatError(`${subject} requires toolCallId matching its tool source`);
19384
+ if (!isSessionFormatJsonObject(source) || source["kind"] !== "tool") throw new SessionFormatError(`${subject} requires a tool source`);
19385
+ if (!Array.isArray(content)) throw new SessionFormatError(`${subject} requires array content`);
19386
+ if (content.some((block) => isSessionFormatJsonObject(block) && block["type"] === "tool-result")) throw new SessionFormatError(`${subject} content must not contain a released tool-result wrapper`);
19387
+ if (isError !== void 0 && typeof isError !== "boolean") throw new SessionFormatError(`${subject} isError must be boolean when present`);
19388
+ if (data["error"] !== void 0 && isError !== true) throw new SessionFormatError(`${subject} carries error metadata for a non-error tool result`);
19389
+ }
19390
+ /** Mandatory native V4 relationships; incomplete tails retain their open transactions. */
19391
+ const SURFACE_TYPES = /* @__PURE__ */ new Set([
19392
+ "system/message",
19393
+ "user/message",
19394
+ "developer/message",
19395
+ "assistant/message",
19396
+ "tool/result"
19397
+ ]);
19398
+ const STEP_EVENT_TYPES = /* @__PURE__ */ new Set([
19399
+ "system/message",
19400
+ "developer/message",
19401
+ "assistant/attempt"
19402
+ ]);
19403
+ const RELATIONSHIP_TYPES = /* @__PURE__ */ new Set([
19404
+ ...SURFACE_TYPES,
19405
+ ...STEP_EVENT_TYPES,
19406
+ "turn/start",
19407
+ "turn/end",
19408
+ "step/start",
19409
+ "step/end",
19410
+ "tool/call",
19411
+ "request/header",
19412
+ "request/context",
19413
+ "tool/ptc-dispatch-start",
19414
+ "tool/ptc-dispatch",
19415
+ "llm/retry",
19416
+ "llm/retry-started",
19417
+ "session/title",
19418
+ "session/title-llm-request",
19419
+ "command/run",
19420
+ "command/done",
19421
+ "compaction/start",
19422
+ "compaction/summary",
19423
+ "compaction/end",
19424
+ "compaction/prune",
19425
+ "session/end-seed"
19426
+ ]);
19427
+ function record(value, subject) {
19428
+ if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(`${subject} requires an object`);
19429
+ return value;
19430
+ }
19431
+ function text$2(value, subject) {
19432
+ if (typeof value !== "string" || value.length === 0) throw new SessionFormatError(`${subject} requires a nonempty string`);
19433
+ return value;
19434
+ }
19435
+ function array$1(value, subject) {
19436
+ if (!Array.isArray(value)) throw new SessionFormatError(`${subject} requires an array`);
19437
+ return value;
19438
+ }
19439
+ function earlier(value, seq, subject) {
19440
+ const coordinate = sessionFormatCount(value, subject);
19441
+ if (coordinate >= seq) throw new SessionFormatError(`${subject} must name an earlier event`);
19442
+ return coordinate;
19443
+ }
19444
+ /** One native artifact's lifecycle state; no state survives between reads. */
19445
+ var Relationships = class {
19446
+ artifact;
19447
+ knownEventTypes;
19448
+ turn = null;
19449
+ step = null;
19450
+ nextTurn = 1;
19451
+ nextStep = 1;
19452
+ provider;
19453
+ surface = [];
19454
+ protectedHead;
19455
+ compaction;
19456
+ tools = /* @__PURE__ */ new Map();
19457
+ dispatches = /* @__PURE__ */ new Map();
19458
+ retries = [];
19459
+ startedRetries = /* @__PURE__ */ new Set();
19460
+ commands = /* @__PURE__ */ new Set();
19461
+ orphanCompactions = /* @__PURE__ */ new Set();
19462
+ constructor(artifact, knownEventTypes) {
19463
+ this.artifact = artifact;
19464
+ this.knownEventTypes = knownEventTypes;
19465
+ let start;
19466
+ for (const event of artifact.events) {
19467
+ if (!knownEventTypes.has(event.type)) continue;
19468
+ if (event.type === "compaction/start") start = event.seq;
19469
+ if (event.type === "compaction/end") start = void 0;
19470
+ if (event.type === "session/end-seed") {
19471
+ if (start !== void 0) this.orphanCompactions.add(start);
19472
+ start = void 0;
19473
+ }
19474
+ }
19475
+ }
19476
+ requireTurn(type) {
19477
+ if (this.turn === null) throw new SessionFormatError(`${type} is outside an open turn`);
19478
+ }
19479
+ requireStep(event, data) {
19480
+ if (this.turn === null || this.step === null || data["turn"] !== this.turn || data["step"] !== this.step) throw new SessionFormatError(`${event.type} does not match an open turn and step`);
19481
+ }
19482
+ closeTools(type) {
19483
+ if (this.tools.size !== 0) throw new SessionFormatError(`${type} leaves unresolved tool call ${String(this.tools.keys().next().value)}`);
19484
+ this.tools.clear();
19485
+ }
19486
+ developer(event, data) {
19487
+ if (data["headerSeq"] === void 0) return;
19488
+ const headerSeq = earlier(data["headerSeq"], event.seq, "developer/message headerSeq");
19489
+ const headerEvent = this.artifact.events[headerSeq];
19490
+ if (headerEvent?.type !== "request/header") throw new SessionFormatError("developer/message headerSeq must reference an earlier request/header");
19491
+ if (!this.knownEventTypes.has(headerEvent.type)) throw new SessionFormatError("developer/message headerSeq must reference a known request/header");
19492
+ const tools = record(record(headerEvent.data, "request/header data")["header"], "request header")["tools"];
19493
+ const content = array$1(record(data["message"], "developer message")["content"], "developer content");
19494
+ for (const value of content) {
19495
+ if (!isSessionFormatJsonObject(value) || value["type"] !== "tool-addition") continue;
19496
+ const toolName = value["toolName"];
19497
+ const definitions = Array.isArray(tools) ? tools.filter((tool) => isSessionFormatJsonObject(tool) && tool["name"] === toolName) : [];
19498
+ if (definitions.length !== 1) throw new SessionFormatError(`developer/message tool-addition "${toolName}" must name exactly one tool in headerSeq ${headerSeq}`);
19499
+ const definition = definitions[0];
19500
+ if (typeof definition["description"] !== "string" || !isSessionFormatJsonObject(definition["parameters"])) throw new SessionFormatError(`developer/message tool-addition "${toolName}" requires a complete tool definition in headerSeq ${headerSeq}`);
19501
+ }
19502
+ }
19503
+ foldSurface(event) {
19504
+ if (!SURFACE_TYPES.has(event.type)) return;
19505
+ if (event.type === "system/message" && this.surface.length > 0 && this.protectedHead === void 0) throw new SessionFormatError("system/message requires a protected first surface head");
19506
+ if (event["surfaceOp"] === "append") {
19507
+ if (event.type === "system/message" && this.surface.length === 0) this.protectedHead = event.seq;
19508
+ this.surface.push(event.seq);
19509
+ return;
19510
+ }
19511
+ const operation = record(event["surfaceOp"], `${event.type} surfaceOp`);
19512
+ const first = this.surface.indexOf(earlier(operation["startSeq"], event.seq, "replacement start"));
19513
+ const last = this.surface.indexOf(earlier(operation["endSeq"], event.seq, "replacement end"));
19514
+ if (first < 0 || last < first) throw new SessionFormatError(`${event.type} replacement range is not on the current surface`);
19515
+ const removed = this.surface.slice(first, last + 1);
19516
+ const sources = array$1(event["sourceEventSeqs"], "replacement sourceEventSeqs");
19517
+ if (removed.some((seq) => !sources.includes(seq))) throw new SessionFormatError("replacement sourceEventSeqs omit a shadowed surface node");
19518
+ if (this.protectedHead !== void 0 && removed.includes(this.protectedHead)) {
19519
+ if (event.type !== "system/message" || removed.length !== 1) throw new SessionFormatError("surface replacement cannot shadow the protected system head");
19520
+ this.protectedHead = event.seq;
19521
+ }
19522
+ this.surface.splice(first, removed.length, event.seq);
19523
+ }
19524
+ tool(event, data) {
19525
+ if (event.type === "tool/result" && event["surfaceOp"] !== "append") {
19526
+ this.requireTurn(event.type);
19527
+ return;
19528
+ }
19529
+ this.requireStep(event, data);
19530
+ if (event.type === "assistant/message") {
19531
+ const message = record(data["message"], "assistant message");
19532
+ for (const value of array$1(message["content"], "assistant content")) {
19533
+ const block = record(value, "assistant content block");
19534
+ if (block["type"] !== "tool-call") continue;
19535
+ const id = text$2(block["id"], "tool call id");
19536
+ if (this.tools.has(id)) throw new SessionFormatError(`assistant/message repeats advertised tool call ${id}`);
19537
+ this.tools.set(id, {
19538
+ name: block["name"],
19539
+ arguments: block["arguments"],
19540
+ started: false
19541
+ });
19542
+ }
19543
+ return;
19544
+ }
19545
+ const message = event.type === "tool/result" ? record(data["message"], "tool result message") : void 0;
19546
+ const id = text$2(message === void 0 ? data["callId"] : message["toolCallId"], "tool call id");
19547
+ const pending = this.tools.get(id);
19548
+ if (pending === void 0) throw new SessionFormatError(`${event.type} ${id} has no advertised tool lifecycle`);
19549
+ if (message === void 0) {
19550
+ if (pending.started || pending.name !== data["name"] || pending.arguments !== data["arguments"]) throw new SessionFormatError(`tool/call ${id} does not match one advertised tool call`);
19551
+ pending.started = true;
19552
+ } else {
19553
+ if (!pending.started && !notStartedRepair(event, data, message, id)) throw new SessionFormatError(`tool/result ${id} is not the exact TOOL_NOT_STARTED repair`);
19554
+ this.tools.delete(id);
19555
+ }
19556
+ }
19557
+ dispatch(event, data) {
19558
+ this.requireTurn(event.type);
19559
+ const id = text$2(data["subCallId"], "PTC subCallId");
19560
+ const root = text$2(data["rootCallId"], "PTC rootCallId");
19561
+ const parent = text$2(data["parentCallId"], "PTC parentCallId");
19562
+ const existing = this.dispatches.get(id);
19563
+ if (existing !== void 0 && existing.data["rootCallId"] !== root) throw new SessionFormatError("PTC dispatch changes its rootCallId");
19564
+ if (parent !== root && this.dispatches.get(parent)?.data["rootCallId"] !== root) throw new SessionFormatError("PTC parentCallId does not belong to rootCallId");
19565
+ if (event.type === "tool/ptc-dispatch-start") {
19566
+ if (existing !== void 0) throw new SessionFormatError("PTC dispatch repeats subCallId");
19567
+ this.dispatches.set(id, {
19568
+ data,
19569
+ settled: false
19570
+ });
19571
+ return;
19572
+ }
19573
+ if (existing === void 0 || existing.settled) throw new SessionFormatError("PTC dispatch has no unique start");
19574
+ if ([
19575
+ "rootCallId",
19576
+ "parentCallId",
19577
+ "name",
19578
+ "arguments"
19579
+ ].some((key) => !isDeepStrictEqual(existing.data[key], data[key]))) throw new SessionFormatError("PTC dispatch does not match its start");
19580
+ existing.settled = true;
19581
+ }
19582
+ retry(event, data) {
19583
+ const id = text$2(data["retryId"], "retryId");
19584
+ const attempt = sessionFormatCount(data["retry"], "retry");
19585
+ if (event.type === "llm/retry-started") {
19586
+ const scheduled = this.retries.find((item) => item["retryId"] === id && item["retry"] === attempt);
19587
+ if (scheduled === void 0) throw new SessionFormatError("llm/retry-started pairs no prior scheduled attempt");
19588
+ if (scheduled["turn"] !== data["turn"] || scheduled["step"] !== data["step"]) throw new SessionFormatError("llm/retry-started changes scheduled coordinates");
19589
+ const key = JSON.stringify([id, attempt]);
19590
+ if (this.startedRetries.has(key)) throw new SessionFormatError("llm/retry-started repeats one scheduled attempt");
19591
+ this.startedRetries.add(key);
19592
+ return;
19593
+ }
19594
+ if (this.turn === null || data["turn"] !== this.turn || data["step"] !== (this.step ?? this.nextStep - 1)) throw new SessionFormatError("llm/retry does not match the current turn and step");
19595
+ if (data["provider"] !== this.provider) throw new SessionFormatError("llm/retry provider does not match the open request/header");
19596
+ const prior = this.retries.findLast((item) => [
19597
+ "turn",
19598
+ "step",
19599
+ "provider",
19600
+ "policyKey"
19601
+ ].every((key) => item[key] === data[key]));
19602
+ if (attempt !== (prior === void 0 ? 1 : sessionFormatCount(prior["retry"], "prior retry") + 1)) throw new SessionFormatError("llm/retry skips its policy attempt sequence");
19603
+ if (prior === void 0 ? this.retries.some((item) => item["retryId"] === id) : prior["retryId"] !== id) throw new SessionFormatError("llm/retry must keep one retryId per policy chain");
19604
+ this.retries.push(data);
19605
+ }
19606
+ compact(event, data) {
19607
+ if (event.type === "compaction/prune" || event.type === "compaction/summary") this.span(event, data);
19608
+ if (event.type === "compaction/prune") return;
19609
+ if (event.type === "compaction/start") {
19610
+ if (this.compaction !== void 0) throw new SessionFormatError("compaction/start overlaps an open compaction");
19611
+ const owner = data["turn"] === null ? null : sessionFormatCount(data["turn"], "compaction turn");
19612
+ if (owner !== this.turn) throw new SessionFormatError("compaction/start does not match the open turn");
19613
+ this.compaction = {
19614
+ id: text$2(data["compactionId"], "compactionId"),
19615
+ command: data["sourceCommandId"],
19616
+ turn: owner,
19617
+ seq: event.seq,
19618
+ summarized: false
19619
+ };
19620
+ return;
19621
+ }
19622
+ const current = this.compactionOwner(data, event.type);
19623
+ if (current.turn !== this.turn) throw new SessionFormatError(`${event.type} does not match the open turn`);
19624
+ if (event.type === "compaction/summary") {
19625
+ if (current.summarized) throw new SessionFormatError("compaction/summary repeats");
19626
+ current.summarized = true;
19627
+ } else {
19628
+ if (data["turn"] !== current.turn) throw new SessionFormatError("compaction/end changes its owner turn");
19629
+ if (data["error"] === void 0 && !current.summarized) throw new SessionFormatError("successful compaction/end requires one summary");
19630
+ this.compaction = void 0;
19631
+ }
19632
+ }
19633
+ compactionOwner(data, subject) {
19634
+ if (this.compaction === void 0 || this.compaction.id !== data["compactionId"] || this.compaction.command !== data["sourceCommandId"]) throw new SessionFormatError(`${subject} has no matching compaction/start`);
19635
+ return this.compaction;
19636
+ }
19637
+ span(event, data) {
19638
+ const range = record(data["shadowedRange"], "compaction shadowedRange");
19639
+ const start = this.surface.indexOf(earlier(range["start"], event.seq, "compaction range start"));
19640
+ const end = this.surface.indexOf(earlier(range["end"], event.seq, "compaction range end"));
19641
+ const seqs = array$1(data["shadowedSeqs"], "compaction shadowedSeqs");
19642
+ if (start < 0 || end < start || !isDeepStrictEqual(this.surface.slice(start, end + 1), seqs)) throw new SessionFormatError(`${event.type} shadowedSeqs do not name an exact current surface span`);
19643
+ if (this.protectedHead !== void 0 && seqs.includes(this.protectedHead)) throw new SessionFormatError("compaction cannot shadow the protected system head");
19644
+ }
19645
+ accept(event) {
19646
+ if (!this.knownEventTypes.has(event.type) || !RELATIONSHIP_TYPES.has(event.type)) return;
19647
+ const data = record(event.data, event.type);
19648
+ if (STEP_EVENT_TYPES.has(event.type)) this.requireStep(event, data);
19649
+ if (event.type.startsWith("turn/") && this.compaction !== void 0 && !this.orphanCompactions.has(this.compaction.seq)) throw new SessionFormatError(`${event.type} crosses an open compaction`);
19650
+ this.foldSurface(event);
19651
+ switch (event.type) {
19652
+ case "turn/start":
19653
+ if (this.turn !== null || data["turn"] !== this.nextTurn) throw new SessionFormatError("turn/start does not open the expected turn");
19654
+ this.turn = this.nextTurn;
19655
+ this.nextStep = 1;
19656
+ this.tools.clear();
19657
+ break;
19658
+ case "turn/end":
19659
+ if (this.turn === null || data["turn"] !== this.turn || this.step !== null) throw new SessionFormatError("turn/end does not match the open turn with no open step");
19660
+ this.closeTools(event.type);
19661
+ this.turn = null;
19662
+ this.nextTurn += 1;
19663
+ break;
19664
+ case "step/start":
19665
+ if (this.turn === null || data["turn"] !== this.turn || this.step !== null || data["step"] !== this.nextStep) throw new SessionFormatError("step/start does not match the open turn and next step");
19666
+ this.step = this.nextStep;
19667
+ break;
19668
+ case "step/end":
19669
+ this.requireStep(event, data);
19670
+ this.closeTools(event.type);
19671
+ this.step = null;
19672
+ this.nextStep += 1;
19673
+ break;
19674
+ case "assistant/message":
19675
+ case "tool/call":
19676
+ case "tool/result":
19677
+ this.tool(event, data);
19678
+ break;
19679
+ case "developer/message":
19680
+ this.developer(event, data);
19681
+ break;
19682
+ case "request/header":
19683
+ this.requireTurn(event.type);
19684
+ this.provider = record(record(data["header"], "request header")["config"], "request config")["provider"];
19685
+ break;
19686
+ case "request/context":
19687
+ this.requireTurn(event.type);
19688
+ break;
19689
+ case "tool/ptc-dispatch-start":
19690
+ case "tool/ptc-dispatch":
19691
+ this.dispatch(event, data);
19692
+ break;
19693
+ case "llm/retry":
19694
+ case "llm/retry-started":
19695
+ this.retry(event, data);
19696
+ break;
19697
+ case "session/title":
19698
+ case "session/title-llm-request":
19699
+ titleSources(this.artifact.events, event, data, this.knownEventTypes);
19700
+ break;
19701
+ case "command/run": {
19702
+ const id = text$2(data["commandId"], "commandId");
19703
+ if (this.commands.has(id)) throw new SessionFormatError(`command/run repeats commandId ${id}`);
19704
+ this.commands.add(id);
19705
+ break;
19706
+ }
19707
+ case "command/done":
19708
+ if (!this.commands.has(text$2(data["commandId"], "commandId"))) throw new SessionFormatError("command/done has no prior command/run");
19709
+ if (data["sourceEventSeq"] !== void 0) {
19710
+ const source = this.artifact.events[earlier(data["sourceEventSeq"], event.seq, "command sourceEventSeq")];
19711
+ if (data["kind"] !== "success" || source?.type === "command/run" || source?.type === "command/done") throw new SessionFormatError("command/done has invalid sourceEventSeq");
19712
+ }
19713
+ break;
19714
+ case "compaction/start":
19715
+ case "compaction/summary":
19716
+ case "compaction/end":
19717
+ case "compaction/prune":
19718
+ this.compact(event, data);
19719
+ break;
19720
+ case "user/message": {
19721
+ const source = record(data["source"], "user message source");
19722
+ if (event["surfaceOp"] !== "append" && source["kind"] === "compact-checkpoint") this.compactionOwner(source, "compaction checkpoint");
19723
+ break;
19724
+ }
19725
+ case "session/end-seed": this.compaction = void 0;
19726
+ }
19727
+ }
19728
+ };
19729
+ function notStartedRepair(event, data, message, callId) {
19730
+ const error = record(data["error"], "not-started error");
19731
+ if (error["name"] !== "ToolNotStartedError" || error["code"] !== "TOOL_NOT_STARTED" || message["isError"] !== true || event["sourceEventSeqs"] !== void 0) return false;
19732
+ const id = text$2(message["id"], "not-started message id");
19733
+ if (id.startsWith(`forked-tool-result-${callId}-`)) return true;
19734
+ const prefix = `interrupted-tool-result-${callId}-`;
19735
+ const suffix = id.slice(prefix.length);
19736
+ const content = array$1(message["content"], "not-started content");
19737
+ const block = content[0];
19738
+ return id.startsWith(prefix) && /^(0|[1-9]\d*)$/.test(suffix) && Number.isSafeInteger(Number(suffix)) && content.length === 1 && isSessionFormatJsonObject(block) && block["type"] === "text" && block["text"] === "The tool call was interrupted before the Harness recorded it as started. Retry it if it is still needed.";
19739
+ }
19740
+ function titleSources(events, event, data, knownEventTypes) {
19741
+ const references = array$1(data["messageSeqs"], "title messageSeqs");
19742
+ if (event.type === "session/title" && references.length === 0 !== (record(data["source"], "title source")["kind"] === "user")) throw new SessionFormatError("session/title messageSeqs must be empty exactly for a user title");
19743
+ const seen = /* @__PURE__ */ new Set();
19744
+ for (const value of references) {
19745
+ const seq = earlier(value, event.seq, "title messageSeqs");
19746
+ const source = events[seq];
19747
+ if (seen.has(seq) || source?.type !== "user/message" || !knownEventTypes.has(source.type) || record(record(source.data, "title input")["source"], "title input source")["kind"] !== "user") throw new SessionFormatError(`${event.type} messageSeqs must cite distinct earlier human user/message events`);
19748
+ seen.add(seq);
19749
+ }
19750
+ if (event.type !== "session/title-llm-request") return;
19751
+ const messages = array$1(data["messages"], "title messages");
19752
+ const message = record(messages[0], "title message");
19753
+ const content = array$1(message["content"], "title content");
19754
+ const block = content[0];
19755
+ if (references.length === 0 || messages.length !== 1 || message["role"] !== "user" || record(message["source"], "title source")["kind"] !== "dsh-session-title-llm" || content.length !== 1 || !isSessionFormatJsonObject(block) || block["type"] !== "text") throw new SessionFormatError("session/title-llm-request messages do not represent messageSeqs");
19756
+ }
19757
+ /**
19758
+ * Validate native V4 lifecycle and ownership facts without rewriting any event.
19759
+ * @param artifact - artifact whose V4 envelopes and messages have been admitted.
19760
+ * @param knownEventTypes - installed event types whose payloads this reader interprets.
19761
+ */
19762
+ function assertV4LifecycleRelationships(artifact, knownEventTypes) {
19763
+ const state = new Relationships(artifact, knownEventTypes);
19764
+ for (const event of artifact.events) state.accept(event);
19765
+ }
19766
+ /**
19767
+ * Validate supplemental child evidence supplied across the migration JSON interface.
19768
+ * @param value - collected child identity and optional descriptor.
19769
+ * @returns validated evidence; descriptor interpretation is deferred until parent membership is known.
19770
+ */
19771
+ function childCatalogSource(value) {
19772
+ if (!isSessionFormatJsonObject(value) || typeof value["childId"] !== "string" || !Object.hasOwn(value, "descriptor")) throw new SessionFormatUnsupportedMigrationError("catalog migration requires historical child identity and descriptor evidence");
19773
+ sessionFormatCount(value["childCreatedAt"], "catalog child creation time");
19774
+ sessionFormatCount(value["descriptorCount"], "child descriptor count");
19775
+ return value;
19776
+ }
19777
+ /**
19778
+ * Interpret only the discovery fields of known historical descriptors.
19779
+ * @param source - validated child evidence.
19780
+ * @returns a catalog fact, or undefined without exactly one supported own descriptor.
19781
+ */
19782
+ function childCatalogFact(source) {
19783
+ const id = source["childId"];
19784
+ const descriptor = source["descriptor"];
19785
+ const count = source["descriptorCount"];
19786
+ const known = isSessionFormatJsonObject(descriptor) && [
19787
+ 1,
19788
+ 2,
19789
+ 3
19790
+ ].includes(descriptor["version"]);
19791
+ if (count !== 1 || !known) return void 0;
19792
+ if (typeof descriptor["provider"] !== "string") throw new SessionFormatUnsupportedMigrationError(`${childCatalogSubject(source)} has an invalid subagent descriptor provider`);
19793
+ if (descriptor["version"] !== 1 && descriptor["mode"] !== "continuable" && descriptor["mode"] !== "one-shot") throw new SessionFormatUnsupportedMigrationError(`${childCatalogSubject(source)} has an invalid subagent descriptor mode`);
19794
+ return catalogFact({
19795
+ version: 0,
19796
+ childId: id,
19797
+ childCreatedAt: source["childCreatedAt"],
19798
+ mode: descriptor["version"] === 1 ? "continuable" : descriptor["mode"],
19799
+ ...descriptor["label"] === void 0 ? {} : { label: descriptor["label"] }
19800
+ }, childCatalogSubject(source));
19801
+ }
19802
+ /**
19803
+ * Validate the historical fields used for catalog membership without interpreting extensions.
19804
+ * @param value - catalog payload or prepared historical discovery fact.
19805
+ * @param subject - event type or child identity used in rejection diagnostics.
19806
+ * @returns validated catalog payload.
19807
+ */
19808
+ function catalogFact(value, subject = "subagent/catalog") {
19809
+ if (!isSessionFormatJsonObject(value) || value["version"] !== 0 && value["version"] !== 1 || typeof value["childId"] !== "string" || value["mode"] !== "continuable" && value["mode"] !== "one-shot" && value["mode"] !== "unknown" || value["version"] === 0 && value["mode"] === "unknown" || value["mode"] === "continuable" && typeof value["label"] !== "string" || value["label"] !== void 0 && typeof value["label"] !== "string") throw new SessionFormatError(`${subject} requires a supported versioned catalog fact`);
19810
+ sessionFormatCount(value["childCreatedAt"], "catalog child creation time");
19811
+ return value;
19812
+ }
19813
+ /**
19814
+ * Name the child and its optional storage-owned location in a migration diagnostic.
19815
+ * @param source - validated supplemental child evidence.
19816
+ * @returns a child identity with its source path when persistence supplied one.
19817
+ */
19818
+ function childCatalogSubject(source) {
19819
+ return `Session ${source["childId"]}${typeof source["sourcePath"] === "string" ? ` (raw log: ${source["sourcePath"]})` : ""}`;
19820
+ }
19821
+ /** Native V4 metadata and generation-owned relationship validation. */
19822
+ /**
19823
+ * Validate the exact native V4 logical header.
19824
+ * @param header - decoded or otherwise untrusted V4 Session header candidate.
19825
+ */
19826
+ function assertReleasedV4Header(header) {
19827
+ if (!isSessionFormatJsonObject(header) || header["version"] !== 4) throw new SessionFormatError("expected format v4 header");
19828
+ const required = [
19829
+ "version",
19830
+ "id",
19831
+ "createdAt",
19832
+ "isSeeded",
19833
+ "delegationDepth"
19834
+ ];
19835
+ const allowed = /* @__PURE__ */ new Set([
19836
+ ...required,
19837
+ "cwd",
19838
+ "parentSession",
19839
+ "origin",
19840
+ "agentPreset"
19841
+ ]);
19842
+ const missing = required.find((key) => !Object.hasOwn(header, key));
19843
+ const unexpected = Object.keys(header).find((key) => !allowed.has(key));
19844
+ if (missing !== void 0) throw new SessionFormatError(`format v4 header lacks required field ${missing}`);
19845
+ if (unexpected !== void 0) throw new SessionFormatError(`format v4 header has unexpected field ${unexpected}`);
19846
+ if (typeof header.id !== "string") throw new SessionFormatError("format v4 header id must be a string");
19847
+ sessionFormatCount(header.createdAt, "format v4 header createdAt");
19848
+ sessionFormatCount(header.delegationDepth, "format v4 header delegationDepth");
19849
+ if (typeof header.isSeeded !== "boolean") throw new SessionFormatError("format v4 header isSeeded must be boolean");
19850
+ if (header.cwd !== void 0 && (typeof header.cwd !== "string" || !isAbsolute(header.cwd))) throw new SessionFormatError("format v4 header cwd must be absolute");
19851
+ for (const key of ["parentSession", "agentPreset"]) if (header[key] !== void 0 && typeof header[key] !== "string") throw new SessionFormatError(`format v4 header ${key} must be a string`);
19852
+ if (header.origin !== void 0 && header.origin !== "subagent") throw new SessionFormatError("format v4 header origin must be \"subagent\"");
19853
+ }
19854
+ /**
19855
+ * Validate V4 inheritance, vocabulary, native message admission, and
19856
+ * lifecycle, compaction, tool, retry, title, command, catalog, and delivery ownership.
19857
+ * Installed Session restoration owns common event envelopes and message acceptance.
19858
+ * @param artifact - complete detached V4 artifact.
19859
+ * @param knownEventTypes - event types understood by the installed Session package.
19860
+ * @returns the same validated artifact and event objects.
19861
+ */
19862
+ function restoreReleasedV4Artifact(artifact, knownEventTypes) {
19863
+ assertReleasedV4Header(artifact.header);
19864
+ const cut = sessionFormatCount(artifact.inheritedEventCount, "format v4 inherited event count");
19865
+ if (cut > artifact.events.length) throw new SessionFormatError("format v4 inherited event count exceeds its events");
19866
+ if (!artifact.header.isSeeded && cut !== 0) throw new SessionFormatError("unseeded format v4 Session has inherited events");
19867
+ let lastInheritedMarker;
19868
+ for (const [index, event] of artifact.events.entries()) {
19869
+ if (!knownEventTypes.has(event.type) && event["ignorable"] !== true) throw new SessionFormatUnsupportedMigrationError(`format v4 contains unknown event type ${JSON.stringify(event.type)} at seq ${index}`);
19870
+ if (event.seq !== index) throw new SessionFormatError(`format v4 event ${index} is not dense`);
19871
+ if (!knownEventTypes.has(event.type)) continue;
19872
+ assertV4RetiredSyntax(event);
19873
+ assertV4SystemMessageFields(event);
19874
+ assertV4ToolResultMessage(event);
19875
+ assertV4ForkResult(event);
19876
+ if (event.type === "session/end-seed" && isSessionFormatJsonObject(event.data) && event.data["inherited"] === true) lastInheritedMarker = index;
19877
+ }
19878
+ if (artifact.header.isSeeded && lastInheritedMarker !== cut) throw new SessionFormatError("format v4 seeded header disagrees with its last inherited end-seed marker");
19879
+ if (!artifact.header.isSeeded && lastInheritedMarker !== void 0) throw new SessionFormatError("format v4 unseeded Session contains an inherited end-seed marker");
19880
+ assertReleasedV4Relationships(artifact, knownEventTypes);
19881
+ return artifact;
19882
+ }
19883
+ /**
19884
+ * Validate delivery generation and active-generation coordinates before evaluating ownership.
19885
+ * @param event - decoded event whose delivery payload may be inspected.
19886
+ * @param currentVersion - generation whose watermark coordinates are active.
19887
+ * @returns the active delivery's nonempty Session id, or undefined for other events and generations.
19888
+ */
19889
+ function validateDeliveryAccepted(event, currentVersion) {
19890
+ if (event.type !== "session-log-deepseek/delivery-accepted") return void 0;
19891
+ const data = event.data;
19892
+ if (!isSessionFormatJsonObject(data)) throw new SessionFormatError("delivery-accepted data must be an object");
19893
+ if (sessionFormatCount(data["sessionFormatVersion"] === void 0 ? 0 : data["sessionFormatVersion"], "delivery sessionFormatVersion") !== currentVersion) return void 0;
19894
+ if (sessionFormatCount(data["throughSeq"], "delivery throughSeq") >= event.seq) throw new SessionFormatError("delivery throughSeq must precede its marker");
19895
+ const id = data["sessionId"];
19896
+ if (typeof id !== "string" || id.length === 0) throw new SessionFormatError("delivery requires a nonempty Session id");
19897
+ return id;
19898
+ }
19899
+ /**
19900
+ * Validate native developer fields, message sources, lifecycle, catalog, and delivery
19901
+ * relationships without changing event vocabulary or tail recovery.
19902
+ * The owning admission stage rejects unknown required events;
19903
+ * unknown ignorable records retain their uninterpreted payloads.
19904
+ * @param artifact - decoded artifact with its final inherited cut.
19905
+ * @param knownEventTypes - installed event types whose payloads this reader interprets.
19906
+ */
19907
+ function assertReleasedV4Relationships(artifact, knownEventTypes) {
19908
+ const ids = /* @__PURE__ */ new Set();
19909
+ for (const event of artifact.events) {
19910
+ if (!knownEventTypes.has(event.type)) continue;
19911
+ assertV4DeveloperData(event);
19912
+ assertV4MessageSources(event);
19913
+ const deliveryId = validateDeliveryAccepted(event, 4);
19914
+ if (deliveryId !== void 0 && !(artifact.header.parentSession !== void 0 && event.seq < artifact.inheritedEventCount) && deliveryId !== artifact.header.id) throw new SessionFormatError("current-generation delivery marker names the wrong Session");
19915
+ if (event.type === "subagent/catalog" && event.seq >= artifact.inheritedEventCount) {
19916
+ const id = catalogFact(event.data)["childId"];
19917
+ if (ids.has(id)) throw new SessionFormatError(`duplicate catalog child ${id}`);
19918
+ ids.add(id);
19919
+ }
19920
+ }
19921
+ assertV4LifecycleRelationships(artifact, knownEventTypes);
19922
+ }
19923
+ /** V4 framing with native tool-role admission and released physical rows. */
19924
+ function physicalV2(value) {
19925
+ if (!isSessionFormatJsonObject(value) || value["version"] !== 4) throw new SessionFormatError("expected format v4 physical header");
19926
+ return {
19927
+ ...value,
19928
+ version: 2
19929
+ };
19930
+ }
19931
+ /**
19932
+ * V4 physical encoder and decoder retain the released row framing while
19933
+ * validating the native tool-role message directly.
19934
+ */
19935
+ const releasedV4SessionFormatCodec = Object.freeze({
19936
+ version: 4,
19937
+ decodeHeader(value) {
19938
+ return {
19939
+ ...releasedV2SessionFormatCodec.decodeHeader(physicalV2(value)),
19940
+ version: 4
19941
+ };
19942
+ },
19943
+ createDecoder(value, recovery) {
19944
+ const decoder = releasedV2SessionFormatCodec.createDecoder(physicalV2(value), recovery);
19945
+ return {
19946
+ ...decoder,
19947
+ header: {
19948
+ ...decoder.header,
19949
+ version: 4
19950
+ },
19951
+ decodeRow(row, context) {
19952
+ assertV4RowAdmission(row);
19953
+ decoder.decodeRow(row, {
19954
+ emitRun: context.emitRun.bind(context),
19955
+ emitEvent: context.emitEvent.bind(context)
19956
+ });
19957
+ }
19958
+ };
19959
+ },
19960
+ encodeHeader(header, inheritedEventCount) {
19961
+ assertReleasedV4Header(header);
19962
+ return {
19963
+ ...releasedV2SessionFormatCodec.encodeHeader({
19964
+ ...header,
19965
+ version: 2
19966
+ }, inheritedEventCount),
19967
+ version: 4
19968
+ };
19969
+ },
19970
+ encodeEvent(event) {
19971
+ if (event.type === "developer/message" && event["ignorable"] === true) {
19972
+ assertV4DeveloperData(event);
19973
+ assertV4RetiredSyntax(event);
19974
+ }
19975
+ assertV4RowAdmission(event);
19976
+ return releasedV2SessionFormatCodec.encodeEvent(event);
19977
+ }
19978
+ });
19979
+ /**
19980
+ * Apply native V4 admission before a scanner discards a recoverable suffix.
19981
+ * Ignorable developer payloads require reader vocabulary; physical decoding defers them.
19982
+ * @param row - parsed physical row before framing and source-event range decoding.
19983
+ * @param knownEventTypes - installed event types, supplied by native readers before tail recovery.
19984
+ */
19985
+ function assertV4RowAdmission(row, knownEventTypes) {
19986
+ if (isSessionFormatJsonObject(row)) {
19987
+ if (row["type"] === "developer/message" && row["ignorable"] === true && knownEventTypes?.has("developer/message") !== true) return;
19988
+ assertV4DeveloperData(row);
19989
+ }
19990
+ assertV4SourceRowAdmission(row);
19991
+ assertV4RetiredSyntax(row);
19992
+ assertV4SystemMessageFields(row);
19993
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "tool/result") return;
19994
+ const event = row;
19995
+ assertV4ToolResultMessage(event);
19996
+ assertV4ForkResult(event);
19997
+ }
19998
+ /** V3 content tags and request-tool fields admitted before V4 interpretation. */
19999
+ const V3_BLOCK_TYPES = /* @__PURE__ */ new Set([
20000
+ "text",
20001
+ "reasoning",
20002
+ "image",
20003
+ "file",
20004
+ "tool-call",
20005
+ "tool-result"
20006
+ ]);
20007
+ function migrateBlock(value, subject) {
20008
+ if (!isSessionFormatJsonObject(value) || typeof value["type"] !== "string") throw new SessionFormatError(`${subject} requires content blocks with string type tags`);
20009
+ const type = value["type"];
20010
+ if (V3_BLOCK_TYPES.has(type)) return value;
20011
+ return {
20012
+ ...value,
20013
+ type: `plugin:${type}`
20014
+ };
20015
+ }
20016
+ /**
20017
+ * Convert declared content tags while keeping arguments and extension data opaque.
20018
+ * @param value - released content array.
20019
+ * @param subject - owning event and content location for diagnostics.
20020
+ * @returns content in the same order, sharing unchanged blocks and image references.
20021
+ */
20022
+ function migrateV3Content(value, subject) {
20023
+ if (!Array.isArray(value)) throw new SessionFormatError(`${subject} content must be an array`);
20024
+ const mapped = value.map((block, index) => migrateBlock(block, `${subject}[${index}]`));
20025
+ return mapped.every((block, index) => block === value[index]) ? value : mapped;
20026
+ }
20027
+ function migrateMessage(message, subject) {
20028
+ const content = migrateV3Content(message["content"], subject);
20029
+ return content === message["content"] ? message : {
20030
+ ...message,
20031
+ content
20032
+ };
20033
+ }
20034
+ function migrateChunk(value, subject) {
20035
+ if (!isSessionFormatJsonObject(value)) return value;
20036
+ if (value["type"] === "block-end") {
20037
+ const block = migrateBlock(value["block"], `${subject}.block`);
20038
+ return block === value["block"] ? value : {
20039
+ ...value,
20040
+ block
20041
+ };
20042
+ }
20043
+ if (value["type"] !== "block-start") return value;
20044
+ const original = value["blockType"];
20045
+ if (typeof original !== "string") throw new SessionFormatError(`${subject} blockType must be a string`);
20046
+ const blockType = V3_BLOCK_TYPES.has(original) ? original : `plugin:${original}`;
20047
+ return blockType === original ? value : {
20048
+ ...value,
20049
+ blockType
20050
+ };
20051
+ }
20052
+ /**
20053
+ * Convert declared V3 extension content and stream tags after canonical result lifting.
20054
+ * @param event - source event after producer attribution conversion.
20055
+ * @returns the event with namespaced content tags, retaining all coordinates and other fields.
20056
+ * @throws {SessionFormatUnsupportedMigrationError} A V3 tool definition contains the V4-only deferLoading field.
20057
+ */
20058
+ function migrateV3EventContent(event) {
20059
+ const subject = `format v3 ${event.type} at seq ${event.seq}`;
20060
+ let mapped = mapEventMessages(event, (message) => migrateMessage(message, subject));
20061
+ if (!isSessionFormatJsonObject(mapped.data)) return mapped;
20062
+ let data = mapped.data;
20063
+ const contentField = (key) => {
20064
+ const content = migrateV3Content(data[key], `${subject}.${key}`);
20065
+ if (content !== data[key]) data = {
20066
+ ...data,
20067
+ [key]: content
20068
+ };
20069
+ };
20070
+ if (event.type === "compaction/summary") {
20071
+ contentField("summary");
20072
+ if (data["rawOutput"] !== void 0) contentField("rawOutput");
20073
+ } else if (event.type === "tool/ptc-dispatch") contentField("content");
20074
+ else if (event.type === "team/message/queued" && isSessionFormatJsonObject(data["message"])) {
20075
+ const message = migrateMessage(data["message"], subject);
20076
+ if (message !== data["message"]) data = {
20077
+ ...data,
20078
+ message
20079
+ };
20080
+ }
20081
+ if (event.type === "request/header" && isSessionFormatJsonObject(data["header"])) {
20082
+ const tools = data["header"]["tools"];
20083
+ if (Array.isArray(tools)) {
20084
+ for (const [index, tool] of tools.entries()) if (isSessionFormatJsonObject(tool) && Object.hasOwn(tool, "deferLoading")) throw new SessionFormatUnsupportedMigrationError(`${subject}.header.tools[${index}] contains deferLoading, which is only defined in V4`);
20085
+ }
20086
+ }
20087
+ if ((event.type === "assistant/message" || event.type === "assistant/attempt") && Array.isArray(data["stream"])) {
20088
+ const stream = data["stream"];
20089
+ const converted = stream.map((entry, index) => {
20090
+ if (!isSessionFormatJsonObject(entry) || entry["type"] !== "chunk") return entry;
20091
+ const chunk = migrateChunk(entry["chunk"], `${subject}.stream[${index}]`);
20092
+ return chunk === entry["chunk"] ? entry : {
20093
+ ...entry,
20094
+ chunk
20095
+ };
20096
+ });
20097
+ if (converted.some((entry, index) => entry !== stream[index])) data = {
20098
+ ...data,
20099
+ stream: converted
20100
+ };
20101
+ }
20102
+ if (data !== mapped.data) mapped = {
20103
+ ...mapped,
20104
+ data
20105
+ };
20106
+ return mapped;
20107
+ }
20108
+ /** Fixed V3 event vocabulary and namespaced historical opaque events. */
20109
+ /** First-party event names understood by the released V3 reader, independent of the installed writer. */
20110
+ const RELEASED_V3_EVENT_TYPES = /* @__PURE__ */ new Set([
20111
+ "agent-preset/selected",
20112
+ "agent/inbox/spliced",
20113
+ "approval/asked",
20114
+ "approval/decided",
20115
+ "approval/policy",
20116
+ "assistant/attempt",
20117
+ "assistant/message",
20118
+ "command/done",
20119
+ "command/run",
20120
+ "compaction/end",
20121
+ "compaction/prune",
20122
+ "compaction/start",
20123
+ "compaction/summary",
20124
+ "deliverables/presented",
20125
+ "feedback/message-delete",
20126
+ "feedback/message-put",
20127
+ "feedback/record",
20128
+ "goal/change",
20129
+ "hook/invoked",
20130
+ "hook/result",
20131
+ "image/offload",
20132
+ "llm/retry",
20133
+ "llm/retry-started",
20134
+ "model/selection",
20135
+ "permission/preset",
20136
+ "plan/mode",
20137
+ "request/context",
20138
+ "request/header",
20139
+ "sandbox/mode",
20140
+ "schedule/change",
20141
+ "session-log-deepseek/delivery-accepted",
20142
+ "session/end-seed",
20143
+ "session/title",
20144
+ "session/title-llm-request",
20145
+ "step/end",
20146
+ "step/start",
20147
+ "subagent/catalog",
20148
+ "subagent/descriptor",
20149
+ "subagent/model-selection-policy",
20150
+ "system/message",
20151
+ "team/member",
20152
+ "team/message/delivered",
20153
+ "team/message/queued",
20154
+ "team/task",
20155
+ "todo/write",
20156
+ "tool-workflow/agent-end",
20157
+ "tool-workflow/agent-start",
20158
+ "tool-workflow/run-end",
20159
+ "tool-workflow/run-start",
20160
+ "tool/call",
20161
+ "tool/ptc-dispatch",
20162
+ "tool/ptc-dispatch-start",
20163
+ "tool/result",
20164
+ "turn/end",
20165
+ "turn/start",
20166
+ "user/message",
20167
+ "web/deepseek-search-llm-request",
20168
+ "workspace/changes"
20169
+ ]);
20170
+ /**
20171
+ * Keep unknown ignorable events opaque after header promotion.
20172
+ * @param event - original V3 event; this incoming identity conversion is applied once.
20173
+ * @returns the same event or an ignorable namespaced event retaining its payload and coordinates.
20174
+ */
20175
+ function namespaceV3OpaqueEvent(event) {
20176
+ return event["ignorable"] === true && !RELEASED_V3_EVENT_TYPES.has(event.type) ? {
20177
+ ...event,
20178
+ type: `plugin:${event.type}`,
20179
+ ignorable: true
20180
+ } : event;
20181
+ }
20182
+ /** Remap released V3 local event references after interrupted-turn insertion. */
20183
+ function object$1(value) {
20184
+ if (!isSessionFormatJsonObject(value)) throw new SessionFormatError("V3 event reference container must be an object");
20185
+ return value;
20186
+ }
20187
+ /**
20188
+ * Remap first-party local references, retaining generation-qualified captures and numeric payloads.
20189
+ * @param event - first-party source event before envelope renumbering.
20190
+ * @param seq - target event position.
20191
+ * @param mapping - target positions of all earlier source events.
20192
+ * @returns the event with target coordinates; unchanged events retain their identity.
20193
+ */
20194
+ function remapV3References(event, seq, mapping) {
20195
+ if (seq === event.seq) return event;
20196
+ const reference = (value) => {
20197
+ const source = sessionFormatCount(value, "V3 source event reference");
20198
+ const target = mapping[source];
20199
+ if (source >= event.seq || target === void 0) throw new SessionFormatError("V3 reference must name an earlier source event");
20200
+ return target;
20201
+ };
20202
+ const references = (value) => {
20203
+ if (!Array.isArray(value)) throw new SessionFormatError("V3 event references must be an array");
20204
+ return value.map(reference);
20205
+ };
20206
+ let data = event.data;
20207
+ switch (event.type) {
20208
+ case "command/done": {
20209
+ const source = object$1(data);
20210
+ if (source["sourceEventSeq"] !== void 0) data = {
20211
+ ...source,
20212
+ sourceEventSeq: reference(source["sourceEventSeq"])
20213
+ };
20214
+ break;
20215
+ }
20216
+ case "compaction/summary":
20217
+ case "compaction/prune": {
20218
+ const source = object$1(data);
20219
+ const range = object$1(source["shadowedRange"]);
20220
+ data = {
20221
+ ...source,
20222
+ shadowedRange: {
20223
+ ...range,
20224
+ start: reference(range["start"]),
20225
+ end: reference(range["end"])
20226
+ },
20227
+ shadowedSeqs: references(source["shadowedSeqs"])
20228
+ };
20229
+ break;
20230
+ }
20231
+ case "session/title":
20232
+ case "session/title-llm-request": {
20233
+ const source = object$1(data);
20234
+ data = {
20235
+ ...source,
20236
+ messageSeqs: references(source["messageSeqs"])
20237
+ };
20238
+ break;
20239
+ }
20240
+ case "image/offload": {
20241
+ const source = object$1(data);
20242
+ const targets = source["targets"];
20243
+ if (!Array.isArray(targets)) throw new SessionFormatError("V3 image offload targets must be an array");
20244
+ data = {
20245
+ ...source,
20246
+ targets: targets.map((value) => {
20247
+ const target = object$1(value);
20248
+ return {
20249
+ ...target,
20250
+ seq: reference(target["seq"])
20251
+ };
20252
+ })
20253
+ };
20254
+ break;
20255
+ }
20256
+ }
20257
+ const surface = event["surfaceOp"];
20258
+ const range = surface === void 0 || surface === "append" ? void 0 : object$1(surface);
20259
+ return {
20260
+ ...event,
20261
+ seq,
20262
+ data,
20263
+ ...event["sourceEventSeqs"] === void 0 ? {} : { sourceEventSeqs: references(event["sourceEventSeqs"]) },
20264
+ ...range === void 0 ? {} : { surfaceOp: {
20265
+ ...range,
20266
+ startSeq: reference(range["startSeq"]),
20267
+ endSeq: reference(range["endSeq"])
20268
+ } }
20269
+ };
20270
+ }
20271
+ /** Append historical child facts after converting V3 source events to V4. */
20272
+ /** Header-only migration declaration; body restoration requires explicit child evidence. */
20273
+ const sessionFormatV3ToV4 = defineSessionFormatMigration({
20274
+ name: "@deepseek-ai/dsh-session-format-v3-to-v4",
20275
+ fromVersion: 3,
20276
+ toVersion: 4,
20277
+ migrateHeader(header) {
20278
+ assertReleasedV3Header(header);
20279
+ return {
20280
+ ...header,
20281
+ version: 4
20282
+ };
20283
+ },
20284
+ createStage() {
20285
+ throw new SessionFormatUnsupportedMigrationError("V3 catalog migration requires explicit historical child facts, including an empty array for a parent without children");
20286
+ },
20287
+ validateTargetHeader: assertReleasedV4Header
20288
+ });
20289
+ /**
20290
+ * Bind one parent's historical child evidence to its V3→V4 migration.
20291
+ * @param children - complete child evidence retained unchanged for the lifetime of this declaration; an empty array declares no children.
20292
+ * @returns an adjacent migration that creates independent stages with the supplied evidence.
20293
+ */
20294
+ function createSessionFormatV3ToV4(children) {
20295
+ return defineSessionFormatMigration({
20296
+ ...sessionFormatV3ToV4,
20297
+ createStage: (input) => new ReleasedV3ToV4Stage(input, children)
20298
+ });
20299
+ }
20300
+ var ReleasedV3ToV4Stage = class {
20301
+ input;
20302
+ headerInheritedEventCount;
20303
+ candidates;
20304
+ catalogs = [];
20305
+ cut;
20306
+ sourceCut;
20307
+ mapping = [];
20308
+ turn;
20309
+ stepOpen = false;
20310
+ nextTurnSpliced = false;
20311
+ nextSeq = 0;
20312
+ time;
20313
+ foreignDeliverySeq;
20314
+ constructor(input, children) {
20315
+ this.input = input;
20316
+ this.candidates = children.map(childCatalogSource).sort((left, right) => left["childCreatedAt"] - right["childCreatedAt"] || (left["childId"] === right["childId"] ? 0 : left["childId"] < right["childId"] ? -1 : 1));
20317
+ this.cut = input.sourceHeader.isSeeded ? void 0 : 0;
20318
+ this.sourceCut = this.cut;
20319
+ if (!input.sourceHeader.isSeeded) this.headerInheritedEventCount = 0;
20320
+ this.time = input.sourceHeader.createdAt;
20321
+ }
20322
+ transformEvent(event, context) {
20323
+ if (event.seq !== this.mapping.length) throw new SessionFormatError("V3 source events must be dense");
20324
+ const interrupted = this.observeRestart(event);
20325
+ if (interrupted !== void 0) context.emitEvent({
20326
+ type: "turn/end",
20327
+ seq: this.nextSeq++,
20328
+ time: event.time,
20329
+ data: {
20330
+ turn: interrupted,
20331
+ reason: { kind: "interrupted" }
20332
+ }
20333
+ });
20334
+ const targetSeq = this.nextSeq++;
20335
+ this.time = event.time;
20336
+ if (event.type === "session/end-seed" && isSessionFormatJsonObject(event.data) && event.data["inherited"] === true) {
20337
+ if (!this.input.sourceHeader.isSeeded) throw new SessionFormatError("unseeded format v3 Session contains an inherited end-seed marker");
20338
+ this.sourceCut = event.seq;
20339
+ this.cut = targetSeq;
20340
+ this.catalogs.length = 0;
20341
+ } else if (event.type === "subagent/catalog") this.catalogs.push(event.data);
20342
+ const deliveryId = validateDeliveryAccepted(event, 3);
20343
+ if (event.type === "session-log-deepseek/delivery-accepted") {
20344
+ if (event.data["sessionFormatVersion"] === 4) throw new SessionFormatUnsupportedMigrationError("format v3 delivery marker claims target format v4");
20345
+ if (deliveryId !== void 0 && deliveryId !== this.input.sourceHeader.id) this.foreignDeliverySeq = event.seq;
20346
+ }
20347
+ const opaque = namespaceV3OpaqueEvent(event);
20348
+ if (opaque !== event) {
20349
+ this.mapping.push(targetSeq);
20350
+ context.emitEvent(opaque.seq === targetSeq ? opaque : {
20351
+ ...opaque,
20352
+ seq: targetSeq
20353
+ });
20354
+ return;
20355
+ }
20356
+ if (!RELEASED_V3_EVENT_TYPES.has(event.type)) throw new SessionFormatUnsupportedMigrationError(`format v3 contains unknown event type ${JSON.stringify(event.type)} at seq ${event.seq}`);
20357
+ const remapped = remapV3References(event, targetSeq, this.mapping);
20358
+ this.mapping.push(targetSeq);
20359
+ const rewritten = mapEventMessages(remapped, (message) => {
20360
+ const source = message["source"];
20361
+ if (!isSessionFormatJsonObject(source)) return message;
20362
+ const converted = rewriteV3MessageSource(source, event.seq, message["role"]);
20363
+ return converted === source ? message : {
20364
+ ...message,
20365
+ source: converted
20366
+ };
20367
+ });
20368
+ context.emitEvent(migrateV3EventContent(liftToolResult(rewritten)));
20369
+ }
20370
+ transformRun(run, context) {
20371
+ for (const event of run.expand()) this.transformEvent(event, context);
20372
+ }
20373
+ observeRestart(event) {
20374
+ const data = event.data;
20375
+ const interrupted = event.type === "turn/start" && this.turn !== void 0 && !this.stepOpen && this.nextTurnSpliced && isSessionFormatJsonObject(data) && data["turn"] === this.turn + 1 ? this.turn : void 0;
20376
+ this.nextTurnSpliced = event.type === "agent/inbox/spliced" && isSessionFormatJsonObject(data) && data["target"] === "next-turn" && Array.isArray(data["inserted"]) && data["inserted"].length > 0;
20377
+ if (event.type === "turn/start" && isSessionFormatJsonObject(data) && typeof data["turn"] === "number") this.turn = data["turn"];
20378
+ else if (event.type === "turn/end") this.turn = void 0;
20379
+ else if (event.type === "step/start") this.stepOpen = true;
20380
+ else if (event.type === "step/end") this.stepOpen = false;
20381
+ return interrupted;
20382
+ }
20383
+ finish(context) {
20384
+ const cut = sessionFormatCount(this.cut, "V3 inherited event count");
20385
+ const sourceCut = sessionFormatCount(this.sourceCut, "V3 source inherited event count");
20386
+ const existingCatalogs = /* @__PURE__ */ new Map();
20387
+ for (const data of this.catalogs) {
20388
+ const fact = catalogFact(data);
20389
+ const id = fact["childId"];
20390
+ if (existingCatalogs.has(id)) throw new SessionFormatUnsupportedMigrationError(`duplicate catalog child ${id}`);
20391
+ existingCatalogs.set(id, fact);
20392
+ }
20393
+ if (this.input.sourceInheritedEventCount !== void 0 && sourceCut !== this.input.sourceInheritedEventCount) throw new SessionFormatError("format v3 inherited cut disagrees with its source marker");
20394
+ if (this.foreignDeliverySeq !== void 0 && (this.input.sourceHeader.parentSession === void 0 || this.foreignDeliverySeq >= sourceCut)) throw new SessionFormatError("current-generation delivery marker names the wrong Session");
20395
+ for (const source of this.candidates) {
20396
+ const id = source["childId"];
20397
+ const existing = existingCatalogs.get(id);
20398
+ const fact = childCatalogFact(source);
20399
+ if (existing !== void 0) {
20400
+ if (existing["childCreatedAt"] !== source["childCreatedAt"]) throw new SessionFormatUnsupportedMigrationError(`${childCatalogSubject(source)} conflicts with its parent catalog`);
20401
+ if (fact !== void 0 && ["mode", "label"].some((key) => existing[key] !== fact[key])) throw new SessionFormatUnsupportedMigrationError(`${childCatalogSubject(source)} conflicts with its parent catalog`);
20402
+ continue;
20403
+ }
20404
+ const entry = fact ?? {
20405
+ version: 1,
20406
+ childId: id,
20407
+ childCreatedAt: source["childCreatedAt"],
20408
+ mode: "unknown"
20409
+ };
20410
+ existingCatalogs.set(id, entry);
20411
+ context.emitEvent({
20412
+ type: "subagent/catalog",
20413
+ seq: this.nextSeq++,
20414
+ time: this.time,
20415
+ data: entry
20416
+ });
20417
+ }
20418
+ return cut;
20419
+ }
20420
+ };
20421
+ //#endregion
18832
20422
  //#region ../../../vendor/deepseek-harness/packages/session/session-format-catalog/lib/index.js
18833
20423
  /** Pure first-party message interpreters for detached current-format replay, including browser readers. */
18834
20424
  /** Installed interpretation definitions; recovery listeners are mounted separately by their owning plugins. */
@@ -18856,34 +20446,72 @@ function validateInstalledCurrentSessionArtifact(artifact) {
18856
20446
  * GENERATED by `scripts/gen-session-format-catalog.ts` — do not edit by hand.
18857
20447
  * The direct imports make historical readability independent of mounted plugins.
18858
20448
  */
18859
- /** Physical codec dispatch and complete adjacent chain, independent of mounted plugins. */
18860
- const sessionFormatCatalog = createSessionFormatCatalog({
18861
- currentVersion: 3,
20449
+ /** Static assembly shared by current reads and parent-specific historical restoration. */
20450
+ const sessionFormatCatalogOptions = {
20451
+ currentVersion: 4,
18862
20452
  codecs: [
18863
20453
  releasedV0SessionFormatCodec,
18864
20454
  releasedV1SessionFormatCodec,
18865
20455
  releasedV2SessionFormatCodec,
18866
- releasedV3SessionFormatCodec
20456
+ releasedV3SessionFormatCodec,
20457
+ releasedV4SessionFormatCodec
18867
20458
  ],
18868
- currentEncoder: releasedV3SessionFormatCodec,
20459
+ currentEncoder: releasedV4SessionFormatCodec,
18869
20460
  migrations: [
18870
20461
  sessionFormatV0ToV1,
18871
20462
  sessionFormatV1ToV2,
18872
- sessionFormatV2ToV3
20463
+ sessionFormatV2ToV3,
20464
+ sessionFormatV3ToV4
18873
20465
  ],
18874
20466
  restoreCurrent(artifact) {
18875
- const restored = restoreReleasedV3Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
20467
+ const restored = restoreReleasedV4Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
18876
20468
  validateInstalledCurrentSessionArtifact(restored);
18877
20469
  return restored;
18878
20470
  },
18879
20471
  restoreTransformedCurrent(artifact) {
18880
- return restoreReleasedV3Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
20472
+ return restoreReleasedV4Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
18881
20473
  },
18882
20474
  restoreCurrentHeader(header) {
18883
- assertReleasedV3Header(header);
20475
+ assertReleasedV4Header(header);
18884
20476
  validateInstalledCurrentSessionHeader(header);
18885
20477
  return header;
18886
20478
  }
20479
+ };
20480
+ /** Physical codec dispatch and complete adjacent chain, independent of mounted plugins. */
20481
+ const sessionFormatCatalog = createSessionFormatCatalog(sessionFormatCatalogOptions);
20482
+ /** Bind parent-specific child evidence into the static first-party migration inventory. */
20483
+ /**
20484
+ * Assemble a catalog whose V3→V4 edge knows one parent's historical children.
20485
+ * @param children - complete child evidence retained unchanged for the catalog's lifetime; an empty array declares no children.
20486
+ * @returns a catalog with independent restore state per artifact and unchanged current-format readers.
20487
+ */
20488
+ function createSessionFormatCatalogWithChildren(children) {
20489
+ const migration = createSessionFormatV3ToV4(children);
20490
+ return createSessionFormatCatalog({
20491
+ ...sessionFormatCatalogOptions,
20492
+ migrations: sessionFormatCatalogOptions.migrations.map((edge) => edge === sessionFormatV3ToV4 ? migration : edge)
20493
+ });
20494
+ }
20495
+ createSessionFormatCatalog({
20496
+ currentVersion: 3,
20497
+ codecs: [
20498
+ releasedV0SessionFormatCodec,
20499
+ releasedV1SessionFormatCodec,
20500
+ releasedV2SessionFormatCodec,
20501
+ releasedV3SessionFormatCodec
20502
+ ],
20503
+ currentEncoder: releasedV3SessionFormatCodec,
20504
+ migrations: [
20505
+ sessionFormatV0ToV1,
20506
+ sessionFormatV1ToV2,
20507
+ sessionFormatV2ToV3
20508
+ ],
20509
+ restoreCurrent: (artifact) => restoreReleasedV3Artifact(artifact, RELEASED_V3_EVENT_TYPES),
20510
+ restoreTransformedCurrent: (artifact) => restoreReleasedV3Artifact(artifact, RELEASED_V3_EVENT_TYPES),
20511
+ restoreCurrentHeader(header) {
20512
+ assertReleasedV3Header(header);
20513
+ return header;
20514
+ }
18887
20515
  });
18888
20516
  //#endregion
18889
20517
  //#region ../session-rdb/src/log.ts
@@ -18910,7 +20538,8 @@ function rowToMeta(row) {
18910
20538
  ...row.fParentSession !== null ? { parentSession: row.fParentSession } : {},
18911
20539
  isSeeded: row.fSeedLength !== null,
18912
20540
  ...row.fOrigin !== null ? { origin: row.fOrigin } : {},
18913
- ...row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth }
20541
+ ...row.fDelegationDepth === null ? {} : { delegationDepth: row.fDelegationDepth },
20542
+ ...row.fAgentPreset === null ? {} : { agentPreset: row.fAgentPreset }
18914
20543
  };
18915
20544
  }
18916
20545
  function sessionInsertRow(storage, incarnation) {
@@ -18926,6 +20555,7 @@ function sessionInsertRow(storage, incarnation) {
18926
20555
  fSeedLength: meta.isSeeded ? storage.inheritedEventCount : null,
18927
20556
  fOrigin: meta.origin ?? null,
18928
20557
  fDelegationDepth: meta.delegationDepth ?? null,
20558
+ fAgentPreset: meta.agentPreset ?? null,
18929
20559
  fIncarnation: incarnation,
18930
20560
  fRevision: 0
18931
20561
  };
@@ -18939,7 +20569,8 @@ function sessionConflictRow(storage) {
18939
20569
  fParentSession: meta.parentSession ?? null,
18940
20570
  fSeedLength: meta.isSeeded ? storage.inheritedEventCount : null,
18941
20571
  fOrigin: meta.origin ?? null,
18942
- fDelegationDepth: meta.delegationDepth ?? null
20572
+ fDelegationDepth: meta.delegationDepth ?? null,
20573
+ fAgentPreset: meta.agentPreset ?? null
18943
20574
  };
18944
20575
  }
18945
20576
  function rowToEvent(row) {
@@ -19030,28 +20661,15 @@ function toolResultRewriteContentOnly(original, replacement) {
19030
20661
  const replacementData = replacement.data;
19031
20662
  const originalMessage = originalData["message"];
19032
20663
  const replacementMessage = replacementData["message"];
19033
- const originalContent = Array.isArray(originalMessage?.content) ? originalMessage.content : void 0;
19034
- const replacementContent = Array.isArray(replacementMessage?.content) ? replacementMessage.content : void 0;
19035
- if (originalContent === void 0 || replacementContent === void 0) return false;
19036
- return isDeepEqualJson({
19037
- ...originalData,
20664
+ if (!Array.isArray(originalMessage?.content) || !Array.isArray(replacementMessage?.content)) return false;
20665
+ const rest = (data, message) => ({
20666
+ ...data,
19038
20667
  message: {
19039
- ...originalMessage,
19040
- content: [{
19041
- ...originalContent[0],
19042
- content: null
19043
- }]
19044
- }
19045
- }, {
19046
- ...replacementData,
19047
- message: {
19048
- ...replacementMessage,
19049
- content: [{
19050
- ...replacementContent[0],
19051
- content: null
19052
- }]
20668
+ ...message,
20669
+ content: null
19053
20670
  }
19054
20671
  });
20672
+ return isDeepEqualJson(rest(originalData, originalMessage), rest(replacementData, replacementMessage));
19055
20673
  }
19056
20674
  function findSurfaceRepairs(events) {
19057
20675
  const nodes = [];
@@ -19241,6 +20859,87 @@ function renameLegacyPtcEvents(events) {
19241
20859
  }
19242
20860
  }
19243
20861
  }
20862
+ /**
20863
+ * 把**旧代(v3 及更早)的消息形状**归一到当前格式(v4):迁移链(严格)拒绝那些形状时,读路径仍要
20864
+ * 能把它们读出来——只动字段与标记,不改坐标、不改内容。
20865
+ *
20866
+ * 三件事:
20867
+ * - `system/message` 的 source:`{ kind: 'plugin', plugin: '@deepseek-ai/dsh-system-prompt' }` → `{ kind: 'system-prompt' }`
20868
+ * (v4 起 system 消息只认这个 kind);
20869
+ * - `tool/result` 的消息:v3 是 user 角色、结果块包在 `content[0]`;v4 是 `role: 'tool'` + 顶层
20870
+ * `toolCallId` + `content` 就是结果块;
20871
+ * - 本仓库自己写过、上游已删除的事件类型(见 `LEGACY_OWN_EVENT_TYPES`)标 `ignorable: true`——v4 的
20872
+ * 校验对未知类型只接受可忽略信封。
20873
+ *
20874
+ * 只认这张白名单:**其它未知类型保持 fail loud**(那是「数据来自更新的 harness」的信号,不能吞掉)。
20875
+ */
20876
+ function normalizeToCurrentShape(events) {
20877
+ for (const event of events) {
20878
+ const type = event.type;
20879
+ if (LEGACY_OWN_EVENT_TYPES.has(type)) {
20880
+ event.ignorable = true;
20881
+ continue;
20882
+ }
20883
+ if (type === "system/message") {
20884
+ const message = event.data["message"];
20885
+ liftSystemMessageSource(message);
20886
+ continue;
20887
+ }
20888
+ if (type === "tool/result") {
20889
+ const message = event.data["message"];
20890
+ liftToolResultMessage(message);
20891
+ }
20892
+ }
20893
+ }
20894
+ function liftSystemMessageSource(message) {
20895
+ if (typeof message !== "object" || message === null) return;
20896
+ const record = message;
20897
+ const source = record["source"];
20898
+ if (typeof source !== "object" || source === null) return;
20899
+ if (source["kind"] !== "plugin") return;
20900
+ record["source"] = { kind: "system-prompt" };
20901
+ }
20902
+ function liftToolResultMessage(message) {
20903
+ if (typeof message !== "object" || message === null) return;
20904
+ const record = message;
20905
+ if (record["role"] === "tool" && typeof record["toolCallId"] === "string") return;
20906
+ const source = record["source"];
20907
+ const block = Array.isArray(record["content"]) ? record["content"][0] : void 0;
20908
+ if (typeof block !== "object" || block === null) return;
20909
+ const lifted = block;
20910
+ const callId = lifted["toolCallId"] ?? source?.["callId"];
20911
+ if (typeof callId !== "string") return;
20912
+ record["role"] = "tool";
20913
+ record["toolCallId"] = callId;
20914
+ record["content"] = Array.isArray(lifted["content"]) ? lifted["content"] : [];
20915
+ if (typeof lifted["isError"] === "boolean") record["isError"] = lifted["isError"];
20916
+ record["source"] = {
20917
+ kind: "tool",
20918
+ callId
20919
+ };
20920
+ }
20921
+ /**
20922
+ * 本仓库写过、后来连同机制一起删除的事件类型:读的时候标 `ignorable: true` 让校验跳过它们。
20923
+ * 新增历史类型时登记到这里——**不要**用「所有未知类型」代替(那会掩盖更新版本的未知事件)。
20924
+ */
20925
+ const LEGACY_OWN_EVENT_TYPES = /* @__PURE__ */ new Set(["session-branch/version"]);
20926
+ /** 行里是否还带着旧代消息形状(`session-branch/version` 这类自造事件也算)——写路径曾把回退视图的结果
20927
+ * 以当前版本号落库,所以**版本号不可信**,读之前要按内容判一次。 */
20928
+ function hasLegacyShape(events) {
20929
+ for (const event of events) {
20930
+ const type = event.type;
20931
+ if (LEGACY_OWN_EVENT_TYPES.has(type)) return true;
20932
+ if (type === "system/message") {
20933
+ if ((event.data["message"]?.["source"])?.["kind"] === "plugin") return true;
20934
+ continue;
20935
+ }
20936
+ if (type === "tool/result") {
20937
+ const message = event.data["message"];
20938
+ if (message !== void 0 && message["role"] !== "tool") return true;
20939
+ }
20940
+ }
20941
+ return false;
20942
+ }
19244
20943
  function repairReadView(events) {
19245
20944
  repairAssistantSettlement(events);
19246
20945
  repairRequestHeaders(events);
@@ -22956,7 +24655,7 @@ function buildColumn(name, c, tables) {
22956
24655
  let col;
22957
24656
  switch (c.type) {
22958
24657
  case "text": {
22959
- const built = text$2(name);
24658
+ const built = text$3(name);
22960
24659
  col = c.primaryKey ? built.primaryKey() : built;
22961
24660
  break;
22962
24661
  }
@@ -23066,6 +24765,7 @@ const sessions = {
23066
24765
  f_seed_length: { type: "integer" },
23067
24766
  f_origin: { type: "text" },
23068
24767
  f_delegation_depth: { type: "integer" },
24768
+ f_agent_preset: { type: "text" },
23069
24769
  f_incarnation: {
23070
24770
  type: "text",
23071
24771
  notNull: true
@@ -23075,6 +24775,8 @@ const sessions = {
23075
24775
  notNull: true
23076
24776
  },
23077
24777
  f_archived_at: { type: "bigint" },
24778
+ f_pinned_seq: { type: "integer" },
24779
+ f_last_event_at: { type: "bigint" },
23078
24780
  f_title: { type: "text" },
23079
24781
  f_title_seq: { type: "integer" }
23080
24782
  }
@@ -23345,23 +25047,28 @@ const eventUsage = {
23345
25047
  f_model: { type: "text" },
23346
25048
  f_input_tokens: {
23347
25049
  type: "integer",
23348
- notNull: true
25050
+ notNull: true,
25051
+ default: 0
23349
25052
  },
23350
25053
  f_output_tokens: {
23351
25054
  type: "integer",
23352
- notNull: true
25055
+ notNull: true,
25056
+ default: 0
23353
25057
  },
23354
25058
  f_cache_read_tokens: {
23355
25059
  type: "integer",
23356
- notNull: true
25060
+ notNull: true,
25061
+ default: 0
23357
25062
  },
23358
25063
  f_reasoning_tokens: {
23359
25064
  type: "integer",
23360
- notNull: true
25065
+ notNull: true,
25066
+ default: 0
23361
25067
  },
23362
25068
  f_total_tokens: {
23363
25069
  type: "integer",
23364
- notNull: true
25070
+ notNull: true,
25071
+ default: 0
23365
25072
  }
23366
25073
  },
23367
25074
  indexes: { idx_event_usage_created_at: { columns: ["f_created_at"] } }
@@ -23492,7 +25199,7 @@ function eventDimensions(event) {
23492
25199
  kind,
23493
25200
  role: "tool",
23494
25201
  name: "",
23495
- actionId: data["message"]?.content?.[0]?.toolCallId ?? ""
25202
+ actionId: data["message"]?.toolCallId ?? ""
23496
25203
  };
23497
25204
  case "tool/call": return {
23498
25205
  kind,
@@ -23975,11 +25682,16 @@ function createStorageRepository(host) {
23975
25682
  if (row === void 0) return null;
23976
25683
  const ordered = await allRows(db.select().from(tWorkspaces).where(gte(tWorkspaces.fPosition, 0)).orderBy(tWorkspaces.fPosition));
23977
25684
  const archives = await allRows(db.select({ fSessionId: tSessions.fSessionId }).from(tSessions).where(isNotNull(tSessions.fArchivedAt)).orderBy(tSessions.fArchivedAt));
25685
+ const pinned = await allRows(db.select({
25686
+ fSessionId: tSessions.fSessionId,
25687
+ fPinnedSeq: tSessions.fPinnedSeq
25688
+ }).from(tSessions).where(isNotNull(tSessions.fPinnedSeq)).orderBy(tSessions.fPinnedSeq));
23978
25689
  const pendingMutation = pendingMutationOf(row);
23979
25690
  return {
23980
25691
  initialized: row.fInitialized !== 0,
23981
25692
  workspaceIds: ordered.map((entry) => entry.fWorkspaceId),
23982
25693
  archivedSessionIds: archives.map((entry) => entry.fSessionId),
25694
+ pinnedSessionIds: pinned.map((entry) => entry.fSessionId),
23983
25695
  ...pendingMutation === void 0 ? {} : { pendingMutation }
23984
25696
  };
23985
25697
  },
@@ -23999,6 +25711,9 @@ function createStorageRepository(host) {
23999
25711
  }));
24000
25712
  await runQuery(db.update(tWorkspaces).set({ fPosition: -1 }));
24001
25713
  for (const [position, workspaceId] of state.workspaceIds.entries()) await runQuery(db.update(tWorkspaces).set({ fPosition: position }).where(eq(tWorkspaces.fWorkspaceId, workspaceId)));
25714
+ const pinned = state.pinnedSessionIds ?? [];
25715
+ await runQuery(pinned.length === 0 ? db.update(tSessions).set({ fPinnedSeq: null }).where(isNotNull(tSessions.fPinnedSeq)) : db.update(tSessions).set({ fPinnedSeq: null }).where(and(isNotNull(tSessions.fPinnedSeq), notInArray(tSessions.fSessionId, pinned))));
25716
+ for (const [position, sessionId] of pinned.entries()) await runQuery(db.update(tSessions).set({ fPinnedSeq: position }).where(eq(tSessions.fSessionId, sessionId)));
24002
25717
  const archived = state.archivedSessionIds;
24003
25718
  await runQuery(archived.length === 0 ? db.update(tSessions).set({ fArchivedAt: null }).where(isNotNull(tSessions.fArchivedAt)) : db.update(tSessions).set({ fArchivedAt: null }).where(and(isNotNull(tSessions.fArchivedAt), notInArray(tSessions.fSessionId, archived))));
24004
25719
  const stamp = Date.now();
@@ -24154,6 +25869,19 @@ function backfillEventUsage(db) {
24154
25869
  AND (json_extract(e.f_data, '$.data.usage') IS NOT NULL
24155
25870
  OR json_extract(e.f_data, '$.usage') IS NOT NULL)`);
24156
25871
  }
25872
+ function sessionListRowOf(row) {
25873
+ return {
25874
+ sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
25875
+ title: typeof row["title"] === "string" ? row["title"] : null,
25876
+ origin: typeof row["origin"] === "string" ? row["origin"] : null,
25877
+ cwd: typeof row["cwd"] === "string" ? row["cwd"] : null,
25878
+ createdAt: Number(row["created_at"]),
25879
+ updatedAt: Number(row["updated_at"]),
25880
+ archived: row["archived"] === 1 || row["archived"] === true,
25881
+ subagent: row["origin"] === "subagent",
25882
+ workspace: typeof row["workspace"] === "string" ? row["workspace"] : null
25883
+ };
25884
+ }
24157
25885
  /** SQL 的缺失求和是 NULL:没有 usage 字段的行使该字段计 0。 */
24158
25886
  function tokenTotalsOf$1(row) {
24159
25887
  return {
@@ -24382,7 +26110,7 @@ var SqliteBackend = class SqliteBackend {
24382
26110
  insertEvents: (events) => this.insertEvents(events),
24383
26111
  insertBridges: (rows) => this.insertBridges(rows),
24384
26112
  updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),
24385
- bumpRevision: (id) => this.bumpRevision(id),
26113
+ bumpRevision: (id, lastEventAt) => this.bumpRevision(id, lastEventAt),
24386
26114
  refreshTitle: (id) => this.refreshTitle(id),
24387
26115
  deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),
24388
26116
  getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),
@@ -24431,8 +26159,11 @@ var SqliteBackend = class SqliteBackend {
24431
26159
  fHeadSequence: headSequence
24432
26160
  }).where(eq(tSessions.fSessionId, id)).run();
24433
26161
  }
24434
- async bumpRevision(id) {
24435
- this.db.update(tSessions).set({ fRevision: sql`${tSessions.fRevision} + 1` }).where(eq(tSessions.fSessionId, id)).run();
26162
+ async bumpRevision(id, lastEventAt) {
26163
+ this.db.update(tSessions).set({
26164
+ fRevision: sql`${tSessions.fRevision} + 1`,
26165
+ ...lastEventAt === void 0 ? {} : { fLastEventAt: sql`MAX(COALESCE(${tSessions.fLastEventAt}, 0), ${lastEventAt})` }
26166
+ }).where(eq(tSessions.fSessionId, id)).run();
24436
26167
  }
24437
26168
  async refreshTitle(id) {
24438
26169
  const row = this.db.select({
@@ -24487,6 +26218,45 @@ var SqliteBackend = class SqliteBackend {
24487
26218
  * 沿 `t_events` 的事件类型数——两者限定在**有 token 用量的会话**里,时间范围各自按自己的
24488
26219
  * `f_created_at`(同一时刻写入,口径一致)。
24489
26220
  */
26221
+ async listSessionRows(query = {}) {
26222
+ const needle = query.query?.trim().toLowerCase() ?? "";
26223
+ const like = `%${needle}%`;
26224
+ const where = `WHERE (? = '' OR lower(COALESCE(s.f_title, '')) LIKE ? OR lower(COALESCE(w.f_title, '')) LIKE ?)
26225
+ AND (? = 1 OR s.f_origin IS NOT 'subagent')`;
26226
+ const filterParams = [
26227
+ needle,
26228
+ like,
26229
+ like,
26230
+ query.includeSubagents === true ? 1 : 0
26231
+ ];
26232
+ const limit = query.limit ?? 20;
26233
+ const offset = query.offset ?? 0;
26234
+ const rows = this.db.$client.prepare(`SELECT s.f_session_id AS session_id,
26235
+ s.f_title AS title,
26236
+ s.f_origin AS origin,
26237
+ s.f_cwd AS cwd,
26238
+ s.f_created_at AS created_at,
26239
+ (s.f_archived_at IS NOT NULL) AS archived,
26240
+ COALESCE(s.f_last_event_at, s.f_created_at) AS updated_at,
26241
+ w.f_title AS workspace
26242
+ FROM t_sessions s
26243
+ LEFT JOIN t_workspaces w ON w.f_workspace_id = (
26244
+ SELECT b.f_workspace_id FROM t_workspace_sessions b
26245
+ WHERE b.f_session_id = s.f_session_id ORDER BY b.f_position LIMIT 1)
26246
+ ${where}
26247
+ ORDER BY updated_at DESC, s.f_session_id
26248
+ LIMIT ? OFFSET ?`).all(...filterParams, limit, offset);
26249
+ const counted = this.db.$client.prepare(`SELECT count(*) AS n
26250
+ FROM t_sessions s
26251
+ LEFT JOIN t_workspaces w ON w.f_workspace_id = (
26252
+ SELECT b.f_workspace_id FROM t_workspace_sessions b
26253
+ WHERE b.f_session_id = s.f_session_id ORDER BY b.f_position LIMIT 1)
26254
+ ${where}`).get(...filterParams);
26255
+ return {
26256
+ items: rows.map((row) => sessionListRowOf(row)),
26257
+ total: Number(counted.n)
26258
+ };
26259
+ }
24490
26260
  async usageReport(sinceMs) {
24491
26261
  const usageSince = sinceMs === void 0 ? "" : " AND u.f_created_at >= ?";
24492
26262
  const params = sinceMs === void 0 ? [] : [sinceMs];
@@ -24777,7 +26547,7 @@ var PostgresBackend = class PostgresBackend {
24777
26547
  insertEvents: (events) => this.insertEvents(tx, events),
24778
26548
  insertBridges: (rows) => this.insertBridges(tx, rows),
24779
26549
  updateHead: (id, headEventId, headSequence) => this.updateHead(tx, id, headEventId, headSequence),
24780
- bumpRevision: (id) => this.bumpRevision(tx, id),
26550
+ bumpRevision: (id, lastEventAt) => this.bumpRevision(tx, id, lastEventAt),
24781
26551
  refreshTitle: (id) => this.refreshTitle(tx, id),
24782
26552
  deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(tx, id, fromSequence),
24783
26553
  getPrevBridge: (id, sequence) => this.getPrevBridge(tx, id, sequence),
@@ -24830,8 +26600,12 @@ var PostgresBackend = class PostgresBackend {
24830
26600
  fHeadSequence: headSequence
24831
26601
  }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
24832
26602
  }
24833
- async bumpRevision(exec, id) {
24834
- await exec.update(this.tables["t_sessions"]).set({ fRevision: sql`${this.tables["t_sessions"].fRevision} + 1` }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
26603
+ async bumpRevision(exec, id, lastEventAt) {
26604
+ const sessions = this.tables["t_sessions"];
26605
+ await exec.update(sessions).set({
26606
+ fRevision: sql`${sessions.fRevision} + 1`,
26607
+ ...lastEventAt === void 0 ? {} : { fLastEventAt: sql`GREATEST(COALESCE(${sessions.fLastEventAt}, 0), ${lastEventAt})` }
26608
+ }).where(eq(sessions.fSessionId, id)).execute();
24835
26609
  }
24836
26610
  async refreshTitle(exec, id) {
24837
26611
  const bridges = this.tables["t_session_events"];
@@ -24917,6 +26691,55 @@ var PostgresBackend = class PostgresBackend {
24917
26691
  `);
24918
26692
  }
24919
26693
  /** 用量聚合:与 SQLite 侧同形,读 `t_event_usage`,数值列回来是字符串。 */
26694
+ async listSessionRows(query = {}) {
26695
+ const needle = query.query?.trim().toLowerCase() ?? "";
26696
+ const like = `%${needle}%`;
26697
+ const includeSubagents = query.includeSubagents === true;
26698
+ const limit = query.limit ?? 20;
26699
+ const offset = query.offset ?? 0;
26700
+ const tSessions = this.tables["t_sessions"];
26701
+ const tWorkspaces = this.tables["t_workspaces"];
26702
+ const tWorkspaceSessions = this.tables["t_workspace_sessions"];
26703
+ const workspaceTitle = sql`(SELECT w.f_title FROM ${tWorkspaceSessions} b
26704
+ JOIN ${tWorkspaces} w ON w.f_workspace_id = b.f_workspace_id
26705
+ WHERE b.f_session_id = s.f_session_id
26706
+ ORDER BY b.f_position LIMIT 1)`;
26707
+ const where = sql`WHERE (${needle} = ''
26708
+ OR lower(COALESCE(s.f_title, '')) LIKE ${like}
26709
+ OR lower(COALESCE(${workspaceTitle}, '')) LIKE ${like})
26710
+ AND (${includeSubagents} OR s.f_origin IS DISTINCT FROM 'subagent')`;
26711
+ const rows = await this.db.execute(sql`
26712
+ SELECT s.f_session_id AS session_id,
26713
+ s.f_title AS title,
26714
+ s.f_origin AS origin,
26715
+ s.f_cwd AS cwd,
26716
+ s.f_created_at AS created_at,
26717
+ (s.f_archived_at IS NOT NULL) AS archived,
26718
+ COALESCE(s.f_last_event_at, s.f_created_at) AS updated_at,
26719
+ ${workspaceTitle} AS workspace
26720
+ FROM ${tSessions} s
26721
+ ${where}
26722
+ ORDER BY updated_at DESC, s.f_session_id
26723
+ LIMIT ${limit} OFFSET ${offset}
26724
+ `);
26725
+ const counted = await this.db.execute(sql`
26726
+ SELECT count(*) AS n FROM ${tSessions} s ${where}
26727
+ `);
26728
+ return {
26729
+ items: rows.rows.map((row) => ({
26730
+ sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
26731
+ title: typeof row["title"] === "string" ? row["title"] : null,
26732
+ origin: typeof row["origin"] === "string" ? row["origin"] : null,
26733
+ cwd: typeof row["cwd"] === "string" ? row["cwd"] : null,
26734
+ createdAt: numeric(row["created_at"]),
26735
+ updatedAt: numeric(row["updated_at"]),
26736
+ archived: row["archived"] === true,
26737
+ subagent: row["origin"] === "subagent",
26738
+ workspace: typeof row["workspace"] === "string" ? row["workspace"] : null
26739
+ })),
26740
+ total: numeric(counted.rows[0]?.["n"] ?? 0)
26741
+ };
26742
+ }
24920
26743
  async usageReport(sinceMs) {
24921
26744
  const usageSince = sinceMs === void 0 ? sql`` : sql` AND u.f_created_at >= ${sinceMs}`;
24922
26745
  const daySince = sinceMs === void 0 ? sql`` : sql` AND c.f_day >= ${localDayKey(sinceMs)}`;
@@ -25008,7 +26831,7 @@ var PostgresBackend = class PostgresBackend {
25008
26831
  ...tokenTotalsOf(row)
25009
26832
  })),
25010
26833
  sessions: tokenSessions.rows.map((row) => ({
25011
- sessionId: String(row["session_id"]),
26834
+ sessionId: typeof row["session_id"] === "string" ? row["session_id"] : String(row["session_id"]),
25012
26835
  title: text(row["title"]),
25013
26836
  subagent: row["subagent"] === true,
25014
26837
  archived: row["archived"] === true,
@@ -25120,7 +26943,8 @@ function physicalHeader(row) {
25120
26943
  ...row.fCwd !== null ? { cwd: row.fCwd } : {},
25121
26944
  ...row.fParentSession !== null ? { parentSession: row.fParentSession } : {},
25122
26945
  ...row.fOrigin !== null ? { origin: row.fOrigin } : {},
25123
- delegationDepth: row.fDelegationDepth ?? 0
26946
+ delegationDepth: row.fDelegationDepth ?? 0,
26947
+ ...row.fAgentPreset !== null ? { agentPreset: row.fAgentPreset } : {}
25124
26948
  };
25125
26949
  return row.fVersion < 2 ? {
25126
26950
  ...common,
@@ -25141,8 +26965,14 @@ function physicalEvent(row) {
25141
26965
  ...row.fSurfaceOp !== null ? { surfaceOp: JSON.parse(row.fSurfaceOp) } : {}
25142
26966
  };
25143
26967
  }
26968
+ /**
26969
+ * 读旧格式用的 catalog:v3→v4 这条边要求显式子会话证据(上游 0.1.7 起),本仓库声明
26970
+ * **无子会话证据**——v3 的目录事实由父会话自己在成功路径上写成 `subagent/catalog` 事件,
26971
+ * 迁移的 child evidence 只是补偿入口;我们没有更权威的子集合来源可给(见 ADR-跟随上游session-format-v4)。
26972
+ */
26973
+ const restoreCatalog = createSessionFormatCatalogWithChildren([]);
25144
26974
  function convertLegacyRows(row, eventRows) {
25145
- const restore = sessionFormatCatalog.createRestore(physicalHeader(row), {
26975
+ const restore = restoreCatalog.createRestore(physicalHeader(row), {
25146
26976
  recovery: "strict",
25147
26977
  validation: "transformed"
25148
26978
  });
@@ -25160,6 +26990,7 @@ function isLegacyVersion(version) {
25160
26990
  function adoptLegacyRows(row, eventRows) {
25161
26991
  const { preserved, tornFrom } = scanRows(eventRows, 0);
25162
26992
  repairRequestHeaders(preserved);
26993
+ normalizeToCurrentShape(preserved);
25163
26994
  return {
25164
26995
  meta: {
25165
26996
  ...rowToMeta(row),
@@ -25294,19 +27125,27 @@ var SessionBranchRdbProvider = class {
25294
27125
  };
25295
27126
  const seed = [...renumber(prefix, 0), ...renumber(seedSuffix, prefix.length)];
25296
27127
  const internals = this.persistence.internals();
25297
- const sourceRows = await internals.backend.getEventRows(sourceId);
25298
- const sourceEventIds = new Map(sourceRows.map((row) => [row.fSequence, row.fEventId]));
27128
+ const reusable = !source.migrated && source.events.length === source.storedCount;
25299
27129
  const reuse = /* @__PURE__ */ new Map();
25300
- for (const event of prefix) {
25301
- const eventId = sourceEventIds.get(event.seq);
25302
- if (eventId !== void 0) reuse.set(event.seq, eventId);
25303
- }
27130
+ if (reusable) {
27131
+ const sourceRows = await internals.backend.getEventRows(sourceId);
27132
+ const sourceEventIds = new Map(sourceRows.map((row) => [row.fSequence, row.fEventId]));
27133
+ for (const event of prefix) {
27134
+ const eventId = sourceEventIds.get(event.seq);
27135
+ if (eventId !== void 0) reuse.set(event.seq, eventId);
27136
+ }
27137
+ } else if (prefix.length > 0) this.live.warn?.(`session-rdb: fork "${sourceId}" copies event rows instead of reusing them — its read view comes from a migration, so stored seqs need not line up`);
25304
27138
  internals.registerReuseEventIds(childId, reuse);
25305
- const handle = await this.persistence.create(childMeta, { inheritedEventCount: SessionLogOffset(prefix.length) });
25306
27139
  try {
25307
- if (seed.length > 0) await handle.append(seed);
25308
- } finally {
25309
- await handle.close();
27140
+ const handle = await this.persistence.create(childMeta, { inheritedEventCount: SessionLogOffset(prefix.length) });
27141
+ try {
27142
+ if (seed.length > 0) await handle.append(seed);
27143
+ } finally {
27144
+ await handle.close();
27145
+ }
27146
+ } catch (error) {
27147
+ internals.dropReuseEventIds(childId);
27148
+ throw error;
25310
27149
  }
25311
27150
  await this.persistence.rebuildEventCounts(childId);
25312
27151
  return childId;
@@ -26669,7 +28508,7 @@ function parseJsonlArtifact(content) {
26669
28508
  }
26670
28509
  let restore;
26671
28510
  try {
26672
- restore = sessionFormatCatalog.createRestore(header, {
28511
+ restore = restoreCatalog.createRestore(header, {
26673
28512
  recovery: "strict",
26674
28513
  validation: "transformed"
26675
28514
  });
@@ -27048,6 +28887,74 @@ function registerSessionGc(ctx, persistence) {
27048
28887
  });
27049
28888
  }
27050
28889
  //#endregion
28890
+ //#region ../session-rdb/src/rows.ts
28891
+ /**
28892
+ * 管理面的会话列表:**我们自己的**一条路由(完整语料,含归档),与官方 `session/list` 分开。
28893
+ *
28894
+ * 官方那条(`session/list` → `ctx.sessionQuery.listSessions`)按部署策略默认排除已归档——那是给上游
28895
+ * UI 用的。归档集的管理动作要有完整列表,所以这里单独给一条:标题直接从会话列取,最后活动时间用
28896
+ * 该会话最后一个事件的时间。
28897
+ */
28898
+ const SESSION_ROWS_PATH = "/api/session.rows";
28899
+ /** 一页的条数上限:超过就按上限截断(这个接口给页面用,不是导出通道)。 */
28900
+ const MAX_PAGE_SIZE = 200;
28901
+ const DEFAULT_PAGE_SIZE = 20;
28902
+ function registerSessionRows(ctx, backend) {
28903
+ ctx.inject(["webServer", "connection"], (webCtx) => {
28904
+ const webServer = webCtx.webServer;
28905
+ const connection = webCtx.get("connection");
28906
+ return webCtx.effect(() => webServer.register({
28907
+ kind: "exact",
28908
+ path: SESSION_ROWS_PATH,
28909
+ handler: async (req, res) => {
28910
+ const rejection = connection.requestRejection(req);
28911
+ if (rejection !== void 0) {
28912
+ res.writeHead(rejection);
28913
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
28914
+ return;
28915
+ }
28916
+ if (req.method !== "POST") {
28917
+ res.writeHead(405, { "content-type": "application/json" });
28918
+ res.end(JSON.stringify({ error: "method not allowed" }));
28919
+ return;
28920
+ }
28921
+ const chunks = [];
28922
+ for await (const chunk of req) chunks.push(chunk);
28923
+ const raw = Buffer.concat(chunks).toString("utf8");
28924
+ let envelope = {};
28925
+ if (raw !== "") try {
28926
+ envelope = JSON.parse(raw);
28927
+ } catch {
28928
+ res.writeHead(400, { "content-type": "application/json" });
28929
+ res.end(JSON.stringify({ error: "request body is not JSON" }));
28930
+ return;
28931
+ }
28932
+ const page = Number.isSafeInteger(envelope.page) && (envelope.page ?? 0) > 0 ? envelope.page : 1;
28933
+ const requested = envelope.pageSize;
28934
+ const pageSize = Number.isSafeInteger(requested) && (requested ?? 0) > 0 ? Math.min(requested, MAX_PAGE_SIZE) : DEFAULT_PAGE_SIZE;
28935
+ try {
28936
+ const { items, total } = await backend.listSessionRows({
28937
+ ...envelope.query === void 0 ? {} : { query: envelope.query },
28938
+ ...envelope.includeSubagents === void 0 ? {} : { includeSubagents: envelope.includeSubagents },
28939
+ limit: pageSize,
28940
+ offset: (page - 1) * pageSize
28941
+ });
28942
+ res.writeHead(200, { "content-type": "application/json" });
28943
+ res.end(JSON.stringify({
28944
+ items,
28945
+ total,
28946
+ page,
28947
+ pageSize
28948
+ }));
28949
+ } catch (error) {
28950
+ res.writeHead(500, { "content-type": "application/json" });
28951
+ res.end(JSON.stringify({ error: error instanceof Error ? error.message : "session rows failed" }));
28952
+ }
28953
+ }
28954
+ }), "session-rdb: session rows route");
28955
+ });
28956
+ }
28957
+ //#endregion
27051
28958
  //#region ../../../vendor/deepseek-harness/packages/session/session-format-catalog/lib/types/message-projections.js
27052
28959
  /** Pure first-party message interpreters for detached current-format replay, including browser readers. */
27053
28960
  /** Installed interpretation definitions; recovery listeners are mounted separately by their owning plugins. */
@@ -27185,7 +29092,7 @@ const allowsEval = /* @__PURE__*/ cached(() => {
27185
29092
  return false;
27186
29093
  }
27187
29094
  });
27188
- function isPlainObject$1(o) {
29095
+ function isPlainObject(o) {
27189
29096
  if (isObject(o) === false) return false;
27190
29097
  const ctor = o.constructor;
27191
29098
  if (ctor === void 0) return true;
@@ -27196,7 +29103,7 @@ function isPlainObject$1(o) {
27196
29103
  return true;
27197
29104
  }
27198
29105
  function shallowClone(o) {
27199
- if (isPlainObject$1(o)) return { ...o };
29106
+ if (isPlainObject(o)) return { ...o };
27200
29107
  if (Array.isArray(o)) return [...o];
27201
29108
  if (o instanceof Map) return new Map(o);
27202
29109
  if (o instanceof Set) return new Set(o);
@@ -27284,7 +29191,7 @@ function omit(schema, mask) {
27284
29191
  }));
27285
29192
  }
27286
29193
  function extend(schema, shape) {
27287
- if (!isPlainObject$1(shape)) throw new Error("Invalid input to extend: expected a plain object");
29194
+ if (!isPlainObject(shape)) throw new Error("Invalid input to extend: expected a plain object");
27288
29195
  const checks = schema._zod.def.checks;
27289
29196
  if (checks && checks.length > 0) {
27290
29197
  const existingShape = sourceShape(schema);
@@ -27299,7 +29206,7 @@ function extended(schema, shape) {
27299
29206
  return newShape;
27300
29207
  }
27301
29208
  function safeExtend(schema, shape) {
27302
- if (!isPlainObject$1(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
29209
+ if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
27303
29210
  return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));
27304
29211
  }
27305
29212
  function merge(a, b) {
@@ -29278,7 +31185,7 @@ function mergeValues(a, b) {
29278
31185
  valid: true,
29279
31186
  data: a
29280
31187
  };
29281
- if (isPlainObject$1(a) && isPlainObject$1(b)) {
31188
+ if (isPlainObject(a) && isPlainObject(b)) {
29282
31189
  const bKeys = Object.keys(b);
29283
31190
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
29284
31191
  const newObj = {
@@ -33126,7 +35033,7 @@ var SessionObservationReader = class {
33126
35033
  const snapshot = await this.statSource(persistence, sessionId, signal);
33127
35034
  const attachedDuringStat = this.ctx.sessions.get(sessionId);
33128
35035
  if (attachedDuringStat !== void 0) return this.live(attachedDuringStat, projectionMode);
33129
- let entry = this.cachedEntry(persistence, sessionId, snapshot.revision);
35036
+ let entry = this.cachedEntry(persistence.identity, sessionId, snapshot.revision);
33130
35037
  if (entry === void 0) {
33131
35038
  const loaded = await this.loadSource(persistence, sessionId, signal);
33132
35039
  throwIfObservationAborted(signal);
@@ -33146,7 +35053,7 @@ var SessionObservationReader = class {
33146
35053
  throw new SessionQueryError(`stored session "${sessionId}" is corrupt: ${errorMessage(error)}`, "SESSION_QUERY_CORRUPT_SESSION", { cause: error });
33147
35054
  }
33148
35055
  entry = {
33149
- persistence,
35056
+ persistenceIdentity: persistence.identity,
33150
35057
  revision: snapshot.revision,
33151
35058
  session,
33152
35059
  events: Object.freeze(seed),
@@ -33187,9 +35094,9 @@ var SessionObservationReader = class {
33187
35094
  }
33188
35095
  }
33189
35096
  /** Return a still-valid cached entry and mark it most recently used. */
33190
- cachedEntry(persistence, sessionId, revision) {
35097
+ cachedEntry(persistenceIdentity, sessionId, revision) {
33191
35098
  const cached = this.cache.get(sessionId);
33192
- if (cached === void 0 || cached.persistence !== persistence || cached.revision !== revision) return;
35099
+ if (cached === void 0 || cached.persistenceIdentity !== persistenceIdentity || cached.revision !== revision) return;
33193
35100
  this.cache.delete(sessionId);
33194
35101
  this.cache.set(sessionId, cached);
33195
35102
  return cached;
@@ -33341,7 +35248,6 @@ function blockText(block) {
33341
35248
  case "text": return [block.text];
33342
35249
  case "reasoning": return [];
33343
35250
  case "tool-call": return [block.name, block.arguments];
33344
- case "tool-result": return block.content.flatMap(blockText);
33345
35251
  default: return [];
33346
35252
  }
33347
35253
  }
@@ -33970,6 +35876,23 @@ var SessionQueryRdb = class extends SessionQueryEngine {
33970
35876
  constructor(ctx, config = {}) {
33971
35877
  super(ctx, config);
33972
35878
  }
35879
+ /**
35880
+ * 官方列表(`session/list`)**默认不含已归档**:归档是「收起来」的会话,列表与搜索都不该把它混进
35881
+ * 日常集合(上游 client 再用 workspace registry 的归档集做「只看归档」的派生)。
35882
+ *
35883
+ * 完整语料(含归档)走我们自己的管理面路由 `SESSION_ROWS_PATH`——两条路分开,不在一处混。
35884
+ */
35885
+ async listSessions(signal) {
35886
+ const records = await super.listSessions(signal);
35887
+ const archived = this.archivedSessionIds();
35888
+ if (archived.size === 0) return records;
35889
+ return records.filter((record) => !archived.has(String(record.header.id)));
35890
+ }
35891
+ /** 归档集合来自 workspace registry;服务未就绪时保守返回空集(宁可多给,不可漏掉可见会话)。 */
35892
+ archivedSessionIds() {
35893
+ const registry = this.ctx.get("workspaceRegistry");
35894
+ return new Set(registry?.archivedSessionIds ?? []);
35895
+ }
33973
35896
  async searchSessions() {
33974
35897
  throw searchDisabled();
33975
35898
  }
@@ -34055,11 +35978,19 @@ var SessionProjectionCacheRdb = class extends Service {
34055
35978
  if (record === void 0) return void 0;
34056
35979
  return identityMatches(record.identity, expected) ? record : void 0;
34057
35980
  }
34058
- cachedSnapshot(meta, inheritedEventCount, keys) {
34059
- const record = this.recordFor(meta.id, identityOf(meta, inheritedEventCount));
35981
+ /**
35982
+ * header-only 读(会话列表这类):上游 0.1.7 起只给 header 与 keys——它要求的是**生命周期**身份,
35983
+ * 不再要求调用方提供 inherited cut(那个 cut 只有写路径与 hydration 用得上)。
35984
+ */
35985
+ cachedSnapshot(meta, keys) {
35986
+ const record = this.recordForLifecycle(meta);
34060
35987
  const snapshot = record === void 0 ? void 0 : this.viewRecord(record, keys);
34061
35988
  return this.withDirectTitle(meta.id, keys, snapshot);
34062
35989
  }
35990
+ recordForLifecycle(meta) {
35991
+ const record = this.lookup(meta.id);
35992
+ return record === void 0 || !identityMatches(record.identity, lifecycleIdentityOf(meta)) ? void 0 : record;
35993
+ }
34063
35994
  withDirectTitle(id, keys, snapshot) {
34064
35995
  if (keys !== void 0 && !keys.includes(PREDECESSOR_TITLE_KEY)) return snapshot;
34065
35996
  if (snapshot?.values[PREDECESSOR_TITLE_KEY] !== void 0) return snapshot;
@@ -34074,15 +36005,10 @@ var SessionProjectionCacheRdb = class extends Service {
34074
36005
  values
34075
36006
  };
34076
36007
  }
34077
- cachedPredecessorTitle(meta, inheritedEventCount) {
34078
- const expected = identityOf(meta, inheritedEventCount);
36008
+ cachedPredecessorTitle(meta) {
34079
36009
  const record = this.lookup(meta.id);
34080
- if (record === void 0 || !predecessorIdentityMatches(record.identity, expected)) return;
34081
- const title = this.viewRecord(record, [PREDECESSOR_TITLE_KEY]);
34082
- return title === void 0 ? void 0 : {
34083
- ...title,
34084
- asOfSeq: -1
34085
- };
36010
+ if (record === void 0 || !predecessorIdentityMatches(record.identity, lifecycleIdentityOf(meta))) return;
36011
+ return this.viewRecord(record, [PREDECESSOR_TITLE_KEY]);
34086
36012
  }
34087
36013
  viewRecord(record, keys) {
34088
36014
  const values = this.ctx.sessionProjections.viewCheckpoint(record.rows, keys);
@@ -34198,6 +36124,15 @@ function detachJson(rows) {
34198
36124
  if (text === void 0) throw new TypeError("projection checkpoint is not losslessly JSON-serializable");
34199
36125
  return JSON.parse(text);
34200
36126
  }
36127
+ /** header 能证明的生命周期身份(不含 inherited cut):header-only 读只比对它。 */
36128
+ function lifecycleIdentityOf(header) {
36129
+ return {
36130
+ formatVersion: header.version,
36131
+ createdAt: header.createdAt,
36132
+ ...header.cwd === void 0 ? {} : { cwd: header.cwd },
36133
+ isSeeded: header.isSeeded
36134
+ };
36135
+ }
34201
36136
  function identityOf(header, inheritedEventCount) {
34202
36137
  const cut = SessionLogOffset(inheritedEventCount);
34203
36138
  if (!header.isSeeded && cut !== 0) throw new Error("unseeded projection-cache identity inherited event count must be 0");
@@ -34216,7 +36151,7 @@ function predecessorIdentityMatches(stored, expected) {
34216
36151
  return (stored.formatVersion === void 0 || stored.formatVersion < expected.formatVersion) && lifecycleIdentityMatches(stored, expected);
34217
36152
  }
34218
36153
  function lifecycleIdentityMatches(stored, expected) {
34219
- return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd && (stored.isSeeded ?? false) === expected.isSeeded && (stored.inheritedEventCount ?? 0) === expected.inheritedEventCount;
36154
+ return stored.createdAt === expected.createdAt && stored.cwd === expected.cwd && (stored.isSeeded ?? false) === expected.isSeeded;
34220
36155
  }
34221
36156
  //#endregion
34222
36157
  //#region ../session-rdb/src/storage-takeover/storage-backend.ts
@@ -34330,11 +36265,12 @@ function workspaceRecordOf(value) {
34330
36265
  }
34331
36266
  function workspaceStateOf(value) {
34332
36267
  const state = value;
34333
- if (typeof state !== "object" || state === null || typeof state.initialized !== "boolean" || !Array.isArray(state.workspaceIds) || !Array.isArray(state.archivedSessionIds)) throw new TypeError("workspace registry state does not match the stored shape");
36268
+ if (typeof state !== "object" || state === null || typeof state.initialized !== "boolean" || !Array.isArray(state.workspaceIds) || !Array.isArray(state.archivedSessionIds) || !Array.isArray(state.pinnedSessionIds)) throw new TypeError("workspace registry state does not match the stored shape");
34334
36269
  return {
34335
36270
  initialized: state.initialized,
34336
36271
  workspaceIds: state.workspaceIds.map((id) => id),
34337
36272
  archivedSessionIds: state.archivedSessionIds.map((id) => id),
36273
+ pinnedSessionIds: state.pinnedSessionIds.map((id) => id),
34338
36274
  ...state.pendingMutation === void 0 ? {} : { pendingMutation: state.pendingMutation }
34339
36275
  };
34340
36276
  }
@@ -34361,6 +36297,16 @@ function installStorageTakeover(ctx, options) {
34361
36297
  //#endregion
34362
36298
  //#region ../session-rdb/src/index.ts
34363
36299
  init_esm();
36300
+ /** 一批事件里的最大时间:写路径据此推进会话行的「最后活动时间」。 */
36301
+ function maxEventTime(events) {
36302
+ let max;
36303
+ for (const event of events) {
36304
+ const time = event.time;
36305
+ if (typeof time !== "number") continue;
36306
+ if (max === void 0 || time > max) max = time;
36307
+ }
36308
+ return max;
36309
+ }
34364
36310
  const DEFAULT_PROJECTION_WRITE_EVERY_EVENTS = 200;
34365
36311
  const DEFAULT_PROJECTION_WRITE_INTERVAL_MS = 5e3;
34366
36312
  var SessionDeletionError = class extends Error {
@@ -34619,9 +36565,9 @@ var RdbSessionHandle = class RdbSessionHandle {
34619
36565
  }
34620
36566
  static LIVE_WRITE_BATCH_MAX_DELAY_MS = 200;
34621
36567
  };
34622
- var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersistence {
36568
+ var SessionPersistenceRdb = class extends SessionPersistence {
34623
36569
  config;
34624
- static inject = ["sessions", "settings"];
36570
+ static inject = ["sessions"];
34625
36571
  static Config = Schema.union([Schema.object({
34626
36572
  type: Schema.const("sqlite"),
34627
36573
  path: Schema.string().required(),
@@ -34651,7 +36597,6 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34651
36597
  writeIntervalMs: DEFAULT_PROJECTION_WRITE_INTERVAL_MS
34652
36598
  })
34653
36599
  })]);
34654
- static settingsNs = "session-rdb";
34655
36600
  name = "session-rdb";
34656
36601
  tracker = new RdbBackendTracker(this.name);
34657
36602
  backend;
@@ -34662,19 +36607,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34662
36607
  liveBuffers = /* @__PURE__ */ new Map();
34663
36608
  liveReady = /* @__PURE__ */ new Map();
34664
36609
  constructor(ctx, config, injectedBackend) {
34665
- let resolved = config;
34666
- const settings = ctx.reflect.get("settings");
34667
- if (settings !== void 0) {
34668
- const scope = settings.register(SessionPersistenceRdb.settingsNs, SessionPersistenceRdb.Config, { base: config });
34669
- resolved = scope.get();
34670
- scope.watch(() => {
34671
- ctx.logger.warn("session-rdb: settings changed; restart to apply the new configuration");
34672
- });
34673
- }
34674
36610
  super(ctx);
34675
36611
  this.config = config;
34676
- this.config = resolved;
34677
- this.backend = injectedBackend ?? createBackend(resolved);
36612
+ this.backend = injectedBackend ?? createBackend(config);
34678
36613
  this.ready = this.init();
34679
36614
  this.installLiveRouting(ctx);
34680
36615
  new SessionBranchRdb(this.ctx);
@@ -34684,6 +36619,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34684
36619
  registerSessionExport(this.ctx, this);
34685
36620
  registerSessionGc(this.ctx, this);
34686
36621
  registerSessionUsage(this.ctx, this);
36622
+ registerSessionRows(this.ctx, this.backend);
34687
36623
  installStorageTakeover(this.ctx, {
34688
36624
  repository: this.backend.storage,
34689
36625
  ready: this.ready,
@@ -34891,7 +36827,6 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34891
36827
  if (events.length === 0) return false;
34892
36828
  validateStoredEvents(meta, [...events]);
34893
36829
  const reuse = this.reuseEventIds.get(meta.id);
34894
- if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
34895
36830
  let confirmedHead = -1;
34896
36831
  await this.backend.transaction(async (tx) => {
34897
36832
  if (tornTruncateTo !== void 0) {
@@ -34911,9 +36846,10 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34911
36846
  nextSeq: head.fHeadSequence + 1
34912
36847
  }, reuse);
34913
36848
  await tx.updateHead(meta.id, headEventId, headSequence);
34914
- await tx.bumpRevision(meta.id);
36849
+ await tx.bumpRevision(meta.id, maxEventTime(events));
34915
36850
  confirmedHead = headSequence;
34916
36851
  });
36852
+ if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
34917
36853
  this.writeGuard.confirmHead(meta.id, confirmedHead);
34918
36854
  this.tracker.materialized(meta.id);
34919
36855
  await this.recordEventCounts(meta.id, events);
@@ -34954,6 +36890,19 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34954
36890
  };
34955
36891
  }
34956
36892
  const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0);
36893
+ if (hasLegacyShape(preserved)) {
36894
+ const adopted = adoptLegacyRows(row, eventRows);
36895
+ return {
36896
+ meta: adopted.meta,
36897
+ inheritedEventCount: adopted.inheritedEventCount,
36898
+ events: adopted.events,
36899
+ incarnation: row.fIncarnation,
36900
+ revision: row.fRevision,
36901
+ storedCount: eventRows.length,
36902
+ migrated: false,
36903
+ ...adopted.tornFrom !== void 0 ? { tornFrom: adopted.tornFrom } : {}
36904
+ };
36905
+ }
34957
36906
  return {
34958
36907
  meta,
34959
36908
  inheritedEventCount: row.fSeedLength ?? 0,
@@ -34996,6 +36945,9 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
34996
36945
  }
34997
36946
  async rewriteMigratedLog(id, log) {
34998
36947
  await this.backend.transaction(async (tx) => {
36948
+ const head = await tx.getHead(id);
36949
+ if (!this.writeGuard.has(id)) this.writeGuard.confirmHead(id, head.fHeadSequence);
36950
+ this.writeGuard.assertNoConcurrentWriter(id, head.fHeadSequence);
34999
36951
  await tx.deleteBridgeTail(id, 0);
35000
36952
  await tx.upsertSession({
35001
36953
  meta: log.meta,
@@ -35006,7 +36958,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
35006
36958
  nextSeq: 0
35007
36959
  });
35008
36960
  await tx.updateHead(id, headEventId, headSequence);
35009
- await tx.bumpRevision(id);
36961
+ await tx.bumpRevision(id, maxEventTime(log.events));
35010
36962
  });
35011
36963
  }
35012
36964
  async listSnapshots(signal) {
@@ -35097,6 +37049,10 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
35097
37049
  registerReuseEventIds(childId, map) {
35098
37050
  this.reuseEventIds.set(childId, new Map(map));
35099
37051
  }
37052
+ /** 丢弃一个还没被消费的复用映射(fork 失败时):留着会让同一个 childId 之后的落写复用父会话的事件行。 */
37053
+ dropReuseEventIds(childId) {
37054
+ this.reuseEventIds.delete(childId);
37055
+ }
35100
37056
  internals() {
35101
37057
  return {
35102
37058
  backend: this.backend,
@@ -35108,7 +37064,8 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
35108
37064
  readFrom: (id, fromSeq, signal) => this.readFrom(id, fromSeq, signal),
35109
37065
  listSnapshots: (signal) => this.listSnapshots(signal),
35110
37066
  readStoredRevision: (id, signal) => this.readStoredRevision(id, signal),
35111
- registerReuseEventIds: (childId, map) => this.registerReuseEventIds(childId, map)
37067
+ registerReuseEventIds: (childId, map) => this.registerReuseEventIds(childId, map),
37068
+ dropReuseEventIds: (childId) => this.dropReuseEventIds(childId)
35112
37069
  };
35113
37070
  }
35114
37071
  rowRevision(row) {
@@ -35318,627 +37275,6 @@ async function appendEventTail(tx, meta, events, anchor, reuse) {
35318
37275
  };
35319
37276
  }
35320
37277
  //#endregion
35321
- //#region ../../../vendor/deepseek-harness/packages/settings/settings/lib/index.js
35322
- /**
35323
- * Structural secret redaction for settings values. `role('secret')` fields are
35324
- * removed from a value before it crosses a wire boundary; a sidecar records
35325
- * each schema-declared secret position and whether it currently holds a value,
35326
- * so a configuration surface can render a write-only input without ever
35327
- * receiving the secret itself.
35328
- * @module @deepseek-ai/dsh-settings/redact
35329
- */
35330
- /** Whether a value is a plain data object the walker may recurse into. */
35331
- function isRecord(value) {
35332
- return typeof value === "object" && value !== null && !Array.isArray(value);
35333
- }
35334
- function walk(node, value, path, secrets) {
35335
- if (node === void 0) return value;
35336
- if (node.meta?.role === "secret") {
35337
- secrets.push({
35338
- path,
35339
- set: value !== void 0
35340
- });
35341
- return;
35342
- }
35343
- switch (node.type) {
35344
- case "object": {
35345
- const properties = node.dict ?? {};
35346
- const source = isRecord(value) ? value : void 0;
35347
- const rebuilt = {};
35348
- if (source !== void 0) for (const [key, entry] of Object.entries(source)) {
35349
- if (key in properties) continue;
35350
- rebuilt[key] = entry;
35351
- }
35352
- for (const [key, child] of Object.entries(properties)) {
35353
- const stripped = walk(child, source?.[key], [...path, key], secrets);
35354
- if (stripped !== void 0) rebuilt[key] = stripped;
35355
- }
35356
- return source === void 0 && Object.keys(rebuilt).length === 0 ? value : rebuilt;
35357
- }
35358
- case "dict": {
35359
- if (!isRecord(value)) return value;
35360
- const rebuilt = {};
35361
- for (const [key, entry] of Object.entries(value)) {
35362
- const stripped = walk(node.inner, entry, [...path, key], secrets);
35363
- if (stripped !== void 0) rebuilt[key] = stripped;
35364
- }
35365
- return rebuilt;
35366
- }
35367
- case "array":
35368
- if (!Array.isArray(value)) return value;
35369
- return value.map((entry, index) => walk(node.inner, entry, [...path, String(index)], secrets));
35370
- default: return value;
35371
- }
35372
- }
35373
- /**
35374
- * Remove every `role('secret')` field a schema declares from a value. The
35375
- * walker follows `object`, `dict`, and `array` containers; a secret must be
35376
- * declared directly on a field reachable through those containers (a secret
35377
- * buried inside a union branch or transform is not reachable and must not be
35378
- * modeled that way). The input is never mutated.
35379
- * @param schema - live schemastery schema describing the value.
35380
- * @param value - the value to strip; `undefined` yields an empty record with
35381
- * object-property secret slots still enumerated.
35382
- * @returns the stripped detached value and the ordered secret positions.
35383
- */
35384
- function redactSecrets(schema, value) {
35385
- const secrets = [];
35386
- return {
35387
- value: walk(schema, value, [], secrets),
35388
- secrets
35389
- };
35390
- }
35391
- /**
35392
- * Service Definition for the user-settings capability seam (`ctx.settings`). Providers store one raw document of
35393
- * per-namespace sections; plugins register a namespace schema and read the
35394
- * resolved value, which layers schema defaults, the registrant's composition
35395
- * `base`, and the user document section, in that order.
35396
- * @module @deepseek-ai/dsh-settings
35397
- */
35398
- const NAMESPACE_PATTERN = /^[a-z][a-z0-9-]*$/;
35399
- function parseSettingsNamespace(value) {
35400
- if (!NAMESPACE_PATTERN.test(value)) throw new TypeError(`settings namespace "${value}" must match ${String(NAMESPACE_PATTERN)}`);
35401
- return value;
35402
- }
35403
- /**
35404
- * A write refused because the namespace moved since the caller read it. The
35405
- * Service Definition's serialized write queue orders writes; it cannot tell a fresh writer
35406
- * from one holding a stale snapshot, which is what this reports.
35407
- */
35408
- var SettingsConflictError = class extends Error {
35409
- /** Stable machine code for wire layers mapping this to their own taxonomy. */
35410
- code = "SETTINGS_CONFLICT";
35411
- /** The revision the write expected. */
35412
- expected;
35413
- /** The revision the namespace actually stands at. */
35414
- actual;
35415
- /**
35416
- * @param ns - the namespace whose write was refused.
35417
- * @param expected - the revision the caller sent.
35418
- * @param actual - the revision now stored.
35419
- */
35420
- constructor(ns, expected, actual) {
35421
- super(`settings namespace "${ns}" changed since it was read (expected revision ${String(expected)}, now ${String(actual)})`);
35422
- this.name = "SettingsConflictError";
35423
- this.expected = expected;
35424
- this.actual = actual;
35425
- }
35426
- };
35427
- /** Whether a value is a plain data object (not an array, null, or class instance). */
35428
- function isPlainObject(value) {
35429
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
35430
- const proto = Object.getPrototypeOf(value);
35431
- return proto === Object.prototype || proto === null;
35432
- }
35433
- /** Apply one path op to a detached section, returning the next section. */
35434
- function applyPathOp(section, op) {
35435
- const [head, ...rest] = op.path;
35436
- if (head === void 0) {
35437
- if (op.op === "unset") return {};
35438
- if (!isPlainObject(op.value)) throw new TypeError("settings mutate: setting the section root requires a plain object");
35439
- return { ...op.value };
35440
- }
35441
- if (rest.length === 0) {
35442
- if (op.op === "set") return {
35443
- ...section,
35444
- [head]: op.value
35445
- };
35446
- const { [head]: _removed, ...kept } = section;
35447
- return kept;
35448
- }
35449
- const child = section[head];
35450
- if (!isPlainObject(child)) {
35451
- if (op.op === "unset") return section;
35452
- return {
35453
- ...section,
35454
- [head]: applyPathOp({}, {
35455
- ...op,
35456
- path: rest
35457
- })
35458
- };
35459
- }
35460
- return {
35461
- ...section,
35462
- [head]: applyPathOp(child, {
35463
- ...op,
35464
- path: rest
35465
- })
35466
- };
35467
- }
35468
- /** Human label for a value that lossless JSON cannot represent (numbers reject inline). */
35469
- function describeRejected(value) {
35470
- if (value === void 0) return "undefined";
35471
- if (typeof value === "object" && value !== null) {
35472
- const name = Object.getPrototypeOf(value)?.constructor?.name;
35473
- return name === void 0 || name === "Object" ? "a non-plain object" : `a ${name}`;
35474
- }
35475
- return `a ${typeof value}`;
35476
- }
35477
- /**
35478
- * Detach and validate one write input in a single walk before persistence:
35479
- * only JSON data (plain objects, arrays, strings, finite numbers,
35480
- * booleans, `null`) may reach a provider document. `structuredClone` alone
35481
- * would admit Dates, Maps, BigInts, and cycles that YAML/JSON storage then
35482
- * silently distorts on the reload round-trip. `undefined` entries in objects
35483
- * are skipped — the same sparse-patch semantics as {@link mergeLayers} — while
35484
- * an `undefined` array entry is rejected rather than coerced.
35485
- * @param root - plain-object write input (caller-checked).
35486
- * @param reject - builds the validation error from a value label and its `$`-rooted path.
35487
- * @returns the detached JSON-compatible clone.
35488
- */
35489
- function cloneJsonShaped(root, reject) {
35490
- const visiting = /* @__PURE__ */ new WeakSet();
35491
- const clone = (value, path) => {
35492
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
35493
- if (typeof value === "number") {
35494
- if (!Number.isFinite(value)) throw reject("a non-finite number", path);
35495
- return value;
35496
- }
35497
- if (Array.isArray(value)) {
35498
- if (visiting.has(value)) throw reject("a circular reference", path);
35499
- visiting.add(value);
35500
- const entries = value.map((entry, index) => clone(entry, `${path}[${index}]`));
35501
- visiting.delete(value);
35502
- return entries;
35503
- }
35504
- if (isPlainObject(value)) {
35505
- if (visiting.has(value)) throw reject("a circular reference", path);
35506
- visiting.add(value);
35507
- const out = {};
35508
- for (const [key, entry] of Object.entries(value)) {
35509
- if (entry === void 0) continue;
35510
- out[key] = clone(entry, `${path}.${key}`);
35511
- }
35512
- visiting.delete(value);
35513
- return out;
35514
- }
35515
- throw reject(describeRejected(value), path);
35516
- };
35517
- return clone(root, "$");
35518
- }
35519
- /**
35520
- * Layer `over` onto `under`: plain objects merge recursively, every other
35521
- * value (arrays included) replaces the lower layer wholesale. `over` never
35522
- * carries `undefined` entries — sections come from parsed documents and write
35523
- * snapshots pass {@link cloneJsonShaped}, which strips them so a sparse patch
35524
- * cannot erase lower keys.
35525
- */
35526
- function mergeLayers(under, over) {
35527
- if (over === void 0) return under;
35528
- if (!isPlainObject(under) || !isPlainObject(over)) return over;
35529
- const merged = { ...under };
35530
- for (const [key, value] of Object.entries(over)) merged[key] = key in merged ? mergeLayers(merged[key], value) : value;
35531
- return merged;
35532
- }
35533
- /**
35534
- * Abstract settings service. Providers implement raw-document storage
35535
- * (`load`/`persist`) and push external changes through {@link Settings.publish};
35536
- * the base class owns namespace registration, resolution, validation, change
35537
- * detection, and the `settings/updated` commit event.
35538
- */
35539
- var SettingsProvider = class extends Service {
35540
- registrations = /* @__PURE__ */ new Map();
35541
- /** Latest published raw document; empty until the provider's first publish. */
35542
- document = {};
35543
- /** Per-namespace write chains; settled tails, so a failure never poisons the queue. */
35544
- writeQueues = /* @__PURE__ */ new Map();
35545
- /** In-flight watcher invocation segments, drained by the dispose teardown. */
35546
- pendingTails = /* @__PURE__ */ new Set();
35547
- /** Set at service dispose: refuse new writes while queued ones drain. */
35548
- stopped = false;
35549
- /** Opaque read of {@link stopped}: control flow cannot narrow it across awaits. */
35550
- isStopped() {
35551
- return this.stopped;
35552
- }
35553
- constructor(ctx) {
35554
- super(ctx, "settings");
35555
- }
35556
- /**
35557
- * Load the provider's document once and publish it before the service
35558
- * becomes injectable, and register the write-drain teardown. Providers with
35559
- * their own init (watchers, connections) delegate here first via
35560
- * `yield* super[Service.init]()`; their disposers then run before the drain.
35561
- */
35562
- async *[Service.init]() {
35563
- yield async () => {
35564
- this.stopped = true;
35565
- await Promise.allSettled([...this.writeQueues.values(), ...this.pendingTails]);
35566
- };
35567
- this.publish(await this.load());
35568
- }
35569
- /**
35570
- * Absolute path of the provider's user-editable document, when its storage
35571
- * is one local file. Configuration surfaces use this only as availability
35572
- * metadata; the guarded open operation resolves the path again Host-side.
35573
- * Non-file providers leave it undefined and expose no open-document affordance.
35574
- * @returns the absolute local document path, or undefined for non-file storage.
35575
- */
35576
- get documentPath() {}
35577
- /**
35578
- * Prepare the provider's user-editable document for a native editor. File
35579
- * providers may materialize an absent document before returning its path;
35580
- * non-file providers return undefined.
35581
- * @returns the absolute local document path, or undefined for non-file storage.
35582
- */
35583
- prepareDocument() {
35584
- return Promise.resolve(this.documentPath);
35585
- }
35586
- /**
35587
- * Register a namespace schema and receive its owner scope. The registration
35588
- * is an effect on the calling plugin's fiber: disposing that fiber removes
35589
- * the namespace and its observers. An invalid stored section fails the
35590
- * registration itself — the earliest point where the schema can judge it.
35591
- * @param ns - unique namespace; duplicate registration fails loud.
35592
- * @param schema - schemastery schema resolving this namespace's value.
35593
- * @param options - composition `base` layer and effect timing.
35594
- * @returns the owner scope for reads, observation, and updates.
35595
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35596
- */
35597
- register(ns, schema, options) {
35598
- const parsedNs = parseSettingsNamespace(ns);
35599
- if (this.registrations.has(parsedNs)) throw new Error(`settings namespace "${parsedNs}" is already registered`);
35600
- const registration = {
35601
- ns: parsedNs,
35602
- schema,
35603
- base: options?.base,
35604
- applies: options?.applies ?? "live",
35605
- ...options?.validate === void 0 ? {} : { validate: options.validate },
35606
- resolved: deepFreeze(this.resolve(schema, options?.base, this.section(parsedNs), options?.validate)),
35607
- revision: 0,
35608
- watchers: /* @__PURE__ */ new Set()
35609
- };
35610
- this.ctx.effect(() => {
35611
- this.registrations.set(parsedNs, registration);
35612
- return () => this.registrations.delete(parsedNs);
35613
- }, `settings.register(${JSON.stringify(String(parsedNs))})`);
35614
- return {
35615
- get: () => registration.resolved,
35616
- watch: (callback) => {
35617
- const watcher = {
35618
- callback,
35619
- tail: Promise.resolve(),
35620
- active: true
35621
- };
35622
- registration.watchers.add(watcher);
35623
- return () => {
35624
- watcher.active = false;
35625
- registration.watchers.delete(watcher);
35626
- };
35627
- },
35628
- update: (patch) => this.update(parsedNs, patch),
35629
- replace: (section) => this.replace(parsedNs, section)
35630
- };
35631
- }
35632
- /**
35633
- * Attach one optional-settings consumer to this provider. The consumer
35634
- * registers its composition entry as the base layer while this provider is
35635
- * present, then falls back to that entry if the provider detaches.
35636
- * @param owner - consumer context whose unload suppresses fallback work.
35637
- * @param ns - consumer-owned settings namespace.
35638
- * @param schema - schema resolving the namespace.
35639
- * @param entry - composition entry used as the base and fallback value.
35640
- * @param hooks - source sink, change notification, and optional validation.
35641
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35642
- */
35643
- installSection(owner, ns, schema, entry, hooks) {
35644
- const scope = this.register(ns, schema, {
35645
- base: entry,
35646
- ...hooks.validate === void 0 ? {} : { validate: hooks.validate }
35647
- });
35648
- hooks.setSource(() => scope.get());
35649
- this.ctx.effect(() => () => {
35650
- if (isUnloading(owner)) return;
35651
- hooks.setSource(() => entry);
35652
- hooks.onChange();
35653
- });
35654
- hooks.onChange();
35655
- scope.watch(() => {
35656
- if (isUnloading(owner)) return;
35657
- hooks.onChange();
35658
- });
35659
- }
35660
- /**
35661
- * Describe every registered namespace for configuration surfaces, including
35662
- * the composition `base` and raw user layers so a form can mark which fields
35663
- * the user overrode (presence in `user`) and what a reset returns to.
35664
- * @param options - redaction switch; wire surfaces must redact.
35665
- * @returns one descriptor per registered namespace, in registration order.
35666
- */
35667
- describe(options) {
35668
- return [...this.registrations.values()].map((registration) => {
35669
- let user;
35670
- try {
35671
- user = this.section(registration.ns);
35672
- } catch {
35673
- user = void 0;
35674
- }
35675
- const base = registration.base === void 0 ? void 0 : structuredClone(registration.base);
35676
- const detachedUser = user === void 0 ? void 0 : structuredClone(user);
35677
- const descriptor = {
35678
- ns: registration.ns,
35679
- schema: registration.schema.toJSON(),
35680
- value: registration.resolved,
35681
- revision: registration.revision,
35682
- ...base === void 0 ? {} : { base },
35683
- ...detachedUser === void 0 ? {} : { user: detachedUser },
35684
- applies: registration.applies
35685
- };
35686
- if (options?.redactSecrets !== true) return descriptor;
35687
- const schema = registration.schema;
35688
- const redacted = redactSecrets(schema, registration.resolved);
35689
- return {
35690
- ...descriptor,
35691
- value: redacted.value,
35692
- ...base === void 0 ? {} : { base: redactSecrets(schema, base).value },
35693
- ...detachedUser === void 0 ? {} : { user: redactSecrets(schema, detachedUser).value },
35694
- secrets: redacted.secrets
35695
- };
35696
- });
35697
- }
35698
- /**
35699
- * Read one registered namespace's resolved value.
35700
- * @param ns - the namespace to read.
35701
- * @returns the resolved value, or `undefined` while unregistered.
35702
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35703
- */
35704
- get(ns) {
35705
- return this.registrations.get(parseSettingsNamespace(ns))?.resolved;
35706
- }
35707
- /**
35708
- * Merge a patch into one registered namespace's user layer, validate the
35709
- * resolved candidate, persist through the provider, then commit and emit.
35710
- * A validation failure rejects before anything is persisted. Writes to one
35711
- * namespace are serialized: concurrent updates apply in call order, each
35712
- * merging over the previous write's committed section.
35713
- * @param ns - the registered namespace to update.
35714
- * @param patch - plain-object patch over the user section.
35715
- * @param expectedRevision - the descriptor `revision` the caller read; a
35716
- * namespace that moved past it rejects with {@link SettingsConflictError}.
35717
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35718
- */
35719
- async update(ns, patch, expectedRevision) {
35720
- return this.write(parseSettingsNamespace(ns), patch, "merge", expectedRevision);
35721
- }
35722
- /**
35723
- * Replace one registered namespace's user section wholesale, validate,
35724
- * persist, then commit and emit. Keys absent from `section` fall back to the
35725
- * composition `base` and schema defaults — this is the removal/reset path a
35726
- * merge-only patch cannot express (`replace({})` re-inherits everything).
35727
- * @param ns - the registered namespace to replace.
35728
- * @param section - the complete next user section.
35729
- * @param expectedRevision - the descriptor `revision` the caller read; a
35730
- * namespace that moved past it rejects with {@link SettingsConflictError}.
35731
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35732
- */
35733
- async replace(ns, section, expectedRevision) {
35734
- return this.write(parseSettingsNamespace(ns), section, "replace", expectedRevision);
35735
- }
35736
- /**
35737
- * Apply path-addressed edits to one registered namespace's user section,
35738
- * validate, persist, then commit and emit. The ops are applied to the
35739
- * section as it stands when the write reaches the front of the queue, so a
35740
- * caller never has to restate fields it did not touch — and, crucially,
35741
- * cannot delete fields it never saw. This is the write path for any caller
35742
- * holding a redacted view; `replace` remains the wholesale reset.
35743
- * @param ns - the registered namespace to edit.
35744
- * @param ops - ordered path edits; later ops observe earlier ones.
35745
- * @param expectedRevision - the descriptor `revision` the caller read; a
35746
- * namespace that moved past it rejects with {@link SettingsConflictError}.
35747
- * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
35748
- */
35749
- async mutate(ns, ops, expectedRevision) {
35750
- const parsedNs = parseSettingsNamespace(ns);
35751
- if (!Array.isArray(ops)) throw new TypeError(`settings mutate for "${parsedNs}" must be an array of path ops`);
35752
- for (const op of ops) {
35753
- if (!isPlainObject(op) || op["op"] !== "set" && op["op"] !== "unset") throw new TypeError(`settings mutate for "${parsedNs}" ops must be {op:'set'|'unset', path}`);
35754
- if (!Array.isArray(op["path"]) || op["path"].some((part) => typeof part !== "string")) throw new TypeError(`settings mutate for "${parsedNs}" op paths must be arrays of strings`);
35755
- }
35756
- return this.write(parsedNs, ops, "mutate", expectedRevision);
35757
- }
35758
- /** Validate a write, then queue it on the namespace's serialized write chain. */
35759
- write(ns, input, mode, expectedRevision) {
35760
- const verb = mode === "merge" ? "update" : mode === "replace" ? "replace" : "mutate";
35761
- const registration = this.registrations.get(ns);
35762
- if (registration === void 0) throw new Error(`settings namespace "${ns}" is not registered`);
35763
- if (this.isStopped()) throw new Error(`settings service is disposed: "${ns}" cannot be written`);
35764
- if (!this.writable) throw new Error(`settings provider is read-only: "${ns}" cannot be updated in-process`);
35765
- let payload;
35766
- if (mode === "mutate") payload = { ops: input };
35767
- else {
35768
- if (!isPlainObject(input)) throw new TypeError(`settings ${verb} for "${ns}" must be a plain object`);
35769
- payload = input;
35770
- }
35771
- const snapshot = cloneJsonShaped(payload, (label, path) => /* @__PURE__ */ new TypeError(`settings ${verb} for "${ns}" must contain only JSON-compatible data (found ${label} at ${path})`));
35772
- const run = (this.writeQueues.get(ns) ?? Promise.resolve()).catch(() => void 0).then(async () => {
35773
- if (this.isStopped()) throw new Error(`settings service was disposed before the queued "${ns}" ${verb} ran`);
35774
- if (this.registrations.get(ns) !== registration) throw new Error(`settings namespace "${ns}" registration was disposed before the queued ${verb} ran`);
35775
- const current = this.section(ns) ?? {};
35776
- if (expectedRevision !== void 0 && expectedRevision !== registration.revision) throw new SettingsConflictError(ns, expectedRevision, registration.revision);
35777
- const section = mode === "merge" ? mergeLayers(current, snapshot) : mode === "replace" ? snapshot : snapshot["ops"].reduce(applyPathOp, current);
35778
- const next = deepFreeze(this.resolve(registration.schema, registration.base, section, registration.validate));
35779
- await this.persist(ns, section);
35780
- this.document[ns] = section;
35781
- if (this.registrations.get(ns) === registration && !this.isStopped()) {
35782
- this.bumpRevision(registration, current, section);
35783
- this.commit(registration, next, "update");
35784
- }
35785
- });
35786
- this.writeQueues.set(ns, run);
35787
- return run;
35788
- }
35789
- /**
35790
- * Provider hook: commit a complete raw document observed in storage. Each
35791
- * registered namespace re-resolves; an invalid section keeps that
35792
- * namespace's last good value and warns, other namespaces still commit.
35793
- * @param doc - the detached raw document (unregistered sections preserved).
35794
- * @param source - change origin; defaults to `provider`.
35795
- */
35796
- publish(doc, source = "provider") {
35797
- const before = /* @__PURE__ */ new Map();
35798
- for (const registration of this.registrations.values()) try {
35799
- before.set(registration.ns, this.section(registration.ns));
35800
- } catch {
35801
- before.set(registration.ns, void 0);
35802
- }
35803
- this.document = doc;
35804
- for (const registration of this.registrations.values()) {
35805
- let next;
35806
- try {
35807
- next = deepFreeze(this.resolve(registration.schema, registration.base, this.section(registration.ns), registration.validate));
35808
- } catch (error) {
35809
- this.ctx.logger.warn("settings: keeping last good \"%s\" after invalid stored section", registration.ns);
35810
- this.ctx.logger.warn(error);
35811
- continue;
35812
- }
35813
- this.bumpRevision(registration, before.get(registration.ns), this.section(registration.ns));
35814
- this.commit(registration, next, source);
35815
- }
35816
- }
35817
- /** Read one namespace's raw user section, rejecting non-object sections. */
35818
- section(ns) {
35819
- const section = this.document[ns];
35820
- if (section === void 0) return void 0;
35821
- if (!isPlainObject(section)) throw new TypeError(`settings section "${ns}" must be an object of keys`);
35822
- return section;
35823
- }
35824
- /** Resolve one namespace value: schema defaults, then `base`, then the user layer. */
35825
- resolve(schema, base, section, validate) {
35826
- const value = schema(mergeLayers(base, section));
35827
- validate?.(value);
35828
- return value;
35829
- }
35830
- /**
35831
- * Advance a namespace's revision when its RAW section changed, and announce
35832
- * it. Deliberately independent of {@link commit}'s resolved-value equality:
35833
- * storing an override equal to the composition base leaves the resolved
35834
- * value alone but changes what the document says, which is exactly what a
35835
- * configuration surface must re-read.
35836
- */
35837
- bumpRevision(registration, before, after) {
35838
- if (deepEqualJson(before, after)) return;
35839
- registration.revision += 1;
35840
- this.emitDocumentUpdated(registration.ns, registration.revision);
35841
- }
35842
- /** Contained fan-out of `settings/document-updated`, mirroring {@link commit}'s. */
35843
- emitDocumentUpdated(ns, revision) {
35844
- let invariantFailure;
35845
- const args = [
35846
- "settings/document-updated",
35847
- ns,
35848
- revision
35849
- ];
35850
- for (const listener of this.ctx.events.dispatch("emit", args)) try {
35851
- const returned = listener(ns, revision);
35852
- if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
35853
- this.warnListenerFailure(ns, error);
35854
- });
35855
- } catch (error) {
35856
- if (error?.code === "INVARIANT") {
35857
- invariantFailure ??= error;
35858
- continue;
35859
- }
35860
- this.warnListenerFailure(ns, error);
35861
- }
35862
- if (invariantFailure !== void 0) throw invariantFailure;
35863
- }
35864
- /** Commit a resolved value when changed: swap, notify watchers, emit the event. */
35865
- commit(registration, next, source) {
35866
- const prev = registration.resolved;
35867
- if (deepEqualJson(next, prev)) return;
35868
- registration.resolved = next;
35869
- for (const watcher of [...registration.watchers]) {
35870
- const segment = watcher.tail.then(() => {
35871
- if (!watcher.active || this.isStopped()) return;
35872
- return watcher.callback(next, prev);
35873
- }).then(() => void 0, (error) => {
35874
- this.warnWatcherFailure(registration.ns, error);
35875
- });
35876
- watcher.tail = segment;
35877
- this.pendingTails.add(segment);
35878
- segment.then(() => this.pendingTails.delete(segment));
35879
- }
35880
- let invariantFailure;
35881
- const args = [
35882
- "settings/updated",
35883
- registration.ns,
35884
- next,
35885
- prev,
35886
- source
35887
- ];
35888
- for (const listener of this.ctx.events.dispatch("emit", args)) try {
35889
- const returned = listener(registration.ns, next, prev, source);
35890
- if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
35891
- this.warnListenerFailure(registration.ns, error);
35892
- });
35893
- } catch (error) {
35894
- if (error?.code === "INVARIANT") {
35895
- invariantFailure ??= error;
35896
- continue;
35897
- }
35898
- this.warnListenerFailure(registration.ns, error);
35899
- }
35900
- if (invariantFailure !== void 0) throw invariantFailure;
35901
- }
35902
- /** Contained-watcher diagnostic shared by the sync and async failure paths. */
35903
- warnWatcherFailure(ns, error) {
35904
- this.ctx.logger.warn("settings: watcher for \"%s\" failed", ns);
35905
- this.ctx.logger.warn(error);
35906
- }
35907
- /** Contained-listener diagnostic shared by the sync and async failure paths. */
35908
- warnListenerFailure(ns, error) {
35909
- this.ctx.logger.warn("settings: a settings/updated listener for \"%s\" failed", ns);
35910
- this.ctx.logger.warn(error);
35911
- }
35912
- };
35913
- /**
35914
- * Value mirror of the `FiberState` members {@link isUnloading} compares
35915
- * against: a const enum has no runtime object to import, and the value is
35916
- * needed at runtime (same rationale as the CLI boot driver's mirror).
35917
- */
35918
- const FIBER_DISPOSED = 4;
35919
- const FIBER_UNLOADING = 5;
35920
- /** Whether the consumer's own fiber is tearing down (not just losing the settings service). */
35921
- function isUnloading(ctx) {
35922
- const state = ctx.fiber.state;
35923
- return state === FIBER_UNLOADING || state === FIBER_DISPOSED;
35924
- }
35925
- //#endregion
35926
- //#region ../session-rdb/src/testing/helpers.ts
35927
- var EmptySettings = class extends SettingsProvider {
35928
- constructor(ctx) {
35929
- super(ctx);
35930
- }
35931
- get writable() {
35932
- return true;
35933
- }
35934
- async load() {
35935
- return {};
35936
- }
35937
- persist(_ns, _section) {
35938
- return Promise.resolve();
35939
- }
35940
- };
35941
- //#endregion
35942
37278
  //#region ../session-rdb/src/testing/contract.ts
35943
37279
  function meta(id, cwd) {
35944
37280
  return {
@@ -36066,7 +37402,6 @@ function oneTurnLog() {
36066
37402
  //#region src/testing.ts
36067
37403
  async function harness(provide) {
36068
37404
  const ctx = new Context();
36069
- await ctx.plugin(EmptySettings);
36070
37405
  await ctx.plugin(SessionStore);
36071
37406
  new SessionProjectionRegistry(ctx);
36072
37407
  const fiber = await ctx.plugin(SessionPersistenceRdb, {
@@ -36194,4 +37529,4 @@ function turnLog(base, turn, opts = {}) {
36194
37529
  return events;
36195
37530
  }
36196
37531
  //#endregion
36197
- export { EmptySettings, SESSION_EDITOR_PATH, Session, SessionIdBrand, SessionSeq, SessionStore, TokenMeter, assistantMessage, createPersisted, harness, meta, oneTurnLog, parseJsonlArtifact, turnLog, twoTurnLog, userMessage };
37532
+ export { SESSION_EDITOR_PATH, Session, SessionIdBrand, SessionSeq, SessionStore, TokenMeter, assistantMessage, createPersisted, harness, meta, oneTurnLog, parseJsonlArtifact, turnLog, twoTurnLog, userMessage };