dsh-pause-session 0.0.0-stage → 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 +79 -0
- package/DEVELOPMENT.md +78 -0
- package/LICENSE +21 -0
- package/README.md +140 -2
- package/README.zh.md +101 -0
- package/REHEARSAL.md +90 -0
- package/cordis.patch.yml +3 -0
- package/docs/images/pause-controls.webp +0 -0
- package/icon.svg +5 -0
- package/lib/client.js +2024 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +2996 -0
- package/lib/index.js.map +1 -0
- 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 +99 -0
- package/lib/types/client/global.d.ts +125 -0
- package/lib/types/client/icons.d.ts +24 -0
- package/lib/types/client/index.d.ts +78 -0
- package/lib/types/client/locales.d.ts +67 -0
- package/lib/types/client/pill.d.ts +49 -0
- package/lib/types/client/remote.d.ts +71 -0
- package/lib/types/client/view.d.ts +151 -0
- package/lib/types/coldlog.d.ts +111 -0
- package/lib/types/composition.d.ts +94 -0
- package/lib/types/control.d.ts +95 -0
- package/lib/types/gate.d.ts +110 -0
- package/lib/types/global.d.ts +103 -0
- package/lib/types/history.d.ts +47 -0
- package/lib/types/index.d.ts +64 -0
- package/lib/types/park-state.d.ts +51 -0
- package/lib/types/park.d.ts +50 -0
- package/lib/types/registry.d.ts +117 -0
- 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 +126 -0
- package/lib/types/shared.d.ts +221 -0
- package/lib/types/snapshot.d.ts +29 -0
- package/lib/types/tree.d.ts +71 -0
- package/lib/types/triage.d.ts +120 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +166 -3
|
@@ -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;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a session is doing between "pause was asked for" and "its next model
|
|
3
|
+
* request is standing still at the gate".
|
|
4
|
+
*
|
|
5
|
+
* Two facts decide that, and each has its own owner:
|
|
6
|
+
*
|
|
7
|
+
* - Whether a turn is open is derived state the harness already maintains:
|
|
8
|
+
* the agent registry owns the `turnBoundary` session projection, which folds
|
|
9
|
+
* `turn/start`, `turn/end`, `step/start`, and `step/end` into
|
|
10
|
+
* `openTurnStartSeq`. Reading it through `ctx.agents` is a synchronous
|
|
11
|
+
* projection read, not a scan of the event log (synchronous history reads are
|
|
12
|
+
* prohibited for new code).
|
|
13
|
+
* - Which tool is in flight is a log-derived per-session fact, so it is a
|
|
14
|
+
* session projection too. The projection folds each committed
|
|
15
|
+
* `tool/call` → `tool/result` pairing and keeps the calls in order; the
|
|
16
|
+
* framework owns replay, caching, and disposal. This module listens only to
|
|
17
|
+
* committed events so it can wake the control, never to keep a second copy
|
|
18
|
+
* of the state itself.
|
|
19
|
+
* @module dsh-pause-session/park
|
|
20
|
+
*/
|
|
21
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
22
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
23
|
+
/** One session's current activity, as the control reports it. */
|
|
24
|
+
export interface ParkActivity {
|
|
25
|
+
/** Whether the session is inside a turn — including one waiting for this very park. */
|
|
26
|
+
readonly turnOpen: boolean;
|
|
27
|
+
/** Names of the calls currently in flight, in call order; empty when no tool is running. */
|
|
28
|
+
readonly runningTools: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/** Both halves of the activity question for one session. */
|
|
31
|
+
export interface ParkWatch {
|
|
32
|
+
/**
|
|
33
|
+
* @param sessionId - session to inspect.
|
|
34
|
+
* @returns the session's current turn and tool activity.
|
|
35
|
+
*/
|
|
36
|
+
activity(sessionId: SessionId): ParkActivity;
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe to activity changes.
|
|
39
|
+
* @param listener - called with the session whose activity changed; it must
|
|
40
|
+
* only record that a look is owed, never re-enter the session.
|
|
41
|
+
* @returns the unsubscribe function.
|
|
42
|
+
*/
|
|
43
|
+
onChange(listener: (sessionId: SessionId) => void): () => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Mount the per-session activity projection and its notification edge.
|
|
47
|
+
* @param ctx - host context carrying the session store and the projection registry.
|
|
48
|
+
* @returns the watch the park state is derived from.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mountParkWatch(ctx: Context): Promise<ParkWatch>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable park registry: which sessions are paused, and how cleanly each
|
|
3
|
+
* one stopped.
|
|
4
|
+
*
|
|
5
|
+
* The registry lives in the profile's storage — `ctx.storageDomain`, the
|
|
6
|
+
* schema-validated domain data form over whichever backend the composition
|
|
7
|
+
* routes it to — and never in the session log: a pause is not part of a
|
|
8
|
+
* session's history, and the list has to be readable by a process that has not
|
|
9
|
+
* opened any session yet. The domain's own backend route is configuration, so
|
|
10
|
+
* this module names no path and performs no IO of its own.
|
|
11
|
+
*
|
|
12
|
+
* The domain keeps the DEFAULT whole-unit layout on purpose. The per-record
|
|
13
|
+
* layout reads a malformed or stale record document as an absent record, which
|
|
14
|
+
* here would silently drop one paused session; a whole-unit file turns any
|
|
15
|
+
* corruption into a failed `open`, and a failed open fails the plugin's mount
|
|
16
|
+
* loudly instead of resuming everything.
|
|
17
|
+
* @module dsh-pause-session/registry
|
|
18
|
+
*/
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
21
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
22
|
+
import type { ParkPhase } from './shared.js';
|
|
23
|
+
/** Domain and backend-unit name; one document per profile under the storage root. */
|
|
24
|
+
export declare const PARK_REGISTRY_DOMAIN = "pause_registry";
|
|
25
|
+
/** One session's stored park outcome. */
|
|
26
|
+
export declare const parkRecord: z.ZodObject<{
|
|
27
|
+
phase: z.ZodEnum<{
|
|
28
|
+
draining: "draining";
|
|
29
|
+
parking: "parking";
|
|
30
|
+
safe: "safe";
|
|
31
|
+
"flush-failed": "flush-failed";
|
|
32
|
+
}>;
|
|
33
|
+
updatedAt: z.ZodNumber;
|
|
34
|
+
rearmGoal: z.ZodDefault<z.ZodBoolean>;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
/** One session's stored park outcome. */
|
|
37
|
+
export type ParkRecord = z.infer<typeof parkRecord>;
|
|
38
|
+
/**
|
|
39
|
+
* The registry declaration. `version: 1` and no `compatibleVersions`: the
|
|
40
|
+
* domain layer rejects a stored unit stamped with any other version rather than
|
|
41
|
+
* migrating it, so an unrecognized registry fails loud at open. Validation is
|
|
42
|
+
* the rejecting default for the same reason — a record this build cannot read
|
|
43
|
+
* must stop the mount, not disappear from the list.
|
|
44
|
+
*/
|
|
45
|
+
export declare const parkRegistryDomain: {
|
|
46
|
+
name: string;
|
|
47
|
+
version: number;
|
|
48
|
+
tables: {
|
|
49
|
+
sessions: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<SessionId, {
|
|
50
|
+
phase: "draining" | "parking" | "safe" | "flush-failed";
|
|
51
|
+
updatedAt: number;
|
|
52
|
+
rearmGoal: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/** One session the registry remembers. */
|
|
57
|
+
export interface ParkedSession {
|
|
58
|
+
readonly sessionId: SessionId;
|
|
59
|
+
readonly phase: ParkPhase;
|
|
60
|
+
/** When the phase was written. */
|
|
61
|
+
readonly updatedAt: number;
|
|
62
|
+
/** Whether resuming this session may re-arm its goal; see {@link parkRecord}. */
|
|
63
|
+
readonly rearmGoal: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** The durable pause list, opened over the profile's storage. */
|
|
66
|
+
export interface ParkRegistry {
|
|
67
|
+
/**
|
|
68
|
+
* @returns every remembered session, in insertion order.
|
|
69
|
+
*/
|
|
70
|
+
list(): readonly ParkedSession[];
|
|
71
|
+
/**
|
|
72
|
+
* The same list as {@link list}, as a lazily kept read-only view.
|
|
73
|
+
*
|
|
74
|
+
* The whole-app aggregate and the decision list read this once per status
|
|
75
|
+
* read, and a status read can happen on every native-stream notification; the
|
|
76
|
+
* view is rebuilt only when the stored entries actually changed, so a read
|
|
77
|
+
* that changes nothing does not build a second copy of the list to look at.
|
|
78
|
+
* A caller must not mutate it.
|
|
79
|
+
*/
|
|
80
|
+
readonly parked: ReadonlyMap<SessionId, ParkedSession>;
|
|
81
|
+
/**
|
|
82
|
+
* @param sessionId - session to look up.
|
|
83
|
+
* @returns the remembered session, or `undefined` when nothing is parked under that id.
|
|
84
|
+
*/
|
|
85
|
+
get(sessionId: SessionId): ParkedSession | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Write one session's outcome; resolves once it is durable. The goal opt-out
|
|
88
|
+
* already recorded for that session survives a phase change — the phases of
|
|
89
|
+
* one park episode all describe the same parked session.
|
|
90
|
+
* @param sessionId - the session being parked.
|
|
91
|
+
* @param phase - how far the park has got.
|
|
92
|
+
*/
|
|
93
|
+
record(sessionId: SessionId, phase: ParkPhase): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Set one session's goal opt-out; resolves once it is durable.
|
|
96
|
+
* @param sessionId - the parked session to change.
|
|
97
|
+
* @param rearmGoal - whether the clean resume path may re-arm its goal.
|
|
98
|
+
* @throws when no record exists for that session — the flag describes a
|
|
99
|
+
* parked session, and inventing a record would invent a park.
|
|
100
|
+
*/
|
|
101
|
+
setRearmGoal(sessionId: SessionId, rearmGoal: boolean): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Drop one session from the list, so a later process does not re-hold it.
|
|
104
|
+
* @param sessionId - the session being released.
|
|
105
|
+
*/
|
|
106
|
+
forget(sessionId: SessionId): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Open the registry over the mounted storage. The caller owns the handle's
|
|
110
|
+
* lifetime; this registers the close as an effect on `ctx`, so the unit is
|
|
111
|
+
* released with the plugin.
|
|
112
|
+
* @param ctx - plugin context carrying `storageDomain`.
|
|
113
|
+
* @returns the open registry.
|
|
114
|
+
* @throws whatever `open` rejects with — a `StorageError` for an unreadable
|
|
115
|
+
* unit, a `DomainError('invalid-record')` for a record this build cannot read.
|
|
116
|
+
*/
|
|
117
|
+
export declare function openParkRegistry(ctx: Context): Promise<ParkRegistry>;
|
|
@@ -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;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing copy and result shapes for the clean resume and tree release.
|
|
3
|
+
* @module dsh-pause-session/resume-text
|
|
4
|
+
*/
|
|
5
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
6
|
+
import type { PauseAnswer } from './shared.js';
|
|
7
|
+
/** Route knowledge the clean resume path needs. */
|
|
8
|
+
export interface ResumeOptions {
|
|
9
|
+
/** Provider routes whose request conversion drops an empty user message. */
|
|
10
|
+
readonly silentWakeRoutes: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The one wake content a route that keeps an empty user message is given.
|
|
14
|
+
*/
|
|
15
|
+
export declare const WAKE_MARKER = "[pause: resumed from a pause]";
|
|
16
|
+
/** How one resume asks the loop for the request it owes the session. */
|
|
17
|
+
export type WakeRequest = {
|
|
18
|
+
readonly kind: 'silent';
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: 'message';
|
|
21
|
+
readonly text: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: 'none';
|
|
24
|
+
};
|
|
25
|
+
/** Result copy for a resumed session; the model never sees it. */
|
|
26
|
+
export declare const RESUMED_TEXT = "This session resumed from where it paused.";
|
|
27
|
+
/** Result copy for a registered session whose park did not finish cleanly. */
|
|
28
|
+
export declare const UNCLEAN_TEXT: string;
|
|
29
|
+
/** Result copy when nothing is parked under that session. */
|
|
30
|
+
export declare const NOT_PARKED_TEXT = "pause has no pause recorded for this session.";
|
|
31
|
+
/** Result copy for a session that is parked but could not be loaded back. */
|
|
32
|
+
export declare const ACTIVATION_FAILED_TEXT = "pause could not reopen this session, so it stayed paused.";
|
|
33
|
+
/** Result copy for a one-shot subagent route. */
|
|
34
|
+
export declare const UNRESUMABLE_TEXT = "pause did not resume this session: it is a one-shot subagent route and cannot be resumed.";
|
|
35
|
+
/** Result copy for the review decision. */
|
|
36
|
+
export declare const REVIEW_TEXT: string;
|
|
37
|
+
/** Copy for one goal opt-out change. */
|
|
38
|
+
export declare const REARM_GOAL_ON_TEXT = "pause will re-open this session's goal when it resumes it.";
|
|
39
|
+
export declare const REARM_GOAL_OFF_TEXT = "pause will leave this session's goal closed when it resumes it.";
|
|
40
|
+
/** Why one record did not produce a running session. */
|
|
41
|
+
export type ResumeRefusal = 'not-parked' | 'not-clean' | 'activation-failed' | 'unrecorded' | 'not-activated' | 'unresumable';
|
|
42
|
+
/** What one resume attempt did. */
|
|
43
|
+
export interface ResumeOutcome {
|
|
44
|
+
readonly sessionId: SessionId;
|
|
45
|
+
readonly resumed: boolean;
|
|
46
|
+
readonly refusal?: ResumeRefusal;
|
|
47
|
+
}
|
|
48
|
+
/** One resume attempt plus the answer the caller receives. */
|
|
49
|
+
export interface ResumeAttempt extends ResumeOutcome {
|
|
50
|
+
readonly answer: PauseAnswer;
|
|
51
|
+
}
|
|
52
|
+
/** What releasing one session's registered tree did. */
|
|
53
|
+
export interface ReleaseTreeOutcome {
|
|
54
|
+
readonly members: number;
|
|
55
|
+
readonly resumed: number;
|
|
56
|
+
readonly refusals: readonly {
|
|
57
|
+
readonly sessionId: SessionId;
|
|
58
|
+
readonly refusal: ResumeRefusal;
|
|
59
|
+
}[];
|
|
60
|
+
readonly answer: PauseAnswer;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* How one tree-wide release answers the person who pressed it.
|
|
64
|
+
* @param released - the sessions this release covered, in release order.
|
|
65
|
+
* @returns one sentence about what came back and what did not.
|
|
66
|
+
*/
|
|
67
|
+
export declare function summarizeTreeRelease(released: readonly ResumeAttempt[]): string;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clean resume path: bring back a session a previous process parked, and
|
|
3
|
+
* let it answer the input that was already pending when it stopped.
|
|
4
|
+
*
|
|
5
|
+
* A resume invents no turn. At the park point the loop had claimed the
|
|
6
|
+
* session's pending input onto the surface and built the request the gate then
|
|
7
|
+
* held; after a restart that input is still the last thing the session says and
|
|
8
|
+
* the model still owes it an answer. What is missing is only a reason for the
|
|
9
|
+
* loop to build a request again, and the documented way to ask for one is a
|
|
10
|
+
* wake input — a user message the loop claims.
|
|
11
|
+
*
|
|
12
|
+
* The wake is invisible only on the routes the composition declares in
|
|
13
|
+
* `silentWakeRoutes`, whose conversion drops an empty user message. On every
|
|
14
|
+
* other route the loop's request carries one visible marker instead, and the
|
|
15
|
+
* marker reaches the model; that is an accepted cost of resuming at all.
|
|
16
|
+
* Whether an empty user message survives is a property of the route's
|
|
17
|
+
* conversion rather than of the loop, so the route is resolved per session —
|
|
18
|
+
* from the request header the session itself persisted.
|
|
19
|
+
*
|
|
20
|
+
* The wake message is attributed to this plugin, never to the user: it is text
|
|
21
|
+
* this module synthesized on the session's behalf, in the session's own log and
|
|
22
|
+
* in front of the model, and a downstream consumer has to be able to tell it
|
|
23
|
+
* apart from something the human typed.
|
|
24
|
+
*
|
|
25
|
+
* Only a cleanly parked session takes this path. A record that never reached
|
|
26
|
+
* the park point, or whose flush did not finish, describes work whose durable
|
|
27
|
+
* state nobody knows; waking it would spend its owner's money on a guess, and
|
|
28
|
+
* deciding those sessions is the morning list's job, not this path's. That
|
|
29
|
+
* holds whether or not the recorded session happens to be live in this process:
|
|
30
|
+
* liveness is not the park outcome, and a session can be live without ever
|
|
31
|
+
* having stopped.
|
|
32
|
+
* @module dsh-pause-session/resume
|
|
33
|
+
*/
|
|
34
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
35
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
36
|
+
import type { PauseGate } from './gate.js';
|
|
37
|
+
import type { ParkRegistry } from './registry.js';
|
|
38
|
+
import type { PauseAnswer } from './shared.js';
|
|
39
|
+
export * from './resume-text.js';
|
|
40
|
+
import type { ReleaseTreeOutcome, ResumeAttempt, ResumeOptions, WakeRequest } from './resume-text.js';
|
|
41
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
42
|
+
interface MessageSourceMap {
|
|
43
|
+
/**
|
|
44
|
+
* Text dsh-pause-session synthesized on the session's behalf: the wake this
|
|
45
|
+
* path delivers and the unknown-outcome note a decision adds. Declared so
|
|
46
|
+
* the log, and anything reading it, can tell plugin-authored input from the
|
|
47
|
+
* user's own.
|
|
48
|
+
*/
|
|
49
|
+
'pause': {
|
|
50
|
+
kind: 'pause';
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** How this path is wired into the session control that owns the pause state. */
|
|
55
|
+
export interface ResumeDeps {
|
|
56
|
+
/**
|
|
57
|
+
* Take a live session's pause back: the durable record goes first, then the
|
|
58
|
+
* gate. Owned by the session control so there is one release order.
|
|
59
|
+
*/
|
|
60
|
+
release(sessionId: SessionId, text: string): Promise<PauseAnswer>;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the pause this process holds was adopted from the registry rather
|
|
63
|
+
* than pressed here. Only an adopted pause is a restart: a press this process
|
|
64
|
+
* made and took back must never wake a model.
|
|
65
|
+
*/
|
|
66
|
+
adopted(sessionId: SessionId): boolean;
|
|
67
|
+
}
|
|
68
|
+
/** The clean resume path, as the session control drives it. */
|
|
69
|
+
export interface ResumePath {
|
|
70
|
+
/**
|
|
71
|
+
* Resume one session the registry remembers.
|
|
72
|
+
* @param sessionId - the session to resume.
|
|
73
|
+
* @param request - which wake this resume delivers; see {@link WakeRequest}.
|
|
74
|
+
* @param parentId - the direct parent a batch release already brought back, when there is one.
|
|
75
|
+
* @param signal - cancellation for the cold load and setup.
|
|
76
|
+
* @returns what happened, and the copy for whoever asked.
|
|
77
|
+
*/
|
|
78
|
+
resumeSession(sessionId: SessionId, request: WakeRequest, parentId?: SessionId, signal?: AbortSignal): Promise<ResumeAttempt>;
|
|
79
|
+
/**
|
|
80
|
+
* Resume one session the decision list officially owns — a record that is
|
|
81
|
+
* known NOT to be clean, which the clean path above must refuse and this entry
|
|
82
|
+
* exists to carry out. Same release order, same wake vocabulary.
|
|
83
|
+
* @param sessionId - the listed session to bring back.
|
|
84
|
+
* @param request - which wake the decision delivers; see {@link WakeRequest}.
|
|
85
|
+
* @param finalize - whether the decision continues the session (`'release'`)
|
|
86
|
+
* or only answers it (`'review'`). A review activates nothing and writes
|
|
87
|
+
* nothing: opening a session to read it is the user's own action, not this
|
|
88
|
+
* plugin's.
|
|
89
|
+
* @param parentId - the direct parent a batch release already brought back, when there is one.
|
|
90
|
+
* @param signal - cancellation for the cold load and setup.
|
|
91
|
+
* @returns what happened, and the copy for whoever asked.
|
|
92
|
+
*/
|
|
93
|
+
decideUnclean(sessionId: SessionId, request: WakeRequest, finalize: 'release' | 'review', parentId?: SessionId, signal?: AbortSignal): Promise<ResumeAttempt>;
|
|
94
|
+
/**
|
|
95
|
+
* Resume every session the registry remembers, clean ones only. One session's
|
|
96
|
+
* failure never stops the others.
|
|
97
|
+
* @param signal - cancellation checked between release levels and forwarded to each load.
|
|
98
|
+
* @returns one attempt per recorded session, in registry order.
|
|
99
|
+
*/
|
|
100
|
+
resumeParked(signal?: AbortSignal): Promise<readonly ResumeAttempt[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Record or clear one session's goal opt-out.
|
|
103
|
+
* @param sessionId - the parked session to change.
|
|
104
|
+
* @param rearmGoal - whether the clean resume path may re-arm its goal.
|
|
105
|
+
* @returns the copy for whoever asked.
|
|
106
|
+
*/
|
|
107
|
+
setRearmGoal(sessionId: SessionId, rearmGoal: boolean): Promise<PauseAnswer>;
|
|
108
|
+
/**
|
|
109
|
+
* Release one parked session and the registered members of its tree: the
|
|
110
|
+
* counterpart of a press that registered a whole tree.
|
|
111
|
+
* @param sessionId - the session whose tree the user released.
|
|
112
|
+
* @param signal - cancellation for the tree read and each member's load.
|
|
113
|
+
* @returns what happened to the tree, and the copy for whoever asked.
|
|
114
|
+
*/
|
|
115
|
+
releaseTree(sessionId: SessionId, signal?: AbortSignal): Promise<ReleaseTreeOutcome>;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build the clean resume path.
|
|
119
|
+
* @param ctx - host context carrying the agent store, the session store, and the goal service when one is mounted.
|
|
120
|
+
* @param gate - the pause gate the resume releases.
|
|
121
|
+
* @param registry - the durable list this path reads and forgets from.
|
|
122
|
+
* @param options - the route knowledge the wake content depends on.
|
|
123
|
+
* @param deps - the control-owned release order and the adopted-pause fact.
|
|
124
|
+
* @returns the path the session control exposes.
|
|
125
|
+
*/
|
|
126
|
+
export declare function installResumePath(ctx: Context, gate: PauseGate, registry: ParkRegistry, options: ResumeOptions, deps: ResumeDeps): ResumePath;
|