@crazx/dsh-api-session-controller 0.1.6-alpha.1.zw.3 → 0.1.6-alpha.2.zw.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.
Files changed (35) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +15 -3
  3. package/README.zh.md +15 -3
  4. package/lib/client.js +428 -471
  5. package/lib/index.js +16 -47
  6. package/lib/typert.host.js +178 -160
  7. package/lib/typert.remote-client.js +167 -149
  8. package/lib/types/agent.d.ts +1 -1
  9. package/lib/types/agent.js +8 -1
  10. package/lib/types/client/contract/sessions.d.ts +55 -20
  11. package/lib/types/client/contract/snapshot.d.ts +0 -14
  12. package/lib/types/client/index.d.ts +11 -2
  13. package/lib/types/client/index.js +16 -5
  14. package/lib/types/client/scope.d.ts +13 -3
  15. package/lib/types/client/scope.js +15 -22
  16. package/lib/types/client/sessions/lineage.d.ts +1 -4
  17. package/lib/types/client/sessions/lineage.js +1 -3
  18. package/lib/types/client/sessions/manager.d.ts +28 -49
  19. package/lib/types/client/sessions/manager.js +75 -174
  20. package/lib/types/client/sessions/projection-store.d.ts +5 -11
  21. package/lib/types/client/sessions/projection-store.js +6 -14
  22. package/lib/types/client/sessions/service.d.ts +34 -119
  23. package/lib/types/client/sessions/service.js +259 -244
  24. package/lib/types/client/sessions/session.d.ts +7 -20
  25. package/lib/types/client/sessions/session.js +29 -33
  26. package/lib/types/commands.d.ts +2 -2
  27. package/lib/types/commands.js +10 -4
  28. package/lib/types/control.d.ts +1 -1
  29. package/lib/types/control.js +1 -41
  30. package/lib/types/index.d.ts +2 -2
  31. package/lib/types/index.js +1 -1
  32. package/lib/types/types.d.ts +3 -17
  33. package/package.json +70 -70
  34. package/lib/types/client/sessions/queue-mirror.d.ts +0 -26
  35. package/lib/types/client/sessions/queue-mirror.js +0 -61
@@ -29,7 +29,7 @@ export declare class ApiSessionPresetConflict extends Error {
29
29
  constructor(sessionId: SessionId, requestedPreset: string, existingPreset: string | undefined);
30
30
  }
31
31
  /** Failures produced while resolving one ordinary Session identity to its live Agent. */
32
- export type ApiSessionAgentError = RemoteError<'session/not-found' | 'session/agent-busy' | 'gateway/internal'>;
32
+ export type ApiSessionAgentError = RemoteError<'session/not-found' | 'session/agent-busy' | 'session/writer-held' | 'gateway/internal'>;
33
33
  /** Result of resolving one ordinary Session identity to its live Agent. */
34
34
  export type ApiSessionAgentResult = {
35
35
  readonly agent: Agent;
@@ -219,7 +219,11 @@ export class ApiSessionAgentController {
219
219
  this.resumes.set(sessionId, resume);
220
220
  }
221
221
  try {
222
- return { agent: await resume };
222
+ const agent = await resume;
223
+ // A shared resume can publish an identity that subagent routing adopts
224
+ // before every waiter observes it; apply the live ownership policy again.
225
+ const published = this.liveAgent(sessionId);
226
+ return published ?? { agent };
223
227
  }
224
228
  catch (error) {
225
229
  if (error instanceof ApiSessionNotFound) {
@@ -235,6 +239,9 @@ export class ApiSessionAgentController {
235
239
  if (racedSession !== undefined && hasApiSessionSubagentOwner(this.ctx, racedSession, undefined)) {
236
240
  return { error: apiSessionSubagentOwnershipError(sessionId) };
237
241
  }
242
+ if (error instanceof Error && error.name === 'SessionAlreadyOwnedError') {
243
+ return { error: new RemoteError('session/writer-held', error.message, { sessionId }) };
244
+ }
238
245
  return {
239
246
  error: new RemoteError('gateway/internal', `resume failed for session "${sessionId}": ${String(error)}`, {}),
240
247
  };
@@ -14,11 +14,58 @@ import type { SessionSearchResultItem } from '../sessions/manager.ts';
14
14
  import type { SessionBinding, SessionListState } from '../sessions/service.ts';
15
15
  import type { SessionFace } from './session.ts';
16
16
  import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
17
+ import type { SessionReferenceSource } from '../index.ts';
17
18
  export type { AgentContext } from '../scope.ts';
19
+ /** Known Session identity or durable direct-parent subagent address; an address owns no lifetime. */
20
+ export type SessionTarget = SessionId | SubagentAddress;
21
+ /** One independent use of an exact Client generation, without Host Agent ownership. */
22
+ export interface SessionReference extends Disposable {
23
+ readonly sessionId: SessionId;
24
+ /** Shared binding; access fails after reference release or generation disposal. */
25
+ readonly binding: SessionBinding;
26
+ /** This reference's cancellable wait for the shared initial `Session.open()` attempt to settle. */
27
+ readonly ready: Promise<SessionBinding>;
28
+ /** Release once; the final reference starts local scope and history teardown. */
29
+ release(): void;
30
+ }
31
+ /** Consumer identity and optional cancellation of one acquisition waiter. */
32
+ export interface SessionRetainOptions {
33
+ readonly source: SessionReferenceSource;
34
+ readonly signal?: AbortSignal | undefined;
35
+ }
36
+ /** Local ownership counts, independent of catalog membership and never persisted. */
37
+ export interface SessionRetainInfo {
38
+ readonly referenceCount: number;
39
+ /** Positive source counts only; a source without references is absent. */
40
+ readonly retainedBy: Readonly<Partial<Record<SessionReferenceSource, number>>>;
41
+ }
18
42
  /** The sessions-service face injected as `ctx.sessions`. */
19
43
  export interface ISessions {
20
- /** The useSessions standard feed (list rows + current selection; read face — writes stay inside the domain). */
44
+ /** Host catalog and local reference-source counts; navigation belongs to view owners. */
21
45
  readonly list: ObservableSnapshot<SessionListState>;
46
+ /**
47
+ * Retain an exact Client generation and start its shared initial history opening.
48
+ * @param target - known identity or durable direct-parent address.
49
+ * @param options - required consumer source and optional independent waiter cancellation.
50
+ * @returns an owned reference immediately; await `reference.ready` when the initial open attempt must settle first.
51
+ */
52
+ retain(target: SessionTarget, options: SessionRetainOptions): SessionReference;
53
+ /**
54
+ * Hold one reference through callback settlement, including synchronous and asynchronous failures.
55
+ * @param target - Session to acquire.
56
+ * @param options - source and acquisition cancellation.
57
+ * @param operation - callback using the reference only until its returned value or Promise settles.
58
+ * @returns the callback result after release; acquisition and callback failures propagate unchanged.
59
+ */
60
+ using<T>(target: SessionTarget, options: SessionRetainOptions, operation: (reference: SessionReference) => T | Promise<T>): Promise<T>;
61
+ /**
62
+ * Observe local reference counts without retaining, creating a scope, or opening history.
63
+ * The returned source keeps stable identity across same-id generations and remains allocated
64
+ * until the Client root is disposed, even after its final subscriber leaves.
65
+ * @param id - explicit Session identity; Host existence is not implied.
66
+ * @returns a stable read-only source across same-id generations, with zero counts when none is live.
67
+ */
68
+ retainInfo(id: SessionId): ObservableSnapshot<SessionRetainInfo>;
22
69
  /**
23
70
  * The `session.search` result bound the wire schema fixes, exposed to
24
71
  * presentation as injected data. Not per-connection state: every transport
@@ -28,23 +75,13 @@ export interface ISessions {
28
75
  /**
29
76
  * Create or adopt a Session on the Host.
30
77
  * @param opts - target workspace, directory, and optional preallocated identity.
31
- * @returns the Session identity after its local binding is addressable.
78
+ * @returns the catalogued identity; retain it before borrowing its binding.
32
79
  */
33
80
  create(opts?: {
34
81
  workspaceId?: WorkspaceId;
35
82
  cwd?: string;
36
83
  sessionId?: SessionId;
37
84
  }): Promise<SessionId>;
38
- /**
39
- * Select a session as current.
40
- * @param id - session id (must exist in the list; unknown ids fail loud).
41
- */
42
- open(id: SessionId): void;
43
- /**
44
- * Open a healthy catalog child through its exact direct-parent address.
45
- * @param address - catalog-derived parent and child ids.
46
- */
47
- openSubagent(address: SubagentAddress): void;
48
85
  /**
49
86
  * Resolve an already discovered direct-parent address without opening it.
50
87
  * @param id - possible addressed child id.
@@ -63,8 +100,6 @@ export interface ISessions {
63
100
  * @returns completion of the current or newly started refresh.
64
101
  */
65
102
  refreshSubagents(parentSessionId: SessionId): Promise<void>;
66
- /** Clear the current selection into the no-session view state. */
67
- clear(): void;
68
103
  /**
69
104
  * Refresh the Host-authoritative Session list.
70
105
  * @returns completion of the current or newly started Session-list refresh.
@@ -83,7 +118,7 @@ export interface ISessions {
83
118
  }>>;
84
119
  /**
85
120
  * Fork a session from a completed-turn prefix of the source; on resolution
86
- * the child is in the list store and `open()` can target it.
121
+ * the child is in the catalog and may be explicitly retained.
87
122
  * @param opts - source session id, the optional event seq anchoring the
88
123
  * cut (the boundary is the first turn/end at or after it; an in-log
89
124
  * anchor in an open turn is unavailable rather than clipped backward),
@@ -97,9 +132,9 @@ export interface ISessions {
97
132
  increaseTitle?: boolean;
98
133
  }): Promise<SessionId>;
99
134
  /**
100
- * Resolve an Agent-scoped context view (use-and-discard).
135
+ * Borrow an already-retained Agent-scoped Context without extending its lifetime.
101
136
  * @param id - session id.
102
- * @returns scoped ctx, or undefined for a session neither listed nor already scoped.
137
+ * @returns the live scoped Context, or undefined without a retained generation.
103
138
  */
104
139
  scope(id: SessionId): AgentContext | undefined;
105
140
  /**
@@ -112,13 +147,13 @@ export interface ISessions {
112
147
  /**
113
148
  * Resolve the session face behind an Agent-scoped context.
114
149
  * @param ctx - an Agent-scoped context.
115
- * @returns the session face, or undefined when the ctx is untagged or its scope was pruned.
150
+ * @returns the matching live Session, or undefined for an untagged, foreign, or ended generation.
116
151
  */
117
152
  sessionOf(ctx: Context): SessionFace | undefined;
118
153
  /**
119
- * Resolve the stable session binding (scope-addressed assembly feed).
154
+ * Borrow an already-retained Session binding without extending its lifetime.
120
155
  * @param id - session id.
121
- * @returns binding, or undefined for a session neither listed nor already scoped.
156
+ * @returns the live binding, or undefined without a retained generation.
122
157
  */
123
158
  binding(id: SessionId): SessionBinding | undefined;
124
159
  }
@@ -1,22 +1,9 @@
1
1
  /** Session-owned observable state excluding Conversation target data. */
2
- import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
3
2
  import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment';
4
- import type { MessageId } from '@deepseek-ai/dsh-llm/brand';
5
3
  import type { SessionId } from '@deepseek-ai/dsh-session/types';
6
4
  import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client';
7
5
  import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol';
8
6
  import type { SessionRequestId } from '../../types.ts';
9
- /** One transient inbox occurrence from the authoritative queue snapshot. */
10
- export interface QueuedMessage {
11
- readonly id: MessageId;
12
- readonly messageId: MessageId;
13
- readonly placement: 'queued' | 'steering' | 'context';
14
- /** Prompt-RPC identity of a browser-submitted occurrence; correlates the local submission echo. */
15
- readonly rpcId?: SessionRequestId;
16
- readonly content: readonly ContentBlock[];
17
- readonly preview: string;
18
- readonly text: string | null;
19
- }
20
7
  /** One image displayed by a local submission echo before durable admission. */
21
8
  export interface PendingSubmissionImage {
22
9
  /** Browser-owned preview URL; its lifecycle belongs to the submitter, never this snapshot. */
@@ -70,7 +57,6 @@ export interface PromptError {
70
57
  /** Immutable Session lifecycle and control snapshot. */
71
58
  export interface SessionSnapshot {
72
59
  readonly sessionId: SessionId;
73
- readonly queue: readonly QueuedMessage[];
74
60
  /** Local prompt-submission echoes not yet observed as durable events or queue occurrences. */
75
61
  readonly pendingSubmissions: readonly PendingSubmission[];
76
62
  readonly running: boolean;
@@ -10,10 +10,19 @@ export type { SessionListPhase, SessionListSnapshot, SessionSearchResultItem, Su
10
10
  export type { Session } from './sessions/session.ts';
11
11
  export type { ProjectionsBaseline, ProjectionValueStore, SessionProjectionMap, UseProjection, } from './sessions/projection-store.ts';
12
12
  export type { BeginSubmissionInput, ISession, PendingSubmissionRetirement, ProjectionsFace, SessionFace, SubmissionHandle, } from './contract/session.ts';
13
- export type { ISessions } from './contract/sessions.ts';
13
+ export type { ISessions, SessionReference, SessionRetainInfo, SessionRetainOptions, SessionTarget, } from './contract/sessions.ts';
14
14
  export { MutableSessionEventSource } from './contract/events.ts';
15
15
  export type { AssistantLiveChunkEvent, SessionAssistantSettlementEntry, SessionEventChange, SessionEventLike, SessionEventLikeEntry, SessionEventSource, SessionEventWindow, SessionLiveEventEntry, SessionTransientEventEntry, } from './contract/events.ts';
16
- export type { OpenState, PendingSubmission, PendingSubmissionAttachment, PendingSubmissionFileAttachment, PendingSubmissionImage, PendingSubmissionImageAttachment, PendingSubmissionPlacement, PromptError, QueuedMessage, SessionSnapshot, } from './contract/snapshot.ts';
16
+ export type { OpenState, PendingSubmission, PendingSubmissionAttachment, PendingSubmissionFileAttachment, PendingSubmissionImage, PendingSubmissionImageAttachment, PendingSubmissionPlacement, PromptError, SessionSnapshot, } from './contract/snapshot.ts';
17
+ /** Consumer-owned reference labels; extend this map through the package's canonical /client entry. */
18
+ export interface SessionReferenceSourceMap {
19
+ /** Temporary Client Controller work, including fork-title preparation. */
20
+ controllerOperation: unknown;
21
+ /** A Client Gateway invocation's synchronous Context ownership. */
22
+ gateway: unknown;
23
+ }
24
+ /** Declaration-merge-extensible labels carried by independent Client references. */
25
+ export type SessionReferenceSource = Extract<keyof SessionReferenceSourceMap, string>;
17
26
  declare module '@deepseek-ai/cordis' {
18
27
  interface Context {
19
28
  /** Client Session object layer and Agent scope owner. */
@@ -1,4 +1,5 @@
1
1
  /** Client Session object layer, Agent scopes, and Remote lifecycle wiring. */
2
+ import { typertOwnedValue } from '@deepseek-ai/dsh-typert-protocol';
2
3
  import { createSessionControlStream } from "./transport.js";
3
4
  import { ClientSessions } from "./sessions/service.js";
4
5
  export { createSessionControlStream, SessionEventStream, SESSION_SEARCH_RESULT_LIMIT, SESSION_SEARCH_SNIPPET_MAX_CODE_POINTS, } from "./transport.js";
@@ -21,6 +22,7 @@ export const inject = [
21
22
  */
22
23
  export function apply(ctx) {
23
24
  const remotes = ctx.remote;
25
+ const connection = ctx.get('connection');
24
26
  const sessions = new ClientSessions(ctx, remotes);
25
27
  ctx.remote.$on('api-session/added', (summary) => { sessions.handleSessionAdded(summary); });
26
28
  ctx.remote.$on('api-session/removed', (sessionId) => { sessions.handleSessionRemoved(sessionId); });
@@ -37,13 +39,22 @@ export function apply(ctx) {
37
39
  accept: (frame) => { sessions.handleControlFrame(frame); },
38
40
  failed: (error) => { console.error('[session-controller] control stream failed:', error); },
39
41
  });
40
- control.start();
41
- ctx.on('connection/reset', () => { sessions.handleConnected(); });
42
- if (ctx.remote.$host.home !== undefined)
42
+ const connected = () => {
43
+ if (connection.generation.getSnapshot() === undefined)
44
+ return;
45
+ // A ready control baseline may arrive before Cordis delivers connection/reset.
43
46
  sessions.handleConnected();
47
+ control.restart();
48
+ control.start();
49
+ };
50
+ ctx.effect(() => connection.generation.subscribe(connected), 'session-controller.client.generation');
51
+ connected();
44
52
  ctx.typert.contexts.registerClient('agent', {
45
- identity: candidate => sessions.scopeOf(candidate),
46
- resolve: sessionId => sessions.resolveAgentScope(sessionId),
53
+ identity: candidate => sessions.sessionOf(candidate)?.sessionId,
54
+ resolve: (sessionId) => {
55
+ const reference = sessions.retainAgentScope(sessionId);
56
+ return typertOwnedValue(reference.binding.ctx, () => { reference.release(); });
57
+ },
47
58
  });
48
59
  ctx.effect(() => async () => { await control.dispose(); }, 'session-controller.client.control');
49
60
  }
@@ -6,11 +6,14 @@ import type { TypertRemoteScopeApi } from '@deepseek-ai/dsh-typert-protocol';
6
6
  export type AgentContext = Omit<Context, 'remote'> & {
7
7
  readonly remote: ClientRemote & TypertRemoteScopeApi<'agent'>;
8
8
  };
9
+ interface ScopeIdentity {
10
+ readonly sessionId: SessionId;
11
+ }
9
12
  /** A minted Agent scope and its disposal boundary. */
10
13
  export interface AgentScopeHandle {
11
14
  /**
12
15
  * Tagged context: scope-owned registrations and scoped dispatch both go
13
- * through it (passing it as the dispatch subject routes to this agent's
16
+ * through it (passing it as the dispatch subject routes to this generation's
14
17
  * tagged listeners plus every untagged one).
15
18
  */
16
19
  ctx: AgentContext;
@@ -20,10 +23,10 @@ export interface AgentScopeHandle {
20
23
  /**
21
24
  * Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
22
25
  * carries the agent tag and the dispatch filter — untagged listeners are
23
- * admitted globally, tagged listeners only for a matching agent.
26
+ * admitted globally, tagged listeners only for the same Client generation.
24
27
  * Registrations through the returned ctx dispose with the fiber.
25
28
  * @param ctx - client root context the scope fiber mounts under.
26
- * @param key - owning agent identity (the routing tag; agent id === session id).
29
+ * @param key - durable Session identity carried by this generation.
27
30
  * @returns the tagged context and its backing fiber.
28
31
  */
29
32
  export declare function createScope(ctx: Context, key: SessionId): AgentScopeHandle;
@@ -33,4 +36,11 @@ export declare function createScope(ctx: Context, key: SessionId): AgentScopeHan
33
36
  * @returns its agent identity (the session id), or undefined for root contexts.
34
37
  */
35
38
  export declare function scopeOf(ctx: Context): SessionId | undefined;
39
+ /**
40
+ * Read the exact generation identity inherited by a Client Context.
41
+ * @param ctx - scoped or root Client Context.
42
+ * @returns the generation identity, or undefined for an unscoped Context.
43
+ */
44
+ export declare function scopeIdentityOf(ctx: Context): ScopeIdentity | undefined;
45
+ export {};
36
46
  //# sourceMappingURL=scope.d.ts.map
@@ -1,20 +1,4 @@
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
- */
1
+ /** Client scope generations route local events independently of Host Agent residency. */
18
2
  import { Context as CordisContext } from '@deepseek-ai/cordis';
19
3
  /** Context tag written by {@link createScope}. */
20
4
  const kScope = Symbol('dsh.client.scope');
@@ -23,19 +7,20 @@ function agentScope() { }
23
7
  /**
24
8
  * Mint an Agent scope under `ctx`: a no-op plugin fiber whose context
25
9
  * carries the agent tag and the dispatch filter — untagged listeners are
26
- * admitted globally, tagged listeners only for a matching agent.
10
+ * admitted globally, tagged listeners only for the same Client generation.
27
11
  * Registrations through the returned ctx dispose with the fiber.
28
12
  * @param ctx - client root context the scope fiber mounts under.
29
- * @param key - owning agent identity (the routing tag; agent id === session id).
13
+ * @param key - durable Session identity carried by this generation.
30
14
  * @returns the tagged context and its backing fiber.
31
15
  */
32
16
  export function createScope(ctx, key) {
33
17
  const fiber = ctx.plugin(agentScope);
18
+ const identity = { sessionId: key };
34
19
  const scoped = fiber.ctx.extend({
35
- [kScope]: key,
20
+ [kScope]: identity,
36
21
  [CordisContext.filter](listenerCtx) {
37
- const tag = scopeOf(listenerCtx);
38
- return tag === undefined || tag === key;
22
+ const tag = scopeIdentityOf(listenerCtx);
23
+ return tag === undefined || tag === identity;
39
24
  },
40
25
  });
41
26
  return {
@@ -49,6 +34,14 @@ export function createScope(ctx, key) {
49
34
  * @returns its agent identity (the session id), or undefined for root contexts.
50
35
  */
51
36
  export function scopeOf(ctx) {
37
+ return scopeIdentityOf(ctx)?.sessionId;
38
+ }
39
+ /**
40
+ * Read the exact generation identity inherited by a Client Context.
41
+ * @param ctx - scoped or root Client Context.
42
+ * @returns the generation identity, or undefined for an unscoped Context.
43
+ */
44
+ export function scopeIdentityOf(ctx) {
52
45
  return ctx[kScope];
53
46
  }
54
47
  //# sourceMappingURL=scope.js.map
@@ -21,8 +21,6 @@ export interface SessionListEntry {
21
21
  cwd?: string;
22
22
  /** Current host-computed projection values for list consumers. */
23
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
24
  /** Lineage indent depth: root = 0; the UI just multiplies by the indent width. */
27
25
  depth: number;
28
26
  }
@@ -31,8 +29,7 @@ export interface SessionListEntry {
31
29
  * follows the established input order; this projection never re-sorts a
32
30
  * hydrated list from mutable timestamps.
33
31
  * @param summaries - the host's session.list items.
34
- * @param completed - sessions with a pending completion reminder (manager-owned live fact; absent = false).
35
32
  * @returns display rows in render order.
36
33
  */
37
- export declare function flattenLineage(summaries: readonly TitledSessionSummary[], completed?: ReadonlySet<SessionId>): SessionListEntry[];
34
+ export declare function flattenLineage(summaries: readonly TitledSessionSummary[]): SessionListEntry[];
38
35
  //# sourceMappingURL=lineage.d.ts.map
@@ -6,10 +6,9 @@
6
6
  * follows the established input order; this projection never re-sorts a
7
7
  * hydrated list from mutable timestamps.
8
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
9
  * @returns display rows in render order.
11
10
  */
12
- export function flattenLineage(summaries, completed) {
11
+ export function flattenLineage(summaries) {
13
12
  const byId = new Map();
14
13
  for (const s of summaries)
15
14
  byId.set(s.sessionId, s);
@@ -35,7 +34,6 @@ export function flattenLineage(summaries, completed) {
35
34
  visited.add(s.sessionId);
36
35
  out.push({
37
36
  ...s,
38
- completed: completed?.has(s.sessionId) ?? false,
39
37
  depth,
40
38
  });
41
39
  const kids = children.get(s.sessionId);
@@ -1,11 +1,14 @@
1
+ /** Host catalog, durable projection caches, and explicitly retained Client instances. */
1
2
  import type { SubagentAddress, SubagentCatalog } from '@deepseek-ai/dsh-subagent/client';
2
3
  import { SessionSeq, type SessionId } from '@deepseek-ai/dsh-session/types';
4
+ import type { SessionProjectionMap } from '@deepseek-ai/dsh-session-projection/types';
3
5
  import type { WorkspaceId } from '@deepseek-ai/dsh-workspace/types';
4
6
  import type { SessionControlFrame, SessionSummary, SessionJob as JobView } from '../../types.ts';
5
7
  import type { RemoteFailure, RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
6
8
  import type { SessionListEntry } from './lineage.ts';
7
9
  import { Session } from './session.ts';
8
10
  import type { SessionRemotes } from './remotes.ts';
11
+ import type { SessionTarget } from '../contract/sessions.ts';
9
12
  /**
10
13
  * List arrival lifecycle, orthogonal to the pull-activity `state` axis:
11
14
  * `pending` (no successful pull yet — an empty items array means "nothing
@@ -23,8 +26,6 @@ export interface SessionSearchResultItem {
23
26
  /** Immutable session-list snapshot for useSessionList. */
24
27
  export interface SessionListSnapshot {
25
28
  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
29
  state: 'idle' | 'loading' | 'error';
29
30
  /** Arrival lifecycle (see {@link SessionListPhase}); `state` stays the pull-activity axis. */
30
31
  phase: SessionListPhase;
@@ -32,7 +33,6 @@ export interface SessionListSnapshot {
32
33
  subagentsByParent: Readonly<Record<SessionId, SubagentCatalogSnapshot>>;
33
34
  /** Background jobs per session; an absent key is an empty set. */
34
35
  jobsBySession: Readonly<Record<SessionId, readonly JobView[]>>;
35
- currentAddress: SubagentAddress | undefined;
36
36
  }
37
37
  /** One parent-addressed durable catalog projected through the sessions snapshot. */
38
38
  export type SubagentCatalogSnapshot = Omit<SubagentCatalog, 'parentAvailable'> & {
@@ -47,16 +47,6 @@ export declare class SessionManager {
47
47
  private readonly sessions;
48
48
  /** In-flight Session disposals remain here after instances leave `sessions`, so manager disposal can await quiescence. */
49
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
50
  /** Per-session projection value stores, retained independently of instance arrival (the
61
51
  * title-snapshot precedent, generalized): push frames land here whether or not the Session
62
52
  * is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
@@ -68,7 +58,7 @@ export declare class SessionManager {
68
58
  private listPhase;
69
59
  private listError;
70
60
  private listInflight;
71
- /** Mutations arriving after a list request starts are replayed over its response. */
61
+ /** Active list request's mutation log; its identity also fences completion after reconnect. */
72
62
  private listMutations;
73
63
  private readonly addresses;
74
64
  private readonly catalogs;
@@ -83,7 +73,6 @@ export declare class SessionManager {
83
73
  * one representation.
84
74
  */
85
75
  private readonly jobsBySession;
86
- private selected;
87
76
  private listSnapshotCache;
88
77
  /** Entry-identity cache (reference stability): list rebuilds reuse the previous entry
89
78
  * object when every field matches — wire refreshes mint all-new summary objects, so identity
@@ -91,23 +80,14 @@ export declare class SessionManager {
91
80
  private entryCache;
92
81
  private itemsCache;
93
82
  private readonly notifier;
83
+ /** @param remote - generated Remote namespaces used by catalog and history readers. */
84
+ constructor(remote: SessionRemotes);
94
85
  /**
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.
86
+ * Resolve an acquisition target without materializing a Session.
87
+ * @param target - known identity or durable direct-parent address.
88
+ * @returns the resolved identity with its explicit or catalog-derived history route installed.
107
89
  */
108
- selectSubagent(address: SubagentAddress): void;
109
- /** Clear the selection (the layout falls to the no-session view state). */
110
- clearSelection(): void;
90
+ resolveTarget(target: SessionTarget): SessionId;
111
91
  /**
112
92
  * Return the durable catalog address retained for one child.
113
93
  * @param sessionId - possible addressed child id.
@@ -121,22 +101,22 @@ export declare class SessionManager {
121
101
  */
122
102
  navigationAddress(sessionId: SessionId): SubagentAddress | undefined;
123
103
  /**
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.
104
+ * Withdraw an exact Client instance before running its teardown callbacks.
105
+ * @param sessionId - identity to withdraw.
106
+ * @param expected - instance being released; a replacement is left untouched.
107
+ * @returns completion of the detached instance's stream teardown.
128
108
  */
129
- drop(sessionId: SessionId): Promise<void>;
109
+ drop(sessionId: SessionId, expected: Session): Promise<void>;
130
110
  /**
131
- * Stop owned timers and every remaining Session instance.
132
- * @returns when every Session Remote iterator has completed teardown.
111
+ * Stop catalog requests and dispose every resident Session.
112
+ * @returns once catalog requests and every Session stream have stopped.
133
113
  */
134
114
  dispose(): Promise<void>;
135
115
  private startSessionDisposal;
136
116
  private drainSessionDisposals;
137
117
  /**
138
118
  * Lazy build: return the existing instance or construct one (no auto-open —
139
- * open is triggered by the container's select callback).
119
+ * the reference allocator opens history after binding the scope).
140
120
  * @param sessionId - the session to get.
141
121
  * @returns the resident instance.
142
122
  */
@@ -155,7 +135,7 @@ export declare class SessionManager {
155
135
  * @param open - current menu state.
156
136
  */
157
137
  setSubagentCatalogOpen(parentSessionId: SessionId, open: boolean): void;
158
- /** Full refresh via session.list (single-flight: an in-flight call is reused). */
138
+ /** Full refresh via session.list (single-flight within one Host generation). */
159
139
  refreshList(): Promise<void>;
160
140
  /**
161
141
  * Search visible session message content without adding transient query
@@ -217,6 +197,12 @@ export declare class SessionManager {
217
197
  * @returns the cached reference (stable until the next flush).
218
198
  */
219
199
  getListSnapshot(): SessionListSnapshot;
200
+ /**
201
+ * Read cached projection values for a Session that may exist only in a loaded subagent catalog.
202
+ * @param sessionId - Session whose control or history baseline supplied projections.
203
+ * @returns current values, or undefined before any projection store exists.
204
+ */
205
+ projectionValues(sessionId: SessionId): Readonly<Partial<SessionProjectionMap>> | undefined;
220
206
  /**
221
207
  * Apply a complete control baseline or one later replacement frame.
222
208
  * @param frame - baseline or live control replacement from Session Controller.
@@ -253,10 +239,12 @@ export declare class SessionManager {
253
239
  handleSessionError(sessionId: SessionId, message: string): void;
254
240
  /**
255
241
  * Repair one re-established Host-event generation with queryable baselines.
242
+ * Discard old projection cuts before new queries, including cold Sessions
243
+ * absent from the process-local control baseline.
256
244
  * Opened Session follow streams resume independently through API Gateway.
257
245
  */
258
246
  handleConnected(): void;
259
- /** Debounce membership refetches while one parent catalog is selected or open. */
247
+ /** Debounce membership refetches for an explicitly consumed catalog. */
260
248
  private scheduleCatalogRefresh;
261
249
  /** Apply one Agent-driver transition to loaded and in-flight catalogs. */
262
250
  private updateCatalogActivity;
@@ -266,15 +254,6 @@ export declare class SessionManager {
266
254
  private applyCatalogParentExpandable;
267
255
  /** Fold request-local row mutations into one catalog result before publication. */
268
256
  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
257
  private buildListSnapshot;
279
258
  }
280
259
  //# sourceMappingURL=manager.d.ts.map