@robota-sdk/agent-ui-web 3.3.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/index.cjs +81 -81
- package/dist/index.d.cts +61 -61
- package/dist/index.d.ts +61 -61
- package/dist/index.js +81 -81
- package/dist/index.js.map +1 -1
- package/docs/SPEC.md +1 -1
- package/package.json +6 -6
- package/src/client/ws-session-client.ts +1 -1
- package/src/clipboard.ts +2 -2
- package/src/components/AgentActivityPanel.tsx +8 -8
- package/src/components/AgentSwitcherSheet.tsx +2 -2
- package/src/components/Composer.tsx +27 -27
- package/src/components/ConversationView.tsx +14 -14
- package/src/components/Dialog.tsx +6 -6
- package/src/components/DiffLines.tsx +2 -2
- package/src/components/ExecutionDetailSheet.tsx +1 -1
- package/src/components/HelpSheet.tsx +2 -2
- package/src/components/PermissionPrompt.tsx +22 -22
- package/src/components/PopupMenu.tsx +1 -1
- package/src/components/ProjectPanel.tsx +2 -2
- package/src/components/SessionMonitor.tsx +1 -1
- package/src/components/SessionSidebar.tsx +8 -8
- package/src/components/SessionSurface.tsx +18 -18
- package/src/components/SessionSurfaceChrome.tsx +8 -8
- package/src/components/SettingsGeneralSection.tsx +1 -1
- package/src/components/SettingsMcpSection.tsx +1 -1
- package/src/components/SettingsPermissionsSection.tsx +1 -1
- package/src/components/SettingsPluginsSection.tsx +1 -1
- package/src/components/SettingsProvidersSection.tsx +3 -3
- package/src/components/SettingsScreen.tsx +3 -3
- package/src/components/StatusControls.tsx +12 -12
- package/src/components/composer-attachments.ts +5 -5
- package/src/driver-labels.ts +3 -3
- package/src/execution-entry-status.ts +1 -1
- package/src/goal-view.ts +1 -1
- package/src/hooks/command-menu.ts +2 -2
- package/src/hooks/excluded-commands.ts +2 -2
- package/src/hooks/prompt-state.ts +4 -4
- package/src/hooks/server-message-handling.ts +11 -11
- package/src/hooks/session-client-types.ts +29 -29
- package/src/hooks/ui-intent-state.ts +6 -6
- package/src/hooks/use-agent-switcher-state.ts +2 -2
- package/src/hooks/use-media-query.ts +1 -1
- package/src/hooks/use-model-list.ts +1 -1
- package/src/hooks/use-project-panel.ts +1 -1
- package/src/hooks/use-schedules-state.ts +4 -4
- package/src/hooks/use-session-directory.ts +4 -4
- package/src/hooks/use-settings-state.ts +4 -4
- package/src/hooks/useSessionClient.ts +44 -44
- package/src/index.ts +5 -5
- package/src/schedule-view.ts +1 -1
- package/src/styles/surface.css +1 -1
|
@@ -30,7 +30,7 @@ function keepsSessionsLive(listing: TSessionListing): boolean {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* A desktop Reconnect reloads the page, which loses every bit of JS state — so the
|
|
34
34
|
* session to go back to cannot live in a ref like `lastSwitchedIdRef` below. `sessionStorage` survives
|
|
35
35
|
* the reload (and only that tab), so it carries the id across it. Namespaced so a page hosting other
|
|
36
36
|
* state under the same origin does not collide.
|
|
@@ -74,7 +74,7 @@ function initialSidebarOpen(): boolean {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
*
|
|
77
|
+
* The host's session list beside the conversation: requests correlated by id (the latest
|
|
78
78
|
* wins), the start/switch commands, and whether the sidebar is shown.
|
|
79
79
|
*
|
|
80
80
|
* A host that keeps sessions live binds each connection to its own session and puts a new connection
|
|
@@ -120,7 +120,7 @@ export function useSessionDirectoryState(
|
|
|
120
120
|
(): void => send({ type: 'new-session', requestId: nextRequestId('session_change') }),
|
|
121
121
|
[send, browserNamespace],
|
|
122
122
|
);
|
|
123
|
-
//
|
|
123
|
+
// Rename or delete a row in the list — current or not. Both answer on their own
|
|
124
124
|
// correlated reply (`session_renamed_in_list`/`session_deleted`, or a `_failed` counterpart); the
|
|
125
125
|
// reducer in `useSessionClient` refreshes the listing on success and raises a notice on failure.
|
|
126
126
|
const renameSessionInList = useCallback(
|
|
@@ -138,7 +138,7 @@ export function useSessionDirectoryState(
|
|
|
138
138
|
const lastSwitchedIdRef = useRef<string | null>(null);
|
|
139
139
|
const restoreTargetRef = useRef<string | null>(null);
|
|
140
140
|
const armRestore = useCallback((): void => {
|
|
141
|
-
// A remembered id (
|
|
141
|
+
// A remembered id (a desktop Reconnect's reload) wins over the same-page target:
|
|
142
142
|
// on a fresh page, `lastSwitchedIdRef` is always null anyway, so this only ever adds a source.
|
|
143
143
|
restoreTargetRef.current = readStoredRestoreId(browserNamespace) ?? lastSwitchedIdRef.current;
|
|
144
144
|
}, [browserNamespace]);
|
|
@@ -24,12 +24,12 @@ function nextRequestId(prefix: string): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* The Settings screen's own correlated request/response state, independent from chat
|
|
28
28
|
* state — the same shape `usePersonalUsageState` already uses for `get-personal-usage-report`.
|
|
29
29
|
*
|
|
30
30
|
* `openSettings` re-fetches every time (opening from the gear, `/settings`, or reopening after a
|
|
31
31
|
* close): the simplest correct way for a change made elsewhere — another typed command, another
|
|
32
|
-
* client — to reach an open-then-reopened screen
|
|
32
|
+
* client — to reach an open-then-reopened screen.
|
|
33
33
|
*
|
|
34
34
|
* A failed UPDATE never touches `settingsSnapshot`, so a control bound to it shows the value it had
|
|
35
35
|
* before the write — "a failed write leaves the control at its previous value" — while
|
|
@@ -39,7 +39,7 @@ function nextRequestId(prefix: string): string {
|
|
|
39
39
|
export function useSettingsState(send: (msg: TClientMessage) => void): TSettingsState & {
|
|
40
40
|
handleSettingsMessage: (msg: TServerMessage) => boolean;
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Re-fetch without touching `settingsOpen`/`settingsInitialSectionId` — for a change
|
|
43
43
|
* made elsewhere while the screen is already open (e.g. "Add provider…"'s ask flow finishing).
|
|
44
44
|
* Unlike `openSettings`, this never resets which section is showing.
|
|
45
45
|
*/
|
|
@@ -49,7 +49,7 @@ export function useSettingsState(send: (msg: TClientMessage) => void): TSettings
|
|
|
49
49
|
const [settingsStatus, setSettingsStatus] = useState<IWsSessionState['settingsStatus']>('idle');
|
|
50
50
|
const [settingsSnapshot, setSettingsSnapshot] = useState<ISettingsSnapshot | null>(null);
|
|
51
51
|
const [settingsError, setSettingsError] = useState<string | null>(null);
|
|
52
|
-
//
|
|
52
|
+
// Which section to land on THIS open — `/plugin` opens straight to
|
|
53
53
|
// Plugins, "Manage providers…" straight to Providers & Models; everything else (the gear,
|
|
54
54
|
// `/settings`) opens on the screen's own default (General).
|
|
55
55
|
const [settingsInitialSectionId, setSettingsInitialSectionId] = useState<string | null>(null);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The transport-neutral session reducer for the GUI presentation layer. Reconstructs conversation
|
|
3
3
|
* state from the wire `TServerMessage` stream, independent of HOW the bytes arrive (a `TMakeSessionClient`
|
|
4
4
|
* factory over `{onMessage, onStatusChange}`). The core is **generic over its status type** (default
|
|
5
5
|
* `TConnectionStatus`) so a surface with extra connection states (e.g. the browser WebRTC surface) can widen it
|
|
@@ -83,7 +83,7 @@ function nextSilentCommandRequestId(): string {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* A finished turn's text and tool calls, in the order they actually happened — a running
|
|
87
87
|
* turn accumulates these alongside (not instead of) the legacy `streamingText`/`activeTools` live
|
|
88
88
|
* view, and `finishTurn` flushes them as SEPARATE conversation entries in order, instead of one
|
|
89
89
|
* tools-block followed by one merged text block. Consecutive tool calls with no text between them
|
|
@@ -118,7 +118,7 @@ function pushToolStartSegment(segments: TTurnSegment[], tool: IActiveTool): TTur
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Attribute a `tool_end` to its call by executionId first — two same-named parallel calls
|
|
122
122
|
* can finish out of start order, and matching by name+running alone would close whichever running
|
|
123
123
|
* call of that name is found first, not the one that actually finished. Falls back to name+running
|
|
124
124
|
* only when the event carries no executionId (legacy fixtures / hosts that predate it).
|
|
@@ -189,7 +189,7 @@ function applyToolEndToSegments(segments: TTurnSegment[], state: IToolState): TT
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
192
|
+
* The files an Edit/Write call touched during the turn, aggregated by path — the "Changed
|
|
193
193
|
* files" row's data. `diffLines` keeps the LATEST edit's diff (what a click on the file opens); the
|
|
194
194
|
* +/- counts sum every edit to that path within the turn.
|
|
195
195
|
*/
|
|
@@ -221,7 +221,7 @@ function activeToolFromHistoricalState(state: IToolState): IActiveTool {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
*
|
|
224
|
+
* Turns the server's chronological `IHistoryDisplaySegment[]` (a reload/reconnect/resume
|
|
225
225
|
* replay) into the SAME `TConversationEntry[]` shape a live turn's `finishTurn` produces — reusing its
|
|
226
226
|
* exact segment-accumulation and "Changed files" logic, so the two paths cannot draw the turn/grouping
|
|
227
227
|
* boundary differently. A `'text', role: 'user'` segment is both its own entry AND the turn boundary:
|
|
@@ -289,15 +289,15 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
289
289
|
const [sessionName, setSessionName] = useState<string | null>(null);
|
|
290
290
|
const [sessionNotices, setSessionNotices] = useState<readonly ISessionNotice[]>([]);
|
|
291
291
|
const [commandCatalog, setCommandCatalog] = useState<TCommandCatalog | null>(null);
|
|
292
|
-
//
|
|
292
|
+
// `send` (below) reads this on every `command` message to catch an excluded command —
|
|
293
293
|
// a ref (not the state above) so `send`'s identity never changes as the catalog arrives or is
|
|
294
294
|
// refreshed, matching every other value a stable callback in this file reads this way.
|
|
295
295
|
const commandCatalogRef = useRef<TCommandCatalog | null>(null);
|
|
296
296
|
const [sessionStatus, setSessionStatus] = useState<TSessionStatus | null>(null);
|
|
297
|
-
//
|
|
297
|
+
// Learned from the first frame the server sends this connection, so its own messages and
|
|
298
298
|
// prompts never carry a "from" label — only ANOTHER driver's do.
|
|
299
299
|
const [ownDriverId, setOwnDriverId] = useState<TDriverId | null>(null);
|
|
300
|
-
//
|
|
300
|
+
// `/help` opens the GUI's own Help sheet — it never reaches the session (`send` below
|
|
301
301
|
// catches it), so there is no round trip and no stale terminal-style text dump to replace.
|
|
302
302
|
const [helpOpen, setHelpOpen] = useState(false);
|
|
303
303
|
const openHelp = useCallback((): void => setHelpOpen(true), []);
|
|
@@ -307,7 +307,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
307
307
|
const streamingTextRef = useRef('');
|
|
308
308
|
// The tools of the running turn, mirrored so the turn's end can keep them in the conversation.
|
|
309
309
|
const activeToolsRef = useRef<IActiveTool[]>([]);
|
|
310
|
-
//
|
|
310
|
+
// The running turn's text/tools in the order they happened — see `TTurnSegment` above.
|
|
311
311
|
const turnSegmentsRef = useRef<TTurnSegment[]>([]);
|
|
312
312
|
// A screen this surface's command asked for: it answers the command in place of the command's own
|
|
313
313
|
// reply — with the "not available" line (`text`), or with nothing when the screen opened (null).
|
|
@@ -328,7 +328,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
328
328
|
}, []);
|
|
329
329
|
/**
|
|
330
330
|
* End the running turn: its text and tool calls stay in the conversation as SEPARATE entries, in
|
|
331
|
-
* the order they happened
|
|
331
|
+
* the order they happened — not one tools-block grouped ahead of a single merged reply.
|
|
332
332
|
*/
|
|
333
333
|
const finishTurn = useCallback(
|
|
334
334
|
(toolStatus: (tool: IActiveTool) => IActiveTool['status']): void => {
|
|
@@ -349,7 +349,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
349
349
|
appendEntry({ id: segment.id, role: 'assistant', content: segment.text });
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
-
//
|
|
352
|
+
// A turn that changed files ends with one compact summary row, after everything else.
|
|
353
353
|
const changedFiles = collectChangedFiles(segments);
|
|
354
354
|
if (changedFiles.length > 0) {
|
|
355
355
|
appendEntry({ id: nextId(), role: 'changed-files', files: changedFiles });
|
|
@@ -359,7 +359,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
359
359
|
);
|
|
360
360
|
// Commands this surface sent whose result has not come back — a screen request pairs with one.
|
|
361
361
|
const commandsInFlightRef = useRef(0);
|
|
362
|
-
//
|
|
362
|
+
// The `requestId`s of commands sent via `sendCommandSilently`, still awaiting
|
|
363
363
|
// their `command_result`. The wire already correlates a command with its reply by `requestId` (see
|
|
364
364
|
// `TClientMessage`'s `command` / `TServerMessage`'s `command_result` in `@robota-sdk/agent-transport`)
|
|
365
365
|
// — a plain `send` command and a `sendCommandSilently` command can be outstanding at once, and only
|
|
@@ -389,12 +389,12 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
389
389
|
setExecutionDetailInput({ taskId: msg.taskId, status: 'pending' });
|
|
390
390
|
}
|
|
391
391
|
if (msg.type === 'command') {
|
|
392
|
-
//
|
|
392
|
+
// `/help` is the GUI's own Help sheet — never sent to the session.
|
|
393
393
|
if (msg.name === 'help') {
|
|
394
394
|
setHelpOpen(true);
|
|
395
395
|
return;
|
|
396
396
|
}
|
|
397
|
-
//
|
|
397
|
+
// A command the GUI leaves out on purpose is caught here, before the session ever
|
|
398
398
|
// sees it — its own plain sentence answers in place of the session's refusal, exactly like a
|
|
399
399
|
// typed command's own result would (same entry shape, same place in the conversation). The
|
|
400
400
|
// catalog entry (when known) carries `runner`/`surfaces`, so a client-only command excluded
|
|
@@ -429,7 +429,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
429
429
|
if (requestId !== undefined) sessionChangeRequestIdsRef.current.delete(requestId);
|
|
430
430
|
}, []);
|
|
431
431
|
/**
|
|
432
|
-
*
|
|
432
|
+
* Run a command the same way `send` does, but flag it as controls-triggered —
|
|
433
433
|
* the model/mode/effort pop-up menus use this instead of `send` directly, so their change is
|
|
434
434
|
* confirmed by the control's own label (via the `get-status` refresh `command_result` already
|
|
435
435
|
* triggers) rather than a conversation card. A typed `/command` still goes through plain `send`
|
|
@@ -438,7 +438,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
438
438
|
* `TClientMessage` — giving `command` a `requestId` there would let ANY caller of `send` opt a typed
|
|
439
439
|
* command into silence, which is exactly what this must not allow.
|
|
440
440
|
*
|
|
441
|
-
*
|
|
441
|
+
* `send` also intercepts `help` and an excluded command before either reaches the wire —
|
|
442
442
|
* for `help` that opens the sheet regardless of how it was asked for, but an excluded command's
|
|
443
443
|
* interception adds a VISIBLE info card, defeating the silence this function promises. Every current
|
|
444
444
|
* caller (model/mode/effort) names neither, so this is dormant, not exercised — a future caller must
|
|
@@ -474,7 +474,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
474
474
|
const { requestSessions, setSessionSidebarOpen } = sessionDirectoryState;
|
|
475
475
|
const { handleSettingsMessage, openSettings, refreshSettings, ...settingsState } =
|
|
476
476
|
useSettingsState(send);
|
|
477
|
-
//
|
|
477
|
+
// Read inside `handleMessage` via a ref, never `settingsState.settingsOpen` directly —
|
|
478
478
|
// that reactive value would have to sit in `handleMessage`'s own dependency array, which sits in
|
|
479
479
|
// the connection effect's dependency array below: opening/closing Settings would give
|
|
480
480
|
// `handleMessage` a new identity each time, tearing down and reopening the WebSocket underneath it.
|
|
@@ -496,7 +496,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
496
496
|
|
|
497
497
|
const handleMessage = useCallback(
|
|
498
498
|
(msg: TServerMessage): void => {
|
|
499
|
-
//
|
|
499
|
+
// Touching the exhaustive registry here keeps every decoded variant tied to an
|
|
500
500
|
// explicit GUI ownership decision, including variants intentionally handled by focused views.
|
|
501
501
|
void SERVER_MESSAGE_HANDLING[msg.type];
|
|
502
502
|
if (handleUsageMessage(msg)) return;
|
|
@@ -508,7 +508,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
508
508
|
if (handleSessionsMessage(msg)) return;
|
|
509
509
|
if (handleSettingsMessage(msg)) return;
|
|
510
510
|
if (handleAgentDefinitionsMessage(msg)) return;
|
|
511
|
-
//
|
|
511
|
+
// Exclusively owns `background_tasks` (the Scheduled group's roster); a
|
|
512
512
|
// schedule write instead rides the generic `command`/`cancel-background-task` messages, so
|
|
513
513
|
// for THOSE types this always returns false — it only refreshes on top of whatever else those
|
|
514
514
|
// types already do below, never in place of it.
|
|
@@ -520,7 +520,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
520
520
|
break;
|
|
521
521
|
}
|
|
522
522
|
case 'messages': {
|
|
523
|
-
//
|
|
523
|
+
// `display` is the server's projection of this same history into tool rows,
|
|
524
524
|
// diffs and "Changed files" — a reload/reconnect/resume replay renders THAT. `display` is
|
|
525
525
|
// absent only for a host that predates it; that host still gets a working (text-only)
|
|
526
526
|
// transcript, exactly what this branch always did.
|
|
@@ -553,7 +553,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
553
553
|
const next = streamingTextRef.current + msg.delta;
|
|
554
554
|
streamingTextRef.current = next;
|
|
555
555
|
setStreamingText(next);
|
|
556
|
-
//
|
|
556
|
+
// Also track WHERE this text sits relative to the turn's tool calls, for finishTurn.
|
|
557
557
|
turnSegmentsRef.current = appendTextDeltaToSegments(turnSegmentsRef.current, msg.delta);
|
|
558
558
|
break;
|
|
559
559
|
}
|
|
@@ -570,7 +570,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
570
570
|
}
|
|
571
571
|
case 'tool_end': {
|
|
572
572
|
const { state } = msg;
|
|
573
|
-
//
|
|
573
|
+
// Attribute to the ONE matching running call (executionId-first) — never map over
|
|
574
574
|
// every running same-named entry, which would close all of them at once.
|
|
575
575
|
updateActiveTools((prev) => {
|
|
576
576
|
const idx = findRunningToolIndex(prev, state);
|
|
@@ -587,7 +587,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
587
587
|
setExecutionWorkspace(msg.snapshot);
|
|
588
588
|
break;
|
|
589
589
|
}
|
|
590
|
-
//
|
|
590
|
+
// A Stop that did not take effect (e.g. the task/loop had already finished) is
|
|
591
591
|
// worth telling the operator; a success shows itself through the snapshot update above.
|
|
592
592
|
case 'background_task_control_result': {
|
|
593
593
|
if (msg.action === 'send' && executionDetailInputRef.current === msg.taskId) {
|
|
@@ -611,7 +611,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
611
611
|
}
|
|
612
612
|
break;
|
|
613
613
|
}
|
|
614
|
-
//
|
|
614
|
+
// The prompt queued behind a running turn — shown above the composer, editable
|
|
615
615
|
// and removable (`cancel-queue`). `pending: null` means nothing waits; the row disappears.
|
|
616
616
|
case 'pending': {
|
|
617
617
|
setQueuedPrompt(
|
|
@@ -622,12 +622,12 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
622
622
|
case 'permission_request':
|
|
623
623
|
case 'ask_request':
|
|
624
624
|
case 'prompt_resolved': {
|
|
625
|
-
//
|
|
625
|
+
// The paired owner renders + answers its own prompts (local == remote).
|
|
626
626
|
setPendingPrompts((prev) => applyPromptEvent(prev, msg));
|
|
627
627
|
break;
|
|
628
628
|
}
|
|
629
629
|
case 'ui_intent': {
|
|
630
|
-
//
|
|
630
|
+
// `/resume` asks for the session picker — the sidebar, when the host lists sessions.
|
|
631
631
|
if (guiScreenForUiIntent(msg.event.intent) === 'session-sidebar' && canListSessions()) {
|
|
632
632
|
setSessionSidebarOpen(true);
|
|
633
633
|
requestSessions();
|
|
@@ -639,7 +639,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
639
639
|
}
|
|
640
640
|
break;
|
|
641
641
|
}
|
|
642
|
-
//
|
|
642
|
+
// `/settings` asks for the Settings screen — always available on this surface.
|
|
643
643
|
if (guiScreenForUiIntent(msg.event.intent) === 'settings') {
|
|
644
644
|
openSettings();
|
|
645
645
|
if (commandsInFlightRef.current > 0) {
|
|
@@ -650,7 +650,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
650
650
|
}
|
|
651
651
|
break;
|
|
652
652
|
}
|
|
653
|
-
//
|
|
653
|
+
// `/plugin` asks for the plugin manager — the Settings screen's
|
|
654
654
|
// Plugins section, the way `show-settings` opens the screen itself above.
|
|
655
655
|
if (guiScreenForUiIntent(msg.event.intent) === 'settings-plugins') {
|
|
656
656
|
openSettings('plugins');
|
|
@@ -662,7 +662,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
662
662
|
}
|
|
663
663
|
break;
|
|
664
664
|
}
|
|
665
|
-
//
|
|
665
|
+
// `/agent` (bare) asks for the switcher — always available on this
|
|
666
666
|
// surface, unlike the session picker, which needs a host that lists sessions.
|
|
667
667
|
if (guiScreenForUiIntent(msg.event.intent) === 'agent-switcher') {
|
|
668
668
|
openAgentSwitcher();
|
|
@@ -674,8 +674,8 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
674
674
|
}
|
|
675
675
|
break;
|
|
676
676
|
}
|
|
677
|
-
//
|
|
678
|
-
// The command's result follows; the unavailable line answers it (
|
|
677
|
+
// A command this surface issued requested a screen the GUI does not have.
|
|
678
|
+
// The command's result follows; the unavailable line answers it (never silent).
|
|
679
679
|
const unavailable = {
|
|
680
680
|
name: uiIntentCommandName(msg.event.intent),
|
|
681
681
|
text: describeUiIntentForGui(msg.event.intent),
|
|
@@ -694,14 +694,14 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
694
694
|
}
|
|
695
695
|
break;
|
|
696
696
|
}
|
|
697
|
-
//
|
|
697
|
+
// Broadcast session events — a rename/clear executed by the HOST (from any
|
|
698
698
|
// surface, co-driving included) is reflected here; never a silent drop.
|
|
699
699
|
case 'session_renamed': {
|
|
700
700
|
setSessionName(msg.event.name);
|
|
701
701
|
requestSessions();
|
|
702
702
|
break;
|
|
703
703
|
}
|
|
704
|
-
//
|
|
704
|
+
// The host made another session current — drop what this one showed, re-read it all.
|
|
705
705
|
case 'session_switched': {
|
|
706
706
|
rejectPendingAppRequests('The active session changed.');
|
|
707
707
|
settleSessionChange();
|
|
@@ -762,7 +762,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
762
762
|
]);
|
|
763
763
|
break;
|
|
764
764
|
}
|
|
765
|
-
//
|
|
765
|
+
// A new or switch this surface asked for was refused; the reason is the toast, and
|
|
766
766
|
// commands in flight are untouched. Only the client that asked is told.
|
|
767
767
|
case 'session_change_failed': {
|
|
768
768
|
if (
|
|
@@ -778,7 +778,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
778
778
|
]);
|
|
779
779
|
break;
|
|
780
780
|
}
|
|
781
|
-
//
|
|
781
|
+
// A row's rename landed — refresh the listing so its title (or the live one, if it
|
|
782
782
|
// was current) is current everywhere it shows.
|
|
783
783
|
case 'session_renamed_in_list': {
|
|
784
784
|
requestSessions();
|
|
@@ -791,7 +791,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
791
791
|
]);
|
|
792
792
|
break;
|
|
793
793
|
}
|
|
794
|
-
//
|
|
794
|
+
// A row was deleted. If it was this surface's own session, `session_switched`
|
|
795
795
|
// already arrived (the host switches away before answering) and reset the conversation; this
|
|
796
796
|
// only needs the listing refreshed so the row is gone everywhere it shows.
|
|
797
797
|
case 'session_deleted': {
|
|
@@ -880,7 +880,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
880
880
|
send({ type: 'get-status' });
|
|
881
881
|
send({ type: 'get-commands' });
|
|
882
882
|
commandsInFlightRef.current = Math.max(0, commandsInFlightRef.current - 1);
|
|
883
|
-
//
|
|
883
|
+
// A successful `/provider` command run through the raw command path (Add, Edit,
|
|
884
884
|
// Duplicate — Use/Model/Delete already return a fresh snapshot from their own
|
|
885
885
|
// `update-settings` reply) changes the "Providers & Models" list; refresh it if the screen
|
|
886
886
|
// is open, the same way `get-status` above refreshes the model/mode chips. Never touches
|
|
@@ -888,13 +888,13 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
888
888
|
if (msg.name === 'provider' && msg.success && settingsOpenRef.current) {
|
|
889
889
|
refreshSettings();
|
|
890
890
|
}
|
|
891
|
-
//
|
|
891
|
+
// Identify silence by THIS reply's own `requestId`, not by position or
|
|
892
892
|
// count — see `silentCommandRequestIdsRef` above. No `requestId` (an older host, or a typed
|
|
893
893
|
// command) always falls through to `silent = false`, never the reverse.
|
|
894
894
|
const silent =
|
|
895
895
|
msg.requestId !== undefined && silentCommandRequestIdsRef.current.delete(msg.requestId);
|
|
896
|
-
//
|
|
897
|
-
// confirmation line answers it, never a conversation card
|
|
896
|
+
// A selection made in the agent switcher sheet — its own plain
|
|
897
|
+
// confirmation line answers it, never a conversation card.
|
|
898
898
|
if (resolveSwitchResult(msg.requestId, msg)) break;
|
|
899
899
|
const unavailable = pendingIntentRef.current;
|
|
900
900
|
pendingIntentRef.current = null;
|
|
@@ -913,7 +913,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
913
913
|
}
|
|
914
914
|
// A command that starts a turn (a skill) says nothing itself; the turn is its answer.
|
|
915
915
|
if (msg.message.trim().length === 0) break;
|
|
916
|
-
//
|
|
916
|
+
// A change made through a status control confirms itself through the
|
|
917
917
|
// control's own label (the `get-status` refresh above lands it) — no conversation card. A
|
|
918
918
|
// failure still needs saying, as a plain notice rather than a card, and the label is left
|
|
919
919
|
// unchanged since nothing actually applied. A typed command (plain `send`) keeps its card
|
|
@@ -939,7 +939,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
939
939
|
case 'complete':
|
|
940
940
|
case 'interrupted': {
|
|
941
941
|
send({ type: 'get-status' });
|
|
942
|
-
//
|
|
942
|
+
// The turn ending can advance the queue (its next entry now runs) or, if this
|
|
943
943
|
// was an abort, drop it entirely (`abort()` clears the whole queue) — either way the
|
|
944
944
|
// composer's queued-message row is stale until this reply refreshes it.
|
|
945
945
|
send({ type: 'get-pending' });
|
|
@@ -1025,7 +1025,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
1025
1025
|
client.send({ type: 'get-surface-role' });
|
|
1026
1026
|
client.send({ type: 'get-commands' });
|
|
1027
1027
|
client.send({ type: 'get-status' });
|
|
1028
|
-
//
|
|
1028
|
+
// A reconnect that lands back on the SAME session (the common case) fires no
|
|
1029
1029
|
// `session_switched` — its own `get-pending` would be missed — so ask here too, or a stale
|
|
1030
1030
|
// `queuedPrompt` from before the drop keeps showing (with working Edit/Remove) until the
|
|
1031
1031
|
// turn it was queued behind happens to end.
|
|
@@ -1108,7 +1108,7 @@ export function useSessionClient<TStatus extends string = TConnectionStatus>(
|
|
|
1108
1108
|
export function useWsSession(
|
|
1109
1109
|
url: string,
|
|
1110
1110
|
): IWsSessionState<TConnectionStatus> & { connectionLost: boolean } {
|
|
1111
|
-
//
|
|
1111
|
+
// Retries gave up — the runtime is not coming back by itself. A presentation layer
|
|
1112
1112
|
// (e.g. `SessionSurface`) reads this to show a banner instead of a callback-driven screen swap.
|
|
1113
1113
|
const [connectionLost, setConnectionLost] = useState(false);
|
|
1114
1114
|
const makeClient = useCallback<TMakeSessionClient<TConnectionStatus>>(
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type {
|
|
|
23
23
|
TSessionListing,
|
|
24
24
|
TSessionsError,
|
|
25
25
|
} from './hooks/session-client-types.js';
|
|
26
|
-
//
|
|
26
|
+
// A host that can restart the runtime calls this before reloading the page, so the
|
|
27
27
|
// first session listing after reconnecting can switch back to the session the person was in.
|
|
28
28
|
export { rememberSessionForRestore } from './hooks/use-session-directory.js';
|
|
29
29
|
|
|
@@ -46,7 +46,7 @@ export type {
|
|
|
46
46
|
export { applyPromptEvent, permissionResponse, askResponse } from './hooks/prompt-state.js';
|
|
47
47
|
export type { TPendingPrompt } from './hooks/prompt-state.js';
|
|
48
48
|
|
|
49
|
-
// ── UI-intent (command screen-request) state —
|
|
49
|
+
// ── UI-intent (command screen-request) state — ──
|
|
50
50
|
export { describeUiIntentForGui, guiScreenForUiIntent } from './hooks/ui-intent-state.js';
|
|
51
51
|
|
|
52
52
|
// ── Presentation components ─────────────────────────────────
|
|
@@ -60,12 +60,12 @@ export { SessionSurface, CenteredChrome } from './components/SessionSurface.js';
|
|
|
60
60
|
export type { IComposerHandle, IPickedFile } from './components/Composer.js';
|
|
61
61
|
export { SessionSidebar } from './components/SessionSidebar.js';
|
|
62
62
|
export { SessionMonitor } from './components/SessionMonitor.js';
|
|
63
|
-
//
|
|
64
|
-
// and
|
|
63
|
+
// The shared modal-shell primitive (also consumed by the mode-change confirmation
|
|
64
|
+
// and the session-delete confirmation) and the Settings screen it backs.
|
|
65
65
|
export { Dialog, ConfirmDialog } from './components/Dialog.js';
|
|
66
66
|
export type { IDialogProps, IConfirmDialogProps } from './components/Dialog.js';
|
|
67
67
|
export { SettingsScreen } from './components/SettingsScreen.js';
|
|
68
|
-
//
|
|
68
|
+
// The Help sheet `/help` opens instead of the terminal-style text list.
|
|
69
69
|
export { HelpSheet } from './components/HelpSheet.js';
|
|
70
70
|
// Pieces of a surface's chrome, not roots: render them inside a `agent-ui` scope.
|
|
71
71
|
export { ProductMark, ProductWordmark } from './components/Brand.js';
|
package/src/schedule-view.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Plain-words presentation for the Agents panel's "Scheduled" group. Never an
|
|
3
3
|
* ISO string or a cron expression: a concrete `nextFireAt` is shown in local time as "Today 9:00" /
|
|
4
4
|
* "Tomorrow 9:00" / a weekday / a date; a handful of common cron shapes are phrased ("Every weekday
|
|
5
5
|
* at 9:00"); anything else falls back to "Repeats".
|
package/src/styles/surface.css
CHANGED
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
/*
|
|
219
|
-
* A code block's own horizontal scrollbar
|
|
219
|
+
* A code block's own horizontal scrollbar stays visible rather than only-on-hover like the
|
|
220
220
|
* rest of the page (`theme.css`'s quiet overlay scrollbar) — a long unwrapped line needs a standing
|
|
221
221
|
* signal that there is more of it, not one that only appears once the person already found the edge.
|
|
222
222
|
* `.gui-code-scroll::-webkit-scrollbar-thumb` outranks that page-wide bare `::-webkit-scrollbar-thumb`
|