@rubixkube/rubix 0.0.3 → 0.0.4
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 +8 -1
- package/README.md +2 -2
- package/dist/commands/chat.js +2 -1
- package/dist/config/env.js +2 -0
- package/dist/core/rubix-api.js +36 -7
- package/dist/core/settings.js +6 -1
- package/dist/ui/App.js +206 -160
- package/dist/ui/components/ChatTranscript.js +68 -35
- package/dist/ui/components/Composer.js +1 -1
- package/dist/ui/components/DashboardPanel.js +32 -9
- package/dist/ui/theme.js +3 -1
- package/package.json +1 -1
package/dist/ui/App.js
CHANGED
|
@@ -8,12 +8,12 @@ import { Select, Spinner, StatusMessage } from "@inkjs/ui";
|
|
|
8
8
|
import { clearAuthConfig, loadAuthConfig, saveAuthConfig } from "../core/auth-store.js";
|
|
9
9
|
import { authenticateWithDeviceFlow, isTokenNearExpiry } from "../core/device-auth.js";
|
|
10
10
|
import { isFolderTrusted, trustFolder, untrustFolder } from "../core/trust-store.js";
|
|
11
|
-
import { clearLocalSessions,
|
|
11
|
+
import { clearLocalSessions, saveLocalSessions } from "../core/session-store.js";
|
|
12
12
|
import { loadSettings, saveSettings } from "../core/settings.js";
|
|
13
13
|
import { loadWhatsNew } from "../core/whats-new.js";
|
|
14
14
|
import { checkForUpdate } from "../core/update-check.js";
|
|
15
15
|
import { VERSION } from "../version.js";
|
|
16
|
-
import { createSession, fetchChatHistory,
|
|
16
|
+
import { createSession, fetchChatHistory, fetchSystemStats, firstHealthyEnvironment, getOrCreateSession, listEnvironments, listModels, listSessions, listApps, refreshAndUpdateAuth, streamChat, StreamError, updateSessionState, } from "../core/rubix-api.js";
|
|
17
17
|
import { readFileContext, fetchUrlContext, formatContextBlock, } from "../core/file-context.js";
|
|
18
18
|
import { ChatTranscript } from "./components/ChatTranscript.js";
|
|
19
19
|
import { Composer } from "./components/Composer.js";
|
|
@@ -21,7 +21,7 @@ import { DashboardPanel } from "./components/DashboardPanel.js";
|
|
|
21
21
|
import { SplashScreen } from "./components/SplashScreen.js";
|
|
22
22
|
import { TrustDisclaimer } from "./components/TrustDisclaimer.js";
|
|
23
23
|
import { getConfig } from "../config/env.js";
|
|
24
|
-
import { RUBIX_THEME } from "./theme.js";
|
|
24
|
+
import { compactSessionId, RUBIX_THEME } from "./theme.js";
|
|
25
25
|
import { useBracketedPaste } from "./hooks/useBracketedPaste.js";
|
|
26
26
|
// Auth | Session | Input | Navigation | Help
|
|
27
27
|
const SLASH_COMMANDS = [
|
|
@@ -30,11 +30,11 @@ const SLASH_COMMANDS = [
|
|
|
30
30
|
{ name: "/status", description: "Show auth and session status" },
|
|
31
31
|
{ name: "/resume", description: "Resume a previous conversation" },
|
|
32
32
|
{ name: "/new", description: "Start a fresh conversation" },
|
|
33
|
-
{ name: "/
|
|
33
|
+
{ name: "/environments", description: "Switch active environment" },
|
|
34
34
|
{ name: "/models", description: "Switch AI model for this session" },
|
|
35
35
|
{ name: "/agents", description: "Switch agent (app) for new sessions" },
|
|
36
36
|
{ name: "/paste", description: "Insert clipboard content (avoids terminal paste truncation)" },
|
|
37
|
-
{ name: "/send
|
|
37
|
+
{ name: "/send", description: "Send last shell output to Rubix" },
|
|
38
38
|
{ name: "/clear", description: "Clear current conversation history" },
|
|
39
39
|
{ name: "/rename", description: "Rename current session" },
|
|
40
40
|
{ name: "/console", description: "Open web dashboard in browser" },
|
|
@@ -61,6 +61,21 @@ const SHORTCUT_ROWS = [
|
|
|
61
61
|
const SESSION_PAGE_SIZE = 100;
|
|
62
62
|
const SESSION_MAX_PAGES = 10;
|
|
63
63
|
const STREAM_THROTTLE_MS = 80;
|
|
64
|
+
const GOODBYE_MESSAGES = [
|
|
65
|
+
"◈ Watching.",
|
|
66
|
+
"◈ Rubix continues monitoring in the cloud.",
|
|
67
|
+
"◈ Bye.",
|
|
68
|
+
"◈ See you.",
|
|
69
|
+
"◈ Stay reliable.",
|
|
70
|
+
"◈ Catch you later.",
|
|
71
|
+
"◈ Laters.",
|
|
72
|
+
"◈ Ta.",
|
|
73
|
+
"◈ Take care.",
|
|
74
|
+
"◈ Until next time.",
|
|
75
|
+
"◈ Adios.",
|
|
76
|
+
"◈ Cheers.",
|
|
77
|
+
"◈ Out.",
|
|
78
|
+
];
|
|
64
79
|
const IDLE_ACTIVITY_WORDS = [
|
|
65
80
|
"Boogieing",
|
|
66
81
|
"Booping",
|
|
@@ -183,8 +198,15 @@ function formatTimeAgo(isoString) {
|
|
|
183
198
|
}
|
|
184
199
|
export function App({ initialSessionId, seedPrompt }) {
|
|
185
200
|
const { exit } = useApp();
|
|
201
|
+
const gracefulExit = useCallback(() => {
|
|
202
|
+
if (typeof process.stdout?.write === "function") {
|
|
203
|
+
const msg = GOODBYE_MESSAGES[Math.floor(Math.random() * GOODBYE_MESSAGES.length)];
|
|
204
|
+
process.stdout.write(`\n \x1b[2m${msg}\x1b[0m\n\n`);
|
|
205
|
+
}
|
|
206
|
+
exit();
|
|
207
|
+
}, [exit]);
|
|
186
208
|
const cwd = useMemo(() => process.cwd(), []);
|
|
187
|
-
const agentName = useMemo(() => process.env.RUBIX_AGENT_NAME ?? "Rubix
|
|
209
|
+
const agentName = useMemo(() => process.env.RUBIX_AGENT_NAME ?? "Rubix", []);
|
|
188
210
|
const [authConfig, setAuthConfig] = useState(null);
|
|
189
211
|
const [sessionId, setSessionId] = useState(initialSessionId ?? null);
|
|
190
212
|
const [status, setStatus] = useState("booting");
|
|
@@ -211,14 +233,15 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
211
233
|
const [composerResetToken, setComposerResetToken] = useState(0);
|
|
212
234
|
const [lastError, setLastError] = useState(null);
|
|
213
235
|
const [escClearArmed, setEscClearArmed] = useState(false);
|
|
236
|
+
const [ctrlCArmed, setCtrlCArmed] = useState(false);
|
|
214
237
|
const [messages, setMessages] = useState([]);
|
|
215
238
|
const [recentActivity, setRecentActivity] = useState([]);
|
|
216
|
-
const [
|
|
217
|
-
const [
|
|
218
|
-
const [
|
|
219
|
-
const [
|
|
220
|
-
const [
|
|
221
|
-
const [
|
|
239
|
+
const [environments, setEnvironments] = useState([]);
|
|
240
|
+
const [selectedEnvironment, setSelectedEnvironment] = useState(null);
|
|
241
|
+
const [showEnvironmentPanel, setShowEnvironmentPanel] = useState(false);
|
|
242
|
+
const [environmentPanelLoading, setEnvironmentPanelLoading] = useState(false);
|
|
243
|
+
const [environmentPanelError, setEnvironmentPanelError] = useState(null);
|
|
244
|
+
const [pendingEnvironmentSwitch, setPendingEnvironmentSwitch] = useState(null);
|
|
222
245
|
const [availableModels, setAvailableModels] = useState([]);
|
|
223
246
|
const [currentSessionModel, setCurrentSessionModel] = useState(null);
|
|
224
247
|
const [showModelPanel, setShowModelPanel] = useState(false);
|
|
@@ -237,6 +260,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
237
260
|
const [atFileCurrentDir, setAtFileCurrentDir] = useState(cwd);
|
|
238
261
|
const [atFileList, setAtFileList] = useState([]);
|
|
239
262
|
const [atFileSelectedIndex, setAtFileSelectedIndex] = useState(0);
|
|
263
|
+
const [systemStats, setSystemStats] = useState(null);
|
|
240
264
|
const [shellMode, setShellMode] = useState(false);
|
|
241
265
|
const [workflowViewMode, setWorkflowViewMode] = useState("minimal");
|
|
242
266
|
const [whatsNew] = useState(() => loadWhatsNew(VERSION));
|
|
@@ -272,7 +296,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
272
296
|
setWorkflowViewMode(cfg.workflowViewMode);
|
|
273
297
|
if (cfg.agentId)
|
|
274
298
|
setCurrentAgent(cfg.agentId);
|
|
275
|
-
// model and
|
|
299
|
+
// model and environment will be applied after their lists are fetched
|
|
276
300
|
// Check for updates
|
|
277
301
|
checkForUpdate(VERSION, cfg.lastUpdateCheck)
|
|
278
302
|
.then((newVer) => {
|
|
@@ -430,19 +454,19 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
430
454
|
setSlashSelectedIndex(0);
|
|
431
455
|
}, [visibleSlashCandidates]);
|
|
432
456
|
useEffect(() => {
|
|
433
|
-
if (slashModeActive && (showSessionsPanel ||
|
|
457
|
+
if (slashModeActive && (showSessionsPanel || showEnvironmentPanel || showModelPanel)) {
|
|
434
458
|
setShowSessionsPanel(false);
|
|
435
|
-
|
|
459
|
+
setShowEnvironmentPanel(false);
|
|
436
460
|
setShowModelPanel(false);
|
|
437
461
|
}
|
|
438
|
-
}, [showSessionsPanel,
|
|
462
|
+
}, [showSessionsPanel, showEnvironmentPanel, showModelPanel, slashModeActive]);
|
|
439
463
|
useEffect(() => {
|
|
440
|
-
if (atModeActive && (showSessionsPanel ||
|
|
464
|
+
if (atModeActive && (showSessionsPanel || showEnvironmentPanel || showModelPanel)) {
|
|
441
465
|
setShowSessionsPanel(false);
|
|
442
|
-
|
|
466
|
+
setShowEnvironmentPanel(false);
|
|
443
467
|
setShowModelPanel(false);
|
|
444
468
|
}
|
|
445
|
-
}, [atModeActive, showSessionsPanel,
|
|
469
|
+
}, [atModeActive, showSessionsPanel, showEnvironmentPanel, showModelPanel]);
|
|
446
470
|
const prevShowSetupSplash = useRef(showSetupSplash);
|
|
447
471
|
useEffect(() => {
|
|
448
472
|
if (showSetupSplash && !prevShowSetupSplash.current) {
|
|
@@ -490,15 +514,15 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
490
514
|
setLastError(message);
|
|
491
515
|
addSystemMessage(message);
|
|
492
516
|
}, [addSystemMessage, updateAssistantMessage]);
|
|
493
|
-
const ensureSession = useCallback(async (config, preferredId,
|
|
494
|
-
const
|
|
495
|
-
const resolved = await getOrCreateSession(config, preferredId,
|
|
517
|
+
const ensureSession = useCallback(async (config, preferredId, environmentIdOverride) => {
|
|
518
|
+
const envId = environmentIdOverride ?? selectedEnvironment?.environment_id;
|
|
519
|
+
const resolved = await getOrCreateSession(config, preferredId, envId, currentAgent);
|
|
496
520
|
setSessionId(resolved);
|
|
497
521
|
return resolved;
|
|
498
|
-
}, [
|
|
522
|
+
}, [selectedEnvironment?.environment_id, currentAgent]);
|
|
499
523
|
const openSessionsPanel = useCallback(async () => {
|
|
500
524
|
if (!authConfig || !isAuthenticated) {
|
|
501
|
-
addSystemMessage("Not
|
|
525
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
502
526
|
return;
|
|
503
527
|
}
|
|
504
528
|
setShowHelp(false);
|
|
@@ -540,63 +564,63 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
540
564
|
setSessionsPanelLoading(false);
|
|
541
565
|
}
|
|
542
566
|
}, [addSystemMessage, authConfig, isAuthenticated]);
|
|
543
|
-
const
|
|
567
|
+
const openEnvironmentPanel = useCallback(async () => {
|
|
544
568
|
if (!authConfig || !isAuthenticated) {
|
|
545
|
-
addSystemMessage("Not
|
|
569
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
546
570
|
return;
|
|
547
571
|
}
|
|
548
572
|
setShowHelp(false);
|
|
549
573
|
setShowSessionsPanel(false);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
574
|
+
setShowEnvironmentPanel(true);
|
|
575
|
+
setEnvironmentPanelLoading(true);
|
|
576
|
+
setEnvironmentPanelError(null);
|
|
553
577
|
try {
|
|
554
|
-
const loaded = await
|
|
555
|
-
|
|
556
|
-
|
|
578
|
+
const loaded = await listEnvironments(authConfig);
|
|
579
|
+
setEnvironments(loaded);
|
|
580
|
+
setEnvironmentPanelError(null);
|
|
557
581
|
}
|
|
558
582
|
catch (error) {
|
|
559
|
-
|
|
583
|
+
setEnvironmentPanelError(error instanceof Error ? error.message : String(error));
|
|
560
584
|
}
|
|
561
585
|
finally {
|
|
562
|
-
|
|
586
|
+
setEnvironmentPanelLoading(false);
|
|
563
587
|
}
|
|
564
588
|
}, [authConfig, isAuthenticated, addSystemMessage]);
|
|
565
|
-
const
|
|
566
|
-
if (!
|
|
589
|
+
const confirmEnvironmentSwitch = useCallback(async () => {
|
|
590
|
+
if (!pendingEnvironmentSwitch || !authConfig)
|
|
567
591
|
return;
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
void updateSetting({
|
|
592
|
+
const env = pendingEnvironmentSwitch;
|
|
593
|
+
setPendingEnvironmentSwitch(null);
|
|
594
|
+
setSelectedEnvironment(env);
|
|
595
|
+
void updateSetting({ environmentId: env.environment_id });
|
|
572
596
|
setStatus("creating session");
|
|
573
597
|
try {
|
|
574
|
-
const nextSession = await createSession(authConfig, currentAgent,
|
|
598
|
+
const nextSession = await createSession(authConfig, currentAgent, env.environment_id, currentSessionModel?.modelId);
|
|
575
599
|
setSessionId(nextSession);
|
|
576
600
|
setSessionTitle(null);
|
|
577
601
|
const latestSessions = await listSessions(authConfig, 20, 0).catch(() => []);
|
|
578
602
|
setRecentSessions(latestSessions.slice(0, 2));
|
|
579
603
|
setMessages([]);
|
|
580
604
|
setStatus("ready");
|
|
581
|
-
addSystemMessage(`
|
|
605
|
+
addSystemMessage(`Now on ${env.name}. New session started.`);
|
|
582
606
|
}
|
|
583
607
|
catch (error) {
|
|
584
608
|
setStatus("ready");
|
|
585
|
-
addSystemMessage(`
|
|
609
|
+
addSystemMessage(`Couldn't start session for environment: ${error instanceof Error ? error.message : String(error)}`);
|
|
586
610
|
}
|
|
587
|
-
}, [
|
|
611
|
+
}, [pendingEnvironmentSwitch, authConfig, addSystemMessage, currentAgent, currentSessionModel]);
|
|
588
612
|
const openModelPanel = useCallback(async () => {
|
|
589
613
|
if (!authConfig || !isAuthenticated) {
|
|
590
|
-
addSystemMessage("Not
|
|
614
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
591
615
|
return;
|
|
592
616
|
}
|
|
593
617
|
if (!sessionId) {
|
|
594
|
-
addSystemMessage("No active session.
|
|
618
|
+
addSystemMessage("No active session. /new or /resume to start.");
|
|
595
619
|
return;
|
|
596
620
|
}
|
|
597
621
|
setShowHelp(false);
|
|
598
622
|
setShowSessionsPanel(false);
|
|
599
|
-
|
|
623
|
+
setShowEnvironmentPanel(false);
|
|
600
624
|
setShowModelPanel(true);
|
|
601
625
|
setModelPanelLoading(true);
|
|
602
626
|
setModelPanelError(null);
|
|
@@ -615,7 +639,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
615
639
|
}, [authConfig, isAuthenticated, sessionId, addSystemMessage]);
|
|
616
640
|
const handleModelChange = useCallback((modelId) => {
|
|
617
641
|
if (!sessionId) {
|
|
618
|
-
addSystemMessage("No active session.
|
|
642
|
+
addSystemMessage("No active session. Can't switch model.");
|
|
619
643
|
return;
|
|
620
644
|
}
|
|
621
645
|
const selected = availableModels.find((m) => m.id === modelId);
|
|
@@ -636,12 +660,12 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
636
660
|
}, [sessionId, availableModels, addSystemMessage]);
|
|
637
661
|
const openAgentPanel = useCallback(async () => {
|
|
638
662
|
if (!authConfig || !isAuthenticated) {
|
|
639
|
-
addSystemMessage("Not
|
|
663
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
640
664
|
return;
|
|
641
665
|
}
|
|
642
666
|
setShowHelp(false);
|
|
643
667
|
setShowSessionsPanel(false);
|
|
644
|
-
|
|
668
|
+
setShowEnvironmentPanel(false);
|
|
645
669
|
setShowModelPanel(false);
|
|
646
670
|
setShowAgentPanel(true);
|
|
647
671
|
setAgentPanelLoading(true);
|
|
@@ -663,7 +687,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
663
687
|
setShowAgentPanel(false);
|
|
664
688
|
setCurrentAgent(agent);
|
|
665
689
|
void updateSetting({ agentId: agent });
|
|
666
|
-
addSystemMessage(`Switched
|
|
690
|
+
addSystemMessage(`Switched to ${agent}. New session starting.`);
|
|
667
691
|
setSessionId(null);
|
|
668
692
|
setSessionTitle(null);
|
|
669
693
|
setMessages([]);
|
|
@@ -671,7 +695,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
671
695
|
return;
|
|
672
696
|
try {
|
|
673
697
|
setStatus("creating session");
|
|
674
|
-
const nextSession = await createSession(authConfig, agent,
|
|
698
|
+
const nextSession = await createSession(authConfig, agent, selectedEnvironment?.environment_id, currentSessionModel?.modelId);
|
|
675
699
|
setSessionId(nextSession);
|
|
676
700
|
const latestSessions = await listSessions(authConfig, 20, 0).catch(() => []);
|
|
677
701
|
setRecentSessions(latestSessions.slice(0, 2));
|
|
@@ -681,7 +705,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
681
705
|
setStatus("ready");
|
|
682
706
|
addSystemMessage(`Failed to create session with agent ${agent}: ` + String(err));
|
|
683
707
|
}
|
|
684
|
-
}, [authConfig,
|
|
708
|
+
}, [authConfig, selectedEnvironment, currentSessionModel, addSystemMessage]);
|
|
685
709
|
const activateSessionById = useCallback((selectedId) => {
|
|
686
710
|
const selected = sessionItems.find((item) => item.id === selectedId);
|
|
687
711
|
if (!selected)
|
|
@@ -699,16 +723,14 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
699
723
|
.then((history) => {
|
|
700
724
|
setMessages(history.length > 0 ? history : []);
|
|
701
725
|
setStatus("ready");
|
|
702
|
-
addSystemMessage(`Session ${sessionLabel(selected.id)}${selected.title ? ` — ${selected.title}` : ""}${history.length > 0 ? ` · ${history.length} messages loaded` : ""}`);
|
|
703
726
|
})
|
|
704
727
|
.catch((err) => {
|
|
705
728
|
setStatus("ready");
|
|
706
|
-
addSystemMessage(`
|
|
729
|
+
addSystemMessage(`History unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
707
730
|
});
|
|
708
731
|
}
|
|
709
732
|
else {
|
|
710
733
|
setStatus("ready");
|
|
711
|
-
addSystemMessage(`Switched to session ${sessionLabel(selected.id)}${selected.title ? ` — ${selected.title}` : ""}`);
|
|
712
734
|
}
|
|
713
735
|
}, [addSystemMessage, authConfig, sessionItems]);
|
|
714
736
|
useEffect(() => {
|
|
@@ -739,18 +761,13 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
739
761
|
setIsAuthenticated(loggedIn);
|
|
740
762
|
setActiveUser(cfg?.userName ?? cfg?.userEmail ?? null);
|
|
741
763
|
if (loggedIn && cfg) {
|
|
742
|
-
setStatus("loading
|
|
764
|
+
setStatus("loading environments");
|
|
743
765
|
try {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
setSessionItems(localSess);
|
|
748
|
-
}
|
|
749
|
-
// Load clusters and recent sessions for dashboard display
|
|
750
|
-
const [clusterList, recentList, appList] = await Promise.all([
|
|
751
|
-
listClusters(cfg).catch((err) => {
|
|
766
|
+
// Don't pre-populate from local cache — wait for API so Recent is never stale
|
|
767
|
+
const [envList, recentList, appList, stats] = await Promise.all([
|
|
768
|
+
listEnvironments(cfg).catch((err) => {
|
|
752
769
|
if (!cancelled) {
|
|
753
|
-
addSystemMessage(`Could not load
|
|
770
|
+
addSystemMessage(`Could not load environments: ${err instanceof Error ? err.message : String(err)}`);
|
|
754
771
|
}
|
|
755
772
|
return [];
|
|
756
773
|
}),
|
|
@@ -761,29 +778,30 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
761
778
|
return [];
|
|
762
779
|
}),
|
|
763
780
|
listApps(cfg).catch(() => []),
|
|
781
|
+
fetchSystemStats(cfg),
|
|
764
782
|
]);
|
|
765
|
-
const
|
|
783
|
+
const firstEnv = firstHealthyEnvironment(envList);
|
|
766
784
|
if (!cancelled) {
|
|
767
785
|
setRecentSessions(recentList.slice(0, 2));
|
|
768
786
|
setSessionItems(recentList);
|
|
769
787
|
void saveLocalSessions(recentList);
|
|
770
788
|
if (appList && appList.length > 0) {
|
|
771
789
|
setAvailableAgents(appList);
|
|
772
|
-
// Prefer saved agentId from settings, then SRI Agent, then first available
|
|
773
790
|
const saved = currentSettingsRef.current;
|
|
774
791
|
const savedAgent = saved.agentId && appList.includes(saved.agentId) ? saved.agentId : null;
|
|
775
792
|
const preferApp = savedAgent ?? (appList.includes("SRI Agent") ? "SRI Agent" : appList[0]);
|
|
776
793
|
if (preferApp)
|
|
777
794
|
setCurrentAgent(preferApp);
|
|
778
795
|
}
|
|
779
|
-
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
: firstCluster;
|
|
796
|
+
const savedEnvId = currentSettingsRef.current.environmentId;
|
|
797
|
+
const preferredEnv = savedEnvId
|
|
798
|
+
? envList.find((c) => c.environment_id === savedEnvId) ?? firstEnv
|
|
799
|
+
: firstEnv;
|
|
784
800
|
if (!cancelled) {
|
|
785
|
-
|
|
786
|
-
|
|
801
|
+
setEnvironments(envList);
|
|
802
|
+
setSelectedEnvironment(preferredEnv);
|
|
803
|
+
if (stats)
|
|
804
|
+
setSystemStats(stats);
|
|
787
805
|
}
|
|
788
806
|
setStatus("ready");
|
|
789
807
|
}
|
|
@@ -791,7 +809,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
791
809
|
catch (error) {
|
|
792
810
|
if (!cancelled) {
|
|
793
811
|
setStatus("ready");
|
|
794
|
-
addSystemMessage(`Failed to load
|
|
812
|
+
addSystemMessage(`Failed to load environments: ${error instanceof Error ? error.message : String(error)}`);
|
|
795
813
|
}
|
|
796
814
|
}
|
|
797
815
|
}
|
|
@@ -819,6 +837,25 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
819
837
|
cancelled = true;
|
|
820
838
|
};
|
|
821
839
|
}, [addSystemMessage, ensureSession, initialSessionId]);
|
|
840
|
+
// Fetch sessions when dashboard is first shown (ensures Recent has fresh API data)
|
|
841
|
+
const hasFetchedSessionsForDashboard = useRef(false);
|
|
842
|
+
useEffect(() => {
|
|
843
|
+
if (!authConfig || !isAuthenticated || !showDashboard || showTrustDisclaimer || messages.length > 0)
|
|
844
|
+
return;
|
|
845
|
+
if (hasFetchedSessionsForDashboard.current)
|
|
846
|
+
return;
|
|
847
|
+
let cancelled = false;
|
|
848
|
+
hasFetchedSessionsForDashboard.current = true;
|
|
849
|
+
listSessions(authConfig, 20, 0)
|
|
850
|
+
.then((list) => {
|
|
851
|
+
if (!cancelled) {
|
|
852
|
+
setRecentSessions(list.slice(0, 2));
|
|
853
|
+
void saveLocalSessions(list.slice(0, 20));
|
|
854
|
+
}
|
|
855
|
+
})
|
|
856
|
+
.catch(() => { });
|
|
857
|
+
return () => { cancelled = true; };
|
|
858
|
+
}, [authConfig, isAuthenticated, showDashboard, showTrustDisclaimer, messages.length]);
|
|
822
859
|
// Check folder trust status after authentication
|
|
823
860
|
useEffect(() => {
|
|
824
861
|
if (!isAuthenticated || folderTrustCheckComplete)
|
|
@@ -903,13 +940,13 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
903
940
|
return;
|
|
904
941
|
case "/exit":
|
|
905
942
|
case "/quit":
|
|
906
|
-
|
|
943
|
+
gracefulExit();
|
|
907
944
|
return;
|
|
908
945
|
case "/status": {
|
|
909
946
|
const workspace = cwd.replace(process.env.HOME ?? "", "~");
|
|
910
|
-
const
|
|
911
|
-
? `${
|
|
912
|
-
: "
|
|
947
|
+
const envStr = selectedEnvironment
|
|
948
|
+
? `${selectedEnvironment.name} (${selectedEnvironment.status})`
|
|
949
|
+
: "none selected";
|
|
913
950
|
const modelStr = currentSessionModel
|
|
914
951
|
? `${currentSessionModel.displayName}${currentSessionModel.thinkingSupported ? " (thinking supported)" : ""}`
|
|
915
952
|
: "default";
|
|
@@ -918,7 +955,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
918
955
|
`Workspace: ${workspace}`,
|
|
919
956
|
`Auth: ${isAuthenticated ? `logged in as ${activeUser ?? "unknown user"}` : "not logged in"}`,
|
|
920
957
|
`Agent: ${currentAgent}`,
|
|
921
|
-
`
|
|
958
|
+
`Environment: ${envStr}`,
|
|
922
959
|
`Model: ${modelStr}`,
|
|
923
960
|
`Session: ${sessionId ?? "no session"}`,
|
|
924
961
|
`State: ${status}`,
|
|
@@ -938,29 +975,29 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
938
975
|
setSessionTitle(null);
|
|
939
976
|
setStatus("setup");
|
|
940
977
|
setMessages([]);
|
|
941
|
-
addSystemMessage("Logged out.
|
|
978
|
+
addSystemMessage("Logged out. /login when you're ready.");
|
|
942
979
|
return;
|
|
943
980
|
case "/login": {
|
|
944
981
|
if (isAuthenticated && authConfig) {
|
|
945
|
-
addSystemMessage(`Already
|
|
982
|
+
addSystemMessage(`Already in as ${activeUser ?? "user"}. /logout to switch.`);
|
|
946
983
|
return;
|
|
947
984
|
}
|
|
948
985
|
setStatus("auth");
|
|
949
|
-
addSystemMessage("Starting login
|
|
986
|
+
addSystemMessage("Starting login...");
|
|
950
987
|
try {
|
|
951
988
|
const nextAuth = await authenticateWithDeviceFlow((message) => addSystemMessage(message));
|
|
952
989
|
await saveAuthConfig(nextAuth);
|
|
953
990
|
setAuthConfig(nextAuth);
|
|
954
991
|
setIsAuthenticated(true);
|
|
955
992
|
setActiveUser(nextAuth.userName ?? nextAuth.userEmail ?? null);
|
|
956
|
-
const
|
|
957
|
-
addSystemMessage(`Could not load
|
|
993
|
+
const envList = await listEnvironments(nextAuth).catch((err) => {
|
|
994
|
+
addSystemMessage(`Could not load environments: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
995
|
return [];
|
|
959
996
|
});
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
const resolved = await ensureSession(nextAuth, undefined,
|
|
997
|
+
const firstEnv = firstHealthyEnvironment(envList);
|
|
998
|
+
setEnvironments(envList);
|
|
999
|
+
setSelectedEnvironment(firstEnv);
|
|
1000
|
+
const resolved = await ensureSession(nextAuth, undefined, firstEnv?.environment_id);
|
|
964
1001
|
const latestSessions = await listSessions(nextAuth, 20, 0).catch((err) => {
|
|
965
1002
|
addSystemMessage(`Could not load recent sessions: ${err instanceof Error ? err.message : String(err)}`);
|
|
966
1003
|
return [];
|
|
@@ -972,7 +1009,6 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
972
1009
|
setSessionTitle(currentSession.title);
|
|
973
1010
|
setStatus("ready");
|
|
974
1011
|
setMessages([]);
|
|
975
|
-
addSystemMessage(`Login successful${nextAuth.userName ? `, ${nextAuth.userName}` : ""}. Session ${sessionLabel(resolved)}.`);
|
|
976
1012
|
}
|
|
977
1013
|
catch (error) {
|
|
978
1014
|
setStatus("setup");
|
|
@@ -986,12 +1022,12 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
986
1022
|
}
|
|
987
1023
|
case "/new": {
|
|
988
1024
|
if (!authConfig || !isAuthenticated) {
|
|
989
|
-
addSystemMessage("Not
|
|
1025
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
990
1026
|
return;
|
|
991
1027
|
}
|
|
992
1028
|
setStatus("creating session");
|
|
993
1029
|
try {
|
|
994
|
-
const nextSession = await createSession(authConfig, currentAgent,
|
|
1030
|
+
const nextSession = await createSession(authConfig, currentAgent, selectedEnvironment?.environment_id, currentSessionModel?.modelId);
|
|
995
1031
|
setSessionId(nextSession);
|
|
996
1032
|
setSessionTitle(null);
|
|
997
1033
|
setMessages([]);
|
|
@@ -1006,20 +1042,19 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1006
1042
|
return [next, ...prev.filter((s) => s.id !== nextSession)].slice(0, 2);
|
|
1007
1043
|
});
|
|
1008
1044
|
setStatus("ready");
|
|
1009
|
-
addSystemMessage(`Started new conversation ${sessionLabel(nextSession)}.`);
|
|
1010
1045
|
}
|
|
1011
1046
|
catch (error) {
|
|
1012
1047
|
setStatus("ready");
|
|
1013
|
-
addSystemMessage(`
|
|
1048
|
+
addSystemMessage(`Couldn't start a session: ${error instanceof Error ? error.message : String(error)}`);
|
|
1014
1049
|
}
|
|
1015
1050
|
return;
|
|
1016
1051
|
}
|
|
1017
|
-
case "/
|
|
1018
|
-
await
|
|
1052
|
+
case "/environments": {
|
|
1053
|
+
await openEnvironmentPanel();
|
|
1019
1054
|
return;
|
|
1020
1055
|
}
|
|
1021
1056
|
case "/models": {
|
|
1022
|
-
addSystemMessage("
|
|
1057
|
+
addSystemMessage("Loading models...");
|
|
1023
1058
|
await openModelPanel();
|
|
1024
1059
|
return;
|
|
1025
1060
|
}
|
|
@@ -1029,11 +1064,11 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1029
1064
|
}
|
|
1030
1065
|
case "/rename": {
|
|
1031
1066
|
if (!authConfig || !isAuthenticated) {
|
|
1032
|
-
addSystemMessage("Not
|
|
1067
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
1033
1068
|
return;
|
|
1034
1069
|
}
|
|
1035
1070
|
if (!sessionId) {
|
|
1036
|
-
addSystemMessage("No active session.
|
|
1071
|
+
addSystemMessage("No active session. /new or /resume to start.");
|
|
1037
1072
|
return;
|
|
1038
1073
|
}
|
|
1039
1074
|
const rest = text.slice(rawCommand.length).trim();
|
|
@@ -1045,7 +1080,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1045
1080
|
await updateSessionState(authConfig, sessionId, { session_title: rest });
|
|
1046
1081
|
setSessionTitle(rest);
|
|
1047
1082
|
setRecentSessions((prev) => prev.map((s) => (s.id === sessionId ? { ...s, title: rest } : s)));
|
|
1048
|
-
addSystemMessage(`
|
|
1083
|
+
addSystemMessage(`Renamed: ${rest}`);
|
|
1049
1084
|
}
|
|
1050
1085
|
catch (error) {
|
|
1051
1086
|
addSystemMessage(`Rename failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1054,7 +1089,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1054
1089
|
}
|
|
1055
1090
|
case "/untrust": {
|
|
1056
1091
|
if (!authConfig || !isAuthenticated) {
|
|
1057
|
-
addSystemMessage("Not
|
|
1092
|
+
addSystemMessage("Not logged in. /login to connect.");
|
|
1058
1093
|
return;
|
|
1059
1094
|
}
|
|
1060
1095
|
await untrustFolder(cwd);
|
|
@@ -1063,7 +1098,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1063
1098
|
addSystemMessage("Folder untrusted. You'll be prompted to trust again next time.");
|
|
1064
1099
|
return;
|
|
1065
1100
|
}
|
|
1066
|
-
case "/send
|
|
1101
|
+
case "/send": {
|
|
1067
1102
|
const last = lastShellRef.current;
|
|
1068
1103
|
const streamFn = streamAssistantRef.current;
|
|
1069
1104
|
if (last && authConfig && isAuthenticated && sessionId && streamFn) {
|
|
@@ -1134,7 +1169,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1134
1169
|
return;
|
|
1135
1170
|
}
|
|
1136
1171
|
default:
|
|
1137
|
-
addSystemMessage(`Unknown
|
|
1172
|
+
addSystemMessage(`Unknown: ${command} — try /help`);
|
|
1138
1173
|
}
|
|
1139
1174
|
}, [
|
|
1140
1175
|
activeUser,
|
|
@@ -1142,15 +1177,15 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1142
1177
|
authConfig,
|
|
1143
1178
|
cwd,
|
|
1144
1179
|
ensureSession,
|
|
1145
|
-
|
|
1180
|
+
gracefulExit,
|
|
1146
1181
|
isAuthenticated,
|
|
1147
|
-
|
|
1182
|
+
openEnvironmentPanel,
|
|
1148
1183
|
openSessionsPanel,
|
|
1149
1184
|
openAgentPanel,
|
|
1150
1185
|
openModelPanel,
|
|
1151
1186
|
currentAgent,
|
|
1152
1187
|
currentSessionModel,
|
|
1153
|
-
|
|
1188
|
+
selectedEnvironment,
|
|
1154
1189
|
recordActivity,
|
|
1155
1190
|
resetComposer,
|
|
1156
1191
|
sessionId,
|
|
@@ -1171,7 +1206,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1171
1206
|
}, [exit, handleSlashCommand]);
|
|
1172
1207
|
const streamAssistant = useCallback(async (prompt, assistantId, messageParts) => {
|
|
1173
1208
|
if (!authConfig || !isAuthenticated) {
|
|
1174
|
-
failAssistantMessage(assistantId, "Not
|
|
1209
|
+
failAssistantMessage(assistantId, "Not logged in. /login to connect.");
|
|
1175
1210
|
return;
|
|
1176
1211
|
}
|
|
1177
1212
|
let resolvedSession = sessionId;
|
|
@@ -1179,11 +1214,6 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1179
1214
|
try {
|
|
1180
1215
|
resolvedSession = await ensureSession(authConfig);
|
|
1181
1216
|
setSessionId(resolvedSession);
|
|
1182
|
-
// If we created a brand‑new session (not the most recent empty one), inform the user.
|
|
1183
|
-
const prevRecent = recentSessions[0];
|
|
1184
|
-
if (!prevRecent || prevRecent.id !== resolvedSession) {
|
|
1185
|
-
addSystemMessage(`New session ${sessionLabel(resolvedSession)} started.`);
|
|
1186
|
-
}
|
|
1187
1217
|
}
|
|
1188
1218
|
catch (error) {
|
|
1189
1219
|
failAssistantMessage(assistantId, `Failed to initialize session: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1271,11 +1301,6 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1271
1301
|
}));
|
|
1272
1302
|
},
|
|
1273
1303
|
onSessionMetadata: (metadata) => {
|
|
1274
|
-
if (metadata.title || metadata.description || metadata.category) {
|
|
1275
|
-
const meta = [metadata.title, metadata.description, metadata.category].filter(Boolean).join(" · ");
|
|
1276
|
-
if (meta)
|
|
1277
|
-
addSystemMessage(`Session updated: ${meta}`);
|
|
1278
|
-
}
|
|
1279
1304
|
if (metadata.title && sessionId) {
|
|
1280
1305
|
setSessionTitle(metadata.title);
|
|
1281
1306
|
setRecentSessions((prev) => {
|
|
@@ -1306,15 +1331,15 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1306
1331
|
if (!(result.text ?? "").trim()) {
|
|
1307
1332
|
const fallback = sawWorkflowEvent
|
|
1308
1333
|
? "The agent processed your request but didn't produce a final response. Check the workflow above."
|
|
1309
|
-
: "
|
|
1334
|
+
: "Backend went quiet. Try again?";
|
|
1310
1335
|
updateAssistantMessage(assistantId, (message) => ({
|
|
1311
1336
|
...message,
|
|
1312
1337
|
content: message.content.trim().length > 0 ? message.content : fallback,
|
|
1313
1338
|
isAccumulating: false,
|
|
1314
1339
|
}));
|
|
1315
1340
|
if (!sawWorkflowEvent) {
|
|
1316
|
-
setLastError("
|
|
1317
|
-
addSystemMessage("
|
|
1341
|
+
setLastError("Backend went quiet. Try again?");
|
|
1342
|
+
addSystemMessage("Backend went quiet. Try again?");
|
|
1318
1343
|
}
|
|
1319
1344
|
}
|
|
1320
1345
|
}
|
|
@@ -1325,7 +1350,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1325
1350
|
...message,
|
|
1326
1351
|
isAccumulating: false,
|
|
1327
1352
|
}));
|
|
1328
|
-
setLastError("
|
|
1353
|
+
setLastError("Paused.");
|
|
1329
1354
|
}
|
|
1330
1355
|
else {
|
|
1331
1356
|
const reason = didTimeOut
|
|
@@ -1387,7 +1412,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1387
1412
|
return;
|
|
1388
1413
|
}
|
|
1389
1414
|
if (!isAuthenticated) {
|
|
1390
|
-
addSystemMessage("
|
|
1415
|
+
addSystemMessage("Not logged in. /login to get started.");
|
|
1391
1416
|
return;
|
|
1392
1417
|
}
|
|
1393
1418
|
if (trimmed.startsWith("@")) {
|
|
@@ -1480,7 +1505,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1480
1505
|
const errOut = (stderr ?? "").trim();
|
|
1481
1506
|
const output = err ? errOut || out || err.message : out || "(no output)";
|
|
1482
1507
|
lastShellRef.current = { cmd, output };
|
|
1483
|
-
const hint = "· /send
|
|
1508
|
+
const hint = "· /send to send to Rubix";
|
|
1484
1509
|
addSystemMessage(`$ ${cmd}\n${output}\n\n${hint}`);
|
|
1485
1510
|
});
|
|
1486
1511
|
return;
|
|
@@ -1576,6 +1601,9 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1576
1601
|
if (!key.escape) {
|
|
1577
1602
|
setEscClearArmed(false);
|
|
1578
1603
|
}
|
|
1604
|
+
if (!(key.ctrl && input === "c")) {
|
|
1605
|
+
setCtrlCArmed(false);
|
|
1606
|
+
}
|
|
1579
1607
|
// Arrow navigation for slash panel
|
|
1580
1608
|
if (showSlashPanel && visibleSlashCandidates.length > 0) {
|
|
1581
1609
|
if (key.downArrow) {
|
|
@@ -1628,7 +1656,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1628
1656
|
}
|
|
1629
1657
|
// Arrow navigation for sessions panel
|
|
1630
1658
|
const isShowingSlashPanel = slashModeActive && !slashPanelDismissed;
|
|
1631
|
-
if (showSessionsPanel && !isShowingSlashPanel && !
|
|
1659
|
+
if (showSessionsPanel && !isShowingSlashPanel && !showEnvironmentPanel && !showModelPanel) {
|
|
1632
1660
|
const query = sessionsSearchQuery.toLowerCase();
|
|
1633
1661
|
const filtered = sessionItems.filter((item) => {
|
|
1634
1662
|
const title = (item.title ?? "").toLowerCase();
|
|
@@ -1662,7 +1690,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1662
1690
|
return;
|
|
1663
1691
|
}
|
|
1664
1692
|
// Up/Down: prompt history when composer empty, no interactive panels
|
|
1665
|
-
const hasInteractivePanelOpen = showHelp || (slashModeActive && !slashPanelDismissed) || showAtFilePanel || showSessionsPanel ||
|
|
1693
|
+
const hasInteractivePanelOpen = showHelp || (slashModeActive && !slashPanelDismissed) || showAtFilePanel || showSessionsPanel || showEnvironmentPanel || showModelPanel;
|
|
1666
1694
|
if (!hasInteractivePanelOpen &&
|
|
1667
1695
|
composer.trim().length === 0 &&
|
|
1668
1696
|
messageQueue.length === 0 &&
|
|
@@ -1686,17 +1714,17 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1686
1714
|
return;
|
|
1687
1715
|
}
|
|
1688
1716
|
}
|
|
1689
|
-
// Pending
|
|
1690
|
-
if (
|
|
1717
|
+
// Pending environment switch: only Enter to confirm or Esc to cancel
|
|
1718
|
+
if (pendingEnvironmentSwitch) {
|
|
1691
1719
|
if (key.return) {
|
|
1692
|
-
|
|
1693
|
-
addSystemMessage(`
|
|
1720
|
+
confirmEnvironmentSwitch().catch((error) => {
|
|
1721
|
+
addSystemMessage(`Environment switch failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1694
1722
|
});
|
|
1695
1723
|
return;
|
|
1696
1724
|
}
|
|
1697
1725
|
if (key.escape) {
|
|
1698
|
-
|
|
1699
|
-
addSystemMessage("
|
|
1726
|
+
setPendingEnvironmentSwitch(null);
|
|
1727
|
+
addSystemMessage("Environment switch cancelled.");
|
|
1700
1728
|
return;
|
|
1701
1729
|
}
|
|
1702
1730
|
return;
|
|
@@ -1712,7 +1740,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1712
1740
|
return;
|
|
1713
1741
|
}
|
|
1714
1742
|
if (input === "q") {
|
|
1715
|
-
|
|
1743
|
+
gracefulExit();
|
|
1716
1744
|
return;
|
|
1717
1745
|
}
|
|
1718
1746
|
}
|
|
@@ -1720,10 +1748,16 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1720
1748
|
if (isStreaming) {
|
|
1721
1749
|
streamController.current?.abort();
|
|
1722
1750
|
streamController.current = null;
|
|
1723
|
-
|
|
1751
|
+
setCtrlCArmed(true);
|
|
1752
|
+
addSystemMessage("Interrupted. Ask something else, or Ctrl+C to leave.");
|
|
1724
1753
|
return;
|
|
1725
1754
|
}
|
|
1726
|
-
|
|
1755
|
+
if (ctrlCArmed) {
|
|
1756
|
+
gracefulExit();
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
setCtrlCArmed(true);
|
|
1760
|
+
addSystemMessage("Press Ctrl+C again to exit.");
|
|
1727
1761
|
return;
|
|
1728
1762
|
}
|
|
1729
1763
|
if (key.ctrl && input === "l") {
|
|
@@ -1733,7 +1767,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1733
1767
|
return;
|
|
1734
1768
|
}
|
|
1735
1769
|
if (key.ctrl && input === "d" && composer.trim().length === 0) {
|
|
1736
|
-
|
|
1770
|
+
gracefulExit();
|
|
1737
1771
|
return;
|
|
1738
1772
|
}
|
|
1739
1773
|
if (key.ctrl && input === "o") {
|
|
@@ -1760,7 +1794,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1760
1794
|
return;
|
|
1761
1795
|
}
|
|
1762
1796
|
const hasOverlayOpen = showHelp || (slashModeActive && !slashPanelDismissed) || showAtFilePanel;
|
|
1763
|
-
const hasInteractivePanelOpen = hasOverlayOpen || showSessionsPanel ||
|
|
1797
|
+
const hasInteractivePanelOpen = hasOverlayOpen || showSessionsPanel || showEnvironmentPanel || showModelPanel;
|
|
1764
1798
|
if (hasInteractivePanelOpen) {
|
|
1765
1799
|
setShowHelp(false);
|
|
1766
1800
|
setSlashPanelDismissed(true);
|
|
@@ -1768,7 +1802,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1768
1802
|
setShowSessionsPanel(false);
|
|
1769
1803
|
setSessionsSearchQuery("");
|
|
1770
1804
|
setSessionsSelectedIndex(0);
|
|
1771
|
-
|
|
1805
|
+
setShowEnvironmentPanel(false);
|
|
1772
1806
|
setEscClearArmed(true);
|
|
1773
1807
|
return;
|
|
1774
1808
|
}
|
|
@@ -1808,15 +1842,16 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1808
1842
|
atFileFiltered,
|
|
1809
1843
|
cwd,
|
|
1810
1844
|
composer,
|
|
1811
|
-
|
|
1845
|
+
confirmEnvironmentSwitch,
|
|
1846
|
+
ctrlCArmed,
|
|
1812
1847
|
escClearArmed,
|
|
1813
|
-
|
|
1848
|
+
gracefulExit,
|
|
1814
1849
|
historyIndex,
|
|
1815
1850
|
isStreaming,
|
|
1816
1851
|
leaderMode,
|
|
1817
1852
|
messageQueue,
|
|
1818
1853
|
messages,
|
|
1819
|
-
|
|
1854
|
+
pendingEnvironmentSwitch,
|
|
1820
1855
|
promptHistory,
|
|
1821
1856
|
resetComposer,
|
|
1822
1857
|
selectedAtFile,
|
|
@@ -1831,17 +1866,17 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1831
1866
|
setHistoryIndex,
|
|
1832
1867
|
setLeaderMode,
|
|
1833
1868
|
setMessages,
|
|
1834
|
-
|
|
1869
|
+
setPendingEnvironmentSwitch,
|
|
1835
1870
|
setShowHelp,
|
|
1836
1871
|
setSlashPanelDismissed,
|
|
1837
1872
|
setSlashSelectedIndex,
|
|
1838
1873
|
setSessionsSelectedIndex,
|
|
1839
|
-
|
|
1874
|
+
setShowEnvironmentPanel,
|
|
1840
1875
|
setShowSessionsPanel,
|
|
1841
1876
|
setWorkflowExpandedIds,
|
|
1842
1877
|
showAtFilePanel,
|
|
1843
1878
|
showHelp,
|
|
1844
|
-
|
|
1879
|
+
showEnvironmentPanel,
|
|
1845
1880
|
showSessionsPanel,
|
|
1846
1881
|
shellMode,
|
|
1847
1882
|
slashModeActive,
|
|
@@ -1856,10 +1891,10 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1856
1891
|
return !showDashboard || messages.length > 0;
|
|
1857
1892
|
}, [messages.length, showDashboard, showSetupSplash]);
|
|
1858
1893
|
const showSlashPanel = slashModeActive && !slashPanelDismissed;
|
|
1859
|
-
const
|
|
1860
|
-
const showSessionsInteractivePanel = showSessionsPanel && !showSlashPanel && !
|
|
1861
|
-
const showModelInteractivePanel = showModelPanel && !showSlashPanel && !showSessionsInteractivePanel && !
|
|
1862
|
-
const showShortcutPanel = showHelp && !showSlashPanel && !showSessionsInteractivePanel && !
|
|
1894
|
+
const showEnvironmentInteractivePanel = showEnvironmentPanel && !showSlashPanel;
|
|
1895
|
+
const showSessionsInteractivePanel = showSessionsPanel && !showSlashPanel && !showEnvironmentInteractivePanel;
|
|
1896
|
+
const showModelInteractivePanel = showModelPanel && !showSlashPanel && !showSessionsInteractivePanel && !showEnvironmentInteractivePanel;
|
|
1897
|
+
const showShortcutPanel = showHelp && !showSlashPanel && !showSessionsInteractivePanel && !showEnvironmentInteractivePanel && !showModelInteractivePanel;
|
|
1863
1898
|
const latestAssistantMessage = useMemo(() => {
|
|
1864
1899
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1865
1900
|
const message = messages[index];
|
|
@@ -1944,10 +1979,10 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1944
1979
|
};
|
|
1945
1980
|
}, [idleActivityTick, isCommandRunning, isStreaming, lastError, latestAssistantMessage, status]);
|
|
1946
1981
|
const composerSuggestions = useMemo(() => (slashModeActive ? visibleSlashCandidates.map((item) => item.name) : []), [slashModeActive, visibleSlashCandidates]);
|
|
1947
|
-
const
|
|
1948
|
-
label: `${c.name} ${c.status}${c.region ? ` · ${c.region}` : ""}${c.
|
|
1949
|
-
value: c.
|
|
1950
|
-
})), [
|
|
1982
|
+
const environmentSelectOptions = useMemo(() => environments.map((c) => ({
|
|
1983
|
+
label: `${c.name} ${c.status}${c.region ? ` · ${c.region}` : ""}${c.environment_id !== c.name ? ` · ${c.environment_id}` : ""}`,
|
|
1984
|
+
value: c.environment_id,
|
|
1985
|
+
})), [environments]);
|
|
1951
1986
|
const modelSelectOptions = useMemo(() => availableModels.map((m) => ({
|
|
1952
1987
|
label: `${m.default ? "●" : " "} ${m.display_name} ${m.thinking_supported ? "·thinking" : ""}${m.experimental ? " ·experimental" : ""}`,
|
|
1953
1988
|
value: m.id,
|
|
@@ -1975,6 +2010,17 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1975
2010
|
setSessionsSelectedIndex(0);
|
|
1976
2011
|
}
|
|
1977
2012
|
}, [sessionSelectOptions.length, sessionsSelectedIndex]);
|
|
2013
|
+
const dynamicPlaceholder = useMemo(() => {
|
|
2014
|
+
if (isStreaming)
|
|
2015
|
+
return "Type to queue (Enter to add)";
|
|
2016
|
+
if (!isAuthenticated)
|
|
2017
|
+
return "/login to get started";
|
|
2018
|
+
if (messages.length === 0)
|
|
2019
|
+
return "What's on your mind?";
|
|
2020
|
+
if (lastError && lastError !== "Paused.")
|
|
2021
|
+
return "Let's try that again.";
|
|
2022
|
+
return "Ask anything, / for commands, @ for files, ! for shell";
|
|
2023
|
+
}, [isStreaming, isAuthenticated, messages.length, lastError]);
|
|
1978
2024
|
const composerStatusBusy = false;
|
|
1979
2025
|
const composerRightStatus = liveActivity
|
|
1980
2026
|
? ""
|
|
@@ -1989,7 +2035,7 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
1989
2035
|
: currentSessionModel
|
|
1990
2036
|
? `model: ${currentSessionModel.displayName}`
|
|
1991
2037
|
: "";
|
|
1992
|
-
return (_jsxs(Box, { flexDirection: "column", children: [showSetupSplash ? (_jsx(SplashScreen, { agentName: agentName, cwd: cwd, whatsNew: whatsNew, onActionSelect: handleSplashAction, selectDisabled: splashSelectionMade })) : null, showTrustDisclaimer ? (_jsx(TrustDisclaimer, { folderPath: cwd, onActionSelect: handleTrustDisclaimer })) : null, showDashboard && !showTrustDisclaimer ? (_jsxs(_Fragment, { children: [_jsx(DashboardPanel, { user: activeUser, agentName: agentName, cwd: cwd, recentSessions: recentSessions,
|
|
2038
|
+
return (_jsxs(Box, { flexDirection: "column", children: [showSetupSplash ? (_jsx(SplashScreen, { agentName: agentName, cwd: cwd, whatsNew: whatsNew, onActionSelect: handleSplashAction, selectDisabled: splashSelectionMade })) : null, showTrustDisclaimer ? (_jsx(TrustDisclaimer, { folderPath: cwd, onActionSelect: handleTrustDisclaimer })) : null, showDashboard && !showTrustDisclaimer ? (_jsxs(_Fragment, { children: [_jsx(DashboardPanel, { user: activeUser, agentName: agentName, cwd: cwd, recentSessions: recentSessions, selectedEnvironment: selectedEnvironment, stats: systemStats }), updateVersion && (_jsx(Box, { marginTop: 1, paddingX: 3, children: _jsxs(Box, { paddingX: 1, borderStyle: "round", borderColor: "yellow", children: [_jsx(Text, { color: "yellow", bold: true, children: "Update Available!" }), _jsxs(Text, { children: [" v", updateVersion, " (current: v", VERSION, ")"] }), _jsx(Text, { dimColor: true, children: " \u00B7 run " }), _jsx(Text, { color: "cyan", children: "npm i -g @rubixkube/rubix" })] }) }))] })) : null, showTranscript ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [sessionId ? (_jsx(Box, { paddingX: 1, marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: ["Session ", compactSessionId(sessionId), sessionTitle ? ` — ${sessionTitle}` : "", " · ", messages.filter((m) => m.role === "user" || m.role === "assistant").length, " messages"] }) })) : null, _jsx(ChatTranscript, { messages: messages, workflowViewMode: workflowViewMode })] })) : null, !showTrustDisclaimer ? (_jsx(Box, { marginTop: 1, paddingX: 1, gap: 1, minHeight: 1, children: liveActivity ? (_jsxs(_Fragment, { children: [liveActivity.spinning ? _jsx(Spinner, {}) : _jsx(Text, { color: liveActivity.color ?? "red", children: "!" }), _jsx(Text, { color: liveActivity.color ?? RUBIX_THEME.colors.assistantText, children: liveActivity.text }), liveActivity.detail ? _jsxs(Text, { dimColor: true, children: [" ", liveActivity.detail] }) : null] })) : (_jsx(Text, { dimColor: true, children: " " })) })) : null, lastError ? (_jsx(Box, { marginTop: 1, paddingX: 1, children: _jsx(StatusMessage, { variant: lastError === "Paused." ? "info" : "error", children: lastError }) })) : null, !showSetupSplash && !showTrustDisclaimer ? (_jsx(Box, { marginTop: 1, children: _jsx(Composer, { value: composer, resetToken: composerResetToken, disabled: isCommandRunning || isAuthLoading || !!pendingEnvironmentSwitch || showTrustDisclaimer, placeholder: dynamicPlaceholder, shellMode: shellMode, captureArrowKeys: showAtFilePanel, onChange: handleComposerChange, onSubmit: submitComposer, suggestions: composerSuggestions, busy: composerStatusBusy, rightStatus: composerRightStatus, suggestion: slashModeActive
|
|
1993
2039
|
? selectedSlashCandidate
|
|
1994
2040
|
? `${selectedSlashCandidate.name} ${selectedSlashCandidate.description}`
|
|
1995
2041
|
: "No matching command"
|
|
@@ -2017,16 +2063,16 @@ export function App({ initialSessionId, seedPrompt }) {
|
|
|
2017
2063
|
const subtitle = `${formatTimeAgo(sessionItem?.updatedAt ?? new Date().toISOString())}${sessionItem?.clusterId ? ` · ${sessionItem.clusterId}` : ""}`;
|
|
2018
2064
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: isSelected ? RUBIX_THEME.colors.brand : undefined, children: isSelected ? "› " : " " }), _jsx(Text, { bold: isSelected, color: isSelected ? RUBIX_THEME.colors.brand : undefined, children: title })] }), _jsxs(Text, { dimColor: true, children: [" ", subtitle] })] }, option.value));
|
|
2019
2065
|
});
|
|
2020
|
-
})() })), _jsxs(Text, { dimColor: true, children: [sessionSelectOptions.length, " session", sessionSelectOptions.length === 1 ? "" : "s", " shown", sessionsHasMore ? " (more available)" : ""] }), _jsx(Text, { dimColor: true, children: "Type to search \u2022 \u2191\u2193 navigate \u2022 Enter switch \u2022 Esc close" })] })) : null,
|
|
2021
|
-
const
|
|
2022
|
-
if (!
|
|
2066
|
+
})() })), _jsxs(Text, { dimColor: true, children: [sessionSelectOptions.length, " session", sessionSelectOptions.length === 1 ? "" : "s", " shown", sessionsHasMore ? " (more available)" : ""] }), _jsx(Text, { dimColor: true, children: "Type to search \u2022 \u2191\u2193 navigate \u2022 Enter switch \u2022 Esc close" })] })) : null, pendingEnvironmentSwitch ? (_jsxs(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ["Switch to environment: ", pendingEnvironmentSwitch.name] }), _jsx(Text, { dimColor: true, children: "This will start a new session with this environment." }), _jsx(Text, { dimColor: true, children: "Press Enter to confirm \u00B7 Esc to cancel" })] })) : null, showEnvironmentInteractivePanel ? (_jsxs(Box, { borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: RUBIX_THEME.colors.brand, children: ["Environments", selectedEnvironment ? ` · active: ${selectedEnvironment.name}` : ""] }), environmentPanelLoading ? (_jsx(Spinner, { label: "loading environments..." })) : environmentPanelError ? (_jsxs(Text, { color: "red", children: ["Failed to load environments: ", environmentPanelError] })) : environments.length === 0 ? (_jsx(Text, { dimColor: true, children: "No environments found. Register one via the console." })) : (_jsx(Select, { options: environmentSelectOptions, visibleOptionCount: 7, onChange: (value) => {
|
|
2067
|
+
const env = environments.find((c) => c.environment_id === value);
|
|
2068
|
+
if (!env)
|
|
2023
2069
|
return;
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
} })), _jsxs(Text, { dimColor: true, children: [
|
|
2070
|
+
setShowEnvironmentPanel(false);
|
|
2071
|
+
setPendingEnvironmentSwitch(env);
|
|
2072
|
+
} })), _jsxs(Text, { dimColor: true, children: [environments.length, " environment", environments.length === 1 ? "" : "s", " \u00B7 \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close"] })] })) : null, showModelInteractivePanel ? (_jsxs(Box, { borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: RUBIX_THEME.colors.brand, children: ["Models", currentSessionModel ? ` · active: ${currentSessionModel.displayName}` : ""] }), modelPanelLoading ? (_jsx(Spinner, { label: "loading models..." })) : modelPanelError ? (_jsxs(Text, { color: "red", children: ["Failed to load models: ", modelPanelError] })) : availableModels.length === 0 ? (_jsx(Text, { dimColor: true, children: "No models available." })) : (_jsx(Select, { options: modelSelectOptions, visibleOptionCount: 7, onChange: (value) => {
|
|
2027
2073
|
setShowModelPanel(false);
|
|
2028
2074
|
handleModelChange(value);
|
|
2029
2075
|
} })), _jsxs(Text, { dimColor: true, children: [availableModels.length, " model", availableModels.length === 1 ? "" : "s", " \u00B7 \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close"] })] })) : null, showAgentPanel ? (_jsxs(Box, { borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: RUBIX_THEME.colors.brand, children: ["Agents", " · active: " + currentAgent] }), agentPanelLoading ? (_jsx(Spinner, { label: "loading agents..." })) : agentPanelError ? (_jsxs(Text, { color: "red", children: ["Failed to load agents: ", agentPanelError] })) : availableAgents.length === 0 ? (_jsx(Text, { dimColor: true, children: "No agents available." })) : (_jsx(Select, { options: availableAgents.map((a) => ({ label: a, value: a })), visibleOptionCount: 7, onChange: (value) => {
|
|
2030
2076
|
void handleAgentChange(value);
|
|
2031
|
-
} })), _jsxs(Text, { dimColor: true, children: [availableAgents.length, " agent", availableAgents.length === 1 ? "" : "s", " \u00B7 \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close"] })] })) : null, showShortcutPanel ? (_jsxs(Box, { borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: RUBIX_THEME.colors.brand, children: "Shortcuts" }), SHORTCUT_ROWS.map((row) => (_jsxs(Text, { dimColor: true, children: [row.key.padEnd(24), " ", row.action] }, row.key))), _jsx(Text, { dimColor: true, children: "/login /logout /status /resume /new /
|
|
2077
|
+
} })), _jsxs(Text, { dimColor: true, children: [availableAgents.length, " agent", availableAgents.length === 1 ? "" : "s", " \u00B7 \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close"] })] })) : null, showShortcutPanel ? (_jsxs(Box, { borderStyle: "single", borderColor: RUBIX_THEME.colors.brand, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: RUBIX_THEME.colors.brand, children: "Shortcuts" }), SHORTCUT_ROWS.map((row) => (_jsxs(Text, { dimColor: true, children: [row.key.padEnd(24), " ", row.action] }, row.key))), _jsx(Text, { dimColor: true, children: "/login /logout /status /resume /new /environments /models /paste /send /clear /rename /console /docs /help /exit /quit" })] })) : null] }));
|
|
2032
2078
|
}
|