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,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side reading and driving of the host's park state for one session.
|
|
3
|
+
*
|
|
4
|
+
* The browser reads the native `pauseSession` stream; actions travel through
|
|
5
|
+
* the same Remote namespace. No slash command is involved. The stream is
|
|
6
|
+
* host-authoritative, so this module never derives a state from its own press.
|
|
7
|
+
* The snapshot also carries the record's goal opt-out, so the switch beside the
|
|
8
|
+
* pause button can show it without a second host round-trip.
|
|
9
|
+
*
|
|
10
|
+
* The stream is the change feed. A pushed snapshot triggers one cached read per
|
|
11
|
+
* shown header; a press or goal switch reads the host's answer once after the
|
|
12
|
+
* action.
|
|
13
|
+
* @module dsh-pause-session/client/control
|
|
14
|
+
*/
|
|
15
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
16
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
17
|
+
import type { PauseTransport } from './remote.js';
|
|
18
|
+
/**
|
|
19
|
+
* What the Session-header control knows about one session: what the host
|
|
20
|
+
* reported, or that no answer has arrived (`undefined`) or the last read
|
|
21
|
+
* failed. Neither of the latter claims a state the host never reported.
|
|
22
|
+
*
|
|
23
|
+
* `draining` and `parking` are the host's own two answers on the way to
|
|
24
|
+
* `safe`; the control shows them rather than rounding them up, because only
|
|
25
|
+
* `safe` means the pending input is durably stored.
|
|
26
|
+
*/
|
|
27
|
+
export type PausePhase = 'active' | 'draining' | 'parking' | 'safe' | 'flush-failed' | 'unavailable';
|
|
28
|
+
/** One host answer about one session. */
|
|
29
|
+
export interface PauseReading {
|
|
30
|
+
readonly phase: PausePhase;
|
|
31
|
+
/** Tools the host reported in flight; empty unless the phase is `draining`. */
|
|
32
|
+
readonly runningTools: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Whether the host's own record lets the next resume re-open this session's
|
|
35
|
+
* goal. Absent — not `true` — when the host did not say: the control shows no
|
|
36
|
+
* switch rather than the product default, because a default here would tell
|
|
37
|
+
* the user their rounds are safe when nobody has answered.
|
|
38
|
+
*/
|
|
39
|
+
readonly rearmGoal?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Per-session control state, keyed by Session id so a slot selector can watch one session. */
|
|
42
|
+
export interface PauseState {
|
|
43
|
+
/** Reading per Session id; absent means no host answer yet. Writable: the store updates through a draft. */
|
|
44
|
+
bySession: Record<string, PauseReading | undefined>;
|
|
45
|
+
}
|
|
46
|
+
/** Owns the published reading per session; nothing else writes that state. */
|
|
47
|
+
export declare class PauseControl {
|
|
48
|
+
private readonly transport;
|
|
49
|
+
private readonly lifetime;
|
|
50
|
+
/** State source the Session-header contribution binds its selector hook to. */
|
|
51
|
+
readonly store: SnapshotStore<PauseState>;
|
|
52
|
+
/** Sessions this control has shown; a pushed snapshot applies to every one. */
|
|
53
|
+
private readonly shown;
|
|
54
|
+
/** Highest read sequence requested per session: an answer older than it is dropped. */
|
|
55
|
+
private readonly latest;
|
|
56
|
+
private reads;
|
|
57
|
+
/**
|
|
58
|
+
* @param transport - the native namespace source and actions.
|
|
59
|
+
* @param lifetime - aborts in-flight reads when the plugin unloads.
|
|
60
|
+
*/
|
|
61
|
+
constructor(transport: PauseTransport, lifetime: AbortSignal);
|
|
62
|
+
/**
|
|
63
|
+
* Read the host's state for one session.
|
|
64
|
+
* @param sessionId - session whose control was rendered.
|
|
65
|
+
* @returns after this read's answer is published or superseded by a newer one.
|
|
66
|
+
*/
|
|
67
|
+
refresh(sessionId: SessionId): Promise<void>;
|
|
68
|
+
/** Apply one pushed snapshot without spending a read per shown header. */
|
|
69
|
+
apply(snapshot: {
|
|
70
|
+
readonly sessions: Readonly<Record<string, {
|
|
71
|
+
readonly phase: PausePhase;
|
|
72
|
+
readonly runningTools: readonly string[];
|
|
73
|
+
readonly rearmGoal?: boolean;
|
|
74
|
+
}>>;
|
|
75
|
+
} | undefined): void;
|
|
76
|
+
/** A new pushed snapshot may have moved every header; read them once. */
|
|
77
|
+
refreshAll(): void;
|
|
78
|
+
private readAll;
|
|
79
|
+
/** One session's read, published unless a newer read of that session supersedes it. */
|
|
80
|
+
private readOne;
|
|
81
|
+
/**
|
|
82
|
+
* Press the control: pause an active session, or take back a pause that is
|
|
83
|
+
* still on its way in (or already settled). The phase shown afterwards is the
|
|
84
|
+
* host's answer, never an assumed flip.
|
|
85
|
+
* @param sessionId - session whose control was pressed.
|
|
86
|
+
* @returns after the control re-reads the host.
|
|
87
|
+
*/
|
|
88
|
+
toggle(sessionId: SessionId): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Flip this session's goal opt-out. The verb belongs to the session it
|
|
91
|
+
* changes; the switch shows the host's next answer, never a local flip.
|
|
92
|
+
* @param sessionId - session whose switch was pressed.
|
|
93
|
+
* @returns after the control re-reads the host.
|
|
94
|
+
*/
|
|
95
|
+
toggleGoal(sessionId: SessionId): Promise<void>;
|
|
96
|
+
/** Ask the native stream what this session's park state is. */
|
|
97
|
+
private read;
|
|
98
|
+
private publish;
|
|
99
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side reading and driving of the host's whole-app park state.
|
|
3
|
+
*
|
|
4
|
+
* The aggregate arrives from the native `pauseSession` stream. The stream is
|
|
5
|
+
* process-wide and writes nothing into any Session; actions travel through the
|
|
6
|
+
* same Remote namespace. A pushed snapshot is applied directly; a settled park
|
|
7
|
+
* costs nothing while its last answer stays on screen.
|
|
8
|
+
* @module dsh-pause-session/client/global
|
|
9
|
+
*/
|
|
10
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
11
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
12
|
+
import type { GlobalStatus, TriageAction, TriageRow } from '../shared.js';
|
|
13
|
+
import type { PauseTransport } from './remote.js';
|
|
14
|
+
/** What the whole-app control knows: the host's answer, or that it could not ask. */
|
|
15
|
+
export type GlobalReading = {
|
|
16
|
+
readonly kind: 'reported';
|
|
17
|
+
readonly status: GlobalStatus;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'unavailable';
|
|
20
|
+
};
|
|
21
|
+
/** What the decision-list panel knows: the host's rows, or that it could not ask. */
|
|
22
|
+
export type TriageReading = {
|
|
23
|
+
readonly kind: 'reported';
|
|
24
|
+
readonly rows: readonly TriageRow[];
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: 'unavailable';
|
|
27
|
+
};
|
|
28
|
+
/** Whole-app control state. */
|
|
29
|
+
export interface GlobalPauseState {
|
|
30
|
+
/** The host's answer; absent until one arrives. Writable: the store updates through a draft. */
|
|
31
|
+
global: GlobalReading | undefined;
|
|
32
|
+
/** Whether the user asked for the decision list; the panel reads only when it is open. */
|
|
33
|
+
panelOpen: boolean;
|
|
34
|
+
/** The host's decision list; absent until the panel asks for it. */
|
|
35
|
+
triage: TriageReading | undefined;
|
|
36
|
+
/** The last action or read refusal, shown verbatim in the panel. */
|
|
37
|
+
error?: string;
|
|
38
|
+
/** The last snapshot's per-session details, for the draining progress copy. */
|
|
39
|
+
sessions?: Readonly<Record<string, {
|
|
40
|
+
readonly runningTools: readonly string[];
|
|
41
|
+
}>>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The session a whole-app action is addressed to.
|
|
45
|
+
*
|
|
46
|
+
* A subagent's own session is never addressed: its agent belongs to a parent
|
|
47
|
+
* and is not the host's business to drive from the shell. Among the rest, the
|
|
48
|
+
* one on screen is the best address (its agent is live by construction), then
|
|
49
|
+
* any session the host reports as running, then any at all.
|
|
50
|
+
* @param state - the session list snapshot the shell provides.
|
|
51
|
+
* @returns the address, or `undefined` when the app has no session to ask through.
|
|
52
|
+
*/
|
|
53
|
+
export declare function addressingSession(state: {
|
|
54
|
+
readonly ids: readonly SessionId[];
|
|
55
|
+
readonly byId: Readonly<Record<string, {
|
|
56
|
+
readonly running?: boolean;
|
|
57
|
+
readonly origin?: string;
|
|
58
|
+
readonly parentId?: unknown;
|
|
59
|
+
readonly retainedBy?: {
|
|
60
|
+
readonly mainView?: number;
|
|
61
|
+
};
|
|
62
|
+
} | undefined>>;
|
|
63
|
+
}): SessionId | undefined;
|
|
64
|
+
/** Owns the published whole-app reading; nothing else writes that state. */
|
|
65
|
+
export declare class PauseGlobalControl {
|
|
66
|
+
private readonly transport;
|
|
67
|
+
private readonly lifetime;
|
|
68
|
+
/** State source the whole-app contribution binds its selector hook to. */
|
|
69
|
+
readonly store: SnapshotStore<GlobalPauseState>;
|
|
70
|
+
/** The session the shell asked this control through; list reads use it. */
|
|
71
|
+
private address;
|
|
72
|
+
/** Highest decision-list read requested: an answer older than it is dropped. */
|
|
73
|
+
private latestTriage;
|
|
74
|
+
/**
|
|
75
|
+
* @param transport - the native namespace source and actions.
|
|
76
|
+
* @param lifetime - aborts in-flight reads when the plugin unloads.
|
|
77
|
+
*/
|
|
78
|
+
constructor(transport: PauseTransport, lifetime: AbortSignal);
|
|
79
|
+
/**
|
|
80
|
+
* The shell handed this control a session. The first call reads the aggregate;
|
|
81
|
+
* later calls only move where list reads and actions are addressed.
|
|
82
|
+
* @param sessionId - the session the shell asks through.
|
|
83
|
+
*/
|
|
84
|
+
refresh(sessionId: SessionId): Promise<void>;
|
|
85
|
+
/** Apply one pushed snapshot directly. */
|
|
86
|
+
apply(snapshot: {
|
|
87
|
+
readonly global: GlobalStatus;
|
|
88
|
+
readonly sessions: Readonly<Record<string, {
|
|
89
|
+
readonly runningTools: readonly string[];
|
|
90
|
+
}>>;
|
|
91
|
+
} | undefined): void;
|
|
92
|
+
/** A pushed snapshot may have moved the aggregate and the open list. */
|
|
93
|
+
refreshAll(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Open or close the decision list. Opening asks for the rows; there is no
|
|
96
|
+
* state to show without that answer.
|
|
97
|
+
* @param open - whether the panel should be showing.
|
|
98
|
+
*/
|
|
99
|
+
setPanel(open: boolean): void;
|
|
100
|
+
/** Ask the host for the decision list for the session the user is on. */
|
|
101
|
+
refreshTriage(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Take one listed verb on one listed session, addressed through the session
|
|
104
|
+
* the user is on. A refusal is published verbatim rather than silently
|
|
105
|
+
* leaving the previous rows in place.
|
|
106
|
+
* @param sessionId - the listed session to act on.
|
|
107
|
+
* @param action - which listed verb was taken.
|
|
108
|
+
*/
|
|
109
|
+
decide(sessionId: string, action: TriageAction): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Press the pill: park everything, or release everything the park owns.
|
|
112
|
+
* Which one it is comes from the host's own last answer, never from a local
|
|
113
|
+
* flip; the state shown afterwards is the host's answer again.
|
|
114
|
+
* @param sessionId - the session to address the verb to.
|
|
115
|
+
*/
|
|
116
|
+
toggle(sessionId: SessionId): Promise<void>;
|
|
117
|
+
/** Read the aggregate once and publish it. */
|
|
118
|
+
private readAggregate;
|
|
119
|
+
private reread;
|
|
120
|
+
/** One decision-list read, addressed at the session the user is on. */
|
|
121
|
+
private readTriage;
|
|
122
|
+
/** Ask the native namespace for the decision list. */
|
|
123
|
+
private readTriageRows;
|
|
124
|
+
private publish;
|
|
125
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The glyphs both pause controls are drawn with, so the session-header
|
|
3
|
+
* control and the whole-app pill cannot drift apart.
|
|
4
|
+
* @module dsh-pause-session/client/icons
|
|
5
|
+
*/
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
/** 16px stroke icon (currentColor): the two bars of a pause. */
|
|
8
|
+
export declare function PauseIcon(): ReactNode;
|
|
9
|
+
/** 16px stroke icon (currentColor): the triangle of a resume. */
|
|
10
|
+
export declare function ResumeIcon(): ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* 16px stroke icon (currentColor): the rings of a target, for the goal switch
|
|
13
|
+
* on the session header. Drawn at 16px like the pause and resume glyphs it sits
|
|
14
|
+
* beside rather than the 14px marks the two reports use.
|
|
15
|
+
*/
|
|
16
|
+
export declare function GoalIcon(): ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* 14px stroke icon (currentColor): the mark that a report carries a caveat.
|
|
19
|
+
* A glyph rather than copy, so the facts it stands for stay in the locale
|
|
20
|
+
* dictionaries beside everything else the user reads.
|
|
21
|
+
*/
|
|
22
|
+
export declare function CaveatIcon(): ReactNode;
|
|
23
|
+
/** 14px stroke icon (currentColor): the chevron that opens the decision list. */
|
|
24
|
+
export declare function ChevronIcon(): ReactNode;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser half of dsh-pause-session.
|
|
3
|
+
*
|
|
4
|
+
* Registers two controls: one in the session header's title-adjacent action
|
|
5
|
+
* group (`conversation.session.header.actions`), which pauses or resumes the
|
|
6
|
+
* session it sits on, and one in the header's right-aligned utilities row
|
|
7
|
+
* (`conversation.session.header.utilities`), which answers for the whole app.
|
|
8
|
+
* Both seats are session-scoped and their props carry `sessionId`, so every
|
|
9
|
+
* open session header renders its own pair and switching sessions follows
|
|
10
|
+
* automatically; the whole-app control speaks for every session from the
|
|
11
|
+
* header of the one on screen.
|
|
12
|
+
*
|
|
13
|
+
* The whole-app control sits inside the conversation column rather than in the
|
|
14
|
+
* frame-wide overlay layer: it is read while looking at one conversation, and
|
|
15
|
+
* a sidebar or drawer that covers that area covers the control with it.
|
|
16
|
+
*
|
|
17
|
+
* Both controls are host-authoritative. On a browser Host they read the
|
|
18
|
+
* native `pauseSession` stream, which keeps status reads out of the Session
|
|
19
|
+
* log. The same path serves Web and Desktop.
|
|
20
|
+
* User actions and status both use the generated native Remote namespace;
|
|
21
|
+
* no slash command is involved. Each control stays inert wherever no
|
|
22
|
+
* round-trip is available rather than rendering a state it cannot support. The
|
|
23
|
+
* header control also reads the whole-app answer the whole-app control
|
|
24
|
+
* publishes, so a park that holds nothing does not cost a host round-trip per
|
|
25
|
+
* header.
|
|
26
|
+
* @module dsh-pause-session/client
|
|
27
|
+
*/
|
|
28
|
+
import type { ReactNode } from 'react';
|
|
29
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
30
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
31
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
32
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
33
|
+
import type { PauseState } from './control.js';
|
|
34
|
+
import { type PauseKey } from './locales.js';
|
|
35
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
36
|
+
interface LocaleNamespaceMap {
|
|
37
|
+
/** The pause control's copy. */
|
|
38
|
+
'pause': PauseKey;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Required services: the slot registry, the locale registry, and the host round-trip. */
|
|
42
|
+
export declare const inject: string[];
|
|
43
|
+
/** Browser state and operations injected into the Session-header contribution. */
|
|
44
|
+
export interface PauseInjected {
|
|
45
|
+
hooks: {
|
|
46
|
+
pause: ObservableSnapshot<PauseState>;
|
|
47
|
+
};
|
|
48
|
+
/** Read the host's state for one session (the control calls this once per mount). */
|
|
49
|
+
refresh: (sessionId: SessionId) => void;
|
|
50
|
+
/** Press the control for one session. */
|
|
51
|
+
toggle: (sessionId: SessionId) => void;
|
|
52
|
+
/** Flip one session's own goal opt-out. */
|
|
53
|
+
toggleGoal: (sessionId: SessionId) => void;
|
|
54
|
+
}
|
|
55
|
+
/** Full props of the Session-header pause control. */
|
|
56
|
+
export type PauseButtonProps = PropsRuntime<'conversation.session.header.actions'> & InjectFace<PauseInjected> & PropsLocale<'pause'>;
|
|
57
|
+
/**
|
|
58
|
+
* The session's pause control: pause the session and its subagents before
|
|
59
|
+
* their next model request, or resume them, with the host's own state on screen.
|
|
60
|
+
* Once the host reports the park's goal opt-out, a second switch sits beside it
|
|
61
|
+
* — the only place a session that stopped CLEANLY can be told not to re-open its
|
|
62
|
+
* goal on the next resume, because the decision list shows only the sessions
|
|
63
|
+
* that did not stop cleanly.
|
|
64
|
+
*
|
|
65
|
+
* A subagent session renders nothing: its work is its parent's, the parent's
|
|
66
|
+
* control already covers it, and a button here would offer to pause "a session"
|
|
67
|
+
* from inside a child.
|
|
68
|
+
* @param props - session runtime, host operations, and localized copy.
|
|
69
|
+
* @returns the header action, or nothing on a subagent session.
|
|
70
|
+
*/
|
|
71
|
+
export declare function PauseButton(props: PauseButtonProps): ReactNode;
|
|
72
|
+
/**
|
|
73
|
+
* Client plugin body: register the locale dictionaries, own both controllers,
|
|
74
|
+
* follow the host's registry changes, and contribute the per-session control
|
|
75
|
+
* and the whole-app control into the session header.
|
|
76
|
+
* @param ctx - client context carrying slots, locale, and the Remote face.
|
|
77
|
+
*/
|
|
78
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** `pause` namespace dictionaries. */
|
|
2
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
|
+
export declare const zh: {
|
|
4
|
+
'pause.aria': string;
|
|
5
|
+
'resume.tip': string;
|
|
6
|
+
'draining.aria': string;
|
|
7
|
+
'parking.aria': string;
|
|
8
|
+
'safe.aria': string;
|
|
9
|
+
'flushFailed.aria': string;
|
|
10
|
+
'state.draining': string;
|
|
11
|
+
'state.parking': string;
|
|
12
|
+
'state.safe': string;
|
|
13
|
+
'state.flushFailed': string;
|
|
14
|
+
'goal.aria': string;
|
|
15
|
+
'goal.on.tip': string;
|
|
16
|
+
'goal.off.tip': string;
|
|
17
|
+
'checking.aria': string;
|
|
18
|
+
'checking.tip': string;
|
|
19
|
+
'unavailable.aria': string;
|
|
20
|
+
'unavailable.tip': string;
|
|
21
|
+
'all.pause.aria': string;
|
|
22
|
+
'all.resume.aria': string;
|
|
23
|
+
'all.idle': string;
|
|
24
|
+
'all.finishing': string;
|
|
25
|
+
'all.cancel': string;
|
|
26
|
+
'all.cancel.aria': string;
|
|
27
|
+
'all.unclean': string;
|
|
28
|
+
'all.unclean.aria': string;
|
|
29
|
+
'all.safe': string;
|
|
30
|
+
'all.checking.aria': string;
|
|
31
|
+
'all.caveat.jobs': string;
|
|
32
|
+
'all.caveat.unresumable': string;
|
|
33
|
+
'all.caveat.stalled': string;
|
|
34
|
+
'all.caveat.unread': string;
|
|
35
|
+
'all.caveat.blindTrees': string;
|
|
36
|
+
'all.caveat.blindJobs': string;
|
|
37
|
+
'triage.open.aria': string;
|
|
38
|
+
'triage.title': string;
|
|
39
|
+
'triage.empty': string;
|
|
40
|
+
'triage.reading': string;
|
|
41
|
+
'triage.unavailable': string;
|
|
42
|
+
'triage.refresh.aria': string;
|
|
43
|
+
'triage.session.aria': string;
|
|
44
|
+
'triage.stopped': string;
|
|
45
|
+
'triage.stoppedUnknown': string;
|
|
46
|
+
'triage.tools': string;
|
|
47
|
+
'triage.toolsNone': string;
|
|
48
|
+
'triage.noWake': string;
|
|
49
|
+
'triage.phase.draining': string;
|
|
50
|
+
'triage.phase.parking': string;
|
|
51
|
+
'triage.phase.flushFailed': string;
|
|
52
|
+
'triage.continue': string;
|
|
53
|
+
'triage.continue.aria': string;
|
|
54
|
+
'triage.note': string;
|
|
55
|
+
'triage.note.aria': string;
|
|
56
|
+
'triage.review': string;
|
|
57
|
+
'triage.review.aria': string;
|
|
58
|
+
'triage.leave': string;
|
|
59
|
+
'triage.leave.aria': string;
|
|
60
|
+
'triage.goal': string;
|
|
61
|
+
'triage.goal.aria': string;
|
|
62
|
+
'all.triage': string;
|
|
63
|
+
};
|
|
64
|
+
/** The pause namespace key union. */
|
|
65
|
+
export type PauseKey = keyof typeof zh;
|
|
66
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
67
|
+
export declare const en: Record<PauseKey, string>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The whole-app control: one pill in the conversation header's utilities row
|
|
3
|
+
* that answers "can I close the lid?", presses the park or the release, and —
|
|
4
|
+
* when something did not stop cleanly — opens the morning decision list.
|
|
5
|
+
*
|
|
6
|
+
* The header's right-aligned utilities row is inside the conversation column,
|
|
7
|
+
* which is where this control belongs: it answers for every session tree, but
|
|
8
|
+
* it is read while looking at one, and a surface inside the layout is covered
|
|
9
|
+
* by whatever covers that column — a sidebar or a drawer — instead of floating
|
|
10
|
+
* above the whole frame. The decision list hangs from the control into the
|
|
11
|
+
* conversation area below.
|
|
12
|
+
*
|
|
13
|
+
* The pill reads the native status stream through `./global.ts`. With no
|
|
14
|
+
* answer, or with no session to address an action through, it renders nothing
|
|
15
|
+
* rather than a state it would have to invent. The decision list is
|
|
16
|
+
* deliberately NOT part of that first read; the panel asks for its own rows
|
|
17
|
+
* when it opens.
|
|
18
|
+
* @module dsh-pause-session/client/pill
|
|
19
|
+
*/
|
|
20
|
+
import type { ReactNode } from 'react';
|
|
21
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
22
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
23
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
24
|
+
import type { TriageAction } from '../shared.js';
|
|
25
|
+
import type { GlobalPauseState } from './global.js';
|
|
26
|
+
/** Browser state and operations injected into the whole-app pill. */
|
|
27
|
+
export interface PausePillInjected {
|
|
28
|
+
hooks: {
|
|
29
|
+
pauseGlobal: ObservableSnapshot<GlobalPauseState>;
|
|
30
|
+
};
|
|
31
|
+
/** Hand the control the session the shell is on; it reads there until it has an answer. */
|
|
32
|
+
refresh: (sessionId: SessionId) => void;
|
|
33
|
+
/** Press the pill for one session. */
|
|
34
|
+
toggle: (sessionId: SessionId) => void;
|
|
35
|
+
/** Show or hide the decision list; opening it also reads its rows. */
|
|
36
|
+
setPanel: (open: boolean) => void;
|
|
37
|
+
/** Take one of the four decisions on one listed session. */
|
|
38
|
+
decide: (sessionId: string, action: TriageAction) => void;
|
|
39
|
+
}
|
|
40
|
+
/** Full props of the whole-app pause control. */
|
|
41
|
+
export type PausePillProps = PropsRuntime<'conversation.session.header.utilities'> & InjectFace<PausePillInjected> & PropsLocale<'pause'>;
|
|
42
|
+
/**
|
|
43
|
+
* The whole-app control: what the host says about the whole park, the one
|
|
44
|
+
* press that parks or releases all of it, and the decision list for whatever
|
|
45
|
+
* did not stop cleanly.
|
|
46
|
+
* @param props - session-header runtime, host operations, and localized copy.
|
|
47
|
+
* @returns the control and its panel, or nothing while the host has not answered.
|
|
48
|
+
*/
|
|
49
|
+
export declare function PausePill(props: PausePillProps): ReactNode;
|
|
@@ -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;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The controls' phase → view mapping.
|
|
3
|
+
*
|
|
4
|
+
* Kept pure functions of the published reading so the rendering decision — what
|
|
5
|
+
* the button says, whether it accepts a press, and which state label is visible
|
|
6
|
+
* — is asserted without a browser renderer. Labels are KEYS, not copy: the
|
|
7
|
+
* visible sentence is localized, and the host's own names join it as data.
|
|
8
|
+
* @module dsh-pause-session/client/view
|
|
9
|
+
*/
|
|
10
|
+
import type { GlobalReading, TriageReading } from './global.js';
|
|
11
|
+
import type { PauseReading } from './control.js';
|
|
12
|
+
import type { PauseKey } from './locales.js';
|
|
13
|
+
import type { TriageAction } from '../shared.js';
|
|
14
|
+
/** What one reading renders as. */
|
|
15
|
+
export interface PauseView {
|
|
16
|
+
/** Whether the host reported this session paused. */
|
|
17
|
+
readonly paused: boolean;
|
|
18
|
+
/** Whether the control accepts a press; an unknown or failed reading does not. */
|
|
19
|
+
readonly enabled: boolean;
|
|
20
|
+
/** Accessible name of the button. */
|
|
21
|
+
readonly ariaKey: PauseKey;
|
|
22
|
+
/** Label shown beside the button while the control has something to say. */
|
|
23
|
+
readonly labelKey?: PauseKey;
|
|
24
|
+
/** Tools in flight, taken verbatim from the host; rendered only with `labelKey`. */
|
|
25
|
+
readonly runningTools: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* The host-reported goal opt-out, or `undefined` when the host did not answer
|
|
28
|
+
* it. Absent means the goal switch is not rendered at all: a switch has to
|
|
29
|
+
* state a fact, and the only fact available is the one the record holds.
|
|
30
|
+
*/
|
|
31
|
+
readonly rearmGoal?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether the pause control renders on one session at all.
|
|
35
|
+
*
|
|
36
|
+
* The control is the user's button for a session's work, and that work includes
|
|
37
|
+
* the subagents under it — which is why the press covers the tree and why the
|
|
38
|
+
* button does not belong inside the tree. The client can tell: a Session
|
|
39
|
+
* snapshot carries a `subagent` field, the same signal the read-only composer
|
|
40
|
+
* reads. A subagent's service is not the user's to pause: its parent's control
|
|
41
|
+
* already speaks for it, and a second button here would offer to stop "a
|
|
42
|
+
* session" from inside a child.
|
|
43
|
+
* @param subagent - the session snapshot's own subagent fact, absent when unknown.
|
|
44
|
+
* @returns whether this session's header renders the control.
|
|
45
|
+
*/
|
|
46
|
+
export declare function showsPauseControl(subagent: {
|
|
47
|
+
readonly address: unknown;
|
|
48
|
+
} | null | undefined): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Map one reading to its view.
|
|
51
|
+
* @param reading - the host-reported reading, or `undefined` before the first answer.
|
|
52
|
+
* @returns the view for that reading.
|
|
53
|
+
*/
|
|
54
|
+
export declare function pauseView(reading: PauseReading | undefined, running?: boolean): PauseView;
|
|
55
|
+
/** One named fact from the whole-app report, with the copy key that introduces it. */
|
|
56
|
+
export interface GlobalCaveat {
|
|
57
|
+
/** What this list of names is. */
|
|
58
|
+
readonly key: PauseKey;
|
|
59
|
+
/** Host names, rendered verbatim. */
|
|
60
|
+
readonly names: readonly string[];
|
|
61
|
+
}
|
|
62
|
+
/** Which verb a whole-app press runs. */
|
|
63
|
+
export type GlobalAction = 'pause-all' | 'resume-all' | 'cancel-all' | 'none';
|
|
64
|
+
/** One decision as the list renders it. */
|
|
65
|
+
export interface TriageActionView {
|
|
66
|
+
/** The decision this button takes. */
|
|
67
|
+
readonly action: TriageAction;
|
|
68
|
+
/** Visible label. */
|
|
69
|
+
readonly labelKey: PauseKey;
|
|
70
|
+
/** What it does, for the accessible name. */
|
|
71
|
+
readonly ariaKey: PauseKey;
|
|
72
|
+
}
|
|
73
|
+
/** The four decisions, in the order a user reads them: least to most cautious. */
|
|
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[];
|
|
83
|
+
/**
|
|
84
|
+
* The fifth listed verb, rendered apart from the four decisions: it is a
|
|
85
|
+
* switch on what the NEXT resume may spend, not a way to decide this row.
|
|
86
|
+
*/
|
|
87
|
+
export declare const TRIAGE_GOAL_VIEW: TriageActionView;
|
|
88
|
+
/** One listed session, as the panel renders it. */
|
|
89
|
+
export interface TriageRowView {
|
|
90
|
+
/** Host data, rendered verbatim. */
|
|
91
|
+
readonly sessionId: string;
|
|
92
|
+
/** The durable outcome, as a word. */
|
|
93
|
+
readonly phaseKey: PauseKey;
|
|
94
|
+
/** Stop position: the event the log ends on and the step it belongs to. */
|
|
95
|
+
readonly stopped?: string;
|
|
96
|
+
/** The tools whose outcomes were never recorded. */
|
|
97
|
+
readonly unknownTools: readonly string[];
|
|
98
|
+
/** Whether the host said it can hand this session the request it owes. */
|
|
99
|
+
readonly wakeable: boolean;
|
|
100
|
+
/** Whether the log could be read at all. */
|
|
101
|
+
readonly readable: boolean;
|
|
102
|
+
/** Whether resuming this session may re-open its goal and spend its rounds. */
|
|
103
|
+
readonly rearmGoal: boolean;
|
|
104
|
+
}
|
|
105
|
+
/** What the decision-list panel renders. */
|
|
106
|
+
export interface TriageView {
|
|
107
|
+
/** Whether the host answered the list read at all. */
|
|
108
|
+
readonly visible: boolean;
|
|
109
|
+
/** Whether the last list read failed rather than returning rows. */
|
|
110
|
+
readonly unavailable: boolean;
|
|
111
|
+
/** The rows the host reported. */
|
|
112
|
+
readonly rows: readonly TriageRowView[];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Map one decision-list reading to its view.
|
|
116
|
+
* @param reading - the host-reported list, or `undefined` before the panel asked.
|
|
117
|
+
* @returns the rows as the panel renders them.
|
|
118
|
+
*/
|
|
119
|
+
export declare function triageView(reading: TriageReading | undefined): TriageView;
|
|
120
|
+
/** What the whole-app reading renders as. */
|
|
121
|
+
export interface GlobalPauseView {
|
|
122
|
+
/** Whether the control renders at all; a control with no host answer claims nothing. */
|
|
123
|
+
readonly visible: boolean;
|
|
124
|
+
/** Whether the control accepts a press. */
|
|
125
|
+
readonly enabled: boolean;
|
|
126
|
+
/** Accessible name of the button. */
|
|
127
|
+
readonly ariaKey: PauseKey;
|
|
128
|
+
/**
|
|
129
|
+
* Headline shown beside the button. Absent while there is nothing to report:
|
|
130
|
+
* an untouched park renders the bare affordance, not a sentence about itself.
|
|
131
|
+
*/
|
|
132
|
+
readonly labelKey?: PauseKey;
|
|
133
|
+
/** Verb the press runs. */
|
|
134
|
+
readonly action: GlobalAction;
|
|
135
|
+
/** Host numbers, rendered verbatim beside the label. */
|
|
136
|
+
readonly detail: string;
|
|
137
|
+
/** How many decisions are waiting; shown on the panel's own entry point. */
|
|
138
|
+
readonly unclean: number;
|
|
139
|
+
/** Named facts the user must not miss; rendered on the tooltip. */
|
|
140
|
+
readonly caveats: readonly GlobalCaveat[];
|
|
141
|
+
/** Draining sessions and what each is running, when the host still has work. */
|
|
142
|
+
readonly running?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Map one whole-app reading to its view.
|
|
146
|
+
* @param reading - the host-reported reading, or `undefined` before the first answer.
|
|
147
|
+
* @returns the view for that reading.
|
|
148
|
+
*/
|
|
149
|
+
export declare function globalPauseView(reading: GlobalReading | undefined, sessions?: Readonly<Record<string, {
|
|
150
|
+
readonly runningTools: readonly string[];
|
|
151
|
+
}>>): GlobalPauseView;
|