@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.1 → 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +42 -7
- package/README.zh.md +42 -7
- package/lib/client.js +1037 -992
- package/lib/index.js +314 -183
- package/lib/typert.host.js +539 -331
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +374 -264
- package/lib/types/agent.d.ts +1 -1
- package/lib/types/agent.js +8 -1
- 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 +2 -0
- package/lib/types/client/contract/sessions.d.ts +65 -35
- package/lib/types/client/contract/snapshot.d.ts +0 -14
- package/lib/types/client/index.d.ts +12 -3
- package/lib/types/client/index.js +16 -5
- package/lib/types/client/scope.d.ts +13 -3
- package/lib/types/client/scope.js +15 -22
- package/lib/types/client/sessions/lineage.d.ts +5 -5
- package/lib/types/client/sessions/lineage.js +3 -4
- package/lib/types/client/sessions/manager.d.ts +91 -105
- package/lib/types/client/sessions/manager.js +307 -434
- package/lib/types/client/sessions/projection-store.d.ts +34 -21
- package/lib/types/client/sessions/projection-store.js +48 -24
- package/lib/types/client/sessions/remotes.d.ts +1 -2
- package/lib/types/client/sessions/service.d.ts +53 -150
- package/lib/types/client/sessions/service.js +267 -264
- package/lib/types/client/sessions/session.d.ts +24 -35
- package/lib/types/client/sessions/session.js +131 -73
- package/lib/types/commands.d.ts +6 -4
- package/lib/types/commands.js +82 -40
- package/lib/types/control.d.ts +2 -4
- package/lib/types/control.js +2 -84
- package/lib/types/history.js +1 -1
- package/lib/types/index.d.ts +34 -7
- package/lib/types/index.js +118 -14
- package/lib/types/list.js +24 -15
- package/lib/types/skill-catalog.js +60 -50
- package/lib/types/types.d.ts +40 -40
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
- package/lib/types/client/sessions/queue-mirror.d.ts +0 -26
- package/lib/types/client/sessions/queue-mirror.js +0 -61
package/lib/client.js
CHANGED
|
@@ -34,6 +34,27 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
this.name = "RemoteError";
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
+
/** Generic invocation-owned values returned by synchronous Client Context resolvers. */
|
|
38
|
+
/** Shared identity across independently bundled Context providers and Gateway. */
|
|
39
|
+
const TYPERT_OWNED_VALUE = Symbol.for("dsh.typert.owned-value");
|
|
40
|
+
/**
|
|
41
|
+
* Transfer cleanup ownership without adding another resource reference count.
|
|
42
|
+
* @param value - resolved payload passed to the invocation.
|
|
43
|
+
* @param release - non-throwing synchronous release, called at most once.
|
|
44
|
+
* @returns an owned payload disposed after invocation and reply settlement.
|
|
45
|
+
*/
|
|
46
|
+
function typertOwnedValue(value, release) {
|
|
47
|
+
let active = true;
|
|
48
|
+
return {
|
|
49
|
+
[TYPERT_OWNED_VALUE]: true,
|
|
50
|
+
value,
|
|
51
|
+
[Symbol.dispose]() {
|
|
52
|
+
if (!active) return;
|
|
53
|
+
active = false;
|
|
54
|
+
release();
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
37
58
|
//#endregion
|
|
38
59
|
//#region lib/types/client/sessions/history-records.js
|
|
39
60
|
/** Client range access and type narrowing for aligned Session history records. */
|
|
@@ -128,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
128
149
|
"compaction/start",
|
|
129
150
|
"compaction/summary",
|
|
130
151
|
"deliverables/presented",
|
|
152
|
+
"developer/message",
|
|
131
153
|
"feedback/message-delete",
|
|
132
154
|
"feedback/message-put",
|
|
133
155
|
"feedback/record",
|
|
@@ -170,13 +192,15 @@ window.__ModuleLoader__.load({
|
|
|
170
192
|
"turn/end",
|
|
171
193
|
"turn/start",
|
|
172
194
|
"user/message",
|
|
173
|
-
"web/deepseek-search-llm-request"
|
|
195
|
+
"web/deepseek-search-llm-request",
|
|
196
|
+
"workspace/changes"
|
|
174
197
|
]);
|
|
175
198
|
//#endregion
|
|
176
199
|
//#region ../../core/session/lib/types/surface.js
|
|
177
200
|
/** Runtime counterpart of the message-producing event union. */
|
|
178
201
|
const SURFACE_EVENT_TYPES = new Set([
|
|
179
202
|
"system/message",
|
|
203
|
+
"developer/message",
|
|
180
204
|
"user/message",
|
|
181
205
|
"assistant/message",
|
|
182
206
|
"tool/result"
|
|
@@ -184,7 +208,7 @@ window.__ModuleLoader__.load({
|
|
|
184
208
|
/**
|
|
185
209
|
* Whether an event type can join the model-visible surface.
|
|
186
210
|
* @param type - event type to test.
|
|
187
|
-
* @returns true for one of the
|
|
211
|
+
* @returns true for one of the message-producing event types.
|
|
188
212
|
*/
|
|
189
213
|
function isSurfaceEligibleType(type) {
|
|
190
214
|
return SURFACE_EVENT_TYPES.has(type);
|
|
@@ -194,14 +218,33 @@ window.__ModuleLoader__.load({
|
|
|
194
218
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
195
219
|
}
|
|
196
220
|
/**
|
|
197
|
-
* Reject noncanonical request-header fields and contradictory tool failure metadata.
|
|
221
|
+
* Reject noncanonical request-header fields, developer roles/content, and contradictory tool failure metadata.
|
|
198
222
|
* This does not validate complete event payloads or embedded provider streams.
|
|
199
223
|
* @param event - event whose locally related payload fields are inspected.
|
|
200
224
|
* @param subject - event location to include in validation errors.
|
|
201
|
-
* @throws when request
|
|
225
|
+
* @throws when request-header fields, developer roles/content, or tool failure metadata are invalid.
|
|
202
226
|
*/
|
|
203
227
|
function validateSessionEventData(event, subject) {
|
|
204
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
|
+
}
|
|
205
248
|
if (event.type === "request/header") {
|
|
206
249
|
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
207
250
|
const header = data["header"];
|
|
@@ -214,9 +257,7 @@ window.__ModuleLoader__.load({
|
|
|
214
257
|
if (!isRecord(data)) throw new Error(`${subject} data must be an object`);
|
|
215
258
|
if (data["error"] === void 0) return;
|
|
216
259
|
const message = data["message"];
|
|
217
|
-
|
|
218
|
-
const block = Array.isArray(content) ? content[0] : void 0;
|
|
219
|
-
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`);
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
/** Whether a runtime value is a non-negative safe event sequence. */
|
|
@@ -309,7 +350,6 @@ window.__ModuleLoader__.load({
|
|
|
309
350
|
}
|
|
310
351
|
//#endregion
|
|
311
352
|
//#region lib/types/types.js
|
|
312
|
-
/** Browser-safe request, result, and lifecycle vocabulary for the Session Remote service. */
|
|
313
353
|
/** Maximum number of Sessions returned by one search. */
|
|
314
354
|
const SESSION_SEARCH_RESULT_LIMIT = 20;
|
|
315
355
|
/** Maximum search snippet length in Unicode code points. */
|
|
@@ -454,23 +494,7 @@ window.__ModuleLoader__.load({
|
|
|
454
494
|
}
|
|
455
495
|
//#endregion
|
|
456
496
|
//#region lib/types/client/scope.js
|
|
457
|
-
/**
|
|
458
|
-
* Client Agent-scope primitive: mint a Cordis context tagged with the owning
|
|
459
|
-
* Agent's identity. The mechanism mirrors the host `dsh-scope` architecture
|
|
460
|
-
* (no-op plugin fiber + context tag + `Context.filter` routing predicate);
|
|
461
|
-
* the shape deliberately diverges: the filter lives on the actx itself
|
|
462
|
-
* instead of a separate carrier object, so scoped dispatch is plain cordis —
|
|
463
|
-
* `actx.bail(actx, event, payload)` / `actx.emit(actx, ...)` — with no
|
|
464
|
-
* wrapper. The host needs a detached carrier because its dispatch subject is
|
|
465
|
-
* the business Agent object; client scope events carry only ids, so the
|
|
466
|
-
* actx is the natural subject. The second divergence stands: the scope key
|
|
467
|
-
* is the branded `SessionId` (value compared), not an object identity — the
|
|
468
|
-
* agent and its session share one id (1:1, same axis; no separate AgentId
|
|
469
|
-
* brand), and a client scope's identity IS that wire id. Third divergence,
|
|
470
|
-
* deliberate: the client scopes the Agent IDENTITY, not a live Agent object
|
|
471
|
-
* — a cold session's host Agent is already disposed while its client actx
|
|
472
|
-
* stays alive for history viewing.
|
|
473
|
-
*/
|
|
497
|
+
/** Client scope generations route local events independently of Host Agent residency. */
|
|
474
498
|
/** Context tag written by {@link createScope}. */
|
|
475
499
|
const kScope = Symbol("dsh.client.scope");
|
|
476
500
|
/** Shared no-op plugin backing each Agent scope fiber. */
|
|
@@ -478,21 +502,22 @@ window.__ModuleLoader__.load({
|
|
|
478
502
|
/**
|
|
479
503
|
* Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
|
|
480
504
|
* carries the agent tag and the dispatch filter — untagged listeners are
|
|
481
|
-
* admitted globally, tagged listeners only for
|
|
505
|
+
* admitted globally, tagged listeners only for the same Client generation.
|
|
482
506
|
* Registrations through the returned ctx dispose with the fiber.
|
|
483
507
|
* @param ctx - client root context the scope fiber mounts under.
|
|
484
|
-
* @param key -
|
|
508
|
+
* @param key - durable Session identity carried by this generation.
|
|
485
509
|
* @returns the tagged context and its backing fiber.
|
|
486
510
|
*/
|
|
487
511
|
function createScope(ctx, key) {
|
|
488
512
|
const fiber = ctx.plugin(agentScope);
|
|
513
|
+
const identity = { sessionId: key };
|
|
489
514
|
return {
|
|
490
515
|
fiber,
|
|
491
516
|
ctx: fiber.ctx.extend({
|
|
492
|
-
[kScope]:
|
|
517
|
+
[kScope]: identity,
|
|
493
518
|
[_deepseek_ai_cordis.Context.filter](listenerCtx) {
|
|
494
|
-
const tag =
|
|
495
|
-
return tag === void 0 || tag ===
|
|
519
|
+
const tag = scopeIdentityOf(listenerCtx);
|
|
520
|
+
return tag === void 0 || tag === identity;
|
|
496
521
|
}
|
|
497
522
|
})
|
|
498
523
|
};
|
|
@@ -503,6 +528,14 @@ window.__ModuleLoader__.load({
|
|
|
503
528
|
* @returns its agent identity (the session id), or undefined for root contexts.
|
|
504
529
|
*/
|
|
505
530
|
function scopeOf(ctx) {
|
|
531
|
+
return scopeIdentityOf(ctx)?.sessionId;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Read the exact generation identity inherited by a Client Context.
|
|
535
|
+
* @param ctx - scoped or root Client Context.
|
|
536
|
+
* @returns the generation identity, or undefined for an unscoped Context.
|
|
537
|
+
*/
|
|
538
|
+
function scopeIdentityOf(ctx) {
|
|
506
539
|
return ctx[kScope];
|
|
507
540
|
}
|
|
508
541
|
//#endregion
|
|
@@ -543,16 +576,219 @@ window.__ModuleLoader__.load({
|
|
|
543
576
|
return merged;
|
|
544
577
|
}
|
|
545
578
|
//#endregion
|
|
579
|
+
//#region ../../util/values/lib/index.js
|
|
580
|
+
/** Duplicate-install-safe JSON and immutable-value helpers. @module @deepseek-ai/dsh-util-values */
|
|
581
|
+
/**
|
|
582
|
+
* Mark an unreachable closed-union branch.
|
|
583
|
+
* @param value - impossible value; an unhandled typed variant fails at the call site.
|
|
584
|
+
* @param context - optional switch-site label included in the failure message.
|
|
585
|
+
* @returns never; a runtime value that escaped its type always throws.
|
|
586
|
+
*/
|
|
587
|
+
function assertNever(value, context) {
|
|
588
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
589
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
590
|
+
}
|
|
591
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
592
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
593
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
594
|
+
if (typeof constructor !== "function") return false;
|
|
595
|
+
try {
|
|
596
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
597
|
+
} catch {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
602
|
+
function isIntrinsicObjectPrototype(value) {
|
|
603
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
604
|
+
}
|
|
605
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
606
|
+
function hasPlainArrayPrototype(value) {
|
|
607
|
+
const prototype = Object.getPrototypeOf(value);
|
|
608
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
609
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
610
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
611
|
+
}
|
|
612
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
613
|
+
function hasPlainObjectPrototype(value) {
|
|
614
|
+
const prototype = Object.getPrototypeOf(value);
|
|
615
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
616
|
+
}
|
|
617
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
618
|
+
function enumerableStringKeys(value) {
|
|
619
|
+
const keys = Reflect.ownKeys(value);
|
|
620
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
621
|
+
return keys;
|
|
622
|
+
}
|
|
623
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
624
|
+
function walkJsonValue(value, detach) {
|
|
625
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
626
|
+
let root;
|
|
627
|
+
const assign = (destination, item) => {
|
|
628
|
+
if (destination === void 0) return;
|
|
629
|
+
if (destination.kind === "root") root = item;
|
|
630
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
631
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
632
|
+
value: item,
|
|
633
|
+
enumerable: true,
|
|
634
|
+
configurable: true,
|
|
635
|
+
writable: true
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
const tasks = [{
|
|
639
|
+
kind: "visit",
|
|
640
|
+
value,
|
|
641
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
642
|
+
}];
|
|
643
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
644
|
+
if (task.kind === "leave") {
|
|
645
|
+
ancestors.delete(task.source);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (task.kind === "array-item") {
|
|
649
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
650
|
+
tasks.push({
|
|
651
|
+
kind: "visit",
|
|
652
|
+
value: task.source[task.index],
|
|
653
|
+
...task.target === void 0 ? {} : { destination: {
|
|
654
|
+
kind: "array",
|
|
655
|
+
target: task.target,
|
|
656
|
+
index: task.index
|
|
657
|
+
} }
|
|
658
|
+
});
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (task.kind === "object-property") {
|
|
662
|
+
tasks.push({
|
|
663
|
+
kind: "visit",
|
|
664
|
+
value: task.source[task.key],
|
|
665
|
+
...task.target === void 0 ? {} : { destination: {
|
|
666
|
+
kind: "object",
|
|
667
|
+
target: task.target,
|
|
668
|
+
key: task.key
|
|
669
|
+
} }
|
|
670
|
+
});
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const current = task.value;
|
|
674
|
+
if (current === null) {
|
|
675
|
+
assign(task.destination, null);
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
679
|
+
assign(task.destination, current);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (typeof current === "number") {
|
|
683
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
684
|
+
assign(task.destination, current);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (typeof current !== "object") return void 0;
|
|
688
|
+
if (ancestors.has(current)) return void 0;
|
|
689
|
+
if (Array.isArray(current)) {
|
|
690
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
691
|
+
const length = current.length;
|
|
692
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
693
|
+
const target = detach ? [] : void 0;
|
|
694
|
+
if (target !== void 0) assign(task.destination, target);
|
|
695
|
+
ancestors.add(current);
|
|
696
|
+
tasks.push({
|
|
697
|
+
kind: "leave",
|
|
698
|
+
source: current
|
|
699
|
+
});
|
|
700
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
701
|
+
kind: "array-item",
|
|
702
|
+
source: current,
|
|
703
|
+
index,
|
|
704
|
+
...target === void 0 ? {} : { target }
|
|
705
|
+
});
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
709
|
+
const keys = enumerableStringKeys(current);
|
|
710
|
+
if (keys === void 0) return void 0;
|
|
711
|
+
const target = detach ? {} : void 0;
|
|
712
|
+
if (target !== void 0) assign(task.destination, target);
|
|
713
|
+
ancestors.add(current);
|
|
714
|
+
tasks.push({
|
|
715
|
+
kind: "leave",
|
|
716
|
+
source: current
|
|
717
|
+
});
|
|
718
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
719
|
+
const key = keys[index];
|
|
720
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
721
|
+
if (key === void 0) return void 0;
|
|
722
|
+
tasks.push({
|
|
723
|
+
kind: "object-property",
|
|
724
|
+
source: current,
|
|
725
|
+
key,
|
|
726
|
+
...target === void 0 ? {} : { target }
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return detach ? root : true;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Validate and detach lossless JSON in one read per property.
|
|
734
|
+
* @param value - candidate value to validate and detach.
|
|
735
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
736
|
+
*/
|
|
737
|
+
function snapshotJsonValue(value) {
|
|
738
|
+
return walkJsonValue(value, true);
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
742
|
+
* @param value - value to freeze.
|
|
743
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
744
|
+
*/
|
|
745
|
+
function deepFreeze(value) {
|
|
746
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
747
|
+
const pending = [{
|
|
748
|
+
kind: "visit",
|
|
749
|
+
node: value
|
|
750
|
+
}];
|
|
751
|
+
while (pending.length > 0) {
|
|
752
|
+
const task = pending.pop();
|
|
753
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
754
|
+
if (task === void 0) continue;
|
|
755
|
+
if (task.kind === "property") {
|
|
756
|
+
pending.push({
|
|
757
|
+
kind: "visit",
|
|
758
|
+
node: task.source[task.key]
|
|
759
|
+
});
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const node = task.node;
|
|
763
|
+
if (node === null || typeof node !== "object") continue;
|
|
764
|
+
if (node instanceof AbortSignal) continue;
|
|
765
|
+
if (seen.has(node)) continue;
|
|
766
|
+
seen.add(node);
|
|
767
|
+
Object.freeze(node);
|
|
768
|
+
const keys = Object.keys(node);
|
|
769
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
770
|
+
const key = keys[index];
|
|
771
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
772
|
+
if (key === void 0) continue;
|
|
773
|
+
pending.push({
|
|
774
|
+
kind: "property",
|
|
775
|
+
source: node,
|
|
776
|
+
key
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return value;
|
|
781
|
+
}
|
|
782
|
+
//#endregion
|
|
546
783
|
//#region lib/types/client/sessions/lineage.js
|
|
547
784
|
/**
|
|
548
785
|
* Summaries -> flat list with lineage indentation. Root and sibling order
|
|
549
786
|
* follows the established input order; this projection never re-sorts a
|
|
550
787
|
* hydrated list from mutable timestamps.
|
|
551
788
|
* @param summaries - the host's session.list items.
|
|
552
|
-
* @param completed - sessions with a pending completion reminder (manager-owned live fact; absent = false).
|
|
553
789
|
* @returns display rows in render order.
|
|
554
790
|
*/
|
|
555
|
-
function flattenLineage(summaries
|
|
791
|
+
function flattenLineage(summaries) {
|
|
556
792
|
const byId = /* @__PURE__ */ new Map();
|
|
557
793
|
for (const s of summaries) byId.set(s.sessionId, s);
|
|
558
794
|
const children = /* @__PURE__ */ new Map();
|
|
@@ -570,9 +806,9 @@ window.__ModuleLoader__.load({
|
|
|
570
806
|
return;
|
|
571
807
|
}
|
|
572
808
|
visited.add(s.sessionId);
|
|
809
|
+
const { agentAvailable: _agentAvailable, ...row } = s;
|
|
573
810
|
out.push({
|
|
574
|
-
...
|
|
575
|
-
completed: completed?.has(s.sessionId) ?? false,
|
|
811
|
+
...row,
|
|
576
812
|
depth
|
|
577
813
|
});
|
|
578
814
|
const kids = children.get(s.sessionId);
|
|
@@ -675,10 +911,14 @@ window.__ModuleLoader__.load({
|
|
|
675
911
|
//#region lib/types/client/sessions/projection-store.js
|
|
676
912
|
/**
|
|
677
913
|
* One session's projection values. Framework semantics, uniform across every
|
|
678
|
-
* key
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
914
|
+
* key. Sequenced writes (a baseline seeds rows at its cut, a push frame
|
|
915
|
+
* updates one row) compare seqs among themselves: a lower-or-equal seq within
|
|
916
|
+
* the Host generation loses, so a replayed frame cannot regress a value and a
|
|
917
|
+
* stale baseline cannot overwrite a newer frame. Cached writes (the session
|
|
918
|
+
* list's zero-I/O block) only fill keys no sequenced row holds, and a baseline
|
|
919
|
+
* discards every cached row before it seeds, regardless of seq: the connected
|
|
920
|
+
* Session is the truth and a cached value never outranks it. A key the store
|
|
921
|
+
* has never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
682
922
|
* per key (create-on-demand, cached) so the React side binds each exactly
|
|
683
923
|
* once; the store-level channel (`subscribeAny`) serves coarse consumers (the
|
|
684
924
|
* manager's list projection reads the `title` key).
|
|
@@ -733,41 +973,60 @@ window.__ModuleLoader__.load({
|
|
|
733
973
|
*/
|
|
734
974
|
apply(key, value, seq) {
|
|
735
975
|
const row = this.rows.get(key);
|
|
736
|
-
if (row
|
|
976
|
+
if (row?.kind === "sequenced" && seq <= row.seq) return;
|
|
737
977
|
this.rows.set(key, {
|
|
978
|
+
kind: "sequenced",
|
|
738
979
|
value,
|
|
739
980
|
seq
|
|
740
981
|
});
|
|
741
982
|
this.changed(key);
|
|
742
983
|
}
|
|
743
984
|
/**
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
985
|
+
* Fill keys from a session-list block the Host labeled `cached`: a zero-I/O
|
|
986
|
+
* view of the persisted checkpoint. A cached value lands only where no
|
|
987
|
+
* sequenced row exists: a connected Session has already answered for such
|
|
988
|
+
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
989
|
+
* than it.
|
|
990
|
+
* @param values - whole values by key viewed from the persisted checkpoint.
|
|
991
|
+
*/
|
|
992
|
+
applyCached(values) {
|
|
993
|
+
for (const key of Object.keys(values)) {
|
|
994
|
+
if (this.rows.get(key)?.kind === "sequenced") continue;
|
|
995
|
+
this.rows.set(key, {
|
|
996
|
+
kind: "cached",
|
|
997
|
+
value: values[key]
|
|
998
|
+
});
|
|
999
|
+
this.changed(key);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Seed from a history tail page's projections block. Every cached row is
|
|
1004
|
+
* discarded first, regardless of seq: the block comes from the connected
|
|
1005
|
+
* Session, and a value viewed from the persisted checkpoint never outranks
|
|
1006
|
+
* it. Then every carried key lands under the same seq rule as frames, and a
|
|
1007
|
+
* key the block omits is capability-absent as of the cut — its row clears
|
|
1008
|
+
* unless a newer frame already superseded the cut (a stale baseline can
|
|
1009
|
+
* neither overwrite nor clear newer sequenced values).
|
|
749
1010
|
* @param baseline - the response's projections block.
|
|
750
1011
|
*/
|
|
751
1012
|
seed(baseline) {
|
|
1013
|
+
for (const [key, row] of this.rows) {
|
|
1014
|
+
if (row.kind !== "cached") continue;
|
|
1015
|
+
this.rows.delete(key);
|
|
1016
|
+
this.changed(key);
|
|
1017
|
+
}
|
|
752
1018
|
const values = baseline.values;
|
|
753
1019
|
for (const key of Object.keys(values)) this.apply(key, values[key], baseline.asOfSeq);
|
|
754
1020
|
for (const [key, row] of this.rows) {
|
|
755
1021
|
if (Object.hasOwn(values, key)) continue;
|
|
756
|
-
if (row.seq > baseline.asOfSeq) continue;
|
|
1022
|
+
if (row.kind === "sequenced" && row.seq > baseline.asOfSeq) continue;
|
|
757
1023
|
this.rows.delete(key);
|
|
758
1024
|
this.changed(key);
|
|
759
1025
|
}
|
|
760
1026
|
}
|
|
761
|
-
/**
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
* outrank recomputed lower-seq values forever. The caller seeds the new
|
|
765
|
-
* baseline immediately afterward.
|
|
766
|
-
* @param lastSeq - highest durable sequence reflected by the baseline.
|
|
767
|
-
*/
|
|
768
|
-
truncate(lastSeq) {
|
|
769
|
-
for (const [key, row] of this.rows) {
|
|
770
|
-
if (row.seq <= lastSeq) continue;
|
|
1027
|
+
/** Discard one Host generation's values and watermarks while preserving subscribed faces. */
|
|
1028
|
+
clear() {
|
|
1029
|
+
for (const key of this.rows.keys()) {
|
|
771
1030
|
this.rows.delete(key);
|
|
772
1031
|
this.changed(key);
|
|
773
1032
|
}
|
|
@@ -890,312 +1149,65 @@ window.__ModuleLoader__.load({
|
|
|
890
1149
|
});
|
|
891
1150
|
}
|
|
892
1151
|
/**
|
|
893
|
-
* Prepend one older contiguous page.
|
|
894
|
-
* @param entries - newly loaded older entries.
|
|
895
|
-
* @param hasMore - whether still older history remains.
|
|
896
|
-
*/
|
|
897
|
-
prepend(entries, hasMore) {
|
|
898
|
-
this.window = concat(leaf(entries), this.window);
|
|
899
|
-
this.publish(hasMore, {
|
|
900
|
-
kind: "prepend",
|
|
901
|
-
entries
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Append one contiguous live entry.
|
|
906
|
-
* @param entry - live tail entry.
|
|
907
|
-
*/
|
|
908
|
-
append(entry) {
|
|
909
|
-
const entries = [entry];
|
|
910
|
-
this.window = concat(this.window, leaf(entries));
|
|
911
|
-
this.publish(this.snapshot.hasMore, {
|
|
912
|
-
kind: "append",
|
|
913
|
-
entries
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
/**
|
|
917
|
-
* Replace one attempt's transient rows with its committed durable settlement.
|
|
918
|
-
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
919
|
-
* @param entry - durable settlement committed for that attempt.
|
|
920
|
-
*/
|
|
921
|
-
settleAssistant(attemptId, entry) {
|
|
922
|
-
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
923
|
-
if (entry !== void 0) {
|
|
924
|
-
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
925
|
-
if (index < 0) entries.push(entry);
|
|
926
|
-
else entries.splice(index, 0, entry);
|
|
927
|
-
}
|
|
928
|
-
this.window = leaf(entries);
|
|
929
|
-
this.publish(this.snapshot.hasMore, {
|
|
930
|
-
kind: "settle-assistant",
|
|
931
|
-
attemptId,
|
|
932
|
-
...entry === void 0 ? {} : { entry }
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
publish(hasMore, change) {
|
|
936
|
-
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
937
|
-
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
938
|
-
}
|
|
939
|
-
};
|
|
940
|
-
//#endregion
|
|
941
|
-
//#region lib/types/client/time-zone.js
|
|
942
|
-
/** Browser-owned time-zone sampling for one prompt RPC. */
|
|
943
|
-
/**
|
|
944
|
-
* Resolve the current browser IANA zone for one outbound operation.
|
|
945
|
-
* @returns The browser-provided canonical zone.
|
|
946
|
-
* @throws when the runtime cannot provide a non-empty zone.
|
|
947
|
-
*/
|
|
948
|
-
function resolvedClientTimeZone() {
|
|
949
|
-
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
950
|
-
if (typeof timeZone !== "string" || timeZone.length === 0) throw new Error("browser time zone is unavailable");
|
|
951
|
-
return timeZone;
|
|
952
|
-
}
|
|
953
|
-
//#endregion
|
|
954
|
-
//#region lib/types/client/sessions/queue-mirror.js
|
|
955
|
-
const QUEUE_PREVIEW_CHARS = 200;
|
|
956
|
-
function previewOf(content) {
|
|
957
|
-
const flat = content.filter((block) => block.type !== "image" && block.type !== "file").map((block) => block.type === "text" ? block.text : `[${block.type}]`).join(" ").replace(/\s+/g, " ").trim();
|
|
958
|
-
const chars = Array.from(flat);
|
|
959
|
-
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}…` : flat;
|
|
960
|
-
}
|
|
961
|
-
function textOf(content) {
|
|
962
|
-
if (!content.every((block) => block.type === "text")) return null;
|
|
963
|
-
return content.map((block) => block.text).join("");
|
|
964
|
-
}
|
|
965
|
-
/** Authoritative transient queue projection and durable steering handoff. */
|
|
966
|
-
var SessionQueueMirror = class {
|
|
967
|
-
current = [];
|
|
968
|
-
/**
|
|
969
|
-
* Return the current immutable queue projection.
|
|
970
|
-
* @returns current queue rows.
|
|
971
|
-
*/
|
|
972
|
-
snapshot() {
|
|
973
|
-
return this.current;
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Replace from one authoritative stream queue frame.
|
|
977
|
-
* @param items - complete host queue snapshot.
|
|
978
|
-
*/
|
|
979
|
-
replace(items) {
|
|
980
|
-
this.current = items.map((item) => {
|
|
981
|
-
const content = item.message.content;
|
|
982
|
-
return {
|
|
983
|
-
id: item.id,
|
|
984
|
-
messageId: item.message.id,
|
|
985
|
-
placement: item.placement,
|
|
986
|
-
...item.rpcId === void 0 ? {} : { rpcId: item.rpcId },
|
|
987
|
-
content,
|
|
988
|
-
preview: previewOf(content),
|
|
989
|
-
text: textOf(content)
|
|
990
|
-
};
|
|
991
|
-
});
|
|
992
|
-
}
|
|
993
|
-
/**
|
|
994
|
-
* Retire a transient steering row once its durable message enters the log.
|
|
995
|
-
* @param event - newly contiguous durable Session event.
|
|
996
|
-
* @returns whether the projection changed.
|
|
997
|
-
*/
|
|
998
|
-
acceptDurable(event) {
|
|
999
|
-
if (event.type !== "user/message") return false;
|
|
1000
|
-
const messageId = event.data.id;
|
|
1001
|
-
const index = this.current.findIndex((item) => item.placement === "steering" && item.messageId === messageId);
|
|
1002
|
-
if (index < 0) return false;
|
|
1003
|
-
this.current = this.current.filter((_item, candidate) => candidate !== index);
|
|
1004
|
-
return true;
|
|
1005
|
-
}
|
|
1006
|
-
};
|
|
1007
|
-
//#endregion
|
|
1008
|
-
//#region ../../util/values/lib/index.js
|
|
1009
|
-
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
1010
|
-
function hasIntrinsicConstructor(prototype, name) {
|
|
1011
|
-
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
1012
|
-
if (typeof constructor !== "function") return false;
|
|
1013
|
-
try {
|
|
1014
|
-
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
1015
|
-
} catch {
|
|
1016
|
-
return false;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
1020
|
-
function isIntrinsicObjectPrototype(value) {
|
|
1021
|
-
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
1022
|
-
}
|
|
1023
|
-
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
1024
|
-
function hasPlainArrayPrototype(value) {
|
|
1025
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1026
|
-
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
1027
|
-
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
1028
|
-
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
1029
|
-
}
|
|
1030
|
-
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
1031
|
-
function hasPlainObjectPrototype(value) {
|
|
1032
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1033
|
-
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
1034
|
-
}
|
|
1035
|
-
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
1036
|
-
function enumerableStringKeys(value) {
|
|
1037
|
-
const keys = Reflect.ownKeys(value);
|
|
1038
|
-
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
1039
|
-
return keys;
|
|
1040
|
-
}
|
|
1041
|
-
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
1042
|
-
function walkJsonValue(value, detach) {
|
|
1043
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
1044
|
-
let root;
|
|
1045
|
-
const assign = (destination, item) => {
|
|
1046
|
-
if (destination === void 0) return;
|
|
1047
|
-
if (destination.kind === "root") root = item;
|
|
1048
|
-
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
1049
|
-
else Object.defineProperty(destination.target, destination.key, {
|
|
1050
|
-
value: item,
|
|
1051
|
-
enumerable: true,
|
|
1052
|
-
configurable: true,
|
|
1053
|
-
writable: true
|
|
1054
|
-
});
|
|
1055
|
-
};
|
|
1056
|
-
const tasks = [{
|
|
1057
|
-
kind: "visit",
|
|
1058
|
-
value,
|
|
1059
|
-
...detach ? { destination: { kind: "root" } } : {}
|
|
1060
|
-
}];
|
|
1061
|
-
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
1062
|
-
if (task.kind === "leave") {
|
|
1063
|
-
ancestors.delete(task.source);
|
|
1064
|
-
continue;
|
|
1065
|
-
}
|
|
1066
|
-
if (task.kind === "array-item") {
|
|
1067
|
-
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
1068
|
-
tasks.push({
|
|
1069
|
-
kind: "visit",
|
|
1070
|
-
value: task.source[task.index],
|
|
1071
|
-
...task.target === void 0 ? {} : { destination: {
|
|
1072
|
-
kind: "array",
|
|
1073
|
-
target: task.target,
|
|
1074
|
-
index: task.index
|
|
1075
|
-
} }
|
|
1076
|
-
});
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
if (task.kind === "object-property") {
|
|
1080
|
-
tasks.push({
|
|
1081
|
-
kind: "visit",
|
|
1082
|
-
value: task.source[task.key],
|
|
1083
|
-
...task.target === void 0 ? {} : { destination: {
|
|
1084
|
-
kind: "object",
|
|
1085
|
-
target: task.target,
|
|
1086
|
-
key: task.key
|
|
1087
|
-
} }
|
|
1088
|
-
});
|
|
1089
|
-
continue;
|
|
1090
|
-
}
|
|
1091
|
-
const current = task.value;
|
|
1092
|
-
if (current === null) {
|
|
1093
|
-
assign(task.destination, null);
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1096
|
-
if (typeof current === "boolean" || typeof current === "string") {
|
|
1097
|
-
assign(task.destination, current);
|
|
1098
|
-
continue;
|
|
1099
|
-
}
|
|
1100
|
-
if (typeof current === "number") {
|
|
1101
|
-
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
1102
|
-
assign(task.destination, current);
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
if (typeof current !== "object") return void 0;
|
|
1106
|
-
if (ancestors.has(current)) return void 0;
|
|
1107
|
-
if (Array.isArray(current)) {
|
|
1108
|
-
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
1109
|
-
const length = current.length;
|
|
1110
|
-
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
1111
|
-
const target = detach ? [] : void 0;
|
|
1112
|
-
if (target !== void 0) assign(task.destination, target);
|
|
1113
|
-
ancestors.add(current);
|
|
1114
|
-
tasks.push({
|
|
1115
|
-
kind: "leave",
|
|
1116
|
-
source: current
|
|
1117
|
-
});
|
|
1118
|
-
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
1119
|
-
kind: "array-item",
|
|
1120
|
-
source: current,
|
|
1121
|
-
index,
|
|
1122
|
-
...target === void 0 ? {} : { target }
|
|
1123
|
-
});
|
|
1124
|
-
continue;
|
|
1125
|
-
}
|
|
1126
|
-
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
1127
|
-
const keys = enumerableStringKeys(current);
|
|
1128
|
-
if (keys === void 0) return void 0;
|
|
1129
|
-
const target = detach ? {} : void 0;
|
|
1130
|
-
if (target !== void 0) assign(task.destination, target);
|
|
1131
|
-
ancestors.add(current);
|
|
1132
|
-
tasks.push({
|
|
1133
|
-
kind: "leave",
|
|
1134
|
-
source: current
|
|
1135
|
-
});
|
|
1136
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1137
|
-
const key = keys[index];
|
|
1138
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1139
|
-
if (key === void 0) return void 0;
|
|
1140
|
-
tasks.push({
|
|
1141
|
-
kind: "object-property",
|
|
1142
|
-
source: current,
|
|
1143
|
-
key,
|
|
1144
|
-
...target === void 0 ? {} : { target }
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
return detach ? root : true;
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* Validate and detach lossless JSON in one read per property.
|
|
1152
|
-
* @param value - candidate value to validate and detach.
|
|
1153
|
-
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
1154
|
-
*/
|
|
1155
|
-
function snapshotJsonValue(value) {
|
|
1156
|
-
return walkJsonValue(value, true);
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
1160
|
-
* @param value - value to freeze.
|
|
1161
|
-
* @returns the same value after every reachable enumerable child is frozen.
|
|
1162
|
-
*/
|
|
1163
|
-
function deepFreeze(value) {
|
|
1164
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
1165
|
-
const pending = [{
|
|
1166
|
-
kind: "visit",
|
|
1167
|
-
node: value
|
|
1168
|
-
}];
|
|
1169
|
-
while (pending.length > 0) {
|
|
1170
|
-
const task = pending.pop();
|
|
1171
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
1172
|
-
if (task === void 0) continue;
|
|
1173
|
-
if (task.kind === "property") {
|
|
1174
|
-
pending.push({
|
|
1175
|
-
kind: "visit",
|
|
1176
|
-
node: task.source[task.key]
|
|
1177
|
-
});
|
|
1178
|
-
continue;
|
|
1179
|
-
}
|
|
1180
|
-
const node = task.node;
|
|
1181
|
-
if (node === null || typeof node !== "object") continue;
|
|
1182
|
-
if (node instanceof AbortSignal) continue;
|
|
1183
|
-
if (seen.has(node)) continue;
|
|
1184
|
-
seen.add(node);
|
|
1185
|
-
Object.freeze(node);
|
|
1186
|
-
const keys = Object.keys(node);
|
|
1187
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
1188
|
-
const key = keys[index];
|
|
1189
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
1190
|
-
if (key === void 0) continue;
|
|
1191
|
-
pending.push({
|
|
1192
|
-
kind: "property",
|
|
1193
|
-
source: node,
|
|
1194
|
-
key
|
|
1195
|
-
});
|
|
1152
|
+
* Prepend one older contiguous page.
|
|
1153
|
+
* @param entries - newly loaded older entries.
|
|
1154
|
+
* @param hasMore - whether still older history remains.
|
|
1155
|
+
*/
|
|
1156
|
+
prepend(entries, hasMore) {
|
|
1157
|
+
this.window = concat(leaf(entries), this.window);
|
|
1158
|
+
this.publish(hasMore, {
|
|
1159
|
+
kind: "prepend",
|
|
1160
|
+
entries
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Append one contiguous live entry.
|
|
1165
|
+
* @param entry - live tail entry.
|
|
1166
|
+
*/
|
|
1167
|
+
append(entry) {
|
|
1168
|
+
const entries = [entry];
|
|
1169
|
+
this.window = concat(this.window, leaf(entries));
|
|
1170
|
+
this.publish(this.snapshot.hasMore, {
|
|
1171
|
+
kind: "append",
|
|
1172
|
+
entries
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Replace one attempt's transient rows with its committed durable settlement.
|
|
1177
|
+
* @param attemptId - process-local attempt whose live rows are now redundant.
|
|
1178
|
+
* @param entry - durable settlement committed for that attempt.
|
|
1179
|
+
*/
|
|
1180
|
+
settleAssistant(attemptId, entry) {
|
|
1181
|
+
const entries = materialize(this.window).filter((candidate) => candidate.type !== "transient" || candidate.event.data.attemptId !== attemptId);
|
|
1182
|
+
if (entry !== void 0) {
|
|
1183
|
+
const index = entries.findIndex((candidate) => candidate.event.seq > entry.event.seq);
|
|
1184
|
+
if (index < 0) entries.push(entry);
|
|
1185
|
+
else entries.splice(index, 0, entry);
|
|
1196
1186
|
}
|
|
1187
|
+
this.window = leaf(entries);
|
|
1188
|
+
this.publish(this.snapshot.hasMore, {
|
|
1189
|
+
kind: "settle-assistant",
|
|
1190
|
+
attemptId,
|
|
1191
|
+
...entry === void 0 ? {} : { entry }
|
|
1192
|
+
});
|
|
1197
1193
|
}
|
|
1198
|
-
|
|
1194
|
+
publish(hasMore, change) {
|
|
1195
|
+
this.snapshot = windowSnapshot(this.window, hasMore, this.snapshot.revision + 1, change);
|
|
1196
|
+
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(this.listeners, "[session-controller] event feed");
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region lib/types/client/time-zone.js
|
|
1201
|
+
/** Browser-owned time-zone sampling for one prompt RPC. */
|
|
1202
|
+
/**
|
|
1203
|
+
* Resolve the current browser IANA zone for one outbound operation.
|
|
1204
|
+
* @returns The browser-provided canonical zone.
|
|
1205
|
+
* @throws when the runtime cannot provide a non-empty zone.
|
|
1206
|
+
*/
|
|
1207
|
+
function resolvedClientTimeZone() {
|
|
1208
|
+
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1209
|
+
if (typeof timeZone !== "string" || timeZone.length === 0) throw new Error("browser time zone is unavailable");
|
|
1210
|
+
return timeZone;
|
|
1199
1211
|
}
|
|
1200
1212
|
//#endregion
|
|
1201
1213
|
//#region ../../llm/llm/lib/types/assistant-stream.js
|
|
@@ -1527,8 +1539,8 @@ window.__ModuleLoader__.load({
|
|
|
1527
1539
|
jumpTargetSeq = null;
|
|
1528
1540
|
/** The running jump loop's completion, shared by retargeting callers. */
|
|
1529
1541
|
jumpPromise = null;
|
|
1530
|
-
|
|
1531
|
-
|
|
1542
|
+
pendingHistory = null;
|
|
1543
|
+
stopObservingInbox;
|
|
1532
1544
|
assistantStream = new ClientAssistantStream();
|
|
1533
1545
|
running = false;
|
|
1534
1546
|
address;
|
|
@@ -1541,7 +1553,7 @@ window.__ModuleLoader__.load({
|
|
|
1541
1553
|
promptAttempted = false;
|
|
1542
1554
|
/** A first accepted prompt stays in the engaging phase until its turn is observable. */
|
|
1543
1555
|
firstPromptPendingTurn = false;
|
|
1544
|
-
/**
|
|
1556
|
+
/** New Session display state; unknown bare sessions begin conservatively blank. */
|
|
1545
1557
|
blankBit = true;
|
|
1546
1558
|
removed = false;
|
|
1547
1559
|
promptError = null;
|
|
@@ -1549,7 +1561,7 @@ window.__ModuleLoader__.load({
|
|
|
1549
1561
|
/** Local submission echoes, insertion-ordered (see SessionSnapshot.pendingSubmissions). */
|
|
1550
1562
|
pendingSubmissions = [];
|
|
1551
1563
|
/** Per-echo settlement state; `retiring` latches the first observation so a
|
|
1552
|
-
*
|
|
1564
|
+
* Inbox projection and its durable event cannot both retire one echo. */
|
|
1553
1565
|
submissionSettlements = /* @__PURE__ */ new Map();
|
|
1554
1566
|
/** Owns the addressed page/follow lifecycle while this Session is open. */
|
|
1555
1567
|
events;
|
|
@@ -1557,8 +1569,9 @@ window.__ModuleLoader__.load({
|
|
|
1557
1569
|
* Per-session projection value store (push model; see the session-projection
|
|
1558
1570
|
* subsystem page, docs/subsystems/session-projection.md): finished whole
|
|
1559
1571
|
* values computed on the Host, seeded by the tail page's
|
|
1560
|
-
* projections block and updated by Session Controller control frames
|
|
1561
|
-
*
|
|
1572
|
+
* projections block and updated by Session Controller control frames;
|
|
1573
|
+
* Host-sequenced writes merge under higher-seq-wins and cached list blocks
|
|
1574
|
+
* yield to them (projection-store.ts). Keys are read via `projections.faceOf(key)`
|
|
1562
1575
|
* (the useProjection resolution face); the conversation snapshot never
|
|
1563
1576
|
* carries projection values, and no client-side domain folding exists.
|
|
1564
1577
|
* Manager-owned when constructed through SessionManager (frames route and
|
|
@@ -1594,6 +1607,9 @@ window.__ModuleLoader__.load({
|
|
|
1594
1607
|
this.snapshotCache = this.buildSnapshot();
|
|
1595
1608
|
});
|
|
1596
1609
|
this.snapshotCache = this.buildSnapshot();
|
|
1610
|
+
this.stopObservingInbox = this.projections.faceOf("inbox").subscribe(() => {
|
|
1611
|
+
this.observeSubmissionInbox();
|
|
1612
|
+
});
|
|
1597
1613
|
}
|
|
1598
1614
|
/**
|
|
1599
1615
|
* Bind the Agent-scoped context minted by ClientSessions (single write;
|
|
@@ -1620,14 +1636,16 @@ window.__ModuleLoader__.load({
|
|
|
1620
1636
|
*/
|
|
1621
1637
|
beginSubmission(input) {
|
|
1622
1638
|
const requestId = randomUUID();
|
|
1639
|
+
const placement = this.running ? input.mode === "steer" ? "steering" : "queued" : "transcript";
|
|
1623
1640
|
this.pendingSubmissions = [...this.pendingSubmissions, {
|
|
1624
1641
|
requestId,
|
|
1625
|
-
placement
|
|
1642
|
+
placement,
|
|
1626
1643
|
time: Date.now(),
|
|
1627
1644
|
text: input.text,
|
|
1628
1645
|
attachments: input.attachments
|
|
1629
1646
|
}];
|
|
1630
1647
|
this.submissionSettlements.set(requestId, {
|
|
1648
|
+
placement,
|
|
1631
1649
|
onRetire: input.onRetire,
|
|
1632
1650
|
retiring: false
|
|
1633
1651
|
});
|
|
@@ -1695,9 +1713,9 @@ window.__ModuleLoader__.load({
|
|
|
1695
1713
|
}
|
|
1696
1714
|
if (this.blankBit) {
|
|
1697
1715
|
this.blankBit = false;
|
|
1698
|
-
this.options.onEngaged?.(this);
|
|
1699
1716
|
this.notifier.markDirty();
|
|
1700
1717
|
}
|
|
1718
|
+
this.options.onEngaged?.(this);
|
|
1701
1719
|
return result;
|
|
1702
1720
|
}
|
|
1703
1721
|
/**
|
|
@@ -1825,36 +1843,44 @@ window.__ModuleLoader__.load({
|
|
|
1825
1843
|
return this.jumpPromise;
|
|
1826
1844
|
}
|
|
1827
1845
|
if (this.loadingOlder) return Promise.resolve();
|
|
1846
|
+
const events = this.events;
|
|
1847
|
+
if (events === void 0) return Promise.resolve();
|
|
1848
|
+
const pending = {
|
|
1849
|
+
beforeSeq: this.baseSeq,
|
|
1850
|
+
hasMore: this.hasMore,
|
|
1851
|
+
pages: []
|
|
1852
|
+
};
|
|
1853
|
+
this.pendingHistory = pending;
|
|
1828
1854
|
this.jumpTargetSeq = seq;
|
|
1829
1855
|
this.loadingOlder = true;
|
|
1830
1856
|
this.notifier.markDirty();
|
|
1831
1857
|
const generation = this.openGeneration;
|
|
1832
1858
|
this.jumpPromise = (async () => {
|
|
1833
1859
|
try {
|
|
1834
|
-
while (
|
|
1860
|
+
while (pending.hasMore && this.jumpTargetSeq !== null && pending.beforeSeq > this.jumpTargetSeq) {
|
|
1835
1861
|
if (generation !== this.openGeneration) return;
|
|
1836
|
-
const
|
|
1837
|
-
if (events === void 0) return;
|
|
1838
|
-
const before = this.baseSeq;
|
|
1862
|
+
const before = pending.beforeSeq;
|
|
1839
1863
|
await events.prepend({
|
|
1840
|
-
beforeSeq:
|
|
1864
|
+
beforeSeq: before,
|
|
1841
1865
|
maxMessages: 200
|
|
1842
1866
|
});
|
|
1843
|
-
if (
|
|
1867
|
+
if (pending.beforeSeq >= before) return;
|
|
1844
1868
|
}
|
|
1845
1869
|
} catch (error) {
|
|
1846
1870
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) console.error("[session-controller] loadThrough failed:", error);
|
|
1847
1871
|
} finally {
|
|
1848
1872
|
this.jumpTargetSeq = null;
|
|
1849
1873
|
this.jumpPromise = null;
|
|
1874
|
+
this.pendingHistory = null;
|
|
1850
1875
|
this.loadingOlder = false;
|
|
1876
|
+
if (generation === this.openGeneration && pending.pages.length > 0) this.prependWindow(pending.pages.reverse().flat(), pending.hasMore);
|
|
1851
1877
|
this.notifier.markDirty();
|
|
1852
1878
|
}
|
|
1853
1879
|
})();
|
|
1854
1880
|
return this.jumpPromise;
|
|
1855
1881
|
}
|
|
1856
1882
|
/** Rebuild an opened history source after address replacement.
|
|
1857
|
-
* Invalidates any in-flight open first;
|
|
1883
|
+
* Invalidates any in-flight open first; projection state belongs to the independently
|
|
1858
1884
|
* reconnecting control stream and remains untouched. */
|
|
1859
1885
|
async resync() {
|
|
1860
1886
|
if (this.openState === "cold") return;
|
|
@@ -1886,24 +1912,6 @@ window.__ModuleLoader__.load({
|
|
|
1886
1912
|
return this.snapshotCache;
|
|
1887
1913
|
}
|
|
1888
1914
|
/**
|
|
1889
|
-
* Replace every transient control value for this Session from one stream baseline.
|
|
1890
|
-
* @param queue - complete pending queue for this Session.
|
|
1891
|
-
*/
|
|
1892
|
-
replaceControl(queue) {
|
|
1893
|
-
this.queueMirror.replace(queue);
|
|
1894
|
-
this.observeSubmissionQueue(queue);
|
|
1895
|
-
this.notifier.markDirty();
|
|
1896
|
-
}
|
|
1897
|
-
/**
|
|
1898
|
-
* Apply one Session-addressed live control update.
|
|
1899
|
-
* @param frame - queue replacement addressed to this Session.
|
|
1900
|
-
*/
|
|
1901
|
-
handleControlFrame(frame) {
|
|
1902
|
-
this.queueMirror.replace(frame.items);
|
|
1903
|
-
this.observeSubmissionQueue(frame.items);
|
|
1904
|
-
this.notifier.markDirty();
|
|
1905
|
-
}
|
|
1906
|
-
/**
|
|
1907
1915
|
* Running-bit relay from the host stream (list entry and snapshot stay consistent).
|
|
1908
1916
|
* @param running - the new running state.
|
|
1909
1917
|
*/
|
|
@@ -1940,13 +1948,15 @@ window.__ModuleLoader__.load({
|
|
|
1940
1948
|
this.notifier.markDirty();
|
|
1941
1949
|
}
|
|
1942
1950
|
/**
|
|
1943
|
-
*
|
|
1944
|
-
* `
|
|
1945
|
-
* running
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1951
|
+
* Apply the Manager's effective display blank, further reconciled with the
|
|
1952
|
+
* current `sessionListMetadata` projection. Local send attempts and current
|
|
1953
|
+
* running state prevent re-blanking; an earlier false summary alone does not.
|
|
1954
|
+
* The Manager retains acceptance and earlier running observations across
|
|
1955
|
+
* Session-object replacement.
|
|
1956
|
+
* @param blank - New Session display state after Manager reconciliation.
|
|
1948
1957
|
*/
|
|
1949
1958
|
handleBlank(blank) {
|
|
1959
|
+
blank = blank && this.projections.values().sessionListMetadata?.blank !== false;
|
|
1950
1960
|
if (blank === this.blankBit) return;
|
|
1951
1961
|
if (blank && (this.promptAttempted || this.running)) return;
|
|
1952
1962
|
this.blankBit = blank;
|
|
@@ -1970,6 +1980,7 @@ window.__ModuleLoader__.load({
|
|
|
1970
1980
|
* @returns when the Remote iterator has completed teardown.
|
|
1971
1981
|
*/
|
|
1972
1982
|
async dispose() {
|
|
1983
|
+
this.stopObservingInbox();
|
|
1973
1984
|
for (const requestId of [...this.submissionSettlements.keys()]) this.retireFailedSubmission(requestId);
|
|
1974
1985
|
this.openGeneration++;
|
|
1975
1986
|
const events = this.events;
|
|
@@ -2025,10 +2036,22 @@ window.__ModuleLoader__.load({
|
|
|
2025
2036
|
const visible = this.assistantStream.replace(entries, assistantStream);
|
|
2026
2037
|
this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
|
|
2027
2038
|
this.hasMore = hasMore;
|
|
2039
|
+
if (this.pendingHistory !== null) {
|
|
2040
|
+
this.pendingHistory.beforeSeq = this.baseSeq;
|
|
2041
|
+
this.pendingHistory.hasMore = hasMore;
|
|
2042
|
+
this.pendingHistory.pages.length = 0;
|
|
2043
|
+
}
|
|
2028
2044
|
if (visible.some((entry) => entry.event.type === "turn/start")) this.firstPromptPendingTurn = false;
|
|
2029
2045
|
if (projections !== void 0) this.projections.seed(projections);
|
|
2030
2046
|
this.eventSource.replace(visible, hasMore);
|
|
2047
|
+
if (projections !== void 0) {
|
|
2048
|
+
for (const [requestId, { receipt }] of this.submissionSettlements) if (receipt !== void 0 && receipt.seq <= projections.asOfSeq) this.scheduleObservedRetirement(requestId, receipt.attachments);
|
|
2049
|
+
}
|
|
2031
2050
|
for (const entry of visible) this.observeSubmissionEvent(entry.event);
|
|
2051
|
+
if (projections !== void 0) {
|
|
2052
|
+
const inbox = projections.values.inbox;
|
|
2053
|
+
this.observeSteeringInsertions(inbox?.["next-step"] ?? [], 0, projections.asOfSeq);
|
|
2054
|
+
}
|
|
2032
2055
|
this.notifier.markDirty();
|
|
2033
2056
|
}
|
|
2034
2057
|
publishAssistantEntry(result) {
|
|
@@ -2058,6 +2081,13 @@ window.__ModuleLoader__.load({
|
|
|
2058
2081
|
}
|
|
2059
2082
|
/** Prepend one stream-validated history page. */
|
|
2060
2083
|
prependWindow(entries, hasMore) {
|
|
2084
|
+
if (this.pendingHistory !== null) {
|
|
2085
|
+
const pending = this.pendingHistory;
|
|
2086
|
+
pending.beforeSeq = entries[0] === void 0 ? pending.beforeSeq : SessionLogOffset(entries[0].event.seq);
|
|
2087
|
+
pending.hasMore = hasMore;
|
|
2088
|
+
pending.pages.push(entries);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2061
2091
|
this.baseSeq = entries[0] === void 0 ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
|
|
2062
2092
|
this.hasMore = hasMore;
|
|
2063
2093
|
this.eventSource.prepend(entries, hasMore);
|
|
@@ -2067,23 +2097,63 @@ window.__ModuleLoader__.load({
|
|
|
2067
2097
|
const event = entry.event;
|
|
2068
2098
|
const awaitingFirstTurn = this.firstPromptPendingTurn;
|
|
2069
2099
|
if (event.type === "turn/start") this.firstPromptPendingTurn = false;
|
|
2070
|
-
const queueChanged = this.queueMirror.acceptDurable(event);
|
|
2071
2100
|
this.eventSource.append(entry);
|
|
2072
2101
|
this.observeSubmissionEvent(event);
|
|
2073
|
-
return
|
|
2102
|
+
return awaitingFirstTurn !== this.firstPromptPendingTurn;
|
|
2074
2103
|
}
|
|
2075
|
-
/**
|
|
2104
|
+
/** Observe durable acceptance even when insertion and claim share one projection notification. */
|
|
2076
2105
|
observeSubmissionEvent(event) {
|
|
2077
|
-
if (this.submissionSettlements.size === 0 || event.type !== "user/message") return;
|
|
2078
|
-
const data = event.data;
|
|
2079
|
-
const source = data?.source;
|
|
2080
|
-
if (source?.kind !== "user" || typeof source.rpcId !== "string") return;
|
|
2081
|
-
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(data?.content));
|
|
2082
|
-
}
|
|
2083
|
-
/** Retire echoes whose prompts landed in the host inbox instead of the log (running-turn submissions). */
|
|
2084
|
-
observeSubmissionQueue(items) {
|
|
2085
2106
|
if (this.submissionSettlements.size === 0) return;
|
|
2086
|
-
|
|
2107
|
+
if (event.type === "agent/inbox/spliced") {
|
|
2108
|
+
const { target, start, removedCount = 0, inserted, outcome } = event.data;
|
|
2109
|
+
if (target === "next-step") {
|
|
2110
|
+
for (const [requestId, settlement] of this.submissionSettlements) {
|
|
2111
|
+
const receipt = settlement.receipt;
|
|
2112
|
+
if (receipt === void 0 || receipt.index === null || receipt.seq >= event.seq) continue;
|
|
2113
|
+
const removed = receipt.index >= start && receipt.index < start + removedCount;
|
|
2114
|
+
if (removed && outcome === "canceled") this.retireFailedSubmission(requestId);
|
|
2115
|
+
else settlement.receipt = {
|
|
2116
|
+
...receipt,
|
|
2117
|
+
seq: event.seq,
|
|
2118
|
+
index: removed ? null : receipt.index < start ? receipt.index : receipt.index + inserted.length - removedCount
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
this.observeSteeringInsertions(inserted, start, event.seq);
|
|
2122
|
+
}
|
|
2123
|
+
for (const message of inserted) this.observeSubmissionMessage(message, false);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
if (event.type === "request/context" || event.type === "turn/end") {
|
|
2127
|
+
for (const [requestId, settlement] of this.submissionSettlements) if (settlement.receipt?.index === null && settlement.receipt.seq < event.seq) this.retireFailedSubmission(requestId);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
if (event.type === "user/message") this.observeSubmissionMessage(event.data, true);
|
|
2131
|
+
}
|
|
2132
|
+
observeSteeringInsertions(messages, start, seq) {
|
|
2133
|
+
for (const [index, message] of messages.entries()) {
|
|
2134
|
+
const source = message.source;
|
|
2135
|
+
if (source.kind !== "user" || !("rpcId" in source)) continue;
|
|
2136
|
+
const settlement = this.submissionSettlements.get(source.rpcId);
|
|
2137
|
+
if (settlement?.placement !== "steering" || settlement.retiring || (settlement.receipt?.seq ?? -1) > seq) continue;
|
|
2138
|
+
settlement.receipt = {
|
|
2139
|
+
seq,
|
|
2140
|
+
index: start + index,
|
|
2141
|
+
attachments: attachmentRefsIn(message.content)
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
observeSubmissionMessage(message, admitted) {
|
|
2146
|
+
const source = message.source;
|
|
2147
|
+
if (source.kind !== "user" || !("rpcId" in source)) return;
|
|
2148
|
+
if (!admitted && this.submissionSettlements.get(source.rpcId)?.placement === "steering") return;
|
|
2149
|
+
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(message.content));
|
|
2150
|
+
}
|
|
2151
|
+
/** Retire non-steering echoes when the Inbox accepts their queue occurrences. */
|
|
2152
|
+
observeSubmissionInbox() {
|
|
2153
|
+
if (this.submissionSettlements.size === 0) return;
|
|
2154
|
+
const inbox = this.projections.get("inbox");
|
|
2155
|
+
if (inbox === void 0) return;
|
|
2156
|
+
for (const message of [...inbox["next-turn"], ...inbox["next-step"]]) this.observeSubmissionMessage(message, false);
|
|
2087
2157
|
}
|
|
2088
2158
|
/**
|
|
2089
2159
|
* Latch one observed settlement and remove the echo an animation frame
|
|
@@ -2132,13 +2202,16 @@ window.__ModuleLoader__.load({
|
|
|
2132
2202
|
this.notifier.markDirty();
|
|
2133
2203
|
}
|
|
2134
2204
|
buildSnapshot() {
|
|
2205
|
+
const identity = this.projections.values().subagent;
|
|
2135
2206
|
return {
|
|
2136
2207
|
sessionId: this.sessionId,
|
|
2137
|
-
queue: this.queueMirror.snapshot(),
|
|
2138
2208
|
pendingSubmissions: this.pendingSubmissions,
|
|
2139
2209
|
running: this.running,
|
|
2140
2210
|
subagent: this.address === void 0 ? null : {
|
|
2141
|
-
address: this.address
|
|
2211
|
+
address: this.address.mode === "unknown" && identity != null ? {
|
|
2212
|
+
...this.address,
|
|
2213
|
+
mode: identity.mode
|
|
2214
|
+
} : this.address,
|
|
2142
2215
|
...this.parentAvailable === void 0 ? {} : { parentAvailable: this.parentAvailable }
|
|
2143
2216
|
},
|
|
2144
2217
|
removed: this.removed,
|
|
@@ -2183,28 +2256,25 @@ window.__ModuleLoader__.load({
|
|
|
2183
2256
|
}
|
|
2184
2257
|
//#endregion
|
|
2185
2258
|
//#region lib/types/client/sessions/manager.js
|
|
2259
|
+
/** Host catalog, durable projection caches, and explicitly retained Client instances. */
|
|
2186
2260
|
function sessionSeqCursor(value) {
|
|
2187
2261
|
return value === -1 ? -1 : SessionSeq(value);
|
|
2188
2262
|
}
|
|
2189
|
-
function catalogAvailability(parentAvailable) {
|
|
2190
|
-
return parentAvailable === void 0 ? {} : { parentAvailable };
|
|
2191
|
-
}
|
|
2192
2263
|
/** Instance cluster + frame entry + the session list. */
|
|
2193
2264
|
var SessionManager = class {
|
|
2194
2265
|
remote;
|
|
2195
2266
|
sessions = /* @__PURE__ */ new Map();
|
|
2196
2267
|
/** In-flight Session disposals remain here after instances leave `sessions`, so manager disposal can await quiescence. */
|
|
2197
2268
|
sessionDisposals = /* @__PURE__ */ new Set();
|
|
2198
|
-
/** Latest transient queues, retained independently of Session object materialization. */
|
|
2199
|
-
queues = /* @__PURE__ */ new Map();
|
|
2200
2269
|
/**
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2270
|
+
* Accepted/running presentation must survive a later empty-history list
|
|
2271
|
+
* response. Host-asserted running is recorded even before a row, instance, or
|
|
2272
|
+
* address holds the identity — the listing that would hold it may not have
|
|
2273
|
+
* landed yet — while the client-local acceptance callback requires a current
|
|
2274
|
+
* holder, because it can arrive from a replaced or already-dropped Session.
|
|
2204
2275
|
*/
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
prevRunning = /* @__PURE__ */ new Map();
|
|
2276
|
+
engagedSessions = /* @__PURE__ */ new Set();
|
|
2277
|
+
disposed = false;
|
|
2208
2278
|
/** Per-session projection value stores, retained independently of instance arrival (the
|
|
2209
2279
|
* title-snapshot precedent, generalized): push frames land here whether or not the Session
|
|
2210
2280
|
* is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
|
|
@@ -2216,22 +2286,11 @@ window.__ModuleLoader__.load({
|
|
|
2216
2286
|
listPhase = "pending";
|
|
2217
2287
|
listError = null;
|
|
2218
2288
|
listInflight = null;
|
|
2219
|
-
/**
|
|
2289
|
+
/** Active list request's mutation log; its identity also fences completion after reconnect. */
|
|
2220
2290
|
listMutations = null;
|
|
2221
2291
|
addresses = /* @__PURE__ */ new Map();
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
/** Catalog owners whose membership changed while a pull was in flight: one trailing refresh after it settles. */
|
|
2225
|
-
catalogStale = /* @__PURE__ */ new Set();
|
|
2226
|
-
openCatalogs = /* @__PURE__ */ new Set();
|
|
2227
|
-
catalogDebounce = /* @__PURE__ */ new Map();
|
|
2228
|
-
/**
|
|
2229
|
-
* Background jobs per session, last-wins from Session Controller's control
|
|
2230
|
-
* stream. An empty set is stored as an absent key, so absence and `[]` are
|
|
2231
|
-
* one representation.
|
|
2232
|
-
*/
|
|
2233
|
-
jobsBySession = /* @__PURE__ */ new Map();
|
|
2234
|
-
selected;
|
|
2292
|
+
projectionLoads = /* @__PURE__ */ new Map();
|
|
2293
|
+
projectionInflight = /* @__PURE__ */ new Map();
|
|
2235
2294
|
listSnapshotCache;
|
|
2236
2295
|
/** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
|
|
2237
2296
|
* object when every field matches — wire refreshes mint all-new summary objects, so identity
|
|
@@ -2241,69 +2300,35 @@ window.__ModuleLoader__.load({
|
|
|
2241
2300
|
notifier = new Notifier(() => {
|
|
2242
2301
|
this.listSnapshotCache = this.buildListSnapshot();
|
|
2243
2302
|
});
|
|
2244
|
-
/**
|
|
2245
|
-
|
|
2246
|
-
* @param restoredSelection - persisted real-Session selection candidate.
|
|
2247
|
-
*/
|
|
2248
|
-
constructor(remote, restoredSelection, restoredAddress) {
|
|
2303
|
+
/** @param remote - generated Remote namespaces used by catalog and history readers. */
|
|
2304
|
+
constructor(remote) {
|
|
2249
2305
|
this.remote = remote;
|
|
2250
|
-
this.selected = restoredSelection;
|
|
2251
|
-
if (restoredAddress !== void 0) this.addresses.set(restoredAddress.childSessionId, restoredAddress);
|
|
2252
2306
|
this.listSnapshotCache = this.buildListSnapshot();
|
|
2253
2307
|
}
|
|
2254
2308
|
/**
|
|
2255
|
-
*
|
|
2256
|
-
* @param
|
|
2257
|
-
|
|
2258
|
-
select(sessionId) {
|
|
2259
|
-
const address = this.navigationAddress(sessionId);
|
|
2260
|
-
if (!this.summaries.some((summary) => summary.sessionId === sessionId) && address === void 0) throw new Error(`sessions.select: unknown session ${sessionId}`);
|
|
2261
|
-
if (address !== void 0) this.addresses.set(sessionId, address);
|
|
2262
|
-
this.sessions.get(sessionId)?.configureSubagent(address, address === void 0 ? void 0 : this.catalogs.get(address.parentSessionId)?.parentAvailable);
|
|
2263
|
-
this.selected = sessionId;
|
|
2264
|
-
this.completedNotifications.delete(sessionId);
|
|
2265
|
-
this.refreshSubagents(sessionId);
|
|
2266
|
-
this.notifier.notifyNow();
|
|
2267
|
-
}
|
|
2268
|
-
/**
|
|
2269
|
-
* Select a healthy child through its durable direct-parent address.
|
|
2270
|
-
* @param address - catalog-derived parent and child ids.
|
|
2271
|
-
*/
|
|
2272
|
-
selectSubagent(address) {
|
|
2273
|
-
const catalog = this.catalogs.get(address.parentSessionId);
|
|
2274
|
-
const entry = catalog?.entries.find((candidate) => candidate.id === address.childSessionId);
|
|
2275
|
-
if (entry === void 0 || entry.kind !== "child" || entry.mode !== address.mode) throw new Error(`sessions.selectSubagent: ${address.childSessionId} is not a healthy catalog child`);
|
|
2276
|
-
this.addresses.set(address.childSessionId, address);
|
|
2277
|
-
this.sessions.get(address.childSessionId)?.configureSubagent(address, catalog?.parentAvailable);
|
|
2278
|
-
this.selected = address.childSessionId;
|
|
2279
|
-
this.completedNotifications.delete(address.childSessionId);
|
|
2280
|
-
this.refreshSubagents(address.childSessionId);
|
|
2281
|
-
this.notifier.notifyNow();
|
|
2282
|
-
}
|
|
2283
|
-
/** Clear the selection (the layout falls to the no-session view state). */
|
|
2284
|
-
clearSelection() {
|
|
2285
|
-
this.selected = void 0;
|
|
2286
|
-
this.notifier.notifyNow();
|
|
2287
|
-
}
|
|
2288
|
-
/**
|
|
2289
|
-
* Return the durable catalog address retained for one child.
|
|
2290
|
-
* @param sessionId - possible addressed child id.
|
|
2291
|
-
* @returns The direct-parent address, when navigation discovered one.
|
|
2309
|
+
* Resolve an acquisition target without materializing a Session.
|
|
2310
|
+
* @param target - known identity or durable direct-parent address.
|
|
2311
|
+
* @returns the resolved identity with its explicit or catalog-derived history route installed.
|
|
2292
2312
|
*/
|
|
2293
|
-
|
|
2294
|
-
|
|
2313
|
+
resolveTarget(target) {
|
|
2314
|
+
const id = typeof target === "string" ? target : target.childSessionId;
|
|
2315
|
+
const address = typeof target === "string" ? this.subagentAddress(id) : target;
|
|
2316
|
+
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}`);
|
|
2317
|
+
if (address !== void 0) this.addresses.set(id, address);
|
|
2318
|
+
this.sessions.get(id)?.configureSubagent(address, address === void 0 ? void 0 : this.agentAvailable(address.parentSessionId));
|
|
2319
|
+
return id;
|
|
2295
2320
|
}
|
|
2296
2321
|
/**
|
|
2297
2322
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
2298
2323
|
* @param sessionId - possible child id in an already-loaded catalog.
|
|
2299
2324
|
* @returns A retained or catalog-derived direct-parent address.
|
|
2300
2325
|
*/
|
|
2301
|
-
|
|
2326
|
+
subagentAddress(sessionId) {
|
|
2302
2327
|
const retained = this.addresses.get(sessionId);
|
|
2303
2328
|
if (retained !== void 0) return retained;
|
|
2304
|
-
for (const
|
|
2305
|
-
const child =
|
|
2306
|
-
if (child
|
|
2329
|
+
for (const parentSessionId of this.projectionStores.keys()) {
|
|
2330
|
+
const child = this.projectionStores.get(parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId);
|
|
2331
|
+
if (child !== void 0) return {
|
|
2307
2332
|
parentSessionId,
|
|
2308
2333
|
childSessionId: sessionId,
|
|
2309
2334
|
mode: child.mode
|
|
@@ -2311,27 +2336,35 @@ window.__ModuleLoader__.load({
|
|
|
2311
2336
|
}
|
|
2312
2337
|
}
|
|
2313
2338
|
/**
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
*
|
|
2317
|
-
* @
|
|
2339
|
+
* Withdraw an exact Client instance before running its teardown callbacks.
|
|
2340
|
+
* @param sessionId - identity to withdraw.
|
|
2341
|
+
* @param expected - instance being released; a replacement is left untouched.
|
|
2342
|
+
* @returns completion of the detached instance's stream teardown.
|
|
2318
2343
|
*/
|
|
2319
|
-
|
|
2344
|
+
drop(sessionId, expected) {
|
|
2320
2345
|
const session = this.sessions.get(sessionId);
|
|
2346
|
+
if (session !== expected) return Promise.resolve();
|
|
2321
2347
|
this.sessions.delete(sessionId);
|
|
2322
|
-
|
|
2348
|
+
this.addresses.delete(sessionId);
|
|
2349
|
+
this.pruneEngagement(sessionId, this.retainedIds(this.summaries));
|
|
2350
|
+
return this.startSessionDisposal(session);
|
|
2323
2351
|
}
|
|
2324
2352
|
/**
|
|
2325
|
-
* Stop
|
|
2326
|
-
* @returns
|
|
2353
|
+
* Stop catalog requests and dispose every resident Session.
|
|
2354
|
+
* @returns once catalog requests and every Session stream have stopped.
|
|
2327
2355
|
*/
|
|
2328
2356
|
async dispose() {
|
|
2329
|
-
|
|
2330
|
-
this.
|
|
2331
|
-
this.
|
|
2332
|
-
this.
|
|
2357
|
+
this.disposed = true;
|
|
2358
|
+
this.listMutations = null;
|
|
2359
|
+
this.listInflight = null;
|
|
2360
|
+
this.engagedSessions.clear();
|
|
2361
|
+
const reads = [...this.projectionInflight.values()];
|
|
2362
|
+
for (const { controller } of reads) controller.abort();
|
|
2363
|
+
this.projectionInflight.clear();
|
|
2364
|
+
await Promise.all(reads.map((read) => read.promise));
|
|
2333
2365
|
const sessions = [...this.sessions.values()];
|
|
2334
2366
|
this.sessions.clear();
|
|
2367
|
+
this.addresses.clear();
|
|
2335
2368
|
for (const session of sessions) this.startSessionDisposal(session);
|
|
2336
2369
|
await this.drainSessionDisposals();
|
|
2337
2370
|
}
|
|
@@ -2350,7 +2383,8 @@ window.__ModuleLoader__.load({
|
|
|
2350
2383
|
}
|
|
2351
2384
|
/**
|
|
2352
2385
|
* Lazy build: return the existing instance or construct one (no auto-open —
|
|
2353
|
-
*
|
|
2386
|
+
* the reference allocator opens history after binding the scope).
|
|
2387
|
+
* New instances reconcile retained metadata before returning.
|
|
2354
2388
|
* @param sessionId - the session to get.
|
|
2355
2389
|
* @returns the resident instance.
|
|
2356
2390
|
*/
|
|
@@ -2359,45 +2393,73 @@ window.__ModuleLoader__.load({
|
|
|
2359
2393
|
if (session === void 0) {
|
|
2360
2394
|
session = this.createSession(sessionId);
|
|
2361
2395
|
this.sessions.set(sessionId, session);
|
|
2362
|
-
session.replaceControl(this.queues.get(sessionId) ?? []);
|
|
2363
2396
|
const summary = this.summaries.find((s) => s.sessionId === sessionId);
|
|
2364
2397
|
if (summary !== void 0) {
|
|
2365
|
-
session.handleBlank(summary
|
|
2398
|
+
session.handleBlank(this.effectiveBlank(summary));
|
|
2366
2399
|
session.handleRunning(summary.running);
|
|
2367
2400
|
} else {
|
|
2368
2401
|
const address = this.addresses.get(sessionId);
|
|
2369
|
-
|
|
2370
|
-
if (child?.kind === "child") {
|
|
2402
|
+
if ((address === void 0 ? void 0 : this.projectionStores.get(address.parentSessionId)?.values().subagentCatalog?.find((entry) => entry.id === sessionId)) !== void 0) {
|
|
2371
2403
|
session.handleBlank(false);
|
|
2372
|
-
session.handleRunning(
|
|
2373
|
-
}
|
|
2404
|
+
session.handleRunning(false);
|
|
2405
|
+
} else session.handleBlank(true);
|
|
2374
2406
|
}
|
|
2375
2407
|
}
|
|
2376
2408
|
return session;
|
|
2377
2409
|
}
|
|
2378
2410
|
createSession(sessionId) {
|
|
2379
2411
|
const address = this.addresses.get(sessionId);
|
|
2380
|
-
const parentAvailable = address === void 0 ? void 0 : this.
|
|
2412
|
+
const parentAvailable = address === void 0 ? void 0 : this.agentAvailable(address.parentSessionId);
|
|
2381
2413
|
return new Session(sessionId, this.remote, {
|
|
2382
2414
|
...address === void 0 ? {} : {
|
|
2383
2415
|
address,
|
|
2384
|
-
...
|
|
2416
|
+
...parentAvailable === void 0 ? {} : { parentAvailable }
|
|
2385
2417
|
},
|
|
2386
2418
|
onEngaged: (engaged) => {
|
|
2387
|
-
this.
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2419
|
+
if (this.disposed || !this.retainedIds(this.summaries).has(engaged.sessionId)) return;
|
|
2420
|
+
if (!this.engagedSessions.has(engaged.sessionId)) {
|
|
2421
|
+
this.engagedSessions.add(engaged.sessionId);
|
|
2422
|
+
this.recordMutation({
|
|
2423
|
+
kind: "engaged",
|
|
2424
|
+
sessionId: engaged.sessionId
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
this.sessions.get(engaged.sessionId)?.handleBlank(false);
|
|
2391
2428
|
},
|
|
2392
2429
|
projections: this.projectionStore(sessionId)
|
|
2393
2430
|
});
|
|
2394
2431
|
}
|
|
2432
|
+
effectiveBlank(summary) {
|
|
2433
|
+
return summary.blank && !this.engagedSessions.has(summary.sessionId);
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* Identities an engagement may still belong to: the given list rows, resident
|
|
2437
|
+
* Session instances, and retained child addresses.
|
|
2438
|
+
* @param summaries - list rows of the caller's snapshot.
|
|
2439
|
+
* @returns the retained identity set.
|
|
2440
|
+
*/
|
|
2441
|
+
retainedIds(summaries) {
|
|
2442
|
+
const retained = new Set(summaries.map((summary) => summary.sessionId));
|
|
2443
|
+
for (const sessionId of this.sessions.keys()) retained.add(sessionId);
|
|
2444
|
+
for (const sessionId of this.addresses.keys()) retained.add(sessionId);
|
|
2445
|
+
return retained;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Forget one engagement that no retained identity holds.
|
|
2449
|
+
* @param sessionId - identity whose engagement may be dropped.
|
|
2450
|
+
* @param retained - identities from {@link retainedIds} for the caller's snapshot.
|
|
2451
|
+
*/
|
|
2452
|
+
pruneEngagement(sessionId, retained) {
|
|
2453
|
+
if (!retained.has(sessionId)) this.engagedSessions.delete(sessionId);
|
|
2454
|
+
}
|
|
2395
2455
|
/** Resident per-session projection store (create-on-demand; outlives instantiation). */
|
|
2396
2456
|
projectionStore(sessionId) {
|
|
2397
2457
|
let store = this.projectionStores.get(sessionId);
|
|
2398
2458
|
if (store === void 0) {
|
|
2399
2459
|
store = new ProjectionValueStore();
|
|
2460
|
+
const projections = store;
|
|
2400
2461
|
store.subscribeAny(() => {
|
|
2462
|
+
if (projections.values().sessionListMetadata?.blank === false) this.sessions.get(sessionId)?.handleBlank(false);
|
|
2401
2463
|
this.notifier.markDirty();
|
|
2402
2464
|
});
|
|
2403
2465
|
this.projectionStores.set(sessionId, store);
|
|
@@ -2405,85 +2467,70 @@ window.__ModuleLoader__.load({
|
|
|
2405
2467
|
return store;
|
|
2406
2468
|
}
|
|
2407
2469
|
/**
|
|
2408
|
-
*
|
|
2409
|
-
* @param
|
|
2470
|
+
* Load a complete projection baseline once per connection; retry unsuccessful reads.
|
|
2471
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
2472
|
+
* @returns completion of the current or newly started read.
|
|
2410
2473
|
*/
|
|
2411
|
-
|
|
2412
|
-
const existing = this.
|
|
2474
|
+
refreshProjections(sessionId) {
|
|
2475
|
+
const existing = this.projectionInflight.get(sessionId);
|
|
2413
2476
|
if (existing !== void 0) return existing.promise;
|
|
2414
|
-
|
|
2415
|
-
const
|
|
2416
|
-
const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
...previous?.parentAvailable === void 0 ? {} : { parentAvailable: previous.parentAvailable },
|
|
2477
|
+
if (this.projectionLoads.get(sessionId)?.state === "ready") return Promise.resolve();
|
|
2478
|
+
const controller = new AbortController();
|
|
2479
|
+
const store = this.projectionStore(sessionId);
|
|
2480
|
+
const initialValues = store.values();
|
|
2481
|
+
this.projectionLoads.set(sessionId, {
|
|
2420
2482
|
state: "loading",
|
|
2421
2483
|
error: null
|
|
2422
2484
|
});
|
|
2423
2485
|
this.notifier.markDirty();
|
|
2424
2486
|
const operation = (async () => {
|
|
2425
2487
|
try {
|
|
2426
|
-
const result = await this.remote.
|
|
2488
|
+
const result = await this.remote.session.projections({ sessionId }, controller.signal);
|
|
2489
|
+
if (controller.signal.aborted) return;
|
|
2427
2490
|
if (result.ok) {
|
|
2428
|
-
|
|
2429
|
-
this.catalogs.set(parentSessionId, {
|
|
2491
|
+
if (result.value !== null) store.seed({
|
|
2430
2492
|
...result.value,
|
|
2431
|
-
|
|
2432
|
-
|
|
2493
|
+
asOfSeq: sessionSeqCursor(result.value.asOfSeq)
|
|
2494
|
+
});
|
|
2495
|
+
else if (store.values() === initialValues) store.clear();
|
|
2496
|
+
this.projectionLoads.set(sessionId, {
|
|
2433
2497
|
state: "ready",
|
|
2434
2498
|
error: null
|
|
2435
2499
|
});
|
|
2436
|
-
|
|
2437
|
-
if (address.parentSessionId !== parentSessionId) continue;
|
|
2438
|
-
this.sessions.get(childId)?.handleSubagentParentAvailable(parentAvailable);
|
|
2439
|
-
}
|
|
2440
|
-
} else this.catalogs.set(parentSessionId, {
|
|
2441
|
-
entries: this.withCatalogMutations(previous?.entries ?? [], expandableRows, activityRows),
|
|
2442
|
-
...catalogAvailability(this.catalogInflight.get(parentSessionId)?.parentAvailableOverride ?? previous?.parentAvailable),
|
|
2500
|
+
} else this.projectionLoads.set(sessionId, {
|
|
2443
2501
|
state: "error",
|
|
2444
2502
|
error: result.error
|
|
2445
2503
|
});
|
|
2446
2504
|
} catch (error) {
|
|
2505
|
+
if (controller.signal.aborted) return;
|
|
2447
2506
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) throw error;
|
|
2448
|
-
this.
|
|
2449
|
-
entries: this.withCatalogMutations(previous?.entries ?? [], expandableRows, activityRows),
|
|
2450
|
-
...catalogAvailability(this.catalogInflight.get(parentSessionId)?.parentAvailableOverride ?? previous?.parentAvailable),
|
|
2507
|
+
this.projectionLoads.set(sessionId, {
|
|
2451
2508
|
state: "error",
|
|
2452
2509
|
error
|
|
2453
2510
|
});
|
|
2454
2511
|
} finally {
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2512
|
+
if (!controller.signal.aborted) {
|
|
2513
|
+
this.projectionInflight.delete(sessionId);
|
|
2514
|
+
this.notifier.markDirty();
|
|
2515
|
+
}
|
|
2458
2516
|
}
|
|
2459
2517
|
})();
|
|
2460
|
-
this.
|
|
2518
|
+
this.projectionInflight.set(sessionId, {
|
|
2461
2519
|
promise: operation,
|
|
2462
|
-
|
|
2463
|
-
activityRows,
|
|
2464
|
-
parentAvailableOverride: void 0
|
|
2520
|
+
controller
|
|
2465
2521
|
});
|
|
2466
2522
|
return operation;
|
|
2467
2523
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
this.openCatalogs.add(parentSessionId);
|
|
2476
|
-
this.refreshSubagents(parentSessionId);
|
|
2477
|
-
} else {
|
|
2478
|
-
this.openCatalogs.delete(parentSessionId);
|
|
2479
|
-
const timer = this.catalogDebounce.get(parentSessionId);
|
|
2480
|
-
if (timer !== void 0) {
|
|
2481
|
-
clearTimeout(timer);
|
|
2482
|
-
this.catalogDebounce.delete(parentSessionId);
|
|
2483
|
-
}
|
|
2524
|
+
agentAvailable(sessionId) {
|
|
2525
|
+
return this.summaries.find((summary) => summary.sessionId === sessionId)?.agentAvailable ?? (this.listPhase === "ready" ? false : void 0);
|
|
2526
|
+
}
|
|
2527
|
+
updateParentAvailability() {
|
|
2528
|
+
for (const [childId, address] of this.addresses) {
|
|
2529
|
+
const available = this.agentAvailable(address.parentSessionId);
|
|
2530
|
+
if (available !== void 0) this.sessions.get(childId)?.handleSubagentParentAvailable(available);
|
|
2484
2531
|
}
|
|
2485
2532
|
}
|
|
2486
|
-
/** Full refresh via session.list (single-flight
|
|
2533
|
+
/** Full refresh via session.list (single-flight within one Host generation). */
|
|
2487
2534
|
refreshList() {
|
|
2488
2535
|
if (this.listInflight !== null) return this.listInflight;
|
|
2489
2536
|
this.listState = "loading";
|
|
@@ -2495,44 +2542,41 @@ window.__ModuleLoader__.load({
|
|
|
2495
2542
|
this.listInflight = (async () => {
|
|
2496
2543
|
try {
|
|
2497
2544
|
const result = await this.remote.session.list({});
|
|
2545
|
+
if (this.listMutations !== mutations) return;
|
|
2498
2546
|
if (result.ok) {
|
|
2499
2547
|
const baseline = this.listPhase === "pending" ? [...result.value.items] : mergeOrderedBaseline(established, result.value.items, (summary) => summary.sessionId);
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
for (const
|
|
2503
|
-
|
|
2504
|
-
this.summaries = summaries;
|
|
2505
|
-
this.syncCompletedNotifications();
|
|
2506
|
-
}
|
|
2548
|
+
const removedSincePull = /* @__PURE__ */ new Set();
|
|
2549
|
+
for (const mutation of mutations) if (mutation.kind === "remove") removedSincePull.add(mutation.sessionId);
|
|
2550
|
+
for (const s of baseline) if (s.running && !removedSincePull.has(s.sessionId)) this.engagedSessions.add(s.sessionId);
|
|
2551
|
+
const summaries = mutations.reduce(applyMutation, baseline);
|
|
2507
2552
|
this.summaries = summaries;
|
|
2553
|
+
const retained = this.retainedIds(summaries);
|
|
2554
|
+
for (const sessionId of this.engagedSessions) this.pruneEngagement(sessionId, retained);
|
|
2508
2555
|
this.listState = "idle";
|
|
2509
2556
|
this.listPhase = "ready";
|
|
2510
|
-
this.
|
|
2557
|
+
this.updateParentAvailability();
|
|
2511
2558
|
for (const s of this.summaries) {
|
|
2512
2559
|
const session = this.sessions.get(s.sessionId);
|
|
2513
2560
|
if (session === void 0) continue;
|
|
2514
|
-
session.handleBlank(s
|
|
2561
|
+
session.handleBlank(this.effectiveBlank(s));
|
|
2515
2562
|
session.handleRunning(s.running);
|
|
2516
2563
|
}
|
|
2517
|
-
for (const s of result.value.items)
|
|
2518
|
-
const block = s.projections;
|
|
2519
|
-
if (block === void 0) continue;
|
|
2520
|
-
const store = this.projectionStore(s.sessionId);
|
|
2521
|
-
const values = block.values;
|
|
2522
|
-
for (const key of Object.keys(values)) store.apply(key, values[key], sessionSeqCursor(block.asOfSeq));
|
|
2523
|
-
}
|
|
2564
|
+
for (const s of result.value.items) if (s.projections !== void 0) this.applyListBlock(s.sessionId, s.projections);
|
|
2524
2565
|
} else {
|
|
2525
2566
|
this.listState = "error";
|
|
2526
2567
|
this.listError = result.error;
|
|
2527
2568
|
}
|
|
2528
2569
|
} catch (error) {
|
|
2529
2570
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) throw error;
|
|
2571
|
+
if (this.listMutations !== mutations) return;
|
|
2530
2572
|
this.listState = "error";
|
|
2531
2573
|
this.listError = error;
|
|
2532
2574
|
} finally {
|
|
2533
|
-
this.listMutations
|
|
2534
|
-
|
|
2535
|
-
|
|
2575
|
+
if (this.listMutations === mutations) {
|
|
2576
|
+
this.listMutations = null;
|
|
2577
|
+
this.listInflight = null;
|
|
2578
|
+
this.notifier.markDirty();
|
|
2579
|
+
}
|
|
2536
2580
|
}
|
|
2537
2581
|
})();
|
|
2538
2582
|
return this.listInflight;
|
|
@@ -2573,8 +2617,9 @@ window.__ModuleLoader__.load({
|
|
|
2573
2617
|
};
|
|
2574
2618
|
const result = await this.remote.session.create(payload);
|
|
2575
2619
|
if (result.ok) this.recordMutation({
|
|
2576
|
-
kind: "
|
|
2620
|
+
kind: "placeholder",
|
|
2577
2621
|
summary: {
|
|
2622
|
+
agentAvailable: true,
|
|
2578
2623
|
sessionId: result.value.sessionId,
|
|
2579
2624
|
updatedAt: Date.now(),
|
|
2580
2625
|
running: false,
|
|
@@ -2585,8 +2630,9 @@ window.__ModuleLoader__.load({
|
|
|
2585
2630
|
else {
|
|
2586
2631
|
const publishedSessionId = workspaceAttachSessionId(result.error);
|
|
2587
2632
|
if (publishedSessionId !== void 0) this.recordMutation({
|
|
2588
|
-
kind: "
|
|
2633
|
+
kind: "placeholder",
|
|
2589
2634
|
summary: {
|
|
2635
|
+
agentAvailable: true,
|
|
2590
2636
|
sessionId: publishedSessionId,
|
|
2591
2637
|
updatedAt: Date.now(),
|
|
2592
2638
|
running: false,
|
|
@@ -2598,11 +2644,12 @@ window.__ModuleLoader__.load({
|
|
|
2598
2644
|
}
|
|
2599
2645
|
/**
|
|
2600
2646
|
* Contract session.fork; on success merge the child into summaries
|
|
2601
|
-
* immediately (same synchronous-addressability guarantee as create).
|
|
2602
|
-
*
|
|
2603
|
-
*
|
|
2604
|
-
*
|
|
2605
|
-
*
|
|
2647
|
+
* immediately (same synchronous-addressability guarantee as create).
|
|
2648
|
+
* Blankness starts provisionally true so the authoritative Host summary can
|
|
2649
|
+
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
2650
|
+
* lineage rides parentSessionId. A child published before Workspace
|
|
2651
|
+
* attachment fails is also reconciled into the list.
|
|
2652
|
+
* @param opts - source session and the optional exact inclusive boundary seq.
|
|
2606
2653
|
* @returns the fork result (the child session id).
|
|
2607
2654
|
*/
|
|
2608
2655
|
async fork(opts) {
|
|
@@ -2613,12 +2660,13 @@ window.__ModuleLoader__.load({
|
|
|
2613
2660
|
});
|
|
2614
2661
|
const childId = result.ok ? result.value.sessionId : workspaceAttachSessionId(result.error);
|
|
2615
2662
|
if (childId !== void 0) this.recordMutation({
|
|
2616
|
-
kind: "
|
|
2663
|
+
kind: "placeholder",
|
|
2617
2664
|
summary: {
|
|
2665
|
+
agentAvailable: true,
|
|
2618
2666
|
sessionId: childId,
|
|
2619
2667
|
updatedAt: Date.now(),
|
|
2620
2668
|
running: false,
|
|
2621
|
-
blank:
|
|
2669
|
+
blank: true,
|
|
2622
2670
|
parentSessionId: opts.sessionId,
|
|
2623
2671
|
...source?.cwd !== void 0 ? { cwd: source.cwd } : {}
|
|
2624
2672
|
}
|
|
@@ -2626,22 +2674,35 @@ window.__ModuleLoader__.load({
|
|
|
2626
2674
|
return result;
|
|
2627
2675
|
}
|
|
2628
2676
|
/**
|
|
2629
|
-
*
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
*
|
|
2677
|
+
* Rename a Session and update its title projection without opening its history.
|
|
2678
|
+
* @param sessionId - Session to rename.
|
|
2679
|
+
* @param title - raw title text for Host normalization.
|
|
2680
|
+
* @returns the accepted title and event position, or the Remote failure.
|
|
2681
|
+
*/
|
|
2682
|
+
async rename(sessionId, title) {
|
|
2683
|
+
const result = await this.remote.session.rename({
|
|
2684
|
+
sessionId,
|
|
2685
|
+
title
|
|
2686
|
+
});
|
|
2687
|
+
if (result.ok) this.projectionStore(sessionId).apply("title", result.value.title, SessionSeq(result.value.seq));
|
|
2688
|
+
return result;
|
|
2689
|
+
}
|
|
2690
|
+
/**
|
|
2691
|
+
* Merge a Host summary, replacing live state and filling missing metadata.
|
|
2692
|
+
* Local create/fork placeholders only fill metadata on an existing row.
|
|
2633
2693
|
*/
|
|
2634
2694
|
mergeSummary(summary) {
|
|
2635
2695
|
this.recordMutation({
|
|
2636
2696
|
kind: "upsert",
|
|
2637
2697
|
summary
|
|
2638
2698
|
});
|
|
2699
|
+
this.updateParentAvailability();
|
|
2639
2700
|
}
|
|
2640
2701
|
/** Apply immediately and retain for replay when a list response is in flight. */
|
|
2641
2702
|
recordMutation(mutation) {
|
|
2703
|
+
if (this.disposed) return;
|
|
2642
2704
|
this.listMutations?.push(mutation);
|
|
2643
2705
|
this.summaries = applyMutation(this.summaries, mutation);
|
|
2644
|
-
this.syncCompletedNotifications();
|
|
2645
2706
|
this.notifier.markDirty();
|
|
2646
2707
|
}
|
|
2647
2708
|
/**
|
|
@@ -2661,6 +2722,14 @@ window.__ModuleLoader__.load({
|
|
|
2661
2722
|
return this.listSnapshotCache;
|
|
2662
2723
|
}
|
|
2663
2724
|
/**
|
|
2725
|
+
* Read cached projection values for a Session that may exist only in a loaded subagent catalog.
|
|
2726
|
+
* @param sessionId - Session whose control or history baseline supplied projections.
|
|
2727
|
+
* @returns current values, or undefined before any projection store exists.
|
|
2728
|
+
*/
|
|
2729
|
+
projectionValues(sessionId) {
|
|
2730
|
+
return this.projectionStores.get(sessionId)?.values();
|
|
2731
|
+
}
|
|
2732
|
+
/**
|
|
2664
2733
|
* Apply a complete control baseline or one later replacement frame.
|
|
2665
2734
|
* @param frame - baseline or live control replacement from Session Controller.
|
|
2666
2735
|
*/
|
|
@@ -2669,35 +2738,18 @@ window.__ModuleLoader__.load({
|
|
|
2669
2738
|
this.replaceControlBaseline(frame.value);
|
|
2670
2739
|
return;
|
|
2671
2740
|
}
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
this.notifier.markDirty();
|
|
2675
|
-
return;
|
|
2676
|
-
}
|
|
2677
|
-
if (frame.type === "jobs") {
|
|
2678
|
-
if (frame.jobs.length === 0) this.jobsBySession.delete(frame.sessionId);
|
|
2679
|
-
else this.jobsBySession.set(frame.sessionId, frame.jobs);
|
|
2680
|
-
this.notifier.markDirty();
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
this.queues.set(frame.sessionId, frame.items);
|
|
2684
|
-
this.sessions.get(frame.sessionId)?.handleControlFrame(frame);
|
|
2741
|
+
this.projectionStore(frame.sessionId).apply(frame.key, frame.value, SessionSeq(frame.seq));
|
|
2742
|
+
this.notifier.markDirty();
|
|
2685
2743
|
}
|
|
2686
2744
|
replaceControlBaseline(baseline) {
|
|
2687
|
-
this.queues.clear();
|
|
2688
|
-
for (const [sessionId, items] of Object.entries(baseline.queues)) this.queues.set(sessionId, items);
|
|
2689
|
-
this.jobsBySession.clear();
|
|
2690
|
-
for (const [sessionId, jobs] of Object.entries(baseline.jobs)) if (jobs.length > 0) this.jobsBySession.set(sessionId, jobs);
|
|
2691
2745
|
for (const [sessionId, block] of Object.entries(baseline.projections)) {
|
|
2692
2746
|
const store = this.projectionStore(sessionId);
|
|
2693
2747
|
const asOfSeq = sessionSeqCursor(block.asOfSeq);
|
|
2694
|
-
store.truncate(asOfSeq);
|
|
2695
2748
|
store.seed({
|
|
2696
2749
|
...block,
|
|
2697
2750
|
asOfSeq
|
|
2698
2751
|
});
|
|
2699
2752
|
}
|
|
2700
|
-
for (const [sessionId, session] of this.sessions) session.replaceControl(this.queues.get(sessionId) ?? []);
|
|
2701
2753
|
this.notifier.markDirty();
|
|
2702
2754
|
}
|
|
2703
2755
|
/**
|
|
@@ -2706,45 +2758,55 @@ window.__ModuleLoader__.load({
|
|
|
2706
2758
|
*/
|
|
2707
2759
|
handleSessionAdded(summary) {
|
|
2708
2760
|
this.mergeSummary(summary);
|
|
2709
|
-
this.
|
|
2710
|
-
|
|
2711
|
-
if (projections !== void 0)
|
|
2712
|
-
|
|
2713
|
-
|
|
2761
|
+
if (!this.disposed && summary.running) this.engagedSessions.add(summary.sessionId);
|
|
2762
|
+
this.sessions.get(summary.sessionId)?.handleBlank(this.effectiveBlank(summary));
|
|
2763
|
+
if (summary.projections !== void 0) this.applyListBlock(summary.sessionId, summary.projections);
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Merge one list-surface projection block by the sequence space it declares.
|
|
2767
|
+
* A `sequenced` block came from the Host's live registry for an attached
|
|
2768
|
+
* Session, so each key lands under higher-seq-wins against that Session's
|
|
2769
|
+
* baselines and frames. A `cached` block was viewed from the persisted
|
|
2770
|
+
* checkpoint by a header-only listing: its watermark is not comparable with
|
|
2771
|
+
* this connection's seqs, so it only fills keys no sequenced row holds.
|
|
2772
|
+
*/
|
|
2773
|
+
applyListBlock(sessionId, block) {
|
|
2774
|
+
const store = this.projectionStore(sessionId);
|
|
2775
|
+
switch (block.kind) {
|
|
2776
|
+
case "sequenced": {
|
|
2777
|
+
const seq = sessionSeqCursor(block.asOfSeq);
|
|
2778
|
+
for (const [key, value] of Object.entries(block.values)) store.apply(key, value, seq);
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
case "cached":
|
|
2782
|
+
store.applyCached(block.values);
|
|
2783
|
+
return;
|
|
2784
|
+
default: assertNever(block.kind, "session list projection block kind");
|
|
2714
2785
|
}
|
|
2715
|
-
if (summary.origin === "subagent" && summary.parentSessionId !== void 0) this.markCatalogParentExpandable(summary.parentSessionId);
|
|
2716
|
-
if (summary.parentSessionId !== void 0 && (this.selected === summary.parentSessionId || this.openCatalogs.has(summary.parentSessionId))) this.scheduleCatalogRefresh(summary.parentSessionId);
|
|
2717
2786
|
}
|
|
2718
2787
|
/**
|
|
2719
2788
|
* Apply one Session removal forwarded through `ctx.remote.$on`.
|
|
2720
2789
|
* @param sessionId - removed Session identity.
|
|
2721
2790
|
*/
|
|
2722
2791
|
handleSessionRemoved(sessionId) {
|
|
2723
|
-
const durableSubagent = this.summaries.
|
|
2792
|
+
const durableSubagent = this.subagentAddress(sessionId) !== void 0 || this.summaries.some((summary) => summary.sessionId === sessionId && summary.origin === "subagent");
|
|
2724
2793
|
this.recordMutation(durableSubagent ? {
|
|
2725
2794
|
kind: "status",
|
|
2726
2795
|
sessionId,
|
|
2727
|
-
running: false
|
|
2796
|
+
running: false,
|
|
2797
|
+
agentAvailable: false
|
|
2728
2798
|
} : {
|
|
2729
2799
|
kind: "remove",
|
|
2730
2800
|
sessionId
|
|
2731
2801
|
});
|
|
2732
|
-
this.updateCatalogActivity(sessionId, false);
|
|
2733
2802
|
if (durableSubagent) this.sessions.get(sessionId)?.handleRunning(false);
|
|
2734
2803
|
else this.sessions.get(sessionId)?.handleRemoved();
|
|
2735
|
-
this.
|
|
2736
|
-
this.
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
this.catalogStale.add(sessionId);
|
|
2742
|
-
}
|
|
2743
|
-
const ownedCatalog = this.catalogs.get(sessionId);
|
|
2744
|
-
if (ownedCatalog !== void 0 && ownedCatalog.parentAvailable) this.catalogs.set(sessionId, {
|
|
2745
|
-
...ownedCatalog,
|
|
2746
|
-
parentAvailable: false
|
|
2747
|
-
});
|
|
2804
|
+
const catalog = this.projectionStores.get(sessionId)?.values().subagentCatalog;
|
|
2805
|
+
if (!durableSubagent && (catalog === void 0 || catalog.length === 0)) this.projectionStores.delete(sessionId);
|
|
2806
|
+
this.pruneEngagement(sessionId, this.retainedIds(this.summaries));
|
|
2807
|
+
this.projectionInflight.get(sessionId)?.controller.abort();
|
|
2808
|
+
this.projectionInflight.delete(sessionId);
|
|
2809
|
+
this.projectionLoads.delete(sessionId);
|
|
2748
2810
|
for (const [childId, address] of this.addresses) if (address.parentSessionId === sessionId) this.sessions.get(childId)?.handleSubagentParentAvailable(false);
|
|
2749
2811
|
}
|
|
2750
2812
|
/**
|
|
@@ -2753,13 +2815,15 @@ window.__ModuleLoader__.load({
|
|
|
2753
2815
|
* @param running - current Agent running state.
|
|
2754
2816
|
*/
|
|
2755
2817
|
handleSessionStatus(sessionId, running) {
|
|
2818
|
+
if (!this.disposed && running) this.engagedSessions.add(sessionId);
|
|
2756
2819
|
this.recordMutation({
|
|
2757
2820
|
kind: "status",
|
|
2758
2821
|
sessionId,
|
|
2759
|
-
running
|
|
2822
|
+
running,
|
|
2823
|
+
agentAvailable: true
|
|
2760
2824
|
});
|
|
2825
|
+
this.updateParentAvailability();
|
|
2761
2826
|
this.sessions.get(sessionId)?.handleRunning(running);
|
|
2762
|
-
this.updateCatalogActivity(sessionId, running);
|
|
2763
2827
|
}
|
|
2764
2828
|
/**
|
|
2765
2829
|
* Advance Session-list activity from one user-authored durable message.
|
|
@@ -2783,165 +2847,87 @@ window.__ModuleLoader__.load({
|
|
|
2783
2847
|
}
|
|
2784
2848
|
/**
|
|
2785
2849
|
* Repair one re-established Host-event generation with queryable baselines.
|
|
2850
|
+
* Discard old projection cuts before new queries, including cold Sessions
|
|
2851
|
+
* absent from the process-local control baseline.
|
|
2786
2852
|
* Opened Session follow streams resume independently through API Gateway.
|
|
2787
2853
|
*/
|
|
2788
2854
|
handleConnected() {
|
|
2855
|
+
for (const store of this.projectionStores.values()) store.clear();
|
|
2856
|
+
this.listMutations = null;
|
|
2857
|
+
this.listInflight = null;
|
|
2789
2858
|
this.refreshList();
|
|
2790
|
-
const
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
}
|
|
2795
|
-
/** Debounce membership refetches while one parent catalog is selected or open. */
|
|
2796
|
-
scheduleCatalogRefresh(parentSessionId) {
|
|
2797
|
-
if (this.catalogDebounce.has(parentSessionId)) return;
|
|
2798
|
-
const timer = setTimeout(() => {
|
|
2799
|
-
this.catalogDebounce.delete(parentSessionId);
|
|
2800
|
-
if (this.catalogInflight.has(parentSessionId)) {
|
|
2801
|
-
this.catalogStale.add(parentSessionId);
|
|
2802
|
-
return;
|
|
2803
|
-
}
|
|
2804
|
-
this.refreshSubagents(parentSessionId);
|
|
2805
|
-
}, 50);
|
|
2806
|
-
this.catalogDebounce.set(parentSessionId, timer);
|
|
2807
|
-
}
|
|
2808
|
-
/** Apply one Agent-driver transition to loaded and in-flight catalogs. */
|
|
2809
|
-
updateCatalogActivity(childSessionId, running) {
|
|
2810
|
-
const activity = running ? "running" : "inactive";
|
|
2811
|
-
for (const inflight of this.catalogInflight.values()) inflight.activityRows.set(childSessionId, activity);
|
|
2812
|
-
let changed = false;
|
|
2813
|
-
for (const [parentSessionId, catalog] of this.catalogs) {
|
|
2814
|
-
if (!catalog.entries.some((entry) => entry.kind === "child" && entry.id === childSessionId && entry.activity !== activity)) continue;
|
|
2815
|
-
const entries = catalog.entries.map((entry) => {
|
|
2816
|
-
if (entry.kind !== "child" || entry.id !== childSessionId) return entry;
|
|
2817
|
-
return {
|
|
2818
|
-
...entry,
|
|
2819
|
-
activity
|
|
2820
|
-
};
|
|
2821
|
-
});
|
|
2822
|
-
changed = true;
|
|
2823
|
-
this.catalogs.set(parentSessionId, {
|
|
2824
|
-
...catalog,
|
|
2825
|
-
entries
|
|
2826
|
-
});
|
|
2827
|
-
}
|
|
2828
|
-
if (changed) this.notifier.markDirty();
|
|
2829
|
-
}
|
|
2830
|
-
/** Preserve and project a positive expandability hint after one direct subagent publishes. */
|
|
2831
|
-
markCatalogParentExpandable(parentSessionId) {
|
|
2832
|
-
this.applyCatalogParentExpandable(parentSessionId);
|
|
2833
|
-
for (const inflight of this.catalogInflight.values()) inflight.expandableRows.add(parentSessionId);
|
|
2834
|
-
}
|
|
2835
|
-
/** Apply one positive expandability hint to every loaded catalog containing that unique row id. */
|
|
2836
|
-
applyCatalogParentExpandable(parentSessionId) {
|
|
2837
|
-
let changed = false;
|
|
2838
|
-
for (const [catalogParentId, catalog] of this.catalogs) {
|
|
2839
|
-
if (!catalog.entries.some((entry) => entry.kind === "child" && entry.id === parentSessionId && !entry.hasChildren)) continue;
|
|
2840
|
-
const entries = catalog.entries.map((entry) => {
|
|
2841
|
-
if (entry.kind !== "child" || entry.id !== parentSessionId || entry.hasChildren) return entry;
|
|
2842
|
-
return {
|
|
2843
|
-
...entry,
|
|
2844
|
-
hasChildren: true
|
|
2845
|
-
};
|
|
2846
|
-
});
|
|
2847
|
-
changed = true;
|
|
2848
|
-
this.catalogs.set(catalogParentId, {
|
|
2849
|
-
...catalog,
|
|
2850
|
-
entries
|
|
2851
|
-
});
|
|
2852
|
-
}
|
|
2853
|
-
if (changed) this.notifier.markDirty();
|
|
2854
|
-
}
|
|
2855
|
-
/** Fold request-local row mutations into one catalog result before publication. */
|
|
2856
|
-
withCatalogMutations(entries, expandableRows, activityRows) {
|
|
2857
|
-
return entries.map((entry) => {
|
|
2858
|
-
if (entry.kind !== "child") return entry;
|
|
2859
|
-
const activity = activityRows.get(entry.id);
|
|
2860
|
-
if (!expandableRows.has(entry.id) && activity === void 0) return entry;
|
|
2861
|
-
return {
|
|
2862
|
-
...entry,
|
|
2863
|
-
...expandableRows.has(entry.id) ? { hasChildren: true } : {},
|
|
2864
|
-
...activity === void 0 ? {} : { activity }
|
|
2865
|
-
};
|
|
2866
|
-
});
|
|
2867
|
-
}
|
|
2868
|
-
/**
|
|
2869
|
-
* Reconcile completion reminders against the latest summaries, eagerly after
|
|
2870
|
-
* every mutation and pull (a snapshot-build-time pass would collapse
|
|
2871
|
-
* consecutive status frames into one observation). A running→idle edge of a
|
|
2872
|
-
* non-selected session arms its reminder; running disarms it; removal drops
|
|
2873
|
-
* it. First observation only records the running bit — sessions already
|
|
2874
|
-
* idle at load get no reminder.
|
|
2875
|
-
*/
|
|
2876
|
-
syncCompletedNotifications() {
|
|
2877
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2878
|
-
for (const s of this.summaries) {
|
|
2879
|
-
seen.add(s.sessionId);
|
|
2880
|
-
const prev = this.prevRunning.get(s.sessionId);
|
|
2881
|
-
if (prev === void 0) {
|
|
2882
|
-
this.prevRunning.set(s.sessionId, s.running);
|
|
2883
|
-
continue;
|
|
2884
|
-
}
|
|
2885
|
-
if (prev && !s.running) {
|
|
2886
|
-
if (s.sessionId !== this.selected) this.completedNotifications.add(s.sessionId);
|
|
2887
|
-
} else if (s.running) this.completedNotifications.delete(s.sessionId);
|
|
2888
|
-
this.prevRunning.set(s.sessionId, s.running);
|
|
2859
|
+
const parents = new Set(this.projectionLoads.keys());
|
|
2860
|
+
for (const id of this.sessions.keys()) {
|
|
2861
|
+
const address = this.addresses.get(id);
|
|
2862
|
+
if (address !== void 0) parents.add(address.parentSessionId);
|
|
2889
2863
|
}
|
|
2890
|
-
for (const
|
|
2891
|
-
|
|
2864
|
+
for (const { controller } of this.projectionInflight.values()) controller.abort();
|
|
2865
|
+
this.projectionInflight.clear();
|
|
2866
|
+
this.projectionLoads.clear();
|
|
2867
|
+
for (const parentSessionId of parents) this.refreshProjections(parentSessionId);
|
|
2892
2868
|
}
|
|
2893
2869
|
buildListSnapshot() {
|
|
2894
2870
|
const items = flattenLineage(this.summaries.map((summary) => {
|
|
2895
2871
|
const projectionStore = this.projectionStores.get(summary.sessionId);
|
|
2896
2872
|
const title = projectionStore?.get("title");
|
|
2897
2873
|
const projectionValues = projectionStore?.values();
|
|
2874
|
+
const metadata = projectionValues?.sessionListMetadata;
|
|
2898
2875
|
return {
|
|
2899
2876
|
...summary,
|
|
2877
|
+
blank: this.effectiveBlank(summary) && metadata?.blank !== false,
|
|
2878
|
+
updatedAt: Math.max(summary.updatedAt, metadata?.lastPromptAt ?? 0),
|
|
2900
2879
|
...typeof title === "string" && title !== "" ? { title } : {},
|
|
2901
2880
|
...projectionValues === void 0 ? {} : { projectionValues }
|
|
2902
2881
|
};
|
|
2903
|
-
})
|
|
2882
|
+
})).map((entry) => {
|
|
2904
2883
|
const prev = this.entryCache.get(entry.sessionId);
|
|
2905
|
-
if (prev !== void 0 && prev.updatedAt === entry.updatedAt && prev.running === entry.running && prev.blank === entry.blank && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth && prev.projectionValues === entry.projectionValues
|
|
2884
|
+
if (prev !== void 0 && prev.updatedAt === entry.updatedAt && prev.running === entry.running && prev.blank === entry.blank && prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd && prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth && prev.projectionValues === entry.projectionValues) return prev;
|
|
2906
2885
|
this.entryCache.set(entry.sessionId, entry);
|
|
2907
2886
|
return entry;
|
|
2908
2887
|
});
|
|
2909
|
-
|
|
2888
|
+
const itemIds = new Set(items.map((entry) => entry.sessionId));
|
|
2889
|
+
for (const id of this.entryCache.keys()) if (!itemIds.has(id)) this.entryCache.delete(id);
|
|
2910
2890
|
if (!(items.length === this.itemsCache.length && items.every((e, i) => e === this.itemsCache[i]))) this.itemsCache = items;
|
|
2911
|
-
const selected = this.selected;
|
|
2912
|
-
const current = selected !== void 0 && (items.some((item) => item.sessionId === selected) || this.addresses.has(selected)) ? selected : void 0;
|
|
2913
2891
|
return {
|
|
2914
2892
|
items: this.itemsCache,
|
|
2915
|
-
current,
|
|
2916
2893
|
state: this.listState,
|
|
2917
2894
|
phase: this.listPhase,
|
|
2918
2895
|
error: this.listError,
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2896
|
+
projectionsBySession: Object.fromEntries([...this.projectionStores].map(([sessionId, store]) => [sessionId, {
|
|
2897
|
+
values: store.values(),
|
|
2898
|
+
state: "idle",
|
|
2899
|
+
error: null,
|
|
2900
|
+
...this.projectionLoads.get(sessionId)
|
|
2901
|
+
}]))
|
|
2922
2902
|
};
|
|
2923
2903
|
}
|
|
2924
2904
|
};
|
|
2925
2905
|
/** Apply one list mutation without deriving display order. */
|
|
2926
2906
|
function applyMutation(summaries, mutation) {
|
|
2927
2907
|
switch (mutation.kind) {
|
|
2928
|
-
case "upsert":
|
|
2908
|
+
case "upsert":
|
|
2909
|
+
case "placeholder": {
|
|
2929
2910
|
const existing = summaries.find((summary) => summary.sessionId === mutation.summary.sessionId);
|
|
2930
2911
|
if (existing === void 0) return [mutation.summary, ...summaries];
|
|
2931
2912
|
const filled = {
|
|
2932
2913
|
...existing,
|
|
2933
2914
|
blank: existing.blank && mutation.summary.blank,
|
|
2915
|
+
...mutation.kind === "upsert" ? {
|
|
2916
|
+
agentAvailable: mutation.summary.agentAvailable,
|
|
2917
|
+
running: mutation.summary.running
|
|
2918
|
+
} : {},
|
|
2934
2919
|
...existing.cwd === void 0 && mutation.summary.cwd !== void 0 ? { cwd: mutation.summary.cwd } : {},
|
|
2935
2920
|
...existing.parentSessionId === void 0 && mutation.summary.parentSessionId !== void 0 ? { parentSessionId: mutation.summary.parentSessionId } : {},
|
|
2936
2921
|
...existing.origin === void 0 && mutation.summary.origin !== void 0 ? { origin: mutation.summary.origin } : {}
|
|
2937
2922
|
};
|
|
2938
|
-
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId && filled.origin === existing.origin && filled.blank === existing.blank) return [...summaries];
|
|
2923
|
+
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];
|
|
2939
2924
|
return summaries.map((summary) => summary.sessionId === mutation.summary.sessionId ? filled : summary);
|
|
2940
2925
|
}
|
|
2941
2926
|
case "remove": return summaries.filter((summary) => summary.sessionId !== mutation.sessionId);
|
|
2942
|
-
case "status": return summaries.map((summary) => summary.sessionId === mutation.sessionId && (summary.running !== mutation.running || mutation.running && summary.blank) ? {
|
|
2927
|
+
case "status": return summaries.map((summary) => summary.sessionId === mutation.sessionId && (summary.running !== mutation.running || summary.agentAvailable !== mutation.agentAvailable || mutation.running && summary.blank) ? {
|
|
2943
2928
|
...summary,
|
|
2944
2929
|
running: mutation.running,
|
|
2930
|
+
agentAvailable: mutation.agentAvailable,
|
|
2945
2931
|
blank: summary.blank && !mutation.running
|
|
2946
2932
|
} : summary);
|
|
2947
2933
|
case "activity": return summaries.map((summary) => summary.sessionId === mutation.sessionId && mutation.updatedAt > summary.updatedAt ? {
|
|
@@ -3015,7 +3001,74 @@ window.__ModuleLoader__.load({
|
|
|
3015
3001
|
if (fullWidth?.[1] !== void 0 && fullWidth[2] !== void 0) return `${fullWidth[1]}(${BigInt(fullWidth[2]) + 1n})`;
|
|
3016
3002
|
return `${title} (1)`;
|
|
3017
3003
|
}
|
|
3018
|
-
/**
|
|
3004
|
+
/** Source labels are dictionary keys, including names also present on Object.prototype. */
|
|
3005
|
+
function freezeRetainedBy(counts) {
|
|
3006
|
+
Object.setPrototypeOf(counts, null);
|
|
3007
|
+
return Object.freeze(counts);
|
|
3008
|
+
}
|
|
3009
|
+
const EMPTY_RETAIN_INFO = Object.freeze({
|
|
3010
|
+
referenceCount: 0,
|
|
3011
|
+
retainedBy: freezeRetainedBy({})
|
|
3012
|
+
});
|
|
3013
|
+
/** A cancelled waiter releases only its own reference, not the shared opening. */
|
|
3014
|
+
async function waitForOpen(opening, signal) {
|
|
3015
|
+
if (signal === void 0) return opening;
|
|
3016
|
+
const aborted = Promise.withResolvers();
|
|
3017
|
+
const onAbort = () => {
|
|
3018
|
+
aborted.reject(signal.reason);
|
|
3019
|
+
};
|
|
3020
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3021
|
+
try {
|
|
3022
|
+
if (signal.aborted) onAbort();
|
|
3023
|
+
await Promise.race([opening, aborted.promise]);
|
|
3024
|
+
} finally {
|
|
3025
|
+
signal.removeEventListener("abort", onAbort);
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
var ClientSessionReference = class {
|
|
3029
|
+
sessionId;
|
|
3030
|
+
record;
|
|
3031
|
+
releaseReference;
|
|
3032
|
+
released = new AbortController();
|
|
3033
|
+
readiness = Promise.withResolvers();
|
|
3034
|
+
ready = this.readiness.promise;
|
|
3035
|
+
constructor(sessionId, record, releaseReference) {
|
|
3036
|
+
this.sessionId = sessionId;
|
|
3037
|
+
this.record = record;
|
|
3038
|
+
this.releaseReference = releaseReference;
|
|
3039
|
+
this.ready.catch(() => {});
|
|
3040
|
+
}
|
|
3041
|
+
get binding() {
|
|
3042
|
+
if (this.record === void 0 || !this.record.live) throw new Error(`Session reference "${this.sessionId}" is released`);
|
|
3043
|
+
return this.record.binding;
|
|
3044
|
+
}
|
|
3045
|
+
attachOpening(opening, signal) {
|
|
3046
|
+
const waitSignal = signal === void 0 ? this.released.signal : AbortSignal.any([this.released.signal, signal]);
|
|
3047
|
+
waitForOpen(opening, waitSignal).then(() => {
|
|
3048
|
+
try {
|
|
3049
|
+
waitSignal.throwIfAborted();
|
|
3050
|
+
this.readiness.resolve(this.binding);
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
this.readiness.reject(error);
|
|
3053
|
+
}
|
|
3054
|
+
}, (error) => {
|
|
3055
|
+
this.readiness.reject(error);
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
release() {
|
|
3059
|
+
const reason = /* @__PURE__ */ new Error(`Session reference "${this.sessionId}" is released`);
|
|
3060
|
+
const release = this.releaseReference;
|
|
3061
|
+
this.released.abort(reason);
|
|
3062
|
+
this.readiness.reject(reason);
|
|
3063
|
+
this.record = void 0;
|
|
3064
|
+
this.releaseReference = void 0;
|
|
3065
|
+
release?.();
|
|
3066
|
+
}
|
|
3067
|
+
[Symbol.dispose]() {
|
|
3068
|
+
this.release();
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
/** Host catalog and local reference allocator; view selection remains outside the Controller. */
|
|
3019
3072
|
var ClientSessions = class {
|
|
3020
3073
|
rootCtx;
|
|
3021
3074
|
/**
|
|
@@ -3025,115 +3078,109 @@ window.__ModuleLoader__.load({
|
|
|
3025
3078
|
* reports the same number.
|
|
3026
3079
|
*/
|
|
3027
3080
|
searchResultLimit = 20;
|
|
3028
|
-
/**
|
|
3081
|
+
/** Catalog metadata and local reference-source projection. */
|
|
3029
3082
|
list;
|
|
3030
3083
|
/** The object-layer instance cluster and frame dispatch entry. */
|
|
3031
3084
|
manager;
|
|
3032
|
-
/**
|
|
3033
|
-
* Persisted selection cell (the durable half of `list.current`). Private on
|
|
3034
|
-
* purpose: reads go through the list snapshot; writes through {@link
|
|
3035
|
-
* ClientSessions.open} / {@link ClientSessions.clear}. Projection
|
|
3036
|
-
* validates it against the live list instead of destructively pruning, so a
|
|
3037
|
-
* selection survives transient list states (reconnect re-pull) and
|
|
3038
|
-
* resurfaces when its session returns.
|
|
3039
|
-
*/
|
|
3040
|
-
selection;
|
|
3041
3085
|
scopes = /* @__PURE__ */ new Map();
|
|
3042
|
-
/**
|
|
3086
|
+
/** Stable per-id sources retained for the Client root lifetime, including across generation replacement. */
|
|
3087
|
+
retainObservers = /* @__PURE__ */ new Map();
|
|
3043
3088
|
scopeDrops = /* @__PURE__ */ new Set();
|
|
3044
|
-
|
|
3045
|
-
* The staged session id — follows `list.current` exactly, holding its last
|
|
3046
|
-
* defined value across masked gaps (a transiently absent selection blanks
|
|
3047
|
-
* `current` without moving the stage, so reconnect re-pulls and removals
|
|
3048
|
-
* keep the staged scope's frozen view alive until the stage moves on).
|
|
3049
|
-
*/
|
|
3050
|
-
watched;
|
|
3051
|
-
/** Removed-while-staged sessions whose teardown waits for the stage to move away. */
|
|
3052
|
-
deferredRemovals = /* @__PURE__ */ new Set();
|
|
3089
|
+
closed = false;
|
|
3053
3090
|
/**
|
|
3054
3091
|
* @param ctx - client root context (scope fibers mount under it).
|
|
3055
3092
|
* @param remote - generated Remote namespaces shared with every Session.
|
|
3056
3093
|
*/
|
|
3057
3094
|
constructor(rootCtx, remote) {
|
|
3058
3095
|
this.rootCtx = rootCtx;
|
|
3059
|
-
this.
|
|
3060
|
-
const restored = this.selection.getSnapshot();
|
|
3061
|
-
this.manager = new SessionManager(remote, restored.sessionId, restored.subagentAddress);
|
|
3096
|
+
this.manager = new SessionManager(remote);
|
|
3062
3097
|
this.list = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
|
|
3063
3098
|
ids: [],
|
|
3064
3099
|
byId: {},
|
|
3065
|
-
current: void 0,
|
|
3066
3100
|
phase: "pending",
|
|
3067
|
-
|
|
3068
|
-
jobsBySession: {},
|
|
3069
|
-
currentAddress: void 0
|
|
3101
|
+
projectionsBySession: {}
|
|
3070
3102
|
});
|
|
3071
3103
|
const disposeManagerProjection = this.manager.subscribe(() => {
|
|
3072
3104
|
this.projectList();
|
|
3073
3105
|
});
|
|
3074
|
-
const disposeStageFollower = this.list.subscribe(() => {
|
|
3075
|
-
this.followCurrent();
|
|
3076
|
-
});
|
|
3077
3106
|
rootCtx.effect(() => async () => {
|
|
3078
|
-
|
|
3107
|
+
this.closed = true;
|
|
3079
3108
|
disposeManagerProjection();
|
|
3080
3109
|
const scopes = [...this.scopes];
|
|
3081
3110
|
this.scopes.clear();
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3111
|
+
for (const [, record] of scopes) {
|
|
3112
|
+
record.live = false;
|
|
3113
|
+
record.session.unbindScope();
|
|
3114
|
+
}
|
|
3115
|
+
const managerDisposal = this.manager.dispose();
|
|
3116
|
+
for (const [id, record] of scopes) {
|
|
3117
|
+
this.startScopeDrop(id, record);
|
|
3118
|
+
this.publishRetention(id);
|
|
3119
|
+
}
|
|
3085
3120
|
await this.drainScopeDrops();
|
|
3086
|
-
await
|
|
3121
|
+
await managerDisposal;
|
|
3087
3122
|
}, "session-controller.client.sessions");
|
|
3088
3123
|
rootCtx.reflect.provide("sessions", this, void 0);
|
|
3089
3124
|
}
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
this.
|
|
3125
|
+
retain(target, options) {
|
|
3126
|
+
const { source, signal } = options;
|
|
3127
|
+
signal?.throwIfAborted();
|
|
3128
|
+
if (this.closed) throw new Error("Session Controller is disposed");
|
|
3129
|
+
const id = this.manager.resolveTarget(target);
|
|
3130
|
+
const reference = this.retainScope(id, source);
|
|
3131
|
+
try {
|
|
3132
|
+
reference.attachOpening(this.manager.get(id).open(), signal);
|
|
3133
|
+
return reference;
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
reference.release();
|
|
3136
|
+
throw error;
|
|
3137
|
+
}
|
|
3096
3138
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3139
|
+
async using(target, options, operation) {
|
|
3140
|
+
const reference = this.retain(target, options);
|
|
3141
|
+
try {
|
|
3142
|
+
await reference.ready;
|
|
3143
|
+
return await operation(reference);
|
|
3144
|
+
} finally {
|
|
3145
|
+
reference.release();
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
retainInfo(id) {
|
|
3149
|
+
let observer = this.retainObservers.get(id);
|
|
3150
|
+
if (observer === void 0) {
|
|
3151
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3152
|
+
observer = {
|
|
3153
|
+
listeners,
|
|
3154
|
+
published: this.retentionSnapshot(id),
|
|
3155
|
+
source: {
|
|
3156
|
+
getSnapshot: () => this.retentionSnapshot(id),
|
|
3157
|
+
subscribe: (listener) => {
|
|
3158
|
+
listeners.add(listener);
|
|
3159
|
+
return () => {
|
|
3160
|
+
listeners.delete(listener);
|
|
3161
|
+
};
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
};
|
|
3165
|
+
this.retainObservers.set(id, observer);
|
|
3166
|
+
}
|
|
3167
|
+
return observer.source;
|
|
3103
3168
|
}
|
|
3104
3169
|
/**
|
|
3105
3170
|
* Resolve an already discovered direct-parent address without opening it.
|
|
3106
3171
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
3107
3172
|
* @param id - possible addressed child id.
|
|
3108
|
-
* @returns
|
|
3173
|
+
* @returns A retained or loaded-catalog address, without retaining a new selection or scope.
|
|
3109
3174
|
*/
|
|
3110
3175
|
subagentAddress(id) {
|
|
3111
3176
|
return this.manager.subagentAddress(id);
|
|
3112
3177
|
}
|
|
3113
3178
|
/**
|
|
3114
|
-
*
|
|
3115
|
-
* @param
|
|
3116
|
-
* @param open - menu state.
|
|
3117
|
-
*/
|
|
3118
|
-
setSubagentCatalogOpen(parentSessionId, open) {
|
|
3119
|
-
this.manager.setSubagentCatalogOpen(parentSessionId, open);
|
|
3120
|
-
}
|
|
3121
|
-
/**
|
|
3122
|
-
* Refresh one direct-child catalog.
|
|
3123
|
-
* @param parentSessionId - catalog owner.
|
|
3124
|
-
*/
|
|
3125
|
-
refreshSubagents(parentSessionId) {
|
|
3126
|
-
return this.manager.refreshSubagents(parentSessionId);
|
|
3127
|
-
}
|
|
3128
|
-
/**
|
|
3129
|
-
* Clear the current selection so the layout shows the no-session empty
|
|
3130
|
-
* state (new-session affordance and the workspace preselection flow).
|
|
3131
|
-
* Wipes the persisted selection too — a reload stays on empty until the
|
|
3132
|
-
* user opens or starts a session. The staged scope keeps its frozen view
|
|
3133
|
-
* per the masked-gap contract until the next open() moves the stage.
|
|
3179
|
+
* Load all Session projections once per connection; retry an unsuccessful initial read.
|
|
3180
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
3134
3181
|
*/
|
|
3135
|
-
|
|
3136
|
-
this.manager.
|
|
3182
|
+
refreshProjections(sessionId) {
|
|
3183
|
+
return this.manager.refreshProjections(sessionId);
|
|
3137
3184
|
}
|
|
3138
3185
|
/**
|
|
3139
3186
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
@@ -3199,12 +3246,8 @@ window.__ModuleLoader__.load({
|
|
|
3199
3246
|
this.manager.handleConnected();
|
|
3200
3247
|
}
|
|
3201
3248
|
/**
|
|
3202
|
-
* Create a
|
|
3203
|
-
*
|
|
3204
|
-
* {@link ClientSessions.binding} resolves it — callers (New Session
|
|
3205
|
-
* draft hand-off) may address the scope synchronously, without waiting a
|
|
3206
|
-
* notifier flush. The synchronous projection below makes this structural
|
|
3207
|
-
* rather than an accident of microtask ordering.
|
|
3249
|
+
* Create a Host Session and publish its catalog row before resolving.
|
|
3250
|
+
* Callers retain the returned identity before borrowing its binding.
|
|
3208
3251
|
* @param opts - target workspace or directory and an optional preallocated id.
|
|
3209
3252
|
* @returns the new session id.
|
|
3210
3253
|
* @throws {SessionCreateError} with the requested id.
|
|
@@ -3216,16 +3259,14 @@ window.__ModuleLoader__.load({
|
|
|
3216
3259
|
return result.value.sessionId;
|
|
3217
3260
|
}
|
|
3218
3261
|
/**
|
|
3219
|
-
* Fork a session from
|
|
3262
|
+
* Fork a session from an exact inclusive prefix of the source (same
|
|
3220
3263
|
* synchronous-addressability guarantee as {@link ClientSessions.create}:
|
|
3221
|
-
* on resolution the child is
|
|
3222
|
-
* @param opts - source session id, the optional
|
|
3223
|
-
*
|
|
3224
|
-
*
|
|
3225
|
-
* and whether to increment an
|
|
3226
|
-
*
|
|
3227
|
-
* interrupted turn carry flow-ordering seqs between two events, and the
|
|
3228
|
-
* wire takes integers only.
|
|
3264
|
+
* on resolution the child is catalogued and may be explicitly retained).
|
|
3265
|
+
* @param opts - source session id, the optional exact inclusive boundary
|
|
3266
|
+
* seq (a real event seq the caller already knows; a cut inside an open
|
|
3267
|
+
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
3268
|
+
* the latest completed-turn prefix), and whether to increment an
|
|
3269
|
+
* inherited durable title before resolving.
|
|
3229
3270
|
* @returns the child session id.
|
|
3230
3271
|
* @throws {SessionForkError} with the source id.
|
|
3231
3272
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -3234,37 +3275,33 @@ window.__ModuleLoader__.load({
|
|
|
3234
3275
|
const sourceTitle = opts.increaseTitle ? this.list.getSnapshot().byId[opts.sessionId]?.title : void 0;
|
|
3235
3276
|
const result = await this.manager.fork({
|
|
3236
3277
|
sessionId: opts.sessionId,
|
|
3237
|
-
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(
|
|
3278
|
+
...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(opts.atSeq) }
|
|
3238
3279
|
});
|
|
3239
3280
|
if (!result.ok) throw new SessionForkError(result.error, opts.sessionId);
|
|
3240
3281
|
this.projectList();
|
|
3241
3282
|
const childId = result.value.sessionId;
|
|
3242
3283
|
if (sourceTitle !== void 0) {
|
|
3243
|
-
const
|
|
3244
|
-
if (child === void 0) throw new Error(`fork child "${childId}" is not locally addressable`);
|
|
3245
|
-
const renamed = await child.rename(increasedForkTitle(sourceTitle));
|
|
3284
|
+
const renamed = await this.manager.rename(childId, increasedForkTitle(sourceTitle));
|
|
3246
3285
|
if (!renamed.ok) throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
3247
3286
|
}
|
|
3248
3287
|
return childId;
|
|
3249
3288
|
}
|
|
3250
3289
|
/**
|
|
3251
|
-
*
|
|
3290
|
+
* Borrow an already-retained Agent-scoped Context.
|
|
3252
3291
|
* @param id - session id (the agent identity — 1:1 same axis).
|
|
3253
|
-
* @returns scoped
|
|
3292
|
+
* @returns the scoped Context, or undefined without a retained generation.
|
|
3254
3293
|
*/
|
|
3255
3294
|
scope(id) {
|
|
3256
|
-
return this.
|
|
3295
|
+
return this.scopes.get(id)?.ctx;
|
|
3257
3296
|
}
|
|
3258
3297
|
/**
|
|
3259
|
-
*
|
|
3260
|
-
*
|
|
3261
|
-
*
|
|
3262
|
-
* arrival order.
|
|
3263
|
-
* @param id - Host-projected Agent identity (the matching Session id).
|
|
3264
|
-
* @returns the identity-stable Agent Context.
|
|
3298
|
+
* Retain a validated Gateway identity synchronously, without history or catalog I/O.
|
|
3299
|
+
* @param id - Host-projected Session identity, possibly not yet catalogued.
|
|
3300
|
+
* @returns a Gateway-source reference owned by the invocation.
|
|
3265
3301
|
*/
|
|
3266
|
-
|
|
3267
|
-
|
|
3302
|
+
retainAgentScope(id) {
|
|
3303
|
+
if (this.closed) throw new Error("Session Controller is disposed");
|
|
3304
|
+
return this.retainScope(id, "gateway");
|
|
3268
3305
|
}
|
|
3269
3306
|
/**
|
|
3270
3307
|
* Read the Agent scope tag off a context. Service-method boundary: fetch
|
|
@@ -3284,55 +3321,83 @@ window.__ModuleLoader__.load({
|
|
|
3284
3321
|
* `agent.session`). Same service-method boundary as
|
|
3285
3322
|
* {@link ClientSessions.scopeOf}.
|
|
3286
3323
|
* @param ctx - an Agent-scoped context.
|
|
3287
|
-
* @returns the
|
|
3324
|
+
* @returns the matching live Session, or undefined for an untagged or ended generation.
|
|
3288
3325
|
*/
|
|
3289
3326
|
sessionOf(ctx) {
|
|
3290
3327
|
const id = scopeOf(ctx);
|
|
3291
3328
|
if (id === void 0) return void 0;
|
|
3292
|
-
|
|
3329
|
+
const record = this.scopes.get(id);
|
|
3330
|
+
return record !== void 0 && scopeIdentityOf(record.ctx) === scopeIdentityOf(ctx) ? record.binding.session : void 0;
|
|
3293
3331
|
}
|
|
3294
3332
|
/**
|
|
3295
|
-
*
|
|
3296
|
-
*
|
|
3297
|
-
* @
|
|
3298
|
-
* @returns binding, or undefined for a session neither listed nor already scoped.
|
|
3333
|
+
* Borrow an already-retained binding without extending its lifetime.
|
|
3334
|
+
* @param id - Session identity.
|
|
3335
|
+
* @returns the live binding, or undefined without a retained generation.
|
|
3299
3336
|
*/
|
|
3300
3337
|
binding(id) {
|
|
3301
|
-
return this.
|
|
3302
|
-
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
record.
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3338
|
+
return this.scopes.get(id)?.binding;
|
|
3339
|
+
}
|
|
3340
|
+
retainScope(id, source) {
|
|
3341
|
+
const record = this.scopes.get(id) ?? this.materializeScope(id);
|
|
3342
|
+
const previous = record.retention;
|
|
3343
|
+
record.retention = Object.freeze({
|
|
3344
|
+
referenceCount: previous.referenceCount + 1,
|
|
3345
|
+
retainedBy: freezeRetainedBy({
|
|
3346
|
+
...previous.retainedBy,
|
|
3347
|
+
[source]: (previous.retainedBy[source] ?? 0) + 1
|
|
3348
|
+
})
|
|
3349
|
+
});
|
|
3350
|
+
const reference = new ClientSessionReference(id, record, () => {
|
|
3351
|
+
if (!record.live) return;
|
|
3352
|
+
const count = record.retention.referenceCount - 1;
|
|
3353
|
+
const { [source]: sourceCount = 0, ...otherSources } = record.retention.retainedBy;
|
|
3354
|
+
const retainedBy = sourceCount > 1 ? {
|
|
3355
|
+
...otherSources,
|
|
3356
|
+
[source]: sourceCount - 1
|
|
3357
|
+
} : otherSources;
|
|
3358
|
+
record.retention = count === 0 ? EMPTY_RETAIN_INFO : Object.freeze({
|
|
3359
|
+
referenceCount: count,
|
|
3360
|
+
retainedBy: freezeRetainedBy(retainedBy)
|
|
3361
|
+
});
|
|
3362
|
+
if (count === 0) this.retireScope(id, record);
|
|
3363
|
+
else this.publishRetention(id);
|
|
3364
|
+
});
|
|
3365
|
+
if (this.list.getSnapshot().byId[id] === void 0 && this.manager.subagentAddress(id) !== void 0) this.projectList();
|
|
3366
|
+
this.publishRetention(id);
|
|
3367
|
+
return reference;
|
|
3368
|
+
}
|
|
3369
|
+
retentionSnapshot(id) {
|
|
3370
|
+
return this.scopes.get(id)?.retention ?? EMPTY_RETAIN_INFO;
|
|
3371
|
+
}
|
|
3372
|
+
publishRetention(id) {
|
|
3373
|
+
const state = this.list.getSnapshot();
|
|
3374
|
+
const row = state.byId[id];
|
|
3375
|
+
const retainedBy = this.retentionSnapshot(id).retainedBy;
|
|
3376
|
+
if (row !== void 0 && row.retainedBy !== retainedBy) this.list.set({
|
|
3377
|
+
...state,
|
|
3378
|
+
byId: {
|
|
3379
|
+
...state.byId,
|
|
3380
|
+
[id]: {
|
|
3381
|
+
...row,
|
|
3382
|
+
retainedBy
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
});
|
|
3386
|
+
const observer = this.retainObservers.get(id);
|
|
3387
|
+
const snapshot = this.retentionSnapshot(id);
|
|
3388
|
+
if (observer === void 0 || observer.published === snapshot) return;
|
|
3389
|
+
observer.published = snapshot;
|
|
3390
|
+
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(observer.listeners, "[session-controller] reference sources");
|
|
3391
|
+
}
|
|
3392
|
+
retireScope(id, record, disposeFiber = true) {
|
|
3393
|
+
if (!record.live) return;
|
|
3394
|
+
record.live = false;
|
|
3395
|
+
if (this.scopes.get(id) === record) this.scopes.delete(id);
|
|
3396
|
+
record.session.unbindScope();
|
|
3397
|
+
const sessionDisposal = this.manager.drop(id, record.session);
|
|
3398
|
+
this.projectList();
|
|
3399
|
+
this.publishRetention(id);
|
|
3400
|
+
this.startScopeDrop(id, record, disposeFiber, sessionDisposal);
|
|
3336
3401
|
}
|
|
3337
3402
|
/** Materialize one scope after its caller establishes that the id may be addressed. */
|
|
3338
3403
|
materializeScope(id) {
|
|
@@ -3348,19 +3413,20 @@ window.__ModuleLoader__.load({
|
|
|
3348
3413
|
eventSource: session.eventSource,
|
|
3349
3414
|
ctx
|
|
3350
3415
|
},
|
|
3351
|
-
session
|
|
3416
|
+
session,
|
|
3417
|
+
retention: EMPTY_RETAIN_INFO,
|
|
3418
|
+
live: true
|
|
3352
3419
|
};
|
|
3353
3420
|
this.scopes.set(id, record);
|
|
3421
|
+
ctx.effect(() => () => {
|
|
3422
|
+
this.retireScope(id, record, false);
|
|
3423
|
+
}, "session-controller: exact generation");
|
|
3354
3424
|
return record;
|
|
3355
3425
|
}
|
|
3356
|
-
/** The one aliveness predicate shared by scope mint and prune: host-listed or currently addressed. */
|
|
3357
|
-
eligible(id) {
|
|
3358
|
-
const { ids, current } = this.list.getSnapshot();
|
|
3359
|
-
return current === id || ids.includes(id);
|
|
3360
|
-
}
|
|
3361
3426
|
/** Project the manager's list snapshot into the store (title derivation is display-only). */
|
|
3362
3427
|
projectList() {
|
|
3363
|
-
const
|
|
3428
|
+
const previousById = this.list.getSnapshot().byId;
|
|
3429
|
+
const { items, phase, projectionsBySession } = this.manager.getListSnapshot();
|
|
3364
3430
|
const ids = [];
|
|
3365
3431
|
const byId = {};
|
|
3366
3432
|
for (const entry of items) {
|
|
@@ -3369,7 +3435,7 @@ window.__ModuleLoader__.load({
|
|
|
3369
3435
|
id: entry.sessionId,
|
|
3370
3436
|
displayTitle: displayTitleOf(entry.title, entry.cwd, entry.sessionId),
|
|
3371
3437
|
running: entry.running,
|
|
3372
|
-
|
|
3438
|
+
retainedBy: this.retentionSnapshot(entry.sessionId).retainedBy,
|
|
3373
3439
|
blank: entry.blank,
|
|
3374
3440
|
updatedAt: entry.updatedAt,
|
|
3375
3441
|
...entry.projectionValues === void 0 ? {} : { projectionValues: entry.projectionValues },
|
|
@@ -3379,68 +3445,67 @@ window.__ModuleLoader__.load({
|
|
|
3379
3445
|
...entry.origin !== void 0 ? { origin: entry.origin } : {}
|
|
3380
3446
|
};
|
|
3381
3447
|
}
|
|
3382
|
-
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3448
|
+
for (const [parentId, projection] of Object.entries(projectionsBySession)) for (const child of projection.values.subagentCatalog ?? []) {
|
|
3449
|
+
const childId = child.id;
|
|
3450
|
+
const summary = byId[childId];
|
|
3451
|
+
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
|
3452
|
+
const projectedTitle = projectionValues?.title;
|
|
3453
|
+
const title = typeof projectedTitle === "string" && projectedTitle !== "" ? projectedTitle : void 0;
|
|
3454
|
+
const displayTitle = title ?? child.label ?? childId;
|
|
3455
|
+
if (summary === void 0) byId[childId] = {
|
|
3456
|
+
id: childId,
|
|
3457
|
+
displayTitle,
|
|
3458
|
+
parentId,
|
|
3459
|
+
origin: "subagent",
|
|
3460
|
+
running: this.scopes.get(childId)?.session.getSnapshot().running ?? false,
|
|
3461
|
+
blank: false,
|
|
3462
|
+
updatedAt: 0,
|
|
3463
|
+
retainedBy: this.retentionSnapshot(childId).retainedBy,
|
|
3464
|
+
...projectionValues === void 0 ? {} : { projectionValues },
|
|
3465
|
+
...title === void 0 ? {} : { title }
|
|
3466
|
+
};
|
|
3467
|
+
else if (summary.displayTitle !== displayTitle || summary.projectionValues !== projectionValues) byId[childId] = {
|
|
3468
|
+
...summary,
|
|
3469
|
+
displayTitle,
|
|
3470
|
+
...projectionValues === void 0 ? {} : { projectionValues }
|
|
3471
|
+
};
|
|
3472
|
+
}
|
|
3473
|
+
for (const [id, record] of this.scopes) {
|
|
3474
|
+
if (byId[id] !== void 0) continue;
|
|
3475
|
+
const address = this.manager.subagentAddress(id);
|
|
3476
|
+
if (address === void 0) continue;
|
|
3477
|
+
const previous = previousById[id];
|
|
3478
|
+
const snapshot = record.session.getSnapshot();
|
|
3479
|
+
const projectionValues = this.manager.projectionValues(id);
|
|
3480
|
+
const projectedTitle = projectionValues?.title;
|
|
3481
|
+
const title = typeof projectedTitle === "string" && projectedTitle !== "" ? projectedTitle : previous?.title;
|
|
3482
|
+
byId[id] = {
|
|
3483
|
+
...previous ?? {
|
|
3484
|
+
id,
|
|
3485
|
+
displayTitle: id,
|
|
3399
3486
|
updatedAt: 0
|
|
3400
|
-
}
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3487
|
+
},
|
|
3488
|
+
running: snapshot.running,
|
|
3489
|
+
retainedBy: record.retention.retainedBy,
|
|
3490
|
+
blank: snapshot.blank,
|
|
3491
|
+
parentId: address.parentSessionId,
|
|
3492
|
+
origin: "subagent",
|
|
3493
|
+
...projectionValues === void 0 ? {} : { projectionValues },
|
|
3494
|
+
...title === void 0 ? {} : {
|
|
3495
|
+
title,
|
|
3496
|
+
displayTitle: title
|
|
3497
|
+
}
|
|
3498
|
+
};
|
|
3409
3499
|
}
|
|
3410
|
-
const persisted = this.selection.getSnapshot().sessionId;
|
|
3411
|
-
if (current === void 0) {
|
|
3412
|
-
if (persisted !== void 0) this.selection.set({});
|
|
3413
|
-
} else if (byId[current] !== void 0 && (persisted !== current || this.selection.getSnapshot().subagentAddress?.childSessionId !== currentAddress?.childSessionId || this.selection.getSnapshot().subagentAddress?.parentSessionId !== currentAddress?.parentSessionId || this.selection.getSnapshot().subagentAddress?.mode !== currentAddress?.mode)) this.selection.set({
|
|
3414
|
-
sessionId: current,
|
|
3415
|
-
...currentAddress === void 0 ? {} : { subagentAddress: currentAddress }
|
|
3416
|
-
});
|
|
3417
3500
|
this.list.set({
|
|
3418
3501
|
ids,
|
|
3419
3502
|
byId,
|
|
3420
|
-
current,
|
|
3421
3503
|
phase,
|
|
3422
|
-
|
|
3423
|
-
jobsBySession,
|
|
3424
|
-
currentAddress
|
|
3504
|
+
projectionsBySession
|
|
3425
3505
|
});
|
|
3426
|
-
this.pruneScopes();
|
|
3427
|
-
}
|
|
3428
|
-
/** Tear down scope + instance for no-longer-eligible sessions off stage; the staged one defers until the stage moves. */
|
|
3429
|
-
pruneScopes() {
|
|
3430
|
-
if (this.list.getSnapshot().phase === "pending") return;
|
|
3431
|
-
for (const [id, record] of this.scopes) {
|
|
3432
|
-
if (this.eligible(id)) continue;
|
|
3433
|
-
if (id === this.watched) {
|
|
3434
|
-
this.deferredRemovals.add(id);
|
|
3435
|
-
continue;
|
|
3436
|
-
}
|
|
3437
|
-
this.scopes.delete(id);
|
|
3438
|
-
this.deferredRemovals.delete(id);
|
|
3439
|
-
this.startScopeDrop(id, record);
|
|
3440
|
-
}
|
|
3441
3506
|
}
|
|
3442
|
-
startScopeDrop(id, record) {
|
|
3443
|
-
const drop = this.dropScope(
|
|
3507
|
+
startScopeDrop(id, record, disposeFiber = true, sessionDisposal = this.manager.drop(id, record.session)) {
|
|
3508
|
+
const drop = this.dropScope(record, disposeFiber, sessionDisposal);
|
|
3444
3509
|
this.scopeDrops.add(drop);
|
|
3445
3510
|
drop.then(() => {
|
|
3446
3511
|
this.scopeDrops.delete(drop);
|
|
@@ -3451,38 +3516,9 @@ window.__ModuleLoader__.load({
|
|
|
3451
3516
|
async drainScopeDrops() {
|
|
3452
3517
|
while (this.scopeDrops.size > 0) await Promise.allSettled([...this.scopeDrops]);
|
|
3453
3518
|
}
|
|
3454
|
-
/**
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
* controller, popup, plugin stores, listeners), the session-keyed slot
|
|
3458
|
-
* registrations and the Session instance itself — the host session log is the
|
|
3459
|
-
* durable truth, a reopen lazily rebuilds and backfills via open().
|
|
3460
|
-
*/
|
|
3461
|
-
async dropScope(id, record) {
|
|
3462
|
-
record.session.unbindScope();
|
|
3463
|
-
await Promise.allSettled([record.fiber.dispose(), this.manager.drop(id)]);
|
|
3464
|
-
}
|
|
3465
|
-
/** Run deferred teardowns whose session is no longer staged (called when the stage moves). */
|
|
3466
|
-
sweepDeferred() {
|
|
3467
|
-
for (const id of [...this.deferredRemovals]) {
|
|
3468
|
-
/* v8 ignore next -- defensive: only the staged id ever defers, and every
|
|
3469
|
-
* stage move sweeps first, so the set cannot contain the id the stage just
|
|
3470
|
-
* moved to; kept as a guard against future extra sweep call sites. */
|
|
3471
|
-
if (id === this.watched) continue;
|
|
3472
|
-
if (this.eligible(id)) {
|
|
3473
|
-
this.deferredRemovals.delete(id);
|
|
3474
|
-
continue;
|
|
3475
|
-
}
|
|
3476
|
-
const record = this.scopes.get(id);
|
|
3477
|
-
this.deferredRemovals.delete(id);
|
|
3478
|
-
/* v8 ignore next -- defensive: prune deletes a scope and its deferral
|
|
3479
|
-
* together, so a deferred id always still owns its record; kept so a
|
|
3480
|
-
* future teardown path cannot double-dispose. */
|
|
3481
|
-
if (record !== void 0) {
|
|
3482
|
-
this.scopes.delete(id);
|
|
3483
|
-
this.startScopeDrop(id, record);
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3519
|
+
/** Await the already-withdrawn Session and scoped cleanup to quiescence. */
|
|
3520
|
+
async dropScope(record, disposeFiber, sessionDisposal) {
|
|
3521
|
+
await Promise.allSettled([sessionDisposal, ...disposeFiber ? [record.fiber.dispose()] : []]);
|
|
3486
3522
|
}
|
|
3487
3523
|
};
|
|
3488
3524
|
//#endregion
|
|
@@ -3504,6 +3540,7 @@ window.__ModuleLoader__.load({
|
|
|
3504
3540
|
*/
|
|
3505
3541
|
function apply(ctx) {
|
|
3506
3542
|
const remotes = ctx.remote;
|
|
3543
|
+
const connection = ctx.get("connection");
|
|
3507
3544
|
const sessions = new ClientSessions(ctx, remotes);
|
|
3508
3545
|
ctx.remote.$on("api-session/added", (summary) => {
|
|
3509
3546
|
sessions.handleSessionAdded(summary);
|
|
@@ -3528,14 +3565,22 @@ window.__ModuleLoader__.load({
|
|
|
3528
3565
|
console.error("[session-controller] control stream failed:", error);
|
|
3529
3566
|
}
|
|
3530
3567
|
});
|
|
3531
|
-
|
|
3532
|
-
|
|
3568
|
+
const connected = () => {
|
|
3569
|
+
if (connection.generation.getSnapshot() === void 0) return;
|
|
3533
3570
|
sessions.handleConnected();
|
|
3534
|
-
|
|
3535
|
-
|
|
3571
|
+
control.restart();
|
|
3572
|
+
control.start();
|
|
3573
|
+
};
|
|
3574
|
+
ctx.effect(() => connection.generation.subscribe(connected), "session-controller.client.generation");
|
|
3575
|
+
connected();
|
|
3536
3576
|
ctx.typert.contexts.registerClient("agent", {
|
|
3537
|
-
identity: (candidate) => sessions.
|
|
3538
|
-
resolve: (sessionId) =>
|
|
3577
|
+
identity: (candidate) => sessions.sessionOf(candidate)?.sessionId,
|
|
3578
|
+
resolve: (sessionId) => {
|
|
3579
|
+
const reference = sessions.retainAgentScope(sessionId);
|
|
3580
|
+
return typertOwnedValue(reference.binding.ctx, () => {
|
|
3581
|
+
reference.release();
|
|
3582
|
+
});
|
|
3583
|
+
}
|
|
3539
3584
|
});
|
|
3540
3585
|
ctx.effect(() => async () => {
|
|
3541
3586
|
await control.dispose();
|