@deepseek-ai/dsh-session-persistence-jsonl 0.1.6-alpha.2 → 0.1.7-alpha.1

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/lib/worker.cjs CHANGED
@@ -264,12 +264,6 @@ function offloadMessageImages(message, indexes) {
264
264
  selected += 1;
265
265
  }
266
266
  imageIndex += 1;
267
- } else if (block.type === "tool-result") {
268
- const content = visit(block.content);
269
- if (content !== block.content) projected = {
270
- ...block,
271
- content
272
- };
273
267
  }
274
268
  if (projected !== block) next ??= blocks.slice(0, index);
275
269
  next?.push(projected);
@@ -356,6 +350,45 @@ function defineProperty(object, key, value) {
356
350
  });
357
351
  }
358
352
  //#endregion
353
+ //#region ../../../vendor/cosmokit/src/volatile.ts
354
+ /** Shared config references used by schema validators and plugin runtimes. */
355
+ const write = Symbol.for("cosmokit.volatile.write");
356
+ function snapshot(value, ancestors = /* @__PURE__ */ new Set()) {
357
+ if (typeof value === "function") throw new TypeError("volatile config cannot contain functions");
358
+ if (value === null || typeof value !== "object") return value;
359
+ if (ancestors.has(value)) throw new TypeError("volatile config cannot contain cycles");
360
+ ancestors.add(value);
361
+ try {
362
+ if (Array.isArray(value)) return Object.freeze(value.map((item) => snapshot(item, ancestors)));
363
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new TypeError("volatile config objects must be plain objects or arrays");
364
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [key, snapshot(item, ancestors)])));
365
+ } finally {
366
+ ancestors.delete(value);
367
+ }
368
+ }
369
+ /**
370
+ * Create a detached reference containing an immutable copy of the supplied data.
371
+ * @param value - validated config data; class instances and functions are unsupported.
372
+ * @returns a reference whose value is updated only by its owning runtime.
373
+ */
374
+ function createVolatile(value) {
375
+ let current = snapshot(value);
376
+ return Object.freeze({
377
+ get: () => current,
378
+ [write]: (value) => {
379
+ current = value;
380
+ }
381
+ });
382
+ }
383
+ /**
384
+ * Identify references across ESM/CJS copies of the shared library.
385
+ * @param value - a parsed config value.
386
+ * @returns whether the value implements the shared reference protocol.
387
+ */
388
+ function isVolatile(value) {
389
+ return typeof value === "object" && value !== null && write in value;
390
+ }
391
+ //#endregion
359
392
  //#region ../../../vendor/cosmokit/src/types.ts
360
393
  /** Test values using `instanceof` with a `toStringTag` fallback. */
361
394
  function is(type, value) {
@@ -436,26 +469,47 @@ function clone(source, refs = /* @__PURE__ */ new Map()) {
436
469
  }
437
470
  return result;
438
471
  }
439
- /** Deeply compare arrays, dates, regexps, buffers, and plain object fields. */
472
+ /**
473
+ * Compare values recursively, treating two volatile references as equal regardless of value.
474
+ * Strict comparison distinguishes null/undefined, treats opaque objects by identity,
475
+ * compares URLs by normalized href, treats array holes as undefined, and considers distinct cyclic structures unequal.
476
+ * @param a - first value.
477
+ * @param b - second value.
478
+ * @param strict - whether to require strict data equality outside volatile references.
479
+ * @returns whether the values compare equal.
480
+ */
440
481
  function deepEqual(a, b, strict) {
441
- if (a === b) return true;
442
- if (!strict && isNullable(a) && isNullable(b)) return true;
443
- if (typeof a !== typeof b) return false;
444
- if (typeof a !== "object") return false;
445
- if (!a || !b) return false;
446
- function check(test, then) {
447
- return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
448
- }
449
- return check(Array.isArray, (a, b) => a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
450
- if (a.byteLength !== b.byteLength) return false;
451
- const viewA = new Uint8Array(a);
452
- const viewB = new Uint8Array(b);
453
- for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
454
- return true;
455
- }) ?? Object.keys({
456
- ...a,
457
- ...b
458
- }).every((key) => deepEqual(a[key], b[key], strict));
482
+ const ancestors = /* @__PURE__ */ new Set();
483
+ function compare(a, b) {
484
+ if (a === b) return true;
485
+ if (isVolatile(a) || isVolatile(b)) return isVolatile(a) && isVolatile(b);
486
+ if (!strict && isNullable(a) && isNullable(b)) return true;
487
+ if (typeof a !== typeof b || typeof a !== "object" || !a || !b) return false;
488
+ if (ancestors.has(a)) return false;
489
+ function check(test, then) {
490
+ return test(a) ? test(b) ? then(a, b) : false : test(b) ? false : void 0;
491
+ }
492
+ ancestors.add(a);
493
+ try {
494
+ return check(Array.isArray, (a, b) => {
495
+ if (a.length !== b.length) return false;
496
+ for (let index = 0; index < a.length; index++) if (!compare(a[index], b[index])) return false;
497
+ return true;
498
+ }) ?? check(is("Date"), (a, b) => a.valueOf() === b.valueOf()) ?? check(is("URL"), (a, b) => a.href === b.href) ?? check(is("RegExp"), (a, b) => a.source === b.source && a.flags === b.flags) ?? check(isArrayBufferLike, (a, b) => {
499
+ if (a.byteLength !== b.byteLength) return false;
500
+ const viewA = new Uint8Array(a);
501
+ const viewB = new Uint8Array(b);
502
+ for (let i = 0; i < viewA.length; i++) if (viewA[i] !== viewB[i]) return false;
503
+ return true;
504
+ }) ?? ((!strict || [a, b].every((value) => Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) && Object.keys({
505
+ ...a,
506
+ ...b
507
+ }).every((key) => compare(a[key], b[key])));
508
+ } finally {
509
+ ancestors.delete(a);
510
+ }
511
+ }
512
+ return compare(a, b);
459
513
  }
460
514
  //#endregion
461
515
  //#region ../../../vendor/cosmokit/src/string.ts
@@ -2462,6 +2516,7 @@ const KNOWN_SESSION_EVENT_TYPES = new Set([
2462
2516
  "compaction/start",
2463
2517
  "compaction/summary",
2464
2518
  "deliverables/presented",
2519
+ "developer/message",
2465
2520
  "feedback/message-delete",
2466
2521
  "feedback/message-put",
2467
2522
  "feedback/record",
@@ -2514,6 +2569,7 @@ const MESSAGE_PROJECTION_EVENT_TYPES = new Set(["image/offload"]);
2514
2569
  /** Runtime counterpart of the message-producing event union. */
2515
2570
  const SURFACE_EVENT_TYPES = new Set([
2516
2571
  "system/message",
2572
+ "developer/message",
2517
2573
  "user/message",
2518
2574
  "assistant/message",
2519
2575
  "tool/result"
@@ -2521,7 +2577,7 @@ const SURFACE_EVENT_TYPES = new Set([
2521
2577
  /**
2522
2578
  * Whether an event type can join the model-visible surface.
2523
2579
  * @param type - event type to test.
2524
- * @returns true for one of the four message-producing event types.
2580
+ * @returns true for one of the message-producing event types.
2525
2581
  */
2526
2582
  function isSurfaceEligibleType(type) {
2527
2583
  return SURFACE_EVENT_TYPES.has(type);
@@ -2529,7 +2585,7 @@ function isSurfaceEligibleType(type) {
2529
2585
  /**
2530
2586
  * Project a single event into the LLM message it derives to, or null when it
2531
2587
  * produces none — a non-surface event (attempt, boundary, log-only record) or an
2532
- * empty-content assistant/message (which exists only to host usage). A caller
2588
+ * empty-content system, developer, or assistant message. A caller
2533
2589
  * reconstructing model input supplies the same prefix's `projectedMessages`
2534
2590
  * from {@link foldSurface}; without that map this function reads original
2535
2591
  * event content. Session instance methods apply the live projection. Messages
@@ -2544,6 +2600,7 @@ function deriveEventMessage(event, projectedMessages) {
2544
2600
  switch (event.type) {
2545
2601
  case "user/message": return event.data;
2546
2602
  case "system/message":
2603
+ case "developer/message":
2547
2604
  case "assistant/message":
2548
2605
  if (event.data.message.content.length === 0) return null;
2549
2606
  return event.data.message;
@@ -2556,14 +2613,33 @@ function isRecord(value) {
2556
2613
  return typeof value === "object" && value !== null && !Array.isArray(value);
2557
2614
  }
2558
2615
  /**
2559
- * Reject noncanonical request-header fields and contradictory tool failure metadata.
2616
+ * Reject noncanonical request-header fields, developer roles/content, and contradictory tool failure metadata.
2560
2617
  * This does not validate complete event payloads or embedded provider streams.
2561
2618
  * @param event - event whose locally related payload fields are inspected.
2562
2619
  * @param subject - event location to include in validation errors.
2563
- * @throws when request data/header is not an object, optional header fields are empty, or tool failure metadata contradicts its message.
2620
+ * @throws when request-header fields, developer roles/content, or tool failure metadata are invalid.
2564
2621
  */
2565
2622
  function validateSessionEventData(event, subject) {
2566
2623
  const data = event.data;
2624
+ if (SURFACE_EVENT_TYPES.has(event.type) && isRecord(data)) {
2625
+ const message = event.type === "user/message" ? data : data["message"];
2626
+ if (isRecord(message)) {
2627
+ if (event.type === "developer/message" !== (message["role"] === "developer")) throw new Error(`${subject} developer/message and developer role must occur together`);
2628
+ if (message["role"] !== "developer" && Array.isArray(message["content"]) && message["content"].some((block) => isRecord(block) && (block["type"] === "tool-addition" || block["type"] === "tool-removal"))) throw new Error(`${subject} tool-change blocks require developer role`);
2629
+ if (event.type === "developer/message" && Array.isArray(message["content"])) {
2630
+ let hasAdditions = false;
2631
+ for (const block of message["content"]) {
2632
+ if (!isRecord(block) || block["type"] !== "tool-addition" && block["type"] !== "tool-removal") continue;
2633
+ if (typeof block["toolName"] !== "string" || block["toolName"].length === 0) throw new Error(`${subject} ${block["type"]} requires a nonempty toolName`);
2634
+ if (block["type"] === "tool-addition") {
2635
+ hasAdditions = true;
2636
+ if (Object.hasOwn(block, "tool")) throw new Error(`${subject} tool-addition must omit inline tool definitions`);
2637
+ }
2638
+ }
2639
+ if (hasAdditions ? !isEventSeq(data["headerSeq"]) : Object.hasOwn(data, "headerSeq")) throw new Error(`${subject} requires headerSeq exactly when tool additions are present`);
2640
+ }
2641
+ }
2642
+ }
2567
2643
  if (event.type === "request/header") {
2568
2644
  if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
2569
2645
  const header = data["header"];
@@ -2576,9 +2652,7 @@ function validateSessionEventData(event, subject) {
2576
2652
  if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
2577
2653
  if (data["error"] === void 0) return;
2578
2654
  const message = data["message"];
2579
- const content = isRecord(message) ? message["content"] : void 0;
2580
- const block = Array.isArray(content) ? content[0] : void 0;
2581
- if (!isRecord(block) || block["isError"] !== true) throw new Error(`${subject} error requires message content[0].isError === true`);
2655
+ if (!isRecord(message) || message["isError"] !== true) throw new Error(`${subject} error requires message.isError === true`);
2582
2656
  }
2583
2657
  }
2584
2658
  /** Create an empty surface fold state. */
@@ -2636,6 +2710,23 @@ function assertSourceEventReferences(event, shadowedSeqs) {
2636
2710
  const missing = shadowedSeqs.filter((seq) => !sources.has(seq));
2637
2711
  if (missing.length > 0) throw new Error(`surface replace: sourceEventSeqs must include every shadowed surface node; missing ${missing.join(", ")}`);
2638
2712
  }
2713
+ /** Resolve tool additions against their immutable historical request header. */
2714
+ function assertDeveloperHeader(event, events, baseSeq) {
2715
+ if (event.type !== "developer/message") return;
2716
+ validateSessionEventData(event, `developer/message at seq ${event.seq}`);
2717
+ if (event.data.headerSeq === void 0) return;
2718
+ const headerSeq = event.data.headerSeq;
2719
+ const headerEvent = events[headerSeq - baseSeq];
2720
+ if (headerSeq >= event.seq || headerEvent?.type !== "request/header") throw new Error("developer/message headerSeq must reference an earlier request/header");
2721
+ for (const block of event.data.message.content) {
2722
+ if (block.type !== "tool-addition") continue;
2723
+ const definitions = headerEvent.data.header.tools?.filter((tool) => tool.name === block.toolName) ?? [];
2724
+ if (definitions.length !== 1) throw new Error(`developer/message tool-addition "${block.toolName}" must name exactly one tool in headerSeq ${headerSeq}`);
2725
+ const definition = definitions[0];
2726
+ if (typeof definition.description !== "string" || !isRecord(definition.parameters)) throw new Error(`developer/message tool-addition "${block.toolName}" requires a complete tool definition in headerSeq ${headerSeq}`);
2727
+ if (Object.hasOwn(definition, "deferLoading") && definition.deferLoading !== true) throw new Error("developer/message referenced tool deferLoading must be true when present");
2728
+ }
2729
+ }
2639
2730
  /**
2640
2731
  * Validate one event's surface metadata without checking membership in a log or surface.
2641
2732
  * @param event - event whose marker and source sequence values are inspected.
@@ -2688,21 +2779,13 @@ function assertToolResultRewrite(event, shadowedSeqs, events, baseSeq) {
2688
2779
  if (original?.type !== "tool/result") throw new Error("tool/result surface replacement must target a current tool/result");
2689
2780
  const originalRest = { ...original.data };
2690
2781
  const replacementRest = { ...event.data };
2691
- const originalResult = original.data.message.content[0];
2692
- const replacementResult = event.data.message.content[0];
2693
2782
  originalRest["message"] = {
2694
2783
  ...original.data.message,
2695
- content: [{
2696
- ...originalResult,
2697
- content: null
2698
- }]
2784
+ content: null
2699
2785
  };
2700
2786
  replacementRest["message"] = {
2701
2787
  ...event.data.message,
2702
- content: [{
2703
- ...replacementResult,
2704
- content: null
2705
- }]
2788
+ content: null
2706
2789
  };
2707
2790
  if (!isDeepEqualJson(originalRest, replacementRest)) throw new Error("tool/result surface replacement may change only content");
2708
2791
  }
@@ -2722,6 +2805,7 @@ function assertSystemHeadRewrite(event, state, startIdx, shadowedSeqs, events, b
2722
2805
  function planSurfaceEvent(state, event, expectedSeq, events, baseSeq, projections) {
2723
2806
  if (event.seq !== expectedSeq) throw new Error(`session event seq ${event.seq} is not contiguous; expected ${expectedSeq}`);
2724
2807
  const surfaceOp = validateSurfaceMetadata(event);
2808
+ assertDeveloperHeader(event, events, baseSeq);
2725
2809
  const projection = projections.find((item) => item.type === event.type);
2726
2810
  if (projection !== void 0) return {
2727
2811
  kind: "project",
@@ -2906,7 +2990,10 @@ function isTypertRemoteSegment(value) {
2906
2990
  }
2907
2991
  const REMOTE_METHOD_DESCRIPTOR = "@deepseek-ai/dsh-typert-protocol/remote-methods";
2908
2992
  /**
2909
- * Bind one visible Service field to a Cordis key and Remote namespace.
2993
+ * Bind one visible Service field to a Cordis key and Remote namespace. A
2994
+ * service that owns a Cordis Context also gives its tree `ctx.invocation`,
2995
+ * `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
2996
+ * a Host composition to read it.
2910
2997
  * @param service - owning Service instance, normally `this`.
2911
2998
  * @param serviceKey - exact Cordis service key.
2912
2999
  * @param options - optional distinct wire namespace.
@@ -2916,6 +3003,8 @@ function bindTypertRemote(service, serviceKey, options = {}) {
2916
3003
  validateName("service key", serviceKey);
2917
3004
  const namespace = options.namespace ?? serviceKey;
2918
3005
  validateName("namespace", namespace);
3006
+ const ctx = Reflect.get(service, "ctx");
3007
+ if (ctx instanceof Context) provideInvocationAccessor(ctx);
2919
3008
  return Object.freeze({
2920
3009
  service,
2921
3010
  serviceKey,
@@ -2937,6 +3026,16 @@ var TypertRemoteService = class extends Service {
2937
3026
  this.typertRemote = bindTypertRemote(this, this.name, options);
2938
3027
  }
2939
3028
  };
3029
+ /**
3030
+ * Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
3031
+ * reflect service's "cannot get property" error; a call-derived Context shadows
3032
+ * the accessor with its own property. The first Remote Service constructed in a
3033
+ * tree registers it on the root, where it outlives any one Service.
3034
+ */
3035
+ function provideInvocationAccessor(ctx) {
3036
+ if (Object.hasOwn(ctx.root.reflect.props, "invocation")) return;
3037
+ ctx.root.accessor("invocation", { get: () => void 0 });
3038
+ }
2940
3039
  function Remote(methodExportOrOptions, context) {
2941
3040
  if (typeof methodExportOrOptions === "string") {
2942
3041
  validateName("Remote export name", methodExportOrOptions);
@@ -3036,9 +3135,24 @@ function freezeMessage(message) {
3036
3135
  * @returns an immutable message with a fresh stable identity.
3037
3136
  */
3038
3137
  function createMessage(input) {
3039
- return freezeMessage({
3138
+ return deepFreeze(structuredClone({
3040
3139
  ...input,
3041
3140
  id: brandString(randomUUID())
3141
+ }));
3142
+ }
3143
+ /**
3144
+ * Create one identified model-produced assistant message and freeze it before publication.
3145
+ * @param input - complete content plus the provider, model, and optional replay state for a new assistant message.
3146
+ * @returns an immutable assistant message with fixed role/source tags and a fresh stable identity.
3147
+ */
3148
+ function createAssistantMessage(input) {
3149
+ return createMessage({
3150
+ role: "assistant",
3151
+ content: input.content,
3152
+ source: {
3153
+ kind: "model",
3154
+ ...input.source
3155
+ }
3042
3156
  });
3043
3157
  }
3044
3158
  //#endregion
@@ -3218,6 +3332,7 @@ Schema.prototype.pattern = function pattern(regexp) {
3218
3332
  return schema;
3219
3333
  };
3220
3334
  Schema.prototype.simplify = function simplify(value) {
3335
+ if (isVolatile(value)) value = value.get();
3221
3336
  if (deepEqual(value, this.meta.default, this.type === "dict")) return null;
3222
3337
  if (isNullable(value)) return value;
3223
3338
  if (this.type === "object" || this.type === "dict") {
@@ -3274,12 +3389,49 @@ for (const key of [
3274
3389
  };
3275
3390
  return schema;
3276
3391
  } });
3392
+ Schema.prototype.volatile = function volatile() {
3393
+ if (this.meta.volatile) throw new TypeError("volatile schema is already wrapped");
3394
+ return this.extra("volatile", true);
3395
+ };
3277
3396
  const resolvers = {};
3397
+ const checkedVolatile = Symbol("checked-volatile-schema");
3398
+ function validateVolatileSchema(schema, path = [], blocked = false, seen = /* @__PURE__ */ new Map()) {
3399
+ const states = seen.get(schema) ?? /* @__PURE__ */ new Set();
3400
+ if (states.has(blocked)) return;
3401
+ states.add(blocked);
3402
+ seen.set(schema, states);
3403
+ if (schema.meta?.volatile && blocked) throw new ValidationError("volatile fields require a fixed object path without an enclosing volatile field", { path });
3404
+ const nested = blocked || !!schema.meta?.volatile;
3405
+ if (schema.dict) for (const [key, child] of Object.entries(schema.dict)) validateVolatileSchema(child, [...path, key], nested, seen);
3406
+ if (schema.sKey) validateVolatileSchema(schema.sKey, [...path, "<key>"], true, seen);
3407
+ if (schema.inner && (schema.type !== "lazy" || schema.inner[kSchema])) validateVolatileSchema(schema.inner, [...path, "*"], true, seen);
3408
+ if (schema.list) for (let index = 0; index < schema.list.length; index++) validateVolatileSchema(schema.list[index], [...path, String(index)], true, seen);
3409
+ }
3278
3410
  Schema.extend = function extend(type, resolve) {
3279
3411
  resolvers[type] = resolve;
3280
3412
  };
3281
3413
  Schema.resolve = function resolve(data, schema, options = {}, strict = false) {
3282
3414
  if (!schema) return [data];
3415
+ if (!options[checkedVolatile]) {
3416
+ validateVolatileSchema(schema, options.path);
3417
+ options = {
3418
+ ...options,
3419
+ [checkedVolatile]: true
3420
+ };
3421
+ }
3422
+ if (schema.meta?.volatile) {
3423
+ const inner = Schema(schema);
3424
+ inner.meta = {
3425
+ ...schema.meta,
3426
+ volatile: false
3427
+ };
3428
+ const [value, adapted] = Schema.resolve(data, inner, options, strict);
3429
+ try {
3430
+ return [createVolatile(value), adapted];
3431
+ } catch (error) {
3432
+ throw new ValidationError(error instanceof Error ? error.message : String(error), options);
3433
+ }
3434
+ }
3283
3435
  if (options.ignore?.(data, schema)) return [data];
3284
3436
  if (isNullable(data) && schema.type !== "lazy") {
3285
3437
  if (schema.meta.required) throw new ValidationError(`missing required value`, options);
@@ -3382,6 +3534,7 @@ Schema.extend("lazy", (data, schema, options, strict) => {
3382
3534
  ...schema.meta,
3383
3535
  ...schema.inner.meta
3384
3536
  };
3537
+ validateVolatileSchema(schema.inner, options.path, true);
3385
3538
  }
3386
3539
  return Schema.resolve(data, schema.inner, options, strict);
3387
3540
  });
@@ -3481,7 +3634,7 @@ function property(data, key, schema, options) {
3481
3634
  } catch (e) {
3482
3635
  if (!options?.autofix) throw e;
3483
3636
  delete data[key];
3484
- return schema.meta.default;
3637
+ return schema.meta.volatile ? createVolatile(schema.meta.default) : schema.meta.default;
3485
3638
  }
3486
3639
  }
3487
3640
  Schema.extend("array", (data, { inner, meta }, options) => {
@@ -3898,25 +4051,23 @@ function textOnlyImageText(ref) {
3898
4051
  return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
3899
4052
  }
3900
4053
  /**
3901
- * True when typed model content contains an image block, walking nested
3902
- * tool-result content. This is the one recursive image walk shared by every
3903
- * image policy (capability gating, text-only serialization, compaction
3904
- * survey), so a consumer cannot silently diverge on nesting depth.
4054
+ * True when typed model content contains an image block. This is the one image
4055
+ * walk shared by every image policy (capability gating, text-only
4056
+ * serialization, compaction survey), so a consumer cannot silently diverge.
3905
4057
  * @param content - typed model content blocks.
3906
- * @returns whether any nested block is an image.
4058
+ * @returns whether any block is an image.
3907
4059
  */
3908
4060
  function contentHasImage(content) {
3909
- return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
4061
+ return content.some((block) => block.type === "image");
3910
4062
  }
3911
4063
  /**
3912
- * True when typed model content contains a file block, walking nested
3913
- * tool-result content on the same recursion every file policy shares.
4064
+ * True when typed model content contains a file block.
3914
4065
  * Reads current content on every call without retaining scan results.
3915
4066
  * @param content - typed model content blocks.
3916
- * @returns whether any nested block is a file.
4067
+ * @returns whether any block is a file.
3917
4068
  */
3918
4069
  function contentHasFile(content) {
3919
- for (const block of content) if (block.type === "file" || block.type === "tool-result" && contentHasFile(block.content)) return true;
4070
+ for (const block of content) if (block.type === "file") return true;
3920
4071
  return false;
3921
4072
  }
3922
4073
  /**
@@ -3933,7 +4084,7 @@ function fileHandleText(ref, readonlyPath) {
3933
4084
  if (readonlyPath === void 0) return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
3934
4085
  return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
3935
4086
  }
3936
- /** Replace every file occurrence, including nested tool results, with handle text. */
4087
+ /** Replace every file occurrence with handle text. */
3937
4088
  function replaceFilesWithHandles(blocks, resolvePath) {
3938
4089
  let next;
3939
4090
  for (const [index, block] of blocks.entries()) {
@@ -3945,29 +4096,10 @@ function replaceFilesWithHandles(blocks, resolvePath) {
3945
4096
  });
3946
4097
  continue;
3947
4098
  }
3948
- if (block.type === "tool-result") {
3949
- const content = replaceFilesWithHandles(block.content, resolvePath);
3950
- if (content !== block.content) {
3951
- next ??= blocks.slice(0, index);
3952
- next.push({
3953
- ...block,
3954
- content
3955
- });
3956
- continue;
3957
- }
3958
- }
3959
4099
  next?.push(block);
3960
4100
  }
3961
4101
  return next ?? blocks;
3962
4102
  }
3963
- /**
3964
- * Project durable file history into deterministic handle text for every model
3965
- * route. Unlike images, no provider receives file blocks natively, so this
3966
- * projection is unconditional in request assembly.
3967
- * @param messages - complete request history.
3968
- * @param resolvePath - resolve one reference's current execution-world read path.
3969
- * @returns the original list without files, otherwise shallow message copies with handle text.
3970
- */
3971
4103
  function projectFilesToText(messages, resolvePath) {
3972
4104
  if (!messages.some((message) => contentHasFile(message.content))) return messages;
3973
4105
  return messages.map((message) => {
@@ -3978,7 +4110,7 @@ function projectFilesToText(messages, resolvePath) {
3978
4110
  };
3979
4111
  });
3980
4112
  }
3981
- /** Replace every image occurrence, including nested tool results, for a text-only model. */
4113
+ /** Replace every image occurrence for a text-only model. */
3982
4114
  function replaceImagesForTextModel(blocks) {
3983
4115
  let next;
3984
4116
  for (const [index, block] of blocks.entries()) {
@@ -3990,26 +4122,10 @@ function replaceImagesForTextModel(blocks) {
3990
4122
  });
3991
4123
  continue;
3992
4124
  }
3993
- if (block.type === "tool-result") {
3994
- const content = replaceImagesForTextModel(block.content);
3995
- if (content !== block.content) {
3996
- next ??= blocks.slice(0, index);
3997
- next.push({
3998
- ...block,
3999
- content
4000
- });
4001
- continue;
4002
- }
4003
- }
4004
4125
  next?.push(block);
4005
4126
  }
4006
4127
  return next ?? blocks;
4007
4128
  }
4008
- /**
4009
- * Project durable image history into deterministic text for an exact text-only model.
4010
- * @param messages - complete request history.
4011
- * @returns the original list without images, otherwise shallow message copies with stable placeholders.
4012
- */
4013
4129
  function projectImagesForTextModel(messages) {
4014
4130
  if (!messages.some((message) => contentHasImage(message.content))) return messages;
4015
4131
  return messages.map((message) => {
@@ -4212,15 +4328,11 @@ var BlockAssembler = class {
4212
4328
  }
4213
4329
  /**
4214
4330
  * The assembled assistant message.
4215
- * @param source - producer attribution for the assembled message.
4331
+ * @param source - provider/model attribution (without the `kind` tag) for the assembled message.
4216
4332
  * @returns a frozen assistant-role message over `blocks()` (same open-block assembly rules).
4217
4333
  */
4218
- message(source = {
4219
- kind: "plugin",
4220
- plugin: "dsh-llm/assembler"
4221
- }) {
4222
- return createMessage({
4223
- role: "assistant",
4334
+ message(source) {
4335
+ return createAssistantMessage({
4224
4336
  content: this.blocks(),
4225
4337
  source
4226
4338
  });
@@ -5054,8 +5166,9 @@ var LlmError = class extends HarnessError {
5054
5166
  /** Remove replay state whose historical route is owned by another adapter. */
5055
5167
  forAdapter(options, adapter) {
5056
5168
  const messages = options.messages.map((message) => {
5169
+ if (message.role !== "assistant") return message;
5057
5170
  const source = message.source;
5058
- if (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
5171
+ if (source.replayState === void 0) return message;
5059
5172
  if (this.adapters.get(source.provider)?.adapter === adapter) return message;
5060
5173
  return freezeMessage({
5061
5174
  ...message,
@@ -5232,7 +5345,7 @@ function validateSessionHeader(id, input) {
5232
5345
  if (input === null || typeof input !== "object" || Array.isArray(input)) throw new Error("session header is not a plain JSON record");
5233
5346
  const record = input;
5234
5347
  if (Object.hasOwn(record, "seedLength")) throw new Error("session header has invalid field \"seedLength\"");
5235
- if (record.version !== 3) throw new Error(`session header version must be 3, got ${String(record.version)}`);
5348
+ if (record.version !== 4) throw new Error(`session header version must be 4, got ${String(record.version)}`);
5236
5349
  if (record.id !== id) throw new Error(`session header id "${String(record.id)}" does not match session id "${id}"`);
5237
5350
  if (typeof record.createdAt !== "number" || !Number.isSafeInteger(record.createdAt) || record.createdAt < 0) throw new Error("session header createdAt must be a non-negative safe integer");
5238
5351
  if (record.cwd !== void 0) {
@@ -5257,7 +5370,7 @@ function validateRestoredSessionHeader(id, input) {
5257
5370
  /** Detach, validate, and freeze the creation metadata published by a session. */
5258
5371
  function snapshotSessionHeader(id, source) {
5259
5372
  const snapshot = snapshotJsonValue(source === void 0 ? {
5260
- version: 3,
5373
+ version: 4,
5261
5374
  id,
5262
5375
  createdAt: Date.now(),
5263
5376
  isSeeded: false
@@ -5282,6 +5395,7 @@ function adoptSessionEvent(event) {
5282
5395
  case "user/message":
5283
5396
  deepFreeze(event.data);
5284
5397
  break;
5398
+ case "developer/message":
5285
5399
  case "system/message":
5286
5400
  case "assistant/message":
5287
5401
  case "tool/result":
@@ -5312,6 +5426,7 @@ function assertSessionEventEnvelope(value, index) {
5312
5426
  validateSessionEventData(event, `seed ${type} at index ${index}`);
5313
5427
  switch (type) {
5314
5428
  case "request/header":
5429
+ case "developer/message":
5315
5430
  case "system/message":
5316
5431
  case "user/message":
5317
5432
  case "assistant/attempt":
@@ -5360,15 +5475,16 @@ function assertAdapterDefaults(value, config, index) {
5360
5475
  const defaults = value;
5361
5476
  if (Object.keys(defaults).some((key) => !allowedAdapterKeys.has(key)) || Object.values(defaults).some((marker) => marker !== true) || defaults["reasoningEffort"] === true && config["reasoningEffort"] === void 0 || defaults["maxTokens"] === true && config["maxTokens"] === void 0) throw new Error(`seed request/header at index ${index} has invalid adapterDefaults`);
5362
5477
  }
5363
- /** The four surface event types whose payload carries an identified message. */
5478
+ /** The surface event types whose payload carries an identified message. */
5364
5479
  function isMessageEventType(type) {
5365
- return type === "system/message" || type === "user/message" || type === "assistant/message" || type === "tool/result";
5480
+ return type === "developer/message" || type === "system/message" || type === "user/message" || type === "assistant/message" || type === "tool/result";
5366
5481
  }
5367
5482
  const MESSAGE_ROLE_BY_TYPE = {
5368
5483
  "system/message": "system",
5484
+ "developer/message": "developer",
5369
5485
  "user/message": "user",
5370
5486
  "assistant/message": "assistant",
5371
- "tool/result": "user"
5487
+ "tool/result": "tool"
5372
5488
  };
5373
5489
  /** Validate only the event-specific invariants needed to safely replay a message. */
5374
5490
  function assertMessageEventShape(event, subject) {
@@ -5386,7 +5502,7 @@ function assertMessageEventShape(event, subject) {
5386
5502
  if (!Array.isArray(messageRecord["content"])) throw new Error(`${subject} message has invalid content`);
5387
5503
  const sourceRecord = source;
5388
5504
  if (type === "system/message") {
5389
- if (sourceRecord["kind"] !== "plugin" || typeof sourceRecord["plugin"] !== "string" || sourceRecord["plugin"] === "") throw new Error(`${subject} message must have plugin source`);
5505
+ if (sourceRecord["kind"] !== "system-prompt") throw new Error(`${subject} message must have system-prompt source`);
5390
5506
  return;
5391
5507
  }
5392
5508
  if (type === "assistant/message") {
@@ -5395,10 +5511,7 @@ function assertMessageEventShape(event, subject) {
5395
5511
  }
5396
5512
  if (type !== "tool/result") return;
5397
5513
  if (sourceRecord["kind"] !== "tool" || typeof sourceRecord["callId"] !== "string" || sourceRecord["callId"] === "") throw new Error(`${subject} message must have tool source`);
5398
- const content = messageRecord["content"];
5399
- const block = content[0];
5400
- if (content.length !== 1 || typeof block !== "object" || block === null || block["type"] !== "tool-result" || !Array.isArray(block["content"])) throw new Error(`${subject} message must contain one tool-result block`);
5401
- if (block["toolCallId"] !== sourceRecord["callId"]) throw new Error(`${subject} message has mismatched tool call ids`);
5514
+ if (messageRecord["toolCallId"] !== sourceRecord["callId"]) throw new Error(`${subject} message has mismatched tool call ids`);
5402
5515
  }
5403
5516
  /** Whether an unknown value carries the current provider/model pair. */
5404
5517
  function hasProviderModel(value) {
@@ -5455,30 +5568,26 @@ var Session = class Session {
5455
5568
  return this.header.id;
5456
5569
  }
5457
5570
  /**
5458
- * The first seq appended IN THIS PROCESS: the length of the constructor
5459
- * seed (0 without one). Events with smaller seq values entered through
5460
- * construction — replay, fork, or resume — and were never published on the
5461
- * `session/event` firehose (constructor seeds do not emit). This offset marks
5462
- * the constructor-input boundary for lifecycle ownership and persistence
5463
- * adoption; consumers that need complete canonical history still start at
5464
- * seq 0. Distinct from {@link inheritedEventCount}, the DURABLE
5465
- * fork-lineage cut: a resumed session's constructor seed is its full stored
5466
- * log, while the inherited count keeps the original fork value — this field is the
5467
- * in-process construction fact.
5571
+ * The constructor seed length (0 without one), before any marker appended
5572
+ * during construction. Seed events never publish on `session/event`. A
5573
+ * marker appended before the store attaches occupies this seq without
5574
+ * publishing either; otherwise this seq is available for the next append.
5468
5575
  *
5469
- * Not persisted itself: a seeded session projects it into the log as the
5470
- * `session/end-seed` event, which is what a consumer reading STORED history
5471
- * reads. Locate the LAST such event, not necessarily one at this seq — a
5472
- * seed already ending in one is not re-marked, so reopening an untouched
5473
- * session leaves that event at a smaller seq than `firstLiveSeq`. Prefer
5474
- * this field in-process: it is exact before the marker reaches storage.
5475
- *
5476
- * When this lifecycle appends the marker, it occupies this seq before the
5477
- * store attaches and therefore does not publish either. Otherwise this seq
5478
- * holds an ordinary published write.
5576
+ * This in-process offset is not persisted. A fork seed can already contain
5577
+ * the child's inherited marker and synthetic closers, so its child-owned
5578
+ * history starts at {@link inheritedEventCount}, before this offset. A
5579
+ * resumed Session's seed contains its full stored log, while its inherited
5580
+ * count keeps the durable fork cut. Consumers needing complete canonical
5581
+ * history start at seq 0.
5479
5582
  */
5480
5583
  firstLiveSeq;
5481
5584
  /**
5585
+ * First event produced for this object lifecycle. A new fork includes its
5586
+ * child-owned seed marker and closers; a restored Session starts after its
5587
+ * complete stored prefix. This in-process capture offset is not persisted.
5588
+ */
5589
+ firstLifecycleSeq;
5590
+ /**
5482
5591
  * Create a detached session by validating and snapshotting borrowed seed
5483
5592
  * events and storage metadata.
5484
5593
  * @param id - session identity.
@@ -5532,10 +5641,14 @@ var Session = class Session {
5532
5641
  const inheritedEventCount = SessionLogOffset(suppliedInheritedEventCount ?? 0);
5533
5642
  if (!this.header.isSeeded && inheritedEventCount !== 0) throw new Error("unseeded session inherited event count must be 0");
5534
5643
  if (inheritedEventCount > this.log.length) throw new Error("session inherited event count exceeds its event log");
5535
- if (mode === "snapshot" && this.header.isSeeded && inheritedEventCount !== this.log.length) throw new Error("seeded session constructor seed must equal its inherited prefix");
5644
+ const seedMarker = this.log[inheritedEventCount];
5645
+ const markedSeed = seedMarker?.type === "session/end-seed" && seedMarker.data.inherited === true;
5646
+ if (mode === "snapshot" && this.header.isSeeded && inheritedEventCount !== this.log.length && !markedSeed) throw new Error("seeded session constructor seed must equal its inherited prefix or mark its inherited cut");
5647
+ if (markedSeed && this.log.slice(inheritedEventCount + 1).some((event) => event.type === "session/end-seed" && event.data.inherited === true)) throw new Error("session inherited event count must identify the final inherited marker");
5536
5648
  this.inheritedEventCount = inheritedEventCount;
5537
- if (seed !== void 0 && mode === "snapshot" && this.header.isSeeded) this.append("session/end-seed", { inherited: true });
5538
- else if (seed !== void 0 && this.log.at(-1)?.type !== "session/end-seed") this.append("session/end-seed", {});
5649
+ this.firstLifecycleSeq = mode === "snapshot" && this.header.isSeeded ? inheritedEventCount : this.firstLiveSeq;
5650
+ if (seed !== void 0 && mode === "snapshot" && this.header.isSeeded && !markedSeed) this.append("session/end-seed", { inherited: true });
5651
+ else if (seed !== void 0 && !(mode === "snapshot" && this.header.isSeeded) && this.log.at(-1)?.type !== "session/end-seed") this.append("session/end-seed", {});
5539
5652
  }
5540
5653
  /** Cached immutable full snapshot of the private append-only log. */
5541
5654
  eventsSnapshot;
@@ -5750,101 +5863,6 @@ var Session = class Session {
5750
5863
  }
5751
5864
  };
5752
5865
  //#endregion
5753
- //#region ../session-persistence/src/errors.ts
5754
- /**
5755
- * Stable failures exposed by the session-persistence service and its handles,
5756
- * including the format refusals shared by every backend: a stored log this
5757
- * build cannot faithfully interpret is refused, never misread, and the
5758
- * refusal points at the raw artifact when the backend keeps one per session.
5759
- * @module @deepseek-ai/dsh-session-persistence/errors
5760
- */
5761
- /** Durable session contents failed validation after a successful backend read. */
5762
- var SessionPersistenceCorruptionError = class extends Error {
5763
- /**
5764
- * @param message - stable corruption context.
5765
- * @param options - original validation failure.
5766
- */
5767
- constructor(message, options) {
5768
- super(message, options);
5769
- this.name = "SessionPersistenceCorruptionError";
5770
- }
5771
- };
5772
- /**
5773
- * The stored log is intact but this runtime cannot faithfully interpret it:
5774
- * the header carries an unsupported format version, or an event's type is
5775
- * unknown to this build. Distinct from {@link SessionPersistenceCorruptionError}
5776
- * — nothing is damaged; the raw log remains readable at {@link location} when
5777
- * the backend keeps one artifact per session.
5778
- */
5779
- var SessionFormatUnsupportedError = class extends Error {
5780
- location;
5781
- /**
5782
- * @param message - stable reason the log cannot be interpreted, already
5783
- * including the raw-log path when one exists.
5784
- * @param location - the backend's artifact location, when one exists.
5785
- */
5786
- constructor(message, location) {
5787
- super(message);
5788
- this.location = location;
5789
- this.name = "SessionFormatUnsupportedError";
5790
- }
5791
- };
5792
- /**
5793
- * Direction-aware refusal text for a stored session whose format version this
5794
- * build does not read. Shared by load-time checks and by backends that must
5795
- * refuse BEFORE decoding version-dependent structure (a future format may not
5796
- * satisfy this build's structural checks at all, and the user must see
5797
- * "upgrade the harness", never "corrupt").
5798
- * @param id - the stored session id, for message context.
5799
- * @param version - the stored format version.
5800
- * @returns the stable refusal text, without a raw-log path suffix.
5801
- */
5802
- function sessionFormatVersionRefusal(id, version) {
5803
- return version > 3 ? `session "${id}" uses log format v${version}, but this harness reads only v3: the log was written by a newer harness — upgrade the harness to open it` : `session "${id}" uses log format v${version}, older than the supported v3, and this build ships no upgrade path for it`;
5804
- }
5805
- //#endregion
5806
- //#region ../session-persistence/src/storage-contract.ts
5807
- /**
5808
- * Backend-shared storage validation: the version gate, the fail-closed event
5809
- * vocabulary, append-batch materialization, and contiguity — one place so
5810
- * every backend refuses the same inputs identically.
5811
- * @module @deepseek-ai/dsh-session-persistence/storage-contract
5812
- */
5813
- /** Build a format refusal that points at the raw artifact when the backend has one. */
5814
- function unsupported(reason, location) {
5815
- return new SessionFormatUnsupportedError(location === void 0 ? reason : `${reason} (raw log: ${location.path})`, location);
5816
- }
5817
- /**
5818
- * Validate one exclusively owned stored event array in place: adopt each
5819
- * record (validating and freezing it) and refuse any event type this build
5820
- * does not know, unless its writer marked it `ignorable: true` — silently
5821
- * skipping an unknown required event could reconstruct a wrong session (the
5822
- * envelope contract on `SessionEvent.ignorable`). Unknown required types and
5823
- * retired pre-release shapes refuse here; this validator performs no migration.
5824
- * @param meta - the stored header the events belong to.
5825
- * @param events - exclusively owned decoded events; validated in place.
5826
- * @param location - the backend's artifact location for refusals, when one exists.
5827
- * @returns the same array, validated and frozen.
5828
- * @throws {SessionFormatUnsupportedError} for unknown event types.
5829
- * @throws {SessionPersistenceCorruptionError} for records that fail validation.
5830
- */
5831
- function validateStoredEvents(meta, events, location) {
5832
- for (const event of events) {
5833
- if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable !== true) throw unsupported(`session "${meta.id}" contains event type "${event.type}" (seq ${event.seq}) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`, location);
5834
- if (event.type === "request/header") {
5835
- const data = event.data;
5836
- if (typeof data === "object" && data !== null && data["reason"] === "fallback") throw unsupported(`session "${meta.id}" contains a request/header event (seq ${event.seq}) with the unsupported legacy reason "fallback"; refusing to interpret the log — it was written by a retired pre-release harness`, location);
5837
- }
5838
- }
5839
- try {
5840
- for (const [index, event] of events.entries()) events[index] = adoptSessionEvent(event);
5841
- } catch (error) {
5842
- if (error instanceof SessionFormatUnsupportedError) throw error;
5843
- throw new SessionPersistenceCorruptionError(`stored session "${meta.id}" failed validation: ${String(error)}`, { cause: error });
5844
- }
5845
- return events;
5846
- }
5847
- //#endregion
5848
5866
  //#region ../session-format/src/error.ts
5849
5867
  /** Error raised when a durable Session artifact cannot be restored or migrated losslessly. */
5850
5868
  var SessionFormatError = class extends Error {
@@ -6319,6 +6337,101 @@ function sessionFormatLogFilename(version) {
6319
6337
  return generation === 0 ? "session.jsonl" : `session.v${generation}.jsonl`;
6320
6338
  }
6321
6339
  //#endregion
6340
+ //#region ../session-persistence/src/errors.ts
6341
+ /**
6342
+ * Stable failures exposed by the session-persistence service and its handles,
6343
+ * including the format refusals shared by every backend: a stored log this
6344
+ * build cannot faithfully interpret is refused, never misread, and the
6345
+ * refusal points at the raw artifact when the backend keeps one per session.
6346
+ * @module @deepseek-ai/dsh-session-persistence/errors
6347
+ */
6348
+ /** Durable session contents failed validation after a successful backend read. */
6349
+ var SessionPersistenceCorruptionError = class extends Error {
6350
+ /**
6351
+ * @param message - stable corruption context.
6352
+ * @param options - original validation failure.
6353
+ */
6354
+ constructor(message, options) {
6355
+ super(message, options);
6356
+ this.name = "SessionPersistenceCorruptionError";
6357
+ }
6358
+ };
6359
+ /**
6360
+ * The stored log is intact but this runtime cannot faithfully interpret it:
6361
+ * the header carries an unsupported format version, or an event's type is
6362
+ * unknown to this build. Distinct from {@link SessionPersistenceCorruptionError}
6363
+ * — nothing is damaged; the raw log remains readable at {@link location} when
6364
+ * the backend keeps one artifact per session.
6365
+ */
6366
+ var SessionFormatUnsupportedError = class extends Error {
6367
+ location;
6368
+ /**
6369
+ * @param message - stable reason the log cannot be interpreted, already
6370
+ * including the raw-log path when one exists.
6371
+ * @param location - the backend's artifact location, when one exists.
6372
+ */
6373
+ constructor(message, location) {
6374
+ super(message);
6375
+ this.location = location;
6376
+ this.name = "SessionFormatUnsupportedError";
6377
+ }
6378
+ };
6379
+ /**
6380
+ * Direction-aware refusal text for a stored session whose format version this
6381
+ * build does not read. Shared by load-time checks and by backends that must
6382
+ * refuse BEFORE decoding version-dependent structure (a future format may not
6383
+ * satisfy this build's structural checks at all, and the user must see
6384
+ * "upgrade the harness", never "corrupt").
6385
+ * @param id - the stored session id, for message context.
6386
+ * @param version - the stored format version.
6387
+ * @returns the stable refusal text, without a raw-log path suffix.
6388
+ */
6389
+ function sessionFormatVersionRefusal(id, version) {
6390
+ return version > 4 ? `session "${id}" uses log format v${version}, but this harness reads only v4: the log was written by a newer harness — upgrade the harness to open it` : `session "${id}" uses log format v${version}, older than the supported v4, and this build ships no upgrade path for it`;
6391
+ }
6392
+ //#endregion
6393
+ //#region ../session-persistence/src/storage-contract.ts
6394
+ /**
6395
+ * Backend-shared storage validation: the version gate, the fail-closed event
6396
+ * vocabulary, append-batch materialization, and contiguity — one place so
6397
+ * every backend refuses the same inputs identically.
6398
+ * @module @deepseek-ai/dsh-session-persistence/storage-contract
6399
+ */
6400
+ /** Build a format refusal that points at the raw artifact when the backend has one. */
6401
+ function unsupported(reason, location) {
6402
+ return new SessionFormatUnsupportedError(location === void 0 ? reason : `${reason} (raw log: ${location.path})`, location);
6403
+ }
6404
+ /**
6405
+ * Validate one exclusively owned stored event array in place: adopt each
6406
+ * record (validating and freezing it) and refuse any event type this build
6407
+ * does not know, unless its writer marked it `ignorable: true` — silently
6408
+ * skipping an unknown required event could reconstruct a wrong session (the
6409
+ * envelope contract on `SessionEvent.ignorable`). Unknown required types and
6410
+ * retired pre-release shapes refuse here; this validator performs no migration.
6411
+ * @param meta - the stored header the events belong to.
6412
+ * @param events - exclusively owned decoded events; validated in place.
6413
+ * @param location - the backend's artifact location for refusals, when one exists.
6414
+ * @returns the same array, validated and frozen.
6415
+ * @throws {SessionFormatUnsupportedError} for unknown event types.
6416
+ * @throws {SessionPersistenceCorruptionError} for records that fail validation.
6417
+ */
6418
+ function validateStoredEvents(meta, events, location) {
6419
+ for (const event of events) {
6420
+ if (!KNOWN_SESSION_EVENT_TYPES.has(event.type) && event.ignorable !== true) throw unsupported(`session "${meta.id}" contains event type "${event.type}" (seq ${event.seq}) unknown to this harness and not marked ignorable; refusing to interpret the log — it was likely written by a newer harness`, location);
6421
+ if (event.type === "request/header") {
6422
+ const data = event.data;
6423
+ if (typeof data === "object" && data !== null && data["reason"] === "fallback") throw unsupported(`session "${meta.id}" contains a request/header event (seq ${event.seq}) with the unsupported legacy reason "fallback"; refusing to interpret the log — it was written by a retired pre-release harness`, location);
6424
+ }
6425
+ }
6426
+ try {
6427
+ for (const [index, event] of events.entries()) events[index] = adoptSessionEvent(event);
6428
+ } catch (error) {
6429
+ if (error instanceof SessionFormatUnsupportedError) throw error;
6430
+ throw new SessionPersistenceCorruptionError(`stored session "${meta.id}" failed validation: ${String(error)}`, { cause: error });
6431
+ }
6432
+ return events;
6433
+ }
6434
+ //#endregion
6322
6435
  //#region ../session-format-catalog/src/current.ts
6323
6436
  /** Current installed Session validation used after vocabulary-aware format restoration. */
6324
6437
  /**
@@ -6327,7 +6440,7 @@ function sessionFormatLogFilename(version) {
6327
6440
  * @returns nothing after successful validation.
6328
6441
  */
6329
6442
  function validateInstalledCurrentSessionHeader(header) {
6330
- if (header.version !== 3) throw new Error(`installed Session format is v3, got v${header.version}`);
6443
+ if (header.version !== 4) throw new Error(`installed Session format is v4, got v${header.version}`);
6331
6444
  Session.fromRestore(SessionId(header.id), [], header, SessionLogOffset(0), "detached");
6332
6445
  }
6333
6446
  /**
@@ -6336,7 +6449,7 @@ function validateInstalledCurrentSessionHeader(header) {
6336
6449
  * @returns nothing after successful validation.
6337
6450
  */
6338
6451
  function validateInstalledCurrentSessionArtifact(artifact) {
6339
- if (artifact.header.version !== 3) throw new Error(`installed Session format is v3, got v${artifact.header.version}`);
6452
+ if (artifact.header.version !== 4) throw new Error(`installed Session format is v4, got v${artifact.header.version}`);
6340
6453
  Session.fromRestore(SessionId(artifact.header.id), artifact.events, artifact.header, SessionLogOffset(artifact.inheritedEventCount), "detached", currentSessionMessageProjections);
6341
6454
  }
6342
6455
  //#endregion
@@ -8529,7 +8642,7 @@ function malformedLegacy(sessionId, type, seq) {
8529
8642
  }
8530
8643
  //#endregion
8531
8644
  //#region ../session-format-v0-to-v1/src/relationships.ts
8532
- const SURFACE_TYPES$2 = new Set([
8645
+ const SURFACE_TYPES$3 = new Set([
8533
8646
  "user/message",
8534
8647
  "assistant/message",
8535
8648
  "tool/result"
@@ -8558,7 +8671,7 @@ function assertReleasedArtifactRelationships(artifact, extensions = {}) {
8558
8671
  const extensionStepEvent = extensions.stepEvents?.has(event.type) === true;
8559
8672
  if (RELEASED_V0_EVENT_DISPOSITIONS[event.type] === void 0 && !extensionStepEvent) continue;
8560
8673
  const data = releasedV0Record(event.data, `${event.type} ${event.seq} data`);
8561
- if (SURFACE_TYPES$2.has(event.type)) surface = applySurface(surface, event);
8674
+ if (SURFACE_TYPES$3.has(event.type)) surface = applySurface(surface, event);
8562
8675
  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`);
8563
8676
  if (extensionStepEvent) {
8564
8677
  requireOpenStep(event, data, openTurn, openStep);
@@ -8895,7 +9008,7 @@ const EVENT_REQUIRED$1 = [
8895
9008
  "time",
8896
9009
  "data"
8897
9010
  ];
8898
- const SURFACE_TYPES$1 = new Set([
9011
+ const SURFACE_TYPES$2 = new Set([
8899
9012
  "user/message",
8900
9013
  "assistant/message",
8901
9014
  "tool/result"
@@ -8941,7 +9054,7 @@ function validateReleasedV2Artifact(artifact, mode, knownEventTypes, relationshi
8941
9054
  const installed = knownEventTypes?.has(type) === true;
8942
9055
  const ignorableUnknown = disposition === void 0 && record["ignorable"] === true;
8943
9056
  if (mode === "current" && disposition === void 0 && !installed && !ignorableUnknown) throw new SessionFormatUnsupportedMigrationError(`format v2 contains unknown event type ${JSON.stringify(type)} at seq ${index}`);
8944
- const surface = disposition !== void 0 && SURFACE_TYPES$1.has(type);
9057
+ const surface = disposition !== void 0 && SURFACE_TYPES$2.has(type);
8945
9058
  assertReleasedV2Keys(record, EVENT_REQUIRED$1, mode === "physical" || disposition === void 0 ? SURFACE_OPTIONAL : surface ? SURFACE_OPTIONAL : LOG_OPTIONAL, `format v2 event ${index}`);
8946
9059
  if (record["seq"] !== index) throw new SessionFormatError(`format v2 event ${index} is not dense`);
8947
9060
  sessionFormatSafeInteger(record["time"], `format v2 event ${index} time`);
@@ -9268,7 +9381,7 @@ function transformReleasedEvent(state, event, context) {
9268
9381
  if (event.type === "assistant/chunk") assertChunkEnvelope(event);
9269
9382
  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}`);
9270
9383
  const interrupted = legacyInterruptedTurn(state.legacyTurns, event);
9271
- 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`);
9384
+ 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`);
9272
9385
  assertSourceDeliveryMarker(state, event);
9273
9386
  observeLegacyTurn(state.legacyTurns, event);
9274
9387
  state.lastTime = event.time;
@@ -9310,7 +9423,7 @@ function assertChunkEnvelope(event) {
9310
9423
  }
9311
9424
  function assertSourceDeliveryMarker(state, event) {
9312
9425
  if (event.type !== "session-log-deepseek/delivery-accepted") return;
9313
- const data = record$1(event.data);
9426
+ const data = record$3(event.data);
9314
9427
  const inherited = state.sourceHeader.parentSession !== void 0 && event.seq < state.sourceCut;
9315
9428
  if (data["sessionFormatVersion"] === 1 && !inherited && data["sessionId"] !== state.sourceHeader.id) throw refusal("current-generation delivery marker names the wrong Session");
9316
9429
  }
@@ -9347,10 +9460,10 @@ function finishMigration(state, context) {
9347
9460
  return state.targetCut;
9348
9461
  }
9349
9462
  function transformChunk(state, event, context) {
9350
- const data = record$1(event.data);
9463
+ const data = record$3(event.data);
9351
9464
  const turn = coordinate(data["turn"]);
9352
9465
  const step = coordinate(data["step"]);
9353
- const chunk = record$1(data["chunk"]);
9466
+ const chunk = record$3(data["chunk"]);
9354
9467
  if (state.pending !== void 0 && (state.pending.group.terminal || state.pending.group.turn !== turn || state.pending.group.step !== step)) finishAttempt(state, context);
9355
9468
  else if (state.pending !== void 0) flushBuffered(state, state.pending, context);
9356
9469
  state.pending ??= {
@@ -9367,7 +9480,7 @@ function transformChunk(state, event, context) {
9367
9480
  if (chunk["type"] === "finish") state.pending.group.terminal = true;
9368
9481
  }
9369
9482
  function transformMessage(state, event, context) {
9370
- const data = record$1(event.data);
9483
+ const data = record$3(event.data);
9371
9484
  const turn = coordinate(data["turn"]);
9372
9485
  const step = coordinate(data["step"]);
9373
9486
  const sources = event.sourceEventSeqs;
@@ -9447,8 +9560,8 @@ function legacyTurnState() {
9447
9560
  };
9448
9561
  }
9449
9562
  function legacyInterruptedTurn(state, event) {
9450
- 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;
9451
- const splice = record$1(state.previous.data);
9563
+ 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;
9564
+ const splice = record$3(state.previous.data);
9452
9565
  if (splice["target"] !== "next-turn" || !Array.isArray(splice["inserted"]) || splice["inserted"].length === 0) return;
9453
9566
  return {
9454
9567
  type: "turn/end",
@@ -9461,7 +9574,7 @@ function legacyInterruptedTurn(state, event) {
9461
9574
  };
9462
9575
  }
9463
9576
  function observeLegacyTurn(state, event) {
9464
- const data = record$1(event.data);
9577
+ const data = record$3(event.data);
9465
9578
  if (event.type === "turn/start") {
9466
9579
  state.openTurn = coordinate(data["turn"]);
9467
9580
  state.openStep = null;
@@ -9474,8 +9587,8 @@ function observeLegacyTurn(state, event) {
9474
9587
  }
9475
9588
  function splitLegacyGoalChange(event) {
9476
9589
  if (event.type !== "user/message") return void 0;
9477
- const data = record$1(event.data);
9478
- const source = record$1(data["source"]);
9590
+ const data = record$3(event.data);
9591
+ const source = record$3(data["source"]);
9479
9592
  if (source["kind"] !== "goal" || source["change"] === void 0) return void 0;
9480
9593
  return {
9481
9594
  change: {
@@ -9594,7 +9707,7 @@ function streamOf(group) {
9594
9707
  return group.stream.map(({ record }) => record);
9595
9708
  }
9596
9709
  function messageEvent(source, group) {
9597
- const data = record$1(source.data);
9710
+ const data = record$3(source.data);
9598
9711
  const { sourceEventSeqs: _sourceEventSeqs, ...event } = source;
9599
9712
  return {
9600
9713
  ...event,
@@ -9621,7 +9734,7 @@ function remapReferences(source, targetSeq, mapping) {
9621
9734
  const sources = sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: mapList(numberArray(sourceEventSeqs), mapping, `${source.type} ${source.seq} sources`) };
9622
9735
  let operation = surfaceOp;
9623
9736
  if (surfaceOp !== void 0 && surfaceOp !== "append") {
9624
- const replacement = record$1(surfaceOp);
9737
+ const replacement = record$3(surfaceOp);
9625
9738
  operation = {
9626
9739
  op: "replace",
9627
9740
  start: mapOne(coordinate(replacement["start"]), mapping, `${source.type} ${source.seq} surface start`),
@@ -9637,7 +9750,7 @@ function remapReferences(source, targetSeq, mapping) {
9637
9750
  };
9638
9751
  }
9639
9752
  function remapPayloadReferences(event, mapping) {
9640
- const data = record$1(event.data);
9753
+ const data = record$3(event.data);
9641
9754
  switch (event.type) {
9642
9755
  case "command/done": return data["sourceEventSeq"] === void 0 ? data : {
9643
9756
  ...data,
@@ -9645,7 +9758,7 @@ function remapPayloadReferences(event, mapping) {
9645
9758
  };
9646
9759
  case "compaction/prune":
9647
9760
  case "compaction/summary": {
9648
- const range = record$1(data["shadowedRange"]);
9761
+ const range = record$3(data["shadowedRange"]);
9649
9762
  return {
9650
9763
  ...data,
9651
9764
  shadowedRange: {
@@ -9671,7 +9784,7 @@ function mapOne(value, mapping, label) {
9671
9784
  if (mapped === void 0) throw refusal(`${label} targets consumed assistant/chunk ${value}`);
9672
9785
  return mapped;
9673
9786
  }
9674
- function record$1(value) {
9787
+ function record$3(value) {
9675
9788
  return value;
9676
9789
  }
9677
9790
  function numberArray(value) {
@@ -9687,7 +9800,7 @@ function refusal(message) {
9687
9800
  //#region ../session-format-v2-to-v3/src/payload.ts
9688
9801
  /** Audited V2 migration admission and V3 payload validation, independent of installed core Session types. */
9689
9802
  /** Audited surface event names; all other admitted events are log-only. */
9690
- const SURFACE_TYPES = new Set([
9803
+ const SURFACE_TYPES$1 = new Set([
9691
9804
  "system/message",
9692
9805
  "user/message",
9693
9806
  "assistant/message",
@@ -9716,7 +9829,7 @@ const SOURCE_KINDS = new Set([
9716
9829
  * @param label - diagnostic subject.
9717
9830
  * @returns the narrowed object.
9718
9831
  */
9719
- function record(value, label) {
9832
+ function record$2(value, label) {
9720
9833
  if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(label + " must be an object");
9721
9834
  return value;
9722
9835
  }
@@ -9746,7 +9859,7 @@ function assertEvent(event, version) {
9746
9859
  const disposition = RELEASED_V2_EVENT_DISPOSITIONS[event.type];
9747
9860
  const feedback = event.type === "feedback/message-put" || event.type === "feedback/message-delete";
9748
9861
  if (disposition === void 0 && !feedback) throw new SessionFormatUnsupportedMigrationError("format v2 to v3 cannot safely transform unclassified event " + event.type);
9749
- const surface = SURFACE_TYPES.has(event.type);
9862
+ const surface = SURFACE_TYPES$1.has(event.type);
9750
9863
  keys(event, [
9751
9864
  "type",
9752
9865
  "seq",
@@ -9764,7 +9877,7 @@ function assertEvent(event, version) {
9764
9877
  assertReleasedSurfaceMetadata(event, event.seq, event.type, "forbid-assistant");
9765
9878
  if (event["surfaceOp"] === void 0) throw new SessionFormatError(event.type + " requires surfaceOp");
9766
9879
  }
9767
- const data = record(event.data, event.type + " data");
9880
+ const data = record$2(event.data, event.type + " data");
9768
9881
  if (feedback) {
9769
9882
  assertFeedback(event.type, data);
9770
9883
  return;
@@ -9778,10 +9891,10 @@ function assertEvent(event, version) {
9778
9891
  }
9779
9892
  if (event.type === "session/end-seed" && data["inherited"] !== void 0 && data["inherited"] !== true) throw new SessionFormatError("session/end-seed inherited must be true");
9780
9893
  if (event.type === "user/message") assertSource(data);
9781
- if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record(data["message"], "message"));
9894
+ if (event.type === "assistant/message" || event.type === "tool/result") assertSource(record$2(data["message"], "message"));
9782
9895
  if (event.type === "tool/result" && isSessionFormatJsonObject(data["error"]) && data["error"]["code"] === "TOOL_NOT_STARTED") {
9783
- const message = record(data["message"], "tool result message");
9784
- const source = record(message["source"], "tool result source");
9896
+ const message = record$2(data["message"], "tool result message");
9897
+ const source = record$2(message["source"], "tool result source");
9785
9898
  if (!isRepairIdentity(message["id"], source["callId"])) throw new SessionFormatError("TOOL_NOT_STARTED repair requires its canonical historical message id");
9786
9899
  }
9787
9900
  if (event.type === "agent/inbox/spliced" || event.type === "session/title-llm-request") {
@@ -9803,7 +9916,7 @@ function isRepairIdentity(id, callId) {
9803
9916
  return /^(0|[1-9]\d*)$/.test(suffix) && Number.isSafeInteger(Number(suffix));
9804
9917
  }
9805
9918
  function assertSource(message) {
9806
- const source = record(message["source"], "message source");
9919
+ const source = record$2(message["source"], "message source");
9807
9920
  if (typeof source["kind"] !== "string" || !SOURCE_KINDS.has(source["kind"])) throw new SessionFormatUnsupportedMigrationError("cannot safely transform unclassified message source");
9808
9921
  if (source["kind"] === "agent-message") {
9809
9922
  keys(source, [
@@ -9836,14 +9949,14 @@ function assertOwnedContent(event, data) {
9836
9949
  case "assistant/message":
9837
9950
  case "tool/result":
9838
9951
  case "team/message/queued":
9839
- assertContentKinds(record(data["message"], label + ".message")["content"], label + ".message.content");
9952
+ assertContentKinds(record$2(data["message"], label + ".message")["content"], label + ".message.content");
9840
9953
  break;
9841
9954
  case "agent/inbox/spliced":
9842
9955
  case "session/title-llm-request": {
9843
9956
  const field = event.type === "agent/inbox/spliced" ? "inserted" : "messages";
9844
9957
  for (const [index, value] of contentArray(data[field], label + "." + field).entries()) {
9845
9958
  const path = label + "." + field + "[" + String(index) + "]";
9846
- assertContentKinds(record(value, path)["content"], path + ".content");
9959
+ assertContentKinds(record$2(value, path)["content"], path + ".content");
9847
9960
  }
9848
9961
  break;
9849
9962
  }
@@ -9854,9 +9967,9 @@ function assertOwnedContent(event, data) {
9854
9967
  }
9855
9968
  if (event.type === "assistant/message" || event.type === "assistant/attempt") for (const [index, value] of contentArray(data["stream"], label + ".stream").entries()) {
9856
9969
  const path = label + ".stream[" + String(index) + "]";
9857
- const entry = record(value, path);
9970
+ const entry = record$2(value, path);
9858
9971
  if (entry["type"] !== "chunk") continue;
9859
- const chunk = record(entry["chunk"], path + ".chunk");
9972
+ const chunk = record$2(entry["chunk"], path + ".chunk");
9860
9973
  if (chunk["type"] === "block-end") assertContentBlock(chunk["block"], path + ".chunk.block");
9861
9974
  if (chunk["type"] === "block-start") assertContentKind(chunk["blockType"], path + ".chunk.blockType");
9862
9975
  }
@@ -9868,7 +9981,7 @@ function assertContentKinds(content, label) {
9868
9981
  for (const [index, value] of contentArray(content, label).entries()) assertContentBlock(value, label + "[" + String(index) + "]");
9869
9982
  }
9870
9983
  function assertContentBlock(value, label) {
9871
- const block = record(value, label);
9984
+ const block = record$2(value, label);
9872
9985
  assertContentKind(block["type"], label);
9873
9986
  if (block["type"] === "tool-result") {
9874
9987
  if (!Array.isArray(block["content"])) throw new SessionFormatError(label + ".content: invalid message content kind \"tool-result\": content must be an array");
@@ -9876,7 +9989,7 @@ function assertContentBlock(value, label) {
9876
9989
  }
9877
9990
  if (block["type"] === "file") {
9878
9991
  keys(block, ["type", "attachment"], [], label + " kind \"file\"");
9879
- const attachment = record(block["attachment"], label + " kind \"file\" attachment");
9992
+ const attachment = record$2(block["attachment"], label + " kind \"file\" attachment");
9880
9993
  keys(attachment, [
9881
9994
  "attachmentId",
9882
9995
  "name",
@@ -9914,10 +10027,10 @@ function assertV3StructuralRow(value) {
9914
10027
  if (typeof value !== "object" || value === null || Array.isArray(value)) return;
9915
10028
  const row = value;
9916
10029
  if (row["type"] === "request/header") {
9917
- const data = record(row["data"], "request/header data");
9918
- if (Object.hasOwn(record(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError("format v3 request/header rejects retired header.system");
10030
+ const data = record$2(row["data"], "request/header data");
10031
+ if (Object.hasOwn(record$2(data["header"], "request header"), "system")) throw new SessionFormatUnsupportedMigrationError("format v3 request/header rejects retired header.system");
9919
10032
  } else if (row["type"] === "system/message") {
9920
- const data = record(row["data"], "system/message data");
10033
+ const data = record$2(row["data"], "system/message data");
9921
10034
  assertSystem({
9922
10035
  type: "system/message",
9923
10036
  seq: 0,
@@ -9933,7 +10046,7 @@ function assertSystem(event, data) {
9933
10046
  "message"
9934
10047
  ], [], "system/message data");
9935
10048
  for (const coordinate of ["turn", "step"]) if (sessionFormatCount(data[coordinate], coordinate) === 0) throw new SessionFormatError(coordinate + " must be positive");
9936
- const message = record(data["message"], "system message");
10049
+ const message = record$2(data["message"], "system message");
9937
10050
  keys(message, [
9938
10051
  "id",
9939
10052
  "role",
@@ -9941,7 +10054,7 @@ function assertSystem(event, data) {
9941
10054
  "content"
9942
10055
  ], [], "system message");
9943
10056
  if (typeof message["id"] !== "string" || message["id"].length === 0 || message["role"] !== "system") throw new SessionFormatError("system message requires an id and system role");
9944
- const source = record(message["source"], "system source");
10057
+ const source = record$2(message["source"], "system source");
9945
10058
  if (source["kind"] !== "plugin" || typeof source["plugin"] !== "string" || source["plugin"].length === 0) throw new SessionFormatError("system message requires plugin source");
9946
10059
  assertReleasedPayloadSemantics({
9947
10060
  ...event,
@@ -9959,7 +10072,7 @@ function assertFeedback(type, data) {
9959
10072
  if (typeof data["messageId"] !== "string") throw new SessionFormatError("feedback messageId must be a string");
9960
10073
  return;
9961
10074
  }
9962
- const item = record(data["item"], "feedback item");
10075
+ const item = record$2(data["item"], "feedback item");
9963
10076
  keys(item, [
9964
10077
  "messageId",
9965
10078
  "rating",
@@ -9980,15 +10093,15 @@ function assertFeedback(type, data) {
9980
10093
  * @param knownEventTypes - additional installed event types whose envelopes are interpreted.
9981
10094
  */
9982
10095
  function assertV3Event(event, knownEventTypes) {
9983
- const value = record(event, "format v3 event");
10096
+ const value = record$2(event, "format v3 event");
9984
10097
  const subject = `format v3 ${event.type} at seq ${event.seq}`;
9985
- 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));
10098
+ 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));
9986
10099
  keys(value, [
9987
10100
  "type",
9988
10101
  "seq",
9989
10102
  "time",
9990
10103
  "data"
9991
- ], SURFACE_TYPES.has(event.type) || opaque ? [
10104
+ ], SURFACE_TYPES$1.has(event.type) || opaque ? [
9992
10105
  "ignorable",
9993
10106
  "surfaceOp",
9994
10107
  "sourceEventSeqs"
@@ -9997,11 +10110,11 @@ function assertV3Event(event, knownEventTypes) {
9997
10110
  sessionFormatCount(event.seq, `${subject} seq`);
9998
10111
  sessionFormatSafeInteger(event.time, `${subject} time`);
9999
10112
  if (Object.hasOwn(value, "ignorable") && value["ignorable"] !== true) throw new SessionFormatError(`${subject} ignorable must be true when present`);
10000
- if (SURFACE_TYPES.has(event.type)) {
10113
+ if (SURFACE_TYPES$1.has(event.type)) {
10001
10114
  const operation = value["surfaceOp"];
10002
10115
  if (operation === void 0) throw new SessionFormatError(`${subject} requires a surfaceOp marker`);
10003
10116
  if (operation !== "append") {
10004
- const replace = record(operation, `${subject} surfaceOp`);
10117
+ const replace = record$2(operation, `${subject} surfaceOp`);
10005
10118
  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`);
10006
10119
  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`);
10007
10120
  }
@@ -10023,13 +10136,13 @@ function assertV3Event(event, knownEventTypes) {
10023
10136
  function assertCanonicalPayload(event) {
10024
10137
  const subject = `format v3 ${event.type} at seq ${event.seq}`;
10025
10138
  if (event.type === "request/header") {
10026
- const header = record(record(event.data, `${subject} data`)["header"], `${subject} header`);
10139
+ const header = record$2(record$2(event.data, `${subject} data`)["header"], `${subject} header`);
10027
10140
  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`);
10028
10141
  }
10029
10142
  if (event.type !== "tool/result") return;
10030
- const data = record(event.data, `${subject} data`);
10143
+ const data = record$2(event.data, `${subject} data`);
10031
10144
  if (data["error"] === void 0) return;
10032
- const content = record(data["message"], `${subject} message`)["content"];
10145
+ const content = record$2(data["message"], `${subject} message`)["content"];
10033
10146
  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`);
10034
10147
  }
10035
10148
  /**
@@ -10041,7 +10154,7 @@ function canonicalizeTransformedEvent(event) {
10041
10154
  let target = event;
10042
10155
  const operation = event["surfaceOp"];
10043
10156
  if (operation !== void 0 && operation !== "append") {
10044
- const replace = record(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
10157
+ const replace = record$2(operation, `format v2 ${event.type} at seq ${event.seq} surfaceOp`);
10045
10158
  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`);
10046
10159
  target = {
10047
10160
  ...event,
@@ -10053,8 +10166,8 @@ function canonicalizeTransformedEvent(event) {
10053
10166
  };
10054
10167
  }
10055
10168
  if (event.type === "request/header") {
10056
- const data = record(event.data, `format v2 request/header at seq ${event.seq} data`);
10057
- const header = record(data["header"], `format v2 request/header at seq ${event.seq} header`);
10169
+ const data = record$2(event.data, `format v2 request/header at seq ${event.seq} data`);
10170
+ const header = record$2(data["header"], `format v2 request/header at seq ${event.seq} header`);
10058
10171
  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);
10059
10172
  if (empty.length > 0) {
10060
10173
  const canonical = Object.fromEntries(Object.entries(header).filter(([key]) => !empty.includes(key)));
@@ -10101,37 +10214,37 @@ function restoreReleasedV3Artifact(artifact, knownEventTypes) {
10101
10214
  assertV3Event(event, knownEventTypes);
10102
10215
  const system = event.type === "system/message";
10103
10216
  if (event.type === "step/start") {
10104
- const data = record(event.data, event.type);
10217
+ const data = record$2(event.data, event.type);
10105
10218
  step = {
10106
10219
  turn: data["turn"],
10107
10220
  step: data["step"]
10108
10221
  };
10109
10222
  } else if (event.type === "step/end" || event.type === "turn/end") step = void 0;
10110
10223
  if (system) {
10111
- const data = record(event.data, "system/message");
10224
+ const data = record$2(event.data, "system/message");
10112
10225
  if (step === void 0 || step.turn !== data["turn"] || step.step !== data["step"]) throw new SessionFormatError("system/message does not match an open step");
10113
10226
  const operation = event["surfaceOp"];
10114
10227
  if (hasSurface && head === void 0) throw new SessionFormatError("system/message requires a protected first surface head");
10115
10228
  if (operation === "append") {
10116
10229
  if (!hasSurface) head = event.seq;
10117
10230
  } else {
10118
- const replace = record(operation, "system replacement");
10231
+ const replace = record$2(operation, "system replacement");
10119
10232
  if (replace["startSeq"] === head || replace["endSeq"] === head) {
10120
10233
  if (replace["startSeq"] !== head || replace["endSeq"] !== head) throw new SessionFormatError("system/message must replace exactly the current system head");
10121
10234
  head = event.seq;
10122
10235
  }
10123
10236
  }
10124
- } else if (SURFACE_TYPES.has(event.type) && event["surfaceOp"] !== "append") {
10125
- const replace = record(event["surfaceOp"], "surface replacement");
10237
+ } else if (SURFACE_TYPES$1.has(event.type) && event["surfaceOp"] !== "append") {
10238
+ const replace = record$2(event["surfaceOp"], "surface replacement");
10126
10239
  if (replace["startSeq"] === head || replace["endSeq"] === head) throw new SessionFormatError("surface replacement cannot shadow the protected system head");
10127
10240
  }
10128
10241
  if (event.type === "compaction/prune" || event.type === "compaction/summary") {
10129
- const seqs = record(event.data, event.type)["shadowedSeqs"];
10242
+ const seqs = record$2(event.data, event.type)["shadowedSeqs"];
10130
10243
  if (Array.isArray(seqs) && seqs.some((seq) => seq === head)) throw new SessionFormatError("compaction cannot shadow the protected system head");
10131
10244
  }
10132
- if (SURFACE_TYPES.has(event.type)) hasSurface = true;
10245
+ if (SURFACE_TYPES$1.has(event.type)) hasSurface = true;
10133
10246
  const projected = relationshipEvent(event);
10134
- if (!SURFACE_TYPES.has(event.type) || event["surfaceOp"] === "append") return projected;
10247
+ if (!SURFACE_TYPES$1.has(event.type) || event["surfaceOp"] === "append") return projected;
10135
10248
  const replacement = event["surfaceOp"];
10136
10249
  return {
10137
10250
  ...projected,
@@ -10178,7 +10291,7 @@ function relationshipEvent(event) {
10178
10291
  };
10179
10292
  }
10180
10293
  if (event.type === "system/message") {
10181
- const message = record(record(event.data, "system data")["message"], "system message");
10294
+ const message = record$2(record$2(event.data, "system data")["message"], "system message");
10182
10295
  return {
10183
10296
  ...event,
10184
10297
  type: "user/message",
@@ -10189,11 +10302,11 @@ function relationshipEvent(event) {
10189
10302
  };
10190
10303
  }
10191
10304
  if (event.type !== "tool/result") return event;
10192
- const data = record(event.data, "tool result");
10305
+ const data = record$2(event.data, "tool result");
10193
10306
  if (data["error"] === void 0) return event;
10194
- if (record(data["error"], "tool error")["code"] !== "TOOL_NOT_STARTED") return event;
10195
- const message = record(data["message"], "tool message");
10196
- const callId = record(message["source"], "tool source")["callId"];
10307
+ if (record$2(data["error"], "tool error")["code"] !== "TOOL_NOT_STARTED") return event;
10308
+ const message = record$2(data["message"], "tool message");
10309
+ const callId = record$2(message["source"], "tool source")["callId"];
10197
10310
  const id = message["id"];
10198
10311
  if (!isRepairIdentity(id, callId)) return event;
10199
10312
  const prefix = "interrupted-tool-result-" + callId + "-";
@@ -10314,14 +10427,14 @@ function remapEvent(event, seq, mapping) {
10314
10427
  return value.map(one);
10315
10428
  };
10316
10429
  const range = (value) => {
10317
- const source = record(value, "sequence range");
10430
+ const source = record$2(value, "sequence range");
10318
10431
  return {
10319
10432
  ...source,
10320
10433
  start: one(source["start"]),
10321
10434
  end: one(source["end"])
10322
10435
  };
10323
10436
  };
10324
- let data = record(event.data, event.type);
10437
+ let data = record$2(event.data, event.type);
10325
10438
  switch (event.type) {
10326
10439
  case "command/done":
10327
10440
  if (data["sourceEventSeq"] !== void 0) data = {
@@ -10399,9 +10512,9 @@ var ReleasedV2ToV3Stage = class {
10399
10512
  assertEvent(event, 2);
10400
10513
  this.observeMessageIds(event);
10401
10514
  let source = event;
10402
- const data = record(event.data, event.type);
10515
+ const data = record$2(event.data, event.type);
10403
10516
  if (event.type === "request/header") {
10404
- const { system, ...header } = record(data["header"], "request header");
10517
+ const { system, ...header } = record$2(data["header"], "request header");
10405
10518
  const prompt = typeof system === "string" ? system : "";
10406
10519
  if (prompt !== this.prompt) this.emitSystem(prompt, event, context);
10407
10520
  source = {
@@ -10412,7 +10525,7 @@ var ReleasedV2ToV3Stage = class {
10412
10525
  }
10413
10526
  };
10414
10527
  }
10415
- 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");
10528
+ 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");
10416
10529
  if (event.type === "session/end-seed" && data["inherited"] === true) {
10417
10530
  if (!this.input.sourceHeader.isSeeded) throw new SessionFormatError("format v2 unseeded Session contains an inherited end-seed marker");
10418
10531
  this.sourceCut = event.seq;
@@ -10443,8 +10556,8 @@ var ReleasedV2ToV3Stage = class {
10443
10556
  return sessionFormatCount(this.targetCut, "format v3 inherited event count");
10444
10557
  }
10445
10558
  observeMessageIds(event) {
10446
- const data = record(event.data, event.type);
10447
- 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"] : [];
10559
+ const data = record$2(event.data, event.type);
10560
+ 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"] : [];
10448
10561
  for (const message of messages) {
10449
10562
  const id = message["id"];
10450
10563
  if (this.generatedIds.has(id)) throw new SessionFormatUnsupportedMigrationError("source message id collides with a generated system message id");
@@ -10549,40 +10662,1067 @@ function renameMessageSource(message) {
10549
10662
  };
10550
10663
  }
10551
10664
  //#endregion
10552
- //#region ../session-format-catalog/src/generated.ts
10665
+ //#region ../session-format-v3-to-v4/src/sources.ts
10666
+ /** Released V3 plugin-source conversion and declared message traversal. */
10667
+ /**
10668
+ * Visit only messages carried by first-party event payloads.
10669
+ * @param event - source or target logical event.
10670
+ * @param transform - message transform preserving unchanged identity.
10671
+ * @returns the event sharing all untouched payloads.
10672
+ */
10673
+ function mapEventMessages(event, transform) {
10674
+ const data = event.data;
10675
+ if (!isSessionFormatJsonObject(data)) return event;
10676
+ if (event.type === "user/message") {
10677
+ const message = transform(data);
10678
+ return message === data ? event : {
10679
+ ...event,
10680
+ data: message
10681
+ };
10682
+ }
10683
+ if (event.type === "developer/message" || event.type === "system/message" || event.type === "assistant/message" || event.type === "tool/result") {
10684
+ if (!isSessionFormatJsonObject(data["message"])) throw new SessionFormatError(event.type + " requires a message");
10685
+ const message = transform(data["message"]);
10686
+ return message === data["message"] ? event : {
10687
+ ...event,
10688
+ data: {
10689
+ ...data,
10690
+ message
10691
+ }
10692
+ };
10693
+ }
10694
+ const key = event.type === "agent/inbox/spliced" ? "inserted" : event.type === "session/title-llm-request" ? "messages" : void 0;
10695
+ if (key === void 0) return event;
10696
+ const messages = data[key];
10697
+ if (!Array.isArray(messages)) throw new SessionFormatError(event.type + " requires message array");
10698
+ const mapped = messages.map((message) => {
10699
+ if (!isSessionFormatJsonObject(message)) throw new SessionFormatError(event.type + " requires message objects");
10700
+ return transform(message);
10701
+ });
10702
+ return mapped.every((message, index) => message === messages[index]) ? event : {
10703
+ ...event,
10704
+ data: {
10705
+ ...data,
10706
+ [key]: mapped
10707
+ }
10708
+ };
10709
+ }
10710
+ Object.freeze({
10711
+ "compact": "compact-checkpoint",
10712
+ "tools-code-mode": "ptc-mode",
10713
+ "tools-ptc": "ptc-mode",
10714
+ "dsh-compaction-basic": "compact-basic",
10715
+ "@deepseek-ai/dsh-system-prompt": "runtime-context"
10716
+ });
10717
+ //#endregion
10718
+ //#region ../session-format-v3-to-v4/src/message-sources.ts
10719
+ /** Native source admission preserves unknown attribution and refuses retired plugin wrappers. */
10720
+ function source(message) {
10721
+ const value = message["source"];
10722
+ 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");
10723
+ }
10724
+ /**
10725
+ * Validate producer attribution in every declared durable message slot.
10726
+ * @param event - complete native V4 event before Session adoption.
10727
+ */
10728
+ function assertV4MessageSources(event) {
10729
+ mapEventMessages(event, (message) => {
10730
+ source(message);
10731
+ return message;
10732
+ });
10733
+ }
10734
+ /**
10735
+ * Refuse retired source syntax even after a malformed recoverable row; leave incomplete messages to decoding.
10736
+ * @param row - parsed row whose incomplete values have not been classified by the scanner.
10737
+ */
10738
+ function assertV4SourceRowAdmission(row) {
10739
+ if (!isSessionFormatJsonObject(row) || !isSessionFormatJsonObject(row["data"])) return;
10740
+ const data = row["data"];
10741
+ 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"] : [];
10742
+ if (!Array.isArray(messages)) return;
10743
+ for (const message of messages) {
10744
+ if (!isSessionFormatJsonObject(message)) continue;
10745
+ const value = message["source"];
10746
+ if (isSessionFormatJsonObject(value) && value["kind"] === "plugin") source(message);
10747
+ }
10748
+ }
10749
+ //#endregion
10750
+ //#region ../session-format-v3-to-v4/src/retired-syntax.ts
10751
+ /** Retired native syntax remains a hard refusal even after a recoverable physical-row failure. */
10752
+ function assertBlock(block, subject) {
10753
+ if (isSessionFormatJsonObject(block) && block["type"] === "tool-result") throw new SessionFormatError(`${subject} must not contain a released tool-result wrapper`);
10754
+ }
10755
+ function assertContent$1(content, subject) {
10756
+ if (Array.isArray(content)) content.forEach((block) => {
10757
+ assertBlock(block, subject);
10758
+ });
10759
+ }
10760
+ function assertMessageContent(message, subject) {
10761
+ if (isSessionFormatJsonObject(message)) assertContent$1(message["content"], subject);
10762
+ }
10763
+ /**
10764
+ * Refuse retired headers, PTC tags, and tool-result blocks in interpreted content slots.
10765
+ * Tool arguments, replay state, schema parameters, and nested extension values remain opaque.
10766
+ * @param row - parsed physical row or complete native logical event.
10767
+ */
10768
+ function assertV4RetiredSyntax(row) {
10769
+ if (!isSessionFormatJsonObject(row)) return;
10770
+ 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"]}`);
10771
+ const data = row["data"];
10772
+ if (row["type"] === "request/header") {
10773
+ if (!isSessionFormatJsonObject(data) || !isSessionFormatJsonObject(data["header"])) throw new SessionFormatError("format v4 request/header requires data and header objects");
10774
+ if (Object.hasOwn(data["header"], "system")) throw new SessionFormatError("format v4 request/header rejects retired header.system");
10775
+ return;
10776
+ }
10777
+ if (!isSessionFormatJsonObject(data)) return;
10778
+ const subject = `format v4 ${String(row["type"])} at seq ${String(row["seq"])} content`;
10779
+ switch (row["type"]) {
10780
+ case "user/message":
10781
+ assertContent$1(data["content"], subject);
10782
+ break;
10783
+ case "developer/message":
10784
+ case "assistant/message":
10785
+ case "team/message/queued":
10786
+ assertMessageContent(data["message"], subject);
10787
+ break;
10788
+ case "agent/inbox/spliced":
10789
+ case "session/title-llm-request": {
10790
+ const messages = data[row["type"] === "agent/inbox/spliced" ? "inserted" : "messages"];
10791
+ if (Array.isArray(messages)) messages.forEach((message) => {
10792
+ assertMessageContent(message, subject);
10793
+ });
10794
+ break;
10795
+ }
10796
+ case "compaction/summary":
10797
+ assertContent$1(data["summary"], subject);
10798
+ assertContent$1(data["rawOutput"], subject);
10799
+ break;
10800
+ case "tool/ptc-dispatch":
10801
+ assertContent$1(data["content"], subject);
10802
+ break;
10803
+ }
10804
+ if ((row["type"] === "assistant/message" || row["type"] === "assistant/attempt") && Array.isArray(data["stream"])) for (const entry of data["stream"]) {
10805
+ if (!isSessionFormatJsonObject(entry) || entry["type"] !== "chunk") continue;
10806
+ const chunk = entry["chunk"];
10807
+ if (!isSessionFormatJsonObject(chunk)) continue;
10808
+ if (chunk["type"] === "block-end") assertBlock(chunk["block"], subject);
10809
+ if (chunk["type"] === "block-start" && chunk["blockType"] === "tool-result") throw new SessionFormatError(`${subject} must not contain a released tool-result wrapper`);
10810
+ }
10811
+ }
10812
+ //#endregion
10813
+ //#region ../session-format-v3-to-v4/src/system-message.ts
10814
+ /** Native system-message fields checked before physical recovery can discard a row. */
10815
+ function record$1(value, subject) {
10816
+ if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(`format v4 ${subject} requires an object`);
10817
+ return value;
10818
+ }
10819
+ function string(value, subject, nonempty = false) {
10820
+ if (typeof value !== "string" || nonempty && value.length === 0) throw new SessionFormatError(`format v4 ${subject} requires a string${nonempty ? " with content" : ""}`);
10821
+ }
10822
+ function positive(value, subject) {
10823
+ if (sessionFormatCount(value, subject) === 0) throw new SessionFormatError(`format v4 ${subject} must be positive`);
10824
+ }
10825
+ function image(value) {
10826
+ const attachment = record$1(value, "system image attachment");
10827
+ string(attachment["attachmentId"], "system image attachmentId", true);
10828
+ if (![
10829
+ "image/png",
10830
+ "image/jpeg",
10831
+ "image/webp",
10832
+ "image/gif"
10833
+ ].includes(attachment["mediaType"])) throw new SessionFormatError("format v4 system image requires a supported mediaType");
10834
+ sessionFormatCount(attachment["bytes"], "system image bytes");
10835
+ positive(attachment["width"], "system image width");
10836
+ positive(attachment["height"], "system image height");
10837
+ if (attachment["name"] !== void 0) string(attachment["name"], "system image name");
10838
+ if (attachment["originalDimensions"] !== void 0) {
10839
+ const original = record$1(attachment["originalDimensions"], "system image originalDimensions");
10840
+ positive(original["width"], "system image original width");
10841
+ positive(original["height"], "system image original height");
10842
+ }
10843
+ }
10844
+ /**
10845
+ * Validate system fields independently of historical message representations; preserve additional JSON fields.
10846
+ * @param row - complete logical event or parsed physical row before corruption recovery.
10847
+ */
10848
+ function assertV4SystemMessageFields(row) {
10849
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "system/message") return;
10850
+ const data = record$1(row["data"], "system/message data");
10851
+ positive(data["turn"], "system/message turn");
10852
+ positive(data["step"], "system/message step");
10853
+ const message = record$1(data["message"], "system message");
10854
+ string(message["id"], "system message id", true);
10855
+ if (message["role"] !== "system") throw new SessionFormatError("format v4 system message requires system role");
10856
+ const content = message["content"];
10857
+ if (!Array.isArray(content)) throw new SessionFormatError("format v4 system message requires content array");
10858
+ for (const value of content) {
10859
+ const block = record$1(value, "system content block");
10860
+ string(block["type"], "system content type", true);
10861
+ switch (block["type"]) {
10862
+ case "text":
10863
+ case "reasoning":
10864
+ string(block["text"], "system content text");
10865
+ break;
10866
+ case "tool-call":
10867
+ string(block["id"], "system tool-call id", true);
10868
+ string(block["name"], "system tool-call name", true);
10869
+ string(block["arguments"], "system tool-call arguments");
10870
+ break;
10871
+ case "image":
10872
+ image(block["attachment"]);
10873
+ break;
10874
+ case "tool-result": throw new SessionFormatError("format v4 system content rejects retired tool-result wrappers");
10875
+ default: break;
10876
+ }
10877
+ }
10878
+ }
10879
+ //#endregion
10880
+ //#region ../session-format-v3-to-v4/src/developer.ts
10881
+ /** Native V4 developer-message and deferred-tool-schema validation. */
10882
+ function assertToolChange(block, developer) {
10883
+ if (!isSessionFormatJsonObject(block) || block["type"] !== "tool-addition" && block["type"] !== "tool-removal") return;
10884
+ if (!developer) throw new SessionFormatError(`format v4 ${block["type"]} requires developer role`);
10885
+ if (typeof block["toolName"] !== "string" || block["toolName"].length === 0) throw new SessionFormatError(`format v4 ${block["type"]} requires a nonempty toolName`);
10886
+ if (block["type"] === "tool-addition" && Object.hasOwn(block, "tool")) throw new SessionFormatError("format v4 tool-addition must omit inline tool definitions");
10887
+ }
10888
+ function assertContent(value, developer = false) {
10889
+ if (Array.isArray(value)) value.forEach((block) => {
10890
+ assertToolChange(block, developer);
10891
+ });
10892
+ }
10893
+ function assertDeveloperMessage(message) {
10894
+ assertContent(message["content"], true);
10895
+ const source = message["source"];
10896
+ 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");
10897
+ }
10898
+ /** Ordinary-message corruption belongs to the decoder; recognized V4-only values are refused here. */
10899
+ function assertOrdinaryMessage(value) {
10900
+ if (!isSessionFormatJsonObject(value)) return;
10901
+ if (value["role"] === "developer") throw new SessionFormatError("format v4 developer/message and developer role must occur together");
10902
+ assertContent(value["content"]);
10903
+ }
10553
10904
  /**
10554
- * GENERATED by `scripts/gen-session-format-catalog.ts` — do not edit by hand.
10555
- * The direct imports make historical readability independent of mounted plugins.
10905
+ * Validate native V4 developer messages, tool-change blocks, and deferred tool schemas.
10906
+ * Additional developer JSON fields are preserved. Malformed legacy message
10907
+ * slots remain available to recoverable decoding.
10908
+ * @param event - logical event or physical row before source-event range decoding.
10556
10909
  */
10910
+ function assertV4DeveloperData(event) {
10911
+ const data = event.data;
10912
+ if (!isSessionFormatJsonObject(data)) {
10913
+ if (event.type === "developer/message") throw new SessionFormatError("format v4 developer/message data must be an object");
10914
+ return;
10915
+ }
10916
+ if (event.type === "developer/message") {
10917
+ const message = data["message"];
10918
+ if (!isSessionFormatJsonObject(message) || message["role"] !== "developer") throw new SessionFormatError("format v4 developer/message requires turn, step, and a developer message");
10919
+ for (const field of ["turn", "step"]) if (sessionFormatCount(data[field], `developer/message ${field}`) === 0) throw new SessionFormatError(`developer/message ${field} must be positive`);
10920
+ assertDeveloperMessage(message);
10921
+ if (message["content"].some((block) => isSessionFormatJsonObject(block) && block["type"] === "tool-addition")) sessionFormatCount(data["headerSeq"], "developer/message headerSeq");
10922
+ else if (Object.hasOwn(data, "headerSeq")) throw new SessionFormatError("format v4 developer/message must omit headerSeq without tool additions");
10923
+ } else switch (event.type) {
10924
+ case "user/message":
10925
+ assertOrdinaryMessage(data);
10926
+ break;
10927
+ case "system/message":
10928
+ case "assistant/message":
10929
+ case "tool/result":
10930
+ assertOrdinaryMessage(data["message"]);
10931
+ break;
10932
+ case "agent/inbox/spliced":
10933
+ case "session/title-llm-request": {
10934
+ const messages = data[event.type === "agent/inbox/spliced" ? "inserted" : "messages"];
10935
+ if (Array.isArray(messages)) messages.forEach(assertOrdinaryMessage);
10936
+ break;
10937
+ }
10938
+ }
10939
+ if (event.type === "compaction/summary") {
10940
+ assertContent(data["summary"]);
10941
+ assertContent(data["rawOutput"]);
10942
+ }
10943
+ if ((event.type === "assistant/message" || event.type === "assistant/attempt") && Array.isArray(data["stream"])) for (const entry of data["stream"]) {
10944
+ if (!isSessionFormatJsonObject(entry) || entry["type"] !== "chunk") continue;
10945
+ const chunk = entry["chunk"];
10946
+ if (!isSessionFormatJsonObject(chunk)) continue;
10947
+ if (chunk["type"] === "block-end") assertToolChange(chunk["block"], false);
10948
+ if (chunk["type"] === "block-start" && (chunk["blockType"] === "tool-addition" || chunk["blockType"] === "tool-removal")) throw new SessionFormatError("format v4 tool-change blocks require developer role");
10949
+ }
10950
+ if (event.type === "request/header" && isSessionFormatJsonObject(data["header"])) {
10951
+ const tools = data["header"]["tools"];
10952
+ if (Array.isArray(tools)) for (const tool of tools) {
10953
+ if (!isSessionFormatJsonObject(tool) || !Object.hasOwn(tool, "deferLoading")) continue;
10954
+ if (tool["deferLoading"] !== true) throw new SessionFormatError("format v4 tool deferLoading must be true when present");
10955
+ }
10956
+ }
10957
+ }
10958
+ //#endregion
10959
+ //#region ../session-format-v3-to-v4/src/fork-result.ts
10960
+ /** Native V4 fork-result identity and not-started error validation. */
10961
+ /**
10962
+ * Validate fork-generated not-started results without changing their identity or text.
10963
+ * Append results carry their own sequence; replacement results retain an earlier identity.
10964
+ * @param row - logical event or physical event row.
10965
+ */
10966
+ function assertV4ForkResult(row) {
10967
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "tool/result") return;
10968
+ const data = row["data"];
10969
+ if (!isSessionFormatJsonObject(data)) return;
10970
+ const error = data["error"];
10971
+ const message = data["message"];
10972
+ if (!isSessionFormatJsonObject(error) || error["code"] !== "TOOL_NOT_STARTED" || !isSessionFormatJsonObject(message) || typeof message["id"] !== "string" || !message["id"].startsWith("forked-tool-result-")) return;
10973
+ const source = message["source"];
10974
+ const callId = isSessionFormatJsonObject(source) ? source["callId"] : void 0;
10975
+ const prefix = `forked-tool-result-${String(callId)}-`;
10976
+ const suffix = message["id"].slice(prefix.length);
10977
+ const operation = row["surfaceOp"];
10978
+ const replacement = isSessionFormatJsonObject(operation) && operation["op"] === "replace";
10979
+ const sequence = Number(suffix);
10980
+ const content = message["content"];
10981
+ const text = Array.isArray(content) && content.length === 1 ? content[0] : void 0;
10982
+ const sourceEventSeqs = row["sourceEventSeqs"];
10983
+ 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");
10984
+ }
10985
+ //#endregion
10986
+ //#region ../session-format-v3-to-v4/src/tool-role.ts
10987
+ /** First-class tool-role messages in the V4 representation. */
10988
+ /**
10989
+ * Validate the native V4 first-class tool-role message of one tool/result row.
10990
+ * @param event - canonical V4 event.
10991
+ * @throws {SessionFormatError} when the row is not an exact first-class tool result.
10992
+ */
10993
+ function assertV4ToolResultMessage(event) {
10994
+ if (event.type !== "tool/result") return;
10995
+ const subject = `format v4 ${event.type} at seq ${event.seq}`;
10996
+ const data = event.data;
10997
+ if (!isSessionFormatJsonObject(data)) throw new SessionFormatError(`${subject} data must be an object`);
10998
+ const message = data["message"];
10999
+ if (!isSessionFormatJsonObject(message)) throw new SessionFormatError(`${subject} message must be an object`);
11000
+ const id = message["id"];
11001
+ const role = message["role"];
11002
+ const toolCallId = message["toolCallId"];
11003
+ const source = message["source"];
11004
+ const isError = message["isError"];
11005
+ const content = message["content"];
11006
+ const sourceCallId = isSessionFormatJsonObject(source) ? source["callId"] : void 0;
11007
+ if (typeof id !== "string" || id.length === 0) throw new SessionFormatError(`${subject} requires a first-class message with a string id`);
11008
+ if (role !== "tool") throw new SessionFormatError(`${subject} requires a tool-role message`);
11009
+ if (typeof toolCallId !== "string" || toolCallId.length === 0 || sourceCallId !== toolCallId) throw new SessionFormatError(`${subject} requires toolCallId matching its tool source`);
11010
+ if (!isSessionFormatJsonObject(source) || source["kind"] !== "tool") throw new SessionFormatError(`${subject} requires a tool source`);
11011
+ if (!Array.isArray(content)) throw new SessionFormatError(`${subject} requires array content`);
11012
+ if (content.some((block) => isSessionFormatJsonObject(block) && block["type"] === "tool-result")) throw new SessionFormatError(`${subject} content must not contain a released tool-result wrapper`);
11013
+ if (isError !== void 0 && typeof isError !== "boolean") throw new SessionFormatError(`${subject} isError must be boolean when present`);
11014
+ if (data["error"] !== void 0 && isError !== true) throw new SessionFormatError(`${subject} carries error metadata for a non-error tool result`);
11015
+ }
11016
+ //#endregion
11017
+ //#region ../session-format-v3-to-v4/src/relationships.ts
11018
+ /** Mandatory native V4 relationships; incomplete tails retain their open transactions. */
11019
+ const SURFACE_TYPES = new Set([
11020
+ "system/message",
11021
+ "user/message",
11022
+ "developer/message",
11023
+ "assistant/message",
11024
+ "tool/result"
11025
+ ]);
11026
+ const STEP_EVENT_TYPES = new Set([
11027
+ "system/message",
11028
+ "developer/message",
11029
+ "assistant/attempt"
11030
+ ]);
11031
+ const RELATIONSHIP_TYPES = new Set([
11032
+ ...SURFACE_TYPES,
11033
+ ...STEP_EVENT_TYPES,
11034
+ "turn/start",
11035
+ "turn/end",
11036
+ "step/start",
11037
+ "step/end",
11038
+ "tool/call",
11039
+ "request/header",
11040
+ "request/context",
11041
+ "tool/ptc-dispatch-start",
11042
+ "tool/ptc-dispatch",
11043
+ "llm/retry",
11044
+ "llm/retry-started",
11045
+ "session/title",
11046
+ "session/title-llm-request",
11047
+ "command/run",
11048
+ "command/done",
11049
+ "compaction/start",
11050
+ "compaction/summary",
11051
+ "compaction/end",
11052
+ "compaction/prune",
11053
+ "session/end-seed"
11054
+ ]);
11055
+ function record(value, subject) {
11056
+ if (!isSessionFormatJsonObject(value)) throw new SessionFormatError(`${subject} requires an object`);
11057
+ return value;
11058
+ }
11059
+ function text(value, subject) {
11060
+ if (typeof value !== "string" || value.length === 0) throw new SessionFormatError(`${subject} requires a nonempty string`);
11061
+ return value;
11062
+ }
11063
+ function array(value, subject) {
11064
+ if (!Array.isArray(value)) throw new SessionFormatError(`${subject} requires an array`);
11065
+ return value;
11066
+ }
11067
+ function earlier(value, seq, subject) {
11068
+ const coordinate = sessionFormatCount(value, subject);
11069
+ if (coordinate >= seq) throw new SessionFormatError(`${subject} must name an earlier event`);
11070
+ return coordinate;
11071
+ }
11072
+ /** One native artifact's lifecycle state; no state survives between reads. */
11073
+ var Relationships = class {
11074
+ artifact;
11075
+ knownEventTypes;
11076
+ turn = null;
11077
+ step = null;
11078
+ nextTurn = 1;
11079
+ nextStep = 1;
11080
+ provider;
11081
+ surface = [];
11082
+ protectedHead;
11083
+ compaction;
11084
+ tools = /* @__PURE__ */ new Map();
11085
+ dispatches = /* @__PURE__ */ new Map();
11086
+ retries = [];
11087
+ startedRetries = /* @__PURE__ */ new Set();
11088
+ commands = /* @__PURE__ */ new Set();
11089
+ orphanCompactions = /* @__PURE__ */ new Set();
11090
+ constructor(artifact, knownEventTypes) {
11091
+ this.artifact = artifact;
11092
+ this.knownEventTypes = knownEventTypes;
11093
+ let start;
11094
+ for (const event of artifact.events) {
11095
+ if (!knownEventTypes.has(event.type)) continue;
11096
+ if (event.type === "compaction/start") start = event.seq;
11097
+ if (event.type === "compaction/end") start = void 0;
11098
+ if (event.type === "session/end-seed") {
11099
+ if (start !== void 0) this.orphanCompactions.add(start);
11100
+ start = void 0;
11101
+ }
11102
+ }
11103
+ }
11104
+ requireTurn(type) {
11105
+ if (this.turn === null) throw new SessionFormatError(`${type} is outside an open turn`);
11106
+ }
11107
+ requireStep(event, data) {
11108
+ 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`);
11109
+ }
11110
+ closeTools(type) {
11111
+ if (this.tools.size !== 0) throw new SessionFormatError(`${type} leaves unresolved tool call ${String(this.tools.keys().next().value)}`);
11112
+ this.tools.clear();
11113
+ }
11114
+ developer(event, data) {
11115
+ if (data["headerSeq"] === void 0) return;
11116
+ const headerSeq = earlier(data["headerSeq"], event.seq, "developer/message headerSeq");
11117
+ const headerEvent = this.artifact.events[headerSeq];
11118
+ if (headerEvent?.type !== "request/header") throw new SessionFormatError("developer/message headerSeq must reference an earlier request/header");
11119
+ if (!this.knownEventTypes.has(headerEvent.type)) throw new SessionFormatError("developer/message headerSeq must reference a known request/header");
11120
+ const tools = record(record(headerEvent.data, "request/header data")["header"], "request header")["tools"];
11121
+ const content = array(record(data["message"], "developer message")["content"], "developer content");
11122
+ for (const value of content) {
11123
+ if (!isSessionFormatJsonObject(value) || value["type"] !== "tool-addition") continue;
11124
+ const toolName = value["toolName"];
11125
+ const definitions = Array.isArray(tools) ? tools.filter((tool) => isSessionFormatJsonObject(tool) && tool["name"] === toolName) : [];
11126
+ if (definitions.length !== 1) throw new SessionFormatError(`developer/message tool-addition "${toolName}" must name exactly one tool in headerSeq ${headerSeq}`);
11127
+ const definition = definitions[0];
11128
+ if (typeof definition["description"] !== "string" || !isSessionFormatJsonObject(definition["parameters"])) throw new SessionFormatError(`developer/message tool-addition "${toolName}" requires a complete tool definition in headerSeq ${headerSeq}`);
11129
+ }
11130
+ }
11131
+ foldSurface(event) {
11132
+ if (!SURFACE_TYPES.has(event.type)) return;
11133
+ if (event.type === "system/message" && this.surface.length > 0 && this.protectedHead === void 0) throw new SessionFormatError("system/message requires a protected first surface head");
11134
+ if (event["surfaceOp"] === "append") {
11135
+ if (event.type === "system/message" && this.surface.length === 0) this.protectedHead = event.seq;
11136
+ this.surface.push(event.seq);
11137
+ return;
11138
+ }
11139
+ const operation = record(event["surfaceOp"], `${event.type} surfaceOp`);
11140
+ const first = this.surface.indexOf(earlier(operation["startSeq"], event.seq, "replacement start"));
11141
+ const last = this.surface.indexOf(earlier(operation["endSeq"], event.seq, "replacement end"));
11142
+ if (first < 0 || last < first) throw new SessionFormatError(`${event.type} replacement range is not on the current surface`);
11143
+ const removed = this.surface.slice(first, last + 1);
11144
+ const sources = array(event["sourceEventSeqs"], "replacement sourceEventSeqs");
11145
+ if (removed.some((seq) => !sources.includes(seq))) throw new SessionFormatError("replacement sourceEventSeqs omit a shadowed surface node");
11146
+ if (this.protectedHead !== void 0 && removed.includes(this.protectedHead)) {
11147
+ if (event.type !== "system/message" || removed.length !== 1) throw new SessionFormatError("surface replacement cannot shadow the protected system head");
11148
+ this.protectedHead = event.seq;
11149
+ }
11150
+ this.surface.splice(first, removed.length, event.seq);
11151
+ }
11152
+ tool(event, data) {
11153
+ if (event.type === "tool/result" && event["surfaceOp"] !== "append") {
11154
+ this.requireTurn(event.type);
11155
+ return;
11156
+ }
11157
+ this.requireStep(event, data);
11158
+ if (event.type === "assistant/message") {
11159
+ const message = record(data["message"], "assistant message");
11160
+ for (const value of array(message["content"], "assistant content")) {
11161
+ const block = record(value, "assistant content block");
11162
+ if (block["type"] !== "tool-call") continue;
11163
+ const id = text(block["id"], "tool call id");
11164
+ if (this.tools.has(id)) throw new SessionFormatError(`assistant/message repeats advertised tool call ${id}`);
11165
+ this.tools.set(id, {
11166
+ name: block["name"],
11167
+ arguments: block["arguments"],
11168
+ started: false
11169
+ });
11170
+ }
11171
+ return;
11172
+ }
11173
+ const message = event.type === "tool/result" ? record(data["message"], "tool result message") : void 0;
11174
+ const id = text(message === void 0 ? data["callId"] : message["toolCallId"], "tool call id");
11175
+ const pending = this.tools.get(id);
11176
+ if (pending === void 0) throw new SessionFormatError(`${event.type} ${id} has no advertised tool lifecycle`);
11177
+ if (message === void 0) {
11178
+ if (pending.started || pending.name !== data["name"] || pending.arguments !== data["arguments"]) throw new SessionFormatError(`tool/call ${id} does not match one advertised tool call`);
11179
+ pending.started = true;
11180
+ } else {
11181
+ if (!pending.started && !notStartedRepair(event, data, message, id)) throw new SessionFormatError(`tool/result ${id} is not the exact TOOL_NOT_STARTED repair`);
11182
+ this.tools.delete(id);
11183
+ }
11184
+ }
11185
+ dispatch(event, data) {
11186
+ this.requireTurn(event.type);
11187
+ const id = text(data["subCallId"], "PTC subCallId");
11188
+ const root = text(data["rootCallId"], "PTC rootCallId");
11189
+ const parent = text(data["parentCallId"], "PTC parentCallId");
11190
+ const existing = this.dispatches.get(id);
11191
+ if (existing !== void 0 && existing.data["rootCallId"] !== root) throw new SessionFormatError("PTC dispatch changes its rootCallId");
11192
+ if (parent !== root && this.dispatches.get(parent)?.data["rootCallId"] !== root) throw new SessionFormatError("PTC parentCallId does not belong to rootCallId");
11193
+ if (event.type === "tool/ptc-dispatch-start") {
11194
+ if (existing !== void 0) throw new SessionFormatError("PTC dispatch repeats subCallId");
11195
+ this.dispatches.set(id, {
11196
+ data,
11197
+ settled: false
11198
+ });
11199
+ return;
11200
+ }
11201
+ if (existing === void 0 || existing.settled) throw new SessionFormatError("PTC dispatch has no unique start");
11202
+ if ([
11203
+ "rootCallId",
11204
+ "parentCallId",
11205
+ "name",
11206
+ "arguments"
11207
+ ].some((key) => !(0, node_util.isDeepStrictEqual)(existing.data[key], data[key]))) throw new SessionFormatError("PTC dispatch does not match its start");
11208
+ existing.settled = true;
11209
+ }
11210
+ retry(event, data) {
11211
+ const id = text(data["retryId"], "retryId");
11212
+ const attempt = sessionFormatCount(data["retry"], "retry");
11213
+ if (event.type === "llm/retry-started") {
11214
+ const scheduled = this.retries.find((item) => item["retryId"] === id && item["retry"] === attempt);
11215
+ if (scheduled === void 0) throw new SessionFormatError("llm/retry-started pairs no prior scheduled attempt");
11216
+ if (scheduled["turn"] !== data["turn"] || scheduled["step"] !== data["step"]) throw new SessionFormatError("llm/retry-started changes scheduled coordinates");
11217
+ const key = JSON.stringify([id, attempt]);
11218
+ if (this.startedRetries.has(key)) throw new SessionFormatError("llm/retry-started repeats one scheduled attempt");
11219
+ this.startedRetries.add(key);
11220
+ return;
11221
+ }
11222
+ 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");
11223
+ if (data["provider"] !== this.provider) throw new SessionFormatError("llm/retry provider does not match the open request/header");
11224
+ const prior = this.retries.findLast((item) => [
11225
+ "turn",
11226
+ "step",
11227
+ "provider",
11228
+ "policyKey"
11229
+ ].every((key) => item[key] === data[key]));
11230
+ if (attempt !== (prior === void 0 ? 1 : sessionFormatCount(prior["retry"], "prior retry") + 1)) throw new SessionFormatError("llm/retry skips its policy attempt sequence");
11231
+ 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");
11232
+ this.retries.push(data);
11233
+ }
11234
+ compact(event, data) {
11235
+ if (event.type === "compaction/prune" || event.type === "compaction/summary") this.span(event, data);
11236
+ if (event.type === "compaction/prune") return;
11237
+ if (event.type === "compaction/start") {
11238
+ if (this.compaction !== void 0) throw new SessionFormatError("compaction/start overlaps an open compaction");
11239
+ const owner = data["turn"] === null ? null : sessionFormatCount(data["turn"], "compaction turn");
11240
+ if (owner !== this.turn) throw new SessionFormatError("compaction/start does not match the open turn");
11241
+ this.compaction = {
11242
+ id: text(data["compactionId"], "compactionId"),
11243
+ command: data["sourceCommandId"],
11244
+ turn: owner,
11245
+ seq: event.seq,
11246
+ summarized: false
11247
+ };
11248
+ return;
11249
+ }
11250
+ const current = this.compactionOwner(data, event.type);
11251
+ if (current.turn !== this.turn) throw new SessionFormatError(`${event.type} does not match the open turn`);
11252
+ if (event.type === "compaction/summary") {
11253
+ if (current.summarized) throw new SessionFormatError("compaction/summary repeats");
11254
+ current.summarized = true;
11255
+ } else {
11256
+ if (data["turn"] !== current.turn) throw new SessionFormatError("compaction/end changes its owner turn");
11257
+ if (data["error"] === void 0 && !current.summarized) throw new SessionFormatError("successful compaction/end requires one summary");
11258
+ this.compaction = void 0;
11259
+ }
11260
+ }
11261
+ compactionOwner(data, subject) {
11262
+ if (this.compaction === void 0 || this.compaction.id !== data["compactionId"] || this.compaction.command !== data["sourceCommandId"]) throw new SessionFormatError(`${subject} has no matching compaction/start`);
11263
+ return this.compaction;
11264
+ }
11265
+ span(event, data) {
11266
+ const range = record(data["shadowedRange"], "compaction shadowedRange");
11267
+ const start = this.surface.indexOf(earlier(range["start"], event.seq, "compaction range start"));
11268
+ const end = this.surface.indexOf(earlier(range["end"], event.seq, "compaction range end"));
11269
+ const seqs = array(data["shadowedSeqs"], "compaction shadowedSeqs");
11270
+ if (start < 0 || end < start || !(0, node_util.isDeepStrictEqual)(this.surface.slice(start, end + 1), seqs)) throw new SessionFormatError(`${event.type} shadowedSeqs do not name an exact current surface span`);
11271
+ if (this.protectedHead !== void 0 && seqs.includes(this.protectedHead)) throw new SessionFormatError("compaction cannot shadow the protected system head");
11272
+ }
11273
+ accept(event) {
11274
+ if (!this.knownEventTypes.has(event.type) || !RELATIONSHIP_TYPES.has(event.type)) return;
11275
+ const data = record(event.data, event.type);
11276
+ if (STEP_EVENT_TYPES.has(event.type)) this.requireStep(event, data);
11277
+ if (event.type.startsWith("turn/") && this.compaction !== void 0 && !this.orphanCompactions.has(this.compaction.seq)) throw new SessionFormatError(`${event.type} crosses an open compaction`);
11278
+ this.foldSurface(event);
11279
+ switch (event.type) {
11280
+ case "turn/start":
11281
+ if (this.turn !== null || data["turn"] !== this.nextTurn) throw new SessionFormatError("turn/start does not open the expected turn");
11282
+ this.turn = this.nextTurn;
11283
+ this.nextStep = 1;
11284
+ this.tools.clear();
11285
+ break;
11286
+ case "turn/end":
11287
+ 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");
11288
+ this.closeTools(event.type);
11289
+ this.turn = null;
11290
+ this.nextTurn += 1;
11291
+ break;
11292
+ case "step/start":
11293
+ 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");
11294
+ this.step = this.nextStep;
11295
+ break;
11296
+ case "step/end":
11297
+ this.requireStep(event, data);
11298
+ this.closeTools(event.type);
11299
+ this.step = null;
11300
+ this.nextStep += 1;
11301
+ break;
11302
+ case "assistant/message":
11303
+ case "tool/call":
11304
+ case "tool/result":
11305
+ this.tool(event, data);
11306
+ break;
11307
+ case "developer/message":
11308
+ this.developer(event, data);
11309
+ break;
11310
+ case "request/header":
11311
+ this.requireTurn(event.type);
11312
+ this.provider = record(record(data["header"], "request header")["config"], "request config")["provider"];
11313
+ break;
11314
+ case "request/context":
11315
+ this.requireTurn(event.type);
11316
+ break;
11317
+ case "tool/ptc-dispatch-start":
11318
+ case "tool/ptc-dispatch":
11319
+ this.dispatch(event, data);
11320
+ break;
11321
+ case "llm/retry":
11322
+ case "llm/retry-started":
11323
+ this.retry(event, data);
11324
+ break;
11325
+ case "session/title":
11326
+ case "session/title-llm-request":
11327
+ titleSources(this.artifact.events, event, data, this.knownEventTypes);
11328
+ break;
11329
+ case "command/run": {
11330
+ const id = text(data["commandId"], "commandId");
11331
+ if (this.commands.has(id)) throw new SessionFormatError(`command/run repeats commandId ${id}`);
11332
+ this.commands.add(id);
11333
+ break;
11334
+ }
11335
+ case "command/done":
11336
+ if (!this.commands.has(text(data["commandId"], "commandId"))) throw new SessionFormatError("command/done has no prior command/run");
11337
+ if (data["sourceEventSeq"] !== void 0) {
11338
+ const source = this.artifact.events[earlier(data["sourceEventSeq"], event.seq, "command sourceEventSeq")];
11339
+ if (data["kind"] !== "success" || source?.type === "command/run" || source?.type === "command/done") throw new SessionFormatError("command/done has invalid sourceEventSeq");
11340
+ }
11341
+ break;
11342
+ case "compaction/start":
11343
+ case "compaction/summary":
11344
+ case "compaction/end":
11345
+ case "compaction/prune":
11346
+ this.compact(event, data);
11347
+ break;
11348
+ case "user/message": {
11349
+ const source = record(data["source"], "user message source");
11350
+ if (event["surfaceOp"] !== "append" && source["kind"] === "compact-checkpoint") this.compactionOwner(source, "compaction checkpoint");
11351
+ break;
11352
+ }
11353
+ case "session/end-seed":
11354
+ this.compaction = void 0;
11355
+ break;
11356
+ }
11357
+ }
11358
+ };
11359
+ function notStartedRepair(event, data, message, callId) {
11360
+ const error = record(data["error"], "not-started error");
11361
+ if (error["name"] !== "ToolNotStartedError" || error["code"] !== "TOOL_NOT_STARTED" || message["isError"] !== true || event["sourceEventSeqs"] !== void 0) return false;
11362
+ const id = text(message["id"], "not-started message id");
11363
+ if (id.startsWith(`forked-tool-result-${callId}-`)) return true;
11364
+ const prefix = `interrupted-tool-result-${callId}-`;
11365
+ const suffix = id.slice(prefix.length);
11366
+ const content = array(message["content"], "not-started content");
11367
+ const block = content[0];
11368
+ 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.";
11369
+ }
11370
+ function titleSources(events, event, data, knownEventTypes) {
11371
+ const references = array(data["messageSeqs"], "title messageSeqs");
11372
+ 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");
11373
+ const seen = /* @__PURE__ */ new Set();
11374
+ for (const value of references) {
11375
+ const seq = earlier(value, event.seq, "title messageSeqs");
11376
+ const source = events[seq];
11377
+ 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`);
11378
+ seen.add(seq);
11379
+ }
11380
+ if (event.type !== "session/title-llm-request") return;
11381
+ const messages = array(data["messages"], "title messages");
11382
+ const message = record(messages[0], "title message");
11383
+ const content = array(message["content"], "title content");
11384
+ const block = content[0];
11385
+ 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");
11386
+ }
11387
+ /**
11388
+ * Validate native V4 lifecycle and ownership facts without rewriting any event.
11389
+ * @param artifact - artifact whose V4 envelopes and messages have been admitted.
11390
+ * @param knownEventTypes - installed event types whose payloads this reader interprets.
11391
+ */
11392
+ function assertV4LifecycleRelationships(artifact, knownEventTypes) {
11393
+ const state = new Relationships(artifact, knownEventTypes);
11394
+ for (const event of artifact.events) state.accept(event);
11395
+ }
11396
+ //#endregion
11397
+ //#region ../session-format-v3-to-v4/src/facts.ts
11398
+ /** Historical child identity reduced to the fields required by a parent's catalog. */
11399
+ /**
11400
+ * Validate the historical fields used for catalog membership without interpreting extensions.
11401
+ * @param value - catalog payload or prepared historical discovery fact.
11402
+ * @param subject - event type or child identity used in rejection diagnostics.
11403
+ * @returns validated catalog payload.
11404
+ */
11405
+ function catalogFact(value, subject = "subagent/catalog") {
11406
+ 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`);
11407
+ sessionFormatCount(value["childCreatedAt"], "catalog child creation time");
11408
+ return value;
11409
+ }
11410
+ //#endregion
11411
+ //#region ../session-format-v3-to-v4/src/validation.ts
11412
+ /** Native V4 metadata and generation-owned relationship validation. */
11413
+ /**
11414
+ * Validate the exact native V4 logical header.
11415
+ * @param header - decoded or otherwise untrusted V4 Session header candidate.
11416
+ */
11417
+ function assertReleasedV4Header(header) {
11418
+ if (!isSessionFormatJsonObject(header) || header["version"] !== 4) throw new SessionFormatError("expected format v4 header");
11419
+ const required = [
11420
+ "version",
11421
+ "id",
11422
+ "createdAt",
11423
+ "isSeeded",
11424
+ "delegationDepth"
11425
+ ];
11426
+ const allowed = new Set([
11427
+ ...required,
11428
+ "cwd",
11429
+ "parentSession",
11430
+ "origin",
11431
+ "agentPreset"
11432
+ ]);
11433
+ const missing = required.find((key) => !Object.hasOwn(header, key));
11434
+ const unexpected = Object.keys(header).find((key) => !allowed.has(key));
11435
+ if (missing !== void 0) throw new SessionFormatError(`format v4 header lacks required field ${missing}`);
11436
+ if (unexpected !== void 0) throw new SessionFormatError(`format v4 header has unexpected field ${unexpected}`);
11437
+ if (typeof header.id !== "string") throw new SessionFormatError("format v4 header id must be a string");
11438
+ sessionFormatCount(header.createdAt, "format v4 header createdAt");
11439
+ sessionFormatCount(header.delegationDepth, "format v4 header delegationDepth");
11440
+ if (typeof header.isSeeded !== "boolean") throw new SessionFormatError("format v4 header isSeeded must be boolean");
11441
+ if (header.cwd !== void 0 && (typeof header.cwd !== "string" || !(0, node_path.isAbsolute)(header.cwd))) throw new SessionFormatError("format v4 header cwd must be absolute");
11442
+ 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`);
11443
+ if (header.origin !== void 0 && header.origin !== "subagent") throw new SessionFormatError("format v4 header origin must be \"subagent\"");
11444
+ }
11445
+ /**
11446
+ * Validate V4 inheritance, vocabulary, native message admission, and
11447
+ * lifecycle, compaction, tool, retry, title, command, catalog, and delivery ownership.
11448
+ * Installed Session restoration owns common event envelopes and message acceptance.
11449
+ * @param artifact - complete detached V4 artifact.
11450
+ * @param knownEventTypes - event types understood by the installed Session package.
11451
+ * @returns the same validated artifact and event objects.
11452
+ */
11453
+ function restoreReleasedV4Artifact(artifact, knownEventTypes) {
11454
+ assertReleasedV4Header(artifact.header);
11455
+ const cut = sessionFormatCount(artifact.inheritedEventCount, "format v4 inherited event count");
11456
+ if (cut > artifact.events.length) throw new SessionFormatError("format v4 inherited event count exceeds its events");
11457
+ if (!artifact.header.isSeeded && cut !== 0) throw new SessionFormatError("unseeded format v4 Session has inherited events");
11458
+ let lastInheritedMarker;
11459
+ for (const [index, event] of artifact.events.entries()) {
11460
+ if (!knownEventTypes.has(event.type) && event["ignorable"] !== true) throw new SessionFormatUnsupportedMigrationError(`format v4 contains unknown event type ${JSON.stringify(event.type)} at seq ${index}`);
11461
+ if (event.seq !== index) throw new SessionFormatError(`format v4 event ${index} is not dense`);
11462
+ if (!knownEventTypes.has(event.type)) continue;
11463
+ assertV4RetiredSyntax(event);
11464
+ assertV4SystemMessageFields(event);
11465
+ assertV4ToolResultMessage(event);
11466
+ assertV4ForkResult(event);
11467
+ if (event.type === "session/end-seed" && isSessionFormatJsonObject(event.data) && event.data["inherited"] === true) lastInheritedMarker = index;
11468
+ }
11469
+ if (artifact.header.isSeeded && lastInheritedMarker !== cut) throw new SessionFormatError("format v4 seeded header disagrees with its last inherited end-seed marker");
11470
+ if (!artifact.header.isSeeded && lastInheritedMarker !== void 0) throw new SessionFormatError("format v4 unseeded Session contains an inherited end-seed marker");
11471
+ assertReleasedV4Relationships(artifact, knownEventTypes);
11472
+ return artifact;
11473
+ }
11474
+ /**
11475
+ * Validate delivery generation and active-generation coordinates before evaluating ownership.
11476
+ * @param event - decoded event whose delivery payload may be inspected.
11477
+ * @param currentVersion - generation whose watermark coordinates are active.
11478
+ * @returns the active delivery's nonempty Session id, or undefined for other events and generations.
11479
+ */
11480
+ function validateDeliveryAccepted(event, currentVersion) {
11481
+ if (event.type !== "session-log-deepseek/delivery-accepted") return void 0;
11482
+ const data = event.data;
11483
+ if (!isSessionFormatJsonObject(data)) throw new SessionFormatError("delivery-accepted data must be an object");
11484
+ if (sessionFormatCount(data["sessionFormatVersion"] === void 0 ? 0 : data["sessionFormatVersion"], "delivery sessionFormatVersion") !== currentVersion) return void 0;
11485
+ if (sessionFormatCount(data["throughSeq"], "delivery throughSeq") >= event.seq) throw new SessionFormatError("delivery throughSeq must precede its marker");
11486
+ const id = data["sessionId"];
11487
+ if (typeof id !== "string" || id.length === 0) throw new SessionFormatError("delivery requires a nonempty Session id");
11488
+ return id;
11489
+ }
11490
+ /**
11491
+ * Validate native developer fields, message sources, lifecycle, catalog, and delivery
11492
+ * relationships without changing event vocabulary or tail recovery.
11493
+ * The owning admission stage rejects unknown required events;
11494
+ * unknown ignorable records retain their uninterpreted payloads.
11495
+ * @param artifact - decoded artifact with its final inherited cut.
11496
+ * @param knownEventTypes - installed event types whose payloads this reader interprets.
11497
+ */
11498
+ function assertReleasedV4Relationships(artifact, knownEventTypes) {
11499
+ const ids = /* @__PURE__ */ new Set();
11500
+ for (const event of artifact.events) {
11501
+ if (!knownEventTypes.has(event.type)) continue;
11502
+ assertV4DeveloperData(event);
11503
+ assertV4MessageSources(event);
11504
+ const deliveryId = validateDeliveryAccepted(event, 4);
11505
+ 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");
11506
+ if (event.type === "subagent/catalog" && event.seq >= artifact.inheritedEventCount) {
11507
+ const id = catalogFact(event.data)["childId"];
11508
+ if (ids.has(id)) throw new SessionFormatError(`duplicate catalog child ${id}`);
11509
+ ids.add(id);
11510
+ }
11511
+ }
11512
+ assertV4LifecycleRelationships(artifact, knownEventTypes);
11513
+ }
11514
+ //#endregion
11515
+ //#region ../session-format-v3-to-v4/src/codec.ts
11516
+ /** V4 framing with native tool-role admission and released physical rows. */
11517
+ function physicalV2(value) {
11518
+ if (!isSessionFormatJsonObject(value) || value["version"] !== 4) throw new SessionFormatError("expected format v4 physical header");
11519
+ return {
11520
+ ...value,
11521
+ version: 2
11522
+ };
11523
+ }
11524
+ /**
11525
+ * V4 physical encoder and decoder retain the released row framing while
11526
+ * validating the native tool-role message directly.
11527
+ */
11528
+ const releasedV4SessionFormatCodec = Object.freeze({
11529
+ version: 4,
11530
+ decodeHeader(value) {
11531
+ return {
11532
+ ...releasedV2SessionFormatCodec.decodeHeader(physicalV2(value)),
11533
+ version: 4
11534
+ };
11535
+ },
11536
+ createDecoder(value, recovery) {
11537
+ const decoder = releasedV2SessionFormatCodec.createDecoder(physicalV2(value), recovery);
11538
+ return {
11539
+ ...decoder,
11540
+ header: {
11541
+ ...decoder.header,
11542
+ version: 4
11543
+ },
11544
+ decodeRow(row, context) {
11545
+ assertV4RowAdmission(row);
11546
+ decoder.decodeRow(row, {
11547
+ emitRun: context.emitRun.bind(context),
11548
+ emitEvent: context.emitEvent.bind(context)
11549
+ });
11550
+ }
11551
+ };
11552
+ },
11553
+ encodeHeader(header, inheritedEventCount) {
11554
+ assertReleasedV4Header(header);
11555
+ return {
11556
+ ...releasedV2SessionFormatCodec.encodeHeader({
11557
+ ...header,
11558
+ version: 2
11559
+ }, inheritedEventCount),
11560
+ version: 4
11561
+ };
11562
+ },
11563
+ encodeEvent(event) {
11564
+ if (event.type === "developer/message" && event["ignorable"] === true) {
11565
+ assertV4DeveloperData(event);
11566
+ assertV4RetiredSyntax(event);
11567
+ }
11568
+ assertV4RowAdmission(event);
11569
+ return releasedV2SessionFormatCodec.encodeEvent(event);
11570
+ }
11571
+ });
11572
+ /**
11573
+ * Apply native V4 admission before a scanner discards a recoverable suffix.
11574
+ * Ignorable developer payloads require reader vocabulary; physical decoding defers them.
11575
+ * @param row - parsed physical row before framing and source-event range decoding.
11576
+ * @param knownEventTypes - installed event types, supplied by native readers before tail recovery.
11577
+ */
11578
+ function assertV4RowAdmission(row, knownEventTypes) {
11579
+ if (isSessionFormatJsonObject(row)) {
11580
+ if (row["type"] === "developer/message" && row["ignorable"] === true && knownEventTypes?.has("developer/message") !== true) return;
11581
+ assertV4DeveloperData(row);
11582
+ }
11583
+ assertV4SourceRowAdmission(row);
11584
+ assertV4RetiredSyntax(row);
11585
+ assertV4SystemMessageFields(row);
11586
+ if (!isSessionFormatJsonObject(row) || row["type"] !== "tool/result") return;
11587
+ const event = row;
11588
+ assertV4ToolResultMessage(event);
11589
+ assertV4ForkResult(event);
11590
+ }
11591
+ //#endregion
11592
+ //#region ../session-format-v3-to-v4/src/extension-identities.ts
11593
+ /** First-party event names understood by the released V3 reader, independent of the installed writer. */
11594
+ const RELEASED_V3_EVENT_TYPES = new Set([
11595
+ "agent-preset/selected",
11596
+ "agent/inbox/spliced",
11597
+ "approval/asked",
11598
+ "approval/decided",
11599
+ "approval/policy",
11600
+ "assistant/attempt",
11601
+ "assistant/message",
11602
+ "command/done",
11603
+ "command/run",
11604
+ "compaction/end",
11605
+ "compaction/prune",
11606
+ "compaction/start",
11607
+ "compaction/summary",
11608
+ "deliverables/presented",
11609
+ "feedback/message-delete",
11610
+ "feedback/message-put",
11611
+ "feedback/record",
11612
+ "goal/change",
11613
+ "hook/invoked",
11614
+ "hook/result",
11615
+ "image/offload",
11616
+ "llm/retry",
11617
+ "llm/retry-started",
11618
+ "model/selection",
11619
+ "permission/preset",
11620
+ "plan/mode",
11621
+ "request/context",
11622
+ "request/header",
11623
+ "sandbox/mode",
11624
+ "schedule/change",
11625
+ "session-log-deepseek/delivery-accepted",
11626
+ "session/end-seed",
11627
+ "session/title",
11628
+ "session/title-llm-request",
11629
+ "step/end",
11630
+ "step/start",
11631
+ "subagent/catalog",
11632
+ "subagent/descriptor",
11633
+ "subagent/model-selection-policy",
11634
+ "system/message",
11635
+ "team/member",
11636
+ "team/message/delivered",
11637
+ "team/message/queued",
11638
+ "team/task",
11639
+ "todo/write",
11640
+ "tool-workflow/agent-end",
11641
+ "tool-workflow/agent-start",
11642
+ "tool-workflow/run-end",
11643
+ "tool-workflow/run-start",
11644
+ "tool/call",
11645
+ "tool/ptc-dispatch",
11646
+ "tool/ptc-dispatch-start",
11647
+ "tool/result",
11648
+ "turn/end",
11649
+ "turn/start",
11650
+ "user/message",
11651
+ "web/deepseek-search-llm-request",
11652
+ "workspace/changes"
11653
+ ]);
11654
+ //#endregion
11655
+ //#region ../session-format-v3-to-v4/src/migration.ts
11656
+ /** Append historical child facts after converting V3 source events to V4. */
11657
+ /** Header-only migration declaration; body restoration requires explicit child evidence. */
11658
+ const sessionFormatV3ToV4 = defineSessionFormatMigration({
11659
+ name: "@deepseek-ai/dsh-session-format-v3-to-v4",
11660
+ fromVersion: 3,
11661
+ toVersion: 4,
11662
+ migrateHeader(header) {
11663
+ assertReleasedV3Header(header);
11664
+ return {
11665
+ ...header,
11666
+ version: 4
11667
+ };
11668
+ },
11669
+ createStage() {
11670
+ throw new SessionFormatUnsupportedMigrationError("V3 catalog migration requires explicit historical child facts, including an empty array for a parent without children");
11671
+ },
11672
+ validateTargetHeader: assertReleasedV4Header
11673
+ });
10557
11674
  /** Physical codec dispatch and complete adjacent chain, independent of mounted plugins. */
10558
11675
  const sessionFormatCatalog = createSessionFormatCatalog({
10559
- currentVersion: 3,
11676
+ currentVersion: 4,
10560
11677
  codecs: [
10561
11678
  releasedV0SessionFormatCodec,
10562
11679
  releasedV1SessionFormatCodec,
10563
11680
  releasedV2SessionFormatCodec,
10564
- releasedV3SessionFormatCodec
11681
+ releasedV3SessionFormatCodec,
11682
+ releasedV4SessionFormatCodec
10565
11683
  ],
10566
- currentEncoder: releasedV3SessionFormatCodec,
11684
+ currentEncoder: releasedV4SessionFormatCodec,
10567
11685
  migrations: [
10568
11686
  sessionFormatV0ToV1,
10569
11687
  sessionFormatV1ToV2,
10570
- sessionFormatV2ToV3
11688
+ sessionFormatV2ToV3,
11689
+ sessionFormatV3ToV4
10571
11690
  ],
10572
11691
  restoreCurrent(artifact) {
10573
- const restored = restoreReleasedV3Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
11692
+ const restored = restoreReleasedV4Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
10574
11693
  validateInstalledCurrentSessionArtifact(restored);
10575
11694
  return restored;
10576
11695
  },
10577
11696
  restoreTransformedCurrent(artifact) {
10578
- return restoreReleasedV3Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
11697
+ return restoreReleasedV4Artifact(artifact, KNOWN_SESSION_EVENT_TYPES);
10579
11698
  },
10580
11699
  restoreCurrentHeader(header) {
10581
- assertReleasedV3Header(header);
11700
+ assertReleasedV4Header(header);
10582
11701
  validateInstalledCurrentSessionHeader(header);
10583
11702
  return header;
10584
11703
  }
10585
11704
  });
11705
+ createSessionFormatCatalog({
11706
+ currentVersion: 3,
11707
+ codecs: [
11708
+ releasedV0SessionFormatCodec,
11709
+ releasedV1SessionFormatCodec,
11710
+ releasedV2SessionFormatCodec,
11711
+ releasedV3SessionFormatCodec
11712
+ ],
11713
+ currentEncoder: releasedV3SessionFormatCodec,
11714
+ migrations: [
11715
+ sessionFormatV0ToV1,
11716
+ sessionFormatV1ToV2,
11717
+ sessionFormatV2ToV3
11718
+ ],
11719
+ restoreCurrent: (artifact) => restoreReleasedV3Artifact(artifact, RELEASED_V3_EVENT_TYPES),
11720
+ restoreTransformedCurrent: (artifact) => restoreReleasedV3Artifact(artifact, RELEASED_V3_EVENT_TYPES),
11721
+ restoreCurrentHeader(header) {
11722
+ assertReleasedV3Header(header);
11723
+ return header;
11724
+ }
11725
+ });
10586
11726
  //#endregion
10587
11727
  //#region lib/types/format.js
10588
11728
  /**
@@ -10648,7 +11788,7 @@ function assertNoRetiredHeaderFields(value) {
10648
11788
  function fromHeaderLine(line) {
10649
11789
  return {
10650
11790
  meta: {
10651
- version: 3,
11791
+ version: 4,
10652
11792
  id: line.id,
10653
11793
  createdAt: line.createdAt,
10654
11794
  ...line.cwd !== void 0 ? { cwd: line.cwd } : {},
@@ -10674,7 +11814,7 @@ function isHeaderLine(value) {
10674
11814
  */
10675
11815
  function refuseForeignFormatVersion(parsed) {
10676
11816
  const { version, id } = parsed;
10677
- if (typeof version !== "number" || version === 3) return;
11817
+ if (typeof version !== "number" || version === 4) return;
10678
11818
  throw new SessionFormatUnsupportedError(sessionFormatVersionRefusal(typeof id === "string" ? id : String(id), version));
10679
11819
  }
10680
11820
  /** Parse one complete header record supplied independently from event rows. */
@@ -10780,6 +11920,7 @@ var SessionLogScanner = class {
10780
11920
  finish() {
10781
11921
  this.finished = true;
10782
11922
  const artifact = this.restore.finish();
11923
+ assertReleasedV4Relationships(artifact, KNOWN_SESSION_EVENT_TYPES);
10783
11924
  return {
10784
11925
  meta: this.meta,
10785
11926
  inheritedEventCount: SessionLogOffset(artifact.inheritedEventCount),
@@ -10800,7 +11941,7 @@ var SessionLogScanner = class {
10800
11941
  return;
10801
11942
  }
10802
11943
  try {
10803
- assertV3RowAdmission(decoded);
11944
+ assertV4RowAdmission(decoded, KNOWN_SESSION_EVENT_TYPES);
10804
11945
  } catch (error) {
10805
11946
  if (error instanceof SessionFormatUnsupportedMigrationError) throw new SessionFormatUnsupportedError(error.message);
10806
11947
  throw error;
@@ -11705,6 +12846,7 @@ async function publishPreparedMigration(options, suffix, artifact, sourceIdentit
11705
12846
  const verifiedStage = await verifyCurrentFile(staged.path, compression, artifact.header.id, eventCount);
11706
12847
  if (verifiedStage.bytes !== staged.bytes || verifiedStage.digest !== staged.digest) throw new Error("staged session generation changed during verification");
11707
12848
  await internals.barrier("before-source-check", 1);
12849
+ await options.validateRelatedSources?.();
11708
12850
  if (identity(await internals.fs.stat(sourcePath)) !== identity(sourceIdentity)) throw new JsonlGenerationSourceChangedError(sourcePath);
11709
12851
  const published = await publishCurrentExclusive(staged.path, currentPath, internals);
11710
12852
  if (published && internals.platform === "win32") staged = {
@@ -11753,6 +12895,7 @@ async function prepareMigration(options, internals) {
11753
12895
  throw error;
11754
12896
  }
11755
12897
  if (artifact.header.version !== format.currentVersion) throw new Error(`format migration returned v${artifact.header.version}, expected v${format.currentVersion}`);
12898
+ await options.validateRelatedSources?.();
11756
12899
  const sourceIdentity = source.identity;
11757
12900
  let publication;
11758
12901
  return {