@robota-sdk/agent-cli 3.0.0-beta.4 → 3.0.0-beta.40
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/README.md +43 -9
- package/dist/node/bin.cjs +2541 -578
- package/dist/node/bin.js +11 -1
- package/dist/node/chunk-CRPNSO52.js +3163 -0
- package/dist/node/index.cjs +2536 -583
- package/dist/node/index.js +1 -1
- package/package.json +4 -3
- package/dist/node/chunk-4DYVT4WI.js +0 -1196
package/dist/node/bin.cjs
CHANGED
|
@@ -24,41 +24,1022 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var import_node_path2 = require("path");
|
|
27
|
+
var import_node_fs4 = require("fs");
|
|
28
|
+
var import_node_path5 = require("path");
|
|
30
29
|
var import_node_url = require("url");
|
|
31
|
-
var
|
|
32
|
-
var
|
|
30
|
+
var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
|
|
31
|
+
var import_agent_sdk6 = require("@robota-sdk/agent-sdk");
|
|
33
32
|
|
|
34
|
-
// src/
|
|
35
|
-
var
|
|
36
|
-
var
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
// src/utils/cli-args.ts
|
|
34
|
+
var import_node_util = require("util");
|
|
35
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
36
|
+
function parsePermissionMode(raw) {
|
|
37
|
+
if (raw === void 0) return void 0;
|
|
38
|
+
if (!VALID_MODES.includes(raw)) {
|
|
39
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
40
|
+
`);
|
|
41
|
+
process.exit(1);
|
|
42
42
|
}
|
|
43
|
-
return
|
|
43
|
+
return raw;
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
function parseMaxTurns(raw) {
|
|
46
|
+
if (raw === void 0) return void 0;
|
|
47
|
+
const n = parseInt(raw, 10);
|
|
48
|
+
if (isNaN(n) || n <= 0) {
|
|
49
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
50
|
+
`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return n;
|
|
54
|
+
}
|
|
55
|
+
function parseCliArgs() {
|
|
56
|
+
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
57
|
+
allowPositionals: true,
|
|
58
|
+
options: {
|
|
59
|
+
p: { type: "boolean", short: "p", default: false },
|
|
60
|
+
c: { type: "boolean", short: "c", default: false },
|
|
61
|
+
r: { type: "string", short: "r" },
|
|
62
|
+
model: { type: "string" },
|
|
63
|
+
language: { type: "string" },
|
|
64
|
+
"permission-mode": { type: "string" },
|
|
65
|
+
"max-turns": { type: "string" },
|
|
66
|
+
version: { type: "boolean", default: false },
|
|
67
|
+
reset: { type: "boolean", default: false }
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
positional: positionals,
|
|
72
|
+
printMode: values["p"] ?? false,
|
|
73
|
+
continueMode: values["c"] ?? false,
|
|
74
|
+
resumeId: values["r"],
|
|
75
|
+
model: values["model"],
|
|
76
|
+
language: values["language"],
|
|
77
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
78
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
79
|
+
version: values["version"] ?? false,
|
|
80
|
+
reset: values["reset"] ?? false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/utils/settings-io.ts
|
|
85
|
+
var import_node_fs = require("fs");
|
|
86
|
+
var import_node_path = require("path");
|
|
87
|
+
function getUserSettingsPath() {
|
|
88
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
89
|
+
return (0, import_node_path.join)(home, ".robota", "settings.json");
|
|
90
|
+
}
|
|
91
|
+
function readSettings(path) {
|
|
92
|
+
if (!(0, import_node_fs.existsSync)(path)) return {};
|
|
93
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
94
|
+
}
|
|
95
|
+
function writeSettings(path, settings) {
|
|
96
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
97
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
98
|
+
}
|
|
99
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
100
|
+
const settings = readSettings(settingsPath);
|
|
101
|
+
const provider = settings.provider ?? {};
|
|
102
|
+
provider.model = modelId;
|
|
103
|
+
settings.provider = provider;
|
|
104
|
+
writeSettings(settingsPath, settings);
|
|
105
|
+
}
|
|
106
|
+
function deleteSettings(path) {
|
|
107
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
108
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
52
112
|
}
|
|
53
113
|
|
|
114
|
+
// src/print-terminal.ts
|
|
115
|
+
var readline = __toESM(require("readline"), 1);
|
|
116
|
+
var PrintTerminal = class {
|
|
117
|
+
write(text) {
|
|
118
|
+
process.stdout.write(text);
|
|
119
|
+
}
|
|
120
|
+
writeLine(text) {
|
|
121
|
+
process.stdout.write(text + "\n");
|
|
122
|
+
}
|
|
123
|
+
writeMarkdown(md) {
|
|
124
|
+
process.stdout.write(md);
|
|
125
|
+
}
|
|
126
|
+
writeError(text) {
|
|
127
|
+
process.stderr.write(text + "\n");
|
|
128
|
+
}
|
|
129
|
+
prompt(question) {
|
|
130
|
+
return new Promise((resolve) => {
|
|
131
|
+
const rl = readline.createInterface({
|
|
132
|
+
input: process.stdin,
|
|
133
|
+
output: process.stdout,
|
|
134
|
+
terminal: false,
|
|
135
|
+
historySize: 0
|
|
136
|
+
});
|
|
137
|
+
rl.question(question, (answer) => {
|
|
138
|
+
rl.close();
|
|
139
|
+
resolve(answer);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async select(options, initialIndex = 0) {
|
|
144
|
+
for (let i = 0; i < options.length; i++) {
|
|
145
|
+
const marker = i === initialIndex ? ">" : " ";
|
|
146
|
+
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
147
|
+
`);
|
|
148
|
+
}
|
|
149
|
+
const answer = await this.prompt(
|
|
150
|
+
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
151
|
+
);
|
|
152
|
+
const trimmed = answer.trim().toLowerCase();
|
|
153
|
+
if (trimmed === "") return initialIndex;
|
|
154
|
+
const num = parseInt(trimmed, 10);
|
|
155
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
156
|
+
return initialIndex;
|
|
157
|
+
}
|
|
158
|
+
spinner(_message) {
|
|
159
|
+
return { stop() {
|
|
160
|
+
}, update() {
|
|
161
|
+
} };
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
54
165
|
// src/ui/render.tsx
|
|
55
|
-
var
|
|
166
|
+
var import_ink14 = require("ink");
|
|
56
167
|
|
|
57
168
|
// src/ui/App.tsx
|
|
58
|
-
var
|
|
59
|
-
var
|
|
169
|
+
var import_react16 = require("react");
|
|
170
|
+
var import_ink13 = require("ink");
|
|
171
|
+
var import_agent_core6 = require("@robota-sdk/agent-core");
|
|
172
|
+
var import_agent_core7 = require("@robota-sdk/agent-core");
|
|
173
|
+
|
|
174
|
+
// src/ui/hooks/useSession.ts
|
|
175
|
+
var import_react = require("react");
|
|
60
176
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
61
177
|
|
|
178
|
+
// src/utils/edit-diff.ts
|
|
179
|
+
var import_node_fs2 = require("fs");
|
|
180
|
+
var CONTEXT_LINES = 2;
|
|
181
|
+
function generateDiffLines(oldStr, newStr, startLine = 1) {
|
|
182
|
+
if (oldStr === newStr) return [];
|
|
183
|
+
const lines = [];
|
|
184
|
+
const oldLines = oldStr.split("\n");
|
|
185
|
+
const newLines = newStr.split("\n");
|
|
186
|
+
for (let i = 0; i < oldLines.length; i++) {
|
|
187
|
+
lines.push({ type: "remove", text: oldLines[i], lineNumber: startLine + i });
|
|
188
|
+
}
|
|
189
|
+
for (let i = 0; i < newLines.length; i++) {
|
|
190
|
+
lines.push({ type: "add", text: newLines[i], lineNumber: startLine + i });
|
|
191
|
+
}
|
|
192
|
+
return lines;
|
|
193
|
+
}
|
|
194
|
+
function generateDiffLinesWithContext(oldStr, newStr, startLine, filePath) {
|
|
195
|
+
if (oldStr === newStr) return [];
|
|
196
|
+
const diffLines = generateDiffLines(oldStr, newStr, startLine);
|
|
197
|
+
let fileLines;
|
|
198
|
+
try {
|
|
199
|
+
fileLines = (0, import_node_fs2.readFileSync)(filePath, "utf-8").split("\n");
|
|
200
|
+
} catch {
|
|
201
|
+
return diffLines;
|
|
202
|
+
}
|
|
203
|
+
const result = [];
|
|
204
|
+
const contextStart = Math.max(0, startLine - 1 - CONTEXT_LINES);
|
|
205
|
+
for (let i = contextStart; i < startLine - 1; i++) {
|
|
206
|
+
if (i < fileLines.length) {
|
|
207
|
+
result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
result.push(...diffLines);
|
|
211
|
+
const newLineCount = newStr.split("\n").length;
|
|
212
|
+
const afterStart = startLine - 1 + newLineCount;
|
|
213
|
+
for (let i = afterStart; i < afterStart + CONTEXT_LINES; i++) {
|
|
214
|
+
if (i < fileLines.length) {
|
|
215
|
+
result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
function extractEditDiff(toolName, toolArgs, startLine) {
|
|
221
|
+
if (toolName !== "Edit" || !toolArgs) return null;
|
|
222
|
+
const filePath = toolArgs.file_path ?? toolArgs.filePath;
|
|
223
|
+
const oldStr = toolArgs.old_string ?? toolArgs.oldString;
|
|
224
|
+
const newStr = toolArgs.new_string ?? toolArgs.newString;
|
|
225
|
+
if (typeof filePath !== "string") return null;
|
|
226
|
+
if (typeof oldStr !== "string" || typeof newStr !== "string") return null;
|
|
227
|
+
let sl = startLine ?? 0;
|
|
228
|
+
if (!sl) {
|
|
229
|
+
try {
|
|
230
|
+
const fileContent = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
231
|
+
const idx = fileContent.indexOf(newStr);
|
|
232
|
+
if (idx >= 0) {
|
|
233
|
+
sl = fileContent.substring(0, idx).split("\n").length;
|
|
234
|
+
} else {
|
|
235
|
+
sl = 1;
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
sl = 1;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const lines = generateDiffLinesWithContext(oldStr, newStr, sl, filePath);
|
|
242
|
+
if (lines.length === 0) return null;
|
|
243
|
+
return { file: filePath, lines };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/ui/hooks/useSession.ts
|
|
247
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
248
|
+
var TAIL_KEEP = 30;
|
|
249
|
+
var MAX_COMPLETED_TOOLS = 50;
|
|
250
|
+
var NOOP_TERMINAL = {
|
|
251
|
+
write: () => {
|
|
252
|
+
},
|
|
253
|
+
writeLine: () => {
|
|
254
|
+
},
|
|
255
|
+
writeMarkdown: () => {
|
|
256
|
+
},
|
|
257
|
+
writeError: () => {
|
|
258
|
+
},
|
|
259
|
+
prompt: () => Promise.resolve(""),
|
|
260
|
+
select: () => Promise.resolve(0),
|
|
261
|
+
spinner: () => ({ stop: () => {
|
|
262
|
+
}, update: () => {
|
|
263
|
+
} })
|
|
264
|
+
};
|
|
265
|
+
function useSession(props) {
|
|
266
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
|
|
267
|
+
const [streamingText, setStreamingText] = (0, import_react.useState)("");
|
|
268
|
+
const streamingTextRef = (0, import_react.useRef)("");
|
|
269
|
+
const [activeTools, setActiveTools] = (0, import_react.useState)([]);
|
|
270
|
+
const permissionQueueRef = (0, import_react.useRef)([]);
|
|
271
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
272
|
+
const processNextPermission = (0, import_react.useCallback)(() => {
|
|
273
|
+
if (processingRef.current) return;
|
|
274
|
+
const next = permissionQueueRef.current[0];
|
|
275
|
+
if (!next) {
|
|
276
|
+
setPermissionRequest(null);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
processingRef.current = true;
|
|
280
|
+
setPermissionRequest({
|
|
281
|
+
toolName: next.toolName,
|
|
282
|
+
toolArgs: next.toolArgs,
|
|
283
|
+
resolve: (result) => {
|
|
284
|
+
permissionQueueRef.current.shift();
|
|
285
|
+
processingRef.current = false;
|
|
286
|
+
setPermissionRequest(null);
|
|
287
|
+
next.resolve(result);
|
|
288
|
+
setTimeout(() => processNextPermission(), 0);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}, []);
|
|
292
|
+
const sessionRef = (0, import_react.useRef)(null);
|
|
293
|
+
if (sessionRef.current === null) {
|
|
294
|
+
const permissionHandler = (toolName, toolArgs) => {
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
297
|
+
processNextPermission();
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
let flushTimer = null;
|
|
301
|
+
const onTextDelta = (delta) => {
|
|
302
|
+
streamingTextRef.current += delta;
|
|
303
|
+
if (!flushTimer) {
|
|
304
|
+
flushTimer = setTimeout(() => {
|
|
305
|
+
setStreamingText(streamingTextRef.current);
|
|
306
|
+
flushTimer = null;
|
|
307
|
+
}, 16);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const onToolExecution = (event) => {
|
|
311
|
+
if (event.type === "start") {
|
|
312
|
+
let firstArg = "";
|
|
313
|
+
if (event.toolArgs) {
|
|
314
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
315
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
316
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
317
|
+
}
|
|
318
|
+
setActiveTools((prev) => [
|
|
319
|
+
...prev,
|
|
320
|
+
{ toolName: event.toolName, firstArg, isRunning: true, _toolArgs: event.toolArgs }
|
|
321
|
+
]);
|
|
322
|
+
} else {
|
|
323
|
+
const toolResult = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
324
|
+
setActiveTools((prev) => {
|
|
325
|
+
const updated = prev.map((t) => {
|
|
326
|
+
if (!(t.toolName === event.toolName && t.isRunning)) return t;
|
|
327
|
+
let startLine;
|
|
328
|
+
if (event.toolResultData && event.toolName === "Edit") {
|
|
329
|
+
try {
|
|
330
|
+
const parsed = JSON.parse(event.toolResultData);
|
|
331
|
+
if (typeof parsed.startLine === "number") {
|
|
332
|
+
startLine = parsed.startLine;
|
|
333
|
+
}
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const editDiff = extractEditDiff(
|
|
338
|
+
event.toolName,
|
|
339
|
+
t._toolArgs,
|
|
340
|
+
startLine
|
|
341
|
+
);
|
|
342
|
+
const finished = {
|
|
343
|
+
...t,
|
|
344
|
+
isRunning: false,
|
|
345
|
+
result: toolResult
|
|
346
|
+
};
|
|
347
|
+
if (editDiff) {
|
|
348
|
+
finished.diffLines = editDiff.lines;
|
|
349
|
+
finished.diffFile = editDiff.file;
|
|
350
|
+
}
|
|
351
|
+
delete finished._toolArgs;
|
|
352
|
+
return finished;
|
|
353
|
+
});
|
|
354
|
+
const completed = updated.filter((t) => !t.isRunning);
|
|
355
|
+
if (completed.length > MAX_COMPLETED_TOOLS) {
|
|
356
|
+
const excess = completed.length - MAX_COMPLETED_TOOLS;
|
|
357
|
+
let removed = 0;
|
|
358
|
+
return updated.filter((t) => {
|
|
359
|
+
if (!t.isRunning && removed < excess) {
|
|
360
|
+
removed++;
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
return true;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return updated;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
371
|
+
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
372
|
+
config: props.config,
|
|
373
|
+
context: props.context,
|
|
374
|
+
terminal: NOOP_TERMINAL,
|
|
375
|
+
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
376
|
+
projectInfo: props.projectInfo,
|
|
377
|
+
sessionStore: props.sessionStore,
|
|
378
|
+
permissionMode: props.permissionMode,
|
|
379
|
+
maxTurns: props.maxTurns,
|
|
380
|
+
permissionHandler,
|
|
381
|
+
onTextDelta,
|
|
382
|
+
onToolExecution
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
const clearStreamingText = (0, import_react.useCallback)(() => {
|
|
386
|
+
setStreamingText("");
|
|
387
|
+
streamingTextRef.current = "";
|
|
388
|
+
setActiveTools([]);
|
|
389
|
+
}, []);
|
|
390
|
+
return {
|
|
391
|
+
session: sessionRef.current,
|
|
392
|
+
permissionRequest,
|
|
393
|
+
streamingText,
|
|
394
|
+
clearStreamingText,
|
|
395
|
+
activeTools
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/ui/hooks/useMessages.ts
|
|
400
|
+
var import_react2 = require("react");
|
|
401
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
402
|
+
function useMessages() {
|
|
403
|
+
const [messages, setMessages] = (0, import_react2.useState)([]);
|
|
404
|
+
const addMessage = (0, import_react2.useCallback)((msg) => {
|
|
405
|
+
setMessages((prev) => {
|
|
406
|
+
const updated = [...prev, msg];
|
|
407
|
+
if (updated.length > MAX_RENDERED_MESSAGES) {
|
|
408
|
+
return updated.slice(-MAX_RENDERED_MESSAGES);
|
|
409
|
+
}
|
|
410
|
+
return updated;
|
|
411
|
+
});
|
|
412
|
+
}, []);
|
|
413
|
+
return { messages, setMessages, addMessage };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
417
|
+
var import_react3 = require("react");
|
|
418
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
419
|
+
|
|
420
|
+
// src/commands/slash-executor.ts
|
|
421
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
422
|
+
var HELP_TEXT = [
|
|
423
|
+
"Available commands:",
|
|
424
|
+
" /help \u2014 Show this help",
|
|
425
|
+
" /clear \u2014 Clear conversation",
|
|
426
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
427
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
428
|
+
" /language [lang] \u2014 Set response language (ko, en, ja, zh)",
|
|
429
|
+
" /cost \u2014 Show session info",
|
|
430
|
+
" /reset \u2014 Delete settings and exit",
|
|
431
|
+
" /exit \u2014 Exit CLI"
|
|
432
|
+
].join("\n");
|
|
433
|
+
function handleHelp(addMessage) {
|
|
434
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
435
|
+
return { handled: true };
|
|
436
|
+
}
|
|
437
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
438
|
+
clearMessages();
|
|
439
|
+
session.clearHistory();
|
|
440
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
441
|
+
return { handled: true };
|
|
442
|
+
}
|
|
443
|
+
async function handleCompact(args, session, addMessage) {
|
|
444
|
+
const instructions = args.trim() || void 0;
|
|
445
|
+
const before = session.getContextState().usedPercentage;
|
|
446
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
447
|
+
await session.compact(instructions);
|
|
448
|
+
const after = session.getContextState().usedPercentage;
|
|
449
|
+
addMessage({
|
|
450
|
+
role: "system",
|
|
451
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
452
|
+
});
|
|
453
|
+
return { handled: true };
|
|
454
|
+
}
|
|
455
|
+
function handleMode(arg, session, addMessage) {
|
|
456
|
+
if (!arg) {
|
|
457
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
458
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
459
|
+
session.setPermissionMode(arg);
|
|
460
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
461
|
+
} else {
|
|
462
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
463
|
+
}
|
|
464
|
+
return { handled: true };
|
|
465
|
+
}
|
|
466
|
+
function handleModel(modelId, addMessage) {
|
|
467
|
+
if (!modelId) {
|
|
468
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
469
|
+
return { handled: true };
|
|
470
|
+
}
|
|
471
|
+
return { handled: true, pendingModelId: modelId };
|
|
472
|
+
}
|
|
473
|
+
function handleCost(session, addMessage) {
|
|
474
|
+
addMessage({
|
|
475
|
+
role: "system",
|
|
476
|
+
content: `Session: ${session.getSessionId()}
|
|
477
|
+
Messages: ${session.getMessageCount()}`
|
|
478
|
+
});
|
|
479
|
+
return { handled: true };
|
|
480
|
+
}
|
|
481
|
+
function handlePermissions(session, addMessage) {
|
|
482
|
+
const mode = session.getPermissionMode();
|
|
483
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
484
|
+
const lines = [`Permission mode: ${mode}`];
|
|
485
|
+
if (sessionAllowed.length > 0) {
|
|
486
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
487
|
+
} else {
|
|
488
|
+
lines.push("No session-approved tools.");
|
|
489
|
+
}
|
|
490
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
491
|
+
return { handled: true };
|
|
492
|
+
}
|
|
493
|
+
function handleContext(session, addMessage) {
|
|
494
|
+
const ctx = session.getContextState();
|
|
495
|
+
addMessage({
|
|
496
|
+
role: "system",
|
|
497
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
498
|
+
});
|
|
499
|
+
return { handled: true };
|
|
500
|
+
}
|
|
501
|
+
function handleLanguage(lang, addMessage) {
|
|
502
|
+
if (!lang) {
|
|
503
|
+
addMessage({ role: "system", content: "Usage: /language <code> (e.g., ko, en, ja, zh)" });
|
|
504
|
+
return { handled: true };
|
|
505
|
+
}
|
|
506
|
+
const settingsPath = getUserSettingsPath();
|
|
507
|
+
const settings = readSettings(settingsPath);
|
|
508
|
+
settings.language = lang;
|
|
509
|
+
writeSettings(settingsPath, settings);
|
|
510
|
+
addMessage({ role: "system", content: `Language set to "${lang}". Restarting...` });
|
|
511
|
+
return { handled: true, exitRequested: true };
|
|
512
|
+
}
|
|
513
|
+
function handleReset(addMessage) {
|
|
514
|
+
const settingsPath = getUserSettingsPath();
|
|
515
|
+
if (deleteSettings(settingsPath)) {
|
|
516
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
517
|
+
} else {
|
|
518
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
519
|
+
}
|
|
520
|
+
return { handled: true, exitRequested: true };
|
|
521
|
+
}
|
|
522
|
+
async function handlePluginCommand(args, addMessage, callbacks) {
|
|
523
|
+
const parts = args.trim().split(/\s+/);
|
|
524
|
+
const subcommand = parts[0] ?? "";
|
|
525
|
+
const subArgs = parts.slice(1).join(" ").trim();
|
|
526
|
+
try {
|
|
527
|
+
switch (subcommand) {
|
|
528
|
+
case "":
|
|
529
|
+
case void 0:
|
|
530
|
+
case "manage": {
|
|
531
|
+
return { handled: true, triggerPluginTUI: true };
|
|
532
|
+
}
|
|
533
|
+
case "install": {
|
|
534
|
+
if (!subArgs) {
|
|
535
|
+
addMessage({ role: "system", content: "Usage: /plugin install <name>@<marketplace>" });
|
|
536
|
+
return { handled: true };
|
|
537
|
+
}
|
|
538
|
+
await callbacks.install(subArgs);
|
|
539
|
+
addMessage({ role: "system", content: `Installed plugin: ${subArgs}` });
|
|
540
|
+
return { handled: true };
|
|
541
|
+
}
|
|
542
|
+
case "uninstall": {
|
|
543
|
+
if (!subArgs) {
|
|
544
|
+
addMessage({ role: "system", content: "Usage: /plugin uninstall <name>@<marketplace>" });
|
|
545
|
+
return { handled: true };
|
|
546
|
+
}
|
|
547
|
+
await callbacks.uninstall(subArgs);
|
|
548
|
+
addMessage({ role: "system", content: `Uninstalled plugin: ${subArgs}` });
|
|
549
|
+
return { handled: true };
|
|
550
|
+
}
|
|
551
|
+
case "enable": {
|
|
552
|
+
if (!subArgs) {
|
|
553
|
+
addMessage({ role: "system", content: "Usage: /plugin enable <name>@<marketplace>" });
|
|
554
|
+
return { handled: true };
|
|
555
|
+
}
|
|
556
|
+
await callbacks.enable(subArgs);
|
|
557
|
+
addMessage({ role: "system", content: `Enabled plugin: ${subArgs}` });
|
|
558
|
+
return { handled: true };
|
|
559
|
+
}
|
|
560
|
+
case "disable": {
|
|
561
|
+
if (!subArgs) {
|
|
562
|
+
addMessage({ role: "system", content: "Usage: /plugin disable <name>@<marketplace>" });
|
|
563
|
+
return { handled: true };
|
|
564
|
+
}
|
|
565
|
+
await callbacks.disable(subArgs);
|
|
566
|
+
addMessage({ role: "system", content: `Disabled plugin: ${subArgs}` });
|
|
567
|
+
return { handled: true };
|
|
568
|
+
}
|
|
569
|
+
case "marketplace": {
|
|
570
|
+
const mpParts = subArgs.split(/\s+/);
|
|
571
|
+
const mpSubcommand = mpParts[0] ?? "";
|
|
572
|
+
const mpArgs = mpParts.slice(1).join(" ").trim();
|
|
573
|
+
if (mpSubcommand === "add" && mpArgs) {
|
|
574
|
+
const registeredName = await callbacks.marketplaceAdd(mpArgs);
|
|
575
|
+
addMessage({
|
|
576
|
+
role: "system",
|
|
577
|
+
content: `Added marketplace: "${registeredName}" (from ${mpArgs})
|
|
578
|
+
Install plugins with: /plugin install <name>@${registeredName}`
|
|
579
|
+
});
|
|
580
|
+
return { handled: true };
|
|
581
|
+
} else if (mpSubcommand === "remove" && mpArgs) {
|
|
582
|
+
await callbacks.marketplaceRemove(mpArgs);
|
|
583
|
+
addMessage({
|
|
584
|
+
role: "system",
|
|
585
|
+
content: `Removed marketplace "${mpArgs}" and uninstalled its plugins.`
|
|
586
|
+
});
|
|
587
|
+
return { handled: true };
|
|
588
|
+
} else if (mpSubcommand === "update" && mpArgs) {
|
|
589
|
+
await callbacks.marketplaceUpdate(mpArgs);
|
|
590
|
+
addMessage({
|
|
591
|
+
role: "system",
|
|
592
|
+
content: `Updated marketplace "${mpArgs}".`
|
|
593
|
+
});
|
|
594
|
+
return { handled: true };
|
|
595
|
+
} else if (mpSubcommand === "list") {
|
|
596
|
+
const sources = await callbacks.marketplaceList();
|
|
597
|
+
if (sources.length === 0) {
|
|
598
|
+
addMessage({ role: "system", content: "No marketplace sources configured." });
|
|
599
|
+
} else {
|
|
600
|
+
const lines = sources.map((s) => ` ${s.name} (${s.type})`);
|
|
601
|
+
addMessage({ role: "system", content: `Marketplace sources:
|
|
602
|
+
${lines.join("\n")}` });
|
|
603
|
+
}
|
|
604
|
+
return { handled: true };
|
|
605
|
+
} else {
|
|
606
|
+
addMessage({
|
|
607
|
+
role: "system",
|
|
608
|
+
content: "Usage: /plugin marketplace add <source> | remove <name> | update <name> | list"
|
|
609
|
+
});
|
|
610
|
+
return { handled: true };
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
default:
|
|
614
|
+
addMessage({ role: "system", content: `Unknown plugin subcommand: ${subcommand}` });
|
|
615
|
+
return { handled: true };
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
619
|
+
addMessage({ role: "system", content: `Plugin error: ${message}` });
|
|
620
|
+
return { handled: true };
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async function handleReloadPlugins(addMessage, callbacks) {
|
|
624
|
+
await callbacks.reloadPlugins();
|
|
625
|
+
addMessage({ role: "system", content: "Plugins reload complete." });
|
|
626
|
+
return { handled: true };
|
|
627
|
+
}
|
|
628
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry, pluginCallbacks) {
|
|
629
|
+
switch (cmd) {
|
|
630
|
+
case "help":
|
|
631
|
+
return handleHelp(addMessage);
|
|
632
|
+
case "clear":
|
|
633
|
+
return handleClear(addMessage, clearMessages, session);
|
|
634
|
+
case "compact":
|
|
635
|
+
return handleCompact(args, session, addMessage);
|
|
636
|
+
case "mode":
|
|
637
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
638
|
+
case "model":
|
|
639
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
640
|
+
case "language":
|
|
641
|
+
return handleLanguage(args.split(/\s+/)[0] || void 0, addMessage);
|
|
642
|
+
case "cost":
|
|
643
|
+
return handleCost(session, addMessage);
|
|
644
|
+
case "permissions":
|
|
645
|
+
return handlePermissions(session, addMessage);
|
|
646
|
+
case "context":
|
|
647
|
+
return handleContext(session, addMessage);
|
|
648
|
+
case "reset":
|
|
649
|
+
return handleReset(addMessage);
|
|
650
|
+
case "exit":
|
|
651
|
+
return { handled: true, exitRequested: true };
|
|
652
|
+
case "plugin":
|
|
653
|
+
if (pluginCallbacks) {
|
|
654
|
+
return handlePluginCommand(args, addMessage, pluginCallbacks);
|
|
655
|
+
}
|
|
656
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
657
|
+
return { handled: true };
|
|
658
|
+
case "reload-plugins":
|
|
659
|
+
if (pluginCallbacks) {
|
|
660
|
+
return handleReloadPlugins(addMessage, pluginCallbacks);
|
|
661
|
+
}
|
|
662
|
+
addMessage({ role: "system", content: "Plugin management is not available." });
|
|
663
|
+
return { handled: true };
|
|
664
|
+
default: {
|
|
665
|
+
const dynamicCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
666
|
+
if (dynamicCmd) {
|
|
667
|
+
addMessage({ role: "system", content: `Invoking ${dynamicCmd.source}: ${cmd}` });
|
|
668
|
+
return { handled: false };
|
|
669
|
+
}
|
|
670
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
671
|
+
return { handled: true };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// src/ui/hooks/useSlashCommands.ts
|
|
677
|
+
var EXIT_DELAY_MS = 500;
|
|
678
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks, setShowPluginTUI) {
|
|
679
|
+
return (0, import_react3.useCallback)(
|
|
680
|
+
async (input) => {
|
|
681
|
+
const parts = input.slice(1).split(/\s+/);
|
|
682
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
683
|
+
const args = parts.slice(1).join(" ");
|
|
684
|
+
const clearMessages = () => setMessages([]);
|
|
685
|
+
const slashAddMessage = (msg) => {
|
|
686
|
+
addMessage((0, import_agent_core.createSystemMessage)(msg.content));
|
|
687
|
+
};
|
|
688
|
+
const result = await executeSlashCommand(
|
|
689
|
+
cmd,
|
|
690
|
+
args,
|
|
691
|
+
session,
|
|
692
|
+
slashAddMessage,
|
|
693
|
+
clearMessages,
|
|
694
|
+
registry,
|
|
695
|
+
pluginCallbacks
|
|
696
|
+
);
|
|
697
|
+
if (result.pendingModelId) {
|
|
698
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
699
|
+
setPendingModelId(result.pendingModelId);
|
|
700
|
+
}
|
|
701
|
+
if (result.triggerPluginTUI) {
|
|
702
|
+
setShowPluginTUI?.(true);
|
|
703
|
+
}
|
|
704
|
+
if (result.exitRequested) {
|
|
705
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
706
|
+
}
|
|
707
|
+
return result.handled;
|
|
708
|
+
},
|
|
709
|
+
[
|
|
710
|
+
session,
|
|
711
|
+
addMessage,
|
|
712
|
+
setMessages,
|
|
713
|
+
exit,
|
|
714
|
+
registry,
|
|
715
|
+
pendingModelChangeRef,
|
|
716
|
+
setPendingModelId,
|
|
717
|
+
pluginCallbacks,
|
|
718
|
+
setShowPluginTUI
|
|
719
|
+
]
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
724
|
+
var import_react4 = require("react");
|
|
725
|
+
var import_node_crypto = require("crypto");
|
|
726
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
727
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
728
|
+
|
|
729
|
+
// src/utils/tool-call-extractor.ts
|
|
730
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
731
|
+
var TAIL_KEEP2 = 30;
|
|
732
|
+
function extractToolCallsWithDiff(history, startIndex) {
|
|
733
|
+
const summaries = [];
|
|
734
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
735
|
+
const msg = history[i];
|
|
736
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
737
|
+
for (const tc of msg.toolCalls) {
|
|
738
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
739
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_MAX_LENGTH - TAIL_KEEP2 - 3) + "..." + value.slice(-TAIL_KEEP2) : value;
|
|
740
|
+
const summary = {
|
|
741
|
+
line: `${tc.function.name}(${truncated})`
|
|
742
|
+
};
|
|
743
|
+
if (tc.function.name === "Edit") {
|
|
744
|
+
try {
|
|
745
|
+
const args = JSON.parse(tc.function.arguments);
|
|
746
|
+
const diff = extractEditDiff("Edit", args);
|
|
747
|
+
if (diff) {
|
|
748
|
+
summary.diffLines = diff.lines;
|
|
749
|
+
summary.diffFile = diff.file;
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
summaries.push(summary);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return summaries;
|
|
759
|
+
}
|
|
760
|
+
function parseFirstArgValue(argsJson) {
|
|
761
|
+
try {
|
|
762
|
+
const parsed = JSON.parse(argsJson);
|
|
763
|
+
const firstVal = Object.values(parsed)[0];
|
|
764
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
765
|
+
} catch {
|
|
766
|
+
return argsJson;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/utils/skill-prompt.ts
|
|
771
|
+
var import_node_child_process = require("child_process");
|
|
772
|
+
function substituteVariables(content, args, context) {
|
|
773
|
+
const argParts = args ? args.split(/\s+/) : [];
|
|
774
|
+
let result = content;
|
|
775
|
+
result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
|
|
776
|
+
return argParts[Number(index)] ?? "";
|
|
777
|
+
});
|
|
778
|
+
result = result.replace(/\$ARGUMENTS/g, args);
|
|
779
|
+
result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
|
|
780
|
+
return argParts[Number(digit)] ?? "";
|
|
781
|
+
});
|
|
782
|
+
result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
|
|
783
|
+
result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
|
|
784
|
+
return result;
|
|
785
|
+
}
|
|
786
|
+
async function preprocessShellCommands(content) {
|
|
787
|
+
const shellPattern = /!`([^`]+)`/g;
|
|
788
|
+
if (!shellPattern.test(content)) {
|
|
789
|
+
return content;
|
|
790
|
+
}
|
|
791
|
+
shellPattern.lastIndex = 0;
|
|
792
|
+
let result = content;
|
|
793
|
+
let match;
|
|
794
|
+
const matches = [];
|
|
795
|
+
while ((match = shellPattern.exec(content)) !== null) {
|
|
796
|
+
matches.push({ full: match[0], command: match[1] });
|
|
797
|
+
}
|
|
798
|
+
for (const { full, command } of matches) {
|
|
799
|
+
let output = "";
|
|
800
|
+
try {
|
|
801
|
+
output = (0, import_node_child_process.execSync)(command, {
|
|
802
|
+
timeout: 5e3,
|
|
803
|
+
encoding: "utf-8",
|
|
804
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
805
|
+
}).trimEnd();
|
|
806
|
+
} catch {
|
|
807
|
+
output = "";
|
|
808
|
+
}
|
|
809
|
+
result = result.replace(full, output);
|
|
810
|
+
}
|
|
811
|
+
return result;
|
|
812
|
+
}
|
|
813
|
+
async function buildSkillPrompt(input, registry, context) {
|
|
814
|
+
const parts = input.slice(1).split(/\s+/);
|
|
815
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
816
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
817
|
+
if (!skillCmd) return null;
|
|
818
|
+
const args = parts.slice(1).join(" ").trim();
|
|
819
|
+
const userInstruction = args || skillCmd.description;
|
|
820
|
+
if (skillCmd.skillContent) {
|
|
821
|
+
let processed = await preprocessShellCommands(skillCmd.skillContent);
|
|
822
|
+
processed = substituteVariables(processed, args, context);
|
|
823
|
+
return `<skill name="${cmd}">
|
|
824
|
+
${processed}
|
|
825
|
+
</skill>
|
|
826
|
+
|
|
827
|
+
Execute the "${cmd}" skill: ${userInstruction}`;
|
|
828
|
+
}
|
|
829
|
+
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/commands/skill-executor.ts
|
|
833
|
+
function buildProcessedContent(skill, args, context) {
|
|
834
|
+
if (!skill.skillContent) return null;
|
|
835
|
+
return substituteVariables(skill.skillContent, args, context);
|
|
836
|
+
}
|
|
837
|
+
function buildInjectPrompt(skill, args, context) {
|
|
838
|
+
const processed = buildProcessedContent(skill, args, context);
|
|
839
|
+
if (processed) {
|
|
840
|
+
const userInstruction = args || skill.description;
|
|
841
|
+
return `<skill name="${skill.name}">
|
|
842
|
+
${processed}
|
|
843
|
+
</skill>
|
|
844
|
+
|
|
845
|
+
Execute the "${skill.name}" skill: ${userInstruction}`;
|
|
846
|
+
}
|
|
847
|
+
return `Use the "${skill.name}" skill: ${args || skill.description}`;
|
|
848
|
+
}
|
|
849
|
+
async function executeSkill(skill, args, callbacks, context) {
|
|
850
|
+
if (skill.context === "fork") {
|
|
851
|
+
if (!callbacks.runInFork) {
|
|
852
|
+
throw new Error("Fork execution is not available. Agent tool deps may not be initialized.");
|
|
853
|
+
}
|
|
854
|
+
const content = buildProcessedContent(skill, args, context);
|
|
855
|
+
const prompt2 = content ?? `Use the "${skill.name}" skill: ${args || skill.description}`;
|
|
856
|
+
const options = {};
|
|
857
|
+
if (skill.agent) options.agent = skill.agent;
|
|
858
|
+
if (skill.allowedTools) options.allowedTools = skill.allowedTools;
|
|
859
|
+
const result = await callbacks.runInFork(prompt2, options);
|
|
860
|
+
return { mode: "fork", result };
|
|
861
|
+
}
|
|
862
|
+
const prompt = buildInjectPrompt(skill, args, context);
|
|
863
|
+
return { mode: "inject", prompt };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/ui/hooks/useSubmitHandler.ts
|
|
867
|
+
function syncContextState(session, setter) {
|
|
868
|
+
const ctx = session.getContextState();
|
|
869
|
+
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
870
|
+
}
|
|
871
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState, rawInput) {
|
|
872
|
+
setIsThinking(true);
|
|
873
|
+
clearStreamingText();
|
|
874
|
+
const historyBefore = session.getHistory().length;
|
|
875
|
+
try {
|
|
876
|
+
const response = await session.run(prompt, rawInput);
|
|
877
|
+
clearStreamingText();
|
|
878
|
+
const history = session.getHistory();
|
|
879
|
+
const toolSummaries = extractToolCallsWithDiff(
|
|
880
|
+
history,
|
|
881
|
+
historyBefore
|
|
882
|
+
);
|
|
883
|
+
if (toolSummaries.length > 0) {
|
|
884
|
+
addMessage(
|
|
885
|
+
(0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
|
|
886
|
+
toolCallId: (0, import_node_crypto.randomUUID)(),
|
|
887
|
+
name: `${toolSummaries.length} tools`
|
|
888
|
+
})
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
addMessage((0, import_agent_core2.createAssistantMessage)(response || "(empty response)"));
|
|
892
|
+
syncContextState(session, setContextState);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
clearStreamingText();
|
|
895
|
+
const isAbortError = err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
|
|
896
|
+
if (isAbortError) {
|
|
897
|
+
const history = session.getHistory();
|
|
898
|
+
const toolSummaries = extractToolCallsWithDiff(
|
|
899
|
+
history,
|
|
900
|
+
historyBefore
|
|
901
|
+
);
|
|
902
|
+
if (toolSummaries.length > 0) {
|
|
903
|
+
addMessage(
|
|
904
|
+
(0, import_agent_core2.createToolMessage)(JSON.stringify(toolSummaries), {
|
|
905
|
+
toolCallId: (0, import_node_crypto.randomUUID)(),
|
|
906
|
+
name: `${toolSummaries.length} tools`
|
|
907
|
+
})
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
const assistantParts = [];
|
|
911
|
+
let lastAssistantState = "complete";
|
|
912
|
+
for (let i = historyBefore; i < history.length; i++) {
|
|
913
|
+
const msg = history[i];
|
|
914
|
+
if (msg && msg.role === "assistant" && msg.content) {
|
|
915
|
+
assistantParts.push(msg.content);
|
|
916
|
+
if (msg.state === "interrupted") lastAssistantState = "interrupted";
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (assistantParts.length > 0) {
|
|
920
|
+
addMessage(
|
|
921
|
+
(0, import_agent_core2.createAssistantMessage)(assistantParts.join("\n\n"), { state: lastAssistantState })
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
addMessage((0, import_agent_core2.createSystemMessage)("Interrupted by user."));
|
|
925
|
+
} else {
|
|
926
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
927
|
+
addMessage((0, import_agent_core2.createSystemMessage)(`Error: ${errMsg}`));
|
|
928
|
+
}
|
|
929
|
+
} finally {
|
|
930
|
+
setIsThinking(false);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function createForkRunner(sessionKey) {
|
|
934
|
+
const deps = (0, import_agent_sdk2.retrieveAgentToolDeps)(sessionKey);
|
|
935
|
+
if (!deps) return void 0;
|
|
936
|
+
return async (content, options) => {
|
|
937
|
+
const agentType = options.agent ?? "general-purpose";
|
|
938
|
+
const agentDef = (0, import_agent_sdk2.getBuiltInAgent)(agentType) ?? deps.customAgentRegistry?.(agentType);
|
|
939
|
+
if (!agentDef) {
|
|
940
|
+
throw new Error(`Unknown agent type for fork execution: ${agentType}`);
|
|
941
|
+
}
|
|
942
|
+
const effectiveDef = options.allowedTools ? { ...agentDef, tools: options.allowedTools } : agentDef;
|
|
943
|
+
const subSession = (0, import_agent_sdk2.createSubagentSession)({
|
|
944
|
+
agentDefinition: effectiveDef,
|
|
945
|
+
parentConfig: deps.config,
|
|
946
|
+
parentContext: deps.context,
|
|
947
|
+
parentTools: deps.tools,
|
|
948
|
+
terminal: deps.terminal,
|
|
949
|
+
isForkWorker: true,
|
|
950
|
+
permissionHandler: deps.permissionHandler,
|
|
951
|
+
onTextDelta: deps.onTextDelta,
|
|
952
|
+
onToolExecution: deps.onToolExecution
|
|
953
|
+
});
|
|
954
|
+
return await subSession.run(content);
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function findSkillCommand(input, registry) {
|
|
958
|
+
const parts = input.slice(1).split(/\s+/);
|
|
959
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
960
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
961
|
+
if (!skillCmd) return null;
|
|
962
|
+
return { skill: skillCmd, args: parts.slice(1).join(" ").trim() };
|
|
963
|
+
}
|
|
964
|
+
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
|
|
965
|
+
return (0, import_react4.useCallback)(
|
|
966
|
+
async (input) => {
|
|
967
|
+
if (input.startsWith("/")) {
|
|
968
|
+
const handled = await handleSlashCommand(input);
|
|
969
|
+
if (handled) {
|
|
970
|
+
syncContextState(session, setContextState);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const found = findSkillCommand(input, registry);
|
|
974
|
+
if (!found) return;
|
|
975
|
+
const { skill, args } = found;
|
|
976
|
+
if (skill.context === "fork") {
|
|
977
|
+
const runInFork = createForkRunner(session);
|
|
978
|
+
const result = await executeSkill(skill, args, { runInFork });
|
|
979
|
+
if (result.mode === "fork") {
|
|
980
|
+
addMessage((0, import_agent_core2.createAssistantMessage)(result.result ?? "(empty response)"));
|
|
981
|
+
syncContextState(session, setContextState);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
if (result.prompt) {
|
|
985
|
+
const cmdName2 = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
986
|
+
const qualifiedName2 = registry.resolveQualifiedName(cmdName2);
|
|
987
|
+
const hookInput2 = qualifiedName2 ? `/${qualifiedName2}${input.slice(1 + cmdName2.length)}` : input;
|
|
988
|
+
return runSessionPrompt(
|
|
989
|
+
result.prompt,
|
|
990
|
+
session,
|
|
991
|
+
addMessage,
|
|
992
|
+
clearStreamingText,
|
|
993
|
+
setIsThinking,
|
|
994
|
+
setContextState,
|
|
995
|
+
hookInput2
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const prompt = await buildSkillPrompt(input, registry);
|
|
1001
|
+
if (!prompt) return;
|
|
1002
|
+
const cmdName = input.slice(1).split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
1003
|
+
const qualifiedName = registry.resolveQualifiedName(cmdName);
|
|
1004
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmdName.length)}` : input;
|
|
1005
|
+
return runSessionPrompt(
|
|
1006
|
+
prompt,
|
|
1007
|
+
session,
|
|
1008
|
+
addMessage,
|
|
1009
|
+
clearStreamingText,
|
|
1010
|
+
setIsThinking,
|
|
1011
|
+
setContextState,
|
|
1012
|
+
hookInput
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
addMessage((0, import_agent_core2.createUserMessage)(input));
|
|
1016
|
+
return runSessionPrompt(
|
|
1017
|
+
input,
|
|
1018
|
+
session,
|
|
1019
|
+
addMessage,
|
|
1020
|
+
clearStreamingText,
|
|
1021
|
+
setIsThinking,
|
|
1022
|
+
setContextState
|
|
1023
|
+
);
|
|
1024
|
+
},
|
|
1025
|
+
[
|
|
1026
|
+
session,
|
|
1027
|
+
addMessage,
|
|
1028
|
+
handleSlashCommand,
|
|
1029
|
+
clearStreamingText,
|
|
1030
|
+
setIsThinking,
|
|
1031
|
+
setContextState,
|
|
1032
|
+
registry
|
|
1033
|
+
]
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
1038
|
+
var import_react5 = require("react");
|
|
1039
|
+
var import_node_os2 = require("os");
|
|
1040
|
+
var import_node_path3 = require("path");
|
|
1041
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
1042
|
+
|
|
62
1043
|
// src/commands/command-registry.ts
|
|
63
1044
|
var CommandRegistry = class {
|
|
64
1045
|
sources = [];
|
|
@@ -75,6 +1056,14 @@ var CommandRegistry = class {
|
|
|
75
1056
|
const lower = filter.toLowerCase();
|
|
76
1057
|
return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
|
|
77
1058
|
}
|
|
1059
|
+
/** Resolve a short name to its fully qualified plugin:name form */
|
|
1060
|
+
resolveQualifiedName(shortName) {
|
|
1061
|
+
const matches = this.getCommands().filter(
|
|
1062
|
+
(c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
|
|
1063
|
+
);
|
|
1064
|
+
if (matches.length !== 1) return null;
|
|
1065
|
+
return matches[0].name;
|
|
1066
|
+
}
|
|
78
1067
|
/** Get subcommands for a specific command */
|
|
79
1068
|
getSubcommands(commandName) {
|
|
80
1069
|
const lower = commandName.toLowerCase();
|
|
@@ -90,6 +1079,21 @@ var CommandRegistry = class {
|
|
|
90
1079
|
};
|
|
91
1080
|
|
|
92
1081
|
// src/commands/builtin-source.ts
|
|
1082
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
1083
|
+
function buildModelSubcommands() {
|
|
1084
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1085
|
+
const commands = [];
|
|
1086
|
+
for (const model of Object.values(import_agent_core3.CLAUDE_MODELS)) {
|
|
1087
|
+
if (seen.has(model.name)) continue;
|
|
1088
|
+
seen.add(model.name);
|
|
1089
|
+
commands.push({
|
|
1090
|
+
name: model.id,
|
|
1091
|
+
description: `${model.name} (${(0, import_agent_core3.formatTokenCount)(model.contextWindow).toUpperCase()})`,
|
|
1092
|
+
source: "builtin"
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
return commands;
|
|
1096
|
+
}
|
|
93
1097
|
function createBuiltinCommands() {
|
|
94
1098
|
return [
|
|
95
1099
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -109,16 +1113,26 @@ function createBuiltinCommands() {
|
|
|
109
1113
|
name: "model",
|
|
110
1114
|
description: "Select AI model",
|
|
111
1115
|
source: "builtin",
|
|
1116
|
+
subcommands: buildModelSubcommands()
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
name: "language",
|
|
1120
|
+
description: "Set response language",
|
|
1121
|
+
source: "builtin",
|
|
112
1122
|
subcommands: [
|
|
113
|
-
{ name: "
|
|
114
|
-
{ name: "
|
|
115
|
-
{ name: "
|
|
1123
|
+
{ name: "ko", description: "Korean", source: "builtin" },
|
|
1124
|
+
{ name: "en", description: "English", source: "builtin" },
|
|
1125
|
+
{ name: "ja", description: "Japanese", source: "builtin" },
|
|
1126
|
+
{ name: "zh", description: "Chinese", source: "builtin" }
|
|
116
1127
|
]
|
|
117
1128
|
},
|
|
118
1129
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
119
1130
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
120
1131
|
{ name: "context", description: "Context window info", source: "builtin" },
|
|
121
1132
|
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
1133
|
+
{ name: "plugin", description: "Manage plugins", source: "builtin" },
|
|
1134
|
+
{ name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
|
|
1135
|
+
{ name: "reset", description: "Delete settings and exit", source: "builtin" },
|
|
122
1136
|
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
123
1137
|
];
|
|
124
1138
|
}
|
|
@@ -134,72 +1148,342 @@ var BuiltinCommandSource = class {
|
|
|
134
1148
|
};
|
|
135
1149
|
|
|
136
1150
|
// src/commands/skill-source.ts
|
|
137
|
-
var
|
|
138
|
-
var
|
|
1151
|
+
var import_node_fs3 = require("fs");
|
|
1152
|
+
var import_node_path2 = require("path");
|
|
139
1153
|
var import_node_os = require("os");
|
|
1154
|
+
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
1155
|
+
var LIST_KEYS = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
1156
|
+
function kebabToCamel(key) {
|
|
1157
|
+
return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
1158
|
+
}
|
|
140
1159
|
function parseFrontmatter(content) {
|
|
141
1160
|
const lines = content.split("\n");
|
|
142
1161
|
if (lines[0]?.trim() !== "---") return null;
|
|
143
|
-
|
|
144
|
-
let description = "";
|
|
1162
|
+
const result = {};
|
|
145
1163
|
for (let i = 1; i < lines.length; i++) {
|
|
146
1164
|
const line = lines[i];
|
|
147
1165
|
if (line.trim() === "---") break;
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
1166
|
+
const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
|
|
1167
|
+
if (!match) continue;
|
|
1168
|
+
const key = match[1];
|
|
1169
|
+
const rawValue = match[2].trim();
|
|
1170
|
+
const camelKey = kebabToCamel(key);
|
|
1171
|
+
if (BOOLEAN_KEYS.has(key)) {
|
|
1172
|
+
result[camelKey] = rawValue === "true";
|
|
1173
|
+
} else if (LIST_KEYS.has(key)) {
|
|
1174
|
+
result[camelKey] = rawValue.split(",").map((s) => s.trim());
|
|
1175
|
+
} else {
|
|
1176
|
+
result[camelKey] = rawValue;
|
|
156
1177
|
}
|
|
157
1178
|
}
|
|
158
|
-
return
|
|
1179
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
1180
|
+
}
|
|
1181
|
+
function buildCommand(frontmatter, content, fallbackName) {
|
|
1182
|
+
const cmd = {
|
|
1183
|
+
name: frontmatter?.name ?? fallbackName,
|
|
1184
|
+
description: frontmatter?.description ?? `Skill: ${fallbackName}`,
|
|
1185
|
+
source: "skill",
|
|
1186
|
+
skillContent: content
|
|
1187
|
+
};
|
|
1188
|
+
if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
|
|
1189
|
+
if (frontmatter?.disableModelInvocation !== void 0)
|
|
1190
|
+
cmd.disableModelInvocation = frontmatter.disableModelInvocation;
|
|
1191
|
+
if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
|
|
1192
|
+
if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
|
|
1193
|
+
if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
|
|
1194
|
+
if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
|
|
1195
|
+
if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
|
|
1196
|
+
if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
|
|
1197
|
+
return cmd;
|
|
159
1198
|
}
|
|
160
1199
|
function scanSkillsDir(skillsDir) {
|
|
161
|
-
if (!(0,
|
|
1200
|
+
if (!(0, import_node_fs3.existsSync)(skillsDir)) return [];
|
|
162
1201
|
const commands = [];
|
|
163
|
-
const entries = (0,
|
|
1202
|
+
const entries = (0, import_node_fs3.readdirSync)(skillsDir, { withFileTypes: true });
|
|
164
1203
|
for (const entry of entries) {
|
|
165
1204
|
if (!entry.isDirectory()) continue;
|
|
166
|
-
const skillFile = (0,
|
|
167
|
-
if (!(0,
|
|
168
|
-
const content = (0,
|
|
1205
|
+
const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
|
|
1206
|
+
if (!(0, import_node_fs3.existsSync)(skillFile)) continue;
|
|
1207
|
+
const content = (0, import_node_fs3.readFileSync)(skillFile, "utf-8");
|
|
169
1208
|
const frontmatter = parseFrontmatter(content);
|
|
170
|
-
commands.push(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
1209
|
+
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
1210
|
+
}
|
|
1211
|
+
return commands;
|
|
1212
|
+
}
|
|
1213
|
+
function scanCommandsDir(commandsDir) {
|
|
1214
|
+
if (!(0, import_node_fs3.existsSync)(commandsDir)) return [];
|
|
1215
|
+
const commands = [];
|
|
1216
|
+
const entries = (0, import_node_fs3.readdirSync)(commandsDir, { withFileTypes: true });
|
|
1217
|
+
for (const entry of entries) {
|
|
1218
|
+
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
1219
|
+
const filePath = (0, import_node_path2.join)(commandsDir, entry.name);
|
|
1220
|
+
const content = (0, import_node_fs3.readFileSync)(filePath, "utf-8");
|
|
1221
|
+
const frontmatter = parseFrontmatter(content);
|
|
1222
|
+
const fallbackName = (0, import_node_path2.basename)(entry.name, ".md");
|
|
1223
|
+
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
175
1224
|
}
|
|
176
1225
|
return commands;
|
|
177
1226
|
}
|
|
178
1227
|
var SkillCommandSource = class {
|
|
179
1228
|
name = "skill";
|
|
180
1229
|
cwd;
|
|
1230
|
+
home;
|
|
181
1231
|
cachedCommands = null;
|
|
182
|
-
constructor(cwd) {
|
|
1232
|
+
constructor(cwd, home) {
|
|
183
1233
|
this.cwd = cwd;
|
|
1234
|
+
this.home = home ?? (0, import_node_os.homedir)();
|
|
184
1235
|
}
|
|
185
1236
|
getCommands() {
|
|
186
1237
|
if (this.cachedCommands) return this.cachedCommands;
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
1238
|
+
const sources = [
|
|
1239
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".claude", "skills")),
|
|
1240
|
+
// 1. project .claude/skills
|
|
1241
|
+
scanCommandsDir((0, import_node_path2.join)(this.cwd, ".claude", "commands")),
|
|
1242
|
+
// 2. project .claude/commands (legacy)
|
|
1243
|
+
scanSkillsDir((0, import_node_path2.join)(this.home, ".robota", "skills")),
|
|
1244
|
+
// 3. user ~/.robota/skills
|
|
1245
|
+
scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"))
|
|
1246
|
+
// 4. project .agents/skills
|
|
1247
|
+
];
|
|
1248
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1249
|
+
const merged = [];
|
|
1250
|
+
for (const commands of sources) {
|
|
1251
|
+
for (const cmd of commands) {
|
|
1252
|
+
if (!seen.has(cmd.name)) {
|
|
1253
|
+
seen.add(cmd.name);
|
|
1254
|
+
merged.push(cmd);
|
|
1255
|
+
}
|
|
194
1256
|
}
|
|
195
1257
|
}
|
|
196
1258
|
this.cachedCommands = merged;
|
|
197
1259
|
return this.cachedCommands;
|
|
198
1260
|
}
|
|
1261
|
+
/** Get skills that models can invoke (excludes disableModelInvocation: true) */
|
|
1262
|
+
getModelInvocableSkills() {
|
|
1263
|
+
return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
|
|
1264
|
+
}
|
|
1265
|
+
/** Get skills that users can invoke (excludes userInvocable: false) */
|
|
1266
|
+
getUserInvocableSkills() {
|
|
1267
|
+
return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
// src/commands/plugin-source.ts
|
|
1272
|
+
var PluginCommandSource = class {
|
|
1273
|
+
name = "plugin";
|
|
1274
|
+
plugins;
|
|
1275
|
+
constructor(plugins) {
|
|
1276
|
+
this.plugins = plugins;
|
|
1277
|
+
}
|
|
1278
|
+
getCommands() {
|
|
1279
|
+
const commands = [];
|
|
1280
|
+
for (const plugin of this.plugins) {
|
|
1281
|
+
for (const skill of plugin.skills) {
|
|
1282
|
+
const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
|
|
1283
|
+
commands.push({
|
|
1284
|
+
name: baseName,
|
|
1285
|
+
description: `(${plugin.manifest.name}) ${skill.description}`,
|
|
1286
|
+
source: "plugin",
|
|
1287
|
+
skillContent: skill.skillContent,
|
|
1288
|
+
pluginDir: plugin.pluginDir
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
for (const cmd of plugin.commands) {
|
|
1292
|
+
commands.push({
|
|
1293
|
+
name: cmd.name,
|
|
1294
|
+
description: cmd.description,
|
|
1295
|
+
source: "plugin",
|
|
1296
|
+
skillContent: cmd.skillContent,
|
|
1297
|
+
pluginDir: plugin.pluginDir
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return commands;
|
|
1302
|
+
}
|
|
199
1303
|
};
|
|
200
1304
|
|
|
1305
|
+
// src/ui/hooks/useCommandRegistry.ts
|
|
1306
|
+
function buildPluginEnv(plugin) {
|
|
1307
|
+
const dataDir = (0, import_node_path3.join)((0, import_node_path3.dirname)((0, import_node_path3.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
1308
|
+
return {
|
|
1309
|
+
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
1310
|
+
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
1311
|
+
CLAUDE_PLUGIN_DATA: dataDir
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
function mergePluginHooks(plugins) {
|
|
1315
|
+
const merged = {};
|
|
1316
|
+
for (const plugin of plugins) {
|
|
1317
|
+
const hooksObj = plugin.hooks;
|
|
1318
|
+
if (!hooksObj) continue;
|
|
1319
|
+
const pluginEnv = buildPluginEnv(plugin);
|
|
1320
|
+
const innerHooks = hooksObj.hooks ?? hooksObj;
|
|
1321
|
+
for (const [event, groups] of Object.entries(innerHooks)) {
|
|
1322
|
+
if (!Array.isArray(groups)) continue;
|
|
1323
|
+
if (!merged[event]) merged[event] = [];
|
|
1324
|
+
const resolved = groups.map((group) => {
|
|
1325
|
+
const resolved2 = resolvePluginRoot(group, plugin.pluginDir);
|
|
1326
|
+
if (typeof resolved2 === "object" && resolved2 !== null) {
|
|
1327
|
+
resolved2.env = pluginEnv;
|
|
1328
|
+
}
|
|
1329
|
+
return resolved2;
|
|
1330
|
+
});
|
|
1331
|
+
merged[event].push(...resolved);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return merged;
|
|
1335
|
+
}
|
|
1336
|
+
function resolvePluginRoot(group, pluginDir) {
|
|
1337
|
+
if (typeof group !== "object" || group === null) return group;
|
|
1338
|
+
const obj = group;
|
|
1339
|
+
if (Array.isArray(obj.hooks)) {
|
|
1340
|
+
return {
|
|
1341
|
+
...obj,
|
|
1342
|
+
hooks: obj.hooks.map((h) => {
|
|
1343
|
+
if (typeof h !== "object" || h === null) return h;
|
|
1344
|
+
const hook = h;
|
|
1345
|
+
if (typeof hook.command === "string") {
|
|
1346
|
+
return {
|
|
1347
|
+
...hook,
|
|
1348
|
+
command: hook.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
return hook;
|
|
1352
|
+
})
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
return group;
|
|
1356
|
+
}
|
|
1357
|
+
function useCommandRegistry(cwd) {
|
|
1358
|
+
const resultRef = (0, import_react5.useRef)(null);
|
|
1359
|
+
if (resultRef.current === null) {
|
|
1360
|
+
const registry = new CommandRegistry();
|
|
1361
|
+
registry.addSource(new BuiltinCommandSource());
|
|
1362
|
+
registry.addSource(new SkillCommandSource(cwd));
|
|
1363
|
+
let pluginHooks = {};
|
|
1364
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
1365
|
+
const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
|
|
1366
|
+
try {
|
|
1367
|
+
const plugins = loader.loadPluginsSync();
|
|
1368
|
+
if (plugins.length > 0) {
|
|
1369
|
+
registry.addSource(new PluginCommandSource(plugins));
|
|
1370
|
+
pluginHooks = mergePluginHooks(plugins);
|
|
1371
|
+
}
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1374
|
+
resultRef.current = { registry, pluginHooks };
|
|
1375
|
+
}
|
|
1376
|
+
return resultRef.current;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
1380
|
+
var import_react6 = require("react");
|
|
1381
|
+
var import_node_os3 = require("os");
|
|
1382
|
+
var import_node_path4 = require("path");
|
|
1383
|
+
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
1384
|
+
function usePluginCallbacks(cwd) {
|
|
1385
|
+
return (0, import_react6.useMemo)(() => {
|
|
1386
|
+
const home = (0, import_node_os3.homedir)();
|
|
1387
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
1388
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
1389
|
+
const settingsStore = new import_agent_sdk4.PluginSettingsStore(userSettingsPath);
|
|
1390
|
+
const marketplace = new import_agent_sdk4.MarketplaceClient({ pluginsDir });
|
|
1391
|
+
const installer = new import_agent_sdk4.BundlePluginInstaller({
|
|
1392
|
+
pluginsDir,
|
|
1393
|
+
settingsStore,
|
|
1394
|
+
marketplaceClient: marketplace
|
|
1395
|
+
});
|
|
1396
|
+
const loader = new import_agent_sdk4.BundlePluginLoader(pluginsDir);
|
|
1397
|
+
return {
|
|
1398
|
+
listInstalled: async () => {
|
|
1399
|
+
const plugins = await loader.loadAll();
|
|
1400
|
+
const enabledMap = settingsStore.getEnabledPlugins();
|
|
1401
|
+
return plugins.map((p) => {
|
|
1402
|
+
const parts = p.pluginDir.split("/");
|
|
1403
|
+
const cacheIdx = parts.indexOf("cache");
|
|
1404
|
+
const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
|
|
1405
|
+
const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
|
|
1406
|
+
return {
|
|
1407
|
+
name: fullId,
|
|
1408
|
+
description: p.manifest.description,
|
|
1409
|
+
enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
|
|
1410
|
+
};
|
|
1411
|
+
});
|
|
1412
|
+
},
|
|
1413
|
+
listAvailablePlugins: async (marketplaceName) => {
|
|
1414
|
+
let manifest;
|
|
1415
|
+
try {
|
|
1416
|
+
manifest = marketplace.fetchManifest(marketplaceName);
|
|
1417
|
+
} catch {
|
|
1418
|
+
return [];
|
|
1419
|
+
}
|
|
1420
|
+
const installed = installer.getInstalledPlugins();
|
|
1421
|
+
const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
|
|
1422
|
+
return manifest.plugins.map((p) => ({
|
|
1423
|
+
name: p.name,
|
|
1424
|
+
description: p.description,
|
|
1425
|
+
installed: installedNames.has(p.name)
|
|
1426
|
+
}));
|
|
1427
|
+
},
|
|
1428
|
+
install: async (pluginId, scope) => {
|
|
1429
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
1430
|
+
if (!name || !marketplaceName) {
|
|
1431
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
1432
|
+
}
|
|
1433
|
+
if (scope === "project") {
|
|
1434
|
+
const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
|
|
1435
|
+
const projectInstaller = new import_agent_sdk4.BundlePluginInstaller({
|
|
1436
|
+
pluginsDir: projectPluginsDir,
|
|
1437
|
+
settingsStore,
|
|
1438
|
+
marketplaceClient: marketplace
|
|
1439
|
+
});
|
|
1440
|
+
await projectInstaller.install(name, marketplaceName);
|
|
1441
|
+
} else {
|
|
1442
|
+
await installer.install(name, marketplaceName);
|
|
1443
|
+
}
|
|
1444
|
+
},
|
|
1445
|
+
uninstall: async (pluginId) => {
|
|
1446
|
+
await installer.uninstall(pluginId);
|
|
1447
|
+
},
|
|
1448
|
+
enable: async (pluginId) => {
|
|
1449
|
+
await installer.enable(pluginId);
|
|
1450
|
+
},
|
|
1451
|
+
disable: async (pluginId) => {
|
|
1452
|
+
await installer.disable(pluginId);
|
|
1453
|
+
},
|
|
1454
|
+
marketplaceAdd: async (source) => {
|
|
1455
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
1456
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
1457
|
+
} else {
|
|
1458
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
1459
|
+
}
|
|
1460
|
+
},
|
|
1461
|
+
marketplaceRemove: async (name) => {
|
|
1462
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
1463
|
+
for (const record of installedFromMarketplace) {
|
|
1464
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
1465
|
+
}
|
|
1466
|
+
marketplace.removeMarketplace(name);
|
|
1467
|
+
},
|
|
1468
|
+
marketplaceUpdate: async (name) => {
|
|
1469
|
+
marketplace.updateMarketplace(name);
|
|
1470
|
+
},
|
|
1471
|
+
marketplaceList: async () => {
|
|
1472
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
1473
|
+
name: m.name,
|
|
1474
|
+
type: m.source.type
|
|
1475
|
+
}));
|
|
1476
|
+
},
|
|
1477
|
+
reloadPlugins: async () => {
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
}, [cwd]);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
201
1483
|
// src/ui/MessageList.tsx
|
|
202
|
-
var
|
|
1484
|
+
var import_react7 = __toESM(require("react"), 1);
|
|
1485
|
+
var import_ink2 = require("ink");
|
|
1486
|
+
var import_agent_core4 = require("@robota-sdk/agent-core");
|
|
203
1487
|
|
|
204
1488
|
// src/ui/render-markdown.ts
|
|
205
1489
|
var import_marked = require("marked");
|
|
@@ -212,54 +1496,161 @@ function renderMarkdown(md) {
|
|
|
212
1496
|
return typeof result === "string" ? result.trimEnd() : md;
|
|
213
1497
|
}
|
|
214
1498
|
|
|
215
|
-
// src/ui/
|
|
1499
|
+
// src/ui/DiffBlock.tsx
|
|
1500
|
+
var import_ink = require("ink");
|
|
216
1501
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1502
|
+
var MAX_DIFF_LINES = 12;
|
|
1503
|
+
var TRUNCATED_SHOW = 10;
|
|
1504
|
+
function DiffBlock({ file, lines }) {
|
|
1505
|
+
const truncated = lines.length > MAX_DIFF_LINES;
|
|
1506
|
+
const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
|
|
1507
|
+
const remaining = lines.length - TRUNCATED_SHOW;
|
|
1508
|
+
const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
|
|
1509
|
+
const numWidth = String(maxLineNum).length;
|
|
1510
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
1511
|
+
file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1512
|
+
"\u2502 ",
|
|
1513
|
+
file
|
|
1514
|
+
] }),
|
|
1515
|
+
visible.map((line, i) => {
|
|
1516
|
+
const lineNum = String(line.lineNumber).padStart(numWidth, " ");
|
|
1517
|
+
if (line.type === "context") {
|
|
1518
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1519
|
+
"\u2502 ",
|
|
1520
|
+
lineNum,
|
|
1521
|
+
" ",
|
|
1522
|
+
line.text
|
|
1523
|
+
] }, i);
|
|
1524
|
+
}
|
|
1525
|
+
const prefix = line.type === "remove" ? "-" : "+";
|
|
1526
|
+
const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
|
|
1527
|
+
const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
|
|
1528
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: fgColor, backgroundColor: bgColor, children: [
|
|
1529
|
+
"\u2502 ",
|
|
1530
|
+
lineNum,
|
|
1531
|
+
" ",
|
|
1532
|
+
prefix,
|
|
1533
|
+
" ",
|
|
1534
|
+
line.text
|
|
1535
|
+
] }, i);
|
|
1536
|
+
}),
|
|
1537
|
+
truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
1538
|
+
"\u2502 ... and ",
|
|
1539
|
+
remaining,
|
|
1540
|
+
" more lines"
|
|
1541
|
+
] })
|
|
1542
|
+
] });
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/ui/MessageList.tsx
|
|
1546
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
217
1547
|
function RoleLabel({ role }) {
|
|
218
1548
|
switch (role) {
|
|
219
1549
|
case "user":
|
|
220
|
-
return /* @__PURE__ */ (0,
|
|
1550
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
|
|
221
1551
|
"You:",
|
|
222
1552
|
" "
|
|
223
1553
|
] });
|
|
224
1554
|
case "assistant":
|
|
225
|
-
return /* @__PURE__ */ (0,
|
|
1555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
|
|
226
1556
|
"Robota:",
|
|
227
1557
|
" "
|
|
228
1558
|
] });
|
|
229
1559
|
case "system":
|
|
230
|
-
return /* @__PURE__ */ (0,
|
|
1560
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
|
|
231
1561
|
"System:",
|
|
232
1562
|
" "
|
|
233
1563
|
] });
|
|
234
1564
|
case "tool":
|
|
235
|
-
return /* @__PURE__ */ (0,
|
|
1565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
236
1566
|
"Tool:",
|
|
237
1567
|
" "
|
|
238
1568
|
] });
|
|
239
1569
|
}
|
|
240
1570
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
/* @__PURE__ */ (0,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
1571
|
+
function ToolMessage({ message }) {
|
|
1572
|
+
if (!(0, import_agent_core4.isToolMessage)(message)) {
|
|
1573
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
|
|
1574
|
+
}
|
|
1575
|
+
const toolName = message.name;
|
|
1576
|
+
const content = message.content;
|
|
1577
|
+
let summaries = null;
|
|
1578
|
+
try {
|
|
1579
|
+
const parsed = JSON.parse(content);
|
|
1580
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
1581
|
+
summaries = parsed;
|
|
1582
|
+
}
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
if (summaries) {
|
|
1586
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1587
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1588
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1589
|
+
"Tool:",
|
|
1590
|
+
" "
|
|
1591
|
+
] }),
|
|
1592
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1593
|
+
"[",
|
|
1594
|
+
toolName,
|
|
1595
|
+
"]"
|
|
1596
|
+
] })
|
|
1597
|
+
] }),
|
|
1598
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1599
|
+
summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
|
|
1600
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1601
|
+
" ",
|
|
1602
|
+
"\u2713",
|
|
1603
|
+
" ",
|
|
1604
|
+
s.line
|
|
1605
|
+
] }),
|
|
1606
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
1607
|
+
] }, i))
|
|
1608
|
+
] });
|
|
1609
|
+
}
|
|
1610
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
1611
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
1613
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
1614
|
+
"Tool:",
|
|
249
1615
|
" "
|
|
1616
|
+
] }),
|
|
1617
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
1618
|
+
"[",
|
|
1619
|
+
toolName,
|
|
1620
|
+
"]"
|
|
250
1621
|
] })
|
|
251
1622
|
] }),
|
|
252
|
-
/* @__PURE__ */ (0,
|
|
253
|
-
|
|
1623
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1624
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
1625
|
+
" ",
|
|
1626
|
+
"\u2713",
|
|
1627
|
+
" ",
|
|
1628
|
+
line
|
|
1629
|
+
] }, i))
|
|
254
1630
|
] });
|
|
255
1631
|
}
|
|
1632
|
+
var MessageItem = import_react7.default.memo(function MessageItem2({
|
|
1633
|
+
message
|
|
1634
|
+
}) {
|
|
1635
|
+
if ((0, import_agent_core4.isToolMessage)(message)) {
|
|
1636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
|
|
1637
|
+
}
|
|
1638
|
+
const content = message.content ?? "";
|
|
1639
|
+
const isInterrupted = message.state === "interrupted";
|
|
1640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1641
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
|
|
1642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
1643
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: (0, import_agent_core4.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
|
|
1644
|
+
] });
|
|
1645
|
+
});
|
|
256
1646
|
function MessageList({ messages }) {
|
|
257
|
-
return /* @__PURE__ */ (0,
|
|
1647
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message: msg }, msg.id)) });
|
|
258
1648
|
}
|
|
259
1649
|
|
|
260
1650
|
// src/ui/StatusBar.tsx
|
|
261
|
-
var
|
|
262
|
-
var
|
|
1651
|
+
var import_ink3 = require("ink");
|
|
1652
|
+
var import_agent_core5 = require("@robota-sdk/agent-core");
|
|
1653
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
263
1654
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
264
1655
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
265
1656
|
function getContextColor(percentage) {
|
|
@@ -278,8 +1669,8 @@ function StatusBar({
|
|
|
278
1669
|
contextMaxTokens
|
|
279
1670
|
}) {
|
|
280
1671
|
const contextColor = getContextColor(contextPercentage);
|
|
281
|
-
return /* @__PURE__ */ (0,
|
|
282
|
-
|
|
1672
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1673
|
+
import_ink3.Box,
|
|
283
1674
|
{
|
|
284
1675
|
borderStyle: "single",
|
|
285
1676
|
borderColor: "gray",
|
|
@@ -287,26 +1678,26 @@ function StatusBar({
|
|
|
287
1678
|
paddingRight: 1,
|
|
288
1679
|
justifyContent: "space-between",
|
|
289
1680
|
children: [
|
|
290
|
-
/* @__PURE__ */ (0,
|
|
291
|
-
/* @__PURE__ */ (0,
|
|
1681
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
|
|
292
1683
|
" ",
|
|
293
|
-
/* @__PURE__ */ (0,
|
|
1684
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
|
|
294
1685
|
" | ",
|
|
295
|
-
/* @__PURE__ */ (0,
|
|
1686
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
|
|
296
1687
|
" | ",
|
|
297
|
-
/* @__PURE__ */ (0,
|
|
1688
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
|
|
298
1689
|
"Context: ",
|
|
299
1690
|
Math.round(contextPercentage),
|
|
300
1691
|
"% (",
|
|
301
|
-
(
|
|
302
|
-
"
|
|
303
|
-
(
|
|
304
|
-
"
|
|
1692
|
+
(0, import_agent_core5.formatTokenCount)(contextUsedTokens),
|
|
1693
|
+
"/",
|
|
1694
|
+
(0, import_agent_core5.formatTokenCount)(contextMaxTokens),
|
|
1695
|
+
")"
|
|
305
1696
|
] })
|
|
306
1697
|
] }),
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
308
|
-
isThinking && /* @__PURE__ */ (0,
|
|
309
|
-
/* @__PURE__ */ (0,
|
|
1698
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
1699
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
|
|
310
1701
|
"msgs: ",
|
|
311
1702
|
messageCount
|
|
312
1703
|
] })
|
|
@@ -317,146 +1708,224 @@ function StatusBar({
|
|
|
317
1708
|
}
|
|
318
1709
|
|
|
319
1710
|
// src/ui/InputArea.tsx
|
|
320
|
-
var
|
|
321
|
-
var
|
|
1711
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1712
|
+
var import_ink7 = require("ink");
|
|
322
1713
|
|
|
323
1714
|
// src/ui/CjkTextInput.tsx
|
|
324
|
-
var
|
|
325
|
-
var
|
|
1715
|
+
var import_react8 = require("react");
|
|
1716
|
+
var import_ink4 = require("ink");
|
|
1717
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
326
1718
|
var import_string_width = __toESM(require("string-width"), 1);
|
|
327
|
-
var
|
|
328
|
-
var
|
|
1719
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1720
|
+
var PASTE_START = "[200~";
|
|
1721
|
+
var PASTE_END = "[201~";
|
|
1722
|
+
function filterPrintable(input) {
|
|
1723
|
+
if (!input || input.length === 0) return "";
|
|
1724
|
+
return input.replace(/[\x00-\x1f\x7f]/g, "");
|
|
1725
|
+
}
|
|
1726
|
+
function insertAtCursor(value, cursor, input) {
|
|
1727
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
1728
|
+
return { value: next, cursor: cursor + input.length };
|
|
1729
|
+
}
|
|
1730
|
+
function displayOffset(chars, charIndex, width) {
|
|
1731
|
+
let offset = 0;
|
|
1732
|
+
for (let i = 0; i < charIndex && i < chars.length; i++) {
|
|
1733
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1734
|
+
const col = offset % width;
|
|
1735
|
+
if (col + w > width) offset += width - col;
|
|
1736
|
+
offset += w;
|
|
1737
|
+
}
|
|
1738
|
+
return offset;
|
|
1739
|
+
}
|
|
1740
|
+
function charIndexAtDisplayOffset(chars, target, width) {
|
|
1741
|
+
let offset = 0;
|
|
1742
|
+
for (let i = 0; i < chars.length; i++) {
|
|
1743
|
+
if (offset >= target) return i;
|
|
1744
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
1745
|
+
const col = offset % width;
|
|
1746
|
+
if (col + w > width) offset += width - col;
|
|
1747
|
+
offset += w;
|
|
1748
|
+
}
|
|
1749
|
+
return chars.length;
|
|
1750
|
+
}
|
|
329
1751
|
function CjkTextInput({
|
|
330
1752
|
value,
|
|
331
1753
|
onChange,
|
|
332
1754
|
onSubmit,
|
|
1755
|
+
onPaste,
|
|
333
1756
|
placeholder = "",
|
|
334
1757
|
focus = true,
|
|
335
|
-
showCursor = true
|
|
1758
|
+
showCursor = true,
|
|
1759
|
+
availableWidth
|
|
336
1760
|
}) {
|
|
337
|
-
const valueRef = (0,
|
|
338
|
-
const cursorRef = (0,
|
|
339
|
-
const [, forceRender] = (0,
|
|
340
|
-
const
|
|
1761
|
+
const valueRef = (0, import_react8.useRef)(value);
|
|
1762
|
+
const cursorRef = (0, import_react8.useRef)(value.length);
|
|
1763
|
+
const [, forceRender] = (0, import_react8.useState)(0);
|
|
1764
|
+
const isPastingRef = (0, import_react8.useRef)(false);
|
|
1765
|
+
const pasteBufferRef = (0, import_react8.useRef)("");
|
|
341
1766
|
if (value !== valueRef.current) {
|
|
342
1767
|
valueRef.current = value;
|
|
343
1768
|
if (cursorRef.current > value.length) {
|
|
344
1769
|
cursorRef.current = value.length;
|
|
345
1770
|
}
|
|
346
1771
|
}
|
|
347
|
-
(0,
|
|
1772
|
+
(0, import_ink4.useInput)(
|
|
348
1773
|
(input, key) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (cursorRef.current > 0) {
|
|
358
|
-
cursorRef.current -= 1;
|
|
359
|
-
forceRender((n) => n + 1);
|
|
1774
|
+
try {
|
|
1775
|
+
if (input === PASTE_START || input.startsWith(PASTE_START)) {
|
|
1776
|
+
isPastingRef.current = true;
|
|
1777
|
+
const afterMarker = input.slice(PASTE_START.length);
|
|
1778
|
+
if (afterMarker.length > 0) {
|
|
1779
|
+
pasteBufferRef.current += afterMarker;
|
|
1780
|
+
}
|
|
1781
|
+
return;
|
|
360
1782
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
1783
|
+
if (isPastingRef.current) {
|
|
1784
|
+
if (input === PASTE_END || input.includes(PASTE_END)) {
|
|
1785
|
+
const beforeMarker = input.split(PASTE_END)[0] ?? "";
|
|
1786
|
+
pasteBufferRef.current += beforeMarker;
|
|
1787
|
+
const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
|
|
1788
|
+
pasteBufferRef.current = "";
|
|
1789
|
+
isPastingRef.current = false;
|
|
1790
|
+
if (text.length > 0) {
|
|
1791
|
+
if (text.includes("\n") && onPaste) {
|
|
1792
|
+
onPaste(text);
|
|
1793
|
+
} else {
|
|
1794
|
+
const printable2 = filterPrintable(text);
|
|
1795
|
+
if (printable2.length > 0) {
|
|
1796
|
+
const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
|
|
1797
|
+
cursorRef.current = result2.cursor;
|
|
1798
|
+
valueRef.current = result2.value;
|
|
1799
|
+
onChange(result2.value);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
} else {
|
|
1804
|
+
pasteBufferRef.current += input;
|
|
1805
|
+
}
|
|
1806
|
+
return;
|
|
367
1807
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (key.backspace || key.delete) {
|
|
371
|
-
if (cursorRef.current > 0) {
|
|
372
|
-
const v2 = valueRef.current;
|
|
373
|
-
const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
|
|
374
|
-
cursorRef.current -= 1;
|
|
375
|
-
valueRef.current = next2;
|
|
376
|
-
onChange(next2);
|
|
1808
|
+
if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
|
|
1809
|
+
return;
|
|
377
1810
|
}
|
|
378
|
-
|
|
1811
|
+
if (key.upArrow || key.downArrow) {
|
|
1812
|
+
if (availableWidth && availableWidth > 0) {
|
|
1813
|
+
const chars = [...valueRef.current];
|
|
1814
|
+
const offset = displayOffset(chars, cursorRef.current, availableWidth);
|
|
1815
|
+
const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
|
|
1816
|
+
if (target >= 0) {
|
|
1817
|
+
const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
|
|
1818
|
+
if (newCursor !== cursorRef.current) {
|
|
1819
|
+
cursorRef.current = newCursor;
|
|
1820
|
+
forceRender((n) => n + 1);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (key.return) {
|
|
1827
|
+
onSubmit?.(valueRef.current);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
|
|
1831
|
+
onPaste(input.replace(/\r\n?/g, "\n"));
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (key.leftArrow) {
|
|
1835
|
+
if (cursorRef.current > 0) {
|
|
1836
|
+
cursorRef.current -= 1;
|
|
1837
|
+
forceRender((n) => n + 1);
|
|
1838
|
+
}
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (key.rightArrow) {
|
|
1842
|
+
if (cursorRef.current < valueRef.current.length) {
|
|
1843
|
+
cursorRef.current += 1;
|
|
1844
|
+
forceRender((n) => n + 1);
|
|
1845
|
+
}
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
if (key.backspace || key.delete) {
|
|
1849
|
+
if (cursorRef.current > 0) {
|
|
1850
|
+
const v = valueRef.current;
|
|
1851
|
+
const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
|
|
1852
|
+
cursorRef.current -= 1;
|
|
1853
|
+
valueRef.current = next;
|
|
1854
|
+
onChange(next);
|
|
1855
|
+
}
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const printable = filterPrintable(input);
|
|
1859
|
+
if (printable.length === 0) return;
|
|
1860
|
+
const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
|
|
1861
|
+
cursorRef.current = result.cursor;
|
|
1862
|
+
valueRef.current = result.value;
|
|
1863
|
+
onChange(result.value);
|
|
1864
|
+
} catch {
|
|
379
1865
|
}
|
|
380
|
-
const v = valueRef.current;
|
|
381
|
-
const c = cursorRef.current;
|
|
382
|
-
const next = v.slice(0, c) + input + v.slice(c);
|
|
383
|
-
cursorRef.current = c + input.length;
|
|
384
|
-
valueRef.current = next;
|
|
385
|
-
onChange(next);
|
|
386
1866
|
},
|
|
387
1867
|
{ isActive: focus }
|
|
388
1868
|
);
|
|
389
|
-
|
|
390
|
-
const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
|
|
391
|
-
const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
|
|
392
|
-
setCursorPosition({ x: cursorX, y: 0 });
|
|
393
|
-
}
|
|
394
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
1869
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
395
1870
|
}
|
|
396
1871
|
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
397
1872
|
if (!showCursor) {
|
|
398
|
-
return value.length > 0 ? value : placeholder ?
|
|
1873
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
399
1874
|
}
|
|
400
1875
|
if (value.length === 0) {
|
|
401
1876
|
if (placeholder.length > 0) {
|
|
402
|
-
return
|
|
1877
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
403
1878
|
}
|
|
404
|
-
return
|
|
1879
|
+
return import_chalk.default.inverse(" ");
|
|
405
1880
|
}
|
|
406
1881
|
const chars = [...value];
|
|
407
1882
|
let rendered = "";
|
|
408
1883
|
for (let i = 0; i < chars.length; i++) {
|
|
409
1884
|
const char = chars[i] ?? "";
|
|
410
|
-
rendered += i === cursorOffset ?
|
|
1885
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
411
1886
|
}
|
|
412
1887
|
if (cursorOffset >= chars.length) {
|
|
413
|
-
rendered +=
|
|
1888
|
+
rendered += import_chalk.default.inverse(" ");
|
|
414
1889
|
}
|
|
415
1890
|
return rendered;
|
|
416
1891
|
}
|
|
417
1892
|
|
|
418
1893
|
// src/ui/WaveText.tsx
|
|
419
|
-
var
|
|
420
|
-
var
|
|
421
|
-
var
|
|
1894
|
+
var import_react9 = require("react");
|
|
1895
|
+
var import_ink5 = require("ink");
|
|
1896
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
422
1897
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
423
1898
|
var INTERVAL_MS = 400;
|
|
424
1899
|
var CHARS_PER_GROUP = 4;
|
|
425
1900
|
function WaveText({ text }) {
|
|
426
|
-
const [tick, setTick] = (0,
|
|
427
|
-
(0,
|
|
1901
|
+
const [tick, setTick] = (0, import_react9.useState)(0);
|
|
1902
|
+
(0, import_react9.useEffect)(() => {
|
|
428
1903
|
const timer = setInterval(() => {
|
|
429
1904
|
setTick((prev) => prev + 1);
|
|
430
1905
|
}, INTERVAL_MS);
|
|
431
1906
|
return () => clearInterval(timer);
|
|
432
1907
|
}, []);
|
|
433
1908
|
const chars = [...text];
|
|
434
|
-
return /* @__PURE__ */ (0,
|
|
1909
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
|
|
435
1910
|
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
436
1911
|
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
437
|
-
return /* @__PURE__ */ (0,
|
|
1912
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
438
1913
|
}) });
|
|
439
1914
|
}
|
|
440
1915
|
|
|
441
1916
|
// src/ui/SlashAutocomplete.tsx
|
|
442
|
-
var
|
|
443
|
-
var
|
|
1917
|
+
var import_ink6 = require("ink");
|
|
1918
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
444
1919
|
var MAX_VISIBLE = 8;
|
|
445
1920
|
function CommandRow(props) {
|
|
446
1921
|
const { cmd, isSelected, showSlash } = props;
|
|
447
|
-
const prefix = showSlash ? "/" : "";
|
|
448
1922
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
449
1923
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
450
1924
|
const dimmed = !isSelected;
|
|
451
|
-
return /* @__PURE__ */ (0,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
cmd.name
|
|
456
|
-
] }),
|
|
457
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
458
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
459
|
-
] });
|
|
1925
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: nameColor, dimColor: dimmed, children: [
|
|
1926
|
+
indicator,
|
|
1927
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
1928
|
+
] }) });
|
|
460
1929
|
}
|
|
461
1930
|
function SlashAutocomplete({
|
|
462
1931
|
commands,
|
|
@@ -467,7 +1936,7 @@ function SlashAutocomplete({
|
|
|
467
1936
|
if (!visible || commands.length === 0) return null;
|
|
468
1937
|
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
469
1938
|
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
470
|
-
return /* @__PURE__ */ (0,
|
|
1939
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
471
1940
|
CommandRow,
|
|
472
1941
|
{
|
|
473
1942
|
cmd,
|
|
@@ -484,8 +1953,14 @@ function computeScrollOffset(selectedIndex, total) {
|
|
|
484
1953
|
return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
|
|
485
1954
|
}
|
|
486
1955
|
|
|
1956
|
+
// src/utils/paste-labels.ts
|
|
1957
|
+
var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
1958
|
+
function expandPasteLabels(text, store) {
|
|
1959
|
+
return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
|
|
1960
|
+
}
|
|
1961
|
+
|
|
487
1962
|
// src/ui/InputArea.tsx
|
|
488
|
-
var
|
|
1963
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
489
1964
|
function parseSlashInput(value) {
|
|
490
1965
|
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
491
1966
|
const afterSlash = value.slice(1);
|
|
@@ -496,16 +1971,16 @@ function parseSlashInput(value) {
|
|
|
496
1971
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
497
1972
|
}
|
|
498
1973
|
function useAutocomplete(value, registry) {
|
|
499
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
500
|
-
const [dismissed, setDismissed] = (0,
|
|
501
|
-
const prevValueRef =
|
|
1974
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react10.useState)(0);
|
|
1975
|
+
const [dismissed, setDismissed] = (0, import_react10.useState)(false);
|
|
1976
|
+
const prevValueRef = import_react10.default.useRef(value);
|
|
502
1977
|
if (prevValueRef.current !== value) {
|
|
503
1978
|
prevValueRef.current = value;
|
|
504
1979
|
if (dismissed) setDismissed(false);
|
|
505
1980
|
}
|
|
506
1981
|
const parsed = parseSlashInput(value);
|
|
507
1982
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
508
|
-
const filteredCommands = (0,
|
|
1983
|
+
const filteredCommands = (0, import_react10.useMemo)(() => {
|
|
509
1984
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
510
1985
|
if (isSubcommandMode) {
|
|
511
1986
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -538,8 +2013,24 @@ function useAutocomplete(value, registry) {
|
|
|
538
2013
|
}
|
|
539
2014
|
};
|
|
540
2015
|
}
|
|
541
|
-
|
|
542
|
-
|
|
2016
|
+
var BORDER_HORIZONTAL = 2;
|
|
2017
|
+
var PADDING_LEFT = 1;
|
|
2018
|
+
var PROMPT_WIDTH = 2;
|
|
2019
|
+
var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
|
|
2020
|
+
function InputArea({
|
|
2021
|
+
onSubmit,
|
|
2022
|
+
onCancelQueue,
|
|
2023
|
+
isDisabled,
|
|
2024
|
+
isAborting,
|
|
2025
|
+
pendingPrompt,
|
|
2026
|
+
registry
|
|
2027
|
+
}) {
|
|
2028
|
+
const [value, setValue] = (0, import_react10.useState)("");
|
|
2029
|
+
const pasteStore = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
|
|
2030
|
+
const { stdout } = (0, import_ink7.useStdout)();
|
|
2031
|
+
const terminalColumns = stdout?.columns ?? 80;
|
|
2032
|
+
const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
|
|
2033
|
+
const pasteIdRef = (0, import_react10.useRef)(0);
|
|
543
2034
|
const {
|
|
544
2035
|
showPopup,
|
|
545
2036
|
filteredCommands,
|
|
@@ -548,7 +2039,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
548
2039
|
isSubcommandMode,
|
|
549
2040
|
setShowPopup
|
|
550
2041
|
} = useAutocomplete(value, registry);
|
|
551
|
-
const
|
|
2042
|
+
const handlePaste = (0, import_react10.useCallback)((text) => {
|
|
2043
|
+
pasteIdRef.current += 1;
|
|
2044
|
+
const id = pasteIdRef.current;
|
|
2045
|
+
pasteStore.current.set(id, text);
|
|
2046
|
+
const lineCount = text.split("\n").length;
|
|
2047
|
+
const label = `[Pasted text #${id} +${lineCount} lines]`;
|
|
2048
|
+
setValue((prev) => prev ? `${prev} ${label}` : label);
|
|
2049
|
+
}, []);
|
|
2050
|
+
const handleSubmit = (0, import_react10.useCallback)(
|
|
552
2051
|
(text) => {
|
|
553
2052
|
const trimmed = text.trim();
|
|
554
2053
|
if (trimmed.length === 0) return;
|
|
@@ -556,12 +2055,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
556
2055
|
selectCommand(filteredCommands[selectedIndex]);
|
|
557
2056
|
return;
|
|
558
2057
|
}
|
|
2058
|
+
const expanded = expandPasteLabels(trimmed, pasteStore.current);
|
|
559
2059
|
setValue("");
|
|
560
|
-
|
|
2060
|
+
pasteStore.current.clear();
|
|
2061
|
+
pasteIdRef.current = 0;
|
|
2062
|
+
onSubmit(expanded);
|
|
561
2063
|
},
|
|
562
2064
|
[showPopup, filteredCommands, selectedIndex, onSubmit]
|
|
563
2065
|
);
|
|
564
|
-
const selectCommand = (0,
|
|
2066
|
+
const selectCommand = (0, import_react10.useCallback)(
|
|
565
2067
|
(cmd) => {
|
|
566
2068
|
const parsed = parseSlashInput(value);
|
|
567
2069
|
if (parsed.parentCommand) {
|
|
@@ -580,7 +2082,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
580
2082
|
},
|
|
581
2083
|
[value, onSubmit, setSelectedIndex]
|
|
582
2084
|
);
|
|
583
|
-
(0,
|
|
2085
|
+
(0, import_ink7.useInput)(
|
|
584
2086
|
(_input, key) => {
|
|
585
2087
|
if (!showPopup) return;
|
|
586
2088
|
if (key.upArrow) {
|
|
@@ -596,8 +2098,16 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
596
2098
|
},
|
|
597
2099
|
{ isActive: showPopup && !isDisabled }
|
|
598
2100
|
);
|
|
599
|
-
|
|
600
|
-
|
|
2101
|
+
(0, import_ink7.useInput)(
|
|
2102
|
+
(_input, key) => {
|
|
2103
|
+
if ((key.backspace || key.delete) && pendingPrompt) {
|
|
2104
|
+
onCancelQueue?.();
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
{ isActive: !!pendingPrompt }
|
|
2108
|
+
);
|
|
2109
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
|
|
2110
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
601
2111
|
SlashAutocomplete,
|
|
602
2112
|
{
|
|
603
2113
|
commands: filteredCommands,
|
|
@@ -606,41 +2116,100 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
606
2116
|
isSubcommandMode
|
|
607
2117
|
}
|
|
608
2118
|
),
|
|
609
|
-
/* @__PURE__ */ (0,
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
2119
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2120
|
+
import_ink7.Box,
|
|
2121
|
+
{
|
|
2122
|
+
borderStyle: "single",
|
|
2123
|
+
borderColor: isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green",
|
|
2124
|
+
paddingLeft: 1,
|
|
2125
|
+
children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: "cyan", children: [
|
|
2126
|
+
" ",
|
|
2127
|
+
"Queued: ",
|
|
2128
|
+
pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
|
|
2129
|
+
" ",
|
|
2130
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: "(Backspace to cancel)" })
|
|
2131
|
+
] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
|
|
2132
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
|
|
2133
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2134
|
+
CjkTextInput,
|
|
2135
|
+
{
|
|
2136
|
+
value,
|
|
2137
|
+
onChange: setValue,
|
|
2138
|
+
onSubmit: handleSubmit,
|
|
2139
|
+
onPaste: handlePaste,
|
|
2140
|
+
placeholder: "Type a message or /help",
|
|
2141
|
+
availableWidth
|
|
2142
|
+
}
|
|
2143
|
+
)
|
|
2144
|
+
] })
|
|
2145
|
+
}
|
|
2146
|
+
)
|
|
2147
|
+
] });
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/ui/ConfirmPrompt.tsx
|
|
2151
|
+
var import_react11 = require("react");
|
|
2152
|
+
var import_ink8 = require("ink");
|
|
2153
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2154
|
+
function ConfirmPrompt({
|
|
2155
|
+
message,
|
|
2156
|
+
options = ["Yes", "No"],
|
|
2157
|
+
onSelect
|
|
2158
|
+
}) {
|
|
2159
|
+
const [selected, setSelected] = (0, import_react11.useState)(0);
|
|
2160
|
+
const resolvedRef = (0, import_react11.useRef)(false);
|
|
2161
|
+
const doSelect = (0, import_react11.useCallback)(
|
|
2162
|
+
(index) => {
|
|
2163
|
+
if (resolvedRef.current) return;
|
|
2164
|
+
resolvedRef.current = true;
|
|
2165
|
+
onSelect(index);
|
|
2166
|
+
},
|
|
2167
|
+
[onSelect]
|
|
2168
|
+
);
|
|
2169
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
2170
|
+
if (resolvedRef.current) return;
|
|
2171
|
+
if (key.leftArrow || key.upArrow) {
|
|
2172
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
2173
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
2174
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
2175
|
+
} else if (key.return) {
|
|
2176
|
+
doSelect(selected);
|
|
2177
|
+
} else if (input === "y" && options.length === 2) {
|
|
2178
|
+
doSelect(0);
|
|
2179
|
+
} else if (input === "n" && options.length === 2) {
|
|
2180
|
+
doSelect(1);
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2184
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
|
|
2185
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
2186
|
+
i === selected ? "> " : " ",
|
|
2187
|
+
opt
|
|
2188
|
+
] }) }, opt)) }),
|
|
2189
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
621
2190
|
] });
|
|
622
2191
|
}
|
|
623
2192
|
|
|
624
2193
|
// src/ui/PermissionPrompt.tsx
|
|
625
|
-
var
|
|
626
|
-
var
|
|
627
|
-
var
|
|
2194
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
2195
|
+
var import_ink9 = require("ink");
|
|
2196
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
628
2197
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
629
|
-
function
|
|
2198
|
+
function formatArgs(args) {
|
|
630
2199
|
const entries = Object.entries(args);
|
|
631
2200
|
if (entries.length === 0) return "(no arguments)";
|
|
632
2201
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
633
2202
|
}
|
|
634
2203
|
function PermissionPrompt({ request }) {
|
|
635
|
-
const [selected, setSelected] =
|
|
636
|
-
const resolvedRef =
|
|
637
|
-
const prevRequestRef =
|
|
2204
|
+
const [selected, setSelected] = import_react12.default.useState(0);
|
|
2205
|
+
const resolvedRef = import_react12.default.useRef(false);
|
|
2206
|
+
const prevRequestRef = import_react12.default.useRef(request);
|
|
638
2207
|
if (prevRequestRef.current !== request) {
|
|
639
2208
|
prevRequestRef.current = request;
|
|
640
2209
|
resolvedRef.current = false;
|
|
641
2210
|
setSelected(0);
|
|
642
2211
|
}
|
|
643
|
-
const doResolve =
|
|
2212
|
+
const doResolve = import_react12.default.useCallback(
|
|
644
2213
|
(index) => {
|
|
645
2214
|
if (resolvedRef.current) return;
|
|
646
2215
|
resolvedRef.current = true;
|
|
@@ -650,7 +2219,7 @@ function PermissionPrompt({ request }) {
|
|
|
650
2219
|
},
|
|
651
2220
|
[request]
|
|
652
2221
|
);
|
|
653
|
-
(0,
|
|
2222
|
+
(0, import_ink9.useInput)((input, key) => {
|
|
654
2223
|
if (resolvedRef.current) return;
|
|
655
2224
|
if (key.upArrow || key.leftArrow) {
|
|
656
2225
|
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
@@ -666,364 +2235,709 @@ function PermissionPrompt({ request }) {
|
|
|
666
2235
|
doResolve(2);
|
|
667
2236
|
}
|
|
668
2237
|
});
|
|
669
|
-
return /* @__PURE__ */ (0,
|
|
670
|
-
/* @__PURE__ */ (0,
|
|
671
|
-
/* @__PURE__ */ (0,
|
|
2238
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2239
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
2240
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
|
|
672
2241
|
"Tool:",
|
|
673
2242
|
" ",
|
|
674
|
-
/* @__PURE__ */ (0,
|
|
2243
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
675
2244
|
] }),
|
|
676
|
-
/* @__PURE__ */ (0,
|
|
2245
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
|
|
677
2246
|
" ",
|
|
678
|
-
|
|
2247
|
+
formatArgs(request.toolArgs)
|
|
679
2248
|
] }),
|
|
680
|
-
/* @__PURE__ */ (0,
|
|
2249
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
681
2250
|
i === selected ? "> " : " ",
|
|
682
2251
|
opt
|
|
683
2252
|
] }) }, opt)) }),
|
|
684
|
-
/* @__PURE__ */ (0,
|
|
2253
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
685
2254
|
] });
|
|
686
2255
|
}
|
|
687
2256
|
|
|
688
|
-
// src/ui/
|
|
689
|
-
var
|
|
690
|
-
var
|
|
691
|
-
function
|
|
692
|
-
|
|
693
|
-
return
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
write: () => {
|
|
697
|
-
},
|
|
698
|
-
writeLine: () => {
|
|
699
|
-
},
|
|
700
|
-
writeMarkdown: () => {
|
|
701
|
-
},
|
|
702
|
-
writeError: () => {
|
|
703
|
-
},
|
|
704
|
-
prompt: () => Promise.resolve(""),
|
|
705
|
-
select: () => Promise.resolve(0),
|
|
706
|
-
spinner: () => ({ stop: () => {
|
|
707
|
-
}, update: () => {
|
|
708
|
-
} })
|
|
709
|
-
};
|
|
710
|
-
function useSession(props) {
|
|
711
|
-
const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
|
|
712
|
-
const [streamingText, setStreamingText] = (0, import_react5.useState)("");
|
|
713
|
-
const permissionQueueRef = (0, import_react5.useRef)([]);
|
|
714
|
-
const processingRef = (0, import_react5.useRef)(false);
|
|
715
|
-
const processNextPermission = (0, import_react5.useCallback)(() => {
|
|
716
|
-
if (processingRef.current) return;
|
|
717
|
-
const next = permissionQueueRef.current[0];
|
|
718
|
-
if (!next) {
|
|
719
|
-
setPermissionRequest(null);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
processingRef.current = true;
|
|
723
|
-
setPermissionRequest({
|
|
724
|
-
toolName: next.toolName,
|
|
725
|
-
toolArgs: next.toolArgs,
|
|
726
|
-
resolve: (result) => {
|
|
727
|
-
permissionQueueRef.current.shift();
|
|
728
|
-
processingRef.current = false;
|
|
729
|
-
setPermissionRequest(null);
|
|
730
|
-
next.resolve(result);
|
|
731
|
-
setTimeout(() => processNextPermission(), 0);
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}, []);
|
|
735
|
-
const sessionRef = (0, import_react5.useRef)(null);
|
|
736
|
-
if (sessionRef.current === null) {
|
|
737
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
738
|
-
return new Promise((resolve) => {
|
|
739
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
740
|
-
processNextPermission();
|
|
741
|
-
});
|
|
742
|
-
};
|
|
743
|
-
const onTextDelta = (delta) => {
|
|
744
|
-
setStreamingText((prev) => prev + delta);
|
|
745
|
-
};
|
|
746
|
-
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
747
|
-
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
748
|
-
config: props.config,
|
|
749
|
-
context: props.context,
|
|
750
|
-
terminal: NOOP_TERMINAL,
|
|
751
|
-
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
752
|
-
projectInfo: props.projectInfo,
|
|
753
|
-
sessionStore: props.sessionStore,
|
|
754
|
-
permissionMode: props.permissionMode,
|
|
755
|
-
maxTurns: props.maxTurns,
|
|
756
|
-
permissionHandler,
|
|
757
|
-
onTextDelta
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
|
|
761
|
-
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
762
|
-
}
|
|
763
|
-
function useMessages() {
|
|
764
|
-
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
765
|
-
const addMessage = (0, import_react5.useCallback)((msg) => {
|
|
766
|
-
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
767
|
-
}, []);
|
|
768
|
-
return { messages, setMessages, addMessage };
|
|
2257
|
+
// src/ui/StreamingIndicator.tsx
|
|
2258
|
+
var import_ink10 = require("ink");
|
|
2259
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2260
|
+
function getToolStyle(t) {
|
|
2261
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
2262
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
2263
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
2264
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
769
2265
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
776
|
-
" /cost \u2014 Show session info",
|
|
777
|
-
" /exit \u2014 Exit CLI"
|
|
778
|
-
].join("\n");
|
|
779
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
780
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
781
|
-
if (!arg) {
|
|
782
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
783
|
-
} else if (validModes.includes(arg)) {
|
|
784
|
-
session.setPermissionMode(arg);
|
|
785
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
786
|
-
} else {
|
|
787
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
2266
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
2267
|
+
const hasTools = activeTools.length > 0;
|
|
2268
|
+
const hasText = text.length > 0;
|
|
2269
|
+
if (!hasTools && !hasText) {
|
|
2270
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
|
|
788
2271
|
}
|
|
789
|
-
return
|
|
2272
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
2273
|
+
hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2274
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
2275
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
2276
|
+
activeTools.map((t, i) => {
|
|
2277
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
2278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
2279
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
|
|
2280
|
+
" ",
|
|
2281
|
+
icon,
|
|
2282
|
+
" ",
|
|
2283
|
+
t.toolName,
|
|
2284
|
+
"(",
|
|
2285
|
+
t.firstArg,
|
|
2286
|
+
")"
|
|
2287
|
+
] }),
|
|
2288
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
2289
|
+
] }, `${t.toolName}-${i}`);
|
|
2290
|
+
})
|
|
2291
|
+
] }),
|
|
2292
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2293
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
2294
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
2295
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
2296
|
+
] })
|
|
2297
|
+
] });
|
|
790
2298
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
2299
|
+
|
|
2300
|
+
// src/ui/PluginTUI.tsx
|
|
2301
|
+
var import_react15 = require("react");
|
|
2302
|
+
|
|
2303
|
+
// src/ui/MenuSelect.tsx
|
|
2304
|
+
var import_react13 = require("react");
|
|
2305
|
+
var import_ink11 = require("ink");
|
|
2306
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2307
|
+
function MenuSelect({
|
|
2308
|
+
title,
|
|
2309
|
+
items,
|
|
2310
|
+
onSelect,
|
|
2311
|
+
onBack,
|
|
2312
|
+
loading,
|
|
2313
|
+
error
|
|
2314
|
+
}) {
|
|
2315
|
+
const [selected, setSelected] = (0, import_react13.useState)(0);
|
|
2316
|
+
const selectedRef = (0, import_react13.useRef)(0);
|
|
2317
|
+
const resolvedRef = (0, import_react13.useRef)(false);
|
|
2318
|
+
const doSelect = (0, import_react13.useCallback)(
|
|
2319
|
+
(index) => {
|
|
2320
|
+
if (resolvedRef.current || items.length === 0) return;
|
|
2321
|
+
resolvedRef.current = true;
|
|
2322
|
+
onSelect(items[index].value);
|
|
2323
|
+
},
|
|
2324
|
+
[items, onSelect]
|
|
2325
|
+
);
|
|
2326
|
+
(0, import_ink11.useInput)((input, key) => {
|
|
2327
|
+
if (resolvedRef.current) return;
|
|
2328
|
+
if (key.escape) {
|
|
2329
|
+
resolvedRef.current = true;
|
|
2330
|
+
onBack();
|
|
2331
|
+
return;
|
|
812
2332
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
2333
|
+
if (loading || error || items.length === 0) return;
|
|
2334
|
+
if (key.upArrow) {
|
|
2335
|
+
const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
|
|
2336
|
+
selectedRef.current = next;
|
|
2337
|
+
setSelected(next);
|
|
2338
|
+
} else if (key.downArrow) {
|
|
2339
|
+
const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
|
|
2340
|
+
selectedRef.current = next;
|
|
2341
|
+
setSelected(next);
|
|
2342
|
+
} else if (key.return) {
|
|
2343
|
+
doSelect(selectedRef.current);
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2347
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: title }),
|
|
2348
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Loading..." }) }),
|
|
2349
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { marginTop: 1, flexDirection: "column", children: [
|
|
2350
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "red", children: error }),
|
|
2351
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Press Esc to go back" })
|
|
2352
|
+
] }),
|
|
2353
|
+
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { children: [
|
|
2354
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
2355
|
+
i === selected ? "> " : " ",
|
|
2356
|
+
item.label
|
|
2357
|
+
] }),
|
|
2358
|
+
item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
|
|
2359
|
+
" ",
|
|
2360
|
+
item.hint
|
|
2361
|
+
] })
|
|
2362
|
+
] }, item.value)) }),
|
|
2363
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
|
|
2364
|
+
] });
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// src/ui/TextPrompt.tsx
|
|
2368
|
+
var import_react14 = require("react");
|
|
2369
|
+
var import_ink12 = require("ink");
|
|
2370
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2371
|
+
function TextPrompt({
|
|
2372
|
+
title,
|
|
2373
|
+
placeholder,
|
|
2374
|
+
onSubmit,
|
|
2375
|
+
onCancel,
|
|
2376
|
+
validate
|
|
2377
|
+
}) {
|
|
2378
|
+
const [value, setValue] = (0, import_react14.useState)("");
|
|
2379
|
+
const [error, setError] = (0, import_react14.useState)();
|
|
2380
|
+
const resolvedRef = (0, import_react14.useRef)(false);
|
|
2381
|
+
const valueRef = (0, import_react14.useRef)("");
|
|
2382
|
+
const handleSubmit = (0, import_react14.useCallback)(() => {
|
|
2383
|
+
if (resolvedRef.current) return;
|
|
2384
|
+
const trimmed = valueRef.current.trim();
|
|
2385
|
+
if (!trimmed) return;
|
|
2386
|
+
if (validate) {
|
|
2387
|
+
const err = validate(trimmed);
|
|
2388
|
+
if (err) {
|
|
2389
|
+
setError(err);
|
|
2390
|
+
return;
|
|
830
2391
|
}
|
|
831
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
832
|
-
return true;
|
|
833
2392
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
2393
|
+
resolvedRef.current = true;
|
|
2394
|
+
onSubmit(trimmed);
|
|
2395
|
+
}, [validate, onSubmit]);
|
|
2396
|
+
(0, import_ink12.useInput)((input, key) => {
|
|
2397
|
+
if (resolvedRef.current) return;
|
|
2398
|
+
if (key.escape) {
|
|
2399
|
+
resolvedRef.current = true;
|
|
2400
|
+
onCancel();
|
|
2401
|
+
return;
|
|
841
2402
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
return
|
|
845
|
-
default: {
|
|
846
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
847
|
-
if (skillCmd) {
|
|
848
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
852
|
-
return true;
|
|
2403
|
+
if (key.return) {
|
|
2404
|
+
handleSubmit();
|
|
2405
|
+
return;
|
|
853
2406
|
}
|
|
2407
|
+
if (key.backspace || key.delete) {
|
|
2408
|
+
valueRef.current = valueRef.current.slice(0, -1);
|
|
2409
|
+
setValue(valueRef.current);
|
|
2410
|
+
setError(void 0);
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
if (input && !key.ctrl && !key.meta) {
|
|
2414
|
+
valueRef.current = valueRef.current + input;
|
|
2415
|
+
setValue(valueRef.current);
|
|
2416
|
+
setError(void 0);
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
2420
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
|
|
2421
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { marginTop: 1, children: [
|
|
2422
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "> " }),
|
|
2423
|
+
value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: placeholder }) : null,
|
|
2424
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "\u2588" })
|
|
2425
|
+
] }),
|
|
2426
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "red", children: error }),
|
|
2427
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
|
|
2428
|
+
] });
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/ui/plugin-tui-handlers.ts
|
|
2432
|
+
function handleMainSelect(value, nav) {
|
|
2433
|
+
if (value === "marketplace") {
|
|
2434
|
+
nav.push({ screen: "marketplace-list" });
|
|
2435
|
+
} else if (value === "installed") {
|
|
2436
|
+
nav.push({ screen: "installed-list" });
|
|
854
2437
|
}
|
|
855
2438
|
}
|
|
856
|
-
function
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
},
|
|
863
|
-
[session, addMessage, setMessages, exit, registry]
|
|
864
|
-
);
|
|
2439
|
+
function handleMarketplaceListSelect(value, nav) {
|
|
2440
|
+
if (value === "__add__") {
|
|
2441
|
+
nav.push({ screen: "marketplace-add" });
|
|
2442
|
+
} else {
|
|
2443
|
+
nav.push({ screen: "marketplace-action", context: { marketplace: value } });
|
|
2444
|
+
}
|
|
865
2445
|
}
|
|
866
|
-
function
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
2446
|
+
function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
|
|
2447
|
+
if (value === "browse") {
|
|
2448
|
+
nav.push({ screen: "marketplace-browse", context: { marketplace } });
|
|
2449
|
+
} else if (value === "update") {
|
|
2450
|
+
callbacks.marketplaceUpdate(marketplace).then(() => {
|
|
2451
|
+
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
2452
|
+
nav.pop();
|
|
2453
|
+
}).catch((err) => {
|
|
2454
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2455
|
+
});
|
|
2456
|
+
} else if (value === "remove") {
|
|
2457
|
+
nav.setConfirm({
|
|
2458
|
+
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
2459
|
+
onConfirm: () => {
|
|
2460
|
+
nav.setConfirm(void 0);
|
|
2461
|
+
callbacks.marketplaceRemove(marketplace).then(() => {
|
|
2462
|
+
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
2463
|
+
nav.popN(2);
|
|
2464
|
+
}).catch((err) => {
|
|
2465
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2466
|
+
});
|
|
2467
|
+
},
|
|
2468
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2469
|
+
});
|
|
876
2470
|
}
|
|
877
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
|
|
878
2471
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
887
|
-
} catch (err) {
|
|
888
|
-
clearStreamingText();
|
|
889
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
890
|
-
addMessage({ role: "system", content: "Cancelled." });
|
|
891
|
-
} else {
|
|
892
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
893
|
-
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
894
|
-
}
|
|
895
|
-
} finally {
|
|
896
|
-
setIsThinking(false);
|
|
2472
|
+
function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
|
|
2473
|
+
const fullId = `${value}@${marketplace}`;
|
|
2474
|
+
const item = items.find((i) => i.value === value);
|
|
2475
|
+
if (item?.hint === "installed") {
|
|
2476
|
+
nav.push({ screen: "installed-action", context: { pluginId: fullId } });
|
|
2477
|
+
} else {
|
|
2478
|
+
nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
|
|
897
2479
|
}
|
|
898
2480
|
}
|
|
899
|
-
function
|
|
900
|
-
const
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
2481
|
+
function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
|
|
2482
|
+
const scope = value;
|
|
2483
|
+
callbacks.install(pluginId, scope).then(() => {
|
|
2484
|
+
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
2485
|
+
nav.popN(2);
|
|
2486
|
+
}).catch((err) => {
|
|
2487
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2488
|
+
});
|
|
906
2489
|
}
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
2490
|
+
function handleInstalledListSelect(value, callbacks, nav) {
|
|
2491
|
+
nav.setConfirm({
|
|
2492
|
+
message: `Uninstall plugin "${value}"?`,
|
|
2493
|
+
onConfirm: () => {
|
|
2494
|
+
nav.setConfirm(void 0);
|
|
2495
|
+
callbacks.uninstall(value).then(() => {
|
|
2496
|
+
nav.notify(`Uninstalled plugin "${value}".`);
|
|
2497
|
+
nav.refresh();
|
|
2498
|
+
}).catch((err) => {
|
|
2499
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2500
|
+
});
|
|
2501
|
+
},
|
|
2502
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
|
|
2506
|
+
if (value === "uninstall") {
|
|
2507
|
+
nav.setConfirm({
|
|
2508
|
+
message: `Uninstall plugin "${pluginId}"?`,
|
|
2509
|
+
onConfirm: () => {
|
|
2510
|
+
nav.setConfirm(void 0);
|
|
2511
|
+
callbacks.uninstall(pluginId).then(() => {
|
|
2512
|
+
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
2513
|
+
nav.popN(2);
|
|
2514
|
+
}).catch((err) => {
|
|
2515
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2516
|
+
});
|
|
2517
|
+
},
|
|
2518
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// src/ui/PluginTUI.tsx
|
|
2524
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
2525
|
+
function PluginTUI({ callbacks, onClose, addMessage }) {
|
|
2526
|
+
const [stack, setStack] = (0, import_react15.useState)([{ screen: "main" }]);
|
|
2527
|
+
const [items, setItems] = (0, import_react15.useState)([]);
|
|
2528
|
+
const [loading, setLoading] = (0, import_react15.useState)(false);
|
|
2529
|
+
const [error, setError] = (0, import_react15.useState)();
|
|
2530
|
+
const [confirm, setConfirm] = (0, import_react15.useState)();
|
|
2531
|
+
const [refreshCounter, setRefreshCounter] = (0, import_react15.useState)(0);
|
|
2532
|
+
const current = stack[stack.length - 1] ?? { screen: "main" };
|
|
2533
|
+
const push = (0, import_react15.useCallback)((state) => {
|
|
2534
|
+
setStack((prev) => [...prev, state]);
|
|
2535
|
+
setItems([]);
|
|
2536
|
+
setError(void 0);
|
|
2537
|
+
}, []);
|
|
2538
|
+
const pop = (0, import_react15.useCallback)(() => {
|
|
2539
|
+
setStack((prev) => {
|
|
2540
|
+
if (prev.length <= 1) {
|
|
2541
|
+
onClose();
|
|
2542
|
+
return prev;
|
|
2543
|
+
}
|
|
2544
|
+
return prev.slice(0, -1);
|
|
2545
|
+
});
|
|
2546
|
+
setItems([]);
|
|
2547
|
+
setError(void 0);
|
|
2548
|
+
}, [onClose]);
|
|
2549
|
+
const popN = (0, import_react15.useCallback)(
|
|
2550
|
+
(n) => {
|
|
2551
|
+
setStack((prev) => {
|
|
2552
|
+
const next = prev.slice(0, Math.max(1, prev.length - n));
|
|
2553
|
+
if (next.length === 0) {
|
|
2554
|
+
onClose();
|
|
2555
|
+
return prev;
|
|
915
2556
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
2557
|
+
return next;
|
|
2558
|
+
});
|
|
2559
|
+
setItems([]);
|
|
2560
|
+
setError(void 0);
|
|
2561
|
+
},
|
|
2562
|
+
[onClose]
|
|
2563
|
+
);
|
|
2564
|
+
const notify = (0, import_react15.useCallback)(
|
|
2565
|
+
(content) => {
|
|
2566
|
+
addMessage?.({ role: "system", content });
|
|
2567
|
+
},
|
|
2568
|
+
[addMessage]
|
|
2569
|
+
);
|
|
2570
|
+
const refresh = (0, import_react15.useCallback)(() => {
|
|
2571
|
+
setItems([]);
|
|
2572
|
+
setRefreshCounter((c) => c + 1);
|
|
2573
|
+
}, []);
|
|
2574
|
+
const nav = { push, pop, popN, notify, setConfirm, refresh };
|
|
2575
|
+
(0, import_react15.useEffect)(() => {
|
|
2576
|
+
const screen2 = current.screen;
|
|
2577
|
+
if (screen2 === "marketplace-list") {
|
|
2578
|
+
setLoading(true);
|
|
2579
|
+
callbacks.marketplaceList().then((sources) => {
|
|
2580
|
+
const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
|
|
2581
|
+
const sourceItems = sources.map((s) => ({
|
|
2582
|
+
label: s.name,
|
|
2583
|
+
value: s.name,
|
|
2584
|
+
hint: s.type
|
|
2585
|
+
}));
|
|
2586
|
+
setItems([...baseItems, ...sourceItems]);
|
|
2587
|
+
setLoading(false);
|
|
2588
|
+
}).catch((err) => {
|
|
2589
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2590
|
+
setLoading(false);
|
|
2591
|
+
});
|
|
2592
|
+
} else if (screen2 === "marketplace-browse") {
|
|
2593
|
+
const marketplace = current.context?.marketplace ?? "";
|
|
2594
|
+
setLoading(true);
|
|
2595
|
+
callbacks.listAvailablePlugins(marketplace).then((plugins) => {
|
|
2596
|
+
setItems(
|
|
2597
|
+
plugins.map((p) => ({
|
|
2598
|
+
label: p.name,
|
|
2599
|
+
value: p.name,
|
|
2600
|
+
hint: p.installed ? "installed" : p.description
|
|
2601
|
+
}))
|
|
2602
|
+
);
|
|
2603
|
+
setLoading(false);
|
|
2604
|
+
}).catch((err) => {
|
|
2605
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2606
|
+
setLoading(false);
|
|
2607
|
+
});
|
|
2608
|
+
} else if (screen2 === "installed-list") {
|
|
2609
|
+
setLoading(true);
|
|
2610
|
+
callbacks.listInstalled().then((plugins) => {
|
|
2611
|
+
setItems(
|
|
2612
|
+
plugins.map((p) => ({
|
|
2613
|
+
label: p.name,
|
|
2614
|
+
value: p.name,
|
|
2615
|
+
hint: p.description
|
|
2616
|
+
}))
|
|
925
2617
|
);
|
|
2618
|
+
setLoading(false);
|
|
2619
|
+
}).catch((err) => {
|
|
2620
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2621
|
+
setLoading(false);
|
|
2622
|
+
});
|
|
2623
|
+
}
|
|
2624
|
+
}, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
|
|
2625
|
+
const handleSelect = (0, import_react15.useCallback)(
|
|
2626
|
+
(value) => {
|
|
2627
|
+
const screen2 = current.screen;
|
|
2628
|
+
const ctx = current.context;
|
|
2629
|
+
if (screen2 === "main") handleMainSelect(value, nav);
|
|
2630
|
+
else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
|
|
2631
|
+
else if (screen2 === "marketplace-action")
|
|
2632
|
+
handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
|
|
2633
|
+
else if (screen2 === "marketplace-browse")
|
|
2634
|
+
handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
|
|
2635
|
+
else if (screen2 === "marketplace-install-scope")
|
|
2636
|
+
handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
2637
|
+
else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
|
|
2638
|
+
else if (screen2 === "installed-action")
|
|
2639
|
+
handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
2640
|
+
},
|
|
2641
|
+
[current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
|
|
2642
|
+
);
|
|
2643
|
+
const handleTextSubmit = (0, import_react15.useCallback)(
|
|
2644
|
+
(value) => {
|
|
2645
|
+
if (current.screen === "marketplace-add") {
|
|
2646
|
+
callbacks.marketplaceAdd(value).then((name) => {
|
|
2647
|
+
notify(`Added marketplace "${name}" from ${value}.`);
|
|
2648
|
+
pop();
|
|
2649
|
+
}).catch((err) => {
|
|
2650
|
+
notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2651
|
+
pop();
|
|
2652
|
+
});
|
|
926
2653
|
}
|
|
927
|
-
addMessage({ role: "user", content: input });
|
|
928
|
-
return runSessionPrompt(
|
|
929
|
-
input,
|
|
930
|
-
session,
|
|
931
|
-
addMessage,
|
|
932
|
-
clearStreamingText,
|
|
933
|
-
setIsThinking,
|
|
934
|
-
setContextPercentage
|
|
935
|
-
);
|
|
936
2654
|
},
|
|
937
|
-
[
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
2655
|
+
[current.screen, callbacks, notify, pop]
|
|
2656
|
+
);
|
|
2657
|
+
if (confirm) {
|
|
2658
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2659
|
+
ConfirmPrompt,
|
|
2660
|
+
{
|
|
2661
|
+
message: confirm.message,
|
|
2662
|
+
onSelect: (index) => {
|
|
2663
|
+
if (index === 0) confirm.onConfirm();
|
|
2664
|
+
else confirm.onCancel();
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
const screen = current.screen;
|
|
2670
|
+
if (screen === "marketplace-add") {
|
|
2671
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2672
|
+
TextPrompt,
|
|
2673
|
+
{
|
|
2674
|
+
title: "Add Marketplace Source",
|
|
2675
|
+
placeholder: "owner/repo or git URL",
|
|
2676
|
+
onSubmit: handleTextSubmit,
|
|
2677
|
+
onCancel: pop,
|
|
2678
|
+
validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
|
|
2679
|
+
}
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
if (screen === "marketplace-action") {
|
|
2683
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2684
|
+
MenuSelect,
|
|
2685
|
+
{
|
|
2686
|
+
title: `Marketplace: ${current.context?.marketplace ?? ""}`,
|
|
2687
|
+
items: [
|
|
2688
|
+
{ label: "Browse plugins", value: "browse" },
|
|
2689
|
+
{ label: "Update", value: "update" },
|
|
2690
|
+
{ label: "Remove", value: "remove" }
|
|
2691
|
+
],
|
|
2692
|
+
onSelect: handleSelect,
|
|
2693
|
+
onBack: pop
|
|
2694
|
+
},
|
|
2695
|
+
stack.length
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
if (screen === "marketplace-install-scope") {
|
|
2699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2700
|
+
MenuSelect,
|
|
2701
|
+
{
|
|
2702
|
+
title: `Install scope for "${current.context?.pluginId ?? ""}"`,
|
|
2703
|
+
items: [
|
|
2704
|
+
{ label: "User scope", value: "user" },
|
|
2705
|
+
{ label: "Project scope", value: "project" }
|
|
2706
|
+
],
|
|
2707
|
+
onSelect: handleSelect,
|
|
2708
|
+
onBack: pop
|
|
2709
|
+
},
|
|
2710
|
+
stack.length
|
|
2711
|
+
);
|
|
2712
|
+
}
|
|
2713
|
+
if (screen === "installed-action") {
|
|
2714
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2715
|
+
MenuSelect,
|
|
2716
|
+
{
|
|
2717
|
+
title: `Plugin: ${current.context?.pluginId ?? ""}`,
|
|
2718
|
+
items: [{ label: "Uninstall", value: "uninstall" }],
|
|
2719
|
+
onSelect: handleSelect,
|
|
2720
|
+
onBack: pop
|
|
2721
|
+
},
|
|
2722
|
+
stack.length
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
const titleMap = {
|
|
2726
|
+
main: "Plugin Management",
|
|
2727
|
+
"marketplace-list": "Marketplace",
|
|
2728
|
+
"marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
|
|
2729
|
+
"installed-list": "Installed Plugins"
|
|
2730
|
+
};
|
|
2731
|
+
const staticItemsMap = {
|
|
2732
|
+
main: [
|
|
2733
|
+
{ label: "Marketplace", value: "marketplace" },
|
|
2734
|
+
{ label: "Installed Plugins", value: "installed" }
|
|
945
2735
|
]
|
|
2736
|
+
};
|
|
2737
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
2738
|
+
MenuSelect,
|
|
2739
|
+
{
|
|
2740
|
+
title: titleMap[screen] ?? "Plugin Management",
|
|
2741
|
+
items: staticItemsMap[screen] ?? items,
|
|
2742
|
+
onSelect: handleSelect,
|
|
2743
|
+
onBack: pop,
|
|
2744
|
+
loading,
|
|
2745
|
+
error
|
|
2746
|
+
},
|
|
2747
|
+
`${screen}-${stack.length}-${refreshCounter}`
|
|
946
2748
|
);
|
|
947
2749
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
2750
|
+
|
|
2751
|
+
// src/ui/App.tsx
|
|
2752
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
2753
|
+
var EXIT_DELAY_MS2 = 500;
|
|
2754
|
+
function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
2755
|
+
const pluginKeys = Object.keys(pluginHooks);
|
|
2756
|
+
if (pluginKeys.length === 0) return configHooks;
|
|
2757
|
+
const merged = {};
|
|
2758
|
+
for (const [event, groups] of Object.entries(pluginHooks)) {
|
|
2759
|
+
merged[event] = [...groups];
|
|
2760
|
+
}
|
|
2761
|
+
if (configHooks) {
|
|
2762
|
+
for (const [event, groups] of Object.entries(configHooks)) {
|
|
2763
|
+
if (!Array.isArray(groups)) continue;
|
|
2764
|
+
if (!merged[event]) merged[event] = [];
|
|
2765
|
+
merged[event].push(...groups);
|
|
2766
|
+
}
|
|
955
2767
|
}
|
|
956
|
-
return
|
|
2768
|
+
return merged;
|
|
957
2769
|
}
|
|
958
2770
|
function App(props) {
|
|
959
|
-
const { exit } = (0,
|
|
960
|
-
const {
|
|
2771
|
+
const { exit } = (0, import_ink13.useApp)();
|
|
2772
|
+
const { registry, pluginHooks } = useCommandRegistry(props.cwd ?? process.cwd());
|
|
2773
|
+
const configWithPluginHooks = {
|
|
2774
|
+
...props.config,
|
|
2775
|
+
hooks: mergeHooksIntoConfig(
|
|
2776
|
+
props.config.hooks,
|
|
2777
|
+
pluginHooks
|
|
2778
|
+
)
|
|
2779
|
+
};
|
|
2780
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(
|
|
2781
|
+
{ ...props, config: configWithPluginHooks }
|
|
2782
|
+
);
|
|
961
2783
|
const { messages, setMessages, addMessage } = useMessages();
|
|
962
|
-
const [isThinking, setIsThinking] = (0,
|
|
963
|
-
const
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
2784
|
+
const [isThinking, setIsThinking] = (0, import_react16.useState)(false);
|
|
2785
|
+
const initialCtx = session.getContextState();
|
|
2786
|
+
const [contextState, setContextState] = (0, import_react16.useState)({
|
|
2787
|
+
percentage: initialCtx.usedPercentage,
|
|
2788
|
+
usedTokens: initialCtx.usedTokens,
|
|
2789
|
+
maxTokens: initialCtx.maxTokens
|
|
2790
|
+
});
|
|
2791
|
+
const pendingModelChangeRef = (0, import_react16.useRef)(null);
|
|
2792
|
+
const [pendingModelId, setPendingModelId] = (0, import_react16.useState)(null);
|
|
2793
|
+
const [showPluginTUI, setShowPluginTUI] = (0, import_react16.useState)(false);
|
|
2794
|
+
const [isAborting, setIsAborting] = (0, import_react16.useState)(false);
|
|
2795
|
+
const [pendingPrompt, setPendingPrompt] = (0, import_react16.useState)(null);
|
|
2796
|
+
const pendingPromptRef = (0, import_react16.useRef)(null);
|
|
2797
|
+
const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
|
|
2798
|
+
const handleSlashCommand = useSlashCommands(
|
|
2799
|
+
session,
|
|
2800
|
+
addMessage,
|
|
2801
|
+
setMessages,
|
|
2802
|
+
exit,
|
|
2803
|
+
registry,
|
|
2804
|
+
pendingModelChangeRef,
|
|
2805
|
+
setPendingModelId,
|
|
2806
|
+
pluginCallbacks,
|
|
2807
|
+
setShowPluginTUI
|
|
2808
|
+
);
|
|
2809
|
+
const executePrompt = useSubmitHandler(
|
|
967
2810
|
session,
|
|
968
2811
|
addMessage,
|
|
969
2812
|
handleSlashCommand,
|
|
970
2813
|
clearStreamingText,
|
|
971
2814
|
setIsThinking,
|
|
972
|
-
|
|
2815
|
+
setContextState,
|
|
973
2816
|
registry
|
|
974
2817
|
);
|
|
975
|
-
(0,
|
|
2818
|
+
const handleSubmit = (0, import_react16.useCallback)(
|
|
2819
|
+
async (input) => {
|
|
2820
|
+
if (isThinking) {
|
|
2821
|
+
setPendingPrompt(input);
|
|
2822
|
+
pendingPromptRef.current = input;
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
await executePrompt(input);
|
|
2826
|
+
},
|
|
2827
|
+
[isThinking, executePrompt]
|
|
2828
|
+
);
|
|
2829
|
+
(0, import_ink13.useInput)(
|
|
976
2830
|
(_input, key) => {
|
|
977
|
-
if (key.
|
|
978
|
-
|
|
2831
|
+
if (key.escape && isThinking) {
|
|
2832
|
+
setIsAborting(true);
|
|
2833
|
+
setPendingPrompt(null);
|
|
2834
|
+
pendingPromptRef.current = null;
|
|
2835
|
+
session.abort();
|
|
2836
|
+
}
|
|
979
2837
|
},
|
|
980
|
-
{ isActive: !permissionRequest }
|
|
2838
|
+
{ isActive: !permissionRequest && !showPluginTUI }
|
|
981
2839
|
);
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
2840
|
+
(0, import_react16.useEffect)(() => {
|
|
2841
|
+
if (!isThinking) {
|
|
2842
|
+
setIsAborting(false);
|
|
2843
|
+
if (pendingPromptRef.current) {
|
|
2844
|
+
const prompt = pendingPromptRef.current;
|
|
2845
|
+
setPendingPrompt(null);
|
|
2846
|
+
pendingPromptRef.current = null;
|
|
2847
|
+
setTimeout(() => executePrompt(prompt), 0);
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
}, [isThinking, pendingPrompt, executePrompt]);
|
|
2851
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", children: [
|
|
2852
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
2853
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { color: "cyan", bold: true, children: `
|
|
985
2854
|
____ ___ ____ ___ _____ _
|
|
986
2855
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
987
2856
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
988
2857
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
989
2858
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
990
2859
|
` }),
|
|
991
|
-
/* @__PURE__ */ (0,
|
|
2860
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
|
|
992
2861
|
" v",
|
|
993
2862
|
props.version ?? "0.0.0"
|
|
994
2863
|
] })
|
|
995
2864
|
] }),
|
|
996
|
-
/* @__PURE__ */ (0,
|
|
997
|
-
/* @__PURE__ */ (0,
|
|
998
|
-
isThinking && /* @__PURE__ */ (0,
|
|
2865
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
2866
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageList, { messages }),
|
|
2867
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
999
2868
|
] }),
|
|
1000
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
1001
|
-
/* @__PURE__ */ (0,
|
|
2869
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
2870
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2871
|
+
ConfirmPrompt,
|
|
2872
|
+
{
|
|
2873
|
+
message: `Change model to ${(0, import_agent_core6.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
2874
|
+
onSelect: (index) => {
|
|
2875
|
+
setPendingModelId(null);
|
|
2876
|
+
pendingModelChangeRef.current = null;
|
|
2877
|
+
if (index === 0) {
|
|
2878
|
+
try {
|
|
2879
|
+
const settingsPath = getUserSettingsPath();
|
|
2880
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
2881
|
+
addMessage(
|
|
2882
|
+
(0, import_agent_core7.createSystemMessage)(
|
|
2883
|
+
`Model changed to ${(0, import_agent_core6.getModelName)(pendingModelId)}. Restarting...`
|
|
2884
|
+
)
|
|
2885
|
+
);
|
|
2886
|
+
setTimeout(() => exit(), EXIT_DELAY_MS2);
|
|
2887
|
+
} catch (err) {
|
|
2888
|
+
addMessage(
|
|
2889
|
+
(0, import_agent_core7.createSystemMessage)(
|
|
2890
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2891
|
+
)
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
} else {
|
|
2895
|
+
addMessage((0, import_agent_core7.createSystemMessage)("Model change cancelled."));
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
),
|
|
2900
|
+
showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
2901
|
+
PluginTUI,
|
|
2902
|
+
{
|
|
2903
|
+
callbacks: pluginCallbacks,
|
|
2904
|
+
onClose: () => setShowPluginTUI(false),
|
|
2905
|
+
addMessage: (msg) => addMessage((0, import_agent_core7.createSystemMessage)(msg.content))
|
|
2906
|
+
}
|
|
2907
|
+
),
|
|
2908
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1002
2909
|
StatusBar,
|
|
1003
2910
|
{
|
|
1004
2911
|
permissionMode: session.getPermissionMode(),
|
|
1005
|
-
modelName: props.config.provider.model,
|
|
2912
|
+
modelName: (0, import_agent_core6.getModelName)(props.config.provider.model),
|
|
1006
2913
|
sessionId: session.getSessionId(),
|
|
1007
2914
|
messageCount: messages.length,
|
|
1008
2915
|
isThinking,
|
|
1009
|
-
contextPercentage,
|
|
1010
|
-
contextUsedTokens:
|
|
1011
|
-
contextMaxTokens:
|
|
2916
|
+
contextPercentage: contextState.percentage,
|
|
2917
|
+
contextUsedTokens: contextState.usedTokens,
|
|
2918
|
+
contextMaxTokens: contextState.maxTokens
|
|
1012
2919
|
}
|
|
1013
2920
|
),
|
|
1014
|
-
/* @__PURE__ */ (0,
|
|
2921
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1015
2922
|
InputArea,
|
|
1016
2923
|
{
|
|
1017
2924
|
onSubmit: handleSubmit,
|
|
1018
|
-
|
|
2925
|
+
onCancelQueue: () => {
|
|
2926
|
+
setPendingPrompt(null);
|
|
2927
|
+
pendingPromptRef.current = null;
|
|
2928
|
+
},
|
|
2929
|
+
isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
|
|
2930
|
+
isAborting,
|
|
2931
|
+
pendingPrompt,
|
|
1019
2932
|
registry
|
|
1020
2933
|
}
|
|
1021
|
-
)
|
|
2934
|
+
),
|
|
2935
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { children: " " })
|
|
1022
2936
|
] });
|
|
1023
2937
|
}
|
|
1024
2938
|
|
|
1025
2939
|
// src/ui/render.tsx
|
|
1026
|
-
var
|
|
2940
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1027
2941
|
function renderApp(options) {
|
|
1028
2942
|
process.on("unhandledRejection", (reason) => {
|
|
1029
2943
|
process.stderr.write(`
|
|
@@ -1034,29 +2948,50 @@ function renderApp(options) {
|
|
|
1034
2948
|
`);
|
|
1035
2949
|
}
|
|
1036
2950
|
});
|
|
1037
|
-
|
|
2951
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
2952
|
+
process.stdout.write("\x1B[?2004h");
|
|
2953
|
+
}
|
|
2954
|
+
const instance = (0, import_ink14.render)(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(App, { ...options }), {
|
|
1038
2955
|
exitOnCtrlC: true
|
|
1039
2956
|
});
|
|
1040
|
-
instance.waitUntilExit().
|
|
2957
|
+
instance.waitUntilExit().then(() => {
|
|
2958
|
+
if (process.stdout.isTTY) {
|
|
2959
|
+
process.stdout.write("\x1B[?2004l");
|
|
2960
|
+
}
|
|
2961
|
+
process.exit(0);
|
|
2962
|
+
}).catch((err) => {
|
|
1041
2963
|
if (err) {
|
|
1042
2964
|
process.stderr.write(`
|
|
1043
2965
|
[EXIT ERROR] ${err}
|
|
1044
2966
|
`);
|
|
1045
2967
|
}
|
|
2968
|
+
process.exit(1);
|
|
1046
2969
|
});
|
|
1047
2970
|
}
|
|
1048
2971
|
|
|
1049
2972
|
// src/cli.ts
|
|
1050
2973
|
var import_meta = {};
|
|
1051
|
-
|
|
2974
|
+
function checkSettingsFile(filePath) {
|
|
2975
|
+
if (!(0, import_node_fs4.existsSync)(filePath)) return "missing";
|
|
2976
|
+
try {
|
|
2977
|
+
const raw = (0, import_node_fs4.readFileSync)(filePath, "utf8").trim();
|
|
2978
|
+
if (raw.length === 0) return "incomplete";
|
|
2979
|
+
const parsed = JSON.parse(raw);
|
|
2980
|
+
const provider = parsed.provider;
|
|
2981
|
+
if (!provider?.apiKey) return "incomplete";
|
|
2982
|
+
return "valid";
|
|
2983
|
+
} catch {
|
|
2984
|
+
return "corrupt";
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
1052
2987
|
function readVersion() {
|
|
1053
2988
|
try {
|
|
1054
2989
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1055
|
-
const dir = (0,
|
|
1056
|
-
const candidates = [(0,
|
|
2990
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
2991
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1057
2992
|
for (const pkgPath of candidates) {
|
|
1058
2993
|
try {
|
|
1059
|
-
const raw = (0,
|
|
2994
|
+
const raw = (0, import_node_fs4.readFileSync)(pkgPath, "utf-8");
|
|
1060
2995
|
const pkg = JSON.parse(raw);
|
|
1061
2996
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1062
2997
|
return pkg.version;
|
|
@@ -1069,97 +3004,107 @@ function readVersion() {
|
|
|
1069
3004
|
return "0.0.0";
|
|
1070
3005
|
}
|
|
1071
3006
|
}
|
|
1072
|
-
function
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
3007
|
+
function promptInput(label, masked = false) {
|
|
3008
|
+
return new Promise((resolve) => {
|
|
3009
|
+
process.stdout.write(label);
|
|
3010
|
+
let input = "";
|
|
3011
|
+
const stdin = process.stdin;
|
|
3012
|
+
const wasRaw = stdin.isRaw;
|
|
3013
|
+
stdin.setRawMode(true);
|
|
3014
|
+
stdin.resume();
|
|
3015
|
+
stdin.setEncoding("utf8");
|
|
3016
|
+
const onData = (data) => {
|
|
3017
|
+
for (const ch of data) {
|
|
3018
|
+
if (ch === "\r" || ch === "\n") {
|
|
3019
|
+
stdin.removeListener("data", onData);
|
|
3020
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
3021
|
+
stdin.pause();
|
|
3022
|
+
process.stdout.write("\n");
|
|
3023
|
+
resolve(input.trim());
|
|
3024
|
+
return;
|
|
3025
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
3026
|
+
if (input.length > 0) {
|
|
3027
|
+
input = input.slice(0, -1);
|
|
3028
|
+
process.stdout.write("\b \b");
|
|
3029
|
+
}
|
|
3030
|
+
} else if (ch === "") {
|
|
3031
|
+
process.stdout.write("\n");
|
|
3032
|
+
process.exit(0);
|
|
3033
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
3034
|
+
input += ch;
|
|
3035
|
+
process.stdout.write(masked ? "*" : ch);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
};
|
|
3039
|
+
stdin.on("data", onData);
|
|
1103
3040
|
});
|
|
1104
|
-
return {
|
|
1105
|
-
positional: positionals,
|
|
1106
|
-
printMode: values["p"] ?? false,
|
|
1107
|
-
continueMode: values["c"] ?? false,
|
|
1108
|
-
resumeId: values["r"],
|
|
1109
|
-
model: values["model"],
|
|
1110
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1111
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1112
|
-
version: values["version"] ?? false
|
|
1113
|
-
};
|
|
1114
3041
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
3042
|
+
async function ensureConfig(cwd) {
|
|
3043
|
+
const userPath = getUserSettingsPath();
|
|
3044
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
3045
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
3046
|
+
const paths = [userPath, projectPath, localPath];
|
|
3047
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
3048
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
3049
|
+
return;
|
|
1118
3050
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
3051
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
3052
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
3053
|
+
process.stdout.write("\n");
|
|
3054
|
+
if (corrupt.length > 0) {
|
|
3055
|
+
for (const c of corrupt) {
|
|
3056
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
3057
|
+
`);
|
|
3058
|
+
}
|
|
3059
|
+
process.stdout.write("\n");
|
|
1121
3060
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
3061
|
+
if (incomplete.length > 0) {
|
|
3062
|
+
for (const c of incomplete) {
|
|
3063
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
3064
|
+
`);
|
|
3065
|
+
}
|
|
3066
|
+
process.stdout.write("\n");
|
|
1124
3067
|
}
|
|
1125
|
-
|
|
1126
|
-
process.
|
|
3068
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
3069
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
3070
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
3071
|
+
} else {
|
|
3072
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
1127
3073
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
terminal: false,
|
|
1134
|
-
historySize: 0
|
|
1135
|
-
});
|
|
1136
|
-
rl.question(question, (answer) => {
|
|
1137
|
-
rl.close();
|
|
1138
|
-
resolve(answer);
|
|
1139
|
-
});
|
|
1140
|
-
});
|
|
3074
|
+
process.stdout.write("\n");
|
|
3075
|
+
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
3076
|
+
if (!apiKey) {
|
|
3077
|
+
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
3078
|
+
process.exit(1);
|
|
1141
3079
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
3080
|
+
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
3081
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
3082
|
+
(0, import_node_fs4.mkdirSync)(settingsDir, { recursive: true });
|
|
3083
|
+
const settings = {
|
|
3084
|
+
provider: {
|
|
3085
|
+
name: "anthropic",
|
|
3086
|
+
model: "claude-sonnet-4-6",
|
|
3087
|
+
apiKey
|
|
1147
3088
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1152
|
-
if (trimmed === "") return initialIndex;
|
|
1153
|
-
const num = parseInt(trimmed, 10);
|
|
1154
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1155
|
-
return initialIndex;
|
|
3089
|
+
};
|
|
3090
|
+
if (language) {
|
|
3091
|
+
settings.language = language;
|
|
1156
3092
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
3093
|
+
(0, import_node_fs4.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
3094
|
+
process.stdout.write(`
|
|
3095
|
+
Config saved to ${userPath}
|
|
3096
|
+
|
|
3097
|
+
`);
|
|
3098
|
+
}
|
|
3099
|
+
function resetConfig() {
|
|
3100
|
+
const userPath = getUserSettingsPath();
|
|
3101
|
+
if (deleteSettings(userPath)) {
|
|
3102
|
+
process.stdout.write(`Deleted ${userPath}
|
|
3103
|
+
`);
|
|
3104
|
+
} else {
|
|
3105
|
+
process.stdout.write("No user settings found.\n");
|
|
1161
3106
|
}
|
|
1162
|
-
}
|
|
3107
|
+
}
|
|
1163
3108
|
async function startCli() {
|
|
1164
3109
|
const args = parseCliArgs();
|
|
1165
3110
|
if (args.version) {
|
|
@@ -1167,16 +3112,24 @@ async function startCli() {
|
|
|
1167
3112
|
`);
|
|
1168
3113
|
return;
|
|
1169
3114
|
}
|
|
3115
|
+
if (args.reset) {
|
|
3116
|
+
resetConfig();
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
1170
3119
|
const cwd = process.cwd();
|
|
3120
|
+
await ensureConfig(cwd);
|
|
1171
3121
|
const [config, context, projectInfo] = await Promise.all([
|
|
1172
|
-
(0,
|
|
1173
|
-
(0,
|
|
1174
|
-
(0,
|
|
3122
|
+
(0, import_agent_sdk5.loadConfig)(cwd),
|
|
3123
|
+
(0, import_agent_sdk5.loadContext)(cwd),
|
|
3124
|
+
(0, import_agent_sdk5.detectProject)(cwd)
|
|
1175
3125
|
]);
|
|
1176
3126
|
if (args.model !== void 0) {
|
|
1177
3127
|
config.provider.model = args.model;
|
|
1178
3128
|
}
|
|
1179
|
-
|
|
3129
|
+
if (args.language !== void 0) {
|
|
3130
|
+
config.language = args.language;
|
|
3131
|
+
}
|
|
3132
|
+
const sessionStore = new import_agent_sdk5.SessionStore();
|
|
1180
3133
|
if (args.printMode) {
|
|
1181
3134
|
const prompt = args.positional.join(" ").trim();
|
|
1182
3135
|
if (prompt.length === 0) {
|
|
@@ -1184,15 +3137,15 @@ async function startCli() {
|
|
|
1184
3137
|
process.exit(1);
|
|
1185
3138
|
}
|
|
1186
3139
|
const terminal = new PrintTerminal();
|
|
1187
|
-
const paths = (0,
|
|
1188
|
-
const session = (0,
|
|
3140
|
+
const paths = (0, import_agent_sdk5.projectPaths)(cwd);
|
|
3141
|
+
const session = (0, import_agent_sdk5.createSession)({
|
|
1189
3142
|
config,
|
|
1190
3143
|
context,
|
|
1191
3144
|
terminal,
|
|
1192
|
-
sessionLogger: new
|
|
3145
|
+
sessionLogger: new import_agent_sdk5.FileSessionLogger(paths.logs),
|
|
1193
3146
|
projectInfo,
|
|
1194
3147
|
permissionMode: args.permissionMode,
|
|
1195
|
-
promptForApproval
|
|
3148
|
+
promptForApproval: import_agent_sdk6.promptForApproval
|
|
1196
3149
|
});
|
|
1197
3150
|
const response = await session.run(prompt);
|
|
1198
3151
|
process.stdout.write(response + "\n");
|
|
@@ -1210,6 +3163,16 @@ async function startCli() {
|
|
|
1210
3163
|
}
|
|
1211
3164
|
|
|
1212
3165
|
// src/bin.ts
|
|
3166
|
+
process.on("uncaughtException", (err) => {
|
|
3167
|
+
const msg = err.message ?? "";
|
|
3168
|
+
const isLikelyIME = msg.includes("string-width") || msg.includes("setCursorPosition") || msg.includes("getStringWidth") || msg.includes("slice") || msg.includes("charCodeAt");
|
|
3169
|
+
if (isLikelyIME) {
|
|
3170
|
+
process.stderr.write(`[robota] IME error suppressed: ${msg}
|
|
3171
|
+
`);
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
throw err;
|
|
3175
|
+
});
|
|
1213
3176
|
startCli().catch((err) => {
|
|
1214
3177
|
const message = err instanceof Error ? err.message : String(err);
|
|
1215
3178
|
process.stderr.write(message + "\n");
|