@best-agent/cli 0.0.2-beta.8 → 0.0.2-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -181
  3. package/dist/launcher.js +32 -0
  4. package/package.json +15 -27
  5. package/dist/best-agent-native-bridge.d.ts +0 -389
  6. package/dist/best-agent-native-bridge.js +0 -4645
  7. package/dist/cli-storage.d.ts +0 -5
  8. package/dist/cli-storage.js +0 -7
  9. package/dist/dimsdk/ask-user.d.ts +0 -21
  10. package/dist/dimsdk/ask-user.js +0 -131
  11. package/dist/dimsdk/cli/version.d.ts +0 -6
  12. package/dist/dimsdk/cli/version.js +0 -36
  13. package/dist/dimsdk/compat/archships/dim-agent/runtime-host.d.ts +0 -49
  14. package/dist/dimsdk/compat/archships/dim-agent/runtime-host.js +0 -215
  15. package/dist/dimsdk/compat/runtime-host.d.ts +0 -2
  16. package/dist/dimsdk/compat/runtime-host.js +0 -2
  17. package/dist/dimsdk/core/debug-log.d.ts +0 -8
  18. package/dist/dimsdk/core/debug-log.js +0 -100
  19. package/dist/dimsdk/core/env.d.ts +0 -26
  20. package/dist/dimsdk/core/env.js +0 -130
  21. package/dist/dimsdk/core/markdown/sanitize.d.ts +0 -2
  22. package/dist/dimsdk/core/markdown/sanitize.js +0 -43
  23. package/dist/dimsdk/core/markdown/wrap.d.ts +0 -9
  24. package/dist/dimsdk/core/markdown/wrap.js +0 -139
  25. package/dist/dimsdk/core/markdown.d.ts +0 -18
  26. package/dist/dimsdk/core/markdown.js +0 -62
  27. package/dist/dimsdk/core/model.d.ts +0 -273
  28. package/dist/dimsdk/core/model.js +0 -16
  29. package/dist/dimsdk/core/observability/perf-log.d.ts +0 -18
  30. package/dist/dimsdk/core/observability/perf-log.js +0 -236
  31. package/dist/dimsdk/core/store/agent-event-mappers.d.ts +0 -26
  32. package/dist/dimsdk/core/store/agent-event-mappers.js +0 -109
  33. package/dist/dimsdk/core/store/message-mutations.d.ts +0 -105
  34. package/dist/dimsdk/core/store/message-mutations.js +0 -1528
  35. package/dist/dimsdk/core/store/semantic-readers.d.ts +0 -2
  36. package/dist/dimsdk/core/store/semantic-readers.js +0 -4
  37. package/dist/dimsdk/core/store/transcript-projector.d.ts +0 -164
  38. package/dist/dimsdk/core/store/transcript-projector.js +0 -580
  39. package/dist/dimsdk/core/tool-metadata.d.ts +0 -12
  40. package/dist/dimsdk/core/tool-metadata.js +0 -70
  41. package/dist/dimsdk/core/transcript-part-id.d.ts +0 -6
  42. package/dist/dimsdk/core/transcript-part-id.js +0 -22
  43. package/dist/dimsdk/i18n/index.d.ts +0 -18
  44. package/dist/dimsdk/i18n/index.js +0 -141
  45. package/dist/dimsdk/i18n/locales/en.d.ts +0 -988
  46. package/dist/dimsdk/i18n/locales/en.js +0 -987
  47. package/dist/dimsdk/i18n/locales/index.d.ts +0 -1976
  48. package/dist/dimsdk/i18n/locales/index.js +0 -8
  49. package/dist/dimsdk/i18n/locales/zh-CN.d.ts +0 -988
  50. package/dist/dimsdk/i18n/locales/zh-CN.js +0 -987
  51. package/dist/dimsdk/i18n/types.d.ts +0 -13
  52. package/dist/dimsdk/i18n/types.js +0 -2
  53. package/dist/dimsdk/shared/value-normalizers.d.ts +0 -7
  54. package/dist/dimsdk/shared/value-normalizers.js +0 -48
  55. package/dist/dimsdk-tui.bundle-size.json +0 -169
  56. package/dist/dimsdk-tui.bundle.js +0 -330756
  57. package/dist/goatchain-adapter.d.ts +0 -41
  58. package/dist/goatchain-adapter.js +0 -499
  59. package/dist/index.d.ts +0 -3
  60. package/dist/index.js +0 -1405
  61. package/dist/native-runtime-bridge.d.ts +0 -387
  62. package/dist/native-runtime-bridge.js +0 -4624
  63. package/dist/provider-config-env.d.ts +0 -7
  64. package/dist/provider-config-env.js +0 -286
  65. package/dist/provider-model-limit-index.generated.d.ts +0 -3
  66. package/dist/provider-model-limit-index.generated.js +0 -7818
  67. package/dist/provider-model-limits.d.ts +0 -8
  68. package/dist/provider-model-limits.js +0 -43
  69. package/dist/runtime.d.ts +0 -47
  70. package/dist/runtime.js +0 -516
package/dist/index.js DELETED
@@ -1,1405 +0,0 @@
1
- #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
3
- import { createInterface } from "node:readline";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, resolve } from "node:path";
6
- import { config as loadEnvConfig } from "dotenv";
7
- import { markStartup, withStartupSpan, withStartupSpanAsync } from "@best-agent/foundation";
8
- import { getProviderConfigFromEnv, parseProviderCompatibilityMode, parseProviderKind, } from "./provider-config-env.js";
9
- import { resolveCliVersion } from "./dimsdk/cli/version.js";
10
- import { resolveCliStorageRoot } from "./cli-storage.js";
11
- const DEFAULT_CLI_DAEMON_IDLE_EXIT_MS = "10000";
12
- const CLI_VERSION = resolveCliVersion()?.version ?? "0.0.0";
13
- markStartup("cli.module.loaded", `argc=${Math.max(0, process.argv.length - 2)}`);
14
- async function main() {
15
- const rawArgs = process.argv.slice(2);
16
- const [rawCommand, ...rawCommandArgs] = rawArgs;
17
- const command = rawCommand ?? "chat";
18
- const args = rawCommand === undefined ? [] : rawCommandArgs;
19
- markStartup("cli.main.start", `command=${command}`);
20
- withStartupSpan("cli.loadDotEnv", undefined, () => loadDotEnv(resolveCliEnvRoot(args)));
21
- if (command === "server") {
22
- const options = parseCommonOptions(args);
23
- const { runStdioServer } = await withStartupSpanAsync("cli.importServer", "command=server", () => import("@best-agent/server"));
24
- markStartup("cli.server.start");
25
- await runStdioServer(options);
26
- return;
27
- }
28
- if (command === "daemon") {
29
- const options = parseCommonOptions(args);
30
- const { runSocketDaemon } = await withStartupSpanAsync("cli.importServer", "command=daemon", () => import("@best-agent/server"));
31
- markStartup("cli.daemon.start");
32
- await runSocketDaemon(options);
33
- return;
34
- }
35
- if (command === "serve") {
36
- const options = parseServeOptions(args);
37
- const { runHttpServer } = await withStartupSpanAsync("cli.importServer", "command=serve", () => import("@best-agent/server"));
38
- markStartup("cli.http.start");
39
- await runHttpServer(options);
40
- return;
41
- }
42
- if (command === "acp") {
43
- const options = parseCommonOptions(args);
44
- const { runStdioAcpServer } = await withStartupSpanAsync("cli.importServer", "command=acp", () => import("@best-agent/server"));
45
- markStartup("cli.acp.start");
46
- await runStdioAcpServer(options);
47
- return;
48
- }
49
- if (command === "run") {
50
- const { prompt, ...options } = parseRunOptions(args);
51
- if (!prompt) {
52
- process.stderr.write("Usage: best-agent run [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--agent <name>] [--cwd <path>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>] [--plan] <prompt>\n");
53
- process.exitCode = 1;
54
- return;
55
- }
56
- markStartup("cli.run.start");
57
- await runSinglePrompt(prompt, options);
58
- return;
59
- }
60
- if (command === "chat") {
61
- const options = parseChatOptions(args);
62
- if (shouldUseTuiChat()) {
63
- const tuiBundle = "./dimsdk-tui.bundle.js";
64
- const { runDimSdkTuiChat } = await withStartupSpanAsync("cli.importTuiBundle", undefined, () => import(tuiBundle));
65
- markStartup("cli.chat.tui.start");
66
- await runDimSdkTuiChat(options);
67
- return;
68
- }
69
- markStartup("cli.chat.legacy.start");
70
- await runInteractiveChat(options);
71
- return;
72
- }
73
- process.stderr.write("Usage: best-agent server [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>]\n");
74
- process.stderr.write(" or: best-agent daemon [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>]\n");
75
- process.stderr.write(" or: best-agent serve [--host <host>] [--port <port>] [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>]\n");
76
- process.stderr.write(" or: best-agent acp [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>]\n");
77
- process.stderr.write(" or: best-agent run [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--agent <name>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>] [--plan] <prompt>\n");
78
- process.stderr.write(" or: best-agent chat [--storage-root <path>] [--daemon-runtime-dir <path>] [--extensions-root <path>] [--agent <name>] [--cwd <path>] [--provider <kind>] [--model <id>] [--base-url <url>] [--compatibility <auto|native|compatible>] [--approval-timeout-ms <ms>] [--guardian-enabled <true|false>] [--guardian-model <id>] [--guardian-threshold <0-100>] [--guardian-timeout-ms <ms>] [--resume <thread-id>] [--latest] [--plan] [--no-mouse] [--no-alt-screen]\n");
79
- process.exitCode = 1;
80
- }
81
- async function runSinglePrompt(prompt, options) {
82
- const entrypoint = process.argv[1];
83
- if (!entrypoint) {
84
- throw new Error("CLI entrypoint path is not available.");
85
- }
86
- const storageRoot = resolveCliStorageRoot(options);
87
- const terminal = createTerminalPrompter();
88
- const { connectDaemonClient } = await withStartupSpanAsync("cli.importSdk", "mode=run", () => import("@best-agent/sdk-ts"));
89
- const client = await withStartupSpanAsync("cli.connectDaemon", "mode=run", () => connectDaemonClient(process.execPath, [entrypoint], {
90
- approvalTimeoutMs: options.approvalTimeoutMs,
91
- cwd: options.cwd,
92
- daemonRuntimeDir: options.daemonRuntimeDir,
93
- env: buildCliDaemonEnv(),
94
- extensionsRoot: options.extensionsRoot,
95
- guardian: options.guardian,
96
- providerConfig: options.providerConfig,
97
- storageRoot,
98
- }));
99
- let inputQueue = Promise.resolve();
100
- const unsubscribe = client.onEvent((event) => {
101
- const runtimeEvent = event.event;
102
- if (runtimeEvent.type === "approval.requested") {
103
- inputQueue = inputQueue.then(async () => {
104
- const resolution = await promptForApprovalWithInterface(runtimeEvent.approval, terminal);
105
- await client.respondToApproval(runtimeEvent.approval.id, resolution.decision, resolution.scope);
106
- });
107
- return;
108
- }
109
- if (runtimeEvent.type === "action.required" && runtimeEvent.kind === "ask_user") {
110
- inputQueue = inputQueue.then(async () => {
111
- const answers = await promptForAskUserWithInterface(runtimeEvent, terminal);
112
- await client.respondToAction(runtimeEvent.actionId, answers);
113
- });
114
- }
115
- });
116
- try {
117
- await withStartupSpanAsync("cli.daemonClient.initialize", "mode=run", () => client.initialize("best-agent-cli-run", CLI_VERSION));
118
- const { thread } = await withStartupSpanAsync("cli.startThread", "mode=run", () => client.startThread({ mode: options.threadMode }));
119
- const result = await client.run(thread.id, buildRunPrompt(prompt, options.agentProfile));
120
- await inputQueue;
121
- const resumed = await client.resumeThread(thread.id);
122
- const displayText = findLatestDisplayText(resumed.items);
123
- if (displayText) {
124
- process.stdout.write(`${displayText}\n`);
125
- return;
126
- }
127
- process.stdout.write(`${result.turn.status}\n`);
128
- }
129
- finally {
130
- unsubscribe();
131
- terminal.close();
132
- client.close();
133
- }
134
- }
135
- async function runInteractiveChat(options) {
136
- const entrypoint = process.argv[1];
137
- if (!entrypoint) {
138
- throw new Error("CLI entrypoint path is not available.");
139
- }
140
- const storageRoot = resolveCliStorageRoot(options);
141
- const { connectDaemonClient } = await withStartupSpanAsync("cli.importSdk", "mode=chat", () => import("@best-agent/sdk-ts"));
142
- const client = await withStartupSpanAsync("cli.connectDaemon", "mode=chat", () => connectDaemonClient(process.execPath, [entrypoint], {
143
- approvalTimeoutMs: options.approvalTimeoutMs,
144
- cwd: options.cwd,
145
- daemonRuntimeDir: options.daemonRuntimeDir,
146
- env: buildCliDaemonEnv(),
147
- extensionsRoot: options.extensionsRoot,
148
- guardian: options.guardian,
149
- providerConfig: options.providerConfig,
150
- storageRoot,
151
- }));
152
- try {
153
- await withStartupSpanAsync("cli.daemonClient.initialize", "mode=chat", () => client.initialize("best-agent-cli", CLI_VERSION));
154
- const thread = await withStartupSpanAsync("cli.resolveInitialChatThread", undefined, () => resolveInitialChatThread(client, options));
155
- let activeAgentProfile = options.agentProfile;
156
- let activeThreadId = thread.id;
157
- let activeThreadMode = thread.mode;
158
- const activeSkillIds = new Set();
159
- let hasSkillOverride = false;
160
- const terminal = createTerminalPrompter();
161
- process.stdout.write(`thread> ${activeThreadId}\n`);
162
- process.stdout.write(`mode> ${activeThreadMode}\n`);
163
- if (activeAgentProfile) {
164
- process.stdout.write(`agent> ${activeAgentProfile}\n`);
165
- }
166
- if (options.providerConfig) {
167
- process.stdout.write(`provider> ${options.providerConfig.kind}/${options.providerConfig.model}\n`);
168
- }
169
- else {
170
- process.stdout.write("provider> none (tool-only prompts will still work)\n");
171
- }
172
- process.stdout.write("type /exit to quit\n");
173
- try {
174
- while (true) {
175
- const prompt = ((await terminal.ask("you> ", process.stderr)) ?? "/exit").trim();
176
- if (!prompt) {
177
- continue;
178
- }
179
- if (prompt === "/exit" || prompt === "/quit") {
180
- return;
181
- }
182
- if (prompt === "/help") {
183
- process.stdout.write("commands> /help /exit /quit /thread /threads [all] /resume <threadId> /new [title] /archive [threadId] /unarchive <threadId> /delete [threadId] /status /memory [show|set|add|edit|clear] /diagnostics /compact /forks /agents /agent <name|clear> /plan <on|off|status|show|todos|approve|reject|export> /skills /use-skill <id> /drop-skill <id> /tools /handoff /research [runId] /evidence [runId] /fork [title]\n");
184
- continue;
185
- }
186
- if (prompt === "/thread") {
187
- process.stdout.write(`thread> ${activeThreadId}\n`);
188
- continue;
189
- }
190
- if (prompt === "/threads" || prompt === "/threads all" || prompt === "/threads --all") {
191
- const listed = await client.listThreads({
192
- includeArchived: prompt !== "/threads",
193
- limit: 50,
194
- });
195
- if (listed.threads.length === 0) {
196
- process.stdout.write("threads> (none)\n");
197
- continue;
198
- }
199
- for (const thread of listed.threads) {
200
- const selected = thread.id === activeThreadId ? "*" : " ";
201
- process.stdout.write(`${selected} ${thread.id} [${thread.status}] - ${thread.title}\n`);
202
- }
203
- continue;
204
- }
205
- if (prompt.startsWith("/resume ")) {
206
- const threadId = prompt.slice("/resume ".length).trim();
207
- if (!threadId) {
208
- process.stdout.write("error> Missing thread id\n");
209
- continue;
210
- }
211
- try {
212
- const resumed = await resumeChatThread(client, threadId);
213
- activeThreadId = resumed.id;
214
- activeThreadMode = resumed.mode;
215
- }
216
- catch (error) {
217
- process.stdout.write(`error> ${error instanceof Error ? error.message : "Unknown thread error"}\n`);
218
- continue;
219
- }
220
- activeSkillIds.clear();
221
- hasSkillOverride = false;
222
- process.stdout.write(`thread> ${activeThreadId}\n`);
223
- process.stdout.write(`mode> ${activeThreadMode}\n`);
224
- continue;
225
- }
226
- if (prompt === "/new" || prompt.startsWith("/new ")) {
227
- const title = prompt === "/new" ? undefined : prompt.slice("/new ".length).trim() || undefined;
228
- const { thread } = await client.startThread({ mode: activeThreadMode, title });
229
- activeThreadId = thread.id;
230
- activeThreadMode = thread.mode;
231
- activeSkillIds.clear();
232
- hasSkillOverride = false;
233
- process.stdout.write(`thread> ${activeThreadId}\n`);
234
- process.stdout.write(`mode> ${activeThreadMode}\n`);
235
- continue;
236
- }
237
- if (prompt === "/archive" || prompt.startsWith("/archive ")) {
238
- const targetThreadId = prompt === "/archive" ? activeThreadId : prompt.slice("/archive ".length).trim();
239
- if (!targetThreadId) {
240
- process.stdout.write("error> Missing thread id\n");
241
- continue;
242
- }
243
- try {
244
- await client.archiveThread(targetThreadId);
245
- }
246
- catch (error) {
247
- process.stdout.write(`error> ${error instanceof Error ? error.message : "Unknown thread error"}\n`);
248
- continue;
249
- }
250
- process.stdout.write(`archived> ${targetThreadId}\n`);
251
- if (targetThreadId === activeThreadId) {
252
- const fallback = await resolveFallbackChatThread(client, targetThreadId, activeThreadMode);
253
- activeThreadId = fallback.id;
254
- activeThreadMode = fallback.mode;
255
- activeSkillIds.clear();
256
- hasSkillOverride = false;
257
- process.stdout.write(`thread> ${activeThreadId}\n`);
258
- process.stdout.write(`mode> ${activeThreadMode}\n`);
259
- }
260
- continue;
261
- }
262
- if (prompt.startsWith("/unarchive ")) {
263
- const threadId = prompt.slice("/unarchive ".length).trim();
264
- if (!threadId) {
265
- process.stdout.write("error> Missing thread id\n");
266
- continue;
267
- }
268
- try {
269
- await client.unarchiveThread(threadId);
270
- }
271
- catch (error) {
272
- process.stdout.write(`error> ${error instanceof Error ? error.message : "Unknown thread error"}\n`);
273
- continue;
274
- }
275
- process.stdout.write(`unarchived> ${threadId}\n`);
276
- continue;
277
- }
278
- if (prompt === "/delete" ||
279
- prompt.startsWith("/delete ") ||
280
- prompt === "/purge" ||
281
- prompt.startsWith("/purge ")) {
282
- const targetThreadId = prompt === "/delete" || prompt === "/purge"
283
- ? activeThreadId
284
- : prompt.startsWith("/delete ")
285
- ? prompt.slice("/delete ".length).trim()
286
- : prompt.slice("/purge ".length).trim();
287
- if (!targetThreadId) {
288
- process.stdout.write("error> Missing thread id\n");
289
- continue;
290
- }
291
- try {
292
- await client.purgeThread(targetThreadId);
293
- }
294
- catch (error) {
295
- process.stdout.write(`error> ${error instanceof Error ? error.message : "Unknown thread error"}\n`);
296
- continue;
297
- }
298
- process.stdout.write(`deleted> ${targetThreadId}\n`);
299
- if (targetThreadId === activeThreadId) {
300
- const fallback = await resolveFallbackChatThread(client, targetThreadId, activeThreadMode);
301
- activeThreadId = fallback.id;
302
- activeThreadMode = fallback.mode;
303
- activeSkillIds.clear();
304
- hasSkillOverride = false;
305
- process.stdout.write(`thread> ${activeThreadId}\n`);
306
- process.stdout.write(`mode> ${activeThreadMode}\n`);
307
- }
308
- continue;
309
- }
310
- if (prompt === "/status") {
311
- const detail = await client.getThread(activeThreadId);
312
- const { plan } = await client.getThreadPlan(activeThreadId);
313
- process.stdout.write(`thread> ${activeThreadId}\n`);
314
- process.stdout.write(`mode> ${detail.thread.mode}\n`);
315
- process.stdout.write(`agent> ${activeAgentProfile ?? "orchestrator"}\n`);
316
- process.stdout.write(`skills> ${hasSkillOverride ? Array.from(activeSkillIds).join(", ") || "(none)" : "(runtime default)"}\n`);
317
- process.stdout.write(`plan> ${plan ? `${plan.status} rev${plan.graphRevision}` : "(none)"}\n`);
318
- if (detail.thread.forkedFromThreadId) {
319
- process.stdout.write(`forked-from> ${detail.thread.forkedFromThreadId}\n`);
320
- }
321
- process.stdout.write(`forks> ${detail.thread.childCount}\n`);
322
- continue;
323
- }
324
- if (prompt === "/memory" || prompt === "/memory show") {
325
- const { memory } = await client.getProjectMemory();
326
- process.stdout.write(`memory> ${memory.path}${memory.updatedAt ? ` [updated ${memory.updatedAt}]` : " [empty]"}\n`);
327
- if (memory.auto) {
328
- process.stdout.write(`memory-auto> ${memory.auto.path}${memory.auto.updatedAt ? ` [updated ${memory.auto.updatedAt}]` : memory.auto.exists ? " [present]" : " [empty]"} entries=${memory.auto.entryCount}\n`);
329
- }
330
- if (memory.sync) {
331
- const modeLabel = memory.sync.enabled
332
- ? `shared ${memory.sync.projectKey ?? ""}`.trim()
333
- : `local-only${memory.sync.reason ? ` (${memory.sync.reason})` : ""}`;
334
- process.stdout.write(`memory-sync> ${modeLabel}\n`);
335
- if (memory.sync.lastPullAt) {
336
- process.stdout.write(`memory-sync-pull> ${memory.sync.lastPullAt}\n`);
337
- }
338
- if (memory.sync.lastPushAt) {
339
- process.stdout.write(`memory-sync-push> ${memory.sync.lastPushAt}\n`);
340
- }
341
- if (memory.sync.lastConflictAt) {
342
- process.stdout.write(`memory-sync-conflict> ${memory.sync.lastConflictAt}\n`);
343
- }
344
- if (memory.sync.lastError) {
345
- process.stdout.write(`memory-sync-error> ${memory.sync.lastError}\n`);
346
- }
347
- }
348
- process.stdout.write(`${memory.renderedContent.trim() || "(empty)"}\n`);
349
- continue;
350
- }
351
- if (prompt === "/memory clear") {
352
- const { memory } = await client.clearProjectMemory();
353
- process.stdout.write(`memory> cleared ${memory.path}\n`);
354
- continue;
355
- }
356
- if (prompt === "/memory edit") {
357
- const editor = process.env.VISUAL ?? process.env.EDITOR;
358
- if (!editor) {
359
- process.stdout.write("error> Set VISUAL or EDITOR to use /memory edit\n");
360
- continue;
361
- }
362
- const { memory } = await client.getProjectMemory();
363
- if (!memory.exists) {
364
- mkdirSync(dirname(memory.path), { recursive: true });
365
- writeFileSync(memory.path, "", "utf8");
366
- }
367
- const result = spawnSync(editor, [memory.path], {
368
- cwd: options.cwd ?? process.cwd(),
369
- shell: true,
370
- stdio: "inherit",
371
- });
372
- if (result.status !== 0) {
373
- process.stdout.write(`error> Editor exited with status ${result.status ?? "unknown"}\n`);
374
- continue;
375
- }
376
- const nextContent = existsSync(memory.path) ? readFileSync(memory.path, "utf8") : "";
377
- const updated = await client.setProjectMemory(nextContent);
378
- process.stdout.write(`memory> updated ${updated.memory.path}\n`);
379
- continue;
380
- }
381
- if (prompt.startsWith("/memory set ")) {
382
- const content = prompt.slice("/memory set ".length).trim();
383
- if (!content) {
384
- process.stdout.write("error> Missing memory text\n");
385
- continue;
386
- }
387
- const { memory } = await client.setProjectMemory(content);
388
- process.stdout.write(`memory> updated ${memory.path}\n`);
389
- continue;
390
- }
391
- if (prompt.startsWith("/memory add ")) {
392
- const content = prompt.slice("/memory add ".length).trim();
393
- if (!content) {
394
- process.stdout.write("error> Missing memory text\n");
395
- continue;
396
- }
397
- const current = await client.getProjectMemory();
398
- const nextContent = current.memory.content.trim()
399
- ? `${current.memory.content.trimEnd()}\n\n${content}`
400
- : content;
401
- const { memory } = await client.setProjectMemory(nextContent);
402
- process.stdout.write(`memory> updated ${memory.path}\n`);
403
- continue;
404
- }
405
- if (prompt === "/plan" || prompt === "/plan status") {
406
- process.stdout.write(`mode> ${activeThreadMode}\n`);
407
- continue;
408
- }
409
- if (prompt === "/plan on" || prompt === "/plan off") {
410
- const nextMode = prompt === "/plan on" ? "plan" : "default";
411
- const { thread } = await client.setThreadMode(activeThreadId, nextMode);
412
- activeThreadMode = thread.mode;
413
- if (activeThreadMode === "plan" &&
414
- activeAgentProfile &&
415
- activeAgentProfile !== "plan" &&
416
- activeAgentProfile !== "explore") {
417
- activeAgentProfile = undefined;
418
- }
419
- process.stdout.write(`mode> ${activeThreadMode}\n`);
420
- continue;
421
- }
422
- if (prompt === "/plan show") {
423
- const { plan } = await client.getThreadPlan(activeThreadId);
424
- if (!plan) {
425
- process.stdout.write("plan> (none)\n");
426
- continue;
427
- }
428
- process.stdout.write(`plan> ${plan.title} [${plan.status}] rev${plan.graphRevision}\n`);
429
- process.stdout.write(`${plan.summary}\n`);
430
- continue;
431
- }
432
- if (prompt === "/plan todos") {
433
- const { plan } = await client.getThreadPlan(activeThreadId);
434
- if (!plan || plan.todos.length === 0) {
435
- process.stdout.write("plan-todos> (none)\n");
436
- continue;
437
- }
438
- for (const todo of plan.todos) {
439
- const checkbox = todo.status === "done" ? "[x]" : "[ ]";
440
- const suffix = todo.status === "in_progress"
441
- ? " (in progress)"
442
- : todo.status === "blocked"
443
- ? " (blocked)"
444
- : todo.status === "cancelled"
445
- ? " (cancelled)"
446
- : "";
447
- process.stdout.write(`plan-todo> ${checkbox} ${todo.content}${suffix}\n`);
448
- }
449
- continue;
450
- }
451
- if (prompt === "/plan approve") {
452
- const { plan } = await client.approveThreadPlan(activeThreadId);
453
- process.stdout.write(`plan> approved ${plan.title}\n`);
454
- continue;
455
- }
456
- if (prompt === "/plan reject") {
457
- const { plan } = await client.rejectThreadPlan(activeThreadId);
458
- process.stdout.write(`plan> rejected ${plan.title}\n`);
459
- continue;
460
- }
461
- if (prompt === "/plan export") {
462
- const exported = await client.exportThreadPlan(activeThreadId);
463
- process.stdout.write(`plan-export>\n${exported.text ?? "(none)"}\n`);
464
- continue;
465
- }
466
- if (prompt === "/diagnostics") {
467
- const { diagnostics } = await client.getThreadDiagnostics(activeThreadId);
468
- process.stdout.write(`cache> builds=${diagnostics.cache.buildCount} exact-hit-rate=${diagnostics.cache.hitRate} effective-hit-rate=${diagnostics.cache.effectiveHitRate} prefix-hit-rate=${diagnostics.cache.prefixHitRate} segment-hit-rate=${diagnostics.cache.segmentHitRate} token-reuse=${diagnostics.cache.tokenReuseRatio}\n`);
469
- process.stdout.write(`memory> commits=${diagnostics.compaction.commitCount} reason=${diagnostics.compaction.lastReason ?? "none"} budget=${diagnostics.compaction.lastBudgetTokens ?? 0} estimated=${diagnostics.compaction.lastEstimatedTokens ?? 0} segments=${diagnostics.segments.totalCount} drift=${diagnostics.segments.driftCount}\n`);
470
- process.stdout.write(`provider> calls=${diagnostics.provider.callCount} metered=${diagnostics.provider.reportedUsageCallCount} input-tokens=${diagnostics.provider.inputTokens} output-tokens=${diagnostics.provider.outputTokens} cache-read=${diagnostics.provider.cacheReadTokens} cache-read-ratio=${diagnostics.provider.cacheReadRatio} cache-write=${diagnostics.provider.cacheWriteTokens} cache-write-ratio=${diagnostics.provider.cacheWriteRatio}\n`);
471
- process.stdout.write(`session> thread=${diagnostics.session.threadId} root=${diagnostics.session.rootThreadId} exclusive-total=${diagnostics.session.exclusive.totalTokens} inclusive-total=${diagnostics.session.inclusive.totalTokens} reported-ratio=${diagnostics.session.coverage.inclusive.reportedTokenRatio} tools=${diagnostics.session.runtime.inclusive.toolCallCount} subagents=${diagnostics.session.runtime.inclusive.subagentThreadCount} wall-ms=${diagnostics.session.runtime.inclusive.wallTimeMs.toFixed(1)} cpu-user-ms=${diagnostics.session.runtime.inclusive.cpuUserMs.toFixed(1)} cpu-system-ms=${diagnostics.session.runtime.inclusive.cpuSystemMs.toFixed(1)} compact-saved=${diagnostics.session.compaction.inclusive.totalEstimatedTokensSaved}\n`);
472
- if (diagnostics.processes?.records.length) {
473
- process.stdout.write(`processes> active=${diagnostics.processes.activeCount} total=${diagnostics.processes.records.length}\n`);
474
- }
475
- continue;
476
- }
477
- if (prompt === "/compact") {
478
- const compacted = await client.compactThread(activeThreadId, {
479
- activeSkills: hasSkillOverride ? Array.from(activeSkillIds) : undefined,
480
- });
481
- process.stdout.write(`compact> ${compacted.summary.summaryText || "(empty)"}\n`);
482
- continue;
483
- }
484
- if (prompt === "/forks") {
485
- const children = await client.listThreads({
486
- forkedFromThreadId: activeThreadId,
487
- includeArchived: true,
488
- });
489
- if (children.threads.length === 0) {
490
- process.stdout.write("forks> (none)\n");
491
- continue;
492
- }
493
- for (const child of children.threads) {
494
- process.stdout.write(`${child.id} - ${child.title} [${child.status}]\n`);
495
- }
496
- continue;
497
- }
498
- if (prompt === "/agents") {
499
- const profiles = await client.listAgentProfiles();
500
- for (const profile of profiles.profiles) {
501
- const selected = profile.name === activeAgentProfile ? "*" : " ";
502
- process.stdout.write(`${selected} ${profile.name} [${profile.source}] - ${profile.description}\n`);
503
- }
504
- continue;
505
- }
506
- if (prompt.startsWith("/agent ")) {
507
- const nextProfile = prompt.slice("/agent ".length).trim();
508
- if (!nextProfile || nextProfile === "clear") {
509
- activeAgentProfile = undefined;
510
- process.stdout.write("agent> orchestrator\n");
511
- continue;
512
- }
513
- if (activeThreadMode === "plan" && nextProfile !== "plan" && nextProfile !== "explore") {
514
- process.stdout.write("error> Plan mode only allows the plan or explore profiles\n");
515
- continue;
516
- }
517
- const profiles = await client.listAgentProfiles();
518
- if (!profiles.profiles.some((profile) => profile.name === nextProfile)) {
519
- process.stdout.write(`error> Unknown agent profile ${nextProfile}\n`);
520
- continue;
521
- }
522
- activeAgentProfile = nextProfile;
523
- process.stdout.write(`agent> ${activeAgentProfile}\n`);
524
- continue;
525
- }
526
- if (prompt === "/skills") {
527
- const skills = await client.listSkills();
528
- for (const skill of skills.skills) {
529
- const selected = activeSkillIds.has(skill.id) ? "*" : " ";
530
- process.stdout.write(`${selected} ${skill.id} - ${skill.description}\n`);
531
- }
532
- continue;
533
- }
534
- if (prompt.startsWith("/use-skill ")) {
535
- const skillId = prompt.slice("/use-skill ".length).trim();
536
- const skills = await client.listSkills();
537
- if (!skills.skills.some((skill) => skill.id === skillId)) {
538
- process.stdout.write(`error> Unknown skill ${skillId}\n`);
539
- continue;
540
- }
541
- activeSkillIds.add(skillId);
542
- hasSkillOverride = true;
543
- process.stdout.write(`skill> enabled ${skillId}\n`);
544
- continue;
545
- }
546
- if (prompt.startsWith("/drop-skill ")) {
547
- const skillId = prompt.slice("/drop-skill ".length).trim();
548
- activeSkillIds.delete(skillId);
549
- hasSkillOverride = true;
550
- process.stdout.write(`skill> disabled ${skillId}\n`);
551
- continue;
552
- }
553
- if (prompt === "/tools") {
554
- const toolProfile = activeAgentProfile ?? (activeThreadMode === "plan" ? "plan" : undefined);
555
- const tools = await client.listTools({ agentProfile: toolProfile });
556
- for (const tool of tools.tools) {
557
- process.stdout.write(`${tool.name} [${tool.source}] - ${tool.description}\n`);
558
- }
559
- continue;
560
- }
561
- if (prompt === "/handoff") {
562
- const { handoff } = await client.getThreadHandoff(activeThreadId);
563
- const { renderThreadHandoffText } = await import("@best-agent/protocol");
564
- process.stdout.write(`handoff> ${renderThreadHandoffText(handoff) || "(empty)"}\n`);
565
- continue;
566
- }
567
- if (prompt === "/research" || prompt.startsWith("/research ")) {
568
- const runId = prompt === "/research"
569
- ? undefined
570
- : prompt.slice("/research ".length).trim() || undefined;
571
- const { report, run } = await client.getThreadResearchReport(activeThreadId, { runId });
572
- if (!run || !report) {
573
- process.stdout.write("research> (none)\n");
574
- continue;
575
- }
576
- process.stdout.write(`research> ${run.runId} [${run.status}] ${report.executiveSummary || "(empty)"}\n`);
577
- continue;
578
- }
579
- if (prompt === "/evidence" || prompt.startsWith("/evidence ")) {
580
- const runId = prompt === "/evidence"
581
- ? undefined
582
- : prompt.slice("/evidence ".length).trim() || undefined;
583
- const { evidence, run } = await client.listThreadResearchEvidence(activeThreadId, {
584
- runId,
585
- });
586
- if (!run || evidence.length === 0) {
587
- process.stdout.write("evidence> (none)\n");
588
- continue;
589
- }
590
- process.stdout.write(`evidence> ${run.runId} (${evidence.length})\n`);
591
- for (const card of evidence) {
592
- process.stdout.write(`- ${card.evidenceId}: ${truncateForTerminal(card.summary)}\n`);
593
- }
594
- continue;
595
- }
596
- if (prompt.startsWith("/fork")) {
597
- const title = prompt.slice("/fork".length).trim() || undefined;
598
- const forked = await client.forkThread(activeThreadId, { title });
599
- activeThreadId = forked.thread.id;
600
- activeThreadMode = forked.thread.mode;
601
- process.stdout.write(`thread> ${activeThreadId}\n`);
602
- process.stdout.write(`mode> ${activeThreadMode}\n`);
603
- continue;
604
- }
605
- const stream = client.runStreamed(activeThreadId, prompt, {
606
- activeSkills: hasSkillOverride ? Array.from(activeSkillIds) : undefined,
607
- agentProfile: activeAgentProfile,
608
- });
609
- for await (const event of stream.events) {
610
- await renderChatEvent(client, event, terminal);
611
- }
612
- const result = await stream.completed;
613
- if (result.turn.status === "interrupted") {
614
- process.stdout.write("status> interrupted\n");
615
- }
616
- }
617
- }
618
- finally {
619
- terminal.close();
620
- }
621
- }
622
- finally {
623
- client.close();
624
- }
625
- }
626
- function buildRunPrompt(prompt, agentProfile) {
627
- if (!agentProfile) {
628
- return prompt;
629
- }
630
- return `agent:${agentProfile} ${prompt}`;
631
- }
632
- function findLatestDisplayText(items) {
633
- for (const item of [...items].reverse()) {
634
- if (item.kind === "message" && item.role === "agent") {
635
- return item.text;
636
- }
637
- if (item.kind === "error") {
638
- return item.message;
639
- }
640
- }
641
- return undefined;
642
- }
643
- function describeApprovalSubject(approval) {
644
- if (approval.group === "shell") {
645
- return `command ${approval.action}`;
646
- }
647
- return `${approval.group} ${approval.action}`;
648
- }
649
- function describeApprovalReviewMode(approval) {
650
- if (approval.reviewMode !== "guardian") {
651
- return undefined;
652
- }
653
- return "Guardian review required due to the current risk classification.";
654
- }
655
- function describeGuardianAssessment(approval) {
656
- const assessment = approval.guardianAssessment;
657
- if (!assessment) {
658
- return undefined;
659
- }
660
- const evidence = assessment.evidence.length > 0 ? ` Evidence: ${assessment.evidence.join(" | ")}` : "";
661
- return `Guardian scored this ${assessment.riskScore}/100 (${assessment.riskLevel}). ${assessment.rationale}${evidence}`;
662
- }
663
- async function promptForApproval(approval) {
664
- const choices = [
665
- "[a]llow once",
666
- ...(approval.availableScopes.includes("session") ? ["[s]ession"] : []),
667
- ...(approval.availableScopes.includes("thread") ? ["[t]hread"] : []),
668
- ...(approval.availableScopes.includes("workspace") ? ["[w]orkspace"] : []),
669
- "[r]eject",
670
- ];
671
- const prompt = [
672
- `Approval required for ${describeApprovalSubject(approval)}.`,
673
- describeApprovalReviewMode(approval),
674
- describeGuardianAssessment(approval),
675
- approval.reason,
676
- `Choose ${choices.join(", ")}: `,
677
- ]
678
- .filter(Boolean)
679
- .join(" ");
680
- try {
681
- const answer = (await promptForLine(prompt, process.stderr)).trim().toLowerCase();
682
- if ((answer === "t" || answer === "thread") && approval.availableScopes.includes("thread")) {
683
- return { decision: "allow", scope: "thread" };
684
- }
685
- if ((answer === "w" || answer === "workspace") &&
686
- approval.availableScopes.includes("workspace")) {
687
- return { decision: "allow", scope: "workspace" };
688
- }
689
- if ((answer === "s" || answer === "session") && approval.availableScopes.includes("session")) {
690
- return { decision: "allow", scope: "session" };
691
- }
692
- if (answer === "a" ||
693
- answer === "allow" ||
694
- answer === "y" ||
695
- answer === "yes" ||
696
- answer === "once") {
697
- return { decision: "allow", scope: "once" };
698
- }
699
- }
700
- catch {
701
- return { decision: "reject", scope: "once" };
702
- }
703
- return { decision: "reject", scope: "once" };
704
- }
705
- async function renderChatEvent(client, event, terminal) {
706
- const runtimeEvent = event.event;
707
- switch (runtimeEvent.type) {
708
- case "approval.requested": {
709
- const resolution = await promptForApprovalWithInterface(runtimeEvent.approval, terminal);
710
- await client.respondToApproval(runtimeEvent.approval.id, resolution.decision, resolution.scope);
711
- return;
712
- }
713
- case "action.required": {
714
- if (runtimeEvent.kind !== "ask_user") {
715
- return;
716
- }
717
- const answers = await promptForAskUserWithInterface(runtimeEvent, terminal);
718
- await client.respondToAction(runtimeEvent.actionId, answers);
719
- return;
720
- }
721
- case "turn.item.added": {
722
- const item = runtimeEvent.item;
723
- if (item.kind === "reasoning") {
724
- process.stdout.write(`reasoning> ${item.summary}\n`);
725
- return;
726
- }
727
- if (item.kind === "tool-call") {
728
- process.stdout.write(`tool-call> ${item.toolName} ${item.input}\n`);
729
- return;
730
- }
731
- if (item.kind === "tool-result") {
732
- process.stdout.write(`tool-result> ${item.toolName}: ${truncateForTerminal(item.output)}\n`);
733
- return;
734
- }
735
- if (item.kind === "message" && item.role === "agent") {
736
- process.stdout.write(`assistant> ${item.text}\n`);
737
- return;
738
- }
739
- if (item.kind === "error") {
740
- process.stdout.write(`error> ${item.message}\n`);
741
- return;
742
- }
743
- if (item.kind === "control") {
744
- process.stdout.write(`control> ${item.action} ${truncateForTerminal(item.summary)}\n`);
745
- }
746
- return;
747
- }
748
- case "turn.queued":
749
- process.stdout.write(`queue> queued ${runtimeEvent.queuedTurn.id}\n`);
750
- return;
751
- case "turn.dequeued":
752
- process.stdout.write(`queue> dequeued ${runtimeEvent.queuedTurn.id}\n`);
753
- return;
754
- case "turn.steered":
755
- process.stdout.write(`steer> ${truncateForTerminal(runtimeEvent.prompt)}\n`);
756
- return;
757
- case "turn.queue.drained":
758
- process.stdout.write(`queue> drained ${runtimeEvent.threadId}\n`);
759
- return;
760
- case "workflow.phase.started":
761
- process.stdout.write(`workflow> ${runtimeEvent.workflowName} ${runtimeEvent.phaseName} started in ${runtimeEvent.childThreadId}\n`);
762
- return;
763
- case "workflow.phase.completed":
764
- process.stdout.write(`workflow> ${runtimeEvent.workflowName} ${runtimeEvent.phaseName} completed: ${truncateForTerminal(runtimeEvent.output)}\n`);
765
- return;
766
- case "workflow.phase.failed":
767
- process.stdout.write(`workflow> ${runtimeEvent.workflowName} ${runtimeEvent.phaseName} failed: ${runtimeEvent.message}\n`);
768
- return;
769
- case "child-thread.item.added": {
770
- const scope = runtimeEvent.relation.kind === "workflow-phase"
771
- ? `${runtimeEvent.relation.workflowName}/${runtimeEvent.relation.phaseName}`
772
- : runtimeEvent.relation.kind === "coordinator-worker"
773
- ? `${runtimeEvent.relation.workflowName}/${runtimeEvent.relation.workerId}`
774
- : `research/${runtimeEvent.relation.subquestionId}`;
775
- const item = runtimeEvent.item;
776
- if (item.kind === "reasoning") {
777
- process.stdout.write(`subagent> [${scope}] reasoning ${item.summary}\n`);
778
- return;
779
- }
780
- if (item.kind === "tool-call") {
781
- process.stdout.write(`subagent> [${scope}] tool-call ${item.toolName} ${item.input}\n`);
782
- return;
783
- }
784
- if (item.kind === "tool-result") {
785
- process.stdout.write(`subagent> [${scope}] tool-result ${item.toolName}: ${truncateForTerminal(item.output)}\n`);
786
- return;
787
- }
788
- if (item.kind === "message" && item.role === "agent") {
789
- process.stdout.write(`subagent> [${scope}] assistant ${truncateForTerminal(item.text)}\n`);
790
- return;
791
- }
792
- if (item.kind === "error") {
793
- process.stdout.write(`subagent> [${scope}] error ${item.message}\n`);
794
- }
795
- return;
796
- }
797
- case "thread.compacted":
798
- process.stdout.write(`compact> ${runtimeEvent.reason} commits=${runtimeEvent.diagnostics.compaction.commitCount} saved=${runtimeEvent.estimatedTokensSaved ?? 0} hit-rate=${runtimeEvent.diagnostics.cache.hitRate}\n`);
799
- return;
800
- case "thread.compaction.started":
801
- process.stdout.write(`compact-start> ${runtimeEvent.reason} estimated-before=${runtimeEvent.estimatedTokensBefore ?? 0}\n`);
802
- return;
803
- case "thread.usage.updated":
804
- process.stdout.write(`usage> ${runtimeEvent.operation} thread=${runtimeEvent.threadId} source=${runtimeEvent.sourceThreadId} total=${runtimeEvent.inclusive.totalTokens} delta=${runtimeEvent.last.totalTokens} reported=${runtimeEvent.reported}\n`);
805
- return;
806
- case "thread.forked":
807
- if (runtimeEvent.turnId) {
808
- process.stdout.write(`fork> ${runtimeEvent.thread.id}\n`);
809
- }
810
- return;
811
- case "plan.started":
812
- process.stdout.write(`plan> started ${runtimeEvent.plan.title}\n`);
813
- return;
814
- case "plan.updated":
815
- process.stdout.write(`plan> updated ${runtimeEvent.plan.title} [${runtimeEvent.plan.status}] rev${runtimeEvent.plan.graphRevision}\n`);
816
- return;
817
- case "plan.todo.updated":
818
- process.stdout.write(`plan> todos ${runtimeEvent.plan.todos.length}\n`);
819
- return;
820
- case "plan.review_ready":
821
- process.stdout.write(`plan> review-ready ${runtimeEvent.plan.title}\n`);
822
- return;
823
- case "plan.approval.requested":
824
- process.stdout.write(`plan> approval requested ${runtimeEvent.plan.title}\n`);
825
- return;
826
- case "plan.approved":
827
- process.stdout.write(`plan> approved ${runtimeEvent.plan.title}\n`);
828
- return;
829
- case "plan.rejected":
830
- process.stdout.write(`plan> rejected ${runtimeEvent.plan.title}\n`);
831
- return;
832
- case "plan.handoff.ready":
833
- process.stdout.write(`plan> handoff ready ${runtimeEvent.plan.title}\n`);
834
- return;
835
- case "research.started":
836
- process.stdout.write(`research> started ${runtimeEvent.run.runId}\n`);
837
- return;
838
- case "research.subquestion.started":
839
- process.stdout.write(`research> ${runtimeEvent.subquestion.profileName}/${runtimeEvent.subquestion.subquestionId} started in ${runtimeEvent.subquestion.childThreadId}\n`);
840
- return;
841
- case "research.subquestion.completed":
842
- process.stdout.write(`research> ${runtimeEvent.subquestionId} completed with ${runtimeEvent.evidenceCount} evidence cards\n`);
843
- return;
844
- case "research.subquestion.failed":
845
- process.stdout.write(`research> ${runtimeEvent.subquestionId} failed: ${runtimeEvent.message}\n`);
846
- return;
847
- case "research.evidence.added":
848
- process.stdout.write(`evidence> ${runtimeEvent.evidence.evidenceId}: ${truncateForTerminal(runtimeEvent.evidence.summary)}\n`);
849
- return;
850
- case "research.synthesis.started":
851
- process.stdout.write(`research> synthesis started for ${runtimeEvent.runId}\n`);
852
- return;
853
- case "research.audit.completed":
854
- process.stdout.write(`research> audit completed contradictions=${runtimeEvent.contradictionCount} unsupported=${runtimeEvent.unsupportedClaimCount}\n`);
855
- return;
856
- case "research.completed":
857
- process.stdout.write(`research> completed ${runtimeEvent.runId}\n`);
858
- return;
859
- case "research.failed":
860
- process.stdout.write(`research> failed ${runtimeEvent.runId}: ${runtimeEvent.message}\n`);
861
- return;
862
- default:
863
- return;
864
- }
865
- }
866
- async function promptForLine(prompt, output = process.stdout) {
867
- output.write(prompt);
868
- const lineInterface = createInterface({
869
- input: process.stdin,
870
- crlfDelay: Number.POSITIVE_INFINITY,
871
- });
872
- try {
873
- return await new Promise((resolve) => {
874
- let settled = false;
875
- const finish = (value) => {
876
- if (settled) {
877
- return;
878
- }
879
- settled = true;
880
- resolve(value);
881
- };
882
- lineInterface.once("line", finish);
883
- lineInterface.once("close", () => {
884
- finish("");
885
- });
886
- });
887
- }
888
- finally {
889
- lineInterface.close();
890
- }
891
- }
892
- async function promptForApprovalWithInterface(approval, terminal) {
893
- if (!terminal) {
894
- return promptForApproval(approval);
895
- }
896
- const choices = [
897
- "[a]llow once",
898
- ...(approval.availableScopes.includes("session") ? ["[s]ession"] : []),
899
- ...(approval.availableScopes.includes("thread") ? ["[t]hread"] : []),
900
- ...(approval.availableScopes.includes("workspace") ? ["[w]orkspace"] : []),
901
- "[r]eject",
902
- ];
903
- const prompt = [
904
- `Approval required for ${describeApprovalSubject(approval)}.`,
905
- describeApprovalReviewMode(approval),
906
- describeGuardianAssessment(approval),
907
- approval.reason,
908
- `Choose ${choices.join(", ")}: `,
909
- ]
910
- .filter(Boolean)
911
- .join(" ");
912
- const answer = ((await terminal.ask(prompt, process.stderr)) ?? "").trim().toLowerCase();
913
- if ((answer === "t" || answer === "thread") && approval.availableScopes.includes("thread")) {
914
- return { decision: "allow", scope: "thread" };
915
- }
916
- if ((answer === "w" || answer === "workspace") &&
917
- approval.availableScopes.includes("workspace")) {
918
- return { decision: "allow", scope: "workspace" };
919
- }
920
- if ((answer === "s" || answer === "session") && approval.availableScopes.includes("session")) {
921
- return { decision: "allow", scope: "session" };
922
- }
923
- if (answer === "a" ||
924
- answer === "allow" ||
925
- answer === "y" ||
926
- answer === "yes" ||
927
- answer === "once") {
928
- return { decision: "allow", scope: "once" };
929
- }
930
- return { decision: "reject", scope: "once" };
931
- }
932
- async function promptForAskUserWithInterface(action, terminal) {
933
- const answers = {};
934
- const total = action.questions.length;
935
- const ask = async (prompt) => {
936
- const answer = terminal
937
- ? await terminal.ask(prompt, process.stderr)
938
- : await promptForLine(prompt, process.stderr);
939
- return (answer ?? "").trim();
940
- };
941
- for (const [index, question] of action.questions.entries()) {
942
- const header = question.header?.trim() || `Question ${index + 1}`;
943
- process.stdout.write(`ask-user> ${header}: ${question.question}\n`);
944
- if (isAskUserChoiceQuestion(question)) {
945
- for (const [optionIndex, option] of question.options.entries()) {
946
- const detail = option.description ? ` - ${option.description}` : "";
947
- process.stdout.write(`ask-user-option> ${optionIndex + 1}. ${option.label}${detail}\n`);
948
- }
949
- if (question.allowCustom !== false) {
950
- process.stdout.write(`ask-user-option> ${question.options.length + 1}. Other - Enter a custom answer\n`);
951
- }
952
- }
953
- else if (question.placeholder?.trim()) {
954
- process.stdout.write(`ask-user-hint> ${question.placeholder.trim()}\n`);
955
- }
956
- const raw = await ask(buildAskUserPromptLabel(question, index, total));
957
- answers[question.id] = await resolveAskUserAnswer(question, raw, ask);
958
- }
959
- return answers;
960
- }
961
- function buildAskUserPromptLabel(question, index, total) {
962
- const base = total > 1 ? `Answer ${index + 1}/${total}` : "Answer";
963
- if (!isAskUserInputQuestion(question)) {
964
- return `${base}: `;
965
- }
966
- const placeholder = question.placeholder?.trim();
967
- return placeholder ? `${base} (${placeholder}): ` : `${base}: `;
968
- }
969
- async function resolveAskUserAnswer(question, raw, ask) {
970
- if (isAskUserInputQuestion(question)) {
971
- return raw.trim();
972
- }
973
- if (question.multiSelect) {
974
- const selections = raw
975
- .split(",")
976
- .map((part) => resolveAskUserOption(question, part.trim()))
977
- .filter((part) => Boolean(part));
978
- const labels = selections.filter((part) => typeof part === "string" && part.length > 0);
979
- if (selections.some((part) => typeof part !== "string")) {
980
- const custom = (await ask("Custom answer: ")).trim();
981
- if (custom) {
982
- labels.push(custom);
983
- }
984
- }
985
- return labels;
986
- }
987
- const selection = resolveAskUserOption(question, raw);
988
- if (typeof selection === "string") {
989
- return selection;
990
- }
991
- return (await ask("Custom answer: ")).trim();
992
- }
993
- function resolveAskUserOption(question, raw) {
994
- const normalized = raw.trim();
995
- if (!normalized) {
996
- return "";
997
- }
998
- const options = question.options;
999
- const numericIndex = Number(normalized);
1000
- if (Number.isInteger(numericIndex) && numericIndex >= 1 && numericIndex <= options.length) {
1001
- return options[numericIndex - 1]?.label ?? normalized;
1002
- }
1003
- if (question.allowCustom !== false &&
1004
- Number.isInteger(numericIndex) &&
1005
- numericIndex === options.length + 1) {
1006
- return { kind: "other" };
1007
- }
1008
- const exact = options.find((option) => option.label.toLowerCase() === normalized.toLowerCase());
1009
- if (exact?.label) {
1010
- return exact.label;
1011
- }
1012
- if (question.allowCustom !== false && normalized.toLowerCase() === "other") {
1013
- return { kind: "other" };
1014
- }
1015
- return normalized;
1016
- }
1017
- function isAskUserChoiceQuestion(question) {
1018
- if (question.kind === "choice") {
1019
- return true;
1020
- }
1021
- return "options" in question && Array.isArray(question.options) && question.options.length > 0;
1022
- }
1023
- function isAskUserInputQuestion(question) {
1024
- return question.kind === "input";
1025
- }
1026
- function createTerminalPrompter() {
1027
- if (!process.stdin.isTTY) {
1028
- const input = readFileSync(0, "utf8");
1029
- const buffered = input.length === 0 ? [] : input.split(/\r?\n/);
1030
- if (buffered.at(-1) === "") {
1031
- buffered.pop();
1032
- }
1033
- return {
1034
- async ask(prompt, output = process.stderr) {
1035
- output.write(prompt);
1036
- return buffered.shift() ?? null;
1037
- },
1038
- close() { },
1039
- };
1040
- }
1041
- const lineInterface = createInterface({
1042
- input: process.stdin,
1043
- crlfDelay: Number.POSITIVE_INFINITY,
1044
- terminal: false,
1045
- });
1046
- const buffered = [];
1047
- let closed = false;
1048
- let pending;
1049
- lineInterface.on("line", (line) => {
1050
- if (pending) {
1051
- const resolve = pending;
1052
- pending = undefined;
1053
- resolve(line);
1054
- return;
1055
- }
1056
- buffered.push(line);
1057
- });
1058
- lineInterface.on("close", () => {
1059
- closed = true;
1060
- if (pending) {
1061
- const resolve = pending;
1062
- pending = undefined;
1063
- resolve(null);
1064
- }
1065
- });
1066
- return {
1067
- async ask(prompt, output = process.stderr) {
1068
- output.write(prompt);
1069
- if (buffered.length > 0) {
1070
- return buffered.shift() ?? null;
1071
- }
1072
- if (closed) {
1073
- return null;
1074
- }
1075
- return new Promise((resolve) => {
1076
- pending = resolve;
1077
- });
1078
- },
1079
- close() {
1080
- lineInterface.close();
1081
- },
1082
- };
1083
- }
1084
- function buildCliDaemonEnv() {
1085
- return process.env.BEST_AGENT_DAEMON_IDLE_EXIT_MS
1086
- ? {}
1087
- : { BEST_AGENT_DAEMON_IDLE_EXIT_MS: DEFAULT_CLI_DAEMON_IDLE_EXIT_MS };
1088
- }
1089
- function parseRunOptions(args) {
1090
- const options = parseCommonOptions(args);
1091
- const promptParts = [];
1092
- for (let index = 0; index < args.length; index += 1) {
1093
- const arg = args[index];
1094
- const next = args[index + 1];
1095
- if (arg === "--plan") {
1096
- continue;
1097
- }
1098
- if (takesValue(arg) && next) {
1099
- index += 1;
1100
- continue;
1101
- }
1102
- promptParts.push(arg);
1103
- }
1104
- return {
1105
- ...options,
1106
- prompt: promptParts.join(" ").trim() || undefined,
1107
- };
1108
- }
1109
- function parseChatOptions(args) {
1110
- const options = parseCommonOptions(args);
1111
- let latest = false;
1112
- let altScreen;
1113
- let enableMouse;
1114
- let resumeThreadId;
1115
- let threadMode = options.threadMode;
1116
- for (let index = 0; index < args.length; index += 1) {
1117
- const arg = args[index];
1118
- const value = args[index + 1];
1119
- if (arg === "--latest") {
1120
- latest = true;
1121
- continue;
1122
- }
1123
- if (arg === "--plan") {
1124
- threadMode = "plan";
1125
- continue;
1126
- }
1127
- if (arg === "--no-mouse") {
1128
- enableMouse = false;
1129
- continue;
1130
- }
1131
- if (arg === "--no-alt-screen") {
1132
- altScreen = false;
1133
- continue;
1134
- }
1135
- if (arg === "--resume" && value) {
1136
- resumeThreadId = value;
1137
- index += 1;
1138
- }
1139
- }
1140
- return {
1141
- ...options,
1142
- altScreen,
1143
- enableMouse,
1144
- latest,
1145
- resumeThreadId,
1146
- threadMode,
1147
- };
1148
- }
1149
- function parseServeOptions(args) {
1150
- const options = parseCommonOptions(args);
1151
- let host;
1152
- let port;
1153
- for (let index = 0; index < args.length; index += 1) {
1154
- const arg = args[index];
1155
- const value = args[index + 1];
1156
- if (arg === "--host" && value) {
1157
- host = value;
1158
- index += 1;
1159
- continue;
1160
- }
1161
- if (arg === "--port" && value) {
1162
- port = Number(value);
1163
- index += 1;
1164
- }
1165
- }
1166
- return {
1167
- approvalTimeoutMs: options.approvalTimeoutMs,
1168
- cwd: options.cwd,
1169
- extensionsRoot: options.extensionsRoot,
1170
- guardian: options.guardian,
1171
- host,
1172
- port,
1173
- providerConfig: options.providerConfig,
1174
- storageRoot: options.storageRoot,
1175
- };
1176
- }
1177
- function parseCommonOptions(args) {
1178
- const options = {};
1179
- const envProviderConfig = getProviderConfigFromEnv();
1180
- if (envProviderConfig) {
1181
- options.providerConfig = envProviderConfig;
1182
- }
1183
- for (let index = 0; index < args.length; index += 1) {
1184
- const arg = args[index];
1185
- const value = args[index + 1];
1186
- if (!takesValue(arg) || !value) {
1187
- if (arg === "--plan") {
1188
- options.threadMode = "plan";
1189
- }
1190
- continue;
1191
- }
1192
- if (arg === "--storage-root") {
1193
- options.storageRoot = value;
1194
- index += 1;
1195
- continue;
1196
- }
1197
- if (arg === "--daemon-runtime-dir") {
1198
- options.daemonRuntimeDir = value;
1199
- index += 1;
1200
- continue;
1201
- }
1202
- if (arg === "--extensions-root") {
1203
- options.extensionsRoot = value;
1204
- index += 1;
1205
- continue;
1206
- }
1207
- if (arg === "--cwd") {
1208
- options.cwd = value;
1209
- index += 1;
1210
- continue;
1211
- }
1212
- if (arg === "--agent") {
1213
- options.agentProfile = value;
1214
- index += 1;
1215
- continue;
1216
- }
1217
- if (arg === "--approval-timeout-ms") {
1218
- options.approvalTimeoutMs = Number(value);
1219
- index += 1;
1220
- continue;
1221
- }
1222
- if (arg === "--guardian-enabled") {
1223
- options.guardian = {
1224
- ...options.guardian,
1225
- enabled: value === "1" || value === "true" || value === "yes",
1226
- };
1227
- index += 1;
1228
- continue;
1229
- }
1230
- if (arg === "--guardian-model") {
1231
- options.guardian = {
1232
- ...options.guardian,
1233
- model: value,
1234
- };
1235
- index += 1;
1236
- continue;
1237
- }
1238
- if (arg === "--guardian-threshold") {
1239
- options.guardian = {
1240
- ...options.guardian,
1241
- threshold: Number(value),
1242
- };
1243
- index += 1;
1244
- continue;
1245
- }
1246
- if (arg === "--guardian-timeout-ms") {
1247
- options.guardian = {
1248
- ...options.guardian,
1249
- timeoutMs: Number(value),
1250
- };
1251
- index += 1;
1252
- continue;
1253
- }
1254
- if (arg === "--provider") {
1255
- const kind = parseProviderKind(value);
1256
- if (!kind) {
1257
- process.stderr.write(`Unsupported provider: ${value}\n`);
1258
- process.exitCode = 1;
1259
- process.exit();
1260
- }
1261
- options.providerConfig = {
1262
- ...options.providerConfig,
1263
- compatibilityMode: options.providerConfig?.compatibilityMode,
1264
- kind,
1265
- model: options.providerConfig?.model ?? "",
1266
- };
1267
- index += 1;
1268
- continue;
1269
- }
1270
- if (arg === "--model") {
1271
- options.providerConfig = {
1272
- ...options.providerConfig,
1273
- compatibilityMode: options.providerConfig?.compatibilityMode,
1274
- kind: options.providerConfig?.kind ?? "openai",
1275
- model: value,
1276
- };
1277
- index += 1;
1278
- continue;
1279
- }
1280
- if (arg === "--base-url") {
1281
- options.providerConfig = {
1282
- ...options.providerConfig,
1283
- compatibilityMode: options.providerConfig?.compatibilityMode,
1284
- kind: options.providerConfig?.kind ?? "openai",
1285
- model: options.providerConfig?.model ?? "",
1286
- baseURL: value,
1287
- };
1288
- index += 1;
1289
- continue;
1290
- }
1291
- if (arg === "--compatibility") {
1292
- const compatibilityMode = parseProviderCompatibilityMode(value);
1293
- if (!compatibilityMode) {
1294
- process.stderr.write(`Unsupported provider compatibility mode: ${value}\n`);
1295
- process.exitCode = 1;
1296
- process.exit();
1297
- }
1298
- options.providerConfig = {
1299
- ...options.providerConfig,
1300
- compatibilityMode,
1301
- kind: options.providerConfig?.kind ?? "openai",
1302
- model: options.providerConfig?.model ?? "",
1303
- };
1304
- index += 1;
1305
- continue;
1306
- }
1307
- if (arg === "--system-prompt") {
1308
- options.providerConfig = {
1309
- ...options.providerConfig,
1310
- compatibilityMode: options.providerConfig?.compatibilityMode,
1311
- kind: options.providerConfig?.kind ?? "openai",
1312
- model: options.providerConfig?.model ?? "",
1313
- systemPrompt: value,
1314
- };
1315
- index += 1;
1316
- }
1317
- }
1318
- if (options.providerConfig && !options.providerConfig.model) {
1319
- process.stderr.write("A provider model is required when provider configuration is supplied.\n");
1320
- process.exitCode = 1;
1321
- process.exit();
1322
- }
1323
- return options;
1324
- }
1325
- function takesValue(arg) {
1326
- return (arg === "--storage-root" ||
1327
- arg === "--daemon-runtime-dir" ||
1328
- arg === "--extensions-root" ||
1329
- arg === "--cwd" ||
1330
- arg === "--agent" ||
1331
- arg === "--provider" ||
1332
- arg === "--model" ||
1333
- arg === "--base-url" ||
1334
- arg === "--compatibility" ||
1335
- arg === "--system-prompt" ||
1336
- arg === "--approval-timeout-ms" ||
1337
- arg === "--guardian-enabled" ||
1338
- arg === "--guardian-model" ||
1339
- arg === "--guardian-threshold" ||
1340
- arg === "--guardian-timeout-ms" ||
1341
- arg === "--resume" ||
1342
- arg === "--host" ||
1343
- arg === "--port");
1344
- }
1345
- async function resolveInitialChatThread(client, options) {
1346
- if (options.resumeThreadId) {
1347
- return resumeChatThread(client, options.resumeThreadId, options.threadMode);
1348
- }
1349
- if (options.latest) {
1350
- const listed = await client.listThreads({ limit: 1 });
1351
- if (listed.threads[0]) {
1352
- return resumeChatThread(client, listed.threads[0].id, options.threadMode);
1353
- }
1354
- }
1355
- const { thread } = await client.startThread({ mode: options.threadMode });
1356
- return thread;
1357
- }
1358
- async function resumeChatThread(client, threadId, requestedMode) {
1359
- const detail = await client.getThread(threadId);
1360
- if (detail.thread.status === "archived") {
1361
- await client.unarchiveThread(threadId);
1362
- }
1363
- if (requestedMode && detail.thread.mode !== requestedMode) {
1364
- await client.setThreadMode(threadId, requestedMode);
1365
- await client.resumeThread(threadId);
1366
- return (await client.getThread(threadId)).thread;
1367
- }
1368
- await client.resumeThread(threadId);
1369
- return detail.thread.status === "archived"
1370
- ? (await client.getThread(threadId)).thread
1371
- : detail.thread;
1372
- }
1373
- async function resolveFallbackChatThread(client, excludedThreadId, mode) {
1374
- const listed = await client.listThreads({ limit: 50 });
1375
- const fallback = listed.threads.find((thread) => thread.id !== excludedThreadId);
1376
- if (fallback) {
1377
- return fallback;
1378
- }
1379
- const { thread } = await client.startThread({ mode });
1380
- return thread;
1381
- }
1382
- function truncateForTerminal(value) {
1383
- const normalized = value.replace(/\s+/g, " ").trim();
1384
- return normalized.length <= 160 ? normalized : `${normalized.slice(0, 157)}...`;
1385
- }
1386
- function resolveCliEnvRoot(args) {
1387
- for (let index = 0; index < args.length; index += 1) {
1388
- if (args[index] === "--cwd" && args[index + 1]) {
1389
- return resolve(args[index + 1]);
1390
- }
1391
- }
1392
- return process.cwd();
1393
- }
1394
- function shouldUseTuiChat() {
1395
- return Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.BEST_AGENT_DISABLE_TUI_CHAT !== "1");
1396
- }
1397
- function loadDotEnv(root) {
1398
- const envPath = resolve(root, ".env");
1399
- if (!existsSync(envPath)) {
1400
- return;
1401
- }
1402
- loadEnvConfig({ path: envPath, override: false, quiet: true });
1403
- }
1404
- void main();
1405
- //# sourceMappingURL=index.js.map