wispy-cli 2.7.17 → 2.7.19
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/bin/wispy.mjs +188 -0
- package/core/oauth-flows.mjs +102 -0
- package/core/onboarding.mjs +47 -2
- package/lib/wispy-tui.mjs +433 -254
- package/package.json +1 -1
package/lib/wispy-tui.mjs
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* wispy-tui.mjs — Workspace OS TUI for Wispy v2.
|
|
3
|
+
* wispy-tui.mjs — Workspace OS TUI for Wispy v2.7+
|
|
4
4
|
*
|
|
5
5
|
* Multi-panel workspace interface:
|
|
6
|
-
* - Left sidebar: Workstreams,
|
|
6
|
+
* - Left sidebar: Workstreams, Agent, Sub-agents, Memory, Browser, Budget
|
|
7
7
|
* - Main area: Chat / Overview / Agents / Memory / Audit / Settings
|
|
8
8
|
* - Bottom: Action Timeline bar + Input
|
|
9
|
-
* - Overlays: Approval dialogs,
|
|
9
|
+
* - Overlays: Approval dialogs, Help, Command Palette
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import React, { useState, useEffect, useRef, useCallback
|
|
13
|
-
import { render, Box, Text, useApp,
|
|
12
|
+
import React, { useState, useEffect, useRef, useCallback } from "react";
|
|
13
|
+
import { render, Box, Text, useApp, useInput, useStdout } from "ink";
|
|
14
14
|
import Spinner from "ink-spinner";
|
|
15
15
|
import TextInput from "ink-text-input";
|
|
16
16
|
|
|
@@ -19,9 +19,21 @@ import { COMMANDS, filterCommands } from "./command-registry.mjs";
|
|
|
19
19
|
import os from "node:os";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { readFile, writeFile, readdir, stat, mkdir } from "node:fs/promises";
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
22
24
|
|
|
23
25
|
import { WispyEngine, CONVERSATIONS_DIR, PROVIDERS, WISPY_DIR, MEMORY_DIR } from "../core/index.mjs";
|
|
24
26
|
|
|
27
|
+
// ─── Version ──────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
let PKG_VERSION = "?";
|
|
30
|
+
try {
|
|
31
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
33
|
+
const pkgRaw = await readFile(pkgPath, "utf8");
|
|
34
|
+
PKG_VERSION = JSON.parse(pkgRaw).version ?? "?";
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
25
37
|
// ─── Parse CLI args ──────────────────────────────────────────────────────────
|
|
26
38
|
|
|
27
39
|
const rawArgs = process.argv.slice(2);
|
|
@@ -34,13 +46,13 @@ const INITIAL_WORKSTREAM =
|
|
|
34
46
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
35
47
|
|
|
36
48
|
const VIEWS = ["chat", "overview", "agents", "memory", "audit", "settings"];
|
|
37
|
-
const SIDEBAR_WIDTH =
|
|
49
|
+
const SIDEBAR_WIDTH = 18;
|
|
38
50
|
const TIMELINE_LINES = 3;
|
|
39
51
|
|
|
40
52
|
const TOOL_ICONS = {
|
|
41
53
|
read_file: "[file]", write_file: "[edit]", file_edit: "[edit]", run_command: "[exec]",
|
|
42
54
|
git: "[git]", web_search: "[search]", web_fetch: "[web]", list_directory: "[dir]",
|
|
43
|
-
spawn_subagent: "[sub
|
|
55
|
+
spawn_subagent: "[sub]", spawn_agent: "[agent]", memory_save: "[save]",
|
|
44
56
|
memory_search: "[find]", memory_list: "[list]", delete_file: "[delete]",
|
|
45
57
|
node_execute: "[run]", update_work_context: "[update]",
|
|
46
58
|
};
|
|
@@ -64,6 +76,13 @@ function fmtRelTime(iso) {
|
|
|
64
76
|
} catch { return ""; }
|
|
65
77
|
}
|
|
66
78
|
|
|
79
|
+
function fmtDuration(ms) {
|
|
80
|
+
if (!ms || ms < 0) return "";
|
|
81
|
+
if (ms < 1000) return `${ms}ms`;
|
|
82
|
+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
|
|
83
|
+
return `${Math.floor(ms / 60_000)}m${Math.floor((ms % 60_000) / 1000)}s`;
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
function truncate(str, n) {
|
|
68
87
|
if (!str) return "";
|
|
69
88
|
return str.length > n ? str.slice(0, n - 1) + "…" : str;
|
|
@@ -82,13 +101,13 @@ function renderMarkdown(text, maxWidth = 60) {
|
|
|
82
101
|
if (line.startsWith("- ") || line.startsWith("* ")) {
|
|
83
102
|
return React.createElement(Box, { key: i },
|
|
84
103
|
React.createElement(Text, { color: "green" }, "• "),
|
|
85
|
-
React.createElement(Text,
|
|
104
|
+
React.createElement(Text, { wrap: "wrap" }, line.slice(2)));
|
|
86
105
|
}
|
|
87
106
|
if (/^\d+\.\s/.test(line)) {
|
|
88
107
|
const match = line.match(/^(\d+\.\s)(.*)/);
|
|
89
108
|
return React.createElement(Box, { key: i },
|
|
90
109
|
React.createElement(Text, { color: "yellow" }, match[1]),
|
|
91
|
-
React.createElement(Text,
|
|
110
|
+
React.createElement(Text, { wrap: "wrap" }, match[2]));
|
|
92
111
|
}
|
|
93
112
|
if (line.includes("**")) {
|
|
94
113
|
const parts = line.split(/(\*\*[^*]+\*\*)/g);
|
|
@@ -109,119 +128,111 @@ function renderMarkdown(text, maxWidth = 60) {
|
|
|
109
128
|
// ─── Status Bar ──────────────────────────────────────────────────────────────
|
|
110
129
|
|
|
111
130
|
function StatusBar({ workstream, model, provider, permMode, syncStatus, tokens, cost, pendingApprovals, view, termWidth }) {
|
|
112
|
-
const providerLabel = PROVIDERS[provider]?.label?.split(" ")[0] ?? provider ?? "?";
|
|
113
|
-
const costStr = cost > 0 ? `$${cost.toFixed(4)}` : "";
|
|
114
|
-
const permIcon = permMode === "approve" ? "🔐" : permMode === "notify" ? "📋" : "✅";
|
|
115
|
-
const syncIcon = syncStatus === "auto" ? "💾" : syncStatus === "manual" ? "⏸" : "🔴";
|
|
116
|
-
const viewLabel = view.toUpperCase();
|
|
117
|
-
|
|
118
131
|
const wide = termWidth >= 100;
|
|
119
132
|
|
|
120
133
|
return React.createElement(
|
|
121
134
|
Box, { paddingX: 1, backgroundColor: "green", width: "100%" },
|
|
122
|
-
React.createElement(Text, { color: "black", bold: true }, "
|
|
135
|
+
React.createElement(Text, { color: "black", bold: true }, "Wispy"),
|
|
123
136
|
React.createElement(Text, { color: "black" }, " ─ "),
|
|
124
137
|
React.createElement(Text, { color: "black", bold: true }, workstream),
|
|
125
138
|
React.createElement(Text, { color: "black" }, " ─ "),
|
|
126
139
|
React.createElement(Text, { color: "black" }, truncate(model ?? "?", 20)),
|
|
127
|
-
wide ? React.createElement(Text, { color: "black" }, ` ─ ${
|
|
128
|
-
wide &&
|
|
129
|
-
wide && costStr ? React.createElement(Text, { color: "black", dimColor: true }, ` ─ ${costStr}`) : null,
|
|
140
|
+
wide ? React.createElement(Text, { color: "black" }, ` ─ ${permMode ?? "approve"}`) : null,
|
|
141
|
+
wide && cost > 0 ? React.createElement(Text, { color: "black", dimColor: true }, ` ─ $${cost.toFixed(4)}`) : null,
|
|
130
142
|
pendingApprovals > 0
|
|
131
|
-
? React.createElement(Text, { color: "black", bold: true }, ` ─
|
|
143
|
+
? React.createElement(Text, { color: "black", bold: true }, ` ─ ! ${pendingApprovals} pending`)
|
|
132
144
|
: null,
|
|
133
|
-
React.createElement(Text, { color: "black", dimColor: true }, ` [${
|
|
145
|
+
React.createElement(Text, { color: "black", dimColor: true }, ` [${view.toUpperCase()}] ? for help`),
|
|
134
146
|
);
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
// ─── Left Sidebar ─────────────────────────────────────────────────────────────
|
|
138
150
|
|
|
139
|
-
function Sidebar({ workstreams, activeWorkstream, agents, memoryCount, userModelLoaded, cronCount, syncStatus,
|
|
140
|
-
const agentRunning = agents.filter(a => a.status === "running");
|
|
141
|
-
const agentPending = agents.filter(a => a.status === "pending");
|
|
142
|
-
|
|
143
|
-
const agentIcon = (status) => {
|
|
144
|
-
if (status === "running") return "🟢";
|
|
145
|
-
if (status === "pending") return "🟡";
|
|
146
|
-
if (status === "completed" || status === "done") return "✅";
|
|
147
|
-
return "❌";
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const syncIcon = syncStatus === "auto" ? "🔄 auto" : syncStatus === "manual" ? "⏸ manual" : "❌ off";
|
|
151
|
-
|
|
151
|
+
function Sidebar({ workstreams, activeWorkstream, activeAgent, agents, memoryCount, userModelLoaded, cronCount, syncStatus, browserStatus, budgetSpent, maxBudget, onSelectWorkstream }) {
|
|
152
152
|
const rows = [];
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
154
|
+
const divider = (key) => React.createElement(Box, {
|
|
155
|
+
key, borderStyle: "single",
|
|
156
|
+
borderTop: false, borderRight: false, borderLeft: false, borderBottom: true,
|
|
157
|
+
borderColor: "gray", width: SIDEBAR_WIDTH,
|
|
158
|
+
});
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
// ── WORKSTREAMS ──
|
|
161
|
+
rows.push(React.createElement(Box, { key: "ws-h", paddingLeft: 1 },
|
|
162
|
+
React.createElement(Text, { bold: true, dimColor: true }, "WORKSTREAMS"),
|
|
163
|
+
));
|
|
164
|
+
workstreams.forEach((ws) => {
|
|
161
165
|
const isActive = ws === activeWorkstream;
|
|
162
|
-
rows.push(React.createElement(
|
|
163
|
-
Box, { key: `ws-${ws}`, paddingLeft: 1 },
|
|
166
|
+
rows.push(React.createElement(Box, { key: `ws-${ws}`, paddingLeft: 1 },
|
|
164
167
|
React.createElement(Text, { color: isActive ? "green" : undefined, bold: isActive },
|
|
165
|
-
`${isActive ? "
|
|
168
|
+
`${isActive ? "●" : "◯"} ${truncate(ws, SIDEBAR_WIDTH - 3)}`),
|
|
166
169
|
));
|
|
167
170
|
});
|
|
168
171
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Box, { key: "
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
// ── ACTIVE AGENT ──
|
|
173
|
+
if (activeAgent && activeAgent !== "default") {
|
|
174
|
+
rows.push(divider("d-agent"));
|
|
175
|
+
rows.push(React.createElement(Box, { key: "agent-h", paddingLeft: 1 },
|
|
176
|
+
React.createElement(Text, { bold: true, dimColor: true }, "AGENT"),
|
|
177
|
+
));
|
|
178
|
+
rows.push(React.createElement(Box, { key: "agent-v", paddingLeft: 1 },
|
|
179
|
+
React.createElement(Text, { color: "cyan" }, truncate(activeAgent, SIDEBAR_WIDTH - 2)),
|
|
180
|
+
));
|
|
181
|
+
}
|
|
175
182
|
|
|
183
|
+
// ── SUB-AGENTS ──
|
|
184
|
+
rows.push(divider("d-sub"));
|
|
185
|
+
rows.push(React.createElement(Box, { key: "sub-h", paddingLeft: 1 },
|
|
186
|
+
React.createElement(Text, { bold: true, dimColor: true }, `AGENTS `),
|
|
187
|
+
React.createElement(Text, { color: agents.length > 0 ? "yellow" : "gray" }, `${agents.length}`),
|
|
188
|
+
));
|
|
176
189
|
if (agents.length === 0) {
|
|
177
|
-
rows.push(React.createElement(
|
|
178
|
-
Box, { key: "no-agents", paddingLeft: 1 },
|
|
190
|
+
rows.push(React.createElement(Box, { key: "sub-none", paddingLeft: 1 },
|
|
179
191
|
React.createElement(Text, { dimColor: true }, " (none)"),
|
|
180
192
|
));
|
|
181
193
|
} else {
|
|
182
194
|
agents.slice(0, 4).forEach((a, i) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
React.createElement(Text, {
|
|
195
|
+
const isRunning = a.status === "running";
|
|
196
|
+
rows.push(React.createElement(Box, { key: `sub-${i}`, paddingLeft: 1 },
|
|
197
|
+
React.createElement(Text, { color: isRunning ? "green" : "gray" }, isRunning ? "● " : "◯ "),
|
|
198
|
+
React.createElement(Text, { dimColor: !isRunning }, truncate(a.label ?? a.role ?? "agent", SIDEBAR_WIDTH - 4)),
|
|
186
199
|
));
|
|
187
200
|
});
|
|
188
201
|
}
|
|
189
202
|
|
|
190
|
-
//
|
|
191
|
-
rows.push(
|
|
192
|
-
rows.push(React.createElement(
|
|
193
|
-
|
|
194
|
-
React.createElement(Text, {
|
|
195
|
-
));
|
|
196
|
-
rows.push(React.createElement(
|
|
197
|
-
Box, { key: "mem-files", paddingLeft: 1 },
|
|
198
|
-
React.createElement(Text, {}, `📝${memoryCount} files`),
|
|
199
|
-
));
|
|
200
|
-
rows.push(React.createElement(
|
|
201
|
-
Box, { key: "mem-model", paddingLeft: 1 },
|
|
202
|
-
React.createElement(Text, {}, `🧠model ${userModelLoaded ? "✅" : "❌"}`),
|
|
203
|
+
// ── MEMORY ──
|
|
204
|
+
rows.push(divider("d-mem"));
|
|
205
|
+
rows.push(React.createElement(Box, { key: "mem-h", paddingLeft: 1 },
|
|
206
|
+
React.createElement(Text, { bold: true, dimColor: true }, "MEMORY "),
|
|
207
|
+
React.createElement(Text, { color: "cyan" }, `${memoryCount}`),
|
|
203
208
|
));
|
|
204
209
|
|
|
205
|
-
//
|
|
206
|
-
rows.push(
|
|
207
|
-
rows.push(React.createElement(
|
|
208
|
-
|
|
209
|
-
React.createElement(Text, { bold: true, dimColor: true }, "CRON"),
|
|
210
|
-
));
|
|
211
|
-
rows.push(React.createElement(
|
|
212
|
-
Box, { key: "cron-jobs", paddingLeft: 1 },
|
|
213
|
-
React.createElement(Text, {}, `⏰${cronCount} jobs`),
|
|
210
|
+
// ── BROWSER ──
|
|
211
|
+
rows.push(divider("d-br"));
|
|
212
|
+
rows.push(React.createElement(Box, { key: "br-h", paddingLeft: 1 },
|
|
213
|
+
React.createElement(Text, { bold: true, dimColor: true }, "BROWSER"),
|
|
214
214
|
));
|
|
215
|
+
if (browserStatus?.session) {
|
|
216
|
+
rows.push(React.createElement(Box, { key: "br-v", paddingLeft: 1 },
|
|
217
|
+
React.createElement(Text, { color: "green" }, "● "),
|
|
218
|
+
React.createElement(Text, {}, truncate(browserStatus.session.browser ?? "connected", SIDEBAR_WIDTH - 4)),
|
|
219
|
+
));
|
|
220
|
+
} else {
|
|
221
|
+
rows.push(React.createElement(Box, { key: "br-v", paddingLeft: 1 },
|
|
222
|
+
React.createElement(Text, { dimColor: true }, "◯ off"),
|
|
223
|
+
));
|
|
224
|
+
}
|
|
215
225
|
|
|
216
|
-
//
|
|
217
|
-
rows.push(
|
|
218
|
-
rows.push(React.createElement(
|
|
219
|
-
|
|
220
|
-
React.createElement(Text, { bold: true, dimColor: true }, "SYNC"),
|
|
226
|
+
// ── BUDGET ──
|
|
227
|
+
rows.push(divider("d-bud"));
|
|
228
|
+
rows.push(React.createElement(Box, { key: "bud-h", paddingLeft: 1 },
|
|
229
|
+
React.createElement(Text, { bold: true, dimColor: true }, "BUDGET"),
|
|
221
230
|
));
|
|
222
|
-
rows.push(React.createElement(
|
|
223
|
-
|
|
224
|
-
|
|
231
|
+
rows.push(React.createElement(Box, { key: "bud-v", paddingLeft: 1 },
|
|
232
|
+
React.createElement(Text, { color: budgetSpent > 0 ? "yellow" : "gray" },
|
|
233
|
+
budgetSpent > 0
|
|
234
|
+
? `$${budgetSpent.toFixed(3)}${maxBudget ? `/$${maxBudget.toFixed(2)}` : ""}`
|
|
235
|
+
: "$0.000"),
|
|
225
236
|
));
|
|
226
237
|
|
|
227
238
|
return React.createElement(
|
|
@@ -241,18 +252,19 @@ function Sidebar({ workstreams, activeWorkstream, agents, memoryCount, userModel
|
|
|
241
252
|
// ─── Chat View ───────────────────────────────────────────────────────────────
|
|
242
253
|
|
|
243
254
|
function ToolLine({ name, args, receipt }) {
|
|
244
|
-
const icon = TOOL_ICONS[name] ?? "
|
|
255
|
+
const icon = TOOL_ICONS[name] ?? "[tool]";
|
|
245
256
|
const primaryArg = args?.path ?? args?.command ?? args?.query ?? args?.task ?? args?.key ?? "";
|
|
246
257
|
const statusIcon = receipt
|
|
247
|
-
? (receipt.success ? "
|
|
248
|
-
: "
|
|
258
|
+
? (receipt.success ? "✓" : "✗")
|
|
259
|
+
: "●";
|
|
260
|
+
const statusColor = receipt ? (receipt.success ? "green" : "red") : "yellow";
|
|
249
261
|
|
|
250
262
|
return React.createElement(
|
|
251
263
|
Box, { flexDirection: "column", paddingLeft: 3 },
|
|
252
264
|
React.createElement(
|
|
253
265
|
Box, {},
|
|
254
|
-
React.createElement(Text, { color:
|
|
255
|
-
|
|
266
|
+
React.createElement(Text, { color: statusColor }, statusIcon + " "),
|
|
267
|
+
React.createElement(Text, { color: "cyan", dimColor: true }, `${icon} ${name}`),
|
|
256
268
|
primaryArg ? React.createElement(Text, { dimColor: true }, ` → ${truncate(primaryArg, 35)}`) : null,
|
|
257
269
|
),
|
|
258
270
|
receipt?.diff?.unified ? React.createElement(MiniDiff, { unified: receipt.diff.unified, filePath: args?.path }) : null,
|
|
@@ -280,7 +292,7 @@ function ChatMessage({ msg, mainWidth }) {
|
|
|
280
292
|
return React.createElement(
|
|
281
293
|
Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1 },
|
|
282
294
|
React.createElement(Box, {},
|
|
283
|
-
React.createElement(Text, { color: "green", bold: true }, "
|
|
295
|
+
React.createElement(Text, { color: "green", bold: true }, "you › "),
|
|
284
296
|
React.createElement(Text, { color: "white", wrap: "wrap" }, msg.content)));
|
|
285
297
|
}
|
|
286
298
|
if (msg.role === "tool_call") {
|
|
@@ -289,7 +301,7 @@ function ChatMessage({ msg, mainWidth }) {
|
|
|
289
301
|
if (msg.role === "assistant") {
|
|
290
302
|
return React.createElement(
|
|
291
303
|
Box, { flexDirection: "column", paddingLeft: 1, marginTop: 1 },
|
|
292
|
-
React.createElement(Text, { color: "cyan", bold: true }, "
|
|
304
|
+
React.createElement(Text, { color: "cyan", bold: true }, "wispy ›"),
|
|
293
305
|
React.createElement(Box, { flexDirection: "column", paddingLeft: 2 },
|
|
294
306
|
...renderMarkdown(msg.content, mainWidth - 4)),
|
|
295
307
|
);
|
|
@@ -310,7 +322,7 @@ function ChatView({ messages, loading, pendingApproval, onApprove, onDeny, onDry
|
|
|
310
322
|
displayMessages.length === 0
|
|
311
323
|
? React.createElement(
|
|
312
324
|
Box, { flexGrow: 1, alignItems: "center", justifyContent: "center", flexDirection: "column" },
|
|
313
|
-
React.createElement(Text, { dimColor: true }, "
|
|
325
|
+
React.createElement(Text, { dimColor: true }, "Wispy — AI workspace assistant"),
|
|
314
326
|
React.createElement(Text, { dimColor: true }, "Type a message to start. ? for help."),
|
|
315
327
|
)
|
|
316
328
|
: React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
|
|
@@ -336,24 +348,36 @@ function ChatView({ messages, loading, pendingApproval, onApprove, onDeny, onDry
|
|
|
336
348
|
|
|
337
349
|
// ─── Overview View ────────────────────────────────────────────────────────────
|
|
338
350
|
|
|
339
|
-
function OverviewView({ workstreams, activeWorkstream, overviewData, mainWidth }) {
|
|
351
|
+
function OverviewView({ workstreams, activeWorkstream, overviewData, browserStatus, mainWidth }) {
|
|
340
352
|
return React.createElement(
|
|
341
353
|
Box, { flexDirection: "column", flexGrow: 1, paddingX: 1 },
|
|
342
354
|
React.createElement(Text, { bold: true, color: "green" }, "Workstream Overview"),
|
|
343
355
|
React.createElement(Box, { height: 1 }),
|
|
344
|
-
|
|
356
|
+
// Browser status section
|
|
357
|
+
React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
358
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Browser"),
|
|
359
|
+
browserStatus?.session
|
|
360
|
+
? React.createElement(Box, { paddingLeft: 2 },
|
|
361
|
+
React.createElement(Text, { color: "green" }, "● "),
|
|
362
|
+
React.createElement(Text, {}, `Connected — ${browserStatus.session.browser ?? "unknown"}`))
|
|
363
|
+
: React.createElement(Box, { paddingLeft: 2 },
|
|
364
|
+
React.createElement(Text, { dimColor: true }, "◯ Not connected")),
|
|
365
|
+
),
|
|
366
|
+
React.createElement(Text, { dimColor: true }, "─".repeat(Math.min(40, mainWidth - 4))),
|
|
367
|
+
React.createElement(Box, { height: 1 }),
|
|
368
|
+
...workstreams.map((ws) => {
|
|
345
369
|
const data = overviewData[ws] ?? {};
|
|
346
370
|
const isActive = ws === activeWorkstream;
|
|
347
371
|
return React.createElement(
|
|
348
372
|
Box, { key: ws, flexDirection: "column", marginBottom: 1 },
|
|
349
373
|
React.createElement(Box, {},
|
|
350
|
-
React.createElement(Text, { bold: true, color: isActive ? "green" : "white" },
|
|
351
|
-
|
|
374
|
+
React.createElement(Text, { bold: true, color: isActive ? "green" : "white" },
|
|
375
|
+
`${isActive ? "●" : "◯"} ${ws}`),
|
|
352
376
|
data.lastActivity
|
|
353
|
-
? React.createElement(Text, { dimColor: true }, `
|
|
377
|
+
? React.createElement(Text, { dimColor: true }, ` ${fmtRelTime(data.lastActivity)}`)
|
|
354
378
|
: null,
|
|
355
379
|
data.agents > 0
|
|
356
|
-
? React.createElement(Text, { color: "yellow" }, `
|
|
380
|
+
? React.createElement(Text, { color: "yellow" }, ` ${data.agents} agents`)
|
|
357
381
|
: null,
|
|
358
382
|
),
|
|
359
383
|
data.lastMessage
|
|
@@ -366,7 +390,7 @@ function OverviewView({ workstreams, activeWorkstream, overviewData, mainWidth }
|
|
|
366
390
|
data.workMd
|
|
367
391
|
? React.createElement(
|
|
368
392
|
Box, { paddingLeft: 2 },
|
|
369
|
-
React.createElement(Text, { dimColor: true }, "└──
|
|
393
|
+
React.createElement(Text, { dimColor: true }, "└── "),
|
|
370
394
|
React.createElement(Text, { dimColor: true, italic: true }, `"${truncate(data.workMd, mainWidth - 20)}"`),
|
|
371
395
|
)
|
|
372
396
|
: null,
|
|
@@ -377,7 +401,19 @@ function OverviewView({ workstreams, activeWorkstream, overviewData, mainWidth }
|
|
|
377
401
|
|
|
378
402
|
// ─── Agents View ─────────────────────────────────────────────────────────────
|
|
379
403
|
|
|
380
|
-
function AgentsView({ agents }) {
|
|
404
|
+
function AgentsView({ agents, agentManager }) {
|
|
405
|
+
const [builtinAgents, setBuiltinAgents] = useState([]);
|
|
406
|
+
const [progressMap, setProgressMap] = useState({});
|
|
407
|
+
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (agentManager) {
|
|
410
|
+
try {
|
|
411
|
+
const list = agentManager.list();
|
|
412
|
+
setBuiltinAgents(list);
|
|
413
|
+
} catch {}
|
|
414
|
+
}
|
|
415
|
+
}, [agentManager]);
|
|
416
|
+
|
|
381
417
|
const statusColor = (s) => {
|
|
382
418
|
if (s === "running") return "green";
|
|
383
419
|
if (s === "pending") return "yellow";
|
|
@@ -385,36 +421,67 @@ function AgentsView({ agents }) {
|
|
|
385
421
|
return "red";
|
|
386
422
|
};
|
|
387
423
|
const statusIcon = (s) => {
|
|
388
|
-
if (s === "running") return "
|
|
389
|
-
if (s === "pending") return "
|
|
390
|
-
if (s === "completed" || s === "done") return "
|
|
391
|
-
return "
|
|
424
|
+
if (s === "running") return "●";
|
|
425
|
+
if (s === "pending") return "◯";
|
|
426
|
+
if (s === "completed" || s === "done") return "✓";
|
|
427
|
+
return "✗";
|
|
392
428
|
};
|
|
393
429
|
|
|
430
|
+
const running = agents.filter(a => a.status === "running");
|
|
431
|
+
const completed = agents.filter(a => a.status === "completed" || a.status === "done");
|
|
432
|
+
|
|
394
433
|
return React.createElement(
|
|
395
434
|
Box, { flexDirection: "column", flexGrow: 1, paddingX: 1 },
|
|
396
|
-
|
|
435
|
+
// ── Custom / Built-in Agents ──
|
|
436
|
+
React.createElement(Text, { bold: true, color: "green" }, "Agent Profiles"),
|
|
437
|
+
React.createElement(Box, { height: 1 }),
|
|
438
|
+
builtinAgents.length === 0
|
|
439
|
+
? React.createElement(Text, { dimColor: true }, "No agents loaded.")
|
|
440
|
+
: React.createElement(Box, { flexDirection: "column", marginBottom: 1 },
|
|
441
|
+
...builtinAgents.slice(0, 10).map((a, i) => React.createElement(
|
|
442
|
+
Box, { key: i },
|
|
443
|
+
React.createElement(Text, { color: a.builtin ? "cyan" : "magenta" },
|
|
444
|
+
a.builtin ? " [built-in] " : " [custom] "),
|
|
445
|
+
React.createElement(Text, { bold: true }, a.name),
|
|
446
|
+
React.createElement(Text, { dimColor: true }, ` — ${truncate(a.description, 45)}`),
|
|
447
|
+
))),
|
|
448
|
+
|
|
449
|
+
React.createElement(Text, { dimColor: true }, "─".repeat(40)),
|
|
450
|
+
React.createElement(Box, { height: 1 }),
|
|
451
|
+
|
|
452
|
+
// ── Running Sub-Agents ──
|
|
453
|
+
React.createElement(Text, { bold: true, color: "green" },
|
|
454
|
+
`Sub-Agents (${running.length} running, ${completed.length} completed)`),
|
|
397
455
|
React.createElement(Box, { height: 1 }),
|
|
398
456
|
agents.length === 0
|
|
399
|
-
? React.createElement(Text, { dimColor: true }, "No agents
|
|
457
|
+
? React.createElement(Text, { dimColor: true }, "No sub-agents recorded.")
|
|
400
458
|
: React.createElement(Box, { flexDirection: "column" },
|
|
401
|
-
...agents.map((a, i) =>
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
459
|
+
...agents.map((a, i) => {
|
|
460
|
+
const runtime = a.createdAt && a.completedAt
|
|
461
|
+
? fmtDuration(new Date(a.completedAt) - new Date(a.createdAt))
|
|
462
|
+
: a.createdAt ? `${fmtRelTime(a.createdAt)}` : "";
|
|
463
|
+
return React.createElement(
|
|
464
|
+
Box, { key: i, flexDirection: "column", marginBottom: 1 },
|
|
465
|
+
React.createElement(Box, {},
|
|
466
|
+
React.createElement(Text, { color: statusColor(a.status) }, `${statusIcon(a.status)} `),
|
|
467
|
+
React.createElement(Text, { bold: true, color: statusColor(a.status) }, truncate(a.label ?? a.id ?? "agent", 20)),
|
|
468
|
+
React.createElement(Text, { dimColor: true }, ` [${a.model ?? "?"}]`),
|
|
469
|
+
runtime ? React.createElement(Text, { dimColor: true }, ` ${runtime}`) : null,
|
|
470
|
+
),
|
|
471
|
+
a.task
|
|
472
|
+
? React.createElement(
|
|
473
|
+
Box, { paddingLeft: 3 },
|
|
474
|
+
React.createElement(Text, { dimColor: true, wrap: "wrap" }, truncate(a.task, 60)),
|
|
475
|
+
)
|
|
409
476
|
: null,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
))
|
|
477
|
+
progressMap[a.id]
|
|
478
|
+
? React.createElement(Box, { paddingLeft: 3 },
|
|
479
|
+
React.createElement(Text, { color: "yellow" }, "… "),
|
|
480
|
+
React.createElement(Text, { dimColor: true, wrap: "wrap" }, truncate(progressMap[a.id], 55)),
|
|
481
|
+
)
|
|
482
|
+
: null,
|
|
483
|
+
);
|
|
484
|
+
})),
|
|
418
485
|
);
|
|
419
486
|
}
|
|
420
487
|
|
|
@@ -474,15 +541,15 @@ function AuditView({ timeline, mainWidth }) {
|
|
|
474
541
|
? React.createElement(Text, { dimColor: true }, "No actions recorded yet.")
|
|
475
542
|
: React.createElement(Box, { flexDirection: "column" },
|
|
476
543
|
...timeline.slice().reverse().slice(0, 30).map((evt, i) => {
|
|
477
|
-
const icon = TOOL_ICONS[evt.toolName] ?? "
|
|
478
|
-
const statusIcon = evt.denied ? "
|
|
544
|
+
const icon = TOOL_ICONS[evt.toolName] ?? "[tool]";
|
|
545
|
+
const statusIcon = evt.denied ? "✗" : evt.dryRun ? "◯" : evt.success ? "✓" : "●";
|
|
479
546
|
const color = evt.denied ? "red" : evt.dryRun ? "blue" : evt.success ? "green" : "yellow";
|
|
480
547
|
const ts = fmtTime(evt.timestamp);
|
|
481
|
-
const arg = evt.primaryArg ? ` → ${truncate(evt.primaryArg,
|
|
548
|
+
const arg = evt.primaryArg ? ` → ${truncate(evt.primaryArg, 28)}` : "";
|
|
482
549
|
return React.createElement(
|
|
483
550
|
Box, { key: i },
|
|
484
551
|
React.createElement(Text, { dimColor: true }, `${ts} `),
|
|
485
|
-
React.createElement(Text, {}, `${icon} `),
|
|
552
|
+
React.createElement(Text, { dimColor: true }, `${icon} `),
|
|
486
553
|
React.createElement(Text, { color: "cyan" }, evt.toolName),
|
|
487
554
|
React.createElement(Text, { dimColor: true }, arg),
|
|
488
555
|
React.createElement(Text, { color }, ` ${statusIcon}`),
|
|
@@ -495,37 +562,117 @@ function AuditView({ timeline, mainWidth }) {
|
|
|
495
562
|
|
|
496
563
|
function SettingsView({ engine }) {
|
|
497
564
|
const [config, setConfig] = useState(null);
|
|
565
|
+
const [features, setFeatures] = useState([]);
|
|
566
|
+
const [browserSt, setBrowserSt] = useState(null);
|
|
567
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
498
568
|
|
|
499
569
|
useEffect(() => {
|
|
570
|
+
let mounted = true;
|
|
500
571
|
(async () => {
|
|
501
572
|
try {
|
|
502
573
|
const { loadConfig } = await import("../core/config.mjs");
|
|
503
574
|
const cfg = await loadConfig();
|
|
504
|
-
setConfig(cfg);
|
|
575
|
+
if (mounted) setConfig(cfg);
|
|
576
|
+
} catch {}
|
|
577
|
+
|
|
578
|
+
// Load features
|
|
579
|
+
try {
|
|
580
|
+
const { getFeatureManager } = await import("../core/features.mjs");
|
|
581
|
+
const fm = getFeatureManager();
|
|
582
|
+
const list = await fm.list();
|
|
583
|
+
if (mounted) setFeatures(list);
|
|
584
|
+
} catch {}
|
|
585
|
+
|
|
586
|
+
// Load browser status
|
|
587
|
+
try {
|
|
588
|
+
const bs = engine?.browser?.status?.() ?? null;
|
|
589
|
+
if (mounted) setBrowserSt(bs);
|
|
505
590
|
} catch {}
|
|
506
591
|
})();
|
|
507
|
-
|
|
592
|
+
return () => { mounted = false; };
|
|
593
|
+
}, [refreshKey]);
|
|
594
|
+
|
|
595
|
+
const budget = engine?.budget;
|
|
596
|
+
const budgetSpent = budget?.sessionSpend ?? 0;
|
|
597
|
+
const maxBudget = budget?.maxBudgetUsd ?? null;
|
|
598
|
+
|
|
599
|
+
const personality = engine?._personality ?? config?.personality ?? "default";
|
|
600
|
+
const effort = engine?._effort ?? config?.effort ?? "medium";
|
|
601
|
+
const permMode = engine?.permissions?.getPolicy?.("run_command") ?? "approve";
|
|
602
|
+
const activeWorkstream = engine?.activeWorkstream ?? engine?._activeWorkstream ?? "default";
|
|
603
|
+
const activeAgent = engine?._activeAgent ?? config?.defaultAgent ?? "default";
|
|
508
604
|
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
["Workstream", engine.activeWorkstream ?? "default"],
|
|
513
|
-
["Permission mode", engine.permissions?.getPolicy?.("run_command") ?? "approve"],
|
|
514
|
-
["Version", "2.0.0"],
|
|
515
|
-
["Config", path.join(os.homedir(), ".wispy", "config.json")],
|
|
516
|
-
] : [];
|
|
605
|
+
const configDir = path.join(os.homedir(), ".wispy");
|
|
606
|
+
const providerLabel = config?.provider ?? engine?.provider ?? "?";
|
|
607
|
+
const modelLabel = config?.model ?? engine?.model ?? "?";
|
|
517
608
|
|
|
518
609
|
return React.createElement(
|
|
519
610
|
Box, { flexDirection: "column", flexGrow: 1, paddingX: 1 },
|
|
520
611
|
React.createElement(Text, { bold: true, color: "green" }, "Settings"),
|
|
521
612
|
React.createElement(Box, { height: 1 }),
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
613
|
+
|
|
614
|
+
// ── General ──
|
|
615
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "General"),
|
|
616
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
|
|
617
|
+
...([
|
|
618
|
+
["Version", PKG_VERSION],
|
|
619
|
+
["Config dir", configDir],
|
|
620
|
+
["Provider", providerLabel],
|
|
621
|
+
["Model", modelLabel],
|
|
622
|
+
["Personality", personality],
|
|
623
|
+
["Effort", effort],
|
|
624
|
+
["Security", permMode],
|
|
625
|
+
]).map(([k, v], i) => React.createElement(
|
|
626
|
+
Box, { key: i },
|
|
627
|
+
React.createElement(Text, { color: "white", dimColor: true }, k.padEnd(14)),
|
|
628
|
+
React.createElement(Text, { color: "white" }, v ?? "?"),
|
|
629
|
+
)),
|
|
630
|
+
),
|
|
631
|
+
|
|
632
|
+
// ── Active Session ──
|
|
633
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Active Session"),
|
|
634
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
|
|
635
|
+
React.createElement(Box, {},
|
|
636
|
+
React.createElement(Text, { dimColor: true }, "Agent".padEnd(14)),
|
|
637
|
+
React.createElement(Text, {}, activeAgent ?? "default"),
|
|
638
|
+
),
|
|
639
|
+
React.createElement(Box, {},
|
|
640
|
+
React.createElement(Text, { dimColor: true }, "Workstream".padEnd(14)),
|
|
641
|
+
React.createElement(Text, {}, activeWorkstream),
|
|
642
|
+
),
|
|
643
|
+
React.createElement(Box, {},
|
|
644
|
+
React.createElement(Text, { dimColor: true }, "Budget".padEnd(14)),
|
|
645
|
+
React.createElement(Text, { color: budgetSpent > 0 ? "yellow" : "white" },
|
|
646
|
+
`$${budgetSpent.toFixed(4)}${maxBudget != null ? ` / $${maxBudget.toFixed(2)}` : " (no limit)"}`),
|
|
647
|
+
),
|
|
648
|
+
),
|
|
649
|
+
|
|
650
|
+
// ── Features ──
|
|
651
|
+
features.length > 0 ? React.createElement(React.Fragment, null,
|
|
652
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Features"),
|
|
653
|
+
React.createElement(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1 },
|
|
654
|
+
...features.map((f, i) => React.createElement(
|
|
655
|
+
Box, { key: i },
|
|
656
|
+
React.createElement(Text, { color: f.enabled ? "green" : "red" }, f.enabled ? "✓ " : "✗ "),
|
|
657
|
+
React.createElement(Text, { color: f.enabled ? "white" : "gray" }, f.name),
|
|
658
|
+
React.createElement(Text, { dimColor: true }, ` [${f.stage}]`),
|
|
659
|
+
)),
|
|
660
|
+
),
|
|
661
|
+
) : null,
|
|
662
|
+
|
|
663
|
+
// ── Browser ──
|
|
664
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Browser"),
|
|
665
|
+
React.createElement(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
666
|
+
React.createElement(Text, { dimColor: true }, "Status".padEnd(14)),
|
|
667
|
+
browserSt?.session
|
|
668
|
+
? React.createElement(Text, { color: "green" }, `connected (${browserSt.session.browser ?? "unknown"})`)
|
|
669
|
+
: React.createElement(Text, { dimColor: true }, "disconnected"),
|
|
670
|
+
),
|
|
671
|
+
|
|
672
|
+
// ── Keys ──
|
|
527
673
|
React.createElement(Box, { height: 1 }),
|
|
528
|
-
React.createElement(Text, { dimColor: true },
|
|
674
|
+
React.createElement(Text, { dimColor: true },
|
|
675
|
+
"Keys: /model <n> /trust <level> /agent <n> /features enable <n> /cost"),
|
|
529
676
|
);
|
|
530
677
|
}
|
|
531
678
|
|
|
@@ -546,15 +693,15 @@ function TimelineBar({ events }) {
|
|
|
546
693
|
paddingX: 1,
|
|
547
694
|
},
|
|
548
695
|
...last.map((evt, i) => {
|
|
549
|
-
const icon = TOOL_ICONS[evt.toolName] ?? "
|
|
550
|
-
const statusIcon = evt.denied ? "
|
|
696
|
+
const icon = TOOL_ICONS[evt.toolName] ?? "[tool]";
|
|
697
|
+
const statusIcon = evt.denied ? "✗" : evt.dryRun ? "◯" : evt.success === null ? "●" : evt.success ? "✓" : "✗";
|
|
551
698
|
const color = evt.denied ? "red" : evt.success === null ? "yellow" : evt.success ? "green" : "red";
|
|
552
699
|
const ts = fmtTime(evt.timestamp);
|
|
553
700
|
const arg = evt.primaryArg ? ` ${truncate(evt.primaryArg, 28)}` : "";
|
|
554
701
|
return React.createElement(
|
|
555
702
|
Box, { key: i },
|
|
556
703
|
React.createElement(Text, { dimColor: true }, `${ts} `),
|
|
557
|
-
React.createElement(Text, {}, `${icon} `),
|
|
704
|
+
React.createElement(Text, { dimColor: true }, `${icon} `),
|
|
558
705
|
React.createElement(Text, { color: "cyan" }, evt.toolName),
|
|
559
706
|
React.createElement(Text, { dimColor: true }, arg),
|
|
560
707
|
React.createElement(Text, { color }, ` ${statusIcon}`),
|
|
@@ -596,14 +743,14 @@ function ApprovalDialog({ action, onApprove, onDeny, onDryRun }) {
|
|
|
596
743
|
marginY: 1,
|
|
597
744
|
marginX: 2,
|
|
598
745
|
},
|
|
599
|
-
React.createElement(Text, { bold: true, color: "yellow" }, "
|
|
746
|
+
React.createElement(Text, { bold: true, color: "yellow" }, "! Permission Required"),
|
|
600
747
|
React.createElement(Box, { height: 1 }),
|
|
601
748
|
React.createElement(Box, {},
|
|
602
749
|
React.createElement(Text, { dimColor: true }, "Tool: "),
|
|
603
750
|
React.createElement(Text, { bold: true }, action?.toolName ?? "?")),
|
|
604
751
|
argSummary ? React.createElement(Box, {},
|
|
605
752
|
React.createElement(Text, { dimColor: true }, "Action: "),
|
|
606
|
-
React.createElement(Text, {}, argSummary)) : null,
|
|
753
|
+
React.createElement(Text, { wrap: "wrap" }, argSummary)) : null,
|
|
607
754
|
React.createElement(Box, {},
|
|
608
755
|
React.createElement(Text, { dimColor: true }, "Risk: "),
|
|
609
756
|
React.createElement(Text, { bold: true, color: riskColor }, risk)),
|
|
@@ -623,22 +770,25 @@ function HelpOverlay({ onClose }) {
|
|
|
623
770
|
});
|
|
624
771
|
|
|
625
772
|
const shortcuts = [
|
|
626
|
-
["Tab", "Switch view
|
|
627
|
-
["1-
|
|
628
|
-
["n", "New workstream (in chat: prompts for name)"],
|
|
773
|
+
["Tab", "Switch view"],
|
|
774
|
+
["1-6", "Jump to view (1=chat 2=overview 3=agents 4=memory 5=audit 6=settings)"],
|
|
629
775
|
["o", "Overview view"],
|
|
630
776
|
["a", "Agents view"],
|
|
631
777
|
["m", "Memory view"],
|
|
632
778
|
["u", "Audit view"],
|
|
633
779
|
["s", "Settings view"],
|
|
634
|
-
["t", "Toggle timeline
|
|
635
|
-
["Ctrl+L", "Clear chat"],
|
|
636
|
-
["q / Ctrl+C", "Quit"],
|
|
780
|
+
["t", "Toggle timeline"],
|
|
637
781
|
["?", "Toggle this help"],
|
|
638
|
-
["
|
|
639
|
-
["/
|
|
782
|
+
["Ctrl+L", "Clear chat"],
|
|
783
|
+
["Ctrl+C / q", "Quit"],
|
|
784
|
+
["/help", "Show all slash commands"],
|
|
785
|
+
["/model <n>", "Change model"],
|
|
786
|
+
["/trust <level>", "Change security level"],
|
|
787
|
+
["/agent <n>", "Switch agent"],
|
|
788
|
+
["/features enable <n>", "Toggle feature"],
|
|
789
|
+
["/cost", "Show budget"],
|
|
640
790
|
["/clear", "Clear conversation"],
|
|
641
|
-
["/
|
|
791
|
+
["/ws <name>", "Switch workstream"],
|
|
642
792
|
];
|
|
643
793
|
|
|
644
794
|
return React.createElement(
|
|
@@ -651,12 +801,12 @@ function HelpOverlay({ onClose }) {
|
|
|
651
801
|
marginY: 1,
|
|
652
802
|
marginX: 2,
|
|
653
803
|
},
|
|
654
|
-
React.createElement(Text, { bold: true, color: "cyan" }, "
|
|
804
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Wispy — Keyboard Shortcuts"),
|
|
655
805
|
React.createElement(Box, { height: 1 }),
|
|
656
806
|
...shortcuts.map(([key, desc], i) => React.createElement(
|
|
657
807
|
Box, { key: i },
|
|
658
|
-
React.createElement(Text, { color: "yellow", bold: true }, key.padEnd(
|
|
659
|
-
React.createElement(Text, { dimColor: true }, desc),
|
|
808
|
+
React.createElement(Text, { color: "yellow", bold: true }, key.padEnd(22)),
|
|
809
|
+
React.createElement(Text, { dimColor: true, wrap: "wrap" }, desc),
|
|
660
810
|
)),
|
|
661
811
|
React.createElement(Box, { height: 1 }),
|
|
662
812
|
React.createElement(Text, { dimColor: true }, "Press ? or q to close"),
|
|
@@ -670,46 +820,32 @@ function CommandPalette({ query, onSelect, onDismiss }) {
|
|
|
670
820
|
|
|
671
821
|
const matches = filterCommands(query, COMMANDS, 8);
|
|
672
822
|
|
|
673
|
-
// Reset selection when query changes
|
|
674
823
|
useEffect(() => { setSelectedIdx(0); }, [query]);
|
|
675
824
|
|
|
676
825
|
useInput((input, key) => {
|
|
677
826
|
if (matches.length === 0) return;
|
|
678
|
-
if (key.upArrow) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
682
|
-
if (key.downArrow) {
|
|
683
|
-
setSelectedIdx(i => Math.min(matches.length - 1, i + 1));
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
if (key.return) {
|
|
687
|
-
onSelect?.(matches[selectedIdx]?.cmd ?? query);
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
if (key.escape || (key.ctrl && input === 'c')) {
|
|
691
|
-
onDismiss?.();
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
827
|
+
if (key.upArrow) { setSelectedIdx(i => Math.max(0, i - 1)); return; }
|
|
828
|
+
if (key.downArrow) { setSelectedIdx(i => Math.min(matches.length - 1, i + 1)); return; }
|
|
829
|
+
if (key.return) { onSelect?.(matches[selectedIdx]?.cmd ?? query); return; }
|
|
830
|
+
if (key.escape || (key.ctrl && input === "c")) { onDismiss?.(); return; }
|
|
694
831
|
});
|
|
695
832
|
|
|
696
833
|
if (matches.length === 0) return null;
|
|
697
834
|
|
|
698
835
|
const maxCmdLen = Math.max(...matches.map(m => m.cmd.length));
|
|
699
|
-
const width = Math.min(60, maxCmdLen + 30);
|
|
700
836
|
|
|
701
837
|
return React.createElement(
|
|
702
838
|
Box, {
|
|
703
|
-
flexDirection:
|
|
704
|
-
borderStyle:
|
|
705
|
-
borderColor:
|
|
839
|
+
flexDirection: "column",
|
|
840
|
+
borderStyle: "single",
|
|
841
|
+
borderColor: "cyan",
|
|
706
842
|
paddingX: 1,
|
|
707
843
|
marginBottom: 0,
|
|
708
844
|
},
|
|
709
845
|
React.createElement(
|
|
710
846
|
Box, { paddingX: 1 },
|
|
711
|
-
React.createElement(Text, { bold: true, color:
|
|
712
|
-
React.createElement(Text, { dimColor: true },
|
|
847
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Commands "),
|
|
848
|
+
React.createElement(Text, { dimColor: true }, "↑↓ navigate Enter select Esc dismiss"),
|
|
713
849
|
),
|
|
714
850
|
...matches.map((cmd, i) => {
|
|
715
851
|
const isActive = i === selectedIdx;
|
|
@@ -717,11 +853,12 @@ function CommandPalette({ query, onSelect, onDismiss }) {
|
|
|
717
853
|
return React.createElement(
|
|
718
854
|
Box, { key: cmd.cmd },
|
|
719
855
|
isActive
|
|
720
|
-
? React.createElement(Text, { color:
|
|
856
|
+
? React.createElement(Text, { color: "black", backgroundColor: "cyan" },
|
|
721
857
|
` ${cmdStr} ${cmd.desc} `)
|
|
722
858
|
: React.createElement(Box, {},
|
|
723
|
-
React.createElement(Text, { color:
|
|
724
|
-
React.createElement(Text, { dimColor: true },
|
|
859
|
+
React.createElement(Text, { color: "cyan" }, ` ${cmd.cmd}`),
|
|
860
|
+
React.createElement(Text, { dimColor: true },
|
|
861
|
+
" ".repeat(Math.max(1, maxCmdLen - cmd.cmd.length + 2)) + cmd.desc),
|
|
725
862
|
),
|
|
726
863
|
);
|
|
727
864
|
}),
|
|
@@ -735,8 +872,8 @@ function InputArea({ value, onChange, onSubmit, loading, workstream, view }) {
|
|
|
735
872
|
const placeholder = loading
|
|
736
873
|
? "waiting for response..."
|
|
737
874
|
: view !== "chat"
|
|
738
|
-
? `${view} mode —
|
|
739
|
-
: "Type a message… (/help for commands)";
|
|
875
|
+
? `${view} mode — Tab to switch to chat`
|
|
876
|
+
: "Type a message… (/help for commands | Tab to switch views)";
|
|
740
877
|
|
|
741
878
|
return React.createElement(
|
|
742
879
|
Box, {
|
|
@@ -849,7 +986,6 @@ async function loadOverviewData(workstreams) {
|
|
|
849
986
|
result[ws] = { lastActivity: null, lastMessage: null, agents: 0 };
|
|
850
987
|
}
|
|
851
988
|
|
|
852
|
-
// Try work.md
|
|
853
989
|
try {
|
|
854
990
|
const workMdPath = path.join(WISPY_DIR, "workstreams", ws, "work.md");
|
|
855
991
|
const wmd = await readFile(workMdPath, "utf8");
|
|
@@ -884,7 +1020,7 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
884
1020
|
const [inputValue, setInputValue] = useState("");
|
|
885
1021
|
const [loading, setLoading] = useState(false);
|
|
886
1022
|
|
|
887
|
-
// Command palette
|
|
1023
|
+
// Command palette
|
|
888
1024
|
const [showPalette, setShowPalette] = useState(false);
|
|
889
1025
|
|
|
890
1026
|
// Engine stats
|
|
@@ -904,6 +1040,9 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
904
1040
|
const [memoryQuery, setMemoryQuery] = useState("");
|
|
905
1041
|
const [cronCount, setCronCount] = useState(0);
|
|
906
1042
|
const [userModelLoaded, setUserModelLoaded] = useState(false);
|
|
1043
|
+
const [browserStatus, setBrowserStatus] = useState(null);
|
|
1044
|
+
const [budgetSpent, setBudgetSpent] = useState(0);
|
|
1045
|
+
const [maxBudget, setMaxBudget] = useState(null);
|
|
907
1046
|
|
|
908
1047
|
// Overview data
|
|
909
1048
|
const [overviewData, setOverviewData] = useState({});
|
|
@@ -911,7 +1050,6 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
911
1050
|
// Refs
|
|
912
1051
|
const approvalResolverRef = useRef(null);
|
|
913
1052
|
const conversationRef = useRef([]);
|
|
914
|
-
const engineRef = useRef(engine);
|
|
915
1053
|
|
|
916
1054
|
// ── Terminal resize ──
|
|
917
1055
|
useEffect(() => {
|
|
@@ -941,7 +1079,6 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
941
1079
|
loadSyncStatus(),
|
|
942
1080
|
]);
|
|
943
1081
|
|
|
944
|
-
// Make sure active workstream is in list
|
|
945
1082
|
const wsSet = new Set(wsList);
|
|
946
1083
|
wsSet.add(initialWorkstream);
|
|
947
1084
|
const wsFinal = Array.from(wsSet);
|
|
@@ -951,15 +1088,28 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
951
1088
|
setCronCount(cron);
|
|
952
1089
|
setSyncStatus(sync);
|
|
953
1090
|
setUserModelLoaded(memFiles.some(f => f.key === "user"));
|
|
1091
|
+
|
|
1092
|
+
// Browser status
|
|
1093
|
+
try {
|
|
1094
|
+
const bs = engine?.browser?.status?.() ?? null;
|
|
1095
|
+
setBrowserStatus(bs);
|
|
1096
|
+
} catch {}
|
|
1097
|
+
|
|
1098
|
+
// Budget
|
|
1099
|
+
try {
|
|
1100
|
+
const bud = engine?.budget;
|
|
1101
|
+
if (bud) {
|
|
1102
|
+
setBudgetSpent(bud.sessionSpend ?? 0);
|
|
1103
|
+
setMaxBudget(bud.maxBudgetUsd ?? null);
|
|
1104
|
+
}
|
|
1105
|
+
} catch {}
|
|
954
1106
|
};
|
|
955
1107
|
loadAll();
|
|
956
|
-
|
|
957
|
-
// Refresh periodically
|
|
958
1108
|
const interval = setInterval(loadAll, 15_000);
|
|
959
1109
|
return () => clearInterval(interval);
|
|
960
|
-
}, [initialWorkstream]);
|
|
1110
|
+
}, [initialWorkstream, engine]);
|
|
961
1111
|
|
|
962
|
-
// ── Load overview data ──
|
|
1112
|
+
// ── Load overview data — refresh when view switches to overview ──
|
|
963
1113
|
useEffect(() => {
|
|
964
1114
|
const refresh = async () => {
|
|
965
1115
|
const data = await loadOverviewData(workstreams);
|
|
@@ -970,6 +1120,27 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
970
1120
|
return () => clearInterval(interval);
|
|
971
1121
|
}, [workstreams]);
|
|
972
1122
|
|
|
1123
|
+
// ── Refresh view data on view switch ──
|
|
1124
|
+
useEffect(() => {
|
|
1125
|
+
if (view === "overview") {
|
|
1126
|
+
loadOverviewData(workstreams).then(setOverviewData);
|
|
1127
|
+
try { setBrowserStatus(engine?.browser?.status?.() ?? null); } catch {}
|
|
1128
|
+
}
|
|
1129
|
+
if (view === "memory") {
|
|
1130
|
+
loadMemoryFiles().then(setMemoryFiles);
|
|
1131
|
+
}
|
|
1132
|
+
if (view === "settings") {
|
|
1133
|
+
try {
|
|
1134
|
+
const bud = engine?.budget;
|
|
1135
|
+
if (bud) {
|
|
1136
|
+
setBudgetSpent(bud.sessionSpend ?? 0);
|
|
1137
|
+
setMaxBudget(bud.maxBudgetUsd ?? null);
|
|
1138
|
+
}
|
|
1139
|
+
setBrowserStatus(engine?.browser?.status?.() ?? null);
|
|
1140
|
+
} catch {}
|
|
1141
|
+
}
|
|
1142
|
+
}, [view]);
|
|
1143
|
+
|
|
973
1144
|
// ── Load agents ──
|
|
974
1145
|
useEffect(() => {
|
|
975
1146
|
const loadAgents = async () => {
|
|
@@ -978,8 +1149,8 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
978
1149
|
const raw = await readFile(agentsFile, "utf8");
|
|
979
1150
|
const all = JSON.parse(raw);
|
|
980
1151
|
setAgents(all.filter(a =>
|
|
981
|
-
a.status === "running" || a.status === "pending"
|
|
982
|
-
).slice(-
|
|
1152
|
+
a.status === "running" || a.status === "pending" || a.status === "completed" || a.status === "done"
|
|
1153
|
+
).slice(-10));
|
|
983
1154
|
} catch { setAgents([]); }
|
|
984
1155
|
};
|
|
985
1156
|
loadAgents();
|
|
@@ -1050,16 +1221,20 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1050
1221
|
|
|
1051
1222
|
// ── Keyboard shortcuts ──
|
|
1052
1223
|
useInput((input, key) => {
|
|
1053
|
-
// Don't intercept when typing in input or approval dialog is up
|
|
1054
1224
|
if (pendingApproval || showHelp) return;
|
|
1055
1225
|
|
|
1056
|
-
// View switches
|
|
1057
1226
|
if (key.tab && !loading) {
|
|
1058
1227
|
const idx = VIEWS.indexOf(view);
|
|
1059
1228
|
setView(VIEWS[(idx + 1) % VIEWS.length]);
|
|
1060
1229
|
return;
|
|
1061
1230
|
}
|
|
1062
1231
|
if (input === "?" && !inputValue) { setShowHelp(true); return; }
|
|
1232
|
+
if (input === "1" && !inputValue && !loading) { setView("chat"); return; }
|
|
1233
|
+
if (input === "2" && !inputValue && !loading) { setView("overview"); return; }
|
|
1234
|
+
if (input === "3" && !inputValue && !loading) { setView("agents"); return; }
|
|
1235
|
+
if (input === "4" && !inputValue && !loading) { setView("memory"); return; }
|
|
1236
|
+
if (input === "5" && !inputValue && !loading) { setView("audit"); return; }
|
|
1237
|
+
if (input === "6" && !inputValue && !loading) { setView("settings"); return; }
|
|
1063
1238
|
if (input === "o" && !inputValue && !loading) { setView("overview"); return; }
|
|
1064
1239
|
if (input === "a" && !inputValue && !loading) { setView("agents"); return; }
|
|
1065
1240
|
if (input === "m" && !inputValue && !loading) { setView("memory"); return; }
|
|
@@ -1067,7 +1242,6 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1067
1242
|
if (input === "s" && !inputValue && !loading) { setView("settings"); return; }
|
|
1068
1243
|
if (input === "t" && !inputValue) { setShowTimeline(prev => !prev); return; }
|
|
1069
1244
|
|
|
1070
|
-
// Ctrl+L = clear chat
|
|
1071
1245
|
if (key.ctrl && input === "l") {
|
|
1072
1246
|
conversationRef.current = [];
|
|
1073
1247
|
setMessages([]);
|
|
@@ -1075,8 +1249,8 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1075
1249
|
return;
|
|
1076
1250
|
}
|
|
1077
1251
|
|
|
1078
|
-
// Workstream number switch
|
|
1079
|
-
if (/^[
|
|
1252
|
+
// Workstream number switch (7-9 and beyond 6)
|
|
1253
|
+
if (/^[7-9]$/.test(input) && !inputValue && !loading) {
|
|
1080
1254
|
const idx = parseInt(input) - 1;
|
|
1081
1255
|
if (idx < workstreams.length) {
|
|
1082
1256
|
switchWorkstream(workstreams[idx]);
|
|
@@ -1084,7 +1258,6 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1084
1258
|
return;
|
|
1085
1259
|
}
|
|
1086
1260
|
|
|
1087
|
-
// q = quit (only when input is empty)
|
|
1088
1261
|
if (input === "q" && !inputValue && !loading) {
|
|
1089
1262
|
exit();
|
|
1090
1263
|
process.exit(0);
|
|
@@ -1128,24 +1301,19 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1128
1301
|
if (action) {
|
|
1129
1302
|
setMessages(prev => [...prev, {
|
|
1130
1303
|
role: "assistant",
|
|
1131
|
-
content:
|
|
1304
|
+
content: `**Dry-run preview**\n\nWould execute: \`${action.toolName}\`\n\`\`\`\n${JSON.stringify(action.args, null, 2).slice(0, 300)}\n\`\`\`\n*(Approve to run.)*`,
|
|
1132
1305
|
}]);
|
|
1133
1306
|
}
|
|
1134
1307
|
}, [pendingApproval]);
|
|
1135
1308
|
|
|
1136
|
-
// ── Palette visibility
|
|
1309
|
+
// ── Palette visibility ──
|
|
1137
1310
|
const handleInputChange = useCallback((val) => {
|
|
1138
1311
|
setInputValue(val);
|
|
1139
|
-
|
|
1140
|
-
setShowPalette(true);
|
|
1141
|
-
} else {
|
|
1142
|
-
setShowPalette(false);
|
|
1143
|
-
}
|
|
1312
|
+
setShowPalette(val.startsWith("/") && val.length >= 2);
|
|
1144
1313
|
}, []);
|
|
1145
1314
|
|
|
1146
1315
|
const handlePaletteSelect = useCallback((cmd) => {
|
|
1147
|
-
|
|
1148
|
-
const base = cmd.replace(/<[^>]+>/g, '').trimEnd();
|
|
1316
|
+
const base = cmd.replace(/<[^>]+>/g, "").trimEnd();
|
|
1149
1317
|
setInputValue(base);
|
|
1150
1318
|
setShowPalette(false);
|
|
1151
1319
|
}, []);
|
|
@@ -1162,7 +1330,6 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1162
1330
|
setInputValue("");
|
|
1163
1331
|
setShowPalette(false);
|
|
1164
1332
|
|
|
1165
|
-
// Switch to chat if not there
|
|
1166
1333
|
if (view !== "chat") setView("chat");
|
|
1167
1334
|
|
|
1168
1335
|
// Slash commands
|
|
@@ -1173,18 +1340,21 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1173
1340
|
if (cmd === "/quit" || cmd === "/exit") { exit(); process.exit(0); return; }
|
|
1174
1341
|
if (cmd === "/clear") {
|
|
1175
1342
|
conversationRef.current = [];
|
|
1176
|
-
setMessages([{ role: "system_info", content: "Conversation cleared.
|
|
1343
|
+
setMessages([{ role: "system_info", content: "Conversation cleared." }]);
|
|
1177
1344
|
await saveConversation(activeWorkstream, []);
|
|
1178
1345
|
return;
|
|
1179
1346
|
}
|
|
1180
1347
|
if (cmd === "/cost") {
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1348
|
+
const bud = engine?.budget;
|
|
1349
|
+
const spent = bud?.sessionSpend ?? totalCost;
|
|
1350
|
+
const max = bud?.maxBudgetUsd;
|
|
1351
|
+
setMessages(prev => [...prev, {
|
|
1352
|
+
role: "system_info",
|
|
1353
|
+
content: `Budget: $${spent.toFixed(4)}${max ? ` / $${max.toFixed(2)}` : ""} — ${totalTokens} tokens`,
|
|
1354
|
+
}]);
|
|
1186
1355
|
return;
|
|
1187
1356
|
}
|
|
1357
|
+
if (cmd === "/timeline") { setView("audit"); return; }
|
|
1188
1358
|
if (cmd === "/overview" || cmd === "/o") { setView("overview"); return; }
|
|
1189
1359
|
if (cmd === "/agents" || cmd === "/a") { setView("agents"); return; }
|
|
1190
1360
|
if (cmd === "/memories" || cmd === "/m") { setView("memory"); return; }
|
|
@@ -1193,19 +1363,19 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1193
1363
|
if (cmd === "/model" && parts[1]) {
|
|
1194
1364
|
engine.providers.setModel(parts[1]);
|
|
1195
1365
|
setModel(parts[1]);
|
|
1196
|
-
setMessages(prev => [...prev, { role: "system_info", content: `Model → ${parts[1]}
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
if (cmd === "/workstream" && parts[1]) {
|
|
1200
|
-
await switchWorkstream(parts[1]);
|
|
1201
|
-
return;
|
|
1202
|
-
}
|
|
1203
|
-
if (cmd === "/help") {
|
|
1204
|
-
setShowHelp(true);
|
|
1366
|
+
setMessages(prev => [...prev, { role: "system_info", content: `Model → ${parts[1]}` }]);
|
|
1205
1367
|
return;
|
|
1206
1368
|
}
|
|
1207
|
-
if (cmd === "/
|
|
1208
|
-
|
|
1369
|
+
if (cmd === "/workstream" && parts[1]) { await switchWorkstream(parts[1]); return; }
|
|
1370
|
+
if (cmd === "/ws" && parts[1]) { await switchWorkstream(parts[1]); return; }
|
|
1371
|
+
if (cmd === "/help") { setShowHelp(true); return; }
|
|
1372
|
+
if (cmd === "/trust" && parts[1]) {
|
|
1373
|
+
try {
|
|
1374
|
+
engine.permissions.setPolicy("run_command", parts[1]);
|
|
1375
|
+
setMessages(prev => [...prev, { role: "system_info", content: `Security level → ${parts[1]}` }]);
|
|
1376
|
+
} catch (e) {
|
|
1377
|
+
setMessages(prev => [...prev, { role: "system_info", content: `Error: ${e.message}` }]);
|
|
1378
|
+
}
|
|
1209
1379
|
return;
|
|
1210
1380
|
}
|
|
1211
1381
|
}
|
|
@@ -1255,18 +1425,25 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1255
1425
|
// Update token/cost tracking
|
|
1256
1426
|
const { input: inputToks = 0, output: outputToks = 0 } = engine.providers.sessionTokens ?? {};
|
|
1257
1427
|
setTotalTokens(inputToks + outputToks);
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1428
|
+
|
|
1429
|
+
// Use budget manager if available
|
|
1430
|
+
const bud = engine?.budget;
|
|
1431
|
+
if (bud) {
|
|
1432
|
+
setBudgetSpent(bud.sessionSpend ?? 0);
|
|
1433
|
+
setMaxBudget(bud.maxBudgetUsd ?? null);
|
|
1434
|
+
setTotalCost(bud.sessionSpend ?? 0);
|
|
1435
|
+
} else {
|
|
1436
|
+
const MODEL_PRICING = {
|
|
1437
|
+
"gemini-2.5-flash": { input: 0.15, output: 0.60 },
|
|
1438
|
+
"gemini-2.5-pro": { input: 1.25, output: 10.0 },
|
|
1439
|
+
"claude-sonnet-4-20250514": { input: 3.0, output: 15.0 },
|
|
1440
|
+
"gpt-4o": { input: 2.50, output: 10.0 },
|
|
1441
|
+
"gpt-4.1": { input: 2.0, output: 8.0 },
|
|
1442
|
+
};
|
|
1443
|
+
const pricing = MODEL_PRICING[model] ?? { input: 1.0, output: 3.0 };
|
|
1444
|
+
const cost = (inputToks * pricing.input + outputToks * pricing.output) / 1_000_000;
|
|
1445
|
+
setTotalCost(cost);
|
|
1446
|
+
}
|
|
1270
1447
|
setModel(engine.model ?? model);
|
|
1271
1448
|
|
|
1272
1449
|
const assistantMsg = { role: "assistant", content: responseText };
|
|
@@ -1274,19 +1451,20 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1274
1451
|
setMessages(prev => [...prev, assistantMsg]);
|
|
1275
1452
|
await saveConversation(activeWorkstream, conversationRef.current.filter(m => m.role !== "system"));
|
|
1276
1453
|
} catch (err) {
|
|
1277
|
-
const errMsg = { role: "assistant", content:
|
|
1454
|
+
const errMsg = { role: "assistant", content: `Error: ${err.message.slice(0, 200)}` };
|
|
1278
1455
|
setMessages(prev => [...prev, errMsg]);
|
|
1279
1456
|
} finally {
|
|
1280
1457
|
setLoading(false);
|
|
1281
1458
|
}
|
|
1282
1459
|
}, [loading, model, totalCost, totalTokens, engine, exit, pendingApproval, activeWorkstream, view, switchWorkstream]);
|
|
1283
1460
|
|
|
1284
|
-
// ── Layout
|
|
1461
|
+
// ── Layout ──
|
|
1285
1462
|
const showSidebar = termWidth >= 80;
|
|
1286
1463
|
const mainWidth = showSidebar ? termWidth - SIDEBAR_WIDTH - 2 : termWidth;
|
|
1287
1464
|
const permMode = engine.permissions?.getPolicy?.("run_command") ?? "approve";
|
|
1465
|
+
const activeAgent = engine?._personality ?? engine?._activeAgent ?? null;
|
|
1288
1466
|
|
|
1289
|
-
// ──
|
|
1467
|
+
// ── Main content ──
|
|
1290
1468
|
const mainContent = showHelp
|
|
1291
1469
|
? React.createElement(HelpOverlay, { onClose: () => setShowHelp(false) })
|
|
1292
1470
|
: view === "chat"
|
|
@@ -1296,9 +1474,9 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1296
1474
|
mainWidth,
|
|
1297
1475
|
})
|
|
1298
1476
|
: view === "overview"
|
|
1299
|
-
? React.createElement(OverviewView, { workstreams, activeWorkstream, overviewData, mainWidth })
|
|
1477
|
+
? React.createElement(OverviewView, { workstreams, activeWorkstream, overviewData, browserStatus, mainWidth })
|
|
1300
1478
|
: view === "agents"
|
|
1301
|
-
? React.createElement(AgentsView, { agents })
|
|
1479
|
+
? React.createElement(AgentsView, { agents, agentManager: engine?.agentManager })
|
|
1302
1480
|
: view === "memory"
|
|
1303
1481
|
? React.createElement(MemoryView, { memoryFiles, memoryQuery, onQueryChange: setMemoryQuery })
|
|
1304
1482
|
: view === "audit"
|
|
@@ -1327,34 +1505,36 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1327
1505
|
React.createElement(
|
|
1328
1506
|
Box, { flexDirection: "row", flexGrow: 1 },
|
|
1329
1507
|
|
|
1330
|
-
// Left sidebar
|
|
1331
1508
|
showSidebar
|
|
1332
1509
|
? React.createElement(Sidebar, {
|
|
1333
1510
|
workstreams,
|
|
1334
1511
|
activeWorkstream,
|
|
1512
|
+
activeAgent,
|
|
1335
1513
|
agents,
|
|
1336
1514
|
memoryCount: memoryFiles.length,
|
|
1337
1515
|
userModelLoaded,
|
|
1338
1516
|
cronCount,
|
|
1339
1517
|
syncStatus,
|
|
1518
|
+
browserStatus,
|
|
1519
|
+
budgetSpent,
|
|
1520
|
+
maxBudget,
|
|
1340
1521
|
onSelectWorkstream: switchWorkstream,
|
|
1341
1522
|
})
|
|
1342
1523
|
: null,
|
|
1343
1524
|
|
|
1344
|
-
// Main view
|
|
1345
1525
|
React.createElement(
|
|
1346
1526
|
Box, { flexDirection: "column", flexGrow: 1 },
|
|
1347
1527
|
mainContent,
|
|
1348
1528
|
),
|
|
1349
1529
|
),
|
|
1350
1530
|
|
|
1351
|
-
// Timeline bar
|
|
1531
|
+
// Timeline bar
|
|
1352
1532
|
(showTimeline || view === "chat") && timeline.length > 0
|
|
1353
1533
|
? React.createElement(TimelineBar, { events: timeline })
|
|
1354
1534
|
: null,
|
|
1355
1535
|
|
|
1356
|
-
// Command palette
|
|
1357
|
-
!showHelp && showPalette && inputValue.startsWith(
|
|
1536
|
+
// Command palette
|
|
1537
|
+
!showHelp && showPalette && inputValue.startsWith("/")
|
|
1358
1538
|
? React.createElement(CommandPalette, {
|
|
1359
1539
|
query: inputValue,
|
|
1360
1540
|
onSelect: handlePaletteSelect,
|
|
@@ -1362,7 +1542,7 @@ function WispyWorkspaceApp({ engine, initialWorkstream }) {
|
|
|
1362
1542
|
})
|
|
1363
1543
|
: null,
|
|
1364
1544
|
|
|
1365
|
-
// Input
|
|
1545
|
+
// Input
|
|
1366
1546
|
!showHelp
|
|
1367
1547
|
? React.createElement(InputArea, {
|
|
1368
1548
|
value: inputValue,
|
|
@@ -1392,7 +1572,6 @@ async function main() {
|
|
|
1392
1572
|
process.exit(1);
|
|
1393
1573
|
}
|
|
1394
1574
|
|
|
1395
|
-
// Clear screen
|
|
1396
1575
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
1397
1576
|
|
|
1398
1577
|
const { waitUntilExit } = render(
|