@crazx/dsh-api-session-controller 0.1.2-alpha.3.zw.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +74 -0
- package/README.zh.md +74 -0
- package/lib/client.js +2724 -0
- package/lib/index.js +2826 -0
- package/lib/invariant.js +13 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +2574 -0
- package/lib/typert.remote-client.d.ts +67 -0
- package/lib/typert.remote-client.js +1119 -0
- package/lib/types/agent.d.ts +156 -0
- package/lib/types/agent.js +537 -0
- package/lib/types/catalog.d.ts +11 -0
- package/lib/types/catalog.js +58 -0
- package/lib/types/client/contract/events.d.ts +71 -0
- package/lib/types/client/contract/events.js +91 -0
- package/lib/types/client/contract/session.d.ts +150 -0
- package/lib/types/client/contract/session.js +2 -0
- package/lib/types/client/contract/sessions.d.ts +125 -0
- package/lib/types/client/contract/sessions.js +2 -0
- package/lib/types/client/contract/snapshot.d.ts +82 -0
- package/lib/types/client/contract/snapshot.js +2 -0
- package/lib/types/client/index.d.ts +30 -0
- package/lib/types/client/index.js +48 -0
- package/lib/types/client/ordered-baseline.d.ts +12 -0
- package/lib/types/client/ordered-baseline.js +41 -0
- package/lib/types/client/scope.d.ts +36 -0
- package/lib/types/client/scope.js +54 -0
- package/lib/types/client/sessions/history-records.d.ts +22 -0
- package/lib/types/client/sessions/history-records.js +31 -0
- package/lib/types/client/sessions/lineage.d.ts +38 -0
- package/lib/types/client/sessions/lineage.js +56 -0
- package/lib/types/client/sessions/manager.d.ts +280 -0
- package/lib/types/client/sessions/manager.js +894 -0
- package/lib/types/client/sessions/notifier.d.ts +39 -0
- package/lib/types/client/sessions/notifier.js +98 -0
- package/lib/types/client/sessions/projection-store.d.ts +108 -0
- package/lib/types/client/sessions/projection-store.js +129 -0
- package/lib/types/client/sessions/queue-mirror.d.ts +26 -0
- package/lib/types/client/sessions/queue-mirror.js +61 -0
- package/lib/types/client/sessions/remotes.d.ts +30 -0
- package/lib/types/client/sessions/remotes.js +8 -0
- package/lib/types/client/sessions/service.d.ts +349 -0
- package/lib/types/client/sessions/service.js +574 -0
- package/lib/types/client/sessions/session.d.ts +294 -0
- package/lib/types/client/sessions/session.js +711 -0
- package/lib/types/client/time-zone.d.ts +8 -0
- package/lib/types/client/time-zone.js +14 -0
- package/lib/types/client/transport.d.ts +73 -0
- package/lib/types/client/transport.js +106 -0
- package/lib/types/commands.d.ts +69 -0
- package/lib/types/commands.js +544 -0
- package/lib/types/control.d.ts +23 -0
- package/lib/types/control.js +192 -0
- package/lib/types/file-references.d.ts +27 -0
- package/lib/types/file-references.js +69 -0
- package/lib/types/history.d.ts +31 -0
- package/lib/types/history.js +376 -0
- package/lib/types/index.d.ts +171 -0
- package/lib/types/index.js +424 -0
- package/lib/types/invariant.d.ts +9 -0
- package/lib/types/invariant.js +12 -0
- package/lib/types/list.d.ts +53 -0
- package/lib/types/list.js +405 -0
- package/lib/types/model-selection-projection.d.ts +8 -0
- package/lib/types/model-selection-projection.js +66 -0
- package/lib/types/remote-events.d.ts +8 -0
- package/lib/types/remote-events.js +2 -0
- package/lib/types/skill-catalog.d.ts +28 -0
- package/lib/types/skill-catalog.js +192 -0
- package/lib/types/types.d.ts +505 -0
- package/lib/types/types.js +6 -0
- package/package.json +154 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Client Session object layer, Agent scopes, and Remote lifecycle wiring. */
|
|
2
|
+
import { createSessionControlStream } from "./transport.js";
|
|
3
|
+
import { ClientSessions } from "./sessions/service.js";
|
|
4
|
+
export { createSessionControlStream, SessionEventStream, SESSION_SEARCH_RESULT_LIMIT, SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS, } from "./transport.js";
|
|
5
|
+
export { createScope, scopeOf } from "./scope.js";
|
|
6
|
+
export { SessionCreateError, SessionForkError } from "./sessions/service.js";
|
|
7
|
+
export { MutableSessionEventSource } from "./contract/events.js";
|
|
8
|
+
/** Required Remote and Context projection services. */
|
|
9
|
+
export const inject = [
|
|
10
|
+
'typert',
|
|
11
|
+
'remote',
|
|
12
|
+
'remote.commands',
|
|
13
|
+
'remote.session',
|
|
14
|
+
'remote.subagents',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Install Client Session state and its reconnecting control stream.
|
|
18
|
+
* @param ctx - Client Cordis context.
|
|
19
|
+
*/
|
|
20
|
+
export function apply(ctx) {
|
|
21
|
+
const remotes = ctx.remote;
|
|
22
|
+
const sessions = new ClientSessions(ctx, remotes);
|
|
23
|
+
ctx.remote.$on('api-session/added', (summary) => { sessions.handleSessionAdded(summary); });
|
|
24
|
+
ctx.remote.$on('api-session/removed', (sessionId) => { sessions.handleSessionRemoved(sessionId); });
|
|
25
|
+
ctx.remote.$on('api-session/status', (sessionId, running) => {
|
|
26
|
+
sessions.handleSessionStatus(sessionId, running);
|
|
27
|
+
});
|
|
28
|
+
ctx.remote.$on('api-session/activity', (sessionId, updatedAt) => {
|
|
29
|
+
sessions.handleSessionActivity(sessionId, updatedAt);
|
|
30
|
+
});
|
|
31
|
+
ctx.remote.$on('api-session/error', (sessionId, message) => {
|
|
32
|
+
sessions.handleSessionError(sessionId, message);
|
|
33
|
+
});
|
|
34
|
+
const control = createSessionControlStream(remotes, {
|
|
35
|
+
accept: (frame) => { sessions.handleControlFrame(frame); },
|
|
36
|
+
failed: (error) => { console.error('[session-controller] control stream failed:', error); },
|
|
37
|
+
});
|
|
38
|
+
control.start();
|
|
39
|
+
ctx.on('connection/reset', () => { sessions.handleConnected(); });
|
|
40
|
+
if (ctx.remote.$host.home !== undefined)
|
|
41
|
+
sessions.handleConnected();
|
|
42
|
+
ctx.typert.contexts.registerClient('agent', {
|
|
43
|
+
identity: candidate => sessions.scopeOf(candidate),
|
|
44
|
+
resolve: sessionId => sessions.resolveAgentScope(sessionId),
|
|
45
|
+
});
|
|
46
|
+
ctx.effect(() => async () => { await control.dispose(); }, 'session-controller.client.control');
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge an authoritative baseline without moving identities already visible to
|
|
3
|
+
* the client. Baseline-only identities are inserted relative to the nearest
|
|
4
|
+
* following known identity; identities absent from the baseline are removed.
|
|
5
|
+
*
|
|
6
|
+
* @param current - the established client order.
|
|
7
|
+
* @param baseline - the latest authoritative rows.
|
|
8
|
+
* @param keyOf - stable identity selector.
|
|
9
|
+
* @returns baseline-valued rows with the established relative order retained.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeOrderedBaseline<T>(current: readonly T[], baseline: readonly T[], keyOf: (value: T) => unknown): T[];
|
|
12
|
+
//# sourceMappingURL=ordered-baseline.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge an authoritative baseline without moving identities already visible to
|
|
3
|
+
* the client. Baseline-only identities are inserted relative to the nearest
|
|
4
|
+
* following known identity; identities absent from the baseline are removed.
|
|
5
|
+
*
|
|
6
|
+
* @param current - the established client order.
|
|
7
|
+
* @param baseline - the latest authoritative rows.
|
|
8
|
+
* @param keyOf - stable identity selector.
|
|
9
|
+
* @returns baseline-valued rows with the established relative order retained.
|
|
10
|
+
*/
|
|
11
|
+
export function mergeOrderedBaseline(current, baseline, keyOf) {
|
|
12
|
+
const baselineByKey = new Map();
|
|
13
|
+
for (const value of baseline)
|
|
14
|
+
baselineByKey.set(keyOf(value), value);
|
|
15
|
+
const merged = current
|
|
16
|
+
.map(value => baselineByKey.get(keyOf(value)))
|
|
17
|
+
.filter((value) => value !== undefined);
|
|
18
|
+
const mergedKeys = new Set(merged.map(keyOf));
|
|
19
|
+
for (let index = 0; index < baseline.length; index++) {
|
|
20
|
+
const value = baseline[index];
|
|
21
|
+
/* v8 ignore next -- dense-array guard: index is bounded by baseline.length. */
|
|
22
|
+
if (value === undefined || mergedKeys.has(keyOf(value)))
|
|
23
|
+
continue;
|
|
24
|
+
let insertion = merged.length;
|
|
25
|
+
for (let following = index + 1; following < baseline.length; following++) {
|
|
26
|
+
const candidate = baseline[following];
|
|
27
|
+
/* v8 ignore next -- dense-array guard: following is bounded by baseline.length. */
|
|
28
|
+
if (candidate === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
const known = merged.findIndex(item => keyOf(item) === keyOf(candidate));
|
|
31
|
+
if (known !== -1) {
|
|
32
|
+
insertion = known;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
merged.splice(insertion, 0, value);
|
|
37
|
+
mergedKeys.add(keyOf(value));
|
|
38
|
+
}
|
|
39
|
+
return merged;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ordered-baseline.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Context, Fiber } from '@deepseek-ai/cordis';
|
|
2
|
+
import type { ClientRemote } from '@deepseek-ai/dsh-api-gateway/client';
|
|
3
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
|
+
import type { TypertRemoteScopeApi } from '@deepseek-ai/dsh-typert-protocol';
|
|
5
|
+
/** Client Cordis Context carrying one Agent identity and its scoped Remote namespaces. */
|
|
6
|
+
export type AgentContext = Omit<Context, 'remote'> & {
|
|
7
|
+
readonly remote: ClientRemote & TypertRemoteScopeApi<'agent'>;
|
|
8
|
+
};
|
|
9
|
+
/** A minted Agent scope and its disposal boundary. */
|
|
10
|
+
export interface AgentScopeHandle {
|
|
11
|
+
/**
|
|
12
|
+
* Tagged context: scope-owned registrations and scoped dispatch both go
|
|
13
|
+
* through it (passing it as the dispatch subject routes to this agent's
|
|
14
|
+
* tagged listeners plus every untagged one).
|
|
15
|
+
*/
|
|
16
|
+
ctx: AgentContext;
|
|
17
|
+
/** Backing fiber (dispose tears down every scope-owned registration). */
|
|
18
|
+
fiber: Fiber;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
|
|
22
|
+
* carries the agent tag and the dispatch filter — untagged listeners are
|
|
23
|
+
* admitted globally, tagged listeners only for a matching agent.
|
|
24
|
+
* Registrations through the returned ctx dispose with the fiber.
|
|
25
|
+
* @param ctx - client root context the scope fiber mounts under.
|
|
26
|
+
* @param key - owning agent identity (the routing tag; agent id === session id).
|
|
27
|
+
* @returns the tagged context and its backing fiber.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createScope(ctx: Context, key: SessionId): AgentScopeHandle;
|
|
30
|
+
/**
|
|
31
|
+
* Read the nearest agent tag inherited by a context.
|
|
32
|
+
* @param ctx - any client context.
|
|
33
|
+
* @returns its agent identity (the session id), or undefined for root contexts.
|
|
34
|
+
*/
|
|
35
|
+
export declare function scopeOf(ctx: Context): SessionId | undefined;
|
|
36
|
+
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Agent-scope primitive: mint a Cordis context tagged with the owning
|
|
3
|
+
* Agent's identity. The mechanism mirrors the host `dsh-scope` architecture
|
|
4
|
+
* (no-op plugin fiber + context tag + `Context.filter` routing predicate);
|
|
5
|
+
* the shape deliberately diverges: the filter lives on the actx itself
|
|
6
|
+
* instead of a separate carrier object, so scoped dispatch is plain cordis —
|
|
7
|
+
* `actx.bail(actx, event, payload)` / `actx.emit(actx, ...)` — with no
|
|
8
|
+
* wrapper. The host needs a detached carrier because its dispatch subject is
|
|
9
|
+
* the business Agent object; client scope events carry only ids, so the
|
|
10
|
+
* actx is the natural subject. The second divergence stands: the scope key
|
|
11
|
+
* is the branded `SessionId` (value compared), not an object identity — the
|
|
12
|
+
* agent and its session share one id (1:1, same axis; no separate AgentId
|
|
13
|
+
* brand), and a client scope's identity IS that wire id. Third divergence,
|
|
14
|
+
* deliberate: the client scopes the Agent IDENTITY, not a live Agent object
|
|
15
|
+
* — a cold session's host Agent is already disposed while its client actx
|
|
16
|
+
* stays alive for history viewing.
|
|
17
|
+
*/
|
|
18
|
+
import { Context as CordisContext } from '@deepseek-ai/cordis';
|
|
19
|
+
/** Context tag written by {@link createScope}. */
|
|
20
|
+
const kScope = Symbol('dsh.client.scope');
|
|
21
|
+
/** Shared no-op plugin backing each Agent scope fiber. */
|
|
22
|
+
function agentScope() { }
|
|
23
|
+
/**
|
|
24
|
+
* Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
|
|
25
|
+
* carries the agent tag and the dispatch filter — untagged listeners are
|
|
26
|
+
* admitted globally, tagged listeners only for a matching agent.
|
|
27
|
+
* Registrations through the returned ctx dispose with the fiber.
|
|
28
|
+
* @param ctx - client root context the scope fiber mounts under.
|
|
29
|
+
* @param key - owning agent identity (the routing tag; agent id === session id).
|
|
30
|
+
* @returns the tagged context and its backing fiber.
|
|
31
|
+
*/
|
|
32
|
+
export function createScope(ctx, key) {
|
|
33
|
+
const fiber = ctx.plugin(agentScope);
|
|
34
|
+
const scoped = fiber.ctx.extend({
|
|
35
|
+
[kScope]: key,
|
|
36
|
+
[CordisContext.filter](listenerCtx) {
|
|
37
|
+
const tag = scopeOf(listenerCtx);
|
|
38
|
+
return tag === undefined || tag === key;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
fiber,
|
|
43
|
+
ctx: scoped,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read the nearest agent tag inherited by a context.
|
|
48
|
+
* @param ctx - any client context.
|
|
49
|
+
* @returns its agent identity (the session id), or undefined for root contexts.
|
|
50
|
+
*/
|
|
51
|
+
export function scopeOf(ctx) {
|
|
52
|
+
return ctx[kScope];
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=scope.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Client range access and type narrowing for aligned Session history records. */
|
|
2
|
+
import type { SessionHistoryRecord } from '../../types.ts';
|
|
3
|
+
import type { SessionEventLikeEntry } from '../contract/events.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Narrow aligned wire records to their Client event types without allocation.
|
|
6
|
+
* @param records - validated history transport records.
|
|
7
|
+
* @returns the same record array with typed inner events.
|
|
8
|
+
*/
|
|
9
|
+
export declare function historyEntries(records: readonly SessionHistoryRecord[]): readonly SessionEventLikeEntry[];
|
|
10
|
+
/**
|
|
11
|
+
* Read the first logical sequence represented by one wire record.
|
|
12
|
+
* @param record - validated scalar event or packed Assistant delta run.
|
|
13
|
+
* @returns inclusive first Session sequence.
|
|
14
|
+
*/
|
|
15
|
+
export declare function historyRecordFirstSeq(record: SessionHistoryRecord): number;
|
|
16
|
+
/**
|
|
17
|
+
* Read the final logical sequence represented by one wire record.
|
|
18
|
+
* @param record - validated scalar event or packed Assistant delta run.
|
|
19
|
+
* @returns inclusive final Session sequence.
|
|
20
|
+
*/
|
|
21
|
+
export declare function historyRecordLastSeq(record: SessionHistoryRecord): number;
|
|
22
|
+
//# sourceMappingURL=history-records.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Client range access and type narrowing for aligned Session history records. */
|
|
2
|
+
/**
|
|
3
|
+
* Narrow aligned wire records to their Client event types without allocation.
|
|
4
|
+
* @param records - validated history transport records.
|
|
5
|
+
* @returns the same record array with typed inner events.
|
|
6
|
+
*/
|
|
7
|
+
export function historyEntries(records) {
|
|
8
|
+
return records;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read the first logical sequence represented by one wire record.
|
|
12
|
+
* @param record - validated scalar event or packed Assistant delta run.
|
|
13
|
+
* @returns inclusive first Session sequence.
|
|
14
|
+
*/
|
|
15
|
+
export function historyRecordFirstSeq(record) {
|
|
16
|
+
return record.event.seq;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read the final logical sequence represented by one wire record.
|
|
20
|
+
* @param record - validated scalar event or packed Assistant delta run.
|
|
21
|
+
* @returns inclusive final Session sequence.
|
|
22
|
+
*/
|
|
23
|
+
export function historyRecordLastSeq(record) {
|
|
24
|
+
if (record.type === 'event')
|
|
25
|
+
return record.event.seq;
|
|
26
|
+
const length = record.event.type === 'chunkrow/tool-call-chunks'
|
|
27
|
+
? record.event.data.args.length
|
|
28
|
+
: record.event.data.texts.length;
|
|
29
|
+
return record.event.seq + length - 1;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=history-records.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
2
|
+
import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types';
|
|
3
|
+
import type { SessionSummary } from '../../types.ts';
|
|
4
|
+
/** Host list summary enriched with the latest Session Controller title projection. */
|
|
5
|
+
export interface TitledSessionSummary extends SessionSummary {
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Current host-computed projection values for list consumers. */
|
|
8
|
+
projectionValues?: Readonly<Partial<SessionProjectionMap>>;
|
|
9
|
+
}
|
|
10
|
+
/** One flattened session-list row with lineage depth. */
|
|
11
|
+
export interface SessionListEntry {
|
|
12
|
+
sessionId: SessionId;
|
|
13
|
+
title?: string;
|
|
14
|
+
updatedAt: number;
|
|
15
|
+
running: boolean;
|
|
16
|
+
/** Empty-log bit mirrored from the summary; lists hide blank sessions (filtering stays with the consumer). */
|
|
17
|
+
blank: boolean;
|
|
18
|
+
parentSessionId?: SessionId;
|
|
19
|
+
/** Coarse durable origin for navigation filtering; not a continuation capability. */
|
|
20
|
+
origin?: 'subagent';
|
|
21
|
+
cwd?: string;
|
|
22
|
+
/** Current host-computed projection values for list consumers. */
|
|
23
|
+
projectionValues?: Readonly<Partial<SessionProjectionMap>>;
|
|
24
|
+
/** Finished running while not selected and not yet opened — the sidebar's green "done" reminder (clears on select or the next run). */
|
|
25
|
+
completed: boolean;
|
|
26
|
+
/** Lineage indent depth: root = 0; the UI just multiplies by the indent width. */
|
|
27
|
+
depth: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Summaries -> flat list with lineage indentation. Root and sibling order
|
|
31
|
+
* follows the established input order; this projection never re-sorts a
|
|
32
|
+
* hydrated list from mutable timestamps.
|
|
33
|
+
* @param summaries - the host's session.list items.
|
|
34
|
+
* @param completed - sessions with a pending completion reminder (manager-owned live fact; absent = false).
|
|
35
|
+
* @returns display rows in render order.
|
|
36
|
+
*/
|
|
37
|
+
export declare function flattenLineage(summaries: readonly TitledSessionSummary[], completed?: ReadonlySet<SessionId>): SessionListEntry[];
|
|
38
|
+
//# sourceMappingURL=lineage.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// flattenLineage: summaries -> flat list with lineage indentation (pure function).
|
|
2
|
+
// The input order is authoritative; lineage only makes each child adjacent to its parent.
|
|
3
|
+
// Orphaned lineage degrades to root level; cycles fail soft and emit as roots.
|
|
4
|
+
/**
|
|
5
|
+
* Summaries -> flat list with lineage indentation. Root and sibling order
|
|
6
|
+
* follows the established input order; this projection never re-sorts a
|
|
7
|
+
* hydrated list from mutable timestamps.
|
|
8
|
+
* @param summaries - the host's session.list items.
|
|
9
|
+
* @param completed - sessions with a pending completion reminder (manager-owned live fact; absent = false).
|
|
10
|
+
* @returns display rows in render order.
|
|
11
|
+
*/
|
|
12
|
+
export function flattenLineage(summaries, completed) {
|
|
13
|
+
const byId = new Map();
|
|
14
|
+
for (const s of summaries)
|
|
15
|
+
byId.set(s.sessionId, s);
|
|
16
|
+
const children = new Map();
|
|
17
|
+
const roots = [];
|
|
18
|
+
for (const s of summaries) {
|
|
19
|
+
if (s.parentSessionId !== undefined && byId.has(s.parentSessionId)) {
|
|
20
|
+
const list = children.get(s.parentSessionId) ?? [];
|
|
21
|
+
list.push(s);
|
|
22
|
+
children.set(s.parentSessionId, list);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
roots.push(s); // root, or an orphan whose parent is absent from summaries (degrade to root, never drop)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const out = [];
|
|
29
|
+
const visited = new Set();
|
|
30
|
+
const walk = (s, depth) => {
|
|
31
|
+
if (visited.has(s.sessionId)) {
|
|
32
|
+
console.warn(`[session-controller] lineage cycle at ${s.sessionId}; emitting as root`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
visited.add(s.sessionId);
|
|
36
|
+
out.push({
|
|
37
|
+
...s,
|
|
38
|
+
completed: completed?.has(s.sessionId) ?? false,
|
|
39
|
+
depth,
|
|
40
|
+
});
|
|
41
|
+
const kids = children.get(s.sessionId);
|
|
42
|
+
if (kids === undefined)
|
|
43
|
+
return;
|
|
44
|
+
for (const kid of kids)
|
|
45
|
+
walk(kid, depth + 1);
|
|
46
|
+
};
|
|
47
|
+
for (const root of roots)
|
|
48
|
+
walk(root, 0);
|
|
49
|
+
// Cycle members (unreachable from any root): emit as roots so no entry is lost.
|
|
50
|
+
for (const s of summaries) {
|
|
51
|
+
if (!visited.has(s.sessionId))
|
|
52
|
+
walk(s, 0);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=lineage.js.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type { SubagentAddress, SubagentCatalog } from '@deepseek-ai/dsh-subagent/client';
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
+
import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
|
|
4
|
+
import type { SessionControlFrame, SessionSummary, SessionJob as JobView } from '../../types.ts';
|
|
5
|
+
import type { RemoteFailure, RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
6
|
+
import type { SessionListEntry } from './lineage.ts';
|
|
7
|
+
import { Session } from './session.ts';
|
|
8
|
+
import type { SessionRemotes } from './remotes.ts';
|
|
9
|
+
/**
|
|
10
|
+
* List arrival lifecycle, orthogonal to the pull-activity `state` axis:
|
|
11
|
+
* `pending` (no successful pull yet — an empty items array means "nothing
|
|
12
|
+
* arrived", not "nothing exists") → `ready` (at least one pull landed).
|
|
13
|
+
* Monotone: `ready` never steps back — later pull failures and reconnect
|
|
14
|
+
* re-pulls ride the `state`/`error` axis, which is where failure is modeled
|
|
15
|
+
* (no `error` phase here; that would duplicate `state`).
|
|
16
|
+
*/
|
|
17
|
+
export type SessionListPhase = 'pending' | 'ready';
|
|
18
|
+
/** Request-local content hit returned to sidebar search consumers. */
|
|
19
|
+
export interface SessionSearchResultItem {
|
|
20
|
+
sessionId: SessionId;
|
|
21
|
+
snippet: string;
|
|
22
|
+
}
|
|
23
|
+
/** Immutable session-list snapshot for useSessionList. */
|
|
24
|
+
export interface SessionListSnapshot {
|
|
25
|
+
items: readonly SessionListEntry[];
|
|
26
|
+
/** Selected Session id (validated against items; masked to undefined while its session is off the list). */
|
|
27
|
+
current: SessionId | undefined;
|
|
28
|
+
state: 'idle' | 'loading' | 'error';
|
|
29
|
+
/** Arrival lifecycle (see {@link SessionListPhase}); `state` stays the pull-activity axis. */
|
|
30
|
+
phase: SessionListPhase;
|
|
31
|
+
error: RemoteFailure | null;
|
|
32
|
+
subagentsByParent: Readonly<Record<SessionId, SubagentCatalogSnapshot>>;
|
|
33
|
+
/** Background jobs per session; an absent key is an empty set. */
|
|
34
|
+
jobsBySession: Readonly<Record<SessionId, readonly JobView[]>>;
|
|
35
|
+
currentAddress: SubagentAddress | undefined;
|
|
36
|
+
}
|
|
37
|
+
/** One parent-addressed durable catalog projected through the sessions snapshot. */
|
|
38
|
+
export type SubagentCatalogSnapshot = Omit<SubagentCatalog, 'parentAvailable'> & {
|
|
39
|
+
/** Absent until the first successful catalog read. */
|
|
40
|
+
readonly parentAvailable?: boolean;
|
|
41
|
+
state: 'loading' | 'ready' | 'error';
|
|
42
|
+
error: RemoteFailure | null;
|
|
43
|
+
};
|
|
44
|
+
/** Instance cluster + frame entry + the session list. */
|
|
45
|
+
export declare class SessionManager {
|
|
46
|
+
private readonly remote;
|
|
47
|
+
private readonly sessions;
|
|
48
|
+
/** In-flight Session disposals remain here after instances leave `sessions`, so manager disposal can await quiescence. */
|
|
49
|
+
private readonly sessionDisposals;
|
|
50
|
+
/** Latest transient queues, retained independently of Session object materialization. */
|
|
51
|
+
private readonly queues;
|
|
52
|
+
/**
|
|
53
|
+
* Sessions that finished running while not selected — the sidebar's green
|
|
54
|
+
* "done" reminder (manager-owned, survives connection generations; cleared
|
|
55
|
+
* on select and session-removed, re-armed by the next completion).
|
|
56
|
+
*/
|
|
57
|
+
private readonly completedNotifications;
|
|
58
|
+
/** Last-observed running bits per session; the true→false edge here arms {@link completedNotifications}. */
|
|
59
|
+
private readonly prevRunning;
|
|
60
|
+
/** Per-session projection value stores, retained independently of instance arrival (the
|
|
61
|
+
* title-snapshot precedent, generalized): push frames land here whether or not the Session
|
|
62
|
+
* is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
|
|
63
|
+
* same store so history-baseline seeding and frames converge on one row set. */
|
|
64
|
+
private readonly projectionStores;
|
|
65
|
+
private summaries;
|
|
66
|
+
private listState;
|
|
67
|
+
/** Arrival phase; the pending → ready edge fires on the first successful pull (see SessionListPhase). */
|
|
68
|
+
private listPhase;
|
|
69
|
+
private listError;
|
|
70
|
+
private listInflight;
|
|
71
|
+
/** Mutations arriving after a list request starts are replayed over its response. */
|
|
72
|
+
private listMutations;
|
|
73
|
+
private readonly addresses;
|
|
74
|
+
private readonly catalogs;
|
|
75
|
+
private readonly catalogInflight;
|
|
76
|
+
/** Catalog owners whose membership changed while a pull was in flight: one trailing refresh after it settles. */
|
|
77
|
+
private readonly catalogStale;
|
|
78
|
+
private readonly openCatalogs;
|
|
79
|
+
private readonly catalogDebounce;
|
|
80
|
+
/**
|
|
81
|
+
* Background jobs per session, last-wins from Session Controller's control
|
|
82
|
+
* stream. An empty set is stored as an absent key, so absence and `[]` are
|
|
83
|
+
* one representation.
|
|
84
|
+
*/
|
|
85
|
+
private readonly jobsBySession;
|
|
86
|
+
private selected;
|
|
87
|
+
private listSnapshotCache;
|
|
88
|
+
/** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
|
|
89
|
+
* object when every field matches — wire refreshes mint all-new summary objects, so identity
|
|
90
|
+
* must be recovered by value or every SessionListItem memo misses on every refresh. */
|
|
91
|
+
private entryCache;
|
|
92
|
+
private itemsCache;
|
|
93
|
+
private readonly notifier;
|
|
94
|
+
/**
|
|
95
|
+
* @param remote - generated Remote namespaces the Session cluster calls.
|
|
96
|
+
* @param restoredSelection - persisted real-Session selection candidate.
|
|
97
|
+
*/
|
|
98
|
+
constructor(remote: SessionRemotes, restoredSelection?: SessionId, restoredAddress?: SubagentAddress);
|
|
99
|
+
/**
|
|
100
|
+
* Select a listed Session or a retained catalog-addressed child.
|
|
101
|
+
* @param sessionId - listed or catalog-addressed Session id.
|
|
102
|
+
*/
|
|
103
|
+
select(sessionId: SessionId): void;
|
|
104
|
+
/**
|
|
105
|
+
* Select a healthy child through its durable direct-parent address.
|
|
106
|
+
* @param address - catalog-derived parent and child ids.
|
|
107
|
+
*/
|
|
108
|
+
selectSubagent(address: SubagentAddress): void;
|
|
109
|
+
/** Clear the selection (the layout falls to the no-session view state). */
|
|
110
|
+
clearSelection(): void;
|
|
111
|
+
/**
|
|
112
|
+
* Return the durable catalog address retained for one child.
|
|
113
|
+
* @param sessionId - possible addressed child id.
|
|
114
|
+
* @returns The direct-parent address, when navigation discovered one.
|
|
115
|
+
*/
|
|
116
|
+
subagentAddress(sessionId: SessionId): SubagentAddress | undefined;
|
|
117
|
+
/**
|
|
118
|
+
* Resolve an address for breadcrumb navigation without retaining transport authority.
|
|
119
|
+
* @param sessionId - possible child id in an already-loaded catalog.
|
|
120
|
+
* @returns A retained or catalog-derived direct-parent address.
|
|
121
|
+
*/
|
|
122
|
+
navigationAddress(sessionId: SessionId): SubagentAddress | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* Drop a session instance (scope-prune companion: instance
|
|
125
|
+
* and scope share one lifecycle). The host session log is the durable
|
|
126
|
+
* truth — a later get() lazily rebuilds and open() backfills history.
|
|
127
|
+
* @param sessionId - the session to drop.
|
|
128
|
+
*/
|
|
129
|
+
drop(sessionId: SessionId): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Stop owned timers and every remaining Session instance.
|
|
132
|
+
* @returns when every Session Remote iterator has completed teardown.
|
|
133
|
+
*/
|
|
134
|
+
dispose(): Promise<void>;
|
|
135
|
+
private startSessionDisposal;
|
|
136
|
+
private drainSessionDisposals;
|
|
137
|
+
/**
|
|
138
|
+
* Lazy build: return the existing instance or construct one (no auto-open —
|
|
139
|
+
* open is triggered by the container's select callback).
|
|
140
|
+
* @param sessionId - the session to get.
|
|
141
|
+
* @returns the resident instance.
|
|
142
|
+
*/
|
|
143
|
+
get(sessionId: SessionId): Session;
|
|
144
|
+
private createSession;
|
|
145
|
+
/** Resident per-session projection store (create-on-demand; outlives instantiation). */
|
|
146
|
+
private projectionStore;
|
|
147
|
+
/**
|
|
148
|
+
* Refresh one direct-child catalog, reusing its in-flight request.
|
|
149
|
+
* @param parentSessionId - catalog owner.
|
|
150
|
+
*/
|
|
151
|
+
refreshSubagents(parentSessionId: SessionId): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Mark whether a catalog menu is consuming live membership updates.
|
|
154
|
+
* @param parentSessionId - catalog owner.
|
|
155
|
+
* @param open - current menu state.
|
|
156
|
+
*/
|
|
157
|
+
setSubagentCatalogOpen(parentSessionId: SessionId, open: boolean): void;
|
|
158
|
+
/** Full refresh via session.list (single-flight: an in-flight call is reused). */
|
|
159
|
+
refreshList(): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Search visible session message content without adding transient query
|
|
162
|
+
* state to the list snapshot.
|
|
163
|
+
* @param query - non-blank literal phrase.
|
|
164
|
+
* @param signal - cancellation for superseded UI queries.
|
|
165
|
+
* @returns the Host result or a folded transport error.
|
|
166
|
+
*/
|
|
167
|
+
search(query: string, signal: AbortSignal): Promise<RemoteResult<{
|
|
168
|
+
items: SessionSearchResultItem[];
|
|
169
|
+
hasMore: boolean;
|
|
170
|
+
}>>;
|
|
171
|
+
/**
|
|
172
|
+
* Contract session.create; on success merge into summaries immediately (no
|
|
173
|
+
* wait for the next refresh). A created session is blank by definition
|
|
174
|
+
* (entity birth precedes the first message).
|
|
175
|
+
* @param opts - target workspace or working directory, plus an optional caller-owned id.
|
|
176
|
+
* @returns the create result.
|
|
177
|
+
*/
|
|
178
|
+
create(opts?: {
|
|
179
|
+
workspaceId?: WorkspaceId;
|
|
180
|
+
cwd?: string;
|
|
181
|
+
sessionId?: SessionId;
|
|
182
|
+
}): Promise<RemoteResult<{
|
|
183
|
+
sessionId: SessionId;
|
|
184
|
+
}>>;
|
|
185
|
+
/**
|
|
186
|
+
* Contract session.fork; on success merge the child into summaries
|
|
187
|
+
* immediately (same synchronous-addressability guarantee as create). The
|
|
188
|
+
* child carries the source's history, so it is never blank; lineage rides
|
|
189
|
+
* parentSessionId so the list nests it under its source. A child published
|
|
190
|
+
* before Workspace attachment fails is also reconciled into the list.
|
|
191
|
+
* @param opts - source session and the optional seq anchoring the cut.
|
|
192
|
+
* @returns the fork result (the child session id).
|
|
193
|
+
*/
|
|
194
|
+
fork(opts: {
|
|
195
|
+
sessionId: SessionId;
|
|
196
|
+
atSeq?: number;
|
|
197
|
+
}): Promise<RemoteResult<{
|
|
198
|
+
sessionId: SessionId;
|
|
199
|
+
}>>;
|
|
200
|
+
/**
|
|
201
|
+
* Insert-or-enrich a locally synthesized summary: a new id prepends; an
|
|
202
|
+
* existing entry only gains fields it lacks (the session-added frame and the
|
|
203
|
+
* create() echo race — whichever lands second must fill the placeholder's
|
|
204
|
+
* missing cwd/parentSessionId, never overwrite list-refresh data).
|
|
205
|
+
*/
|
|
206
|
+
private mergeSummary;
|
|
207
|
+
/** Apply immediately and retain for replay when a list response is in flight. */
|
|
208
|
+
private recordMutation;
|
|
209
|
+
/**
|
|
210
|
+
* uSES subscription entry for useSessionList.
|
|
211
|
+
* @param listener - change callback.
|
|
212
|
+
* @returns the unsubscribe function.
|
|
213
|
+
*/
|
|
214
|
+
subscribe(listener: () => void): () => void;
|
|
215
|
+
/**
|
|
216
|
+
* Cached list snapshot (rebuilt lazily when dirty with no listeners).
|
|
217
|
+
* @returns the cached reference (stable until the next flush).
|
|
218
|
+
*/
|
|
219
|
+
getListSnapshot(): SessionListSnapshot;
|
|
220
|
+
/**
|
|
221
|
+
* Apply a complete control baseline or one later replacement frame.
|
|
222
|
+
* @param frame - baseline or live control replacement from Session Controller.
|
|
223
|
+
*/
|
|
224
|
+
handleControlFrame(frame: SessionControlFrame): void;
|
|
225
|
+
private replaceControlBaseline;
|
|
226
|
+
/**
|
|
227
|
+
* Apply one Session-list addition forwarded through `ctx.remote.$on`.
|
|
228
|
+
* @param summary - current Host summary for the added Session.
|
|
229
|
+
*/
|
|
230
|
+
handleSessionAdded(summary: SessionSummary): void;
|
|
231
|
+
/**
|
|
232
|
+
* Apply one Session removal forwarded through `ctx.remote.$on`.
|
|
233
|
+
* @param sessionId - removed Session identity.
|
|
234
|
+
*/
|
|
235
|
+
handleSessionRemoved(sessionId: SessionId): void;
|
|
236
|
+
/**
|
|
237
|
+
* Apply one live Agent running-state change.
|
|
238
|
+
* @param sessionId - Session whose Agent state changed.
|
|
239
|
+
* @param running - current Agent running state.
|
|
240
|
+
*/
|
|
241
|
+
handleSessionStatus(sessionId: SessionId, running: boolean): void;
|
|
242
|
+
/**
|
|
243
|
+
* Advance Session-list activity from one user-authored durable message.
|
|
244
|
+
* @param sessionId - Session whose activity changed.
|
|
245
|
+
* @param updatedAt - durable message timestamp.
|
|
246
|
+
*/
|
|
247
|
+
handleSessionActivity(sessionId: SessionId, updatedAt: number): void;
|
|
248
|
+
/**
|
|
249
|
+
* Surface one live Agent failure on an already-materialized Session.
|
|
250
|
+
* @param sessionId - Session whose Agent failed.
|
|
251
|
+
* @param message - caller-visible failure description.
|
|
252
|
+
*/
|
|
253
|
+
handleSessionError(sessionId: SessionId, message: string): void;
|
|
254
|
+
/**
|
|
255
|
+
* Repair one re-established Host-event generation with queryable baselines.
|
|
256
|
+
* Opened Session follow streams resume independently through API Gateway.
|
|
257
|
+
*/
|
|
258
|
+
handleConnected(): void;
|
|
259
|
+
/** Debounce membership refetches while one parent catalog is selected or open. */
|
|
260
|
+
private scheduleCatalogRefresh;
|
|
261
|
+
/** Apply one Agent-driver transition to loaded and in-flight catalogs. */
|
|
262
|
+
private updateCatalogActivity;
|
|
263
|
+
/** Preserve and project a positive expandability hint after one direct subagent publishes. */
|
|
264
|
+
private markCatalogParentExpandable;
|
|
265
|
+
/** Apply one positive expandability hint to every loaded catalog containing that unique row id. */
|
|
266
|
+
private applyCatalogParentExpandable;
|
|
267
|
+
/** Fold request-local row mutations into one catalog result before publication. */
|
|
268
|
+
private withCatalogMutations;
|
|
269
|
+
/**
|
|
270
|
+
* Reconcile completion reminders against the latest summaries, eagerly after
|
|
271
|
+
* every mutation and pull (a snapshot-build-time pass would collapse
|
|
272
|
+
* consecutive status frames into one observation). A running→idle edge of a
|
|
273
|
+
* non-selected session arms its reminder; running disarms it; removal drops
|
|
274
|
+
* it. First observation only records the running bit — sessions already
|
|
275
|
+
* idle at load get no reminder.
|
|
276
|
+
*/
|
|
277
|
+
private syncCompletedNotifications;
|
|
278
|
+
private buildListSnapshot;
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=manager.d.ts.map
|