@botbotgo/agent-harness 0.0.294 → 0.0.296
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 +27 -0
- package/README.zh.md +27 -0
- package/dist/acp.d.ts +28 -3
- package/dist/acp.js +100 -7
- package/dist/api.d.ts +2 -2
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +923 -12
- package/dist/client/acp.d.ts +44 -0
- package/dist/client/acp.js +165 -0
- package/dist/client/in-process.d.ts +44 -0
- package/dist/client/in-process.js +69 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/types.d.ts +56 -0
- package/dist/client/types.js +1 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +1 -0
- package/dist/config/agents/orchestra.yaml +16 -3
- package/dist/index.d.ts +4 -2
- package/dist/index.js +1 -0
- package/dist/init-project.js +89 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/protocol/acp/client.d.ts +8 -2
- package/dist/protocol/acp/client.js +143 -0
- package/dist/resource/resource-impl.js +21 -4
- package/dist/resources/package.json +6 -0
- package/dist/resources/skills/approval-execution-policy/SKILL.md +22 -0
- package/dist/resources/skills/completion-discipline/SKILL.md +22 -0
- package/dist/resources/skills/delegation-discipline/SKILL.md +22 -0
- package/dist/resources/skills/safe-editing/SKILL.md +22 -0
- package/dist/resources/skills/workspace-inspection/SKILL.md +22 -0
- package/dist/runtime/adapter/runtime-adapter-support.d.ts +4 -2
- package/dist/runtime/adapter/runtime-adapter-support.js +10 -0
- package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +74 -0
- package/dist/runtime/adapter/tool/builtin-middleware-tools.js +192 -1
- package/dist/runtime/agent-runtime-adapter.js +10 -0
- package/package.json +12 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { createInterface as createReadlineInterface } from "node:readline";
|
|
5
|
+
import { Writable } from "node:stream";
|
|
2
6
|
import path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import YAML from "yaml";
|
|
4
9
|
import { createAgentHarness } from "./api.js";
|
|
10
|
+
import { createAcpHttpHarnessClient, createAcpStdioHarnessClient } from "./client.js";
|
|
5
11
|
import { initProject } from "./init-project.js";
|
|
6
12
|
import { serveA2aOverHttp } from "./protocol/a2a/http.js";
|
|
7
13
|
import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
|
|
@@ -11,6 +17,7 @@ import { serveRuntimeMcpOverStdio } from "./mcp.js";
|
|
|
11
17
|
function renderUsage() {
|
|
12
18
|
return `Usage:
|
|
13
19
|
agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
|
|
20
|
+
agent-harness chat [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>] [--agent <agentId>] [--session <sessionId>] [--message <text>]
|
|
14
21
|
agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
|
|
15
22
|
agent-harness a2a serve [--workspace <path>] [--host <hostname>] [--port <port>]
|
|
16
23
|
agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
|
|
@@ -159,6 +166,56 @@ function parseHttpServeOptions(args, serviceLabel = "HTTP") {
|
|
|
159
166
|
}
|
|
160
167
|
return { workspaceRoot, hostname, port };
|
|
161
168
|
}
|
|
169
|
+
function parseChatOptions(args) {
|
|
170
|
+
let workspaceRoot;
|
|
171
|
+
let agentId;
|
|
172
|
+
let sessionId;
|
|
173
|
+
let message;
|
|
174
|
+
let transport = "stdio";
|
|
175
|
+
let hostname;
|
|
176
|
+
let port;
|
|
177
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
178
|
+
const arg = args[index];
|
|
179
|
+
if (arg === "--workspace" || arg === "--agent" || arg === "--session" || arg === "--message" || arg === "--transport" || arg === "--host" || arg === "--port") {
|
|
180
|
+
const value = args[index + 1];
|
|
181
|
+
if (!value) {
|
|
182
|
+
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Missing value for ${arg}` };
|
|
183
|
+
}
|
|
184
|
+
if (arg === "--workspace") {
|
|
185
|
+
workspaceRoot = value;
|
|
186
|
+
}
|
|
187
|
+
else if (arg === "--agent") {
|
|
188
|
+
agentId = value;
|
|
189
|
+
}
|
|
190
|
+
else if (arg === "--session") {
|
|
191
|
+
sessionId = value;
|
|
192
|
+
}
|
|
193
|
+
else if (arg === "--message") {
|
|
194
|
+
message = value;
|
|
195
|
+
}
|
|
196
|
+
else if (arg === "--transport") {
|
|
197
|
+
if (value !== "stdio" && value !== "http") {
|
|
198
|
+
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Unsupported chat transport: ${value}` };
|
|
199
|
+
}
|
|
200
|
+
transport = value;
|
|
201
|
+
}
|
|
202
|
+
else if (arg === "--host") {
|
|
203
|
+
hostname = value;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
207
|
+
if (!Number.isFinite(parsedPort) || parsedPort <= 0) {
|
|
208
|
+
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Invalid chat port: ${value}` };
|
|
209
|
+
}
|
|
210
|
+
port = parsedPort;
|
|
211
|
+
}
|
|
212
|
+
index += 1;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Unknown option: ${arg}` };
|
|
216
|
+
}
|
|
217
|
+
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port };
|
|
218
|
+
}
|
|
162
219
|
function parseRuntimeInspectOptions(args) {
|
|
163
220
|
let workspaceRoot;
|
|
164
221
|
let json = false;
|
|
@@ -294,6 +351,25 @@ function parseRuntimeExportOptions(args) {
|
|
|
294
351
|
json,
|
|
295
352
|
};
|
|
296
353
|
}
|
|
354
|
+
function resolveCliWorkspaceRoot(cwd, inputPath) {
|
|
355
|
+
const resolved = path.resolve(cwd, inputPath ?? ".");
|
|
356
|
+
const configRuntimePath = path.join(resolved, "config", "runtime", "workspace.yaml");
|
|
357
|
+
if (existsSync(configRuntimePath)) {
|
|
358
|
+
return resolved;
|
|
359
|
+
}
|
|
360
|
+
const directRuntimePath = path.join(resolved, "runtime", "workspace.yaml");
|
|
361
|
+
if (existsSync(directRuntimePath) && path.basename(resolved) === "config") {
|
|
362
|
+
return path.dirname(resolved);
|
|
363
|
+
}
|
|
364
|
+
return resolved;
|
|
365
|
+
}
|
|
366
|
+
function resolveCliConfigRoot(workspaceRoot) {
|
|
367
|
+
const nested = path.join(workspaceRoot, "config");
|
|
368
|
+
if (existsSync(path.join(nested, "runtime", "workspace.yaml"))) {
|
|
369
|
+
return nested;
|
|
370
|
+
}
|
|
371
|
+
return workspaceRoot;
|
|
372
|
+
}
|
|
297
373
|
function renderJson(value) {
|
|
298
374
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
299
375
|
}
|
|
@@ -423,11 +499,584 @@ function renderOperatorOverview(overview, workspacePath) {
|
|
|
423
499
|
}
|
|
424
500
|
return `${lines.join("\n")}\n`;
|
|
425
501
|
}
|
|
502
|
+
function renderChatHelp() {
|
|
503
|
+
const rows = [
|
|
504
|
+
["/context", "Show the current agent/session/request context"],
|
|
505
|
+
["/new", "Clear the current session/request context"],
|
|
506
|
+
["/help", "Show chat commands"],
|
|
507
|
+
["/agent <agentId>", "Switch the active agent for new requests"],
|
|
508
|
+
["/session", "Show current session id"],
|
|
509
|
+
["/request [requestId]", "Show or switch the active request id"],
|
|
510
|
+
["/sessions", "List recent sessions"],
|
|
511
|
+
["/requests", "List requests for the current session or agent"],
|
|
512
|
+
["/resume <sessionId>", "Switch chat to an existing session"],
|
|
513
|
+
["/cancel", "Cancel the latest active request"],
|
|
514
|
+
["/approvals", "List pending approvals for the current session"],
|
|
515
|
+
["/approve <id>", "Approve a pending approval"],
|
|
516
|
+
["/reject <id>", "Reject a pending approval"],
|
|
517
|
+
["/events", "Show persisted events for the latest request"],
|
|
518
|
+
["/trace", "Show persisted trace items for the latest request"],
|
|
519
|
+
["/health", "Show runtime health"],
|
|
520
|
+
["/overview", "Show runtime overview"],
|
|
521
|
+
["/exit", "Exit chat"],
|
|
522
|
+
];
|
|
523
|
+
const cmdW = Math.max(...rows.map(([cmd]) => cmd.length));
|
|
524
|
+
const lines = rows.map(([cmd, desc]) => ` ${cmd.padEnd(cmdW)} ${desc}`);
|
|
525
|
+
return [
|
|
526
|
+
"Commands:",
|
|
527
|
+
"────────",
|
|
528
|
+
...lines,
|
|
529
|
+
"",
|
|
530
|
+
"Starter tasks:",
|
|
531
|
+
"─────────────",
|
|
532
|
+
" - Inspect this workspace and explain the main entry points.",
|
|
533
|
+
" - Review this project structure before making any edits.",
|
|
534
|
+
" - Update README.md to make the setup steps clearer.",
|
|
535
|
+
" - Find the likeliest config issue in this workspace and propose the smallest fix.",
|
|
536
|
+
"",
|
|
537
|
+
].join("\n");
|
|
538
|
+
}
|
|
539
|
+
function trimAsciiBlock(block) {
|
|
540
|
+
return block
|
|
541
|
+
.split("\n")
|
|
542
|
+
.map((line) => line.replace(/\s+$/u, ""))
|
|
543
|
+
.join("\n");
|
|
544
|
+
}
|
|
545
|
+
/** Figlet Small font — trimmed for clean right edge */
|
|
546
|
+
const CHAT_ASCII_BOTBOTGO = trimAsciiBlock(" ____ ___ _____ ____ ___ _____ ____ ___ \n" +
|
|
547
|
+
" | __ ) / _ \\_ _| __ ) / _ \\_ _/ ___|/ _ \\ \n" +
|
|
548
|
+
" | _ \\| | | || | | _ \\| | | || || | _| | | |\n" +
|
|
549
|
+
" | |_) | |_| || | | |_) | |_| || || |_| | |_| |\n" +
|
|
550
|
+
" |____/ \\___/ |_| |____/ \\___/ |_| \\____|\\___/");
|
|
551
|
+
const CHAT_ASCII_AGENT_HARNESS = trimAsciiBlock(" _ ____ _____ _ _ _____ _ _ _ ____ _ _ _____ ____ ____ \n" +
|
|
552
|
+
" / \\ / ___| ____| \\ | |_ _| | | | | / \\ | _ \\| \\ | | ____/ ___/ ___| \n" +
|
|
553
|
+
" / _ \\| | _| _| | \\| | | | | |_| | / _ \\ | |_) | \\| | _| \\___ \\___ \\ \n" +
|
|
554
|
+
" / ___ \\ |_| | |___| |\\ | | | | _ |/ ___ \\| _ <| |\\ | |___ ___) |__) |\n" +
|
|
555
|
+
" /_/ \\_\\____|_____|_| \\_| |_| |_| |_/_/ \\_\\_| \\_\\_| \\_|_____|____/____/");
|
|
556
|
+
/**
|
|
557
|
+
* Solid fills (one 256-color per Figlet row, no gradient):
|
|
558
|
+
* BOTBOTGO ≈ US flag: blue / white / red / white / red stripes top→bottom.
|
|
559
|
+
* AGENT HARNESS ≈ 中国国旗: upper rows gold (星区黄), lower rows field red.
|
|
560
|
+
*/
|
|
561
|
+
const CHAT_LOGO_BRAND_LINE_COLORS = [27, 255, 196, 255, 196];
|
|
562
|
+
const CHAT_LOGO_PRODUCT_LINE_COLORS = [226, 226, 196, 196, 196];
|
|
563
|
+
/** One foreground color per entire line — flat, no relief shading */
|
|
564
|
+
function colorizeSolidAsciiBlock(block, lineColors, enabled) {
|
|
565
|
+
const trimmed = trimAsciiBlock(block);
|
|
566
|
+
const lines = trimmed.split("\n");
|
|
567
|
+
if (!enabled) {
|
|
568
|
+
return trimmed;
|
|
569
|
+
}
|
|
570
|
+
return lines
|
|
571
|
+
.map((line, i) => {
|
|
572
|
+
const code = lineColors[Math.min(i, lineColors.length - 1)] ?? lineColors[lineColors.length - 1];
|
|
573
|
+
return `\x1b[38;5;${code}m${line}\x1b[0m`;
|
|
574
|
+
})
|
|
575
|
+
.join("\n");
|
|
576
|
+
}
|
|
577
|
+
function ellipsizeChatId(value, maxChars) {
|
|
578
|
+
if (value.length <= maxChars) {
|
|
579
|
+
return value;
|
|
580
|
+
}
|
|
581
|
+
if (maxChars <= 1) {
|
|
582
|
+
return "…";
|
|
583
|
+
}
|
|
584
|
+
return `${value.slice(0, maxChars - 1)}…`;
|
|
585
|
+
}
|
|
586
|
+
function renderChatPromptLine(input) {
|
|
587
|
+
const agent = input.agentId ?? "—";
|
|
588
|
+
const separator = " ────────────────────────────────────────";
|
|
589
|
+
if (!input.color) {
|
|
590
|
+
return `${separator}\n botbotgo │ agent ${agent} › `;
|
|
591
|
+
}
|
|
592
|
+
return (` \x1b[38;5;240m${"─".repeat(40)}\x1b[0m\n ` +
|
|
593
|
+
`\x1b[1;36mbotbotgo\x1b[0m \x1b[38;5;240m│\x1b[0m ` +
|
|
594
|
+
`\x1b[90magent\x1b[0m \x1b[97m${agent}\x1b[0m \x1b[32m›\x1b[0m `);
|
|
595
|
+
}
|
|
596
|
+
async function* iterateChatLines(rl, nextPrompt) {
|
|
597
|
+
rl.setPrompt(nextPrompt());
|
|
598
|
+
rl.prompt();
|
|
599
|
+
const asyncIterator = rl[Symbol.asyncIterator]();
|
|
600
|
+
while (true) {
|
|
601
|
+
const step = await asyncIterator.next();
|
|
602
|
+
if (step.done) {
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
yield step.value;
|
|
606
|
+
rl.setPrompt(nextPrompt());
|
|
607
|
+
rl.prompt();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function renderChatBanner(input) {
|
|
611
|
+
const color = input.color === true;
|
|
612
|
+
const subtitle = "ACP workspace shell · @botbotgo/agent-harness";
|
|
613
|
+
const labelW = 12;
|
|
614
|
+
const rows = [
|
|
615
|
+
["Workspace", input.workspacePath],
|
|
616
|
+
["Transport", input.transport],
|
|
617
|
+
];
|
|
618
|
+
if (input.agentId) {
|
|
619
|
+
rows.push(["Agent", input.agentId]);
|
|
620
|
+
}
|
|
621
|
+
if (input.sessionId) {
|
|
622
|
+
rows.push(["Session", input.sessionId]);
|
|
623
|
+
}
|
|
624
|
+
const bodyLines = rows.map(([label, value]) => `${label.padEnd(labelW)} ${value}`);
|
|
625
|
+
const inner = Math.max(subtitle.length, ...bodyLines.map((line) => line.length), 44);
|
|
626
|
+
const horizontal = (left, mid, right) => ` ${left}${mid.repeat(inner + 2)}${right}`;
|
|
627
|
+
const boxed = (text) => ` │ ${text.padEnd(inner)} │`;
|
|
628
|
+
const logoWidth = Math.max(...CHAT_ASCII_BOTBOTGO.split("\n").map((line) => line.length), ...CHAT_ASCII_AGENT_HARNESS.split("\n").map((line) => line.length), inner + 4);
|
|
629
|
+
const ruleLen = Math.min(logoWidth, 96);
|
|
630
|
+
const rulePlain = ` ${"·".repeat(ruleLen)}`;
|
|
631
|
+
const rule = color ? `\x1b[38;5;253m${rulePlain}\x1b[0m` : rulePlain;
|
|
632
|
+
const brandArt = colorizeSolidAsciiBlock(CHAT_ASCII_BOTBOTGO, CHAT_LOGO_BRAND_LINE_COLORS, color);
|
|
633
|
+
const productArt = colorizeSolidAsciiBlock(CHAT_ASCII_AGENT_HARNESS, CHAT_LOGO_PRODUCT_LINE_COLORS, color);
|
|
634
|
+
const blockSepWidth = Math.max(36, ruleLen - 4);
|
|
635
|
+
const blockSep = color ? ` \x1b[38;5;255m${"─".repeat(blockSepWidth)}\x1b[0m` : "";
|
|
636
|
+
const boxBorder = color ? "\x1b[38;5;27m" : "";
|
|
637
|
+
const boxBorderReset = color ? "\x1b[0m" : "";
|
|
638
|
+
const horizontalColored = (left, mid, right) => color
|
|
639
|
+
? ` ${boxBorder}${left}${boxBorderReset}\x1b[38;5;253m${mid.repeat(inner + 2)}\x1b[0m${boxBorder}${right}${boxBorderReset}`
|
|
640
|
+
: horizontal(left, mid, right);
|
|
641
|
+
const boxedColored = (text) => color
|
|
642
|
+
? ` ${boxBorder}│${boxBorderReset} \x1b[38;5;255m${text.padEnd(inner)}\x1b[0m ${boxBorder}│${boxBorderReset}`
|
|
643
|
+
: boxed(text);
|
|
644
|
+
const hint = color
|
|
645
|
+
? ` \x1b[2m\x1b[38;5;253mType /help for commands and starter tasks\x1b[0m`
|
|
646
|
+
: ` Type /help for commands and starter tasks`;
|
|
647
|
+
const mid = [brandArt, ""];
|
|
648
|
+
if (color) {
|
|
649
|
+
mid.push(blockSep, "");
|
|
650
|
+
}
|
|
651
|
+
mid.push(productArt, "", rule, "", horizontalColored("╭", "─", "╮"), boxedColored(subtitle), horizontalColored("├", "─", "┤"), ...bodyLines.map((line) => boxedColored(line)), horizontalColored("╰", "─", "╯"), "", rule, hint, "");
|
|
652
|
+
return ["", ...mid].join("\n");
|
|
653
|
+
}
|
|
654
|
+
function renderRequestEvents(events) {
|
|
655
|
+
if (events.length === 0) {
|
|
656
|
+
return "No events recorded.\n";
|
|
657
|
+
}
|
|
658
|
+
return events.map((event) => {
|
|
659
|
+
const timestamp = formatTimestamp(event.timestamp) ?? "unknown-time";
|
|
660
|
+
const eventType = typeof event.eventType === "string" ? event.eventType : "unknown-event";
|
|
661
|
+
return `${timestamp} ${eventType}`;
|
|
662
|
+
}).join("\n") + "\n";
|
|
663
|
+
}
|
|
664
|
+
function renderSessionSummaries(summaries) {
|
|
665
|
+
if (summaries.length === 0) {
|
|
666
|
+
return "No sessions recorded.\n";
|
|
667
|
+
}
|
|
668
|
+
return summaries.map((summary) => {
|
|
669
|
+
const sessionId = typeof summary.sessionId === "string" ? summary.sessionId : "unknown";
|
|
670
|
+
const entryAgentId = typeof summary.entryAgentId === "string" ? ` agent=${summary.entryAgentId}` : "";
|
|
671
|
+
const state = typeof summary.currentState === "string" ? ` state=${summary.currentState}` : "";
|
|
672
|
+
const messageCount = typeof summary.messageCount === "number" ? ` messages=${summary.messageCount}` : "";
|
|
673
|
+
const title = typeof summary.title === "string" && summary.title.trim().length > 0 ? ` title=${summary.title}` : "";
|
|
674
|
+
const snippet = typeof summary.snippet === "string" && summary.snippet.trim().length > 0 ? ` snippet=${summary.snippet}` : "";
|
|
675
|
+
return `${sessionId}${entryAgentId}${state}${messageCount}${title}${snippet}`;
|
|
676
|
+
}).join("\n") + "\n";
|
|
677
|
+
}
|
|
678
|
+
function renderRequestTraceItems(items) {
|
|
679
|
+
if (items.length === 0) {
|
|
680
|
+
return "No trace items recorded.\n";
|
|
681
|
+
}
|
|
682
|
+
return items.map((item) => {
|
|
683
|
+
const surfaceItem = isObject(item.surfaceItem) ? item.surfaceItem : {};
|
|
684
|
+
const kind = typeof surfaceItem.kind === "string" ? surfaceItem.kind : "unknown";
|
|
685
|
+
const id = typeof surfaceItem.id === "string" ? surfaceItem.id : typeof surfaceItem.name === "string" ? surfaceItem.name : "unknown";
|
|
686
|
+
const agentId = typeof surfaceItem.agentId === "string" ? ` agent=${surfaceItem.agentId}` : "";
|
|
687
|
+
return `${kind}:${id}${agentId}`;
|
|
688
|
+
}).join("\n") + "\n";
|
|
689
|
+
}
|
|
690
|
+
function renderChatContext(input) {
|
|
691
|
+
return [
|
|
692
|
+
`agent=${input.agentId ?? "none"}`,
|
|
693
|
+
`session=${input.sessionId ?? "none"}`,
|
|
694
|
+
`request=${input.requestId ?? "none"}`,
|
|
695
|
+
].join(" ") + "\n";
|
|
696
|
+
}
|
|
697
|
+
function normalizeChatCommand(line) {
|
|
698
|
+
const trimmed = line.trim();
|
|
699
|
+
if (!trimmed.startsWith("/")) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
const [name, ...rest] = trimmed.slice(1).split(/\s+/);
|
|
703
|
+
return {
|
|
704
|
+
name,
|
|
705
|
+
arg: rest.length > 0 ? rest.join(" ") : undefined,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function asRecord(value) {
|
|
709
|
+
return typeof value === "object" && value !== null ? value : undefined;
|
|
710
|
+
}
|
|
711
|
+
function readYamlFile(filePath) {
|
|
712
|
+
return YAML.parse(readFileSync(filePath, "utf8"));
|
|
713
|
+
}
|
|
714
|
+
function getWorkspaceDefaultAgentId(workspaceRoot) {
|
|
715
|
+
const runtimePath = path.join(resolveCliConfigRoot(workspaceRoot), "runtime", "workspace.yaml");
|
|
716
|
+
if (!existsSync(runtimePath)) {
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
const parsed = asRecord(readYamlFile(runtimePath));
|
|
720
|
+
const spec = asRecord(parsed?.spec);
|
|
721
|
+
const routing = asRecord(spec?.routing);
|
|
722
|
+
return typeof routing?.defaultAgentId === "string" && routing.defaultAgentId.trim().length > 0
|
|
723
|
+
? routing.defaultAgentId.trim()
|
|
724
|
+
: undefined;
|
|
725
|
+
}
|
|
726
|
+
function getAgentModelRef(workspaceRoot, agentId) {
|
|
727
|
+
const agentPath = path.join(resolveCliConfigRoot(workspaceRoot), "agents", `${agentId}.yaml`);
|
|
728
|
+
if (!existsSync(agentPath)) {
|
|
729
|
+
return undefined;
|
|
730
|
+
}
|
|
731
|
+
const parsed = asRecord(readYamlFile(agentPath));
|
|
732
|
+
const spec = asRecord(parsed?.spec);
|
|
733
|
+
return typeof spec?.modelRef === "string" && spec.modelRef.trim().length > 0
|
|
734
|
+
? spec.modelRef.trim()
|
|
735
|
+
: undefined;
|
|
736
|
+
}
|
|
737
|
+
function getModelInfo(workspaceRoot, modelRef) {
|
|
738
|
+
const modelsPath = path.join(resolveCliConfigRoot(workspaceRoot), "catalogs", "models.yaml");
|
|
739
|
+
if (!existsSync(modelsPath)) {
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
const parsed = asRecord(readYamlFile(modelsPath));
|
|
743
|
+
const spec = Array.isArray(parsed?.spec) ? parsed.spec.filter(asRecord) : [];
|
|
744
|
+
const modelName = modelRef.startsWith("model/") ? modelRef.slice("model/".length) : modelRef;
|
|
745
|
+
const model = spec.find((item) => item.name === modelName);
|
|
746
|
+
if (!model) {
|
|
747
|
+
return undefined;
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
provider: typeof model.provider === "string" ? model.provider : undefined,
|
|
751
|
+
model: typeof model.model === "string" ? model.model : undefined,
|
|
752
|
+
baseUrl: typeof model.baseUrl === "string" ? model.baseUrl : undefined,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function readChatWorkspaceModelInfo(workspaceRoot, agentId) {
|
|
756
|
+
try {
|
|
757
|
+
const resolvedAgentId = agentId ?? getWorkspaceDefaultAgentId(workspaceRoot);
|
|
758
|
+
if (!resolvedAgentId) {
|
|
759
|
+
return getModelInfo(workspaceRoot, "model/default");
|
|
760
|
+
}
|
|
761
|
+
const modelRef = getAgentModelRef(workspaceRoot, resolvedAgentId) ?? "model/default";
|
|
762
|
+
return getModelInfo(workspaceRoot, modelRef);
|
|
763
|
+
}
|
|
764
|
+
catch {
|
|
765
|
+
return undefined;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function renderProviderFailureHint(modelInfo) {
|
|
769
|
+
if (!modelInfo?.provider) {
|
|
770
|
+
return undefined;
|
|
771
|
+
}
|
|
772
|
+
if (modelInfo.provider === "ollama") {
|
|
773
|
+
const modelText = modelInfo.model ? ` and ensure \`${modelInfo.model}\` is available` : "";
|
|
774
|
+
return `Hint: start Ollama${modelInfo.baseUrl ? ` at ${modelInfo.baseUrl}` : ""}${modelText}. Example: \`ollama serve\`${modelInfo.model ? ` and \`ollama pull ${modelInfo.model}\`` : ""}.`;
|
|
775
|
+
}
|
|
776
|
+
if (modelInfo.provider === "openai") {
|
|
777
|
+
return "Hint: verify network access and that `OPENAI_API_KEY` is set for this shell.";
|
|
778
|
+
}
|
|
779
|
+
if (modelInfo.provider === "anthropic") {
|
|
780
|
+
return "Hint: verify network access and that `ANTHROPIC_API_KEY` is set for this shell.";
|
|
781
|
+
}
|
|
782
|
+
if (modelInfo.provider === "google" || modelInfo.provider === "google-genai" || modelInfo.provider === "gemini") {
|
|
783
|
+
return "Hint: verify network access and that the configured Google API key is available in this shell.";
|
|
784
|
+
}
|
|
785
|
+
if (modelInfo.provider === "openai-compatible") {
|
|
786
|
+
return `Hint: verify the configured endpoint${modelInfo.baseUrl ? ` (${modelInfo.baseUrl})` : ""} and the required API key for that provider.`;
|
|
787
|
+
}
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
export function renderChatRuntimeFailure(output, modelInfo) {
|
|
791
|
+
const trimmed = output.trim();
|
|
792
|
+
if (!trimmed.startsWith("runtime_error=")) {
|
|
793
|
+
return output;
|
|
794
|
+
}
|
|
795
|
+
const normalized = trimmed.toLowerCase();
|
|
796
|
+
if (!normalized.includes("fetch failed") &&
|
|
797
|
+
!normalized.includes("connection error") &&
|
|
798
|
+
!normalized.includes("timed out") &&
|
|
799
|
+
!normalized.includes("404 page not found")) {
|
|
800
|
+
return output;
|
|
801
|
+
}
|
|
802
|
+
const lines = [trimmed];
|
|
803
|
+
if (modelInfo?.provider || modelInfo?.model) {
|
|
804
|
+
lines.push(`provider=${modelInfo?.provider ?? "unknown"}${modelInfo?.model ? ` model=${modelInfo.model}` : ""}`);
|
|
805
|
+
}
|
|
806
|
+
if (modelInfo?.baseUrl) {
|
|
807
|
+
lines.push(`endpoint=${modelInfo.baseUrl}`);
|
|
808
|
+
}
|
|
809
|
+
if (normalized.includes("404 page not found") && modelInfo?.provider === "ollama") {
|
|
810
|
+
lines.push("Hint: the configured endpoint responded, but it does not look like an Ollama API route. Check that `baseUrl` points at the Ollama server root and that no other service is bound to this port.");
|
|
811
|
+
}
|
|
812
|
+
const hint = renderProviderFailureHint(modelInfo);
|
|
813
|
+
if (hint) {
|
|
814
|
+
lines.push(hint);
|
|
815
|
+
}
|
|
816
|
+
return lines.join("\n");
|
|
817
|
+
}
|
|
818
|
+
function renderChatTextChunk(text, modelInfo) {
|
|
819
|
+
return renderChatRuntimeFailure(text, modelInfo);
|
|
820
|
+
}
|
|
821
|
+
function truncateChatToolPreview(value, maxChars = 800) {
|
|
822
|
+
if (value.length <= maxChars) {
|
|
823
|
+
return value;
|
|
824
|
+
}
|
|
825
|
+
return `${value.slice(0, maxChars - 15)}\n...[truncated]`;
|
|
826
|
+
}
|
|
827
|
+
function extractChatToolTextContent(value) {
|
|
828
|
+
if (typeof value === "string") {
|
|
829
|
+
return value;
|
|
830
|
+
}
|
|
831
|
+
if (Array.isArray(value)) {
|
|
832
|
+
return value
|
|
833
|
+
.map((item) => extractChatToolTextContent(item))
|
|
834
|
+
.filter((item) => item.trim().length > 0)
|
|
835
|
+
.join("\n");
|
|
836
|
+
}
|
|
837
|
+
if (!value || typeof value !== "object") {
|
|
838
|
+
return "";
|
|
839
|
+
}
|
|
840
|
+
const typed = value;
|
|
841
|
+
if (typeof typed.text === "string") {
|
|
842
|
+
return typed.text;
|
|
843
|
+
}
|
|
844
|
+
if (typeof typed.content === "string") {
|
|
845
|
+
return typed.content;
|
|
846
|
+
}
|
|
847
|
+
if (typed.content !== undefined) {
|
|
848
|
+
const nestedContent = extractChatToolTextContent(typed.content);
|
|
849
|
+
if (nestedContent.trim().length > 0) {
|
|
850
|
+
return nestedContent;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (typed.kwargs !== undefined) {
|
|
854
|
+
const nestedKwargs = extractChatToolTextContent(typed.kwargs);
|
|
855
|
+
if (nestedKwargs.trim().length > 0) {
|
|
856
|
+
return nestedKwargs;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
if (typed.message !== undefined) {
|
|
860
|
+
const nestedMessage = extractChatToolTextContent(typed.message);
|
|
861
|
+
if (nestedMessage.trim().length > 0) {
|
|
862
|
+
return nestedMessage;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (typed.body !== undefined) {
|
|
866
|
+
const nestedBody = extractChatToolTextContent(typed.body);
|
|
867
|
+
if (nestedBody.trim().length > 0) {
|
|
868
|
+
return nestedBody;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (typed.answer !== undefined) {
|
|
872
|
+
const nestedAnswer = extractChatToolTextContent(typed.answer);
|
|
873
|
+
if (nestedAnswer.trim().length > 0) {
|
|
874
|
+
return nestedAnswer;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return "";
|
|
878
|
+
}
|
|
879
|
+
function summarizeChatToolResult(output) {
|
|
880
|
+
if (typeof output === "string") {
|
|
881
|
+
return truncateChatToolPreview(output);
|
|
882
|
+
}
|
|
883
|
+
if (typeof output === "number" || typeof output === "boolean") {
|
|
884
|
+
return String(output);
|
|
885
|
+
}
|
|
886
|
+
if (!output || typeof output !== "object") {
|
|
887
|
+
return JSON.stringify(output);
|
|
888
|
+
}
|
|
889
|
+
const typed = output;
|
|
890
|
+
const content = extractChatToolTextContent(output);
|
|
891
|
+
if (content && content.trim().length > 0) {
|
|
892
|
+
return truncateChatToolPreview(content.trim());
|
|
893
|
+
}
|
|
894
|
+
const summary = typeof typed.summary === "object" && typed.summary !== null ? typed.summary : undefined;
|
|
895
|
+
if (summary) {
|
|
896
|
+
return truncateChatToolPreview(JSON.stringify(summary, null, 2));
|
|
897
|
+
}
|
|
898
|
+
return truncateChatToolPreview(JSON.stringify(output, null, 2));
|
|
899
|
+
}
|
|
900
|
+
export async function probeChatWorkspace(input) {
|
|
901
|
+
const modelInfo = readChatWorkspaceModelInfo(input.workspaceRoot, input.agentId);
|
|
902
|
+
if (!modelInfo?.provider || modelInfo.provider !== "ollama" || !modelInfo.baseUrl) {
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
const response = await fetch(new URL("/api/tags", modelInfo.baseUrl), {
|
|
907
|
+
method: "GET",
|
|
908
|
+
headers: { accept: "application/json" },
|
|
909
|
+
});
|
|
910
|
+
if (response.ok) {
|
|
911
|
+
return undefined;
|
|
912
|
+
}
|
|
913
|
+
if (response.status === 404) {
|
|
914
|
+
return renderChatRuntimeFailure("runtime_error=404 page not found", modelInfo);
|
|
915
|
+
}
|
|
916
|
+
return renderChatRuntimeFailure(`runtime_error=HTTP ${response.status} ${response.statusText}`.trim(), modelInfo);
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
920
|
+
return renderChatRuntimeFailure(`runtime_error=${message}`, modelInfo);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
export function isChatServerNoiseLine(line) {
|
|
924
|
+
const trimmed = line.trim();
|
|
925
|
+
if (!trimmed) {
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
return (trimmed.startsWith("Serving ACP over stdio from ") ||
|
|
929
|
+
trimmed === "langsmith/experimental/sandbox is in alpha. This feature is experimental, and breaking changes are expected." ||
|
|
930
|
+
trimmed === "llamaindex was already imported. This breaks constructor checks and will lead to issues!");
|
|
931
|
+
}
|
|
932
|
+
async function createSubprocessChatClient(input) {
|
|
933
|
+
if (input.transport === "http") {
|
|
934
|
+
return createAcpHttpHarnessClient({
|
|
935
|
+
rpcUrl: `http://${input.hostname ?? "127.0.0.1"}:${input.port ?? 8787}/rpc`,
|
|
936
|
+
eventsUrl: `http://${input.hostname ?? "127.0.0.1"}:${input.port ?? 8787}/events`,
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
const cliFilePath = fileURLToPath(import.meta.url);
|
|
940
|
+
const child = spawn(process.execPath, [
|
|
941
|
+
cliFilePath,
|
|
942
|
+
"acp",
|
|
943
|
+
"serve",
|
|
944
|
+
"--workspace",
|
|
945
|
+
input.workspaceRoot,
|
|
946
|
+
"--transport",
|
|
947
|
+
"stdio",
|
|
948
|
+
], {
|
|
949
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
950
|
+
});
|
|
951
|
+
if (!child.stdin || !child.stdout || !child.stderr) {
|
|
952
|
+
throw new Error("Failed to open ACP stdio pipes for chat client.");
|
|
953
|
+
}
|
|
954
|
+
let stderrBuffer = "";
|
|
955
|
+
child.stderr.setEncoding("utf8");
|
|
956
|
+
child.stderr.on("data", (chunk) => {
|
|
957
|
+
stderrBuffer += chunk;
|
|
958
|
+
const lines = stderrBuffer.split(/\r?\n/);
|
|
959
|
+
stderrBuffer = lines.pop() ?? "";
|
|
960
|
+
for (const line of lines) {
|
|
961
|
+
if (!isChatServerNoiseLine(line)) {
|
|
962
|
+
input.stderr?.(`[runtime] ${line}\n`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
const baseClient = createAcpStdioHarnessClient({
|
|
967
|
+
input: child.stdin,
|
|
968
|
+
output: child.stdout,
|
|
969
|
+
idPrefix: "chat",
|
|
970
|
+
});
|
|
971
|
+
return wrapChatClientForLifecycle(baseClient, async () => {
|
|
972
|
+
if (!child.killed) {
|
|
973
|
+
child.kill();
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
export function wrapChatClientForLifecycle(client, onStop) {
|
|
978
|
+
return {
|
|
979
|
+
request: client.request.bind(client),
|
|
980
|
+
streamRequest: client.streamRequest.bind(client),
|
|
981
|
+
resolveApproval: client.resolveApproval.bind(client),
|
|
982
|
+
cancelRequest: client.cancelRequest.bind(client),
|
|
983
|
+
subscribe: client.subscribe.bind(client),
|
|
984
|
+
listSessions: client.listSessions.bind(client),
|
|
985
|
+
listSessionSummaries: client.listSessionSummaries.bind(client),
|
|
986
|
+
listRequests: client.listRequests.bind(client),
|
|
987
|
+
getSession: client.getSession.bind(client),
|
|
988
|
+
getRequest: client.getRequest.bind(client),
|
|
989
|
+
listApprovals: client.listApprovals.bind(client),
|
|
990
|
+
getApproval: client.getApproval.bind(client),
|
|
991
|
+
listRequestEvents: client.listRequestEvents.bind(client),
|
|
992
|
+
listRequestTraceItems: client.listRequestTraceItems.bind(client),
|
|
993
|
+
getHealth: client.getHealth.bind(client),
|
|
994
|
+
getOperatorOverview: client.getOperatorOverview.bind(client),
|
|
995
|
+
async stop() {
|
|
996
|
+
try {
|
|
997
|
+
await client.stop();
|
|
998
|
+
}
|
|
999
|
+
finally {
|
|
1000
|
+
await onStop();
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
async function streamChatMessage(input) {
|
|
1006
|
+
let latestSessionId = input.sessionId;
|
|
1007
|
+
let latestRequestId;
|
|
1008
|
+
let latestAgentId = input.agentId;
|
|
1009
|
+
let wroteContent = false;
|
|
1010
|
+
let wroteRenderableBlocks = false;
|
|
1011
|
+
for await (const item of input.client.streamRequest({
|
|
1012
|
+
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
1013
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
1014
|
+
input: input.message,
|
|
1015
|
+
})) {
|
|
1016
|
+
if (item.type === "content") {
|
|
1017
|
+
input.stdout(renderChatTextChunk(item.content, input.modelInfo));
|
|
1018
|
+
latestSessionId = item.sessionId;
|
|
1019
|
+
latestRequestId = item.requestId;
|
|
1020
|
+
latestAgentId = item.agentId;
|
|
1021
|
+
wroteContent = true;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (item.type === "content-blocks") {
|
|
1025
|
+
latestSessionId = item.sessionId;
|
|
1026
|
+
latestRequestId = item.requestId;
|
|
1027
|
+
latestAgentId = item.agentId;
|
|
1028
|
+
if (!wroteContent) {
|
|
1029
|
+
const rendered = item.contentBlocks
|
|
1030
|
+
.map((block) => {
|
|
1031
|
+
if (typeof block === "string") {
|
|
1032
|
+
return block;
|
|
1033
|
+
}
|
|
1034
|
+
if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
|
|
1035
|
+
return block.text;
|
|
1036
|
+
}
|
|
1037
|
+
return "";
|
|
1038
|
+
})
|
|
1039
|
+
.filter((block) => block.trim().length > 0)
|
|
1040
|
+
.join("");
|
|
1041
|
+
if (rendered) {
|
|
1042
|
+
input.stdout(renderChatTextChunk(rendered, input.modelInfo));
|
|
1043
|
+
wroteRenderableBlocks = true;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (item.type === "tool-result") {
|
|
1049
|
+
latestSessionId = item.sessionId;
|
|
1050
|
+
latestRequestId = item.requestId;
|
|
1051
|
+
latestAgentId = item.agentId;
|
|
1052
|
+
input.stderr(`\n[tool:${item.toolName}] ${summarizeChatToolResult(item.output)}${item.isError ? " (error)" : ""}\n`);
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (item.type === "result") {
|
|
1056
|
+
latestSessionId = item.result.sessionId;
|
|
1057
|
+
latestRequestId = item.result.requestId;
|
|
1058
|
+
if (!wroteContent && !wroteRenderableBlocks && item.result.output.trim().length > 0) {
|
|
1059
|
+
input.stdout(renderChatRuntimeFailure(item.result.output, input.modelInfo));
|
|
1060
|
+
}
|
|
1061
|
+
if (item.result.state === "waiting_for_approval") {
|
|
1062
|
+
input.stderr(`\nRequest is waiting for approval${item.result.approvalId ? ` (${item.result.approvalId})` : ""}.\n`);
|
|
1063
|
+
}
|
|
1064
|
+
else if (wroteContent || wroteRenderableBlocks || item.result.output.trim().length > 0) {
|
|
1065
|
+
input.stdout("\n");
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return { sessionId: latestSessionId, requestId: latestRequestId, agentId: latestAgentId };
|
|
1070
|
+
}
|
|
426
1071
|
export async function runCli(argv, io = {}, deps = {}) {
|
|
427
1072
|
const cwd = io.cwd ?? process.cwd();
|
|
1073
|
+
const stdin = io.stdin ?? process.stdin;
|
|
428
1074
|
const stdout = io.stdout ?? ((message) => process.stdout.write(message));
|
|
429
1075
|
const stderr = io.stderr ?? ((message) => process.stderr.write(message));
|
|
430
1076
|
const [command, projectName, ...rest] = argv;
|
|
1077
|
+
const createChatClient = deps.createChatClient ?? createSubprocessChatClient;
|
|
1078
|
+
const probeWorkspace = deps.probeChatWorkspace ?? probeChatWorkspace;
|
|
1079
|
+
const createChatLineReader = deps.createReadlineInterface ?? createReadlineInterface;
|
|
431
1080
|
const createHarness = deps.createAgentHarness ?? createAgentHarness;
|
|
432
1081
|
const serveA2a = deps.serveA2aOverHttp ?? serveA2aOverHttp;
|
|
433
1082
|
const serveAgUi = deps.serveAgUiOverHttp ?? serveAgUiOverHttp;
|
|
@@ -464,6 +1113,268 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
464
1113
|
return 1;
|
|
465
1114
|
}
|
|
466
1115
|
}
|
|
1116
|
+
if (command === "chat") {
|
|
1117
|
+
const parsed = parseChatOptions([projectName, ...rest].filter((item) => typeof item === "string"));
|
|
1118
|
+
if (parsed.error) {
|
|
1119
|
+
stderr(`${parsed.error}\n`);
|
|
1120
|
+
stderr(renderUsage());
|
|
1121
|
+
return 1;
|
|
1122
|
+
}
|
|
1123
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1124
|
+
const workspaceModelInfo = readChatWorkspaceModelInfo(workspacePath, parsed.agentId);
|
|
1125
|
+
let client;
|
|
1126
|
+
try {
|
|
1127
|
+
client = await createChatClient({
|
|
1128
|
+
workspaceRoot: workspacePath,
|
|
1129
|
+
transport: parsed.transport,
|
|
1130
|
+
hostname: parsed.hostname,
|
|
1131
|
+
port: parsed.port,
|
|
1132
|
+
stderr,
|
|
1133
|
+
});
|
|
1134
|
+
let activeAgentId = parsed.agentId;
|
|
1135
|
+
let activeSessionId = parsed.sessionId;
|
|
1136
|
+
let latestRequestId;
|
|
1137
|
+
const preflightWarning = await probeWorkspace({
|
|
1138
|
+
workspaceRoot: workspacePath,
|
|
1139
|
+
agentId: activeAgentId,
|
|
1140
|
+
});
|
|
1141
|
+
if (parsed.message) {
|
|
1142
|
+
const streamed = await streamChatMessage({
|
|
1143
|
+
client,
|
|
1144
|
+
stdout,
|
|
1145
|
+
stderr,
|
|
1146
|
+
agentId: activeAgentId,
|
|
1147
|
+
sessionId: activeSessionId,
|
|
1148
|
+
message: parsed.message,
|
|
1149
|
+
modelInfo: workspaceModelInfo,
|
|
1150
|
+
});
|
|
1151
|
+
activeSessionId = streamed.sessionId;
|
|
1152
|
+
latestRequestId = streamed.requestId;
|
|
1153
|
+
activeAgentId = streamed.agentId ?? activeAgentId;
|
|
1154
|
+
if (activeSessionId) {
|
|
1155
|
+
stderr(`session=${activeSessionId}${latestRequestId ? ` request=${latestRequestId}` : ""}\n`);
|
|
1156
|
+
}
|
|
1157
|
+
await client.stop();
|
|
1158
|
+
return 0;
|
|
1159
|
+
}
|
|
1160
|
+
const stdinStream = stdin;
|
|
1161
|
+
const useColor = stdinStream.isTTY === true && io.stdout === undefined;
|
|
1162
|
+
stdout(renderChatBanner({
|
|
1163
|
+
workspacePath,
|
|
1164
|
+
transport: parsed.transport,
|
|
1165
|
+
agentId: activeAgentId,
|
|
1166
|
+
sessionId: activeSessionId,
|
|
1167
|
+
color: useColor,
|
|
1168
|
+
}));
|
|
1169
|
+
if (preflightWarning) {
|
|
1170
|
+
stdout(`${preflightWarning}\n\n`);
|
|
1171
|
+
}
|
|
1172
|
+
const stdoutSink = new Writable({
|
|
1173
|
+
decodeStrings: false,
|
|
1174
|
+
write(chunk, _encoding, callback) {
|
|
1175
|
+
stdout(typeof chunk === "string" ? chunk : String(chunk));
|
|
1176
|
+
callback();
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
const lineReader = createChatLineReader({
|
|
1180
|
+
input: stdin,
|
|
1181
|
+
output: stdoutSink,
|
|
1182
|
+
crlfDelay: Infinity,
|
|
1183
|
+
terminal: stdinStream.isTTY === true,
|
|
1184
|
+
});
|
|
1185
|
+
try {
|
|
1186
|
+
for await (const raw of iterateChatLines(lineReader, () => renderChatPromptLine({
|
|
1187
|
+
agentId: activeAgentId,
|
|
1188
|
+
sessionId: activeSessionId,
|
|
1189
|
+
requestId: latestRequestId,
|
|
1190
|
+
color: useColor,
|
|
1191
|
+
}))) {
|
|
1192
|
+
const trimmed = raw.trim();
|
|
1193
|
+
if (!trimmed) {
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
const chatCommand = normalizeChatCommand(trimmed);
|
|
1197
|
+
if (!chatCommand) {
|
|
1198
|
+
const streamed = await streamChatMessage({
|
|
1199
|
+
client,
|
|
1200
|
+
stdout,
|
|
1201
|
+
stderr,
|
|
1202
|
+
agentId: activeAgentId,
|
|
1203
|
+
sessionId: activeSessionId,
|
|
1204
|
+
message: trimmed,
|
|
1205
|
+
modelInfo: workspaceModelInfo,
|
|
1206
|
+
});
|
|
1207
|
+
activeSessionId = streamed.sessionId;
|
|
1208
|
+
latestRequestId = streamed.requestId;
|
|
1209
|
+
activeAgentId = streamed.agentId ?? activeAgentId;
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
if (chatCommand.name === "exit" || chatCommand.name === "quit") {
|
|
1213
|
+
break;
|
|
1214
|
+
}
|
|
1215
|
+
if (chatCommand.name === "help") {
|
|
1216
|
+
stdout(renderChatHelp());
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
if (chatCommand.name === "context") {
|
|
1220
|
+
stdout(renderChatContext({
|
|
1221
|
+
agentId: activeAgentId,
|
|
1222
|
+
sessionId: activeSessionId,
|
|
1223
|
+
requestId: latestRequestId,
|
|
1224
|
+
}));
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
if (chatCommand.name === "new") {
|
|
1228
|
+
activeSessionId = undefined;
|
|
1229
|
+
latestRequestId = undefined;
|
|
1230
|
+
stdout(renderChatContext({
|
|
1231
|
+
agentId: activeAgentId,
|
|
1232
|
+
sessionId: activeSessionId,
|
|
1233
|
+
requestId: latestRequestId,
|
|
1234
|
+
}));
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
if (chatCommand.name === "agent") {
|
|
1238
|
+
if (!chatCommand.arg) {
|
|
1239
|
+
stdout(activeAgentId ? `${activeAgentId}\n` : "No active agent override.\n");
|
|
1240
|
+
continue;
|
|
1241
|
+
}
|
|
1242
|
+
activeAgentId = chatCommand.arg;
|
|
1243
|
+
stdout(`agent=${activeAgentId}\n`);
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
if (chatCommand.name === "session") {
|
|
1247
|
+
stdout(activeSessionId ? `${activeSessionId}\n` : "No active session.\n");
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
if (chatCommand.name === "request") {
|
|
1251
|
+
if (!chatCommand.arg) {
|
|
1252
|
+
stdout(latestRequestId ? `${latestRequestId}\n` : "No active request.\n");
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
const selected = await client.getRequest(chatCommand.arg);
|
|
1256
|
+
if (!selected) {
|
|
1257
|
+
stdout(`Request not found: ${chatCommand.arg}\n`);
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
latestRequestId = selected.requestId;
|
|
1261
|
+
activeSessionId = selected.sessionId;
|
|
1262
|
+
activeAgentId = selected.agentId;
|
|
1263
|
+
stdout(`request=${latestRequestId} session=${activeSessionId}\n`);
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (chatCommand.name === "sessions") {
|
|
1267
|
+
const summaries = await client.listSessionSummaries(parsed.agentId ? { agentId: parsed.agentId } : undefined);
|
|
1268
|
+
stdout(renderSessionSummaries(summaries));
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
if (chatCommand.name === "requests") {
|
|
1272
|
+
const requests = await client.listRequests(activeSessionId
|
|
1273
|
+
? { sessionId: activeSessionId }
|
|
1274
|
+
: activeAgentId
|
|
1275
|
+
? { agentId: activeAgentId }
|
|
1276
|
+
: undefined);
|
|
1277
|
+
stdout(renderRequestList(requests));
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (chatCommand.name === "resume") {
|
|
1281
|
+
if (!chatCommand.arg) {
|
|
1282
|
+
stdout("Usage: /resume <sessionId>\n");
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const session = await client.getSession(chatCommand.arg);
|
|
1286
|
+
if (!session) {
|
|
1287
|
+
stdout(`Session not found: ${chatCommand.arg}\n`);
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
activeSessionId = chatCommand.arg;
|
|
1291
|
+
latestRequestId = session.latestRequestId;
|
|
1292
|
+
activeAgentId = session.currentAgentId ?? session.entryAgentId ?? activeAgentId;
|
|
1293
|
+
stdout(`session=${activeSessionId}\n`);
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
if (chatCommand.name === "cancel") {
|
|
1297
|
+
if (!latestRequestId) {
|
|
1298
|
+
stdout("No active request.\n");
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
const result = await client.cancelRequest({
|
|
1302
|
+
requestId: latestRequestId,
|
|
1303
|
+
reason: "Cancelled from chat CLI",
|
|
1304
|
+
});
|
|
1305
|
+
activeSessionId = result.sessionId;
|
|
1306
|
+
latestRequestId = result.requestId;
|
|
1307
|
+
stdout(`${result.state}: ${result.output}\n`);
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (chatCommand.name === "approvals") {
|
|
1311
|
+
const approvals = await client.listApprovals(activeSessionId ? { sessionId: activeSessionId, status: "pending" } : { status: "pending" });
|
|
1312
|
+
stdout(renderApprovalList(approvals));
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if ((chatCommand.name === "approve" || chatCommand.name === "reject") && chatCommand.arg) {
|
|
1316
|
+
const result = await client.resolveApproval({
|
|
1317
|
+
approvalId: chatCommand.arg,
|
|
1318
|
+
decision: chatCommand.name === "approve" ? "approve" : "reject",
|
|
1319
|
+
...(activeSessionId ? { sessionId: activeSessionId } : {}),
|
|
1320
|
+
});
|
|
1321
|
+
activeSessionId = result.sessionId;
|
|
1322
|
+
latestRequestId = result.requestId;
|
|
1323
|
+
stdout(`${result.state}: ${result.output}\n`);
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
if (chatCommand.name === "events") {
|
|
1327
|
+
if (!activeSessionId || !latestRequestId) {
|
|
1328
|
+
stdout("No active request.\n");
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
const events = await client.listRequestEvents({
|
|
1332
|
+
sessionId: activeSessionId,
|
|
1333
|
+
requestId: latestRequestId,
|
|
1334
|
+
});
|
|
1335
|
+
stdout(renderRequestEvents(events));
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (chatCommand.name === "trace") {
|
|
1339
|
+
if (!activeSessionId || !latestRequestId) {
|
|
1340
|
+
stdout("No active request.\n");
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
const traceItems = await client.listRequestTraceItems({
|
|
1344
|
+
sessionId: activeSessionId,
|
|
1345
|
+
requestId: latestRequestId,
|
|
1346
|
+
});
|
|
1347
|
+
stdout(renderRequestTraceItems(traceItems));
|
|
1348
|
+
continue;
|
|
1349
|
+
}
|
|
1350
|
+
if (chatCommand.name === "health") {
|
|
1351
|
+
const health = await client.getHealth();
|
|
1352
|
+
stdout(renderHealthSnapshot(health, workspacePath));
|
|
1353
|
+
continue;
|
|
1354
|
+
}
|
|
1355
|
+
if (chatCommand.name === "overview") {
|
|
1356
|
+
const overview = await client.getOperatorOverview({ limit: 5 });
|
|
1357
|
+
stdout(renderOperatorOverview(overview, workspacePath));
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
stdout("Unknown chat command. Use /help.\n");
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
finally {
|
|
1364
|
+
lineReader.close();
|
|
1365
|
+
}
|
|
1366
|
+
await client.stop();
|
|
1367
|
+
return 0;
|
|
1368
|
+
}
|
|
1369
|
+
catch (error) {
|
|
1370
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1371
|
+
stderr(`${message}\n`);
|
|
1372
|
+
if (client) {
|
|
1373
|
+
await client.stop().catch(() => undefined);
|
|
1374
|
+
}
|
|
1375
|
+
return 1;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
467
1378
|
if (command === "acp") {
|
|
468
1379
|
const [subcommand, ...subcommandArgs] = [projectName, ...rest];
|
|
469
1380
|
if (subcommand !== "serve") {
|
|
@@ -477,8 +1388,8 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
477
1388
|
return 1;
|
|
478
1389
|
}
|
|
479
1390
|
try {
|
|
480
|
-
const
|
|
481
|
-
const
|
|
1391
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1392
|
+
const runtime = await createHarness(workspacePath);
|
|
482
1393
|
if (parsed.transport === "http") {
|
|
483
1394
|
const server = await serveAcpHttp(runtime, {
|
|
484
1395
|
hostname: parsed.hostname,
|
|
@@ -514,8 +1425,8 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
514
1425
|
return 1;
|
|
515
1426
|
}
|
|
516
1427
|
try {
|
|
517
|
-
const
|
|
518
|
-
const
|
|
1428
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1429
|
+
const runtime = await createHarness(workspacePath);
|
|
519
1430
|
const server = await serveAgUi(runtime, {
|
|
520
1431
|
hostname: parsed.hostname,
|
|
521
1432
|
port: parsed.port,
|
|
@@ -544,8 +1455,8 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
544
1455
|
return 1;
|
|
545
1456
|
}
|
|
546
1457
|
try {
|
|
547
|
-
const
|
|
548
|
-
const
|
|
1458
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1459
|
+
const runtime = await createHarness(workspacePath);
|
|
549
1460
|
const server = await serveA2a(runtime, {
|
|
550
1461
|
hostname: parsed.hostname,
|
|
551
1462
|
port: parsed.port,
|
|
@@ -574,8 +1485,8 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
574
1485
|
return 1;
|
|
575
1486
|
}
|
|
576
1487
|
try {
|
|
577
|
-
const
|
|
578
|
-
const
|
|
1488
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1489
|
+
const runtime = await createHarness(workspacePath);
|
|
579
1490
|
stderr(`Serving runtime MCP over stdio from ${workspacePath}\n`);
|
|
580
1491
|
const server = await serveRuntimeMcp(runtime);
|
|
581
1492
|
await new Promise((resolve, reject) => {
|
|
@@ -621,7 +1532,7 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
621
1532
|
return 1;
|
|
622
1533
|
}
|
|
623
1534
|
try {
|
|
624
|
-
const runtime = await createHarness(
|
|
1535
|
+
const runtime = await createHarness(resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot));
|
|
625
1536
|
try {
|
|
626
1537
|
if (exportTarget === "request") {
|
|
627
1538
|
const pkg = await runtime.exportRequestPackage({
|
|
@@ -667,8 +1578,8 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
667
1578
|
return 1;
|
|
668
1579
|
}
|
|
669
1580
|
try {
|
|
670
|
-
const
|
|
671
|
-
const
|
|
1581
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1582
|
+
const runtime = await createHarness(workspacePath);
|
|
672
1583
|
if (subcommand === "health") {
|
|
673
1584
|
const snapshot = await runtime.getHealth();
|
|
674
1585
|
stdout(parsed.json ? renderJson(snapshot) : renderHealthSnapshot(snapshot, workspacePath));
|