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