@astroanywhere/cli 0.2.1 → 0.2.2
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/dist/{chunk-SYY2HHOY.js → chunk-MJRAJPBU.js} +41 -17
- package/dist/client.js +3 -1
- package/dist/index.js +3 -1
- package/dist/tui.js +1146 -405
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
AstroClient
|
|
4
|
-
|
|
3
|
+
AstroClient,
|
|
4
|
+
readSSEStream
|
|
5
|
+
} from "./chunk-MJRAJPBU.js";
|
|
5
6
|
|
|
6
7
|
// src/tui/index.tsx
|
|
7
8
|
import "react";
|
|
8
9
|
import { render } from "ink";
|
|
9
10
|
|
|
10
11
|
// src/tui/app.tsx
|
|
11
|
-
import { useMemo, useCallback as useCallback5 } from "react";
|
|
12
|
+
import { useMemo as useMemo2, useCallback as useCallback5, useEffect as useEffect7 } from "react";
|
|
12
13
|
|
|
13
14
|
// src/tui/components/layout/main-layout.tsx
|
|
14
15
|
import "react";
|
|
15
|
-
import { Box as
|
|
16
|
+
import { Box as Box13, useStdout as useStdout3 } from "ink";
|
|
16
17
|
|
|
17
18
|
// src/tui/components/layout/status-bar.tsx
|
|
18
19
|
import "react";
|
|
@@ -149,8 +150,16 @@ function formatCost(usd) {
|
|
|
149
150
|
return `$${usd.toFixed(2)}`;
|
|
150
151
|
}
|
|
151
152
|
function truncate(str, maxLen) {
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
const clean = str.replace(/[\r\n]+/g, " ").trim();
|
|
154
|
+
if (clean.length <= maxLen) return clean;
|
|
155
|
+
return clean.slice(0, maxLen - 1) + "\u2026";
|
|
156
|
+
}
|
|
157
|
+
function getVisibleProjects(projects) {
|
|
158
|
+
return projects.filter((p) => p.projectType !== "playground").sort((a, b) => {
|
|
159
|
+
const ta = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
|
160
|
+
const tb = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
|
161
|
+
return tb - ta;
|
|
162
|
+
});
|
|
154
163
|
}
|
|
155
164
|
|
|
156
165
|
// src/tui/components/layout/status-bar.tsx
|
|
@@ -160,7 +169,7 @@ var VIEW_LABELS = {
|
|
|
160
169
|
"plan-gen": "Plan",
|
|
161
170
|
projects: "Projects",
|
|
162
171
|
playground: "Playground",
|
|
163
|
-
|
|
172
|
+
active: "Active"
|
|
164
173
|
};
|
|
165
174
|
function StatusBar() {
|
|
166
175
|
const connected = useTuiStore((s) => s.connected);
|
|
@@ -363,6 +372,11 @@ function renderTreeLines(roots, collapsedSet) {
|
|
|
363
372
|
}
|
|
364
373
|
|
|
365
374
|
// src/tui/stores/plan-store.ts
|
|
375
|
+
function buildView(nodes, edges, collapsedNodes) {
|
|
376
|
+
const treeRoots = buildTree(nodes, edges);
|
|
377
|
+
const treeLines = renderTreeLines(treeRoots, collapsedNodes);
|
|
378
|
+
return { treeRoots, treeLines };
|
|
379
|
+
}
|
|
366
380
|
var usePlanStore = create4((set, get) => ({
|
|
367
381
|
projectId: null,
|
|
368
382
|
nodes: [],
|
|
@@ -372,11 +386,57 @@ var usePlanStore = create4((set, get) => ({
|
|
|
372
386
|
collapsedNodes: /* @__PURE__ */ new Set(),
|
|
373
387
|
loading: false,
|
|
374
388
|
error: null,
|
|
389
|
+
cache: /* @__PURE__ */ new Map(),
|
|
375
390
|
setPlan: (projectId, nodes, edges) => {
|
|
376
|
-
const { collapsedNodes } = get();
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
391
|
+
const { collapsedNodes, cache } = get();
|
|
392
|
+
const next = new Map(cache);
|
|
393
|
+
next.set(projectId, { nodes, edges });
|
|
394
|
+
const { treeRoots, treeLines } = buildView(nodes, edges, collapsedNodes);
|
|
395
|
+
set({ projectId, nodes, edges, treeRoots, treeLines, loading: false, error: null, cache: next });
|
|
396
|
+
},
|
|
397
|
+
setAllPlans: (allNodes, allEdges) => {
|
|
398
|
+
const { projectId, collapsedNodes } = get();
|
|
399
|
+
const next = /* @__PURE__ */ new Map();
|
|
400
|
+
const nodesByProject = /* @__PURE__ */ new Map();
|
|
401
|
+
for (const node of allNodes) {
|
|
402
|
+
const list = nodesByProject.get(node.projectId) ?? [];
|
|
403
|
+
list.push(node);
|
|
404
|
+
nodesByProject.set(node.projectId, list);
|
|
405
|
+
}
|
|
406
|
+
const nodeProjectMap = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const node of allNodes) {
|
|
408
|
+
nodeProjectMap.set(node.id, node.projectId);
|
|
409
|
+
}
|
|
410
|
+
const edgesByProject = /* @__PURE__ */ new Map();
|
|
411
|
+
for (const edge of allEdges) {
|
|
412
|
+
const pid = nodeProjectMap.get(edge.source) ?? nodeProjectMap.get(edge.target);
|
|
413
|
+
if (pid) {
|
|
414
|
+
const list = edgesByProject.get(pid) ?? [];
|
|
415
|
+
list.push(edge);
|
|
416
|
+
edgesByProject.set(pid, list);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
for (const [pid, nodes] of nodesByProject) {
|
|
420
|
+
next.set(pid, { nodes, edges: edgesByProject.get(pid) ?? [] });
|
|
421
|
+
}
|
|
422
|
+
const update = { cache: next, loading: false, error: null };
|
|
423
|
+
if (projectId && next.has(projectId)) {
|
|
424
|
+
const cached = next.get(projectId);
|
|
425
|
+
const { treeRoots, treeLines } = buildView(cached.nodes, cached.edges, collapsedNodes);
|
|
426
|
+
Object.assign(update, { nodes: cached.nodes, edges: cached.edges, treeRoots, treeLines });
|
|
427
|
+
}
|
|
428
|
+
set(update);
|
|
429
|
+
},
|
|
430
|
+
selectProject: (projectId) => {
|
|
431
|
+
const { cache, collapsedNodes, projectId: currentProjectId } = get();
|
|
432
|
+
if (projectId === currentProjectId) return;
|
|
433
|
+
const cached = cache.get(projectId);
|
|
434
|
+
if (cached) {
|
|
435
|
+
const { treeRoots, treeLines } = buildView(cached.nodes, cached.edges, collapsedNodes);
|
|
436
|
+
set({ projectId, nodes: cached.nodes, edges: cached.edges, treeRoots, treeLines, loading: false, error: null });
|
|
437
|
+
} else {
|
|
438
|
+
set({ projectId, nodes: [], edges: [], treeRoots: [], treeLines: [], loading: false, error: null });
|
|
439
|
+
}
|
|
380
440
|
},
|
|
381
441
|
setLoading: (loading) => set({ loading }),
|
|
382
442
|
setError: (error) => set({ error, loading: false }),
|
|
@@ -392,11 +452,16 @@ var usePlanStore = create4((set, get) => ({
|
|
|
392
452
|
set({ collapsedNodes: next, treeLines });
|
|
393
453
|
},
|
|
394
454
|
updateNodeStatus: (nodeId, status) => {
|
|
395
|
-
const { nodes, edges, collapsedNodes } = get();
|
|
455
|
+
const { nodes, edges, collapsedNodes, projectId, cache } = get();
|
|
396
456
|
const updated = nodes.map((n) => n.id === nodeId ? { ...n, status } : n);
|
|
397
|
-
const treeRoots =
|
|
398
|
-
|
|
399
|
-
|
|
457
|
+
const { treeRoots, treeLines } = buildView(updated, edges, collapsedNodes);
|
|
458
|
+
if (projectId) {
|
|
459
|
+
const next = new Map(cache);
|
|
460
|
+
next.set(projectId, { nodes: updated, edges });
|
|
461
|
+
set({ nodes: updated, treeRoots, treeLines, cache: next });
|
|
462
|
+
} else {
|
|
463
|
+
set({ nodes: updated, treeRoots, treeLines });
|
|
464
|
+
}
|
|
400
465
|
},
|
|
401
466
|
clear: () => set({
|
|
402
467
|
projectId: null,
|
|
@@ -458,12 +523,13 @@ var useExecutionStore = create6((set, get) => ({
|
|
|
458
523
|
outputs: /* @__PURE__ */ new Map(),
|
|
459
524
|
watchingId: null,
|
|
460
525
|
pendingApproval: null,
|
|
461
|
-
initExecution: (executionId, nodeId) => {
|
|
526
|
+
initExecution: (executionId, nodeId, title) => {
|
|
462
527
|
const { outputs } = get();
|
|
463
528
|
const next = new Map(outputs);
|
|
464
529
|
next.set(executionId, {
|
|
465
530
|
executionId,
|
|
466
531
|
nodeId,
|
|
532
|
+
title: title ?? nodeId,
|
|
467
533
|
lines: [],
|
|
468
534
|
status: "running",
|
|
469
535
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -471,6 +537,23 @@ var useExecutionStore = create6((set, get) => ({
|
|
|
471
537
|
});
|
|
472
538
|
set({ outputs: next });
|
|
473
539
|
},
|
|
540
|
+
seedHistorical: (entries) => {
|
|
541
|
+
const { outputs } = get();
|
|
542
|
+
const next = new Map(outputs);
|
|
543
|
+
for (const entry of entries) {
|
|
544
|
+
if (next.has(entry.executionId)) continue;
|
|
545
|
+
next.set(entry.executionId, {
|
|
546
|
+
executionId: entry.executionId,
|
|
547
|
+
nodeId: entry.nodeId,
|
|
548
|
+
title: entry.title,
|
|
549
|
+
lines: [],
|
|
550
|
+
status: entry.status,
|
|
551
|
+
startedAt: entry.startedAt,
|
|
552
|
+
pendingToolCount: 0
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
set({ outputs: next });
|
|
556
|
+
},
|
|
474
557
|
appendToolCall: (executionId) => {
|
|
475
558
|
const { outputs } = get();
|
|
476
559
|
const current = outputs.get(executionId);
|
|
@@ -543,6 +626,53 @@ var useExecutionStore = create6((set, get) => ({
|
|
|
543
626
|
}
|
|
544
627
|
}));
|
|
545
628
|
|
|
629
|
+
// src/tui/stores/chat-store.ts
|
|
630
|
+
import { create as create7 } from "zustand";
|
|
631
|
+
var useChatStore = create7((set, get) => ({
|
|
632
|
+
messages: [],
|
|
633
|
+
sessionId: null,
|
|
634
|
+
projectId: null,
|
|
635
|
+
nodeId: null,
|
|
636
|
+
streaming: false,
|
|
637
|
+
streamBuffer: "",
|
|
638
|
+
addMessage: (role, content) => {
|
|
639
|
+
set((s) => ({
|
|
640
|
+
messages: [...s.messages, { role, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() }]
|
|
641
|
+
}));
|
|
642
|
+
},
|
|
643
|
+
appendStream: (text) => {
|
|
644
|
+
set((s) => ({ streamBuffer: s.streamBuffer + text }));
|
|
645
|
+
},
|
|
646
|
+
flushStream: () => {
|
|
647
|
+
const { streamBuffer } = get();
|
|
648
|
+
if (streamBuffer.length > 0) {
|
|
649
|
+
set((s) => ({
|
|
650
|
+
messages: [...s.messages, { role: "assistant", content: streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
651
|
+
streamBuffer: ""
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
setSessionId: (sessionId) => set({ sessionId }),
|
|
656
|
+
setContext: (projectId, nodeId) => set({ projectId, nodeId: nodeId ?? null }),
|
|
657
|
+
setStreaming: (streaming) => set({ streaming }),
|
|
658
|
+
clear: () => set({ messages: [], sessionId: null, streamBuffer: "", streaming: false })
|
|
659
|
+
}));
|
|
660
|
+
|
|
661
|
+
// src/tui/stores/session-settings-store.ts
|
|
662
|
+
import { create as create8 } from "zustand";
|
|
663
|
+
var useSessionSettingsStore = create8((set) => ({
|
|
664
|
+
machineId: null,
|
|
665
|
+
machineName: null,
|
|
666
|
+
workingDirectory: process.cwd(),
|
|
667
|
+
focusedField: null,
|
|
668
|
+
pickerOpen: false,
|
|
669
|
+
setMachine: (id, name) => set({ machineId: id, machineName: name }),
|
|
670
|
+
setWorkingDirectory: (workingDirectory) => set({ workingDirectory }),
|
|
671
|
+
setFocusedField: (focusedField) => set({ focusedField, pickerOpen: false }),
|
|
672
|
+
setPickerOpen: (pickerOpen) => set({ pickerOpen }),
|
|
673
|
+
init: (machineId, machineName, workingDirectory) => set({ machineId, machineName, workingDirectory })
|
|
674
|
+
}));
|
|
675
|
+
|
|
546
676
|
// src/tui/commands/handlers.ts
|
|
547
677
|
var PALETTE_COMMANDS = [
|
|
548
678
|
{ name: "project list", description: "List all projects" },
|
|
@@ -562,6 +692,9 @@ var PALETTE_COMMANDS = [
|
|
|
562
692
|
{ name: "activity", description: "Show recent activity feed" },
|
|
563
693
|
{ name: "playground", description: "Start a playground (Cloud Code) session", usage: "playground <description>" },
|
|
564
694
|
{ name: "plan generate", description: "Generate a plan using AI", usage: "plan generate <description>" },
|
|
695
|
+
{ name: "project chat", description: "Chat with AI about the selected project", usage: "project chat <message>" },
|
|
696
|
+
{ name: "task chat", description: "Chat with AI about the selected task", usage: "task chat <message>" },
|
|
697
|
+
{ name: "summarize", description: "AI-generated summary of an execution", usage: "summarize [executionId]" },
|
|
565
698
|
{ name: "refresh", description: "Refresh all data" },
|
|
566
699
|
{ name: "help", description: "Toggle keybinding reference" },
|
|
567
700
|
{ name: "quit", description: "Exit the TUI" }
|
|
@@ -670,44 +803,29 @@ var handlers = {
|
|
|
670
803
|
useTuiStore.getState().setLastError("No node/project selected for dispatch");
|
|
671
804
|
return;
|
|
672
805
|
}
|
|
806
|
+
const machines = useMachinesStore.getState().machines;
|
|
807
|
+
const connectedMachine = machines.find((m) => m.isConnected);
|
|
808
|
+
if (!connectedMachine) {
|
|
809
|
+
useTuiStore.getState().setLastError("No connected machines. Start an agent runner first.");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const execId = `exec-${Date.now()}`;
|
|
813
|
+
const planNode = usePlanStore.getState().nodes.find((n) => n.id === nodeId);
|
|
814
|
+
const title = planNode?.title ?? nodeId;
|
|
815
|
+
useExecutionStore.getState().initExecution(execId, nodeId, title);
|
|
816
|
+
useExecutionStore.getState().setWatching(execId);
|
|
817
|
+
useTuiStore.getState().focusPanel("output");
|
|
818
|
+
useExecutionStore.getState().appendLine(execId, `[progress] Dispatching task...`);
|
|
673
819
|
try {
|
|
674
|
-
const response = await client.dispatchTask({
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const reader = response.body.getReader();
|
|
681
|
-
const decoder = new TextDecoder();
|
|
682
|
-
let buffer = "";
|
|
683
|
-
while (true) {
|
|
684
|
-
const { done, value } = await reader.read();
|
|
685
|
-
if (done) break;
|
|
686
|
-
buffer += decoder.decode(value, { stream: true });
|
|
687
|
-
const lines = buffer.split("\n");
|
|
688
|
-
buffer = lines.pop() ?? "";
|
|
689
|
-
for (const line of lines) {
|
|
690
|
-
if (line.startsWith("data: ")) {
|
|
691
|
-
try {
|
|
692
|
-
const event = JSON.parse(line.slice(6));
|
|
693
|
-
const eventType = event.type;
|
|
694
|
-
if (eventType === "text") {
|
|
695
|
-
useExecutionStore.getState().appendText(execId, event.content ?? "");
|
|
696
|
-
} else if (eventType === "tool_use") {
|
|
697
|
-
useExecutionStore.getState().appendToolCall(execId, event.name ?? "");
|
|
698
|
-
} else if (eventType === "result") {
|
|
699
|
-
useExecutionStore.getState().setStatus(execId, event.status ?? "completed");
|
|
700
|
-
} else if (eventType === "error") {
|
|
701
|
-
useExecutionStore.getState().appendLine(execId, `[error] ${event.message}`);
|
|
702
|
-
useExecutionStore.getState().setStatus(execId, "failure");
|
|
703
|
-
}
|
|
704
|
-
} catch {
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
820
|
+
const response = await client.dispatchTask({
|
|
821
|
+
nodeId,
|
|
822
|
+
projectId,
|
|
823
|
+
targetMachineId: connectedMachine.id
|
|
824
|
+
});
|
|
825
|
+
await streamSSEToExecution(response, execId, client, projectId);
|
|
710
826
|
} catch (err) {
|
|
827
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
828
|
+
useExecutionStore.getState().appendLine(execId, `[error] ${err instanceof Error ? err.message : String(err)}`);
|
|
711
829
|
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
712
830
|
}
|
|
713
831
|
},
|
|
@@ -796,35 +914,68 @@ var handlers = {
|
|
|
796
914
|
}
|
|
797
915
|
},
|
|
798
916
|
// ── Playground ──
|
|
917
|
+
// Matches frontend: creates a new playground project, then dispatches.
|
|
918
|
+
// No project selection required — uses session settings for machine + workdir.
|
|
799
919
|
playground: async (args, client) => {
|
|
800
920
|
const description = args.join(" ");
|
|
801
921
|
if (!description) {
|
|
802
922
|
useTuiStore.getState().setLastError("Usage: playground <description>");
|
|
803
923
|
return;
|
|
804
924
|
}
|
|
805
|
-
const
|
|
806
|
-
if (!
|
|
807
|
-
|
|
925
|
+
const settings = useSessionSettingsStore.getState();
|
|
926
|
+
if (!settings.machineId) {
|
|
927
|
+
const machines = useMachinesStore.getState().machines;
|
|
928
|
+
const localPlatform = process.platform;
|
|
929
|
+
const m = machines.find((m2) => m2.isConnected && m2.platform === localPlatform) ?? machines.find((m2) => m2.isConnected);
|
|
930
|
+
if (m) {
|
|
931
|
+
settings.init(m.id, m.name, settings.workingDirectory || process.cwd());
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
const targetMachineId = settings.machineId;
|
|
935
|
+
const targetMachineName = settings.machineName;
|
|
936
|
+
const workDir = settings.workingDirectory || process.cwd();
|
|
937
|
+
if (!targetMachineId) {
|
|
938
|
+
useTuiStore.getState().setLastError("No connected machines. Start an agent runner first.");
|
|
808
939
|
return;
|
|
809
940
|
}
|
|
810
|
-
const
|
|
941
|
+
const execId = `playground-${Date.now()}`;
|
|
942
|
+
const title = `Playground: ${description.slice(0, 50)}`;
|
|
943
|
+
useExecutionStore.getState().initExecution(execId, execId, title);
|
|
944
|
+
useExecutionStore.getState().setWatching(execId);
|
|
945
|
+
useTuiStore.getState().setActiveView("playground");
|
|
946
|
+
useExecutionStore.getState().appendLine(execId, `> ${description}`);
|
|
947
|
+
useExecutionStore.getState().appendLine(execId, `[progress] Creating playground session...`);
|
|
811
948
|
try {
|
|
949
|
+
const projectId = crypto.randomUUID();
|
|
950
|
+
const project = await client.createProject({
|
|
951
|
+
id: projectId,
|
|
952
|
+
name: description.slice(0, 60) || "Playground Session",
|
|
953
|
+
description,
|
|
954
|
+
workingDirectory: workDir,
|
|
955
|
+
defaultMachineId: targetMachineId,
|
|
956
|
+
projectType: "playground"
|
|
957
|
+
});
|
|
958
|
+
const nodeId = `playground-${project.id}`;
|
|
959
|
+
useExecutionStore.getState().appendLine(execId, `[progress] Dispatching to ${targetMachineName ?? targetMachineId} (${workDir})...`);
|
|
812
960
|
const response = await client.dispatchTask({
|
|
813
961
|
nodeId,
|
|
814
|
-
projectId,
|
|
815
|
-
|
|
962
|
+
projectId: project.id,
|
|
963
|
+
title,
|
|
816
964
|
description,
|
|
817
|
-
|
|
965
|
+
targetMachineId,
|
|
966
|
+
workingDirectory: workDir,
|
|
967
|
+
deliveryMode: "direct",
|
|
968
|
+
skipSafetyCheck: true,
|
|
969
|
+
force: true
|
|
818
970
|
});
|
|
819
|
-
|
|
820
|
-
useExecutionStore.getState().setWatching(nodeId);
|
|
821
|
-
useTuiStore.getState().setActiveView("playground");
|
|
822
|
-
await streamExecution(nodeId, response);
|
|
971
|
+
await streamSSEToExecution(response, execId, client, project.id);
|
|
823
972
|
} catch (err) {
|
|
973
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
974
|
+
useExecutionStore.getState().appendLine(execId, `[error] ${err instanceof Error ? err.message : String(err)}`);
|
|
824
975
|
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
825
976
|
}
|
|
826
977
|
},
|
|
827
|
-
// ── Plan Generate ──
|
|
978
|
+
// ── Plan Generate (uses /api/dispatch/task SSE with isInteractivePlan) ──
|
|
828
979
|
"plan generate": async (args, client) => {
|
|
829
980
|
const description = args.join(" ");
|
|
830
981
|
if (!description) {
|
|
@@ -836,26 +987,166 @@ var handlers = {
|
|
|
836
987
|
useTuiStore.getState().setLastError("No project selected. Select a project first.");
|
|
837
988
|
return;
|
|
838
989
|
}
|
|
990
|
+
const machines = useMachinesStore.getState().machines;
|
|
991
|
+
const connectedMachine = machines.find((m) => m.isConnected);
|
|
992
|
+
if (!connectedMachine) {
|
|
993
|
+
useTuiStore.getState().setLastError("No connected machines. Start an agent runner first.");
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
839
996
|
const nodeId = `plan-${projectId}`;
|
|
997
|
+
const execId = `plan-${projectId}-${Date.now()}`;
|
|
998
|
+
const title = `Plan: ${description.slice(0, 50)}`;
|
|
999
|
+
useExecutionStore.getState().initExecution(execId, nodeId, title);
|
|
1000
|
+
useExecutionStore.getState().setWatching(execId);
|
|
1001
|
+
useTuiStore.getState().setActiveView("plan-gen");
|
|
1002
|
+
useExecutionStore.getState().appendLine(execId, `[progress] Plan generation started`);
|
|
840
1003
|
try {
|
|
841
1004
|
const response = await client.dispatchTask({
|
|
842
1005
|
nodeId,
|
|
843
1006
|
projectId,
|
|
1007
|
+
title: `Interactive planning: ${description.slice(0, 80)}`,
|
|
1008
|
+
description,
|
|
1009
|
+
targetMachineId: connectedMachine.id,
|
|
844
1010
|
isInteractivePlan: true,
|
|
845
|
-
|
|
1011
|
+
verification: "human"
|
|
846
1012
|
});
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1013
|
+
await streamSSEToExecution(response, execId, client, projectId);
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
1016
|
+
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
// ── Project Chat (uses /api/agent/project-chat SSE) ──
|
|
1020
|
+
"project chat": async (args, client) => {
|
|
1021
|
+
const message = args.join(" ");
|
|
1022
|
+
if (!message) {
|
|
1023
|
+
useTuiStore.getState().setLastError("Usage: project chat <message>");
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const projectId = useTuiStore.getState().selectedProjectId;
|
|
1027
|
+
if (!projectId) {
|
|
1028
|
+
useTuiStore.getState().setLastError("No project selected");
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const execId = `chat-project-${projectId}-${Date.now()}`;
|
|
1032
|
+
const title = `Chat: ${message.slice(0, 50)}`;
|
|
1033
|
+
const sessionId = useChatStore.getState().sessionId;
|
|
1034
|
+
const messages = useChatStore.getState().messages.map((m) => ({
|
|
1035
|
+
role: m.role,
|
|
1036
|
+
content: m.content
|
|
1037
|
+
}));
|
|
1038
|
+
useExecutionStore.getState().initExecution(execId, `chat-${projectId}`, title);
|
|
1039
|
+
useExecutionStore.getState().setWatching(execId);
|
|
1040
|
+
useExecutionStore.getState().appendLine(execId, `> ${message}`);
|
|
1041
|
+
useChatStore.getState().addMessage("user", message);
|
|
1042
|
+
try {
|
|
1043
|
+
useChatStore.getState().setStreaming(true);
|
|
1044
|
+
useChatStore.getState().setContext(projectId);
|
|
852
1045
|
const { nodes, edges } = await client.getPlan(projectId);
|
|
853
|
-
|
|
854
|
-
|
|
1046
|
+
const response = await client.projectChat({
|
|
1047
|
+
message,
|
|
1048
|
+
sessionId: sessionId ?? void 0,
|
|
1049
|
+
projectId,
|
|
1050
|
+
planNodes: nodes,
|
|
1051
|
+
planEdges: edges,
|
|
1052
|
+
messages
|
|
1053
|
+
});
|
|
1054
|
+
await streamSSEToExecution(response, execId, client, projectId);
|
|
1055
|
+
useChatStore.getState().flushStream();
|
|
1056
|
+
useChatStore.getState().setStreaming(false);
|
|
855
1057
|
} catch (err) {
|
|
1058
|
+
useChatStore.getState().setStreaming(false);
|
|
1059
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
856
1060
|
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
857
1061
|
}
|
|
858
1062
|
},
|
|
1063
|
+
// ── Task Chat (uses /api/agent/task-chat SSE) ──
|
|
1064
|
+
"task chat": async (args, client) => {
|
|
1065
|
+
const message = args.join(" ");
|
|
1066
|
+
if (!message) {
|
|
1067
|
+
useTuiStore.getState().setLastError("Usage: task chat <message>");
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const projectId = useTuiStore.getState().selectedProjectId;
|
|
1071
|
+
const nodeId = useTuiStore.getState().selectedNodeId;
|
|
1072
|
+
if (!projectId || !nodeId) {
|
|
1073
|
+
useTuiStore.getState().setLastError("No project/task selected");
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const planNode = usePlanStore.getState().nodes.find((n) => n.id === nodeId);
|
|
1077
|
+
const taskTitle = planNode?.title ?? nodeId;
|
|
1078
|
+
const execId = `chat-task-${nodeId}-${Date.now()}`;
|
|
1079
|
+
const title = `Task Chat: ${taskTitle.slice(0, 40)}`;
|
|
1080
|
+
const sessionId = useChatStore.getState().sessionId;
|
|
1081
|
+
const messages = useChatStore.getState().messages.map((m) => ({
|
|
1082
|
+
role: m.role,
|
|
1083
|
+
content: m.content
|
|
1084
|
+
}));
|
|
1085
|
+
useExecutionStore.getState().initExecution(execId, `chat-${nodeId}`, title);
|
|
1086
|
+
useExecutionStore.getState().setWatching(execId);
|
|
1087
|
+
useExecutionStore.getState().appendLine(execId, `> ${message}`);
|
|
1088
|
+
useChatStore.getState().addMessage("user", message);
|
|
1089
|
+
try {
|
|
1090
|
+
useChatStore.getState().setStreaming(true);
|
|
1091
|
+
useChatStore.getState().setContext(projectId, nodeId);
|
|
1092
|
+
const response = await client.taskChat({
|
|
1093
|
+
message,
|
|
1094
|
+
sessionId: sessionId ?? void 0,
|
|
1095
|
+
nodeId,
|
|
1096
|
+
projectId,
|
|
1097
|
+
taskTitle,
|
|
1098
|
+
taskDescription: planNode?.description,
|
|
1099
|
+
taskOutput: planNode?.executionOutput ?? void 0,
|
|
1100
|
+
branchName: planNode?.branchName ?? void 0,
|
|
1101
|
+
prUrl: planNode?.prUrl ?? void 0,
|
|
1102
|
+
messages
|
|
1103
|
+
});
|
|
1104
|
+
await streamSSEToExecution(response, execId, client, projectId);
|
|
1105
|
+
useChatStore.getState().flushStream();
|
|
1106
|
+
useChatStore.getState().setStreaming(false);
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
useChatStore.getState().setStreaming(false);
|
|
1109
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
1110
|
+
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
1111
|
+
}
|
|
1112
|
+
},
|
|
1113
|
+
// ── Summarize ──
|
|
1114
|
+
summarize: async (args, client) => {
|
|
1115
|
+
const executionId = args[0] ?? useExecutionStore.getState().watchingId;
|
|
1116
|
+
if (!executionId) {
|
|
1117
|
+
useTuiStore.getState().setLastError("Usage: summarize [executionId]");
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
try {
|
|
1121
|
+
const result = await client.summarize({ executionId });
|
|
1122
|
+
useExecutionStore.getState().appendLine(executionId, `
|
|
1123
|
+
--- Summary ---
|
|
1124
|
+
${result.summary}`);
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
// ── Resume ──
|
|
1130
|
+
resume: async (args) => {
|
|
1131
|
+
const execId = args[0];
|
|
1132
|
+
if (!execId) {
|
|
1133
|
+
useTuiStore.getState().setLastError("Usage: resume <executionId>");
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
const exec = useExecutionStore.getState().outputs.get(execId);
|
|
1137
|
+
if (!exec) {
|
|
1138
|
+
useTuiStore.getState().setLastError(`Session not found: ${execId}`);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
useExecutionStore.getState().setWatching(execId);
|
|
1142
|
+
if (exec.nodeId.startsWith("playground-")) {
|
|
1143
|
+
useTuiStore.getState().setActiveView("playground");
|
|
1144
|
+
} else if (exec.nodeId.startsWith("plan-")) {
|
|
1145
|
+
useTuiStore.getState().setActiveView("plan-gen");
|
|
1146
|
+
} else {
|
|
1147
|
+
useTuiStore.getState().setActiveView("active");
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
859
1150
|
// ── Help ──
|
|
860
1151
|
help: async () => {
|
|
861
1152
|
useTuiStore.getState().toggleHelp();
|
|
@@ -864,79 +1155,141 @@ var handlers = {
|
|
|
864
1155
|
useTuiStore.getState().toggleHelp();
|
|
865
1156
|
}
|
|
866
1157
|
};
|
|
867
|
-
async function
|
|
868
|
-
if (!response.body)
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
for (const line of lines) {
|
|
879
|
-
if (!line.startsWith("data: ")) continue;
|
|
880
|
-
try {
|
|
881
|
-
const event = JSON.parse(line.slice(6));
|
|
882
|
-
const eventType = event.type;
|
|
883
|
-
if (eventType === "text") {
|
|
884
|
-
useExecutionStore.getState().appendText(executionId, event.content ?? "");
|
|
885
|
-
} else if (eventType === "tool_use") {
|
|
886
|
-
useExecutionStore.getState().appendToolCall(executionId, event.name ?? "");
|
|
887
|
-
} else if (eventType === "file_change") {
|
|
888
|
-
useExecutionStore.getState().appendFileChange(
|
|
889
|
-
executionId,
|
|
890
|
-
event.path ?? "",
|
|
891
|
-
event.action ?? "modified",
|
|
892
|
-
event.linesAdded,
|
|
893
|
-
event.linesRemoved
|
|
894
|
-
);
|
|
895
|
-
} else if (eventType === "result" || eventType === "done") {
|
|
896
|
-
useExecutionStore.getState().setStatus(executionId, event.status ?? "completed");
|
|
897
|
-
} else if (eventType === "error") {
|
|
898
|
-
useExecutionStore.getState().appendLine(executionId, `[error] ${event.message}`);
|
|
899
|
-
useExecutionStore.getState().setStatus(executionId, "failure");
|
|
900
|
-
} else if (eventType === "plan_result") {
|
|
901
|
-
useExecutionStore.getState().appendLine(executionId, "[plan] Plan generated successfully");
|
|
902
|
-
} else if (eventType === "approval_request") {
|
|
903
|
-
useExecutionStore.getState().setPendingApproval({
|
|
904
|
-
requestId: event.requestId,
|
|
905
|
-
question: event.question,
|
|
906
|
-
options: event.options,
|
|
907
|
-
machineId: event.machineId,
|
|
908
|
-
taskId: event.taskId
|
|
909
|
-
});
|
|
1158
|
+
async function streamSSEToExecution(response, execId, client, projectId) {
|
|
1159
|
+
if (!response.body) {
|
|
1160
|
+
useExecutionStore.getState().setStatus(execId, "completed");
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
await readSSEStream(response, async (event) => {
|
|
1164
|
+
const type = event.type;
|
|
1165
|
+
switch (type) {
|
|
1166
|
+
case "init":
|
|
1167
|
+
if (event.executionId) {
|
|
1168
|
+
useExecutionStore.getState().appendLine(execId, `[progress] Execution started (${event.executionId})`);
|
|
910
1169
|
}
|
|
911
|
-
|
|
1170
|
+
break;
|
|
1171
|
+
case "text": {
|
|
1172
|
+
const content = event.content ?? event.text ?? "";
|
|
1173
|
+
if (content) {
|
|
1174
|
+
useExecutionStore.getState().appendText(execId, content);
|
|
1175
|
+
useChatStore.getState().appendStream(content);
|
|
1176
|
+
}
|
|
1177
|
+
break;
|
|
912
1178
|
}
|
|
1179
|
+
case "tool_use":
|
|
1180
|
+
useExecutionStore.getState().appendToolCall(execId, event.toolName ?? event.name);
|
|
1181
|
+
break;
|
|
1182
|
+
case "tool_result":
|
|
1183
|
+
break;
|
|
1184
|
+
case "file_change":
|
|
1185
|
+
useExecutionStore.getState().appendFileChange(
|
|
1186
|
+
execId,
|
|
1187
|
+
event.path,
|
|
1188
|
+
event.action,
|
|
1189
|
+
event.linesAdded,
|
|
1190
|
+
event.linesRemoved
|
|
1191
|
+
);
|
|
1192
|
+
break;
|
|
1193
|
+
case "progress":
|
|
1194
|
+
useExecutionStore.getState().appendLine(execId, `[progress] ${event.message}`);
|
|
1195
|
+
break;
|
|
1196
|
+
case "session_init":
|
|
1197
|
+
if (event.sessionId) {
|
|
1198
|
+
useChatStore.getState().setSessionId(event.sessionId);
|
|
1199
|
+
}
|
|
1200
|
+
break;
|
|
1201
|
+
case "plan_result":
|
|
1202
|
+
useExecutionStore.getState().appendLine(execId, "[plan] Plan generated \u2014 refreshing...");
|
|
1203
|
+
if (projectId) {
|
|
1204
|
+
setTimeout(async () => {
|
|
1205
|
+
try {
|
|
1206
|
+
const { nodes, edges } = await client.getPlan(projectId);
|
|
1207
|
+
usePlanStore.getState().setPlan(projectId, nodes, edges);
|
|
1208
|
+
} catch {
|
|
1209
|
+
}
|
|
1210
|
+
}, 500);
|
|
1211
|
+
}
|
|
1212
|
+
break;
|
|
1213
|
+
case "result":
|
|
1214
|
+
useExecutionStore.getState().setStatus(execId, event.status === "success" ? "completed" : "error");
|
|
1215
|
+
if (event.error) {
|
|
1216
|
+
useExecutionStore.getState().appendLine(execId, `[error] ${event.error}`);
|
|
1217
|
+
}
|
|
1218
|
+
break;
|
|
1219
|
+
case "approval_request":
|
|
1220
|
+
useExecutionStore.getState().setPendingApproval({
|
|
1221
|
+
requestId: event.requestId,
|
|
1222
|
+
question: event.question,
|
|
1223
|
+
options: event.options,
|
|
1224
|
+
machineId: event.machineId,
|
|
1225
|
+
taskId: event.taskId
|
|
1226
|
+
});
|
|
1227
|
+
break;
|
|
1228
|
+
case "done":
|
|
1229
|
+
useExecutionStore.getState().setStatus(execId, "completed");
|
|
1230
|
+
break;
|
|
1231
|
+
case "error": {
|
|
1232
|
+
const msg = event.error ?? event.message ?? "Unknown error";
|
|
1233
|
+
useExecutionStore.getState().appendLine(execId, `[error] ${msg}`);
|
|
1234
|
+
useExecutionStore.getState().setStatus(execId, "error");
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
case "heartbeat":
|
|
1238
|
+
case "compaction":
|
|
1239
|
+
case "aborted":
|
|
1240
|
+
break;
|
|
913
1241
|
}
|
|
1242
|
+
});
|
|
1243
|
+
const exec = useExecutionStore.getState().outputs.get(execId);
|
|
1244
|
+
if (exec?.status === "running") {
|
|
1245
|
+
useExecutionStore.getState().setStatus(execId, "completed");
|
|
914
1246
|
}
|
|
915
1247
|
}
|
|
916
1248
|
async function refreshAll(client) {
|
|
917
1249
|
try {
|
|
918
|
-
const [projects, machines] = await Promise.all([
|
|
1250
|
+
const [projects, machines, fullPlan] = await Promise.all([
|
|
919
1251
|
client.listProjects(),
|
|
920
|
-
client.listMachines()
|
|
1252
|
+
client.listMachines(),
|
|
1253
|
+
client.getFullPlan()
|
|
921
1254
|
]);
|
|
922
1255
|
useProjectsStore.getState().setProjects(projects);
|
|
923
1256
|
useMachinesStore.getState().setMachines(machines);
|
|
924
1257
|
useTuiStore.getState().setMachineCount(machines.filter((m) => m.isConnected).length);
|
|
925
|
-
|
|
926
|
-
if (projectId) {
|
|
927
|
-
const { nodes, edges } = await client.getPlan(projectId);
|
|
928
|
-
usePlanStore.getState().setPlan(projectId, nodes, edges);
|
|
929
|
-
}
|
|
1258
|
+
usePlanStore.getState().setAllPlans(fullPlan.nodes, fullPlan.edges);
|
|
930
1259
|
} catch (err) {
|
|
931
1260
|
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
932
1261
|
}
|
|
933
1262
|
}
|
|
934
1263
|
|
|
935
1264
|
// src/tui/commands/palette-filter.ts
|
|
1265
|
+
function getResumeCommands() {
|
|
1266
|
+
const outputs = useExecutionStore.getState().outputs;
|
|
1267
|
+
const entries = [];
|
|
1268
|
+
for (const [id, exec] of outputs) {
|
|
1269
|
+
const statusLabel = exec.status === "running" ? "\u25B6" : exec.status === "success" || exec.status === "completed" ? "\u2713" : "\xB7";
|
|
1270
|
+
entries.push({
|
|
1271
|
+
name: `resume ${exec.title}`,
|
|
1272
|
+
description: `${statusLabel} ${exec.status} \u2014 switch to this session`,
|
|
1273
|
+
usage: `resume:${id}`
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
entries.sort((a, b) => {
|
|
1277
|
+
const aExec = outputs.get(a.usage.replace("resume:", ""));
|
|
1278
|
+
const bExec = outputs.get(b.usage.replace("resume:", ""));
|
|
1279
|
+
if (aExec?.status === "running" && bExec?.status !== "running") return -1;
|
|
1280
|
+
if (bExec?.status === "running" && aExec?.status !== "running") return 1;
|
|
1281
|
+
const ta = aExec?.startedAt ? new Date(aExec.startedAt).getTime() : 0;
|
|
1282
|
+
const tb = bExec?.startedAt ? new Date(bExec.startedAt).getTime() : 0;
|
|
1283
|
+
return tb - ta;
|
|
1284
|
+
});
|
|
1285
|
+
return entries;
|
|
1286
|
+
}
|
|
936
1287
|
function getFilteredPaletteCommands(query) {
|
|
937
|
-
|
|
1288
|
+
const resumeCommands = getResumeCommands();
|
|
1289
|
+
const allCommands = [...PALETTE_COMMANDS, ...resumeCommands];
|
|
1290
|
+
if (!query) return allCommands;
|
|
938
1291
|
const lower = query.toLowerCase();
|
|
939
|
-
return
|
|
1292
|
+
return allCommands.filter(
|
|
940
1293
|
(cmd) => cmd.name.toLowerCase().includes(lower) || cmd.description.toLowerCase().includes(lower)
|
|
941
1294
|
);
|
|
942
1295
|
}
|
|
@@ -948,10 +1301,11 @@ var SHORTCUTS = [
|
|
|
948
1301
|
{ key: "2", label: "Plan" },
|
|
949
1302
|
{ key: "3", label: "Projects" },
|
|
950
1303
|
{ key: "4", label: "Playground" },
|
|
951
|
-
{ key: "5", label: "
|
|
1304
|
+
{ key: "5", label: "Active" },
|
|
952
1305
|
{ key: "/", label: "Search" },
|
|
953
1306
|
{ key: "C-p", label: "Commands" },
|
|
954
1307
|
{ key: "d", label: "Dispatch" },
|
|
1308
|
+
{ key: "x", label: "Stop" },
|
|
955
1309
|
{ key: "?", label: "Help" },
|
|
956
1310
|
{ key: "q", label: "Quit" }
|
|
957
1311
|
];
|
|
@@ -1053,7 +1407,17 @@ function CommandLine({ height }) {
|
|
|
1053
1407
|
] });
|
|
1054
1408
|
}
|
|
1055
1409
|
if (mode === "input") {
|
|
1056
|
-
return /* @__PURE__ */
|
|
1410
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, gap: 1, height, children: [
|
|
1411
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Input active" }),
|
|
1412
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1413
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, bold: true, children: " C-d " }),
|
|
1414
|
+
/* @__PURE__ */ jsx2(Text2, { children: "Stop" })
|
|
1415
|
+
] }),
|
|
1416
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1417
|
+
/* @__PURE__ */ jsx2(Text2, { inverse: true, bold: true, children: " Esc " }),
|
|
1418
|
+
/* @__PURE__ */ jsx2(Text2, { children: "Exit input" })
|
|
1419
|
+
] })
|
|
1420
|
+
] });
|
|
1057
1421
|
}
|
|
1058
1422
|
return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, gap: 1, height, children: SHORTCUTS.map(({ key, label }) => /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1059
1423
|
/* @__PURE__ */ jsx2(Text2, { inverse: true, bold: true, children: ` ${key} ` }),
|
|
@@ -1194,7 +1558,7 @@ function SearchOverlay() {
|
|
|
1194
1558
|
}
|
|
1195
1559
|
|
|
1196
1560
|
// src/tui/components/panels/projects-panel.tsx
|
|
1197
|
-
import "react";
|
|
1561
|
+
import { useEffect as useEffect2, useMemo } from "react";
|
|
1198
1562
|
import { Box as Box5, Text as Text6 } from "ink";
|
|
1199
1563
|
|
|
1200
1564
|
// src/tui/components/layout/panel.tsx
|
|
@@ -1240,13 +1604,14 @@ function Spinner({ label }) {
|
|
|
1240
1604
|
}
|
|
1241
1605
|
|
|
1242
1606
|
// src/tui/components/panels/projects-panel.tsx
|
|
1243
|
-
import {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1607
|
+
import { jsx as jsx5, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1608
|
+
var TERMINAL_NODE_STATUSES = /* @__PURE__ */ new Set([
|
|
1609
|
+
"completed",
|
|
1610
|
+
"auto_verified",
|
|
1611
|
+
"awaiting_judgment",
|
|
1612
|
+
"awaiting_approval",
|
|
1613
|
+
"pruned"
|
|
1614
|
+
]);
|
|
1250
1615
|
function ProjectsPanel({ height }) {
|
|
1251
1616
|
const projects = useProjectsStore((s) => s.projects);
|
|
1252
1617
|
const loading = useProjectsStore((s) => s.loading);
|
|
@@ -1255,32 +1620,63 @@ function ProjectsPanel({ height }) {
|
|
|
1255
1620
|
const scrollIndex = useTuiStore((s) => s.scrollIndex.projects);
|
|
1256
1621
|
const selectedProjectId = useTuiStore((s) => s.selectedProjectId);
|
|
1257
1622
|
const outputs = useExecutionStore((s) => s.outputs);
|
|
1623
|
+
const planNodes = usePlanStore((s) => s.nodes);
|
|
1258
1624
|
const isFocused = focusedPanel === "projects";
|
|
1259
1625
|
const visibleHeight = Math.max(1, height - 3);
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1626
|
+
const projectsWithRunning = useMemo(() => {
|
|
1627
|
+
const running = /* @__PURE__ */ new Set();
|
|
1628
|
+
for (const [, exec] of outputs) {
|
|
1629
|
+
if (exec.status !== "running" && exec.status !== "pending" && exec.status !== "dispatched") continue;
|
|
1630
|
+
const nodeId = exec.nodeId;
|
|
1631
|
+
if (nodeId.startsWith("plan-")) {
|
|
1632
|
+
running.add(nodeId.replace(/^plan-/, "").replace(/-\d+$/, ""));
|
|
1633
|
+
} else if (nodeId.startsWith("playground-")) {
|
|
1634
|
+
const parts = nodeId.replace(/^playground-/, "").split("-");
|
|
1635
|
+
parts.pop();
|
|
1636
|
+
running.add(parts.join("-"));
|
|
1637
|
+
} else if (nodeId.startsWith("chat-")) {
|
|
1638
|
+
running.add(nodeId.replace(/^chat-(project-|task-)?/, "").replace(/-\d+$/, ""));
|
|
1639
|
+
} else {
|
|
1640
|
+
const node = planNodes.find((n) => n.id === nodeId);
|
|
1641
|
+
if (node && !TERMINAL_NODE_STATUSES.has(node.status)) {
|
|
1642
|
+
running.add(node.projectId);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1267
1645
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1646
|
+
for (const node of planNodes) {
|
|
1647
|
+
if ((node.status === "in_progress" || node.status === "dispatched") && !node.deletedAt) {
|
|
1648
|
+
running.add(node.projectId);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
return running;
|
|
1652
|
+
}, [outputs, planNodes]);
|
|
1653
|
+
const sorted = getVisibleProjects(projects);
|
|
1654
|
+
const projectCount = sorted.length;
|
|
1655
|
+
const maxIndex = Math.max(0, projectCount - 1);
|
|
1656
|
+
const cursor = projectCount === 0 ? 0 : Math.min(Math.max(0, scrollIndex), maxIndex);
|
|
1657
|
+
useEffect2(() => {
|
|
1658
|
+
if (projectCount > 0 && scrollIndex !== cursor) {
|
|
1659
|
+
useTuiStore.setState((s) => ({
|
|
1660
|
+
scrollIndex: { ...s.scrollIndex, projects: cursor }
|
|
1661
|
+
}));
|
|
1662
|
+
}
|
|
1663
|
+
}, [scrollIndex, cursor, projectCount]);
|
|
1273
1664
|
let start = 0;
|
|
1274
|
-
if (projectCount >
|
|
1275
|
-
|
|
1665
|
+
if (projectCount > visibleHeight) {
|
|
1666
|
+
if (cursor >= projectCount - visibleHeight) {
|
|
1667
|
+
start = projectCount - visibleHeight;
|
|
1668
|
+
} else {
|
|
1669
|
+
start = Math.max(0, cursor - Math.floor(visibleHeight / 2));
|
|
1670
|
+
}
|
|
1276
1671
|
}
|
|
1277
|
-
const visibleProjects =
|
|
1672
|
+
const visibleProjects = sorted.slice(start, start + visibleHeight);
|
|
1278
1673
|
return /* @__PURE__ */ jsx5(Panel, { title: "PROJECTS", isFocused, height, children: loading && projects.length === 0 ? /* @__PURE__ */ jsx5(Spinner, { label: "Loading projects..." }) : error ? /* @__PURE__ */ jsx5(Text6, { color: "red", children: error }) : /* @__PURE__ */ jsxs6(Box5, { flexDirection: "column", children: [
|
|
1279
|
-
visibleProjects.length === 0 &&
|
|
1674
|
+
visibleProjects.length === 0 && /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: " No projects yet" }),
|
|
1280
1675
|
visibleProjects.map((p, i) => {
|
|
1281
1676
|
const actualIndex = start + i;
|
|
1282
|
-
const isSelected = isFocused &&
|
|
1677
|
+
const isSelected = isFocused && cursor === actualIndex;
|
|
1283
1678
|
const isActive = p.id === selectedProjectId;
|
|
1679
|
+
const hasRunning = projectsWithRunning.has(p.id);
|
|
1284
1680
|
return /* @__PURE__ */ jsxs6(Box5, { children: [
|
|
1285
1681
|
/* @__PURE__ */ jsxs6(
|
|
1286
1682
|
Text6,
|
|
@@ -1291,7 +1687,8 @@ function ProjectsPanel({ height }) {
|
|
|
1291
1687
|
wrap: "truncate",
|
|
1292
1688
|
children: [
|
|
1293
1689
|
isSelected ? " > " : " ",
|
|
1294
|
-
|
|
1690
|
+
hasRunning ? "\u25B6 " : " ",
|
|
1691
|
+
truncate(p.name, 28)
|
|
1295
1692
|
]
|
|
1296
1693
|
}
|
|
1297
1694
|
),
|
|
@@ -1301,36 +1698,14 @@ function ProjectsPanel({ height }) {
|
|
|
1301
1698
|
] })
|
|
1302
1699
|
] }, p.id);
|
|
1303
1700
|
}),
|
|
1304
|
-
projectCount >
|
|
1701
|
+
projectCount > visibleHeight && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
1305
1702
|
" [",
|
|
1306
1703
|
start + 1,
|
|
1307
1704
|
"-",
|
|
1308
|
-
Math.min(start +
|
|
1705
|
+
Math.min(start + visibleHeight, projectCount),
|
|
1309
1706
|
"/",
|
|
1310
1707
|
projectCount,
|
|
1311
1708
|
"]"
|
|
1312
|
-
] }),
|
|
1313
|
-
planGenEntries.length > 0 && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1314
|
-
/* @__PURE__ */ jsx5(Text6, { bold: true, color: "yellow", children: " Plan Generation" }),
|
|
1315
|
-
planGenEntries.map((item) => /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate", children: [
|
|
1316
|
-
" ",
|
|
1317
|
-
statusSymbol(item.status),
|
|
1318
|
-
" ",
|
|
1319
|
-
truncate(item.label, 28),
|
|
1320
|
-
" ",
|
|
1321
|
-
item.status
|
|
1322
|
-
] }, item.id))
|
|
1323
|
-
] }),
|
|
1324
|
-
playgroundEntries.length > 0 && /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1325
|
-
/* @__PURE__ */ jsx5(Text6, { bold: true, color: "green", children: " Playground" }),
|
|
1326
|
-
playgroundEntries.map((item) => /* @__PURE__ */ jsxs6(Text6, { dimColor: true, wrap: "truncate", children: [
|
|
1327
|
-
" ",
|
|
1328
|
-
statusSymbol(item.status),
|
|
1329
|
-
" ",
|
|
1330
|
-
truncate(item.label, 28),
|
|
1331
|
-
" ",
|
|
1332
|
-
item.status
|
|
1333
|
-
] }, item.id))
|
|
1334
1709
|
] })
|
|
1335
1710
|
] }) });
|
|
1336
1711
|
}
|
|
@@ -1349,16 +1724,21 @@ function ScrollableList({ items, selectedIndex, height, isFocused }) {
|
|
|
1349
1724
|
if (items.length === 0) {
|
|
1350
1725
|
return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " No items." }) });
|
|
1351
1726
|
}
|
|
1727
|
+
const cursor = Math.min(Math.max(0, selectedIndex), items.length - 1);
|
|
1352
1728
|
const visibleHeight = Math.max(1, height - 1);
|
|
1353
1729
|
let start = 0;
|
|
1354
|
-
if (
|
|
1355
|
-
|
|
1730
|
+
if (items.length > visibleHeight) {
|
|
1731
|
+
if (cursor >= items.length - visibleHeight) {
|
|
1732
|
+
start = items.length - visibleHeight;
|
|
1733
|
+
} else {
|
|
1734
|
+
start = Math.max(0, cursor - Math.floor(visibleHeight / 2));
|
|
1735
|
+
}
|
|
1356
1736
|
}
|
|
1357
1737
|
const visibleItems = items.slice(start, start + visibleHeight);
|
|
1358
1738
|
return /* @__PURE__ */ jsxs7(Box6, { flexDirection: "column", children: [
|
|
1359
1739
|
visibleItems.map((item, i) => {
|
|
1360
1740
|
const actualIndex = start + i;
|
|
1361
|
-
const isSelected = actualIndex ===
|
|
1741
|
+
const isSelected = actualIndex === cursor && isFocused;
|
|
1362
1742
|
const prefix = isSelected ? " \u25B6 " : " ";
|
|
1363
1743
|
const sublabel = item.sublabel ? ` ${item.sublabel}` : "";
|
|
1364
1744
|
const rightLabel = item.rightLabel ?? "";
|
|
@@ -1477,10 +1857,10 @@ function OutputPanel({ height }) {
|
|
|
1477
1857
|
const isFocused = focusedPanel === "output";
|
|
1478
1858
|
const execution = watchingId ? outputs.get(watchingId) : null;
|
|
1479
1859
|
const visibleHeight = Math.max(1, height - 4);
|
|
1480
|
-
let title = "OUTPUT";
|
|
1860
|
+
let title = "PROCESS OUTPUT";
|
|
1481
1861
|
if (execution) {
|
|
1482
1862
|
const shortId = execution.nodeId.length > 20 ? execution.nodeId.slice(0, 8) + "\u2026" : execution.nodeId;
|
|
1483
|
-
title =
|
|
1863
|
+
title = `${shortId} [${execution.status}]`;
|
|
1484
1864
|
}
|
|
1485
1865
|
if (!execution) {
|
|
1486
1866
|
return /* @__PURE__ */ jsx9(Panel, { title, isFocused, height, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " No active execution. Dispatch a task with 'd' or :dispatch" }) });
|
|
@@ -1515,40 +1895,6 @@ function OutputPanel({ height }) {
|
|
|
1515
1895
|
import "react";
|
|
1516
1896
|
import { Box as Box8, Text as Text11 } from "ink";
|
|
1517
1897
|
import { TextInput } from "@inkjs/ui";
|
|
1518
|
-
|
|
1519
|
-
// src/tui/stores/chat-store.ts
|
|
1520
|
-
import { create as create7 } from "zustand";
|
|
1521
|
-
var useChatStore = create7((set, get) => ({
|
|
1522
|
-
messages: [],
|
|
1523
|
-
sessionId: null,
|
|
1524
|
-
projectId: null,
|
|
1525
|
-
nodeId: null,
|
|
1526
|
-
streaming: false,
|
|
1527
|
-
streamBuffer: "",
|
|
1528
|
-
addMessage: (role, content) => {
|
|
1529
|
-
set((s) => ({
|
|
1530
|
-
messages: [...s.messages, { role, content, timestamp: (/* @__PURE__ */ new Date()).toISOString() }]
|
|
1531
|
-
}));
|
|
1532
|
-
},
|
|
1533
|
-
appendStream: (text) => {
|
|
1534
|
-
set((s) => ({ streamBuffer: s.streamBuffer + text }));
|
|
1535
|
-
},
|
|
1536
|
-
flushStream: () => {
|
|
1537
|
-
const { streamBuffer } = get();
|
|
1538
|
-
if (streamBuffer.length > 0) {
|
|
1539
|
-
set((s) => ({
|
|
1540
|
-
messages: [...s.messages, { role: "assistant", content: streamBuffer, timestamp: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
1541
|
-
streamBuffer: ""
|
|
1542
|
-
}));
|
|
1543
|
-
}
|
|
1544
|
-
},
|
|
1545
|
-
setSessionId: (sessionId) => set({ sessionId }),
|
|
1546
|
-
setContext: (projectId, nodeId) => set({ projectId, nodeId: nodeId ?? null }),
|
|
1547
|
-
setStreaming: (streaming) => set({ streaming }),
|
|
1548
|
-
clear: () => set({ messages: [], sessionId: null, streamBuffer: "", streaming: false })
|
|
1549
|
-
}));
|
|
1550
|
-
|
|
1551
|
-
// src/tui/components/panels/chat-panel.tsx
|
|
1552
1898
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1553
1899
|
function ChatPanel({ height }) {
|
|
1554
1900
|
const messages = useChatStore((s) => s.messages);
|
|
@@ -1603,16 +1949,24 @@ function lineColor2(line) {
|
|
|
1603
1949
|
if (line.startsWith("> ")) return "cyan";
|
|
1604
1950
|
return void 0;
|
|
1605
1951
|
}
|
|
1952
|
+
function machineLabel(name, id) {
|
|
1953
|
+
if (!id) return "No machine";
|
|
1954
|
+
return name || id.slice(0, 12);
|
|
1955
|
+
}
|
|
1606
1956
|
function SessionPanel({ height, title, sessionType, onSubmit }) {
|
|
1607
1957
|
const watchingId = useExecutionStore((s) => s.watchingId);
|
|
1608
1958
|
const outputs = useExecutionStore((s) => s.outputs);
|
|
1609
1959
|
const streaming = useChatStore((s) => s.streaming);
|
|
1610
1960
|
const mode = useTuiStore((s) => s.mode);
|
|
1961
|
+
const { machineId, machineName, workingDirectory, focusedField, pickerOpen } = useSessionSettingsStore();
|
|
1962
|
+
const machines = useMachinesStore((s) => s.machines);
|
|
1963
|
+
const connectedMachines = machines.filter((m) => m.isConnected);
|
|
1611
1964
|
const execution = watchingId ? outputs.get(watchingId) : null;
|
|
1612
1965
|
const isRunning = execution?.status === "running";
|
|
1966
|
+
const settingsHeight = pickerOpen ? 2 + Math.min(connectedMachines.length, 5) : 2;
|
|
1613
1967
|
const inputHeight = 2;
|
|
1614
|
-
const outputHeight = Math.max(1, height - 5 - inputHeight);
|
|
1615
|
-
const isInputActive = mode === "input";
|
|
1968
|
+
const outputHeight = Math.max(1, height - 5 - settingsHeight - inputHeight);
|
|
1969
|
+
const isInputActive = mode === "input" && !focusedField && !pickerOpen;
|
|
1616
1970
|
const lines = execution?.lines ?? [];
|
|
1617
1971
|
const hasPendingTools = (execution?.pendingToolCount ?? 0) > 0;
|
|
1618
1972
|
const start = Math.max(0, lines.length - outputHeight);
|
|
@@ -1641,6 +1995,75 @@ function SessionPanel({ height, title, sessionType, onSubmit }) {
|
|
|
1641
1995
|
lines.length,
|
|
1642
1996
|
"]"
|
|
1643
1997
|
] }),
|
|
1998
|
+
/* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
|
|
1999
|
+
/* @__PURE__ */ jsxs10(Box9, { gap: 2, children: [
|
|
2000
|
+
/* @__PURE__ */ jsxs10(Box9, { children: [
|
|
2001
|
+
/* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "Machine: " }),
|
|
2002
|
+
/* @__PURE__ */ jsxs10(
|
|
2003
|
+
Text12,
|
|
2004
|
+
{
|
|
2005
|
+
color: focusedField === "machine" ? "cyan" : void 0,
|
|
2006
|
+
bold: focusedField === "machine",
|
|
2007
|
+
inverse: focusedField === "machine",
|
|
2008
|
+
children: [
|
|
2009
|
+
" ",
|
|
2010
|
+
machineLabel(machineName, machineId),
|
|
2011
|
+
" "
|
|
2012
|
+
]
|
|
2013
|
+
}
|
|
2014
|
+
)
|
|
2015
|
+
] }),
|
|
2016
|
+
/* @__PURE__ */ jsxs10(Box9, { children: [
|
|
2017
|
+
/* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "Dir: " }),
|
|
2018
|
+
/* @__PURE__ */ jsxs10(
|
|
2019
|
+
Text12,
|
|
2020
|
+
{
|
|
2021
|
+
color: focusedField === "workdir" ? "cyan" : void 0,
|
|
2022
|
+
bold: focusedField === "workdir",
|
|
2023
|
+
inverse: focusedField === "workdir",
|
|
2024
|
+
children: [
|
|
2025
|
+
" ",
|
|
2026
|
+
truncate(workingDirectory, 60),
|
|
2027
|
+
" "
|
|
2028
|
+
]
|
|
2029
|
+
}
|
|
2030
|
+
)
|
|
2031
|
+
] }),
|
|
2032
|
+
!focusedField && /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " (\u2191 to change settings)" }),
|
|
2033
|
+
focusedField && !pickerOpen && /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " (Enter to edit, \u2193 to input)" })
|
|
2034
|
+
] }),
|
|
2035
|
+
pickerOpen && focusedField === "machine" && /* @__PURE__ */ jsx11(Box9, { flexDirection: "column", marginLeft: 2, children: connectedMachines.length === 0 ? /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " No connected machines" }) : connectedMachines.slice(0, 5).map((m) => /* @__PURE__ */ jsxs10(
|
|
2036
|
+
Text12,
|
|
2037
|
+
{
|
|
2038
|
+
color: m.id === machineId ? "green" : void 0,
|
|
2039
|
+
children: [
|
|
2040
|
+
m.id === machineId ? "\u25CF " : " ",
|
|
2041
|
+
m.name || m.id.slice(0, 12),
|
|
2042
|
+
" (",
|
|
2043
|
+
m.hostname,
|
|
2044
|
+
", ",
|
|
2045
|
+
m.platform,
|
|
2046
|
+
")"
|
|
2047
|
+
]
|
|
2048
|
+
},
|
|
2049
|
+
m.id
|
|
2050
|
+
)) }),
|
|
2051
|
+
pickerOpen && focusedField === "workdir" && /* @__PURE__ */ jsxs10(Box9, { marginLeft: 2, children: [
|
|
2052
|
+
/* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "Path: " }),
|
|
2053
|
+
/* @__PURE__ */ jsx11(
|
|
2054
|
+
TextInput2,
|
|
2055
|
+
{
|
|
2056
|
+
defaultValue: workingDirectory,
|
|
2057
|
+
onSubmit: (val) => {
|
|
2058
|
+
if (val.trim()) {
|
|
2059
|
+
useSessionSettingsStore.getState().setWorkingDirectory(val.trim());
|
|
2060
|
+
}
|
|
2061
|
+
useSessionSettingsStore.getState().setPickerOpen(false);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
)
|
|
2065
|
+
] })
|
|
2066
|
+
] }),
|
|
1644
2067
|
/* @__PURE__ */ jsx11(Box9, { borderStyle: "single", borderColor: isInputActive ? "cyan" : "gray", paddingX: 1, children: isRunning || streaming ? /* @__PURE__ */ jsxs10(Box9, { children: [
|
|
1645
2068
|
/* @__PURE__ */ jsx11(Spinner, {}),
|
|
1646
2069
|
/* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " Agent is working... (press Esc to return to navigation)" })
|
|
@@ -1661,22 +2084,235 @@ function SessionPanel({ height, title, sessionType, onSubmit }) {
|
|
|
1661
2084
|
}
|
|
1662
2085
|
}
|
|
1663
2086
|
}
|
|
1664
|
-
) : /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "Press Enter to start typing..." })
|
|
2087
|
+
) : /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: focusedField ? "Press \u2193 to return to input" : "Press Enter to start typing..." })
|
|
1665
2088
|
] }) })
|
|
1666
2089
|
] }) });
|
|
1667
2090
|
}
|
|
1668
2091
|
|
|
2092
|
+
// src/tui/components/panels/active-list-panel.tsx
|
|
2093
|
+
import { useState as useState2, useEffect as useEffect3 } from "react";
|
|
2094
|
+
import { Box as Box10, Text as Text13, useInput as useInput2 } from "ink";
|
|
2095
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2096
|
+
var TERMINAL_NODE_STATUSES2 = /* @__PURE__ */ new Set([
|
|
2097
|
+
"completed",
|
|
2098
|
+
"auto_verified",
|
|
2099
|
+
"awaiting_judgment",
|
|
2100
|
+
"awaiting_approval",
|
|
2101
|
+
"pruned"
|
|
2102
|
+
]);
|
|
2103
|
+
function statusSymbol(status) {
|
|
2104
|
+
if (status === "generating") return "\u2728";
|
|
2105
|
+
if (status === "running") return "\u25B6";
|
|
2106
|
+
if (status === "pending") return "\u25CB";
|
|
2107
|
+
return "\xB7";
|
|
2108
|
+
}
|
|
2109
|
+
function statusColor(status) {
|
|
2110
|
+
if (status === "generating") return "magenta";
|
|
2111
|
+
if (status === "running") return "cyan";
|
|
2112
|
+
if (status === "pending") return "yellow";
|
|
2113
|
+
return "gray";
|
|
2114
|
+
}
|
|
2115
|
+
function elapsedLabel(startedAt) {
|
|
2116
|
+
if (!startedAt) return "";
|
|
2117
|
+
const ms = Date.now() - new Date(startedAt).getTime();
|
|
2118
|
+
if (ms < 0) return "";
|
|
2119
|
+
const s = Math.floor(ms / 1e3);
|
|
2120
|
+
const m = Math.floor(s / 60);
|
|
2121
|
+
if (m > 0) return `${m}m${(s % 60).toString().padStart(2, "0")}s`;
|
|
2122
|
+
return `${s}s`;
|
|
2123
|
+
}
|
|
2124
|
+
function ActiveListPanel({ height }) {
|
|
2125
|
+
const outputs = useExecutionStore((s) => s.outputs);
|
|
2126
|
+
const watchingId = useExecutionStore((s) => s.watchingId);
|
|
2127
|
+
const pendingApproval = useExecutionStore((s) => s.pendingApproval);
|
|
2128
|
+
const mode = useTuiStore((s) => s.mode);
|
|
2129
|
+
const projects = useProjectsStore((s) => s.projects);
|
|
2130
|
+
const planNodes = usePlanStore((s) => s.nodes);
|
|
2131
|
+
const [cursor, setCursor] = useState2(0);
|
|
2132
|
+
const tasksByProject = /* @__PURE__ */ new Map();
|
|
2133
|
+
for (const [id, exec] of outputs) {
|
|
2134
|
+
if (exec.status !== "running" && exec.status !== "pending" && exec.status !== "dispatched") continue;
|
|
2135
|
+
const isPlanSession = exec.nodeId.startsWith("plan-");
|
|
2136
|
+
const isPlayground = exec.nodeId.startsWith("playground-");
|
|
2137
|
+
const isChatSession = exec.nodeId.startsWith("chat-");
|
|
2138
|
+
const node = isPlanSession || isPlayground || isChatSession ? void 0 : planNodes.find((n) => n.id === exec.nodeId);
|
|
2139
|
+
if (node && TERMINAL_NODE_STATUSES2.has(node.status)) continue;
|
|
2140
|
+
let projectId = null;
|
|
2141
|
+
if (node?.projectId) {
|
|
2142
|
+
projectId = node.projectId;
|
|
2143
|
+
} else if (isPlanSession) {
|
|
2144
|
+
projectId = exec.nodeId.replace(/^plan-/, "").replace(/-\d+$/, "");
|
|
2145
|
+
} else if (isPlayground) {
|
|
2146
|
+
const parts = exec.nodeId.replace(/^playground-/, "").split("-");
|
|
2147
|
+
parts.pop();
|
|
2148
|
+
projectId = parts.join("-");
|
|
2149
|
+
} else if (isChatSession) {
|
|
2150
|
+
projectId = exec.nodeId.replace(/^chat-/, "").replace(/-\d+$/, "");
|
|
2151
|
+
}
|
|
2152
|
+
if (!projectId) continue;
|
|
2153
|
+
let title = exec.title;
|
|
2154
|
+
if (!title || title === exec.nodeId) {
|
|
2155
|
+
if (isPlanSession) {
|
|
2156
|
+
title = "Interactive Planning";
|
|
2157
|
+
} else if (isPlayground) {
|
|
2158
|
+
const proj = projects.find((p) => p.id === projectId);
|
|
2159
|
+
title = proj?.name ? `Playground: ${proj.name}` : "Playground Session";
|
|
2160
|
+
} else {
|
|
2161
|
+
title = node?.title ?? `Task ${exec.nodeId.slice(0, 8)}`;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const task = {
|
|
2165
|
+
nodeId: exec.nodeId,
|
|
2166
|
+
projectId,
|
|
2167
|
+
title,
|
|
2168
|
+
status: isPlanSession ? "generating" : exec.status === "running" ? "running" : "pending",
|
|
2169
|
+
executionId: id,
|
|
2170
|
+
startedAt: exec.startedAt
|
|
2171
|
+
};
|
|
2172
|
+
const list = tasksByProject.get(projectId) ?? [];
|
|
2173
|
+
list.push(task);
|
|
2174
|
+
tasksByProject.set(projectId, list);
|
|
2175
|
+
}
|
|
2176
|
+
for (const node of planNodes) {
|
|
2177
|
+
if (node.status !== "in_progress" && node.status !== "dispatched") continue;
|
|
2178
|
+
if (node.deletedAt) continue;
|
|
2179
|
+
const alreadyCovered = Array.from(outputs.values()).some(
|
|
2180
|
+
(exec) => exec.nodeId === node.id && (exec.status === "running" || exec.status === "pending" || exec.status === "dispatched")
|
|
2181
|
+
);
|
|
2182
|
+
if (alreadyCovered) continue;
|
|
2183
|
+
const task = {
|
|
2184
|
+
nodeId: node.id,
|
|
2185
|
+
projectId: node.projectId,
|
|
2186
|
+
title: node.title,
|
|
2187
|
+
status: node.status === "in_progress" ? "running" : "pending",
|
|
2188
|
+
startedAt: node.executionStartedAt
|
|
2189
|
+
};
|
|
2190
|
+
const list = tasksByProject.get(node.projectId) ?? [];
|
|
2191
|
+
list.push(task);
|
|
2192
|
+
tasksByProject.set(node.projectId, list);
|
|
2193
|
+
}
|
|
2194
|
+
const sortedProjects = Array.from(tasksByProject.entries()).map(([projectId, tasks]) => {
|
|
2195
|
+
const project = projects.find((p) => p.id === projectId);
|
|
2196
|
+
return {
|
|
2197
|
+
projectId,
|
|
2198
|
+
projectName: project?.name ?? `Project ${projectId.slice(0, 8)}`,
|
|
2199
|
+
projectColor: project?.color,
|
|
2200
|
+
tasks
|
|
2201
|
+
};
|
|
2202
|
+
}).sort((a, b) => b.tasks.length - a.tasks.length);
|
|
2203
|
+
const rows = [];
|
|
2204
|
+
for (const group of sortedProjects) {
|
|
2205
|
+
rows.push({
|
|
2206
|
+
type: "header",
|
|
2207
|
+
label: group.projectName,
|
|
2208
|
+
color: "blue",
|
|
2209
|
+
taskCount: group.tasks.length
|
|
2210
|
+
});
|
|
2211
|
+
for (const task of group.tasks) {
|
|
2212
|
+
rows.push({
|
|
2213
|
+
type: "entry",
|
|
2214
|
+
label: task.title,
|
|
2215
|
+
executionId: task.executionId,
|
|
2216
|
+
status: task.status,
|
|
2217
|
+
startedAt: task.startedAt
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
const selectableIndices = rows.map((r, i) => r.type === "entry" ? i : -1).filter((i) => i >= 0);
|
|
2222
|
+
useEffect3(() => {
|
|
2223
|
+
if (selectableIndices.length > 0 && cursor >= selectableIndices.length) {
|
|
2224
|
+
setCursor(selectableIndices.length - 1);
|
|
2225
|
+
}
|
|
2226
|
+
}, [selectableIndices.length, cursor]);
|
|
2227
|
+
useInput2((input, key) => {
|
|
2228
|
+
if (mode !== "normal") return;
|
|
2229
|
+
if (key.upArrow || input === "k") {
|
|
2230
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
2231
|
+
} else if (key.downArrow || input === "j") {
|
|
2232
|
+
setCursor((c) => Math.min(selectableIndices.length - 1, c + 1));
|
|
2233
|
+
} else if (key.return) {
|
|
2234
|
+
const rowIdx = selectableIndices[cursor];
|
|
2235
|
+
const row = rows[rowIdx];
|
|
2236
|
+
if (row?.executionId) {
|
|
2237
|
+
useExecutionStore.getState().setWatching(row.executionId);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
const totalCount = selectableIndices.length;
|
|
2242
|
+
const visibleHeight = Math.max(1, height - 3);
|
|
2243
|
+
const cursorRowIdx = selectableIndices[cursor] ?? 0;
|
|
2244
|
+
let start = 0;
|
|
2245
|
+
if (rows.length > visibleHeight) {
|
|
2246
|
+
if (cursorRowIdx >= rows.length - visibleHeight) {
|
|
2247
|
+
start = rows.length - visibleHeight;
|
|
2248
|
+
} else {
|
|
2249
|
+
start = Math.max(0, cursorRowIdx - Math.floor(visibleHeight / 2));
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
const visibleRows = rows.slice(start, start + visibleHeight);
|
|
2253
|
+
const titleSuffix = totalCount > 0 ? ` (${totalCount})` : "";
|
|
2254
|
+
return /* @__PURE__ */ jsx12(Panel, { title: `ACTIVE${titleSuffix}`, isFocused: true, height, children: rows.length === 0 ? /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: " No active tasks" }) : /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", children: [
|
|
2255
|
+
visibleRows.map((row, i) => {
|
|
2256
|
+
const actualIndex = start + i;
|
|
2257
|
+
if (row.type === "header") {
|
|
2258
|
+
return /* @__PURE__ */ jsxs11(Text13, { bold: true, color: "blue", children: [
|
|
2259
|
+
` ${truncate(row.label, 24)} `,
|
|
2260
|
+
/* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
|
|
2261
|
+
"(",
|
|
2262
|
+
row.taskCount,
|
|
2263
|
+
")"
|
|
2264
|
+
] })
|
|
2265
|
+
] }, `hdr-${actualIndex}`);
|
|
2266
|
+
}
|
|
2267
|
+
const isCursor = actualIndex === cursorRowIdx;
|
|
2268
|
+
const isWatched = row.executionId === watchingId;
|
|
2269
|
+
const hasApproval = pendingApproval?.taskId === row.executionId;
|
|
2270
|
+
const elapsed = elapsedLabel(row.startedAt);
|
|
2271
|
+
return /* @__PURE__ */ jsxs11(Box10, { children: [
|
|
2272
|
+
/* @__PURE__ */ jsxs11(
|
|
2273
|
+
Text13,
|
|
2274
|
+
{
|
|
2275
|
+
color: hasApproval ? "yellow" : isCursor ? "cyan" : isWatched ? "green" : statusColor(row.status),
|
|
2276
|
+
bold: isCursor,
|
|
2277
|
+
inverse: isCursor,
|
|
2278
|
+
wrap: "truncate",
|
|
2279
|
+
children: [
|
|
2280
|
+
isCursor ? " > " : isWatched ? " * " : " ",
|
|
2281
|
+
hasApproval ? "!" : statusSymbol(row.status),
|
|
2282
|
+
" ",
|
|
2283
|
+
truncate(row.label, 20)
|
|
2284
|
+
]
|
|
2285
|
+
}
|
|
2286
|
+
),
|
|
2287
|
+
elapsed && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
|
|
2288
|
+
" ",
|
|
2289
|
+
elapsed
|
|
2290
|
+
] })
|
|
2291
|
+
] }, row.executionId ?? `row-${actualIndex}`);
|
|
2292
|
+
}),
|
|
2293
|
+
rows.length > visibleHeight && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
|
|
2294
|
+
" [",
|
|
2295
|
+
start + 1,
|
|
2296
|
+
"-",
|
|
2297
|
+
Math.min(start + visibleHeight, rows.length),
|
|
2298
|
+
"/",
|
|
2299
|
+
rows.length,
|
|
2300
|
+
"]"
|
|
2301
|
+
] })
|
|
2302
|
+
] }) });
|
|
2303
|
+
}
|
|
2304
|
+
|
|
1669
2305
|
// src/tui/components/panels/detail-overlay.tsx
|
|
1670
2306
|
import "react";
|
|
1671
|
-
import { Box as
|
|
1672
|
-
import { jsx as
|
|
2307
|
+
import { Box as Box11, Text as Text14 } from "ink";
|
|
2308
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1673
2309
|
function DetailOverlay() {
|
|
1674
2310
|
const showDetail = useTuiStore((s) => s.showDetail);
|
|
1675
2311
|
const detailType = useTuiStore((s) => s.detailType);
|
|
1676
2312
|
const detailId = useTuiStore((s) => s.detailId);
|
|
1677
2313
|
if (!showDetail || !detailType || !detailId) return null;
|
|
1678
|
-
return /* @__PURE__ */
|
|
1679
|
-
|
|
2314
|
+
return /* @__PURE__ */ jsxs12(
|
|
2315
|
+
Box11,
|
|
1680
2316
|
{
|
|
1681
2317
|
flexDirection: "column",
|
|
1682
2318
|
borderStyle: "round",
|
|
@@ -1684,89 +2320,89 @@ function DetailOverlay() {
|
|
|
1684
2320
|
paddingX: 2,
|
|
1685
2321
|
paddingY: 1,
|
|
1686
2322
|
children: [
|
|
1687
|
-
detailType === "project" && /* @__PURE__ */
|
|
1688
|
-
detailType === "node" && /* @__PURE__ */
|
|
1689
|
-
detailType === "machine" && /* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
2323
|
+
detailType === "project" && /* @__PURE__ */ jsx13(ProjectDetail, { id: detailId }),
|
|
2324
|
+
detailType === "node" && /* @__PURE__ */ jsx13(NodeDetail, { id: detailId }),
|
|
2325
|
+
detailType === "machine" && /* @__PURE__ */ jsx13(MachineDetail, { id: detailId }),
|
|
2326
|
+
/* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "Press Esc to close" })
|
|
1691
2327
|
]
|
|
1692
2328
|
}
|
|
1693
2329
|
);
|
|
1694
2330
|
}
|
|
1695
2331
|
function ProjectDetail({ id }) {
|
|
1696
2332
|
const project = useProjectsStore((s) => s.projects.find((p) => p.id === id));
|
|
1697
|
-
if (!project) return /* @__PURE__ */
|
|
1698
|
-
return /* @__PURE__ */
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
1702
|
-
/* @__PURE__ */
|
|
1703
|
-
/* @__PURE__ */
|
|
1704
|
-
/* @__PURE__ */
|
|
1705
|
-
/* @__PURE__ */
|
|
1706
|
-
/* @__PURE__ */
|
|
1707
|
-
/* @__PURE__ */
|
|
1708
|
-
/* @__PURE__ */
|
|
1709
|
-
/* @__PURE__ */
|
|
1710
|
-
/* @__PURE__ */
|
|
1711
|
-
/* @__PURE__ */
|
|
1712
|
-
/* @__PURE__ */
|
|
1713
|
-
/* @__PURE__ */
|
|
2333
|
+
if (!project) return /* @__PURE__ */ jsx13(Text14, { color: "red", children: "Project not found" });
|
|
2334
|
+
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
|
|
2335
|
+
/* @__PURE__ */ jsx13(Text14, { bold: true, color: "cyan", children: project.name }),
|
|
2336
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " }),
|
|
2337
|
+
/* @__PURE__ */ jsx13(Field, { label: "ID", value: project.id }),
|
|
2338
|
+
/* @__PURE__ */ jsx13(Field, { label: "Status", value: project.status, color: getStatusColor(project.status) }),
|
|
2339
|
+
/* @__PURE__ */ jsx13(Field, { label: "Description", value: project.description || "\u2014" }),
|
|
2340
|
+
/* @__PURE__ */ jsx13(Field, { label: "Health", value: project.health ?? "\u2014" }),
|
|
2341
|
+
/* @__PURE__ */ jsx13(Field, { label: "Progress", value: `${project.progress}%` }),
|
|
2342
|
+
/* @__PURE__ */ jsx13(Field, { label: "Working Dir", value: project.workingDirectory ?? "\u2014" }),
|
|
2343
|
+
/* @__PURE__ */ jsx13(Field, { label: "Repository", value: project.repository ?? "\u2014" }),
|
|
2344
|
+
/* @__PURE__ */ jsx13(Field, { label: "Delivery", value: project.deliveryMode ?? "\u2014" }),
|
|
2345
|
+
/* @__PURE__ */ jsx13(Field, { label: "Start Date", value: project.startDate ?? "\u2014" }),
|
|
2346
|
+
/* @__PURE__ */ jsx13(Field, { label: "Target Date", value: project.targetDate ?? "\u2014" }),
|
|
2347
|
+
/* @__PURE__ */ jsx13(Field, { label: "Created", value: formatRelativeTime(project.createdAt) }),
|
|
2348
|
+
/* @__PURE__ */ jsx13(Field, { label: "Updated", value: formatRelativeTime(project.updatedAt) }),
|
|
2349
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " })
|
|
1714
2350
|
] });
|
|
1715
2351
|
}
|
|
1716
2352
|
function NodeDetail({ id }) {
|
|
1717
2353
|
const node = usePlanStore((s) => s.nodes.find((n) => n.id === id));
|
|
1718
|
-
if (!node) return /* @__PURE__ */
|
|
1719
|
-
return /* @__PURE__ */
|
|
1720
|
-
/* @__PURE__ */
|
|
1721
|
-
/* @__PURE__ */
|
|
1722
|
-
/* @__PURE__ */
|
|
1723
|
-
/* @__PURE__ */
|
|
1724
|
-
/* @__PURE__ */
|
|
1725
|
-
/* @__PURE__ */
|
|
1726
|
-
/* @__PURE__ */
|
|
1727
|
-
/* @__PURE__ */
|
|
1728
|
-
/* @__PURE__ */
|
|
1729
|
-
/* @__PURE__ */
|
|
1730
|
-
/* @__PURE__ */
|
|
1731
|
-
/* @__PURE__ */
|
|
1732
|
-
/* @__PURE__ */
|
|
1733
|
-
/* @__PURE__ */
|
|
1734
|
-
/* @__PURE__ */
|
|
1735
|
-
/* @__PURE__ */
|
|
1736
|
-
/* @__PURE__ */
|
|
2354
|
+
if (!node) return /* @__PURE__ */ jsx13(Text14, { color: "red", children: "Node not found" });
|
|
2355
|
+
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
|
|
2356
|
+
/* @__PURE__ */ jsx13(Text14, { bold: true, color: "cyan", children: node.title }),
|
|
2357
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " }),
|
|
2358
|
+
/* @__PURE__ */ jsx13(Field, { label: "ID", value: node.id }),
|
|
2359
|
+
/* @__PURE__ */ jsx13(Field, { label: "Type", value: node.type }),
|
|
2360
|
+
/* @__PURE__ */ jsx13(Field, { label: "Status", value: node.status, color: getStatusColor(node.status) }),
|
|
2361
|
+
/* @__PURE__ */ jsx13(Field, { label: "Description", value: node.description || "\u2014" }),
|
|
2362
|
+
/* @__PURE__ */ jsx13(Field, { label: "Priority", value: node.priority ?? "\u2014" }),
|
|
2363
|
+
/* @__PURE__ */ jsx13(Field, { label: "Estimate", value: node.estimate ?? "\u2014" }),
|
|
2364
|
+
/* @__PURE__ */ jsx13(Field, { label: "Start Date", value: node.startDate ?? "\u2014" }),
|
|
2365
|
+
/* @__PURE__ */ jsx13(Field, { label: "End Date", value: node.endDate ?? "\u2014" }),
|
|
2366
|
+
/* @__PURE__ */ jsx13(Field, { label: "Due Date", value: node.dueDate ?? "\u2014" }),
|
|
2367
|
+
/* @__PURE__ */ jsx13(Field, { label: "Branch", value: node.branchName ?? "\u2014" }),
|
|
2368
|
+
/* @__PURE__ */ jsx13(Field, { label: "PR URL", value: node.prUrl ?? "\u2014" }),
|
|
2369
|
+
/* @__PURE__ */ jsx13(Field, { label: "Execution ID", value: node.executionId ?? "\u2014" }),
|
|
2370
|
+
/* @__PURE__ */ jsx13(Field, { label: "Exec Started", value: node.executionStartedAt ? formatRelativeTime(node.executionStartedAt) : "\u2014" }),
|
|
2371
|
+
/* @__PURE__ */ jsx13(Field, { label: "Exec Completed", value: node.executionCompletedAt ? formatRelativeTime(node.executionCompletedAt) : "\u2014" }),
|
|
2372
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " })
|
|
1737
2373
|
] });
|
|
1738
2374
|
}
|
|
1739
2375
|
function MachineDetail({ id }) {
|
|
1740
2376
|
const machine = useMachinesStore((s) => s.machines.find((m) => m.id === id));
|
|
1741
|
-
if (!machine) return /* @__PURE__ */
|
|
1742
|
-
return /* @__PURE__ */
|
|
1743
|
-
/* @__PURE__ */
|
|
1744
|
-
/* @__PURE__ */
|
|
1745
|
-
/* @__PURE__ */
|
|
1746
|
-
/* @__PURE__ */
|
|
1747
|
-
/* @__PURE__ */
|
|
1748
|
-
/* @__PURE__ */
|
|
1749
|
-
/* @__PURE__ */
|
|
1750
|
-
/* @__PURE__ */
|
|
1751
|
-
/* @__PURE__ */
|
|
1752
|
-
/* @__PURE__ */
|
|
1753
|
-
/* @__PURE__ */
|
|
2377
|
+
if (!machine) return /* @__PURE__ */ jsx13(Text14, { color: "red", children: "Machine not found" });
|
|
2378
|
+
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", children: [
|
|
2379
|
+
/* @__PURE__ */ jsx13(Text14, { bold: true, color: "cyan", children: machine.name }),
|
|
2380
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " }),
|
|
2381
|
+
/* @__PURE__ */ jsx13(Field, { label: "ID", value: machine.id }),
|
|
2382
|
+
/* @__PURE__ */ jsx13(Field, { label: "Hostname", value: machine.hostname }),
|
|
2383
|
+
/* @__PURE__ */ jsx13(Field, { label: "Platform", value: machine.platform }),
|
|
2384
|
+
/* @__PURE__ */ jsx13(Field, { label: "Env Type", value: machine.environmentType }),
|
|
2385
|
+
/* @__PURE__ */ jsx13(Field, { label: "Connected", value: machine.isConnected ? "Yes" : "No", color: machine.isConnected ? "green" : "red" }),
|
|
2386
|
+
/* @__PURE__ */ jsx13(Field, { label: "Providers", value: machine.providers.join(", ") || "\u2014" }),
|
|
2387
|
+
/* @__PURE__ */ jsx13(Field, { label: "Registered", value: formatRelativeTime(machine.registeredAt) }),
|
|
2388
|
+
/* @__PURE__ */ jsx13(Field, { label: "Last Seen", value: formatRelativeTime(machine.lastSeenAt) }),
|
|
2389
|
+
/* @__PURE__ */ jsx13(Text14, { children: " " })
|
|
1754
2390
|
] });
|
|
1755
2391
|
}
|
|
1756
2392
|
function Field({ label, value, color }) {
|
|
1757
|
-
return /* @__PURE__ */
|
|
1758
|
-
/* @__PURE__ */
|
|
1759
|
-
color ? /* @__PURE__ */
|
|
2393
|
+
return /* @__PURE__ */ jsxs12(Box11, { children: [
|
|
2394
|
+
/* @__PURE__ */ jsx13(Text14, { dimColor: true, children: label.padEnd(16) }),
|
|
2395
|
+
color ? /* @__PURE__ */ jsx13(Text14, { color, children: value }) : /* @__PURE__ */ jsx13(Text14, { children: value })
|
|
1760
2396
|
] });
|
|
1761
2397
|
}
|
|
1762
2398
|
|
|
1763
2399
|
// src/tui/components/shared/approval-dialog.tsx
|
|
1764
|
-
import { useState as
|
|
1765
|
-
import { Box as
|
|
1766
|
-
import { jsx as
|
|
2400
|
+
import { useState as useState3 } from "react";
|
|
2401
|
+
import { Box as Box12, Text as Text15, useInput as useInput3 } from "ink";
|
|
2402
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1767
2403
|
function ApprovalDialog({ question, options, onSelect, onDismiss }) {
|
|
1768
|
-
const [selectedIndex, setSelectedIndex] =
|
|
1769
|
-
|
|
2404
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
2405
|
+
useInput3((input, key) => {
|
|
1770
2406
|
if (key.escape) {
|
|
1771
2407
|
onDismiss();
|
|
1772
2408
|
return;
|
|
@@ -1786,8 +2422,8 @@ function ApprovalDialog({ question, options, onSelect, onDismiss }) {
|
|
|
1786
2422
|
onSelect(num - 1);
|
|
1787
2423
|
}
|
|
1788
2424
|
});
|
|
1789
|
-
return /* @__PURE__ */
|
|
1790
|
-
|
|
2425
|
+
return /* @__PURE__ */ jsxs13(
|
|
2426
|
+
Box12,
|
|
1791
2427
|
{
|
|
1792
2428
|
flexDirection: "column",
|
|
1793
2429
|
borderStyle: "round",
|
|
@@ -1795,16 +2431,16 @@ function ApprovalDialog({ question, options, onSelect, onDismiss }) {
|
|
|
1795
2431
|
paddingX: 1,
|
|
1796
2432
|
paddingY: 0,
|
|
1797
2433
|
children: [
|
|
1798
|
-
/* @__PURE__ */
|
|
1799
|
-
/* @__PURE__ */
|
|
1800
|
-
/* @__PURE__ */
|
|
2434
|
+
/* @__PURE__ */ jsx14(Text15, { color: "yellow", bold: true, children: "Approval Required" }),
|
|
2435
|
+
/* @__PURE__ */ jsx14(Text15, { wrap: "wrap", children: question }),
|
|
2436
|
+
/* @__PURE__ */ jsx14(Box12, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsx14(Box12, { children: /* @__PURE__ */ jsxs13(Text15, { color: i === selectedIndex ? "cyan" : "white", children: [
|
|
1801
2437
|
i === selectedIndex ? "\u25B8 " : " ",
|
|
1802
2438
|
"[",
|
|
1803
2439
|
i + 1,
|
|
1804
2440
|
"] ",
|
|
1805
2441
|
opt
|
|
1806
2442
|
] }) }, i)) }),
|
|
1807
|
-
/* @__PURE__ */
|
|
2443
|
+
/* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
|
|
1808
2444
|
"\u2191\u2193/jk navigate \u2022 Enter select \u2022 1-",
|
|
1809
2445
|
options.length,
|
|
1810
2446
|
" quick select \u2022 Esc dismiss"
|
|
@@ -1815,7 +2451,7 @@ function ApprovalDialog({ question, options, onSelect, onDismiss }) {
|
|
|
1815
2451
|
}
|
|
1816
2452
|
|
|
1817
2453
|
// src/tui/components/layout/main-layout.tsx
|
|
1818
|
-
import { Fragment
|
|
2454
|
+
import { Fragment, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1819
2455
|
function MainLayout({ onSessionMessage }) {
|
|
1820
2456
|
const showHelp = useTuiStore((s) => s.showHelp);
|
|
1821
2457
|
const showDetail = useTuiStore((s) => s.showDetail);
|
|
@@ -1831,20 +2467,20 @@ function MainLayout({ onSessionMessage }) {
|
|
|
1831
2467
|
const bottomPanelHeight = panelOpen ? Math.floor(termHeight / 2) : 1;
|
|
1832
2468
|
const contentHeight = termHeight - 2 - bottomPanelHeight;
|
|
1833
2469
|
if (showHelp) {
|
|
1834
|
-
return /* @__PURE__ */
|
|
1835
|
-
/* @__PURE__ */
|
|
1836
|
-
/* @__PURE__ */
|
|
1837
|
-
/* @__PURE__ */
|
|
2470
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", width: termWidth, height: termHeight, children: [
|
|
2471
|
+
/* @__PURE__ */ jsx15(StatusBar, {}),
|
|
2472
|
+
/* @__PURE__ */ jsx15(HelpOverlay, {}),
|
|
2473
|
+
/* @__PURE__ */ jsx15(CommandLine, { height: 1 })
|
|
1838
2474
|
] });
|
|
1839
2475
|
}
|
|
1840
2476
|
if (showDetail) {
|
|
1841
|
-
return /* @__PURE__ */
|
|
1842
|
-
/* @__PURE__ */
|
|
1843
|
-
/* @__PURE__ */
|
|
1844
|
-
/* @__PURE__ */
|
|
2477
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", width: termWidth, height: termHeight, children: [
|
|
2478
|
+
/* @__PURE__ */ jsx15(StatusBar, {}),
|
|
2479
|
+
/* @__PURE__ */ jsx15(DetailOverlay, {}),
|
|
2480
|
+
/* @__PURE__ */ jsx15(CommandLine, { height: 1 })
|
|
1845
2481
|
] });
|
|
1846
2482
|
}
|
|
1847
|
-
const approvalOverlay = pendingApproval ? /* @__PURE__ */
|
|
2483
|
+
const approvalOverlay = pendingApproval ? /* @__PURE__ */ jsx15(Box13, { position: "absolute", marginTop: 4, marginLeft: Math.floor(termWidth / 4), children: /* @__PURE__ */ jsx15(
|
|
1848
2484
|
ApprovalDialog,
|
|
1849
2485
|
{
|
|
1850
2486
|
question: pendingApproval.question,
|
|
@@ -1860,42 +2496,45 @@ function MainLayout({ onSessionMessage }) {
|
|
|
1860
2496
|
) }) : null;
|
|
1861
2497
|
let content;
|
|
1862
2498
|
if (activeView === "plan-gen") {
|
|
1863
|
-
content = /* @__PURE__ */
|
|
2499
|
+
content = /* @__PURE__ */ jsx15(Box13, { flexDirection: "row", height: contentHeight, children: /* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: /* @__PURE__ */ jsx15(SessionPanel, { height: contentHeight, title: "PLAN GENERATION", sessionType: "plan-generate", onSubmit: onSessionMessage }) }) });
|
|
1864
2500
|
} else if (activeView === "projects") {
|
|
1865
|
-
content = /* @__PURE__ */
|
|
1866
|
-
/* @__PURE__ */
|
|
1867
|
-
/* @__PURE__ */
|
|
2501
|
+
content = /* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", height: contentHeight, children: [
|
|
2502
|
+
/* @__PURE__ */ jsx15(Box13, { width: "40%", children: /* @__PURE__ */ jsx15(ProjectsPanel, { height: contentHeight }) }),
|
|
2503
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: /* @__PURE__ */ jsx15(PlanPanel, { height: contentHeight }) })
|
|
1868
2504
|
] });
|
|
1869
2505
|
} else if (activeView === "playground") {
|
|
1870
|
-
content = /* @__PURE__ */
|
|
1871
|
-
} else if (activeView === "
|
|
1872
|
-
content = /* @__PURE__ */
|
|
2506
|
+
content = /* @__PURE__ */ jsx15(Box13, { flexDirection: "row", height: contentHeight, children: /* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: /* @__PURE__ */ jsx15(SessionPanel, { height: contentHeight, title: "PLAYGROUND", sessionType: "playground", onSubmit: onSessionMessage }) }) });
|
|
2507
|
+
} else if (activeView === "active") {
|
|
2508
|
+
content = /* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", height: contentHeight, children: [
|
|
2509
|
+
/* @__PURE__ */ jsx15(Box13, { width: "25%", children: /* @__PURE__ */ jsx15(ActiveListPanel, { height: contentHeight }) }),
|
|
2510
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: /* @__PURE__ */ jsx15(OutputPanel, { height: contentHeight }) })
|
|
2511
|
+
] });
|
|
1873
2512
|
} else {
|
|
1874
2513
|
const topRowHeight = Math.floor(contentHeight / 2);
|
|
1875
2514
|
const bottomRowHeight = contentHeight - topRowHeight;
|
|
1876
|
-
content = /* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
1879
|
-
/* @__PURE__ */
|
|
2515
|
+
content = /* @__PURE__ */ jsxs14(Fragment, { children: [
|
|
2516
|
+
/* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", height: topRowHeight, children: [
|
|
2517
|
+
/* @__PURE__ */ jsx15(Box13, { width: "30%", children: /* @__PURE__ */ jsx15(ProjectsPanel, { height: topRowHeight }) }),
|
|
2518
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: /* @__PURE__ */ jsx15(PlanPanel, { height: topRowHeight }) })
|
|
1880
2519
|
] }),
|
|
1881
|
-
/* @__PURE__ */
|
|
1882
|
-
/* @__PURE__ */
|
|
1883
|
-
/* @__PURE__ */
|
|
2520
|
+
/* @__PURE__ */ jsxs14(Box13, { flexDirection: "row", height: bottomRowHeight, children: [
|
|
2521
|
+
/* @__PURE__ */ jsx15(Box13, { width: "30%", children: /* @__PURE__ */ jsx15(MachinesPanel, { height: bottomRowHeight }) }),
|
|
2522
|
+
/* @__PURE__ */ jsx15(Box13, { flexGrow: 1, children: showChat ? /* @__PURE__ */ jsx15(ChatPanel, { height: bottomRowHeight }) : /* @__PURE__ */ jsx15(OutputPanel, { height: bottomRowHeight }) })
|
|
1884
2523
|
] })
|
|
1885
2524
|
] });
|
|
1886
2525
|
}
|
|
1887
|
-
return /* @__PURE__ */
|
|
1888
|
-
/* @__PURE__ */
|
|
2526
|
+
return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", width: termWidth, height: termHeight, children: [
|
|
2527
|
+
/* @__PURE__ */ jsx15(StatusBar, {}),
|
|
1889
2528
|
content,
|
|
1890
|
-
/* @__PURE__ */
|
|
2529
|
+
/* @__PURE__ */ jsx15(SearchOverlay, {}),
|
|
1891
2530
|
approvalOverlay,
|
|
1892
|
-
/* @__PURE__ */
|
|
2531
|
+
/* @__PURE__ */ jsx15(CommandLine, { height: bottomPanelHeight })
|
|
1893
2532
|
] });
|
|
1894
2533
|
}
|
|
1895
2534
|
|
|
1896
2535
|
// src/tui/hooks/use-vim-mode.ts
|
|
1897
2536
|
import { useCallback, useRef } from "react";
|
|
1898
|
-
import { useInput as
|
|
2537
|
+
import { useInput as useInput4, useApp } from "ink";
|
|
1899
2538
|
|
|
1900
2539
|
// src/tui/lib/vim-state-machine.ts
|
|
1901
2540
|
function initialVimState() {
|
|
@@ -1940,7 +2579,7 @@ function handleKey(state, key, ctrl, meta) {
|
|
|
1940
2579
|
case "search":
|
|
1941
2580
|
return handleSearchMode(state, key);
|
|
1942
2581
|
case "input":
|
|
1943
|
-
return handleInputMode(state, key);
|
|
2582
|
+
return handleInputMode(state, key, ctrl);
|
|
1944
2583
|
}
|
|
1945
2584
|
}
|
|
1946
2585
|
function handleNormalMode(state, key, ctrl, meta) {
|
|
@@ -1950,6 +2589,8 @@ function handleNormalMode(state, key, ctrl, meta) {
|
|
|
1950
2589
|
return [{ ...state, mode: "palette", commandBuffer: "" }, { type: "palette" }];
|
|
1951
2590
|
case "f":
|
|
1952
2591
|
return [state, { type: "search" }];
|
|
2592
|
+
case "d":
|
|
2593
|
+
return [state, { type: "cancel" }];
|
|
1953
2594
|
case "c":
|
|
1954
2595
|
return [state, { type: "quit" }];
|
|
1955
2596
|
case "r":
|
|
@@ -2012,10 +2653,12 @@ function handleNormalMode(state, key, ctrl, meta) {
|
|
|
2012
2653
|
case "4":
|
|
2013
2654
|
return [state, { type: "view", value: "playground" }];
|
|
2014
2655
|
case "5":
|
|
2015
|
-
return [state, { type: "view", value: "
|
|
2656
|
+
return [state, { type: "view", value: "active" }];
|
|
2016
2657
|
// Function-key style shortcuts (single letter, no prefix needed)
|
|
2017
2658
|
case "d":
|
|
2018
2659
|
return [state, { type: "dispatch" }];
|
|
2660
|
+
case "x":
|
|
2661
|
+
return [state, { type: "cancel" }];
|
|
2019
2662
|
case "q":
|
|
2020
2663
|
return [state, { type: "quit" }];
|
|
2021
2664
|
case "?":
|
|
@@ -2077,7 +2720,10 @@ function handleSearchMode(state, key) {
|
|
|
2077
2720
|
}
|
|
2078
2721
|
return [state, { type: "none" }];
|
|
2079
2722
|
}
|
|
2080
|
-
function handleInputMode(state,
|
|
2723
|
+
function handleInputMode(state, key, ctrl) {
|
|
2724
|
+
if (ctrl && key === "d") {
|
|
2725
|
+
return [{ ...state, mode: "normal" }, { type: "cancel" }];
|
|
2726
|
+
}
|
|
2081
2727
|
return [state, { type: "none" }];
|
|
2082
2728
|
}
|
|
2083
2729
|
|
|
@@ -2120,6 +2766,13 @@ function useVimMode(callbacks = {}) {
|
|
|
2120
2766
|
store.pageDown();
|
|
2121
2767
|
break;
|
|
2122
2768
|
}
|
|
2769
|
+
if (store.focusedPanel === "projects") {
|
|
2770
|
+
const sorted = getVisibleProjects(useProjectsStore.getState().projects);
|
|
2771
|
+
const idx = useTuiStore.getState().scrollIndex.projects;
|
|
2772
|
+
if (sorted[idx]) {
|
|
2773
|
+
useTuiStore.getState().setSelectedProject(sorted[idx].id);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2123
2776
|
break;
|
|
2124
2777
|
case "focus":
|
|
2125
2778
|
if (effect.panel != null) {
|
|
@@ -2132,7 +2785,7 @@ function useVimMode(callbacks = {}) {
|
|
|
2132
2785
|
break;
|
|
2133
2786
|
case "select": {
|
|
2134
2787
|
const view = store.activeView;
|
|
2135
|
-
if (view === "playground" || view === "plan-gen" || view === "
|
|
2788
|
+
if (view === "playground" || view === "plan-gen" || view === "active") {
|
|
2136
2789
|
vimState.current = { ...vimState.current, mode: "input" };
|
|
2137
2790
|
store.setMode("input");
|
|
2138
2791
|
} else {
|
|
@@ -2147,7 +2800,8 @@ function useVimMode(callbacks = {}) {
|
|
|
2147
2800
|
const filtered = getFilteredPaletteCommands(vimState.current.commandBuffer);
|
|
2148
2801
|
const selected = filtered[store.paletteIndex];
|
|
2149
2802
|
if (selected) {
|
|
2150
|
-
|
|
2803
|
+
const cmd = selected.usage?.startsWith("resume:") ? selected.usage : selected.name;
|
|
2804
|
+
callbacks.onCommand?.(cmd);
|
|
2151
2805
|
}
|
|
2152
2806
|
} else if (effect.value?.startsWith("__autocomplete__")) {
|
|
2153
2807
|
} else if (effect.value) {
|
|
@@ -2177,7 +2831,7 @@ function useVimMode(callbacks = {}) {
|
|
|
2177
2831
|
store.toggleChat();
|
|
2178
2832
|
break;
|
|
2179
2833
|
case "view":
|
|
2180
|
-
if (effect.value === "dashboard" || effect.value === "plan-gen" || effect.value === "projects" || effect.value === "playground" || effect.value === "
|
|
2834
|
+
if (effect.value === "dashboard" || effect.value === "plan-gen" || effect.value === "projects" || effect.value === "playground" || effect.value === "active") {
|
|
2181
2835
|
store.setActiveView(effect.value);
|
|
2182
2836
|
}
|
|
2183
2837
|
break;
|
|
@@ -2187,7 +2841,7 @@ function useVimMode(callbacks = {}) {
|
|
|
2187
2841
|
},
|
|
2188
2842
|
[store, callbacks, exit]
|
|
2189
2843
|
);
|
|
2190
|
-
|
|
2844
|
+
useInput4((input, key) => {
|
|
2191
2845
|
const searchOpen = useSearchStore.getState().isOpen;
|
|
2192
2846
|
if (store.showHelp || store.showSearch || store.showDetail || searchOpen) {
|
|
2193
2847
|
if (key.escape) {
|
|
@@ -2201,6 +2855,61 @@ function useVimMode(callbacks = {}) {
|
|
|
2201
2855
|
}
|
|
2202
2856
|
return;
|
|
2203
2857
|
}
|
|
2858
|
+
const isSessionView = store.activeView === "playground" || store.activeView === "plan-gen";
|
|
2859
|
+
if (isSessionView && vimState.current.mode === "input") {
|
|
2860
|
+
const settings = useSessionSettingsStore.getState();
|
|
2861
|
+
if (key.escape) {
|
|
2862
|
+
if (settings.pickerOpen) {
|
|
2863
|
+
settings.setPickerOpen(false);
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
if (settings.focusedField) {
|
|
2867
|
+
settings.setFocusedField(null);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
if (settings.pickerOpen && settings.focusedField === "machine") {
|
|
2872
|
+
const machines = useMachinesStore.getState().machines.filter((m) => m.isConnected);
|
|
2873
|
+
const currentIdx = machines.findIndex((m) => m.id === settings.machineId);
|
|
2874
|
+
if (key.upArrow) {
|
|
2875
|
+
const newIdx = Math.max(0, currentIdx - 1);
|
|
2876
|
+
if (machines[newIdx]) settings.setMachine(machines[newIdx].id, machines[newIdx].name);
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (key.downArrow) {
|
|
2880
|
+
const newIdx = Math.min(machines.length - 1, currentIdx + 1);
|
|
2881
|
+
if (machines[newIdx]) settings.setMachine(machines[newIdx].id, machines[newIdx].name);
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
if (key.return) {
|
|
2885
|
+
settings.setPickerOpen(false);
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
if (settings.pickerOpen && settings.focusedField === "workdir") {
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
if (settings.focusedField) {
|
|
2894
|
+
if (key.return) {
|
|
2895
|
+
settings.setPickerOpen(true);
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
if (key.leftArrow || key.rightArrow) {
|
|
2899
|
+
settings.setFocusedField(settings.focusedField === "machine" ? "workdir" : "machine");
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
if (key.downArrow) {
|
|
2903
|
+
settings.setFocusedField(null);
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
if (key.upArrow) return;
|
|
2907
|
+
}
|
|
2908
|
+
if (!settings.focusedField && key.upArrow) {
|
|
2909
|
+
settings.setFocusedField("machine");
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2204
2913
|
let keyStr = input;
|
|
2205
2914
|
if (key.escape) keyStr = "escape";
|
|
2206
2915
|
else if (key.return) keyStr = "return";
|
|
@@ -2231,8 +2940,26 @@ function useVimMode(callbacks = {}) {
|
|
|
2231
2940
|
}
|
|
2232
2941
|
|
|
2233
2942
|
// src/tui/hooks/use-polling.ts
|
|
2234
|
-
import { useEffect as
|
|
2235
|
-
function
|
|
2943
|
+
import { useEffect as useEffect4, useCallback as useCallback2 } from "react";
|
|
2944
|
+
function deriveTitle(nodeId, exec, projects, planNodes) {
|
|
2945
|
+
const projectName = projects.find((p) => p.id === exec.projectId)?.name;
|
|
2946
|
+
if (nodeId.startsWith("playground-")) {
|
|
2947
|
+
const firstLine = exec.streamText?.split("\n").find((l) => l.trim().length > 0)?.trim();
|
|
2948
|
+
if (firstLine && firstLine.length > 5) {
|
|
2949
|
+
return `Playground: ${firstLine.slice(0, 50)}`;
|
|
2950
|
+
}
|
|
2951
|
+
return `Playground${projectName ? ` \u2014 ${projectName}` : ""}`;
|
|
2952
|
+
}
|
|
2953
|
+
if (nodeId.startsWith("plan-")) {
|
|
2954
|
+
return `Plan${projectName ? ` \u2014 ${projectName}` : ""}`;
|
|
2955
|
+
}
|
|
2956
|
+
const planNode = planNodes.find((n) => n.id === nodeId);
|
|
2957
|
+
if (planNode) {
|
|
2958
|
+
return planNode.title;
|
|
2959
|
+
}
|
|
2960
|
+
return projectName ? `Task \u2014 ${projectName}` : nodeId.slice(0, 30);
|
|
2961
|
+
}
|
|
2962
|
+
function usePolling(client, intervalMs = 1e4) {
|
|
2236
2963
|
const selectedProjectId = useTuiStore((s) => s.selectedProjectId);
|
|
2237
2964
|
const loadProjects = useCallback2(async () => {
|
|
2238
2965
|
useProjectsStore.getState().setLoading(true);
|
|
@@ -2252,6 +2979,13 @@ function usePolling(client, intervalMs = 3e4) {
|
|
|
2252
2979
|
usePlanStore.getState().setError(err instanceof Error ? err.message : String(err));
|
|
2253
2980
|
}
|
|
2254
2981
|
}, [client]);
|
|
2982
|
+
const loadAllPlans = useCallback2(async () => {
|
|
2983
|
+
try {
|
|
2984
|
+
const { nodes, edges } = await client.getFullPlan();
|
|
2985
|
+
usePlanStore.getState().setAllPlans(nodes, edges);
|
|
2986
|
+
} catch {
|
|
2987
|
+
}
|
|
2988
|
+
}, [client]);
|
|
2255
2989
|
const loadMachines = useCallback2(async () => {
|
|
2256
2990
|
useMachinesStore.getState().setLoading(true);
|
|
2257
2991
|
try {
|
|
@@ -2262,6 +2996,25 @@ function usePolling(client, intervalMs = 3e4) {
|
|
|
2262
2996
|
useMachinesStore.getState().setError(err instanceof Error ? err.message : String(err));
|
|
2263
2997
|
}
|
|
2264
2998
|
}, [client]);
|
|
2999
|
+
const loadExecutions = useCallback2(async () => {
|
|
3000
|
+
try {
|
|
3001
|
+
const execMap = await client.getExecutions();
|
|
3002
|
+
const projects = useProjectsStore.getState().projects;
|
|
3003
|
+
const planNodes = usePlanStore.getState().nodes;
|
|
3004
|
+
const entries = Object.values(execMap).map((e) => {
|
|
3005
|
+
const nodeId = e.nodeClientId ?? e.nodeId ?? e.executionId;
|
|
3006
|
+
return {
|
|
3007
|
+
executionId: e.executionId,
|
|
3008
|
+
nodeId,
|
|
3009
|
+
title: deriveTitle(nodeId, e, projects, planNodes),
|
|
3010
|
+
status: e.status,
|
|
3011
|
+
startedAt: e.startedAt
|
|
3012
|
+
};
|
|
3013
|
+
});
|
|
3014
|
+
useExecutionStore.getState().seedHistorical(entries);
|
|
3015
|
+
} catch {
|
|
3016
|
+
}
|
|
3017
|
+
}, [client]);
|
|
2265
3018
|
const loadUsage = useCallback2(async () => {
|
|
2266
3019
|
try {
|
|
2267
3020
|
const history = await client.getUsageHistory(1);
|
|
@@ -2275,29 +3028,30 @@ function usePolling(client, intervalMs = 3e4) {
|
|
|
2275
3028
|
await Promise.allSettled([
|
|
2276
3029
|
loadProjects(),
|
|
2277
3030
|
loadMachines(),
|
|
3031
|
+
loadExecutions(),
|
|
2278
3032
|
loadUsage(),
|
|
2279
|
-
|
|
3033
|
+
loadAllPlans()
|
|
2280
3034
|
]);
|
|
2281
|
-
}, [loadProjects, loadMachines,
|
|
2282
|
-
|
|
3035
|
+
}, [loadProjects, loadMachines, loadExecutions, loadUsage, loadAllPlans]);
|
|
3036
|
+
useEffect4(() => {
|
|
2283
3037
|
refreshAll2();
|
|
2284
3038
|
}, [refreshAll2]);
|
|
2285
|
-
|
|
3039
|
+
useEffect4(() => {
|
|
2286
3040
|
if (selectedProjectId) {
|
|
2287
|
-
|
|
3041
|
+
usePlanStore.getState().selectProject(selectedProjectId);
|
|
2288
3042
|
} else {
|
|
2289
3043
|
usePlanStore.getState().clear();
|
|
2290
3044
|
}
|
|
2291
|
-
}, [selectedProjectId
|
|
2292
|
-
|
|
3045
|
+
}, [selectedProjectId]);
|
|
3046
|
+
useEffect4(() => {
|
|
2293
3047
|
const timer = setInterval(refreshAll2, intervalMs);
|
|
2294
3048
|
return () => clearInterval(timer);
|
|
2295
3049
|
}, [refreshAll2, intervalMs]);
|
|
2296
|
-
return { refreshAll: refreshAll2, loadProjects, loadPlan, loadMachines };
|
|
3050
|
+
return { refreshAll: refreshAll2, loadProjects, loadPlan, loadAllPlans, loadMachines };
|
|
2297
3051
|
}
|
|
2298
3052
|
|
|
2299
3053
|
// src/tui/hooks/use-sse-stream.ts
|
|
2300
|
-
import { useEffect as
|
|
3054
|
+
import { useEffect as useEffect5, useRef as useRef2 } from "react";
|
|
2301
3055
|
|
|
2302
3056
|
// src/tui/sse-client.ts
|
|
2303
3057
|
var SSEClient = class {
|
|
@@ -2382,17 +3136,18 @@ var SSEClient = class {
|
|
|
2382
3136
|
};
|
|
2383
3137
|
|
|
2384
3138
|
// src/tui/hooks/use-sse-stream.ts
|
|
2385
|
-
function useSSEStream(client) {
|
|
3139
|
+
function useSSEStream(client, onReconnect) {
|
|
2386
3140
|
const sseRef = useRef2(null);
|
|
2387
3141
|
const setConnected = useTuiStore((s) => s.setConnected);
|
|
2388
3142
|
const setMachineCount = useTuiStore((s) => s.setMachineCount);
|
|
2389
3143
|
const setLastError = useTuiStore((s) => s.setLastError);
|
|
2390
|
-
|
|
3144
|
+
useEffect5(() => {
|
|
2391
3145
|
const handler = (event) => {
|
|
2392
3146
|
switch (event.type) {
|
|
2393
3147
|
case "__connected":
|
|
2394
3148
|
setConnected(true);
|
|
2395
3149
|
setLastError(null);
|
|
3150
|
+
onReconnect?.();
|
|
2396
3151
|
break;
|
|
2397
3152
|
case "__disconnected":
|
|
2398
3153
|
setConnected(false);
|
|
@@ -2465,7 +3220,8 @@ function useSSEStream(client) {
|
|
|
2465
3220
|
case "task:session_init": {
|
|
2466
3221
|
const taskId = event.data.taskId;
|
|
2467
3222
|
const nodeId = event.data.nodeId ?? taskId;
|
|
2468
|
-
|
|
3223
|
+
const title = event.data.title ?? nodeId;
|
|
3224
|
+
useExecutionStore.getState().initExecution(taskId, nodeId, title);
|
|
2469
3225
|
useExecutionStore.getState().setWatching(taskId);
|
|
2470
3226
|
break;
|
|
2471
3227
|
}
|
|
@@ -2509,7 +3265,7 @@ function useSSEStream(client) {
|
|
|
2509
3265
|
}
|
|
2510
3266
|
|
|
2511
3267
|
// src/tui/hooks/use-fuzzy-search.ts
|
|
2512
|
-
import { useEffect as
|
|
3268
|
+
import { useEffect as useEffect6, useCallback as useCallback3 } from "react";
|
|
2513
3269
|
import Fuse from "fuse.js";
|
|
2514
3270
|
var fuseInstance = null;
|
|
2515
3271
|
function useFuzzySearch() {
|
|
@@ -2517,7 +3273,7 @@ function useFuzzySearch() {
|
|
|
2517
3273
|
const nodes = usePlanStore((s) => s.nodes);
|
|
2518
3274
|
const machines = useMachinesStore((s) => s.machines);
|
|
2519
3275
|
const { setItems, setResults, query } = useSearchStore();
|
|
2520
|
-
|
|
3276
|
+
useEffect6(() => {
|
|
2521
3277
|
const items = [
|
|
2522
3278
|
...projects.map((p) => ({
|
|
2523
3279
|
type: "project",
|
|
@@ -2556,7 +3312,7 @@ function useFuzzySearch() {
|
|
|
2556
3312
|
const results = fuseInstance.search(q, { limit: 20 });
|
|
2557
3313
|
setResults(results.map((r) => r.item));
|
|
2558
3314
|
}, [setResults]);
|
|
2559
|
-
|
|
3315
|
+
useEffect6(() => {
|
|
2560
3316
|
search(query);
|
|
2561
3317
|
}, [query, search]);
|
|
2562
3318
|
return { search };
|
|
@@ -2606,6 +3362,19 @@ for (const key of Object.keys(handlers)) {
|
|
|
2606
3362
|
trie.insert(key);
|
|
2607
3363
|
}
|
|
2608
3364
|
async function executeCommand(input, client) {
|
|
3365
|
+
const colonIdx = input.indexOf(":");
|
|
3366
|
+
if (colonIdx > 0) {
|
|
3367
|
+
const prefix = input.slice(0, colonIdx);
|
|
3368
|
+
const value = input.slice(colonIdx + 1);
|
|
3369
|
+
if (handlers[prefix]) {
|
|
3370
|
+
try {
|
|
3371
|
+
await handlers[prefix]([value], client);
|
|
3372
|
+
} catch (err) {
|
|
3373
|
+
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
3374
|
+
}
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
2609
3378
|
const parts = input.split(/\s+/);
|
|
2610
3379
|
if (parts.length >= 2) {
|
|
2611
3380
|
const twoWord = `${parts[0]} ${parts[1]}`;
|
|
@@ -2653,18 +3422,28 @@ function useCommandParser(client) {
|
|
|
2653
3422
|
}
|
|
2654
3423
|
|
|
2655
3424
|
// src/tui/app.tsx
|
|
2656
|
-
import { jsx as
|
|
3425
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2657
3426
|
function App({ serverUrl }) {
|
|
2658
|
-
const client =
|
|
3427
|
+
const client = useMemo2(() => new AstroClient({ serverUrl }), [serverUrl]);
|
|
2659
3428
|
const { refreshAll: refreshAll2 } = usePolling(client);
|
|
2660
|
-
useSSEStream(client);
|
|
3429
|
+
useSSEStream(client, refreshAll2);
|
|
2661
3430
|
useFuzzySearch();
|
|
3431
|
+
const machines = useMachinesStore((s) => s.machines);
|
|
3432
|
+
useEffect7(() => {
|
|
3433
|
+
const settings = useSessionSettingsStore.getState();
|
|
3434
|
+
if (settings.machineId) return;
|
|
3435
|
+
const localPlatform = process.platform;
|
|
3436
|
+
const m = machines.find((m2) => m2.isConnected && m2.platform === localPlatform) ?? machines.find((m2) => m2.isConnected);
|
|
3437
|
+
if (m) {
|
|
3438
|
+
settings.init(m.id, m.name, process.cwd());
|
|
3439
|
+
}
|
|
3440
|
+
}, [machines]);
|
|
2662
3441
|
const { execute } = useCommandParser(client);
|
|
2663
3442
|
const onSelect = useCallback5(() => {
|
|
2664
3443
|
const { focusedPanel, scrollIndex } = useTuiStore.getState();
|
|
2665
3444
|
switch (focusedPanel) {
|
|
2666
3445
|
case "projects": {
|
|
2667
|
-
const projects = useProjectsStore.getState().projects;
|
|
3446
|
+
const projects = getVisibleProjects(useProjectsStore.getState().projects);
|
|
2668
3447
|
const idx = scrollIndex.projects;
|
|
2669
3448
|
if (projects[idx]) {
|
|
2670
3449
|
useTuiStore.getState().setSelectedProject(projects[idx].id);
|
|
@@ -2725,70 +3504,32 @@ function App({ serverUrl }) {
|
|
|
2725
3504
|
refreshAll2();
|
|
2726
3505
|
}, [refreshAll2]);
|
|
2727
3506
|
const onSessionMessage = useCallback5(async (message) => {
|
|
2728
|
-
const { selectedProjectId, activeView } = useTuiStore.getState();
|
|
2729
|
-
if (!selectedProjectId) {
|
|
2730
|
-
useTuiStore.getState().setLastError("No project selected");
|
|
2731
|
-
return;
|
|
2732
|
-
}
|
|
3507
|
+
const { selectedProjectId, activeView, selectedNodeId } = useTuiStore.getState();
|
|
2733
3508
|
const watchingId = useExecutionStore.getState().watchingId;
|
|
2734
|
-
const sessionId = useChatStore.getState().sessionId;
|
|
2735
|
-
const messages = useChatStore.getState().messages.map((m) => ({
|
|
2736
|
-
role: m.role,
|
|
2737
|
-
content: m.content
|
|
2738
|
-
}));
|
|
2739
3509
|
if (!watchingId) {
|
|
2740
|
-
if (activeView === "
|
|
3510
|
+
if (activeView === "playground") {
|
|
3511
|
+
await execute(`playground ${message}`);
|
|
3512
|
+
} else if (activeView === "plan-gen") {
|
|
3513
|
+
if (!selectedProjectId) {
|
|
3514
|
+
useTuiStore.getState().setLastError("No project selected for plan generation");
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
2741
3517
|
await execute(`plan generate ${message}`);
|
|
2742
3518
|
} else {
|
|
2743
3519
|
await execute(`playground ${message}`);
|
|
2744
3520
|
}
|
|
2745
3521
|
return;
|
|
2746
3522
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
});
|
|
2755
|
-
if (!response.body) {
|
|
2756
|
-
useChatStore.getState().setStreaming(false);
|
|
2757
|
-
return;
|
|
2758
|
-
}
|
|
2759
|
-
const reader = response.body.getReader();
|
|
2760
|
-
const decoder = new TextDecoder();
|
|
2761
|
-
let buffer = "";
|
|
2762
|
-
while (true) {
|
|
2763
|
-
const { done, value } = await reader.read();
|
|
2764
|
-
if (done) break;
|
|
2765
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2766
|
-
const lines = buffer.split("\n");
|
|
2767
|
-
buffer = lines.pop() ?? "";
|
|
2768
|
-
for (const line of lines) {
|
|
2769
|
-
if (!line.startsWith("data: ")) continue;
|
|
2770
|
-
const data = line.slice(6);
|
|
2771
|
-
if (data === "[DONE]") continue;
|
|
2772
|
-
try {
|
|
2773
|
-
const event = JSON.parse(data);
|
|
2774
|
-
if (event.type === "text" && event.text) {
|
|
2775
|
-
useChatStore.getState().appendStream(event.text);
|
|
2776
|
-
useExecutionStore.getState().appendText(watchingId, event.text);
|
|
2777
|
-
} else if (event.type === "session_init" && event.sessionId) {
|
|
2778
|
-
useChatStore.getState().setSessionId(event.sessionId);
|
|
2779
|
-
} else if (event.type === "done") {
|
|
2780
|
-
}
|
|
2781
|
-
} catch {
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
useChatStore.getState().flushStream();
|
|
2786
|
-
useChatStore.getState().setStreaming(false);
|
|
2787
|
-
} catch (err) {
|
|
2788
|
-
useChatStore.getState().setStreaming(false);
|
|
2789
|
-
useTuiStore.getState().setLastError(err instanceof Error ? err.message : String(err));
|
|
3523
|
+
if (!selectedProjectId) {
|
|
3524
|
+
useTuiStore.getState().setLastError("No project selected");
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
if (selectedNodeId) {
|
|
3528
|
+
await execute(`task chat ${message}`);
|
|
3529
|
+
} else {
|
|
3530
|
+
await execute(`project chat ${message}`);
|
|
2790
3531
|
}
|
|
2791
|
-
}, [
|
|
3532
|
+
}, [execute]);
|
|
2792
3533
|
useVimMode({
|
|
2793
3534
|
onSelect,
|
|
2794
3535
|
onCommand,
|
|
@@ -2797,13 +3538,13 @@ function App({ serverUrl }) {
|
|
|
2797
3538
|
onCancel,
|
|
2798
3539
|
onRefresh
|
|
2799
3540
|
});
|
|
2800
|
-
return /* @__PURE__ */
|
|
3541
|
+
return /* @__PURE__ */ jsx16(MainLayout, { onSessionMessage });
|
|
2801
3542
|
}
|
|
2802
3543
|
|
|
2803
3544
|
// src/tui/index.tsx
|
|
2804
|
-
import { jsx as
|
|
3545
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2805
3546
|
async function launchTui(serverUrl) {
|
|
2806
|
-
render(/* @__PURE__ */
|
|
3547
|
+
render(/* @__PURE__ */ jsx17(App, { serverUrl }), {
|
|
2807
3548
|
exitOnCtrlC: true
|
|
2808
3549
|
});
|
|
2809
3550
|
}
|