dsh-pause-session 0.1.0 → 0.2.0
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/CHANGELOG.md +38 -0
- package/DEVELOPMENT.md +25 -2
- package/README.md +41 -31
- package/README.zh.md +31 -21
- package/REHEARSAL.md +6 -6
- package/lib/client.js +1067 -1342
- package/lib/client.js.map +1 -1
- package/lib/index.js +1041 -1020
- package/lib/index.js.map +1 -1
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +339 -0
- package/lib/typert.remote-client.d.ts +38 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +244 -0
- package/lib/types/client/control.d.ts +25 -158
- package/lib/types/client/global.d.ts +39 -239
- package/lib/types/client/index.d.ts +6 -5
- package/lib/types/client/locales.d.ts +2 -4
- package/lib/types/client/pill.d.ts +5 -5
- package/lib/types/client/remote.d.ts +71 -0
- package/lib/types/client/view.d.ts +19 -5
- package/lib/types/coldlog.d.ts +1 -1
- package/lib/types/control.d.ts +41 -60
- package/lib/types/global.d.ts +6 -4
- package/lib/types/index.d.ts +1 -1
- package/lib/types/park-state.d.ts +51 -0
- package/lib/types/registry.d.ts +1 -1
- package/lib/types/release-order.d.ts +21 -0
- package/lib/types/remote-adapter.d.ts +22 -0
- package/lib/types/remote.d.ts +158 -0
- package/lib/types/resume-activation.d.ts +27 -0
- package/lib/types/resume-text.d.ts +67 -0
- package/lib/types/resume.d.ts +5 -100
- package/lib/types/shared.d.ts +20 -123
- package/lib/types/snapshot.d.ts +29 -0
- package/lib/types/triage.d.ts +8 -9
- package/package.json +37 -18
- package/lib/types/client/burst.d.ts +0 -253
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
* @param remote - the generated Remote namespace.
|
|
51
|
+
* @param lifetime - client lifetime; aborting it releases the stream.
|
|
52
|
+
* @returns the cached source and its change subscription.
|
|
53
|
+
*/
|
|
54
|
+
export declare function openRemoteSource(remote: RemotePauseNamespace, lifetime: AbortSignal): RemoteStatusSource;
|
|
55
|
+
/** Direct browser transport over the generated namespace. */
|
|
56
|
+
export interface PauseTransport {
|
|
57
|
+
readonly source: RemoteStatusSource;
|
|
58
|
+
act(request: {
|
|
59
|
+
readonly action: 'pause' | 'resume' | 'pause-all' | 'resume-all' | 'cancel-all' | 'goal';
|
|
60
|
+
readonly sessionId: string;
|
|
61
|
+
}, signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
62
|
+
triage(caller: string, signal?: AbortSignal): Promise<readonly TriageRowWire[] | undefined>;
|
|
63
|
+
decide(caller: string, target: string, decision: 'continue' | 'note' | 'review' | 'leave' | 'goal', signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build the direct transport over one mounted Remote namespace.
|
|
67
|
+
* @param remote - the generated `pauseSession` namespace.
|
|
68
|
+
* @param lifetime - client lifetime.
|
|
69
|
+
* @returns the source and action methods the controls use.
|
|
70
|
+
*/
|
|
71
|
+
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
|
|
107
|
+
/** Whether the host answered the list read at all. */
|
|
100
108
|
readonly visible: boolean;
|
|
101
|
-
/** Whether
|
|
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
|
|
149
|
+
export declare function globalPauseView(reading: GlobalReading | undefined, sessions?: Readonly<Record<string, {
|
|
150
|
+
readonly runningTools: readonly string[];
|
|
151
|
+
}>>): GlobalPauseView;
|
package/lib/types/coldlog.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/lib/types/control.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
package/lib/types/global.d.ts
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -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;
|
package/lib/types/registry.d.ts
CHANGED
|
@@ -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
|
|
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,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The order a tree release walks: parent before child, shallowest first.
|
|
3
|
+
* @module dsh-pause-session/release-order
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
7
|
+
/** How a batch release is sequenced. */
|
|
8
|
+
export interface ReleaseOrder {
|
|
9
|
+
/** The tree edge each child was found under. */
|
|
10
|
+
readonly parents: ReadonlyMap<SessionId, SessionId>;
|
|
11
|
+
/** One list per depth, shallowest first. */
|
|
12
|
+
readonly levels: readonly (readonly SessionId[])[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sequence a batch release parent before child.
|
|
16
|
+
* @param ctx - host context carrying the optional subagent catalog.
|
|
17
|
+
* @param sessionIds - every recorded session, in registry order.
|
|
18
|
+
* @param signal - cancellation for each catalog read.
|
|
19
|
+
* @returns the parent of each child, and one list per depth level.
|
|
20
|
+
*/
|
|
21
|
+
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;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Remote transport for the pause controls.
|
|
3
|
+
*
|
|
4
|
+
* This module is deliberately self-contained: it imports only Cordis and the
|
|
5
|
+
* Typert decorators. The Typert generator analyzes this file alone so a
|
|
6
|
+
* third-party package can emit its Host reflection and Client contribution
|
|
7
|
+
* without pulling the plugin's DSH-typed modules into the generation face.
|
|
8
|
+
*
|
|
9
|
+
* The UI uses this namespace on Web and Desktop alike. No slash command is
|
|
10
|
+
* registered; the stream is the live status channel and the methods below are
|
|
11
|
+
* the button actions.
|
|
12
|
+
* @module dsh-pause-session/remote
|
|
13
|
+
*/
|
|
14
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
16
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
17
|
+
interface RemoteErrorDetailsMap {
|
|
18
|
+
/** The pause control is personal; only the owner may read or drive it. */
|
|
19
|
+
'pause/owner-only': {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** The phase a Session reports to the controls. */
|
|
23
|
+
export type PausePhaseWire = 'active' | 'draining' | 'parking' | 'safe' | 'flush-failed';
|
|
24
|
+
/** One Session's pause state. */
|
|
25
|
+
export interface SessionStatusWire {
|
|
26
|
+
readonly phase: PausePhaseWire;
|
|
27
|
+
readonly runningTools: readonly string[];
|
|
28
|
+
readonly rearmGoal?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** The whole-app verdict. */
|
|
31
|
+
export interface GlobalStatusWire {
|
|
32
|
+
readonly verdict: 'idle' | 'waiting' | 'safe';
|
|
33
|
+
readonly registered: number;
|
|
34
|
+
readonly safe: number;
|
|
35
|
+
readonly parkedAt?: string;
|
|
36
|
+
readonly draining: readonly string[];
|
|
37
|
+
readonly parking: number;
|
|
38
|
+
readonly stalled: readonly string[];
|
|
39
|
+
readonly unresumable: readonly string[];
|
|
40
|
+
readonly unreadTrees: readonly string[];
|
|
41
|
+
readonly jobs: readonly string[];
|
|
42
|
+
readonly blind: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
/** One live status snapshot: the aggregate plus every held Session. */
|
|
45
|
+
export interface PauseSnapshotWire {
|
|
46
|
+
readonly global: GlobalStatusWire;
|
|
47
|
+
readonly sessions: Readonly<Record<string, SessionStatusWire>>;
|
|
48
|
+
}
|
|
49
|
+
/** One row in the morning decision list. */
|
|
50
|
+
export interface TriageRowWire {
|
|
51
|
+
readonly sessionId: string;
|
|
52
|
+
readonly phase: 'draining' | 'parking' | 'flush-failed';
|
|
53
|
+
readonly updatedAt: number;
|
|
54
|
+
readonly reading: 'read' | 'unreadable';
|
|
55
|
+
readonly lastEvent?: string;
|
|
56
|
+
readonly turn?: number;
|
|
57
|
+
readonly step?: number;
|
|
58
|
+
readonly traces: readonly {
|
|
59
|
+
readonly tool: string;
|
|
60
|
+
readonly callId: string;
|
|
61
|
+
}[];
|
|
62
|
+
readonly owed: boolean;
|
|
63
|
+
readonly wakeable: boolean;
|
|
64
|
+
readonly rearmGoal: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** One button action. */
|
|
67
|
+
export interface PauseActionRequest {
|
|
68
|
+
readonly action: 'pause' | 'resume' | 'pause-all' | 'resume-all' | 'cancel-all' | 'goal';
|
|
69
|
+
/** The Session the control is addressed through. */
|
|
70
|
+
readonly sessionId: string;
|
|
71
|
+
}
|
|
72
|
+
/** One action outcome. */
|
|
73
|
+
export interface PauseActionResultWire {
|
|
74
|
+
readonly ok: boolean;
|
|
75
|
+
readonly message: string;
|
|
76
|
+
}
|
|
77
|
+
/** One decision-list read. */
|
|
78
|
+
export interface PauseTriageRequest {
|
|
79
|
+
readonly caller: string;
|
|
80
|
+
}
|
|
81
|
+
/** One decision on a listed Session. */
|
|
82
|
+
export interface PauseDecisionRequest {
|
|
83
|
+
readonly caller: string;
|
|
84
|
+
readonly target: string;
|
|
85
|
+
readonly decision: 'continue' | 'note' | 'review' | 'leave' | 'goal';
|
|
86
|
+
}
|
|
87
|
+
export interface PauseRemoteApi {
|
|
88
|
+
snapshot(signal?: AbortSignal): Promise<PauseSnapshotWire>;
|
|
89
|
+
subscribe(listener: () => void): () => void;
|
|
90
|
+
act(request: PauseActionRequest, signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
91
|
+
triage(request: PauseTriageRequest, signal?: AbortSignal): Promise<readonly TriageRowWire[]>;
|
|
92
|
+
decide(request: PauseDecisionRequest, signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
93
|
+
}
|
|
94
|
+
declare module '@deepseek-ai/cordis' {
|
|
95
|
+
interface Context {
|
|
96
|
+
pauseSession: PauseRemote;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Host service exposed to the browser as the `pauseSession` Remote namespace. */
|
|
100
|
+
export declare class PauseRemote extends TypertRemoteService {
|
|
101
|
+
private readonly api;
|
|
102
|
+
/** One snapshot per host version, shared by every connected observer. */
|
|
103
|
+
private snapshotCache;
|
|
104
|
+
/** Bumped by every host notification; each observer compares it to its own cursor. */
|
|
105
|
+
private version;
|
|
106
|
+
/** Observers waiting for the next version. */
|
|
107
|
+
private readonly waiters;
|
|
108
|
+
private readonly unsubscribe;
|
|
109
|
+
/**
|
|
110
|
+
* @param ctx - host context owning the service.
|
|
111
|
+
* @param api - the pause engine behind the wire methods.
|
|
112
|
+
*/
|
|
113
|
+
constructor(ctx: Context, api: PauseRemoteApi);
|
|
114
|
+
/**
|
|
115
|
+
* One exact snapshot, used by tests and by a reconnect that cannot wait for
|
|
116
|
+
* the first stream item.
|
|
117
|
+
* @param signal - carrier cancellation.
|
|
118
|
+
* @returns the current aggregate and session states.
|
|
119
|
+
*/
|
|
120
|
+
snapshot(signal?: AbortSignal): Promise<PauseSnapshotWire>;
|
|
121
|
+
/**
|
|
122
|
+
* The snapshot for one host version, computed once even when several clients
|
|
123
|
+
* are watching. A new notification discards the cached promise.
|
|
124
|
+
* @param version - the host version this snapshot answers.
|
|
125
|
+
* @param signal - stream cancellation.
|
|
126
|
+
* @returns the shared snapshot promise.
|
|
127
|
+
*/
|
|
128
|
+
private snapshotFor;
|
|
129
|
+
/**
|
|
130
|
+
* Live status: the opening snapshot, then one new snapshot whenever the host
|
|
131
|
+
* reports a change.
|
|
132
|
+
* @param signal - stream cancellation.
|
|
133
|
+
* @returns the snapshot stream.
|
|
134
|
+
*/
|
|
135
|
+
observe(signal?: AbortSignal): AsyncIterable<PauseSnapshotWire>;
|
|
136
|
+
/**
|
|
137
|
+
* Pause, resume, press every tree, resume every clean park, or flip one
|
|
138
|
+
* Session's goal re-arm.
|
|
139
|
+
* @param request - the button action.
|
|
140
|
+
* @param signal - carrier cancellation.
|
|
141
|
+
* @returns the user-facing outcome.
|
|
142
|
+
*/
|
|
143
|
+
act(request: PauseActionRequest, signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
144
|
+
/**
|
|
145
|
+
* Read the morning decision list.
|
|
146
|
+
* @param request - the calling Session whose list is read.
|
|
147
|
+
* @param signal - read cancellation.
|
|
148
|
+
* @returns the listed rows.
|
|
149
|
+
*/
|
|
150
|
+
triage(request: PauseTriageRequest, signal?: AbortSignal): Promise<readonly TriageRowWire[]>;
|
|
151
|
+
/**
|
|
152
|
+
* Take one decision on one listed Session.
|
|
153
|
+
* @param request - the caller, target, and decision.
|
|
154
|
+
* @param signal - load cancellation.
|
|
155
|
+
* @returns the user-facing outcome.
|
|
156
|
+
*/
|
|
157
|
+
decide(request: PauseDecisionRequest, signal?: AbortSignal): Promise<PauseActionResultWire>;
|
|
158
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactivate one parked session: restore its composition, give it the route its
|
|
3
|
+
* log recorded, deliver the wake it owes, and only then drop the gate and record.
|
|
4
|
+
* @module dsh-pause-session/resume-activation
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
8
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
9
|
+
import type { PauseGate } from './gate.js';
|
|
10
|
+
import type { ParkRegistry } from './registry.js';
|
|
11
|
+
import type { ResumeAttempt, ResumeOptions, WakeRequest } from './resume-text.js';
|
|
12
|
+
/** The activation entry points the resume path composes. */
|
|
13
|
+
export interface Activator {
|
|
14
|
+
activate(sessionId: SessionId, request: WakeRequest, rearm: boolean, parentId?: SessionId, signal?: AbortSignal): Promise<ResumeAttempt>;
|
|
15
|
+
ensureRoute(agent: Agent): void;
|
|
16
|
+
wake(agent: Agent, request: WakeRequest): void;
|
|
17
|
+
rearmGoal(agent: Agent): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Build the activation closure.
|
|
21
|
+
* @param ctx - host context carrying the agent store and goal service.
|
|
22
|
+
* @param gate - the pause gate released after activation.
|
|
23
|
+
* @param registry - the durable list the release forgets from.
|
|
24
|
+
* @param options - the route knowledge the wake content depends on.
|
|
25
|
+
* @returns the activation methods the resume path uses.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createActivator(ctx: Context, gate: PauseGate, registry: ParkRegistry, options: ResumeOptions): Activator;
|