cli-jaw 2.17.41 → 2.17.42
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/README.md +3 -3
- package/dist/bin/commands/browser.js +52 -5
- package/dist/bin/commands/browser.js.map +1 -1
- package/dist/src/agent/args.js +69 -19
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/codex-app-events.js +31 -1
- package/dist/src/agent/codex-app-events.js.map +1 -1
- package/dist/src/agent/cursor-model-inventory.js +140 -0
- package/dist/src/agent/cursor-model-inventory.js.map +1 -0
- package/dist/src/agent/grok-models.js +80 -0
- package/dist/src/agent/grok-models.js.map +1 -0
- package/dist/src/browser/actions.js +383 -20
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/grounding-candidate.js +272 -0
- package/dist/src/browser/grounding-candidate.js.map +1 -0
- package/dist/src/browser/grounding-eval.js +247 -0
- package/dist/src/browser/grounding-eval.js.map +1 -0
- package/dist/src/browser/image-size.js +63 -0
- package/dist/src/browser/image-size.js.map +1 -0
- package/dist/src/browser/index.js +1 -0
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/occlusion.js +148 -0
- package/dist/src/browser/occlusion.js.map +1 -0
- package/dist/src/browser/verify-candidate.js +162 -0
- package/dist/src/browser/verify-candidate.js.map +1 -0
- package/dist/src/browser/vision-input.js +74 -0
- package/dist/src/browser/vision-input.js.map +1 -0
- package/dist/src/browser/vision-provider.js +75 -0
- package/dist/src/browser/vision-provider.js.map +1 -0
- package/dist/src/browser/vision.js +360 -47
- package/dist/src/browser/vision.js.map +1 -1
- package/dist/src/browser/web-ai/candidate-reconcile.js +14 -5
- package/dist/src/browser/web-ai/candidate-reconcile.js.map +1 -1
- package/dist/src/cli/claude-model-discovery.js +175 -0
- package/dist/src/cli/claude-model-discovery.js.map +1 -0
- package/dist/src/cli/claude-models.js +46 -0
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/registry-live.js +104 -1
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/cli/registry.js +28 -5
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/code-mode/host.js +6 -0
- package/dist/src/code-mode/host.js.map +1 -1
- package/dist/src/code-mode/manager.js +37 -8
- package/dist/src/code-mode/manager.js.map +1 -1
- package/dist/src/code-mode/providers/catalog.js +53 -1
- package/dist/src/code-mode/providers/catalog.js.map +1 -1
- package/dist/src/code-mode/providers/codex-app.js +6 -0
- package/dist/src/code-mode/providers/codex-app.js.map +1 -1
- package/dist/src/code-mode/providers/live-models.js +85 -0
- package/dist/src/code-mode/providers/live-models.js.map +1 -0
- package/dist/src/code-mode/providers/provider-live-models.js +120 -0
- package/dist/src/code-mode/providers/provider-live-models.js.map +1 -0
- package/dist/src/code-mode/session.js +37 -0
- package/dist/src/code-mode/session.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +24 -14
- package/dist/src/prompt/templates/control-system.md +6 -11
- package/dist/src/prompt/templates/employee.md +5 -5
- package/dist/src/routes/browser.js +17 -5
- package/dist/src/routes/browser.js.map +1 -1
- package/package.json +1 -1
- package/public/css/activity.css +20 -1
- package/public/css/layout.css +7 -3
- package/public/dist/.vite/manifest.json +104 -105
- package/public/dist/assets/Agent-CQ4EBgY8.js +1 -0
- package/public/dist/assets/CodeCanvas-DWsx6sv5.js +2 -0
- package/public/dist/assets/DashboardMeta-Dh-tasbY.js +1 -0
- package/public/dist/assets/Display-NrqY_IaN.js +1 -0
- package/public/dist/assets/{Permissions-BzbnqenZ.js → Permissions-Bh947tUG.js} +1 -1
- package/public/dist/assets/Permissions-DzNJCt8K.js +1 -0
- package/public/dist/assets/app-BCBnxiGr.css +1 -0
- package/public/dist/assets/{app-x5Kray4W.js → app-Q0prKOFx.js} +4 -4
- package/public/dist/assets/{bgtask-badge-W9djRQVu.js → bgtask-badge-DAcWaUfS.js} +1 -1
- package/public/dist/assets/bgtask-badge-DHA2OQm8.js +1 -0
- package/public/dist/assets/{employees--_dmDw_a.js → employees-CWS3Juca.js} +1 -1
- package/public/dist/assets/{iframe-renderer-D4BmLtAq.js → iframe-renderer-DCDKBMb7.js} +2 -2
- package/public/dist/assets/iframe-renderer-RpvBly2x.js +1 -0
- package/public/dist/assets/manager-Nk7-RYPs.css +1 -0
- package/public/dist/assets/manager-XkR-DRaQ.js +5 -0
- package/public/dist/assets/manager-shortcuts-jsiX-zV6.js +1 -0
- package/public/dist/assets/memory-4YH8sz0L.js +1 -0
- package/public/dist/assets/{memory-DENQWzM8.js → memory-DhNdKZLO.js} +1 -1
- package/public/dist/assets/{message-history-BsrLcGJj.js → message-history-DbRTl1P0.js} +1 -1
- package/public/dist/assets/message-history-lVcLpAw8.js +1 -0
- package/public/dist/assets/{render-CF6Q1oIh.js → render-DP8SJtdZ.js} +1 -1
- package/public/dist/assets/settings--EGy3_B9.js +1 -0
- package/public/dist/assets/settings-85n9NjvX.js +1 -0
- package/public/dist/assets/{settings-B-C_jdZR.js → settings-C9_D-pn0.js} +32 -32
- package/public/dist/assets/settings-core-8wBl1mg-.js +1 -0
- package/public/dist/assets/{settings-core-BzAJLsKN.js → settings-core-BcVD2FgV.js} +1 -1
- package/public/dist/assets/{settings-embedding-dEMyByVi.js → settings-embedding-BSpq785l.js} +1 -1
- package/public/dist/assets/{settings-embedding-BSA42oKM.css → settings-embedding-LIMq5ebM.css} +1 -1
- package/public/dist/assets/{sidebar-fFEOpvJl.js → sidebar-BVhLb5Ft.js} +3 -3
- package/public/dist/assets/skills-6GFkHTlQ.js +1 -0
- package/public/dist/assets/{skills-Da_b06ci.js → skills-X7qxmHka.js} +1 -1
- package/public/dist/assets/{slash-commands-BbPUHHF4.js → slash-commands-BnDH4IU_.js} +1 -1
- package/public/dist/assets/slash-commands-qu8fPAZ3.js +1 -0
- package/public/dist/assets/{trace-drawer-B-QmzUfP.js → trace-drawer-DasaS1z4.js} +1 -1
- package/public/dist/assets/{ui-CkRUrIfp.js → ui-BtiBTXng.js} +1 -1
- package/public/dist/assets/ui-BucJ1zMA.js +1 -0
- package/public/dist/index.html +2 -2
- package/public/dist/manager/index.html +3 -3
- package/public/dist/settings/index.html +2 -2
- package/public/js/features/settings.ts +16 -0
- package/public/manager/src/App.tsx +10 -12
- package/public/manager/src/AppChrome.tsx +2 -3
- package/public/manager/src/InstancePreview.tsx +47 -0
- package/public/manager/src/SidebarRailRouter.tsx +34 -30
- package/public/manager/src/code/CodeComposer.tsx +31 -6
- package/public/manager/src/code/CodeSessionList.tsx +25 -7
- package/public/manager/src/code/CodeToastHost.tsx +49 -0
- package/public/manager/src/code/CodeTranscript.tsx +136 -26
- package/public/manager/src/code/CodeWorkbench.tsx +16 -2
- package/public/manager/src/code/ComposerFooter.tsx +130 -46
- package/public/manager/src/code/ContextUsageMeter.tsx +72 -0
- package/public/manager/src/code/ProviderGlyph.tsx +58 -0
- package/public/manager/src/code/code-composer.css +159 -175
- package/public/manager/src/code/code-controller-runtime.ts +3 -1
- package/public/manager/src/code/code-session-state.ts +7 -1
- package/public/manager/src/code/code-toasts.ts +73 -0
- package/public/manager/src/code/code-workbench.css +42 -1
- package/public/manager/src/code/code.css +130 -15
- package/public/manager/src/code/context-usage.ts +74 -0
- package/public/manager/src/code/pending-user-item.ts +46 -0
- package/public/manager/src/code/session-order.ts +70 -0
- package/public/manager/src/code/tool-summary.ts +101 -0
- package/public/manager/src/code/use-dictation.ts +130 -0
- package/public/manager/src/components/CommandCenter.tsx +4 -8
- package/public/manager/src/components/InstanceDetailPanel.tsx +0 -18
- package/public/manager/src/components/Workbench.tsx +6 -10
- package/public/manager/src/components/WorkbenchHeader.tsx +0 -11
- package/public/manager/src/dashboard-settings/dashboard-settings-ui.ts +3 -1
- package/public/manager/src/hooks/useDashboardView.ts +38 -38
- package/public/manager/src/manager-components.css +0 -8
- package/public/manager/src/manager-shortcut-runner.ts +0 -1
- package/public/manager/src/manager-shortcuts.ts +0 -2
- package/public/manager/src/settings/pages/Agent.tsx +5 -2
- package/public/manager/src/settings/pages/DashboardMeta.tsx +20 -1
- package/public/manager/src/settings/pages/Permissions.tsx +23 -4
- package/public/manager/src/settings/pages/components/agent/PermissionQuickSection.tsx +6 -2
- package/public/manager/src/settings/pages/components/agent/agent-save.ts +24 -1
- package/public/manager/src/settings/settings-icons.ts +3 -1
- package/public/manager/src/settings/settings-registry.ts +3 -3
- package/public/manager/src/settings-controls.css +0 -1
- package/public/manager/src/settings-standalone.tsx +8 -8
- package/public/manager/src/types.ts +0 -1
- package/scripts/check-private-boundary.mjs +101 -0
- package/scripts/grounding-eval.mjs +223 -0
- package/public/dist/assets/Agent-B8ZEWJ6B.js +0 -1
- package/public/dist/assets/CodeCanvas-Bj3lTSu6.js +0 -2
- package/public/dist/assets/DashboardMeta-CmZavI4H.js +0 -1
- package/public/dist/assets/Display-DFkjKcMk.js +0 -1
- package/public/dist/assets/Permissions-CJftCO6p.js +0 -1
- package/public/dist/assets/app-DVbrIl8f.css +0 -1
- package/public/dist/assets/bgtask-badge-CuAc8bAl.js +0 -1
- package/public/dist/assets/iframe-renderer-By2WO5bd.js +0 -1
- package/public/dist/assets/manager-CxLXWCGi.css +0 -1
- package/public/dist/assets/manager-DA3dF3eN.js +0 -5
- package/public/dist/assets/manager-shortcuts-CzGkE0ZG.js +0 -1
- package/public/dist/assets/memory-thjr2apY.js +0 -1
- package/public/dist/assets/message-history-CfU7_P5t.js +0 -1
- package/public/dist/assets/settings-DXA5L4DR.js +0 -1
- package/public/dist/assets/settings-Dy_IaNVH.js +0 -1
- package/public/dist/assets/settings-core-D7sAQqhC.js +0 -1
- package/public/dist/assets/skills-BcIievZp.js +0 -1
- package/public/dist/assets/slash-commands-B-5Shayl.js +0 -1
- package/public/dist/assets/ui-CJLdMfRx.js +0 -1
|
@@ -9,9 +9,6 @@ type WorkbenchProps = {
|
|
|
9
9
|
overview: ReactNode;
|
|
10
10
|
preview: ReactNode;
|
|
11
11
|
logs: ReactNode;
|
|
12
|
-
settings: ReactNode;
|
|
13
|
-
settingsOpen: boolean;
|
|
14
|
-
onSettingsClose: () => void;
|
|
15
12
|
active: boolean;
|
|
16
13
|
};
|
|
17
14
|
|
|
@@ -23,8 +20,8 @@ function modeLabel(mode: DashboardDetailTab): string {
|
|
|
23
20
|
|
|
24
21
|
export function Workbench(props: WorkbenchProps) {
|
|
25
22
|
return (
|
|
26
|
-
<section className={`workbench workbench-${props.mode}`}
|
|
27
|
-
<div className="workbench-header"
|
|
23
|
+
<section className={`workbench workbench-${props.mode}`} aria-label="Selected instance workbench">
|
|
24
|
+
<div className="workbench-header">
|
|
28
25
|
{props.header}
|
|
29
26
|
<div className="workbench-mode-bar">
|
|
30
27
|
<div className="workbench-mode-tabs" role="tablist" aria-label="Workbench modes"
|
|
@@ -56,7 +53,7 @@ export function Workbench(props: WorkbenchProps) {
|
|
|
56
53
|
</div>
|
|
57
54
|
</div>
|
|
58
55
|
<div className="workbench-body">
|
|
59
|
-
{
|
|
56
|
+
{props.mode === 'overview' && (
|
|
60
57
|
<div key="overview" id="workbench-panel-overview" role="tabpanel" aria-labelledby="workbench-tab-overview" className="workbench-panel workbench-panel-overview">{props.overview}</div>
|
|
61
58
|
)}
|
|
62
59
|
<div
|
|
@@ -65,16 +62,15 @@ export function Workbench(props: WorkbenchProps) {
|
|
|
65
62
|
role="tabpanel"
|
|
66
63
|
aria-labelledby="workbench-tab-preview"
|
|
67
64
|
className="workbench-panel workbench-panel-preview"
|
|
68
|
-
hidden={props.
|
|
69
|
-
aria-hidden={props.
|
|
65
|
+
hidden={props.mode !== 'preview'}
|
|
66
|
+
aria-hidden={props.mode !== 'preview'}
|
|
70
67
|
data-preview-host="persistent"
|
|
71
68
|
>
|
|
72
69
|
{props.preview}
|
|
73
70
|
</div>
|
|
74
|
-
{
|
|
71
|
+
{props.mode === 'logs' && (
|
|
75
72
|
<div key="logs" id="workbench-panel-logs" role="tabpanel" aria-labelledby="workbench-tab-logs" className="workbench-panel workbench-panel-logs">{props.logs}</div>
|
|
76
73
|
)}
|
|
77
|
-
{props.settingsOpen && <div id="workbench-instance-settings" className="workbench-settings-page">{props.settings}</div>}
|
|
78
74
|
</div>
|
|
79
75
|
</section>
|
|
80
76
|
);
|
|
@@ -107,14 +107,3 @@ export function WorkbenchHeader(props: WorkbenchHeaderProps) {
|
|
|
107
107
|
</div>
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
export function WorkbenchSettingsToggle(props: { open: boolean; onToggle: () => void }) {
|
|
112
|
-
const [slot, setSlot] = useState<HTMLElement | null>(null);
|
|
113
|
-
useEffect(() => { setSlot(document.getElementById('command-settings-slot')); }, []);
|
|
114
|
-
if (!slot) return null;
|
|
115
|
-
return createPortal(<button type="button" className="workbench-settings-toggle" aria-label="Instance settings"
|
|
116
|
-
title="Instance settings" aria-pressed={props.open} aria-controls="workbench-instance-settings"
|
|
117
|
-
onClick={props.onToggle}>
|
|
118
|
-
<span aria-hidden="true" dangerouslySetInnerHTML={{ __html: icon('settings', 16) }} />
|
|
119
|
-
</button>, slot);
|
|
120
|
-
}
|
|
@@ -11,7 +11,9 @@ export function dashboardSettingsUiFromView(
|
|
|
11
11
|
return {
|
|
12
12
|
selectedPort: view.selectedPort,
|
|
13
13
|
selectedTab: view.activeDetailTab,
|
|
14
|
-
|
|
14
|
+
// Retained in the schema for one version so older clients and PATCH callers do not
|
|
15
|
+
// 400, but the rail owns settings now and nothing opens a Workbench panel.
|
|
16
|
+
instanceSettingsOpen: false,
|
|
15
17
|
sidebarCollapsed: view.sidebarCollapsed,
|
|
16
18
|
activityDockCollapsed: view.activityDockCollapsed,
|
|
17
19
|
activityDockHeight: view.activityDockHeight,
|
|
@@ -6,7 +6,6 @@ import type { DashboardDetailTab, DashboardDiffMode, DashboardDiffRootPolicy, Da
|
|
|
6
6
|
export function useDashboardView() {
|
|
7
7
|
const [selectedPort, setSelectedPort] = useState<number | null>(null);
|
|
8
8
|
const [activeDetailTab, setActiveDetailTab] = useState<DashboardDetailTab>('overview');
|
|
9
|
-
const [instanceSettingsOpen, setInstanceSettingsOpen] = useState(false);
|
|
10
9
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
11
10
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
12
11
|
const [activityDockCollapsed, setActivityDockCollapsed] = useState(false);
|
|
@@ -42,7 +41,6 @@ export function useDashboardView() {
|
|
|
42
41
|
return {
|
|
43
42
|
selectedPort,
|
|
44
43
|
setSelectedPort,
|
|
45
|
-
instanceSettingsOpen, setInstanceSettingsOpen,
|
|
46
44
|
activeDetailTab,
|
|
47
45
|
setActiveDetailTab,
|
|
48
46
|
drawerOpen,
|
|
@@ -102,19 +100,26 @@ export function useDashboardView() {
|
|
|
102
100
|
};
|
|
103
101
|
}
|
|
104
102
|
|
|
105
|
-
|
|
103
|
+
/**
|
|
104
|
+
* The in-Workbench instance settings panel is gone; the rail owns settings now. A registry
|
|
105
|
+
* written by an older client can still carry `instanceSettingsOpen: true` or the retired
|
|
106
|
+
* `selectedTab: 'settings'`, so map either onto the rail's sidebar mode rather than dropping
|
|
107
|
+
* the user on a workspace whose settings tab no longer exists. The field stays accepted by
|
|
108
|
+
* the server normalizer for one version; nothing writes it any more.
|
|
109
|
+
*/
|
|
110
|
+
export function hydrateInstanceSettings(ui: Pick<DashboardRegistryUi, 'selectedTab' | 'sidebarMode'> & { instanceSettingsOpen?: unknown }) {
|
|
111
|
+
const legacySettingsRequest = ui.selectedTab === 'settings' || ui.instanceSettingsOpen === true;
|
|
106
112
|
return { selectedTab: ui.selectedTab === 'settings' ? 'overview' as const : ui.selectedTab,
|
|
107
|
-
|
|
113
|
+
sidebarMode: legacySettingsRequest ? 'settings' as const : ui.sidebarMode };
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
type SettingsNavigation = {
|
|
111
|
-
view: Pick<ReturnType<typeof useDashboardView>, '
|
|
112
|
-
'setSelectedPort' | '
|
|
117
|
+
view: Pick<ReturnType<typeof useDashboardView>, 'activeDetailTab' | 'sidebarMode' |
|
|
118
|
+
'setSelectedPort' | 'setSidebarMode' | 'setViewMode' | 'setDrawerOpen'>;
|
|
113
119
|
selectedPort: number | null;
|
|
114
120
|
settingsDirty: boolean;
|
|
115
|
-
panelSettingsDirty: boolean;
|
|
116
121
|
dashboardSettingsDirty: boolean;
|
|
117
|
-
clearDirty: (entry: '
|
|
122
|
+
clearDirty: (entry: 'dashboard') => void;
|
|
118
123
|
saveUi: (patch: Partial<DashboardRegistryUi>) => Promise<void>;
|
|
119
124
|
confirmDiscard?: () => boolean;
|
|
120
125
|
};
|
|
@@ -125,49 +130,44 @@ export function createInstanceSettingsNavigation(args: SettingsNavigation) {
|
|
|
125
130
|
(args.confirmDiscard ?? (() => window.confirm('Discard unsaved Settings changes?')))();
|
|
126
131
|
}
|
|
127
132
|
function guardSettingsTransition(target: Partial<{
|
|
128
|
-
sidebarMode: DashboardSidebarMode; selectedPort: number | null;
|
|
133
|
+
sidebarMode: DashboardSidebarMode; selectedPort: number | null;
|
|
129
134
|
}>): boolean {
|
|
130
135
|
const portChanged = target.selectedPort !== undefined && target.selectedPort !== selectedPort;
|
|
131
136
|
const leavingDashboard = view.sidebarMode === 'settings' && (target.sidebarMode ?? view.sidebarMode) !== 'settings';
|
|
132
|
-
|
|
133
|
-
const departing =
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
// The rail workspace is the only settings surface, so there is one draft to guard.
|
|
138
|
+
const departing = portChanged || leavingDashboard;
|
|
139
|
+
if (!departing) return true;
|
|
140
|
+
if (args.dashboardSettingsDirty
|
|
141
|
+
&& !(args.confirmDiscard ?? (() => window.confirm('Discard unsaved Settings changes?')))()) return false;
|
|
142
|
+
args.clearDirty('dashboard');
|
|
138
143
|
return true;
|
|
139
144
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
selectedTab: view.activeDetailTab === 'settings' ? 'overview' : view.activeDetailTab,
|
|
150
|
-
...(open ? { selectedPort: port, sidebarMode: 'instances' as const } : {}) });
|
|
145
|
+
/** Opens or closes the rail's manager settings workspace, honouring the dirty guard. */
|
|
146
|
+
function setDashboardSettingsOpen(open: boolean): void {
|
|
147
|
+
const sidebarMode = open ? 'settings' as const : 'instances' as const;
|
|
148
|
+
if (view.sidebarMode === sidebarMode) return;
|
|
149
|
+
if (!guardSettingsTransition({ sidebarMode })) return;
|
|
150
|
+
if (open) { view.setViewMode('jaw'); view.setDrawerOpen(false); }
|
|
151
|
+
view.setSidebarMode(sidebarMode);
|
|
152
|
+
void args.saveUi({ sidebarMode,
|
|
153
|
+
selectedTab: view.activeDetailTab === 'settings' ? 'overview' : view.activeDetailTab });
|
|
151
154
|
}
|
|
152
|
-
return { canLeaveDirtySettings, guardSettingsTransition,
|
|
155
|
+
return { canLeaveDirtySettings, guardSettingsTransition, setDashboardSettingsOpen };
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
// One settings surface remains, so there is one draft. The entry name is kept so the
|
|
159
|
+
// dirty-store call sites read the same as before.
|
|
160
|
+
export function settingsDirtyAfter(current: { dashboard: boolean },
|
|
161
|
+
entry: 'dashboard', dirty: boolean) {
|
|
158
162
|
return { ...current, [entry]: dirty };
|
|
159
163
|
}
|
|
160
164
|
export function useSettingsDirtyState() {
|
|
161
|
-
const [entries, setEntries] = useState({
|
|
162
|
-
const onSettingsDirtyChange = useCallback((entry: '
|
|
165
|
+
const [entries, setEntries] = useState({ dashboard: false });
|
|
166
|
+
const onSettingsDirtyChange = useCallback((entry: 'dashboard', dirty: boolean) => {
|
|
163
167
|
setEntries(current => settingsDirtyAfter(current, entry, dirty));
|
|
164
168
|
}, []);
|
|
165
|
-
// SettingsShell keys its dirty notification and unmount cleanup to this callback.
|
|
166
|
-
const onPanelSettingsDirtyChange = useCallback((dirty: boolean) => {
|
|
167
|
-
onSettingsDirtyChange('panel', dirty);
|
|
168
|
-
}, [onSettingsDirtyChange]);
|
|
169
169
|
const setSettingsDirty = useCallback((dirty: boolean) => {
|
|
170
|
-
setEntries({
|
|
170
|
+
setEntries({ dashboard: dirty });
|
|
171
171
|
}, []);
|
|
172
|
-
return { settingsDirty: entries.
|
|
172
|
+
return { settingsDirty: entries.dashboard, dashboardSettingsDirty: entries.dashboard, setSettingsDirty, onSettingsDirtyChange };
|
|
173
173
|
}
|
|
@@ -1297,15 +1297,7 @@ button.instance-group-header[aria-expanded="false"] .instance-group-chevron {
|
|
|
1297
1297
|
.drawer-body { min-height: 0; overflow: auto; }
|
|
1298
1298
|
|
|
1299
1299
|
/* Settings replaces the workspace while retaining the preview host. */
|
|
1300
|
-
.workbench[data-instance-settings-open="true"] { grid-template-rows: minmax(0, 1fr); }
|
|
1301
1300
|
.workbench .workbench-header[hidden], #manager-sidebar-list[hidden] { display: none; }
|
|
1302
|
-
.workbench[data-instance-settings-open="true"] .workbench-body { display: grid; min-height: 0; padding: 0; }
|
|
1303
|
-
.workbench-settings-page { min-width: 0; min-height: 0; height: 100%; }
|
|
1304
|
-
.workbench-settings-toggle { min-width: 32px; min-height: 32px; display: inline-flex; align-items: center; justify-content: center; }
|
|
1305
|
-
.workbench-settings-toggle svg { width: 16px; height: 16px; stroke-width: 1.8; }
|
|
1306
|
-
.workbench-settings-toggle[aria-pressed="true"] { color: var(--accent); background: var(--accent-soft); }
|
|
1307
|
-
.workbench-settings-toggle:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
|
|
1308
|
-
@media (pointer: coarse) { .workbench-settings-toggle { min-width: 44px; min-height: 44px; } }
|
|
1309
1301
|
|
|
1310
1302
|
@media (max-width: 639px) {
|
|
1311
1303
|
.command-center .command-title { display: none; }
|
|
@@ -76,7 +76,6 @@ export function runManagerShortcut(action: DashboardShortcutAction, deps: Manage
|
|
|
76
76
|
if (action === 'switchTab1') { deps.handleTabChange('overview'); return; }
|
|
77
77
|
if (action === 'switchTab2') { deps.handleTabChange('preview'); return; }
|
|
78
78
|
if (action === 'switchTab3') { deps.handleTabChange('logs'); return; }
|
|
79
|
-
if (action === 'switchTab4') { deps.handleTabChange('settings'); return; }
|
|
80
79
|
if (action === 'previousTab' || action === 'nextTab') {
|
|
81
80
|
const tabs: DashboardDetailTab[] = ['overview', 'preview', 'logs'];
|
|
82
81
|
const idx = tabs.indexOf(deps.activeDetailTab);
|
|
@@ -17,7 +17,6 @@ export const MANAGER_SHORTCUT_ACTIONS: DashboardShortcutAction[] = [
|
|
|
17
17
|
'switchTab1',
|
|
18
18
|
'switchTab2',
|
|
19
19
|
'switchTab3',
|
|
20
|
-
'switchTab4',
|
|
21
20
|
'previousTab',
|
|
22
21
|
'nextTab',
|
|
23
22
|
'browserReload',
|
|
@@ -57,7 +56,6 @@ export const DEFAULT_MANAGER_SHORTCUT_KEYMAP: DashboardShortcutKeymap = {
|
|
|
57
56
|
switchTab1: 'Meta+1',
|
|
58
57
|
switchTab2: 'Meta+2',
|
|
59
58
|
switchTab3: 'Meta+3',
|
|
60
|
-
switchTab4: 'Meta+4',
|
|
61
59
|
previousTab: 'Meta+Shift+[',
|
|
62
60
|
nextTab: 'Meta+Shift+]',
|
|
63
61
|
browserReload: 'Meta+R',
|
|
@@ -124,7 +124,7 @@ type RuntimeDraft = {
|
|
|
124
124
|
model: string;
|
|
125
125
|
effort: string;
|
|
126
126
|
workingDir: string;
|
|
127
|
-
permissions: 'auto' | string[];
|
|
127
|
+
permissions: 'auto' | 'safe' | string[];
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
export default function Agent({ port, client, dirty, registerSave }: SettingsPageProps) {
|
|
@@ -284,7 +284,10 @@ export default function Agent({ port, client, dirty, registerSave }: SettingsPag
|
|
|
284
284
|
model: runtimeModelFor(cli, state.data.perCli, state.data.activeOverrides),
|
|
285
285
|
effort: runtimeEffortFor(cli, state.data.perCli, state.data.activeOverrides),
|
|
286
286
|
workingDir: state.data.workingDir || '',
|
|
287
|
-
|
|
287
|
+
// Preserve 'safe' rather than collapsing it: an untouched Safe instance must not
|
|
288
|
+
// become Auto (YOLO) as a side effect of saving something unrelated on this page.
|
|
289
|
+
permissions: permissions.mode === 'custom' ? permissions.tokens
|
|
290
|
+
: permissions.mode === 'safe' ? 'safe' : 'auto',
|
|
288
291
|
});
|
|
289
292
|
}, [cliMeta, state]);
|
|
290
293
|
|
|
@@ -95,7 +95,26 @@ export type DashboardMetaProps = SettingsPageProps & {
|
|
|
95
95
|
managerClient?: ManagerClient;
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
/**
|
|
99
|
+
* No selected instance. SettingsShell passes `port={port ?? 0}`, and this page writes to the
|
|
100
|
+
* MANAGER registry keyed by port, so rendering the form here would let a save PATCH
|
|
101
|
+
* /api/dashboard/registry under instance key "0". This is a separate component rather than an
|
|
102
|
+
* early return because the form below calls hooks before its first return: an early return
|
|
103
|
+
* above them breaks the rules of hooks, and one below them has already issued the fetch.
|
|
104
|
+
*/
|
|
105
|
+
function DashboardMetaEmpty() {
|
|
106
|
+
return (
|
|
107
|
+
<p className="settings-empty" role="status">
|
|
108
|
+
Select an instance to edit its dashboard metadata.
|
|
109
|
+
</p>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default function DashboardMeta(props: DashboardMetaProps) {
|
|
114
|
+
return props.port ? <DashboardMetaForm {...props} /> : <DashboardMetaEmpty />;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function DashboardMetaForm({
|
|
99
118
|
port,
|
|
100
119
|
client,
|
|
101
120
|
dirty,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from './page-shell';
|
|
17
17
|
import { InlineWarn } from './components/InlineWarn';
|
|
18
18
|
|
|
19
|
-
export type PermissionMode = 'auto' | 'custom';
|
|
19
|
+
export type PermissionMode = 'auto' | 'safe' | 'custom';
|
|
20
20
|
|
|
21
21
|
type PermissionsSnapshot = {
|
|
22
22
|
permissions?: 'auto' | string[] | unknown;
|
|
@@ -58,9 +58,13 @@ export function parsePermissionsValue(
|
|
|
58
58
|
value: unknown,
|
|
59
59
|
):
|
|
60
60
|
| { mode: 'auto' }
|
|
61
|
+
| { mode: 'safe' }
|
|
61
62
|
| { mode: 'custom'; tokens: string[] }
|
|
62
63
|
| { mode: 'unknown' } {
|
|
63
64
|
if (isPermissionsAuto(value)) return { mode: 'auto' };
|
|
65
|
+
// 'safe' is a real stored policy — the Classic sidebar is its only writer today. Reporting
|
|
66
|
+
// it as 'unknown' is what let callers collapse it to 'auto', silently widening permissions.
|
|
67
|
+
if (value === 'safe') return { mode: 'safe' };
|
|
64
68
|
if (Array.isArray(value)) {
|
|
65
69
|
const tokens = value
|
|
66
70
|
.filter((t): t is string => typeof t === 'string')
|
|
@@ -102,6 +106,7 @@ export function isAllowlistValid(tokens: ReadonlyArray<string>): boolean {
|
|
|
102
106
|
|
|
103
107
|
const MODE_OPTIONS = [
|
|
104
108
|
{ value: 'auto', label: 'Auto (YOLO)' },
|
|
109
|
+
{ value: 'safe', label: 'Safe' },
|
|
105
110
|
{ value: 'custom', label: 'Custom — explicit allowlist below' },
|
|
106
111
|
];
|
|
107
112
|
|
|
@@ -125,7 +130,10 @@ export default function Permissions({ port, client, dirty, registerSave }: Setti
|
|
|
125
130
|
setMode('custom');
|
|
126
131
|
setTokens(parsed.tokens);
|
|
127
132
|
} else {
|
|
128
|
-
|
|
133
|
+
// A Safe instance must read as Safe. Showing it as Auto (YOLO) hides the policy the
|
|
134
|
+
// user is about to change and reports 'auto' as the original, so a round trip
|
|
135
|
+
// through Custom and back writes a widened policy the user never chose.
|
|
136
|
+
setMode(parsed.mode === 'safe' ? 'safe' : 'auto');
|
|
129
137
|
setTokens([]);
|
|
130
138
|
}
|
|
131
139
|
}, [state]);
|
|
@@ -141,9 +149,10 @@ export default function Permissions({ port, client, dirty, registerSave }: Setti
|
|
|
141
149
|
[dirty],
|
|
142
150
|
);
|
|
143
151
|
|
|
144
|
-
const originalSerialized = useMemo<'auto' | string[]>(() => {
|
|
152
|
+
const originalSerialized = useMemo<'auto' | 'safe' | string[]>(() => {
|
|
145
153
|
if (!original) return 'auto';
|
|
146
154
|
if (original.mode === 'custom') return original.tokens;
|
|
155
|
+
if (original.mode === 'safe') return 'safe';
|
|
147
156
|
return 'auto';
|
|
148
157
|
}, [original]);
|
|
149
158
|
|
|
@@ -159,6 +168,16 @@ export default function Permissions({ port, client, dirty, registerSave }: Setti
|
|
|
159
168
|
});
|
|
160
169
|
return;
|
|
161
170
|
}
|
|
171
|
+
if (next === 'safe') {
|
|
172
|
+
setMode('safe');
|
|
173
|
+
setTokens([]);
|
|
174
|
+
writeEntry({
|
|
175
|
+
value: 'safe',
|
|
176
|
+
original: originalSerialized,
|
|
177
|
+
valid: true,
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
162
181
|
if (next === 'custom') {
|
|
163
182
|
setMode('custom');
|
|
164
183
|
const seed =
|
|
@@ -204,7 +223,7 @@ export default function Permissions({ port, client, dirty, registerSave }: Setti
|
|
|
204
223
|
setMode('custom');
|
|
205
224
|
setTokens(parsed.tokens);
|
|
206
225
|
} else {
|
|
207
|
-
setMode('auto');
|
|
226
|
+
setMode(parsed.mode === 'safe' ? 'safe' : 'auto');
|
|
208
227
|
setTokens([]);
|
|
209
228
|
}
|
|
210
229
|
setData(fresh);
|
|
@@ -10,17 +10,20 @@ import {
|
|
|
10
10
|
type PermissionQuickSectionProps = {
|
|
11
11
|
value: unknown;
|
|
12
12
|
configuredValue: unknown;
|
|
13
|
-
onChange(next: 'auto' | string[]): void;
|
|
13
|
+
onChange(next: 'auto' | 'safe' | string[]): void;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const MODE_OPTIONS = [
|
|
17
17
|
{ value: 'auto', label: 'Auto (YOLO)' },
|
|
18
|
+
{ value: 'safe', label: 'Safe' },
|
|
18
19
|
{ value: 'custom', label: 'Custom allowlist' },
|
|
19
20
|
];
|
|
20
21
|
|
|
21
22
|
export function PermissionQuickSection({ value, configuredValue, onChange }: PermissionQuickSectionProps) {
|
|
22
23
|
const parsed = parsePermissionsValue(value);
|
|
23
|
-
|
|
24
|
+
// Never fall through to 'auto': a Safe instance shown as Auto (YOLO) means the user cannot
|
|
25
|
+
// see the permission they are about to lose when they touch this control.
|
|
26
|
+
const mode = parsed.mode === 'custom' ? 'custom' : parsed.mode === 'safe' ? 'safe' : 'auto';
|
|
24
27
|
const tokens = parsed.mode === 'custom' ? parsed.tokens : [];
|
|
25
28
|
const summary = parsed.mode === 'custom'
|
|
26
29
|
? `Editor draft: ${tokens.length} explicit token${tokens.length === 1 ? '' : 's'}`
|
|
@@ -38,6 +41,7 @@ export function PermissionQuickSection({ value, configuredValue, onChange }: Per
|
|
|
38
41
|
options={MODE_OPTIONS}
|
|
39
42
|
onChange={(next) => {
|
|
40
43
|
if (next === 'auto') onChange('auto');
|
|
44
|
+
else if (next === 'safe') onChange('safe');
|
|
41
45
|
else onChange(tokens.length > 0 && isAllowlistValid(tokens) ? tokens : seedAutoAllowlist(null));
|
|
42
46
|
}}
|
|
43
47
|
/>
|
|
@@ -3,7 +3,9 @@ import { expandPatch } from '../../path-utils';
|
|
|
3
3
|
import {
|
|
4
4
|
applyRuntimeEmployeesDiff,
|
|
5
5
|
runtimeEmployeesHaveErrors,
|
|
6
|
+
unwrapRuntimeEmployees,
|
|
6
7
|
type RuntimeEmployeeRecord,
|
|
8
|
+
type RuntimeEmployeesResponse,
|
|
7
9
|
} from './runtime-employees-helpers';
|
|
8
10
|
|
|
9
11
|
export type AgentSettingsSnapshot = {
|
|
@@ -62,7 +64,28 @@ export async function saveAgentRuntime(options: {
|
|
|
62
64
|
}
|
|
63
65
|
try {
|
|
64
66
|
if (runtimeEmployeesNext) {
|
|
65
|
-
|
|
67
|
+
// The Classic sidebar writes employees immediately and independently, so by save
|
|
68
|
+
// time the server can hold rows this page never saw. Two things follow, and only
|
|
69
|
+
// doing the first is worse than doing neither.
|
|
70
|
+
//
|
|
71
|
+
// Re-read so the diff is computed against reality rather than a stale snapshot.
|
|
72
|
+
// Then restrict deletion to rows this page actually knew about: a row that appeared
|
|
73
|
+
// on the server while the page was open is absent from the draft, which the plain
|
|
74
|
+
// diff reads as "the user removed it" and deletes. It was never the user's to
|
|
75
|
+
// remove. Additions and updates still come from the draft as the user expressed
|
|
76
|
+
// them.
|
|
77
|
+
//
|
|
78
|
+
// A failed re-read falls back to the snapshot rather than blocking the save: the
|
|
79
|
+
// stale diff is still the caller's explicit intent, and refusing to save would
|
|
80
|
+
// discard it.
|
|
81
|
+
let baseline = employeeOriginal;
|
|
82
|
+
try {
|
|
83
|
+
const server = unwrapRuntimeEmployees(await client.get<RuntimeEmployeesResponse>('/api/employees'));
|
|
84
|
+
const knownToThisPage = new Set(employeeOriginal.map(row => row.id));
|
|
85
|
+
const draftIds = new Set(runtimeEmployeesNext.map(row => row.id));
|
|
86
|
+
baseline = server.filter(row => knownToThisPage.has(row.id) || draftIds.has(row.id));
|
|
87
|
+
} catch { /* keep the snapshot baseline */ }
|
|
88
|
+
await applyRuntimeEmployeesDiff(client, baseline, runtimeEmployeesNext);
|
|
66
89
|
}
|
|
67
90
|
} catch (err: unknown) {
|
|
68
91
|
throw new Error(`runtime employees save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10,6 +10,8 @@ const SETTINGS_ICONS: Readonly<Record<string, IconName>> = {
|
|
|
10
10
|
'advanced-export': 'download', employees: 'people',
|
|
11
11
|
'manager-display': 'monitor', 'manager-activity': 'heartPulse',
|
|
12
12
|
'manager-developer': 'tool', 'manager-embedding': 'search',
|
|
13
|
-
'
|
|
13
|
+
// 'unknown' resolves through this table rather than the 'settings' fallback, so it rendered
|
|
14
|
+
// a literal question mark. The page edits an instance's label, group, favorite and notes.
|
|
15
|
+
'telegram-hub': 'radio', 'dashboard-meta': 'pencil',
|
|
14
16
|
};
|
|
15
17
|
export function settingsIcon(id: string): IconName { return SETTINGS_ICONS[id] ?? 'settings'; }
|
|
@@ -3,7 +3,7 @@ import type { ComponentType } from 'react';
|
|
|
3
3
|
import type { DashboardLocale } from '../types';
|
|
4
4
|
import { DASHBOARD_SETTINGS_COPY } from '../dashboard-settings/DashboardSettingsSidebar';
|
|
5
5
|
type Entry = { id: SettingsCategoryId; scope: SettingsScope; group: SettingsCategoryGroup;
|
|
6
|
-
label: string | ((locale: DashboardLocale) => string);
|
|
6
|
+
label: string | ((locale: DashboardLocale) => string); hidden?: boolean; load: () => Promise<{ default: ComponentType<SettingsPageProps> }> };
|
|
7
7
|
export const SETTINGS_REGISTRY: readonly Entry[] = [
|
|
8
8
|
{ id:'agent', scope:'instance', group:'runtime', label:'Agent', load:()=>import('./pages/Agent') },
|
|
9
9
|
{ id:'model', scope:'instance', group:'runtime', label:'Model defaults', load:()=>import('./pages/ModelProvider') },
|
|
@@ -27,9 +27,9 @@ export const SETTINGS_REGISTRY: readonly Entry[] = [
|
|
|
27
27
|
{ id:'manager-developer', scope:'manager', group:'advanced', label:locale=>DASHBOARD_SETTINGS_COPY[locale].sections.developer.label, load:()=>import('./pages/manager/Developer') },
|
|
28
28
|
{ id:'manager-embedding', scope:'manager', group:'automation', label:locale=>DASHBOARD_SETTINGS_COPY[locale].sections.embedding.label, load:()=>import('./pages/manager/Embedding') },
|
|
29
29
|
{ id:'telegram-hub', scope:'manager', group:'channels', label:locale=>DASHBOARD_SETTINGS_COPY[locale].sections.telegramHub.label, load:()=>import('./pages/manager/TelegramHub') },
|
|
30
|
-
{ id:'dashboard-meta', scope:'manager',
|
|
30
|
+
{ id:'dashboard-meta', scope:'manager', group:'advanced', label:'Dashboard meta', load:()=>import('./pages/DashboardMeta') },
|
|
31
31
|
];
|
|
32
32
|
export const entriesForScopes = (scopes: readonly SettingsScope[], hasInstance: boolean, locale: DashboardLocale) =>
|
|
33
33
|
SETTINGS_REGISTRY.filter(entry => !entry.hidden && scopes.includes(entry.scope)
|
|
34
|
-
&& (hasInstance ||
|
|
34
|
+
&& (hasInstance || entry.scope !== 'instance'))
|
|
35
35
|
.map(entry => ({...entry, label:typeof entry.label==='function' ? entry.label(locale) : entry.label}));
|
|
@@ -224,7 +224,6 @@
|
|
|
224
224
|
.settings-shell .settings-field-error { grid-column:1 / -1; }
|
|
225
225
|
.settings-shell .settings-select-menu { left:auto; right:0; width:min(100%,560px); }
|
|
226
226
|
.settings-shell .settings-field :is(input:not([type=checkbox]),textarea), .settings-shell .settings-select-trigger { background:var(--surface-raised); box-shadow:none; }
|
|
227
|
-
.workbench-settings-toggle:focus-visible,
|
|
228
227
|
.settings-shell :focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
|
|
229
228
|
@container (max-width:719px) {
|
|
230
229
|
.settings-shell .settings-field, .settings-shell .dashboard-settings-row { grid-template-columns:minmax(0,1fr); row-gap:12px; }
|
|
@@ -13,7 +13,6 @@ import './settings-embedding.css';
|
|
|
13
13
|
|
|
14
14
|
const proxyMatch = location.pathname.match(/^\/i\/(\d+)\//);
|
|
15
15
|
const apiBase = proxyMatch ? `/i/${proxyMatch[1]}` : '';
|
|
16
|
-
const bootstrapPort = (window as Window & { __JAW_PORT?: unknown }).__JAW_PORT;
|
|
17
16
|
const host = document.getElementById('settings-root');
|
|
18
17
|
if (!host) throw new Error('Invalid settings host');
|
|
19
18
|
|
|
@@ -53,13 +52,14 @@ const client = createDirectClient(0, { base: apiBase, getHeaders: async () => {
|
|
|
53
52
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
54
53
|
} });
|
|
55
54
|
let snapshot: { port?: unknown } | null = null;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
// There used to be a `window.__JAW_PORT` bootstrap fast path here. Nothing in the tree ever
|
|
56
|
+
// assigned it, so it always resolved to null and this fetch ran regardless; keeping it implied
|
|
57
|
+
// a handshake that did not exist.
|
|
58
|
+
try {
|
|
59
|
+
const result = await client.get<{ port?: unknown; data?: { port?: unknown } }>('/api/settings');
|
|
60
|
+
snapshot = result.data ?? result;
|
|
61
|
+
} catch { /* Location is the final fallback; pages report their own API failures. */ }
|
|
62
|
+
const port = validPort(snapshot?.port) ?? validPort(location.port);
|
|
63
63
|
if (port === null) {
|
|
64
64
|
host.setAttribute('role', 'alert');
|
|
65
65
|
host.textContent = 'Cannot resolve instance settings port';
|