@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
|
@@ -10,7 +10,7 @@ import type { IPopupMenuItem, IPopupMenuSection } from './PopupMenu.js';
|
|
|
10
10
|
import type { TCommandCatalog, TModelListSnapshot, TSessionStatus } from '../hooks/session-client-types.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* The plain label for each effort selection. Kept in step with
|
|
14
14
|
* `EFFORT_LEVEL_LABELS` in `packages/agent-framework/src/effort/effort-resolution.ts` (which
|
|
15
15
|
* `agent-command`'s `/effort` re-exports and reports as its own outcome text) — this package cannot
|
|
16
16
|
* import that one directly (it is a Node-side package this browser bundle does not depend on), so the
|
|
@@ -28,7 +28,7 @@ const EFFORT_LEVEL_LABELS: Readonly<Record<TSessionStatus['effort'], string>> =
|
|
|
28
28
|
max: 'Maximum',
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
/** The primary row
|
|
31
|
+
/** The primary row; the rest sit under "More". */
|
|
32
32
|
const EFFORT_PRIMARY: readonly TSessionStatus['effort'][] = ['auto', 'low', 'medium', 'high'];
|
|
33
33
|
const EFFORT_MORE: readonly TSessionStatus['effort'][] = ['none', 'minimal', 'xhigh', 'max'];
|
|
34
34
|
|
|
@@ -48,7 +48,7 @@ const CHIP_CLASS =
|
|
|
48
48
|
'flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-[13px] text-muted-foreground hover:bg-hover hover:text-foreground disabled:cursor-not-allowed disabled:opacity-60 disabled:hover:bg-transparent';
|
|
49
49
|
const CHIP_WARNING_CLASS = 'text-warning hover:text-warning';
|
|
50
50
|
|
|
51
|
-
/** One status chip that opens a `PopupMenu` HIG pop-up beneath it
|
|
51
|
+
/** One status chip that opens a `PopupMenu` HIG pop-up beneath it. */
|
|
52
52
|
function StatusChip({
|
|
53
53
|
chipLabel,
|
|
54
54
|
value,
|
|
@@ -79,7 +79,7 @@ function StatusChip({
|
|
|
79
79
|
aria-label={`${chipLabel}: ${value}`}
|
|
80
80
|
aria-haspopup="menu"
|
|
81
81
|
aria-expanded={open}
|
|
82
|
-
//
|
|
82
|
+
// Below `md` the value text is hidden (icon-only) — the tooltip is where it stays
|
|
83
83
|
// reachable on hover and keyboard focus, so it always carries the value, not just "Change X".
|
|
84
84
|
title={connected ? `${chipLabel}: ${value}` : 'Reconnecting…'}
|
|
85
85
|
disabled={!connected}
|
|
@@ -90,7 +90,7 @@ function StatusChip({
|
|
|
90
90
|
className={`${CHIP_CLASS} ${warning ? CHIP_WARNING_CLASS : ''}`}
|
|
91
91
|
>
|
|
92
92
|
{icon}
|
|
93
|
-
{/*
|
|
93
|
+
{/* Below `md` (the same breakpoint the session-list sheet uses) the chip collapses
|
|
94
94
|
to its icon alone — never a truncated fragment ("d…", "claude-…") — the value stays
|
|
95
95
|
reachable via the tooltip above and the accessible name either way. */}
|
|
96
96
|
<span className="hidden truncate md:inline">{value}</span>
|
|
@@ -110,7 +110,7 @@ function StatusChip({
|
|
|
110
110
|
const ICON_PROPS = { size: 14, strokeWidth: 1.75, 'aria-hidden': true } as const;
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Model · mode · effort, each a pop-up menu
|
|
113
|
+
* Model · mode · effort, each a pop-up menu, and the context the conversation fills.
|
|
114
114
|
* Every menu applies its choice through the same command wire path the equivalent typed slash
|
|
115
115
|
* command uses — `onSilentCommand` — so `/model <id>`, `/mode <name>` and `/effort <value>` and a
|
|
116
116
|
* click in these menus can never drift apart; the control's own chip label (fed by the session's next
|
|
@@ -129,14 +129,14 @@ export function StatusRow({
|
|
|
129
129
|
catalog: TCommandCatalog | null;
|
|
130
130
|
modelList: TModelListSnapshot | null;
|
|
131
131
|
onRequestModelList: () => void;
|
|
132
|
-
/**
|
|
132
|
+
/** "Manage providers…" opens Settings at the Providers & Models section. */
|
|
133
133
|
onManageProviders: () => void;
|
|
134
134
|
/** Applies a model/mode/effort choice without a conversation card (see the module doc). */
|
|
135
135
|
onSilentCommand: (name: string, args?: string) => void;
|
|
136
136
|
connected: boolean;
|
|
137
137
|
}): React.ReactElement {
|
|
138
138
|
const used = status ? Math.round(status.context.usedPercentage) : null;
|
|
139
|
-
//
|
|
139
|
+
// "Skip all checks" asks for confirmation on the shared `ConfirmDialog` before
|
|
140
140
|
// applying — every other mode applies at once. `true` while that confirmation is open.
|
|
141
141
|
const [pendingBypassConfirm, setPendingBypassConfirm] = useState(false);
|
|
142
142
|
|
|
@@ -158,7 +158,7 @@ export function StatusRow({
|
|
|
158
158
|
key: 'manage-providers',
|
|
159
159
|
label: 'Manage providers…',
|
|
160
160
|
kind: 'action',
|
|
161
|
-
//
|
|
161
|
+
// Opens Settings at the "Providers & Models" section — the same list, Use/Model/
|
|
162
162
|
// Edit/Test/Duplicate/Delete actions and "Add provider…" flow now live there instead of in
|
|
163
163
|
// this menu's own server-driven picker.
|
|
164
164
|
onSelect: onManageProviders,
|
|
@@ -175,7 +175,7 @@ export function StatusRow({
|
|
|
175
175
|
label: sub.displayName ?? sub.name,
|
|
176
176
|
description: sub.description,
|
|
177
177
|
checked: sub.name === status?.permissionMode,
|
|
178
|
-
//
|
|
178
|
+
// Every mode but "Skip all checks" applies at once; that one opens a
|
|
179
179
|
// confirmation first (rendered below) instead of applying here directly.
|
|
180
180
|
onSelect:
|
|
181
181
|
sub.name === 'bypassPermissions'
|
|
@@ -253,8 +253,8 @@ export function StatusRow({
|
|
|
253
253
|
<ContextRing percent={used ?? 0} />
|
|
254
254
|
{used === null ? '' : `${used}%`}
|
|
255
255
|
</span>
|
|
256
|
-
{/*
|
|
257
|
-
ships for the identical action (
|
|
256
|
+
{/* Matches the wording the Settings screen's own permission-mode picker already
|
|
257
|
+
ships for the identical action (`SettingsScreen.tsx`), so switching to "Skip all
|
|
258
258
|
checks" reads the same warning regardless of which control was used. */}
|
|
259
259
|
<ConfirmDialog
|
|
260
260
|
open={pendingBypassConfirm}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pure logic behind the composer's attach button and drag-and-drop, kept apart from
|
|
3
3
|
* `Composer.tsx` so it is testable without React or a DOM.
|
|
4
4
|
*
|
|
5
5
|
* The runtime resolves `@relative/path` references already (`prompt-file-reference-resolver.ts` in
|
|
@@ -26,7 +26,7 @@ export const MAX_ATTACHMENT_TOTAL_BYTES = 256 * 1024;
|
|
|
26
26
|
*/
|
|
27
27
|
export const MAX_ATTACHMENT_COUNT = 8;
|
|
28
28
|
|
|
29
|
-
/** Shown whenever a file cannot be resolved to a path inside the workspace — rule 3
|
|
29
|
+
/** Shown whenever a file cannot be resolved to a path inside the workspace — rule 3. */
|
|
30
30
|
export const ATTACHMENT_NO_PATH_NOTICE = 'Only files inside this project folder can be attached.';
|
|
31
31
|
|
|
32
32
|
/** Shown once the running count of attachments would pass `MAX_ATTACHMENT_COUNT`. */
|
|
@@ -50,7 +50,7 @@ export interface IDraftAttachment {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* One file the desktop host's native "Attach files" dialog returned
|
|
53
|
+
* One file the desktop host's native "Attach files" dialog returned. Kept in structural
|
|
54
54
|
* sync with `IPickedFile` in `apps/agent-gui-web/src/gui-host.ts` and
|
|
55
55
|
* `apps/agent-app/electron/sidecar.ts` — the three are not imported across the Electron/browser/
|
|
56
56
|
* presentation package boundaries, same as this codebase already does for the trust question.
|
|
@@ -228,11 +228,11 @@ export function evaluateCandidateFile(
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
|
-
* Inserts each chip as the runtime's existing `@relative/path` reference syntax — rule 2
|
|
231
|
+
* Inserts each chip as the runtime's existing `@relative/path` reference syntax — rule 2.
|
|
232
232
|
* Always prefixed with `./`: the parser's `isPathLikeReference` only treats a token as a file
|
|
233
233
|
* reference when it has a `.` somewhere or an explicit relative-path prefix, so a dotless name like
|
|
234
234
|
* `Makefile`, `Dockerfile` or `LICENSE` — common, useful, and never refused for lacking one — would
|
|
235
|
-
* otherwise be sent as plain text with no file and no diagnostic
|
|
235
|
+
* otherwise be sent as plain text with no file and no diagnostic. `./` makes
|
|
236
236
|
* every path path-like regardless of its name, and the resolver collapses it back to the plain
|
|
237
237
|
* relative path before use.
|
|
238
238
|
*/
|
package/src/driver-labels.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Plain, human labels for a driver id, and whether one names the SAME KIND
|
|
3
3
|
* of surface as this connection's own. Shared by every place the GUI shows who drove a turn (a
|
|
4
4
|
* message, a permission/ask prompt): the person's own input never carries a label, and a turn from a
|
|
5
5
|
* different kind of surface does — in words, never a raw id.
|
|
@@ -16,7 +16,7 @@ import { AGENT_DRIVER_ID, OWNER_DRIVER_ID } from '@robota-sdk/agent-interface-se
|
|
|
16
16
|
import type { TDriverId } from '@robota-sdk/agent-interface-session';
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* `remote` (a plain WS remote connection's own id, `'remote:ws'`) and `peer` (an unrelated
|
|
19
|
+
* `remote` (a plain WS remote connection's own id, `'remote:ws'`) and `peer` (an unrelated
|
|
20
20
|
* mesh peer's turn, `'peer:<sessionId>'`) are kept as DIFFERENT kinds even though both render the same
|
|
21
21
|
* label text: a plain WS viewer's own id is `'remote:ws'`, and folding a peer's turn into that same
|
|
22
22
|
* kind would make `isSameSurface` treat a co-driver's turn as this connection's own and hide it — the
|
|
@@ -76,7 +76,7 @@ export function humanDriverLabel(driverId: TDriverId): string {
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* The ready-to-render attribution phrase: "from <label>" for a co-driver, or "Automatic — loop"
|
|
79
|
-
* for the agent's own wake-ups — "from automatic" reads as a typo, not a source.
|
|
79
|
+
* for the agent's own wake-ups — "from automatic" reads as a typo, not a source. Every
|
|
80
80
|
* `AGENT_DRIVER_ID` turn is, today, a loop's own injected iteration (fixed or self-paced —
|
|
81
81
|
* `requestWakeup` in `interactive-session.ts` is the one place that stamps this id); if a
|
|
82
82
|
* differently-sourced agent-wakeup turn is ever added, this label needs to name that source
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* One true-status label for an execution-workspace entry, shared by the Agents panel row
|
|
3
3
|
* and its detail sheet so the two never say something different about the same entry.
|
|
4
4
|
*
|
|
5
5
|
* A completed task whose result still reports a refused tool call is not "Done" — it is "Needs
|
package/src/goal-view.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* What the composer's `/` menu offers for a draft. Pure, so the matching is testable without
|
|
3
3
|
* rendering: the TUI's autocomplete offers commands and skills the same way.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* A command the GUI cannot run at all (`isExcludedCommand`) never appears here — there is
|
|
6
6
|
* nothing useful for choosing it to do, and typing it by hand is answered separately (see
|
|
7
7
|
* `excluded-commands.ts`, `useSessionClient.ts`). Commands are listed before skills, each in its own
|
|
8
8
|
* group, so the menu can head them "Commands" and "Skills".
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Built-in commands left out of the GUI on purpose (the design decisions in
|
|
3
3
|
* `packages/agent-gui-web/docs/SPEC.md` group them by reason). The `/` menu never lists one of these
|
|
4
4
|
* (`command-menu.ts`), and typing one by hand anyway is caught before it reaches the session
|
|
5
5
|
* (`useSessionClient.ts`'s `send`) and answered with the plain sentence here — never the session's own
|
|
6
|
-
* refusal, and never the generic "not available on this surface" line
|
|
6
|
+
* refusal, and never the generic "not available on this surface" line used for a screen this
|
|
7
7
|
* surface simply has not built yet (see `ui-intent-state.ts`).
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure prompt-state derivation for the web UI (
|
|
2
|
+
* Pure prompt-state derivation for the web UI (render + answer).
|
|
3
3
|
*
|
|
4
4
|
* The paired browser is the session OWNER (local == remote), so it must render and ANSWER the owner's own
|
|
5
5
|
* permission/ask prompts. The server carries `permission_request`/`ask_request` (a prompt appears) and
|
|
@@ -32,9 +32,9 @@ export function applyPromptEvent(
|
|
|
32
32
|
): readonly TPendingPrompt[] {
|
|
33
33
|
switch (msg.type) {
|
|
34
34
|
case 'permission_request': {
|
|
35
|
-
//
|
|
36
|
-
// prompt.
|
|
37
|
-
//
|
|
35
|
+
// Keep requesterDriverId so the surface can show WHICH driver's turn raised the
|
|
36
|
+
// prompt. Keep `requester` too, so it can show WHICH background agent did.
|
|
37
|
+
// diffLines/diffFile (Edit/Write) and cwd (Shell, only when it differs) are the
|
|
38
38
|
// server's diff/command preview — carried through unchanged so the prompt can render them
|
|
39
39
|
// instead of raw args.
|
|
40
40
|
const { id, toolName, toolArgs, requesterDriverId, requester, diffLines, diffFile, cwd } =
|
|
@@ -4,7 +4,7 @@ export type TServerMessageHandling =
|
|
|
4
4
|
'reducer-state' | 'visible-notice' | 'intentionally-not-rendered' | 'transport-control';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Exhaustive ownership for every decoded server variant on the shared GUI surface.
|
|
8
8
|
* Adding a wire variant without deciding its GUI disposition is a compile error instead of a silent
|
|
9
9
|
* reducer omission. `intentionally-not-rendered` is an explicit disposition for server-side query
|
|
10
10
|
* snapshots and domain events that this session/usage surface does not currently visualize.
|
|
@@ -28,10 +28,10 @@ export const SERVER_MESSAGE_HANDLING = {
|
|
|
28
28
|
turn_source: 'intentionally-not-rendered',
|
|
29
29
|
commands: 'reducer-state',
|
|
30
30
|
session_status: 'reducer-state',
|
|
31
|
-
//
|
|
31
|
+
// The reply to `list-models`, fed into `modelList` state by the model control's
|
|
32
32
|
// pop-up menu (`useModelListState`).
|
|
33
33
|
model_list: 'reducer-state',
|
|
34
|
-
//
|
|
34
|
+
// The agent switcher sheet's own correlated request/response state (`useAgentSwitcherState`).
|
|
35
35
|
agent_definitions: 'reducer-state',
|
|
36
36
|
sessions: 'reducer-state',
|
|
37
37
|
sessions_error: 'reducer-state',
|
|
@@ -47,10 +47,10 @@ export const SERVER_MESSAGE_HANDLING = {
|
|
|
47
47
|
stored_session_usage_report: 'reducer-state',
|
|
48
48
|
stored_session_usage_report_error: 'visible-notice',
|
|
49
49
|
executing: 'intentionally-not-rendered',
|
|
50
|
-
//
|
|
50
|
+
// The prompt queued behind a running turn — shown above the composer with Edit/Remove.
|
|
51
51
|
pending: 'reducer-state',
|
|
52
52
|
execution_workspace_event: 'reducer-state',
|
|
53
|
-
//
|
|
53
|
+
// The Agents panel's detail sheet reads a page at a time.
|
|
54
54
|
execution_detail: 'reducer-state',
|
|
55
55
|
execution_detail_error: 'reducer-state',
|
|
56
56
|
// Esc's loop stop is a TUI-only shortcut; this surface's loops stop through the Agents panel
|
|
@@ -58,7 +58,7 @@ export const SERVER_MESSAGE_HANDLING = {
|
|
|
58
58
|
waiting_loop_stop: 'intentionally-not-rendered',
|
|
59
59
|
// The Agents panel already reflects a task's cancel/close/send through `execution_workspace_event`;
|
|
60
60
|
// nothing here renders this narrower, task-only event on its own.
|
|
61
|
-
//
|
|
61
|
+
// The Project panel's reads — reduced into state by `useProjectPanelState`.
|
|
62
62
|
project_status: 'reducer-state',
|
|
63
63
|
project_diff: 'reducer-state',
|
|
64
64
|
project_memory: 'reducer-state',
|
|
@@ -70,7 +70,7 @@ export const SERVER_MESSAGE_HANDLING = {
|
|
|
70
70
|
plan_event: 'intentionally-not-rendered',
|
|
71
71
|
context_file_refreshed: 'intentionally-not-rendered',
|
|
72
72
|
branch_event: 'intentionally-not-rendered',
|
|
73
|
-
//
|
|
73
|
+
// The Agents panel's "Scheduled" group's own roster (`useSchedulesState`).
|
|
74
74
|
background_tasks: 'reducer-state',
|
|
75
75
|
background_task: 'intentionally-not-rendered',
|
|
76
76
|
background_job_groups: 'intentionally-not-rendered',
|
|
@@ -82,16 +82,16 @@ export const SERVER_MESSAGE_HANDLING = {
|
|
|
82
82
|
ui_intent: 'reducer-state',
|
|
83
83
|
session_renamed: 'reducer-state',
|
|
84
84
|
history_cleared: 'reducer-state',
|
|
85
|
-
//
|
|
86
|
-
// the operator; a success shows itself through the workspace snapshot's own update.
|
|
87
|
-
//
|
|
85
|
+
// A failed Stop (e.g. a task that finished a moment before it arrived) is worth telling
|
|
86
|
+
// the operator; a success shows itself through the workspace snapshot's own update. A schedule's
|
|
87
|
+
// Delete uses this same `cancel-background-task` write — `useSchedulesState`
|
|
88
88
|
// refreshes the roster on a successful cancel, and a failure gets the same visible notice as Stop.
|
|
89
89
|
background_task_control_result: 'visible-notice',
|
|
90
90
|
protocol_error: 'visible-notice',
|
|
91
91
|
resume_gap: 'transport-control',
|
|
92
92
|
// The connection's effective role: the session client holds it and stops sending what an observer may not.
|
|
93
93
|
surface_role: 'reducer-state',
|
|
94
|
-
//
|
|
94
|
+
// The Settings screen's own correlated request/response state (`useSettingsState`).
|
|
95
95
|
settings: 'reducer-state',
|
|
96
96
|
settings_error: 'reducer-state',
|
|
97
97
|
} as const satisfies Readonly<Record<TServerMessage['type'], TServerMessageHandling>>;
|
|
@@ -38,7 +38,7 @@ export type TCommandCatalog = Omit<Extract<TServerMessage, { type: 'commands' }>
|
|
|
38
38
|
export type TSessionStatus = Extract<TServerMessage, { type: 'session_status' }>['status'];
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* The models a person may switch to, grouped by configured provider profile —
|
|
42
42
|
* what the model control's pop-up menu is built from. `null` until requested (`list-models`).
|
|
43
43
|
*/
|
|
44
44
|
export type TModelListSnapshot = Omit<
|
|
@@ -59,7 +59,7 @@ export type TCurrentSessionUsageReport = Extract<
|
|
|
59
59
|
{ type: 'usage_report' }
|
|
60
60
|
>['report'];
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/** The Project panel's reads. Each result crosses the wire unchanged from the session
|
|
63
63
|
* method's own return value (see `wire-messages.ts`'s doc comment on `project_status`). */
|
|
64
64
|
export type TProjectStatusRead = Extract<TServerMessage, { type: 'project_status' }>['result'];
|
|
65
65
|
export type TProjectDiffRead = Extract<TServerMessage, { type: 'project_diff' }>['result'];
|
|
@@ -70,7 +70,7 @@ export interface IConversationMessage {
|
|
|
70
70
|
role: 'user' | 'assistant';
|
|
71
71
|
content: string;
|
|
72
72
|
isStreaming?: boolean;
|
|
73
|
-
/**
|
|
73
|
+
/** The co-driving author of a user turn. */
|
|
74
74
|
author?: string;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -101,7 +101,7 @@ export interface IChangedFileSummary {
|
|
|
101
101
|
diffLines: readonly IDiffLine[];
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/**
|
|
104
|
+
/** A turn that changed files ends with this compact summary row. */
|
|
105
105
|
export interface IChangedFilesEntry {
|
|
106
106
|
id: string;
|
|
107
107
|
role: 'changed-files';
|
|
@@ -117,28 +117,28 @@ export interface IActiveTool {
|
|
|
117
117
|
status: 'running' | 'done' | 'error';
|
|
118
118
|
input?: string;
|
|
119
119
|
result?: IToolState['result'];
|
|
120
|
-
/**
|
|
120
|
+
/** Correlates this call across `tool_start`/`tool_end` — how a finished call is matched. */
|
|
121
121
|
executionId?: string;
|
|
122
|
-
/**
|
|
122
|
+
/** A unified diff (Edit/Write), built server-side — the same one an approval prompt showed. */
|
|
123
123
|
diffLines?: readonly IDiffLine[];
|
|
124
|
-
/**
|
|
124
|
+
/** The file `diffLines` concerns, workspace-relative when the server could make it so. */
|
|
125
125
|
diffFile?: string;
|
|
126
|
-
/**
|
|
126
|
+
/** The tool's raw result payload (Shell output, Read content, …), already capped server-side. */
|
|
127
127
|
toolResultData?: string;
|
|
128
128
|
/** Admitted observations; opaque resource URIs do not authorize a client-side fetch. */
|
|
129
129
|
toolResultParts?: IToolState['toolResultParts'];
|
|
130
|
-
/**
|
|
130
|
+
/** A workspace-relative display form of a path argument, additive to `input`/`firstArg`. */
|
|
131
131
|
displayPath?: string;
|
|
132
|
-
/**
|
|
132
|
+
/** Set when this call is a projected `/command` tool — render "Ran /<commandName>". */
|
|
133
133
|
commandName?: string;
|
|
134
|
-
/**
|
|
134
|
+
/** True for an internal signal tool (e.g. goal-status) — never rendered as a call. */
|
|
135
135
|
internal?: boolean;
|
|
136
136
|
|
|
137
137
|
appView?: IToolAppViewRef;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
|
-
*
|
|
141
|
+
* A prompt the host queued behind the running turn — the server has already taken it, so it
|
|
142
142
|
* is not lost, but it will not run until the turn ends. `count` is the TOTAL number waiting (the shown
|
|
143
143
|
* `text` plus any behind it), matching the wire's `pendingCount`.
|
|
144
144
|
*/
|
|
@@ -152,7 +152,7 @@ export interface ISessionNotice {
|
|
|
152
152
|
/**
|
|
153
153
|
* `session-change-refused`: the host refused a new or switch this surface asked for.
|
|
154
154
|
* `background-task-control-failed`: a Stop in the Agents panel did not take effect.
|
|
155
|
-
* `command-failed
|
|
155
|
+
* `command-failed`: a change made through a status control (model/mode/effort)
|
|
156
156
|
* failed — a plain notice rather than a conversation card, since the control's own label is where
|
|
157
157
|
* a successful change confirms itself and the label stays unchanged when it fails.
|
|
158
158
|
*/
|
|
@@ -165,7 +165,7 @@ export interface ISessionNotice {
|
|
|
165
165
|
| 'context-compacted';
|
|
166
166
|
message: string;
|
|
167
167
|
/**
|
|
168
|
-
* A `session-error` classified at the wire boundary
|
|
168
|
+
* A `session-error` classified at the wire boundary — present only when the host could
|
|
169
169
|
* tell what kind of failure this was. `SessionNotices` maps it to one plain sentence and keeps
|
|
170
170
|
* `message` behind a "Details" disclosure; absent, it shows `message` as before.
|
|
171
171
|
*/
|
|
@@ -195,7 +195,7 @@ export type TMakeSessionClient<TStatus extends string = TConnectionStatus> = (ca
|
|
|
195
195
|
export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
196
196
|
status: TStatus;
|
|
197
197
|
/**
|
|
198
|
-
* Set by `useWsSession` once its reconnect retries give up
|
|
198
|
+
* Set by `useWsSession` once its reconnect retries give up: the runtime is not
|
|
199
199
|
* coming back by itself. Cleared again once `status` reaches `connected`. Undefined for a reducer
|
|
200
200
|
* with no such concept (e.g. the WebRTC surface, which has its own `failed`/`refused` statuses).
|
|
201
201
|
*/
|
|
@@ -215,7 +215,7 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
215
215
|
/** Null until the session has answered; refreshed after every command and turn. */
|
|
216
216
|
sessionStatus: TSessionStatus | null;
|
|
217
217
|
/**
|
|
218
|
-
*
|
|
218
|
+
* The models the model control's pop-up menu offers. Null until requested — the
|
|
219
219
|
* menu asks for it when it opens (or refreshes it) rather than every surface fetching it eagerly.
|
|
220
220
|
*/
|
|
221
221
|
modelList: TModelListSnapshot | null;
|
|
@@ -239,13 +239,13 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
239
239
|
setSessionSidebarOpen: (open: boolean) => void;
|
|
240
240
|
send: (msg: TClientMessage) => void;
|
|
241
241
|
/**
|
|
242
|
-
*
|
|
242
|
+
* Run a command the SAME way `send({type:'command', ...})` does, but mark its
|
|
243
243
|
* outcome as controls-triggered — `useSessionClient` skips the conversation card for a change made
|
|
244
244
|
* this way (the control's own label confirms it instead), while a typed command keeps its card.
|
|
245
245
|
*/
|
|
246
246
|
sendCommandSilently: (name: string, args?: string) => void;
|
|
247
247
|
pendingPrompts: readonly TPendingPrompt[];
|
|
248
|
-
/**
|
|
248
|
+
/** The prompt queued behind a running turn, or null when none is queued. */
|
|
249
249
|
queuedPrompt: IQueuedPrompt | null;
|
|
250
250
|
answerPermission: (id: string, result: TPermissionResultValue) => void;
|
|
251
251
|
answerAsk: (id: string, response: TActionResponse) => void;
|
|
@@ -263,29 +263,29 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
263
263
|
requestCurrentSessionUsage: () => void;
|
|
264
264
|
sessionNotices: readonly ISessionNotice[];
|
|
265
265
|
dismissSessionNotice: (id: string) => void;
|
|
266
|
-
/**
|
|
266
|
+
/** Whether the Settings modal is shown. `/settings` and the gear button both open it. */
|
|
267
267
|
settingsOpen: boolean;
|
|
268
268
|
settingsStatus: 'idle' | 'loading' | 'ready' | 'error';
|
|
269
269
|
/** Null until the first `get-settings` reply arrives. */
|
|
270
270
|
settingsSnapshot: ISettingsSnapshot | null;
|
|
271
271
|
/** The plain message from the most recent failed read or write, if any. */
|
|
272
272
|
settingsError: string | null;
|
|
273
|
-
/**
|
|
273
|
+
/** Which section the screen should land on for the open in progress. */
|
|
274
274
|
settingsInitialSectionId: string | null;
|
|
275
275
|
/**
|
|
276
276
|
* Opens the screen and (re-)fetches its snapshot — the "reopen and see the new value" path.
|
|
277
277
|
* `sectionId` jumps straight to that section (e.g. `/plugin` → `'plugins'`, "Manage providers…"
|
|
278
|
-
*
|
|
278
|
+
* → `'providers'`); omitted opens on the screen's own default (General).
|
|
279
279
|
*/
|
|
280
280
|
openSettings: (sectionId?: string) => void;
|
|
281
281
|
closeSettings: () => void;
|
|
282
282
|
/** A failed write leaves `settingsSnapshot` at its previous value; `settingsError` names why. */
|
|
283
283
|
updateSettings: (patch: TSettingsPatch) => void;
|
|
284
|
-
/**
|
|
284
|
+
/** Whether the Help sheet is shown. `/help` opens it instead of reaching the session. */
|
|
285
285
|
helpOpen: boolean;
|
|
286
286
|
openHelp: () => void;
|
|
287
287
|
closeHelp: () => void;
|
|
288
|
-
/**
|
|
288
|
+
/** The id of the execution-workspace entry open in the detail sheet, or null when closed. */
|
|
289
289
|
openEntryId: string | null;
|
|
290
290
|
executionDetailStatus: 'idle' | 'loading' | 'ready' | 'error';
|
|
291
291
|
executionDetailRecords: readonly IExecutionDetailRecord[];
|
|
@@ -300,21 +300,21 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
300
300
|
openExecutionDetail: (entryId: string) => void;
|
|
301
301
|
loadMoreExecutionDetail: () => void;
|
|
302
302
|
closeExecutionDetail: () => void;
|
|
303
|
-
/**
|
|
303
|
+
/** The Project panel's "Changes" section (git status). */
|
|
304
304
|
projectStatusState: 'idle' | 'loading' | 'ready' | 'error';
|
|
305
305
|
projectStatus: TProjectStatusRead | null;
|
|
306
306
|
requestProjectStatus: () => void;
|
|
307
|
-
/**
|
|
307
|
+
/** The Project panel's "File diff" section, for the file `projectDiffPath` names. */
|
|
308
308
|
projectDiffState: 'idle' | 'loading' | 'ready' | 'error';
|
|
309
309
|
projectDiffPath: string | null;
|
|
310
310
|
projectDiff: TProjectDiffRead | null;
|
|
311
311
|
requestProjectDiff: (path: string) => void;
|
|
312
|
-
/**
|
|
312
|
+
/** The Project panel's "Memory" section. */
|
|
313
313
|
projectMemoryState: 'idle' | 'loading' | 'ready' | 'error';
|
|
314
314
|
projectMemory: TProjectMemoryRead | null;
|
|
315
315
|
requestProjectMemory: () => void;
|
|
316
316
|
/**
|
|
317
|
-
*
|
|
317
|
+
* Whether the agent switcher sheet is shown. `/agent` (bare) and this surface's
|
|
318
318
|
* own control both open it.
|
|
319
319
|
*/
|
|
320
320
|
agentSwitcherOpen: boolean;
|
|
@@ -331,7 +331,7 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
331
331
|
/** Runs the same path as `/agent <name>`. */
|
|
332
332
|
selectAgent: (agentType: string) => void;
|
|
333
333
|
/**
|
|
334
|
-
*
|
|
334
|
+
* The schedules shown in the Agents panel's "Scheduled" group — every
|
|
335
335
|
* `IBackgroundTaskState` of `kind: 'scheduled'`, refreshed on connect and after a write.
|
|
336
336
|
*/
|
|
337
337
|
scheduledTasks: readonly IBackgroundTaskState<'scheduled'>[];
|
|
@@ -340,7 +340,7 @@ export interface IWsSessionState<TStatus extends string = TConnectionStatus> {
|
|
|
340
340
|
resumeSchedule: (taskId: string) => void;
|
|
341
341
|
/**
|
|
342
342
|
* Permanently stops a schedule — the same `cancel-background-task` write Stop already uses. A
|
|
343
|
-
* failure surfaces the same way a failed Stop does (
|
|
343
|
+
* failure surfaces the same way a failed Stop does (the `background-task-control-failed`
|
|
344
344
|
* session notice) — no separate error state here.
|
|
345
345
|
*/
|
|
346
346
|
deleteSchedule: (taskId: string) => void;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* How the GUI surface answers a `ui_intent`.
|
|
3
3
|
*
|
|
4
4
|
* The server requester-routes `ui_intent`, so everything that arrives here was asked for by THIS
|
|
5
5
|
* surface and must be answered visibly. An intent with a GUI screen opens it (the session picker is
|
|
6
|
-
* the session sidebar
|
|
7
|
-
*
|
|
6
|
+
* the session sidebar; settings and the plugin manager both open the Settings modal; the agent
|
|
7
|
+
* switcher is a sheet). An intent whose
|
|
8
8
|
* command is on the GUI's exclusion list never reaches here at all (`excluded-commands.ts` catches it
|
|
9
9
|
* before the session is asked) — this module only answers a screen this HOST cannot back even though
|
|
10
10
|
* the GUI knows how to render it (the session picker, when the host cannot list sessions), or a kind
|
|
@@ -40,8 +40,8 @@ export function guiScreenForUiIntent(
|
|
|
40
40
|
): 'session-sidebar' | 'settings' | 'settings-plugins' | 'agent-switcher' | null {
|
|
41
41
|
if (intent.type === 'show-session-picker') return 'session-sidebar';
|
|
42
42
|
if (intent.type === 'show-settings') return 'settings';
|
|
43
|
-
//
|
|
44
|
-
//
|
|
43
|
+
// `/plugin` opens the Settings screen's Plugins section, the way `show-settings`
|
|
44
|
+
// does.
|
|
45
45
|
if (intent.type === 'show-plugin-manager') return 'settings-plugins';
|
|
46
46
|
if (intent.type === 'show-agent-switcher') return 'agent-switcher';
|
|
47
47
|
return null;
|
|
@@ -51,7 +51,7 @@ export function guiScreenForUiIntent(
|
|
|
51
51
|
* Describe one intent for this surface: a plain, honest sentence for a screen this HOST cannot back
|
|
52
52
|
* right now (a session picker with no session list), the `/theme` exclusion this intent defensively
|
|
53
53
|
* reuses, or an unrecognized future kind. Never called for `show-settings`, `show-plugin-manager` or
|
|
54
|
-
* `show-agent-switcher` — all three always have a screen now
|
|
54
|
+
* `show-agent-switcher` — all three always have a screen now —
|
|
55
55
|
* but their cases are left out of this switch rather than kept as dead branches that would claim
|
|
56
56
|
* otherwise; an unknown wire-level kind (a newer host) still yields an explicit notice naming the raw
|
|
57
57
|
* kind, so the "never a silent no-op" floor holds for future intents too.
|
|
@@ -24,10 +24,10 @@ function nextRequestId(prefix: string): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* The agent switcher sheet's own correlated request/response state, the same
|
|
28
28
|
* shape `useSettingsState` already uses for `get-settings`/`update-settings` — independent from the
|
|
29
29
|
* chat conversation, because choosing an agent is answered by the sheet's own confirmation line
|
|
30
|
-
* ("Default agent: Explore"), never a conversation card
|
|
30
|
+
* ("Default agent: Explore"), never a conversation card.
|
|
31
31
|
*
|
|
32
32
|
* `openAgentSwitcher` re-fetches every time (`/agent` bare, or reopening after a close), so a change
|
|
33
33
|
* made elsewhere reaches an open-then-reopened sheet, matching `useSettingsState`'s own reasoning.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Tracks whether `query` currently matches, reactively (
|
|
4
|
+
* Tracks whether `query` currently matches, reactively (the narrow-window session sheet
|
|
5
5
|
* below `md`). A host without `window.matchMedia` (an older embedding, or a non-browser test) reports
|
|
6
6
|
* `false` and never subscribes, rather than throwing.
|
|
7
7
|
*/
|
|
@@ -13,7 +13,7 @@ function nextRequestId(): string {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* The model list the model control's pop-up menu is built from, requested on
|
|
17
17
|
* demand (`list-models`) and correlated by `requestId` (mirrors `usePersonalUsageState`'s pattern) so
|
|
18
18
|
* a reply to an older request — one already superseded by a newer one, e.g. across a reconnect — is
|
|
19
19
|
* never applied over the list a still-open menu is currently showing.
|
|
@@ -25,7 +25,7 @@ function nextRequestId(prefix: string): string {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* The Project panel's correlated request/response state (status, one file's diff, project
|
|
29
29
|
* memory), independent from chat state. Mirrors `usePersonalUsageState`'s shape: a status per read, a
|
|
30
30
|
* `requestId` ref that drops a stale reply (a diff request superseded by clicking another file before
|
|
31
31
|
* the first answered), and a request function per read.
|
|
@@ -10,15 +10,15 @@ type TSchedulesState = Pick<
|
|
|
10
10
|
>;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* The Agents panel's "Scheduled" group. No `requestId` correlates
|
|
14
14
|
* `get-background-tasks`/`background_tasks` (the message carries none — see `wire-messages.ts`), so
|
|
15
15
|
* this owns that whole message type for the GUI: every reply is a full refresh of the roster,
|
|
16
16
|
* filtered server-side to `kind: 'scheduled'` (the same tasks `/schedule list` shows).
|
|
17
17
|
*
|
|
18
18
|
* Pause/resume send the SAME `command` text `/schedule pause <id>` / `/schedule resume <id>` runs.
|
|
19
19
|
* Delete sends the existing `cancel-background-task` write — the same one Stop already uses for any
|
|
20
|
-
* other background-task kind
|
|
21
|
-
* one is permanent (distinct from the reversible pause)
|
|
20
|
+
* other background-task kind — since a schedule IS a background task, and cancelling
|
|
21
|
+
* one is permanent (distinct from the reversible pause).
|
|
22
22
|
*/
|
|
23
23
|
export function useSchedulesState(send: (msg: TClientMessage) => void): TSchedulesState & {
|
|
24
24
|
/** Handles `background_tasks` and a `cancel` `background_task_control_result` — both refresh. */
|
|
@@ -46,7 +46,7 @@ export function useSchedulesState(send: (msg: TClientMessage) => void): TSchedul
|
|
|
46
46
|
if (msg.type === 'background_task_control_result' && msg.action === 'cancel') {
|
|
47
47
|
// A schedule's Delete — refresh so a successful cancel drops it from the group at once; a
|
|
48
48
|
// refused one leaves the row exactly as it was (the central reducer's own case already
|
|
49
|
-
// turns a failure into a plain-message session notice —
|
|
49
|
+
// turns a failure into a plain-message session notice — so this never duplicates it).
|
|
50
50
|
if (msg.success) requestSchedules();
|
|
51
51
|
return false; // not exclusively ours — other future consumers may still want this event
|
|
52
52
|
}
|