@parallel-cli/parallel 0.4.8 → 0.5.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/CHANGELOG.md +57 -0
- package/README.md +43 -7
- package/dist/agents/agent.js +213 -31
- package/dist/agents/execution-policy.js +58 -0
- package/dist/agents/tools.js +83 -22
- package/dist/commands.js +67 -5
- package/dist/config.js +5 -2
- package/dist/controller.js +229 -11
- package/dist/coordination/blackboard.js +8 -7
- package/dist/diagnostics.js +209 -0
- package/dist/i18n.js +60 -4
- package/dist/index.js +31 -7
- package/dist/llm/client.js +7 -3
- package/dist/project-context.js +477 -0
- package/dist/project-index.js +186 -0
- package/dist/security.js +93 -0
- package/dist/server.js +41 -2
- package/dist/ui/AgentPanel.js +6 -2
- package/dist/ui/App.js +4 -2
- package/dist/ui/AttachApp.js +6 -3
- package/dist/ui/CommandInput.js +9 -0
- package/dist/ui/SettingsPanel.js +22 -23
- package/dist/ui/Timeline.js +3 -0
- package/dist/ui/Wizard.js +49 -21
- package/dist/ui/events.js +4 -0
- package/dist/ui/views.js +4 -2
- package/dist/update.js +3 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/security.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const PRIVATE_DIR = 0o700;
|
|
4
|
+
const PRIVATE_FILE = 0o600;
|
|
5
|
+
export function ensurePrivateDir(dir) {
|
|
6
|
+
fs.mkdirSync(dir, { recursive: true, mode: PRIVATE_DIR });
|
|
7
|
+
try {
|
|
8
|
+
fs.chmodSync(dir, PRIVATE_DIR);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Best effort: some filesystems do not support chmod.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function chmodPrivateFile(file) {
|
|
15
|
+
try {
|
|
16
|
+
fs.chmodSync(file, PRIVATE_FILE);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Best effort only.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function chmodPrivateTree(root) {
|
|
23
|
+
if (!fs.existsSync(root))
|
|
24
|
+
return;
|
|
25
|
+
const stat = fs.statSync(root);
|
|
26
|
+
if (stat.isDirectory()) {
|
|
27
|
+
try {
|
|
28
|
+
fs.chmodSync(root, PRIVATE_DIR);
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
32
|
+
chmodPrivateTree(path.join(root, entry.name));
|
|
33
|
+
}
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (stat.isFile())
|
|
37
|
+
chmodPrivateFile(root);
|
|
38
|
+
}
|
|
39
|
+
export function writeFileAtomicPrivate(file, content) {
|
|
40
|
+
ensurePrivateDir(path.dirname(file));
|
|
41
|
+
const tmp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`);
|
|
42
|
+
let fd;
|
|
43
|
+
try {
|
|
44
|
+
fd = fs.openSync(tmp, 'w', PRIVATE_FILE);
|
|
45
|
+
fs.writeFileSync(fd, content, 'utf8');
|
|
46
|
+
fs.fsyncSync(fd);
|
|
47
|
+
fs.closeSync(fd);
|
|
48
|
+
fd = undefined;
|
|
49
|
+
fs.renameSync(tmp, file);
|
|
50
|
+
chmodPrivateFile(file);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
if (fd !== undefined) {
|
|
54
|
+
try {
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
}
|
|
57
|
+
catch { }
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(tmp))
|
|
61
|
+
fs.unlinkSync(tmp);
|
|
62
|
+
}
|
|
63
|
+
catch { }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function appendFilePrivate(file, content) {
|
|
67
|
+
ensurePrivateDir(path.dirname(file));
|
|
68
|
+
fs.appendFileSync(file, content, { encoding: 'utf8', mode: PRIVATE_FILE });
|
|
69
|
+
chmodPrivateFile(file);
|
|
70
|
+
}
|
|
71
|
+
export function writeJsonAtomicPrivate(file, value) {
|
|
72
|
+
writeFileAtomicPrivate(file, JSON.stringify(value, null, 2));
|
|
73
|
+
}
|
|
74
|
+
export function sanitizeTerminalText(text) {
|
|
75
|
+
return text
|
|
76
|
+
// OSC sequences, including hyperlinks/window-title changes.
|
|
77
|
+
.replace(/\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)/g, '')
|
|
78
|
+
// CSI sequences.
|
|
79
|
+
.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
80
|
+
// Other one-byte ESC sequences.
|
|
81
|
+
.replace(/\x1B[@-Z\\-_]/g, '')
|
|
82
|
+
// C0 controls except tab/newline/carriage return.
|
|
83
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
|
|
84
|
+
}
|
|
85
|
+
export function redactPersistedText(text) {
|
|
86
|
+
return text
|
|
87
|
+
.replace(/data:image\/png;base64,[A-Za-z0-9+/=]+/g, 'data:image/png;base64,[redacted]')
|
|
88
|
+
.replace(/([A-Za-z0-9_]*API[_-]?KEY[A-Za-z0-9_]*\s*[:=]\s*)['"]?[A-Za-z0-9._~+/=-]{12,}['"]?/gi, '$1[redacted]')
|
|
89
|
+
.replace(/(sk-[A-Za-z0-9]{16,})/g, '[redacted-api-key]');
|
|
90
|
+
}
|
|
91
|
+
export function sanitizeForPersistence(text) {
|
|
92
|
+
return redactPersistedText(sanitizeTerminalText(text));
|
|
93
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import net from 'node:net';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { ensurePrivateDir, writeFileAtomicPrivate } from './security.js';
|
|
4
6
|
export function socketPath(projectRoot) {
|
|
5
7
|
return path.join(projectRoot, '.parallel', 'session.sock');
|
|
6
8
|
}
|
|
9
|
+
export function sessionTokenPath(projectRoot) {
|
|
10
|
+
return path.join(projectRoot, '.parallel', 'session.token');
|
|
11
|
+
}
|
|
12
|
+
export function readSessionToken(projectRoot) {
|
|
13
|
+
try {
|
|
14
|
+
return fs.readFileSync(sessionTokenPath(projectRoot), 'utf8').trim() || null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
/** Start the session server. Returns a stop function (closes socket + clients). */
|
|
8
21
|
export function startSessionServer(ctl) {
|
|
9
22
|
const sock = socketPath(ctl.projectRoot);
|
|
23
|
+
const tokenFile = sessionTokenPath(ctl.projectRoot);
|
|
24
|
+
const token = randomBytes(32).toString('hex');
|
|
10
25
|
try {
|
|
11
|
-
|
|
26
|
+
ensurePrivateDir(path.dirname(sock));
|
|
12
27
|
// A previous run may have crashed without cleaning up: remove the stale socket.
|
|
13
28
|
if (fs.existsSync(sock))
|
|
14
29
|
fs.unlinkSync(sock);
|
|
30
|
+
writeFileAtomicPrivate(tokenFile, token);
|
|
15
31
|
}
|
|
16
32
|
catch {
|
|
17
33
|
return null;
|
|
@@ -69,7 +85,7 @@ export function startSessionServer(ctl) {
|
|
|
69
85
|
};
|
|
70
86
|
ctl.on('update', onUpdate);
|
|
71
87
|
const server = net.createServer((socket) => {
|
|
72
|
-
const client = { socket, agent: '', lastSeq: 0 };
|
|
88
|
+
const client = { socket, agent: '', lastSeq: 0, authenticated: false };
|
|
73
89
|
let buffer = '';
|
|
74
90
|
socket.setEncoding('utf8');
|
|
75
91
|
socket.on('data', (chunk) => {
|
|
@@ -88,10 +104,19 @@ export function startSessionServer(ctl) {
|
|
|
88
104
|
continue;
|
|
89
105
|
}
|
|
90
106
|
if (msg.type === 'hello' && typeof msg.agent === 'string') {
|
|
107
|
+
if (msg.token !== token) {
|
|
108
|
+
send(socket, { type: 'bye' });
|
|
109
|
+
socket.destroy();
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
client.authenticated = true;
|
|
91
113
|
client.agent = msg.agent;
|
|
92
114
|
clients.add(client);
|
|
93
115
|
pushTo(client); // immediate first snapshot (full backlog: lastSeq = 0)
|
|
94
116
|
}
|
|
117
|
+
else if (!client.authenticated) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
95
120
|
else if (msg.type === 'input' && typeof msg.text === 'string' && client.agent) {
|
|
96
121
|
const text = msg.text.trim();
|
|
97
122
|
if (!text)
|
|
@@ -148,6 +173,14 @@ export function startSessionServer(ctl) {
|
|
|
148
173
|
catch {
|
|
149
174
|
return null;
|
|
150
175
|
}
|
|
176
|
+
server.on('listening', () => {
|
|
177
|
+
try {
|
|
178
|
+
fs.chmodSync(sock, 0o600);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
/* best effort */
|
|
182
|
+
}
|
|
183
|
+
});
|
|
151
184
|
server.on('error', () => {
|
|
152
185
|
/* keep the TUI alive even if the server dies */
|
|
153
186
|
});
|
|
@@ -165,5 +198,11 @@ export function startSessionServer(ctl) {
|
|
|
165
198
|
catch {
|
|
166
199
|
/* already gone */
|
|
167
200
|
}
|
|
201
|
+
try {
|
|
202
|
+
fs.unlinkSync(tokenFile);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
/* already gone */
|
|
206
|
+
}
|
|
168
207
|
};
|
|
169
208
|
}
|
package/dist/ui/AgentPanel.js
CHANGED
|
@@ -13,6 +13,7 @@ export const KIND_COLOR = {
|
|
|
13
13
|
llm: UI.muted,
|
|
14
14
|
error: UI.danger,
|
|
15
15
|
note: UI.note,
|
|
16
|
+
memory: COLOR.creamMuted,
|
|
16
17
|
system: UI.warn,
|
|
17
18
|
info: UI.text,
|
|
18
19
|
};
|
|
@@ -29,8 +30,11 @@ export function cleanHubSummary(text) {
|
|
|
29
30
|
export function formatAgentTelemetry(agent) {
|
|
30
31
|
const ctx = agent.ctxPct !== undefined ? ` · ${agent.ctxPct}% ctx` : '';
|
|
31
32
|
const perf = agent.perf ? ` · ${agent.perf.modelTurns}t/${agent.perf.toolCalls} tools` : '';
|
|
33
|
+
const llm = agent.perf?.llmMs ? ` · llm ${Math.round(agent.perf.llmMs / 1000)}s` : '';
|
|
32
34
|
const runtime = agent.endedAt ? `ended ${elapsed(agent.startedAt, agent.endedAt)}` : elapsed(agent.startedAt);
|
|
33
|
-
|
|
35
|
+
const cache = agent.perf?.cachedTokens ? ` · cache ${Math.round(agent.perf.cachedTokens / 1000)}k` : '';
|
|
36
|
+
const profile = agent.profile ? ` · ${agent.profile}` : '';
|
|
37
|
+
return `${runtime}${profile}${ctx}${perf}${llm}${cache} · ${agent.cost === null ? '$-' : fmtCost(agent.cost)}`;
|
|
34
38
|
}
|
|
35
39
|
function firstSectionLine(text, labels) {
|
|
36
40
|
const lines = text.replace(/\r/g, '').split('\n');
|
|
@@ -166,7 +170,7 @@ export function AgentRow({ agent, logs, cols, }) {
|
|
|
166
170
|
else if (claims) {
|
|
167
171
|
line2 = { text: claims, color: UI.warn };
|
|
168
172
|
}
|
|
169
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { wrap: "truncate-end", children: [SPINNER_STATES.has(agent.state) ? (_jsx(Spinner, { color: pulseColor ?? spinnerColor(agent.state) })) : (_jsx(Text, { color: pulseColor ?? meta.color, bold: true, children: meta.mark })), _jsx(Text, { children: " " }), _jsx(Text, { color: agent.color, bold: true, children: name }), _jsxs(Text, { color: mode.color, children: [" [", mode.label, "]"] }), specialist ? _jsx(Text, { color: UI.note, children: specialist }) : null, _jsxs(Text, { color: UI.text, children: [" ", truncate(agent.task, taskMax)] })] }), _jsx(Text, { color: UI.muted, wrap: "truncate-end", children: truncate(quickActions, actionBudget) })] }), summary.length > 0 ? (_jsx(Box, { flexDirection: "column", children: summary.map((line, i) => (_jsxs(Box, { flexDirection: "row", justifyContent: i === 0 ? 'space-between' : undefined, children: [_jsxs(Text, { color: COLOR.cream, wrap: "truncate-end", children: [_jsx(Text, { color: COLOR.cream, children: "\u2022 " }), line] }), i === 0 ? _jsx(Text, { color: UI.muted, children: telemetry }) : null] }, `${i}-${line}`))) })) : line2 ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: line2.color, wrap: "truncate-end", children: line2.text }), _jsx(Text, { color: UI.muted, children: telemetry })] })) : null, !agent.lastResult ? _jsx(ProgressSteps, { agent: agent, max: 3, cols: line2Max, showRemaining: true }) : null] }));
|
|
173
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Text, { wrap: "truncate-end", children: [SPINNER_STATES.has(agent.state) ? (_jsx(Spinner, { color: pulseColor ?? spinnerColor(agent.state) })) : (_jsx(Text, { color: pulseColor ?? meta.color, bold: true, children: meta.mark })), _jsx(Text, { children: " " }), _jsx(Text, { color: agent.color, bold: true, children: name }), _jsxs(Text, { color: mode.color, children: [" [", mode.label, "]"] }), agent.profile ? _jsxs(Text, { color: UI.muted, children: [" [", agent.profile.toUpperCase(), "]"] }) : null, specialist ? _jsx(Text, { color: UI.note, children: specialist }) : null, _jsxs(Text, { color: UI.text, children: [" ", truncate(agent.task, taskMax)] })] }), _jsx(Text, { color: UI.muted, wrap: "truncate-end", children: truncate(quickActions, actionBudget) })] }), summary.length > 0 ? (_jsx(Box, { flexDirection: "column", children: summary.map((line, i) => (_jsxs(Box, { flexDirection: "row", justifyContent: i === 0 ? 'space-between' : undefined, children: [_jsxs(Text, { color: COLOR.cream, wrap: "truncate-end", children: [_jsx(Text, { color: COLOR.cream, children: "\u2022 " }), line] }), i === 0 ? _jsx(Text, { color: UI.muted, children: telemetry }) : null] }, `${i}-${line}`))) })) : line2 ? (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsx(Text, { color: line2.color, wrap: "truncate-end", children: line2.text }), _jsx(Text, { color: UI.muted, children: telemetry })] })) : null, !agent.lastResult ? _jsx(ProgressSteps, { agent: agent, max: 3, cols: line2Max, showRemaining: true }) : null] }));
|
|
170
174
|
}
|
|
171
175
|
export function AgentTranscript({ agent, logs, raw = false, scrolled = 0, cols = 100, }) {
|
|
172
176
|
const meta = STATE_META[agent.state];
|
package/dist/ui/App.js
CHANGED
|
@@ -644,7 +644,7 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
644
644
|
specialists: 'specialists',
|
|
645
645
|
};
|
|
646
646
|
const viewLabel = VIEW_LABEL[view] ?? 'control room';
|
|
647
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: headerColor, children: ["\u256D", '─'.repeat(cols - 2), "\u256E"] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: headerColor, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { bold: true, color: BRAND.primary, children: "PARALLEL" }), _jsx(Text, { color: globalDotColor, children: " \u25CF" }), _jsxs(Text, { color: view === 'agents' ? CHROME.muted : BRAND.muted, children: [" ", agents.length === 0 ? 'ready' : viewLabel] }), rawLogs && focused ? _jsx(Text, { color: UI.warn, children: " [RAW]" }) : null] }), _jsx(Text, { color: CHROME.muted, children: middleTruncate(folder, folderMax) })] }), _jsx(Text, { color: headerColor, children: " \u2502" })] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: headerColor, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [agents.length > 0 ? (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { children: [_jsxs(Text, { color: CHROME.muted, children: ["\u25C7 ", idleCount, " idle"] }), ' · ', _jsxs(Text, { color: workingCount > 0 ? STATE.working : CHROME.muted, children: ["\u25CF ", workingCount, " active"] }), ' · ', _jsxs(Text, { color: doneCount > 0 ? STATE.done : CHROME.muted, children: ["\u2713 ", doneCount, " done"] }), ' · ', _jsxs(Text, { color: errorCount > 0 ? STATE.error : CHROME.muted, children: ["\u2717 ", errorCount, " err"] }), workMapAlerts.length > 0 ? (_jsxs(Text, { color: conflictAlerts.length > 0 ? UI.danger : UI.warn, children: [" \u00B7 \u26A0 work-map ", workMapAlerts.length] })) : null] }) })) : (_jsx(Text, { color: CHROME.muted, children: providerModel })), _jsxs(Text, { color: CHROME.muted, children: ["v", VERSION] })] }), _jsx(Text, { color: headerColor, children: " \u2502" })] }), _jsxs(Text, { color: headerColor, children: ["\u2570", '─'.repeat(cols - 2), "\u256F"] })] }), _jsx(Box, { height: bodyHeight, overflow: "hidden", flexDirection: "column", children: view === 'settings' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "global", height: bodyHeight, onClose: onEscape })) : view === 'settings-session' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "session", height: bodyHeight, onClose: onEscape })) : view === 'board' ? (_jsx(BoardView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'notes' ? (_jsx(NotesView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'sessions' ? (_jsx(SessionsView, { projectRoot: ctl.projectRoot, bodyHeight: bodyHeight })) : view === 'diff' ? (_jsx(DiffView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'cost' ? (_jsx(CostView, {
|
|
647
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: headerColor, children: ["\u256D", '─'.repeat(cols - 2), "\u256E"] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: headerColor, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { bold: true, color: BRAND.primary, children: "PARALLEL" }), _jsx(Text, { color: globalDotColor, children: " \u25CF" }), _jsxs(Text, { color: view === 'agents' ? CHROME.muted : BRAND.muted, children: [" ", agents.length === 0 ? 'ready' : viewLabel] }), rawLogs && focused ? _jsx(Text, { color: UI.warn, children: " [RAW]" }) : null] }), _jsx(Text, { color: CHROME.muted, children: middleTruncate(folder, folderMax) })] }), _jsx(Text, { color: headerColor, children: " \u2502" })] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: headerColor, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [agents.length > 0 ? (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { children: [_jsxs(Text, { color: CHROME.muted, children: ["\u25C7 ", idleCount, " idle"] }), ' · ', _jsxs(Text, { color: workingCount > 0 ? STATE.working : CHROME.muted, children: ["\u25CF ", workingCount, " active"] }), ' · ', _jsxs(Text, { color: doneCount > 0 ? STATE.done : CHROME.muted, children: ["\u2713 ", doneCount, " done"] }), ' · ', _jsxs(Text, { color: errorCount > 0 ? STATE.error : CHROME.muted, children: ["\u2717 ", errorCount, " err"] }), workMapAlerts.length > 0 ? (_jsxs(Text, { color: conflictAlerts.length > 0 ? UI.danger : UI.warn, children: [" \u00B7 \u26A0 work-map ", workMapAlerts.length] })) : null] }) })) : (_jsx(Text, { color: CHROME.muted, children: providerModel })), _jsxs(Text, { color: CHROME.muted, children: ["v", VERSION] })] }), _jsx(Text, { color: headerColor, children: " \u2502" })] }), _jsxs(Text, { color: headerColor, children: ["\u2570", '─'.repeat(cols - 2), "\u256F"] })] }), _jsx(Box, { height: bodyHeight, overflow: "hidden", flexDirection: "column", children: view === 'settings' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "global", height: bodyHeight, onClose: onEscape })) : view === 'settings-session' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "session", height: bodyHeight, onClose: onEscape })) : view === 'board' ? (_jsx(BoardView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'notes' ? (_jsx(NotesView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'sessions' ? (_jsx(SessionsView, { projectRoot: ctl.projectRoot, bodyHeight: bodyHeight })) : view === 'diff' ? (_jsx(DiffView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'cost' ? (_jsx(CostView, { ctl: ctl, bodyHeight: bodyHeight })) : view === 'skills' ? (_jsx(SkillsView, { skills: ctl.getSkills(), bodyHeight: bodyHeight })) : view === 'specialists' ? (_jsx(SpecialistsView, { specialists: ctl.getSpecialists(), bodyHeight: bodyHeight })) : view === 'help' ? (_jsx(HelpView, { bodyHeight: bodyHeight, onSelect: (cmd) => onInput(cmd) })) : agents.length === 0 ? (_jsx(EmptyHub, { bodyHeight: bodyHeight })) : focused ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(AgentTranscript, { agent: focused, logs: visibleLogs, raw: rawLogs, scrolled: clampedScroll, cols: cols }), !focusFollowTail ? _jsx(Text, { color: UI.warn, children: "Viewing older \u00B7 PgDn to latest" }) : null] })) : (_jsx(AgentHub, { agents: agents, ctl: ctl, cols: cols, scroll: clampedHub, visibleRows: hubRows })) }), systemLines.length > 0 && !settingsOpen && (_jsxs(Box, { flexDirection: "column", children: [agents.length > 0 ? _jsx(Text, { color: UI.muted, bold: true, children: "Session" }) : null, (agents.length > 0
|
|
648
648
|
? systemLines
|
|
649
649
|
.filter((l) => !/^Ready|^Type a task|^⚡ Ready|^Default \/task|^Agent .* launched/.test(l.text))
|
|
650
650
|
.slice(-2)
|
|
@@ -658,7 +658,9 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
658
658
|
return lines.map((line, j) => (_jsx(Text, { color: levelColor, wrap: "truncate-end", children: line }, `${i}-${j}`)));
|
|
659
659
|
})] })), approval && (_jsx(ApprovalPrompt, { request: approval, pendingCount: ctl.approvals.length, onAnswer: (id, ok, always) => ctl.answerApproval(id, ok, always) })), question && (_jsx(QuestionPrompt, { question: question, pendingCount: ctl.questions.length, onAnswer: (id, answer, auto) => ctl.answerQuestion(id, answer, auto) }, question.id)), _jsx(Text, { children: " " }), _jsx(CommandInput, { active: inputActive, placeholder: focus ? `Message ${focus} or /command` : t('main.prompt'), context: focus ? 'focus' : 'hub', targetAgent: focused?.name, modelLabel: ctl.sessionProvider() ? `${ctl.sessionProvider()?.name}:${ctl.session.model}` : undefined, agentNames: agentNames, agents: agents, width: cols, onHeightChange: setInputRows, onSubmit: onInput, onEscape: onEscape, notify: notify }), _jsx(Text, { children: " " }), _jsx(Box, { flexDirection: "column", children: _jsxs(Text, { children: [_jsx(Text, { color: CHROME.muted, children: "/ for commands" }), _jsx(Text, { color: CHROME.muted, children: " \u00B7 Shell " }), _jsx(Text, { color: ctl.session.approvalMode === 'ask' ? UI.warn :
|
|
660
660
|
ctl.session.approvalMode === 'yolo' ? UI.danger :
|
|
661
|
-
UI.ok, children: ctl.session.approvalMode === 'auto-safe' ? 'auto' : ctl.session.approvalMode }), agents.length > 0 ? _jsxs(Text, { color: CHROME.muted, children: [" \u00B7 Sessions ", Controller.listSessions(ctl.projectRoot).length] }) : null,
|
|
661
|
+
UI.ok, children: ctl.session.approvalMode === 'auto-safe' ? 'auto' : ctl.session.approvalMode }), agents.length > 0 ? _jsxs(Text, { color: CHROME.muted, children: [" \u00B7 Sessions ", Controller.listSessions(ctl.projectRoot).length] }) : null, _jsxs(Text, { color: ctl.projectContextStatus().status === 'ready' ? UI.ok :
|
|
662
|
+
ctl.projectContextStatus().status === 'indexing' ? UI.warn :
|
|
663
|
+
CHROME.muted, children: [" \u00B7 ", t('memory.label'), " ", ctl.projectContextStatus().status] }), _jsxs(Text, { color: CHROME.muted, children: [" \u00B7 index ", ctl.projectIndexStatus().files] }), ctl.questions.length > 0 ? (_jsxs(Text, { color: UI.warn, children: [" \u00B7 \u2753", ctl.questions.length] })) : null, ctl.approvals.length > 0 ? (_jsxs(Text, { color: UI.warn, children: [" \u00B7 \u23F3", ctl.approvals.length] })) : null, workMapAlerts.length > 0 ? (_jsx(Text, { color: conflictAlerts.length > 0 ? UI.danger : UI.warn, children: " \u00B7 \u26A0 /board" })) : null, conflictAlerts.length > 0 ? (_jsx(Text, { color: UI.danger, children: " \u00B7 run /review all" })) : null, focused ? (_jsxs(Text, { color: BRAND.muted, children: [" \u00B7 \uD83C\uDFAF ", focused.name] })) : null] }) })] }));
|
|
662
664
|
}
|
|
663
665
|
function groupAgents(agents) {
|
|
664
666
|
const needs = agents.filter((a) => ['waiting', 'paused'].includes(a.state));
|
package/dist/ui/AttachApp.js
CHANGED
|
@@ -60,6 +60,9 @@ function AttachStaticLine({ item, raw }) {
|
|
|
60
60
|
const event = toUIEvents([item.log])[0];
|
|
61
61
|
if (!event || event.kind === 'thought')
|
|
62
62
|
return _jsx(Text, { color: UI.muted, children: " " });
|
|
63
|
+
if (event.kind === 'memory') {
|
|
64
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, borderStyle: "round", borderColor: UI.muted, paddingX: 1, children: _jsxs(Text, { color: COLOR.creamMuted, wrap: "wrap", children: [_jsx(Text, { bold: true, children: "o " }), truncate(event.detail, process.stdout.columns ? process.stdout.columns - 8 : 120)] }) }));
|
|
65
|
+
}
|
|
63
66
|
const color = event.kind === 'error' ? UI.danger : event.kind === 'note' ? UI.note : event.kind === 'command' ? UI.accent : UI.muted;
|
|
64
67
|
const detail = event.detail.replace(/\r/g, '').split('\n').filter(Boolean).slice(0, 3).join(' ↳ ');
|
|
65
68
|
return (_jsxs(Text, { color: color, wrap: "truncate-end", children: [_jsx(Text, { color: UI.muted, children: "\u2022 " }), _jsx(Text, { bold: true, children: event.label }), detail ? _jsxs(Text, { color: event.kind === 'command_output' ? UI.muted : color, children: [" ", truncate(detail, process.stdout.columns ? process.stdout.columns - 8 : 120)] }) : null] }));
|
|
@@ -76,7 +79,7 @@ function AttachResultCard({ item }) {
|
|
|
76
79
|
const st = STATE_META[item.info.state];
|
|
77
80
|
return (_jsxs(Box, { borderStyle: "single", borderColor: st.color, flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsxs(Text, { color: COLOR.cream, bold: true, children: ["Result \u00B7 ", item.info.name, " [", st.label, "]"] }), _jsx(Md, { text: item.result })] }));
|
|
78
81
|
}
|
|
79
|
-
export function AttachApp({ agentRef, sock }) {
|
|
82
|
+
export function AttachApp({ agentRef, sock, token }) {
|
|
80
83
|
const { exit } = useApp();
|
|
81
84
|
const { stdout } = useStdout();
|
|
82
85
|
const [info, setInfo] = useState(null);
|
|
@@ -101,7 +104,7 @@ export function AttachApp({ agentRef, sock }) {
|
|
|
101
104
|
let buffer = '';
|
|
102
105
|
socket.setEncoding('utf8');
|
|
103
106
|
socket.on('connect', () => {
|
|
104
|
-
socket.write(JSON.stringify({ type: 'hello', agent: agentRef }) + '\n');
|
|
107
|
+
socket.write(JSON.stringify({ type: 'hello', agent: agentRef, token }) + '\n');
|
|
105
108
|
});
|
|
106
109
|
socket.on('data', (chunk) => {
|
|
107
110
|
buffer += chunk;
|
|
@@ -138,7 +141,7 @@ export function AttachApp({ agentRef, sock }) {
|
|
|
138
141
|
return () => {
|
|
139
142
|
socket.destroy();
|
|
140
143
|
};
|
|
141
|
-
}, [agentRef, sock]);
|
|
144
|
+
}, [agentRef, sock, token]);
|
|
142
145
|
useEffect(() => {
|
|
143
146
|
if (!info || launchRendered.current)
|
|
144
147
|
return;
|
package/dist/ui/CommandInput.js
CHANGED
|
@@ -89,6 +89,8 @@ export function CommandInput({ active, placeholder, mask, context = 'hub', targe
|
|
|
89
89
|
const [selectedSuggestion, setSelectedSuggestion] = useState(0);
|
|
90
90
|
const [cursorOn, setCursorOn] = useState(true);
|
|
91
91
|
const attSeq = useRef(0);
|
|
92
|
+
const imageConsentUntil = useRef(0);
|
|
93
|
+
const imageConsentGranted = useRef(false);
|
|
92
94
|
const reset = () => {
|
|
93
95
|
setValue('');
|
|
94
96
|
setAttachments([]);
|
|
@@ -127,6 +129,13 @@ export function CommandInput({ active, placeholder, mask, context = 'hub', targe
|
|
|
127
129
|
notify?.(t('input.imageNone'));
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
if (!imageConsentGranted.current && imageConsentUntil.current < now) {
|
|
134
|
+
imageConsentUntil.current = now + 10_000;
|
|
135
|
+
notify?.(t('input.imageConsent'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
imageConsentGranted.current = true;
|
|
130
139
|
const n = ++attSeq.current;
|
|
131
140
|
setAttachments((arr) => [...arr, { kind: 'image', n, dataUri: img.dataUri, label: img.label }]);
|
|
132
141
|
notify?.(t('input.imageAdded'));
|
package/dist/ui/SettingsPanel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { createSkillTemplate, createSpecialistTemplate } from '../skills.js';
|
|
5
5
|
import { priceFor } from '../pricing.js';
|
|
@@ -7,6 +7,9 @@ import { SelectList as BaseSelectList } from './Wizard.js';
|
|
|
7
7
|
import { LANGS, getLang, setLang, t } from '../i18n.js';
|
|
8
8
|
import { detectProviderModels, isLocalProvider, isPlaceholderModel, providerNeedsApiKey, PROVIDER_PRESETS } from '../config.js';
|
|
9
9
|
import { BRAND } from './tokens.js';
|
|
10
|
+
function SettingsSelectList({ defaultBack, onBack, ...rest }) {
|
|
11
|
+
return _jsx(BaseSelectList, { ...rest, onBack: onBack ?? defaultBack });
|
|
12
|
+
}
|
|
10
13
|
function masked(key) {
|
|
11
14
|
if (!key)
|
|
12
15
|
return '—';
|
|
@@ -40,16 +43,12 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
40
43
|
const saved = () => setFlash(t('set.saved'));
|
|
41
44
|
const cfg = ctl.config;
|
|
42
45
|
const listHeight = height ? Math.max(3, height - 5) : undefined;
|
|
43
|
-
const goBack = () => {
|
|
46
|
+
const goBack = useCallback(() => {
|
|
44
47
|
if (step.id === 'root')
|
|
45
48
|
return onClose();
|
|
46
49
|
setStep(returnStep ?? { id: 'root' });
|
|
47
50
|
setReturnStep(null);
|
|
48
|
-
};
|
|
49
|
-
const SelectList = (props) => {
|
|
50
|
-
const { onBack, ...rest } = props;
|
|
51
|
-
return _jsx(BaseSelectList, { ...rest, onBack: onBack ?? goBack });
|
|
52
|
-
};
|
|
51
|
+
}, [onClose, returnStep, step.id]);
|
|
53
52
|
// ---- root menu items ----
|
|
54
53
|
const rootItems = scope === 'global'
|
|
55
54
|
? [
|
|
@@ -164,12 +163,12 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
164
163
|
setStep(next);
|
|
165
164
|
};
|
|
166
165
|
// ---- render ----
|
|
167
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor: BRAND.muted, flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: BRAND.primary, children: scope === 'global' ? t('set.title') : t('sset.title') }), flash ? _jsx(Text, { color: "green", children: flash }) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [step.id === 'root' && _jsx(
|
|
166
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: BRAND.muted, flexDirection: "column", paddingX: 1, children: [_jsx(Text, { bold: true, color: BRAND.primary, children: scope === 'global' ? t('set.title') : t('sset.title') }), flash ? _jsx(Text, { color: "green", children: flash }) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [step.id === 'root' && _jsx(SettingsSelectList, { defaultBack: goBack, items: rootItems, height: listHeight, onSelect: chooseRoot }), step.id === 'lang' && (_jsx(SettingsSelectList, { defaultBack: goBack, items: LANGS.map((l) => ({ label: l.label, value: l.code })), height: listHeight, onSelect: (code) => {
|
|
168
167
|
setLang(code);
|
|
169
168
|
ctl.setLanguage(code);
|
|
170
169
|
saved();
|
|
171
170
|
setStep({ id: 'root' });
|
|
172
|
-
} })), step.id === 'pickProvider' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.chooseProvider') }), _jsx(
|
|
171
|
+
} })), step.id === 'pickProvider' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.chooseProvider') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
173
172
|
...cfg.providers.map((p) => ({ label: p.name, value: p.name, hint: `(${p.baseUrl})` })),
|
|
174
173
|
{ label: t('set.back'), value: '__back__' },
|
|
175
174
|
], height: listHeight, onSelect: (v) => {
|
|
@@ -183,7 +182,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
183
182
|
if (step.next === 'models')
|
|
184
183
|
return setStep({ id: 'modelList', provider: p });
|
|
185
184
|
setStep({ id: 'model', provider: p });
|
|
186
|
-
} })] })), step.id === 'model' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.chooseModel', { name: step.provider.name }) }), _jsx(
|
|
185
|
+
} })] })), step.id === 'model' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.chooseModel', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
187
186
|
...step.provider.models.map((m) => ({
|
|
188
187
|
label: m,
|
|
189
188
|
value: m,
|
|
@@ -197,7 +196,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
197
196
|
return;
|
|
198
197
|
}
|
|
199
198
|
pickModel(step.provider, v);
|
|
200
|
-
}, onInput: (m) => pickModel(step.provider, m) })] })), step.id === 'endpoint' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.endpoint.title', { name: step.provider.name }) }), _jsx(Text, { color: "gray", children: step.provider.baseUrl }), _jsx(Text, { color: "gray", children: t('wiz.provider.endpoint.model', { model: step.provider.defaultModel || step.provider.models[0] || '—' }) }), _jsx(
|
|
199
|
+
}, onInput: (m) => pickModel(step.provider, m) })] })), step.id === 'endpoint' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.endpoint.title', { name: step.provider.name }) }), _jsx(Text, { color: "gray", children: step.provider.baseUrl }), _jsx(Text, { color: "gray", children: t('wiz.provider.endpoint.model', { model: step.provider.defaultModel || step.provider.models[0] || '—' }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
201
200
|
{ label: t('wiz.provider.endpoint.use'), value: 'use' },
|
|
202
201
|
{ label: t('wiz.provider.endpoint.edit'), value: 'edit' },
|
|
203
202
|
{ label: t('set.back'), value: '__back__' },
|
|
@@ -220,7 +219,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
220
219
|
saved();
|
|
221
220
|
setStep(returnStep ?? { id: 'providerDetail', provider: step.provider, scope });
|
|
222
221
|
setReturnStep(null);
|
|
223
|
-
} })] })), step.id === 'setupScope' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.setupScope.title', { name: step.provider.name }) }), _jsx(
|
|
222
|
+
} })] })), step.id === 'setupScope' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.setupScope.title', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
224
223
|
{ label: t('set.setupScope.session'), value: 'session' },
|
|
225
224
|
{ label: t('set.setupScope.global'), value: 'global' },
|
|
226
225
|
{ label: t('set.back'), value: '__back__' },
|
|
@@ -228,10 +227,10 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
228
227
|
if (v === '__back__')
|
|
229
228
|
return setStep({ id: 'endpoint', provider: step.provider, setup: true });
|
|
230
229
|
finishProviderSetup(step.provider, v === 'global');
|
|
231
|
-
} })] })), step.id === 'editEndpoint' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.url.title') }), _jsx(
|
|
230
|
+
} })] })), step.id === 'editEndpoint' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.url.title') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: step.provider.baseUrl, onInput: (url) => {
|
|
232
231
|
const provider = { ...step.provider, baseUrl: url.trim() };
|
|
233
232
|
setStep({ id: 'endpoint', provider, setup: step.setup });
|
|
234
|
-
} })] })), step.id === 'modelList' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.modelsFor', { name: step.provider.name }) }), _jsx(
|
|
233
|
+
} })] })), step.id === 'modelList' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.modelsFor', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
235
234
|
...step.provider.models.map((m) => ({
|
|
236
235
|
label: m,
|
|
237
236
|
value: m,
|
|
@@ -262,7 +261,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
262
261
|
saved();
|
|
263
262
|
setStep(returnStep ?? { id: 'root' });
|
|
264
263
|
setReturnStep(null);
|
|
265
|
-
} })] })), step.id === 'priceModel' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.priceModel', { name: step.provider.name }) }), _jsx(
|
|
264
|
+
} })] })), step.id === 'priceModel' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.priceModel', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
266
265
|
...step.provider.models.map((m) => {
|
|
267
266
|
const pr = priceFor(step.provider, m);
|
|
268
267
|
return {
|
|
@@ -281,7 +280,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
281
280
|
return;
|
|
282
281
|
}
|
|
283
282
|
goSub({ id: 'priceValue', provider: step.provider, model: v });
|
|
284
|
-
}, onInput: (m) => goSub({ id: 'priceValue', provider: step.provider, model: m.trim() }) })] })), step.id === 'priceValue' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.priceValue', { model: step.model }) }), _jsx(
|
|
283
|
+
}, onInput: (m) => goSub({ id: 'priceValue', provider: step.provider, model: m.trim() }) })] })), step.id === 'priceValue' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.priceValue', { model: step.model }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: "0.27, 1.10", onInput: (v) => {
|
|
285
284
|
const m = v.match(/^\s*([\d.]+)\s*[,;\s]\s*([\d.]+)\s*$/);
|
|
286
285
|
if (!m)
|
|
287
286
|
return setFlash(t('set.priceBad'));
|
|
@@ -293,7 +292,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
293
292
|
saved();
|
|
294
293
|
setStep(returnStep ?? { id: 'root' });
|
|
295
294
|
setReturnStep(null);
|
|
296
|
-
} })] })), step.id === 'key' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.key.title', { name: step.provider.name }) }), _jsx(
|
|
295
|
+
} })] })), step.id === 'key' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.key.title', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, mask: true, inputPlaceholder: "sk-\u2026", onInput: (k) => {
|
|
297
296
|
const provider = { ...step.provider, apiKey: k.trim() };
|
|
298
297
|
if (step.setup) {
|
|
299
298
|
if (scope === 'session') {
|
|
@@ -309,7 +308,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
309
308
|
}
|
|
310
309
|
setStep(returnStep ?? { id: 'root' });
|
|
311
310
|
setReturnStep(null);
|
|
312
|
-
} })] })), step.id === 'newName' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.name.title') }), _jsx(
|
|
311
|
+
} })] })), step.id === 'newName' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.name.title') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: t('wiz.provider.name.ph'), onInput: (name) => setStep({ id: 'newUrl', name }) })] })), step.id === 'newUrl' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.url.title') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: t('wiz.provider.url.ph'), onInput: (url) => setStep({ id: 'newModel', name: step.name, url }) })] })), step.id === 'newModel' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.model.title') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: t('wiz.provider.model.ph'), onInput: (model) => {
|
|
313
312
|
const trimmed = model.trim();
|
|
314
313
|
if (isPlaceholderModel(trimmed)) {
|
|
315
314
|
setFlash(t('set.modelPlaceholder'));
|
|
@@ -328,7 +327,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
328
327
|
requiresApiKey: !local,
|
|
329
328
|
},
|
|
330
329
|
});
|
|
331
|
-
} })] })), step.id === 'newKey' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.key.title', { name: step.name }) }), _jsx(
|
|
330
|
+
} })] })), step.id === 'newKey' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('wiz.provider.key.title', { name: step.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, mask: true, inputPlaceholder: "sk-\u2026", onInput: (key) => finishProviderSetup({ name: step.name, baseUrl: step.url, apiKey: key.trim(), models: [step.model], defaultModel: step.model }) })] })), step.id === 'newSkill' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.newSkillName') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: "review, deploy, tests\u2026", onInput: (name) => {
|
|
332
331
|
try {
|
|
333
332
|
const file = createSkillTemplate(name.trim(), '', 'global', ctl.projectRoot);
|
|
334
333
|
setFlash(t('m.skillCreated', { file }));
|
|
@@ -337,7 +336,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
337
336
|
setFlash(t('m.alreadyExists', { msg: e?.message ?? '' }));
|
|
338
337
|
}
|
|
339
338
|
setStep({ id: 'root' });
|
|
340
|
-
} })] })), step.id === 'newSpecialist' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.newSpecialistName') }), _jsx(
|
|
339
|
+
} })] })), step.id === 'newSpecialist' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.newSpecialistName') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [], height: listHeight, allowInput: true, inputPlaceholder: "reviewer, architect, tester\u2026", onInput: (name) => {
|
|
341
340
|
try {
|
|
342
341
|
const file = createSpecialistTemplate(name.trim(), '', 'global', ctl.projectRoot);
|
|
343
342
|
setFlash(t('m.specCreated', { file }));
|
|
@@ -346,7 +345,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
346
345
|
setFlash(t('m.alreadyExists', { msg: e?.message ?? '' }));
|
|
347
346
|
}
|
|
348
347
|
setStep({ id: 'root' });
|
|
349
|
-
} })] })), step.id === 'providers' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: step.scope === 'global' ? t('set.providers.title') : t('sset.providers.title') }), _jsx(
|
|
348
|
+
} })] })), step.id === 'providers' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: step.scope === 'global' ? t('set.providers.title') : t('sset.providers.title') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: (() => {
|
|
350
349
|
const configuredNames = new Set(cfg.providers.map((p) => p.name.toLowerCase()));
|
|
351
350
|
const items = [];
|
|
352
351
|
// Section: Configured
|
|
@@ -443,7 +442,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
443
442
|
else {
|
|
444
443
|
setStep({ id: 'providerDetail', provider: p, scope: 'global' });
|
|
445
444
|
}
|
|
446
|
-
} })] })), step.id === 'providerDetail' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.providerDetail.title', { name: step.provider.name }) }), _jsx(
|
|
445
|
+
} })] })), step.id === 'providerDetail' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.providerDetail.title', { name: step.provider.name }) }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
447
446
|
{
|
|
448
447
|
label: t('set.providerDetail.key'),
|
|
449
448
|
value: 'key',
|
|
@@ -508,7 +507,7 @@ export function SettingsPanel({ ctl, scope, height, onClose, }) {
|
|
|
508
507
|
}
|
|
509
508
|
if (v === 'remove')
|
|
510
509
|
return setStep({ id: 'removeProvider', provider: step.provider, scope: step.scope });
|
|
511
|
-
} })] })), step.id === 'removeProvider' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.removeProvider.title', { name: step.provider.name }) }), _jsx(Text, { color: "yellow", children: t('set.removeProvider.confirm') }), _jsx(
|
|
510
|
+
} })] })), step.id === 'removeProvider' && (_jsxs(_Fragment, { children: [_jsx(Text, { color: "gray", children: t('set.removeProvider.title', { name: step.provider.name }) }), _jsx(Text, { color: "yellow", children: t('set.removeProvider.confirm') }), _jsx(SettingsSelectList, { defaultBack: goBack, items: [
|
|
512
511
|
{ label: t('set.removeProvider.yes'), value: 'yes' },
|
|
513
512
|
{ label: t('set.removeProvider.no'), value: 'no' },
|
|
514
513
|
], height: listHeight, onSelect: (v) => {
|
package/dist/ui/Timeline.js
CHANGED
|
@@ -42,6 +42,9 @@ function TimelineRow({ item, cols }) {
|
|
|
42
42
|
if (item.kind === 'narration') {
|
|
43
43
|
return (_jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: UI.text, wrap: "wrap", children: item.detail }) }));
|
|
44
44
|
}
|
|
45
|
+
if (item.label === 'memory') {
|
|
46
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, borderStyle: "round", borderColor: UI.muted, paddingX: 1, children: _jsxs(Text, { color: UI.muted, wrap: "wrap", children: [_jsx(Text, { bold: true, children: "o " }), truncate(item.detail ?? '', max)] }) }));
|
|
47
|
+
}
|
|
45
48
|
if (item.kind === 'command') {
|
|
46
49
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: item.status === 'error' ? UI.danger : UI.text, wrap: "truncate-end", children: [_jsx(Text, { color: UI.muted, children: "\u2022 " }), _jsxs(Text, { bold: true, children: [t('timeline.ran'), " "] }), _jsx(Text, { color: UI.accent, children: truncate(item.command ?? '', max) })] }), _jsx(OutputLines, { item: item, cols: cols })] }));
|
|
47
50
|
}
|