@deepseek-ai/dsh-api-session-controller 0.2.0-rc.2 → 0.2.1-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 +8 -8
- package/README.md +7 -3
- package/README.zh.md +7 -3
- package/lib/client.js +666 -20
- package/lib/index.js +76 -23
- package/lib/typert.host.js +376 -90
- package/lib/typert.remote-client.js +311 -69
- package/lib/types/client/contract/sessions.d.ts +4 -2
- package/lib/types/client/sessions/lineage.d.ts +2 -0
- package/lib/types/client/sessions/manager.d.ts +7 -5
- package/lib/types/client/sessions/manager.js +36 -11
- package/lib/types/client/sessions/projection-store.d.ts +3 -3
- package/lib/types/client/sessions/projection-store.js +1 -1
- package/lib/types/client/sessions/service.d.ts +8 -4
- package/lib/types/client/sessions/service.js +7 -3
- package/lib/types/index.d.ts +14 -7
- package/lib/types/index.js +45 -13
- package/lib/types/list.d.ts +7 -3
- package/lib/types/list.js +33 -10
- package/lib/types/skill-catalog.js +7 -6
- package/lib/types/types.d.ts +14 -2
- package/package.json +79 -76
|
@@ -85,11 +85,11 @@ export interface ISessions {
|
|
|
85
85
|
/**
|
|
86
86
|
* Resolve an already discovered direct-parent address without opening it.
|
|
87
87
|
* @param id - possible addressed child id.
|
|
88
|
-
* @returns a retained or
|
|
88
|
+
* @returns a retained, catalog-derived, or unknown-mode listed address, without retaining a new selection or scope.
|
|
89
89
|
*/
|
|
90
90
|
subagentAddress(id: SessionId): SubagentAddress | undefined;
|
|
91
91
|
/**
|
|
92
|
-
*
|
|
92
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
93
93
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
94
94
|
* @returns completion of the current or newly started refresh.
|
|
95
95
|
*/
|
|
@@ -126,6 +126,8 @@ export interface ISessions {
|
|
|
126
126
|
atSeq?: number;
|
|
127
127
|
increaseTitle?: boolean;
|
|
128
128
|
onCreated?: (childId: SessionId) => void;
|
|
129
|
+
/** False refuses source migration; omission preserves the Host default. */
|
|
130
|
+
allowMigration?: boolean;
|
|
129
131
|
}): Promise<SessionId>;
|
|
130
132
|
/**
|
|
131
133
|
* Borrow an already-retained Agent-scoped Context without extending its lifetime.
|
|
@@ -21,6 +21,8 @@ export interface SessionListEntry {
|
|
|
21
21
|
parentSessionId?: SessionId;
|
|
22
22
|
/** Coarse durable origin for navigation filtering; not a continuation capability. */
|
|
23
23
|
origin?: 'subagent';
|
|
24
|
+
/** Format classification supplied by the Host list. */
|
|
25
|
+
formatStatus?: SessionSummary['formatStatus'];
|
|
24
26
|
cwd?: string;
|
|
25
27
|
/** Current host-computed projection values for list consumers. */
|
|
26
28
|
projectionValues?: Readonly<Partial<SessionProjectionMap>>;
|
|
@@ -35,7 +35,7 @@ export interface SessionListSnapshot {
|
|
|
35
35
|
/** Shared projection values and the lifecycle of their explicit baseline read. */
|
|
36
36
|
export interface SessionProjectionSnapshot {
|
|
37
37
|
readonly values: Readonly<Partial<SessionProjectionMap>>;
|
|
38
|
-
readonly state: 'idle' | 'loading' | 'ready' | 'error';
|
|
38
|
+
readonly state: 'idle' | 'loading' | 'ready' | 'migration-required' | 'error';
|
|
39
39
|
readonly error: RemoteFailure | null;
|
|
40
40
|
}
|
|
41
41
|
/** Instance cluster + frame entry + the session list. */
|
|
@@ -86,8 +86,9 @@ export declare class SessionManager {
|
|
|
86
86
|
resolveTarget(target: SessionTarget): SessionId;
|
|
87
87
|
/**
|
|
88
88
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
89
|
-
* @param sessionId - possible child id in
|
|
90
|
-
* @returns A retained
|
|
89
|
+
* @param sessionId - possible child id in received Session metadata.
|
|
90
|
+
* @returns A retained address, then a catalog address, then an unknown-mode address
|
|
91
|
+
* from a listed subagent header; undefined without that evidence.
|
|
91
92
|
*/
|
|
92
93
|
subagentAddress(sessionId: SessionId): SubagentAddress | undefined;
|
|
93
94
|
/**
|
|
@@ -130,7 +131,7 @@ export declare class SessionManager {
|
|
|
130
131
|
/** Resident per-session projection store (create-on-demand; outlives instantiation). */
|
|
131
132
|
private projectionStore;
|
|
132
133
|
/**
|
|
133
|
-
*
|
|
134
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
134
135
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
135
136
|
* @returns completion of the current or newly started read.
|
|
136
137
|
*/
|
|
@@ -171,12 +172,13 @@ export declare class SessionManager {
|
|
|
171
172
|
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
172
173
|
* lineage rides parentSessionId. A child published before Workspace
|
|
173
174
|
* attachment fails is also reconciled into the list.
|
|
174
|
-
* @param opts - source session
|
|
175
|
+
* @param opts - source session, optional exact inclusive boundary seq, and permission to start migration.
|
|
175
176
|
* @returns the fork result (the child session id).
|
|
176
177
|
*/
|
|
177
178
|
fork(opts: {
|
|
178
179
|
sessionId: SessionId;
|
|
179
180
|
atSeq?: SessionSeq;
|
|
181
|
+
allowMigration?: boolean;
|
|
180
182
|
}): Promise<RemoteResult<{
|
|
181
183
|
sessionId: SessionId;
|
|
182
184
|
}>>;
|
|
@@ -78,8 +78,9 @@ export class SessionManager {
|
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
81
|
-
* @param sessionId - possible child id in
|
|
82
|
-
* @returns A retained
|
|
81
|
+
* @param sessionId - possible child id in received Session metadata.
|
|
82
|
+
* @returns A retained address, then a catalog address, then an unknown-mode address
|
|
83
|
+
* from a listed subagent header; undefined without that evidence.
|
|
83
84
|
*/
|
|
84
85
|
subagentAddress(sessionId) {
|
|
85
86
|
const retained = this.addresses.get(sessionId);
|
|
@@ -88,7 +89,7 @@ export class SessionManager {
|
|
|
88
89
|
for (const parentSessionId of this.projectionStores.keys()) {
|
|
89
90
|
const child = this.projectionStores.get(parentSessionId)?.values().subagentCatalog
|
|
90
91
|
?.find(entry => entry.id === sessionId);
|
|
91
|
-
if (child !== undefined) {
|
|
92
|
+
if (child !== undefined && child.mode !== 'external') {
|
|
92
93
|
return {
|
|
93
94
|
parentSessionId,
|
|
94
95
|
childSessionId: sessionId,
|
|
@@ -96,7 +97,14 @@ export class SessionManager {
|
|
|
96
97
|
};
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
const summary = this.summaries.find(summary => summary.sessionId === sessionId);
|
|
101
|
+
if (summary?.origin !== 'subagent' || summary.parentSessionId === undefined)
|
|
102
|
+
return undefined;
|
|
103
|
+
return {
|
|
104
|
+
parentSessionId: summary.parentSessionId,
|
|
105
|
+
childSessionId: sessionId,
|
|
106
|
+
mode: 'unknown',
|
|
107
|
+
};
|
|
100
108
|
}
|
|
101
109
|
// ---- Instance management ----
|
|
102
110
|
/**
|
|
@@ -169,7 +177,7 @@ export class SessionManager {
|
|
|
169
177
|
const address = this.addresses.get(sessionId);
|
|
170
178
|
const child = address === undefined ? undefined : this.projectionStores.get(address.parentSessionId)?.values().subagentCatalog
|
|
171
179
|
?.find(entry => entry.id === sessionId);
|
|
172
|
-
if (child !== undefined) {
|
|
180
|
+
if (child !== undefined && child.mode !== 'external') {
|
|
173
181
|
// A catalogued child exists only after its delegated session has
|
|
174
182
|
// durable history, even though child rows do not carry `blank`.
|
|
175
183
|
session.handleBlank(false);
|
|
@@ -252,7 +260,7 @@ export class SessionManager {
|
|
|
252
260
|
return store;
|
|
253
261
|
}
|
|
254
262
|
/**
|
|
255
|
-
*
|
|
263
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
256
264
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
257
265
|
* @returns completion of the current or newly started read.
|
|
258
266
|
*/
|
|
@@ -273,14 +281,27 @@ export class SessionManager {
|
|
|
273
281
|
if (controller.signal.aborted)
|
|
274
282
|
return;
|
|
275
283
|
if (result.ok) {
|
|
276
|
-
|
|
277
|
-
|
|
284
|
+
const value = result.value;
|
|
285
|
+
if (value !== null) {
|
|
286
|
+
switch (value.kind) {
|
|
287
|
+
case 'sequenced':
|
|
288
|
+
store.seed({ values: value.values, asOfSeq: sessionSeqCursor(value.asOfSeq) });
|
|
289
|
+
break;
|
|
290
|
+
case 'migration-required':
|
|
291
|
+
store.applyCached(value.values);
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
assertNever(value, 'session projections result');
|
|
295
|
+
}
|
|
278
296
|
}
|
|
279
297
|
else if (store.values() === initialValues) {
|
|
280
298
|
// A later frame proves existence independently of an earlier missing read.
|
|
281
299
|
store.clear();
|
|
282
300
|
}
|
|
283
|
-
this.projectionLoads.set(sessionId, {
|
|
301
|
+
this.projectionLoads.set(sessionId, {
|
|
302
|
+
state: value?.kind === 'migration-required' ? 'migration-required' : 'ready',
|
|
303
|
+
error: null,
|
|
304
|
+
});
|
|
284
305
|
}
|
|
285
306
|
else {
|
|
286
307
|
this.projectionLoads.set(sessionId, { state: 'error', error: result.error });
|
|
@@ -454,7 +475,7 @@ export class SessionManager {
|
|
|
454
475
|
* preserve it or lower it after an exact cut before the first `turn/start`;
|
|
455
476
|
* lineage rides parentSessionId. A child published before Workspace
|
|
456
477
|
* attachment fails is also reconciled into the list.
|
|
457
|
-
* @param opts - source session
|
|
478
|
+
* @param opts - source session, optional exact inclusive boundary seq, and permission to start migration.
|
|
458
479
|
* @returns the fork result (the child session id).
|
|
459
480
|
*/
|
|
460
481
|
async fork(opts) {
|
|
@@ -462,6 +483,7 @@ export class SessionManager {
|
|
|
462
483
|
const result = await this.remote.session.fork({
|
|
463
484
|
sessionId: opts.sessionId,
|
|
464
485
|
...opts.atSeq === undefined ? {} : { atSeq: opts.atSeq },
|
|
486
|
+
...opts.allowMigration === undefined ? {} : { allowMigration: opts.allowMigration },
|
|
465
487
|
});
|
|
466
488
|
const childId = result.ok
|
|
467
489
|
? result.value.sessionId
|
|
@@ -691,6 +713,7 @@ export class SessionManager {
|
|
|
691
713
|
&& prev.blank === entry.blank
|
|
692
714
|
&& prev.parentSessionId === entry.parentSessionId && prev.cwd === entry.cwd
|
|
693
715
|
&& prev.origin === entry.origin && prev.title === entry.title && prev.depth === entry.depth
|
|
716
|
+
&& prev.formatStatus === entry.formatStatus
|
|
694
717
|
&& prev.projectionValues === entry.projectionValues)
|
|
695
718
|
return prev;
|
|
696
719
|
this.entryCache.set(entry.sessionId, entry);
|
|
@@ -732,6 +755,7 @@ function applyMutation(summaries, mutation) {
|
|
|
732
755
|
...(mutation.kind === 'upsert' ? {
|
|
733
756
|
agentAvailable: mutation.summary.agentAvailable,
|
|
734
757
|
running: mutation.summary.running,
|
|
758
|
+
...(mutation.summary.formatStatus === undefined ? {} : { formatStatus: mutation.summary.formatStatus }),
|
|
735
759
|
} : {}),
|
|
736
760
|
...(existing.cwd === undefined && mutation.summary.cwd !== undefined ? { cwd: mutation.summary.cwd } : {}),
|
|
737
761
|
...(existing.parentSessionId === undefined && mutation.summary.parentSessionId !== undefined
|
|
@@ -741,7 +765,8 @@ function applyMutation(summaries, mutation) {
|
|
|
741
765
|
};
|
|
742
766
|
if (filled.cwd === existing.cwd && filled.parentSessionId === existing.parentSessionId
|
|
743
767
|
&& filled.origin === existing.origin && filled.blank === existing.blank
|
|
744
|
-
&& filled.agentAvailable === existing.agentAvailable && filled.running === existing.running
|
|
768
|
+
&& filled.agentAvailable === existing.agentAvailable && filled.running === existing.running
|
|
769
|
+
&& filled.formatStatus === existing.formatStatus)
|
|
745
770
|
return [...summaries];
|
|
746
771
|
return summaries.map(summary => summary.sessionId === mutation.summary.sessionId ? filled : summary);
|
|
747
772
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* (`{ value, seq }`) comes from the connected Host — a follow opening
|
|
7
7
|
* baseline, a Session Controller `projection` frame, a list block the Host
|
|
8
8
|
* computed for an attached Session — and merges under **higher seq wins**
|
|
9
|
-
* against other sequenced rows. A `cached` row (`{ value }`) comes from
|
|
10
|
-
*
|
|
9
|
+
* against other sequenced rows. A `cached` row (`{ value }`) comes from
|
|
10
|
+
* a Host view of the persisted checkpoint, carries no comparable
|
|
11
11
|
* seq, and yields to every sequenced write. No client-side domain folding
|
|
12
12
|
* exists: a domain ships projection support with zero client code. Per-key
|
|
13
13
|
* bare observable faces feed `useProjection` (ui-renderer binds them).
|
|
@@ -101,7 +101,7 @@ export declare class ProjectionValueStore {
|
|
|
101
101
|
*/
|
|
102
102
|
apply(key: string, value: unknown, seq: SessionSeqCursor): void;
|
|
103
103
|
/**
|
|
104
|
-
* Fill keys from a
|
|
104
|
+
* Fill keys from a Host block labeled `cached`: a zero-I/O
|
|
105
105
|
* view of the persisted checkpoint. A cached value lands only where no
|
|
106
106
|
* sequenced row exists: a connected Session has already answered for such
|
|
107
107
|
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
@@ -82,7 +82,7 @@ export class ProjectionValueStore {
|
|
|
82
82
|
this.changed(key);
|
|
83
83
|
}
|
|
84
84
|
/**
|
|
85
|
-
* Fill keys from a
|
|
85
|
+
* Fill keys from a Host block labeled `cached`: a zero-I/O
|
|
86
86
|
* view of the persisted checkpoint. A cached value lands only where no
|
|
87
87
|
* sequenced row exists: a connected Session has already answered for such
|
|
88
88
|
* a key, and the list's view of the persisted checkpoint cannot be newer
|
|
@@ -12,6 +12,7 @@ import type { AgentContext, ISessions, SessionReference, SessionRetainInfo, Sess
|
|
|
12
12
|
import { SessionManager } from './manager.ts';
|
|
13
13
|
import type { SessionRemotes } from './remotes.ts';
|
|
14
14
|
import type { SessionListPhase, SessionSearchResultItem, SessionProjectionSnapshot } from './manager.ts';
|
|
15
|
+
import type { SessionListEntry } from './lineage.ts';
|
|
15
16
|
/** Session list row projected from the host list RPC plus live stream increments. */
|
|
16
17
|
export interface SessionSummary {
|
|
17
18
|
id: SessionId;
|
|
@@ -23,6 +24,8 @@ export interface SessionSummary {
|
|
|
23
24
|
parentId?: SessionId;
|
|
24
25
|
/** Coarse durable origin for navigation filtering; not a continuation capability. */
|
|
25
26
|
origin?: 'subagent';
|
|
27
|
+
/** Format classification supplied by the Host list, when available. */
|
|
28
|
+
formatStatus?: SessionListEntry['formatStatus'];
|
|
26
29
|
/** Host running state for `ids` members; a display fallback for other rows. */
|
|
27
30
|
running: boolean;
|
|
28
31
|
/** Local ownership counts; Host metadata refreshes cannot overwrite them. */
|
|
@@ -30,7 +33,7 @@ export interface SessionSummary {
|
|
|
30
33
|
/**
|
|
31
34
|
* New Session presentation and reuse eligibility, derived from the Host
|
|
32
35
|
* summary, `sessionListMetadata`, and client acceptance/running observations.
|
|
33
|
-
*
|
|
36
|
+
* Workspace reconnection may reuse a blank in the same workspace. Filtering
|
|
34
37
|
* stays with the consumer: the store carries every row, while the Workspace
|
|
35
38
|
* browser shows only the selected blank entry.
|
|
36
39
|
*/
|
|
@@ -113,11 +116,11 @@ export declare class ClientSessions implements ISessions {
|
|
|
113
116
|
* Resolve an already discovered direct-parent address without opening it.
|
|
114
117
|
* Feature plugins use this to avoid Agent-bound RPCs in persisted child views.
|
|
115
118
|
* @param id - possible addressed child id.
|
|
116
|
-
* @returns A retained or
|
|
119
|
+
* @returns A retained, catalog-derived, or unknown-mode listed address, without retaining a new selection or scope.
|
|
117
120
|
*/
|
|
118
121
|
subagentAddress(id: SessionId): SubagentAddress | undefined;
|
|
119
122
|
/**
|
|
120
|
-
*
|
|
123
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
121
124
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
122
125
|
*/
|
|
123
126
|
refreshProjections(sessionId: SessionId): Promise<void>;
|
|
@@ -189,7 +192,7 @@ export declare class ClientSessions implements ISessions {
|
|
|
189
192
|
* seq (a real event seq the caller already knows; a cut inside an open
|
|
190
193
|
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
191
194
|
* the latest completed-turn prefix), and whether to increment an
|
|
192
|
-
* inherited durable title before resolving.
|
|
195
|
+
* inherited durable title before resolving, plus permission to start source migration.
|
|
193
196
|
* @returns the child session id.
|
|
194
197
|
* @throws {SessionForkError} with the source id.
|
|
195
198
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -199,6 +202,7 @@ export declare class ClientSessions implements ISessions {
|
|
|
199
202
|
atSeq?: number;
|
|
200
203
|
increaseTitle?: boolean;
|
|
201
204
|
onCreated?: (childId: SessionId) => void;
|
|
205
|
+
allowMigration?: boolean;
|
|
202
206
|
}): Promise<SessionId>;
|
|
203
207
|
/**
|
|
204
208
|
* Borrow an already-retained Agent-scoped Context.
|
|
@@ -234,13 +234,13 @@ 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 A retained or
|
|
237
|
+
* @returns A retained, catalog-derived, or unknown-mode listed 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
|
-
*
|
|
243
|
+
* Read projections without starting migration; retry failed or migration-deferred reads.
|
|
244
244
|
* @param sessionId - Session to inspect without opening its conversation.
|
|
245
245
|
*/
|
|
246
246
|
refreshProjections(sessionId) {
|
|
@@ -331,7 +331,7 @@ export class ClientSessions {
|
|
|
331
331
|
* seq (a real event seq the caller already knows; a cut inside an open
|
|
332
332
|
* turn is balanced Host-side with synthetic closers, and omission selects
|
|
333
333
|
* the latest completed-turn prefix), and whether to increment an
|
|
334
|
-
* inherited durable title before resolving.
|
|
334
|
+
* inherited durable title before resolving, plus permission to start source migration.
|
|
335
335
|
* @returns the child session id.
|
|
336
336
|
* @throws {SessionForkError} with the source id.
|
|
337
337
|
* @throws {Error} when a requested child-title rename fails after creation.
|
|
@@ -343,6 +343,7 @@ export class ClientSessions {
|
|
|
343
343
|
const result = await this.manager.fork({
|
|
344
344
|
sessionId: opts.sessionId,
|
|
345
345
|
...(opts.atSeq === undefined ? {} : { atSeq: SessionSeq(opts.atSeq) }),
|
|
346
|
+
...(opts.allowMigration === undefined ? {} : { allowMigration: opts.allowMigration }),
|
|
346
347
|
});
|
|
347
348
|
if (!result.ok)
|
|
348
349
|
throw new SessionForkError(result.error, opts.sessionId);
|
|
@@ -502,6 +503,7 @@ export class ClientSessions {
|
|
|
502
503
|
retainedBy: this.retentionSnapshot(entry.sessionId).retainedBy,
|
|
503
504
|
blank: entry.blank,
|
|
504
505
|
updatedAt: entry.updatedAt,
|
|
506
|
+
formatStatus: entry.formatStatus,
|
|
505
507
|
...(entry.projectionValues === undefined
|
|
506
508
|
? {}
|
|
507
509
|
: { projectionValues: entry.projectionValues }),
|
|
@@ -513,6 +515,8 @@ export class ClientSessions {
|
|
|
513
515
|
}
|
|
514
516
|
for (const [parentId, projection] of Object.entries(projectionsBySession)) {
|
|
515
517
|
for (const child of projection.values.subagentCatalog ?? []) {
|
|
518
|
+
if (child.mode === 'external')
|
|
519
|
+
continue;
|
|
516
520
|
const childId = child.id;
|
|
517
521
|
const summary = byId[childId];
|
|
518
522
|
const projectionValues = summary?.projectionValues ?? this.manager.projectionValues(childId);
|
package/lib/types/index.d.ts
CHANGED
|
@@ -22,7 +22,13 @@ declare module '@deepseek-ai/cordis' {
|
|
|
22
22
|
export interface Config {
|
|
23
23
|
/** Override platform desktop-opener detection. */
|
|
24
24
|
readonly nativeOpen?: boolean;
|
|
25
|
+
/** Positive integral milliseconds of list work before yielding between complete rows. */
|
|
26
|
+
readonly listWorkSliceMs?: number;
|
|
25
27
|
}
|
|
28
|
+
/** Deployment policy after schema defaults have been applied. */
|
|
29
|
+
type ResolvedConfig = Config & {
|
|
30
|
+
readonly listWorkSliceMs: number;
|
|
31
|
+
};
|
|
26
32
|
/** Host integrations replaceable by direct unit tests. */
|
|
27
33
|
export interface SessionControllerInternals {
|
|
28
34
|
/** Native default-application handoff. */
|
|
@@ -39,7 +45,7 @@ export interface SessionControllerInternals {
|
|
|
39
45
|
/** Host service backing the generated `ctx.remote.session` namespace. */
|
|
40
46
|
export declare class SessionController extends TypertRemoteService {
|
|
41
47
|
static inject: string[];
|
|
42
|
-
static Config: z<Config>;
|
|
48
|
+
static Config: z<Config, ResolvedConfig>;
|
|
43
49
|
private readonly agents;
|
|
44
50
|
private readonly commands;
|
|
45
51
|
private readonly controlState;
|
|
@@ -53,7 +59,7 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
53
59
|
private readonly promotions;
|
|
54
60
|
/**
|
|
55
61
|
* @param ctx - Host context containing the Session capability assembly.
|
|
56
|
-
* @param config - native-opener deployment policy.
|
|
62
|
+
* @param config - native-opener and list-scheduling deployment policy.
|
|
57
63
|
* @param internals - host integrations replaceable by direct unit tests.
|
|
58
64
|
*/
|
|
59
65
|
constructor(ctx: Context, config: Config, internals?: SessionControllerInternals);
|
|
@@ -74,7 +80,7 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
74
80
|
/**
|
|
75
81
|
* Read all visible Session rows without resuming an Agent.
|
|
76
82
|
* @param _request - reserved empty list request.
|
|
77
|
-
* @param signal - cancellation for persistence reads.
|
|
83
|
+
* @param signal - cancellation for persistence reads and summary generation.
|
|
78
84
|
* @returns visible Session summaries ordered by activity.
|
|
79
85
|
*/
|
|
80
86
|
list(_request: SessionListRequest, signal: AbortSignal): Promise<SessionListValue>;
|
|
@@ -150,7 +156,7 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
150
156
|
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
151
157
|
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
152
158
|
* synthetic fork closers.
|
|
153
|
-
* @param request - source Session
|
|
159
|
+
* @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
|
|
154
160
|
* @returns the new Session identity.
|
|
155
161
|
*/
|
|
156
162
|
fork(request: SessionForkRequest): Promise<SessionForkValue>;
|
|
@@ -195,12 +201,13 @@ export declare class SessionController extends TypertRemoteService {
|
|
|
195
201
|
*/
|
|
196
202
|
follow(request: SessionFollowRequest, signal: AbortSignal): AsyncIterable<SessionFollowFrame>;
|
|
197
203
|
/**
|
|
198
|
-
* Read
|
|
204
|
+
* Read exact projections, returning cached hints only when migration is required.
|
|
199
205
|
* @param request - Session whose current values are required.
|
|
200
|
-
* @param signal - cancellation for the Session
|
|
201
|
-
* @returns
|
|
206
|
+
* @param signal - cancellation for the Session read.
|
|
207
|
+
* @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
|
|
202
208
|
*/
|
|
203
209
|
projections(request: SessionProjectionsRequest, signal: AbortSignal): Promise<SessionProjectionsValue>;
|
|
210
|
+
private liveProjections;
|
|
204
211
|
/**
|
|
205
212
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
206
213
|
* @param signal - cancellation owned by the Remote stream carrier.
|
package/lib/types/index.js
CHANGED
|
@@ -187,6 +187,7 @@ let SessionController = (() => {
|
|
|
187
187
|
];
|
|
188
188
|
static Config = z.object({
|
|
189
189
|
nativeOpen: z.boolean(),
|
|
190
|
+
listWorkSliceMs: z.natural().min(1).default(16),
|
|
190
191
|
});
|
|
191
192
|
agents = __runInitializers(this, _instanceExtraInitializers);
|
|
192
193
|
commands;
|
|
@@ -201,11 +202,12 @@ let SessionController = (() => {
|
|
|
201
202
|
promotions = new Set();
|
|
202
203
|
/**
|
|
203
204
|
* @param ctx - Host context containing the Session capability assembly.
|
|
204
|
-
* @param config - native-opener deployment policy.
|
|
205
|
+
* @param config - native-opener and list-scheduling deployment policy.
|
|
205
206
|
* @param internals - host integrations replaceable by direct unit tests.
|
|
206
207
|
*/
|
|
207
208
|
constructor(ctx, config, internals = {}) {
|
|
208
209
|
super(ctx, 'sessionController', { namespace: 'session' });
|
|
210
|
+
const resolved = SessionController.Config(config);
|
|
209
211
|
installModelSelectionProjection(ctx);
|
|
210
212
|
this.agents = new ApiSessionAgentController(ctx);
|
|
211
213
|
this.commands = new SessionCommandController(ctx, this.agents, process.cwd());
|
|
@@ -222,7 +224,7 @@ let SessionController = (() => {
|
|
|
222
224
|
await Promise.allSettled([...this.promotions]);
|
|
223
225
|
}, 'session-controller.promotions');
|
|
224
226
|
this.history = new SessionHistoryController(ctx, (observation) => { this.promote(observation); });
|
|
225
|
-
this.listState = new ApiSessionList(ctx);
|
|
227
|
+
this.listState = new ApiSessionList(ctx, resolved.listWorkSliceMs);
|
|
226
228
|
this.fileApplications = internals.fileApplications ?? nativeFileApplications;
|
|
227
229
|
this.openFileApplication = internals.openFileApplication ?? openNativeFileApplication;
|
|
228
230
|
this.openPath = internals.openPath ?? openNativeAssociatedPath;
|
|
@@ -318,7 +320,7 @@ let SessionController = (() => {
|
|
|
318
320
|
/**
|
|
319
321
|
* Read all visible Session rows without resuming an Agent.
|
|
320
322
|
* @param _request - reserved empty list request.
|
|
321
|
-
* @param signal - cancellation for persistence reads.
|
|
323
|
+
* @param signal - cancellation for persistence reads and summary generation.
|
|
322
324
|
* @returns visible Session summaries ordered by activity.
|
|
323
325
|
*/
|
|
324
326
|
async list(_request, signal) {
|
|
@@ -461,10 +463,18 @@ let SessionController = (() => {
|
|
|
461
463
|
* Fork one cold-readable exact event prefix into a new Session. An omitted
|
|
462
464
|
* boundary selects the latest completed-turn prefix; an open cut receives
|
|
463
465
|
* synthetic fork closers.
|
|
464
|
-
* @param request - source Session
|
|
466
|
+
* @param request - source Session, optional exact inclusive event boundary, and migration preflight choice.
|
|
465
467
|
* @returns the new Session identity.
|
|
466
468
|
*/
|
|
467
|
-
fork(request) {
|
|
469
|
+
async fork(request) {
|
|
470
|
+
if (request.allowMigration === false && this.ctx.sessions.get(request.sessionId) === undefined) {
|
|
471
|
+
const stored = await this.ctx.get('sessionPersistence')?.stat(request.sessionId);
|
|
472
|
+
if (stored?.formatStatus === 'migration-required' && this.ctx.sessions.get(request.sessionId) === undefined) {
|
|
473
|
+
throw new RemoteError('session/migration-required', 'Open the source Session to complete migration before forking', {
|
|
474
|
+
sessionId: request.sessionId,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
468
478
|
return this.commands.fork(request);
|
|
469
479
|
}
|
|
470
480
|
/**
|
|
@@ -521,10 +531,10 @@ let SessionController = (() => {
|
|
|
521
531
|
return this.history.follow(request, signal);
|
|
522
532
|
}
|
|
523
533
|
/**
|
|
524
|
-
* Read
|
|
534
|
+
* Read exact projections, returning cached hints only when migration is required.
|
|
525
535
|
* @param request - Session whose current values are required.
|
|
526
|
-
* @param signal - cancellation for the Session
|
|
527
|
-
* @returns
|
|
536
|
+
* @param signal - cancellation for the Session read.
|
|
537
|
+
* @returns a sequenced baseline, cached hints when migration is deferred, or null when absent.
|
|
528
538
|
*/
|
|
529
539
|
async projections(request, signal) {
|
|
530
540
|
const { sessionId } = request;
|
|
@@ -534,12 +544,27 @@ let SessionController = (() => {
|
|
|
534
544
|
try {
|
|
535
545
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
536
546
|
try {
|
|
547
|
+
signal.throwIfAborted();
|
|
548
|
+
const live = this.liveProjections(sessionId);
|
|
549
|
+
if (live !== undefined)
|
|
550
|
+
return live;
|
|
551
|
+
const stored = await this.ctx.get('sessionPersistence')?.stat(sessionId, { signal });
|
|
552
|
+
signal.throwIfAborted();
|
|
553
|
+
const attached = this.liveProjections(sessionId);
|
|
554
|
+
if (attached !== undefined)
|
|
555
|
+
return attached;
|
|
556
|
+
if (stored === undefined)
|
|
557
|
+
return null;
|
|
558
|
+
if (stored.formatStatus === 'migration-required') {
|
|
559
|
+
const cached = this.ctx.get('sessionProjectionCache')?.cachedSnapshot(stored.header);
|
|
560
|
+
return { kind: 'migration-required', values: { ...cached?.values } };
|
|
561
|
+
}
|
|
537
562
|
const observation = __addDisposableResource(env_2, await this.ctx.sessionQuery.observeSession(sessionId, { signal }), false);
|
|
538
563
|
const projections = observation.projections;
|
|
539
564
|
if (projections === undefined) {
|
|
540
565
|
throw new RemoteError('session/projections-unavailable', 'Session projections are unavailable', {});
|
|
541
566
|
}
|
|
542
|
-
return { asOfSeq: projections.asOfSeq, values: projections.values };
|
|
567
|
+
return { kind: 'sequenced', asOfSeq: projections.asOfSeq, values: projections.values };
|
|
543
568
|
}
|
|
544
569
|
catch (e_2) {
|
|
545
570
|
env_2.error = e_2;
|
|
@@ -550,10 +575,10 @@ let SessionController = (() => {
|
|
|
550
575
|
}
|
|
551
576
|
}
|
|
552
577
|
catch (error) {
|
|
553
|
-
if (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND')
|
|
554
|
-
return null;
|
|
555
|
-
|
|
556
|
-
|
|
578
|
+
if (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_SESSION_NOT_FOUND') {
|
|
579
|
+
return this.liveProjections(sessionId) ?? null;
|
|
580
|
+
}
|
|
581
|
+
if (signal.aborted || (error instanceof SessionQueryError && error.code === 'SESSION_QUERY_ABORTED')) {
|
|
557
582
|
throw new RemoteError('gateway/cancelled', 'Session projection read was cancelled', {}, { cause: error });
|
|
558
583
|
}
|
|
559
584
|
if (error instanceof RemoteError)
|
|
@@ -561,6 +586,13 @@ let SessionController = (() => {
|
|
|
561
586
|
throw new RemoteError('gateway/internal', 'Session projection read failed', {}, { cause: error });
|
|
562
587
|
}
|
|
563
588
|
}
|
|
589
|
+
liveProjections(sessionId) {
|
|
590
|
+
const session = this.ctx.sessions.get(sessionId);
|
|
591
|
+
if (session === undefined)
|
|
592
|
+
return undefined;
|
|
593
|
+
const snapshot = this.ctx.sessionProjections.snapshot(session);
|
|
594
|
+
return { kind: 'sequenced', asOfSeq: snapshot.asOfSeq, values: snapshot.values };
|
|
595
|
+
}
|
|
564
596
|
/**
|
|
565
597
|
* Stream a complete live-control baseline followed by replacement frames.
|
|
566
598
|
* @param signal - cancellation owned by the Remote stream carrier.
|
package/lib/types/list.d.ts
CHANGED
|
@@ -19,8 +19,12 @@ export declare function truncateUnicodeCodePoints(value: string, maximum: number
|
|
|
19
19
|
/** Owns list projection registration, bounded cold summaries, and authorized search. */
|
|
20
20
|
export declare class ApiSessionList {
|
|
21
21
|
private readonly ctx;
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
private readonly workSliceMs;
|
|
23
|
+
/**
|
|
24
|
+
* @param ctx - Host context carrying Session, query, persistence, and projection services.
|
|
25
|
+
* @param workSliceMs - Resolved positive integral list-work budget in milliseconds.
|
|
26
|
+
*/
|
|
27
|
+
constructor(ctx: Context, workSliceMs: number);
|
|
24
28
|
/**
|
|
25
29
|
* Build one current attached-Session summary.
|
|
26
30
|
* @param session - attached Session to summarize.
|
|
@@ -29,7 +33,7 @@ export declare class ApiSessionList {
|
|
|
29
33
|
summaryFor(session: Session): SessionSummary;
|
|
30
34
|
/**
|
|
31
35
|
* Read every visible attached and persisted Session without activating an Agent.
|
|
32
|
-
* @param signal - optional cancellation for persistence reads.
|
|
36
|
+
* @param signal - optional cancellation for persistence reads and summary generation.
|
|
33
37
|
* @returns visible Session summaries ordered by activity.
|
|
34
38
|
*/
|
|
35
39
|
list(signal?: AbortSignal): Promise<SessionSummary[]>;
|