@deepseek-ai/dsh-api-session-controller 0.1.5-rc.2 → 0.1.6-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +20 -4
- package/README.zh.md +26 -10
- package/lib/client.js +432 -474
- package/lib/index.js +18 -49
- package/lib/typert.host.js +242 -200
- package/lib/typert.remote-client.js +195 -165
- package/lib/types/agent.d.ts +1 -1
- package/lib/types/agent.js +8 -1
- package/lib/types/client/contract/sessions.d.ts +55 -20
- package/lib/types/client/contract/snapshot.d.ts +0 -14
- package/lib/types/client/index.d.ts +11 -2
- 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 +1 -4
- package/lib/types/client/sessions/lineage.js +1 -3
- package/lib/types/client/sessions/manager.d.ts +28 -49
- package/lib/types/client/sessions/manager.js +75 -174
- package/lib/types/client/sessions/projection-store.d.ts +5 -11
- package/lib/types/client/sessions/projection-store.js +6 -14
- package/lib/types/client/sessions/service.d.ts +34 -119
- package/lib/types/client/sessions/service.js +259 -244
- package/lib/types/client/sessions/session.d.ts +7 -20
- package/lib/types/client/sessions/session.js +29 -33
- package/lib/types/client/time-zone.d.ts +1 -1
- package/lib/types/client/time-zone.js +1 -1
- package/lib/types/commands.d.ts +2 -2
- package/lib/types/commands.js +13 -8
- package/lib/types/control.d.ts +1 -1
- package/lib/types/control.js +1 -41
- package/lib/types/history.js +1 -0
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.js +2 -1
- package/lib/types/skill-catalog.js +1 -0
- package/lib/types/types.d.ts +5 -17
- package/package.json +72 -69
- 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. */
|
|
@@ -134,6 +155,7 @@ window.__ModuleLoader__.load({
|
|
|
134
155
|
"goal/change",
|
|
135
156
|
"hook/invoked",
|
|
136
157
|
"hook/result",
|
|
158
|
+
"image/offload",
|
|
137
159
|
"llm/retry",
|
|
138
160
|
"llm/retry-started",
|
|
139
161
|
"model/selection",
|
|
@@ -169,7 +191,8 @@ window.__ModuleLoader__.load({
|
|
|
169
191
|
"turn/end",
|
|
170
192
|
"turn/start",
|
|
171
193
|
"user/message",
|
|
172
|
-
"web/deepseek-search-llm-request"
|
|
194
|
+
"web/deepseek-search-llm-request",
|
|
195
|
+
"workspace/changes"
|
|
173
196
|
]);
|
|
174
197
|
//#endregion
|
|
175
198
|
//#region ../../core/session/lib/types/surface.js
|
|
@@ -244,7 +267,7 @@ window.__ModuleLoader__.load({
|
|
|
244
267
|
return op;
|
|
245
268
|
}
|
|
246
269
|
/** Validate cited source-event seqs against prior log entries and the replacement range. */
|
|
247
|
-
function
|
|
270
|
+
function assertSourceEventReferences(event, shadowedSeqs) {
|
|
248
271
|
const raw = event.sourceEventSeqs;
|
|
249
272
|
if (event.type === "assistant/message" && raw !== void 0) throw new Error("assistant/message embeds its source stream and cannot carry sourceEventSeqs");
|
|
250
273
|
const sources = /* @__PURE__ */ new Set();
|
|
@@ -273,7 +296,7 @@ window.__ModuleLoader__.load({
|
|
|
273
296
|
function validateSurfaceMetadata(event) {
|
|
274
297
|
const op = surfaceOpOf(event);
|
|
275
298
|
if (op !== void 0 && op !== "append" && (op.startSeq >= event.seq || op.endSeq >= event.seq)) throw new Error(`surface replace at seq ${event.seq}: startSeq and endSeq must reference earlier events`);
|
|
276
|
-
if (op !== void 0)
|
|
299
|
+
if (op !== void 0) assertSourceEventReferences(event, []);
|
|
277
300
|
return op;
|
|
278
301
|
}
|
|
279
302
|
//#endregion
|
|
@@ -453,23 +476,7 @@ window.__ModuleLoader__.load({
|
|
|
453
476
|
}
|
|
454
477
|
//#endregion
|
|
455
478
|
//#region lib/types/client/scope.js
|
|
456
|
-
/**
|
|
457
|
-
* Client Agent-scope primitive: mint a Cordis context tagged with the owning
|
|
458
|
-
* Agent's identity. The mechanism mirrors the host `dsh-scope` architecture
|
|
459
|
-
* (no-op plugin fiber + context tag + `Context.filter` routing predicate);
|
|
460
|
-
* the shape deliberately diverges: the filter lives on the actx itself
|
|
461
|
-
* instead of a separate carrier object, so scoped dispatch is plain cordis —
|
|
462
|
-
* `actx.bail(actx, event, payload)` / `actx.emit(actx, ...)` — with no
|
|
463
|
-
* wrapper. The host needs a detached carrier because its dispatch subject is
|
|
464
|
-
* the business Agent object; client scope events carry only ids, so the
|
|
465
|
-
* actx is the natural subject. The second divergence stands: the scope key
|
|
466
|
-
* is the branded `SessionId` (value compared), not an object identity — the
|
|
467
|
-
* agent and its session share one id (1:1, same axis; no separate AgentId
|
|
468
|
-
* brand), and a client scope's identity IS that wire id. Third divergence,
|
|
469
|
-
* deliberate: the client scopes the Agent IDENTITY, not a live Agent object
|
|
470
|
-
* — a cold session's host Agent is already disposed while its client actx
|
|
471
|
-
* stays alive for history viewing.
|
|
472
|
-
*/
|
|
479
|
+
/** Client scope generations route local events independently of Host Agent residency. */
|
|
473
480
|
/** Context tag written by {@link createScope}. */
|
|
474
481
|
const kScope = Symbol("dsh.client.scope");
|
|
475
482
|
/** Shared no-op plugin backing each Agent scope fiber. */
|
|
@@ -477,21 +484,22 @@ window.__ModuleLoader__.load({
|
|
|
477
484
|
/**
|
|
478
485
|
* Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
|
|
479
486
|
* carries the agent tag and the dispatch filter — untagged listeners are
|
|
480
|
-
* admitted globally, tagged listeners only for
|
|
487
|
+
* admitted globally, tagged listeners only for the same Client generation.
|
|
481
488
|
* Registrations through the returned ctx dispose with the fiber.
|
|
482
489
|
* @param ctx - client root context the scope fiber mounts under.
|
|
483
|
-
* @param key -
|
|
490
|
+
* @param key - durable Session identity carried by this generation.
|
|
484
491
|
* @returns the tagged context and its backing fiber.
|
|
485
492
|
*/
|
|
486
493
|
function createScope(ctx, key) {
|
|
487
494
|
const fiber = ctx.plugin(agentScope);
|
|
495
|
+
const identity = { sessionId: key };
|
|
488
496
|
return {
|
|
489
497
|
fiber,
|
|
490
498
|
ctx: fiber.ctx.extend({
|
|
491
|
-
[kScope]:
|
|
499
|
+
[kScope]: identity,
|
|
492
500
|
[_deepseek_ai_cordis.Context.filter](listenerCtx) {
|
|
493
|
-
const tag =
|
|
494
|
-
return tag === void 0 || tag ===
|
|
501
|
+
const tag = scopeIdentityOf(listenerCtx);
|
|
502
|
+
return tag === void 0 || tag === identity;
|
|
495
503
|
}
|
|
496
504
|
})
|
|
497
505
|
};
|
|
@@ -502,6 +510,14 @@ window.__ModuleLoader__.load({
|
|
|
502
510
|
* @returns its agent identity (the session id), or undefined for root contexts.
|
|
503
511
|
*/
|
|
504
512
|
function scopeOf(ctx) {
|
|
513
|
+
return scopeIdentityOf(ctx)?.sessionId;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Read the exact generation identity inherited by a Client Context.
|
|
517
|
+
* @param ctx - scoped or root Client Context.
|
|
518
|
+
* @returns the generation identity, or undefined for an unscoped Context.
|
|
519
|
+
*/
|
|
520
|
+
function scopeIdentityOf(ctx) {
|
|
505
521
|
return ctx[kScope];
|
|
506
522
|
}
|
|
507
523
|
//#endregion
|
|
@@ -548,10 +564,9 @@ window.__ModuleLoader__.load({
|
|
|
548
564
|
* follows the established input order; this projection never re-sorts a
|
|
549
565
|
* hydrated list from mutable timestamps.
|
|
550
566
|
* @param summaries - the host's session.list items.
|
|
551
|
-
* @param completed - sessions with a pending completion reminder (manager-owned live fact; absent = false).
|
|
552
567
|
* @returns display rows in render order.
|
|
553
568
|
*/
|
|
554
|
-
function flattenLineage(summaries
|
|
569
|
+
function flattenLineage(summaries) {
|
|
555
570
|
const byId = /* @__PURE__ */ new Map();
|
|
556
571
|
for (const s of summaries) byId.set(s.sessionId, s);
|
|
557
572
|
const children = /* @__PURE__ */ new Map();
|
|
@@ -571,7 +586,6 @@ window.__ModuleLoader__.load({
|
|
|
571
586
|
visited.add(s.sessionId);
|
|
572
587
|
out.push({
|
|
573
588
|
...s,
|
|
574
|
-
completed: completed?.has(s.sessionId) ?? false,
|
|
575
589
|
depth
|
|
576
590
|
});
|
|
577
591
|
const kids = children.get(s.sessionId);
|
|
@@ -675,9 +689,9 @@ window.__ModuleLoader__.load({
|
|
|
675
689
|
/**
|
|
676
690
|
* One session's projection values. Framework semantics, uniform across every
|
|
677
691
|
* key: a baseline seeds rows at its cut, a push frame updates one row, and in
|
|
678
|
-
* both paths a lower-or-equal seq
|
|
679
|
-
* value
|
|
680
|
-
* never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
692
|
+
* both paths a lower-or-equal seq within the Host generation loses. A replayed
|
|
693
|
+
* frame cannot regress a value; a stale baseline cannot overwrite a newer
|
|
694
|
+
* frame. A key the store has never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
681
695
|
* per key (create-on-demand, cached) so the React side binds each exactly
|
|
682
696
|
* once; the store-level channel (`subscribeAny`) serves coarse consumers (the
|
|
683
697
|
* manager's list projection reads the `title` key).
|
|
@@ -757,16 +771,9 @@ window.__ModuleLoader__.load({
|
|
|
757
771
|
this.changed(key);
|
|
758
772
|
}
|
|
759
773
|
}
|
|
760
|
-
/**
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
* outrank recomputed lower-seq values forever. The caller seeds the new
|
|
764
|
-
* baseline immediately afterward.
|
|
765
|
-
* @param lastSeq - highest durable sequence reflected by the baseline.
|
|
766
|
-
*/
|
|
767
|
-
truncate(lastSeq) {
|
|
768
|
-
for (const [key, row] of this.rows) {
|
|
769
|
-
if (row.seq <= lastSeq) continue;
|
|
774
|
+
/** Discard one Host generation's values and watermarks while preserving subscribed faces. */
|
|
775
|
+
clear() {
|
|
776
|
+
for (const key of this.rows.keys()) {
|
|
770
777
|
this.rows.delete(key);
|
|
771
778
|
this.changed(key);
|
|
772
779
|
}
|
|
@@ -938,7 +945,7 @@ window.__ModuleLoader__.load({
|
|
|
938
945
|
};
|
|
939
946
|
//#endregion
|
|
940
947
|
//#region lib/types/client/time-zone.js
|
|
941
|
-
/** Browser-owned time-zone sampling for prompt RPC
|
|
948
|
+
/** Browser-owned time-zone sampling for one prompt RPC. */
|
|
942
949
|
/**
|
|
943
950
|
* Resolve the current browser IANA zone for one outbound operation.
|
|
944
951
|
* @returns The browser-provided canonical zone.
|
|
@@ -950,60 +957,6 @@ window.__ModuleLoader__.load({
|
|
|
950
957
|
return timeZone;
|
|
951
958
|
}
|
|
952
959
|
//#endregion
|
|
953
|
-
//#region lib/types/client/sessions/queue-mirror.js
|
|
954
|
-
const QUEUE_PREVIEW_CHARS = 200;
|
|
955
|
-
function previewOf(content) {
|
|
956
|
-
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();
|
|
957
|
-
const chars = Array.from(flat);
|
|
958
|
-
return chars.length > QUEUE_PREVIEW_CHARS ? `${chars.slice(0, QUEUE_PREVIEW_CHARS).join("")}…` : flat;
|
|
959
|
-
}
|
|
960
|
-
function textOf(content) {
|
|
961
|
-
if (!content.every((block) => block.type === "text")) return null;
|
|
962
|
-
return content.map((block) => block.text).join("");
|
|
963
|
-
}
|
|
964
|
-
/** Authoritative transient queue projection and durable steering handoff. */
|
|
965
|
-
var SessionQueueMirror = class {
|
|
966
|
-
current = [];
|
|
967
|
-
/**
|
|
968
|
-
* Return the current immutable queue projection.
|
|
969
|
-
* @returns current queue rows.
|
|
970
|
-
*/
|
|
971
|
-
snapshot() {
|
|
972
|
-
return this.current;
|
|
973
|
-
}
|
|
974
|
-
/**
|
|
975
|
-
* Replace from one authoritative stream queue frame.
|
|
976
|
-
* @param items - complete host queue snapshot.
|
|
977
|
-
*/
|
|
978
|
-
replace(items) {
|
|
979
|
-
this.current = items.map((item) => {
|
|
980
|
-
const content = item.message.content;
|
|
981
|
-
return {
|
|
982
|
-
id: item.id,
|
|
983
|
-
messageId: item.message.id,
|
|
984
|
-
placement: item.placement,
|
|
985
|
-
...item.rpcId === void 0 ? {} : { rpcId: item.rpcId },
|
|
986
|
-
content,
|
|
987
|
-
preview: previewOf(content),
|
|
988
|
-
text: textOf(content)
|
|
989
|
-
};
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
/**
|
|
993
|
-
* Retire a transient steering row once its durable message enters the log.
|
|
994
|
-
* @param event - newly contiguous durable Session event.
|
|
995
|
-
* @returns whether the projection changed.
|
|
996
|
-
*/
|
|
997
|
-
acceptDurable(event) {
|
|
998
|
-
if (event.type !== "user/message") return false;
|
|
999
|
-
const messageId = event.data.id;
|
|
1000
|
-
const index = this.current.findIndex((item) => item.placement === "steering" && item.messageId === messageId);
|
|
1001
|
-
if (index < 0) return false;
|
|
1002
|
-
this.current = this.current.filter((_item, candidate) => candidate !== index);
|
|
1003
|
-
return true;
|
|
1004
|
-
}
|
|
1005
|
-
};
|
|
1006
|
-
//#endregion
|
|
1007
960
|
//#region ../../util/values/lib/index.js
|
|
1008
961
|
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
1009
962
|
function hasIntrinsicConstructor(prototype, name) {
|
|
@@ -1526,8 +1479,7 @@ window.__ModuleLoader__.load({
|
|
|
1526
1479
|
jumpTargetSeq = null;
|
|
1527
1480
|
/** The running jump loop's completion, shared by retargeting callers. */
|
|
1528
1481
|
jumpPromise = null;
|
|
1529
|
-
|
|
1530
|
-
queueMirror = new SessionQueueMirror();
|
|
1482
|
+
stopObservingInbox;
|
|
1531
1483
|
assistantStream = new ClientAssistantStream();
|
|
1532
1484
|
running = false;
|
|
1533
1485
|
address;
|
|
@@ -1548,7 +1500,7 @@ window.__ModuleLoader__.load({
|
|
|
1548
1500
|
/** Local submission echoes, insertion-ordered (see SessionSnapshot.pendingSubmissions). */
|
|
1549
1501
|
pendingSubmissions = [];
|
|
1550
1502
|
/** Per-echo settlement state; `retiring` latches the first observation so a
|
|
1551
|
-
*
|
|
1503
|
+
* Inbox projection and its durable event cannot both retire one echo. */
|
|
1552
1504
|
submissionSettlements = /* @__PURE__ */ new Map();
|
|
1553
1505
|
/** Owns the addressed page/follow lifecycle while this Session is open. */
|
|
1554
1506
|
events;
|
|
@@ -1593,6 +1545,9 @@ window.__ModuleLoader__.load({
|
|
|
1593
1545
|
this.snapshotCache = this.buildSnapshot();
|
|
1594
1546
|
});
|
|
1595
1547
|
this.snapshotCache = this.buildSnapshot();
|
|
1548
|
+
this.stopObservingInbox = this.projections.faceOf("inbox").subscribe(() => {
|
|
1549
|
+
this.observeSubmissionInbox();
|
|
1550
|
+
});
|
|
1596
1551
|
}
|
|
1597
1552
|
/**
|
|
1598
1553
|
* Bind the Agent-scoped context minted by ClientSessions (single write;
|
|
@@ -1853,7 +1808,7 @@ window.__ModuleLoader__.load({
|
|
|
1853
1808
|
return this.jumpPromise;
|
|
1854
1809
|
}
|
|
1855
1810
|
/** Rebuild an opened history source after address replacement.
|
|
1856
|
-
* Invalidates any in-flight open first;
|
|
1811
|
+
* Invalidates any in-flight open first; projection state belongs to the independently
|
|
1857
1812
|
* reconnecting control stream and remains untouched. */
|
|
1858
1813
|
async resync() {
|
|
1859
1814
|
if (this.openState === "cold") return;
|
|
@@ -1885,24 +1840,6 @@ window.__ModuleLoader__.load({
|
|
|
1885
1840
|
return this.snapshotCache;
|
|
1886
1841
|
}
|
|
1887
1842
|
/**
|
|
1888
|
-
* Replace every transient control value for this Session from one stream baseline.
|
|
1889
|
-
* @param queue - complete pending queue for this Session.
|
|
1890
|
-
*/
|
|
1891
|
-
replaceControl(queue) {
|
|
1892
|
-
this.queueMirror.replace(queue);
|
|
1893
|
-
this.observeSubmissionQueue(queue);
|
|
1894
|
-
this.notifier.markDirty();
|
|
1895
|
-
}
|
|
1896
|
-
/**
|
|
1897
|
-
* Apply one Session-addressed live control update.
|
|
1898
|
-
* @param frame - queue replacement addressed to this Session.
|
|
1899
|
-
*/
|
|
1900
|
-
handleControlFrame(frame) {
|
|
1901
|
-
this.queueMirror.replace(frame.items);
|
|
1902
|
-
this.observeSubmissionQueue(frame.items);
|
|
1903
|
-
this.notifier.markDirty();
|
|
1904
|
-
}
|
|
1905
|
-
/**
|
|
1906
1843
|
* Running-bit relay from the host stream (list entry and snapshot stay consistent).
|
|
1907
1844
|
* @param running - the new running state.
|
|
1908
1845
|
*/
|
|
@@ -1969,6 +1906,7 @@ window.__ModuleLoader__.load({
|
|
|
1969
1906
|
* @returns when the Remote iterator has completed teardown.
|
|
1970
1907
|
*/
|
|
1971
1908
|
async dispose() {
|
|
1909
|
+
this.stopObservingInbox();
|
|
1972
1910
|
for (const requestId of [...this.submissionSettlements.keys()]) this.retireFailedSubmission(requestId);
|
|
1973
1911
|
this.openGeneration++;
|
|
1974
1912
|
const events = this.events;
|
|
@@ -2066,23 +2004,36 @@ window.__ModuleLoader__.load({
|
|
|
2066
2004
|
const event = entry.event;
|
|
2067
2005
|
const awaitingFirstTurn = this.firstPromptPendingTurn;
|
|
2068
2006
|
if (event.type === "turn/start") this.firstPromptPendingTurn = false;
|
|
2069
|
-
const queueChanged = this.queueMirror.acceptDurable(event);
|
|
2070
2007
|
this.eventSource.append(entry);
|
|
2071
2008
|
this.observeSubmissionEvent(event);
|
|
2072
|
-
return
|
|
2009
|
+
return awaitingFirstTurn !== this.firstPromptPendingTurn;
|
|
2073
2010
|
}
|
|
2074
|
-
/**
|
|
2011
|
+
/** Observe durable acceptance even when insertion and claim share one projection notification. */
|
|
2075
2012
|
observeSubmissionEvent(event) {
|
|
2076
|
-
if (this.submissionSettlements.size === 0
|
|
2013
|
+
if (this.submissionSettlements.size === 0) return;
|
|
2014
|
+
if (event.type === "agent/inbox/spliced") {
|
|
2015
|
+
const splice = event.data;
|
|
2016
|
+
if (Array.isArray(splice?.inserted)) for (const message of splice.inserted) this.observeSubmissionEvent({
|
|
2017
|
+
type: "user/message",
|
|
2018
|
+
data: message
|
|
2019
|
+
});
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
if (event.type !== "user/message") return;
|
|
2077
2023
|
const data = event.data;
|
|
2078
2024
|
const source = data?.source;
|
|
2079
2025
|
if (source?.kind !== "user" || typeof source.rpcId !== "string") return;
|
|
2080
2026
|
this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(data?.content));
|
|
2081
2027
|
}
|
|
2082
|
-
/** Retire echoes
|
|
2083
|
-
|
|
2028
|
+
/** Retire local echoes when their accepted messages appear in the durable Inbox projection. */
|
|
2029
|
+
observeSubmissionInbox() {
|
|
2084
2030
|
if (this.submissionSettlements.size === 0) return;
|
|
2085
|
-
|
|
2031
|
+
const inbox = this.projections.get("inbox");
|
|
2032
|
+
if (inbox === void 0) return;
|
|
2033
|
+
for (const message of [...inbox["next-turn"], ...inbox["next-step"]]) {
|
|
2034
|
+
const source = message.source;
|
|
2035
|
+
if (source.kind === "user" && "rpcId" in source) this.scheduleObservedRetirement(source.rpcId, attachmentRefsIn(message.content));
|
|
2036
|
+
}
|
|
2086
2037
|
}
|
|
2087
2038
|
/**
|
|
2088
2039
|
* Latch one observed settlement and remove the echo an animation frame
|
|
@@ -2133,7 +2084,6 @@ window.__ModuleLoader__.load({
|
|
|
2133
2084
|
buildSnapshot() {
|
|
2134
2085
|
return {
|
|
2135
2086
|
sessionId: this.sessionId,
|
|
2136
|
-
queue: this.queueMirror.snapshot(),
|
|
2137
2087
|
pendingSubmissions: this.pendingSubmissions,
|
|
2138
2088
|
running: this.running,
|
|
2139
2089
|
subagent: this.address === void 0 ? null : {
|
|
@@ -2182,6 +2132,7 @@ window.__ModuleLoader__.load({
|
|
|
2182
2132
|
}
|
|
2183
2133
|
//#endregion
|
|
2184
2134
|
//#region lib/types/client/sessions/manager.js
|
|
2135
|
+
/** Host catalog, durable projection caches, and explicitly retained Client instances. */
|
|
2185
2136
|
function sessionSeqCursor(value) {
|
|
2186
2137
|
return value === -1 ? -1 : SessionSeq(value);
|
|
2187
2138
|
}
|
|
@@ -2194,16 +2145,6 @@ window.__ModuleLoader__.load({
|
|
|
2194
2145
|
sessions = /* @__PURE__ */ new Map();
|
|
2195
2146
|
/** In-flight Session disposals remain here after instances leave `sessions`, so manager disposal can await quiescence. */
|
|
2196
2147
|
sessionDisposals = /* @__PURE__ */ new Set();
|
|
2197
|
-
/** Latest transient queues, retained independently of Session object materialization. */
|
|
2198
|
-
queues = /* @__PURE__ */ new Map();
|
|
2199
|
-
/**
|
|
2200
|
-
* Sessions that finished running while not selected — the sidebar's green
|
|
2201
|
-
* "done" reminder (manager-owned, survives connection generations; cleared
|
|
2202
|
-
* on select and session-removed, re-armed by the next completion).
|
|
2203
|
-
*/
|
|
2204
|
-
completedNotifications = /* @__PURE__ */ new Set();
|
|
2205
|
-
/** Last-observed running bits per session; the true→false edge here arms {@link completedNotifications}. */
|
|
2206
|
-
prevRunning = /* @__PURE__ */ new Map();
|
|
2207
2148
|
/** Per-session projection value stores, retained independently of instance arrival (the
|
|
2208
2149
|
* title-snapshot precedent, generalized): push frames land here whether or not the Session
|
|
2209
2150
|
* is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
|
|
@@ -2215,7 +2156,7 @@ window.__ModuleLoader__.load({
|
|
|
2215
2156
|
listPhase = "pending";
|
|
2216
2157
|
listError = null;
|
|
2217
2158
|
listInflight = null;
|
|
2218
|
-
/**
|
|
2159
|
+
/** Active list request's mutation log; its identity also fences completion after reconnect. */
|
|
2219
2160
|
listMutations = null;
|
|
2220
2161
|
addresses = /* @__PURE__ */ new Map();
|
|
2221
2162
|
catalogs = /* @__PURE__ */ new Map();
|
|
@@ -2230,7 +2171,6 @@ window.__ModuleLoader__.load({
|
|
|
2230
2171
|
* one representation.
|
|
2231
2172
|
*/
|
|
2232
2173
|
jobsBySession = /* @__PURE__ */ new Map();
|
|
2233
|
-
selected;
|
|
2234
2174
|
listSnapshotCache;
|
|
2235
2175
|
/** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
|
|
2236
2176
|
* object when every field matches — wire refreshes mint all-new summary objects, so identity
|
|
@@ -2240,49 +2180,23 @@ window.__ModuleLoader__.load({
|
|
|
2240
2180
|
notifier = new Notifier(() => {
|
|
2241
2181
|
this.listSnapshotCache = this.buildListSnapshot();
|
|
2242
2182
|
});
|
|
2243
|
-
/**
|
|
2244
|
-
|
|
2245
|
-
* @param restoredSelection - persisted real-Session selection candidate.
|
|
2246
|
-
*/
|
|
2247
|
-
constructor(remote, restoredSelection, restoredAddress) {
|
|
2183
|
+
/** @param remote - generated Remote namespaces used by catalog and history readers. */
|
|
2184
|
+
constructor(remote) {
|
|
2248
2185
|
this.remote = remote;
|
|
2249
|
-
this.selected = restoredSelection;
|
|
2250
|
-
if (restoredAddress !== void 0) this.addresses.set(restoredAddress.childSessionId, restoredAddress);
|
|
2251
2186
|
this.listSnapshotCache = this.buildListSnapshot();
|
|
2252
2187
|
}
|
|
2253
2188
|
/**
|
|
2254
|
-
*
|
|
2255
|
-
* @param
|
|
2256
|
-
|
|
2257
|
-
select(sessionId) {
|
|
2258
|
-
const address = this.navigationAddress(sessionId);
|
|
2259
|
-
if (!this.summaries.some((summary) => summary.sessionId === sessionId) && address === void 0) throw new Error(`sessions.select: unknown session ${sessionId}`);
|
|
2260
|
-
if (address !== void 0) this.addresses.set(sessionId, address);
|
|
2261
|
-
this.sessions.get(sessionId)?.configureSubagent(address, address === void 0 ? void 0 : this.catalogs.get(address.parentSessionId)?.parentAvailable);
|
|
2262
|
-
this.selected = sessionId;
|
|
2263
|
-
this.completedNotifications.delete(sessionId);
|
|
2264
|
-
this.refreshSubagents(sessionId);
|
|
2265
|
-
this.notifier.notifyNow();
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* Select a healthy child through its durable direct-parent address.
|
|
2269
|
-
* @param address - catalog-derived parent and child ids.
|
|
2189
|
+
* Resolve an acquisition target without materializing a Session.
|
|
2190
|
+
* @param target - known identity or durable direct-parent address.
|
|
2191
|
+
* @returns the resolved identity with its explicit or catalog-derived history route installed.
|
|
2270
2192
|
*/
|
|
2271
|
-
|
|
2272
|
-
const
|
|
2273
|
-
const
|
|
2274
|
-
if (
|
|
2275
|
-
this.addresses.set(
|
|
2276
|
-
this.sessions.get(
|
|
2277
|
-
|
|
2278
|
-
this.completedNotifications.delete(address.childSessionId);
|
|
2279
|
-
this.refreshSubagents(address.childSessionId);
|
|
2280
|
-
this.notifier.notifyNow();
|
|
2281
|
-
}
|
|
2282
|
-
/** Clear the selection (the layout falls to the no-session view state). */
|
|
2283
|
-
clearSelection() {
|
|
2284
|
-
this.selected = void 0;
|
|
2285
|
-
this.notifier.notifyNow();
|
|
2193
|
+
resolveTarget(target) {
|
|
2194
|
+
const id = typeof target === "string" ? target : target.childSessionId;
|
|
2195
|
+
const address = typeof target === "string" ? this.navigationAddress(id) : target;
|
|
2196
|
+
if (typeof target === "string" && !this.sessions.has(id) && !this.summaries.some((summary) => summary.sessionId === id) && address === void 0) throw new Error(`sessions.retain: unknown session ${id}`);
|
|
2197
|
+
if (address !== void 0) this.addresses.set(id, address);
|
|
2198
|
+
this.sessions.get(id)?.configureSubagent(address, address === void 0 ? void 0 : this.catalogs.get(address.parentSessionId)?.parentAvailable);
|
|
2199
|
+
return id;
|
|
2286
2200
|
}
|
|
2287
2201
|
/**
|
|
2288
2202
|
* Return the durable catalog address retained for one child.
|
|
@@ -2290,7 +2204,7 @@ window.__ModuleLoader__.load({
|
|
|
2290
2204
|
* @returns The direct-parent address, when navigation discovered one.
|
|
2291
2205
|
*/
|
|
2292
2206
|
subagentAddress(sessionId) {
|
|
2293
|
-
return this.
|
|
2207
|
+
return this.navigationAddress(sessionId);
|
|
2294
2208
|
}
|
|
2295
2209
|
/**
|
|
2296
2210
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
@@ -2310,19 +2224,21 @@ window.__ModuleLoader__.load({
|
|
|
2310
2224
|
}
|
|
2311
2225
|
}
|
|
2312
2226
|
/**
|
|
2313
|
-
*
|
|
2314
|
-
*
|
|
2315
|
-
*
|
|
2316
|
-
* @
|
|
2227
|
+
* Withdraw an exact Client instance before running its teardown callbacks.
|
|
2228
|
+
* @param sessionId - identity to withdraw.
|
|
2229
|
+
* @param expected - instance being released; a replacement is left untouched.
|
|
2230
|
+
* @returns completion of the detached instance's stream teardown.
|
|
2317
2231
|
*/
|
|
2318
|
-
|
|
2232
|
+
drop(sessionId, expected) {
|
|
2319
2233
|
const session = this.sessions.get(sessionId);
|
|
2234
|
+
if (session !== expected) return Promise.resolve();
|
|
2320
2235
|
this.sessions.delete(sessionId);
|
|
2321
|
-
|
|
2236
|
+
this.addresses.delete(sessionId);
|
|
2237
|
+
return this.startSessionDisposal(session);
|
|
2322
2238
|
}
|
|
2323
2239
|
/**
|
|
2324
|
-
* Stop
|
|
2325
|
-
* @returns
|
|
2240
|
+
* Stop catalog requests and dispose every resident Session.
|
|
2241
|
+
* @returns once catalog requests and every Session stream have stopped.
|
|
2326
2242
|
*/
|
|
2327
2243
|
async dispose() {
|
|
2328
2244
|
for (const timer of this.catalogDebounce.values()) clearTimeout(timer);
|
|
@@ -2331,6 +2247,7 @@ window.__ModuleLoader__.load({
|
|
|
2331
2247
|
this.openCatalogs.clear();
|
|
2332
2248
|
const sessions = [...this.sessions.values()];
|
|
2333
2249
|
this.sessions.clear();
|
|
2250
|
+
this.addresses.clear();
|
|
2334
2251
|
for (const session of sessions) this.startSessionDisposal(session);
|
|
2335
2252
|
await this.drainSessionDisposals();
|
|
2336
2253
|
}
|
|
@@ -2349,7 +2266,7 @@ window.__ModuleLoader__.load({
|
|
|
2349
2266
|
}
|
|
2350
2267
|
/**
|
|
2351
2268
|
* Lazy build: return the existing instance or construct one (no auto-open —
|
|
2352
|
-
*
|
|
2269
|
+
* the reference allocator opens history after binding the scope).
|
|
2353
2270
|
* @param sessionId - the session to get.
|
|
2354
2271
|
* @returns the resident instance.
|
|
2355
2272
|
*/
|
|
@@ -2358,7 +2275,6 @@ window.__ModuleLoader__.load({
|
|
|
2358
2275
|
if (session === void 0) {
|
|
2359
2276
|
session = this.createSession(sessionId);
|
|
2360
2277
|
this.sessions.set(sessionId, session);
|
|
2361
|
-
session.replaceControl(this.queues.get(sessionId) ?? []);
|
|
2362
2278
|
const summary = this.summaries.find((s) => s.sessionId === sessionId);
|
|
2363
2279
|
if (summary !== void 0) {
|
|
2364
2280
|
session.handleBlank(summary.blank);
|
|
@@ -2482,7 +2398,7 @@ window.__ModuleLoader__.load({
|
|
|
2482
2398
|
}
|
|
2483
2399
|
}
|
|
2484
2400
|
}
|
|
2485
|
-
/** Full refresh via session.list (single-flight
|
|
2401
|
+
/** Full refresh via session.list (single-flight within one Host generation). */
|
|
2486
2402
|
refreshList() {
|
|
2487
2403
|
if (this.listInflight !== null) return this.listInflight;
|
|
2488
2404
|
this.listState = "loading";
|
|
@@ -2494,19 +2410,12 @@ window.__ModuleLoader__.load({
|
|
|
2494
2410
|
this.listInflight = (async () => {
|
|
2495
2411
|
try {
|
|
2496
2412
|
const result = await this.remote.session.list({});
|
|
2413
|
+
if (this.listMutations !== mutations) return;
|
|
2497
2414
|
if (result.ok) {
|
|
2498
2415
|
const baseline = this.listPhase === "pending" ? [...result.value.items] : mergeOrderedBaseline(established, result.value.items, (summary) => summary.sessionId);
|
|
2499
|
-
|
|
2500
|
-
let summaries = baseline;
|
|
2501
|
-
for (const mutation of mutations) {
|
|
2502
|
-
summaries = applyMutation(summaries, mutation);
|
|
2503
|
-
this.summaries = summaries;
|
|
2504
|
-
this.syncCompletedNotifications();
|
|
2505
|
-
}
|
|
2506
|
-
this.summaries = summaries;
|
|
2416
|
+
this.summaries = mutations.reduce(applyMutation, baseline);
|
|
2507
2417
|
this.listState = "idle";
|
|
2508
2418
|
this.listPhase = "ready";
|
|
2509
|
-
this.syncCompletedNotifications();
|
|
2510
2419
|
for (const s of this.summaries) {
|
|
2511
2420
|
const session = this.sessions.get(s.sessionId);
|
|
2512
2421
|
if (session === void 0) continue;
|
|
@@ -2526,12 +2435,15 @@ window.__ModuleLoader__.load({
|
|
|
2526
2435
|
}
|
|
2527
2436
|
} catch (error) {
|
|
2528
2437
|
if (!(0, _deepseek_ai_dsh_api_gateway_client.isRemoteFailure)(error)) throw error;
|
|
2438
|
+
if (this.listMutations !== mutations) return;
|
|
2529
2439
|
this.listState = "error";
|
|
2530
2440
|
this.listError = error;
|
|
2531
2441
|
} finally {
|
|
2532
|
-
this.listMutations
|
|
2533
|
-
|
|
2534
|
-
|
|
2442
|
+
if (this.listMutations === mutations) {
|
|
2443
|
+
this.listMutations = null;
|
|
2444
|
+
this.listInflight = null;
|
|
2445
|
+
this.notifier.markDirty();
|
|
2446
|
+
}
|
|
2535
2447
|
}
|
|
2536
2448
|
})();
|
|
2537
2449
|
return this.listInflight;
|
|
@@ -2640,7 +2552,6 @@ window.__ModuleLoader__.load({
|
|
|
2640
2552
|
recordMutation(mutation) {
|
|
2641
2553
|
this.listMutations?.push(mutation);
|
|
2642
2554
|
this.summaries = applyMutation(this.summaries, mutation);
|
|
2643
|
-
this.syncCompletedNotifications();
|
|
2644
2555
|
this.notifier.markDirty();
|
|
2645
2556
|
}
|
|
2646
2557
|
/**
|
|
@@ -2660,6 +2571,14 @@ window.__ModuleLoader__.load({
|
|
|
2660
2571
|
return this.listSnapshotCache;
|
|
2661
2572
|
}
|
|
2662
2573
|
/**
|
|
2574
|
+
* Read cached projection values for a Session that may exist only in a loaded subagent catalog.
|
|
2575
|
+
* @param sessionId - Session whose control or history baseline supplied projections.
|
|
2576
|
+
* @returns current values, or undefined before any projection store exists.
|
|
2577
|
+
*/
|
|
2578
|
+
projectionValues(sessionId) {
|
|
2579
|
+
return this.projectionStores.get(sessionId)?.values();
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2663
2582
|
* Apply a complete control baseline or one later replacement frame.
|
|
2664
2583
|
* @param frame - baseline or live control replacement from Session Controller.
|
|
2665
2584
|
*/
|
|
@@ -2673,30 +2592,21 @@ window.__ModuleLoader__.load({
|
|
|
2673
2592
|
this.notifier.markDirty();
|
|
2674
2593
|
return;
|
|
2675
2594
|
}
|
|
2676
|
-
if (frame.
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
this.notifier.markDirty();
|
|
2680
|
-
return;
|
|
2681
|
-
}
|
|
2682
|
-
this.queues.set(frame.sessionId, frame.items);
|
|
2683
|
-
this.sessions.get(frame.sessionId)?.handleControlFrame(frame);
|
|
2595
|
+
if (frame.jobs.length === 0) this.jobsBySession.delete(frame.sessionId);
|
|
2596
|
+
else this.jobsBySession.set(frame.sessionId, frame.jobs);
|
|
2597
|
+
this.notifier.markDirty();
|
|
2684
2598
|
}
|
|
2685
2599
|
replaceControlBaseline(baseline) {
|
|
2686
|
-
this.queues.clear();
|
|
2687
|
-
for (const [sessionId, items] of Object.entries(baseline.queues)) this.queues.set(sessionId, items);
|
|
2688
2600
|
this.jobsBySession.clear();
|
|
2689
2601
|
for (const [sessionId, jobs] of Object.entries(baseline.jobs)) if (jobs.length > 0) this.jobsBySession.set(sessionId, jobs);
|
|
2690
2602
|
for (const [sessionId, block] of Object.entries(baseline.projections)) {
|
|
2691
2603
|
const store = this.projectionStore(sessionId);
|
|
2692
2604
|
const asOfSeq = sessionSeqCursor(block.asOfSeq);
|
|
2693
|
-
store.truncate(asOfSeq);
|
|
2694
2605
|
store.seed({
|
|
2695
2606
|
...block,
|
|
2696
2607
|
asOfSeq
|
|
2697
2608
|
});
|
|
2698
2609
|
}
|
|
2699
|
-
for (const [sessionId, session] of this.sessions) session.replaceControl(this.queues.get(sessionId) ?? []);
|
|
2700
2610
|
this.notifier.markDirty();
|
|
2701
2611
|
}
|
|
2702
2612
|
/**
|
|
@@ -2712,7 +2622,7 @@ window.__ModuleLoader__.load({
|
|
|
2712
2622
|
for (const [key, value] of Object.entries(projections.values)) store.apply(key, value, sessionSeqCursor(projections.asOfSeq));
|
|
2713
2623
|
}
|
|
2714
2624
|
if (summary.origin === "subagent" && summary.parentSessionId !== void 0) this.markCatalogParentExpandable(summary.parentSessionId);
|
|
2715
|
-
if (summary.parentSessionId !== void 0 &&
|
|
2625
|
+
if (summary.parentSessionId !== void 0 && this.openCatalogs.has(summary.parentSessionId)) this.scheduleCatalogRefresh(summary.parentSessionId);
|
|
2716
2626
|
}
|
|
2717
2627
|
/**
|
|
2718
2628
|
* Apply one Session removal forwarded through `ctx.remote.$on`.
|
|
@@ -2731,7 +2641,6 @@ window.__ModuleLoader__.load({
|
|
|
2731
2641
|
this.updateCatalogActivity(sessionId, false);
|
|
2732
2642
|
if (durableSubagent) this.sessions.get(sessionId)?.handleRunning(false);
|
|
2733
2643
|
else this.sessions.get(sessionId)?.handleRemoved();
|
|
2734
|
-
this.queues.delete(sessionId);
|
|
2735
2644
|
this.jobsBySession.delete(sessionId);
|
|
2736
2645
|
if (!durableSubagent) this.projectionStores.delete(sessionId);
|
|
2737
2646
|
const inflightCatalog = this.catalogInflight.get(sessionId);
|
|
@@ -2782,16 +2691,23 @@ window.__ModuleLoader__.load({
|
|
|
2782
2691
|
}
|
|
2783
2692
|
/**
|
|
2784
2693
|
* Repair one re-established Host-event generation with queryable baselines.
|
|
2694
|
+
* Discard old projection cuts before new queries, including cold Sessions
|
|
2695
|
+
* absent from the process-local control baseline.
|
|
2785
2696
|
* Opened Session follow streams resume independently through API Gateway.
|
|
2786
2697
|
*/
|
|
2787
2698
|
handleConnected() {
|
|
2699
|
+
for (const store of this.projectionStores.values()) store.clear();
|
|
2700
|
+
this.listMutations = null;
|
|
2701
|
+
this.listInflight = null;
|
|
2788
2702
|
this.refreshList();
|
|
2789
|
-
const
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2703
|
+
const parents = new Set(this.openCatalogs);
|
|
2704
|
+
for (const id of this.sessions.keys()) {
|
|
2705
|
+
const address = this.addresses.get(id);
|
|
2706
|
+
if (address !== void 0) parents.add(address.parentSessionId);
|
|
2707
|
+
}
|
|
2708
|
+
for (const parentSessionId of parents) this.refreshSubagents(parentSessionId);
|
|
2793
2709
|
}
|
|
2794
|
-
/** Debounce membership refetches
|
|
2710
|
+
/** Debounce membership refetches for an explicitly consumed catalog. */
|
|
2795
2711
|
scheduleCatalogRefresh(parentSessionId) {
|
|
2796
2712
|
if (this.catalogDebounce.has(parentSessionId)) return;
|
|
2797
2713
|
const timer = setTimeout(() => {
|
|
@@ -2864,31 +2780,6 @@ window.__ModuleLoader__.load({
|
|
|
2864
2780
|
};
|
|
2865
2781
|
});
|
|
2866
2782
|
}
|
|
2867
|
-
/**
|
|
2868
|
-
* Reconcile completion reminders against the latest summaries, eagerly after
|
|
2869
|
-
* every mutation and pull (a snapshot-build-time pass would collapse
|
|
2870
|
-
* consecutive status frames into one observation). A running→idle edge of a
|
|
2871
|
-
* non-selected session arms its reminder; running disarms it; removal drops
|
|
2872
|
-
* it. First observation only records the running bit — sessions already
|
|
2873
|
-
* idle at load get no reminder.
|
|
2874
|
-
*/
|
|
2875
|
-
syncCompletedNotifications() {
|
|
2876
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2877
|
-
for (const s of this.summaries) {
|
|
2878
|
-
seen.add(s.sessionId);
|
|
2879
|
-
const prev = this.prevRunning.get(s.sessionId);
|
|
2880
|
-
if (prev === void 0) {
|
|
2881
|
-
this.prevRunning.set(s.sessionId, s.running);
|
|
2882
|
-
continue;
|
|
2883
|
-
}
|
|
2884
|
-
if (prev && !s.running) {
|
|
2885
|
-
if (s.sessionId !== this.selected) this.completedNotifications.add(s.sessionId);
|
|
2886
|
-
} else if (s.running) this.completedNotifications.delete(s.sessionId);
|
|
2887
|
-
this.prevRunning.set(s.sessionId, s.running);
|
|
2888
|
-
}
|
|
2889
|
-
for (const id of this.prevRunning.keys()) if (!seen.has(id)) this.prevRunning.delete(id);
|
|
2890
|
-
for (const id of this.completedNotifications) if (!seen.has(id)) this.completedNotifications.delete(id);
|
|
2891
|
-
}
|
|
2892
2783
|
buildListSnapshot() {
|
|
2893
2784
|
const items = flattenLineage(this.summaries.map((summary) => {
|
|
2894
2785
|
const projectionStore = this.projectionStores.get(summary.sessionId);
|
|
@@ -2899,25 +2790,22 @@ window.__ModuleLoader__.load({
|
|
|
2899
2790
|
...typeof title === "string" && title !== "" ? { title } : {},
|
|
2900
2791
|
...projectionValues === void 0 ? {} : { projectionValues }
|
|
2901
2792
|
};
|
|
2902
|
-
})
|
|
2793
|
+
})).map((entry) => {
|
|
2903
2794
|
const prev = this.entryCache.get(entry.sessionId);
|
|
2904
|
-
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
|
|
2795
|
+
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;
|
|
2905
2796
|
this.entryCache.set(entry.sessionId, entry);
|
|
2906
2797
|
return entry;
|
|
2907
2798
|
});
|
|
2908
|
-
|
|
2799
|
+
const itemIds = new Set(items.map((entry) => entry.sessionId));
|
|
2800
|
+
for (const id of this.entryCache.keys()) if (!itemIds.has(id)) this.entryCache.delete(id);
|
|
2909
2801
|
if (!(items.length === this.itemsCache.length && items.every((e, i) => e === this.itemsCache[i]))) this.itemsCache = items;
|
|
2910
|
-
const selected = this.selected;
|
|
2911
|
-
const current = selected !== void 0 && (items.some((item) => item.sessionId === selected) || this.addresses.has(selected)) ? selected : void 0;
|
|
2912
2802
|
return {
|
|
2913
2803
|
items: this.itemsCache,
|
|
2914
|
-
current,
|
|
2915
2804
|
state: this.listState,
|
|
2916
2805
|
phase: this.listPhase,
|
|
2917
2806
|
error: this.listError,
|
|
2918
2807
|
subagentsByParent: Object.fromEntries(this.catalogs),
|
|
2919
|
-
jobsBySession: Object.fromEntries(this.jobsBySession)
|
|
2920
|
-
currentAddress: current === void 0 ? void 0 : this.addresses.get(current)
|
|
2808
|
+
jobsBySession: Object.fromEntries(this.jobsBySession)
|
|
2921
2809
|
};
|
|
2922
2810
|
}
|
|
2923
2811
|
};
|
|
@@ -3014,7 +2902,74 @@ window.__ModuleLoader__.load({
|
|
|
3014
2902
|
if (fullWidth?.[1] !== void 0 && fullWidth[2] !== void 0) return `${fullWidth[1]}(${BigInt(fullWidth[2]) + 1n})`;
|
|
3015
2903
|
return `${title} (1)`;
|
|
3016
2904
|
}
|
|
3017
|
-
/**
|
|
2905
|
+
/** Source labels are dictionary keys, including names also present on Object.prototype. */
|
|
2906
|
+
function freezeRetainedBy(counts) {
|
|
2907
|
+
Object.setPrototypeOf(counts, null);
|
|
2908
|
+
return Object.freeze(counts);
|
|
2909
|
+
}
|
|
2910
|
+
const EMPTY_RETAIN_INFO = Object.freeze({
|
|
2911
|
+
referenceCount: 0,
|
|
2912
|
+
retainedBy: freezeRetainedBy({})
|
|
2913
|
+
});
|
|
2914
|
+
/** A cancelled waiter releases only its own reference, not the shared opening. */
|
|
2915
|
+
async function waitForOpen(opening, signal) {
|
|
2916
|
+
if (signal === void 0) return opening;
|
|
2917
|
+
const aborted = Promise.withResolvers();
|
|
2918
|
+
const onAbort = () => {
|
|
2919
|
+
aborted.reject(signal.reason);
|
|
2920
|
+
};
|
|
2921
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2922
|
+
try {
|
|
2923
|
+
if (signal.aborted) onAbort();
|
|
2924
|
+
await Promise.race([opening, aborted.promise]);
|
|
2925
|
+
} finally {
|
|
2926
|
+
signal.removeEventListener("abort", onAbort);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
var ClientSessionReference = class {
|
|
2930
|
+
sessionId;
|
|
2931
|
+
record;
|
|
2932
|
+
releaseReference;
|
|
2933
|
+
released = new AbortController();
|
|
2934
|
+
readiness = Promise.withResolvers();
|
|
2935
|
+
ready = this.readiness.promise;
|
|
2936
|
+
constructor(sessionId, record, releaseReference) {
|
|
2937
|
+
this.sessionId = sessionId;
|
|
2938
|
+
this.record = record;
|
|
2939
|
+
this.releaseReference = releaseReference;
|
|
2940
|
+
this.ready.catch(() => {});
|
|
2941
|
+
}
|
|
2942
|
+
get binding() {
|
|
2943
|
+
if (this.record === void 0 || !this.record.live) throw new Error(`Session reference "${this.sessionId}" is released`);
|
|
2944
|
+
return this.record.binding;
|
|
2945
|
+
}
|
|
2946
|
+
attachOpening(opening, signal) {
|
|
2947
|
+
const waitSignal = signal === void 0 ? this.released.signal : AbortSignal.any([this.released.signal, signal]);
|
|
2948
|
+
waitForOpen(opening, waitSignal).then(() => {
|
|
2949
|
+
try {
|
|
2950
|
+
waitSignal.throwIfAborted();
|
|
2951
|
+
this.readiness.resolve(this.binding);
|
|
2952
|
+
} catch (error) {
|
|
2953
|
+
this.readiness.reject(error);
|
|
2954
|
+
}
|
|
2955
|
+
}, (error) => {
|
|
2956
|
+
this.readiness.reject(error);
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
release() {
|
|
2960
|
+
const reason = /* @__PURE__ */ new Error(`Session reference "${this.sessionId}" is released`);
|
|
2961
|
+
const release = this.releaseReference;
|
|
2962
|
+
this.released.abort(reason);
|
|
2963
|
+
this.readiness.reject(reason);
|
|
2964
|
+
this.record = void 0;
|
|
2965
|
+
this.releaseReference = void 0;
|
|
2966
|
+
release?.();
|
|
2967
|
+
}
|
|
2968
|
+
[Symbol.dispose]() {
|
|
2969
|
+
this.release();
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2972
|
+
/** Host catalog and local reference allocator; view selection remains outside the Controller. */
|
|
3018
2973
|
var ClientSessions = class {
|
|
3019
2974
|
rootCtx;
|
|
3020
2975
|
/**
|
|
@@ -3024,81 +2979,94 @@ window.__ModuleLoader__.load({
|
|
|
3024
2979
|
* reports the same number.
|
|
3025
2980
|
*/
|
|
3026
2981
|
searchResultLimit = 20;
|
|
3027
|
-
/**
|
|
2982
|
+
/** Catalog metadata and local reference-source projection. */
|
|
3028
2983
|
list;
|
|
3029
2984
|
/** The object-layer instance cluster and frame dispatch entry. */
|
|
3030
2985
|
manager;
|
|
3031
|
-
/**
|
|
3032
|
-
* Persisted selection cell (the durable half of `list.current`). Private on
|
|
3033
|
-
* purpose: reads go through the list snapshot; writes through {@link
|
|
3034
|
-
* ClientSessions.open} / {@link ClientSessions.clear}. Projection
|
|
3035
|
-
* validates it against the live list instead of destructively pruning, so a
|
|
3036
|
-
* selection survives transient list states (reconnect re-pull) and
|
|
3037
|
-
* resurfaces when its session returns.
|
|
3038
|
-
*/
|
|
3039
|
-
selection;
|
|
3040
2986
|
scopes = /* @__PURE__ */ new Map();
|
|
3041
|
-
/**
|
|
2987
|
+
/** Stable per-id sources retained for the Client root lifetime, including across generation replacement. */
|
|
2988
|
+
retainObservers = /* @__PURE__ */ new Map();
|
|
3042
2989
|
scopeDrops = /* @__PURE__ */ new Set();
|
|
3043
|
-
|
|
3044
|
-
* The staged session id — follows `list.current` exactly, holding its last
|
|
3045
|
-
* defined value across masked gaps (a transiently absent selection blanks
|
|
3046
|
-
* `current` without moving the stage, so reconnect re-pulls and removals
|
|
3047
|
-
* keep the staged scope's frozen view alive until the stage moves on).
|
|
3048
|
-
*/
|
|
3049
|
-
watched;
|
|
3050
|
-
/** Removed-while-staged sessions whose teardown waits for the stage to move away. */
|
|
3051
|
-
deferredRemovals = /* @__PURE__ */ new Set();
|
|
2990
|
+
closed = false;
|
|
3052
2991
|
/**
|
|
3053
2992
|
* @param ctx - client root context (scope fibers mount under it).
|
|
3054
2993
|
* @param remote - generated Remote namespaces shared with every Session.
|
|
3055
2994
|
*/
|
|
3056
2995
|
constructor(rootCtx, remote) {
|
|
3057
2996
|
this.rootCtx = rootCtx;
|
|
3058
|
-
this.
|
|
3059
|
-
const restored = this.selection.getSnapshot();
|
|
3060
|
-
this.manager = new SessionManager(remote, restored.sessionId, restored.subagentAddress);
|
|
2997
|
+
this.manager = new SessionManager(remote);
|
|
3061
2998
|
this.list = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
|
|
3062
2999
|
ids: [],
|
|
3063
3000
|
byId: {},
|
|
3064
|
-
current: void 0,
|
|
3065
3001
|
phase: "pending",
|
|
3066
3002
|
subagentsByParent: {},
|
|
3067
|
-
jobsBySession: {}
|
|
3068
|
-
currentAddress: void 0
|
|
3003
|
+
jobsBySession: {}
|
|
3069
3004
|
});
|
|
3070
3005
|
const disposeManagerProjection = this.manager.subscribe(() => {
|
|
3071
3006
|
this.projectList();
|
|
3072
3007
|
});
|
|
3073
|
-
const disposeStageFollower = this.list.subscribe(() => {
|
|
3074
|
-
this.followCurrent();
|
|
3075
|
-
});
|
|
3076
3008
|
rootCtx.effect(() => async () => {
|
|
3077
|
-
|
|
3009
|
+
this.closed = true;
|
|
3078
3010
|
disposeManagerProjection();
|
|
3079
3011
|
const scopes = [...this.scopes];
|
|
3080
3012
|
this.scopes.clear();
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3013
|
+
for (const [, record] of scopes) {
|
|
3014
|
+
record.live = false;
|
|
3015
|
+
record.session.unbindScope();
|
|
3016
|
+
}
|
|
3017
|
+
const managerDisposal = this.manager.dispose();
|
|
3018
|
+
for (const [id, record] of scopes) {
|
|
3019
|
+
this.startScopeDrop(id, record);
|
|
3020
|
+
this.publishRetention(id);
|
|
3021
|
+
}
|
|
3084
3022
|
await this.drainScopeDrops();
|
|
3085
|
-
await
|
|
3023
|
+
await managerDisposal;
|
|
3086
3024
|
}, "session-controller.client.sessions");
|
|
3087
3025
|
rootCtx.reflect.provide("sessions", this, void 0);
|
|
3088
3026
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
this.
|
|
3027
|
+
retain(target, options) {
|
|
3028
|
+
const { source, signal } = options;
|
|
3029
|
+
signal?.throwIfAborted();
|
|
3030
|
+
if (this.closed) throw new Error("Session Controller is disposed");
|
|
3031
|
+
const id = this.manager.resolveTarget(target);
|
|
3032
|
+
const reference = this.retainScope(id, source);
|
|
3033
|
+
try {
|
|
3034
|
+
reference.attachOpening(this.manager.get(id).open(), signal);
|
|
3035
|
+
return reference;
|
|
3036
|
+
} catch (error) {
|
|
3037
|
+
reference.release();
|
|
3038
|
+
throw error;
|
|
3039
|
+
}
|
|
3095
3040
|
}
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3041
|
+
async using(target, options, operation) {
|
|
3042
|
+
const reference = this.retain(target, options);
|
|
3043
|
+
try {
|
|
3044
|
+
await reference.ready;
|
|
3045
|
+
return await operation(reference);
|
|
3046
|
+
} finally {
|
|
3047
|
+
reference.release();
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
retainInfo(id) {
|
|
3051
|
+
let observer = this.retainObservers.get(id);
|
|
3052
|
+
if (observer === void 0) {
|
|
3053
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3054
|
+
observer = {
|
|
3055
|
+
listeners,
|
|
3056
|
+
published: this.retentionSnapshot(id),
|
|
3057
|
+
source: {
|
|
3058
|
+
getSnapshot: () => this.retentionSnapshot(id),
|
|
3059
|
+
subscribe: (listener) => {
|
|
3060
|
+
listeners.add(listener);
|
|
3061
|
+
return () => {
|
|
3062
|
+
listeners.delete(listener);
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
};
|
|
3067
|
+
this.retainObservers.set(id, observer);
|
|
3068
|
+
}
|
|
3069
|
+
return observer.source;
|
|
3102
3070
|
}
|
|
3103
3071
|
/**
|
|
3104
3072
|
* Resolve an already discovered direct-parent address without opening it.
|
|
@@ -3125,16 +3093,6 @@ window.__ModuleLoader__.load({
|
|
|
3125
3093
|
return this.manager.refreshSubagents(parentSessionId);
|
|
3126
3094
|
}
|
|
3127
3095
|
/**
|
|
3128
|
-
* Clear the current selection so the layout shows the no-session empty
|
|
3129
|
-
* state (new-session affordance and the workspace preselection flow).
|
|
3130
|
-
* Wipes the persisted selection too — a reload stays on empty until the
|
|
3131
|
-
* user opens or starts a session. The staged scope keeps its frozen view
|
|
3132
|
-
* per the masked-gap contract until the next open() moves the stage.
|
|
3133
|
-
*/
|
|
3134
|
-
clear() {
|
|
3135
|
-
this.manager.clearSelection();
|
|
3136
|
-
}
|
|
3137
|
-
/**
|
|
3138
3096
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
3139
3097
|
* @returns completion of the current or newly started baseline pull.
|
|
3140
3098
|
*/
|
|
@@ -3198,12 +3156,8 @@ window.__ModuleLoader__.load({
|
|
|
3198
3156
|
this.manager.handleConnected();
|
|
3199
3157
|
}
|
|
3200
3158
|
/**
|
|
3201
|
-
* Create a
|
|
3202
|
-
*
|
|
3203
|
-
* {@link ClientSessions.binding} resolves it — callers (New Session
|
|
3204
|
-
* draft hand-off) may address the scope synchronously, without waiting a
|
|
3205
|
-
* notifier flush. The synchronous projection below makes this structural
|
|
3206
|
-
* rather than an accident of microtask ordering.
|
|
3159
|
+
* Create a Host Session and publish its catalog row before resolving.
|
|
3160
|
+
* Callers retain the returned identity before borrowing its binding.
|
|
3207
3161
|
* @param opts - target workspace or directory and an optional preallocated id.
|
|
3208
3162
|
* @returns the new session id.
|
|
3209
3163
|
* @throws {SessionCreateError} with the requested id.
|
|
@@ -3215,9 +3169,8 @@ window.__ModuleLoader__.load({
|
|
|
3215
3169
|
return result.value.sessionId;
|
|
3216
3170
|
}
|
|
3217
3171
|
/**
|
|
3218
|
-
* Fork a
|
|
3219
|
-
*
|
|
3220
|
-
* on resolution the child is in the list store and open() can target it).
|
|
3172
|
+
* Fork a Session from a completed-turn prefix of the source and publish
|
|
3173
|
+
* the child in the catalog before resolving.
|
|
3221
3174
|
* @param opts - source session id, the optional event seq anchoring the
|
|
3222
3175
|
* cut (the boundary is the first turn/end at or after it; an in-log
|
|
3223
3176
|
* anchor in an open turn is unavailable rather than clipped backward),
|
|
@@ -3239,31 +3192,33 @@ window.__ModuleLoader__.load({
|
|
|
3239
3192
|
this.projectList();
|
|
3240
3193
|
const childId = result.value.sessionId;
|
|
3241
3194
|
if (sourceTitle !== void 0) {
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3195
|
+
const reference = this.retain(childId, { source: "controllerOperation" });
|
|
3196
|
+
try {
|
|
3197
|
+
await reference.ready;
|
|
3198
|
+
const renamed = await reference.binding.session.rename(increasedForkTitle(sourceTitle));
|
|
3199
|
+
if (!renamed.ok) throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
3200
|
+
} finally {
|
|
3201
|
+
reference.release();
|
|
3202
|
+
}
|
|
3246
3203
|
}
|
|
3247
3204
|
return childId;
|
|
3248
3205
|
}
|
|
3249
3206
|
/**
|
|
3250
|
-
*
|
|
3207
|
+
* Borrow an already-retained Agent-scoped Context.
|
|
3251
3208
|
* @param id - session id (the agent identity — 1:1 same axis).
|
|
3252
|
-
* @returns scoped
|
|
3209
|
+
* @returns the scoped Context, or undefined without a retained generation.
|
|
3253
3210
|
*/
|
|
3254
3211
|
scope(id) {
|
|
3255
|
-
return this.
|
|
3212
|
+
return this.scopes.get(id)?.ctx;
|
|
3256
3213
|
}
|
|
3257
3214
|
/**
|
|
3258
|
-
*
|
|
3259
|
-
*
|
|
3260
|
-
*
|
|
3261
|
-
* arrival order.
|
|
3262
|
-
* @param id - Host-projected Agent identity (the matching Session id).
|
|
3263
|
-
* @returns the identity-stable Agent Context.
|
|
3215
|
+
* Retain a validated Gateway identity synchronously, without history or catalog I/O.
|
|
3216
|
+
* @param id - Host-projected Session identity, possibly not yet catalogued.
|
|
3217
|
+
* @returns a Gateway-source reference owned by the invocation.
|
|
3264
3218
|
*/
|
|
3265
|
-
|
|
3266
|
-
|
|
3219
|
+
retainAgentScope(id) {
|
|
3220
|
+
if (this.closed) throw new Error("Session Controller is disposed");
|
|
3221
|
+
return this.retainScope(id, "gateway");
|
|
3267
3222
|
}
|
|
3268
3223
|
/**
|
|
3269
3224
|
* Read the Agent scope tag off a context. Service-method boundary: fetch
|
|
@@ -3283,55 +3238,83 @@ window.__ModuleLoader__.load({
|
|
|
3283
3238
|
* `agent.session`). Same service-method boundary as
|
|
3284
3239
|
* {@link ClientSessions.scopeOf}.
|
|
3285
3240
|
* @param ctx - an Agent-scoped context.
|
|
3286
|
-
* @returns the
|
|
3241
|
+
* @returns the matching live Session, or undefined for an untagged or ended generation.
|
|
3287
3242
|
*/
|
|
3288
3243
|
sessionOf(ctx) {
|
|
3289
3244
|
const id = scopeOf(ctx);
|
|
3290
3245
|
if (id === void 0) return void 0;
|
|
3291
|
-
|
|
3246
|
+
const record = this.scopes.get(id);
|
|
3247
|
+
return record !== void 0 && scopeIdentityOf(record.ctx) === scopeIdentityOf(ctx) ? record.binding.session : void 0;
|
|
3292
3248
|
}
|
|
3293
3249
|
/**
|
|
3294
|
-
*
|
|
3295
|
-
*
|
|
3296
|
-
* @
|
|
3297
|
-
* @returns binding, or undefined for a session neither listed nor already scoped.
|
|
3250
|
+
* Borrow an already-retained binding without extending its lifetime.
|
|
3251
|
+
* @param id - Session identity.
|
|
3252
|
+
* @returns the live binding, or undefined without a retained generation.
|
|
3298
3253
|
*/
|
|
3299
3254
|
binding(id) {
|
|
3300
|
-
return this.
|
|
3301
|
-
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
record.
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3255
|
+
return this.scopes.get(id)?.binding;
|
|
3256
|
+
}
|
|
3257
|
+
retainScope(id, source) {
|
|
3258
|
+
const record = this.scopes.get(id) ?? this.materializeScope(id);
|
|
3259
|
+
const previous = record.retention;
|
|
3260
|
+
record.retention = Object.freeze({
|
|
3261
|
+
referenceCount: previous.referenceCount + 1,
|
|
3262
|
+
retainedBy: freezeRetainedBy({
|
|
3263
|
+
...previous.retainedBy,
|
|
3264
|
+
[source]: (previous.retainedBy[source] ?? 0) + 1
|
|
3265
|
+
})
|
|
3266
|
+
});
|
|
3267
|
+
const reference = new ClientSessionReference(id, record, () => {
|
|
3268
|
+
if (!record.live) return;
|
|
3269
|
+
const count = record.retention.referenceCount - 1;
|
|
3270
|
+
const { [source]: sourceCount = 0, ...otherSources } = record.retention.retainedBy;
|
|
3271
|
+
const retainedBy = sourceCount > 1 ? {
|
|
3272
|
+
...otherSources,
|
|
3273
|
+
[source]: sourceCount - 1
|
|
3274
|
+
} : otherSources;
|
|
3275
|
+
record.retention = count === 0 ? EMPTY_RETAIN_INFO : Object.freeze({
|
|
3276
|
+
referenceCount: count,
|
|
3277
|
+
retainedBy: freezeRetainedBy(retainedBy)
|
|
3278
|
+
});
|
|
3279
|
+
if (count === 0) this.retireScope(id, record);
|
|
3280
|
+
else this.publishRetention(id);
|
|
3281
|
+
});
|
|
3282
|
+
if (this.list.getSnapshot().byId[id] === void 0) this.projectList();
|
|
3283
|
+
this.publishRetention(id);
|
|
3284
|
+
return reference;
|
|
3285
|
+
}
|
|
3286
|
+
retentionSnapshot(id) {
|
|
3287
|
+
return this.scopes.get(id)?.retention ?? EMPTY_RETAIN_INFO;
|
|
3288
|
+
}
|
|
3289
|
+
publishRetention(id) {
|
|
3290
|
+
const state = this.list.getSnapshot();
|
|
3291
|
+
const row = state.byId[id];
|
|
3292
|
+
const retainedBy = this.retentionSnapshot(id).retainedBy;
|
|
3293
|
+
if (row !== void 0 && row.retainedBy !== retainedBy) this.list.set({
|
|
3294
|
+
...state,
|
|
3295
|
+
byId: {
|
|
3296
|
+
...state.byId,
|
|
3297
|
+
[id]: {
|
|
3298
|
+
...row,
|
|
3299
|
+
retainedBy
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
});
|
|
3303
|
+
const observer = this.retainObservers.get(id);
|
|
3304
|
+
const snapshot = this.retentionSnapshot(id);
|
|
3305
|
+
if (observer === void 0 || observer.published === snapshot) return;
|
|
3306
|
+
observer.published = snapshot;
|
|
3307
|
+
(0, _deepseek_ai_dsh_client_store.notifySubscribers)(observer.listeners, "[session-controller] reference sources");
|
|
3308
|
+
}
|
|
3309
|
+
retireScope(id, record, disposeFiber = true) {
|
|
3310
|
+
if (!record.live) return;
|
|
3311
|
+
record.live = false;
|
|
3312
|
+
if (this.scopes.get(id) === record) this.scopes.delete(id);
|
|
3313
|
+
record.session.unbindScope();
|
|
3314
|
+
const sessionDisposal = this.manager.drop(id, record.session);
|
|
3315
|
+
this.projectList();
|
|
3316
|
+
this.publishRetention(id);
|
|
3317
|
+
this.startScopeDrop(id, record, disposeFiber, sessionDisposal);
|
|
3335
3318
|
}
|
|
3336
3319
|
/** Materialize one scope after its caller establishes that the id may be addressed. */
|
|
3337
3320
|
materializeScope(id) {
|
|
@@ -3347,19 +3330,20 @@ window.__ModuleLoader__.load({
|
|
|
3347
3330
|
eventSource: session.eventSource,
|
|
3348
3331
|
ctx
|
|
3349
3332
|
},
|
|
3350
|
-
session
|
|
3333
|
+
session,
|
|
3334
|
+
retention: EMPTY_RETAIN_INFO,
|
|
3335
|
+
live: true
|
|
3351
3336
|
};
|
|
3352
3337
|
this.scopes.set(id, record);
|
|
3338
|
+
ctx.effect(() => () => {
|
|
3339
|
+
this.retireScope(id, record, false);
|
|
3340
|
+
}, "session-controller: exact generation");
|
|
3353
3341
|
return record;
|
|
3354
3342
|
}
|
|
3355
|
-
/** The one aliveness predicate shared by scope mint and prune: host-listed or currently addressed. */
|
|
3356
|
-
eligible(id) {
|
|
3357
|
-
const { ids, current } = this.list.getSnapshot();
|
|
3358
|
-
return current === id || ids.includes(id);
|
|
3359
|
-
}
|
|
3360
3343
|
/** Project the manager's list snapshot into the store (title derivation is display-only). */
|
|
3361
3344
|
projectList() {
|
|
3362
|
-
const
|
|
3345
|
+
const previousById = this.list.getSnapshot().byId;
|
|
3346
|
+
const { items, phase, subagentsByParent, jobsBySession } = this.manager.getListSnapshot();
|
|
3363
3347
|
const ids = [];
|
|
3364
3348
|
const byId = {};
|
|
3365
3349
|
for (const entry of items) {
|
|
@@ -3368,7 +3352,7 @@ window.__ModuleLoader__.load({
|
|
|
3368
3352
|
id: entry.sessionId,
|
|
3369
3353
|
displayTitle: displayTitleOf(entry.title, entry.cwd, entry.sessionId),
|
|
3370
3354
|
running: entry.running,
|
|
3371
|
-
|
|
3355
|
+
retainedBy: this.retentionSnapshot(entry.sessionId).retainedBy,
|
|
3372
3356
|
blank: entry.blank,
|
|
3373
3357
|
updatedAt: entry.updatedAt,
|
|
3374
3358
|
...entry.projectionValues === void 0 ? {} : { projectionValues: entry.projectionValues },
|
|
@@ -3378,68 +3362,62 @@ window.__ModuleLoader__.load({
|
|
|
3378
3362
|
...entry.origin !== void 0 ? { origin: entry.origin } : {}
|
|
3379
3363
|
};
|
|
3380
3364
|
}
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3365
|
+
for (const [parentId, catalog] of Object.entries(subagentsByParent)) for (const child of catalog.entries) {
|
|
3366
|
+
if (child.kind !== "child") continue;
|
|
3367
|
+
const childId = child.id;
|
|
3368
|
+
const summary = byId[childId];
|
|
3369
|
+
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
|
3370
|
+
const projectedTitle = projectionValues?.title;
|
|
3371
|
+
const title = typeof projectedTitle === "string" && projectedTitle !== "" ? projectedTitle : void 0;
|
|
3372
|
+
const displayTitle = title ?? child.label ?? childId;
|
|
3373
|
+
if (summary === void 0) byId[childId] = {
|
|
3374
|
+
id: childId,
|
|
3375
|
+
displayTitle,
|
|
3376
|
+
parentId,
|
|
3377
|
+
origin: "subagent",
|
|
3378
|
+
running: child.activity === "running",
|
|
3379
|
+
blank: false,
|
|
3380
|
+
updatedAt: 0,
|
|
3381
|
+
retainedBy: this.retentionSnapshot(childId).retainedBy,
|
|
3382
|
+
...projectionValues === void 0 ? {} : { projectionValues },
|
|
3383
|
+
...title === void 0 ? {} : { title }
|
|
3384
|
+
};
|
|
3385
|
+
else if (summary.displayTitle !== displayTitle || summary.projectionValues !== projectionValues) byId[childId] = {
|
|
3386
|
+
...summary,
|
|
3387
|
+
displayTitle,
|
|
3388
|
+
...projectionValues === void 0 ? {} : { projectionValues }
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
for (const [id, record] of this.scopes) {
|
|
3392
|
+
if (byId[id] !== void 0) continue;
|
|
3393
|
+
const previous = previousById[id];
|
|
3394
|
+
const snapshot = record.session.getSnapshot();
|
|
3395
|
+
const address = this.manager.subagentAddress(id);
|
|
3396
|
+
byId[id] = {
|
|
3397
|
+
...previous ?? {
|
|
3398
|
+
id,
|
|
3399
|
+
displayTitle: id,
|
|
3398
3400
|
updatedAt: 0
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
}
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
}
|
|
3401
|
+
},
|
|
3402
|
+
running: snapshot.running,
|
|
3403
|
+
retainedBy: record.retention.retainedBy,
|
|
3404
|
+
blank: snapshot.blank,
|
|
3405
|
+
...address === void 0 ? {} : {
|
|
3406
|
+
parentId: address.parentSessionId,
|
|
3407
|
+
origin: "subagent"
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
3408
3410
|
}
|
|
3409
|
-
const persisted = this.selection.getSnapshot().sessionId;
|
|
3410
|
-
if (current === void 0) {
|
|
3411
|
-
if (persisted !== void 0) this.selection.set({});
|
|
3412
|
-
} 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({
|
|
3413
|
-
sessionId: current,
|
|
3414
|
-
...currentAddress === void 0 ? {} : { subagentAddress: currentAddress }
|
|
3415
|
-
});
|
|
3416
3411
|
this.list.set({
|
|
3417
3412
|
ids,
|
|
3418
3413
|
byId,
|
|
3419
|
-
current,
|
|
3420
3414
|
phase,
|
|
3421
3415
|
subagentsByParent,
|
|
3422
|
-
jobsBySession
|
|
3423
|
-
currentAddress
|
|
3416
|
+
jobsBySession
|
|
3424
3417
|
});
|
|
3425
|
-
this.pruneScopes();
|
|
3426
|
-
}
|
|
3427
|
-
/** Tear down scope + instance for no-longer-eligible sessions off stage; the staged one defers until the stage moves. */
|
|
3428
|
-
pruneScopes() {
|
|
3429
|
-
if (this.list.getSnapshot().phase === "pending") return;
|
|
3430
|
-
for (const [id, record] of this.scopes) {
|
|
3431
|
-
if (this.eligible(id)) continue;
|
|
3432
|
-
if (id === this.watched) {
|
|
3433
|
-
this.deferredRemovals.add(id);
|
|
3434
|
-
continue;
|
|
3435
|
-
}
|
|
3436
|
-
this.scopes.delete(id);
|
|
3437
|
-
this.deferredRemovals.delete(id);
|
|
3438
|
-
this.startScopeDrop(id, record);
|
|
3439
|
-
}
|
|
3440
3418
|
}
|
|
3441
|
-
startScopeDrop(id, record) {
|
|
3442
|
-
const drop = this.dropScope(
|
|
3419
|
+
startScopeDrop(id, record, disposeFiber = true, sessionDisposal = this.manager.drop(id, record.session)) {
|
|
3420
|
+
const drop = this.dropScope(record, disposeFiber, sessionDisposal);
|
|
3443
3421
|
this.scopeDrops.add(drop);
|
|
3444
3422
|
drop.then(() => {
|
|
3445
3423
|
this.scopeDrops.delete(drop);
|
|
@@ -3450,38 +3428,9 @@ window.__ModuleLoader__.load({
|
|
|
3450
3428
|
async drainScopeDrops() {
|
|
3451
3429
|
while (this.scopeDrops.size > 0) await Promise.allSettled([...this.scopeDrops]);
|
|
3452
3430
|
}
|
|
3453
|
-
/**
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
* controller, popup, plugin stores, listeners), the session-keyed slot
|
|
3457
|
-
* registrations and the Session instance itself — the host session log is the
|
|
3458
|
-
* durable truth, a reopen lazily rebuilds and backfills via open().
|
|
3459
|
-
*/
|
|
3460
|
-
async dropScope(id, record) {
|
|
3461
|
-
record.session.unbindScope();
|
|
3462
|
-
await Promise.allSettled([record.fiber.dispose(), this.manager.drop(id)]);
|
|
3463
|
-
}
|
|
3464
|
-
/** Run deferred teardowns whose session is no longer staged (called when the stage moves). */
|
|
3465
|
-
sweepDeferred() {
|
|
3466
|
-
for (const id of [...this.deferredRemovals]) {
|
|
3467
|
-
/* v8 ignore next -- defensive: only the staged id ever defers, and every
|
|
3468
|
-
* stage move sweeps first, so the set cannot contain the id the stage just
|
|
3469
|
-
* moved to; kept as a guard against future extra sweep call sites. */
|
|
3470
|
-
if (id === this.watched) continue;
|
|
3471
|
-
if (this.eligible(id)) {
|
|
3472
|
-
this.deferredRemovals.delete(id);
|
|
3473
|
-
continue;
|
|
3474
|
-
}
|
|
3475
|
-
const record = this.scopes.get(id);
|
|
3476
|
-
this.deferredRemovals.delete(id);
|
|
3477
|
-
/* v8 ignore next -- defensive: prune deletes a scope and its deferral
|
|
3478
|
-
* together, so a deferred id always still owns its record; kept so a
|
|
3479
|
-
* future teardown path cannot double-dispose. */
|
|
3480
|
-
if (record !== void 0) {
|
|
3481
|
-
this.scopes.delete(id);
|
|
3482
|
-
this.startScopeDrop(id, record);
|
|
3483
|
-
}
|
|
3484
|
-
}
|
|
3431
|
+
/** Await the already-withdrawn Session and scoped cleanup to quiescence. */
|
|
3432
|
+
async dropScope(record, disposeFiber, sessionDisposal) {
|
|
3433
|
+
await Promise.allSettled([sessionDisposal, ...disposeFiber ? [record.fiber.dispose()] : []]);
|
|
3485
3434
|
}
|
|
3486
3435
|
};
|
|
3487
3436
|
//#endregion
|
|
@@ -3503,6 +3452,7 @@ window.__ModuleLoader__.load({
|
|
|
3503
3452
|
*/
|
|
3504
3453
|
function apply(ctx) {
|
|
3505
3454
|
const remotes = ctx.remote;
|
|
3455
|
+
const connection = ctx.get("connection");
|
|
3506
3456
|
const sessions = new ClientSessions(ctx, remotes);
|
|
3507
3457
|
ctx.remote.$on("api-session/added", (summary) => {
|
|
3508
3458
|
sessions.handleSessionAdded(summary);
|
|
@@ -3527,14 +3477,22 @@ window.__ModuleLoader__.load({
|
|
|
3527
3477
|
console.error("[session-controller] control stream failed:", error);
|
|
3528
3478
|
}
|
|
3529
3479
|
});
|
|
3530
|
-
|
|
3531
|
-
|
|
3480
|
+
const connected = () => {
|
|
3481
|
+
if (connection.generation.getSnapshot() === void 0) return;
|
|
3532
3482
|
sessions.handleConnected();
|
|
3533
|
-
|
|
3534
|
-
|
|
3483
|
+
control.restart();
|
|
3484
|
+
control.start();
|
|
3485
|
+
};
|
|
3486
|
+
ctx.effect(() => connection.generation.subscribe(connected), "session-controller.client.generation");
|
|
3487
|
+
connected();
|
|
3535
3488
|
ctx.typert.contexts.registerClient("agent", {
|
|
3536
|
-
identity: (candidate) => sessions.
|
|
3537
|
-
resolve: (sessionId) =>
|
|
3489
|
+
identity: (candidate) => sessions.sessionOf(candidate)?.sessionId,
|
|
3490
|
+
resolve: (sessionId) => {
|
|
3491
|
+
const reference = sessions.retainAgentScope(sessionId);
|
|
3492
|
+
return typertOwnedValue(reference.binding.ctx, () => {
|
|
3493
|
+
reference.release();
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3538
3496
|
});
|
|
3539
3497
|
ctx.effect(() => async () => {
|
|
3540
3498
|
await control.dispose();
|