neoctl 0.1.7 → 0.1.9
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 +29 -1
- package/dist/core/query-engine.d.ts +2 -0
- package/dist/core/query-engine.js +20 -0
- package/dist/core/query-engine.js.map +1 -1
- package/dist/model/context-window.js +1 -0
- package/dist/model/context-window.js.map +1 -1
- package/dist/repl/commands.d.ts +8 -0
- package/dist/repl/commands.js +45 -0
- package/dist/repl/commands.js.map +1 -1
- package/dist/repl/index.js +229 -73
- package/dist/repl/index.js.map +1 -1
- package/dist/web/html.js +183 -39
- package/dist/web/html.js.map +1 -1
- package/dist/web/index.js +327 -38
- package/dist/web/index.js.map +1 -1
- package/package.json +4 -1
- package/scripts/build-standalone.mjs +139 -0
package/dist/web/index.js
CHANGED
|
@@ -197,6 +197,9 @@ class WebRepl {
|
|
|
197
197
|
status;
|
|
198
198
|
busy = false;
|
|
199
199
|
queuedInput;
|
|
200
|
+
foregroundRun;
|
|
201
|
+
backgroundSessionRuns = new Map();
|
|
202
|
+
suppressReattachedStreaming = new Set();
|
|
200
203
|
backgroundTaskCount;
|
|
201
204
|
constructor(runtime) {
|
|
202
205
|
this.runtime = runtime;
|
|
@@ -228,6 +231,9 @@ class WebRepl {
|
|
|
228
231
|
busy: this.busy,
|
|
229
232
|
queuedInput: this.queuedInput,
|
|
230
233
|
backgroundTaskCount: this.backgroundTaskCount,
|
|
234
|
+
backgroundTasks: this.backgroundTasks(),
|
|
235
|
+
backgroundSessionRunCount: this.backgroundSessionRuns.size,
|
|
236
|
+
runningSessionIds: [...this.backgroundSessionRuns.keys()],
|
|
231
237
|
session: this.runtime.engine.snapshot().session,
|
|
232
238
|
catalog: includeCatalog ? webCatalog(this.runtime) : undefined,
|
|
233
239
|
interactive: includeCatalog ? webInteractiveCatalog(this.runtime) : undefined,
|
|
@@ -239,39 +245,65 @@ class WebRepl {
|
|
|
239
245
|
const trimmed = text.trim();
|
|
240
246
|
if (!trimmed && attachments.length === 0)
|
|
241
247
|
return { ok: true };
|
|
242
|
-
|
|
248
|
+
const command = parseReplCommand(text);
|
|
249
|
+
const startsNewSessionWhileBusy = this.busy && command.type === "new";
|
|
250
|
+
if (this.busy && !startsNewSessionWhileBusy) {
|
|
243
251
|
if (this.queuedInput !== undefined)
|
|
244
252
|
return { ok: false, error: "A queued prompt is already waiting. Press Esc/Ctrl+C in the web UI to clear it." };
|
|
245
253
|
this.queuedInput = text;
|
|
246
254
|
this.broadcastSync();
|
|
247
255
|
return { ok: true };
|
|
248
256
|
}
|
|
249
|
-
|
|
257
|
+
const run = this.handleCommandOrPrompt(text, attachments).catch((error) => {
|
|
250
258
|
this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
251
259
|
this.setBusy(false);
|
|
252
260
|
this.setStatus({ ...this.status, phase: "ready", detail: undefined });
|
|
253
261
|
});
|
|
262
|
+
this.foregroundRun = run;
|
|
263
|
+
run.finally(() => {
|
|
264
|
+
if (this.foregroundRun === run)
|
|
265
|
+
this.foregroundRun = undefined;
|
|
266
|
+
}).catch(() => undefined);
|
|
254
267
|
return { ok: true };
|
|
255
268
|
}
|
|
256
269
|
async listSessions() {
|
|
257
|
-
|
|
270
|
+
const sessions = await this.runtime.engine.listSessions(Number.POSITIVE_INFINITY);
|
|
271
|
+
const runningSessionIds = [...this.backgroundSessionRuns.keys()];
|
|
272
|
+
return { sessions, runningSessionIds };
|
|
258
273
|
}
|
|
259
274
|
async resumeSession(sessionId) {
|
|
260
275
|
if (!sessionId)
|
|
261
276
|
return { ok: false, error: "sessionId is required" };
|
|
262
277
|
try {
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this.
|
|
269
|
-
this.
|
|
270
|
-
this.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
this.
|
|
278
|
+
const running = this.backgroundSessionRuns.get(sessionId);
|
|
279
|
+
if (running) {
|
|
280
|
+
await this.reattachRunningSession(running);
|
|
281
|
+
return { ok: true };
|
|
282
|
+
}
|
|
283
|
+
await this.detachRunningForeground("session switch");
|
|
284
|
+
this.runtime.engine = this.runtime.engine.forkForSession(sessionId, true);
|
|
285
|
+
await this.runtime.engine.initialize();
|
|
286
|
+
const snapshot = this.runtime.engine.snapshot().session;
|
|
287
|
+
if (!snapshot)
|
|
288
|
+
throw new Error("session transcripts are disabled");
|
|
289
|
+
await this.refreshSessionView(systemLine(formatResume(snapshot)));
|
|
290
|
+
return { ok: true };
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
294
|
+
this.append({ kind: "error", text: message });
|
|
295
|
+
return { ok: false, error: message };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async newSession() {
|
|
299
|
+
try {
|
|
300
|
+
await this.detachRunningForeground("new session");
|
|
301
|
+
this.runtime.engine = this.runtime.engine.forkForSession(undefined, false);
|
|
302
|
+
await this.runtime.engine.initialize();
|
|
303
|
+
const snapshot = this.runtime.engine.snapshot().session;
|
|
304
|
+
if (!snapshot)
|
|
305
|
+
throw new Error("session transcripts are disabled");
|
|
306
|
+
await this.refreshSessionView(systemLine(`new session ${snapshot.sessionId}`));
|
|
275
307
|
return { ok: true };
|
|
276
308
|
}
|
|
277
309
|
catch (error) {
|
|
@@ -280,6 +312,21 @@ class WebRepl {
|
|
|
280
312
|
return { ok: false, error: message };
|
|
281
313
|
}
|
|
282
314
|
}
|
|
315
|
+
async refreshSessionView(line) {
|
|
316
|
+
const metrics = await this.runtime.engine.contextMetrics();
|
|
317
|
+
this.runtime.usage.reset();
|
|
318
|
+
this.status = initialStatus(this.runtime, metrics);
|
|
319
|
+
const lineId = { current: 0 };
|
|
320
|
+
this.lines = initialLines(this.runtime, lineId);
|
|
321
|
+
this.lineId = lineId.current;
|
|
322
|
+
this.assistantLineId = undefined;
|
|
323
|
+
this.thinkingLineId = undefined;
|
|
324
|
+
this.finalizedThinkingLineId = undefined;
|
|
325
|
+
this.toolLineIds.clear();
|
|
326
|
+
if (line)
|
|
327
|
+
this.append(line);
|
|
328
|
+
this.broadcastSync();
|
|
329
|
+
}
|
|
283
330
|
async deleteSession(sessionId) {
|
|
284
331
|
if (!sessionId)
|
|
285
332
|
return { ok: false, error: "sessionId is required" };
|
|
@@ -370,6 +417,61 @@ class WebRepl {
|
|
|
370
417
|
this.status = next;
|
|
371
418
|
this.broadcastSync();
|
|
372
419
|
}
|
|
420
|
+
backgroundTasks() {
|
|
421
|
+
return this.runtime.taskStore.list()
|
|
422
|
+
.filter((task) => !this.runtime.taskStore.isTerminal(task))
|
|
423
|
+
.map((task) => ({
|
|
424
|
+
taskId: task.taskId,
|
|
425
|
+
agentId: task.agentId,
|
|
426
|
+
type: task.type,
|
|
427
|
+
status: task.status,
|
|
428
|
+
description: task.description,
|
|
429
|
+
createdAt: task.createdAt,
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
async detachRunningForeground(reason) {
|
|
433
|
+
if (!this.busy)
|
|
434
|
+
return false;
|
|
435
|
+
const snapshot = this.runtime.engine.snapshot().session;
|
|
436
|
+
const sessionId = snapshot?.sessionId ?? `session-${Date.now().toString(36)}`;
|
|
437
|
+
const run = this.foregroundRun;
|
|
438
|
+
if (run && !this.backgroundSessionRuns.has(sessionId)) {
|
|
439
|
+
const backgroundRun = {
|
|
440
|
+
sessionId,
|
|
441
|
+
title: snapshot?.title,
|
|
442
|
+
reason,
|
|
443
|
+
startedAt: Date.now(),
|
|
444
|
+
engine: this.runtime.engine,
|
|
445
|
+
abortController: this.activeAbortController ?? new AbortController(),
|
|
446
|
+
promise: run,
|
|
447
|
+
};
|
|
448
|
+
this.backgroundSessionRuns.set(sessionId, backgroundRun);
|
|
449
|
+
run.finally(() => {
|
|
450
|
+
this.backgroundSessionRuns.delete(sessionId);
|
|
451
|
+
this.suppressReattachedStreaming.delete(backgroundRun.engine);
|
|
452
|
+
this.broadcastSync();
|
|
453
|
+
}).catch(() => undefined);
|
|
454
|
+
}
|
|
455
|
+
this.activeAbortController = undefined;
|
|
456
|
+
this.interruptArmed = false;
|
|
457
|
+
this.queuedInput = undefined;
|
|
458
|
+
this.busy = false;
|
|
459
|
+
this.status = { ...this.status, phase: "ready", detail: undefined };
|
|
460
|
+
this.append(systemLine(`Detached running ${sessionId} to background for ${reason}.`));
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
async reattachRunningSession(run) {
|
|
464
|
+
await this.detachRunningForeground("session switch");
|
|
465
|
+
this.backgroundSessionRuns.delete(run.sessionId);
|
|
466
|
+
this.runtime.engine = run.engine;
|
|
467
|
+
this.activeAbortController = run.abortController;
|
|
468
|
+
this.interruptArmed = false;
|
|
469
|
+
this.foregroundRun = run.promise;
|
|
470
|
+
this.suppressReattachedStreaming.add(run.engine);
|
|
471
|
+
await this.refreshSessionView(systemLine(`reattached running session ${run.sessionId}`));
|
|
472
|
+
this.setBusy(true);
|
|
473
|
+
this.setStatus({ ...this.status, phase: "running", detail: "working" });
|
|
474
|
+
}
|
|
373
475
|
reduce(event) {
|
|
374
476
|
this.status = reduceStatus(this.status, event);
|
|
375
477
|
if (event.type === "usage")
|
|
@@ -496,12 +598,17 @@ class WebRepl {
|
|
|
496
598
|
if (command.type === "reset") {
|
|
497
599
|
this.runtime.engine.reset();
|
|
498
600
|
this.runtime.usage.reset();
|
|
499
|
-
this.status = resetStatus(this.runtime);
|
|
601
|
+
this.status = await resetStatus(this.runtime);
|
|
500
602
|
this.append(systemLine("transcript reset"));
|
|
501
603
|
return;
|
|
502
604
|
}
|
|
503
605
|
if (command.type === "state") {
|
|
504
|
-
|
|
606
|
+
const contextMetrics = await this.runtime.engine.contextMetrics();
|
|
607
|
+
this.append(systemLine(formatReplData({ ...this.runtime.engine.snapshot(), contextMetrics, communicationLog: this.runtime.communicationLogger.snapshot() }, 12000), EXPANDED_SUMMARY_MAX_LINES));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (command.type === "new") {
|
|
611
|
+
await this.newSession();
|
|
505
612
|
return;
|
|
506
613
|
}
|
|
507
614
|
if (command.type === "sessions") {
|
|
@@ -576,20 +683,35 @@ class WebRepl {
|
|
|
576
683
|
this.interruptArmed = false;
|
|
577
684
|
this.setBusy(true);
|
|
578
685
|
this.setStatus({ ...this.status, phase: "running", detail: "working", usage: undefined, streamedOutputTokens: 0, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
|
|
686
|
+
const engine = this.runtime.engine;
|
|
579
687
|
try {
|
|
580
|
-
for await (const event of
|
|
688
|
+
for await (const event of engine.sendUserText(promptPayload.text, { abortSignal: abortController.signal, blocks: promptPayload.blocks, displayText: promptPayload.displayText })) {
|
|
689
|
+
if (this.runtime.engine !== engine)
|
|
690
|
+
continue;
|
|
691
|
+
if (this.suppressReattachedStreaming.has(engine)) {
|
|
692
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage") {
|
|
693
|
+
if (event.type === "message" || event.type === "terminal" || event.type === "error")
|
|
694
|
+
this.suppressReattachedStreaming.delete(engine);
|
|
695
|
+
this.handleEvent(event);
|
|
696
|
+
}
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
581
699
|
this.handleEvent(event);
|
|
582
700
|
}
|
|
583
701
|
}
|
|
584
702
|
catch (error) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
703
|
+
if (this.runtime.engine === engine) {
|
|
704
|
+
this.finalizeLiveLine(this.assistantLineId);
|
|
705
|
+
this.finalizeThinkingLine();
|
|
706
|
+
this.finalizeActiveToolLines();
|
|
707
|
+
this.assistantLineId = undefined;
|
|
708
|
+
this.finalizedThinkingLineId = undefined;
|
|
709
|
+
this.append({ kind: "error", text: error instanceof Error ? error.message : String(error) });
|
|
710
|
+
}
|
|
591
711
|
}
|
|
592
712
|
finally {
|
|
713
|
+
if (this.runtime.engine !== engine)
|
|
714
|
+
return;
|
|
593
715
|
if (this.activeAbortController === abortController)
|
|
594
716
|
this.activeAbortController = undefined;
|
|
595
717
|
this.interruptArmed = false;
|
|
@@ -673,11 +795,13 @@ async function route(req, res, repl) {
|
|
|
673
795
|
if (req.method === "POST" && url.pathname === "/api/interrupt")
|
|
674
796
|
return sendJson(res, repl.interrupt());
|
|
675
797
|
if (req.method === "GET" && url.pathname === "/api/sessions")
|
|
676
|
-
return sendJson(res,
|
|
798
|
+
return sendJson(res, await repl.listSessions());
|
|
677
799
|
if (req.method === "POST" && url.pathname === "/api/sessions/resume") {
|
|
678
800
|
const body = await readJsonBody(req);
|
|
679
801
|
return sendJson(res, await repl.resumeSession(String(body.sessionId ?? "")));
|
|
680
802
|
}
|
|
803
|
+
if (req.method === "POST" && url.pathname === "/api/sessions/new")
|
|
804
|
+
return sendJson(res, await repl.newSession());
|
|
681
805
|
if (req.method === "POST" && url.pathname === "/api/sessions/delete") {
|
|
682
806
|
const body = await readJsonBody(req);
|
|
683
807
|
return sendJson(res, await repl.deleteSession(String(body.sessionId ?? "")));
|
|
@@ -765,8 +889,8 @@ function restoredHistoryLines(runtime) {
|
|
|
765
889
|
function initialStatus(runtime, metrics = runtime.initialMetrics) {
|
|
766
890
|
return { phase: "ready", metrics: { ...metrics, messageCount: runtime.engine.snapshot().messages }, streamedOutputTokens: 0, activityTick: 0 };
|
|
767
891
|
}
|
|
768
|
-
function resetStatus(runtime) {
|
|
769
|
-
return initialStatus(runtime,
|
|
892
|
+
async function resetStatus(runtime) {
|
|
893
|
+
return initialStatus(runtime, await runtime.engine.contextMetrics());
|
|
770
894
|
}
|
|
771
895
|
function buildWebPromptPayload(displayText, attachments) {
|
|
772
896
|
const activeAttachments = attachments.filter((attachment) => displayText.includes(attachment.label));
|
|
@@ -1339,26 +1463,190 @@ function formatPlanToolPayload(payload) {
|
|
|
1339
1463
|
return sections.filter(Boolean).join("\n");
|
|
1340
1464
|
}
|
|
1341
1465
|
function formatToolResult(toolName, output, ok) {
|
|
1342
|
-
if (
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
return { text:
|
|
1352
|
-
|
|
1466
|
+
if ((toolName === "edit" || toolName === "write") && isRecord(output) && isEditToolOutput(output))
|
|
1467
|
+
return { text: formatEditToolDiff(output, ok), format: "diff", summaryMaxLines: EDIT_TOOL_SUMMARY_MAX_LINES };
|
|
1468
|
+
if (isExecOutput(output))
|
|
1469
|
+
return { text: formatExecToolResult(output, ok), format: "plain", summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
1470
|
+
if (toolName === "list" && isRecord(output))
|
|
1471
|
+
return { text: formatListToolResult(output, ok) };
|
|
1472
|
+
if (toolName === "read" && isRecord(output))
|
|
1473
|
+
return { text: formatReadToolResult(output, ok) };
|
|
1474
|
+
if (toolName === "grep" && isRecord(output))
|
|
1475
|
+
return { text: formatGrepToolResult(output, ok) };
|
|
1476
|
+
if (toolName === "search" && isRecord(output))
|
|
1477
|
+
return { text: formatWebSearchToolResult(output, ok), summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
1353
1478
|
if (toolName === "plan" && isPlanToolPayload(output))
|
|
1354
1479
|
return { text: formatPlanToolPayload(output), full: true, bodyTitle: planToolBodyTitle(output) };
|
|
1355
1480
|
if (typeof output === "string")
|
|
1356
1481
|
return { text: output, format: hasAnsi(output) ? "ansi" : undefined, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
1357
1482
|
return { text: `${ok ? "ok" : "failed"}\n${formatReplData(output, 6000)}`, summaryMaxLines: EXPANDED_SUMMARY_MAX_LINES };
|
|
1358
1483
|
}
|
|
1484
|
+
function isEditToolOutput(value) {
|
|
1485
|
+
return typeof value.path === "string" && typeof value.operation === "string" && typeof value.replacements === "number" && Array.isArray(value.patch) && value.patch.every(isEditPatchHunk);
|
|
1486
|
+
}
|
|
1487
|
+
function isEditPatchHunk(value) {
|
|
1488
|
+
return isRecord(value) && typeof value.oldStart === "number" && typeof value.oldLines === "number" && typeof value.newStart === "number" && typeof value.newLines === "number" && Array.isArray(value.lines) && value.lines.every((line) => typeof line === "string");
|
|
1489
|
+
}
|
|
1490
|
+
function formatEditToolDiff(output, ok) {
|
|
1491
|
+
const lines = [
|
|
1492
|
+
`${ok ? output.operation : "failed"} ${output.path}, ${output.replacements} replacement(s)`,
|
|
1493
|
+
`--- ${output.path}`,
|
|
1494
|
+
`+++ ${output.path}`,
|
|
1495
|
+
];
|
|
1496
|
+
for (const hunk of output.patch) {
|
|
1497
|
+
lines.push(`@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`);
|
|
1498
|
+
lines.push(...formatEditPatchHunkLines(hunk));
|
|
1499
|
+
}
|
|
1500
|
+
if (output.patch.length === 0)
|
|
1501
|
+
lines.push("no changes");
|
|
1502
|
+
return lines.join("\n");
|
|
1503
|
+
}
|
|
1504
|
+
function formatEditPatchHunkLines(hunk) {
|
|
1505
|
+
const oldLineWidth = diffLineNumberWidth(hunk.oldStart, hunk.oldLines);
|
|
1506
|
+
const newLineWidth = diffLineNumberWidth(hunk.newStart, hunk.newLines);
|
|
1507
|
+
let oldLineNumber = hunk.oldStart;
|
|
1508
|
+
let newLineNumber = hunk.newStart;
|
|
1509
|
+
return hunk.lines.map((rawLine) => {
|
|
1510
|
+
const marker = diffLineMarker(rawLine);
|
|
1511
|
+
if (!marker)
|
|
1512
|
+
return rawLine;
|
|
1513
|
+
const showOldLineNumber = marker !== "+";
|
|
1514
|
+
const showNewLineNumber = marker !== "-";
|
|
1515
|
+
const oldLineLabel = showOldLineNumber ? String(oldLineNumber).padStart(oldLineWidth) : " ".repeat(oldLineWidth);
|
|
1516
|
+
const newLineLabel = showNewLineNumber ? String(newLineNumber).padStart(newLineWidth) : " ".repeat(newLineWidth);
|
|
1517
|
+
if (showOldLineNumber)
|
|
1518
|
+
oldLineNumber += 1;
|
|
1519
|
+
if (showNewLineNumber)
|
|
1520
|
+
newLineNumber += 1;
|
|
1521
|
+
return `${oldLineLabel} ${newLineLabel} │ ${marker}${rawLine.slice(1)}`;
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
function diffLineNumberWidth(start, lineCount) {
|
|
1525
|
+
const end = lineCount > 0 ? start + lineCount - 1 : start;
|
|
1526
|
+
return Math.max(String(start).length, String(end).length, 2);
|
|
1527
|
+
}
|
|
1528
|
+
function diffLineMarker(line) {
|
|
1529
|
+
const marker = line[0];
|
|
1530
|
+
return marker === "+" || marker === "-" || marker === " " ? marker : undefined;
|
|
1531
|
+
}
|
|
1359
1532
|
function isExecOutput(value) {
|
|
1360
1533
|
return isRecord(value) && typeof value.command === "string" && typeof value.durationMs === "number";
|
|
1361
1534
|
}
|
|
1535
|
+
function formatExecToolResult(output, ok) {
|
|
1536
|
+
const status = output.timedOut ? "timed out" : output.exitCode === 0 ? "exit 0" : `exit ${output.exitCode ?? output.signal ?? "unknown"}`;
|
|
1537
|
+
const lines = ["exec result", `status: ${status}`, `duration: ${output.durationMs}ms`, `command: ${output.command}`];
|
|
1538
|
+
const stdout = typeof output.stdout === "string" ? output.stdout.replace(/\s+$/u, "") : "";
|
|
1539
|
+
const stderr = typeof output.stderr === "string" ? output.stderr.replace(/\s+$/u, "") : "";
|
|
1540
|
+
if (stdout)
|
|
1541
|
+
lines.push("stdout:", stdout);
|
|
1542
|
+
if (stderr)
|
|
1543
|
+
lines.push("stderr:", stderr);
|
|
1544
|
+
if (!stdout && !stderr)
|
|
1545
|
+
lines.push(ok ? "output: (none)" : "output: (not captured)");
|
|
1546
|
+
return lines.join("\n");
|
|
1547
|
+
}
|
|
1548
|
+
function formatListToolResult(output, ok) {
|
|
1549
|
+
const pathValue = typeof output.path === "string" ? output.path : "";
|
|
1550
|
+
const typeValue = typeof output.type === "string" ? output.type : "result";
|
|
1551
|
+
const returnedEntries = typeof output.returnedEntries === "number" ? output.returnedEntries : undefined;
|
|
1552
|
+
const totalFiles = typeof output.totalFiles === "number" ? output.totalFiles : undefined;
|
|
1553
|
+
const totalDirectories = typeof output.totalDirectories === "number" ? output.totalDirectories : undefined;
|
|
1554
|
+
const entries = Array.isArray(output.entries) ? output.entries : [];
|
|
1555
|
+
const names = entries.map((entry) => (isRecord(entry) && typeof entry.name === "string" ? entry.name : undefined)).filter((name) => Boolean(name)).slice(0, 5);
|
|
1556
|
+
const lines = [ok ? "list result" : "failed"];
|
|
1557
|
+
if (pathValue)
|
|
1558
|
+
lines.push(`path: ${pathValue}`);
|
|
1559
|
+
lines.push(`type: ${typeValue}`);
|
|
1560
|
+
const counts = [returnedEntries !== undefined ? `${returnedEntries} shown` : undefined, totalFiles !== undefined ? `${totalFiles} files` : undefined, totalDirectories !== undefined ? `${totalDirectories} dirs` : undefined].filter((value) => Boolean(value));
|
|
1561
|
+
if (counts.length > 0)
|
|
1562
|
+
lines.push(`entries: ${counts.join(" · ")}`);
|
|
1563
|
+
if (names.length > 0)
|
|
1564
|
+
lines.push("sample:", ...names.map((name) => ` ${name}`));
|
|
1565
|
+
return lines.join("\n");
|
|
1566
|
+
}
|
|
1567
|
+
function formatReadToolResult(output, ok) {
|
|
1568
|
+
const error = typeof output.error === "string" ? output.error : undefined;
|
|
1569
|
+
if (!ok || error)
|
|
1570
|
+
return ["failed", error ?? formatReplData(output, 1200)].join("\n");
|
|
1571
|
+
const pathValue = typeof output.path === "string" ? output.path : undefined;
|
|
1572
|
+
const startLine = typeof output.startLine === "number" ? output.startLine : undefined;
|
|
1573
|
+
const endLine = typeof output.endLine === "number" ? output.endLine : undefined;
|
|
1574
|
+
const totalLines = typeof output.totalLines === "number" ? output.totalLines : undefined;
|
|
1575
|
+
const hasMoreBefore = output.hasMoreBefore === true;
|
|
1576
|
+
const hasMoreAfter = output.hasMoreAfter === true;
|
|
1577
|
+
const content = typeof output.content === "string" ? output.content.trimEnd() : "";
|
|
1578
|
+
const lines = ["read result"];
|
|
1579
|
+
if (pathValue)
|
|
1580
|
+
lines.push(`file: ${pathValue}`);
|
|
1581
|
+
if (startLine !== undefined && endLine !== undefined && totalLines !== undefined) {
|
|
1582
|
+
const more = [hasMoreBefore ? "more before" : undefined, hasMoreAfter ? "more after" : undefined].filter((value) => Boolean(value)).join(", ");
|
|
1583
|
+
lines.push(`range: lines ${startLine}-${endLine} of ${totalLines}${more ? ` (${more})` : ""}`);
|
|
1584
|
+
}
|
|
1585
|
+
lines.push("content:", content || "(empty range)");
|
|
1586
|
+
return lines.join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
function formatWebSearchToolResult(output, ok) {
|
|
1589
|
+
const error = typeof output.error === "string" ? output.error : undefined;
|
|
1590
|
+
if (!ok || error)
|
|
1591
|
+
return ["failed", error ?? formatReplData(output, 1200)].join("\n");
|
|
1592
|
+
const provider = typeof output.provider === "string" ? output.provider : "unknown";
|
|
1593
|
+
const query = typeof output.query === "string" ? output.query : "";
|
|
1594
|
+
const returnedResults = typeof output.returnedResults === "number" ? output.returnedResults : undefined;
|
|
1595
|
+
const results = Array.isArray(output.results) ? output.results : [];
|
|
1596
|
+
const lines = [`${returnedResults ?? results.length} web result(s) via ${provider}`];
|
|
1597
|
+
if (query)
|
|
1598
|
+
lines.push(`query: ${query}`);
|
|
1599
|
+
if (output.truncated === true)
|
|
1600
|
+
lines.push("truncated");
|
|
1601
|
+
if (results.length === 0)
|
|
1602
|
+
return [...lines, "no results"].join("\n");
|
|
1603
|
+
results.slice(0, 8).forEach((item, index) => {
|
|
1604
|
+
if (!isRecord(item))
|
|
1605
|
+
return;
|
|
1606
|
+
const title = typeof item.title === "string" && item.title.trim() ? item.title.trim() : "Untitled";
|
|
1607
|
+
const url = typeof item.url === "string" ? item.url : "";
|
|
1608
|
+
const published = typeof item.published === "string" ? ` · ${item.published}` : "";
|
|
1609
|
+
lines.push(`[${index + 1}] ${title}${published}`);
|
|
1610
|
+
if (url)
|
|
1611
|
+
lines.push(url);
|
|
1612
|
+
const highlights = Array.isArray(item.highlights) ? item.highlights.filter((value) => typeof value === "string" && value.trim().length > 0) : [];
|
|
1613
|
+
const snippet = highlights[0] ?? (typeof item.text === "string" ? item.text : undefined);
|
|
1614
|
+
if (snippet)
|
|
1615
|
+
lines.push(truncate(snippet.replace(/\s+/gu, " "), 400));
|
|
1616
|
+
});
|
|
1617
|
+
return lines.join("\n");
|
|
1618
|
+
}
|
|
1619
|
+
function formatGrepToolResult(output, ok) {
|
|
1620
|
+
const error = typeof output.error === "string" ? output.error : undefined;
|
|
1621
|
+
if (!ok || error)
|
|
1622
|
+
return ["failed", error ?? formatReplData(output, 1200)].join("\n");
|
|
1623
|
+
const query = typeof output.query === "string" ? output.query : undefined;
|
|
1624
|
+
const grepPath = typeof output.grepPath === "string" ? output.grepPath : typeof output.path === "string" ? output.path : undefined;
|
|
1625
|
+
const returnedMatches = typeof output.returnedMatches === "number" ? output.returnedMatches : undefined;
|
|
1626
|
+
const totalMatchesKnown = typeof output.totalMatchesKnown === "number" ? output.totalMatchesKnown : undefined;
|
|
1627
|
+
const truncated = output.truncated === true;
|
|
1628
|
+
const matches = Array.isArray(output.matches) ? output.matches.filter(isGrepMatchLike) : [];
|
|
1629
|
+
const lines = ["grep result"];
|
|
1630
|
+
if (query !== undefined)
|
|
1631
|
+
lines.push(`query: ${query}`);
|
|
1632
|
+
if (grepPath !== undefined)
|
|
1633
|
+
lines.push(`path: ${grepPath}`);
|
|
1634
|
+
const countParts = [`${returnedMatches ?? matches.length} shown`, totalMatchesKnown !== undefined ? `${totalMatchesKnown} known` : undefined, truncated ? "truncated" : undefined].filter((value) => Boolean(value));
|
|
1635
|
+
lines.push(`matches: ${countParts.join(" · ")}`);
|
|
1636
|
+
if (matches.length === 0)
|
|
1637
|
+
return [...lines, "no matches"].join("\n");
|
|
1638
|
+
lines.push("results:");
|
|
1639
|
+
for (const match of matches)
|
|
1640
|
+
lines.push(formatGrepMatchLine(match));
|
|
1641
|
+
return lines.join("\n");
|
|
1642
|
+
}
|
|
1643
|
+
function isGrepMatchLike(value) {
|
|
1644
|
+
return isRecord(value) && typeof value.file === "string" && typeof value.line === "number" && typeof value.text === "string" && (value.column === undefined || typeof value.column === "number");
|
|
1645
|
+
}
|
|
1646
|
+
function formatGrepMatchLine(match) {
|
|
1647
|
+
const column = match.column !== undefined ? `:${match.column}` : "";
|
|
1648
|
+
return ` ${match.file}:${match.line}${column}: ${match.text}`;
|
|
1649
|
+
}
|
|
1362
1650
|
function formatReplData(value, maxLength) {
|
|
1363
1651
|
return truncate(formatReplValue(value), maxLength);
|
|
1364
1652
|
}
|
|
@@ -1456,6 +1744,7 @@ function formatNumber(value) {
|
|
|
1456
1744
|
}
|
|
1457
1745
|
const THINKING_SUMMARY_MAX_LINES = 1000;
|
|
1458
1746
|
const EXPANDED_SUMMARY_MAX_LINES = 1000;
|
|
1747
|
+
const EDIT_TOOL_SUMMARY_MAX_LINES = EXPANDED_SUMMARY_MAX_LINES;
|
|
1459
1748
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
1460
1749
|
runWebServer().catch((error) => {
|
|
1461
1750
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|