@snoglobe/helios 0.1.0 → 0.2.0
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/acp/entry.d.ts +7 -0
- package/dist/acp/entry.d.ts.map +1 -0
- package/dist/acp/entry.js +46 -0
- package/dist/acp/entry.js.map +1 -0
- package/dist/acp/server.d.ts +57 -0
- package/dist/acp/server.d.ts.map +1 -0
- package/dist/acp/server.js +434 -0
- package/dist/acp/server.js.map +1 -0
- package/dist/acp/transport.d.ts +31 -0
- package/dist/acp/transport.d.ts.map +1 -0
- package/dist/acp/transport.js +116 -0
- package/dist/acp/transport.js.map +1 -0
- package/dist/acp/types.d.ts +213 -0
- package/dist/acp/types.d.ts.map +1 -0
- package/dist/acp/types.js +3 -0
- package/dist/acp/types.js.map +1 -0
- package/dist/app.d.ts +8 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +51 -281
- package/dist/app.js.map +1 -1
- package/dist/bootstrap.d.ts +3 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +26 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/auth.d.ts +20 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +62 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/doctor.d.ts +9 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +368 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/env.d.ts +13 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +16 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/export.d.ts +11 -0
- package/dist/cli/export.d.ts.map +1 -0
- package/dist/cli/export.js +103 -0
- package/dist/cli/export.js.map +1 -0
- package/dist/cli/index.d.ts +22 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init-cmd.d.ts +11 -0
- package/dist/cli/init-cmd.d.ts.map +1 -0
- package/dist/cli/init-cmd.js +37 -0
- package/dist/cli/init-cmd.js.map +1 -0
- package/dist/cli/kill.d.ts +8 -0
- package/dist/cli/kill.d.ts.map +1 -0
- package/dist/cli/kill.js +46 -0
- package/dist/cli/kill.js.map +1 -0
- package/dist/cli/options.d.ts +18 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +24 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/cli/replay.d.ts +9 -0
- package/dist/cli/replay.d.ts.map +1 -0
- package/dist/cli/replay.js +65 -0
- package/dist/cli/replay.js.map +1 -0
- package/dist/cli/report.d.ts +11 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +70 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/run.d.ts +17 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +120 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/search.d.ts +9 -0
- package/dist/cli/search.d.ts.map +1 -0
- package/dist/cli/search.js +52 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/cli/sessions.d.ts +8 -0
- package/dist/cli/sessions.d.ts.map +1 -0
- package/dist/cli/sessions.js +38 -0
- package/dist/cli/sessions.js.map +1 -0
- package/dist/cli/watch.d.ts +8 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +81 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/config/project.d.ts +63 -0
- package/dist/config/project.d.ts.map +1 -0
- package/dist/config/project.js +112 -0
- package/dist/config/project.js.map +1 -0
- package/dist/core/orchestrator.d.ts +4 -2
- package/dist/core/orchestrator.d.ts.map +1 -1
- package/dist/core/orchestrator.js +8 -3
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/task-poller.d.ts +43 -0
- package/dist/core/task-poller.d.ts.map +1 -0
- package/dist/core/task-poller.js +98 -0
- package/dist/core/task-poller.js.map +1 -0
- package/dist/experiments/branching.d.ts +38 -0
- package/dist/experiments/branching.d.ts.map +1 -0
- package/dist/experiments/branching.js +80 -0
- package/dist/experiments/branching.js.map +1 -0
- package/dist/hub/client.d.ts +70 -0
- package/dist/hub/client.d.ts.map +1 -0
- package/dist/hub/client.js +140 -0
- package/dist/hub/client.js.map +1 -0
- package/dist/hub/config.d.ts +9 -0
- package/dist/hub/config.d.ts.map +1 -0
- package/dist/hub/config.js +38 -0
- package/dist/hub/config.js.map +1 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +25 -3
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +44 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +384 -0
- package/dist/init.js.map +1 -0
- package/dist/memory/context-gate.d.ts.map +1 -1
- package/dist/memory/context-gate.js +1 -1
- package/dist/memory/context-gate.js.map +1 -1
- package/dist/memory/experiment-tracker.d.ts.map +1 -1
- package/dist/memory/experiment-tracker.js +2 -6
- package/dist/memory/experiment-tracker.js.map +1 -1
- package/dist/memory/memory-store.d.ts.map +1 -1
- package/dist/memory/memory-store.js +16 -20
- package/dist/memory/memory-store.js.map +1 -1
- package/dist/metrics/collector.d.ts.map +1 -1
- package/dist/metrics/collector.js +7 -7
- package/dist/metrics/collector.js.map +1 -1
- package/dist/metrics/resources.d.ts +44 -0
- package/dist/metrics/resources.d.ts.map +1 -0
- package/dist/metrics/resources.js +382 -0
- package/dist/metrics/resources.js.map +1 -0
- package/dist/metrics/store.d.ts +10 -2
- package/dist/metrics/store.d.ts.map +1 -1
- package/dist/metrics/store.js +91 -27
- package/dist/metrics/store.js.map +1 -1
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +2 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/notifier.d.ts +36 -0
- package/dist/notifications/notifier.d.ts.map +1 -0
- package/dist/notifications/notifier.js +102 -0
- package/dist/notifications/notifier.js.map +1 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +5 -0
- package/dist/paths.js.map +1 -0
- package/dist/providers/claude/provider.d.ts +7 -3
- package/dist/providers/claude/provider.d.ts.map +1 -1
- package/dist/providers/claude/provider.js +48 -8
- package/dist/providers/claude/provider.js.map +1 -1
- package/dist/providers/openai/callback-server.d.ts +6 -1
- package/dist/providers/openai/callback-server.d.ts.map +1 -1
- package/dist/providers/openai/callback-server.js +44 -20
- package/dist/providers/openai/callback-server.js.map +1 -1
- package/dist/providers/openai/oauth.d.ts +4 -0
- package/dist/providers/openai/oauth.d.ts.map +1 -1
- package/dist/providers/openai/oauth.js +34 -14
- package/dist/providers/openai/oauth.js.map +1 -1
- package/dist/providers/openai/provider.d.ts +6 -3
- package/dist/providers/openai/provider.d.ts.map +1 -1
- package/dist/providers/openai/provider.js +35 -9
- package/dist/providers/openai/provider.js.map +1 -1
- package/dist/providers/types.d.ts +9 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/remote/config.d.ts.map +1 -1
- package/dist/remote/config.js +4 -8
- package/dist/remote/config.js.map +1 -1
- package/dist/remote/connection-pool.d.ts.map +1 -1
- package/dist/remote/connection-pool.js +9 -5
- package/dist/remote/connection-pool.js.map +1 -1
- package/dist/store/database.d.ts.map +1 -1
- package/dist/store/database.js +2 -2
- package/dist/store/database.js.map +1 -1
- package/dist/store/migrations.d.ts.map +1 -1
- package/dist/store/migrations.js +27 -0
- package/dist/store/migrations.js.map +1 -1
- package/dist/store/preferences.js +2 -2
- package/dist/store/preferences.js.map +1 -1
- package/dist/store/session-store.d.ts +6 -0
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +20 -5
- package/dist/store/session-store.js.map +1 -1
- package/dist/tools/env-snapshot.d.ts +5 -0
- package/dist/tools/env-snapshot.d.ts.map +1 -0
- package/dist/tools/env-snapshot.js +174 -0
- package/dist/tools/env-snapshot.js.map +1 -0
- package/dist/tools/experiment-branch.d.ts +7 -0
- package/dist/tools/experiment-branch.d.ts.map +1 -0
- package/dist/tools/experiment-branch.js +167 -0
- package/dist/tools/experiment-branch.js.map +1 -0
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +1 -3
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/hub.d.ts +5 -0
- package/dist/tools/hub.d.ts.map +1 -0
- package/dist/tools/hub.js +298 -0
- package/dist/tools/hub.js.map +1 -0
- package/dist/tools/sweep.d.ts +6 -0
- package/dist/tools/sweep.d.ts.map +1 -0
- package/dist/tools/sweep.js +153 -0
- package/dist/tools/sweep.js.map +1 -0
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +2 -1
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/writeup.d.ts +11 -0
- package/dist/tools/writeup.d.ts.map +1 -0
- package/dist/tools/writeup.js +102 -0
- package/dist/tools/writeup.js.map +1 -0
- package/dist/ui/commands.d.ts +36 -0
- package/dist/ui/commands.d.ts.map +1 -1
- package/dist/ui/commands.js +495 -1
- package/dist/ui/commands.js.map +1 -1
- package/dist/ui/components/input-bar.js.map +1 -1
- package/dist/ui/components/overlay-header.d.ts.map +1 -1
- package/dist/ui/components/overlay-header.js.map +1 -1
- package/dist/ui/components/status-bar.js +1 -1
- package/dist/ui/components/status-bar.js.map +1 -1
- package/dist/ui/format.d.ts +4 -0
- package/dist/ui/format.d.ts.map +1 -1
- package/dist/ui/format.js +16 -0
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/layout.d.ts +7 -42
- package/dist/ui/layout.d.ts.map +1 -1
- package/dist/ui/layout.js +71 -543
- package/dist/ui/layout.js.map +1 -1
- package/dist/ui/overlays/metrics-overlay.js.map +1 -1
- package/dist/ui/overlays/task-overlay.d.ts +1 -1
- package/dist/ui/overlays/task-overlay.d.ts.map +1 -1
- package/dist/ui/overlays/task-overlay.js.map +1 -1
- package/dist/ui/panels/conversation.d.ts +1 -1
- package/dist/ui/panels/conversation.d.ts.map +1 -1
- package/dist/ui/panels/conversation.js +1 -2
- package/dist/ui/panels/conversation.js.map +1 -1
- package/dist/ui/panels/metrics-dashboard.d.ts.map +1 -1
- package/dist/ui/panels/metrics-dashboard.js.map +1 -1
- package/dist/ui/panels/sleep-panel.js.map +1 -1
- package/dist/ui/panels/sticky-notes.d.ts.map +1 -1
- package/dist/ui/panels/sticky-notes.js.map +1 -1
- package/dist/ui/panels/task-list.d.ts +4 -2
- package/dist/ui/panels/task-list.d.ts.map +1 -1
- package/dist/ui/panels/task-list.js +41 -9
- package/dist/ui/panels/task-list.js.map +1 -1
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js.map +1 -1
- package/dist/ui/types.d.ts +22 -0
- package/dist/ui/types.d.ts.map +1 -0
- package/dist/ui/types.js +2 -0
- package/dist/ui/types.js.map +1 -0
- package/package.json +19 -6
package/dist/ui/layout.js
CHANGED
|
@@ -5,22 +5,24 @@ import { useScreenSize } from "fullscreen-ink";
|
|
|
5
5
|
import { ScrollView } from "ink-scroll-view";
|
|
6
6
|
import { ConversationPanel } from "./panels/conversation.js";
|
|
7
7
|
import { TaskListPanel } from "./panels/task-list.js";
|
|
8
|
-
import { MetricsDashboard
|
|
8
|
+
import { MetricsDashboard } from "./panels/metrics-dashboard.js";
|
|
9
9
|
import { StatusBar } from "./components/status-bar.js";
|
|
10
10
|
import { InputBar } from "./components/input-bar.js";
|
|
11
11
|
import { C, G, HRule } from "./theme.js";
|
|
12
12
|
import { KeyHintRule } from "./components/key-hint-rule.js";
|
|
13
13
|
import { TaskOverlay } from "./overlays/task-overlay.js";
|
|
14
14
|
import { MetricsOverlay } from "./overlays/metrics-overlay.js";
|
|
15
|
-
import { formatMetricValue, formatError } from "./format.js";
|
|
16
|
-
import { loadMachines, addMachine as addMachineConfig, removeMachine as removeMachineConfig, parseMachineSpec, } from "../remote/config.js";
|
|
17
15
|
import { StickyNotesPanel } from "./panels/sticky-notes.js";
|
|
18
|
-
import { savePreferences } from "../store/preferences.js";
|
|
19
16
|
import { VERSION, checkForUpdate } from "../version.js";
|
|
17
|
+
import { handleSlashCommand } from "./commands.js";
|
|
18
|
+
import { pollTaskStatuses, handleFinishedTasks, buildMonitorMessage } from "../core/task-poller.js";
|
|
20
19
|
let messageIdCounter = 0;
|
|
21
|
-
export function Layout({
|
|
20
|
+
export function Layout({ runtime, mouseEmitter, headless, initialPrompt, initialAttachments }) {
|
|
21
|
+
const { orchestrator, sleepManager, connectionPool, executor, metricStore, metricCollector, monitorManager, experimentTracker, memoryStore, stickyManager, agentName, } = runtime;
|
|
22
22
|
const { exit } = useApp();
|
|
23
23
|
const [messages, setMessages] = useState([]);
|
|
24
|
+
const messagesRef = useRef(messages);
|
|
25
|
+
messagesRef.current = messages;
|
|
24
26
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
25
27
|
const scrollRef = useRef(null);
|
|
26
28
|
const [userScrolled, setUserScrolled] = useState(false);
|
|
@@ -28,6 +30,7 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
28
30
|
const [metricData, setMetricData] = useState(new Map());
|
|
29
31
|
const [stickyNotes, setStickyNotes] = useState([]);
|
|
30
32
|
const [activeOverlay, setActiveOverlay] = useState("none");
|
|
33
|
+
const [resourceData, setResourceData] = useState(new Map());
|
|
31
34
|
const [updateAvailable, setUpdateAvailable] = useState(null);
|
|
32
35
|
// Check for updates on mount (non-blocking)
|
|
33
36
|
useEffect(() => {
|
|
@@ -38,72 +41,29 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
38
41
|
useEffect(() => {
|
|
39
42
|
const poll = async () => {
|
|
40
43
|
let didCollect = false;
|
|
41
|
-
// Update task list from executor's background processes
|
|
42
44
|
if (executor && connectionPool) {
|
|
43
|
-
const
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
try {
|
|
47
|
-
const running = await executor.isRunning(proc.machineId, proc.pid);
|
|
48
|
-
return { proc, running };
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return { proc, running: true }; // Transient error — assume still running
|
|
52
|
-
}
|
|
53
|
-
}));
|
|
54
|
-
const finished = [];
|
|
55
|
-
const updated = [];
|
|
56
|
-
for (const { proc, running } of statuses) {
|
|
57
|
-
const key = `${proc.machineId}:${proc.pid}`;
|
|
58
|
-
const status = running ? "running" : "completed";
|
|
59
|
-
if (!running)
|
|
60
|
-
finished.push(key);
|
|
45
|
+
const { statuses, finished } = await pollTaskStatuses(executor);
|
|
46
|
+
// Build TaskInfo[] for UI state
|
|
47
|
+
const updated = statuses.map(({ proc, running }) => {
|
|
61
48
|
const shortCmd = proc.command.length > 40
|
|
62
49
|
? proc.command.slice(0, 40) + "..."
|
|
63
50
|
: proc.command;
|
|
64
|
-
|
|
65
|
-
id:
|
|
51
|
+
return {
|
|
52
|
+
id: `${proc.machineId}:${proc.pid}`,
|
|
66
53
|
name: shortCmd,
|
|
67
|
-
status,
|
|
54
|
+
status: running ? "running" : "completed",
|
|
68
55
|
machineId: proc.machineId,
|
|
69
56
|
pid: proc.pid,
|
|
70
57
|
startedAt: proc.startedAt,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
if (finished.length > 0) {
|
|
61
|
+
await handleFinishedTasks(finished, {
|
|
62
|
+
executor, connectionPool, metricCollector, metricStore,
|
|
63
|
+
experimentTracker, notifier: runtime.notifier,
|
|
71
64
|
});
|
|
72
|
-
}
|
|
73
|
-
// Collect metrics before removing finished processes (so final data is captured)
|
|
74
|
-
if (finished.length > 0 && metricCollector) {
|
|
75
|
-
await metricCollector.collectAll().catch(() => { });
|
|
76
65
|
didCollect = true;
|
|
77
66
|
}
|
|
78
|
-
for (const key of finished) {
|
|
79
|
-
const [machineId, pidStr] = key.split(":");
|
|
80
|
-
const pid = parseInt(pidStr, 10);
|
|
81
|
-
// Fetch actual exit code before cleanup
|
|
82
|
-
let exitCode = 0;
|
|
83
|
-
try {
|
|
84
|
-
const result = await connectionPool.exec(machineId, `wait ${pid} 2>/dev/null; echo $?`);
|
|
85
|
-
const parsed = parseInt(result.stdout.trim(), 10);
|
|
86
|
-
if (!isNaN(parsed))
|
|
87
|
-
exitCode = parsed;
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
// Can't determine exit code — default to 0
|
|
91
|
-
}
|
|
92
|
-
// Update experiment tracker with final metrics
|
|
93
|
-
if (experimentTracker && metricStore) {
|
|
94
|
-
const names = metricStore.getMetricNames(key);
|
|
95
|
-
const metrics = {};
|
|
96
|
-
for (const name of names) {
|
|
97
|
-
const latest = metricStore.getLatest(key, name);
|
|
98
|
-
if (latest)
|
|
99
|
-
metrics[name] = latest.value;
|
|
100
|
-
}
|
|
101
|
-
experimentTracker.updateExperiment(machineId, pid, exitCode, Object.keys(metrics).length > 0 ? metrics : undefined);
|
|
102
|
-
}
|
|
103
|
-
// Clean up collector source so it stops tailing the dead process log
|
|
104
|
-
metricCollector?.removeSource(key);
|
|
105
|
-
executor.removeBackgroundProcess(key);
|
|
106
|
-
}
|
|
107
67
|
setTasks(updated);
|
|
108
68
|
}
|
|
109
69
|
// Collect metrics from all sources (skip if we already collected for finished tasks above)
|
|
@@ -111,21 +71,24 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
111
71
|
if (!didCollect) {
|
|
112
72
|
await metricCollector.collectAll().catch(() => { });
|
|
113
73
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
newMetricData.set(name, series.map((p) => p.value));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
setMetricData(newMetricData);
|
|
74
|
+
setMetricData(metricStore.getAllSeries(50));
|
|
75
|
+
}
|
|
76
|
+
// Collect resource usage from connected machines
|
|
77
|
+
if (runtime.resourceCollector) {
|
|
78
|
+
const res = await runtime.resourceCollector.collectAll().catch(() => new Map());
|
|
79
|
+
setResourceData(res);
|
|
124
80
|
}
|
|
125
81
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
82
|
+
let timer = null;
|
|
83
|
+
let stopped = false;
|
|
84
|
+
const loop = async () => {
|
|
85
|
+
await poll();
|
|
86
|
+
if (!stopped)
|
|
87
|
+
timer = setTimeout(loop, 5000);
|
|
88
|
+
};
|
|
89
|
+
loop();
|
|
90
|
+
return () => { stopped = true; if (timer)
|
|
91
|
+
clearTimeout(timer); };
|
|
129
92
|
}, [executor, connectionPool, metricCollector, metricStore]);
|
|
130
93
|
// Auto-scroll to bottom when messages change, overlay closes, or user hasn't scrolled up
|
|
131
94
|
useEffect(() => {
|
|
@@ -235,17 +198,19 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
235
198
|
const updateMessage = useCallback((id, updates) => {
|
|
236
199
|
setMessages((prev) => prev.map((m) => (m.id === id ? { ...m, ...updates } : m)));
|
|
237
200
|
}, []);
|
|
238
|
-
const handleSubmit = useCallback(async (input) => {
|
|
201
|
+
const handleSubmit = useCallback(async (input, attachments) => {
|
|
239
202
|
if (!input.trim())
|
|
240
203
|
return;
|
|
241
204
|
if (input.startsWith("/")) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
205
|
+
await handleSlashCommand(input, {
|
|
206
|
+
orchestrator, addMessage, updateMessage, setMessages, messages: messagesRef.current, setIsStreaming,
|
|
207
|
+
connectionPool, metricStore, metricCollector, memoryStore,
|
|
208
|
+
stickyManager, setStickyNotes, executor,
|
|
209
|
+
restoreMessages: (msgs) => msgs.map((m) => ({
|
|
210
|
+
id: ++messageIdCounter,
|
|
211
|
+
role: m.role,
|
|
212
|
+
content: m.content,
|
|
213
|
+
})),
|
|
249
214
|
});
|
|
250
215
|
return;
|
|
251
216
|
}
|
|
@@ -262,7 +227,7 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
262
227
|
let assistantMsgId = null;
|
|
263
228
|
// Map tool callId -> message id for attaching results
|
|
264
229
|
const toolMsgIds = new Map();
|
|
265
|
-
for await (const event of orchestrator.send(input)) {
|
|
230
|
+
for await (const event of orchestrator.send(input, attachments)) {
|
|
266
231
|
// Feed events to experiment tracker for auto-populating /experiments/
|
|
267
232
|
experimentTracker?.onEvent(event);
|
|
268
233
|
if (event.type === "text" && event.delta) {
|
|
@@ -308,14 +273,17 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
308
273
|
setIsStreaming(false);
|
|
309
274
|
}
|
|
310
275
|
}, [orchestrator, sleepManager, addMessage, updateMessage, setMessages, connectionPool, metricStore]);
|
|
276
|
+
// Auto-submit initial prompt (from --prompt CLI flag)
|
|
277
|
+
const promptSent = useRef(false);
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (initialPrompt && !promptSent.current) {
|
|
280
|
+
promptSent.current = true;
|
|
281
|
+
handleSubmit(initialPrompt, initialAttachments);
|
|
282
|
+
}
|
|
283
|
+
}, [initialPrompt, initialAttachments, handleSubmit]);
|
|
311
284
|
// Monitor: auto-invoke model on tick
|
|
312
285
|
const isStreamingRef = useRef(false);
|
|
313
286
|
isStreamingRef.current = isStreaming;
|
|
314
|
-
// Use refs for tasks/metricData so the monitor effect doesn't re-subscribe every poll
|
|
315
|
-
const tasksRef = useRef(tasks);
|
|
316
|
-
tasksRef.current = tasks;
|
|
317
|
-
const metricDataRef = useRef(metricData);
|
|
318
|
-
metricDataRef.current = metricData;
|
|
319
287
|
const handleSubmitRef = useRef(handleSubmit);
|
|
320
288
|
handleSubmitRef.current = handleSubmit;
|
|
321
289
|
useEffect(() => {
|
|
@@ -324,29 +292,8 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
324
292
|
const onTick = (config) => {
|
|
325
293
|
if (isStreamingRef.current)
|
|
326
294
|
return;
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
const intervalMin = Math.round(config.intervalMs / 60_000);
|
|
330
|
-
const parts = [
|
|
331
|
-
`[Monitor check — ${elapsedMin}m elapsed, interval ${intervalMin}m]`,
|
|
332
|
-
`Goal: ${config.goal}`,
|
|
333
|
-
];
|
|
334
|
-
const currentTasks = tasksRef.current;
|
|
335
|
-
if (currentTasks.length > 0) {
|
|
336
|
-
parts.push("Tasks:");
|
|
337
|
-
for (const t of currentTasks) {
|
|
338
|
-
parts.push(` ${t.status === "running" ? "◆" : "◇"} ${t.machineId}:${t.pid ?? "?"} ${t.status} — ${t.name}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
const currentMetrics = metricDataRef.current;
|
|
342
|
-
if (currentMetrics.size > 0) {
|
|
343
|
-
parts.push("Metrics:");
|
|
344
|
-
for (const [name, values] of currentMetrics.entries()) {
|
|
345
|
-
const latest = values[values.length - 1];
|
|
346
|
-
parts.push(` ${name}: ${latest}`);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
handleSubmitRef.current(parts.join("\n"));
|
|
295
|
+
const message = buildMonitorMessage(config, executor, metricStore);
|
|
296
|
+
handleSubmitRef.current(message);
|
|
350
297
|
};
|
|
351
298
|
monitorManager.on("tick", onTick);
|
|
352
299
|
return () => {
|
|
@@ -356,6 +303,13 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
356
303
|
// Sleep/wake: auto-resume model when a trigger fires
|
|
357
304
|
const addMessageRef = useRef(addMessage);
|
|
358
305
|
addMessageRef.current = addMessage;
|
|
306
|
+
// Route OpenAI OAuth URL display through the TUI instead of stderr
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
runtime.openaiOAuth.onAuthUrl = (url) => {
|
|
309
|
+
addMessageRef.current("system", url);
|
|
310
|
+
};
|
|
311
|
+
return () => { runtime.openaiOAuth.onAuthUrl = null; };
|
|
312
|
+
}, [runtime.openaiOAuth]);
|
|
359
313
|
useEffect(() => {
|
|
360
314
|
const onWake = (_session, _reason, wakeMessage) => {
|
|
361
315
|
if (isStreamingRef.current)
|
|
@@ -381,441 +335,15 @@ export function Layout({ orchestrator, sleepManager, connectionPool, executor, m
|
|
|
381
335
|
return (_jsx(Box, { flexDirection: "column", height: height, width: width, children: _jsx(MetricsOverlay, { metricData: metricData, metricStore: metricStore, width: width, height: height, onClose: () => setActiveOverlay("none") }) }));
|
|
382
336
|
}
|
|
383
337
|
// ── Normal layout ─────────────────────────────────────────────
|
|
384
|
-
return (_jsxs(Box, { flexDirection: "column", height: height, width: width, children: [_jsx(Box, { flexShrink: 0, children: _jsx(HeaderWithPanels, { width: width }) }), _jsxs(Box, { flexShrink: 0, flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexBasis: 0, flexDirection: "column", paddingX: 1, children: _jsx(MetricsDashboard, { metricData: metricData, width: Math.floor((width - 1) / 2) - 2 }) }), _jsx(Box, { width: 1, flexDirection: "column", alignItems: "center", children: _jsx(Text, { color: C.primary, wrap: "truncate", children: Array.from({ length: panelHeight }, () => "│").join("\n") }) }), _jsx(Box, { flexGrow: 1, flexBasis: 0, flexDirection: "column", paddingX: 1, children: _jsx(TaskListPanel, { tasks: tasks, width: Math.floor((width - 1) / 2) - 2 }) })] }), _jsx(Box, { flexShrink: 0, children: _jsx(HRule, {}) }), _jsxs(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: messages.length === 0 ? (_jsxs(Box, { flexGrow: 1, alignItems: "center", justifyContent: "center", flexDirection: "column", children: [_jsx(Text, { color: C.primary, bold: true, children: G.brand }), _jsx(Text, { color: C.primary, bold: true, children: "H E L I O S" }), _jsx(Text, { color: C.dim, children: "autonomous ml research" }), _jsxs(Text, { color: C.dim, dimColor: true, children: ["v", VERSION] }), _jsx(Text, { color: C.dim, dimColor: true, children: "" }), _jsx(Text, { color: C.dim, dimColor: true, children: "/help for commands" }), updateAvailable && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: C.bright, children: ["update available: v", updateAvailable, " \u2014 npm i -g helios"] }) }))] })) : (_jsx(ScrollView, { ref: scrollRef, children: _jsx(ConversationPanel, { messages: messages, isStreaming: isStreaming }) })) }), stickyNotes.length > 0 && (_jsx(Box, { flexShrink: 0, children: _jsx(StickyNotesPanel, { notes: stickyNotes, width: Math.min(30, Math.floor(width * 0.25)) }) }))] }), _jsx(Box, { flexShrink: 0, children: _jsx(KeyHintRule, {}) }), _jsx(Box, { flexShrink: 0, children: _jsx(StatusBar, { orchestrator: orchestrator, sleepManager: sleepManager, monitorManager: monitorManager }) }), _jsx(Box, { flexShrink: 0, children: _jsx(InputBar, { onSubmit: handleSubmit, disabled: isStreaming, placeholder: isSleeping
|
|
338
|
+
return (_jsxs(Box, { flexDirection: "column", height: height, width: width, children: [_jsx(Box, { flexShrink: 0, children: headless && agentName ? (_jsx(HeadlessHeader, { agentName: agentName, width: width })) : (_jsx(HeaderWithPanels, { width: width })) }), _jsxs(Box, { flexShrink: 0, flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexBasis: 0, flexDirection: "column", paddingX: 1, children: _jsx(MetricsDashboard, { metricData: metricData, width: Math.floor((width - 1) / 2) - 2 }) }), _jsx(Box, { width: 1, flexDirection: "column", alignItems: "center", children: _jsx(Text, { color: C.primary, wrap: "truncate", children: Array.from({ length: panelHeight }, () => "│").join("\n") }) }), _jsx(Box, { flexGrow: 1, flexBasis: 0, flexDirection: "column", paddingX: 1, children: _jsx(TaskListPanel, { tasks: tasks, resources: resourceData, width: Math.floor((width - 1) / 2) - 2 }) })] }), _jsx(Box, { flexShrink: 0, children: _jsx(HRule, {}) }), _jsxs(Box, { flexGrow: 1, flexShrink: 1, flexDirection: "row", children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, children: messages.length === 0 && !headless ? (_jsxs(Box, { flexGrow: 1, alignItems: "center", justifyContent: "center", flexDirection: "column", children: [_jsx(Text, { color: C.primary, bold: true, children: G.brand }), _jsx(Text, { color: C.primary, bold: true, children: "H E L I O S" }), _jsx(Text, { color: C.dim, children: "autonomous ml research" }), _jsxs(Text, { color: C.dim, dimColor: true, children: ["v", VERSION] }), _jsx(Text, { color: C.dim, dimColor: true, children: "" }), _jsx(Text, { color: C.dim, dimColor: true, children: "/help for commands" }), updateAvailable && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: C.bright, children: ["update available: v", updateAvailable, " \u2014 npm i -g helios"] }) }))] })) : (_jsx(ScrollView, { ref: scrollRef, children: _jsx(ConversationPanel, { messages: messages, isStreaming: isStreaming }) })) }), stickyNotes.length > 0 && (_jsx(Box, { flexShrink: 0, children: _jsx(StickyNotesPanel, { notes: stickyNotes, width: Math.min(30, Math.floor(width * 0.25)) }) }))] }), !headless && _jsx(Box, { flexShrink: 0, children: _jsx(KeyHintRule, {}) }), _jsx(Box, { flexShrink: 0, children: _jsx(StatusBar, { orchestrator: orchestrator, sleepManager: sleepManager, monitorManager: monitorManager }) }), !headless && (_jsx(Box, { flexShrink: 0, children: _jsx(InputBar, { onSubmit: handleSubmit, disabled: isStreaming, placeholder: isSleeping
|
|
385
339
|
? "type to wake agent..."
|
|
386
|
-
: "send a message... (/help for commands)" }) })] }));
|
|
340
|
+
: "send a message... (/help for commands)" }) }))] }));
|
|
387
341
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
switch (cmd) {
|
|
394
|
-
case "switch": {
|
|
395
|
-
const provider = args[0];
|
|
396
|
-
if (provider !== "claude" && provider !== "openai") {
|
|
397
|
-
addMessage("system", "Usage: /switch <claude|openai>");
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
addMessage("system", `Switching to ${provider}...`);
|
|
401
|
-
orchestrator.switchProvider(provider).then(() => addMessage("system", `Switched to ${provider}`), (err) => addMessage("error", `Failed to switch: ${formatError(err)}`));
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
case "model": {
|
|
405
|
-
const modelId = args[0];
|
|
406
|
-
if (!modelId) {
|
|
407
|
-
addMessage("system", `Current model: ${orchestrator.currentModel ?? "default"}\nUsage: /model <model-id>`);
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
addMessage("system", `Setting model to ${modelId}...`);
|
|
411
|
-
orchestrator.setModel(modelId).then(() => addMessage("system", `Model set to ${modelId}`), (err) => addMessage("error", `Failed to set model: ${formatError(err)}`));
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
case "reasoning": {
|
|
415
|
-
const level = args[0];
|
|
416
|
-
const validLevels = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
417
|
-
if (!level || !validLevels.includes(level)) {
|
|
418
|
-
const provider = orchestrator.currentProvider?.name;
|
|
419
|
-
const hint = provider === "claude"
|
|
420
|
-
? "Claude: medium, high, max"
|
|
421
|
-
: "OpenAI: none, minimal, low, medium, high, xhigh";
|
|
422
|
-
addMessage("system", `Current reasoning effort: ${orchestrator.reasoningEffort ?? "medium"}\n${hint}\nUsage: /reasoning <level>`);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
orchestrator.setReasoningEffort(level).then(() => addMessage("system", `Reasoning effort set to ${level}`), (err) => addMessage("error", `Failed: ${formatError(err)}`));
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
case "models": {
|
|
429
|
-
addMessage("system", "Fetching available models...");
|
|
430
|
-
orchestrator.fetchModels().then((models) => {
|
|
431
|
-
const current = orchestrator.currentModel;
|
|
432
|
-
const lines = models.map((m) => {
|
|
433
|
-
const marker = m.id === current ? " ◆" : "";
|
|
434
|
-
const desc = m.description ? ` — ${m.description}` : "";
|
|
435
|
-
return ` ${m.id}${marker}${desc}`;
|
|
436
|
-
});
|
|
437
|
-
addMessage("system", `Available models:\n${lines.join("\n")}`);
|
|
438
|
-
}, (err) => addMessage("error", `Failed to fetch models: ${formatError(err)}`));
|
|
439
|
-
break;
|
|
440
|
-
}
|
|
441
|
-
case "claude-mode": {
|
|
442
|
-
const mode = args[0];
|
|
443
|
-
if (mode !== "cli" && mode !== "api") {
|
|
444
|
-
const current = orchestrator.getProvider("claude")?.currentAuthMode;
|
|
445
|
-
addMessage("system", `Current Claude mode: ${current === "cli" ? "cli (Agent SDK)" : "api (API key)"}\nUsage: /claude-mode <cli|api>`);
|
|
446
|
-
break;
|
|
447
|
-
}
|
|
448
|
-
const claude = orchestrator.getProvider("claude");
|
|
449
|
-
if (!claude) {
|
|
450
|
-
addMessage("error", "Claude provider not registered");
|
|
451
|
-
break;
|
|
452
|
-
}
|
|
453
|
-
claude.setPreferredAuthMode(mode);
|
|
454
|
-
savePreferences({ claudeAuthMode: mode });
|
|
455
|
-
// Re-authenticate to apply the new mode
|
|
456
|
-
claude.authenticate().then(() => addMessage("system", `Claude mode set to ${mode === "cli" ? "cli (Agent SDK)" : "api (API key)"}`), (err) => addMessage("error", `Failed to switch Claude mode: ${formatError(err)}`));
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
case "machine":
|
|
460
|
-
case "machines": {
|
|
461
|
-
handleMachineCommand(args, addMessage, connectionPool);
|
|
462
|
-
break;
|
|
463
|
-
}
|
|
464
|
-
case "resume": {
|
|
465
|
-
handleResumeCommand(args, orchestrator, addMessage, setMessages);
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
case "metric":
|
|
469
|
-
case "metrics": {
|
|
470
|
-
if (!metricStore) {
|
|
471
|
-
addMessage("error", "Metric store not available");
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
if (args[0] === "clear") {
|
|
475
|
-
const deleted = metricStore.clear();
|
|
476
|
-
metricCollector?.reset();
|
|
477
|
-
addMessage("system", `Cleared ${deleted} metric points.`);
|
|
478
|
-
}
|
|
479
|
-
else if (args.length === 0) {
|
|
480
|
-
// /metric with no args — list all known metric names
|
|
481
|
-
const allNames = metricStore.getAllMetricNames();
|
|
482
|
-
if (allNames.length === 0) {
|
|
483
|
-
addMessage("system", "No metrics recorded yet.");
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
addMessage("system", `Known metrics:\n ${allNames.join(" ")}\n\nUsage: /metric <name1> [name2] ... | /metrics clear`);
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
// /metric loss acc lr — show sparklines for named metrics
|
|
491
|
-
const lines = [];
|
|
492
|
-
for (const name of args) {
|
|
493
|
-
const series = metricStore.getSeriesAcrossTasks(name, 50);
|
|
494
|
-
if (series.length === 0) {
|
|
495
|
-
lines.push(` ${name} (no data)`);
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
const values = series.map((p) => p.value);
|
|
499
|
-
const latest = values[values.length - 1];
|
|
500
|
-
const min = Math.min(...values);
|
|
501
|
-
const max = Math.max(...values);
|
|
502
|
-
const spark = sparkline(values, 30);
|
|
503
|
-
lines.push(` ${name} ${spark} ${formatMetricValue(latest)} (min ${formatMetricValue(min)} max ${formatMetricValue(max)})`);
|
|
504
|
-
}
|
|
505
|
-
addMessage("system", lines.join("\n"));
|
|
506
|
-
}
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
|
-
case "help":
|
|
510
|
-
addMessage("system", [
|
|
511
|
-
"Commands:",
|
|
512
|
-
" /switch <claude|openai> Switch model provider",
|
|
513
|
-
" /claude-mode <cli|api> Switch Claude auth (cli=Agent SDK, api=API key)",
|
|
514
|
-
" /model <model-id> Set model",
|
|
515
|
-
" /models List available models",
|
|
516
|
-
" /reasoning <level> Set reasoning effort",
|
|
517
|
-
" /resume List recent sessions",
|
|
518
|
-
" /resume <number> Resume a past session",
|
|
519
|
-
" /metric [name1 name2 ...] Show metric sparklines",
|
|
520
|
-
" /metrics clear Clear all metrics",
|
|
521
|
-
" /writeup Generate experiment writeup",
|
|
522
|
-
" /machine add <id> <user@host> Add remote machine",
|
|
523
|
-
" /machine rm <id> Remove machine",
|
|
524
|
-
" /machines List machines",
|
|
525
|
-
" /status Show current state",
|
|
526
|
-
" /clear Clear conversation",
|
|
527
|
-
" /quit Exit Helios",
|
|
528
|
-
"",
|
|
529
|
-
"Keys:",
|
|
530
|
-
" Tab Autocomplete command",
|
|
531
|
-
" ↑↓ Navigate menu / history",
|
|
532
|
-
" ←→ Move cursor",
|
|
533
|
-
" Ctrl+T Task output overlay",
|
|
534
|
-
" Ctrl+G Metrics overlay",
|
|
535
|
-
" Escape Interrupt / close overlay",
|
|
536
|
-
" Ctrl+A/E Start / end of line",
|
|
537
|
-
" Ctrl+W Delete word backward",
|
|
538
|
-
" Ctrl+U Clear line",
|
|
539
|
-
" Ctrl+C Interrupt / Exit",
|
|
540
|
-
].join("\n"));
|
|
541
|
-
break;
|
|
542
|
-
case "status":
|
|
543
|
-
addMessage("system", [
|
|
544
|
-
`Provider: ${orchestrator.currentProvider?.displayName ?? "None"}`,
|
|
545
|
-
`Model: ${orchestrator.currentModel ?? "default"}`,
|
|
546
|
-
`Reasoning: ${orchestrator.reasoningEffort ?? "medium"}`,
|
|
547
|
-
`State: ${orchestrator.currentState}`,
|
|
548
|
-
`Cost: $${orchestrator.totalCostUsd.toFixed(4)}`,
|
|
549
|
-
].join("\n"));
|
|
550
|
-
break;
|
|
551
|
-
case "sticky": {
|
|
552
|
-
if (!stickyManager || !setStickyNotes) {
|
|
553
|
-
addMessage("system", "Sticky notes not available.");
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
556
|
-
const stickyText = args.join(" ").trim();
|
|
557
|
-
if (!stickyText) {
|
|
558
|
-
addMessage("system", "Usage: /sticky <text to pin>");
|
|
559
|
-
break;
|
|
560
|
-
}
|
|
561
|
-
const note = stickyManager.add(stickyText);
|
|
562
|
-
setStickyNotes(stickyManager.list());
|
|
563
|
-
addMessage("system", `Pinned sticky #${note.num}: ${stickyText}`);
|
|
564
|
-
break;
|
|
565
|
-
}
|
|
566
|
-
case "stickies": {
|
|
567
|
-
if (!stickyManager || !setStickyNotes) {
|
|
568
|
-
addMessage("system", "Sticky notes not available.");
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
if (args[0] === "rm" && args[1]) {
|
|
572
|
-
const num = parseInt(args[1], 10);
|
|
573
|
-
if (isNaN(num)) {
|
|
574
|
-
addMessage("system", "Usage: /stickies rm <number>");
|
|
575
|
-
break;
|
|
576
|
-
}
|
|
577
|
-
const removed = stickyManager.remove(num);
|
|
578
|
-
setStickyNotes(stickyManager.list());
|
|
579
|
-
addMessage("system", removed ? `Removed sticky #${num}` : `Sticky #${num} not found`);
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
const notes = stickyManager.list();
|
|
583
|
-
if (notes.length === 0) {
|
|
584
|
-
addMessage("system", "No sticky notes. Use /sticky <text> to add one.");
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
const listing = notes.map((n) => ` [${n.num}] ${n.text}`).join("\n");
|
|
588
|
-
addMessage("system", `Sticky notes:\n${listing}`);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
break;
|
|
592
|
-
}
|
|
593
|
-
case "memory": {
|
|
594
|
-
if (!memoryStore) {
|
|
595
|
-
addMessage("system", "Memory system not initialized.");
|
|
596
|
-
break;
|
|
597
|
-
}
|
|
598
|
-
const memPath = args[0] ?? "/";
|
|
599
|
-
const tree = memoryStore.formatTree(memPath);
|
|
600
|
-
addMessage("system", `Memory tree (${memPath}):\n${tree}`);
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
case "clear":
|
|
604
|
-
setMessages([]);
|
|
605
|
-
break;
|
|
606
|
-
case "quit":
|
|
607
|
-
case "exit":
|
|
608
|
-
process.exit(0);
|
|
609
|
-
default:
|
|
610
|
-
addMessage("system", `Unknown command: /${cmd}. Try /help`);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
function handleMachineCommand(args, addMessage, connectionPool) {
|
|
614
|
-
const subCmd = args[0];
|
|
615
|
-
if (!subCmd || subCmd === "list") {
|
|
616
|
-
const machines = loadMachines();
|
|
617
|
-
if (machines.length === 0) {
|
|
618
|
-
addMessage("system", "No machines configured.\nUsage: /machine add <id> <user@host[:port]> [--key <path>]");
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
const lines = machines.map((m) => {
|
|
622
|
-
const status = connectionPool?.getStatus(m.id);
|
|
623
|
-
let statusText = status?.connected ? "◆ connected" : "◇ disconnected";
|
|
624
|
-
if (!status?.connected && status?.error) {
|
|
625
|
-
statusText += ` — ${status.error}`;
|
|
626
|
-
}
|
|
627
|
-
return ` ${m.id} ${m.username}@${m.host}:${m.port} [${m.authMethod}] ${statusText}`;
|
|
628
|
-
});
|
|
629
|
-
addMessage("system", `Machines:\n${lines.join("\n")}`);
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (subCmd === "add") {
|
|
633
|
-
const id = args[1];
|
|
634
|
-
const spec = args[2];
|
|
635
|
-
if (!id || !spec) {
|
|
636
|
-
addMessage("system", "Usage: /machine add <id> <user@host[:port]> [--key <path>]");
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
const options = {};
|
|
640
|
-
for (let i = 3; i < args.length; i++) {
|
|
641
|
-
if (args[i] === "--key" && args[i + 1]) {
|
|
642
|
-
options.key = args[++i];
|
|
643
|
-
}
|
|
644
|
-
else if (args[i] === "--auth" && args[i + 1]) {
|
|
645
|
-
options.auth = args[++i];
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
try {
|
|
649
|
-
const machine = parseMachineSpec(id, spec, options);
|
|
650
|
-
addMachineConfig(machine);
|
|
651
|
-
connectionPool?.addMachine(machine);
|
|
652
|
-
addMessage("system", `Added machine "${id}" (${machine.username}@${machine.host}:${machine.port}). Connecting...`);
|
|
653
|
-
connectionPool?.connect(id).then(() => addMessage("system", `Machine "${id}" connected ◆`), (err) => addMessage("error", `Machine "${id}" added but connection failed: ${formatError(err)}\nThe agent can still try to connect later.`));
|
|
654
|
-
}
|
|
655
|
-
catch (err) {
|
|
656
|
-
addMessage("error", `Failed to add machine: ${formatError(err)}`);
|
|
657
|
-
}
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
if (subCmd === "rm" || subCmd === "remove") {
|
|
661
|
-
const id = args[1];
|
|
662
|
-
if (!id) {
|
|
663
|
-
addMessage("system", "Usage: /machine rm <id>");
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
if (removeMachineConfig(id)) {
|
|
667
|
-
connectionPool?.removeMachine(id);
|
|
668
|
-
addMessage("system", `Removed machine "${id}"`);
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
addMessage("error", `Machine "${id}" not found`);
|
|
672
|
-
}
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
addMessage("system", "Usage: /machine <add|rm|list>");
|
|
676
|
-
}
|
|
677
|
-
// Stash the last session listing so /resume <n> can look up by index
|
|
678
|
-
let lastSessionListing = [];
|
|
679
|
-
function handleResumeCommand(args, orchestrator, addMessage, setMessages) {
|
|
680
|
-
const index = args[0] ? Number.parseInt(args[0], 10) : NaN;
|
|
681
|
-
// --- /resume (no args) — list recent sessions ---
|
|
682
|
-
if (Number.isNaN(index)) {
|
|
683
|
-
const sessions = orchestrator.sessionStore.listSessionSummaries(20);
|
|
684
|
-
if (sessions.length === 0) {
|
|
685
|
-
addMessage("system", "No past sessions found.");
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
lastSessionListing = sessions;
|
|
689
|
-
const lines = sessions.map((s, i) => {
|
|
690
|
-
const date = new Date(s.lastActiveAt).toLocaleString();
|
|
691
|
-
const provider = s.provider;
|
|
692
|
-
const preview = s.firstUserMessage ?? "(no messages)";
|
|
693
|
-
const msgs = `${s.messageCount} msg${s.messageCount !== 1 ? "s" : ""}`;
|
|
694
|
-
return ` ${i + 1}. [${date}] ${provider} (${msgs})\n ${preview}`;
|
|
695
|
-
});
|
|
696
|
-
addMessage("system", `Recent sessions:\n${lines.join("\n")}\n\nUse /resume <number> to resume a session.`);
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
// --- /resume <number> — resume by index ---
|
|
700
|
-
if (index < 1 || index > lastSessionListing.length) {
|
|
701
|
-
addMessage("system", lastSessionListing.length === 0
|
|
702
|
-
? "Run /resume first to list sessions."
|
|
703
|
-
: `Invalid index. Choose 1-${lastSessionListing.length}.`);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const target = lastSessionListing[index - 1];
|
|
707
|
-
addMessage("system", `Resuming session from ${new Date(target.lastActiveAt).toLocaleString()}...`);
|
|
708
|
-
// Load stored messages and restore them into the UI
|
|
709
|
-
const storedMessages = orchestrator.sessionStore.getMessages(target.id, 500);
|
|
710
|
-
// Build Message[] from stored messages, resetting the id counter
|
|
711
|
-
const restored = storedMessages
|
|
712
|
-
.filter((m) => m.role === "user" || m.role === "assistant")
|
|
713
|
-
.map((m) => ({
|
|
714
|
-
id: ++messageIdCounter,
|
|
715
|
-
role: m.role,
|
|
716
|
-
content: m.content,
|
|
717
|
-
}));
|
|
718
|
-
setMessages(restored);
|
|
719
|
-
// Tell the orchestrator / provider to resume the session
|
|
720
|
-
orchestrator.resumeSession(target.id).then(() => addMessage("system", `Session resumed (${target.provider}, ${storedMessages.length} messages loaded)`), (err) => addMessage("error", `Failed to resume session: ${formatError(err)}`));
|
|
721
|
-
}
|
|
722
|
-
const WRITEUP_SYSTEM_PROMPT = `You are a scientific writing assistant. You will receive the full transcript of an ML experiment session — including the researcher's goals, the agent's actions, tool calls, metric results, and conclusions.
|
|
723
|
-
|
|
724
|
-
Your task: produce a clean, structured experiment writeup. Write it as a practitioner's report, not an academic paper. Be concise but thorough.
|
|
725
|
-
|
|
726
|
-
## Format
|
|
727
|
-
|
|
728
|
-
# [Title — infer from the goal]
|
|
729
|
-
|
|
730
|
-
## Objective
|
|
731
|
-
What was the researcher trying to achieve?
|
|
732
|
-
|
|
733
|
-
## Setup
|
|
734
|
-
- Model architecture, dataset, hardware
|
|
735
|
-
- Key hyperparameters and configuration
|
|
736
|
-
|
|
737
|
-
## Experiments
|
|
738
|
-
For each distinct experiment/run:
|
|
739
|
-
- What was tried and why
|
|
740
|
-
- Key metrics (include actual numbers)
|
|
741
|
-
- Whether it improved over the previous best
|
|
742
|
-
|
|
743
|
-
## Results
|
|
744
|
-
- Best configuration found
|
|
745
|
-
- Final metric values
|
|
746
|
-
- Comparison to baseline / starting point
|
|
747
|
-
|
|
748
|
-
## Observations
|
|
749
|
-
- What worked, what didn't
|
|
750
|
-
- Surprising findings
|
|
751
|
-
- Hypotheses about why certain changes helped/hurt
|
|
752
|
-
|
|
753
|
-
## Next Steps (if applicable)
|
|
754
|
-
- Promising directions not yet explored
|
|
755
|
-
- Known limitations
|
|
756
|
-
|
|
757
|
-
Keep the writing direct and data-driven. Use actual metric values from the transcript. Do not invent data.`;
|
|
758
|
-
async function handleWriteup(orchestrator, messages, addMessage, updateMessage, setIsStreaming) {
|
|
759
|
-
if (messages.length === 0) {
|
|
760
|
-
addMessage("system", "No conversation to write up.");
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
// Build a transcript from the conversation
|
|
764
|
-
const transcript = messages
|
|
765
|
-
.map((m) => {
|
|
766
|
-
if (m.role === "user")
|
|
767
|
-
return `[USER] ${m.content}`;
|
|
768
|
-
if (m.role === "assistant")
|
|
769
|
-
return `[ASSISTANT] ${m.content}`;
|
|
770
|
-
if (m.role === "tool" && m.tool) {
|
|
771
|
-
const result = m.tool.result ? `\nResult: ${m.tool.result}` : "";
|
|
772
|
-
return `[TOOL: ${m.tool.name}] ${JSON.stringify(m.tool.args)}${result}`;
|
|
773
|
-
}
|
|
774
|
-
if (m.role === "system")
|
|
775
|
-
return `[SYSTEM] ${m.content}`;
|
|
776
|
-
if (m.role === "error")
|
|
777
|
-
return `[ERROR] ${m.content}`;
|
|
778
|
-
return "";
|
|
779
|
-
})
|
|
780
|
-
.filter(Boolean)
|
|
781
|
-
.join("\n\n");
|
|
782
|
-
addMessage("system", "Generating writeup...");
|
|
783
|
-
setIsStreaming(true);
|
|
784
|
-
try {
|
|
785
|
-
// Get the active provider and create a one-shot session for the writeup
|
|
786
|
-
const provider = orchestrator.currentProvider;
|
|
787
|
-
if (!provider) {
|
|
788
|
-
addMessage("error", "No active provider");
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
const writeupSession = await provider.createSession({
|
|
792
|
-
systemPrompt: WRITEUP_SYSTEM_PROMPT,
|
|
793
|
-
});
|
|
794
|
-
try {
|
|
795
|
-
let writeupText = "";
|
|
796
|
-
let writeupMsgId = null;
|
|
797
|
-
for await (const event of provider.send(writeupSession, `Here is the full experiment session transcript:\n\n${transcript}`, [])) {
|
|
798
|
-
if (event.type === "text" && event.delta) {
|
|
799
|
-
writeupText += event.delta;
|
|
800
|
-
if (writeupMsgId === null) {
|
|
801
|
-
writeupMsgId = addMessage("assistant", writeupText);
|
|
802
|
-
}
|
|
803
|
-
else {
|
|
804
|
-
updateMessage(writeupMsgId, { content: writeupText });
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
finally {
|
|
810
|
-
await provider.closeSession(writeupSession).catch(() => { });
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
catch (err) {
|
|
814
|
-
addMessage("error", `Writeup failed: ${formatError(err)}`);
|
|
815
|
-
}
|
|
816
|
-
finally {
|
|
817
|
-
setIsStreaming(false);
|
|
818
|
-
}
|
|
342
|
+
/** Compact header for headless mode — shows agent name prominently. */
|
|
343
|
+
function HeadlessHeader({ agentName, width }) {
|
|
344
|
+
const label = ` ${G.brand} ${agentName} `;
|
|
345
|
+
const fill = Math.max(0, width - label.length);
|
|
346
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: C.bright, bold: true, children: label }), _jsx(Text, { color: C.primary, children: G.rule.repeat(fill) })] }));
|
|
819
347
|
}
|
|
820
348
|
/** Single header line: logo on the left, panel labels right-aligned in each half. */
|
|
821
349
|
function HeaderWithPanels({ width }) {
|