@deepseek-ai/dsh-api-session-controller 0.1.6-alpha.2 → 0.1.7-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +31 -8
- package/README.zh.md +31 -8
- package/lib/client.js +603 -515
- package/lib/index.js +299 -137
- package/lib/typert.host.js +373 -183
- package/lib/typert.remote-client.d.ts +10 -5
- package/lib/typert.remote-client.js +216 -124
- 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 +11 -16
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/sessions/lineage.d.ts +4 -1
- package/lib/types/client/sessions/lineage.js +2 -1
- package/lib/types/client/sessions/manager.d.ts +66 -59
- package/lib/types/client/sessions/manager.js +249 -277
- package/lib/types/client/sessions/projection-store.d.ts +32 -13
- package/lib/types/client/sessions/projection-store.js +45 -13
- package/lib/types/client/sessions/remotes.d.ts +1 -2
- package/lib/types/client/sessions/service.d.ts +23 -35
- package/lib/types/client/sessions/service.js +35 -47
- package/lib/types/client/sessions/session.d.ts +18 -16
- package/lib/types/client/sessions/session.js +112 -50
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.js +72 -36
- package/lib/types/control.d.ts +2 -4
- package/lib/types/control.js +2 -44
- package/lib/types/history.js +1 -1
- package/lib/types/index.d.ts +32 -5
- package/lib/types/index.js +117 -13
- package/lib/types/list.js +24 -15
- package/lib/types/skill-catalog.js +60 -50
- package/lib/types/types.d.ts +37 -23
- package/lib/types/types.js +0 -1
- package/package.json +71 -76
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generic per-session projection value store (push model; see the
|
|
3
3
|
* session-projection subsystem page, docs/subsystems/session-projection.md):
|
|
4
|
-
* the host is the only computation site; the client holds finished
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* the host is the only computation site; the client holds finished whole
|
|
5
|
+
* values per key in one of two row kinds. A `sequenced` row
|
|
6
|
+
* (`{ value, seq }`) comes from the connected Host — a follow opening
|
|
7
|
+
* baseline, a Session Controller `projection` frame, a list block the Host
|
|
8
|
+
* computed for an attached Session — and merges under **higher seq wins**
|
|
9
|
+
* against other sequenced rows. A `cached` row (`{ value }`) comes from the
|
|
10
|
+
* session list's view of the persisted checkpoint, carries no comparable
|
|
11
|
+
* seq, and yields to every sequenced write. No client-side domain folding
|
|
8
12
|
* exists: a domain ships projection support with zero client code. Per-key
|
|
9
13
|
* bare observable faces feed `useProjection` (ui-renderer binds them).
|
|
10
14
|
*/
|
|
@@ -38,10 +42,14 @@ export interface ProjectionsBaseline {
|
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* One session's projection values. Framework semantics, uniform across every
|
|
41
|
-
* key
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
+
* key. Sequenced writes (a baseline seeds rows at its cut, a push frame
|
|
46
|
+
* updates one row) compare seqs among themselves: a lower-or-equal seq within
|
|
47
|
+
* the Host generation loses, so a replayed frame cannot regress a value and a
|
|
48
|
+
* stale baseline cannot overwrite a newer frame. Cached writes (the session
|
|
49
|
+
* list's zero-I/O block) only fill keys no sequenced row holds, and a baseline
|
|
50
|
+
* discards every cached row before it seeds, regardless of seq: the connected
|
|
51
|
+
* Session is the truth and a cached value never outranks it. A key the store
|
|
52
|
+
* has never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
45
53
|
* per key (create-on-demand, cached) so the React side binds each exactly
|
|
46
54
|
* once; the store-level channel (`subscribeAny`) serves coarse consumers (the
|
|
47
55
|
* manager's list projection reads the `title` key).
|
|
@@ -87,11 +95,22 @@ export declare class ProjectionValueStore {
|
|
|
87
95
|
*/
|
|
88
96
|
apply(key: string, value: unknown, seq: SessionSeqCursor): void;
|
|
89
97
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
98
|
+
* Fill keys from a session-list block the Host labeled `cached`: a zero-I/O
|
|
99
|
+
* view of the persisted checkpoint. A cached value lands only where no
|
|
100
|
+
* sequenced row exists: a connected Session has already answered for such
|
|
101
|
+
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
102
|
+
* than it.
|
|
103
|
+
* @param values - whole values by key viewed from the persisted checkpoint.
|
|
104
|
+
*/
|
|
105
|
+
applyCached(values: Readonly<Record<string, unknown>>): void;
|
|
106
|
+
/**
|
|
107
|
+
* Seed from a history tail page's projections block. Every cached row is
|
|
108
|
+
* discarded first, regardless of seq: the block comes from the connected
|
|
109
|
+
* Session, and a value viewed from the persisted checkpoint never outranks
|
|
110
|
+
* it. Then every carried key lands under the same seq rule as frames, and a
|
|
111
|
+
* key the block omits is capability-absent as of the cut — its row clears
|
|
112
|
+
* unless a newer frame already superseded the cut (a stale baseline can
|
|
113
|
+
* neither overwrite nor clear newer sequenced values).
|
|
95
114
|
* @param baseline - the response's projections block.
|
|
96
115
|
*/
|
|
97
116
|
seed(baseline: ProjectionsBaseline): void;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Notifier } from "./notifier.js";
|
|
2
2
|
/**
|
|
3
3
|
* One session's projection values. Framework semantics, uniform across every
|
|
4
|
-
* key
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* key. Sequenced writes (a baseline seeds rows at its cut, a push frame
|
|
5
|
+
* updates one row) compare seqs among themselves: a lower-or-equal seq within
|
|
6
|
+
* the Host generation loses, so a replayed frame cannot regress a value and a
|
|
7
|
+
* stale baseline cannot overwrite a newer frame. Cached writes (the session
|
|
8
|
+
* list's zero-I/O block) only fill keys no sequenced row holds, and a baseline
|
|
9
|
+
* discards every cached row before it seeds, regardless of seq: the connected
|
|
10
|
+
* Session is the truth and a cached value never outranks it. A key the store
|
|
11
|
+
* has never seen reads `undefined` (capability absent). Faces are identity-stable
|
|
8
12
|
* per key (create-on-demand, cached) so the React side binds each exactly
|
|
9
13
|
* once; the store-level channel (`subscribeAny`) serves coarse consumers (the
|
|
10
14
|
* manager's list projection reads the `title` key).
|
|
@@ -61,20 +65,46 @@ export class ProjectionValueStore {
|
|
|
61
65
|
*/
|
|
62
66
|
apply(key, value, seq) {
|
|
63
67
|
const row = this.rows.get(key);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
// higher seq wins among sequenced rows; replays and stale frames drop. A
|
|
69
|
+
// cached row has no comparable seq and always yields.
|
|
70
|
+
if (row?.kind === 'sequenced' && seq <= row.seq)
|
|
71
|
+
return;
|
|
72
|
+
this.rows.set(key, { kind: 'sequenced', value, seq });
|
|
67
73
|
this.changed(key);
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
76
|
+
* Fill keys from a session-list block the Host labeled `cached`: a zero-I/O
|
|
77
|
+
* view of the persisted checkpoint. A cached value lands only where no
|
|
78
|
+
* sequenced row exists: a connected Session has already answered for such
|
|
79
|
+
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
80
|
+
* than it.
|
|
81
|
+
* @param values - whole values by key viewed from the persisted checkpoint.
|
|
82
|
+
*/
|
|
83
|
+
applyCached(values) {
|
|
84
|
+
for (const key of Object.keys(values)) {
|
|
85
|
+
if (this.rows.get(key)?.kind === 'sequenced')
|
|
86
|
+
continue;
|
|
87
|
+
this.rows.set(key, { kind: 'cached', value: values[key] });
|
|
88
|
+
this.changed(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Seed from a history tail page's projections block. Every cached row is
|
|
93
|
+
* discarded first, regardless of seq: the block comes from the connected
|
|
94
|
+
* Session, and a value viewed from the persisted checkpoint never outranks
|
|
95
|
+
* it. Then every carried key lands under the same seq rule as frames, and a
|
|
96
|
+
* key the block omits is capability-absent as of the cut — its row clears
|
|
97
|
+
* unless a newer frame already superseded the cut (a stale baseline can
|
|
98
|
+
* neither overwrite nor clear newer sequenced values).
|
|
75
99
|
* @param baseline - the response's projections block.
|
|
76
100
|
*/
|
|
77
101
|
seed(baseline) {
|
|
102
|
+
for (const [key, row] of this.rows) {
|
|
103
|
+
if (row.kind !== 'cached')
|
|
104
|
+
continue;
|
|
105
|
+
this.rows.delete(key);
|
|
106
|
+
this.changed(key);
|
|
107
|
+
}
|
|
78
108
|
// Erased walk: the framework crosses the open key space; per-key typing
|
|
79
109
|
// is re-established at the consumer (useProjection's map lookup).
|
|
80
110
|
const values = baseline.values;
|
|
@@ -83,7 +113,9 @@ export class ProjectionValueStore {
|
|
|
83
113
|
for (const [key, row] of this.rows) {
|
|
84
114
|
if (Object.hasOwn(values, key))
|
|
85
115
|
continue;
|
|
86
|
-
|
|
116
|
+
// Every cached row was deleted above; the kind test only narrows the
|
|
117
|
+
// type so `row.seq` is readable.
|
|
118
|
+
if (row.kind === 'sequenced' && row.seq > baseline.asOfSeq)
|
|
87
119
|
continue;
|
|
88
120
|
this.rows.delete(key);
|
|
89
121
|
this.changed(key);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client';
|
|
8
8
|
import type { CommandSubmitAttachment } from '@deepseek-ai/dsh-commands/types';
|
|
9
9
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
10
|
-
import type {
|
|
10
|
+
import type { SubagentInterruptReceipt, SubagentPromptReceipt, SubagentPromptRequest } from '@deepseek-ai/dsh-subagent/client';
|
|
11
11
|
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
12
12
|
import type { SessionRemote } from '../transport.ts';
|
|
13
13
|
/** Narrow Commands namespace consumed by a Client Session. */
|
|
@@ -16,7 +16,6 @@ export interface SessionCommandsRemote {
|
|
|
16
16
|
}
|
|
17
17
|
/** Narrow subagent namespace consumed by a Client Session and its manager. */
|
|
18
18
|
export interface SessionSubagentsRemote {
|
|
19
|
-
list(parentSessionId: SessionId, signal?: AbortSignal): Promise<RemoteResult<SubagentCatalog>>;
|
|
20
19
|
prompt(request: SubagentPromptRequest, signal?: AbortSignal): Promise<RemoteResult<SubagentPromptReceipt>>;
|
|
21
20
|
interruptByParent(childSessionId: SessionId, parentSessionId: SessionId, mode: 'continuable'): Promise<RemoteResult<SubagentInterruptReceipt>>;
|
|
22
21
|
}
|
|
@@ -3,7 +3,6 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
3
3
|
import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client';
|
|
4
4
|
import { type SessionId } from '@deepseek-ai/dsh-session/types';
|
|
5
5
|
import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
|
|
6
|
-
import type { SessionJob as JobView } from '../../types.ts';
|
|
7
6
|
import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types';
|
|
8
7
|
import { type ObservableSnapshot, type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
9
8
|
import type { RemoteFailure, RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
@@ -12,7 +11,7 @@ import type { SessionFace } from '../contract/session.ts';
|
|
|
12
11
|
import type { AgentContext, ISessions, SessionReference, SessionRetainInfo, SessionRetainOptions, SessionTarget } from '../contract/sessions.ts';
|
|
13
12
|
import { SessionManager } from './manager.ts';
|
|
14
13
|
import type { SessionRemotes } from './remotes.ts';
|
|
15
|
-
import type { SessionListPhase, SessionSearchResultItem,
|
|
14
|
+
import type { SessionListPhase, SessionSearchResultItem, SessionProjectionSnapshot } from './manager.ts';
|
|
16
15
|
/** Session list row projected from the host list RPC plus live stream increments. */
|
|
17
16
|
export interface SessionSummary {
|
|
18
17
|
id: SessionId;
|
|
@@ -24,14 +23,16 @@ export interface SessionSummary {
|
|
|
24
23
|
parentId?: SessionId;
|
|
25
24
|
/** Coarse durable origin for navigation filtering; not a continuation capability. */
|
|
26
25
|
origin?: 'subagent';
|
|
26
|
+
/** Host running state for `ids` members; a display fallback for other rows. */
|
|
27
27
|
running: boolean;
|
|
28
28
|
/** Local ownership counts; Host metadata refreshes cannot overwrite them. */
|
|
29
29
|
readonly retainedBy: SessionRetainInfo['retainedBy'];
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* New Session presentation and reuse eligibility, derived from the Host
|
|
32
|
+
* summary, `sessionListMetadata`, and client acceptance/running observations.
|
|
33
|
+
* New Session reuses a blank one targeting the same workspace. Filtering
|
|
34
|
+
* stays with the consumer: the store carries every row, while the Workspace
|
|
35
|
+
* browser shows only the selected blank entry.
|
|
35
36
|
*/
|
|
36
37
|
blank: boolean;
|
|
37
38
|
updatedAt: number;
|
|
@@ -40,20 +41,14 @@ export interface SessionSummary {
|
|
|
40
41
|
}
|
|
41
42
|
/** Catalog metadata and local source counts; catalog membership owns no Client generation. */
|
|
42
43
|
export interface SessionListState {
|
|
43
|
-
/** Host
|
|
44
|
+
/** Host list order; every id has a matching byId row in the same snapshot. */
|
|
44
45
|
ids: SessionId[];
|
|
45
|
-
/** Host/catalog rows plus
|
|
46
|
+
/** Host/catalog rows plus retained subagent fallbacks; only `ids` expresses Host-list membership. */
|
|
46
47
|
byId: Record<SessionId, SessionSummary>;
|
|
47
48
|
/** Arrival lifecycle projected 1:1 from the manager snapshot (see SessionListPhase): empty-with-ready means "truly no sessions". */
|
|
48
49
|
phase: SessionListPhase;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Background jobs each session can see, mirrored last-wins from Session
|
|
53
|
-
* Controller's control baseline and `jobs` frames. A missing key is an empty
|
|
54
|
-
* set, so consumers read absence rather than a sentinel.
|
|
55
|
-
*/
|
|
56
|
-
jobsBySession: Readonly<Record<SessionId, readonly JobView[]>>;
|
|
50
|
+
/** Shared projection values and explicit-read state, including unopened Sessions. */
|
|
51
|
+
projectionsBySession: Readonly<Record<SessionId, SessionProjectionSnapshot>>;
|
|
57
52
|
}
|
|
58
53
|
/** Structured session-create failure. */
|
|
59
54
|
export declare class SessionCreateError extends Error {
|
|
@@ -118,20 +113,14 @@ export declare class ClientSessions implements ISessions {
|
|
|
118
113
|
* Resolve an already discovered direct-parent address without opening it.
|
|
119
114
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
120
115
|
* @param id - possible addressed child id.
|
|
121
|
-
* @returns
|
|
116
|
+
* @returns A retained or loaded-catalog address, without retaining a new selection or scope.
|
|
122
117
|
*/
|
|
123
118
|
subagentAddress(id: SessionId): SubagentAddress | undefined;
|
|
124
119
|
/**
|
|
125
|
-
*
|
|
126
|
-
* @param
|
|
127
|
-
* @param open - menu state.
|
|
128
|
-
*/
|
|
129
|
-
setSubagentCatalogOpen(parentSessionId: SessionId, open: boolean): void;
|
|
130
|
-
/**
|
|
131
|
-
* Refresh one direct-child catalog.
|
|
132
|
-
* @param parentSessionId - catalog owner.
|
|
120
|
+
* Load all Session projections once per connection; retry an unsuccessful initial read.
|
|
121
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
133
122
|
*/
|
|
134
|
-
|
|
123
|
+
refreshProjections(sessionId: SessionId): Promise<void>;
|
|
135
124
|
/**
|
|
136
125
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
137
126
|
* @returns completion of the current or newly started baseline pull.
|
|
@@ -193,15 +182,14 @@ export declare class ClientSessions implements ISessions {
|
|
|
193
182
|
sessionId?: SessionId;
|
|
194
183
|
}): Promise<SessionId>;
|
|
195
184
|
/**
|
|
196
|
-
* Fork a
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* wire takes integers only.
|
|
185
|
+
* Fork a session from an exact inclusive prefix of the source (same
|
|
186
|
+
* synchronous-addressability guarantee as {@link ClientSessions.create}:
|
|
187
|
+
* on resolution the child is catalogued and may be explicitly retained).
|
|
188
|
+
* @param opts - source session id, the optional exact inclusive boundary
|
|
189
|
+
* seq (a real event seq the caller already knows; a cut inside an open
|
|
190
|
+
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
191
|
+
* the latest completed-turn prefix), and whether to increment an
|
|
192
|
+
* inherited durable title before resolving.
|
|
205
193
|
* @returns the child session id.
|
|
206
194
|
* @throws {SessionForkError} with the source id.
|
|
207
195
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -163,7 +163,7 @@ export class ClientSessions {
|
|
|
163
163
|
this.rootCtx = rootCtx;
|
|
164
164
|
this.manager = new SessionManager(remote);
|
|
165
165
|
this.list = createSnapshotStore({
|
|
166
|
-
ids: [], byId: {}, phase: 'pending',
|
|
166
|
+
ids: [], byId: {}, phase: 'pending', projectionsBySession: {},
|
|
167
167
|
});
|
|
168
168
|
const disposeManagerProjection = this.manager.subscribe(() => { this.projectList(); });
|
|
169
169
|
rootCtx.effect(() => async () => {
|
|
@@ -234,25 +234,17 @@ export class ClientSessions {
|
|
|
234
234
|
* Resolve an already discovered direct-parent address without opening it.
|
|
235
235
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
236
236
|
* @param id - possible addressed child id.
|
|
237
|
-
* @returns
|
|
237
|
+
* @returns A retained or loaded-catalog address, without retaining a new selection or scope.
|
|
238
238
|
*/
|
|
239
239
|
subagentAddress(id) {
|
|
240
240
|
return this.manager.subagentAddress(id);
|
|
241
241
|
}
|
|
242
242
|
/**
|
|
243
|
-
*
|
|
244
|
-
* @param
|
|
245
|
-
* @param open - menu state.
|
|
243
|
+
* Load all Session projections once per connection; retry an unsuccessful initial read.
|
|
244
|
+
* @param sessionId - Session to inspect without opening its conversation.
|
|
246
245
|
*/
|
|
247
|
-
|
|
248
|
-
this.manager.
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Refresh one direct-child catalog.
|
|
252
|
-
* @param parentSessionId - catalog owner.
|
|
253
|
-
*/
|
|
254
|
-
refreshSubagents(parentSessionId) {
|
|
255
|
-
return this.manager.refreshSubagents(parentSessionId);
|
|
246
|
+
refreshProjections(sessionId) {
|
|
247
|
+
return this.manager.refreshProjections(sessionId);
|
|
256
248
|
}
|
|
257
249
|
/**
|
|
258
250
|
* Refresh the real Session baseline, reusing an in-flight pull.
|
|
@@ -332,15 +324,14 @@ export class ClientSessions {
|
|
|
332
324
|
return result.value.sessionId;
|
|
333
325
|
}
|
|
334
326
|
/**
|
|
335
|
-
* Fork a
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
* wire takes integers only.
|
|
327
|
+
* Fork a session from an exact inclusive prefix of the source (same
|
|
328
|
+
* synchronous-addressability guarantee as {@link ClientSessions.create}:
|
|
329
|
+
* on resolution the child is catalogued and may be explicitly retained).
|
|
330
|
+
* @param opts - source session id, the optional exact inclusive boundary
|
|
331
|
+
* seq (a real event seq the caller already knows; a cut inside an open
|
|
332
|
+
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
333
|
+
* the latest completed-turn prefix), and whether to increment an
|
|
334
|
+
* inherited durable title before resolving.
|
|
344
335
|
* @returns the child session id.
|
|
345
336
|
* @throws {SessionForkError} with the source id.
|
|
346
337
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -351,26 +342,16 @@ export class ClientSessions {
|
|
|
351
342
|
: undefined;
|
|
352
343
|
const result = await this.manager.fork({
|
|
353
344
|
sessionId: opts.sessionId,
|
|
354
|
-
|
|
355
|
-
// turn/start), so the host's first-turn/end-at-or-after cut still ends
|
|
356
|
-
// on that turn — never clipped back to the previous one.
|
|
357
|
-
...(opts.atSeq === undefined ? {} : { atSeq: SessionSeq(Math.floor(opts.atSeq)) }),
|
|
345
|
+
...(opts.atSeq === undefined ? {} : { atSeq: SessionSeq(opts.atSeq) }),
|
|
358
346
|
});
|
|
359
347
|
if (!result.ok)
|
|
360
348
|
throw new SessionForkError(result.error, opts.sessionId);
|
|
361
349
|
this.projectList();
|
|
362
350
|
const childId = result.value.sessionId;
|
|
363
351
|
if (sourceTitle !== undefined) {
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const renamed = await reference.binding.session.rename(increasedForkTitle(sourceTitle));
|
|
368
|
-
if (!renamed.ok)
|
|
369
|
-
throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
reference.release();
|
|
373
|
-
}
|
|
352
|
+
const renamed = await this.manager.rename(childId, increasedForkTitle(sourceTitle));
|
|
353
|
+
if (!renamed.ok)
|
|
354
|
+
throw new Error(`fork child rename failed: ${renamed.error.code}: ${renamed.error.message}`);
|
|
374
355
|
}
|
|
375
356
|
return childId;
|
|
376
357
|
}
|
|
@@ -450,8 +431,9 @@ export class ClientSessions {
|
|
|
450
431
|
else
|
|
451
432
|
this.publishRetention(id);
|
|
452
433
|
});
|
|
453
|
-
if (this.list.getSnapshot().byId[id] === undefined)
|
|
434
|
+
if (this.list.getSnapshot().byId[id] === undefined && this.manager.subagentAddress(id) !== undefined) {
|
|
454
435
|
this.projectList();
|
|
436
|
+
}
|
|
455
437
|
this.publishRetention(id);
|
|
456
438
|
return reference;
|
|
457
439
|
}
|
|
@@ -507,7 +489,7 @@ export class ClientSessions {
|
|
|
507
489
|
/** Project the manager's list snapshot into the store (title derivation is display-only). */
|
|
508
490
|
projectList() {
|
|
509
491
|
const previousById = this.list.getSnapshot().byId;
|
|
510
|
-
const { items, phase,
|
|
492
|
+
const { items, phase, projectionsBySession, } = this.manager.getListSnapshot();
|
|
511
493
|
const ids = [];
|
|
512
494
|
const byId = {};
|
|
513
495
|
for (const entry of items) {
|
|
@@ -528,10 +510,8 @@ export class ClientSessions {
|
|
|
528
510
|
...(entry.origin !== undefined ? { origin: entry.origin } : {}),
|
|
529
511
|
};
|
|
530
512
|
}
|
|
531
|
-
for (const [parentId,
|
|
532
|
-
for (const child of
|
|
533
|
-
if (child.kind !== 'child')
|
|
534
|
-
continue;
|
|
513
|
+
for (const [parentId, projection] of Object.entries(projectionsBySession)) {
|
|
514
|
+
for (const child of projection.values.subagentCatalog ?? []) {
|
|
535
515
|
const childId = child.id;
|
|
536
516
|
const summary = byId[childId];
|
|
537
517
|
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
|
@@ -541,7 +521,7 @@ export class ClientSessions {
|
|
|
541
521
|
if (summary === undefined) {
|
|
542
522
|
byId[childId] = {
|
|
543
523
|
id: childId, displayTitle, parentId: parentId,
|
|
544
|
-
origin: 'subagent', running:
|
|
524
|
+
origin: 'subagent', running: this.scopes.get(childId)?.session.getSnapshot().running ?? false, blank: false, updatedAt: 0,
|
|
545
525
|
retainedBy: this.retentionSnapshot(childId).retainedBy,
|
|
546
526
|
...(projectionValues === undefined ? {} : { projectionValues }),
|
|
547
527
|
...(title === undefined ? {} : { title }),
|
|
@@ -559,18 +539,26 @@ export class ClientSessions {
|
|
|
559
539
|
for (const [id, record] of this.scopes) {
|
|
560
540
|
if (byId[id] !== undefined)
|
|
561
541
|
continue;
|
|
542
|
+
const address = this.manager.subagentAddress(id);
|
|
543
|
+
if (address === undefined)
|
|
544
|
+
continue;
|
|
562
545
|
const previous = previousById[id];
|
|
563
546
|
const snapshot = record.session.getSnapshot();
|
|
564
|
-
const
|
|
547
|
+
const projectionValues = this.manager.projectionValues(id);
|
|
548
|
+
const projectedTitle = projectionValues?.title;
|
|
549
|
+
const title = typeof projectedTitle === 'string' && projectedTitle !== '' ? projectedTitle : previous?.title;
|
|
565
550
|
byId[id] = {
|
|
566
551
|
...(previous ?? { id, displayTitle: id, updatedAt: 0 }),
|
|
567
552
|
running: snapshot.running,
|
|
568
553
|
retainedBy: record.retention.retainedBy,
|
|
569
554
|
blank: snapshot.blank,
|
|
570
|
-
|
|
555
|
+
parentId: address.parentSessionId,
|
|
556
|
+
origin: 'subagent',
|
|
557
|
+
...(projectionValues === undefined ? {} : { projectionValues }),
|
|
558
|
+
...(title === undefined ? {} : { title, displayTitle: title }),
|
|
571
559
|
};
|
|
572
560
|
}
|
|
573
|
-
this.list.set({ ids, byId, phase,
|
|
561
|
+
this.list.set({ ids, byId, phase, projectionsBySession });
|
|
574
562
|
}
|
|
575
563
|
startScopeDrop(id, record, disposeFiber = true, sessionDisposal = this.manager.drop(id, record.session)) {
|
|
576
564
|
const drop = this.dropScope(record, disposeFiber, sessionDisposal);
|
|
@@ -18,15 +18,12 @@ export declare const JUMP_PAGE_MESSAGES = 200;
|
|
|
18
18
|
export interface SessionOptions {
|
|
19
19
|
/** Catalog-discovered address selecting non-activating subagent transport. */
|
|
20
20
|
address?: SubagentAddress;
|
|
21
|
-
/** Whether the exact direct parent Agent
|
|
21
|
+
/** Whether the exact direct parent Agent is available in Host summaries; absent until known. */
|
|
22
22
|
parentAvailable?: boolean;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* frame. Acceptance is the flip point because it proves the user message
|
|
28
|
-
* is in the host log; a rejected first prompt keeps the session blank
|
|
29
|
-
* (hidden, still reusable by connectWorkspace).
|
|
24
|
+
* Publish each accepted prompt to the Manager, including after this Session
|
|
25
|
+
* object is replaced. Acceptance converts display state, but does not
|
|
26
|
+
* establish that a turn started or reached durable history.
|
|
30
27
|
*/
|
|
31
28
|
onEngaged?(session: Session): void;
|
|
32
29
|
/**
|
|
@@ -59,6 +56,7 @@ export declare class Session implements SessionFace {
|
|
|
59
56
|
private jumpTargetSeq;
|
|
60
57
|
/** The running jump loop's completion, shared by retargeting callers. */
|
|
61
58
|
private jumpPromise;
|
|
59
|
+
private pendingHistory;
|
|
62
60
|
private readonly stopObservingInbox;
|
|
63
61
|
private readonly assistantStream;
|
|
64
62
|
private running;
|
|
@@ -72,7 +70,7 @@ export declare class Session implements SessionFace {
|
|
|
72
70
|
private promptAttempted;
|
|
73
71
|
/** A first accepted prompt stays in the engaging phase until its turn is observable. */
|
|
74
72
|
private firstPromptPendingTurn;
|
|
75
|
-
/**
|
|
73
|
+
/** New Session display state; unknown bare sessions begin conservatively blank. */
|
|
76
74
|
private blankBit;
|
|
77
75
|
private removed;
|
|
78
76
|
private promptError;
|
|
@@ -88,8 +86,9 @@ export declare class Session implements SessionFace {
|
|
|
88
86
|
* Per-session projection value store (push model; see the session-projection
|
|
89
87
|
* subsystem page, docs/subsystems/session-projection.md): finished whole
|
|
90
88
|
* values computed on the Host, seeded by the tail page's
|
|
91
|
-
* projections block and updated by Session Controller control frames
|
|
92
|
-
*
|
|
89
|
+
* projections block and updated by Session Controller control frames;
|
|
90
|
+
* Host-sequenced writes merge under higher-seq-wins and cached list blocks
|
|
91
|
+
* yield to them (projection-store.ts). Keys are read via `projections.faceOf(key)`
|
|
93
92
|
* (the useProjection resolution face); the conversation snapshot never
|
|
94
93
|
* carries projection values, and no client-side domain folding exists.
|
|
95
94
|
* Manager-owned when constructed through SessionManager (frames route and
|
|
@@ -230,11 +229,12 @@ export declare class Session implements SessionFace {
|
|
|
230
229
|
*/
|
|
231
230
|
handleSubagentParentAvailable(available: boolean): void;
|
|
232
231
|
/**
|
|
233
|
-
*
|
|
234
|
-
* `
|
|
235
|
-
* running
|
|
236
|
-
*
|
|
237
|
-
*
|
|
232
|
+
* Apply the Manager's effective display blank, further reconciled with the
|
|
233
|
+
* current `sessionListMetadata` projection. Local send attempts and current
|
|
234
|
+
* running state prevent re-blanking; an earlier false summary alone does not.
|
|
235
|
+
* The Manager retains acceptance and earlier running observations across
|
|
236
|
+
* Session-object replacement.
|
|
237
|
+
* @param blank - New Session display state after Manager reconciliation.
|
|
238
238
|
*/
|
|
239
239
|
handleBlank(blank: boolean): void;
|
|
240
240
|
/** `api-session/removed` relay: flag the snapshot while retaining the resident instance. */
|
|
@@ -262,7 +262,9 @@ export declare class Session implements SessionFace {
|
|
|
262
262
|
private appendLive;
|
|
263
263
|
/** Observe durable acceptance even when insertion and claim share one projection notification. */
|
|
264
264
|
private observeSubmissionEvent;
|
|
265
|
-
|
|
265
|
+
private observeSteeringInsertions;
|
|
266
|
+
private observeSubmissionMessage;
|
|
267
|
+
/** Retire non-steering echoes when the Inbox accepts their queue occurrences. */
|
|
266
268
|
private observeSubmissionInbox;
|
|
267
269
|
/**
|
|
268
270
|
* Latch one observed settlement and remove the echo an animation frame
|