@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.2 → 0.1.7-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +34 -9
- package/README.zh.md +34 -9
- package/lib/client.js +774 -641
- package/lib/index.js +318 -143
- package/lib/typert.host.js +384 -186
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +225 -125
- package/lib/types/archived-session-gate.d.ts +30 -0
- package/lib/types/archived-session-gate.js +52 -0
- package/lib/types/client/contract/session.d.ts +11 -4
- package/lib/types/client/contract/sessions.d.ts +11 -16
- package/lib/types/client/contract/snapshot.d.ts +1 -1
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/sessions/lineage.d.ts +4 -1
- package/lib/types/client/sessions/lineage.js +2 -1
- package/lib/types/client/sessions/manager.d.ts +66 -59
- package/lib/types/client/sessions/manager.js +249 -277
- package/lib/types/client/sessions/projection-store.d.ts +38 -13
- package/lib/types/client/sessions/projection-store.js +54 -13
- package/lib/types/client/sessions/remotes.d.ts +1 -2
- package/lib/types/client/sessions/service.d.ts +23 -35
- package/lib/types/client/sessions/service.js +35 -47
- package/lib/types/client/sessions/session.d.ts +23 -19
- package/lib/types/client/sessions/session.js +158 -59
- package/lib/types/client/transport.js +5 -2
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.js +72 -36
- package/lib/types/control.d.ts +2 -4
- package/lib/types/control.js +2 -44
- package/lib/types/history.js +25 -10
- package/lib/types/index.d.ts +32 -5
- package/lib/types/index.js +117 -13
- package/lib/types/list.js +24 -15
- package/lib/types/skill-catalog.js +60 -50
- package/lib/types/types.d.ts +45 -25
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
package/lib/client.js
CHANGED
|
@@ -149,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
149
149
|
"compaction/start",
|
|
150
150
|
"compaction/summary",
|
|
151
151
|
"deliverables/presented",
|
|
152
|
+
"developer/message",
|
|
152
153
|
"feedback/message-delete",
|
|
153
154
|
"feedback/message-put",
|
|
154
155
|
"feedback/record",
|
|
@@ -199,6 +200,7 @@ window.__ModuleLoader__.load({
|
|
|
199
200
|
/** Runtime counterpart of the message-producing event union. */
|
|
200
201
|
const SURFACE_EVENT_TYPES = new Set([
|
|
201
202
|
"system/message",
|
|
203
|
+
"developer/message",
|
|
202
204
|
"user/message",
|
|
203
205
|
"assistant/message",
|
|
204
206
|
"tool/result"
|
|
@@ -206,7 +208,7 @@ window.__ModuleLoader__.load({
|
|
|
206
208
|
/**
|
|
207
209
|
* Whether an event type can join the model-visible surface.
|
|
208
210
|
* @param type - event type to test.
|
|
209
|
-
* @returns true for one of the
|
|
211
|
+
* @returns true for one of the message-producing event types.
|
|
210
212
|
*/
|
|
211
213
|
function isSurfaceEligibleType(type) {
|
|
212
214
|
return SURFACE_EVENT_TYPES.has(type);
|
|
@@ -216,14 +218,33 @@ window.__ModuleLoader__.load({
|
|
|
216
218
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
217
219
|
}
|
|
218
220
|
/**
|
|
219
|
-
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
221
|
+
* Reject noncanonical request-header fields, developer roles/content, and contradictory tool failure metadata.
|
|
220
222
|
* This does not validate complete event payloads or embedded provider streams.
|
|
221
223
|
* @param event - event whose locally related payload fields are inspected.
|
|
222
224
|
* @param subject - event location to include in validation errors.
|
|
223
|
-
* @throws when request
|
|
225
|
+
* @throws when request-header fields, developer roles/content, or tool failure metadata are invalid.
|
|
224
226
|
*/
|
|
225
227
|
function validateSessionEventData(event, subject) {
|
|
226
228
|
const data = event.data;
|
|
229
|
+
if (SURFACE_EVENT_TYPES.has(event.type) && isRecord(data)) {
|
|
230
|
+
const message = event.type === "user/message" ? data : data["message"];
|
|
231
|
+
if (isRecord(message)) {
|
|
232
|
+
if (event.type === "developer/message" !== (message["role"] === "developer")) throw new Error(`${subject} developer/message and developer role must occur together`);
|
|
233
|
+
if (message["role"] !== "developer" && Array.isArray(message["content"]) && message["content"].some((block) => isRecord(block) && (block["type"] === "tool-addition" || block["type"] === "tool-removal"))) throw new Error(`${subject} tool-change blocks require developer role`);
|
|
234
|
+
if (event.type === "developer/message" && Array.isArray(message["content"])) {
|
|
235
|
+
let hasAdditions = false;
|
|
236
|
+
for (const block of message["content"]) {
|
|
237
|
+
if (!isRecord(block) || block["type"] !== "tool-addition" && block["type"] !== "tool-removal") continue;
|
|
238
|
+
if (typeof block["toolName"] !== "string" || block["toolName"].length === 0) throw new Error(`${subject} ${block["type"]} requires a nonempty toolName`);
|
|
239
|
+
if (block["type"] === "tool-addition") {
|
|
240
|
+
hasAdditions = true;
|
|
241
|
+
if (Object.hasOwn(block, "tool")) throw new Error(`${subject} tool-addition must omit inline tool definitions`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (hasAdditions ? !isEventSeq(data["headerSeq"]) : Object.hasOwn(data, "headerSeq")) throw new Error(`${subject} requires headerSeq exactly when tool additions are present`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
227
248
|
if (event.type === "request/header") {
|
|
228
249
|
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
229
250
|
const header = data["header"];
|
|
@@ -236,9 +257,7 @@ window.__ModuleLoader__.load({
|
|
|
236
257
|
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
237
258
|
if (data["error"] === void 0) return;
|
|
238
259
|
const message = data["message"];
|
|
239
|
-
|
|
240
|
-
const block = Array.isArray(content) ? content[0] : void 0;
|
|
241
|
-
if (!isRecord(block) || block["isError"] !== true) throw new Error(`${subject} error requires message content[0].isError === true`);
|
|
260
|
+
if (!isRecord(message) || message["isError"] !== true) throw new Error(`${subject} error requires message.isError === true`);
|
|
242
261
|
}
|
|
243
262
|
}
|
|
244
263
|
/** Whether a runtime value is a non-negative safe event sequence. */
|
|
@@ -331,7 +350,6 @@ window.__ModuleLoader__.load({
|
|
|
331
350
|
}
|
|
332
351
|
//#endregion
|
|
333
352
|
//#region lib/types/types.js
|
|
334
|
-
/** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
|
|
335
353
|
/** Maximum number of Sessions returned by one search. */
|
|
336
354
|
const SESSION_SEARCH_RESULT_LIMIT = 20;
|
|
337
355
|
/** Maximum search snippet length in Unicode code points. */
|
|
@@ -410,7 +428,7 @@ window.__ModuleLoader__.load({
|
|
|
410
428
|
for await (const frame of this.remote.session.follow({
|
|
411
429
|
address: this.address,
|
|
412
430
|
assistantStream: true,
|
|
413
|
-
...
|
|
431
|
+
...this.repairRequest(request)
|
|
414
432
|
}, signal)) {
|
|
415
433
|
if (frame.type === "snapshot") {
|
|
416
434
|
for (const record of frame.records) assertSessionWireEvent(record.event);
|
|
@@ -458,7 +476,10 @@ window.__ModuleLoader__.load({
|
|
|
458
476
|
}
|
|
459
477
|
/** @inheritdoc */
|
|
460
478
|
repairRequest(request) {
|
|
461
|
-
return
|
|
479
|
+
return {
|
|
480
|
+
...request.maxMessages === void 0 ? {} : { maxMessages: request.maxMessages },
|
|
481
|
+
...request.turnWindow === void 0 ? {} : { turnWindow: request.turnWindow }
|
|
482
|
+
};
|
|
462
483
|
}
|
|
463
484
|
};
|
|
464
485
|
//#endregion
|
|
@@ -558,6 +579,210 @@ window.__ModuleLoader__.load({
|
|
|
558
579
|
return merged;
|
|
559
580
|
}
|
|
560
581
|
//#endregion
|
|
582
|
+
//#region ../../util/values/lib/index.js
|
|
583
|
+
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
584
|
+
/**
|
|
585
|
+
* Mark an unreachable closed-union branch.
|
|
586
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
587
|
+
* @param context - optional switch-site label included in the failure message.
|
|
588
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
589
|
+
*/
|
|
590
|
+
function assertNever(value, context) {
|
|
591
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
592
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
593
|
+
}
|
|
594
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
595
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
596
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
597
|
+
if (typeof constructor !== "function") return false;
|
|
598
|
+
try {
|
|
599
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
600
|
+
} catch {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
605
|
+
function isIntrinsicObjectPrototype(value) {
|
|
606
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
607
|
+
}
|
|
608
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
609
|
+
function hasPlainArrayPrototype(value) {
|
|
610
|
+
const prototype = Object.getPrototypeOf(value);
|
|
611
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
612
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
613
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
614
|
+
}
|
|
615
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
616
|
+
function hasPlainObjectPrototype(value) {
|
|
617
|
+
const prototype = Object.getPrototypeOf(value);
|
|
618
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
619
|
+
}
|
|
620
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
621
|
+
function enumerableStringKeys(value) {
|
|
622
|
+
const keys = Reflect.ownKeys(value);
|
|
623
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
624
|
+
return keys;
|
|
625
|
+
}
|
|
626
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
627
|
+
function walkJsonValue(value, detach) {
|
|
628
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
629
|
+
let root;
|
|
630
|
+
const assign = (destination, item) => {
|
|
631
|
+
if (destination === void 0) return;
|
|
632
|
+
if (destination.kind === "root") root = item;
|
|
633
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
634
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
635
|
+
value: item,
|
|
636
|
+
enumerable: true,
|
|
637
|
+
configurable: true,
|
|
638
|
+
writable: true
|
|
639
|
+
});
|
|
640
|
+
};
|
|
641
|
+
const tasks = [{
|
|
642
|
+
kind: "visit",
|
|
643
|
+
value,
|
|
644
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
645
|
+
}];
|
|
646
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
647
|
+
if (task.kind === "leave") {
|
|
648
|
+
ancestors.delete(task.source);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (task.kind === "array-item") {
|
|
652
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
653
|
+
tasks.push({
|
|
654
|
+
kind: "visit",
|
|
655
|
+
value: task.source[task.index],
|
|
656
|
+
...task.target === void 0 ? {} : { destination: {
|
|
657
|
+
kind: "array",
|
|
658
|
+
target: task.target,
|
|
659
|
+
index: task.index
|
|
660
|
+
} }
|
|
661
|
+
});
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (task.kind === "object-property") {
|
|
665
|
+
tasks.push({
|
|
666
|
+
kind: "visit",
|
|
667
|
+
value: task.source[task.key],
|
|
668
|
+
...task.target === void 0 ? {} : { destination: {
|
|
669
|
+
kind: "object",
|
|
670
|
+
target: task.target,
|
|
671
|
+
key: task.key
|
|
672
|
+
} }
|
|
673
|
+
});
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const current = task.value;
|
|
677
|
+
if (current === null) {
|
|
678
|
+
assign(task.destination, null);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
682
|
+
assign(task.destination, current);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (typeof current === "number") {
|
|
686
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
687
|
+
assign(task.destination, current);
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (typeof current !== "object") return void 0;
|
|
691
|
+
if (ancestors.has(current)) return void 0;
|
|
692
|
+
if (Array.isArray(current)) {
|
|
693
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
694
|
+
const length = current.length;
|
|
695
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
696
|
+
const target = detach ? [] : void 0;
|
|
697
|
+
if (target !== void 0) assign(task.destination, target);
|
|
698
|
+
ancestors.add(current);
|
|
699
|
+
tasks.push({
|
|
700
|
+
kind: "leave",
|
|
701
|
+
source: current
|
|
702
|
+
});
|
|
703
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
704
|
+
kind: "array-item",
|
|
705
|
+
source: current,
|
|
706
|
+
index,
|
|
707
|
+
...target === void 0 ? {} : { target }
|
|
708
|
+
});
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
712
|
+
const keys = enumerableStringKeys(current);
|
|
713
|
+
if (keys === void 0) return void 0;
|
|
714
|
+
const target = detach ? {} : void 0;
|
|
715
|
+
if (target !== void 0) assign(task.destination, target);
|
|
716
|
+
ancestors.add(current);
|
|
717
|
+
tasks.push({
|
|
718
|
+
kind: "leave",
|
|
719
|
+
source: current
|
|
720
|
+
});
|
|
721
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
722
|
+
const key = keys[index];
|
|
723
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
724
|
+
if (key === void 0) return void 0;
|
|
725
|
+
tasks.push({
|
|
726
|
+
kind: "object-property",
|
|
727
|
+
source: current,
|
|
728
|
+
key,
|
|
729
|
+
...target === void 0 ? {} : { target }
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return detach ? root : true;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Validate and detach lossless JSON in one read per property.
|
|
737
|
+
* @param value - candidate value to validate and detach.
|
|
738
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
739
|
+
*/
|
|
740
|
+
function snapshotJsonValue(value) {
|
|
741
|
+
return walkJsonValue(value, true);
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
745
|
+
* @param value - value to freeze.
|
|
746
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
747
|
+
*/
|
|
748
|
+
function deepFreeze(value) {
|
|
749
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
750
|
+
const pending = [{
|
|
751
|
+
kind: "visit",
|
|
752
|
+
node: value
|
|
753
|
+
}];
|
|
754
|
+
while (pending.length > 0) {
|
|
755
|
+
const task = pending.pop();
|
|
756
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
757
|
+
if (task === void 0) continue;
|
|
758
|
+
if (task.kind === "property") {
|
|
759
|
+
pending.push({
|
|
760
|
+
kind: "visit",
|
|
761
|
+
node: task.source[task.key]
|
|
762
|
+
});
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const node = task.node;
|
|
766
|
+
if (node === null || typeof node !== "object") continue;
|
|
767
|
+
if (node instanceof AbortSignal) continue;
|
|
768
|
+
if (seen.has(node)) continue;
|
|
769
|
+
seen.add(node);
|
|
770
|
+
Object.freeze(node);
|
|
771
|
+
const keys = Object.keys(node);
|
|
772
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
773
|
+
const key = keys[index];
|
|
774
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
775
|
+
if (key === void 0) continue;
|
|
776
|
+
pending.push({
|
|
777
|
+
kind: "property",
|
|
778
|
+
source: node,
|
|
779
|
+
key
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return value;
|
|
784
|
+
}
|
|
785
|
+
//#endregion
|
|
561
786
|
//#region lib/types/client/sessions/lineage.js
|
|
562
787
|
/**
|
|
563
788
|
* Summaries -> flat list with lineage indentation. Root and sibling order
|
|
@@ -584,8 +809,9 @@ window.__ModuleLoader__.load({
|
|
|
584
809
|
return;
|
|
585
810
|
}
|
|
586
811
|
visited.add(s.sessionId);
|
|
812
|
+
const { agentAvailable: _agentAvailable, ...row } = s;
|
|
587
813
|
out.push({
|
|
588
|
-
...
|
|
814
|
+
...row,
|
|
589
815
|
depth
|
|
590
816
|
});
|
|
591
817
|
const kids = children.get(s.sessionId);
|
|
@@ -688,10 +914,14 @@ window.__ModuleLoader__.load({
|
|
|
688
914
|
//#region lib/types/client/sessions/projection-store.js
|
|
689
915
|
/**
|
|
690
916
|
* One session's projection values. Framework semantics, uniform across every
|
|
691
|
-
* key
|
|
692
|
-
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
917
|
+
* key. Sequenced writes (a baseline seeds rows at its cut, a push frame
|
|
918
|
+
* updates one row) compare seqs among themselves: a lower-or-equal seq within
|
|
919
|
+
* the Host generation loses, so a replayed frame cannot regress a value and a
|
|
920
|
+
* stale baseline cannot overwrite a newer frame. Cached writes (the session
|
|
921
|
+
* list's zero-I/O block) only fill keys no sequenced row holds, and a baseline
|
|
922
|
+
* discards every cached row before it seeds, regardless of seq: the connected
|
|
923
|
+
* Session is the truth and a cached value never outranks it. A key the store
|
|
924
|
+
* has never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
695
925
|
* per key (create-on-demand, cached) so the React side binds each exactly
|
|
696
926
|
* once; the store-level channel (`subscribeAny`) serves coarse consumers (the
|
|
697
927
|
* manager's list projection reads the `title` key).
|
|
@@ -722,6 +952,15 @@ window.__ModuleLoader__.load({
|
|
|
722
952
|
return this.rows.get(key)?.value;
|
|
723
953
|
}
|
|
724
954
|
/**
|
|
955
|
+
* Read the accepted Host watermark without subscribing or copying a value.
|
|
956
|
+
* @param key - projection key.
|
|
957
|
+
* @returns the current sequence, or undefined for absent and cached values.
|
|
958
|
+
*/
|
|
959
|
+
seqOf(key) {
|
|
960
|
+
const row = this.rows.get(key);
|
|
961
|
+
return row?.kind === "sequenced" ? row.seq : void 0;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
725
964
|
* Read every current projection value as one reference-stable snapshot.
|
|
726
965
|
* @returns The same frozen value map until a row changes.
|
|
727
966
|
*/
|
|
@@ -746,27 +985,53 @@ window.__ModuleLoader__.load({
|
|
|
746
985
|
*/
|
|
747
986
|
apply(key, value, seq) {
|
|
748
987
|
const row = this.rows.get(key);
|
|
749
|
-
if (row
|
|
988
|
+
if (row?.kind === "sequenced" && seq <= row.seq) return;
|
|
750
989
|
this.rows.set(key, {
|
|
990
|
+
kind: "sequenced",
|
|
751
991
|
value,
|
|
752
992
|
seq
|
|
753
993
|
});
|
|
754
994
|
this.changed(key);
|
|
755
995
|
}
|
|
756
996
|
/**
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
997
|
+
* Fill keys from a session-list block the Host labeled `cached`: a zero-I/O
|
|
998
|
+
* view of the persisted checkpoint. A cached value lands only where no
|
|
999
|
+
* sequenced row exists: a connected Session has already answered for such
|
|
1000
|
+
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
1001
|
+
* than it.
|
|
1002
|
+
* @param values - whole values by key viewed from the persisted checkpoint.
|
|
1003
|
+
*/
|
|
1004
|
+
applyCached(values) {
|
|
1005
|
+
for (const key of Object.keys(values)) {
|
|
1006
|
+
if (this.rows.get(key)?.kind === "sequenced") continue;
|
|
1007
|
+
this.rows.set(key, {
|
|
1008
|
+
kind: "cached",
|
|
1009
|
+
value: values[key]
|
|
1010
|
+
});
|
|
1011
|
+
this.changed(key);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Seed from a history tail page's projections block. Every cached row is
|
|
1016
|
+
* discarded first, regardless of seq: the block comes from the connected
|
|
1017
|
+
* Session, and a value viewed from the persisted checkpoint never outranks
|
|
1018
|
+
* it. Then every carried key lands under the same seq rule as frames, and a
|
|
1019
|
+
* key the block omits is capability-absent as of the cut — its row clears
|
|
1020
|
+
* unless a newer frame already superseded the cut (a stale baseline can
|
|
1021
|
+
* neither overwrite nor clear newer sequenced values).
|
|
762
1022
|
* @param baseline - the response's projections block.
|
|
763
1023
|
*/
|
|
764
1024
|
seed(baseline) {
|
|
1025
|
+
for (const [key, row] of this.rows) {
|
|
1026
|
+
if (row.kind !== "cached") continue;
|
|
1027
|
+
this.rows.delete(key);
|
|
1028
|
+
this.changed(key);
|
|
1029
|
+
}
|
|
765
1030
|
const values = baseline.values;
|
|
766
1031
|
for (const key of Object.keys(values)) this.apply(key, values[key], baseline.asOfSeq);
|
|
767
1032
|
for (const [key, row] of this.rows) {
|
|
768
1033
|
if (Object.hasOwn(values, key)) continue;
|
|
769
|
-
if (row.seq > baseline.asOfSeq) continue;
|
|
1034
|
+
if (row.kind === "sequenced" && row.seq > baseline.asOfSeq) continue;
|
|
770
1035
|
this.rows.delete(key);
|
|
771
1036
|
this.changed(key);
|
|
772
1037
|
}
|
|
@@ -831,323 +1096,130 @@ window.__ModuleLoader__.load({
|
|
|
831
1096
|
};
|
|
832
1097
|
}
|
|
833
1098
|
function materialize(node) {
|
|
834
|
-
if (node.kind === "leaf") return node.entries;
|
|
835
|
-
const entries = new Array(node.length);
|
|
836
|
-
const pending = [node];
|
|
837
|
-
let index = 0;
|
|
838
|
-
while (pending.length > 0) {
|
|
839
|
-
const current = pending.pop();
|
|
840
|
-
if (current.kind === "concat") {
|
|
841
|
-
pending.push(current.right, current.left);
|
|
842
|
-
continue;
|
|
843
|
-
}
|
|
844
|
-
for (const entry of current.entries) {
|
|
845
|
-
entries[index] = entry;
|
|
846
|
-
index += 1;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return entries;
|
|
850
|
-
}
|
|
851
|
-
function windowSnapshot(node, hasMore, revision, change) {
|
|
852
|
-
let entries;
|
|
853
|
-
return {
|
|
854
|
-
get entries() {
|
|
855
|
-
entries ??= materialize(node);
|
|
856
|
-
return entries;
|
|
857
|
-
},
|
|
858
|
-
hasMore,
|
|
859
|
-
revision,
|
|
860
|
-
change
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
/** Session-owned event feed; every accepted window mutation publishes synchronously. */
|
|
864
|
-
var MutableSessionEventSource = class {
|
|
865
|
-
listeners = /* @__PURE__ */ new Set();
|
|
866
|
-
window = leaf([]);
|
|
867
|
-
snapshot = windowSnapshot(this.window, false, 0, {
|
|
868
|
-
kind: "replace",
|
|
869
|
-
entries: []
|
|
870
|
-
});
|
|
871
|
-
/** @returns the cached event-window snapshot. */
|
|
872
|
-
getSnapshot() {
|
|
873
|
-
return this.snapshot;
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Subscribe to synchronous window publication.
|
|
877
|
-
* @param listener - invalidation callback.
|
|
878
|
-
* @returns unsubscribe function.
|
|
879
|
-
*/
|
|
880
|
-
subscribe(listener) {
|
|
881
|
-
this.listeners.add(listener);
|
|
882
|
-
return () => {
|
|
883
|
-
this.listeners.delete(listener);
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Replace the complete contiguous window.
|
|
888
|
-
* @param entries - complete window.
|
|
889
|
-
* @param hasMore - whether older history remains.
|
|
890
|
-
*/
|
|
891
|
-
replace(entries, hasMore) {
|
|
892
|
-
this.window = leaf(entries);
|
|
893
|
-
this.publish(hasMore, {
|
|
894
|
-
kind: "replace",
|
|
895
|
-
entries
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Prepend one older contiguous page.
|
|
900
|
-
* @param entries - newly loaded older entries.
|
|
901
|
-
* @param hasMore - whether still older history remains.
|
|
902
|
-
*/
|
|
903
|
-
prepend(entries, hasMore) {
|
|
904
|
-
this.window = concat(leaf(entries), this.window);
|
|
905
|
-
this.publish(hasMore, {
|
|
906
|
-
kind: "prepend",
|
|
907
|
-
entries
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
/**
|
|
911
|
-
* Append one contiguous live entry.
|
|
912
|
-
* @param entry - live tail entry.
|
|
913
|
-
*/
|
|
914
|
-
append(entry) {
|
|
915
|
-
const entries = [entry];
|
|
916
|
-
this.window = concat(this.window, leaf(entries));
|
|
917
|
-
this.publish(this.snapshot.hasMore, {
|
|
918
|
-
kind: "append",
|
|
919
|
-
entries
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
/**
|
|
923
|
-
* Replace one attempt's transient rows with its committed durable settlement.
|
|
924
|
-
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
925
|
-
* @param entry - durable settlement committed for that attempt.
|
|
926
|
-
*/
|
|
927
|
-
settleAssistant(attemptId, entry) {
|
|
928
|
-
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
929
|
-
if (entry !== void 0) {
|
|
930
|
-
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
931
|
-
if (index < 0) entries.push(entry);
|
|
932
|
-
else entries.splice(index, 0, entry);
|
|
933
|
-
}
|
|
934
|
-
this.window = leaf(entries);
|
|
935
|
-
this.publish(this.snapshot.hasMore, {
|
|
936
|
-
kind: "settle-assistant",
|
|
937
|
-
attemptId,
|
|
938
|
-
...entry === void 0 ? {} : { entry }
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
publish(hasMore, change) {
|
|
942
|
-
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
943
|
-
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
//#endregion
|
|
947
|
-
//#region lib/types/client/time-zone.js
|
|
948
|
-
/** Browser-owned time-zone sampling for one prompt RPC. */
|
|
949
|
-
/**
|
|
950
|
-
* Resolve the current browser IANA zone for one outbound operation.
|
|
951
|
-
* @returns The browser-provided canonical zone.
|
|
952
|
-
* @throws when the runtime cannot provide a non-empty zone.
|
|
953
|
-
*/
|
|
954
|
-
function resolvedClientTimeZone() {
|
|
955
|
-
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
956
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) throw new Error("browser time zone is unavailable");
|
|
957
|
-
return timeZone;
|
|
958
|
-
}
|
|
959
|
-
//#endregion
|
|
960
|
-
//#region ../../util/values/lib/index.js
|
|
961
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
962
|
-
function hasIntrinsicConstructor(prototype, name) {
|
|
963
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
964
|
-
if (typeof constructor !== "function") return false;
|
|
965
|
-
try {
|
|
966
|
-
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
967
|
-
} catch {
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
972
|
-
function isIntrinsicObjectPrototype(value) {
|
|
973
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
974
|
-
}
|
|
975
|
-
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
976
|
-
function hasPlainArrayPrototype(value) {
|
|
977
|
-
const prototype = Object.getPrototypeOf(value);
|
|
978
|
-
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
979
|
-
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
980
|
-
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
981
|
-
}
|
|
982
|
-
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
983
|
-
function hasPlainObjectPrototype(value) {
|
|
984
|
-
const prototype = Object.getPrototypeOf(value);
|
|
985
|
-
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
986
|
-
}
|
|
987
|
-
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
988
|
-
function enumerableStringKeys(value) {
|
|
989
|
-
const keys = Reflect.ownKeys(value);
|
|
990
|
-
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
991
|
-
return keys;
|
|
992
|
-
}
|
|
993
|
-
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
994
|
-
function walkJsonValue(value, detach) {
|
|
995
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
996
|
-
let root;
|
|
997
|
-
const assign = (destination, item) => {
|
|
998
|
-
if (destination === void 0) return;
|
|
999
|
-
if (destination.kind === "root") root = item;
|
|
1000
|
-
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
1001
|
-
else Object.defineProperty(destination.target, destination.key, {
|
|
1002
|
-
value: item,
|
|
1003
|
-
enumerable: true,
|
|
1004
|
-
configurable: true,
|
|
1005
|
-
writable: true
|
|
1006
|
-
});
|
|
1007
|
-
};
|
|
1008
|
-
const tasks = [{
|
|
1009
|
-
kind: "visit",
|
|
1010
|
-
value,
|
|
1011
|
-
...detach ? { destination: { kind: "root" } } : {}
|
|
1012
|
-
}];
|
|
1013
|
-
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
1014
|
-
if (task.kind === "leave") {
|
|
1015
|
-
ancestors.delete(task.source);
|
|
1016
|
-
continue;
|
|
1017
|
-
}
|
|
1018
|
-
if (task.kind === "array-item") {
|
|
1019
|
-
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
1020
|
-
tasks.push({
|
|
1021
|
-
kind: "visit",
|
|
1022
|
-
value: task.source[task.index],
|
|
1023
|
-
...task.target === void 0 ? {} : { destination: {
|
|
1024
|
-
kind: "array",
|
|
1025
|
-
target: task.target,
|
|
1026
|
-
index: task.index
|
|
1027
|
-
} }
|
|
1028
|
-
});
|
|
1029
|
-
continue;
|
|
1030
|
-
}
|
|
1031
|
-
if (task.kind === "object-property") {
|
|
1032
|
-
tasks.push({
|
|
1033
|
-
kind: "visit",
|
|
1034
|
-
value: task.source[task.key],
|
|
1035
|
-
...task.target === void 0 ? {} : { destination: {
|
|
1036
|
-
kind: "object",
|
|
1037
|
-
target: task.target,
|
|
1038
|
-
key: task.key
|
|
1039
|
-
} }
|
|
1040
|
-
});
|
|
1041
|
-
continue;
|
|
1042
|
-
}
|
|
1043
|
-
const current = task.value;
|
|
1044
|
-
if (current === null) {
|
|
1045
|
-
assign(task.destination, null);
|
|
1046
|
-
continue;
|
|
1047
|
-
}
|
|
1048
|
-
if (typeof current === "boolean" || typeof current === "string") {
|
|
1049
|
-
assign(task.destination, current);
|
|
1050
|
-
continue;
|
|
1051
|
-
}
|
|
1052
|
-
if (typeof current === "number") {
|
|
1053
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
1054
|
-
assign(task.destination, current);
|
|
1055
|
-
continue;
|
|
1056
|
-
}
|
|
1057
|
-
if (typeof current !== "object") return void 0;
|
|
1058
|
-
if (ancestors.has(current)) return void 0;
|
|
1059
|
-
if (Array.isArray(current)) {
|
|
1060
|
-
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
1061
|
-
const length = current.length;
|
|
1062
|
-
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
1063
|
-
const target = detach ? [] : void 0;
|
|
1064
|
-
if (target !== void 0) assign(task.destination, target);
|
|
1065
|
-
ancestors.add(current);
|
|
1066
|
-
tasks.push({
|
|
1067
|
-
kind: "leave",
|
|
1068
|
-
source: current
|
|
1069
|
-
});
|
|
1070
|
-
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
1071
|
-
kind: "array-item",
|
|
1072
|
-
source: current,
|
|
1073
|
-
index,
|
|
1074
|
-
...target === void 0 ? {} : { target }
|
|
1075
|
-
});
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
1079
|
-
const keys = enumerableStringKeys(current);
|
|
1080
|
-
if (keys === void 0) return void 0;
|
|
1081
|
-
const target = detach ? {} : void 0;
|
|
1082
|
-
if (target !== void 0) assign(task.destination, target);
|
|
1083
|
-
ancestors.add(current);
|
|
1084
|
-
tasks.push({
|
|
1085
|
-
kind: "leave",
|
|
1086
|
-
source: current
|
|
1087
|
-
});
|
|
1088
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1089
|
-
const key = keys[index];
|
|
1090
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1091
|
-
if (key === void 0) return void 0;
|
|
1092
|
-
tasks.push({
|
|
1093
|
-
kind: "object-property",
|
|
1094
|
-
source: current,
|
|
1095
|
-
key,
|
|
1096
|
-
...target === void 0 ? {} : { target }
|
|
1097
|
-
});
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
return detach ? root : true;
|
|
1101
|
-
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Validate and detach lossless JSON in one read per property.
|
|
1104
|
-
* @param value - candidate value to validate and detach.
|
|
1105
|
-
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
1106
|
-
*/
|
|
1107
|
-
function snapshotJsonValue(value) {
|
|
1108
|
-
return walkJsonValue(value, true);
|
|
1109
|
-
}
|
|
1110
|
-
/**
|
|
1111
|
-
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
1112
|
-
* @param value - value to freeze.
|
|
1113
|
-
* @returns the same value after every reachable enumerable child is frozen.
|
|
1114
|
-
*/
|
|
1115
|
-
function deepFreeze(value) {
|
|
1116
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1117
|
-
const pending = [{
|
|
1118
|
-
kind: "visit",
|
|
1119
|
-
node: value
|
|
1120
|
-
}];
|
|
1099
|
+
if (node.kind === "leaf") return node.entries;
|
|
1100
|
+
const entries = new Array(node.length);
|
|
1101
|
+
const pending = [node];
|
|
1102
|
+
let index = 0;
|
|
1121
1103
|
while (pending.length > 0) {
|
|
1122
|
-
const
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (task.kind === "property") {
|
|
1126
|
-
pending.push({
|
|
1127
|
-
kind: "visit",
|
|
1128
|
-
node: task.source[task.key]
|
|
1129
|
-
});
|
|
1104
|
+
const current = pending.pop();
|
|
1105
|
+
if (current.kind === "concat") {
|
|
1106
|
+
pending.push(current.right, current.left);
|
|
1130
1107
|
continue;
|
|
1131
1108
|
}
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
if (seen.has(node)) continue;
|
|
1136
|
-
seen.add(node);
|
|
1137
|
-
Object.freeze(node);
|
|
1138
|
-
const keys = Object.keys(node);
|
|
1139
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1140
|
-
const key = keys[index];
|
|
1141
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1142
|
-
if (key === void 0) continue;
|
|
1143
|
-
pending.push({
|
|
1144
|
-
kind: "property",
|
|
1145
|
-
source: node,
|
|
1146
|
-
key
|
|
1147
|
-
});
|
|
1109
|
+
for (const entry of current.entries) {
|
|
1110
|
+
entries[index] = entry;
|
|
1111
|
+
index += 1;
|
|
1148
1112
|
}
|
|
1149
1113
|
}
|
|
1150
|
-
return
|
|
1114
|
+
return entries;
|
|
1115
|
+
}
|
|
1116
|
+
function windowSnapshot(node, hasMore, revision, change) {
|
|
1117
|
+
let entries;
|
|
1118
|
+
return {
|
|
1119
|
+
get entries() {
|
|
1120
|
+
entries ??= materialize(node);
|
|
1121
|
+
return entries;
|
|
1122
|
+
},
|
|
1123
|
+
hasMore,
|
|
1124
|
+
revision,
|
|
1125
|
+
change
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
/** Session-owned event feed; every accepted window mutation publishes synchronously. */
|
|
1129
|
+
var MutableSessionEventSource = class {
|
|
1130
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1131
|
+
window = leaf([]);
|
|
1132
|
+
snapshot = windowSnapshot(this.window, false, 0, {
|
|
1133
|
+
kind: "replace",
|
|
1134
|
+
entries: []
|
|
1135
|
+
});
|
|
1136
|
+
/** @returns the cached event-window snapshot. */
|
|
1137
|
+
getSnapshot() {
|
|
1138
|
+
return this.snapshot;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Subscribe to synchronous window publication.
|
|
1142
|
+
* @param listener - invalidation callback.
|
|
1143
|
+
* @returns unsubscribe function.
|
|
1144
|
+
*/
|
|
1145
|
+
subscribe(listener) {
|
|
1146
|
+
this.listeners.add(listener);
|
|
1147
|
+
return () => {
|
|
1148
|
+
this.listeners.delete(listener);
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Replace the complete contiguous window.
|
|
1153
|
+
* @param entries - complete window.
|
|
1154
|
+
* @param hasMore - whether older history remains.
|
|
1155
|
+
*/
|
|
1156
|
+
replace(entries, hasMore) {
|
|
1157
|
+
this.window = leaf(entries);
|
|
1158
|
+
this.publish(hasMore, {
|
|
1159
|
+
kind: "replace",
|
|
1160
|
+
entries
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Prepend one older contiguous page.
|
|
1165
|
+
* @param entries - newly loaded older entries.
|
|
1166
|
+
* @param hasMore - whether still older history remains.
|
|
1167
|
+
*/
|
|
1168
|
+
prepend(entries, hasMore) {
|
|
1169
|
+
this.window = concat(leaf(entries), this.window);
|
|
1170
|
+
this.publish(hasMore, {
|
|
1171
|
+
kind: "prepend",
|
|
1172
|
+
entries
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Append one contiguous live entry.
|
|
1177
|
+
* @param entry - live tail entry.
|
|
1178
|
+
*/
|
|
1179
|
+
append(entry) {
|
|
1180
|
+
const entries = [entry];
|
|
1181
|
+
this.window = concat(this.window, leaf(entries));
|
|
1182
|
+
this.publish(this.snapshot.hasMore, {
|
|
1183
|
+
kind: "append",
|
|
1184
|
+
entries
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Replace one attempt's transient rows with its committed durable settlement.
|
|
1189
|
+
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
1190
|
+
* @param entry - durable settlement committed for that attempt.
|
|
1191
|
+
*/
|
|
1192
|
+
settleAssistant(attemptId, entry) {
|
|
1193
|
+
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
1194
|
+
if (entry !== void 0) {
|
|
1195
|
+
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
1196
|
+
if (index < 0) entries.push(entry);
|
|
1197
|
+
else entries.splice(index, 0, entry);
|
|
1198
|
+
}
|
|
1199
|
+
this.window = leaf(entries);
|
|
1200
|
+
this.publish(this.snapshot.hasMore, {
|
|
1201
|
+
kind: "settle-assistant",
|
|
1202
|
+
attemptId,
|
|
1203
|
+
...entry === void 0 ? {} : { entry }
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
publish(hasMore, change) {
|
|
1207
|
+
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
1208
|
+
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
//#endregion
|
|
1212
|
+
//#region lib/types/client/time-zone.js
|
|
1213
|
+
/** Browser-owned time-zone sampling for one prompt RPC. */
|
|
1214
|
+
/**
|
|
1215
|
+
* Resolve the current browser IANA zone for one outbound operation.
|
|
1216
|
+
* @returns The browser-provided canonical zone.
|
|
1217
|
+
* @throws when the runtime cannot provide a non-empty zone.
|
|
1218
|
+
*/
|
|
1219
|
+
function resolvedClientTimeZone() {
|
|
1220
|
+
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1221
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) throw new Error("browser time zone is unavailable");
|
|
1222
|
+
return timeZone;
|
|
1151
1223
|
}
|
|
1152
1224
|
//#endregion
|
|
1153
1225
|
//#region ../../llm/llm/lib/types/assistant-stream.js
|
|
@@ -1456,6 +1528,20 @@ window.__ModuleLoader__.load({
|
|
|
1456
1528
|
asOfSeq: value.asOfSeq === -1 ? -1 : SessionSeq(value.asOfSeq)
|
|
1457
1529
|
};
|
|
1458
1530
|
}
|
|
1531
|
+
const HISTORY_PAGE_OPTIONS = {
|
|
1532
|
+
maxMessages: 500,
|
|
1533
|
+
turnWindow: {
|
|
1534
|
+
minMessages: 50,
|
|
1535
|
+
minTurns: 2
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
const JUMP_PAGE_OPTIONS = {
|
|
1539
|
+
...HISTORY_PAGE_OPTIONS,
|
|
1540
|
+
turnWindow: {
|
|
1541
|
+
...HISTORY_PAGE_OPTIONS.turnWindow,
|
|
1542
|
+
minMessages: 200
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1459
1545
|
/**
|
|
1460
1546
|
* Owns a session's event window, lifecycle state, and observable
|
|
1461
1547
|
* snapshot. React bindings remain outside this data layer. Features see only
|
|
@@ -1479,6 +1565,7 @@ window.__ModuleLoader__.load({
|
|
|
1479
1565
|
jumpTargetSeq = null;
|
|
1480
1566
|
/** The running jump loop's completion, shared by retargeting callers. */
|
|
1481
1567
|
jumpPromise = null;
|
|
1568
|
+
pendingHistory = null;
|
|
1482
1569
|
stopObservingInbox;
|
|
1483
1570
|
assistantStream = new ClientAssistantStream();
|
|
1484
1571
|
running = false;
|
|
@@ -1492,7 +1579,7 @@ window.__ModuleLoader__.load({
|
|
|
1492
1579
|
promptAttempted = false;
|
|
1493
1580
|
/** A first accepted prompt stays in the engaging phase until its turn is observable. */
|
|
1494
1581
|
firstPromptPendingTurn = false;
|
|
1495
|
-
/**
|
|
1582
|
+
/** New Session display state; unknown bare sessions begin conservatively blank. */
|
|
1496
1583
|
blankBit = true;
|
|
1497
1584
|
removed = false;
|
|
1498
1585
|
promptError = null;
|
|
@@ -1508,8 +1595,9 @@ window.__ModuleLoader__.load({
|
|
|
1508
1595
|
* Per-session projection value store (push model; see the session-projection
|
|
1509
1596
|
* subsystem page, docs/subsystems/session-projection.md): finished whole
|
|
1510
1597
|
* values computed on the Host, seeded by the tail page's
|
|
1511
|
-
* projections block and updated by Session Controller control frames
|
|
1512
|
-
*
|
|
1598
|
+
* projections block and updated by Session Controller control frames;
|
|
1599
|
+
* Host-sequenced writes merge under higher-seq-wins and cached list blocks
|
|
1600
|
+
* yield to them (projection-store.ts). Keys are read via `projections.faceOf(key)`
|
|
1513
1601
|
* (the useProjection resolution face); the conversation snapshot never
|
|
1514
1602
|
* carries projection values, and no client-side domain folding exists.
|
|
1515
1603
|
* Manager-owned when constructed through SessionManager (frames route and
|
|
@@ -1574,14 +1662,16 @@ window.__ModuleLoader__.load({
|
|
|
1574
1662
|
*/
|
|
1575
1663
|
beginSubmission(input) {
|
|
1576
1664
|
const requestId = randomUUID();
|
|
1665
|
+
const placement = this.running ? input.mode === "steer" ? "steering" : "queued" : "transcript";
|
|
1577
1666
|
this.pendingSubmissions = [...this.pendingSubmissions, {
|
|
1578
1667
|
requestId,
|
|
1579
|
-
placement
|
|
1668
|
+
placement,
|
|
1580
1669
|
time: Date.now(),
|
|
1581
1670
|
text: input.text,
|
|
1582
1671
|
attachments: input.attachments
|
|
1583
1672
|
}];
|
|
1584
1673
|
this.submissionSettlements.set(requestId, {
|
|
1674
|
+
placement,
|
|
1585
1675
|
onRetire: input.onRetire,
|
|
1586
1676
|
retiring: false
|
|
1587
1677
|
});
|
|
@@ -1649,9 +1739,9 @@ window.__ModuleLoader__.load({
|
|
|
1649
1739
|
}
|
|
1650
1740
|
if (this.blankBit) {
|
|
1651
1741
|
this.blankBit = false;
|
|
1652
|
-
this.options.onEngaged?.(this);
|
|
1653
1742
|
this.notifier.markDirty();
|
|
1654
1743
|
}
|
|
1744
|
+
this.options.onEngaged?.(this);
|
|
1655
1745
|
return result;
|
|
1656
1746
|
}
|
|
1657
1747
|
/**
|
|
@@ -1752,7 +1842,7 @@ window.__ModuleLoader__.load({
|
|
|
1752
1842
|
this.openPromise = promise;
|
|
1753
1843
|
return promise;
|
|
1754
1844
|
}
|
|
1755
|
-
/**
|
|
1845
|
+
/** Prepend one Turn-aligned page: at least 50 messages and two Turn starts, capped at 500 messages. */
|
|
1756
1846
|
async loadOlder() {
|
|
1757
1847
|
if (this.openState !== "open" || !this.hasMore || this.loadingOlder) return;
|
|
1758
1848
|
const events = this.events;
|
|
@@ -1762,7 +1852,7 @@ window.__ModuleLoader__.load({
|
|
|
1762
1852
|
try {
|
|
1763
1853
|
await events.prepend({
|
|
1764
1854
|
beforeSeq: this.baseSeq,
|
|
1765
|
-
|
|
1855
|
+
...HISTORY_PAGE_OPTIONS
|
|
1766
1856
|
});
|
|
1767
1857
|
} catch (error) {
|
|
1768
1858
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) console.error("[session-controller] loadOlder failed:", error);
|
|
@@ -1779,29 +1869,37 @@ window.__ModuleLoader__.load({
|
|
|
1779
1869
|
return this.jumpPromise;
|
|
1780
1870
|
}
|
|
1781
1871
|
if (this.loadingOlder) return Promise.resolve();
|
|
1872
|
+
const events = this.events;
|
|
1873
|
+
if (events === void 0) return Promise.resolve();
|
|
1874
|
+
const pending = {
|
|
1875
|
+
beforeSeq: this.baseSeq,
|
|
1876
|
+
hasMore: this.hasMore,
|
|
1877
|
+
pages: []
|
|
1878
|
+
};
|
|
1879
|
+
this.pendingHistory = pending;
|
|
1782
1880
|
this.jumpTargetSeq = seq;
|
|
1783
1881
|
this.loadingOlder = true;
|
|
1784
1882
|
this.notifier.markDirty();
|
|
1785
1883
|
const generation = this.openGeneration;
|
|
1786
1884
|
this.jumpPromise = (async () => {
|
|
1787
1885
|
try {
|
|
1788
|
-
while (
|
|
1886
|
+
while (pending.hasMore && this.jumpTargetSeq !== null && pending.beforeSeq > this.jumpTargetSeq) {
|
|
1789
1887
|
if (generation !== this.openGeneration) return;
|
|
1790
|
-
const
|
|
1791
|
-
if (events === void 0) return;
|
|
1792
|
-
const before = this.baseSeq;
|
|
1888
|
+
const before = pending.beforeSeq;
|
|
1793
1889
|
await events.prepend({
|
|
1794
|
-
beforeSeq:
|
|
1795
|
-
|
|
1890
|
+
beforeSeq: before,
|
|
1891
|
+
...JUMP_PAGE_OPTIONS
|
|
1796
1892
|
});
|
|
1797
|
-
if (
|
|
1893
|
+
if (pending.beforeSeq >= before) return;
|
|
1798
1894
|
}
|
|
1799
1895
|
} catch (error) {
|
|
1800
1896
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) console.error("[session-controller] loadThrough failed:", error);
|
|
1801
1897
|
} finally {
|
|
1802
1898
|
this.jumpTargetSeq = null;
|
|
1803
1899
|
this.jumpPromise = null;
|
|
1900
|
+
this.pendingHistory = null;
|
|
1804
1901
|
this.loadingOlder = false;
|
|
1902
|
+
if (generation === this.openGeneration && pending.pages.length > 0) this.prependWindow(pending.pages.reverse().flat(), pending.hasMore);
|
|
1805
1903
|
this.notifier.markDirty();
|
|
1806
1904
|
}
|
|
1807
1905
|
})();
|
|
@@ -1876,13 +1974,15 @@ window.__ModuleLoader__.load({
|
|
|
1876
1974
|
this.notifier.markDirty();
|
|
1877
1975
|
}
|
|
1878
1976
|
/**
|
|
1879
|
-
*
|
|
1880
|
-
* `
|
|
1881
|
-
* running
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1977
|
+
* Apply the Manager's effective display blank, further reconciled with the
|
|
1978
|
+
* current `sessionListMetadata` projection. Local send attempts and current
|
|
1979
|
+
* running state prevent re-blanking; an earlier false summary alone does not.
|
|
1980
|
+
* The Manager retains acceptance and earlier running observations across
|
|
1981
|
+
* Session-object replacement.
|
|
1982
|
+
* @param blank - New Session display state after Manager reconciliation.
|
|
1884
1983
|
*/
|
|
1885
1984
|
handleBlank(blank) {
|
|
1985
|
+
blank = blank && this.projections.values().sessionListMetadata?.blank !== false;
|
|
1886
1986
|
if (blank === this.blankBit) return;
|
|
1887
1987
|
if (blank && (this.promptAttempted || this.running)) return;
|
|
1888
1988
|
this.blankBit = blank;
|
|
@@ -1907,7 +2007,8 @@ window.__ModuleLoader__.load({
|
|
|
1907
2007
|
*/
|
|
1908
2008
|
async dispose() {
|
|
1909
2009
|
this.stopObservingInbox();
|
|
1910
|
-
for (const requestId of [...this.submissionSettlements
|
|
2010
|
+
for (const [requestId, settlement] of [...this.submissionSettlements]) if (settlement.admitted !== void 0) this.scheduleObservedRetirement(requestId, settlement.admitted);
|
|
2011
|
+
else this.retireFailedSubmission(requestId);
|
|
1911
2012
|
this.openGeneration++;
|
|
1912
2013
|
const events = this.events;
|
|
1913
2014
|
this.events = void 0;
|
|
@@ -1929,7 +2030,7 @@ window.__ModuleLoader__.load({
|
|
|
1929
2030
|
});
|
|
1930
2031
|
this.events = events;
|
|
1931
2032
|
try {
|
|
1932
|
-
await events.open(
|
|
2033
|
+
await events.open(HISTORY_PAGE_OPTIONS);
|
|
1933
2034
|
if (generation !== this.openGeneration || this.events !== events) return;
|
|
1934
2035
|
this.openState = "open";
|
|
1935
2036
|
} catch (error) {
|
|
@@ -1962,10 +2063,22 @@ window.__ModuleLoader__.load({
|
|
|
1962
2063
|
const visible = this.assistantStream.replace(entries, assistantStream);
|
|
1963
2064
|
this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
|
|
1964
2065
|
this.hasMore = hasMore;
|
|
2066
|
+
if (this.pendingHistory !== null) {
|
|
2067
|
+
this.pendingHistory.beforeSeq = this.baseSeq;
|
|
2068
|
+
this.pendingHistory.hasMore = hasMore;
|
|
2069
|
+
this.pendingHistory.pages.length = 0;
|
|
2070
|
+
}
|
|
1965
2071
|
if (visible.some((entry) => entry.event.type === "turn/start")) this.firstPromptPendingTurn = false;
|
|
1966
2072
|
if (projections !== void 0) this.projections.seed(projections);
|
|
1967
2073
|
this.eventSource.replace(visible, hasMore);
|
|
2074
|
+
if (projections !== void 0) {
|
|
2075
|
+
for (const [requestId, { receipt }] of this.submissionSettlements) if (receipt !== void 0 && receipt.seq <= projections.asOfSeq) this.scheduleObservedRetirement(requestId, receipt.attachments);
|
|
2076
|
+
}
|
|
1968
2077
|
for (const entry of visible) this.observeSubmissionEvent(entry.event);
|
|
2078
|
+
if (projections !== void 0) {
|
|
2079
|
+
const inbox = projections.values.inbox;
|
|
2080
|
+
for (const target of ["next-turn", "next-step"]) this.observeSubmissionInsertions(target, inbox?.[target] ?? [], 0, projections.asOfSeq);
|
|
2081
|
+
}
|
|
1969
2082
|
this.notifier.markDirty();
|
|
1970
2083
|
}
|
|
1971
2084
|
publishAssistantEntry(result) {
|
|
@@ -1995,6 +2108,13 @@ window.__ModuleLoader__.load({
|
|
|
1995
2108
|
}
|
|
1996
2109
|
/** Prepend one stream-validated history page. */
|
|
1997
2110
|
prependWindow(entries, hasMore) {
|
|
2111
|
+
if (this.pendingHistory !== null) {
|
|
2112
|
+
const pending = this.pendingHistory;
|
|
2113
|
+
pending.beforeSeq = entries[0] === void 0 ? pending.beforeSeq : SessionLogOffset(entries[0].event.seq);
|
|
2114
|
+
pending.hasMore = hasMore;
|
|
2115
|
+
pending.pages.push(entries);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
1998
2118
|
this.baseSeq = entries[0] === void 0 ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
|
|
1999
2119
|
this.hasMore = hasMore;
|
|
2000
2120
|
this.eventSource.prepend(entries, hasMore);
|
|
@@ -2012,28 +2132,73 @@ window.__ModuleLoader__.load({
|
|
|
2012
2132
|
observeSubmissionEvent(event) {
|
|
2013
2133
|
if (this.submissionSettlements.size === 0) return;
|
|
2014
2134
|
if (event.type === "agent/inbox/spliced") {
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2135
|
+
const { target, start, removedCount = 0, inserted, outcome } = event.data;
|
|
2136
|
+
for (const [requestId, settlement] of this.submissionSettlements) {
|
|
2137
|
+
const receipt = settlement.receipt;
|
|
2138
|
+
if (receipt?.target !== target || receipt.index === null || receipt.seq >= event.seq) continue;
|
|
2139
|
+
const removed = receipt.index >= start && receipt.index < start + removedCount;
|
|
2140
|
+
if (removed && outcome === "canceled") this.retireFailedSubmission(requestId);
|
|
2141
|
+
else settlement.receipt = {
|
|
2142
|
+
...receipt,
|
|
2143
|
+
seq: event.seq,
|
|
2144
|
+
index: removed ? null : receipt.index < start ? receipt.index : receipt.index + inserted.length - removedCount
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
this.observeSubmissionInsertions(target, inserted, start, event.seq);
|
|
2148
|
+
for (const message of inserted) this.observeSubmissionMessage(message, false);
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
if (event.type === "request/context" || event.type === "turn/end") {
|
|
2152
|
+
for (const [requestId, settlement] of this.submissionSettlements) if (settlement.admitted === void 0 && settlement.receipt?.index === null && settlement.receipt.seq < event.seq) this.retireFailedSubmission(requestId);
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
if (event.type === "user/message") this.observeSubmissionMessage(event.data, true);
|
|
2156
|
+
}
|
|
2157
|
+
observeSubmissionInsertions(target, messages, start, seq) {
|
|
2158
|
+
for (const [index, message] of messages.entries()) {
|
|
2159
|
+
const source = message.source;
|
|
2160
|
+
if (source.kind !== "user" || !("rpcId" in source)) continue;
|
|
2161
|
+
const settlement = this.submissionSettlements.get(source.rpcId);
|
|
2162
|
+
if (settlement === void 0 || settlement.placement === "queued" || settlement.retiring || (settlement.receipt?.seq ?? -1) > seq) continue;
|
|
2163
|
+
settlement.receipt = {
|
|
2164
|
+
target,
|
|
2165
|
+
seq,
|
|
2166
|
+
index: start + index,
|
|
2167
|
+
attachments: attachmentRefsIn(message.content)
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
observeSubmissionMessage(message, admitted) {
|
|
2172
|
+
const source = message.source;
|
|
2173
|
+
if (source.kind !== "user" || !("rpcId" in source)) return;
|
|
2174
|
+
const settlement = this.submissionSettlements.get(source.rpcId);
|
|
2175
|
+
if (settlement === void 0 || settlement.retiring) return;
|
|
2176
|
+
if (!admitted) {
|
|
2177
|
+
if (settlement.placement === "queued") this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(message.content));
|
|
2020
2178
|
return;
|
|
2021
2179
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2180
|
+
settlement.admitted = attachmentRefsIn(message.content);
|
|
2181
|
+
this.retireAdmittedSubmission(source.rpcId);
|
|
2182
|
+
}
|
|
2183
|
+
/** Retire admitted Chat identities only after stale Inbox rows can no longer reappear. */
|
|
2184
|
+
retireAdmittedSubmission(requestId) {
|
|
2185
|
+
const settlement = this.submissionSettlements.get(requestId);
|
|
2186
|
+
if (settlement?.admitted === void 0) return;
|
|
2187
|
+
const receipt = settlement.receipt;
|
|
2188
|
+
if (receipt?.index === null && (this.projections.seqOf("inbox") ?? -1) < receipt.seq) return;
|
|
2189
|
+
this.scheduleObservedRetirement(requestId, settlement.admitted);
|
|
2027
2190
|
}
|
|
2028
|
-
/**
|
|
2191
|
+
/** Inbox acceptance retires queued echoes; its watermark completes admitted Chat handoffs. */
|
|
2029
2192
|
observeSubmissionInbox() {
|
|
2030
2193
|
if (this.submissionSettlements.size === 0) return;
|
|
2031
2194
|
const inbox = this.projections.get("inbox");
|
|
2032
2195
|
if (inbox === void 0) return;
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
if (
|
|
2196
|
+
const seq = this.projections.seqOf("inbox");
|
|
2197
|
+
for (const target of ["next-turn", "next-step"]) {
|
|
2198
|
+
if (seq !== void 0) this.observeSubmissionInsertions(target, inbox[target], 0, seq);
|
|
2199
|
+
for (const message of inbox[target]) this.observeSubmissionMessage(message, false);
|
|
2036
2200
|
}
|
|
2201
|
+
for (const requestId of this.submissionSettlements.keys()) this.retireAdmittedSubmission(requestId);
|
|
2037
2202
|
}
|
|
2038
2203
|
/**
|
|
2039
2204
|
* Latch one observed settlement and remove the echo an animation frame
|
|
@@ -2055,7 +2220,7 @@ window.__ModuleLoader__.load({
|
|
|
2055
2220
|
/** Remove one unsettled echo immediately (prompt rejection, abort, or disposal). */
|
|
2056
2221
|
retireFailedSubmission(requestId) {
|
|
2057
2222
|
const settlement = this.submissionSettlements.get(requestId);
|
|
2058
|
-
if (settlement === void 0 || settlement.retiring) return;
|
|
2223
|
+
if (settlement === void 0 || settlement.retiring || settlement.admitted !== void 0) return;
|
|
2059
2224
|
settlement.retiring = true;
|
|
2060
2225
|
this.finishSubmission(requestId, { reason: "failed" });
|
|
2061
2226
|
}
|
|
@@ -2082,12 +2247,16 @@ window.__ModuleLoader__.load({
|
|
|
2082
2247
|
this.notifier.markDirty();
|
|
2083
2248
|
}
|
|
2084
2249
|
buildSnapshot() {
|
|
2250
|
+
const identity = this.projections.values().subagent;
|
|
2085
2251
|
return {
|
|
2086
2252
|
sessionId: this.sessionId,
|
|
2087
2253
|
pendingSubmissions: this.pendingSubmissions,
|
|
2088
2254
|
running: this.running,
|
|
2089
2255
|
subagent: this.address === void 0 ? null : {
|
|
2090
|
-
address: this.address
|
|
2256
|
+
address: this.address.mode === "unknown" && identity != null ? {
|
|
2257
|
+
...this.address,
|
|
2258
|
+
mode: identity.mode
|
|
2259
|
+
} : this.address,
|
|
2091
2260
|
...this.parentAvailable === void 0 ? {} : { parentAvailable: this.parentAvailable }
|
|
2092
2261
|
},
|
|
2093
2262
|
removed: this.removed,
|
|
@@ -2136,15 +2305,21 @@ window.__ModuleLoader__.load({
|
|
|
2136
2305
|
function sessionSeqCursor(value) {
|
|
2137
2306
|
return value === -1 ? -1 : SessionSeq(value);
|
|
2138
2307
|
}
|
|
2139
|
-
function catalogAvailability(parentAvailable) {
|
|
2140
|
-
return parentAvailable === void 0 ? {} : { parentAvailable };
|
|
2141
|
-
}
|
|
2142
2308
|
/** Instance cluster + frame entry + the session list. */
|
|
2143
2309
|
var SessionManager = class {
|
|
2144
2310
|
remote;
|
|
2145
2311
|
sessions = /* @__PURE__ */ new Map();
|
|
2146
2312
|
/** In-flight Session disposals remain here after instances leave `sessions`, so manager disposal can await quiescence. */
|
|
2147
2313
|
sessionDisposals = /* @__PURE__ */ new Set();
|
|
2314
|
+
/**
|
|
2315
|
+
* Accepted/running presentation must survive a later empty-history list
|
|
2316
|
+
* response. Host-asserted running is recorded even before a row, instance, or
|
|
2317
|
+
* address holds the identity — the listing that would hold it may not have
|
|
2318
|
+
* landed yet — while the client-local acceptance callback requires a current
|
|
2319
|
+
* holder, because it can arrive from a replaced or already-dropped Session.
|
|
2320
|
+
*/
|
|
2321
|
+
engagedSessions = /* @__PURE__ */ new Set();
|
|
2322
|
+
disposed = false;
|
|
2148
2323
|
/** Per-session projection value stores, retained independently of instance arrival (the
|
|
2149
2324
|
* title-snapshot precedent, generalized): push frames land here whether or not the Session
|
|
2150
2325
|
* is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
|
|
@@ -2159,18 +2334,8 @@ window.__ModuleLoader__.load({
|
|
|
2159
2334
|
/** Active list request's mutation log; its identity also fences completion after reconnect. */
|
|
2160
2335
|
listMutations = null;
|
|
2161
2336
|
addresses = /* @__PURE__ */ new Map();
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
/** Catalog owners whose membership changed while a pull was in flight: one trailing refresh after it settles. */
|
|
2165
|
-
catalogStale = /* @__PURE__ */ new Set();
|
|
2166
|
-
openCatalogs = /* @__PURE__ */ new Set();
|
|
2167
|
-
catalogDebounce = /* @__PURE__ */ new Map();
|
|
2168
|
-
/**
|
|
2169
|
-
* Background jobs per session, last-wins from Session Controller's control
|
|
2170
|
-
* stream. An empty set is stored as an absent key, so absence and `[]` are
|
|
2171
|
-
* one representation.
|
|
2172
|
-
*/
|
|
2173
|
-
jobsBySession = /* @__PURE__ */ new Map();
|
|
2337
|
+
projectionLoads = /* @__PURE__ */ new Map();
|
|
2338
|
+
projectionInflight = /* @__PURE__ */ new Map();
|
|
2174
2339
|
listSnapshotCache;
|
|
2175
2340
|
/** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
|
|
2176
2341
|
* object when every field matches — wire refreshes mint all-new summary objects, so identity
|
|
@@ -2192,31 +2357,23 @@ window.__ModuleLoader__.load({
|
|
|
2192
2357
|
*/
|
|
2193
2358
|
resolveTarget(target) {
|
|
2194
2359
|
const id = typeof target === "string" ? target : target.childSessionId;
|
|
2195
|
-
const address = typeof target === "string" ? this.
|
|
2360
|
+
const address = typeof target === "string" ? this.subagentAddress(id) : target;
|
|
2196
2361
|
if (typeof target === "string" && !this.sessions.has(id) && !this.summaries.some((summary) => summary.sessionId === id) && address === void 0) throw new Error(`sessions.retain: unknown session ${id}`);
|
|
2197
2362
|
if (address !== void 0) this.addresses.set(id, address);
|
|
2198
|
-
this.sessions.get(id)?.configureSubagent(address, address === void 0 ? void 0 : this.
|
|
2363
|
+
this.sessions.get(id)?.configureSubagent(address, address === void 0 ? void 0 : this.agentAvailable(address.parentSessionId));
|
|
2199
2364
|
return id;
|
|
2200
2365
|
}
|
|
2201
2366
|
/**
|
|
2202
|
-
* Return the durable catalog address retained for one child.
|
|
2203
|
-
* @param sessionId - possible addressed child id.
|
|
2204
|
-
* @returns The direct-parent address, when navigation discovered one.
|
|
2205
|
-
*/
|
|
2206
|
-
subagentAddress(sessionId) {
|
|
2207
|
-
return this.navigationAddress(sessionId);
|
|
2208
|
-
}
|
|
2209
|
-
/**
|
|
2210
2367
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
2211
2368
|
* @param sessionId - possible child id in an already-loaded catalog.
|
|
2212
2369
|
* @returns A retained or catalog-derived direct-parent address.
|
|
2213
2370
|
*/
|
|
2214
|
-
|
|
2371
|
+
subagentAddress(sessionId) {
|
|
2215
2372
|
const retained = this.addresses.get(sessionId);
|
|
2216
2373
|
if (retained !== void 0) return retained;
|
|
2217
|
-
for (const
|
|
2218
|
-
const child =
|
|
2219
|
-
if (child
|
|
2374
|
+
for (const parentSessionId of this.projectionStores.keys()) {
|
|
2375
|
+
const child = this.projectionStores.get(parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId);
|
|
2376
|
+
if (child !== void 0) return {
|
|
2220
2377
|
parentSessionId,
|
|
2221
2378
|
childSessionId: sessionId,
|
|
2222
2379
|
mode: child.mode
|
|
@@ -2234,6 +2391,7 @@ window.__ModuleLoader__.load({
|
|
|
2234
2391
|
if (session !== expected) return Promise.resolve();
|
|
2235
2392
|
this.sessions.delete(sessionId);
|
|
2236
2393
|
this.addresses.delete(sessionId);
|
|
2394
|
+
this.pruneEngagement(sessionId, this.retainedIds(this.summaries));
|
|
2237
2395
|
return this.startSessionDisposal(session);
|
|
2238
2396
|
}
|
|
2239
2397
|
/**
|
|
@@ -2241,10 +2399,14 @@ window.__ModuleLoader__.load({
|
|
|
2241
2399
|
* @returns once catalog requests and every Session stream have stopped.
|
|
2242
2400
|
*/
|
|
2243
2401
|
async dispose() {
|
|
2244
|
-
|
|
2245
|
-
this.
|
|
2246
|
-
this.
|
|
2247
|
-
this.
|
|
2402
|
+
this.disposed = true;
|
|
2403
|
+
this.listMutations = null;
|
|
2404
|
+
this.listInflight = null;
|
|
2405
|
+
this.engagedSessions.clear();
|
|
2406
|
+
const reads = [...this.projectionInflight.values()];
|
|
2407
|
+
for (const { controller } of reads) controller.abort();
|
|
2408
|
+
this.projectionInflight.clear();
|
|
2409
|
+
await Promise.all(reads.map((read) => read.promise));
|
|
2248
2410
|
const sessions = [...this.sessions.values()];
|
|
2249
2411
|
this.sessions.clear();
|
|
2250
2412
|
this.addresses.clear();
|
|
@@ -2267,6 +2429,7 @@ window.__ModuleLoader__.load({
|
|
|
2267
2429
|
/**
|
|
2268
2430
|
* Lazy build: return the existing instance or construct one (no auto-open —
|
|
2269
2431
|
* the reference allocator opens history after binding the scope).
|
|
2432
|
+
* New instances reconcile retained metadata before returning.
|
|
2270
2433
|
* @param sessionId - the session to get.
|
|
2271
2434
|
* @returns the resident instance.
|
|
2272
2435
|
*/
|
|
@@ -2277,42 +2440,71 @@ window.__ModuleLoader__.load({
|
|
|
2277
2440
|
this.sessions.set(sessionId, session);
|
|
2278
2441
|
const summary = this.summaries.find((s) => s.sessionId === sessionId);
|
|
2279
2442
|
if (summary !== void 0) {
|
|
2280
|
-
session.handleBlank(summary
|
|
2443
|
+
session.handleBlank(this.effectiveBlank(summary));
|
|
2281
2444
|
session.handleRunning(summary.running);
|
|
2282
2445
|
} else {
|
|
2283
2446
|
const address = this.addresses.get(sessionId);
|
|
2284
|
-
|
|
2285
|
-
if (child?.kind === "child") {
|
|
2447
|
+
if ((address === void 0 ? void 0 : this.projectionStores.get(address.parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId)) !== void 0) {
|
|
2286
2448
|
session.handleBlank(false);
|
|
2287
|
-
session.handleRunning(
|
|
2288
|
-
}
|
|
2449
|
+
session.handleRunning(false);
|
|
2450
|
+
} else session.handleBlank(true);
|
|
2289
2451
|
}
|
|
2290
2452
|
}
|
|
2291
2453
|
return session;
|
|
2292
2454
|
}
|
|
2293
2455
|
createSession(sessionId) {
|
|
2294
2456
|
const address = this.addresses.get(sessionId);
|
|
2295
|
-
const parentAvailable = address === void 0 ? void 0 : this.
|
|
2457
|
+
const parentAvailable = address === void 0 ? void 0 : this.agentAvailable(address.parentSessionId);
|
|
2296
2458
|
return new Session(sessionId, this.remote, {
|
|
2297
2459
|
...address === void 0 ? {} : {
|
|
2298
2460
|
address,
|
|
2299
|
-
...
|
|
2461
|
+
...parentAvailable === void 0 ? {} : { parentAvailable }
|
|
2300
2462
|
},
|
|
2301
2463
|
onEngaged: (engaged) => {
|
|
2302
|
-
this.
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2464
|
+
if (this.disposed || !this.retainedIds(this.summaries).has(engaged.sessionId)) return;
|
|
2465
|
+
if (!this.engagedSessions.has(engaged.sessionId)) {
|
|
2466
|
+
this.engagedSessions.add(engaged.sessionId);
|
|
2467
|
+
this.recordMutation({
|
|
2468
|
+
kind: "engaged",
|
|
2469
|
+
sessionId: engaged.sessionId
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
this.sessions.get(engaged.sessionId)?.handleBlank(false);
|
|
2306
2473
|
},
|
|
2307
2474
|
projections: this.projectionStore(sessionId)
|
|
2308
2475
|
});
|
|
2309
2476
|
}
|
|
2477
|
+
effectiveBlank(summary) {
|
|
2478
|
+
return summary.blank && !this.engagedSessions.has(summary.sessionId);
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Identities an engagement may still belong to: the given list rows, resident
|
|
2482
|
+
* Session instances, and retained child addresses.
|
|
2483
|
+
* @param summaries - list rows of the caller's snapshot.
|
|
2484
|
+
* @returns the retained identity set.
|
|
2485
|
+
*/
|
|
2486
|
+
retainedIds(summaries) {
|
|
2487
|
+
const retained = new Set(summaries.map((summary) => summary.sessionId));
|
|
2488
|
+
for (const sessionId of this.sessions.keys()) retained.add(sessionId);
|
|
2489
|
+
for (const sessionId of this.addresses.keys()) retained.add(sessionId);
|
|
2490
|
+
return retained;
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Forget one engagement that no retained identity holds.
|
|
2494
|
+
* @param sessionId - identity whose engagement may be dropped.
|
|
2495
|
+
* @param retained - identities from {@link retainedIds} for the caller's snapshot.
|
|
2496
|
+
*/
|
|
2497
|
+
pruneEngagement(sessionId, retained) {
|
|
2498
|
+
if (!retained.has(sessionId)) this.engagedSessions.delete(sessionId);
|
|
2499
|
+
}
|
|
2310
2500
|
/** Resident per-session projection store (create-on-demand; outlives instantiation). */
|
|
2311
2501
|
projectionStore(sessionId) {
|
|
2312
2502
|
let store = this.projectionStores.get(sessionId);
|
|
2313
2503
|
if (store === void 0) {
|
|
2314
2504
|
store = new ProjectionValueStore();
|
|
2505
|
+
const projections = store;
|
|
2315
2506
|
store.subscribeAny(() => {
|
|
2507
|
+
if (projections.values().sessionListMetadata?.blank === false) this.sessions.get(sessionId)?.handleBlank(false);
|
|
2316
2508
|
this.notifier.markDirty();
|
|
2317
2509
|
});
|
|
2318
2510
|
this.projectionStores.set(sessionId, store);
|
|
@@ -2320,82 +2512,67 @@ window.__ModuleLoader__.load({
|
|
|
2320
2512
|
return store;
|
|
2321
2513
|
}
|
|
2322
2514
|
/**
|
|
2323
|
-
*
|
|
2324
|
-
* @param
|
|
2515
|
+
* Load a complete projection baseline once per connection; retry unsuccessful reads.
|
|
2516
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
2517
|
+
* @returns completion of the current or newly started read.
|
|
2325
2518
|
*/
|
|
2326
|
-
|
|
2327
|
-
const existing = this.
|
|
2519
|
+
refreshProjections(sessionId) {
|
|
2520
|
+
const existing = this.projectionInflight.get(sessionId);
|
|
2328
2521
|
if (existing !== void 0) return existing.promise;
|
|
2329
|
-
|
|
2330
|
-
const
|
|
2331
|
-
const
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
...previous?.parentAvailable === void 0 ? {} : { parentAvailable: previous.parentAvailable },
|
|
2522
|
+
if (this.projectionLoads.get(sessionId)?.state === "ready") return Promise.resolve();
|
|
2523
|
+
const controller = new AbortController();
|
|
2524
|
+
const store = this.projectionStore(sessionId);
|
|
2525
|
+
const initialValues = store.values();
|
|
2526
|
+
this.projectionLoads.set(sessionId, {
|
|
2335
2527
|
state: "loading",
|
|
2336
2528
|
error: null
|
|
2337
2529
|
});
|
|
2338
2530
|
this.notifier.markDirty();
|
|
2339
2531
|
const operation = (async () => {
|
|
2340
2532
|
try {
|
|
2341
|
-
const result = await this.remote.
|
|
2533
|
+
const result = await this.remote.session.projections({ sessionId }, controller.signal);
|
|
2534
|
+
if (controller.signal.aborted) return;
|
|
2342
2535
|
if (result.ok) {
|
|
2343
|
-
|
|
2344
|
-
this.catalogs.set(parentSessionId, {
|
|
2536
|
+
if (result.value !== null) store.seed({
|
|
2345
2537
|
...result.value,
|
|
2346
|
-
|
|
2347
|
-
|
|
2538
|
+
asOfSeq: sessionSeqCursor(result.value.asOfSeq)
|
|
2539
|
+
});
|
|
2540
|
+
else if (store.values() === initialValues) store.clear();
|
|
2541
|
+
this.projectionLoads.set(sessionId, {
|
|
2348
2542
|
state: "ready",
|
|
2349
2543
|
error: null
|
|
2350
2544
|
});
|
|
2351
|
-
|
|
2352
|
-
if (address.parentSessionId !== parentSessionId) continue;
|
|
2353
|
-
this.sessions.get(childId)?.handleSubagentParentAvailable(parentAvailable);
|
|
2354
|
-
}
|
|
2355
|
-
} else this.catalogs.set(parentSessionId, {
|
|
2356
|
-
entries: this.withCatalogMutations(previous?.entries ?? [], expandableRows, activityRows),
|
|
2357
|
-
...catalogAvailability(this.catalogInflight.get(parentSessionId)?.parentAvailableOverride ?? previous?.parentAvailable),
|
|
2545
|
+
} else this.projectionLoads.set(sessionId, {
|
|
2358
2546
|
state: "error",
|
|
2359
2547
|
error: result.error
|
|
2360
2548
|
});
|
|
2361
2549
|
} catch (error) {
|
|
2550
|
+
if (controller.signal.aborted) return;
|
|
2362
2551
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) throw error;
|
|
2363
|
-
this.
|
|
2364
|
-
entries: this.withCatalogMutations(previous?.entries ?? [], expandableRows, activityRows),
|
|
2365
|
-
...catalogAvailability(this.catalogInflight.get(parentSessionId)?.parentAvailableOverride ?? previous?.parentAvailable),
|
|
2552
|
+
this.projectionLoads.set(sessionId, {
|
|
2366
2553
|
state: "error",
|
|
2367
2554
|
error
|
|
2368
2555
|
});
|
|
2369
2556
|
} finally {
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2557
|
+
if (!controller.signal.aborted) {
|
|
2558
|
+
this.projectionInflight.delete(sessionId);
|
|
2559
|
+
this.notifier.markDirty();
|
|
2560
|
+
}
|
|
2373
2561
|
}
|
|
2374
2562
|
})();
|
|
2375
|
-
this.
|
|
2563
|
+
this.projectionInflight.set(sessionId, {
|
|
2376
2564
|
promise: operation,
|
|
2377
|
-
|
|
2378
|
-
activityRows,
|
|
2379
|
-
parentAvailableOverride: void 0
|
|
2565
|
+
controller
|
|
2380
2566
|
});
|
|
2381
2567
|
return operation;
|
|
2382
2568
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
this.openCatalogs.add(parentSessionId);
|
|
2391
|
-
this.refreshSubagents(parentSessionId);
|
|
2392
|
-
} else {
|
|
2393
|
-
this.openCatalogs.delete(parentSessionId);
|
|
2394
|
-
const timer = this.catalogDebounce.get(parentSessionId);
|
|
2395
|
-
if (timer !== void 0) {
|
|
2396
|
-
clearTimeout(timer);
|
|
2397
|
-
this.catalogDebounce.delete(parentSessionId);
|
|
2398
|
-
}
|
|
2569
|
+
agentAvailable(sessionId) {
|
|
2570
|
+
return this.summaries.find((summary) => summary.sessionId === sessionId)?.agentAvailable ?? (this.listPhase === "ready" ? false : void 0);
|
|
2571
|
+
}
|
|
2572
|
+
updateParentAvailability() {
|
|
2573
|
+
for (const [childId, address] of this.addresses) {
|
|
2574
|
+
const available = this.agentAvailable(address.parentSessionId);
|
|
2575
|
+
if (available !== void 0) this.sessions.get(childId)?.handleSubagentParentAvailable(available);
|
|
2399
2576
|
}
|
|
2400
2577
|
}
|
|
2401
2578
|
/** Full refresh via session.list (single-flight within one Host generation). */
|
|
@@ -2413,22 +2590,23 @@ window.__ModuleLoader__.load({
|
|
|
2413
2590
|
if (this.listMutations !== mutations) return;
|
|
2414
2591
|
if (result.ok) {
|
|
2415
2592
|
const baseline = this.listPhase === "pending" ? [...result.value.items] : mergeOrderedBaseline(established, result.value.items, (summary) => summary.sessionId);
|
|
2416
|
-
|
|
2593
|
+
const removedSincePull = /* @__PURE__ */ new Set();
|
|
2594
|
+
for (const mutation of mutations) if (mutation.kind === "remove") removedSincePull.add(mutation.sessionId);
|
|
2595
|
+
for (const s of baseline) if (s.running && !removedSincePull.has(s.sessionId)) this.engagedSessions.add(s.sessionId);
|
|
2596
|
+
const summaries = mutations.reduce(applyMutation, baseline);
|
|
2597
|
+
this.summaries = summaries;
|
|
2598
|
+
const retained = this.retainedIds(summaries);
|
|
2599
|
+
for (const sessionId of this.engagedSessions) this.pruneEngagement(sessionId, retained);
|
|
2417
2600
|
this.listState = "idle";
|
|
2418
2601
|
this.listPhase = "ready";
|
|
2602
|
+
this.updateParentAvailability();
|
|
2419
2603
|
for (const s of this.summaries) {
|
|
2420
2604
|
const session = this.sessions.get(s.sessionId);
|
|
2421
2605
|
if (session === void 0) continue;
|
|
2422
|
-
session.handleBlank(s
|
|
2606
|
+
session.handleBlank(this.effectiveBlank(s));
|
|
2423
2607
|
session.handleRunning(s.running);
|
|
2424
2608
|
}
|
|
2425
|
-
for (const s of result.value.items)
|
|
2426
|
-
const block = s.projections;
|
|
2427
|
-
if (block === void 0) continue;
|
|
2428
|
-
const store = this.projectionStore(s.sessionId);
|
|
2429
|
-
const values = block.values;
|
|
2430
|
-
for (const key of Object.keys(values)) store.apply(key, values[key], sessionSeqCursor(block.asOfSeq));
|
|
2431
|
-
}
|
|
2609
|
+
for (const s of result.value.items) if (s.projections !== void 0) this.applyListBlock(s.sessionId, s.projections);
|
|
2432
2610
|
} else {
|
|
2433
2611
|
this.listState = "error";
|
|
2434
2612
|
this.listError = result.error;
|
|
@@ -2484,8 +2662,9 @@ window.__ModuleLoader__.load({
|
|
|
2484
2662
|
};
|
|
2485
2663
|
const result = await this.remote.session.create(payload);
|
|
2486
2664
|
if (result.ok) this.recordMutation({
|
|
2487
|
-
kind: "
|
|
2665
|
+
kind: "placeholder",
|
|
2488
2666
|
summary: {
|
|
2667
|
+
agentAvailable: true,
|
|
2489
2668
|
sessionId: result.value.sessionId,
|
|
2490
2669
|
updatedAt: Date.now(),
|
|
2491
2670
|
running: false,
|
|
@@ -2496,8 +2675,9 @@ window.__ModuleLoader__.load({
|
|
|
2496
2675
|
else {
|
|
2497
2676
|
const publishedSessionId = workspaceAttachSessionId(result.error);
|
|
2498
2677
|
if (publishedSessionId !== void 0) this.recordMutation({
|
|
2499
|
-
kind: "
|
|
2678
|
+
kind: "placeholder",
|
|
2500
2679
|
summary: {
|
|
2680
|
+
agentAvailable: true,
|
|
2501
2681
|
sessionId: publishedSessionId,
|
|
2502
2682
|
updatedAt: Date.now(),
|
|
2503
2683
|
running: false,
|
|
@@ -2509,11 +2689,12 @@ window.__ModuleLoader__.load({
|
|
|
2509
2689
|
}
|
|
2510
2690
|
/**
|
|
2511
2691
|
* Contract session.fork; on success merge the child into summaries
|
|
2512
|
-
* immediately (same synchronous-addressability guarantee as create).
|
|
2513
|
-
*
|
|
2514
|
-
*
|
|
2515
|
-
*
|
|
2516
|
-
*
|
|
2692
|
+
* immediately (same synchronous-addressability guarantee as create).
|
|
2693
|
+
* Blankness starts provisionally true so the authoritative Host summary can
|
|
2694
|
+
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
2695
|
+
* lineage rides parentSessionId. A child published before Workspace
|
|
2696
|
+
* attachment fails is also reconciled into the list.
|
|
2697
|
+
* @param opts - source session and the optional exact inclusive boundary seq.
|
|
2517
2698
|
* @returns the fork result (the child session id).
|
|
2518
2699
|
*/
|
|
2519
2700
|
async fork(opts) {
|
|
@@ -2524,12 +2705,13 @@ window.__ModuleLoader__.load({
|
|
|
2524
2705
|
});
|
|
2525
2706
|
const childId = result.ok ? result.value.sessionId : workspaceAttachSessionId(result.error);
|
|
2526
2707
|
if (childId !== void 0) this.recordMutation({
|
|
2527
|
-
kind: "
|
|
2708
|
+
kind: "placeholder",
|
|
2528
2709
|
summary: {
|
|
2710
|
+
agentAvailable: true,
|
|
2529
2711
|
sessionId: childId,
|
|
2530
2712
|
updatedAt: Date.now(),
|
|
2531
2713
|
running: false,
|
|
2532
|
-
blank:
|
|
2714
|
+
blank: true,
|
|
2533
2715
|
parentSessionId: opts.sessionId,
|
|
2534
2716
|
...source?.cwd !== void 0 ? { cwd: source.cwd } : {}
|
|
2535
2717
|
}
|
|
@@ -2537,19 +2719,33 @@ window.__ModuleLoader__.load({
|
|
|
2537
2719
|
return result;
|
|
2538
2720
|
}
|
|
2539
2721
|
/**
|
|
2540
|
-
*
|
|
2541
|
-
*
|
|
2542
|
-
*
|
|
2543
|
-
*
|
|
2722
|
+
* Rename a Session and update its title projection without opening its history.
|
|
2723
|
+
* @param sessionId - Session to rename.
|
|
2724
|
+
* @param title - raw title text for Host normalization.
|
|
2725
|
+
* @returns the accepted title and event position, or the Remote failure.
|
|
2726
|
+
*/
|
|
2727
|
+
async rename(sessionId, title) {
|
|
2728
|
+
const result = await this.remote.session.rename({
|
|
2729
|
+
sessionId,
|
|
2730
|
+
title
|
|
2731
|
+
});
|
|
2732
|
+
if (result.ok) this.projectionStore(sessionId).apply("title", result.value.title, SessionSeq(result.value.seq));
|
|
2733
|
+
return result;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Merge a Host summary, replacing live state and filling missing metadata.
|
|
2737
|
+
* Local create/fork placeholders only fill metadata on an existing row.
|
|
2544
2738
|
*/
|
|
2545
2739
|
mergeSummary(summary) {
|
|
2546
2740
|
this.recordMutation({
|
|
2547
2741
|
kind: "upsert",
|
|
2548
2742
|
summary
|
|
2549
2743
|
});
|
|
2744
|
+
this.updateParentAvailability();
|
|
2550
2745
|
}
|
|
2551
2746
|
/** Apply immediately and retain for replay when a list response is in flight. */
|
|
2552
2747
|
recordMutation(mutation) {
|
|
2748
|
+
if (this.disposed) return;
|
|
2553
2749
|
this.listMutations?.push(mutation);
|
|
2554
2750
|
this.summaries = applyMutation(this.summaries, mutation);
|
|
2555
2751
|
this.notifier.markDirty();
|
|
@@ -2587,18 +2783,10 @@ window.__ModuleLoader__.load({
|
|
|
2587
2783
|
this.replaceControlBaseline(frame.value);
|
|
2588
2784
|
return;
|
|
2589
2785
|
}
|
|
2590
|
-
|
|
2591
|
-
this.projectionStore(frame.sessionId).apply(frame.key, frame.value, SessionSeq(frame.seq));
|
|
2592
|
-
this.notifier.markDirty();
|
|
2593
|
-
return;
|
|
2594
|
-
}
|
|
2595
|
-
if (frame.jobs.length === 0) this.jobsBySession.delete(frame.sessionId);
|
|
2596
|
-
else this.jobsBySession.set(frame.sessionId, frame.jobs);
|
|
2786
|
+
this.projectionStore(frame.sessionId).apply(frame.key, frame.value, SessionSeq(frame.seq));
|
|
2597
2787
|
this.notifier.markDirty();
|
|
2598
2788
|
}
|
|
2599
2789
|
replaceControlBaseline(baseline) {
|
|
2600
|
-
this.jobsBySession.clear();
|
|
2601
|
-
for (const [sessionId, jobs] of Object.entries(baseline.jobs)) if (jobs.length > 0) this.jobsBySession.set(sessionId, jobs);
|
|
2602
2790
|
for (const [sessionId, block] of Object.entries(baseline.projections)) {
|
|
2603
2791
|
const store = this.projectionStore(sessionId);
|
|
2604
2792
|
const asOfSeq = sessionSeqCursor(block.asOfSeq);
|
|
@@ -2615,44 +2803,55 @@ window.__ModuleLoader__.load({
|
|
|
2615
2803
|
*/
|
|
2616
2804
|
handleSessionAdded(summary) {
|
|
2617
2805
|
this.mergeSummary(summary);
|
|
2618
|
-
this.
|
|
2619
|
-
|
|
2620
|
-
if (projections !== void 0)
|
|
2621
|
-
|
|
2622
|
-
|
|
2806
|
+
if (!this.disposed && summary.running) this.engagedSessions.add(summary.sessionId);
|
|
2807
|
+
this.sessions.get(summary.sessionId)?.handleBlank(this.effectiveBlank(summary));
|
|
2808
|
+
if (summary.projections !== void 0) this.applyListBlock(summary.sessionId, summary.projections);
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* Merge one list-surface projection block by the sequence space it declares.
|
|
2812
|
+
* A `sequenced` block came from the Host's live registry for an attached
|
|
2813
|
+
* Session, so each key lands under higher-seq-wins against that Session's
|
|
2814
|
+
* baselines and frames. A `cached` block was viewed from the persisted
|
|
2815
|
+
* checkpoint by a header-only listing: its watermark is not comparable with
|
|
2816
|
+
* this connection's seqs, so it only fills keys no sequenced row holds.
|
|
2817
|
+
*/
|
|
2818
|
+
applyListBlock(sessionId, block) {
|
|
2819
|
+
const store = this.projectionStore(sessionId);
|
|
2820
|
+
switch (block.kind) {
|
|
2821
|
+
case "sequenced": {
|
|
2822
|
+
const seq = sessionSeqCursor(block.asOfSeq);
|
|
2823
|
+
for (const [key, value] of Object.entries(block.values)) store.apply(key, value, seq);
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
case "cached":
|
|
2827
|
+
store.applyCached(block.values);
|
|
2828
|
+
return;
|
|
2829
|
+
default: assertNever(block.kind, "session list projection block kind");
|
|
2623
2830
|
}
|
|
2624
|
-
if (summary.origin === "subagent" && summary.parentSessionId !== void 0) this.markCatalogParentExpandable(summary.parentSessionId);
|
|
2625
|
-
if (summary.parentSessionId !== void 0 && this.openCatalogs.has(summary.parentSessionId)) this.scheduleCatalogRefresh(summary.parentSessionId);
|
|
2626
2831
|
}
|
|
2627
2832
|
/**
|
|
2628
2833
|
* Apply one Session removal forwarded through `ctx.remote.$on`.
|
|
2629
2834
|
* @param sessionId - removed Session identity.
|
|
2630
2835
|
*/
|
|
2631
2836
|
handleSessionRemoved(sessionId) {
|
|
2632
|
-
const durableSubagent = this.summaries.
|
|
2837
|
+
const durableSubagent = this.subagentAddress(sessionId) !== void 0 || this.summaries.some((summary) => summary.sessionId === sessionId && summary.origin === "subagent");
|
|
2633
2838
|
this.recordMutation(durableSubagent ? {
|
|
2634
2839
|
kind: "status",
|
|
2635
2840
|
sessionId,
|
|
2636
|
-
running: false
|
|
2841
|
+
running: false,
|
|
2842
|
+
agentAvailable: false
|
|
2637
2843
|
} : {
|
|
2638
2844
|
kind: "remove",
|
|
2639
2845
|
sessionId
|
|
2640
2846
|
});
|
|
2641
|
-
this.updateCatalogActivity(sessionId, false);
|
|
2642
2847
|
if (durableSubagent) this.sessions.get(sessionId)?.handleRunning(false);
|
|
2643
2848
|
else this.sessions.get(sessionId)?.handleRemoved();
|
|
2644
|
-
this.
|
|
2645
|
-
if (!durableSubagent) this.projectionStores.delete(sessionId);
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
}
|
|
2651
|
-
const ownedCatalog = this.catalogs.get(sessionId);
|
|
2652
|
-
if (ownedCatalog !== void 0 && ownedCatalog.parentAvailable) this.catalogs.set(sessionId, {
|
|
2653
|
-
...ownedCatalog,
|
|
2654
|
-
parentAvailable: false
|
|
2655
|
-
});
|
|
2849
|
+
const catalog = this.projectionStores.get(sessionId)?.values().subagentCatalog;
|
|
2850
|
+
if (!durableSubagent && (catalog === void 0 || catalog.length === 0)) this.projectionStores.delete(sessionId);
|
|
2851
|
+
this.pruneEngagement(sessionId, this.retainedIds(this.summaries));
|
|
2852
|
+
this.projectionInflight.get(sessionId)?.controller.abort();
|
|
2853
|
+
this.projectionInflight.delete(sessionId);
|
|
2854
|
+
this.projectionLoads.delete(sessionId);
|
|
2656
2855
|
for (const [childId, address] of this.addresses) if (address.parentSessionId === sessionId) this.sessions.get(childId)?.handleSubagentParentAvailable(false);
|
|
2657
2856
|
}
|
|
2658
2857
|
/**
|
|
@@ -2661,13 +2860,15 @@ window.__ModuleLoader__.load({
|
|
|
2661
2860
|
* @param running - current Agent running state.
|
|
2662
2861
|
*/
|
|
2663
2862
|
handleSessionStatus(sessionId, running) {
|
|
2863
|
+
if (!this.disposed && running) this.engagedSessions.add(sessionId);
|
|
2664
2864
|
this.recordMutation({
|
|
2665
2865
|
kind: "status",
|
|
2666
2866
|
sessionId,
|
|
2667
|
-
running
|
|
2867
|
+
running,
|
|
2868
|
+
agentAvailable: true
|
|
2668
2869
|
});
|
|
2870
|
+
this.updateParentAvailability();
|
|
2669
2871
|
this.sessions.get(sessionId)?.handleRunning(running);
|
|
2670
|
-
this.updateCatalogActivity(sessionId, running);
|
|
2671
2872
|
}
|
|
2672
2873
|
/**
|
|
2673
2874
|
* Advance Session-list activity from one user-authored durable message.
|
|
@@ -2700,93 +2901,26 @@ window.__ModuleLoader__.load({
|
|
|
2700
2901
|
this.listMutations = null;
|
|
2701
2902
|
this.listInflight = null;
|
|
2702
2903
|
this.refreshList();
|
|
2703
|
-
const parents = new Set(this.
|
|
2904
|
+
const parents = new Set(this.projectionLoads.keys());
|
|
2704
2905
|
for (const id of this.sessions.keys()) {
|
|
2705
2906
|
const address = this.addresses.get(id);
|
|
2706
2907
|
if (address !== void 0) parents.add(address.parentSessionId);
|
|
2707
2908
|
}
|
|
2708
|
-
for (const
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
if (this.catalogDebounce.has(parentSessionId)) return;
|
|
2713
|
-
const timer = setTimeout(() => {
|
|
2714
|
-
this.catalogDebounce.delete(parentSessionId);
|
|
2715
|
-
if (this.catalogInflight.has(parentSessionId)) {
|
|
2716
|
-
this.catalogStale.add(parentSessionId);
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
|
-
this.refreshSubagents(parentSessionId);
|
|
2720
|
-
}, 50);
|
|
2721
|
-
this.catalogDebounce.set(parentSessionId, timer);
|
|
2722
|
-
}
|
|
2723
|
-
/** Apply one Agent-driver transition to loaded and in-flight catalogs. */
|
|
2724
|
-
updateCatalogActivity(childSessionId, running) {
|
|
2725
|
-
const activity = running ? "running" : "inactive";
|
|
2726
|
-
for (const inflight of this.catalogInflight.values()) inflight.activityRows.set(childSessionId, activity);
|
|
2727
|
-
let changed = false;
|
|
2728
|
-
for (const [parentSessionId, catalog] of this.catalogs) {
|
|
2729
|
-
if (!catalog.entries.some((entry) => entry.kind === "child" && entry.id === childSessionId && entry.activity !== activity)) continue;
|
|
2730
|
-
const entries = catalog.entries.map((entry) => {
|
|
2731
|
-
if (entry.kind !== "child" || entry.id !== childSessionId) return entry;
|
|
2732
|
-
return {
|
|
2733
|
-
...entry,
|
|
2734
|
-
activity
|
|
2735
|
-
};
|
|
2736
|
-
});
|
|
2737
|
-
changed = true;
|
|
2738
|
-
this.catalogs.set(parentSessionId, {
|
|
2739
|
-
...catalog,
|
|
2740
|
-
entries
|
|
2741
|
-
});
|
|
2742
|
-
}
|
|
2743
|
-
if (changed) this.notifier.markDirty();
|
|
2744
|
-
}
|
|
2745
|
-
/** Preserve and project a positive expandability hint after one direct subagent publishes. */
|
|
2746
|
-
markCatalogParentExpandable(parentSessionId) {
|
|
2747
|
-
this.applyCatalogParentExpandable(parentSessionId);
|
|
2748
|
-
for (const inflight of this.catalogInflight.values()) inflight.expandableRows.add(parentSessionId);
|
|
2749
|
-
}
|
|
2750
|
-
/** Apply one positive expandability hint to every loaded catalog containing that unique row id. */
|
|
2751
|
-
applyCatalogParentExpandable(parentSessionId) {
|
|
2752
|
-
let changed = false;
|
|
2753
|
-
for (const [catalogParentId, catalog] of this.catalogs) {
|
|
2754
|
-
if (!catalog.entries.some((entry) => entry.kind === "child" && entry.id === parentSessionId && !entry.hasChildren)) continue;
|
|
2755
|
-
const entries = catalog.entries.map((entry) => {
|
|
2756
|
-
if (entry.kind !== "child" || entry.id !== parentSessionId || entry.hasChildren) return entry;
|
|
2757
|
-
return {
|
|
2758
|
-
...entry,
|
|
2759
|
-
hasChildren: true
|
|
2760
|
-
};
|
|
2761
|
-
});
|
|
2762
|
-
changed = true;
|
|
2763
|
-
this.catalogs.set(catalogParentId, {
|
|
2764
|
-
...catalog,
|
|
2765
|
-
entries
|
|
2766
|
-
});
|
|
2767
|
-
}
|
|
2768
|
-
if (changed) this.notifier.markDirty();
|
|
2769
|
-
}
|
|
2770
|
-
/** Fold request-local row mutations into one catalog result before publication. */
|
|
2771
|
-
withCatalogMutations(entries, expandableRows, activityRows) {
|
|
2772
|
-
return entries.map((entry) => {
|
|
2773
|
-
if (entry.kind !== "child") return entry;
|
|
2774
|
-
const activity = activityRows.get(entry.id);
|
|
2775
|
-
if (!expandableRows.has(entry.id) && activity === void 0) return entry;
|
|
2776
|
-
return {
|
|
2777
|
-
...entry,
|
|
2778
|
-
...expandableRows.has(entry.id) ? { hasChildren: true } : {},
|
|
2779
|
-
...activity === void 0 ? {} : { activity }
|
|
2780
|
-
};
|
|
2781
|
-
});
|
|
2909
|
+
for (const { controller } of this.projectionInflight.values()) controller.abort();
|
|
2910
|
+
this.projectionInflight.clear();
|
|
2911
|
+
this.projectionLoads.clear();
|
|
2912
|
+
for (const parentSessionId of parents) this.refreshProjections(parentSessionId);
|
|
2782
2913
|
}
|
|
2783
2914
|
buildListSnapshot() {
|
|
2784
2915
|
const items = flattenLineage(this.summaries.map((summary) => {
|
|
2785
2916
|
const projectionStore = this.projectionStores.get(summary.sessionId);
|
|
2786
2917
|
const title = projectionStore?.get("title");
|
|
2787
2918
|
const projectionValues = projectionStore?.values();
|
|
2919
|
+
const metadata = projectionValues?.sessionListMetadata;
|
|
2788
2920
|
return {
|
|
2789
2921
|
...summary,
|
|
2922
|
+
blank: this.effectiveBlank(summary) && metadata?.blank !== false,
|
|
2923
|
+
updatedAt: Math.max(summary.updatedAt, metadata?.lastPromptAt ?? 0),
|
|
2790
2924
|
...typeof title === "string" && title !== "" ? { title } : {},
|
|
2791
2925
|
...projectionValues === void 0 ? {} : { projectionValues }
|
|
2792
2926
|
};
|
|
@@ -2804,31 +2938,41 @@ window.__ModuleLoader__.load({
|
|
|
2804
2938
|
state: this.listState,
|
|
2805
2939
|
phase: this.listPhase,
|
|
2806
2940
|
error: this.listError,
|
|
2807
|
-
|
|
2808
|
-
|
|
2941
|
+
projectionsBySession: Object.fromEntries([...this.projectionStores].map(([sessionId, store]) => [sessionId, {
|
|
2942
|
+
values: store.values(),
|
|
2943
|
+
state: "idle",
|
|
2944
|
+
error: null,
|
|
2945
|
+
...this.projectionLoads.get(sessionId)
|
|
2946
|
+
}]))
|
|
2809
2947
|
};
|
|
2810
2948
|
}
|
|
2811
2949
|
};
|
|
2812
2950
|
/** Apply one list mutation without deriving display order. */
|
|
2813
2951
|
function applyMutation(summaries, mutation) {
|
|
2814
2952
|
switch (mutation.kind) {
|
|
2815
|
-
case "upsert":
|
|
2953
|
+
case "upsert":
|
|
2954
|
+
case "placeholder": {
|
|
2816
2955
|
const existing = summaries.find((summary) => summary.sessionId === mutation.summary.sessionId);
|
|
2817
2956
|
if (existing === void 0) return [mutation.summary, ...summaries];
|
|
2818
2957
|
const filled = {
|
|
2819
2958
|
...existing,
|
|
2820
2959
|
blank: existing.blank && mutation.summary.blank,
|
|
2960
|
+
...mutation.kind === "upsert" ? {
|
|
2961
|
+
agentAvailable: mutation.summary.agentAvailable,
|
|
2962
|
+
running: mutation.summary.running
|
|
2963
|
+
} : {},
|
|
2821
2964
|
...existing.cwd === void 0 && mutation.summary.cwd !== void 0 ? { cwd: mutation.summary.cwd } : {},
|
|
2822
2965
|
...existing.parentSessionId === void 0 && mutation.summary.parentSessionId !== void 0 ? { parentSessionId: mutation.summary.parentSessionId } : {},
|
|
2823
2966
|
...existing.origin === void 0 && mutation.summary.origin !== void 0 ? { origin: mutation.summary.origin } : {}
|
|
2824
2967
|
};
|
|
2825
|
-
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId && filled.origin === existing.origin && filled.blank === existing.blank) return [...summaries];
|
|
2968
|
+
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId && filled.origin === existing.origin && filled.blank === existing.blank && filled.agentAvailable === existing.agentAvailable && filled.running === existing.running) return [...summaries];
|
|
2826
2969
|
return summaries.map((summary) => summary.sessionId === mutation.summary.sessionId ? filled : summary);
|
|
2827
2970
|
}
|
|
2828
2971
|
case "remove": return summaries.filter((summary) => summary.sessionId !== mutation.sessionId);
|
|
2829
|
-
case "status": return summaries.map((summary) => summary.sessionId === mutation.sessionId && (summary.running !== mutation.running || mutation.running && summary.blank) ? {
|
|
2972
|
+
case "status": return summaries.map((summary) => summary.sessionId === mutation.sessionId && (summary.running !== mutation.running || summary.agentAvailable !== mutation.agentAvailable || mutation.running && summary.blank) ? {
|
|
2830
2973
|
...summary,
|
|
2831
2974
|
running: mutation.running,
|
|
2975
|
+
agentAvailable: mutation.agentAvailable,
|
|
2832
2976
|
blank: summary.blank && !mutation.running
|
|
2833
2977
|
} : summary);
|
|
2834
2978
|
case "activity": return summaries.map((summary) => summary.sessionId === mutation.sessionId && mutation.updatedAt > summary.updatedAt ? {
|
|
@@ -2999,8 +3143,7 @@ window.__ModuleLoader__.load({
|
|
|
2999
3143
|
ids: [],
|
|
3000
3144
|
byId: {},
|
|
3001
3145
|
phase: "pending",
|
|
3002
|
-
|
|
3003
|
-
jobsBySession: {}
|
|
3146
|
+
projectionsBySession: {}
|
|
3004
3147
|
});
|
|
3005
3148
|
const disposeManagerProjection = this.manager.subscribe(() => {
|
|
3006
3149
|
this.projectList();
|
|
@@ -3072,25 +3215,17 @@ window.__ModuleLoader__.load({
|
|
|
3072
3215
|
* Resolve an already discovered direct-parent address without opening it.
|
|
3073
3216
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
3074
3217
|
* @param id - possible addressed child id.
|
|
3075
|
-
* @returns
|
|
3218
|
+
* @returns A retained or loaded-catalog address, without retaining a new selection or scope.
|
|
3076
3219
|
*/
|
|
3077
3220
|
subagentAddress(id) {
|
|
3078
3221
|
return this.manager.subagentAddress(id);
|
|
3079
3222
|
}
|
|
3080
3223
|
/**
|
|
3081
|
-
*
|
|
3082
|
-
* @param
|
|
3083
|
-
* @param open - menu state.
|
|
3224
|
+
* Load all Session projections once per connection; retry an unsuccessful initial read.
|
|
3225
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
3084
3226
|
*/
|
|
3085
|
-
|
|
3086
|
-
this.manager.
|
|
3087
|
-
}
|
|
3088
|
-
/**
|
|
3089
|
-
* Refresh one direct-child catalog.
|
|
3090
|
-
* @param parentSessionId - catalog owner.
|
|
3091
|
-
*/
|
|
3092
|
-
refreshSubagents(parentSessionId) {
|
|
3093
|
-
return this.manager.refreshSubagents(parentSessionId);
|
|
3227
|
+
refreshProjections(sessionId) {
|
|
3228
|
+
return this.manager.refreshProjections(sessionId);
|
|
3094
3229
|
}
|
|
3095
3230
|
/**
|
|
3096
3231
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
@@ -3169,15 +3304,14 @@ window.__ModuleLoader__.load({
|
|
|
3169
3304
|
return result.value.sessionId;
|
|
3170
3305
|
}
|
|
3171
3306
|
/**
|
|
3172
|
-
* Fork a
|
|
3173
|
-
*
|
|
3174
|
-
*
|
|
3175
|
-
*
|
|
3176
|
-
*
|
|
3177
|
-
*
|
|
3178
|
-
*
|
|
3179
|
-
*
|
|
3180
|
-
* wire takes integers only.
|
|
3307
|
+
* Fork a session from an exact inclusive prefix of the source (same
|
|
3308
|
+
* synchronous-addressability guarantee as {@link ClientSessions.create}:
|
|
3309
|
+
* on resolution the child is catalogued and may be explicitly retained).
|
|
3310
|
+
* @param opts - source session id, the optional exact inclusive boundary
|
|
3311
|
+
* seq (a real event seq the caller already knows; a cut inside an open
|
|
3312
|
+
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
3313
|
+
* the latest completed-turn prefix), and whether to increment an
|
|
3314
|
+
* inherited durable title before resolving.
|
|
3181
3315
|
* @returns the child session id.
|
|
3182
3316
|
* @throws {SessionForkError} with the source id.
|
|
3183
3317
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -3186,20 +3320,14 @@ window.__ModuleLoader__.load({
|
|
|
3186
3320
|
const sourceTitle = opts.increaseTitle ? this.list.getSnapshot().byId[opts.sessionId]?.title : void 0;
|
|
3187
3321
|
const result = await this.manager.fork({
|
|
3188
3322
|
sessionId: opts.sessionId,
|
|
3189
|
-
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(
|
|
3323
|
+
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(opts.atSeq) }
|
|
3190
3324
|
});
|
|
3191
3325
|
if (!result.ok) throw new SessionForkError(result.error, opts.sessionId);
|
|
3192
3326
|
this.projectList();
|
|
3193
3327
|
const childId = result.value.sessionId;
|
|
3194
3328
|
if (sourceTitle !== void 0) {
|
|
3195
|
-
const
|
|
3196
|
-
|
|
3197
|
-
await reference.ready;
|
|
3198
|
-
const renamed = await reference.binding.session.rename(increasedForkTitle(sourceTitle));
|
|
3199
|
-
if (!renamed.ok) throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
3200
|
-
} finally {
|
|
3201
|
-
reference.release();
|
|
3202
|
-
}
|
|
3329
|
+
const renamed = await this.manager.rename(childId, increasedForkTitle(sourceTitle));
|
|
3330
|
+
if (!renamed.ok) throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
3203
3331
|
}
|
|
3204
3332
|
return childId;
|
|
3205
3333
|
}
|
|
@@ -3279,7 +3407,7 @@ window.__ModuleLoader__.load({
|
|
|
3279
3407
|
if (count === 0) this.retireScope(id, record);
|
|
3280
3408
|
else this.publishRetention(id);
|
|
3281
3409
|
});
|
|
3282
|
-
if (this.list.getSnapshot().byId[id] === void 0) this.projectList();
|
|
3410
|
+
if (this.list.getSnapshot().byId[id] === void 0 && this.manager.subagentAddress(id) !== void 0) this.projectList();
|
|
3283
3411
|
this.publishRetention(id);
|
|
3284
3412
|
return reference;
|
|
3285
3413
|
}
|
|
@@ -3343,7 +3471,7 @@ window.__ModuleLoader__.load({
|
|
|
3343
3471
|
/** Project the manager's list snapshot into the store (title derivation is display-only). */
|
|
3344
3472
|
projectList() {
|
|
3345
3473
|
const previousById = this.list.getSnapshot().byId;
|
|
3346
|
-
const { items, phase,
|
|
3474
|
+
const { items, phase, projectionsBySession } = this.manager.getListSnapshot();
|
|
3347
3475
|
const ids = [];
|
|
3348
3476
|
const byId = {};
|
|
3349
3477
|
for (const entry of items) {
|
|
@@ -3362,8 +3490,7 @@ window.__ModuleLoader__.load({
|
|
|
3362
3490
|
...entry.origin !== void 0 ? { origin: entry.origin } : {}
|
|
3363
3491
|
};
|
|
3364
3492
|
}
|
|
3365
|
-
for (const [parentId,
|
|
3366
|
-
if (child.kind !== "child") continue;
|
|
3493
|
+
for (const [parentId, projection] of Object.entries(projectionsBySession)) for (const child of projection.values.subagentCatalog ?? []) {
|
|
3367
3494
|
const childId = child.id;
|
|
3368
3495
|
const summary = byId[childId];
|
|
3369
3496
|
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
|
@@ -3375,7 +3502,7 @@ window.__ModuleLoader__.load({
|
|
|
3375
3502
|
displayTitle,
|
|
3376
3503
|
parentId,
|
|
3377
3504
|
origin: "subagent",
|
|
3378
|
-
running:
|
|
3505
|
+
running: this.scopes.get(childId)?.session.getSnapshot().running ?? false,
|
|
3379
3506
|
blank: false,
|
|
3380
3507
|
updatedAt: 0,
|
|
3381
3508
|
retainedBy: this.retentionSnapshot(childId).retainedBy,
|
|
@@ -3390,9 +3517,13 @@ window.__ModuleLoader__.load({
|
|
|
3390
3517
|
}
|
|
3391
3518
|
for (const [id, record] of this.scopes) {
|
|
3392
3519
|
if (byId[id] !== void 0) continue;
|
|
3520
|
+
const address = this.manager.subagentAddress(id);
|
|
3521
|
+
if (address === void 0) continue;
|
|
3393
3522
|
const previous = previousById[id];
|
|
3394
3523
|
const snapshot = record.session.getSnapshot();
|
|
3395
|
-
const
|
|
3524
|
+
const projectionValues = this.manager.projectionValues(id);
|
|
3525
|
+
const projectedTitle = projectionValues?.title;
|
|
3526
|
+
const title = typeof projectedTitle === "string" && projectedTitle !== "" ? projectedTitle : previous?.title;
|
|
3396
3527
|
byId[id] = {
|
|
3397
3528
|
...previous ?? {
|
|
3398
3529
|
id,
|
|
@@ -3402,9 +3533,12 @@ window.__ModuleLoader__.load({
|
|
|
3402
3533
|
running: snapshot.running,
|
|
3403
3534
|
retainedBy: record.retention.retainedBy,
|
|
3404
3535
|
blank: snapshot.blank,
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3536
|
+
parentId: address.parentSessionId,
|
|
3537
|
+
origin: "subagent",
|
|
3538
|
+
...projectionValues === void 0 ? {} : { projectionValues },
|
|
3539
|
+
...title === void 0 ? {} : {
|
|
3540
|
+
title,
|
|
3541
|
+
displayTitle: title
|
|
3408
3542
|
}
|
|
3409
3543
|
};
|
|
3410
3544
|
}
|
|
@@ -3412,8 +3546,7 @@ window.__ModuleLoader__.load({
|
|
|
3412
3546
|
ids,
|
|
3413
3547
|
byId,
|
|
3414
3548
|
phase,
|
|
3415
|
-
|
|
3416
|
-
jobsBySession
|
|
3549
|
+
projectionsBySession
|
|
3417
3550
|
});
|
|
3418
3551
|
}
|
|
3419
3552
|
startScopeDrop(id, record, disposeFiber = true, sessionDisposal = this.manager.drop(id, record.session)) {
|