dsh-pause-session 0.0.0-stage → 0.1.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 +41 -0
- package/DEVELOPMENT.md +55 -0
- package/LICENSE +21 -0
- package/README.md +130 -2
- package/README.zh.md +91 -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 +2299 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +2975 -0
- package/lib/index.js.map +1 -0
- package/lib/types/client/burst.d.ts +253 -0
- package/lib/types/client/control.d.ts +232 -0
- package/lib/types/client/global.d.ts +325 -0
- package/lib/types/client/icons.d.ts +24 -0
- package/lib/types/client/index.d.ts +77 -0
- package/lib/types/client/locales.d.ts +69 -0
- package/lib/types/client/pill.d.ts +49 -0
- package/lib/types/client/view.d.ts +137 -0
- package/lib/types/coldlog.d.ts +111 -0
- package/lib/types/composition.d.ts +94 -0
- package/lib/types/control.d.ts +114 -0
- package/lib/types/gate.d.ts +110 -0
- package/lib/types/global.d.ts +101 -0
- package/lib/types/history.d.ts +47 -0
- package/lib/types/index.d.ts +64 -0
- package/lib/types/park.d.ts +50 -0
- package/lib/types/registry.d.ts +117 -0
- package/lib/types/resume.d.ts +221 -0
- package/lib/types/shared.d.ts +324 -0
- package/lib/types/tree.d.ts +71 -0
- package/lib/types/triage.d.ts +121 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +148 -4
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side reading and driving of the host's whole-app park state.
|
|
3
|
+
*
|
|
4
|
+
* The same one live channel the per-session control uses — a session command
|
|
5
|
+
* run through `ctx.remote.commands.execute`, plus the forwarded
|
|
6
|
+
* `commands/change` event as the only notification — carries the aggregate
|
|
7
|
+
* here. There is no push channel a third-party plugin can add, so the browser
|
|
8
|
+
* asks: `pause-state-all` is registered globally and its RESULT carries the
|
|
9
|
+
* whole-app status as of the read.
|
|
10
|
+
*
|
|
11
|
+
* The command channel is session-addressed, so the control needs one session
|
|
12
|
+
* to talk through. It picks a user session the host can serve (never a
|
|
13
|
+
* subagent's own session), preferring the one on screen; with no session at
|
|
14
|
+
* all there is nothing to ask, and the control claims nothing rather than
|
|
15
|
+
* inventing an answer. Once the host's own answer names the session the park
|
|
16
|
+
* belongs to, reads are addressed there instead: a read writes two records into
|
|
17
|
+
* the address session's transcript, and they belong in the session being parked
|
|
18
|
+
* rather than in whatever the user happens to be looking at. That name outlives
|
|
19
|
+
* the phases — a session that drained and parked cleanly names nothing else — so
|
|
20
|
+
* a clean park is addressed like a moving one. Failing that, the session the
|
|
21
|
+
* last answer reported as registered is remembered, then the one a read was last
|
|
22
|
+
* served through — an address change is the user looking somewhere else, not
|
|
23
|
+
* news about the park, and a park that has emptied out is no reason to start
|
|
24
|
+
* writing rows into every session the user opens next. An address that stops
|
|
25
|
+
* answering (its agent is gone) is forgotten and the read falls back to the next
|
|
26
|
+
* one, the session the shell asks with last, so a stale address costs an honest
|
|
27
|
+
* answer rather than a control that can never speak again.
|
|
28
|
+
*
|
|
29
|
+
* Every phase a session reaches announces itself through the registry — a
|
|
30
|
+
* transition re-registers that session's status verb — so a change is what buys
|
|
31
|
+
* the next read, and the flush that settles a park is announced like every other
|
|
32
|
+
* step (`./burst.ts`). A read that finds work still in flight is therefore the
|
|
33
|
+
* answer to the transition that produced it rather than a reason to keep asking;
|
|
34
|
+
* what the burst still owes is the delivery of a change, and the rows of a panel
|
|
35
|
+
* whose own read did not answer. A settled answer is left on screen unread: the
|
|
36
|
+
* panel stays readable and the pill keeps saying what it says, but the browser
|
|
37
|
+
* stops spending host round-trips and log records on a state that has stopped
|
|
38
|
+
* moving. What owes a read is the host's forwarded registry change, the user's
|
|
39
|
+
* own press or decision, and the first read at mount; once an answer is on
|
|
40
|
+
* screen, the shell handing this control another session to ask through is none
|
|
41
|
+
* of those. A press, a decision and an opened panel each read the host as they
|
|
42
|
+
* run, and the changes they set off are that action's own echo: those are
|
|
43
|
+
* absorbed for a short window (`./burst.ts`), so a forwarded change owes a read
|
|
44
|
+
* exactly when something moved that this browser did not do — with one look owed
|
|
45
|
+
* at the close of that window when the reading the action left is still moving,
|
|
46
|
+
* which is how a park that reaches its final phase inside the echo window is
|
|
47
|
+
* still read.
|
|
48
|
+
* @module dsh-pause-session/client/global
|
|
49
|
+
*/
|
|
50
|
+
import type { ClientRemote } from '@deepseek-ai/dsh-api-remotes/client';
|
|
51
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
52
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
53
|
+
import type { GlobalStatus, TriageAction, TriageRow } from '../shared.js';
|
|
54
|
+
import type { BurstPolicy } from './burst.js';
|
|
55
|
+
/** What the whole-app control knows: the host's answer, or that it could not ask. */
|
|
56
|
+
export type GlobalReading = {
|
|
57
|
+
readonly kind: 'reported';
|
|
58
|
+
readonly status: GlobalStatus;
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: 'unavailable';
|
|
61
|
+
};
|
|
62
|
+
/** What the decision-list panel knows: the host's rows, or that it could not ask. */
|
|
63
|
+
export type TriageReading = {
|
|
64
|
+
readonly kind: 'reported';
|
|
65
|
+
readonly rows: readonly TriageRow[];
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: 'unavailable';
|
|
68
|
+
};
|
|
69
|
+
/** Whole-app control state. */
|
|
70
|
+
export interface GlobalPauseState {
|
|
71
|
+
/** The host's answer; absent until one arrives. Writable: the store updates through a draft. */
|
|
72
|
+
global: GlobalReading | undefined;
|
|
73
|
+
/** Whether the user asked for the decision list; the panel reads only when it is open. */
|
|
74
|
+
panelOpen: boolean;
|
|
75
|
+
/** The host's decision list; absent until the panel asks for it. */
|
|
76
|
+
triage: TriageReading | undefined;
|
|
77
|
+
}
|
|
78
|
+
/** The command one listed verb runs, named from the one vocabulary the host registers. */
|
|
79
|
+
export declare const TRIAGE_DECISION_COMMAND: Readonly<Record<TriageAction, string>>;
|
|
80
|
+
/**
|
|
81
|
+
* The session a whole-app command is addressed to.
|
|
82
|
+
*
|
|
83
|
+
* A subagent's own session is never addressed: its agent belongs to a parent
|
|
84
|
+
* and is not the host's business to drive from the shell. Among the rest, the
|
|
85
|
+
* one on screen is the best address (its agent is live by construction), then
|
|
86
|
+
* any session the host reports as running, then any at all.
|
|
87
|
+
* @param state - the session list snapshot the shell provides.
|
|
88
|
+
* @returns the address, or `undefined` when the app has no session to ask through.
|
|
89
|
+
*/
|
|
90
|
+
export declare function addressingSession(state: {
|
|
91
|
+
readonly ids: readonly SessionId[];
|
|
92
|
+
readonly byId: Readonly<Record<string, {
|
|
93
|
+
readonly running?: boolean;
|
|
94
|
+
readonly origin?: string;
|
|
95
|
+
readonly parentId?: unknown;
|
|
96
|
+
readonly retainedBy?: {
|
|
97
|
+
readonly mainView?: number;
|
|
98
|
+
};
|
|
99
|
+
} | undefined>>;
|
|
100
|
+
}): SessionId | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* The session a registered park owns, when the host's answer names one that is
|
|
103
|
+
* still moving. This is how a read stops landing in whatever session the user
|
|
104
|
+
* happens to be looking at.
|
|
105
|
+
* @param status - the host's last whole-app answer.
|
|
106
|
+
* @returns a draining session, or `undefined` when the answer names none.
|
|
107
|
+
*/
|
|
108
|
+
export declare function registeredAddress(status: GlobalStatus): SessionId | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* The session the host names as the park's own, whatever phase the park
|
|
111
|
+
* reached — the address a whole-app read belongs at.
|
|
112
|
+
*
|
|
113
|
+
* This is the fact {@link registeredAddress} cannot carry once a park is clean:
|
|
114
|
+
* a session that reached `safe` has nothing left to do, so no bucket names it,
|
|
115
|
+
* and a reader in another window would fall back to whatever session it is
|
|
116
|
+
* showing. An answer written before this field existed names no session here:
|
|
117
|
+
* an absent address, never one to guess at.
|
|
118
|
+
* @param status - the host's last whole-app answer.
|
|
119
|
+
* @returns the session the park belongs to, or `undefined` when the answer names none.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parkedAddress(status: GlobalStatus): SessionId | undefined;
|
|
122
|
+
/** Owns the published whole-app reading; nothing else writes that state. */
|
|
123
|
+
export declare class PauseGlobalControl {
|
|
124
|
+
private readonly commands;
|
|
125
|
+
private readonly lifetime;
|
|
126
|
+
/** State source the whole-app contribution binds its selector hook to. */
|
|
127
|
+
readonly store: SnapshotStore<GlobalPauseState>;
|
|
128
|
+
/** The session the shell asked this control through; reads start here. */
|
|
129
|
+
private address;
|
|
130
|
+
/**
|
|
131
|
+
* The last session an answer reported as registered — the address a read
|
|
132
|
+
* falls back to when the host's own answer names no parked session, which is
|
|
133
|
+
* every reading written before that field existed.
|
|
134
|
+
*
|
|
135
|
+
* The buckets that feed it name only what is still moving, so they say
|
|
136
|
+
* nothing about a park that finished cleanly; the park's own name is the live
|
|
137
|
+
* fact, and this memory is what keeps the rows in the park's transcript for an
|
|
138
|
+
* answer that predates it. An empty registry does not clear it: a park whose
|
|
139
|
+
* sessions stopped moving has not moved out of its sessions.
|
|
140
|
+
*/
|
|
141
|
+
private lastRegistered;
|
|
142
|
+
/**
|
|
143
|
+
* The session an aggregate read was last served through. Later reads keep
|
|
144
|
+
* going there: every read is two records in the address session's log, and
|
|
145
|
+
* the shell's address follows whatever the user is looking at, which is not
|
|
146
|
+
* where a row about the park belongs.
|
|
147
|
+
*/
|
|
148
|
+
private served;
|
|
149
|
+
/** Highest decision-list read requested: an answer older than it is dropped. */
|
|
150
|
+
private latestTriage;
|
|
151
|
+
/** Highest read sequence requested: an answer older than it is dropped. */
|
|
152
|
+
private latest;
|
|
153
|
+
/**
|
|
154
|
+
* Whether a registry change has arrived that no completed read pass has
|
|
155
|
+
* answered. Set only for a change that is news — an absorbed echo of this
|
|
156
|
+
* browser's own action is already answered by that action's read.
|
|
157
|
+
*/
|
|
158
|
+
private owed;
|
|
159
|
+
/** The bounded burst of reads one transition buys; see `./burst.ts`. */
|
|
160
|
+
private readonly burst;
|
|
161
|
+
/**
|
|
162
|
+
* @param commands - the Client-callable command namespace.
|
|
163
|
+
* @param lifetime - aborts in-flight calls and the burst when the plugin unloads.
|
|
164
|
+
* @param windowMs - how long the burst collects the registrations of one
|
|
165
|
+
* transition before its first read; also the wait after a press.
|
|
166
|
+
* @param policy - the burst waits and visibility source; defaults to the real page.
|
|
167
|
+
*/
|
|
168
|
+
constructor(commands: ClientRemote['commands'], lifetime: AbortSignal, windowMs?: number, policy?: Omit<BurstPolicy, 'windowMs' | 'waitsMs'>);
|
|
169
|
+
/**
|
|
170
|
+
* The shell handed this control a session: the one the pill mounted on, or
|
|
171
|
+
* the one the user has just moved to. Only a control with no answer yet reads
|
|
172
|
+
* here — the view moving is not the park moving, and after the first answer
|
|
173
|
+
* the forwarded registry change and the user's own press are what ask again.
|
|
174
|
+
* A read that came back with nothing is not an answer, so moving to another
|
|
175
|
+
* session is one more chance to reach the host rather than a control that
|
|
176
|
+
* stays mute until something else happens to change. The address is kept
|
|
177
|
+
* either way: it is where this control's presses and its decision list go, and
|
|
178
|
+
* the fallback a remembered address falls back to.
|
|
179
|
+
* @param sessionId - the session the shell asks through.
|
|
180
|
+
* @returns after this read's answer is published or superseded by a newer one.
|
|
181
|
+
*/
|
|
182
|
+
refresh(sessionId: SessionId): Promise<void>;
|
|
183
|
+
/** Whether the host has answered: a state, not the absence of one it never sent. */
|
|
184
|
+
private answered;
|
|
185
|
+
/**
|
|
186
|
+
* The host's command registry changed: something may have moved, so a burst
|
|
187
|
+
* is owed. The reads run on the burst's own schedule rather than here, which
|
|
188
|
+
* is what coalesces a tick's worth of changes — parking N sessions announces
|
|
189
|
+
* three registrations each, and a phase change re-registers one more — into
|
|
190
|
+
* one burst. A change that is this browser's own action's echo owes nothing:
|
|
191
|
+
* the action read the host as it ran.
|
|
192
|
+
*/
|
|
193
|
+
refreshAll(): void;
|
|
194
|
+
/**
|
|
195
|
+
* One read of the whole screen: the aggregate, plus the decision list while
|
|
196
|
+
* the user has it open. A closed panel spends no command calls on rows nobody
|
|
197
|
+
* is reading.
|
|
198
|
+
*/
|
|
199
|
+
private readAll;
|
|
200
|
+
/**
|
|
201
|
+
* The session a read goes through: the one the park is still draining, else
|
|
202
|
+
* the one the host names as the park's own, else the one a read was last
|
|
203
|
+
* served through, else the session the shell is on now. The park's own address
|
|
204
|
+
* comes first because a read's rows land in the transcript it was taken
|
|
205
|
+
* through, and the park — not the reader — is what they are about; its name
|
|
206
|
+
* survives the clean case, where every bucket above is empty and a park that
|
|
207
|
+
* finished would otherwise have no address at all.
|
|
208
|
+
* @param skip - an address this read already tried and could not reach. The
|
|
209
|
+
* fallback is the next candidate rather than the same one again.
|
|
210
|
+
*/
|
|
211
|
+
private target;
|
|
212
|
+
/**
|
|
213
|
+
* Ask the host for the aggregate through the session a read goes through,
|
|
214
|
+
* with the one fallback an address that stopped answering is worth: the
|
|
215
|
+
* remembered address is forgotten, and the read is taken again through the
|
|
216
|
+
* next one — the session the shell is on at the latest. A memory of a session
|
|
217
|
+
* this host no longer holds would otherwise leave the control unable to say
|
|
218
|
+
* anything at all, and a single retry is the whole price of keeping it honest.
|
|
219
|
+
*/
|
|
220
|
+
private readAggregate;
|
|
221
|
+
/**
|
|
222
|
+
* Publish one aggregate answer, unless a newer read was requested while this
|
|
223
|
+
* one was in flight: that answer would show a state the host has moved past.
|
|
224
|
+
* A served address is remembered, and one that did not answer is forgotten —
|
|
225
|
+
* that is what makes the read fall back to the next address rather than ask
|
|
226
|
+
* it again.
|
|
227
|
+
* @param sessionId - the session this read goes through.
|
|
228
|
+
* @returns whether this read came back without an answer; a superseded read is
|
|
229
|
+
* not one, because the newer read already owns the outcome.
|
|
230
|
+
*/
|
|
231
|
+
private readAt;
|
|
232
|
+
/**
|
|
233
|
+
* Open or close the decision list. Opening asks the host for the rows; there
|
|
234
|
+
* is no state to show without that answer. Opening is also a client action
|
|
235
|
+
* that reads, so the changes it sets off are absorbed rather than read back,
|
|
236
|
+
* and whether the burst owes anything is what its own read decides.
|
|
237
|
+
* @param open - whether the panel should be showing.
|
|
238
|
+
*/
|
|
239
|
+
setPanel(open: boolean): void;
|
|
240
|
+
/**
|
|
241
|
+
* Ask the host for the decision list through the session the user is on. The
|
|
242
|
+
* list is recorded per calling session and the decisions act only on rows
|
|
243
|
+
* that session was shown, so this read — unlike the aggregate — stays on the
|
|
244
|
+
* shell's own address.
|
|
245
|
+
*/
|
|
246
|
+
refreshTriage(): Promise<void>;
|
|
247
|
+
/**
|
|
248
|
+
* Read the aggregate again because the user asked this control to act: the
|
|
249
|
+
* address is the session the shell is on, since a press is a user action on
|
|
250
|
+
* the session the user is looking at, and the host's answer is what the
|
|
251
|
+
* control shows next.
|
|
252
|
+
* @param sessionId - the session the shell is on.
|
|
253
|
+
*/
|
|
254
|
+
private reread;
|
|
255
|
+
/** One decision-list read, addressed at the session the user is on. */
|
|
256
|
+
private readTriage;
|
|
257
|
+
/**
|
|
258
|
+
* Take one listed verb on one listed session, addressed through the session
|
|
259
|
+
* the user is on. The list is read first, because the host acts only on rows
|
|
260
|
+
* it has shown the calling session, and read again afterwards because the
|
|
261
|
+
* host's answer — not this control's assumption — is what the next render
|
|
262
|
+
* shows: a decision can release a session, and the goal switch changes what
|
|
263
|
+
* the row itself says.
|
|
264
|
+
* @param sessionId - the listed session to act on.
|
|
265
|
+
* @param action - which listed verb was taken.
|
|
266
|
+
*/
|
|
267
|
+
decide(sessionId: string, action: TriageAction): Promise<void>;
|
|
268
|
+
/**
|
|
269
|
+
* Press the pill: park everything, or release everything the park owns.
|
|
270
|
+
* Which one it is comes from the host's own last answer, never from a local
|
|
271
|
+
* flip; the state shown afterwards is the host's answer again. A reading the
|
|
272
|
+
* view does not turn into a verb — every registered session is one the host
|
|
273
|
+
* will not wake — is not pressed at all.
|
|
274
|
+
* @param sessionId - the session to address the verb to.
|
|
275
|
+
* @returns after the control re-reads the host.
|
|
276
|
+
*/
|
|
277
|
+
toggle(sessionId: SessionId): Promise<void>;
|
|
278
|
+
/** Ask the host for the decision list, in the browser's vocabulary. */
|
|
279
|
+
private readTriageRows;
|
|
280
|
+
/** Ask the host what the whole-app park state is, in the browser's vocabulary. */
|
|
281
|
+
private read;
|
|
282
|
+
private publish;
|
|
283
|
+
/**
|
|
284
|
+
* Decide what the burst owes after a read: another look while a change this
|
|
285
|
+
* browser has not answered is outstanding, or while the open panel is still
|
|
286
|
+
* owed rows, and nothing once neither holds.
|
|
287
|
+
*
|
|
288
|
+
* A registered session standing at the park point is left alone: the flush
|
|
289
|
+
* that settles it announces itself on the command channel like every other
|
|
290
|
+
* transition, so watching it from here would spend reads on a move that is
|
|
291
|
+
* going to speak for itself. A verdict that stays `waiting` because a record
|
|
292
|
+
* is stalled, unresumable or unread is not settling either: nothing in this
|
|
293
|
+
* process will change it, and a session that starts moving again registers its
|
|
294
|
+
* markers, which is what asks next.
|
|
295
|
+
*
|
|
296
|
+
* The one transition that cannot speak for itself is the one this browser's
|
|
297
|
+
* own echo window swallowed: a press reads the host, the park reaches its
|
|
298
|
+
* final phase while the window is still open, and the changes that would have
|
|
299
|
+
* announced it look exactly like the press's own registrations. An action that
|
|
300
|
+
* left the park moving therefore asks for one look when that window closes,
|
|
301
|
+
* and only then. Once nothing is owed, the last reading stays published — the
|
|
302
|
+
* verdict, the pill and the rows are exactly what the host last reported.
|
|
303
|
+
*/
|
|
304
|
+
private follow;
|
|
305
|
+
/**
|
|
306
|
+
* Whether the park this control last read still has work on its way — the only
|
|
307
|
+
* state an action's own echo could be hiding a transition behind. A record
|
|
308
|
+
* that is stalled, unresumable or unread is not moving: nothing in this
|
|
309
|
+
* process will finish it.
|
|
310
|
+
*/
|
|
311
|
+
private moving;
|
|
312
|
+
/**
|
|
313
|
+
* Whether the open panel is still owed its rows. Opening the panel reads them,
|
|
314
|
+
* so this holds only after a read that did not answer — the one debt no change
|
|
315
|
+
* created, and the one place a retry is the difference between an empty panel
|
|
316
|
+
* and the facts the host has. It is bounded by the burst like everything else.
|
|
317
|
+
*/
|
|
318
|
+
private unansweredPanel;
|
|
319
|
+
/**
|
|
320
|
+
* Follow the page in and out of view. A hidden page is not being read — and
|
|
321
|
+
* the read the burst was about to take is not lost, it waits for the page to
|
|
322
|
+
* come back.
|
|
323
|
+
*/
|
|
324
|
+
watchVisibility(): void;
|
|
325
|
+
}
|
|
@@ -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,77 @@
|
|
|
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: they read state from the host's command
|
|
18
|
+
* registry and drive it through the host's session commands (`./control.ts` and
|
|
19
|
+
* `./global.ts` document why the command registry is the only live channel a
|
|
20
|
+
* third-party plugin can use). Each stays inert wherever that round-trip is
|
|
21
|
+
* unavailable rather than rendering a control that cannot tell the truth. The
|
|
22
|
+
* header control also reads the whole-app answer the whole-app control
|
|
23
|
+
* publishes, so a park that holds nothing does not cost a host round-trip per
|
|
24
|
+
* header.
|
|
25
|
+
* @module dsh-pause-session/client
|
|
26
|
+
*/
|
|
27
|
+
import type { ReactNode } from 'react';
|
|
28
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
29
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
30
|
+
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
31
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
32
|
+
import type { PauseState } from './control.js';
|
|
33
|
+
import { type PauseKey } from './locales.js';
|
|
34
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
35
|
+
interface LocaleNamespaceMap {
|
|
36
|
+
/** The pause control's copy. */
|
|
37
|
+
'pause': PauseKey;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Required services: the slot registry, the locale registry, and the host round-trip. */
|
|
41
|
+
export declare const inject: string[];
|
|
42
|
+
/** Browser state and operations injected into the Session-header contribution. */
|
|
43
|
+
export interface PauseInjected {
|
|
44
|
+
hooks: {
|
|
45
|
+
pause: ObservableSnapshot<PauseState>;
|
|
46
|
+
};
|
|
47
|
+
/** Read the host's state for one session (the control calls this once per mount). */
|
|
48
|
+
refresh: (sessionId: SessionId) => void;
|
|
49
|
+
/** Press the control for one session. */
|
|
50
|
+
toggle: (sessionId: SessionId) => void;
|
|
51
|
+
/** Flip one session's own goal opt-out. */
|
|
52
|
+
toggleGoal: (sessionId: SessionId) => void;
|
|
53
|
+
}
|
|
54
|
+
/** Full props of the Session-header pause control. */
|
|
55
|
+
export type PauseButtonProps = PropsRuntime<'conversation.session.header.actions'> & InjectFace<PauseInjected> & PropsLocale<'pause'>;
|
|
56
|
+
/**
|
|
57
|
+
* The session's pause control: pause the session and its subagents before
|
|
58
|
+
* their next model request, or resume them, with the host's own state on screen.
|
|
59
|
+
* Once the host reports the park's goal opt-out, a second switch sits beside it
|
|
60
|
+
* — the only place a session that stopped CLEANLY can be told not to re-open its
|
|
61
|
+
* goal on the next resume, because the decision list shows only the sessions
|
|
62
|
+
* that did not stop cleanly.
|
|
63
|
+
*
|
|
64
|
+
* A subagent session renders nothing: its work is its parent's, the parent's
|
|
65
|
+
* control already covers it, and a button here would offer to pause "a session"
|
|
66
|
+
* from inside a child.
|
|
67
|
+
* @param props - session runtime, host operations, and localized copy.
|
|
68
|
+
* @returns the header action, or nothing on a subagent session.
|
|
69
|
+
*/
|
|
70
|
+
export declare function PauseButton(props: PauseButtonProps): ReactNode;
|
|
71
|
+
/**
|
|
72
|
+
* Client plugin body: register the locale dictionaries, own both controllers,
|
|
73
|
+
* follow the host's registry changes, and contribute the per-session control
|
|
74
|
+
* and the whole-app control into the session header.
|
|
75
|
+
* @param ctx - client context carrying slots, locale, and the Remote face.
|
|
76
|
+
*/
|
|
77
|
+
export declare function apply(ctx: ClientContext): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** `pause` namespace dictionaries. */
|
|
2
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
|
+
export declare const zh: {
|
|
4
|
+
'pause.aria': string;
|
|
5
|
+
'pause.tip': string;
|
|
6
|
+
'resume.aria': string;
|
|
7
|
+
'resume.tip': string;
|
|
8
|
+
'draining.aria': string;
|
|
9
|
+
'parking.aria': string;
|
|
10
|
+
'safe.aria': string;
|
|
11
|
+
'flushFailed.aria': string;
|
|
12
|
+
'state.paused': string;
|
|
13
|
+
'state.draining': string;
|
|
14
|
+
'state.parking': string;
|
|
15
|
+
'state.safe': string;
|
|
16
|
+
'state.flushFailed': string;
|
|
17
|
+
'goal.aria': string;
|
|
18
|
+
'goal.on.tip': string;
|
|
19
|
+
'goal.off.tip': string;
|
|
20
|
+
'checking.aria': string;
|
|
21
|
+
'checking.tip': string;
|
|
22
|
+
'unavailable.aria': string;
|
|
23
|
+
'unavailable.tip': string;
|
|
24
|
+
'all.pause.aria': string;
|
|
25
|
+
'all.resume.aria': string;
|
|
26
|
+
'all.idle': string;
|
|
27
|
+
'all.finishing': string;
|
|
28
|
+
'all.unclean': string;
|
|
29
|
+
'all.unclean.aria': string;
|
|
30
|
+
'all.safe': string;
|
|
31
|
+
'all.checking.aria': string;
|
|
32
|
+
'all.caveat.jobs': string;
|
|
33
|
+
'all.caveat.unresumable': string;
|
|
34
|
+
'all.caveat.stalled': string;
|
|
35
|
+
'all.caveat.unread': string;
|
|
36
|
+
'all.caveat.blindTrees': string;
|
|
37
|
+
'all.caveat.blindJobs': string;
|
|
38
|
+
'triage.open.aria': string;
|
|
39
|
+
'triage.title': string;
|
|
40
|
+
'triage.empty': string;
|
|
41
|
+
'triage.reading': string;
|
|
42
|
+
'triage.unavailable': string;
|
|
43
|
+
'triage.refresh.aria': string;
|
|
44
|
+
'triage.session.aria': string;
|
|
45
|
+
'triage.stopped': string;
|
|
46
|
+
'triage.stoppedUnknown': string;
|
|
47
|
+
'triage.tools': string;
|
|
48
|
+
'triage.toolsNone': string;
|
|
49
|
+
'triage.noWake': string;
|
|
50
|
+
'triage.phase.draining': string;
|
|
51
|
+
'triage.phase.parking': string;
|
|
52
|
+
'triage.phase.flushFailed': string;
|
|
53
|
+
'triage.continue': string;
|
|
54
|
+
'triage.continue.aria': string;
|
|
55
|
+
'triage.note': string;
|
|
56
|
+
'triage.note.aria': string;
|
|
57
|
+
'triage.note.hint': string;
|
|
58
|
+
'triage.review': string;
|
|
59
|
+
'triage.review.aria': string;
|
|
60
|
+
'triage.leave': string;
|
|
61
|
+
'triage.leave.aria': string;
|
|
62
|
+
'triage.goal': string;
|
|
63
|
+
'triage.goal.aria': string;
|
|
64
|
+
'all.triage': string;
|
|
65
|
+
};
|
|
66
|
+
/** The pause namespace key union. */
|
|
67
|
+
export type PauseKey = keyof typeof zh;
|
|
68
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
69
|
+
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 host through the command channel only (`./global.ts`):
|
|
14
|
+
* with no answer, or with no session to ask through, it renders nothing rather
|
|
15
|
+
* than a state it would have to invent. The decision list is deliberately NOT
|
|
16
|
+
* part of that first read — it costs two more command calls, and a user who
|
|
17
|
+
* never opens it pays nothing; the panel asks for its own rows 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;
|