@robota-sdk/agent-ui-web 3.2.1 → 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 +29 -0
- package/dist/index.cjs +161 -118
- package/dist/index.d.cts +65 -63
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +65 -63
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +161 -118
- package/dist/index.js.map +1 -1
- package/docs/SPEC.md +6 -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 +46 -39
- package/src/components/AgentSwitcherSheet.tsx +2 -2
- package/src/components/Composer.tsx +40 -37
- 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 +83 -56
- 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 +13 -11
- package/src/hooks/session-client-types.ts +36 -30
- package/src/hooks/ui-intent-state.ts +6 -6
- package/src/hooks/use-agent-switcher-state.ts +2 -2
- package/src/hooks/use-app-view-requests.ts +8 -1
- 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 +11 -6
- package/src/hooks/use-settings-state.ts +4 -4
- package/src/hooks/useSessionClient.ts +81 -50
- package/src/index.ts +5 -5
- package/src/schedule-view.ts +1 -1
- package/src/styles/surface.css +1 -1
|
@@ -14,7 +14,7 @@ const STATUS: Record<string, { dot: string; label: string }> = {
|
|
|
14
14
|
error: { dot: 'bg-destructive', label: 'Connection error' },
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/** Every view the primary nav can show — 'project' is always offered (desktop and
|
|
18
18
|
* browser alike); 'usage' only where the host opted the surface into it. */
|
|
19
19
|
export type TSessionView = 'chat' | 'usage' | 'project';
|
|
20
20
|
|
|
@@ -25,7 +25,7 @@ const VIEW_LABEL: Readonly<Record<TSessionView, string>> = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* The folder this session works in, wherever `document.title` is settable: a page (the
|
|
29
29
|
* browser tab) and the desktop window that loads it (Electron follows the page's title unless told
|
|
30
30
|
* otherwise, and nothing in `apps/agent-app/electron/main.ts` overrides it — see its own SPEC note).
|
|
31
31
|
* Absent while the host has not said, so a not-yet-connected surface keeps whatever title it loaded
|
|
@@ -58,14 +58,14 @@ export function SessionTitleBar({
|
|
|
58
58
|
status: string;
|
|
59
59
|
surface?: string;
|
|
60
60
|
title?: string | null;
|
|
61
|
-
/** The folder this session works in
|
|
61
|
+
/** The folder this session works in; absent when the host has not said. */
|
|
62
62
|
workspace?: { readonly name: string; readonly path: string };
|
|
63
63
|
showBrand: boolean;
|
|
64
64
|
view: TSessionView;
|
|
65
65
|
onView: (view: TSessionView) => void;
|
|
66
66
|
personalUsageEnabled: boolean;
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* Mirrors `ConnectionBanner`'s own "give-up" flag (`useWsSession`'s
|
|
69
69
|
* `connectionLost`) so this corner indicator can tell exactly when that banner is on screen, to
|
|
70
70
|
* never say the same thing twice.
|
|
71
71
|
*/
|
|
@@ -73,7 +73,7 @@ export function SessionTitleBar({
|
|
|
73
73
|
}): React.ReactElement {
|
|
74
74
|
const state = STATUS[status] ?? STATUS.disconnected;
|
|
75
75
|
useWorkspaceDocumentTitle(workspace);
|
|
76
|
-
//
|
|
76
|
+
// The same "has this surface ever connected" rule `ConnectionBanner` uses to
|
|
77
77
|
// decide whether it has anything to show — kept in step here so the corner text disappears exactly
|
|
78
78
|
// when, and only when, the banner takes over saying it (never both, never neither).
|
|
79
79
|
const everConnectedRef = useRef(false);
|
|
@@ -81,7 +81,7 @@ export function SessionTitleBar({
|
|
|
81
81
|
if (status === 'connected') everConnectedRef.current = true;
|
|
82
82
|
}, [status]);
|
|
83
83
|
const bannerShown = status !== 'connected' && (connectionLost || everConnectedRef.current);
|
|
84
|
-
//
|
|
84
|
+
// Everything the agent implements is usable from the GUI through
|
|
85
85
|
// organized screens — the Project panel (git status/diff, project memory) is one, offered next to
|
|
86
86
|
// Chat on every surface, desktop and browser alike; Usage stays surface-gated.
|
|
87
87
|
const items: readonly TSessionView[] = personalUsageEnabled
|
|
@@ -142,7 +142,7 @@ export function SessionTitleBar({
|
|
|
142
142
|
const BANNER_COLUMN = 'mx-auto w-full max-w-[760px] px-6';
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* A connection that drops after a working session
|
|
145
|
+
* A connection that drops after a working session: a banner directly above the
|
|
146
146
|
* conversation — never a full-screen replacement — so the conversation and its history stay visible
|
|
147
147
|
* and scrollable throughout. Shown as soon as the drop happens; says nothing on the very first
|
|
148
148
|
* connect, because nothing has been lost yet.
|
|
@@ -234,7 +234,7 @@ function displayProvider(provider: string | undefined): string {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/**
|
|
237
|
-
* A provider/session error classified at the wire boundary
|
|
237
|
+
* A provider/session error classified at the wire boundary as one plain sentence naming
|
|
238
238
|
* what happened and, where there is one, the next step — `null` for a notice this classification does
|
|
239
239
|
* not cover, which keeps showing its own `message` instead.
|
|
240
240
|
*
|
|
@@ -6,7 +6,7 @@ import type { ISettingsSnapshot, TSettingsPatch } from '@robota-sdk/agent-interf
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Language, output style and preset — reusing the exact choices and descriptions the runtime
|
|
9
|
-
* already builds for `/language`, `/output-style` and `/preset
|
|
9
|
+
* already builds for `/language`, `/output-style` and `/preset`. Never internal ids:
|
|
10
10
|
* every pop-up shows the runtime's human labels.
|
|
11
11
|
*/
|
|
12
12
|
export function SettingsGeneralSection({
|
|
@@ -92,7 +92,7 @@ function ServerRow({
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* The MCP Servers section
|
|
95
|
+
* The MCP Servers section: every configured server's name, where it is
|
|
96
96
|
* configured, its live connection status and tool count, an enable/disable switch (approve/reject),
|
|
97
97
|
* an expandable list of its tool names, and a section-level Reload. Adding or editing a server is
|
|
98
98
|
* left out — the runtime has no command path that writes MCP config (recorded in
|
|
@@ -48,7 +48,7 @@ function PermissionRuleRow({
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Default permission mode, the rules `/permissions` shows, and the sandbox switch
|
|
51
|
+
* Default permission mode, the rules `/permissions` shows, and the sandbox switch.
|
|
52
52
|
* "Skip all checks" as the mode and a preset that would set it both go through
|
|
53
53
|
* `onRequestModeChange`'s parent-owned confirmation before anything is sent.
|
|
54
54
|
*/
|
|
@@ -60,7 +60,7 @@ function PluginRow({
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* The Plugins section
|
|
63
|
+
* The Plugins section: every installed plugin's name, description and enabled
|
|
64
64
|
* state, an enable/disable switch, a section-level Reload, and — local surfaces only — install and
|
|
65
65
|
* uninstall, since installing runs third-party code. `canInstall` (from the snapshot) mirrors the
|
|
66
66
|
* refusal `/plugin install|uninstall` itself gives a remote device or an observer.
|
|
@@ -14,7 +14,7 @@ const PRIMARY_BUTTON = `${BUTTON} bg-primary text-primary-foreground hover:opaci
|
|
|
14
14
|
const SECONDARY_BUTTON = `${BUTTON} bg-raised text-foreground hover:bg-hover`;
|
|
15
15
|
const DESTRUCTIVE_BUTTON = `${BUTTON} text-destructive hover:bg-destructive/10`;
|
|
16
16
|
|
|
17
|
-
/** The "Model" action: a `PopupMenu` of this ONE profile's catalog models
|
|
17
|
+
/** The "Model" action: a `PopupMenu` of this ONE profile's catalog models. */
|
|
18
18
|
function ProviderModelButton({
|
|
19
19
|
profile,
|
|
20
20
|
modelList,
|
|
@@ -142,7 +142,7 @@ function ProviderProfileRow({
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
|
-
* The configured provider profiles
|
|
145
|
+
* The configured provider profiles, each row a plain provider name, its model's label,
|
|
146
146
|
* a checkmark on the one in use, and a plain connection state when one is known. Every action goes
|
|
147
147
|
* through the same path the equivalent command does: Use/Model apply through `state.updateSettings`
|
|
148
148
|
* (the same `field` patterns Preset/Mode/Sandbox use); Edit/Test/Duplicate/Add dispatch the raw
|
|
@@ -169,7 +169,7 @@ export function SettingsProvidersSection({
|
|
|
169
169
|
onRequestDelete: (profileName: string) => void;
|
|
170
170
|
}): React.ReactElement {
|
|
171
171
|
// The "Model" button needs each profile's catalog as soon as this section is visible, not only
|
|
172
|
-
// once a person opens one — `list-models` is a cheap, observer-safe read
|
|
172
|
+
// once a person opens one — `list-models` is a cheap, observer-safe read.
|
|
173
173
|
// Intentionally empty deps: fetch once per mount, not on every render.
|
|
174
174
|
useEffect(() => {
|
|
175
175
|
onRequestModelList();
|
|
@@ -19,7 +19,7 @@ interface ISettingsSectionDefinition {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* The Settings screen's sections
|
|
22
|
+
* The Settings screen's sections. Adding a later section (Advisor) is one entry here plus
|
|
23
23
|
* one component in the switch below — nothing else in this file changes.
|
|
24
24
|
*/
|
|
25
25
|
const SECTIONS: readonly ISettingsSectionDefinition[] = [
|
|
@@ -36,7 +36,7 @@ type TPendingPluginAction =
|
|
|
36
36
|
| { kind: 'uninstall'; plugin: ISettingsPlugin };
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* A large modal sheet with a section sidebar on the left and content on the right
|
|
39
|
+
* A large modal sheet with a section sidebar on the left and content on the right — the
|
|
40
40
|
* GUI's Settings screen, opened by the sidebar's gear button, the desktop app's ⌘, menu item, or
|
|
41
41
|
* `/settings` (its `show-settings` `ui_intent` calls `state.openSettings()`, wired in
|
|
42
42
|
* `useSessionClient`). Every change applies at once; there is no Save button. Esc and the Close
|
|
@@ -56,7 +56,7 @@ export function SettingsScreen({ state }: { state: IWsSessionState }): React.Rea
|
|
|
56
56
|
const [pendingPluginAction, setPendingPluginAction] = useState<TPendingPluginAction | null>(null);
|
|
57
57
|
const [mcpReloading, setMcpReloading] = useState(false);
|
|
58
58
|
const [pluginsReloading, setPluginsReloading] = useState(false);
|
|
59
|
-
//
|
|
59
|
+
// The profile a pending "Delete…" confirms, or null when none is pending.
|
|
60
60
|
const [pendingProviderDelete, setPendingProviderDelete] = useState<string | null>(null);
|
|
61
61
|
|
|
62
62
|
// Each time the screen opens, land on the section the opener asked for (`/plugin` → Plugins,
|
|
@@ -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,14 +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
|
+
surface_role: 'reducer-state',
|
|
94
|
+
// The Settings screen's own correlated request/response state (`useSettingsState`).
|
|
93
95
|
settings: 'reducer-state',
|
|
94
96
|
settings_error: 'reducer-state',
|
|
95
97
|
} as const satisfies Readonly<Record<TServerMessage['type'], TServerMessageHandling>>;
|