dsh-pause-session 0.1.0 → 0.2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +51 -20
  2. package/DEVELOPMENT.md +25 -2
  3. package/README.md +41 -31
  4. package/README.zh.md +31 -21
  5. package/REHEARSAL.md +6 -6
  6. package/lib/client.js +1125 -1375
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +1107 -1081
  9. package/lib/index.js.map +1 -1
  10. package/lib/typert.host.d.ts +3 -0
  11. package/lib/typert.host.js +339 -0
  12. package/lib/typert.remote-client.d.ts +38 -0
  13. package/lib/typert.remote-client.d.ts.map +1 -0
  14. package/lib/typert.remote-client.js +244 -0
  15. package/lib/types/client/control.d.ts +25 -158
  16. package/lib/types/client/global.d.ts +39 -239
  17. package/lib/types/client/index.d.ts +6 -5
  18. package/lib/types/client/locales.d.ts +3 -4
  19. package/lib/types/client/pill.d.ts +5 -5
  20. package/lib/types/client/remote.d.ts +77 -0
  21. package/lib/types/client/view.d.ts +19 -5
  22. package/lib/types/coldlog.d.ts +1 -1
  23. package/lib/types/composition.d.ts +35 -61
  24. package/lib/types/control.d.ts +41 -60
  25. package/lib/types/global.d.ts +6 -4
  26. package/lib/types/index.d.ts +2 -2
  27. package/lib/types/park-state.d.ts +51 -0
  28. package/lib/types/registry.d.ts +1 -1
  29. package/lib/types/release-order.d.ts +25 -0
  30. package/lib/types/remote-adapter.d.ts +22 -0
  31. package/lib/types/remote.d.ts +158 -0
  32. package/lib/types/resume-activation.d.ts +36 -0
  33. package/lib/types/resume-text.d.ts +67 -0
  34. package/lib/types/resume.d.ts +7 -104
  35. package/lib/types/shared.d.ts +20 -123
  36. package/lib/types/snapshot.d.ts +29 -0
  37. package/lib/types/triage.d.ts +8 -9
  38. package/package.json +38 -19
  39. package/lib/types/client/burst.d.ts +0 -253
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Client-side bridge from the generated `pauseSession` Remote namespace to the
3
+ * control state machines.
4
+ *
5
+ * The native namespace is the only transport: the stream supplies snapshots and
6
+ * the action methods drive every button. No slash command is involved.
7
+ * @module dsh-pause-session/client/remote
8
+ */
9
+ import type { PauseActionResultWire, PauseDecisionRequest, PauseSnapshotWire, TriageRowWire } from '../remote.js';
10
+ import type { GlobalStatus, ParkStatus } from '../shared.js';
11
+ /** The generated namespace shape this bridge needs. */
12
+ export interface RemotePauseNamespace {
13
+ observe(signal?: AbortSignal): AsyncIterable<PauseSnapshotWire>;
14
+ act(request: {
15
+ readonly action: 'pause' | 'resume' | 'pause-all' | 'resume-all' | 'cancel-all' | 'goal';
16
+ readonly sessionId: string;
17
+ }, signal?: AbortSignal): Promise<{
18
+ readonly ok: boolean;
19
+ readonly value?: PauseActionResultWire;
20
+ readonly error?: {
21
+ readonly code: string;
22
+ };
23
+ }>;
24
+ triage(request: {
25
+ readonly caller: string;
26
+ }, signal?: AbortSignal): Promise<{
27
+ readonly ok: boolean;
28
+ readonly value?: readonly TriageRowWire[];
29
+ }>;
30
+ decide(request: PauseDecisionRequest, signal?: AbortSignal): Promise<{
31
+ readonly ok: boolean;
32
+ readonly value?: PauseActionResultWire;
33
+ }>;
34
+ }
35
+ /** The status shape shared by the controls, with branded ids erased at this boundary. */
36
+ export interface PauseSnapshotLike {
37
+ readonly global: GlobalStatus;
38
+ readonly sessions: Readonly<Record<string, ParkStatus>>;
39
+ }
40
+ /** A remote-backed status source with a change subscription. */
41
+ export interface RemoteStatusSource {
42
+ /** Read the latest host snapshot; undefined before the first answer or after failure. */
43
+ read(signal?: AbortSignal): Promise<PauseSnapshotLike | undefined>;
44
+ onChange(listener: () => void): () => void;
45
+ /** Receive each pushed snapshot directly; `undefined` means the stream failed. */
46
+ onSnapshot(listener: (snapshot: PauseSnapshotLike | undefined) => void): () => void;
47
+ }
48
+ /**
49
+ * Open the native status stream and expose its latest snapshot to both controls.
50
+ *
51
+ * The stream owns no retry of its own: a carrier can drop it on a mux reconnect,
52
+ * a host reload, or a network blip, and the controls must not stay unavailable
53
+ * until the page is refreshed. This source reconnects with a bounded backoff and
54
+ * republishes the new snapshot; while it is disconnected the last value is
55
+ * discarded, so readers see "unavailable" rather than a stale claim.
56
+ * @param remote - the generated Remote namespace.
57
+ * @param lifetime - client lifetime; aborting it releases the stream.
58
+ * @returns the cached source and its change subscription.
59
+ */
60
+ export declare function openRemoteSource(remote: RemotePauseNamespace, lifetime: AbortSignal): RemoteStatusSource;
61
+ /** Direct browser transport over the generated namespace. */
62
+ export interface PauseTransport {
63
+ readonly source: RemoteStatusSource;
64
+ act(request: {
65
+ readonly action: 'pause' | 'resume' | 'pause-all' | 'resume-all' | 'cancel-all' | 'goal';
66
+ readonly sessionId: string;
67
+ }, signal?: AbortSignal): Promise<PauseActionResultWire>;
68
+ triage(caller: string, signal?: AbortSignal): Promise<readonly TriageRowWire[] | undefined>;
69
+ decide(caller: string, target: string, decision: 'continue' | 'note' | 'review' | 'leave' | 'goal', signal?: AbortSignal): Promise<PauseActionResultWire>;
70
+ }
71
+ /**
72
+ * Build the direct transport over one mounted Remote namespace.
73
+ * @param remote - the generated `pauseSession` namespace.
74
+ * @param lifetime - client lifetime.
75
+ * @returns the source and action methods the controls use.
76
+ */
77
+ export declare function remoteTransport(remote: RemotePauseNamespace, lifetime: AbortSignal): PauseTransport;
@@ -51,7 +51,7 @@ export declare function showsPauseControl(subagent: {
51
51
  * @param reading - the host-reported reading, or `undefined` before the first answer.
52
52
  * @returns the view for that reading.
53
53
  */
54
- export declare function pauseView(reading: PauseReading | undefined): PauseView;
54
+ export declare function pauseView(reading: PauseReading | undefined, running?: boolean): PauseView;
55
55
  /** One named fact from the whole-app report, with the copy key that introduces it. */
56
56
  export interface GlobalCaveat {
57
57
  /** What this list of names is. */
@@ -60,7 +60,7 @@ export interface GlobalCaveat {
60
60
  readonly names: readonly string[];
61
61
  }
62
62
  /** Which verb a whole-app press runs. */
63
- export type GlobalAction = 'pause-all' | 'resume-all' | 'none';
63
+ export type GlobalAction = 'pause-all' | 'resume-all' | 'cancel-all' | 'none';
64
64
  /** One decision as the list renders it. */
65
65
  export interface TriageActionView {
66
66
  /** The decision this button takes. */
@@ -72,6 +72,14 @@ export interface TriageActionView {
72
72
  }
73
73
  /** The four decisions, in the order a user reads them: least to most cautious. */
74
74
  export declare const TRIAGE_ACTION_VIEWS: readonly TriageActionView[];
75
+ /**
76
+ * The decisions one row may offer. Continue and note both deliver the owed
77
+ * request, so they are absent when the log is unreadable or the host says it
78
+ * cannot wake the entry; the remaining decisions still give the user a way out.
79
+ * @param row - the rendered row.
80
+ * @returns the actions that can be taken on it.
81
+ */
82
+ export declare function triageActionsFor(row: TriageRowView): readonly TriageActionView[];
75
83
  /**
76
84
  * The fifth listed verb, rendered apart from the four decisions: it is a
77
85
  * switch on what the NEXT resume may spend, not a way to decide this row.
@@ -96,9 +104,11 @@ export interface TriageRowView {
96
104
  }
97
105
  /** What the decision-list panel renders. */
98
106
  export interface TriageView {
99
- /** Whether the panel has anything to show; a host that cannot answer shows nothing. */
107
+ /** Whether the host answered the list read at all. */
100
108
  readonly visible: boolean;
101
- /** Whether any listed session can actually be continued. */
109
+ /** Whether the last list read failed rather than returning rows. */
110
+ readonly unavailable: boolean;
111
+ /** The rows the host reported. */
102
112
  readonly rows: readonly TriageRowView[];
103
113
  }
104
114
  /**
@@ -128,10 +138,14 @@ export interface GlobalPauseView {
128
138
  readonly unclean: number;
129
139
  /** Named facts the user must not miss; rendered on the tooltip. */
130
140
  readonly caveats: readonly GlobalCaveat[];
141
+ /** Draining sessions and what each is running, when the host still has work. */
142
+ readonly running?: string;
131
143
  }
132
144
  /**
133
145
  * Map one whole-app reading to its view.
134
146
  * @param reading - the host-reported reading, or `undefined` before the first answer.
135
147
  * @returns the view for that reading.
136
148
  */
137
- export declare function globalPauseView(reading: GlobalReading | undefined): GlobalPauseView;
149
+ export declare function globalPauseView(reading: GlobalReading | undefined, sessions?: Readonly<Record<string, {
150
+ readonly runningTools: readonly string[];
151
+ }>>): GlobalPauseView;
@@ -7,7 +7,7 @@
7
7
  * list only has to name where the session stopped, which tool call the log
8
8
  * never heard back from, and whether an answer is still owed — all of them
9
9
  * facts about the log's END. Reading from seq 0 to the end for every row, on
10
- * every command-registry change while the panel is open, would make a long
10
+ * every time the panel opens, would make a long
11
11
  * session's cost grow with a log nobody is asking about.
12
12
  *
13
13
  * WHAT THE BOUND BUYS. A stored log is append-only, so the tail is stable: the
@@ -1,58 +1,34 @@
1
1
  /**
2
- * Rebuild the scoped Agent composition a parked Session ran under.
2
+ * What a parked Session needs to come back as itself.
3
3
  *
4
- * Activation is not only a persisted Session: the Agent preset supplies the
5
- * scoped tools, prompt sections, skills, and — for a subagent child — the
6
- * delegation context, persona, and tool restriction that were in force before
7
- * the park. A resume that loads the log without that setup starts a different
8
- * Agent on the same history.
4
+ * A root Session is loaded directly: the Agent preset it ran supplies the
5
+ * scoped tools, prompt sections, and skills, so the resume mounts that preset
6
+ * before the Agent is published. A subagent child is not loaded here at all —
7
+ * the subagent runtime owns its activation, composition, and tree membership,
8
+ * and cold-resumes it when this plugin delivers the wake through
9
+ * `ctx.subagents.prompt`.
9
10
  *
10
- * The preset a Session runs is the `agentPreset` projection, never the
11
- * creation-time header alone: a blank Session may select another preset before
12
- * its first turn, and the projection records the selection that actually ran.
13
- * A child with no independent selection joins its live parent's preset
14
- * generation; one that selected independently mounts the projected preset.
15
- * The durable child descriptor supplies the persona, tool filter, and route
16
- * that are reapplied only on cold resume.
17
- *
18
- * Both services are optional. A profile without `agentPresets` has no scoped
19
- * preset composition to restore, and this module leaves the resume unchanged.
20
- * A profile that mounts one but cannot read the Session projection cannot
21
- * prove what composition the Session ran, so it fails the resume instead of
22
- * silently starting a different Agent.
11
+ * The two cases are told apart by one read of the Session's own log, which is
12
+ * the same read the press-time resumability check already pays for.
23
13
  * @module dsh-pause-session/composition
24
14
  */
25
15
  import type { Context } from '@deepseek-ai/cordis';
26
- import type { Agent, AgentOptions, AgentSetup } from '@deepseek-ai/dsh-agent';
27
- import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
28
- /** The setup and route options a resumed Agent receives from its stored composition. */
29
- export interface RestoredComposition {
30
- /** The durable child route, when the descriptor recorded one. */
31
- readonly agentOptions?: AgentOptions;
16
+ import type { AgentSetup } from '@deepseek-ai/dsh-agent';
17
+ import type { SessionId } from '@deepseek-ai/dsh-session';
18
+ /** How one parked Session comes back. */
19
+ export type ParkedComposition = {
20
+ /** An ordinary Session the plugin loads itself. */
21
+ readonly kind: 'root';
32
22
  /** The scoped Agent world to mount before the Agent is published. */
33
23
  readonly setup?: AgentSetup;
34
- }
35
- /**
36
- * A stored subagent route that no resume may reactivate.
37
- *
38
- * A one-shot child is terminal by contract; activating it as a root Agent
39
- * would turn a finished delegation into an ordinary conversation that happens
40
- * to share its Session id.
41
- */
42
- export declare class UnresumableSessionError extends Error {
43
- constructor(sessionId: SessionId);
44
- }
45
- /**
46
- * Whether the child's own log selected a preset before its descriptor.
47
- *
48
- * The creation-time selection precedes the first `subagent/descriptor`; a
49
- * selection after the descriptor belongs to the child's own later history and
50
- * does not decide which composition cold resume joins. This mirrors the
51
- * subagent runtime's own cold-resume rule.
52
- * @param events - the child's own log suffix, without the fork-inherited prefix.
53
- * @returns whether the child carries an independent preset selection.
54
- */
55
- export declare function hasIndependentPresetSelection(events: readonly SessionEvent[]): boolean;
24
+ } | {
25
+ /**
26
+ * A subagent child. The subagent runtime restores its composition from the
27
+ * persisted descriptor and preset projection, so the plugin only delivers
28
+ * the wake.
29
+ */
30
+ readonly kind: 'child';
31
+ };
56
32
  /**
57
33
  * Whether a local Session may be parked under a promise of a later resume.
58
34
  *
@@ -73,22 +49,20 @@ export declare function hasIndependentPresetSelection(events: readonly SessionEv
73
49
  */
74
50
  export declare function isResumableSession(ctx: Context, sessionId: SessionId, signal?: AbortSignal): Promise<boolean>;
75
51
  /**
76
- * Read one parked Session's stored composition and build the resume options.
52
+ * Read one parked Session's own log and decide how it comes back.
53
+ *
54
+ * A child returns as `child` with no setup: the subagent runtime restores the
55
+ * composition from the descriptor and the preset projection, and mounting a
56
+ * second one here would start a different Agent on the same history.
77
57
  *
78
- * The returned `setup` belongs to the unpublished Agent scope, so all
79
- * registrations exist before `agent/created`. For a root Session the setup
80
- * mounts the Session's projected preset, or the profile's default when the
81
- * Session never selected one. For a child under a live parent it applies the
82
- * child composition: an independent preset mounts separately, while an
83
- * inherited composition joins the parent's current generation; the historical
84
- * descriptor's persona and tool filter are reapplied in both cases.
58
+ * A root resolves the preset it ran — the projection, never the creation-time
59
+ * header alone, because a blank Session may select another preset before its
60
+ * first turn — and hands back the setup that mounts it. A profile with no
61
+ * preset roster has no scoped composition to restore and resumes unchanged.
85
62
  * @param ctx - host context carrying the optional preset and Session-query services.
86
63
  * @param sessionId - the parked Session being resumed.
87
- * @param parent - the live direct parent, when a child is resumed under it.
88
64
  * @param signal - cancellation for the observation read.
89
- * @returns the Agent options and setup to pass to `agents.resume`.
90
- * @throws {UnresumableSessionError} when the stored child route is one-shot.
91
- * @throws when a mounted preset roster cannot read the Session projection, or
92
- * a recorded preset cannot be restored.
65
+ * @returns how this Session comes back.
66
+ * @throws when a mounted preset roster cannot read the Session projection.
93
67
  */
94
- export declare function restoreComposition(ctx: Context, sessionId: SessionId, parent: Agent | undefined, signal?: AbortSignal): Promise<RestoredComposition>;
68
+ export declare function readParkedComposition(ctx: Context, sessionId: SessionId, signal?: AbortSignal): Promise<ParkedComposition>;
@@ -2,55 +2,11 @@
2
2
  * The session control the browser half drives: the pause verb, the park state
3
3
  * machine behind it, and the live state channel that accompanies both.
4
4
  *
5
- * A third-party plugin can add neither a `ctx.remote` namespace nor a forwarded
6
- * event: the Client's Remote namespaces and the forwarded-event allowlist are
7
- * compiled into the harness's own API assembly. The documented Client-callable
8
- * Host entry point is therefore a session command run through
9
- * `ctx.remote.commands.execute`, and the one live channel such a plugin can
10
- * publish through is the command registry itself — `commands/change` is a
11
- * forwarded event, and `commands.list(sessionId)` is a Client-callable read.
12
- *
13
- * That same registry is how a phase change leaves this process: a transition
14
- * re-registers the status verb, which emits the change a client needs to read
15
- * the new phase. It is the only way a third-party plugin can announce anything,
16
- * and it is what makes the flush that resolves `parking` into `safe` observable
17
- * at all — that step changes no registration of its own.
18
- *
19
- * So the resume definition IS the paused marker: it is registered on a paused
20
- * session's own agent context and removed on resume, and the browser half reads
21
- * "paused" from that registration rather than inventing it. A descriptor cannot
22
- * change after registration, so the park state rides a companion status
23
- * definition (registered beside the marker) whose answer is derived per call
24
- * rather than frozen into a descriptor — see `./shared.ts`. That one read
25
- * carries the record's goal opt-out too, so the session-header control can show
26
- * and switch it while the session is parked and clean — the state in which a
27
- * silent morning resume would otherwise spend the user's rounds unasked. The
28
- * switch is addressed to the session it changes, so it needs no listed row.
29
- *
30
- * The header control is the user's button for ONE session's work, and that work
31
- * includes the subagents under it: the press registers the session and its
32
- * descendant tree, the release takes the same tree back, and the status the
33
- * header shows is the worst phase among the members the press registered. The
34
- * whole-app control covers every running tree and answers for all of them; this
35
- * one answers for the tree it owns.
36
- *
37
- * A mark is not a park. A session is parked only once nothing is left to drain:
38
- * no call in flight AND either its next model request is held at the gate or no
39
- * turn is open at all (there will be no next request to hold). Only then is the
40
- * session flushed, at the session level — `SessionHandle.flush()` does not
41
- * drain the routed live buffer, so it would record a park that a crash erases.
42
- * The "safe to close" phase appears only after that flush resolves.
43
- *
44
- * Every phase a pause reaches is written to the durable registry before it is
45
- * published — including the move into `parking`, which the status command may
46
- * therefore never serve ahead of the record. A restart re-holds exactly the
47
- * sessions the last process left parked, and a phase that could not be recorded
48
- * is never shown as cleaner than the record says.
49
- *
50
- * A park verb changes state, so it acts only on a person's command: a command
51
- * dispatched from inside a model turn is refused. The listed verbs add the
52
- * one binding their target needs — the row must be one the calling session was
53
- * shown — and that rule lives with the list (`./triage.ts`).
5
+ * The host exposes this control through the native Typert Remote namespace:
6
+ * `observe` streams snapshots and `act` / `triage` / `decide` drive the UI. No
7
+ * slash command is registered. Phase changes wake the Remote stream through a
8
+ * local listener set; the durable registry remains the source of truth for what
9
+ * the snapshot reports.
54
10
  * @module dsh-pause-session/control
55
11
  */
56
12
  import type { Context } from '@deepseek-ai/cordis';
@@ -58,14 +14,10 @@ import { SessionId } from '@deepseek-ai/dsh-session';
58
14
  import type { PauseGate } from './gate.js';
59
15
  import type { ParkWatch } from './park.js';
60
16
  import type { ParkRegistry } from './registry.js';
61
- import type { ResumeAttempt, ResumeOptions } from './resume.js';
62
- /**
63
- * Result copy for a state-changing park verb issued from inside a model turn.
64
- * The invocation carries no origin of its own, so this refusal is the plugin's
65
- * own reading of the one signal that separates a person's command from a
66
- * model's.
67
- */
68
- export declare const UNATTENDED_TEXT: string;
17
+ import type { ReleaseTreeOutcome, ResumeAttempt, ResumeOptions } from './resume.js';
18
+ import type { TriageAction } from './shared.js';
19
+ import type { PauseSnapshot, TriageList } from './shared.js';
20
+ import type { PauseAnswer } from './shared.js';
69
21
  /** What one tree-wide press registered, and what it could not. */
70
22
  export interface PressTreeOutcome {
71
23
  /** How many sessions this press covered: the session plus its descendants. */
@@ -76,8 +28,12 @@ export interface PressTreeOutcome {
76
28
  readonly rootRegistered: boolean;
77
29
  /** The members whose record could not be written; they are not paused. */
78
30
  readonly unrecorded: readonly SessionId[];
79
- /** Whether the tree walk itself failed, so the session's subagents were not covered. */
31
+ /** Whether any branch was unread, so the subagent tree was not fully covered. */
80
32
  readonly unread: boolean;
33
+ /** Tree members on a one-shot or unreadable route; they were not registered. */
34
+ readonly unresumable: readonly SessionId[];
35
+ /** Branch ids the tree walk could not read. */
36
+ readonly unreadBranches: readonly SessionId[];
81
37
  }
82
38
  /** What mounting the session control hands back to the plugin entry. */
83
39
  export interface SessionControl {
@@ -87,8 +43,33 @@ export interface SessionControl {
87
43
  * after mount.
88
44
  */
89
45
  adopt(): Promise<void>;
46
+ /**
47
+ * Read the whole app and every live paused session as one JSON-safe snapshot.
48
+ * The native snapshot serves this; it never enters a Session log.
49
+ * @param signal - cancellation for the tree reads this snapshot performs.
50
+ * @returns the whole-app verdict and the paused sessions this process holds.
51
+ */
52
+ readSnapshot(signal?: AbortSignal): Promise<PauseSnapshot>;
53
+ /** Wake a listener after any visible status change. */
54
+ subscribe(listener: () => void): () => void;
55
+ /** Press one Session tree. */
56
+ pressTree(sessionId: SessionId, signal?: AbortSignal): Promise<PressTreeOutcome>;
57
+ /** Release one Session's registered tree. */
58
+ releaseTree(sessionId: SessionId, signal?: AbortSignal): Promise<ReleaseTreeOutcome>;
59
+ /** Press every running Session tree. */
60
+ pauseAll(signal?: AbortSignal): Promise<PauseAnswer>;
61
+ /** Resume every cleanly paused Session. */
62
+ resumeAll(signal?: AbortSignal): Promise<PauseAnswer>;
63
+ /** Release every registered tree, cancelling a press that is still draining. */
64
+ cancelAll(signal?: AbortSignal): Promise<PauseAnswer>;
65
+ /** Flip one Session's goal re-arm opt-out. */
66
+ toggleGoal(sessionId: SessionId, signal?: AbortSignal): Promise<PauseAnswer>;
67
+ /** Read the morning list for one calling Session. */
68
+ readTriage(caller: SessionId, signal?: AbortSignal): Promise<TriageList>;
69
+ /** Take one listed decision for one calling Session. */
70
+ decideTriage(caller: SessionId, target: SessionId, action: TriageAction, signal?: AbortSignal): Promise<PauseAnswer>;
90
71
  }
91
- /** How resuming all parked sessions answers the command that asked for it. */
72
+ /** How resuming all parked sessions answers the caller. */
92
73
  export declare function summarizeResume(attempts: readonly ResumeAttempt[]): string;
93
74
  /**
94
75
  * Everything the session control needs from the composition: the clean resume
@@ -103,7 +84,7 @@ export interface SessionControlOptions extends ResumeOptions {
103
84
  * Contribute the session control: the pause verb, the per-session status read,
104
85
  * the global resume verb, and — for every paused session that has a live agent
105
86
  * — the resume marker.
106
- * @param ctx - host context carrying the command registry, the agent store, the
87
+ * @param ctx - host context carrying the agent store, the
107
88
  * session store, and the projection registry.
108
89
  * @param gate - the pause gate this control drives and reflects.
109
90
  * @param watch - the per-session activity fold the park predicate reads.
@@ -23,11 +23,12 @@
23
23
  * @module dsh-pause-session/global
24
24
  */
25
25
  import type { Context } from '@deepseek-ai/cordis';
26
- import type { CommandResult } from '@deepseek-ai/dsh-commands';
27
26
  import type { SessionId } from '@deepseek-ai/dsh-session';
28
27
  import type { ParkWatch } from './park.js';
29
28
  import type { ParkRegistry } from './registry.js';
30
- import type { ParkPhase } from './shared.js';
29
+ import type { GlobalStatus, ParkPhase } from './shared.js';
30
+ import type { TreeWalk } from './tree.js';
31
+ import type { PauseAnswer } from './shared.js';
31
32
  /** One session's live park state, as the per-session control sees it. */
32
33
  export interface LivePark {
33
34
  readonly phase: ParkPhase;
@@ -74,12 +75,13 @@ export interface GlobalPause {
74
75
  * @param signal - cancellation for the tree reads this action performs.
75
76
  * @returns the aggregate as of this action's end.
76
77
  */
77
- pressAll(signal?: AbortSignal): Promise<CommandResult>;
78
+ pressAll(signal?: AbortSignal): Promise<PauseAnswer>;
78
79
  /**
80
+ * The aggregate as structured data for the native Remote stream.
79
81
  * @param signal - cancellation for the tree reads this read performs.
80
82
  * @returns the aggregate as of this read.
81
83
  */
82
- readStatus(signal?: AbortSignal): Promise<CommandResult>;
84
+ readState(signal?: AbortSignal, walks?: ReadonlyMap<SessionId, TreeWalk>): Promise<GlobalStatus>;
83
85
  /**
84
86
  * Report one session a press could not register, so the aggregate names it.
85
87
  * The whole-app action records that fact for its own targets; a press of a
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { z } from 'zod';
16
16
  import type { Context } from '@deepseek-ai/cordis';
17
+ export { PauseRemote } from './remote.js';
17
18
  /** Plugin name; the Loader row and the bundle patch both use it. */
18
19
  export declare const name = "dsh-pause-session";
19
20
  /**
@@ -50,7 +51,7 @@ export type Config = z.infer<typeof ConfigShape>;
50
51
  export declare const Config: z.ZodPreprocess<z.ZodObject<{
51
52
  silentWakeRoutes: z.ZodDefault<z.ZodArray<z.ZodString>>;
52
53
  maxListedLogEvents: z.ZodDefault<z.ZodNumber>;
53
- }, z.core.$strip>>;
54
+ }, z.core.$strip>, unknown>;
54
55
  /**
55
56
  * Mount the durable pause registry, the pause gate, the activity fold, their
56
57
  * session control, and the clean resume path.
@@ -61,4 +62,3 @@ export declare const Config: z.ZodPreprocess<z.ZodObject<{
61
62
  * @param config - validated route knowledge for the resume path's wake content.
62
63
  */
63
64
  export declare function apply(ctx: Context, config?: Config): Promise<void>;
64
- export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * One session's park episode: the durable phase writes, the activity walk that
3
+ * settles them, and the press/release lifecycle. The control composes this with
4
+ * the aggregate, the resume path, and the decision list.
5
+ * @module dsh-pause-session/park-state
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import { SessionId } from '@deepseek-ai/dsh-session';
9
+ import type { PauseGate } from './gate.js';
10
+ import type { LivePark, PressResult } from './global.js';
11
+ import type { ParkWatch } from './park.js';
12
+ import type { ParkRegistry } from './registry.js';
13
+ import type { ParkPhase, PauseAnswer } from './shared.js';
14
+ /** One session's park in progress. */
15
+ export interface ParkMark {
16
+ /** The phase the durable registry confirms for this session. */
17
+ phase: ParkPhase;
18
+ /** Generation of the attempt that owns this mark. */
19
+ generation: number;
20
+ /** Whether this mark is a live press or a record adopted at mount. */
21
+ origin: 'press' | 'registry';
22
+ /** Whether a settle attempt is already walking this mark. */
23
+ advancing: boolean;
24
+ }
25
+ /** The park lifecycle the session control composes. */
26
+ export interface ParkState {
27
+ readonly marks: Map<SessionId, ParkMark>;
28
+ readonly unrecorded: Set<SessionId>;
29
+ readonly unheld: Set<SessionId>;
30
+ subscribe(listener: () => void): () => void;
31
+ notify(): void;
32
+ press(sessionId: SessionId, signal?: AbortSignal, knownResumable?: boolean): Promise<PressResult>;
33
+ /** Adopt one durable record as a mark that never advances in this process. */
34
+ adopt(sessionId: SessionId, phase: ParkPhase): void;
35
+ look(sessionId: SessionId): void;
36
+ drop(sessionId: SessionId): void;
37
+ sync(sessionId: SessionId): void;
38
+ release(sessionId: SessionId, text: string): Promise<PauseAnswer>;
39
+ markUnrecorded(sessionId: SessionId, unrecorded: boolean): void;
40
+ live(): Map<SessionId, LivePark>;
41
+ cleanup(): void;
42
+ }
43
+ /**
44
+ * Mount the park episode state machine over the gate, registry, and activity fold.
45
+ * @param ctx - host context carrying the session store and logger.
46
+ * @param gate - the pause gate this state drives.
47
+ * @param registry - the durable pause list every phase write goes to.
48
+ * @param watch - the activity fold the park predicate reads.
49
+ * @returns the park lifecycle the control composes.
50
+ */
51
+ export declare function installParkState(ctx: Context, gate: PauseGate, registry: ParkRegistry, watch: ParkWatch): ParkState;
@@ -72,7 +72,7 @@ export interface ParkRegistry {
72
72
  * The same list as {@link list}, as a lazily kept read-only view.
73
73
  *
74
74
  * The whole-app aggregate and the decision list read this once per status
75
- * read, and a status read can happen on every command-registry change; the
75
+ * read, and a status read can happen on every native-stream notification; the
76
76
  * view is rebuilt only when the stored entries actually changed, so a read
77
77
  * that changes nothing does not build a second copy of the list to look at.
78
78
  * A caller must not mutate it.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The order a batch release walks: parent before child, shallowest first.
3
+ *
4
+ * A single tree release already has the pre-order from its own walk, so this
5
+ * module serves the batch path, where the recorded sessions may span several
6
+ * trees. `listDescendants` stamps each row with its distance from the listing
7
+ * root, so one catalog read per recorded root places every member. A session
8
+ * the walk never reached is its own root and releases with the first level.
9
+ * @module dsh-pause-session/release-order
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import type { SessionId } from '@deepseek-ai/dsh-session';
13
+ /** How a batch release is sequenced. */
14
+ export interface ReleaseOrder {
15
+ /** One list per depth, shallowest first. */
16
+ readonly levels: readonly (readonly SessionId[])[];
17
+ }
18
+ /**
19
+ * Sequence a batch release parent before child.
20
+ * @param ctx - host context carrying the optional subagent catalog.
21
+ * @param sessionIds - every recorded session, in registry order.
22
+ * @param signal - cancellation for each catalog read.
23
+ * @returns one list per depth level, shallowest first.
24
+ */
25
+ export declare function releaseOrder(ctx: Context, sessionIds: readonly SessionId[], signal?: AbortSignal): Promise<ReleaseOrder>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Wire adapter between the pause engine and the generated Remote namespace.
3
+ *
4
+ * The engine speaks in branded Session ids and user-facing answer copy; the Remote
5
+ * contract is plain JSON. Mapping lives here so neither side depends on the
6
+ * other's types.
7
+ * @module dsh-pause-session/remote-adapter
8
+ */
9
+ import type { SessionControl } from './control.js';
10
+ import type { PauseRemoteApi, PauseSnapshotWire } from './remote.js';
11
+ import type { GlobalStatus, ParkStatus } from './shared.js';
12
+ /** Map the engine's snapshot to the wire shape. */
13
+ export declare function snapshotOf(snapshot: {
14
+ global: GlobalStatus;
15
+ sessions: Readonly<Record<string, ParkStatus>>;
16
+ }): PauseSnapshotWire;
17
+ /**
18
+ * Build the Remote callbacks over the mounted pause control.
19
+ * @param control - the engine's host control surface.
20
+ * @returns the wire API.
21
+ */
22
+ export declare function remoteApiOf(control: SessionControl): PauseRemoteApi;