@botbotgo/agent-harness 0.0.298 → 0.0.300
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 +78 -38
- package/README.zh.md +80 -31
- package/dist/acp.d.ts +3 -0
- package/dist/acp.js +10 -2
- package/dist/api.d.ts +14 -2
- package/dist/api.js +19 -3
- package/dist/cli.d.ts +18 -1
- package/dist/cli.js +1408 -319
- package/dist/client/acp.d.ts +9 -3
- package/dist/client/acp.js +55 -1
- package/dist/client/in-process.d.ts +5 -2
- package/dist/client/in-process.js +4 -6
- package/dist/client/index.d.ts +1 -1
- package/dist/client/types.d.ts +6 -5
- package/dist/config/agents/direct.yaml +7 -17
- package/dist/config/agents/orchestra.yaml +9 -65
- package/dist/config/catalogs/embedding-models.yaml +1 -1
- package/dist/config/catalogs/stores.yaml +1 -1
- package/dist/config/knowledge/knowledge-runtime.yaml +36 -2
- package/dist/config/knowledge/procedural-memory-runtime.yaml +78 -0
- package/dist/config/{catalogs/models.yaml → models.yaml} +2 -2
- package/dist/config/prompts/direct-system.md +16 -0
- package/dist/config/prompts/orchestra-system.md +62 -0
- package/dist/config/prompts/routing-system.md +14 -0
- package/dist/config/runtime/runtime-memory.yaml +39 -5
- package/dist/config/runtime/workspace.yaml +7 -16
- package/dist/contracts/runtime.d.ts +242 -1
- package/dist/contracts/workspace.d.ts +2 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/init-project.js +178 -33
- package/dist/knowledge/contracts.d.ts +5 -0
- package/dist/knowledge/module.d.ts +5 -0
- package/dist/knowledge/module.js +340 -18
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +5 -1
- package/dist/persistence/file-store.js +16 -0
- package/dist/persistence/sqlite-store.d.ts +4 -1
- package/dist/persistence/sqlite-store.js +88 -14
- package/dist/persistence/types.d.ts +4 -1
- package/dist/procedural/config.d.ts +63 -0
- package/dist/procedural/config.js +125 -0
- package/dist/procedural/index.d.ts +2 -0
- package/dist/procedural/index.js +1 -0
- package/dist/protocol/ag-ui/http.d.ts +3 -0
- package/dist/protocol/ag-ui/http.js +10 -0
- package/dist/request-events.d.ts +63 -0
- package/dist/request-events.js +400 -0
- package/dist/resource/isolation.js +11 -0
- package/dist/resource/resource-impl.d.ts +1 -0
- package/dist/resource/resource-impl.js +103 -12
- package/dist/resources/init-templates/agent-context/deep-research.md +5 -0
- package/dist/resources/init-templates/prompts/research-analyst-basic.md +1 -0
- package/dist/resources/init-templates/prompts/research-analyst-web-search.md +1 -0
- package/dist/resources/init-templates/prompts/research-host-deep-research-basic.md +1 -0
- package/dist/resources/init-templates/prompts/research-host-deep-research-web-search.md +1 -0
- package/dist/resources/init-templates/prompts/research-host-single-agent-basic.md +1 -0
- package/dist/resources/init-templates/prompts/research-host-single-agent-web-search.md +1 -0
- package/dist/resources/prompts/runtime/browser-capability-disclaimer-recovery.md +1 -0
- package/dist/resources/prompts/runtime/default-subagent.md +2 -0
- package/dist/resources/prompts/runtime/durable-memory-context.md +7 -0
- package/dist/resources/prompts/runtime/execution-with-tool-evidence-retry.md +1 -0
- package/dist/resources/prompts/runtime/execution-with-tool-evidence.md +1 -0
- package/dist/resources/prompts/runtime/invalid-tool-selection-recovery.md +1 -0
- package/dist/resources/prompts/runtime/memory-manager.md +31 -0
- package/dist/resources/prompts/runtime/memory-mutation-reconciliation.md +22 -0
- package/dist/resources/prompts/runtime/slash-command-skill.md +6 -0
- package/dist/resources/prompts/runtime/strict-tool-json.md +1 -0
- package/dist/resources/prompts/runtime/workspace-boundary-guidance.md +3 -0
- package/dist/resources/prompts/runtime/workspace-relative-path.md +1 -0
- package/dist/resources/prompts/runtime/write-todos-descriptive-content.md +1 -0
- package/dist/resources/prompts/runtime/write-todos-full-entry.md +1 -0
- package/dist/resources/prompts/runtime/write-todos-non-empty-initial-list.md +1 -0
- package/dist/resources/tools/_runtime_tool_helpers.mjs +152 -0
- package/dist/resources/tools/cancel_request.mjs +21 -0
- package/dist/resources/tools/fetch_url.mjs +23 -0
- package/dist/resources/tools/http_request.mjs +30 -0
- package/dist/resources/tools/inspect_approvals.mjs +27 -0
- package/dist/resources/tools/inspect_artifacts.mjs +21 -0
- package/dist/resources/tools/inspect_events.mjs +21 -0
- package/dist/resources/tools/inspect_requests.mjs +27 -0
- package/dist/resources/tools/inspect_sessions.mjs +21 -0
- package/dist/resources/tools/list_files.mjs +27 -0
- package/dist/resources/tools/read_artifact.mjs +22 -0
- package/dist/resources/tools/request_approval.mjs +27 -0
- package/dist/resources/tools/run_command.mjs +21 -0
- package/dist/resources/tools/schedule_task.mjs +76 -0
- package/dist/resources/tools/search_files.mjs +47 -0
- package/dist/resources/tools/send_message.mjs +23 -0
- package/dist/runtime/adapter/direct-builtin-utility.d.ts +1 -0
- package/dist/runtime/adapter/direct-builtin-utility.js +90 -0
- package/dist/runtime/adapter/flow/execution-context.d.ts +1 -1
- package/dist/runtime/adapter/flow/execution-context.js +1 -1
- package/dist/runtime/adapter/flow/invocation-flow.d.ts +1 -0
- package/dist/runtime/adapter/flow/invocation-flow.js +9 -1
- package/dist/runtime/adapter/flow/invoke-runtime.d.ts +1 -1
- package/dist/runtime/adapter/flow/stream-runtime.d.ts +5 -1
- package/dist/runtime/adapter/flow/stream-runtime.js +556 -35
- package/dist/runtime/adapter/invocation-result.js +3 -2
- package/dist/runtime/adapter/local-tool-invocation.d.ts +1 -1
- package/dist/runtime/adapter/local-tool-invocation.js +28 -4
- package/dist/runtime/adapter/middleware-assembly.js +3 -1
- package/dist/runtime/adapter/model/invocation-request.d.ts +4 -1
- package/dist/runtime/adapter/model/invocation-request.js +138 -16
- package/dist/runtime/adapter/model/message-assembly.js +2 -6
- package/dist/runtime/adapter/model/model-providers.js +103 -5
- package/dist/runtime/adapter/resilience.js +17 -2
- package/dist/runtime/adapter/runtime-adapter-support.d.ts +11 -7
- package/dist/runtime/adapter/runtime-adapter-support.js +39 -5
- package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +63 -1
- package/dist/runtime/adapter/tool/builtin-middleware-tools.js +193 -21
- package/dist/runtime/adapter/tool/tool-arguments.d.ts +3 -1
- package/dist/runtime/adapter/tool/tool-arguments.js +52 -17
- package/dist/runtime/adapter/tool-resolution.d.ts +1 -0
- package/dist/runtime/adapter/tool-resolution.js +4 -2
- package/dist/runtime/agent-runtime-adapter.d.ts +27 -0
- package/dist/runtime/agent-runtime-adapter.js +163 -11
- package/dist/runtime/harness/events/event-bus.d.ts +1 -0
- package/dist/runtime/harness/events/event-bus.js +3 -0
- package/dist/runtime/harness/events/event-sink.d.ts +3 -0
- package/dist/runtime/harness/events/event-sink.js +16 -7
- package/dist/runtime/harness/events/streaming.d.ts +18 -1
- package/dist/runtime/harness/events/streaming.js +23 -10
- package/dist/runtime/harness/run/inspection.js +26 -5
- package/dist/runtime/harness/run/stream-run.d.ts +13 -4
- package/dist/runtime/harness/run/stream-run.js +448 -4
- package/dist/runtime/harness/run/surface-semantics.js +7 -34
- package/dist/runtime/harness/system/runtime-memory-manager.d.ts +3 -0
- package/dist/runtime/harness/system/runtime-memory-manager.js +384 -69
- package/dist/runtime/harness/system/runtime-memory-policy.d.ts +20 -1
- package/dist/runtime/harness/system/runtime-memory-policy.js +65 -17
- package/dist/runtime/harness/system/runtime-memory-records.js +100 -0
- package/dist/runtime/harness/system/runtime-memory-sync.js +2 -2
- package/dist/runtime/harness/system/store.d.ts +4 -0
- package/dist/runtime/harness/system/store.js +153 -0
- package/dist/runtime/harness.d.ts +9 -1
- package/dist/runtime/harness.js +141 -7
- package/dist/runtime/maintenance/sqlite-checkpoint-saver.d.ts +8 -3
- package/dist/runtime/maintenance/sqlite-checkpoint-saver.js +152 -53
- package/dist/runtime/parsing/output-parsing.d.ts +10 -2
- package/dist/runtime/parsing/output-parsing.js +223 -16
- package/dist/runtime/parsing/stream-event-parsing.d.ts +7 -0
- package/dist/runtime/parsing/stream-event-parsing.js +51 -1
- package/dist/runtime/scheduling/system-schedule-manager.d.ts +41 -0
- package/dist/runtime/scheduling/system-schedule-manager.js +532 -0
- package/dist/runtime/support/embedding-models.d.ts +1 -1
- package/dist/runtime/support/embedding-models.js +5 -2
- package/dist/runtime/support/runtime-factories.js +1 -1
- package/dist/runtime/support/runtime-layout.d.ts +3 -0
- package/dist/runtime/support/runtime-layout.js +10 -1
- package/dist/runtime/support/runtime-prompts.d.ts +30 -0
- package/dist/runtime/support/runtime-prompts.js +55 -0
- package/dist/runtime/support/vector-stores.d.ts +1 -1
- package/dist/runtime/support/vector-stores.js +5 -2
- package/dist/upstream-events.js +8 -7
- package/dist/utils/bundled-text.d.ts +3 -0
- package/dist/utils/bundled-text.js +25 -0
- package/dist/utils/id.js +3 -2
- package/dist/workspace/agent-binding-compiler.js +53 -13
- package/dist/workspace/object-loader.js +64 -2
- package/dist/workspace/support/workspace-ref-utils.d.ts +2 -1
- package/dist/workspace/support/workspace-ref-utils.js +24 -5
- package/dist/workspace/yaml-object-reader.d.ts +1 -0
- package/dist/workspace/yaml-object-reader.js +95 -17
- package/package.json +11 -5
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { createInterface as createReadlineInterface } from "node:readline";
|
|
5
5
|
import { Writable } from "node:stream";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { fileURLToPath
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
8
9
|
import YAML from "yaml";
|
|
9
10
|
import { createAgentHarness } from "./api.js";
|
|
10
|
-
import { createAcpHttpHarnessClient,
|
|
11
|
+
import { createAcpHttpHarnessClient, createInProcessHarnessClient, } from "./client.js";
|
|
11
12
|
import { initProject } from "./init-project.js";
|
|
12
13
|
import { serveA2aOverHttp } from "./protocol/a2a/http.js";
|
|
13
14
|
import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
|
|
14
15
|
import { serveAcpOverHttp } from "./protocol/acp/http.js";
|
|
15
16
|
import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
|
|
16
17
|
import { serveRuntimeMcpOverStdio } from "./mcp.js";
|
|
18
|
+
import { interpolateEnvPlaceholders } from "./workspace/yaml-object-reader.js";
|
|
17
19
|
function renderUsage() {
|
|
18
20
|
return `Usage:
|
|
19
21
|
agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
|
|
20
|
-
agent-harness chat [
|
|
22
|
+
agent-harness chat [-w <path>] [--transport stdio|http] [--host <hostname>] [--port <port>] [--agent <agentId>] [--session <sessionId>] [--message <text>]
|
|
23
|
+
agent-harness [-w <path>] [prompt]
|
|
24
|
+
botbotgo [-w <path>] [prompt]
|
|
21
25
|
agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
|
|
22
26
|
agent-harness a2a serve [--workspace <path>] [--host <hostname>] [--port <port>]
|
|
23
27
|
agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
|
|
@@ -27,9 +31,14 @@ function renderUsage() {
|
|
|
27
31
|
agent-harness runtime approvals watch [--workspace <path>] [--status <pending|approved|edited|rejected|expired>] [--poll-ms <ms>] [--once] [--json]
|
|
28
32
|
agent-harness runtime requests list [--workspace <path>] [--agent <agentId>] [--session <sessionId>] [--state <state>] [--json]
|
|
29
33
|
agent-harness runtime requests tail [--workspace <path>] [--agent <agentId>] [--session <sessionId>] [--state <state>] [--poll-ms <ms>] [--once] [--json]
|
|
34
|
+
agent-harness runtime scheduled-run --workspace <path> --schedule <scheduleId>
|
|
30
35
|
agent-harness runtime export request --workspace <path> --session <sessionId> --request <requestId> [--artifacts] [--artifact-contents] [--health] [--json]
|
|
31
36
|
agent-harness runtime export session --workspace <path> --session <sessionId> [--artifacts] [--artifact-contents] [--health] [--json]
|
|
32
37
|
agent-harness runtime-mcp serve [--workspace <path>]
|
|
38
|
+
|
|
39
|
+
Run botbotgo or agent-harness from any folder.
|
|
40
|
+
If ./config/ is absent, the runtime falls back to the bundled system defaults and bundled resources.
|
|
41
|
+
Chat defaults to the current directory as the workspace. Use -w to point at another workspace.
|
|
33
42
|
`;
|
|
34
43
|
}
|
|
35
44
|
function isTemplate(value) {
|
|
@@ -171,17 +180,26 @@ function parseChatOptions(args) {
|
|
|
171
180
|
let agentId;
|
|
172
181
|
let sessionId;
|
|
173
182
|
let message;
|
|
183
|
+
let requestEvents = false;
|
|
174
184
|
let transport = "stdio";
|
|
175
185
|
let hostname;
|
|
176
186
|
let port;
|
|
187
|
+
const positional = [];
|
|
177
188
|
for (let index = 0; index < args.length; index += 1) {
|
|
178
189
|
const arg = args[index];
|
|
179
|
-
if (arg === "--workspace"
|
|
190
|
+
if (arg === "--workspace"
|
|
191
|
+
|| arg === "-w"
|
|
192
|
+
|| arg === "--agent"
|
|
193
|
+
|| arg === "--session"
|
|
194
|
+
|| arg === "--message"
|
|
195
|
+
|| arg === "--transport"
|
|
196
|
+
|| arg === "--host"
|
|
197
|
+
|| arg === "--port") {
|
|
180
198
|
const value = args[index + 1];
|
|
181
199
|
if (!value) {
|
|
182
|
-
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Missing value for ${arg}` };
|
|
200
|
+
return { workspaceRoot, agentId, sessionId, message, requestEvents, transport, hostname, port, error: `Missing value for ${arg}` };
|
|
183
201
|
}
|
|
184
|
-
if (arg === "--workspace") {
|
|
202
|
+
if (arg === "--workspace" || arg === "-w") {
|
|
185
203
|
workspaceRoot = value;
|
|
186
204
|
}
|
|
187
205
|
else if (arg === "--agent") {
|
|
@@ -195,7 +213,7 @@ function parseChatOptions(args) {
|
|
|
195
213
|
}
|
|
196
214
|
else if (arg === "--transport") {
|
|
197
215
|
if (value !== "stdio" && value !== "http") {
|
|
198
|
-
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Unsupported chat transport: ${value}` };
|
|
216
|
+
return { workspaceRoot, agentId, sessionId, message, requestEvents, transport, hostname, port, error: `Unsupported chat transport: ${value}` };
|
|
199
217
|
}
|
|
200
218
|
transport = value;
|
|
201
219
|
}
|
|
@@ -205,16 +223,35 @@ function parseChatOptions(args) {
|
|
|
205
223
|
else {
|
|
206
224
|
const parsedPort = Number.parseInt(value, 10);
|
|
207
225
|
if (!Number.isFinite(parsedPort) || parsedPort <= 0) {
|
|
208
|
-
return { workspaceRoot, agentId, sessionId, message, transport, hostname, port, error: `Invalid chat port: ${value}` };
|
|
226
|
+
return { workspaceRoot, agentId, sessionId, message, requestEvents, transport, hostname, port, error: `Invalid chat port: ${value}` };
|
|
209
227
|
}
|
|
210
228
|
port = parsedPort;
|
|
211
229
|
}
|
|
212
230
|
index += 1;
|
|
213
231
|
continue;
|
|
214
232
|
}
|
|
215
|
-
|
|
233
|
+
if (arg === "--request-events") {
|
|
234
|
+
requestEvents = true;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (arg.startsWith("-")) {
|
|
238
|
+
return { workspaceRoot, agentId, sessionId, message, requestEvents, transport, hostname, port, error: `Unknown option: ${arg}` };
|
|
239
|
+
}
|
|
240
|
+
positional.push(arg);
|
|
216
241
|
}
|
|
217
|
-
|
|
242
|
+
if (!message && positional.length > 0) {
|
|
243
|
+
message = positional.join(" ");
|
|
244
|
+
}
|
|
245
|
+
return { workspaceRoot, agentId, sessionId, message, requestEvents, transport, hostname, port };
|
|
246
|
+
}
|
|
247
|
+
function isTopLevelCliCommand(value) {
|
|
248
|
+
return value === "init"
|
|
249
|
+
|| value === "chat"
|
|
250
|
+
|| value === "acp"
|
|
251
|
+
|| value === "a2a"
|
|
252
|
+
|| value === "ag-ui"
|
|
253
|
+
|| value === "runtime"
|
|
254
|
+
|| value === "runtime-mcp";
|
|
218
255
|
}
|
|
219
256
|
function parseRuntimeInspectOptions(args) {
|
|
220
257
|
let workspaceRoot;
|
|
@@ -351,28 +388,390 @@ function parseRuntimeExportOptions(args) {
|
|
|
351
388
|
json,
|
|
352
389
|
};
|
|
353
390
|
}
|
|
391
|
+
function parseScheduledRunOptions(args) {
|
|
392
|
+
let workspaceRoot;
|
|
393
|
+
let scheduleId;
|
|
394
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
395
|
+
const arg = args[index];
|
|
396
|
+
if (arg === "--workspace" || arg === "--schedule") {
|
|
397
|
+
const value = args[index + 1];
|
|
398
|
+
if (!value) {
|
|
399
|
+
return { workspaceRoot, scheduleId, error: `Missing value for ${arg}` };
|
|
400
|
+
}
|
|
401
|
+
if (arg === "--workspace") {
|
|
402
|
+
workspaceRoot = value;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
scheduleId = value;
|
|
406
|
+
}
|
|
407
|
+
index += 1;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
return { workspaceRoot, scheduleId, error: `Unknown option: ${arg}` };
|
|
411
|
+
}
|
|
412
|
+
return { workspaceRoot, scheduleId };
|
|
413
|
+
}
|
|
354
414
|
function resolveCliWorkspaceRoot(cwd, inputPath) {
|
|
355
415
|
const resolved = path.resolve(cwd, inputPath ?? ".");
|
|
356
|
-
|
|
357
|
-
if (existsSync(configRuntimePath)) {
|
|
416
|
+
if (existsSync(path.join(resolved, "config", "runtime", "workspace.yaml"))) {
|
|
358
417
|
return resolved;
|
|
359
418
|
}
|
|
360
|
-
|
|
361
|
-
if (existsSync(directRuntimePath) && path.basename(resolved) === "config") {
|
|
419
|
+
if (path.basename(resolved) === "config" && hasCliConfigYaml(resolved)) {
|
|
362
420
|
return path.dirname(resolved);
|
|
363
421
|
}
|
|
364
422
|
return resolved;
|
|
365
423
|
}
|
|
424
|
+
function getWorkspaceConfigPath(workspaceRoot) {
|
|
425
|
+
return path.join(workspaceRoot, "config", "runtime", "workspace.yaml");
|
|
426
|
+
}
|
|
427
|
+
function hasCliConfigYaml(configRoot) {
|
|
428
|
+
if (!existsSync(configRoot) || !statSync(configRoot).isDirectory()) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
const pending = [configRoot];
|
|
432
|
+
while (pending.length > 0) {
|
|
433
|
+
const current = pending.pop();
|
|
434
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
435
|
+
if (entry.isDirectory()) {
|
|
436
|
+
if (entry.name === "node_modules") {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
pending.push(path.join(current, entry.name));
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
function validateCliWorkspaceRoot(workspaceRoot, inputPath) {
|
|
450
|
+
if (!existsSync(workspaceRoot)) {
|
|
451
|
+
return undefined;
|
|
452
|
+
}
|
|
453
|
+
if (!statSync(workspaceRoot).isDirectory()) {
|
|
454
|
+
const workspaceLabel = inputPath ? "Workspace path" : "Current directory";
|
|
455
|
+
return `${workspaceLabel} is not a directory: ${workspaceRoot}`;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
366
459
|
function resolveCliConfigRoot(workspaceRoot) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
460
|
+
if (path.basename(workspaceRoot) === "config") {
|
|
461
|
+
return workspaceRoot;
|
|
462
|
+
}
|
|
463
|
+
return path.join(workspaceRoot, "config");
|
|
464
|
+
}
|
|
465
|
+
function frameworkCliWorkspaceRoot() {
|
|
466
|
+
const resolved = fileURLToPath(new URL("..", import.meta.url));
|
|
467
|
+
const distSegment = `${path.sep}dist`;
|
|
468
|
+
if (!resolved.endsWith(distSegment)) {
|
|
469
|
+
return resolved;
|
|
470
|
+
}
|
|
471
|
+
const sourceRoot = resolved.slice(0, -distSegment.length);
|
|
472
|
+
if (!existsSync(sourceRoot)) {
|
|
473
|
+
return resolved;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
return statSync(sourceRoot).mtimeMs >= statSync(resolved).mtimeMs ? sourceRoot : resolved;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return sourceRoot;
|
|
370
480
|
}
|
|
371
|
-
return workspaceRoot;
|
|
372
481
|
}
|
|
373
482
|
function renderJson(value) {
|
|
374
483
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
375
484
|
}
|
|
485
|
+
function formatTreeBranch(isLast) {
|
|
486
|
+
return isLast ? "└─" : "├─";
|
|
487
|
+
}
|
|
488
|
+
function formatTreeIndent(ancestorLast) {
|
|
489
|
+
return ancestorLast ? " " : "│ ";
|
|
490
|
+
}
|
|
491
|
+
function applyAnsi(text, code, enabled) {
|
|
492
|
+
if (!enabled) {
|
|
493
|
+
return text;
|
|
494
|
+
}
|
|
495
|
+
return `\x1b[${code}m${text}\x1b[0m`;
|
|
496
|
+
}
|
|
497
|
+
function renderRequestStateBadge(state, color) {
|
|
498
|
+
if (state === "completed") {
|
|
499
|
+
return color ? `\x1b[32;1m✔ DONE\x1b[0m` : "✔ DONE";
|
|
500
|
+
}
|
|
501
|
+
if (state === "running") {
|
|
502
|
+
return color ? `\x1b[36;1m▶ RUNNING\x1b[0m` : "▶ RUNNING";
|
|
503
|
+
}
|
|
504
|
+
if (state === "waiting" || state === "waiting_for_approval") {
|
|
505
|
+
return color ? `\x1b[33;1m⚠ WAITING\x1b[0m` : "⚠ WAITING";
|
|
506
|
+
}
|
|
507
|
+
if (state === "failed") {
|
|
508
|
+
return color ? `\x1b[31;1m✘ FAILED\x1b[0m` : "✘ FAILED";
|
|
509
|
+
}
|
|
510
|
+
if (state === "cancelled") {
|
|
511
|
+
return color ? `\x1b[31m○ CANCELLED\x1b[0m` : "○ CANCELLED";
|
|
512
|
+
}
|
|
513
|
+
if (state === "queued") {
|
|
514
|
+
return color ? `\x1b[38;5;242m· QUEUED\x1b[0m` : "· QUEUED";
|
|
515
|
+
}
|
|
516
|
+
return color ? `\x1b[37m${state.toUpperCase()}\x1b[0m` : state.toUpperCase();
|
|
517
|
+
}
|
|
518
|
+
function summarizeRequestPlan(snapshot) {
|
|
519
|
+
const summary = snapshot.plan.summary;
|
|
520
|
+
const eventCount = flattenRequestExecutionSteps(snapshot).length;
|
|
521
|
+
const parts = [
|
|
522
|
+
`${summary.total} todo${summary.total === 1 ? "" : "s"}`,
|
|
523
|
+
summary.inProgress > 0 ? `${summary.inProgress} active` : undefined,
|
|
524
|
+
summary.completed > 0 ? `${summary.completed} done` : undefined,
|
|
525
|
+
summary.failed > 0 ? `${summary.failed} failed` : undefined,
|
|
526
|
+
eventCount > 0 ? `${eventCount} event${eventCount === 1 ? "" : "s"}` : undefined,
|
|
527
|
+
].filter((part) => typeof part === "string");
|
|
528
|
+
return parts.join(" · ");
|
|
529
|
+
}
|
|
530
|
+
function summarizeRequestOutput(output, color) {
|
|
531
|
+
const trimmed = output.trim();
|
|
532
|
+
if (!trimmed) {
|
|
533
|
+
return applyAnsi("empty", "2", color);
|
|
534
|
+
}
|
|
535
|
+
const normalized = trimmed.replace(/\s+/g, " ");
|
|
536
|
+
const preview = normalized.length > 72 ? `${normalized.slice(0, 69)}...` : normalized;
|
|
537
|
+
return applyAnsi(preview, "2", color);
|
|
538
|
+
}
|
|
539
|
+
function formatCompactClockTime(value) {
|
|
540
|
+
const date = new Date(value);
|
|
541
|
+
if (Number.isNaN(date.getTime())) {
|
|
542
|
+
return value;
|
|
543
|
+
}
|
|
544
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
545
|
+
hour12: false,
|
|
546
|
+
hour: "2-digit",
|
|
547
|
+
minute: "2-digit",
|
|
548
|
+
second: "2-digit",
|
|
549
|
+
}).format(date);
|
|
550
|
+
}
|
|
551
|
+
function renderTimeRange(startedAt, endedAt, detail, color) {
|
|
552
|
+
if (!startedAt && !endedAt) {
|
|
553
|
+
return "";
|
|
554
|
+
}
|
|
555
|
+
const from = startedAt ? formatCompactClockTime(startedAt) : "?";
|
|
556
|
+
const to = endedAt ? formatCompactClockTime(endedAt) : "now";
|
|
557
|
+
const durationMs = typeof detail?.durationMs === "number" ? detail.durationMs : undefined;
|
|
558
|
+
const durationLabel = durationMs !== undefined
|
|
559
|
+
? ` · ${(durationMs / 1000).toFixed(durationMs >= 10_000 ? 0 : durationMs >= 1_000 ? 1 : 3)}s`
|
|
560
|
+
: "";
|
|
561
|
+
return ` ${applyAnsi(`[${from} -> ${to}${durationLabel}]`, "2", color)}`;
|
|
562
|
+
}
|
|
563
|
+
function readStepSkillNames(step) {
|
|
564
|
+
const value = step.detail?.skillNames;
|
|
565
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
566
|
+
}
|
|
567
|
+
function buildRequestStepEventKey(step) {
|
|
568
|
+
return `${step.kind}:${step.id}`;
|
|
569
|
+
}
|
|
570
|
+
function resolveEventTimestamp(step) {
|
|
571
|
+
return step.occurredAt ?? step.endedAt ?? step.startedAt;
|
|
572
|
+
}
|
|
573
|
+
function summarizeRequestStepDetail(step, displayName, color) {
|
|
574
|
+
if (step.kind === "memory" && step.detail?.source === "runtime-durable-memory") {
|
|
575
|
+
const count = typeof step.detail.count === "number" ? step.detail.count : undefined;
|
|
576
|
+
const scopes = Array.isArray(step.detail.scopes)
|
|
577
|
+
? step.detail.scopes.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
578
|
+
: [];
|
|
579
|
+
const countLabel = count !== undefined ? `${count} record${count === 1 ? "" : "s"}` : undefined;
|
|
580
|
+
const scopeLabel = scopes.length > 0 ? scopes.join(", ") : undefined;
|
|
581
|
+
const summary = typeof step.detail.summary === "string" ? step.detail.summary.trim() : "";
|
|
582
|
+
const detailLabel = [countLabel, scopeLabel].filter((value) => Boolean(value)).join(" · ");
|
|
583
|
+
if (detailLabel) {
|
|
584
|
+
return ` ${applyAnsi(`(${detailLabel})`, "2", color)}`;
|
|
585
|
+
}
|
|
586
|
+
if (summary && !summary.toLowerCase().includes(displayName.toLowerCase())) {
|
|
587
|
+
const preview = summary.length > 48 ? `${summary.slice(0, 45)}...` : summary;
|
|
588
|
+
return ` ${applyAnsi(`(${preview})`, "2", color)}`;
|
|
589
|
+
}
|
|
590
|
+
return "";
|
|
591
|
+
}
|
|
592
|
+
if (step.ownerAgentId && step.ownerAgentId !== step.agentId) {
|
|
593
|
+
return ` ${applyAnsi(`(from ${step.ownerAgentId})`, "2", color)}`;
|
|
594
|
+
}
|
|
595
|
+
return "";
|
|
596
|
+
}
|
|
597
|
+
function renderRequestStepLine(step, prefix, isLast, _activeEventId, color = false) {
|
|
598
|
+
const skillNames = step.kind === "skill" ? readStepSkillNames(step) : [];
|
|
599
|
+
const profileDisplayName = step.id.startsWith("profile:") && step.kind === "agent"
|
|
600
|
+
? `${step.name} ${step.action ?? ""}`.trim()
|
|
601
|
+
: undefined;
|
|
602
|
+
const displayName = profileDisplayName
|
|
603
|
+
? profileDisplayName
|
|
604
|
+
: step.kind === "skill" && skillNames.length === 1
|
|
605
|
+
? skillNames[0]
|
|
606
|
+
: step.kind === "skill" && skillNames.length > 1
|
|
607
|
+
? skillNames.join(", ")
|
|
608
|
+
: step.name;
|
|
609
|
+
const icon = STEP_KIND_ICON[step.kind] ?? "·";
|
|
610
|
+
const kindLabel = color
|
|
611
|
+
? `\x1b[38;5;242m${icon} ${step.kind}\x1b[0m`
|
|
612
|
+
: `${icon} ${step.kind}`;
|
|
613
|
+
const stIcon = statusIcon(step.status === "started" ? "in_progress" : step.status, color);
|
|
614
|
+
const nameFormatted = color ? `\x1b[38;5;252m${displayName}\x1b[0m` : displayName;
|
|
615
|
+
const eventTime = resolveEventTimestamp(step);
|
|
616
|
+
const timeLabel = eventTime ? applyAnsi(`[${formatCompactClockTime(eventTime)}]`, "2", color) : applyAnsi("[time?]", "2", color);
|
|
617
|
+
const detailSuffix = summarizeRequestStepDetail(step, displayName, color);
|
|
618
|
+
return `${prefix}${formatTreeBranch(isLast)} ${timeLabel} ${stIcon} ${kindLabel} ${nameFormatted}${detailSuffix}`;
|
|
619
|
+
}
|
|
620
|
+
function resolveRequestStepDisplayName(step) {
|
|
621
|
+
const skillNames = step.kind === "skill" ? readStepSkillNames(step) : [];
|
|
622
|
+
if (step.id.startsWith("profile:") && step.kind === "agent") {
|
|
623
|
+
return `${step.name} ${step.action}`.trim();
|
|
624
|
+
}
|
|
625
|
+
if (step.kind === "skill" && skillNames.length === 1) {
|
|
626
|
+
return skillNames[0];
|
|
627
|
+
}
|
|
628
|
+
if (step.kind === "skill" && skillNames.length > 1) {
|
|
629
|
+
return skillNames.join(", ");
|
|
630
|
+
}
|
|
631
|
+
return step.name;
|
|
632
|
+
}
|
|
633
|
+
function isInternalAgentLifecycleStep(step) {
|
|
634
|
+
return step.kind === "agent" && step.id.startsWith("profile:agent:");
|
|
635
|
+
}
|
|
636
|
+
function isVisibleRequestTreeStep(step) {
|
|
637
|
+
if (step.kind === "skill") {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
return !isInternalAgentLifecycleStep(step);
|
|
641
|
+
}
|
|
642
|
+
function getVisibleTodoEvents(todo) {
|
|
643
|
+
return todo.events.filter(isVisibleRequestTreeStep);
|
|
644
|
+
}
|
|
645
|
+
function getVisibleRootEvents(snapshot) {
|
|
646
|
+
return snapshot.events.filter(isVisibleRequestTreeStep);
|
|
647
|
+
}
|
|
648
|
+
function flattenRequestExecutionSteps(snapshot) {
|
|
649
|
+
return [
|
|
650
|
+
...snapshot.plan.items.flatMap((item) => getVisibleTodoEvents(item)),
|
|
651
|
+
...getVisibleRootEvents(snapshot),
|
|
652
|
+
];
|
|
653
|
+
}
|
|
654
|
+
function buildExecutionStepEventKey(step) {
|
|
655
|
+
return [
|
|
656
|
+
buildRequestStepEventKey(step),
|
|
657
|
+
step.status,
|
|
658
|
+
step.startedAt ?? "",
|
|
659
|
+
step.endedAt ?? "",
|
|
660
|
+
].join("|");
|
|
661
|
+
}
|
|
662
|
+
function renderRequestSnapshotTree(snapshot, color = false, timingSummary) {
|
|
663
|
+
const lines = [];
|
|
664
|
+
const eventSteps = flattenRequestExecutionSteps(snapshot);
|
|
665
|
+
const rootEvents = getVisibleRootEvents(snapshot);
|
|
666
|
+
// Header row: status badge + agent
|
|
667
|
+
const badge = renderRequestStateBadge(snapshot.state, color);
|
|
668
|
+
const agentLabel = snapshot.agentId
|
|
669
|
+
? (color ? `\x1b[38;5;33m${snapshot.agentId}\x1b[0m` : snapshot.agentId)
|
|
670
|
+
: applyAnsi("unknown", "2", color);
|
|
671
|
+
const headerLabel = color ? `\x1b[1;37mREQUEST\x1b[0m` : "REQUEST";
|
|
672
|
+
lines.push(`${headerLabel} ${badge} ${applyAnsi("agent:", "2", color)}${agentLabel}`);
|
|
673
|
+
// IDs (compact, muted)
|
|
674
|
+
const muted = (s) => applyAnsi(s, "2", color);
|
|
675
|
+
lines.push(`├─ ${muted("session")} ${applyAnsi(ellipsizeChatId(snapshot.sessionId, 24), "2", color)}`);
|
|
676
|
+
lines.push(`├─ ${muted("request")} ${applyAnsi(ellipsizeChatId(snapshot.requestId, 24), "2", color)}`);
|
|
677
|
+
// Summary + timing on one line
|
|
678
|
+
const planSummary = summarizeRequestPlan(snapshot);
|
|
679
|
+
const timing = timingSummary ?? "collecting";
|
|
680
|
+
lines.push(`├─ ${muted("plan")} ${applyAnsi(`v${snapshot.plan.version}`, "2", color)} ${muted("·")} ${planSummary ? applyAnsi(planSummary, "2", color) : muted("no tracked work yet")}`);
|
|
681
|
+
lines.push(`├─ ${muted("timing")} ${applyAnsi(timing, "2", color)}`);
|
|
682
|
+
// Todo items
|
|
683
|
+
lines.push(`├─ ${muted("todos")}`);
|
|
684
|
+
const planPrefix = `${formatTreeIndent(false)}`;
|
|
685
|
+
if (snapshot.plan.items.length === 0) {
|
|
686
|
+
lines.push(`${planPrefix}└─ ${muted("none yet")}`);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
snapshot.plan.items.forEach((todo, todoIndex) => {
|
|
690
|
+
const todoEvents = getVisibleTodoEvents(todo);
|
|
691
|
+
const todoIsLast = todoIndex === snapshot.plan.items.length - 1
|
|
692
|
+
&& rootEvents.length === 0
|
|
693
|
+
&& !snapshot.approval;
|
|
694
|
+
const todoActive = todo.key === snapshot.activeTodoKey
|
|
695
|
+
? (color ? ` \x1b[36m●\x1b[0m` : " ●")
|
|
696
|
+
: "";
|
|
697
|
+
const tIcon = statusIcon(todo.status, color);
|
|
698
|
+
const todoTimeRange = renderTimeRange(todo.startedAt, todo.endedAt, undefined, color);
|
|
699
|
+
const todoContent = color ? `\x1b[38;5;253m${todo.content}\x1b[0m` : todo.content;
|
|
700
|
+
lines.push(`${planPrefix}${formatTreeBranch(todoIsLast)} ${tIcon} ${todoContent}${todoTimeRange}${todoActive}`);
|
|
701
|
+
if (todoEvents.length > 0) {
|
|
702
|
+
const todoEventPrefix = `${planPrefix}${formatTreeIndent(todoIsLast)}`;
|
|
703
|
+
todoEvents.forEach((step, eventIndex) => {
|
|
704
|
+
lines.push(renderRequestStepLine(step, todoEventPrefix, eventIndex === todoEvents.length - 1, snapshot.activeEventId, color));
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
// Root-level execution events only: keep streamed data outside the tree.
|
|
710
|
+
if (rootEvents.length > 0) {
|
|
711
|
+
lines.push(`├─ ${muted("events")}`);
|
|
712
|
+
rootEvents.forEach((step, index) => {
|
|
713
|
+
lines.push(renderRequestStepLine(step, `${formatTreeIndent(false)}`, index === rootEvents.length - 1 && !snapshot.approval, snapshot.activeEventId, color));
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
// Approval
|
|
717
|
+
if (snapshot.approval) {
|
|
718
|
+
const approvalStatus = snapshot.approval.status ?? "pending";
|
|
719
|
+
const approvalColor = approvalStatus === "approved" ? "32" : approvalStatus === "rejected" ? "31" : "33";
|
|
720
|
+
lines.push(`└─ ${color ? `\x1b[33m⚠ approval\x1b[0m` : "⚠ approval"} ${muted("·")} ${snapshot.approval.toolName ?? "unknown-tool"} ${applyAnsi(`(${approvalStatus})`, approvalColor, color)}`);
|
|
721
|
+
}
|
|
722
|
+
return `${lines.join("\n")}\n`;
|
|
723
|
+
}
|
|
724
|
+
function renderRequestEventContinuation(snapshot, fromIndex, color = false) {
|
|
725
|
+
const eventSteps = flattenRequestExecutionSteps(snapshot).slice(fromIndex);
|
|
726
|
+
if (eventSteps.length === 0) {
|
|
727
|
+
return "";
|
|
728
|
+
}
|
|
729
|
+
const muted = (s) => applyAnsi(s, "2", color);
|
|
730
|
+
const lines = [`${muted("events (continued)")}`];
|
|
731
|
+
eventSteps.forEach((step, index) => {
|
|
732
|
+
lines.push(renderRequestStepLine(step, "", index === eventSteps.length - 1, snapshot.activeEventId, color));
|
|
733
|
+
});
|
|
734
|
+
return `${lines.join("\n")}\n`;
|
|
735
|
+
}
|
|
736
|
+
function buildTodoContinuationSignature(snapshot) {
|
|
737
|
+
return snapshot.plan.items
|
|
738
|
+
.map((item) => [
|
|
739
|
+
item.id ?? "",
|
|
740
|
+
item.content,
|
|
741
|
+
item.status,
|
|
742
|
+
item.ownerAgentId ?? "",
|
|
743
|
+
item.startedAt ?? "",
|
|
744
|
+
item.endedAt ?? "",
|
|
745
|
+
].join("|"))
|
|
746
|
+
.join(";");
|
|
747
|
+
}
|
|
748
|
+
function renderRequestTodoContinuation(snapshot, previousSignature, color = false) {
|
|
749
|
+
const nextSignature = buildTodoContinuationSignature(snapshot);
|
|
750
|
+
if (!nextSignature || nextSignature === previousSignature || snapshot.plan.items.length === 0) {
|
|
751
|
+
return "";
|
|
752
|
+
}
|
|
753
|
+
const muted = (s) => applyAnsi(s, "2", color);
|
|
754
|
+
const lines = [`${muted("todos (continued)")}`];
|
|
755
|
+
snapshot.plan.items.forEach((todo, index) => {
|
|
756
|
+
const tIcon = statusIcon(todo.status, color);
|
|
757
|
+
const todoTimeRange = renderTimeRange(todo.startedAt, todo.endedAt, undefined, color);
|
|
758
|
+
const todoContent = color ? `\x1b[38;5;253m${todo.content}\x1b[0m` : todo.content;
|
|
759
|
+
lines.push(`${formatTreeBranch(index === snapshot.plan.items.length - 1)} ${tIcon} ${todoContent}${todoTimeRange}`);
|
|
760
|
+
});
|
|
761
|
+
return `${lines.join("\n")}\n`;
|
|
762
|
+
}
|
|
763
|
+
function buildTerminalRequestSnapshot(snapshot, result) {
|
|
764
|
+
return {
|
|
765
|
+
...snapshot,
|
|
766
|
+
state: result.state,
|
|
767
|
+
updatedAt: new Date().toISOString(),
|
|
768
|
+
output: result.output,
|
|
769
|
+
activeEventId: undefined,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function countRenderedLines(text) {
|
|
773
|
+
return text.replace(/\n$/, "").split("\n").length;
|
|
774
|
+
}
|
|
376
775
|
function isObject(value) {
|
|
377
776
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
378
777
|
}
|
|
@@ -445,6 +844,39 @@ function renderApprovalList(approvals) {
|
|
|
445
844
|
return `${approvalId} status=${status} tool=${toolName}${sessionId}${requestId}${reason}${requested}${resolved}`;
|
|
446
845
|
}).join("\n") + "\n";
|
|
447
846
|
}
|
|
847
|
+
function renderPlanResultSummary(result) {
|
|
848
|
+
if (typeof result === "string" && result.trim().length > 0) {
|
|
849
|
+
return result.trim();
|
|
850
|
+
}
|
|
851
|
+
if (!isObject(result)) {
|
|
852
|
+
return undefined;
|
|
853
|
+
}
|
|
854
|
+
const summary = typeof result.summary === "string" && result.summary.trim().length > 0
|
|
855
|
+
? result.summary.trim()
|
|
856
|
+
: typeof result.message === "string" && result.message.trim().length > 0
|
|
857
|
+
? result.message.trim()
|
|
858
|
+
: undefined;
|
|
859
|
+
if (summary) {
|
|
860
|
+
return summary;
|
|
861
|
+
}
|
|
862
|
+
const serialized = JSON.stringify(result);
|
|
863
|
+
return serialized && serialized !== "{}" ? serialized : undefined;
|
|
864
|
+
}
|
|
865
|
+
function renderChatPlanState(planState) {
|
|
866
|
+
const lines = [`[plan v${planState.version}] ${planState.summary.total} todo(s)`];
|
|
867
|
+
if (planState.summary.total === 0) {
|
|
868
|
+
lines.push("No todos tracked yet.");
|
|
869
|
+
return `${lines.join("\n")}\n`;
|
|
870
|
+
}
|
|
871
|
+
for (const [index, item] of planState.items.entries()) {
|
|
872
|
+
lines.push(`${index + 1}. [${item.status}] ${item.content}`);
|
|
873
|
+
const resultSummary = renderPlanResultSummary(item.result);
|
|
874
|
+
if (resultSummary) {
|
|
875
|
+
lines.push(` result: ${resultSummary}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return `${lines.join("\n")}\n`;
|
|
879
|
+
}
|
|
448
880
|
function renderRequestList(requests) {
|
|
449
881
|
if (requests.length === 0) {
|
|
450
882
|
return "No requests matched.\n";
|
|
@@ -500,41 +932,64 @@ function renderOperatorOverview(overview, workspacePath) {
|
|
|
500
932
|
return `${lines.join("\n")}\n`;
|
|
501
933
|
}
|
|
502
934
|
function renderChatHelp() {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
935
|
+
const sections = [
|
|
936
|
+
{
|
|
937
|
+
heading: "Context & navigation",
|
|
938
|
+
rows: [
|
|
939
|
+
["/context", "Show current agent / session / request"],
|
|
940
|
+
["/new", "Clear session — start a fresh conversation"],
|
|
941
|
+
["/agent <id>", "Switch agent for new requests"],
|
|
942
|
+
["/session", "Print current session id"],
|
|
943
|
+
["/sessions", "List recent sessions"],
|
|
944
|
+
["/resume <id>", "Resume an existing session"],
|
|
945
|
+
["/request [id]", "Show or select active request"],
|
|
946
|
+
["/requests", "List requests for current session / agent"],
|
|
947
|
+
],
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
heading: "Approvals",
|
|
951
|
+
rows: [
|
|
952
|
+
["/approvals", "List pending approvals"],
|
|
953
|
+
["/approve <id>", "Approve a pending approval"],
|
|
954
|
+
["/reject <id>", "Reject a pending approval"],
|
|
955
|
+
["/cancel", "Cancel the latest active request"],
|
|
956
|
+
],
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
heading: "Diagnostics",
|
|
960
|
+
rows: [
|
|
961
|
+
["/events", "Persisted events for the latest request"],
|
|
962
|
+
["/trace", "Trace items for the latest request"],
|
|
963
|
+
["/health", "Runtime health snapshot"],
|
|
964
|
+
["/overview", "Runtime operator overview"],
|
|
965
|
+
],
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
heading: "Session",
|
|
969
|
+
rows: [
|
|
970
|
+
["/help", "Show this help"],
|
|
971
|
+
["/exit", "Quit chat"],
|
|
972
|
+
],
|
|
973
|
+
},
|
|
522
974
|
];
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
975
|
+
const allRows = sections.flatMap((s) => s.rows);
|
|
976
|
+
const cmdW = Math.max(...allRows.map(([cmd]) => cmd.length));
|
|
977
|
+
const out = [""];
|
|
978
|
+
for (const section of sections) {
|
|
979
|
+
out.push(` ${section.heading}`);
|
|
980
|
+
out.push(` ${"─".repeat(section.heading.length)}`);
|
|
981
|
+
for (const [cmd, desc] of section.rows) {
|
|
982
|
+
out.push(` ${cmd.padEnd(cmdW)} ${desc}`);
|
|
983
|
+
}
|
|
984
|
+
out.push("");
|
|
985
|
+
}
|
|
986
|
+
out.push(" Starter tasks:");
|
|
987
|
+
out.push(" ─────────────");
|
|
988
|
+
out.push(" Inspect this workspace and explain the main entry points.");
|
|
989
|
+
out.push(" Review this project structure before making any edits.");
|
|
990
|
+
out.push(" Find the likeliest config issue and propose the smallest fix.");
|
|
991
|
+
out.push("");
|
|
992
|
+
return out.join("\n");
|
|
538
993
|
}
|
|
539
994
|
function trimAsciiBlock(block) {
|
|
540
995
|
return block
|
|
@@ -558,8 +1013,31 @@ const CHAT_ASCII_AGENT_HARNESS = trimAsciiBlock(" _ ____ _____ _ _ ____
|
|
|
558
1013
|
* BOTBOTGO ≈ US flag: blue / white / red / white / red stripes top→bottom.
|
|
559
1014
|
* AGENT HARNESS ≈ 中国国旗: upper rows gold (星区黄), lower rows field red.
|
|
560
1015
|
*/
|
|
561
|
-
const CHAT_LOGO_BRAND_LINE_COLORS = [
|
|
562
|
-
const CHAT_LOGO_PRODUCT_LINE_COLORS = [
|
|
1016
|
+
const CHAT_LOGO_BRAND_LINE_COLORS = [25, 238, 160, 238, 160];
|
|
1017
|
+
const CHAT_LOGO_PRODUCT_LINE_COLORS = [136, 136, 160, 160, 160];
|
|
1018
|
+
/** Step-kind icons for the request tree */
|
|
1019
|
+
const STEP_KIND_ICON = {
|
|
1020
|
+
llm: "◆",
|
|
1021
|
+
tool: "⚙",
|
|
1022
|
+
skill: "★",
|
|
1023
|
+
agent: "◉",
|
|
1024
|
+
memory: "◈",
|
|
1025
|
+
approval: "⚠",
|
|
1026
|
+
};
|
|
1027
|
+
/** Status icons for todos and steps */
|
|
1028
|
+
function statusIcon(status, color) {
|
|
1029
|
+
if (status === "completed" || status === "done")
|
|
1030
|
+
return applyAnsi("✔", "32", color);
|
|
1031
|
+
if (status === "in_progress" || status === "started")
|
|
1032
|
+
return applyAnsi("▶", "36", color);
|
|
1033
|
+
if (status === "failed")
|
|
1034
|
+
return applyAnsi("✘", "31", color);
|
|
1035
|
+
if (status === "cancelled")
|
|
1036
|
+
return applyAnsi("○", "31", color);
|
|
1037
|
+
if (status === "pending" || status === "queued")
|
|
1038
|
+
return applyAnsi("·", "238", color);
|
|
1039
|
+
return applyAnsi("?", "37", color);
|
|
1040
|
+
}
|
|
563
1041
|
/** One foreground color per entire line — flat, no relief shading */
|
|
564
1042
|
function colorizeSolidAsciiBlock(block, lineColors, enabled) {
|
|
565
1043
|
const trimmed = trimAsciiBlock(block);
|
|
@@ -583,15 +1061,23 @@ function ellipsizeChatId(value, maxChars) {
|
|
|
583
1061
|
}
|
|
584
1062
|
return `${value.slice(0, maxChars - 1)}…`;
|
|
585
1063
|
}
|
|
586
|
-
function renderChatPromptLine(input) {
|
|
1064
|
+
export function renderChatPromptLine(input) {
|
|
1065
|
+
const c = input.color;
|
|
587
1066
|
const agent = input.agentId ?? "—";
|
|
588
|
-
const
|
|
589
|
-
if (!
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
1067
|
+
const sessionShort = input.sessionId ? ellipsizeChatId(input.sessionId, 8) : null;
|
|
1068
|
+
if (!c) {
|
|
1069
|
+
const ctx = sessionShort ? ` [${sessionShort}]` : "";
|
|
1070
|
+
return `\n ─────────────────────────────────────────────\n ❯ agent:${agent}${ctx} › `;
|
|
1071
|
+
}
|
|
1072
|
+
const sep = ` \x1b[38;5;236m${"─".repeat(45)}\x1b[0m`;
|
|
1073
|
+
// left: brand tag + agent
|
|
1074
|
+
const brand = `\x1b[38;5;33m◆\x1b[0m \x1b[1;37magent\x1b[0m\x1b[38;5;242m:\x1b[0m\x1b[38;5;252m${agent}\x1b[0m`;
|
|
1075
|
+
// right: session pill (muted)
|
|
1076
|
+
const sessionTag = sessionShort
|
|
1077
|
+
? ` \x1b[38;5;238m[\x1b[0m\x1b[38;5;244m${sessionShort}\x1b[0m\x1b[38;5;238m]\x1b[0m`
|
|
1078
|
+
: "";
|
|
1079
|
+
const arrow = `\x1b[38;5;33m›\x1b[0m`;
|
|
1080
|
+
return `\n${sep}\n ${brand}${sessionTag} ${arrow} `;
|
|
595
1081
|
}
|
|
596
1082
|
async function* iterateChatLines(rl, nextPrompt) {
|
|
597
1083
|
rl.setPrompt(nextPrompt());
|
|
@@ -607,49 +1093,72 @@ async function* iterateChatLines(rl, nextPrompt) {
|
|
|
607
1093
|
rl.prompt();
|
|
608
1094
|
}
|
|
609
1095
|
}
|
|
610
|
-
function renderChatBanner(input) {
|
|
1096
|
+
export function renderChatBanner(input) {
|
|
611
1097
|
const color = input.color === true;
|
|
612
|
-
|
|
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;
|
|
1098
|
+
// — Logo block (single ASCII art) —
|
|
632
1099
|
const brandArt = colorizeSolidAsciiBlock(CHAT_ASCII_BOTBOTGO, CHAT_LOGO_BRAND_LINE_COLORS, color);
|
|
633
1100
|
const productArt = colorizeSolidAsciiBlock(CHAT_ASCII_AGENT_HARNESS, CHAT_LOGO_PRODUCT_LINE_COLORS, color);
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
1101
|
+
const logoWidth = Math.max(...CHAT_ASCII_BOTBOTGO.split("\n").map((l) => l.length), ...CHAT_ASCII_AGENT_HARNESS.split("\n").map((l) => l.length));
|
|
1102
|
+
const ruleLen = Math.min(logoWidth + 4, 96);
|
|
1103
|
+
const dimRule = (ch, len) => color ? `\x1b[38;5;237m${ch.repeat(len)}\x1b[0m` : ch.repeat(len);
|
|
1104
|
+
// — Info panel —
|
|
1105
|
+
const labelColor = color ? "\x1b[38;5;242m" : "";
|
|
1106
|
+
const valueColor = color ? "\x1b[38;5;252m" : "";
|
|
1107
|
+
const accentColor = color ? "\x1b[38;5;33m" : ""; // bright cyan-blue
|
|
1108
|
+
const resetColor = color ? "\x1b[0m" : "";
|
|
1109
|
+
const mutedColor = color ? "\x1b[38;5;238m" : "";
|
|
1110
|
+
const borderColor = color ? "\x1b[38;5;237m" : "";
|
|
1111
|
+
const kvRow = (label, value, labelW, accent = false) => {
|
|
1112
|
+
const lPad = label.padEnd(labelW);
|
|
1113
|
+
const lFormatted = `${labelColor}${lPad}${resetColor}`;
|
|
1114
|
+
const vFormatted = accent
|
|
1115
|
+
? `${accentColor}${value}${resetColor}`
|
|
1116
|
+
: `${valueColor}${value}${resetColor}`;
|
|
1117
|
+
return ` ${borderColor}│${resetColor} ${lFormatted} ${mutedColor}·${resetColor} ${vFormatted}`;
|
|
1118
|
+
};
|
|
1119
|
+
const rows = [
|
|
1120
|
+
["workspace", input.workspacePath, false],
|
|
1121
|
+
["transport", input.transport, false],
|
|
1122
|
+
];
|
|
1123
|
+
if (input.agentId)
|
|
1124
|
+
rows.push(["agent", input.agentId, true]);
|
|
1125
|
+
if (input.sessionId)
|
|
1126
|
+
rows.push(["session", input.sessionId, false]);
|
|
1127
|
+
const labelW = Math.max(...rows.map(([l]) => l.length));
|
|
1128
|
+
const panelInner = Math.max(...rows.map(([, v]) => labelW + 4 + v.length), 44);
|
|
1129
|
+
const hLine = (l, m, r) => color
|
|
1130
|
+
? ` ${borderColor}${l}${resetColor}${borderColor}${m.repeat(panelInner + 2)}${r}${resetColor}`
|
|
1131
|
+
: ` ${l}${m.repeat(panelInner + 2)}${r}`;
|
|
1132
|
+
const versionTagText = "agent-harness";
|
|
1133
|
+
const versionTag = color
|
|
1134
|
+
? `${mutedColor}agent-harness${resetColor}`
|
|
1135
|
+
: versionTagText;
|
|
1136
|
+
const versionDividerSpan = Math.max(36, ruleLen);
|
|
1137
|
+
const versionGap = 2;
|
|
1138
|
+
const versionRuleBudget = Math.max(24, versionDividerSpan - versionTagText.length - versionGap * 2);
|
|
1139
|
+
const versionRuleLeftLen = Math.floor(versionRuleBudget / 2);
|
|
1140
|
+
const versionRuleRightLen = versionRuleBudget - versionRuleLeftLen;
|
|
644
1141
|
const hint = color
|
|
645
|
-
? ` \x1b[
|
|
646
|
-
: ` Type /help for commands
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
1142
|
+
? ` ${mutedColor}Type \x1b[38;5;246m/help\x1b[0m${mutedColor} for commands · \x1b[38;5;246m/exit\x1b[0m${mutedColor} to quit${resetColor}`
|
|
1143
|
+
: ` Type /help for commands · /exit to quit`;
|
|
1144
|
+
const lines = [
|
|
1145
|
+
"",
|
|
1146
|
+
brandArt,
|
|
1147
|
+
"",
|
|
1148
|
+
` ${dimRule("─", Math.max(36, ruleLen - 4))}`,
|
|
1149
|
+
"",
|
|
1150
|
+
productArt,
|
|
1151
|
+
"",
|
|
1152
|
+
` ${dimRule("─", versionRuleLeftLen)}${" ".repeat(versionGap)}${versionTag}${" ".repeat(versionGap)}${dimRule("─", versionRuleRightLen)}`,
|
|
1153
|
+
"",
|
|
1154
|
+
hLine("╭", "─", "╮"),
|
|
1155
|
+
...rows.map(([l, v, accent]) => kvRow(l, v, labelW, accent)),
|
|
1156
|
+
hLine("╰", "─", "╯"),
|
|
1157
|
+
"",
|
|
1158
|
+
hint,
|
|
1159
|
+
"",
|
|
1160
|
+
];
|
|
1161
|
+
return lines.join("\n");
|
|
653
1162
|
}
|
|
654
1163
|
function renderRequestEvents(events) {
|
|
655
1164
|
if (events.length === 0) {
|
|
@@ -688,11 +1197,14 @@ function renderRequestTraceItems(items) {
|
|
|
688
1197
|
}).join("\n") + "\n";
|
|
689
1198
|
}
|
|
690
1199
|
function renderChatContext(input) {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
]
|
|
1200
|
+
const rows = [
|
|
1201
|
+
["agent", input.agentId ?? "—"],
|
|
1202
|
+
["session", input.sessionId ?? "—"],
|
|
1203
|
+
["request", input.requestId ?? "—"],
|
|
1204
|
+
];
|
|
1205
|
+
const labelW = Math.max(...rows.map(([l]) => l.length));
|
|
1206
|
+
const lines = rows.map(([label, value]) => ` ${label.padEnd(labelW)} ${value}`);
|
|
1207
|
+
return ["", " Context", " ───────", ...lines, ""].join("\n");
|
|
696
1208
|
}
|
|
697
1209
|
function normalizeChatCommand(line) {
|
|
698
1210
|
const trimmed = line.trim();
|
|
@@ -709,40 +1221,100 @@ function asRecord(value) {
|
|
|
709
1221
|
return typeof value === "object" && value !== null ? value : undefined;
|
|
710
1222
|
}
|
|
711
1223
|
function readYamlFile(filePath) {
|
|
712
|
-
|
|
1224
|
+
const parsed = YAML.parse(readFileSync(filePath, "utf8"));
|
|
1225
|
+
return interpolateEnvPlaceholders(parsed, filePath);
|
|
713
1226
|
}
|
|
714
|
-
function
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
1227
|
+
function readCliConfigObjects(workspaceRoot) {
|
|
1228
|
+
const roots = [
|
|
1229
|
+
resolveCliConfigRoot(frameworkCliWorkspaceRoot()),
|
|
1230
|
+
resolveCliConfigRoot(workspaceRoot),
|
|
1231
|
+
];
|
|
1232
|
+
const merged = new Map();
|
|
1233
|
+
for (const configRoot of roots) {
|
|
1234
|
+
if (!hasCliConfigYaml(configRoot)) {
|
|
1235
|
+
continue;
|
|
1236
|
+
}
|
|
1237
|
+
const files = [];
|
|
1238
|
+
const pending = [configRoot];
|
|
1239
|
+
while (pending.length > 0) {
|
|
1240
|
+
const current = pending.pop();
|
|
1241
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
1242
|
+
const entryPath = path.join(current, entry.name);
|
|
1243
|
+
if (entry.isDirectory()) {
|
|
1244
|
+
if (entry.name === "node_modules") {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
pending.push(entryPath);
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
|
|
1251
|
+
files.push(entryPath);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
for (const filePath of files.sort()) {
|
|
1256
|
+
const content = readFileSync(filePath, "utf8");
|
|
1257
|
+
for (const document of YAML.parseAllDocuments(content)) {
|
|
1258
|
+
const resolved = interpolateEnvPlaceholders(document.toJSON(), filePath);
|
|
1259
|
+
const typed = asRecord(resolved);
|
|
1260
|
+
if (!typed) {
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
if (typed.kind === "Models" && Array.isArray(typed.spec)) {
|
|
1264
|
+
for (const item of typed.spec) {
|
|
1265
|
+
const record = asRecord(item);
|
|
1266
|
+
if (!record) {
|
|
1267
|
+
continue;
|
|
1268
|
+
}
|
|
1269
|
+
const id = typeof record.name === "string" ? record.name : record.id;
|
|
1270
|
+
if (typeof id !== "string" || !id) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
merged.set(`Model/${id}`, {
|
|
1274
|
+
...record,
|
|
1275
|
+
kind: "Model",
|
|
1276
|
+
id,
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const metadata = asRecord(typed.metadata);
|
|
1282
|
+
const spec = asRecord(typed.spec);
|
|
1283
|
+
if (typeof typed.kind === "string" && spec) {
|
|
1284
|
+
const id = typeof metadata?.name === "string" ? metadata.name : typed.id;
|
|
1285
|
+
if (typeof id !== "string" || !id) {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
merged.set(`${typed.kind}/${id}`, {
|
|
1289
|
+
...spec,
|
|
1290
|
+
kind: typed.kind,
|
|
1291
|
+
id,
|
|
1292
|
+
});
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
const kind = typeof typed.kind === "string" ? typed.kind : undefined;
|
|
1296
|
+
const id = typeof typed.id === "string" ? typed.id : undefined;
|
|
1297
|
+
if (!kind || !id) {
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
merged.set(`${kind}/${id}`, typed);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
718
1303
|
}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1304
|
+
return Array.from(merged.values());
|
|
1305
|
+
}
|
|
1306
|
+
function getWorkspaceDefaultAgentId(workspaceRoot) {
|
|
1307
|
+
const runtime = readCliConfigObjects(workspaceRoot).find((item) => item.kind === "Runtime" && item.id === "default");
|
|
1308
|
+
const routing = asRecord(runtime?.routing);
|
|
1309
|
+
return typeof routing?.defaultAgentId === "string" && routing.defaultAgentId.trim().length > 0 ? routing.defaultAgentId.trim() : undefined;
|
|
725
1310
|
}
|
|
726
1311
|
function getAgentModelRef(workspaceRoot, agentId) {
|
|
727
|
-
const
|
|
728
|
-
|
|
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;
|
|
1312
|
+
const agent = readCliConfigObjects(workspaceRoot).find((item) => item.kind === "Agent" && item.id === agentId);
|
|
1313
|
+
return typeof agent?.modelRef === "string" && agent.modelRef.trim().length > 0 ? agent.modelRef.trim() : undefined;
|
|
736
1314
|
}
|
|
737
1315
|
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
1316
|
const modelName = modelRef.startsWith("model/") ? modelRef.slice("model/".length) : modelRef;
|
|
745
|
-
const model =
|
|
1317
|
+
const model = readCliConfigObjects(workspaceRoot).find((item) => item.kind === "Model" && item.id === modelName);
|
|
746
1318
|
if (!model) {
|
|
747
1319
|
return undefined;
|
|
748
1320
|
}
|
|
@@ -818,6 +1390,37 @@ export function renderChatRuntimeFailure(output, modelInfo) {
|
|
|
818
1390
|
function renderChatTextChunk(text, modelInfo) {
|
|
819
1391
|
return renderChatRuntimeFailure(text, modelInfo);
|
|
820
1392
|
}
|
|
1393
|
+
function renderChatRequestRunning(input) {
|
|
1394
|
+
const parts = [
|
|
1395
|
+
input.sessionId ? `session=${input.sessionId}` : undefined,
|
|
1396
|
+
input.requestId ? `request=${input.requestId}` : undefined,
|
|
1397
|
+
input.agentId ? `agent=${input.agentId}` : undefined,
|
|
1398
|
+
"running: waiting for model output",
|
|
1399
|
+
].filter((part) => typeof part === "string" && part.length > 0);
|
|
1400
|
+
return `\n${parts.join(" ")}\n`;
|
|
1401
|
+
}
|
|
1402
|
+
function summarizeChatToolError(output) {
|
|
1403
|
+
if (typeof output === "string") {
|
|
1404
|
+
const trimmed = output.trim();
|
|
1405
|
+
return trimmed.length > 0 ? truncateChatToolPreview(trimmed, 240) : "failed";
|
|
1406
|
+
}
|
|
1407
|
+
if (typeof output === "number" || typeof output === "boolean") {
|
|
1408
|
+
return String(output);
|
|
1409
|
+
}
|
|
1410
|
+
if (!output || typeof output !== "object") {
|
|
1411
|
+
return "failed";
|
|
1412
|
+
}
|
|
1413
|
+
const typed = output;
|
|
1414
|
+
const content = extractChatToolTextContent(output);
|
|
1415
|
+
if (content && content.trim().length > 0) {
|
|
1416
|
+
return truncateChatToolPreview(content.trim(), 240);
|
|
1417
|
+
}
|
|
1418
|
+
const summary = typeof typed.summary === "object" && typed.summary !== null ? typed.summary : undefined;
|
|
1419
|
+
if (summary) {
|
|
1420
|
+
return truncateChatToolPreview(JSON.stringify(summary, null, 2), 240);
|
|
1421
|
+
}
|
|
1422
|
+
return truncateChatToolPreview(JSON.stringify(output, null, 2), 240);
|
|
1423
|
+
}
|
|
821
1424
|
function truncateChatToolPreview(value, maxChars = 800) {
|
|
822
1425
|
if (value.length <= maxChars) {
|
|
823
1426
|
return value;
|
|
@@ -876,26 +1479,11 @@ function extractChatToolTextContent(value) {
|
|
|
876
1479
|
}
|
|
877
1480
|
return "";
|
|
878
1481
|
}
|
|
879
|
-
function summarizeChatToolResult(output) {
|
|
880
|
-
if (
|
|
881
|
-
return
|
|
1482
|
+
function summarizeChatToolResult(output, isError) {
|
|
1483
|
+
if (!isError) {
|
|
1484
|
+
return "completed";
|
|
882
1485
|
}
|
|
883
|
-
|
|
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));
|
|
1486
|
+
return summarizeChatToolError(output);
|
|
899
1487
|
}
|
|
900
1488
|
export async function probeChatWorkspace(input) {
|
|
901
1489
|
const modelInfo = readChatWorkspaceModelInfo(input.workspaceRoot, input.agentId);
|
|
@@ -929,143 +1517,512 @@ export function isChatServerNoiseLine(line) {
|
|
|
929
1517
|
trimmed === "langsmith/experimental/sandbox is in alpha. This feature is experimental, and breaking changes are expected." ||
|
|
930
1518
|
trimmed === "llamaindex was already imported. This breaks constructor checks and will lead to issues!");
|
|
931
1519
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
const
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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.");
|
|
1520
|
+
const CLI_IGNORED_STDERR_WARNING_SNIPPETS = [
|
|
1521
|
+
"llamaindex was already imported. This breaks constructor checks and will lead to issues!",
|
|
1522
|
+
"MaxListenersExceededWarning: Possible EventEmitter memory leak detected.",
|
|
1523
|
+
"(Use `node --trace-warnings ...` to show where the warning was created)",
|
|
1524
|
+
];
|
|
1525
|
+
function installCliWriteListenerGuard(streams, minimum = 64) {
|
|
1526
|
+
const restore = [];
|
|
1527
|
+
for (const stream of streams) {
|
|
1528
|
+
if (!stream?.getMaxListeners || !stream?.setMaxListeners) {
|
|
1529
|
+
continue;
|
|
1530
|
+
}
|
|
1531
|
+
const previous = stream.getMaxListeners();
|
|
1532
|
+
if (previous === 0 || previous >= minimum) {
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
stream.setMaxListeners(minimum);
|
|
1536
|
+
restore.push(() => stream.setMaxListeners?.(previous));
|
|
953
1537
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1538
|
+
return () => {
|
|
1539
|
+
for (const reset of restore.reverse()) {
|
|
1540
|
+
reset();
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function installCliEmitterListenerGuard(minimum = 64) {
|
|
1545
|
+
const previous = EventEmitter.defaultMaxListeners;
|
|
1546
|
+
const hadPrototypeMaxListeners = Object.prototype.hasOwnProperty.call(EventEmitter.prototype, "_maxListeners");
|
|
1547
|
+
const previousPrototypeMaxListeners = EventEmitter.prototype._maxListeners;
|
|
1548
|
+
if (previous === 0 || previous >= minimum) {
|
|
1549
|
+
return () => undefined;
|
|
1550
|
+
}
|
|
1551
|
+
EventEmitter.defaultMaxListeners = minimum;
|
|
1552
|
+
EventEmitter.prototype._maxListeners = minimum;
|
|
1553
|
+
return () => {
|
|
1554
|
+
EventEmitter.defaultMaxListeners = previous;
|
|
1555
|
+
if (hadPrototypeMaxListeners) {
|
|
1556
|
+
EventEmitter.prototype._maxListeners = previousPrototypeMaxListeners;
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
delete EventEmitter.prototype._maxListeners;
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
function installCliStderrNoiseFilter(enabled) {
|
|
1563
|
+
if (!enabled) {
|
|
1564
|
+
return () => undefined;
|
|
1565
|
+
}
|
|
1566
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
1567
|
+
process.stderr.write = ((chunk, encoding, callback) => {
|
|
1568
|
+
const text = typeof chunk === "string"
|
|
1569
|
+
? chunk
|
|
1570
|
+
: chunk instanceof Uint8Array
|
|
1571
|
+
? Buffer.from(chunk).toString(typeof encoding === "string" ? encoding : undefined)
|
|
1572
|
+
: "";
|
|
1573
|
+
if (CLI_IGNORED_STDERR_WARNING_SNIPPETS.some((snippet) => text.includes(snippet))) {
|
|
1574
|
+
if (typeof encoding === "function") {
|
|
1575
|
+
encoding();
|
|
1576
|
+
}
|
|
1577
|
+
else if (typeof callback === "function") {
|
|
1578
|
+
callback();
|
|
963
1579
|
}
|
|
1580
|
+
return true;
|
|
964
1581
|
}
|
|
1582
|
+
if (typeof encoding === "function") {
|
|
1583
|
+
return originalStderrWrite(chunk, encoding);
|
|
1584
|
+
}
|
|
1585
|
+
return originalStderrWrite(chunk, encoding, callback);
|
|
965
1586
|
});
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1587
|
+
return () => {
|
|
1588
|
+
process.stderr.write = originalStderrWrite;
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
function installCliWarningFilter() {
|
|
1592
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
1593
|
+
process.emitWarning = ((warning, ...args) => {
|
|
1594
|
+
const text = typeof warning === "string" ? warning : warning?.message ?? "";
|
|
1595
|
+
const name = typeof warning === "string" ? String(args[1] ?? args[0] ?? "") : warning?.name ?? "";
|
|
1596
|
+
if (CLI_IGNORED_STDERR_WARNING_SNIPPETS.some((snippet) => text.includes(snippet))
|
|
1597
|
+
|| name === "MaxListenersExceededWarning") {
|
|
1598
|
+
return;
|
|
974
1599
|
}
|
|
1600
|
+
return originalEmitWarning(warning, ...args);
|
|
975
1601
|
});
|
|
1602
|
+
return () => {
|
|
1603
|
+
process.emitWarning = originalEmitWarning;
|
|
1604
|
+
};
|
|
976
1605
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1606
|
+
function terminateChatSubprocess(child, signal = "SIGTERM") {
|
|
1607
|
+
if (child.killed || child.exitCode !== null || child.signalCode !== null) {
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
try {
|
|
1611
|
+
child.kill(signal);
|
|
1612
|
+
}
|
|
1613
|
+
catch {
|
|
1614
|
+
// Ignore termination races during shutdown.
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
export function installChatSubprocessExitGuard(child, host = process) {
|
|
1618
|
+
let cleanedUp = false;
|
|
1619
|
+
let forceKillTimer;
|
|
1620
|
+
const handleParentExit = () => {
|
|
1621
|
+
terminateChatSubprocess(child, "SIGTERM");
|
|
1622
|
+
if (!forceKillTimer && child.exitCode === null && child.signalCode === null) {
|
|
1623
|
+
forceKillTimer = setTimeout(() => {
|
|
1624
|
+
terminateChatSubprocess(child, "SIGKILL");
|
|
1625
|
+
}, 2_000);
|
|
1626
|
+
forceKillTimer.unref?.();
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
const cleanup = () => {
|
|
1630
|
+
if (cleanedUp) {
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
cleanedUp = true;
|
|
1634
|
+
if (forceKillTimer) {
|
|
1635
|
+
clearTimeout(forceKillTimer);
|
|
1636
|
+
forceKillTimer = undefined;
|
|
1637
|
+
}
|
|
1638
|
+
host.off("beforeExit", handleParentExit);
|
|
1639
|
+
host.off("exit", handleParentExit);
|
|
1640
|
+
child.off("close", cleanup);
|
|
1641
|
+
child.off("exit", cleanup);
|
|
1003
1642
|
};
|
|
1643
|
+
host.on("beforeExit", handleParentExit);
|
|
1644
|
+
host.on("exit", handleParentExit);
|
|
1645
|
+
child.on("close", cleanup);
|
|
1646
|
+
child.on("exit", cleanup);
|
|
1647
|
+
return cleanup;
|
|
1004
1648
|
}
|
|
1005
1649
|
async function streamChatMessage(input) {
|
|
1650
|
+
const requestStartedAt = Date.now();
|
|
1651
|
+
let firstSnapshotAt;
|
|
1652
|
+
let firstDataAt;
|
|
1653
|
+
let latestSnapshot;
|
|
1006
1654
|
let latestSessionId = input.sessionId;
|
|
1007
1655
|
let latestRequestId;
|
|
1008
1656
|
let latestAgentId = input.agentId;
|
|
1009
1657
|
let wroteContent = false;
|
|
1010
1658
|
let wroteRenderableBlocks = false;
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1659
|
+
let lastRenderedRequestTreeKey;
|
|
1660
|
+
let lastRenderedRequestTree = "";
|
|
1661
|
+
let lastRenderedRequestTreeLineCount = 0;
|
|
1662
|
+
let lastRenderedRequestTreeAt = 0;
|
|
1663
|
+
let announcedRunningState = false;
|
|
1664
|
+
let requestTreeVisible = false;
|
|
1665
|
+
let requestTreePersisted = false;
|
|
1666
|
+
let liveRequestAnnotations = [];
|
|
1667
|
+
let persistedRequestTreeEventCount = 0;
|
|
1668
|
+
let persistedRequestTreeTodoSignature = "";
|
|
1669
|
+
const requestTreeRenderThrottleMs = 75;
|
|
1670
|
+
let suppressRequestTreeRendering = false;
|
|
1671
|
+
let lastStableRequestTreeKey;
|
|
1672
|
+
let stdoutWriteChain = Promise.resolve();
|
|
1673
|
+
let stderrWriteChain = Promise.resolve();
|
|
1674
|
+
const enqueueChatWrite = (sink, _stream, chain, message) => chain.then(async () => {
|
|
1675
|
+
sink(message);
|
|
1676
|
+
});
|
|
1677
|
+
const summarizeRequestTreeSteps = (steps) => steps
|
|
1678
|
+
.map((step) => [
|
|
1679
|
+
step.id ?? "",
|
|
1680
|
+
step.kind,
|
|
1681
|
+
step.name,
|
|
1682
|
+
step.status,
|
|
1683
|
+
step.startedAt ?? "",
|
|
1684
|
+
step.endedAt ?? "",
|
|
1685
|
+
].join("|"))
|
|
1686
|
+
.join(";");
|
|
1687
|
+
const buildRequestSnapshotRenderKey = (snapshot) => {
|
|
1688
|
+
const todoSignatures = snapshot.plan.items
|
|
1689
|
+
.map((item) => [
|
|
1690
|
+
item.id ?? "",
|
|
1691
|
+
item.content,
|
|
1692
|
+
item.status,
|
|
1693
|
+
item.ownerAgentId ?? "",
|
|
1694
|
+
item.startedAt ?? "",
|
|
1695
|
+
item.endedAt ?? "",
|
|
1696
|
+
item.result === undefined ? "" : String(item.result),
|
|
1697
|
+
summarizeRequestTreeSteps(item.events),
|
|
1698
|
+
].join("|"))
|
|
1699
|
+
.join(";");
|
|
1700
|
+
const approvalSignature = snapshot.approval
|
|
1701
|
+
? [
|
|
1702
|
+
snapshot.approval.approvalId,
|
|
1703
|
+
snapshot.approval.status,
|
|
1704
|
+
snapshot.approval.toolName ?? "",
|
|
1705
|
+
].join("|")
|
|
1706
|
+
: "";
|
|
1707
|
+
return [
|
|
1708
|
+
snapshot.sessionId,
|
|
1709
|
+
snapshot.requestId,
|
|
1710
|
+
snapshot.state,
|
|
1711
|
+
snapshot.agentId ?? "",
|
|
1712
|
+
snapshot.plan.version,
|
|
1713
|
+
snapshot.plan.updatedAt,
|
|
1714
|
+
snapshot.plan.summary.total,
|
|
1715
|
+
snapshot.plan.summary.inProgress,
|
|
1716
|
+
snapshot.plan.summary.completed,
|
|
1717
|
+
snapshot.plan.summary.failed,
|
|
1718
|
+
approvalSignature,
|
|
1719
|
+
summarizeRequestTreeSteps(snapshot.events),
|
|
1720
|
+
todoSignatures,
|
|
1721
|
+
].join("||");
|
|
1722
|
+
};
|
|
1723
|
+
const buildPlanSnapshotRenderKey = (snapshot) => [
|
|
1724
|
+
snapshot.plan.version,
|
|
1725
|
+
snapshot.plan.updatedAt,
|
|
1726
|
+
snapshot.plan.summary.total,
|
|
1727
|
+
snapshot.plan.summary.inProgress,
|
|
1728
|
+
snapshot.plan.summary.completed,
|
|
1729
|
+
snapshot.plan.summary.failed,
|
|
1730
|
+
snapshot.plan.items
|
|
1731
|
+
.map((item) => [
|
|
1732
|
+
item.id ?? "",
|
|
1733
|
+
item.content,
|
|
1734
|
+
item.status,
|
|
1735
|
+
item.ownerAgentId ?? "",
|
|
1736
|
+
item.startedAt ?? "",
|
|
1737
|
+
item.endedAt ?? "",
|
|
1738
|
+
item.result === undefined ? "" : String(item.result),
|
|
1739
|
+
].join("|"))
|
|
1740
|
+
.join(";"),
|
|
1741
|
+
].join("||");
|
|
1742
|
+
const formatPerfClock = (timestamp) => {
|
|
1743
|
+
const value = new Date(timestamp);
|
|
1744
|
+
return value.toLocaleTimeString("en-US", {
|
|
1745
|
+
hour12: false,
|
|
1746
|
+
hour: "2-digit",
|
|
1747
|
+
minute: "2-digit",
|
|
1748
|
+
second: "2-digit",
|
|
1749
|
+
fractionalSecondDigits: 3,
|
|
1750
|
+
});
|
|
1751
|
+
};
|
|
1752
|
+
const formatElapsed = (timestamp) => `${((timestamp - requestStartedAt) / 1000).toFixed(3)}s`;
|
|
1753
|
+
const buildTimingSummary = (completedAt) => {
|
|
1754
|
+
const parts = [`start ${formatPerfClock(requestStartedAt)}`];
|
|
1755
|
+
if (firstSnapshotAt) {
|
|
1756
|
+
parts.push(`first event +${formatElapsed(firstSnapshotAt)}`);
|
|
1023
1757
|
}
|
|
1024
|
-
if (
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1758
|
+
if (firstDataAt) {
|
|
1759
|
+
parts.push(`first data +${formatElapsed(firstDataAt)}`);
|
|
1760
|
+
}
|
|
1761
|
+
if (completedAt) {
|
|
1762
|
+
parts.push(`done +${formatElapsed(completedAt)}`);
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
parts.push(`live +${formatElapsed(Date.now())}`);
|
|
1766
|
+
}
|
|
1767
|
+
return parts.join(" · ");
|
|
1768
|
+
};
|
|
1769
|
+
const buildLiveRequestTreeBlock = () => {
|
|
1770
|
+
if (!lastRenderedRequestTree) {
|
|
1771
|
+
return "";
|
|
1772
|
+
}
|
|
1773
|
+
const annotationBlock = liveRequestAnnotations.length > 0
|
|
1774
|
+
? `\n${liveRequestAnnotations.join("")}`
|
|
1775
|
+
: "";
|
|
1776
|
+
return `${lastRenderedRequestTree}${annotationBlock}`;
|
|
1777
|
+
};
|
|
1778
|
+
const clearLiveRequestTree = () => {
|
|
1779
|
+
if (!input.requestEvents || !input.liveRequestTree || !requestTreeVisible || lastRenderedRequestTreeLineCount <= 0) {
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, `\x1b[${lastRenderedRequestTreeLineCount}F\x1b[0J`);
|
|
1783
|
+
requestTreeVisible = false;
|
|
1784
|
+
};
|
|
1785
|
+
const drawLiveRequestTree = () => {
|
|
1786
|
+
if (!input.requestEvents || !input.liveRequestTree || !lastRenderedRequestTree) {
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const block = buildLiveRequestTreeBlock();
|
|
1790
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, block);
|
|
1791
|
+
lastRenderedRequestTreeLineCount = countRenderedLines(block);
|
|
1792
|
+
requestTreeVisible = true;
|
|
1793
|
+
};
|
|
1794
|
+
const persistLiveRequestTree = () => {
|
|
1795
|
+
if (!input.requestEvents || !input.liveRequestTree || requestTreePersisted || !lastRenderedRequestTree) {
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (requestTreeVisible && lastRenderedRequestTreeLineCount > 0) {
|
|
1799
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, `\x1b[${lastRenderedRequestTreeLineCount}F\x1b[0J`);
|
|
1800
|
+
requestTreeVisible = false;
|
|
1801
|
+
}
|
|
1802
|
+
const combinedBlock = buildLiveRequestTreeBlock();
|
|
1803
|
+
const treeBlock = combinedBlock.endsWith("\n")
|
|
1804
|
+
? combinedBlock
|
|
1805
|
+
: `${combinedBlock}\n`;
|
|
1806
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, treeBlock);
|
|
1807
|
+
persistedRequestTreeEventCount = latestSnapshot ? flattenRequestExecutionSteps(latestSnapshot).length : 0;
|
|
1808
|
+
persistedRequestTreeTodoSignature = latestSnapshot ? buildTodoContinuationSignature(latestSnapshot) : "";
|
|
1809
|
+
requestTreePersisted = true;
|
|
1810
|
+
};
|
|
1811
|
+
const enqueueOrderedStdout = (message) => {
|
|
1812
|
+
const barrier = Promise.allSettled([stdoutWriteChain, stderrWriteChain]).then(() => undefined);
|
|
1813
|
+
stdoutWriteChain = enqueueChatWrite(input.stdout, input.stdoutStream, barrier, message);
|
|
1814
|
+
};
|
|
1815
|
+
const writeChatStdout = (message) => {
|
|
1816
|
+
if (input.requestEvents && input.liveRequestTree && !suppressRequestTreeRendering) {
|
|
1817
|
+
clearLiveRequestTree();
|
|
1818
|
+
enqueueOrderedStdout(message);
|
|
1819
|
+
drawLiveRequestTree();
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
if (input.requestEvents && input.liveRequestTree) {
|
|
1823
|
+
enqueueOrderedStdout(message);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
stdoutWriteChain = enqueueChatWrite(input.stdout, input.stdoutStream, stdoutWriteChain, message);
|
|
1827
|
+
};
|
|
1828
|
+
const writeChatStderr = (message) => {
|
|
1829
|
+
if (input.requestEvents && input.liveRequestTree && !suppressRequestTreeRendering) {
|
|
1830
|
+
clearLiveRequestTree();
|
|
1831
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, message);
|
|
1832
|
+
drawLiveRequestTree();
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, message);
|
|
1836
|
+
};
|
|
1837
|
+
const renderPlanSnapshot = (snapshot) => {
|
|
1838
|
+
return;
|
|
1839
|
+
};
|
|
1840
|
+
const suspendRequestTreeRendering = () => {
|
|
1841
|
+
if (!input.requestEvents || !input.liveRequestTree) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
persistLiveRequestTree();
|
|
1845
|
+
suppressRequestTreeRendering = true;
|
|
1846
|
+
clearLiveRequestTree();
|
|
1847
|
+
};
|
|
1848
|
+
const renderExecutionStepEvents = (snapshot) => {
|
|
1849
|
+
return;
|
|
1850
|
+
};
|
|
1851
|
+
const renderContentBlocks = (contentBlocks, agentId) => {
|
|
1852
|
+
latestAgentId = agentId || latestAgentId;
|
|
1853
|
+
if (!wroteContent) {
|
|
1854
|
+
const rendered = contentBlocks
|
|
1855
|
+
.map((block) => {
|
|
1856
|
+
if (typeof block === "string") {
|
|
1857
|
+
return block;
|
|
1044
1858
|
}
|
|
1859
|
+
if (block && typeof block === "object" && "text" in block && typeof block.text === "string") {
|
|
1860
|
+
return block.text;
|
|
1861
|
+
}
|
|
1862
|
+
return "";
|
|
1863
|
+
})
|
|
1864
|
+
.filter((block) => block.trim().length > 0)
|
|
1865
|
+
.join("");
|
|
1866
|
+
if (rendered) {
|
|
1867
|
+
if (input.requestEvents) {
|
|
1868
|
+
suspendRequestTreeRendering();
|
|
1869
|
+
}
|
|
1870
|
+
writeChatStdout(renderChatTextChunk(rendered, input.modelInfo));
|
|
1871
|
+
wroteRenderableBlocks = true;
|
|
1045
1872
|
}
|
|
1046
|
-
continue;
|
|
1047
1873
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
input.
|
|
1053
|
-
|
|
1874
|
+
};
|
|
1875
|
+
let result;
|
|
1876
|
+
try {
|
|
1877
|
+
result = await input.client.request({
|
|
1878
|
+
...(input.agentId ? { agentId: input.agentId } : {}),
|
|
1879
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
1880
|
+
input: input.message,
|
|
1881
|
+
eventListener(snapshot) {
|
|
1882
|
+
latestSessionId = snapshot.sessionId || latestSessionId;
|
|
1883
|
+
latestRequestId = snapshot.requestId || latestRequestId;
|
|
1884
|
+
latestAgentId = snapshot.agentId || latestAgentId;
|
|
1885
|
+
latestSnapshot = snapshot;
|
|
1886
|
+
firstSnapshotAt ??= Date.now();
|
|
1887
|
+
if (input.requestEvents && !suppressRequestTreeRendering) {
|
|
1888
|
+
const now = Date.now();
|
|
1889
|
+
const nextTreeKey = buildRequestSnapshotRenderKey(snapshot);
|
|
1890
|
+
const terminalSnapshot = snapshot.state !== "queued" && snapshot.state !== "claimed" && snapshot.state !== "running";
|
|
1891
|
+
const snapshotCarriesRenderableOutput = snapshot.output.trim().length > 0;
|
|
1892
|
+
const shouldRenderSnapshot = !snapshotCarriesRenderableOutput
|
|
1893
|
+
&& nextTreeKey !== lastRenderedRequestTreeKey
|
|
1894
|
+
&& nextTreeKey !== lastStableRequestTreeKey
|
|
1895
|
+
&& ((terminalSnapshot && !input.liveRequestTree)
|
|
1896
|
+
|| !input.liveRequestTree
|
|
1897
|
+
|| now - lastRenderedRequestTreeAt >= requestTreeRenderThrottleMs);
|
|
1898
|
+
if (shouldRenderSnapshot) {
|
|
1899
|
+
lastRenderedRequestTree = renderRequestSnapshotTree(snapshot, input.colorRequestTree === true, buildTimingSummary());
|
|
1900
|
+
if (input.liveRequestTree) {
|
|
1901
|
+
clearLiveRequestTree();
|
|
1902
|
+
drawLiveRequestTree();
|
|
1903
|
+
}
|
|
1904
|
+
else {
|
|
1905
|
+
input.stderr(`\n${lastRenderedRequestTree}`);
|
|
1906
|
+
}
|
|
1907
|
+
lastRenderedRequestTreeKey = nextTreeKey;
|
|
1908
|
+
if (!snapshot.activeEventId && snapshot.state !== "running") {
|
|
1909
|
+
lastStableRequestTreeKey = nextTreeKey;
|
|
1910
|
+
}
|
|
1911
|
+
lastRenderedRequestTreeAt = now;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
if ((input.showRunningState ?? true) && !input.requestEvents && !announcedRunningState && !wroteContent && !wroteRenderableBlocks) {
|
|
1915
|
+
input.stderr(renderChatRequestRunning({
|
|
1916
|
+
sessionId: snapshot.sessionId,
|
|
1917
|
+
requestId: snapshot.requestId,
|
|
1918
|
+
agentId: snapshot.agentId,
|
|
1919
|
+
}));
|
|
1920
|
+
announcedRunningState = true;
|
|
1921
|
+
}
|
|
1922
|
+
renderExecutionStepEvents(snapshot);
|
|
1923
|
+
renderPlanSnapshot({
|
|
1924
|
+
sessionId: snapshot.sessionId,
|
|
1925
|
+
requestId: snapshot.requestId,
|
|
1926
|
+
plan: {
|
|
1927
|
+
version: snapshot.plan.version,
|
|
1928
|
+
updatedAt: snapshot.plan.updatedAt,
|
|
1929
|
+
items: snapshot.plan.items.map((item) => ({
|
|
1930
|
+
id: item.id,
|
|
1931
|
+
content: item.content,
|
|
1932
|
+
status: item.status,
|
|
1933
|
+
ownerAgentId: item.ownerAgentId,
|
|
1934
|
+
startedAt: item.startedAt,
|
|
1935
|
+
endedAt: item.endedAt,
|
|
1936
|
+
result: item.result,
|
|
1937
|
+
metadata: item.metadata,
|
|
1938
|
+
})),
|
|
1939
|
+
summary: snapshot.plan.summary,
|
|
1940
|
+
},
|
|
1941
|
+
});
|
|
1942
|
+
},
|
|
1943
|
+
dataListener(delta) {
|
|
1944
|
+
latestSessionId = delta.sessionId || latestSessionId;
|
|
1945
|
+
latestRequestId = delta.requestId || latestRequestId;
|
|
1946
|
+
firstDataAt ??= Date.now();
|
|
1947
|
+
if (delta.type === "output.text.delta") {
|
|
1948
|
+
latestAgentId = delta.agentId || latestAgentId;
|
|
1949
|
+
if (input.requestEvents) {
|
|
1950
|
+
suspendRequestTreeRendering();
|
|
1951
|
+
}
|
|
1952
|
+
writeChatStdout(renderChatTextChunk(delta.text, input.modelInfo));
|
|
1953
|
+
wroteContent = true;
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (delta.type === "output.content-blocks") {
|
|
1957
|
+
suspendRequestTreeRendering();
|
|
1958
|
+
renderContentBlocks(delta.contentBlocks, delta.agentId);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
if (delta.type === "tool.result") {
|
|
1962
|
+
latestAgentId = delta.agentId || latestAgentId;
|
|
1963
|
+
if ((input.showToolResults ?? true) && !input.requestEvents) {
|
|
1964
|
+
writeChatStderr(`\n[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}] [tool:${delta.toolName}] ${summarizeChatToolResult(delta.output, delta.isError === true)}${delta.isError ? " (error)" : ""}\n`);
|
|
1965
|
+
}
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (delta.type === "progress.commentary") {
|
|
1969
|
+
latestAgentId = delta.agentId || latestAgentId;
|
|
1970
|
+
if (wroteContent || wroteRenderableBlocks) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const progressLine = `[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}] ${delta.text}\n`;
|
|
1974
|
+
if (input.requestEvents && input.liveRequestTree && !suppressRequestTreeRendering && lastRenderedRequestTree) {
|
|
1975
|
+
liveRequestAnnotations.push(progressLine);
|
|
1976
|
+
clearLiveRequestTree();
|
|
1977
|
+
drawLiveRequestTree();
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
writeChatStderr(`[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}] ${delta.text}\n`);
|
|
1981
|
+
}
|
|
1982
|
+
},
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
catch (error) {
|
|
1986
|
+
throw error;
|
|
1987
|
+
}
|
|
1988
|
+
if (!result) {
|
|
1989
|
+
throw new Error("chat request completed without a terminal result");
|
|
1990
|
+
}
|
|
1991
|
+
latestSessionId = result.sessionId;
|
|
1992
|
+
latestRequestId = result.requestId;
|
|
1993
|
+
latestAgentId = result.agentId ?? latestAgentId;
|
|
1994
|
+
const completedAt = Date.now();
|
|
1995
|
+
if (input.requestEvents && latestSnapshot) {
|
|
1996
|
+
const terminalSnapshot = buildTerminalRequestSnapshot(latestSnapshot, { state: result.state, output: result.output });
|
|
1997
|
+
lastRenderedRequestTree = renderRequestSnapshotTree(terminalSnapshot, input.colorRequestTree === true, buildTimingSummary(completedAt));
|
|
1998
|
+
if (input.liveRequestTree && !requestTreePersisted) {
|
|
1999
|
+
clearLiveRequestTree();
|
|
2000
|
+
drawLiveRequestTree();
|
|
1054
2001
|
}
|
|
1055
|
-
if (
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (
|
|
1059
|
-
input.
|
|
2002
|
+
else if (input.liveRequestTree && requestTreePersisted) {
|
|
2003
|
+
const todoContinuation = renderRequestTodoContinuation(terminalSnapshot, persistedRequestTreeTodoSignature, input.colorRequestTree === true);
|
|
2004
|
+
const continuation = renderRequestEventContinuation(terminalSnapshot, persistedRequestTreeEventCount, input.colorRequestTree === true);
|
|
2005
|
+
if (todoContinuation) {
|
|
2006
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, `\n${todoContinuation}`);
|
|
1060
2007
|
}
|
|
1061
|
-
if (
|
|
1062
|
-
input.stderr
|
|
1063
|
-
}
|
|
1064
|
-
else if (wroteContent || wroteRenderableBlocks || item.result.output.trim().length > 0) {
|
|
1065
|
-
input.stdout("\n");
|
|
2008
|
+
if (continuation) {
|
|
2009
|
+
stderrWriteChain = enqueueChatWrite(input.stderr, input.stderrStream, stderrWriteChain, `\n${continuation}`);
|
|
1066
2010
|
}
|
|
1067
2011
|
}
|
|
2012
|
+
else if (!input.liveRequestTree) {
|
|
2013
|
+
input.stderr(`\n${lastRenderedRequestTree}`);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (!wroteContent && !wroteRenderableBlocks && result.output.trim().length > 0) {
|
|
2017
|
+
writeChatStdout(renderChatTextChunk(result.output, input.modelInfo));
|
|
2018
|
+
}
|
|
2019
|
+
if (result.state === "waiting_for_approval") {
|
|
2020
|
+
writeChatStderr(`\nRequest is waiting for approval${result.approvalId ? ` (${result.approvalId})` : ""}.\n`);
|
|
1068
2021
|
}
|
|
2022
|
+
else if ((wroteContent || wroteRenderableBlocks || result.output.trim().length > 0)) {
|
|
2023
|
+
writeChatStdout("\n");
|
|
2024
|
+
}
|
|
2025
|
+
await Promise.allSettled([stdoutWriteChain, stderrWriteChain]);
|
|
1069
2026
|
return { sessionId: latestSessionId, requestId: latestRequestId, agentId: latestAgentId };
|
|
1070
2027
|
}
|
|
1071
2028
|
export async function runCli(argv, io = {}, deps = {}) {
|
|
@@ -1073,8 +2030,9 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1073
2030
|
const stdin = io.stdin ?? process.stdin;
|
|
1074
2031
|
const stdout = io.stdout ?? ((message) => process.stdout.write(message));
|
|
1075
2032
|
const stderr = io.stderr ?? ((message) => process.stderr.write(message));
|
|
2033
|
+
const stdoutStream = io.stdout === undefined ? process.stdout : undefined;
|
|
2034
|
+
const stderrStream = io.stderr === undefined ? process.stderr : undefined;
|
|
1076
2035
|
const [command, projectName, ...rest] = argv;
|
|
1077
|
-
const createChatClient = deps.createChatClient ?? createSubprocessChatClient;
|
|
1078
2036
|
const probeWorkspace = deps.probeChatWorkspace ?? probeChatWorkspace;
|
|
1079
2037
|
const createChatLineReader = deps.createReadlineInterface ?? createReadlineInterface;
|
|
1080
2038
|
const createHarness = deps.createAgentHarness ?? createAgentHarness;
|
|
@@ -1083,47 +2041,34 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1083
2041
|
const serveAcpHttp = deps.serveAcpOverHttp ?? serveAcpOverHttp;
|
|
1084
2042
|
const serveAcp = deps.serveAcpOverStdio ?? serveAcpOverStdio;
|
|
1085
2043
|
const serveRuntimeMcp = deps.serveRuntimeMcpOverStdio ?? serveRuntimeMcpOverStdio;
|
|
1086
|
-
|
|
1087
|
-
if (
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (parsed.error) {
|
|
1093
|
-
stderr(`${parsed.error}\n`);
|
|
1094
|
-
stderr(renderUsage());
|
|
1095
|
-
return 1;
|
|
1096
|
-
}
|
|
1097
|
-
try {
|
|
1098
|
-
const projectRoot = path.resolve(cwd, projectName);
|
|
1099
|
-
const result = await initProject(projectRoot, projectName, parsed.options);
|
|
1100
|
-
stdout(`Created ${result.projectSlug} at ${result.projectRoot}\n`);
|
|
1101
|
-
stdout("Next steps:\n");
|
|
1102
|
-
stdout(` cd ${projectName}\n`);
|
|
1103
|
-
stdout(" npm install\n");
|
|
1104
|
-
if ((parsed.options?.provider ?? "openai") === "openai") {
|
|
1105
|
-
stdout(" export OPENAI_API_KEY=your_key_here\n");
|
|
1106
|
-
}
|
|
1107
|
-
stdout(' npm run start -- "Research a topic"\n');
|
|
1108
|
-
return 0;
|
|
1109
|
-
}
|
|
1110
|
-
catch (error) {
|
|
1111
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1112
|
-
stderr(`${message}\n`);
|
|
1113
|
-
return 1;
|
|
2044
|
+
const createChatClient = deps.createChatClient ?? (async (input) => {
|
|
2045
|
+
if (input.transport === "http") {
|
|
2046
|
+
return createAcpHttpHarnessClient({
|
|
2047
|
+
rpcUrl: `http://${input.hostname ?? "127.0.0.1"}:${input.port ?? 8787}/rpc`,
|
|
2048
|
+
eventsUrl: `http://${input.hostname ?? "127.0.0.1"}:${input.port ?? 8787}/events`,
|
|
2049
|
+
});
|
|
1114
2050
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
2051
|
+
const runtime = await createHarness(input.workspaceRoot);
|
|
2052
|
+
return createInProcessHarnessClient(runtime);
|
|
2053
|
+
});
|
|
2054
|
+
const executeChat = async (parsed) => {
|
|
2055
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2056
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2057
|
+
if (workspaceError) {
|
|
2058
|
+
stderr(`${workspaceError}\n`);
|
|
1121
2059
|
return 1;
|
|
1122
2060
|
}
|
|
1123
|
-
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
1124
2061
|
const workspaceModelInfo = readChatWorkspaceModelInfo(workspacePath, parsed.agentId);
|
|
2062
|
+
const chatStdinStream = stdin;
|
|
1125
2063
|
let client;
|
|
2064
|
+
let restoreWriteListenerGuard;
|
|
2065
|
+
let restoreEmitterListenerGuard;
|
|
2066
|
+
let restoreStderrNoiseFilter;
|
|
2067
|
+
let restoreWarningFilter;
|
|
1126
2068
|
try {
|
|
2069
|
+
restoreEmitterListenerGuard = installCliEmitterListenerGuard();
|
|
2070
|
+
restoreStderrNoiseFilter = installCliStderrNoiseFilter(io.stderr === undefined);
|
|
2071
|
+
restoreWarningFilter = installCliWarningFilter();
|
|
1127
2072
|
client = await createChatClient({
|
|
1128
2073
|
workspaceRoot: workspacePath,
|
|
1129
2074
|
transport: parsed.transport,
|
|
@@ -1131,34 +2076,41 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1131
2076
|
port: parsed.port,
|
|
1132
2077
|
stderr,
|
|
1133
2078
|
});
|
|
2079
|
+
restoreWriteListenerGuard = installCliWriteListenerGuard([stdoutStream, stderrStream]);
|
|
1134
2080
|
let activeAgentId = parsed.agentId;
|
|
1135
2081
|
let activeSessionId = parsed.sessionId;
|
|
1136
2082
|
let latestRequestId;
|
|
1137
|
-
const
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
2083
|
+
const useColor = false;
|
|
2084
|
+
const preflightWarning = parsed.message
|
|
2085
|
+
? undefined
|
|
2086
|
+
: await probeWorkspace({
|
|
2087
|
+
workspaceRoot: workspacePath,
|
|
2088
|
+
agentId: activeAgentId,
|
|
2089
|
+
});
|
|
1141
2090
|
if (parsed.message) {
|
|
1142
|
-
|
|
2091
|
+
await streamChatMessage({
|
|
1143
2092
|
client,
|
|
1144
2093
|
stdout,
|
|
2094
|
+
stdoutStream,
|
|
1145
2095
|
stderr,
|
|
2096
|
+
stderrStream,
|
|
1146
2097
|
agentId: activeAgentId,
|
|
1147
2098
|
sessionId: activeSessionId,
|
|
1148
2099
|
message: parsed.message,
|
|
1149
2100
|
modelInfo: workspaceModelInfo,
|
|
2101
|
+
requestEvents: parsed.requestEvents,
|
|
2102
|
+
liveRequestTree: parsed.requestEvents && chatStdinStream.isTTY === true,
|
|
2103
|
+
colorRequestTree: useColor,
|
|
2104
|
+
showToolResults: parsed.requestEvents,
|
|
2105
|
+
showRunningState: false,
|
|
1150
2106
|
});
|
|
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
2107
|
await client.stop();
|
|
2108
|
+
restoreWriteListenerGuard?.();
|
|
2109
|
+
restoreEmitterListenerGuard?.();
|
|
2110
|
+
restoreStderrNoiseFilter?.();
|
|
2111
|
+
restoreWarningFilter?.();
|
|
1158
2112
|
return 0;
|
|
1159
2113
|
}
|
|
1160
|
-
const stdinStream = stdin;
|
|
1161
|
-
const useColor = stdinStream.isTTY === true && io.stdout === undefined;
|
|
1162
2114
|
stdout(renderChatBanner({
|
|
1163
2115
|
workspacePath,
|
|
1164
2116
|
transport: parsed.transport,
|
|
@@ -1180,7 +2132,7 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1180
2132
|
input: stdin,
|
|
1181
2133
|
output: stdoutSink,
|
|
1182
2134
|
crlfDelay: Infinity,
|
|
1183
|
-
terminal:
|
|
2135
|
+
terminal: chatStdinStream.isTTY === true,
|
|
1184
2136
|
});
|
|
1185
2137
|
try {
|
|
1186
2138
|
for await (const raw of iterateChatLines(lineReader, () => renderChatPromptLine({
|
|
@@ -1198,11 +2150,18 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1198
2150
|
const streamed = await streamChatMessage({
|
|
1199
2151
|
client,
|
|
1200
2152
|
stdout,
|
|
2153
|
+
stdoutStream,
|
|
1201
2154
|
stderr,
|
|
2155
|
+
stderrStream,
|
|
1202
2156
|
agentId: activeAgentId,
|
|
1203
2157
|
sessionId: activeSessionId,
|
|
1204
2158
|
message: trimmed,
|
|
1205
2159
|
modelInfo: workspaceModelInfo,
|
|
2160
|
+
requestEvents: parsed.requestEvents,
|
|
2161
|
+
liveRequestTree: parsed.requestEvents && chatStdinStream.isTTY === true,
|
|
2162
|
+
colorRequestTree: useColor,
|
|
2163
|
+
showToolResults: parsed.requestEvents,
|
|
2164
|
+
showRunningState: false,
|
|
1206
2165
|
});
|
|
1207
2166
|
activeSessionId = streamed.sessionId;
|
|
1208
2167
|
latestRequestId = streamed.requestId;
|
|
@@ -1364,9 +2323,17 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1364
2323
|
lineReader.close();
|
|
1365
2324
|
}
|
|
1366
2325
|
await client.stop();
|
|
2326
|
+
restoreWriteListenerGuard?.();
|
|
2327
|
+
restoreEmitterListenerGuard?.();
|
|
2328
|
+
restoreStderrNoiseFilter?.();
|
|
2329
|
+
restoreWarningFilter?.();
|
|
1367
2330
|
return 0;
|
|
1368
2331
|
}
|
|
1369
2332
|
catch (error) {
|
|
2333
|
+
restoreWriteListenerGuard?.();
|
|
2334
|
+
restoreEmitterListenerGuard?.();
|
|
2335
|
+
restoreStderrNoiseFilter?.();
|
|
2336
|
+
restoreWarningFilter?.();
|
|
1370
2337
|
const message = error instanceof Error ? error.message : String(error);
|
|
1371
2338
|
stderr(`${message}\n`);
|
|
1372
2339
|
if (client) {
|
|
@@ -1374,6 +2341,54 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1374
2341
|
}
|
|
1375
2342
|
return 1;
|
|
1376
2343
|
}
|
|
2344
|
+
};
|
|
2345
|
+
if (command === "init") {
|
|
2346
|
+
if (!projectName?.trim()) {
|
|
2347
|
+
stderr(renderUsage());
|
|
2348
|
+
return 1;
|
|
2349
|
+
}
|
|
2350
|
+
const parsed = parseInitOptions(rest);
|
|
2351
|
+
if (parsed.error) {
|
|
2352
|
+
stderr(`${parsed.error}\n`);
|
|
2353
|
+
stderr(renderUsage());
|
|
2354
|
+
return 1;
|
|
2355
|
+
}
|
|
2356
|
+
try {
|
|
2357
|
+
const projectRoot = path.resolve(cwd, projectName);
|
|
2358
|
+
const result = await initProject(projectRoot, projectName, parsed.options);
|
|
2359
|
+
stdout(`Created ${result.projectSlug} at ${result.projectRoot}\n`);
|
|
2360
|
+
stdout("Next steps:\n");
|
|
2361
|
+
stdout(` cd ${projectName}\n`);
|
|
2362
|
+
stdout(" npm install\n");
|
|
2363
|
+
if ((parsed.options?.provider ?? "openai") === "openai") {
|
|
2364
|
+
stdout(" export OPENAI_API_KEY=your_key_here\n");
|
|
2365
|
+
}
|
|
2366
|
+
stdout(' npm run start -- "Research a topic"\n');
|
|
2367
|
+
return 0;
|
|
2368
|
+
}
|
|
2369
|
+
catch (error) {
|
|
2370
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2371
|
+
stderr(`${message}\n`);
|
|
2372
|
+
return 1;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (command === "chat") {
|
|
2376
|
+
const parsed = parseChatOptions([projectName, ...rest].filter((item) => typeof item === "string"));
|
|
2377
|
+
if (parsed.error) {
|
|
2378
|
+
stderr(`${parsed.error}\n`);
|
|
2379
|
+
stderr(renderUsage());
|
|
2380
|
+
return 1;
|
|
2381
|
+
}
|
|
2382
|
+
return executeChat(parsed);
|
|
2383
|
+
}
|
|
2384
|
+
if (!isTopLevelCliCommand(command)) {
|
|
2385
|
+
const parsed = parseChatOptions(argv);
|
|
2386
|
+
if (parsed.error) {
|
|
2387
|
+
stderr(`${parsed.error}\n`);
|
|
2388
|
+
stderr(renderUsage());
|
|
2389
|
+
return 1;
|
|
2390
|
+
}
|
|
2391
|
+
return executeChat(parsed);
|
|
1377
2392
|
}
|
|
1378
2393
|
if (command === "acp") {
|
|
1379
2394
|
const [subcommand, ...subcommandArgs] = [projectName, ...rest];
|
|
@@ -1389,6 +2404,11 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1389
2404
|
}
|
|
1390
2405
|
try {
|
|
1391
2406
|
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2407
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2408
|
+
if (workspaceError) {
|
|
2409
|
+
stderr(`${workspaceError}\n`);
|
|
2410
|
+
return 1;
|
|
2411
|
+
}
|
|
1392
2412
|
const runtime = await createHarness(workspacePath);
|
|
1393
2413
|
if (parsed.transport === "http") {
|
|
1394
2414
|
const server = await serveAcpHttp(runtime, {
|
|
@@ -1426,6 +2446,11 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1426
2446
|
}
|
|
1427
2447
|
try {
|
|
1428
2448
|
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2449
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2450
|
+
if (workspaceError) {
|
|
2451
|
+
stderr(`${workspaceError}\n`);
|
|
2452
|
+
return 1;
|
|
2453
|
+
}
|
|
1429
2454
|
const runtime = await createHarness(workspacePath);
|
|
1430
2455
|
const server = await serveAgUi(runtime, {
|
|
1431
2456
|
hostname: parsed.hostname,
|
|
@@ -1456,6 +2481,11 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1456
2481
|
}
|
|
1457
2482
|
try {
|
|
1458
2483
|
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2484
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2485
|
+
if (workspaceError) {
|
|
2486
|
+
stderr(`${workspaceError}\n`);
|
|
2487
|
+
return 1;
|
|
2488
|
+
}
|
|
1459
2489
|
const runtime = await createHarness(workspacePath);
|
|
1460
2490
|
const server = await serveA2a(runtime, {
|
|
1461
2491
|
hostname: parsed.hostname,
|
|
@@ -1486,6 +2516,11 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1486
2516
|
}
|
|
1487
2517
|
try {
|
|
1488
2518
|
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2519
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2520
|
+
if (workspaceError) {
|
|
2521
|
+
stderr(`${workspaceError}\n`);
|
|
2522
|
+
return 1;
|
|
2523
|
+
}
|
|
1489
2524
|
const runtime = await createHarness(workspacePath);
|
|
1490
2525
|
stderr(`Serving runtime MCP over stdio from ${workspacePath}\n`);
|
|
1491
2526
|
const server = await serveRuntimeMcp(runtime);
|
|
@@ -1509,6 +2544,41 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1509
2544
|
stderr(renderUsage());
|
|
1510
2545
|
return 1;
|
|
1511
2546
|
}
|
|
2547
|
+
if (subcommand === "scheduled-run") {
|
|
2548
|
+
const parsed = parseScheduledRunOptions([possibleNestedCommand, possibleThirdCommand, ...remainingArgs].filter((item) => typeof item === "string"));
|
|
2549
|
+
if (parsed.error) {
|
|
2550
|
+
stderr(`${parsed.error}\n`);
|
|
2551
|
+
stderr(renderUsage());
|
|
2552
|
+
return 1;
|
|
2553
|
+
}
|
|
2554
|
+
if (!parsed.scheduleId) {
|
|
2555
|
+
stderr("Missing value for --schedule\n");
|
|
2556
|
+
stderr(renderUsage());
|
|
2557
|
+
return 1;
|
|
2558
|
+
}
|
|
2559
|
+
try {
|
|
2560
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2561
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2562
|
+
if (workspaceError) {
|
|
2563
|
+
stderr(`${workspaceError}\n`);
|
|
2564
|
+
return 1;
|
|
2565
|
+
}
|
|
2566
|
+
const runtime = await createHarness(workspacePath);
|
|
2567
|
+
try {
|
|
2568
|
+
const result = await runtime.runScheduledTask(parsed.scheduleId);
|
|
2569
|
+
stdout(`${String(result.output ?? "").trim()}\n`);
|
|
2570
|
+
}
|
|
2571
|
+
finally {
|
|
2572
|
+
await runtime.stop();
|
|
2573
|
+
}
|
|
2574
|
+
return 0;
|
|
2575
|
+
}
|
|
2576
|
+
catch (error) {
|
|
2577
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2578
|
+
stderr(`${message}\n`);
|
|
2579
|
+
return 1;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
1512
2582
|
if (subcommand === "export") {
|
|
1513
2583
|
const exportTarget = possibleNestedCommand;
|
|
1514
2584
|
const parsed = parseRuntimeExportOptions([possibleThirdCommand, ...remainingArgs].filter((item) => typeof item === "string"));
|
|
@@ -1532,7 +2602,13 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1532
2602
|
return 1;
|
|
1533
2603
|
}
|
|
1534
2604
|
try {
|
|
1535
|
-
const
|
|
2605
|
+
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2606
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2607
|
+
if (workspaceError) {
|
|
2608
|
+
stderr(`${workspaceError}\n`);
|
|
2609
|
+
return 1;
|
|
2610
|
+
}
|
|
2611
|
+
const runtime = await createHarness(workspacePath);
|
|
1536
2612
|
try {
|
|
1537
2613
|
if (exportTarget === "request") {
|
|
1538
2614
|
const pkg = await runtime.exportRequestPackage({
|
|
@@ -1579,6 +2655,11 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1579
2655
|
}
|
|
1580
2656
|
try {
|
|
1581
2657
|
const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
|
|
2658
|
+
const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
|
|
2659
|
+
if (workspaceError) {
|
|
2660
|
+
stderr(`${workspaceError}\n`);
|
|
2661
|
+
return 1;
|
|
2662
|
+
}
|
|
1582
2663
|
const runtime = await createHarness(workspacePath);
|
|
1583
2664
|
if (subcommand === "health") {
|
|
1584
2665
|
const snapshot = await runtime.getHealth();
|
|
@@ -1643,7 +2724,15 @@ export async function runCli(argv, io = {}, deps = {}) {
|
|
|
1643
2724
|
stderr(renderUsage());
|
|
1644
2725
|
return 1;
|
|
1645
2726
|
}
|
|
1646
|
-
|
|
2727
|
+
export function resolveInvokedCliHref(argvPath) {
|
|
2728
|
+
if (!argvPath) {
|
|
2729
|
+
return "";
|
|
2730
|
+
}
|
|
2731
|
+
const resolved = path.resolve(argvPath);
|
|
2732
|
+
const realPath = existsSync(resolved) ? realpathSync(resolved) : resolved;
|
|
2733
|
+
return pathToFileURL(realPath).href;
|
|
2734
|
+
}
|
|
2735
|
+
const invokedPath = resolveInvokedCliHref(process.argv[1]);
|
|
1647
2736
|
if (import.meta.url === invokedPath) {
|
|
1648
2737
|
const exitCode = await runCli(process.argv.slice(2));
|
|
1649
2738
|
process.exitCode = exitCode;
|