@kimbho/kimbho-cli 0.1.9 → 0.1.11

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/dist/index.cjs CHANGED
@@ -3346,7 +3346,7 @@ var {
3346
3346
  // package.json
3347
3347
  var package_default = {
3348
3348
  name: "@kimbho/kimbho-cli",
3349
- version: "0.1.9",
3349
+ version: "0.1.11",
3350
3350
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
3351
3351
  type: "module",
3352
3352
  engines: {
@@ -10690,7 +10690,7 @@ async function generateNextPrisma(cwd, projectName) {
10690
10690
  ]
10691
10691
  }),
10692
10692
  "next-env.d.ts": '/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n',
10693
- "next.config.js": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n",
10693
+ "next.config.mjs": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n",
10694
10694
  ".env.example": 'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/app"\n',
10695
10695
  "prisma/schema.prisma": [
10696
10696
  "generator client {",
@@ -10722,6 +10722,8 @@ async function generateNextPrisma(cwd, projectName) {
10722
10722
  "}"
10723
10723
  ].join("\n"),
10724
10724
  "src/app/page.tsx": [
10725
+ `const title = "${title}";`,
10726
+ "",
10725
10727
  "export default function HomePage() {",
10726
10728
  " return (",
10727
10729
  " <main style={{ fontFamily: 'Georgia, serif', padding: '4rem 1.5rem', maxWidth: 960, margin: '0 auto' }}>",
@@ -11088,6 +11090,29 @@ var READ_ONLY_SHELL_PREFIXES = [
11088
11090
  "pnpm ls",
11089
11091
  "yarn list"
11090
11092
  ];
11093
+ var VERIFICATION_SHELL_PREFIXES = [
11094
+ "npm test",
11095
+ "npm run test",
11096
+ "npm run build",
11097
+ "npm run lint",
11098
+ "pnpm test",
11099
+ "pnpm run test",
11100
+ "pnpm build",
11101
+ "pnpm run build",
11102
+ "pnpm lint",
11103
+ "pnpm run lint",
11104
+ "yarn test",
11105
+ "yarn build",
11106
+ "yarn lint",
11107
+ "bun test",
11108
+ "bun run test",
11109
+ "bun run build",
11110
+ "bun run lint",
11111
+ "tsc",
11112
+ "vitest",
11113
+ "jest",
11114
+ "next build"
11115
+ ];
11091
11116
  var DESTRUCTIVE_SHELL_PATTERNS = [
11092
11117
  /\brm\s+-rf\b/i,
11093
11118
  /\brm\s+-fr\b/i,
@@ -11109,6 +11134,10 @@ function isReadOnlyShellCommand(command) {
11109
11134
  const normalized = command.trim().toLowerCase();
11110
11135
  return READ_ONLY_SHELL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(prefix));
11111
11136
  }
11137
+ function isVerificationShellCommand(command) {
11138
+ const normalized = command.trim().toLowerCase();
11139
+ return VERIFICATION_SHELL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix} `));
11140
+ }
11112
11141
  function isDestructiveShellCommand(command) {
11113
11142
  return DESTRUCTIVE_SHELL_PATTERNS.some((pattern) => pattern.test(command));
11114
11143
  }
@@ -11178,7 +11207,7 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
11178
11207
  const approvalMode = context.approvalMode ?? "manual";
11179
11208
  const command = typeof input.command === "string" ? input.command : "";
11180
11209
  if (sandboxMode === "read-only") {
11181
- if (toolId === "shell.exec" && isReadOnlyShellCommand(command)) {
11210
+ if (toolId === "shell.exec" && (isReadOnlyShellCommand(command) || isVerificationShellCommand(command))) {
11182
11211
  return null;
11183
11212
  }
11184
11213
  if (descriptor.permission !== "safe") {
@@ -11190,6 +11219,9 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
11190
11219
  return new ToolApprovalRequiredError(createApprovalRequest(toolId, input, descriptor, context, `Approval required for destructive shell command: ${command}`));
11191
11220
  }
11192
11221
  }
11222
+ if (toolId === "shell.exec" && isVerificationShellCommand(command)) {
11223
+ return null;
11224
+ }
11193
11225
  if ((toolId === "file.write" || toolId === "file.patch") && sandboxMode === "workspace-write") {
11194
11226
  const targets = extractProspectiveWriteTargets(toolId, input, context.cwd);
11195
11227
  const protectedTargets = targets.filter((target) => isProtectedWritePath(target));
@@ -12214,6 +12246,7 @@ function buildSystemPrompt(agent, task, request, allowedTools, plan) {
12214
12246
  `- Use browser.open, browser.inspect, browser.click, browser.fill, and browser.close when a web UI needs browser-level verification.`,
12215
12247
  `- Use process.start/process.logs/process.stop for long-running dev servers or watchers when they are available.`,
12216
12248
  `- Keep paths relative to the workspace.`,
12249
+ `- In an existing repo, inspect and change the likely source files before running build or test verification. Do not front-load verification unless you are confirming an already-completed change or diagnosing a failure.`,
12217
12250
  `- After changing code, run verification with tests.run or shell.exec when appropriate.`,
12218
12251
  `- Do not claim success unless the task acceptance criteria are satisfied.`,
12219
12252
  `- If the task is underspecified, make a pragmatic implementation choice and continue.`
@@ -12279,6 +12312,114 @@ function buildInitialUserPrompt(task, request) {
12279
12312
  `Choose the next single action now.`
12280
12313
  ].join("\n");
12281
12314
  }
12315
+ function extractCodeFences(raw) {
12316
+ const matches = Array.from(raw.matchAll(/```([a-z0-9_-]*)\s*\n([\s\S]*?)```/gi));
12317
+ return matches.map((match) => ({
12318
+ language: (match[1] ?? "").trim().toLowerCase(),
12319
+ code: (match[2] ?? "").trim()
12320
+ })).filter((match) => match.code.length > 0);
12321
+ }
12322
+ function normalizeWritableTaskFiles(task) {
12323
+ return Array.from(new Set(task.filesLikelyTouched.map((filePath) => filePath.trim()).filter((filePath) => filePath.length > 0 && !filePath.endsWith("/") && !filePath.includes("*") && !filePath.startsWith(".kimbho/"))));
12324
+ }
12325
+ function extractMentionedFilePaths(raw, task) {
12326
+ const mentioned = /* @__PURE__ */ new Set();
12327
+ const normalizedTaskFiles = normalizeWritableTaskFiles(task);
12328
+ const directMatches = raw.match(/([A-Za-z0-9_./-]+\.(?:tsx|ts|jsx|js|css|html|md|json|sql|prisma))/g) ?? [];
12329
+ for (const match of directMatches) {
12330
+ mentioned.add(match);
12331
+ }
12332
+ for (const candidate of normalizedTaskFiles) {
12333
+ const basename = import_node_path9.default.basename(candidate);
12334
+ if (raw.includes(candidate) || raw.includes(basename)) {
12335
+ mentioned.add(candidate);
12336
+ }
12337
+ }
12338
+ return Array.from(mentioned);
12339
+ }
12340
+ function inferFallbackFilePath(raw, task, fence) {
12341
+ const mentioned = extractMentionedFilePaths(raw, task);
12342
+ if (mentioned.length === 1) {
12343
+ return mentioned[0] ?? null;
12344
+ }
12345
+ const writableFiles = normalizeWritableTaskFiles(task);
12346
+ const sourceFiles = writableFiles.filter((filePath) => /\.[a-z0-9]+$/i.test(filePath));
12347
+ if (sourceFiles.length === 1) {
12348
+ return sourceFiles[0] ?? null;
12349
+ }
12350
+ if (fence.language === "html") {
12351
+ return sourceFiles.find((filePath) => filePath.endsWith(".html")) ?? null;
12352
+ }
12353
+ if (fence.language === "css") {
12354
+ return sourceFiles.find((filePath) => filePath.endsWith(".css")) ?? null;
12355
+ }
12356
+ if ([
12357
+ "tsx",
12358
+ "ts",
12359
+ "jsx",
12360
+ "js",
12361
+ "typescript",
12362
+ "javascript"
12363
+ ].includes(fence.language)) {
12364
+ return sourceFiles.find((filePath) => /\.(tsx?|jsx?)$/i.test(filePath)) ?? null;
12365
+ }
12366
+ if (fence.language === "json") {
12367
+ return sourceFiles.find((filePath) => filePath.endsWith(".json")) ?? null;
12368
+ }
12369
+ return sourceFiles[0] ?? null;
12370
+ }
12371
+ function extractCommandFromResponse(raw) {
12372
+ const trimmed = raw.trim();
12373
+ if (/^(npm|pnpm|yarn|bun|npx|tsx|tsc|vitest|jest|next)\b/i.test(trimmed) && !trimmed.includes("\n")) {
12374
+ return trimmed;
12375
+ }
12376
+ const bashFence = extractCodeFences(raw).find((fence) => [
12377
+ "bash",
12378
+ "sh",
12379
+ "shell",
12380
+ "zsh"
12381
+ ].includes(fence.language));
12382
+ if (!bashFence) {
12383
+ return null;
12384
+ }
12385
+ const lines = bashFence.code.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
12386
+ return lines.length === 1 ? lines[0] ?? null : null;
12387
+ }
12388
+ function inferFallbackActionFromResponse(raw, task, allowedTools) {
12389
+ if (allowedTools.includes("shell.exec")) {
12390
+ const command = extractCommandFromResponse(raw);
12391
+ if (command) {
12392
+ return {
12393
+ type: "tool",
12394
+ tool: "shell.exec",
12395
+ input: {
12396
+ command
12397
+ },
12398
+ reason: "Run the command inferred from the model response."
12399
+ };
12400
+ }
12401
+ }
12402
+ if (!allowedTools.includes("file.write")) {
12403
+ return null;
12404
+ }
12405
+ const fences = extractCodeFences(raw);
12406
+ for (const fence of fences) {
12407
+ const filePath = inferFallbackFilePath(raw, task, fence);
12408
+ if (!filePath) {
12409
+ continue;
12410
+ }
12411
+ return {
12412
+ type: "tool",
12413
+ tool: "file.write",
12414
+ input: {
12415
+ path: filePath,
12416
+ content: fence.code
12417
+ },
12418
+ reason: `Apply the drafted contents for ${filePath} inferred from the model response.`
12419
+ };
12420
+ }
12421
+ return null;
12422
+ }
12282
12423
  function buildToolResultUserMessage(step, result) {
12283
12424
  return [
12284
12425
  `Step ${step} tool result:`,
@@ -12852,6 +12993,19 @@ var AutonomousTaskExecutor = class {
12852
12993
  runtimeNote: `${parseSummary} ${error instanceof Error ? error.message : String(error)}`
12853
12994
  });
12854
12995
  if (attempt === MAX_PARSE_RETRIES) {
12996
+ const inferredFallbackAction = inferFallbackActionFromResponse(response.text, task, allowedTools);
12997
+ if (inferredFallbackAction) {
12998
+ parsedAction = inferredFallbackAction;
12999
+ await emitProgress({
13000
+ type: "task-note",
13001
+ sessionId,
13002
+ taskId: task.id,
13003
+ agentRole: task.agentRole,
13004
+ step,
13005
+ message: `Model stayed out of structured mode; inferred ${inferredFallbackAction.tool} from the response.`
13006
+ });
13007
+ break;
13008
+ }
12855
13009
  const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
12856
13010
  artifacts.add(transcriptPath2);
12857
13011
  await emitProgress({
@@ -13109,16 +13263,27 @@ function foundationMilestone(shape) {
13109
13263
  "docs/",
13110
13264
  "packages/"
13111
13265
  ], "medium"),
13112
- buildTask("t3-scaffold", "Scaffold the repository foundation", "Create the workspace layout, TypeScript build config, package manifests, and initial CLI entry points.", shape === "cli-agent" ? "backend-specialist" : "infra-specialist", "scaffold", [
13266
+ buildTask("t3-scaffold", "Scaffold the repository foundation", shape === "static-site" ? "Create or adapt the primary landing-page entrypoint, styles, and workspace shell needed for the requested site." : "Create the workspace layout, TypeScript build config, package manifests, and initial CLI entry points.", shape === "cli-agent" ? "backend-specialist" : "infra-specialist", "scaffold", [
13113
13267
  "t2-architecture"
13114
13268
  ], [
13115
- "The monorepo or app workspace builds cleanly.",
13116
- "Core commands or entry points exist."
13117
- ], [
13118
- "package manifests",
13119
- "TypeScript config",
13120
- "CLI or app shell"
13269
+ shape === "static-site" ? "The primary page entry exists and renders the requested brand direction." : "The monorepo or app workspace builds cleanly.",
13270
+ shape === "static-site" ? "Core layout files or styles exist for the landing page." : "Core commands or entry points exist."
13121
13271
  ], [
13272
+ ...shape === "static-site" ? [
13273
+ "Page entrypoint",
13274
+ "Stylesheet or layout shell"
13275
+ ] : [
13276
+ "package manifests",
13277
+ "TypeScript config",
13278
+ "CLI or app shell"
13279
+ ]
13280
+ ], shape === "static-site" ? [
13281
+ "src/app/page.tsx",
13282
+ "src/app/layout.tsx",
13283
+ "src/pages/index.tsx",
13284
+ "index.html",
13285
+ "styles.css"
13286
+ ] : [
13122
13287
  "package.json",
13123
13288
  "packages/",
13124
13289
  "src/"
@@ -13141,6 +13306,9 @@ function implementationMilestone(shape) {
13141
13306
  ], [
13142
13307
  "Customized landing page"
13143
13308
  ], [
13309
+ "src/app/page.tsx",
13310
+ "src/app/layout.tsx",
13311
+ "src/pages/index.tsx",
13144
13312
  "index.html",
13145
13313
  "styles.css",
13146
13314
  "src/"
@@ -15685,14 +15853,25 @@ var ExecutionOrchestrator = class {
15685
15853
  async executeRepoAnalysisTask(sessionId, task, request, emitProgress, signal) {
15686
15854
  const context = { cwd: request.cwd };
15687
15855
  const toolResults = [];
15688
- const probes = request.workspaceState === "empty" ? [
15856
+ const probes = [
15689
15857
  { toolId: "repo.index", input: {} }
15690
- ] : [
15691
- { toolId: "repo.index", input: {} },
15692
- { toolId: "git.status", input: {} },
15693
- { toolId: "file.read", input: { path: "package.json" } },
15694
- { toolId: "file.read", input: { path: "README.md" } }
15695
15858
  ];
15859
+ if (request.workspaceState !== "empty") {
15860
+ const candidatePaths = await Promise.all([
15861
+ (0, import_promises11.access)(import_node_path11.default.join(request.cwd, ".git")).then(() => true).catch(() => false),
15862
+ (0, import_promises11.access)(import_node_path11.default.join(request.cwd, "package.json")).then(() => true).catch(() => false),
15863
+ (0, import_promises11.access)(import_node_path11.default.join(request.cwd, "README.md")).then(() => true).catch(() => false)
15864
+ ]);
15865
+ if (candidatePaths[0]) {
15866
+ probes.push({ toolId: "git.status", input: {} });
15867
+ }
15868
+ if (candidatePaths[1]) {
15869
+ probes.push({ toolId: "file.read", input: { path: "package.json" } });
15870
+ }
15871
+ if (candidatePaths[2]) {
15872
+ probes.push({ toolId: "file.read", input: { path: "README.md" } });
15873
+ }
15874
+ }
15696
15875
  for (const probe of probes) {
15697
15876
  if (emitProgress) {
15698
15877
  await emitProgress({
@@ -16601,7 +16780,8 @@ function renderPlanGenerationNotes(result) {
16601
16780
  lines.push(`planner tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`);
16602
16781
  }
16603
16782
  if (result.warning) {
16604
- lines.push(`planner note: ${result.source === "fallback" ? `${result.warning} Using the safe default plan.` : result.warning}`);
16783
+ const warning = result.source === "fallback" ? `${summarizeStructuredOutputError("Planner", new Error(result.warning))} Using the safe default plan.` : result.warning;
16784
+ lines.push(`planner note: ${warning}`);
16605
16785
  }
16606
16786
  return lines;
16607
16787
  }
@@ -17279,6 +17459,7 @@ function createProgram(onOpenShell) {
17279
17459
  }
17280
17460
 
17281
17461
  // src/shell.ts
17462
+ var import_node_readline = require("node:readline");
17282
17463
  var import_promises14 = require("node:readline/promises");
17283
17464
  var import_node_process12 = __toESM(require("node:process"), 1);
17284
17465
  var AMBER = "\x1B[38;5;214m";
@@ -17287,7 +17468,9 @@ var BOLD = "\x1B[1m";
17287
17468
  var DIM = "\x1B[2m";
17288
17469
  var RESET = "\x1B[0m";
17289
17470
  var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
17471
+ "approval",
17290
17472
  "approve",
17473
+ "approve-all",
17291
17474
  "agents",
17292
17475
  "brain",
17293
17476
  "brains",
@@ -17331,6 +17514,20 @@ var MAX_CHAT_MESSAGES = 12;
17331
17514
  var DEFAULT_MAX_AUTO_TASKS = 3;
17332
17515
  var DEFAULT_MAX_AGENT_STEPS = 8;
17333
17516
  var DEFAULT_MAX_REPAIR_ATTEMPTS2 = 2;
17517
+ var SPINNER_FRAMES = [
17518
+ "-",
17519
+ "\\",
17520
+ "|",
17521
+ "/"
17522
+ ];
17523
+ var IDLE_STATUS_LABELS = [
17524
+ "thinking",
17525
+ "musing",
17526
+ "discombobulating",
17527
+ "assembling",
17528
+ "drafting",
17529
+ "tinkering"
17530
+ ];
17334
17531
  var EXECUTION_PREFIXES = [
17335
17532
  "build ",
17336
17533
  "create ",
@@ -17390,6 +17587,71 @@ function createExecutionTelemetry() {
17390
17587
  outputTokens: 0
17391
17588
  };
17392
17589
  }
17590
+ var ShellActivityIndicator = class {
17591
+ interval = null;
17592
+ frameIndex = 0;
17593
+ label;
17594
+ activeLine = false;
17595
+ constructor(label) {
17596
+ this.label = label;
17597
+ }
17598
+ start() {
17599
+ if (!import_node_process12.default.stdout.isTTY || this.interval) {
17600
+ return;
17601
+ }
17602
+ this.activeLine = true;
17603
+ this.render();
17604
+ this.interval = setInterval(() => {
17605
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
17606
+ this.render();
17607
+ }, 120);
17608
+ this.interval.unref?.();
17609
+ }
17610
+ update(label) {
17611
+ this.label = label;
17612
+ if (this.interval) {
17613
+ this.render();
17614
+ }
17615
+ }
17616
+ suspend() {
17617
+ if (!import_node_process12.default.stdout.isTTY) {
17618
+ return;
17619
+ }
17620
+ this.clear();
17621
+ }
17622
+ resume() {
17623
+ if (!import_node_process12.default.stdout.isTTY || !this.interval) {
17624
+ return;
17625
+ }
17626
+ this.render();
17627
+ }
17628
+ stop() {
17629
+ if (this.interval) {
17630
+ clearInterval(this.interval);
17631
+ this.interval = null;
17632
+ }
17633
+ this.clear();
17634
+ this.activeLine = false;
17635
+ }
17636
+ render() {
17637
+ if (!import_node_process12.default.stdout.isTTY) {
17638
+ return;
17639
+ }
17640
+ const frame = color(AMBER, SPINNER_FRAMES[this.frameIndex]);
17641
+ const status = color(BOLD, this.label);
17642
+ const raw = `${frame} ${status}${color(DIM, "...")}`;
17643
+ this.clear();
17644
+ (0, import_node_readline.cursorTo)(import_node_process12.default.stdout, 0);
17645
+ import_node_process12.default.stdout.write(raw);
17646
+ }
17647
+ clear() {
17648
+ if (!import_node_process12.default.stdout.isTTY || !this.activeLine) {
17649
+ return;
17650
+ }
17651
+ (0, import_node_readline.cursorTo)(import_node_process12.default.stdout, 0);
17652
+ (0, import_node_readline.clearLine)(import_node_process12.default.stdout, 0);
17653
+ }
17654
+ };
17393
17655
  function renderExecutionTelemetry(telemetry, startedAt) {
17394
17656
  return `telemetry: ${((Date.now() - startedAt) / 1e3).toFixed(1)}s | ${telemetry.toolCalls} tools | ${telemetry.modelCalls} model calls | ${telemetry.inputTokens} in / ${telemetry.outputTokens} out`;
17395
17657
  }
@@ -17579,7 +17841,9 @@ function renderHelp() {
17579
17841
  "/plan <goal> Create a structured implementation plan.",
17580
17842
  "/run <goal> Start a Kimbho execution session for a goal.",
17581
17843
  "/resume Show the latest saved session.",
17844
+ "/approval [mode] Show or set approval mode: manual or auto.",
17582
17845
  "/approve [id] Approve a pending risky action and continue the session.",
17846
+ "/approve-all Approve all pending actions in the current session.",
17583
17847
  "/deny [id] Deny a pending risky action.",
17584
17848
  "/agents Inspect agent roles and the active session.",
17585
17849
  "/review Review the current git diff and summarize risk.",
@@ -17604,7 +17868,7 @@ function renderStartupCard(cwd, state) {
17604
17868
  renderCardLine("approval", state.approvalMode),
17605
17869
  renderCardLine("sandbox", state.sandboxMode),
17606
17870
  renderCardLine("preset", state.stackPreset),
17607
- renderCardLine("shortcuts", "/ask /run /approve /brain /providers /models /quit")
17871
+ renderCardLine("shortcuts", "/ask /run /approval /approve-all /models /quit")
17608
17872
  ];
17609
17873
  if (!state.configured) {
17610
17874
  cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
@@ -17755,6 +18019,29 @@ function pickStatusLabel(message) {
17755
18019
  "checking"
17756
18020
  ]);
17757
18021
  }
18022
+ function pickIdleStatusLabel(seed) {
18023
+ return IDLE_STATUS_LABELS[hashString(seed) % IDLE_STATUS_LABELS.length];
18024
+ }
18025
+ function statusLabelForEvent(event) {
18026
+ switch (event.type) {
18027
+ case "task-note":
18028
+ return pickStatusLabel(event.message);
18029
+ case "task-started":
18030
+ return pickStatusLabel(event.task.title);
18031
+ case "tool-started":
18032
+ return pickStatusLabel(event.reason ?? event.toolId);
18033
+ case "model-usage":
18034
+ return "thinking";
18035
+ case "approval-requested":
18036
+ return "waiting";
18037
+ case "approval-resolved":
18038
+ return "continuing";
18039
+ case "task-finished":
18040
+ return event.status === "handoff" ? "rerouting" : "settling";
18041
+ default:
18042
+ return "thinking";
18043
+ }
18044
+ }
17758
18045
  function simplifyTaskNote(message) {
17759
18046
  const trimmed = message.trim();
17760
18047
  if (trimmed.startsWith("Using ") && trimmed.includes(" via ")) {
@@ -17977,7 +18264,7 @@ function renderLiveExecutionEvent(event) {
17977
18264
  ];
17978
18265
  case "task-note":
17979
18266
  return [
17980
- `${color(DIM, `${pickStatusLabel(event.message)}...`)} ${simplifyTaskNote(event.message)}`
18267
+ `${color(AMBER, `[${pickStatusLabel(event.message)}]`)} ${simplifyTaskNote(event.message)}`
17981
18268
  ];
17982
18269
  case "approval-requested":
17983
18270
  return [
@@ -18110,6 +18397,8 @@ async function handleChatPrompt(cwd, prompt, runtime) {
18110
18397
  }
18111
18398
  ]);
18112
18399
  let result;
18400
+ const activity = new ShellActivityIndicator(pickIdleStatusLabel(prompt));
18401
+ activity.start();
18113
18402
  try {
18114
18403
  result = await brain.client.generateText({
18115
18404
  model: brain.model,
@@ -18125,9 +18414,11 @@ async function handleChatPrompt(cwd, prompt, runtime) {
18125
18414
  } : {}
18126
18415
  });
18127
18416
  } catch (error) {
18417
+ activity.stop();
18128
18418
  const message = error instanceof Error ? error.message : String(error);
18129
18419
  throw new Error(`Chat failed for ${brain.role} via ${brain.provider.id}/${brain.model}: ${message}`);
18130
18420
  }
18421
+ activity.stop();
18131
18422
  const nextConversation = trimConversation([
18132
18423
  ...messages,
18133
18424
  {
@@ -18152,13 +18443,29 @@ async function runGoalExecution(cwd, goal, runtime) {
18152
18443
  workspaceState: workspace.workspaceState,
18153
18444
  constraints: []
18154
18445
  };
18155
- const planResult = await generatePlanForRequest(request);
18446
+ const startedAt = Date.now();
18447
+ const telemetry = createExecutionTelemetry();
18448
+ const controller = new AbortController();
18449
+ runtime.activeExecution = {
18450
+ controller,
18451
+ label: goal
18452
+ };
18453
+ const planningSpinner = new ShellActivityIndicator("planning");
18454
+ console.log(color(DIM, `Working on: ${goal}`));
18455
+ for (const note of workspace.notes) {
18456
+ console.log(color(DIM, note));
18457
+ }
18458
+ planningSpinner.start();
18459
+ let planResult;
18460
+ try {
18461
+ planResult = await generatePlanForRequest(request);
18462
+ } finally {
18463
+ planningSpinner.stop();
18464
+ }
18156
18465
  const plan = planResult.plan;
18157
18466
  const planPath = await savePlan(plan, request.cwd);
18158
18467
  const envelope = orchestrator.buildEnvelope(request, plan);
18159
18468
  const initialSnapshot = orchestrator.createSessionSnapshot(envelope);
18160
- const startedAt = Date.now();
18161
- const telemetry = createExecutionTelemetry();
18162
18469
  const liveBoard = createLiveRunBoard(
18163
18470
  initialSnapshot.id,
18164
18471
  goal,
@@ -18168,21 +18475,14 @@ async function runGoalExecution(cwd, goal, runtime) {
18168
18475
  DEFAULT_MAX_AGENT_STEPS,
18169
18476
  DEFAULT_MAX_REPAIR_ATTEMPTS2
18170
18477
  );
18171
- const controller = new AbortController();
18172
- runtime.activeExecution = {
18173
- controller,
18174
- label: goal
18175
- };
18176
- console.log(color(DIM, `Working on: ${goal}`));
18177
- for (const note of workspace.notes) {
18178
- console.log(color(DIM, note));
18179
- }
18180
18478
  for (const line of renderPlanGenerationNotes(planResult)) {
18181
18479
  console.log(color(DIM, line));
18182
18480
  }
18183
18481
  console.log(renderShellPlanPreview(plan).join("\n"));
18184
18482
  console.log(renderRunStartCard(liveBoard));
18185
18483
  console.log("");
18484
+ const activity = new ShellActivityIndicator("starting");
18485
+ activity.start();
18186
18486
  let snapshot;
18187
18487
  try {
18188
18488
  snapshot = await orchestrator.continueSession(initialSnapshot, {
@@ -18200,6 +18500,8 @@ async function runGoalExecution(cwd, goal, runtime) {
18200
18500
  telemetry.inputTokens += event.usage?.inputTokens ?? 0;
18201
18501
  telemetry.outputTokens += event.usage?.outputTokens ?? 0;
18202
18502
  }
18503
+ activity.update(statusLabelForEvent(event));
18504
+ activity.suspend();
18203
18505
  for (const line of renderLiveExecutionEvent(event)) {
18204
18506
  console.log(line);
18205
18507
  }
@@ -18208,12 +18510,15 @@ async function runGoalExecution(cwd, goal, runtime) {
18208
18510
  console.log(line);
18209
18511
  }
18210
18512
  }
18513
+ activity.resume();
18211
18514
  }
18212
18515
  });
18213
18516
  } catch (error) {
18517
+ activity.stop();
18214
18518
  const message = error instanceof Error ? error.message : String(error);
18215
18519
  throw new Error(`Execution failed while working on "${goal}": ${message}`);
18216
18520
  } finally {
18521
+ activity.stop();
18217
18522
  runtime.activeExecution = null;
18218
18523
  }
18219
18524
  const sessionPath = await saveSession(snapshot, request.cwd);
@@ -18247,6 +18552,8 @@ async function resumeGoalExecution(cwd, runtime) {
18247
18552
  label: session.id
18248
18553
  };
18249
18554
  console.log(renderRunStartCard(liveBoard));
18555
+ const activity = new ShellActivityIndicator("resuming");
18556
+ activity.start();
18250
18557
  let snapshot;
18251
18558
  try {
18252
18559
  snapshot = await new ExecutionOrchestrator().continueSession(session, {
@@ -18264,6 +18571,8 @@ async function resumeGoalExecution(cwd, runtime) {
18264
18571
  telemetry.inputTokens += event.usage?.inputTokens ?? 0;
18265
18572
  telemetry.outputTokens += event.usage?.outputTokens ?? 0;
18266
18573
  }
18574
+ activity.update(statusLabelForEvent(event));
18575
+ activity.suspend();
18267
18576
  for (const line of renderLiveExecutionEvent(event)) {
18268
18577
  console.log(line);
18269
18578
  }
@@ -18272,12 +18581,15 @@ async function resumeGoalExecution(cwd, runtime) {
18272
18581
  console.log(line);
18273
18582
  }
18274
18583
  }
18584
+ activity.resume();
18275
18585
  }
18276
18586
  });
18277
18587
  } catch (error) {
18588
+ activity.stop();
18278
18589
  const message = error instanceof Error ? error.message : String(error);
18279
18590
  throw new Error(`Resume failed for ${session.id}: ${message}`);
18280
18591
  } finally {
18592
+ activity.stop();
18281
18593
  runtime.activeExecution = null;
18282
18594
  }
18283
18595
  const sessionPath = await saveSession(snapshot, cwd);
@@ -18286,29 +18598,42 @@ async function resumeGoalExecution(cwd, runtime) {
18286
18598
  console.log(color(DIM, renderExecutionTelemetry(telemetry, startedAt)));
18287
18599
  console.log(renderShellSessionSummary(snapshot, null, sessionPath));
18288
18600
  }
18289
- function resolveApprovalChoice(snapshot, requestedId) {
18601
+ function resolveApprovalChoices(snapshot, requestedId, options = {}) {
18290
18602
  if (snapshot.pendingApprovals.length === 0) {
18291
18603
  throw new Error("No pending approvals in the current session.");
18292
18604
  }
18605
+ if (requestedId === "all") {
18606
+ if (!options.allowAll) {
18607
+ throw new Error("Use /approve-all to approve every pending action.");
18608
+ }
18609
+ return [
18610
+ ...snapshot.pendingApprovals
18611
+ ];
18612
+ }
18293
18613
  if (requestedId) {
18294
18614
  const approval = snapshot.pendingApprovals.find((candidate) => candidate.id === requestedId);
18295
18615
  if (!approval) {
18296
18616
  throw new Error(`No pending approval found for "${requestedId}".`);
18297
18617
  }
18298
- return approval;
18618
+ return [
18619
+ approval
18620
+ ];
18299
18621
  }
18300
18622
  if (snapshot.pendingApprovals.length > 1) {
18301
- throw new Error("Multiple pending approvals exist. Use /approve <approval-id> or /deny <approval-id>.");
18623
+ throw new Error("Multiple pending approvals exist. Use /approve <approval-id>, /deny <approval-id>, or /approve-all.");
18302
18624
  }
18303
- return snapshot.pendingApprovals[0];
18625
+ return [
18626
+ snapshot.pendingApprovals[0]
18627
+ ];
18304
18628
  }
18305
- async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
18629
+ async function resolvePendingApproval(cwd, runtime, decision, approvalId, options = {}) {
18306
18630
  const session = await loadLatestSession(cwd);
18307
18631
  if (!session) {
18308
18632
  throw new Error("No saved session found. Run a goal first.");
18309
18633
  }
18310
- const approval = resolveApprovalChoice(session, approvalId);
18311
- console.log(color(DIM, `${decision === "approve" ? "Approving" : "Denying"} ${approval.toolId} for ${approval.taskId}...`));
18634
+ const approvals = resolveApprovalChoices(session, approvalId, options);
18635
+ const label = approvals.length === 1 ? `${approvals[0].toolId} for ${approvals[0].taskId}` : `${approvals.length} pending actions`;
18636
+ console.log(color(DIM, `${decision === "approve" ? "Approving" : "Denying"} ${label}...`));
18312
18637
  const startedAt = Date.now();
18313
18638
  const telemetry = createExecutionTelemetry();
18314
18639
  const liveBoard = createLiveRunBoard(
@@ -18324,21 +18649,21 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
18324
18649
  const controller = new AbortController();
18325
18650
  runtime.activeExecution = {
18326
18651
  controller,
18327
- label: `${session.id}:${approval.id}`
18652
+ label: approvals.length === 1 ? `${session.id}:${approvals[0].id}` : `${session.id}:batch-approval`
18328
18653
  };
18329
18654
  console.log(renderRunStartCard(liveBoard));
18655
+ const activity = new ShellActivityIndicator(decision === "approve" ? "approving" : "denying");
18656
+ activity.start();
18330
18657
  let snapshot;
18331
18658
  try {
18332
18659
  snapshot = await new ExecutionOrchestrator().continueSession(session, {
18333
18660
  maxAutoTasks: DEFAULT_MAX_AUTO_TASKS,
18334
18661
  maxAgentSteps: DEFAULT_MAX_AGENT_STEPS,
18335
18662
  maxRepairAttempts: DEFAULT_MAX_REPAIR_ATTEMPTS2,
18336
- approvalDecisions: [
18337
- {
18338
- approvalId: approval.id,
18339
- decision
18340
- }
18341
- ],
18663
+ approvalDecisions: approvals.map((approval) => ({
18664
+ approvalId: approval.id,
18665
+ decision
18666
+ })),
18342
18667
  signal: controller.signal,
18343
18668
  onProgress: async (event) => {
18344
18669
  updateLiveRunBoard(liveBoard, event);
@@ -18350,6 +18675,8 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
18350
18675
  telemetry.inputTokens += event.usage?.inputTokens ?? 0;
18351
18676
  telemetry.outputTokens += event.usage?.outputTokens ?? 0;
18352
18677
  }
18678
+ activity.update(statusLabelForEvent(event));
18679
+ activity.suspend();
18353
18680
  for (const line of renderLiveExecutionEvent(event)) {
18354
18681
  console.log(line);
18355
18682
  }
@@ -18358,12 +18685,15 @@ async function resolvePendingApproval(cwd, runtime, decision, approvalId) {
18358
18685
  console.log(line);
18359
18686
  }
18360
18687
  }
18688
+ activity.resume();
18361
18689
  }
18362
18690
  });
18363
18691
  } catch (error) {
18692
+ activity.stop();
18364
18693
  const message = error instanceof Error ? error.message : String(error);
18365
- throw new Error(`Approval resolution failed for ${approval.id}: ${message}`);
18694
+ throw new Error(`Approval resolution failed for ${approvals.map((approval) => approval.id).join(", ")}: ${message}`);
18366
18695
  } finally {
18696
+ activity.stop();
18367
18697
  runtime.activeExecution = null;
18368
18698
  }
18369
18699
  const sessionPath = await saveSession(snapshot, cwd);
@@ -18379,6 +18709,28 @@ async function printLatestPlanSummary(cwd) {
18379
18709
  }
18380
18710
  console.log(renderShellPlanSummary(plan).join("\n"));
18381
18711
  }
18712
+ async function handleApprovalModeCommand(cwd, tokens) {
18713
+ const config = await loadConfig(cwd);
18714
+ if (!config) {
18715
+ throw new Error("No config found. Run /init or /providers add <template> first.");
18716
+ }
18717
+ const subcommand = tokens[1]?.trim().toLowerCase();
18718
+ if (!subcommand || subcommand === "status") {
18719
+ console.log(`approval mode: ${config.approvalMode}`);
18720
+ console.log("manual = ask before risky actions");
18721
+ console.log("auto = approve non-destructive actions automatically");
18722
+ return;
18723
+ }
18724
+ if (subcommand !== "manual" && subcommand !== "auto") {
18725
+ throw new Error("Usage: /approval [manual|auto|status]");
18726
+ }
18727
+ const outputPath = await saveConfig({
18728
+ ...config,
18729
+ approvalMode: subcommand
18730
+ }, cwd);
18731
+ console.log(`Updated ${outputPath}`);
18732
+ console.log(`approval mode: ${subcommand}`);
18733
+ }
18382
18734
  async function createPlanOnly(cwd, goal) {
18383
18735
  const request = {
18384
18736
  goal,
@@ -18387,7 +18739,14 @@ async function createPlanOnly(cwd, goal) {
18387
18739
  workspaceState: await inferPlanningWorkspaceState(cwd, goal),
18388
18740
  constraints: []
18389
18741
  };
18390
- const planResult = await generatePlanForRequest(request);
18742
+ const activity = new ShellActivityIndicator("planning");
18743
+ activity.start();
18744
+ let planResult;
18745
+ try {
18746
+ planResult = await generatePlanForRequest(request);
18747
+ } finally {
18748
+ activity.stop();
18749
+ }
18391
18750
  const plan = planResult.plan;
18392
18751
  const planPath = await savePlan(plan, cwd);
18393
18752
  for (const line of renderPlanGenerationNotes(planResult)) {
@@ -18966,13 +19325,35 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
18966
19325
  await resumeGoalExecution(cwd, runtime);
18967
19326
  return cwd;
18968
19327
  }
19328
+ if (head === "approval") {
19329
+ await handleApprovalModeCommand(cwd, [
19330
+ "approval",
19331
+ ...tokens.slice(1)
19332
+ ]);
19333
+ return cwd;
19334
+ }
19335
+ if (head === "approve-all") {
19336
+ await resolvePendingApproval(
19337
+ cwd,
19338
+ runtime,
19339
+ "approve",
19340
+ "all",
19341
+ {
19342
+ allowAll: true
19343
+ }
19344
+ );
19345
+ return cwd;
19346
+ }
18969
19347
  if (head === "approve" || head === "deny") {
18970
19348
  const approvalId = tokens[1]?.trim();
18971
19349
  await resolvePendingApproval(
18972
19350
  cwd,
18973
19351
  runtime,
18974
19352
  head === "approve" ? "approve" : "deny",
18975
- approvalId && approvalId.length > 0 ? approvalId : void 0
19353
+ approvalId && approvalId.length > 0 ? approvalId : void 0,
19354
+ {
19355
+ allowAll: false
19356
+ }
18976
19357
  );
18977
19358
  return cwd;
18978
19359
  }