agentcorp-broker 0.1.0-alpha.1

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 (71) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/CONTRIBUTING.md +20 -0
  3. package/LICENSE +201 -0
  4. package/README.md +250 -0
  5. package/SECURITY.md +23 -0
  6. package/dist/audit.d.ts +25 -0
  7. package/dist/audit.js +203 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/broker.d.ts +103 -0
  10. package/dist/broker.js +805 -0
  11. package/dist/broker.js.map +1 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +712 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +3 -0
  16. package/dist/config.js +41 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/console/console.css +794 -0
  19. package/dist/console/console.js +802 -0
  20. package/dist/console/index.html +309 -0
  21. package/dist/credentials.d.ts +16 -0
  22. package/dist/credentials.js +72 -0
  23. package/dist/credentials.js.map +1 -0
  24. package/dist/database.d.ts +119 -0
  25. package/dist/database.js +1356 -0
  26. package/dist/database.js.map +1 -0
  27. package/dist/diagnostics.d.ts +44 -0
  28. package/dist/diagnostics.js +357 -0
  29. package/dist/diagnostics.js.map +1 -0
  30. package/dist/errors.d.ts +5 -0
  31. package/dist/errors.js +14 -0
  32. package/dist/errors.js.map +1 -0
  33. package/dist/index.d.ts +23 -0
  34. package/dist/index.js +15 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/mcp.d.ts +3 -0
  37. package/dist/mcp.js +215 -0
  38. package/dist/mcp.js.map +1 -0
  39. package/dist/migrations.d.ts +13 -0
  40. package/dist/migrations.js +266 -0
  41. package/dist/migrations.js.map +1 -0
  42. package/dist/policy.d.ts +22 -0
  43. package/dist/policy.js +33 -0
  44. package/dist/policy.js.map +1 -0
  45. package/dist/server.d.ts +49 -0
  46. package/dist/server.js +529 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/stdio-adapter.d.ts +230 -0
  49. package/dist/stdio-adapter.js +406 -0
  50. package/dist/stdio-adapter.js.map +1 -0
  51. package/dist/tui.d.ts +26 -0
  52. package/dist/tui.js +291 -0
  53. package/dist/tui.js.map +1 -0
  54. package/dist/types.d.ts +355 -0
  55. package/dist/types.js +85 -0
  56. package/dist/types.js.map +1 -0
  57. package/docs/ARCHITECTURE.md +119 -0
  58. package/docs/README.md +37 -0
  59. package/docs/RELEASING.md +228 -0
  60. package/docs/ROADMAP.md +112 -0
  61. package/docs/cli-reference.md +133 -0
  62. package/docs/dogfooding-report.md +83 -0
  63. package/docs/getting-started.md +228 -0
  64. package/docs/guides/antigravity-setup.md +84 -0
  65. package/docs/guides/claude-cursor-setup.md +76 -0
  66. package/docs/guides/codex-setup.md +75 -0
  67. package/docs/guides/human-console.md +177 -0
  68. package/docs/mcp-tools-reference.md +235 -0
  69. package/docs/policy-guide.md +105 -0
  70. package/examples/org.toml +65 -0
  71. package/package.json +68 -0
package/dist/cli.js ADDED
@@ -0,0 +1,712 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { existsSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { stdin as input, stdout as outputStream } from "node:process";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { Command } from "commander";
8
+ import { exportAuditTrail } from "./audit.js";
9
+ import { AgentCorpBroker } from "./broker.js";
10
+ import { loadOrgConfig } from "./config.js";
11
+ import { ensureCredentials, loadCredentials } from "./credentials.js";
12
+ import { AgentCorpDatabase } from "./database.js";
13
+ import { recordCrashDiagnostics, RotatingLogger, runDoctor, sanitizeBrokerEventForLog } from "./diagnostics.js";
14
+ import { AgentCorpError } from "./errors.js";
15
+ import { AgentCorpServer, readDaemonInfo } from "./server.js";
16
+ import { ensureDaemonRunning, isDaemonHealthy, resolveDefaultPath, runStdioAdapter } from "./stdio-adapter.js";
17
+ import { AgentCorpTui } from "./tui.js";
18
+ process.on("uncaughtException", (error) => {
19
+ recordCrashDiagnostics(".agentcorp/crash.log", error, {
20
+ args: process.argv,
21
+ cwd: process.cwd(),
22
+ });
23
+ console.error("Fatal uncaught exception recorded in .agentcorp/crash.log");
24
+ process.exit(1);
25
+ });
26
+ process.on("unhandledRejection", (reason) => {
27
+ recordCrashDiagnostics(".agentcorp/crash.log", reason, {
28
+ args: process.argv,
29
+ cwd: process.cwd(),
30
+ });
31
+ console.error("Fatal unhandled rejection recorded in .agentcorp/crash.log");
32
+ process.exit(1);
33
+ });
34
+ const SAMPLE_ORG = `[company]
35
+ name = "My Agent Company"
36
+
37
+ [limits]
38
+ max_request_body_bytes = 2097152
39
+ max_message_payload_bytes = 1048576
40
+ max_artifact_bytes = 5242880
41
+ default_page_size = 50
42
+ max_page_size = 200
43
+ max_audit_payload_bytes = 65536
44
+
45
+ [[roles]]
46
+ id = "architect"
47
+ display_name = "Architect"
48
+ model = "openai/codex"
49
+ interface = "mcp"
50
+ capabilities = ["propose_plan", "review", "approve_merge"]
51
+ allowed_peers = ["developer"]
52
+ artifact_visibility = ["architect", "developer"]
53
+
54
+ [[roles]]
55
+ id = "developer"
56
+ display_name = "Developer"
57
+ model = "any/mcp-capable-agent"
58
+ interface = "mcp"
59
+ capabilities = ["write_code", "run_tests", "report"]
60
+ allowed_peers = ["architect"]
61
+ artifact_visibility = ["architect", "developer"]
62
+
63
+ # Higher priority rules win. If no rule matches, human approval is required.
64
+ [[policies]]
65
+ id = "gate-critical-proposals"
66
+ subject = "message"
67
+ message_type = "proposal"
68
+ priority = 200
69
+ action = "require_human"
70
+
71
+ [[policies]]
72
+ id = "allow-read-only-status-updates"
73
+ subject = "message"
74
+ message_type = "status_update"
75
+ priority = 100
76
+ risk_tags = ["read_only"]
77
+ action = "auto_approve"
78
+
79
+ [[policies]]
80
+ id = "allow-read-only-reports"
81
+ subject = "message"
82
+ message_type = "report"
83
+ priority = 100
84
+ risk_tags = ["read_only"]
85
+ action = "auto_approve"
86
+
87
+ [[policies]]
88
+ id = "allow-progress-updates"
89
+ subject = "task"
90
+ priority = 50
91
+ to_status = "in_progress"
92
+ action = "auto_approve"
93
+
94
+ [[policies]]
95
+ id = "gate-task-completion"
96
+ subject = "task"
97
+ priority = 100
98
+ to_status = "completed"
99
+ action = "require_human"
100
+ `;
101
+ function output(value) {
102
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
103
+ }
104
+ async function refreshDaemonInfo(recorded) {
105
+ try {
106
+ const res = await fetch(`${recorded.url}/health`, { signal: AbortSignal.timeout(1500) });
107
+ if (!res.ok)
108
+ return null;
109
+ const health = await res.json();
110
+ if (health.status !== "ok" || !Number.isInteger(health.pid) || !health.startedAt)
111
+ return null;
112
+ const info = { ...recorded, pid: health.pid, startedAt: health.startedAt };
113
+ const changed = info.pid !== recorded.pid || info.startedAt !== recorded.startedAt;
114
+ let controlFileRepaired = false;
115
+ if (changed) {
116
+ try {
117
+ writeFileSync(resolve(".agentcorp/daemon.json"), JSON.stringify(info, null, 2), "utf8");
118
+ controlFileRepaired = true;
119
+ }
120
+ catch {
121
+ // Health remains authoritative even if the local control file cannot be repaired.
122
+ }
123
+ }
124
+ return { info, health, controlFileRepaired };
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ }
130
+ async function resolveDaemonInfo(explicitUrl) {
131
+ const recorded = readDaemonInfo();
132
+ if (recorded) {
133
+ const refreshed = await refreshDaemonInfo(recorded);
134
+ if (refreshed)
135
+ return refreshed;
136
+ }
137
+ const candidateUrl = explicitUrl ?? "http://127.0.0.1:54321";
138
+ try {
139
+ const res = await fetch(`${candidateUrl}/health`, { signal: AbortSignal.timeout(1500) });
140
+ if (!res.ok)
141
+ return null;
142
+ const health = (await res.json());
143
+ if (health.status !== "ok" || !Number.isInteger(health.pid) || !health.startedAt)
144
+ return null;
145
+ const parsed = new URL(candidateUrl);
146
+ const info = {
147
+ pid: health.pid,
148
+ port: Number(parsed.port || 54321),
149
+ host: parsed.hostname,
150
+ url: candidateUrl,
151
+ startedAt: health.startedAt,
152
+ };
153
+ let controlFileRepaired = false;
154
+ try {
155
+ writeFileSync(resolve(".agentcorp/daemon.json"), JSON.stringify(info, null, 2), "utf8");
156
+ controlFileRepaired = true;
157
+ }
158
+ catch {
159
+ // Best effort
160
+ }
161
+ return { info, health, controlFileRepaired };
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
167
+ function openBroker(options) {
168
+ const config = loadOrgConfig(options.config);
169
+ const db = new AgentCorpDatabase(options.db, {
170
+ ...(config.limits?.default_page_size !== undefined ? { defaultPageSize: config.limits.default_page_size } : {}),
171
+ ...(config.limits?.max_page_size !== undefined ? { maxPageSize: config.limits.max_page_size } : {}),
172
+ });
173
+ return { broker: new AgentCorpBroker(config, db), db };
174
+ }
175
+ async function getAdminClient(options) {
176
+ const live = await resolveDaemonInfo();
177
+ if (live) {
178
+ const credPath = options.credentials ?? resolveDefaultPath({ configPath: options.config, dbPath: options.db }, "credentials.json");
179
+ const creds = loadCredentials(credPath);
180
+ const adminToken = creds?.adminToken;
181
+ if (adminToken) {
182
+ const headers = {
183
+ Authorization: `Bearer ${adminToken}`,
184
+ "Content-Type": "application/json",
185
+ };
186
+ return {
187
+ isDaemon: true,
188
+ listApprovals: async () => (await (await fetch(`${live.info.url}/api/approvals`, { headers })).json()),
189
+ approve: async (id, note, payload) => await (await fetch(`${live.info.url}/api/approvals/${id}/approve`, {
190
+ method: "POST",
191
+ headers,
192
+ body: JSON.stringify({ note, payload }),
193
+ })).json(),
194
+ reject: async (id, note) => await (await fetch(`${live.info.url}/api/approvals/${id}/reject`, {
195
+ method: "POST",
196
+ headers,
197
+ body: JSON.stringify({ note }),
198
+ })).json(),
199
+ listPolicies: async () => (await (await fetch(`${live.info.url}/api/policies`, { headers })).json()),
200
+ savePolicy: async (rule) => await (await fetch(`${live.info.url}/api/policies`, {
201
+ method: "POST",
202
+ headers,
203
+ body: JSON.stringify(rule),
204
+ })).json(),
205
+ setPolicyEnabled: async (id, enabled) => await (await fetch(`${live.info.url}/api/policies/${id}/${enabled ? "enable" : "disable"}`, {
206
+ method: "POST",
207
+ headers,
208
+ })).json(),
209
+ prune: async (olderThanDays, dryRun, deleteArtifacts) => await (await fetch(`${live.info.url}/api/maintenance/prune`, {
210
+ method: "POST",
211
+ headers,
212
+ body: JSON.stringify({ olderThanDays, dryRun, deleteArtifacts }),
213
+ })).json(),
214
+ compact: async () => await (await fetch(`${live.info.url}/api/maintenance/compact`, {
215
+ method: "POST",
216
+ headers,
217
+ })).json(),
218
+ close: () => { },
219
+ };
220
+ }
221
+ }
222
+ const { broker, db } = openBroker(options);
223
+ return {
224
+ isDaemon: false,
225
+ listApprovals: async () => broker.listPendingApprovals(),
226
+ approve: async (id, note, payload) => broker.approve(id, note, payload),
227
+ reject: async (id, note) => broker.reject(id, note),
228
+ listPolicies: async () => broker.listPolicies(),
229
+ savePolicy: async (rule) => broker.savePolicy(rule),
230
+ setPolicyEnabled: async (id, enabled) => broker.setPolicyEnabled(id, enabled),
231
+ prune: async (olderThanDays, dryRun, deleteArtifacts) => broker.prune({ olderThanDays, dryRun, deleteArtifacts }),
232
+ compact: async () => broker.checkpointAndCompact(),
233
+ close: () => db.close(),
234
+ };
235
+ }
236
+ function openInBrowser(url) {
237
+ const plat = process.platform;
238
+ try {
239
+ if (plat === "win32") {
240
+ spawn("cmd.exe", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
241
+ }
242
+ else if (plat === "darwin") {
243
+ spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
244
+ }
245
+ else {
246
+ spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
247
+ }
248
+ }
249
+ catch {
250
+ // Best-effort browser launch
251
+ }
252
+ }
253
+ async function getTui(options) {
254
+ const live = await resolveDaemonInfo();
255
+ if (live) {
256
+ const creds = loadCredentials();
257
+ return {
258
+ tui: new AgentCorpTui({
259
+ daemonUrl: live.info.url,
260
+ adminToken: creds?.adminToken,
261
+ }),
262
+ close: () => { },
263
+ };
264
+ }
265
+ const { broker, db } = openBroker(options);
266
+ return {
267
+ tui: new AgentCorpTui({ broker }),
268
+ close: () => db.close(),
269
+ };
270
+ }
271
+ const program = new Command()
272
+ .name("agentcorp")
273
+ .description("Coordinate role-based AI agent teams over MCP")
274
+ .version("0.1.0-alpha.1")
275
+ .option("--config <path>", "organization configuration", "org.toml")
276
+ .option("--db <path>", "SQLite broker database", ".agentcorp/agentcorp.db")
277
+ .option("--credentials <path>", "credentials file path");
278
+ program
279
+ .command("init")
280
+ .description("Create starter org.toml and initialize credentials")
281
+ .option("--force", "overwrite an existing org.toml")
282
+ .action((options) => {
283
+ const target = resolve(program.opts().config);
284
+ if (existsSync(target) && !options.force) {
285
+ throw new AgentCorpError("CONFIG_EXISTS", `${target} already exists; use --force to replace it`);
286
+ }
287
+ writeFileSync(target, SAMPLE_ORG, "utf8");
288
+ const config = loadOrgConfig(target);
289
+ const creds = ensureCredentials(config);
290
+ output({
291
+ created: target,
292
+ credentials: ".agentcorp/credentials.json",
293
+ roles: Object.keys(creds.roleTokens),
294
+ });
295
+ });
296
+ program
297
+ .command("validate")
298
+ .description("Validate org.toml and its role graph")
299
+ .action(() => {
300
+ const config = loadOrgConfig(program.opts().config);
301
+ output({ valid: true, company: config.company.name, roles: config.roles.map((role) => role.id) });
302
+ });
303
+ program
304
+ .command("start")
305
+ .description("Start the central local broker daemon")
306
+ .option("--port <number>", "HTTP port to listen on; 0 selects an available port", "0")
307
+ .option("--host <string>", "Host address to bind to", "127.0.0.1")
308
+ .option("--daemon", "Run detached in the background")
309
+ .option("--daemon-file <path>", "path to daemon.json control file")
310
+ .action(async (options) => {
311
+ const gOpts = program.opts();
312
+ if (options.daemon) {
313
+ const targetUrl = `http://${options.host}:${options.port}`;
314
+ const live = await resolveDaemonInfo(targetUrl);
315
+ if (live) {
316
+ output({ startedInBackground: false, alreadyRunning: true, ...live.info });
317
+ return;
318
+ }
319
+ const recorded = readDaemonInfo(options.daemonFile);
320
+ if (recorded && (await isDaemonHealthy(recorded.url))) {
321
+ output({
322
+ startedInBackground: false,
323
+ alreadyRunning: true,
324
+ ...recorded,
325
+ identityVerified: false,
326
+ message: "Daemon is healthy but predates live identity reporting; restart it before relying on PID control",
327
+ });
328
+ return;
329
+ }
330
+ const args = [
331
+ resolve(process.argv[1] ?? "dist/cli.js"),
332
+ "start",
333
+ "--port",
334
+ options.port,
335
+ "--host",
336
+ options.host,
337
+ "--config",
338
+ resolve(gOpts.config),
339
+ "--db",
340
+ resolve(gOpts.db),
341
+ ];
342
+ if (options.daemonFile) {
343
+ args.push("--daemon-file", resolve(options.daemonFile));
344
+ }
345
+ if (gOpts.credentials) {
346
+ args.push("--credentials", resolve(gOpts.credentials));
347
+ }
348
+ const child = spawn(process.execPath, args, {
349
+ detached: true,
350
+ stdio: "ignore",
351
+ windowsHide: true,
352
+ env: process.env,
353
+ });
354
+ child.unref();
355
+ const deadline = Date.now() + 6000;
356
+ while (Date.now() < deadline) {
357
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 150));
358
+ const spawnedLive = await resolveDaemonInfo(targetUrl);
359
+ if (spawnedLive) {
360
+ output({ startedInBackground: true, ...spawnedLive.info });
361
+ return;
362
+ }
363
+ }
364
+ throw new AgentCorpError("DAEMON_SPAWN_FAILED", `Background daemon process ${child.pid ?? "unknown"} did not become healthy within 6 seconds. ` +
365
+ "Inspect .agentcorp/daemon.log or run 'agentcorp start' without --daemon to see the startup error.");
366
+ }
367
+ const { broker } = openBroker(gOpts);
368
+ const credPath = gOpts.credentials ?? resolveDefaultPath({ configPath: gOpts.config, dbPath: gOpts.db }, "credentials.json");
369
+ const creds = ensureCredentials(broker.config, credPath);
370
+ const targetDaemonFile = options.daemonFile
371
+ ? resolve(options.daemonFile)
372
+ : resolveDefaultPath({ configPath: gOpts.config, dbPath: gOpts.db }, "daemon.json");
373
+ const server = new AgentCorpServer(broker, creds, {
374
+ port: parseInt(options.port, 10),
375
+ host: options.host,
376
+ daemonFilePath: targetDaemonFile,
377
+ });
378
+ const daemonLogDir = dirname(targetDaemonFile);
379
+ const daemonLogger = new RotatingLogger(resolve(daemonLogDir, "daemon.log"));
380
+ daemonLogger.write(`AgentCorp daemon starting on ${options.host}:${options.port} (PID: ${process.pid}, config: ${gOpts.config}, db: ${gOpts.db})`);
381
+ broker.on("event", (evt) => {
382
+ const sanitized = sanitizeBrokerEventForLog(evt);
383
+ daemonLogger.write(`[event:${evt.type}] ${JSON.stringify(sanitized)}`);
384
+ });
385
+ let info;
386
+ try {
387
+ info = await server.start();
388
+ }
389
+ catch (error) {
390
+ const message = error instanceof Error ? error.message : String(error);
391
+ daemonLogger.write(`AgentCorp daemon failed to start (PID: ${process.pid}): ${message}`);
392
+ throw error;
393
+ }
394
+ daemonLogger.write(`AgentCorp daemon listening at ${info.url}`);
395
+ console.error(`AgentCorp Daemon started at ${info.url} (PID: ${info.pid})`);
396
+ output({
397
+ status: "running",
398
+ ...info,
399
+ adminToken: creds.adminToken,
400
+ roles: Object.keys(creds.roleTokens),
401
+ });
402
+ const shutdown = async () => {
403
+ console.error("\nShutting down AgentCorp daemon...");
404
+ daemonLogger.write(`AgentCorp daemon shut down cleanly (PID: ${process.pid})`);
405
+ await server.stop();
406
+ process.exit(0);
407
+ };
408
+ process.on("SIGINT", () => void shutdown());
409
+ process.on("SIGTERM", () => void shutdown());
410
+ });
411
+ program
412
+ .command("stop")
413
+ .description("Stop the running central local broker daemon")
414
+ .action(async () => {
415
+ const live = await resolveDaemonInfo();
416
+ if (!live) {
417
+ const info = readDaemonInfo();
418
+ const healthyLegacyDaemon = info ? await isDaemonHealthy(info.url) : false;
419
+ output({
420
+ stopped: false,
421
+ message: healthyLegacyDaemon
422
+ ? "Daemon is healthy but does not report a verifiable PID; refusing unsafe process termination"
423
+ : "No running daemon recorded",
424
+ recordedPid: info?.pid,
425
+ });
426
+ return;
427
+ }
428
+ try {
429
+ process.kill(live.info.pid, "SIGTERM");
430
+ output({ stopped: true, pid: live.info.pid, controlFileRepaired: live.controlFileRepaired });
431
+ }
432
+ catch (err) {
433
+ output({ stopped: false, message: String(err) });
434
+ }
435
+ });
436
+ program
437
+ .command("status")
438
+ .description("Check daemon status and health")
439
+ .action(async () => {
440
+ const live = await resolveDaemonInfo();
441
+ if (!live) {
442
+ const info = readDaemonInfo();
443
+ const healthyLegacyDaemon = info ? await isDaemonHealthy(info.url) : false;
444
+ output({
445
+ status: healthyLegacyDaemon ? "running" : "stopped",
446
+ ...(info ?? {}),
447
+ identityVerified: false,
448
+ });
449
+ return;
450
+ }
451
+ output({
452
+ status: "running",
453
+ ...live.info,
454
+ health: live.health,
455
+ controlFileRepaired: live.controlFileRepaired,
456
+ });
457
+ });
458
+ program
459
+ .command("doctor")
460
+ .description("Perform comprehensive self-healing and observability diagnostics")
461
+ .action(async () => {
462
+ const gOpts = program.opts();
463
+ const report = await runDoctor({
464
+ configPath: gOpts.config,
465
+ dbPath: gOpts.db,
466
+ });
467
+ output(report);
468
+ });
469
+ program
470
+ .command("mcp")
471
+ .description("Run a role-bound AgentCorp MCP server over stdio (proxies to central daemon)")
472
+ .requiredOption("--role <id>", "role identity for this connection")
473
+ .option("--no-spawn", "do not auto-start daemon if it is not running")
474
+ .option("--daemon-url <url>", "explicit daemon URL to connect to")
475
+ .action(async (options) => {
476
+ const gOpts = program.opts();
477
+ await runStdioAdapter({
478
+ role: options.role,
479
+ configPath: gOpts.config,
480
+ dbPath: gOpts.db,
481
+ credentialsPath: gOpts.credentials,
482
+ noSpawn: options.spawn === false,
483
+ daemonUrl: options.daemonUrl,
484
+ });
485
+ });
486
+ program
487
+ .command("console")
488
+ .description("Launch terminal-native human console or open web dashboard")
489
+ .option("--browser", "Open the modern web dashboard in your default browser")
490
+ .option("--web", "Alias for --browser")
491
+ .action(async (options) => {
492
+ const gOpts = program.opts();
493
+ if (options.browser || options.web) {
494
+ const daemon = await ensureDaemonRunning({
495
+ configPath: gOpts.config,
496
+ dbPath: gOpts.db,
497
+ });
498
+ const consoleUrl = `${daemon.url}/console`;
499
+ console.log(`\nOpening Web Dashboard at: ${consoleUrl}\n`);
500
+ openInBrowser(consoleUrl);
501
+ return;
502
+ }
503
+ const { tui, close } = await getTui(gOpts);
504
+ try {
505
+ const summary = await tui.runDashboard();
506
+ if (summary.pendingCount > 0) {
507
+ const rl = createInterface({ input, output: outputStream });
508
+ try {
509
+ const answer = (await rl.question(" Review pending approvals now? [Y/n] ")).trim().toLowerCase();
510
+ if (answer === "" || answer === "y" || answer === "yes") {
511
+ console.log("");
512
+ await tui.runReview();
513
+ }
514
+ }
515
+ finally {
516
+ rl.close();
517
+ }
518
+ }
519
+ }
520
+ finally {
521
+ close();
522
+ }
523
+ });
524
+ program
525
+ .command("review")
526
+ .description("Interactive terminal-native approval review (alias for 'agentcorp approvals review')")
527
+ .action(async () => {
528
+ const { tui, close } = await getTui(program.opts());
529
+ try {
530
+ await tui.runReview();
531
+ }
532
+ finally {
533
+ close();
534
+ }
535
+ });
536
+ const approvals = program.command("approvals").description("Human approval console commands");
537
+ approvals
538
+ .command("review")
539
+ .description("Interactive terminal-native human approval review loop")
540
+ .action(async () => {
541
+ const { tui, close } = await getTui(program.opts());
542
+ try {
543
+ await tui.runReview();
544
+ }
545
+ finally {
546
+ close();
547
+ }
548
+ });
549
+ approvals
550
+ .command("list")
551
+ .description("List pending approvals")
552
+ .action(async () => {
553
+ const admin = await getAdminClient(program.opts());
554
+ try {
555
+ output(await admin.listApprovals());
556
+ }
557
+ finally {
558
+ admin.close();
559
+ }
560
+ });
561
+ approvals
562
+ .command("approve")
563
+ .description("Approve a pending message or task transition")
564
+ .argument("<approval-id>")
565
+ .option("--note <text>", "decision note")
566
+ .option("--payload <json>", "edited message payload as JSON")
567
+ .action(async (approvalId, options) => {
568
+ const admin = await getAdminClient(program.opts());
569
+ try {
570
+ const payload = options.payload === undefined ? undefined : JSON.parse(options.payload);
571
+ output(await admin.approve(approvalId, options.note, payload));
572
+ }
573
+ finally {
574
+ admin.close();
575
+ }
576
+ });
577
+ approvals
578
+ .command("reject")
579
+ .description("Reject a pending message or task transition")
580
+ .argument("<approval-id>")
581
+ .option("--note <text>", "decision note")
582
+ .action(async (approvalId, options) => {
583
+ const admin = await getAdminClient(program.opts());
584
+ try {
585
+ output(await admin.reject(approvalId, options.note));
586
+ }
587
+ finally {
588
+ admin.close();
589
+ }
590
+ });
591
+ const policies = program.command("policies").description("Manage runtime approval policies");
592
+ policies
593
+ .command("list")
594
+ .description("List approval policies")
595
+ .action(async () => {
596
+ const admin = await getAdminClient(program.opts());
597
+ try {
598
+ output(await admin.listPolicies());
599
+ }
600
+ finally {
601
+ admin.close();
602
+ }
603
+ });
604
+ policies
605
+ .command("set")
606
+ .description("Create or replace a policy from a JSON object")
607
+ .argument("<json>", "policy JSON")
608
+ .action(async (raw) => {
609
+ const admin = await getAdminClient(program.opts());
610
+ try {
611
+ output(await admin.savePolicy(JSON.parse(raw)));
612
+ }
613
+ finally {
614
+ admin.close();
615
+ }
616
+ });
617
+ policies
618
+ .command("enable")
619
+ .description("Enable a policy")
620
+ .argument("<policy-id>")
621
+ .action(async (policyId) => {
622
+ const admin = await getAdminClient(program.opts());
623
+ try {
624
+ output(await admin.setPolicyEnabled(policyId, true));
625
+ }
626
+ finally {
627
+ admin.close();
628
+ }
629
+ });
630
+ policies
631
+ .command("disable")
632
+ .description("Disable a policy without deleting its history")
633
+ .argument("<policy-id>")
634
+ .action(async (policyId) => {
635
+ const admin = await getAdminClient(program.opts());
636
+ try {
637
+ output(await admin.setPolicyEnabled(policyId, false));
638
+ }
639
+ finally {
640
+ admin.close();
641
+ }
642
+ });
643
+ const audit = program.command("audit").description("Audit export commands");
644
+ audit
645
+ .command("export")
646
+ .description("Export human-readable Markdown and JSON audit trail to /coord")
647
+ .option("--out <dir>", "output directory", "coord")
648
+ .option("--limit <number>", "limit exported entries per category")
649
+ .option("--since <iso-date>", "only export entries created/updated since ISO timestamp")
650
+ .action((options) => {
651
+ const { broker, db } = openBroker(program.opts());
652
+ try {
653
+ const result = exportAuditTrail(broker, options.out, {
654
+ limit: options.limit ? parseInt(options.limit, 10) : undefined,
655
+ since: options.since,
656
+ });
657
+ output({
658
+ exported: true,
659
+ markdownPath: result.markdownPath,
660
+ jsonPath: result.jsonPath,
661
+ });
662
+ }
663
+ finally {
664
+ db.close();
665
+ }
666
+ });
667
+ program
668
+ .command("prune")
669
+ .description("Prune resolved history older than specified days (defaults to dry-run)")
670
+ .option("--older-than <days>", "age in days of resolved tasks/messages to prune", "30")
671
+ .option("--execute", "perform live deletion (defaults to safe simulation without deleting)")
672
+ .option("--delete-artifacts", "delete associated artifacts instead of detaching them")
673
+ .option("--compact", "run WAL checkpoint and VACUUM after pruning")
674
+ .action(async (options) => {
675
+ const admin = await getAdminClient(program.opts());
676
+ try {
677
+ const olderThanDays = parseInt(options.olderThan, 10);
678
+ const dryRun = !options.execute;
679
+ const pruneResult = await admin.prune(olderThanDays, dryRun, options.deleteArtifacts);
680
+ let compactResult = undefined;
681
+ if (options.compact && !dryRun) {
682
+ compactResult = await admin.compact();
683
+ }
684
+ output({
685
+ ...pruneResult,
686
+ ...(dryRun ? { notice: "Dry run completed safely without deleting data. Pass --execute to delete records." } : {}),
687
+ ...(compactResult ? { compact: compactResult } : {}),
688
+ });
689
+ }
690
+ finally {
691
+ admin.close();
692
+ }
693
+ });
694
+ program
695
+ .command("compact")
696
+ .description("Run SQLite WAL checkpoint (TRUNCATE) and VACUUM to reclaim disk space")
697
+ .action(async () => {
698
+ const admin = await getAdminClient(program.opts());
699
+ try {
700
+ output(await admin.compact());
701
+ }
702
+ finally {
703
+ admin.close();
704
+ }
705
+ });
706
+ program.parseAsync().catch((error) => {
707
+ const code = error instanceof AgentCorpError ? error.code : "UNEXPECTED_ERROR";
708
+ const message = error instanceof Error ? error.message : String(error);
709
+ process.stderr.write(`${JSON.stringify({ error: code, message })}\n`);
710
+ process.exitCode = 1;
711
+ });
712
+ //# sourceMappingURL=cli.js.map