@pentoshi/clai 2.0.36 → 2.0.38

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 (47) hide show
  1. package/README.md +1 -0
  2. package/dist/agent/confirm-port.js +6 -1
  3. package/dist/agent/confirm-port.js.map +1 -1
  4. package/dist/agent/events.d.ts +5 -0
  5. package/dist/agent/loop-guard.js +8 -1
  6. package/dist/agent/loop-guard.js.map +1 -1
  7. package/dist/agent/progress.js +5 -1
  8. package/dist/agent/progress.js.map +1 -1
  9. package/dist/agent/runner.js +215 -31
  10. package/dist/agent/runner.js.map +1 -1
  11. package/dist/agent/tool-call-parser.d.ts +11 -0
  12. package/dist/agent/tool-call-parser.js +151 -8
  13. package/dist/agent/tool-call-parser.js.map +1 -1
  14. package/dist/agent/tool-output-formatting.js +1 -0
  15. package/dist/agent/tool-output-formatting.js.map +1 -1
  16. package/dist/commands/update.js +1 -1
  17. package/dist/prompts/index.d.ts +8 -1
  18. package/dist/prompts/index.js +8 -4
  19. package/dist/prompts/index.js.map +1 -1
  20. package/dist/repl/slash-commands.js +5 -0
  21. package/dist/repl/slash-commands.js.map +1 -1
  22. package/dist/repl.js +43 -0
  23. package/dist/repl.js.map +1 -1
  24. package/dist/safety/classifier.js +3 -1
  25. package/dist/safety/classifier.js.map +1 -1
  26. package/dist/store/config.d.ts +2 -0
  27. package/dist/store/config.js +1 -0
  28. package/dist/store/config.js.map +1 -1
  29. package/dist/tools/fs.d.ts +4 -0
  30. package/dist/tools/fs.js +48 -0
  31. package/dist/tools/fs.js.map +1 -1
  32. package/dist/tools/registry.js +7 -1
  33. package/dist/tools/registry.js.map +1 -1
  34. package/dist/tui/App.js +51 -4
  35. package/dist/tui/App.js.map +1 -1
  36. package/dist/tui/components/ConfirmModal.d.ts +2 -1
  37. package/dist/tui/components/ConfirmModal.js +3 -1
  38. package/dist/tui/components/ConfirmModal.js.map +1 -1
  39. package/dist/tui/render-lines.d.ts +1 -0
  40. package/dist/tui/render-lines.js +4 -1
  41. package/dist/tui/render-lines.js.map +1 -1
  42. package/dist/tui/state.js +18 -1
  43. package/dist/tui/state.js.map +1 -1
  44. package/dist/ui/intro-card.d.ts +1 -0
  45. package/dist/ui/intro-card.js +8 -6
  46. package/dist/ui/intro-card.js.map +1 -1
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -204,6 +204,7 @@ export OLLAMA_HOST=http://localhost:11434
204
204
  | `/reset` | Clear all saved history |
205
205
  | `/cwd <path>` | Change working directory |
206
206
  | `/allow <tool>` | Whitelist a tool for the session |
207
+ | `/permissions [default\|allow-all]` | Toggle confirmation prompting (allow-all bypasses confirm gates except passwords) |
207
208
  | `/plan` | View the current session plan (also `Ctrl+P`) |
208
209
  | `/implement` | Approve the current plan and have clai execute it |
209
210
  | `/discard` | Discard the current plan so later messages ignore it |
@@ -55,8 +55,11 @@ export const inquirerConfirmPort = {
55
55
  export async function ensurePentestAuthorization(call, autoConfirm, session, confirmPort) {
56
56
  if (!isPentestToolCall(call))
57
57
  return true;
58
+ const config = getConfig();
59
+ if (config.permissions === "allow-all")
60
+ return true;
58
61
  // Persistent auth (via `clai authorize-pentest AGREE`) wins.
59
- if (getConfig().pentestAuthorized)
62
+ if (config.pentestAuthorized)
60
63
  return true;
61
64
  // Session auth flipped earlier in this session — no re-prompt.
62
65
  if (session.pentestAuthorized.value)
@@ -75,6 +78,8 @@ export async function ensurePentestAuthorization(call, autoConfirm, session, con
75
78
  }
76
79
  export async function confirmToolExecution(call, autoConfirm, session, confirmPort) {
77
80
  const config = getConfig();
81
+ if (config.permissions === "allow-all")
82
+ return true;
78
83
  if (autoConfirm)
79
84
  return true;
80
85
  if (session.allow.has(call.name))
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-port.js","sourceRoot":"","sources":["../../src/agent/confirm-port.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"confirm-port.js","sourceRoot":"","sources":["../../src/agent/confirm-port.ts"],"names":[],"mappings":"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"}
@@ -59,4 +59,9 @@ export type AgentEvent = {
59
59
  } | {
60
60
  type: "turn-error";
61
61
  message: string;
62
+ } | {
63
+ type: "compacted";
64
+ summary: string;
65
+ beforeTokens: number;
66
+ afterTokens: number;
62
67
  };
@@ -75,6 +75,9 @@ export class LoopGuard {
75
75
  * removes their results.
76
76
  */
77
77
  shouldBlock(name, args) {
78
+ if (name === "task.update" || name === "plan.create") {
79
+ return { block: false };
80
+ }
78
81
  const sig = this.canonicalize(name, args);
79
82
  const count = this.signatureCount.get(sig) ?? 0;
80
83
  if (count === 0)
@@ -85,7 +88,11 @@ export class LoopGuard {
85
88
  // Mutating file tools deserve tool-appropriate wording. Telling a model
86
89
  // that just wrote a file to "use the results you already have" is
87
90
  // nonsensical and has caused models to assume the whole task is done.
88
- const isWrite = name === "fs.write" || name === "fs.writeMany" || name === "fs.edit" || name === "fs.replaceLines";
91
+ const isWrite = name === "fs.write" ||
92
+ name === "fs.writeMany" ||
93
+ name === "fs.edit" ||
94
+ name === "fs.replaceLines" ||
95
+ name === "fs.append";
89
96
  // Read-only tools get a higher threshold — they may need re-calling
90
97
  // after context compaction removes their earlier results.
91
98
  const threshold = READ_ONLY_TOOLS.has(name) ? 3 : 2;
@@ -1 +1 @@
1
- {"version":3,"file":"loop-guard.js","sourceRoot":"","sources":["../../src/agent/loop-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loop-guard.js","sourceRoot":"","sources":["../../src/agent/loop-guard.ts"],"names":[],"mappings":"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"}
@@ -67,7 +67,11 @@ export function evaluateProgress(plan, call, result) {
67
67
  };
68
68
  }
69
69
  // File edit/write goals
70
- if ((call.name === "fs.edit" || call.name === "fs.replaceLines" || call.name === "fs.write") && ok) {
70
+ if ((call.name === "fs.edit" ||
71
+ call.name === "fs.replaceLines" ||
72
+ call.name === "fs.write" ||
73
+ call.name === "fs.append") &&
74
+ ok) {
71
75
  return {
72
76
  madeProgress: true,
73
77
  goalSatisfied: true,
@@ -1 +1 @@
1
- {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/agent/progress.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/agent/progress.ts"],"names":[],"mappings":"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"}
@@ -1,16 +1,16 @@
1
1
  import chalk from "chalk";
2
2
  import { homedir } from "node:os";
3
- import { join, resolve } from "node:path";
3
+ import { join, relative, resolve } from "node:path";
4
4
  import { streamWithProvider, completeWithProvider } from "../llm/router.js";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { jobManager } from "../tools/jobs.js";
7
- import { renderAgentSystemPrompt, } from "../prompts/index.js";
7
+ import { renderAgentSystemPrompt, scratchDirFor, } from "../prompts/index.js";
8
8
  import { getConfig } from "../store/config.js";
9
9
  import { classifyToolCall, isPentestToolCall, scopeHint, scopeTargetForToolCall, } from "../safety/classifier.js";
10
10
  import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
11
11
  import { looksInteractiveStdin } from "../tools/shell.js";
12
12
  import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
13
- import { compactMessagesWithSummary, estimateMessagesTokens, } from "./context-manager.js";
13
+ import { compactMessagesWithSummary, estimateMessagesTokens, COMPACTION_MEMORY_PREFIX, } from "./context-manager.js";
14
14
  import { auditLog } from "../store/logs.js";
15
15
  import { loadProjectContext } from "../store/project.js";
16
16
  import { loadScope, isScopeActive, targetInScope } from "../store/scope.js";
@@ -23,7 +23,7 @@ import { analyzeTask } from "./task-analyzer.js";
23
23
  import { LoopGuard } from "./loop-guard.js";
24
24
  import { loadPlan } from "../store/plan.js";
25
25
  import { pathInsideSandbox } from "../tools/fs.js";
26
- import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
26
+ import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeActionNarration, looksLikePlanNarration, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, pentestWorkflowDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
27
27
  import { createSessionPolicy, isPreApprovalAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
28
28
  import { saveToolOutput, summarizeOutput, formatToolContext, } from "./tool-output-formatting.js";
29
29
  import { planContextMessage, handlePlanTool, } from "./plan-tool.js";
@@ -38,6 +38,78 @@ export { createSessionPolicy, isPreApprovalAllowedTool, isPlanApprovedByStatus,
38
38
  export function styleToolChatter(call, text) {
39
39
  return shouldDimToolChatter(call) ? chalk.dim(text) : text;
40
40
  }
41
+ /**
42
+ * Tool names that may write into the project tree. Writes restricted to the
43
+ * per-project scratch directory (under tmpdir()/clai/<name>) are exempted
44
+ * from the active-plan and plan-approved gates so the model can use scratch
45
+ * space to build / inspect / stage work without first creating a plan.
46
+ */
47
+ const SCRATCH_WRITABLE_TOOLS = new Set([
48
+ "fs.write",
49
+ "fs.writeMany",
50
+ "fs.edit",
51
+ "fs.replaceLines",
52
+ "fs.append",
53
+ "fs.delete",
54
+ ]);
55
+ /**
56
+ * Expand `~` the same way `src/tools/fs.ts` does so callers can compare an
57
+ * already-expanded scratch path against paths supplied by the model.
58
+ */
59
+ function expandHomeLocal(path) {
60
+ if (path === "~")
61
+ return homedir();
62
+ if (path.startsWith("~/") || path.startsWith("~\\")) {
63
+ return resolve(homedir(), path.slice(2));
64
+ }
65
+ return path;
66
+ }
67
+ /**
68
+ * Extract the target path(s) a write-shaped tool call would touch. Returns
69
+ * an empty array when the call has no resolvable path (so the caller can
70
+ * treat it as NOT scratch-only and fall through to the normal gates).
71
+ */
72
+ function scratchWriteTargetPaths(call) {
73
+ if (call.name === "fs.writeMany") {
74
+ const files = call.args.files;
75
+ if (!Array.isArray(files))
76
+ return [];
77
+ const paths = [];
78
+ for (const entry of files) {
79
+ if (entry && typeof entry === "object" && "path" in entry) {
80
+ const p = entry.path;
81
+ if (typeof p === "string" && p.length > 0)
82
+ paths.push(p);
83
+ }
84
+ }
85
+ return paths;
86
+ }
87
+ const pathArg = call.args.path;
88
+ if (typeof pathArg !== "string" || pathArg.length === 0)
89
+ return [];
90
+ return [pathArg];
91
+ }
92
+ /**
93
+ * True iff every target path this call would write is inside the resolved
94
+ * scratch directory. A path is considered inside when its `path.relative`
95
+ * against the scratch root is empty (the scratch root itself) or does not
96
+ * start with `..` (no parent traversal). Calls without a recognizable
97
+ * target path return false so they fall through to the normal gates.
98
+ */
99
+ function isScratchOnlyWrite(call, scratchDir) {
100
+ if (!SCRATCH_WRITABLE_TOOLS.has(call.name))
101
+ return false;
102
+ const paths = scratchWriteTargetPaths(call);
103
+ if (paths.length === 0)
104
+ return false;
105
+ const resolvedScratch = resolve(scratchDir);
106
+ return paths.every((raw) => {
107
+ const expanded = expandHomeLocal(raw);
108
+ const resolved = resolve(expanded);
109
+ const rel = relative(resolvedScratch, resolved);
110
+ return rel === "" || (!rel.startsWith("..") && rel !== "..");
111
+ });
112
+ }
41
113
  export async function runAgentLoop(prompt, options = {}) {
42
114
  const writesDirectly = !options.onEvent;
43
115
  const emit = (event) => options.onEvent?.(event);
@@ -114,6 +186,22 @@ export async function runAgentLoop(prompt, options = {}) {
114
186
  }
115
187
  emit(event);
116
188
  };
189
+ /** Strip a known prefix from a string, returning the remainder unchanged. */
190
+ const insertedText = (value, prefix) => value.startsWith(prefix) ? value.slice(prefix.length) : value;
191
+ /**
192
+ * Surface the compacted-context summary to both the TUI (via an event)
193
+ * and, when running with a direct stdout writer, as a rendered box.
194
+ * Token-count stats are always emitted for logs.
195
+ */
196
+ const writeCompacted = (summary, beforeTokens, afterTokens) => {
197
+ emit({ type: "compacted", summary, beforeTokens, afterTokens });
198
+ if (writesDirectly) {
199
+ const header = chalk.dim(" \u2726 Compacted Context");
200
+ const footer = chalk.dim(` ~${beforeTokens.toLocaleString()} \u2192 ~${afterTokens.toLocaleString()} tokens`);
201
+ const body = summary ? renderMarkdown(summary) : "(empty summary)";
202
+ process.stdout.write(`${header}\n\n${body}\n${footer}\n`);
203
+ }
204
+ };
117
205
  // Points at the live message array so finishTurn can hand the full
118
206
  // conversation back to the caller. Assigned once `messages` is built below;
119
207
  // all later mutations are in-place so this reference stays current.
@@ -199,6 +287,15 @@ export async function runAgentLoop(prompt, options = {}) {
199
287
  if (buildLikeTurn && !activePlan && !informationalQuery) {
200
288
  systemSections.push(buildWorkflowDirective());
201
289
  }
290
+ // Pentest / security engagements need a different shape than a coding
291
+ // build: recon first, then a plan built from real findings, then
292
+ // incremental task additions as new attack surface appears. The
293
+ // directive is only injected before a plan exists; once a plan is in
294
+ // place (or being refined), the ACTIVE PLAN block already carries the
295
+ // current task state and recon-vs-active-tool guidance.
296
+ if (pentestLikeTurn && !activePlan && !informationalQuery) {
297
+ systemSections.push(pentestWorkflowDirective());
298
+ }
202
299
  const fullSystemPrompt = systemSections.join("\n\n");
203
300
  const userMessage = { role: "user", content: prompt };
204
301
  if (options.images && options.images.length > 0) {
@@ -306,6 +403,7 @@ export async function runAgentLoop(prompt, options = {}) {
306
403
  let productiveSteps = 0;
307
404
  let step = -1;
308
405
  let nextToolEventId = 0;
406
+ const alreadyPrintedIds = new Set();
309
407
  const promptMutex = {
310
408
  promise: Promise.resolve(),
311
409
  async acquire() {
@@ -320,6 +418,9 @@ export async function runAgentLoop(prompt, options = {}) {
320
418
  },
321
419
  };
322
420
  async function executeSingleTool(rawCall, toolEventId, parentSignal) {
421
+ // Resolved once per call so the scratch-only exemption can compare the
422
+ // model-supplied paths against the canonical per-project scratch root.
423
+ const scratchDir = scratchDirFor(safeCwd());
323
424
  let call = normalizeToolCall(rawCall);
324
425
  if (call.name === "image.ocr" && !imageOcrEnabled) {
325
426
  writeNotice("info", "skipped OCR because the original image is attached to the vision model", chalk.dim(" ℹ skipped OCR — inspecting the attached image directly\n"));
@@ -333,7 +434,8 @@ export async function runAgentLoop(prompt, options = {}) {
333
434
  const isWrite = call.name === "fs.write" ||
334
435
  call.name === "fs.writeMany" ||
335
436
  call.name === "fs.edit" ||
336
- call.name === "fs.replaceLines";
437
+ call.name === "fs.replaceLines" ||
438
+ call.name === "fs.append";
337
439
  const reason = `${call.name} was already called with the same arguments — ${isWrite ? "moving on" : "forcing summary"}`;
338
440
  writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
339
441
  const result = { ok: false, output: reason, exitCode: 1 };
@@ -355,10 +457,18 @@ export async function runAgentLoop(prompt, options = {}) {
355
457
  });
356
458
  if (planResult.handled) {
357
459
  loopGuard.recordAttempt(step, call.name, call.args, planResult.ok, 0);
460
+ if (!alreadyPrintedIds.has(toolEventId)) {
461
+ const toolCallLine = chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`);
462
+ writeToolCall(toolEventId, call, styleToolChatter(call, toolCallLine) + "\n");
463
+ alreadyPrintedIds.add(toolEventId);
464
+ }
358
465
  if (planResult.plan) {
359
466
  writePlanUpdate(planResult.plan, planResult.display);
360
467
  }
361
468
  const result = { ok: planResult.ok, output: planResult.modelNote };
469
+ emitToolResult(toolEventId, result, planResult.modelNote);
470
+ const statusIcon = result.ok ? chalk.green(" ✓") : chalk.red(" ✗");
471
+ writeToolOutput(toolEventId, result.ok ? "ok\n" : "failed\n", statusIcon + "\n");
362
472
  return {
363
473
  ok: planResult.ok,
364
474
  call,
@@ -374,19 +484,34 @@ export async function runAgentLoop(prompt, options = {}) {
374
484
  decision,
375
485
  scope: isScopeActive(scope) ? (scope.name ?? "(unnamed)") : "(none)",
376
486
  });
377
- if (activePlan &&
378
- !session.planApproved.value &&
379
- !isPreApprovalAllowedTool(call.name)) {
380
- const reason = `plan awaiting approval — ${call.name} is blocked until you /implement (or /discard)`;
381
- writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
382
- const result = { ok: false, output: reason, exitCode: 1 };
383
- return {
384
- ok: false,
385
- call,
386
- result,
387
- contextOutput: reason,
388
- blockOrCancel: true,
389
- };
487
+ const isMutatingAction = (decision.level === "confirm" || decision.level === "block") &&
488
+ !isPreApprovalAllowedTool(call.name) &&
489
+ !isScratchOnlyWrite(call, scratchDir);
490
+ if (isMutatingAction) {
491
+ if (!activePlan) {
492
+ const reason = `No active plan — ${call.name} is blocked. You must first create a plan using plan.create before executing mutating actions.`;
493
+ writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
494
+ const result = { ok: false, output: reason, exitCode: 1 };
495
+ return {
496
+ ok: false,
497
+ call,
498
+ result,
499
+ contextOutput: reason,
500
+ blockOrCancel: true,
501
+ };
502
+ }
503
+ if (!session.planApproved.value) {
504
+ const reason = `plan awaiting approval — ${call.name} is blocked until you /implement (or /discard)`;
505
+ writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
506
+ const result = { ok: false, output: reason, exitCode: 1 };
507
+ return {
508
+ ok: false,
509
+ call,
510
+ result,
511
+ contextOutput: reason,
512
+ blockOrCancel: true,
513
+ };
514
+ }
390
515
  }
391
516
  // Task-scoped execution gate
392
517
  // Once a plan is approved, every non-plan tool call must run while
@@ -423,8 +548,11 @@ export async function runAgentLoop(prompt, options = {}) {
423
548
  if (call.name === "web.search") {
424
549
  sawFreshWebSearch = true;
425
550
  }
426
- const toolCallLine = chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`);
427
- writeToolCall(toolEventId, call, styleToolChatter(call, toolCallLine) + "\n");
551
+ if (!alreadyPrintedIds.has(toolEventId)) {
552
+ const toolCallLine = chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`);
553
+ writeToolCall(toolEventId, call, styleToolChatter(call, toolCallLine) + "\n");
554
+ alreadyPrintedIds.add(toolEventId);
555
+ }
428
556
  const scopeTarget = scopeTargetForToolCall(call);
429
557
  if (scopeTarget &&
430
558
  (!isScopeActive(scope) || !targetInScope(scopeTarget, scope))) {
@@ -499,7 +627,7 @@ export async function runAgentLoop(prompt, options = {}) {
499
627
  restoreInteractiveStdin();
500
628
  if (!ok) {
501
629
  const lastAnswer = "Cancelled.";
502
- writeNotice("warn", "cancelled", chalk.yellow(` ✗ cancelled`) + "\n");
630
+ writeToolBlocked(toolEventId, call.name, lastAnswer, chalk.red(` ✗ cancelled`) + "\n");
503
631
  const result = { ok: false, output: lastAnswer, exitCode: 1 };
504
632
  return {
505
633
  ok: false,
@@ -746,6 +874,16 @@ export async function runAgentLoop(prompt, options = {}) {
746
874
  estimatedTokens: afterTokens,
747
875
  reason,
748
876
  });
877
+ // Extract the inserted compaction memory so we can surface the
878
+ // summary itself (not just token-count stats). The summary lives in
879
+ // the first system message whose content begins with
880
+ // COMPACTION_MEMORY_PREFIX.
881
+ const insertedSummary = messages.find((m) => m.role === "system" &&
882
+ m.content.startsWith(COMPACTION_MEMORY_PREFIX))?.content ?? "";
883
+ const summaryText = insertedSummary.startsWith(`${COMPACTION_MEMORY_PREFIX}\n\n`)
884
+ ? insertedText(insertedSummary, `${COMPACTION_MEMORY_PREFIX}\n\n`)
885
+ : insertedText(insertedSummary, COMPACTION_MEMORY_PREFIX);
886
+ writeCompacted(summaryText, beforeTokens, afterTokens);
749
887
  writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens)`, chalk.dim(` ℹ context auto-compacted (~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens)\n`));
750
888
  }
751
889
  catch (error) {
@@ -836,7 +974,7 @@ export async function runAgentLoop(prompt, options = {}) {
836
974
  // A spinner gives the user feedback during long thinking phases on
837
975
  // models like glm-5.1 / deepseek-v4-flash that stream reasoning first.
838
976
  const streamLabel = step === 0 ? "waiting for model" : `step ${step + 1}`;
839
- const spinner = writesDirectly
977
+ let spinner = writesDirectly
840
978
  ? startThinkingSpinner(streamLabel, options.signal)
841
979
  : noopSpinner;
842
980
  if (!writesDirectly) {
@@ -845,6 +983,10 @@ export async function runAgentLoop(prompt, options = {}) {
845
983
  let sawReasoning = false;
846
984
  let inThinking = false;
847
985
  let emittedThinkingStatus = false;
986
+ let generatedTokens = 0;
987
+ let accumulatedText = "";
988
+ const callIds = [];
989
+ let streamedCallsCount = 0;
848
990
  const deltaParser = writesDirectly
849
991
  ? undefined
850
992
  : createThinkingStreamParser((text) => emit({ type: "assistant-delta", text }), (text) => {
@@ -877,6 +1019,26 @@ export async function runAgentLoop(prompt, options = {}) {
877
1019
  thinking: config.thinking,
878
1020
  }, (token) => {
879
1021
  deltaParser?.push(token);
1022
+ generatedTokens += 1;
1023
+ accumulatedText += token;
1024
+ const parsedCalls = parseAllToolCalls(accumulatedText);
1025
+ if (parsedCalls.length > streamedCallsCount) {
1026
+ if (writesDirectly) {
1027
+ spinner.stop();
1028
+ }
1029
+ while (streamedCallsCount < parsedCalls.length) {
1030
+ const call = parsedCalls[streamedCallsCount];
1031
+ const eventId = `tool-${++nextToolEventId}`;
1032
+ callIds.push(eventId);
1033
+ alreadyPrintedIds.add(eventId);
1034
+ const toolCallLine = chalk.cyan(` ▶ ${call.name}`) + chalk.gray(` ${formatToolArgs(call)}`);
1035
+ writeToolCall(eventId, call, styleToolChatter(call, toolCallLine) + "\n");
1036
+ streamedCallsCount += 1;
1037
+ }
1038
+ if (writesDirectly) {
1039
+ spinner = startThinkingSpinner(`generating response (${generatedTokens} tokens)`, options.signal);
1040
+ }
1041
+ }
880
1042
  // Heuristic: <think>… markers and reasoning_content tokens flow
881
1043
  // through onToken. Surface activity in the spinner so the screen
882
1044
  // is never empty for minutes.
@@ -889,6 +1051,8 @@ export async function runAgentLoop(prompt, options = {}) {
889
1051
  }
890
1052
  if (/<\/think>/i.test(token)) {
891
1053
  inThinking = false;
1054
+ spinner.setLabel("generating response (0 tokens)");
1055
+ generatedTokens = 0;
892
1056
  }
893
1057
  // Only push reasoning tokens to the spinner preview. Visible
894
1058
  // answer / tool-call tokens should NOT go through the dim
@@ -904,6 +1068,11 @@ export async function runAgentLoop(prompt, options = {}) {
904
1068
  spinner.bumpReasoning(approx);
905
1069
  }
906
1070
  }
1071
+ else {
1072
+ if (generatedTokens % 10 === 0) {
1073
+ spinner.setLabel(`generating response (${generatedTokens} tokens)`);
1074
+ }
1075
+ }
907
1076
  }, (status) => {
908
1077
  spinner.stop();
909
1078
  writeStatus(status, chalk.dim(status));
@@ -1235,9 +1404,7 @@ export async function runAgentLoop(prompt, options = {}) {
1235
1404
  ].filter(Boolean);
1236
1405
  messages.push({
1237
1406
  role: "user",
1238
- content: "Runtime verification is incomplete. Do not claim the app is running. " +
1239
- `Missing evidence: ${missing.join(", ")}. ` +
1240
- "Run the missing checks now. Stop the job afterward unless the user explicitly asked to keep it running, and report whether it remains running truthfully.",
1407
+ content: "Run the missing checks now. Keep the dev server/job running in the background so that the user can interact with the live application, and print the localhost link. Report whether it remains running truthfully.",
1241
1408
  });
1242
1409
  continue;
1243
1410
  }
@@ -1259,15 +1426,21 @@ export async function runAgentLoop(prompt, options = {}) {
1259
1426
  role: "assistant",
1260
1427
  content: assistantText.visible,
1261
1428
  });
1429
+ let instruction = `Resume now with the NEXT task ${next.id} ("${next.title}"): `;
1430
+ if (next.state === "pending") {
1431
+ instruction += `call task.update {taskId:"${next.id}", state:"in_progress"}, then do the real work with a tool call (fs.writeMany / shell.exec / shell.start), VERIFY it, and mark it done. `;
1432
+ }
1433
+ else {
1434
+ instruction += `do the real work with a tool call (fs.writeMany / shell.exec / shell.start) to complete it, VERIFY it, and mark it done (call task.update {taskId:"${next.id}", state:"done"}). `;
1435
+ }
1436
+ instruction += `Continue task by task until EVERY task is actually finished.`;
1262
1437
  messages.push({
1263
1438
  role: "user",
1264
1439
  content: `You have NOT finished the approved plan: ${unfinished.length} task(s) remain ` +
1265
1440
  `(${unfinished.map((t) => `[${t.id}] ${t.title}`).join("; ")}). ` +
1266
1441
  `Do NOT claim the work is complete, that files were created, or that a server is running ` +
1267
1442
  `unless a tool call actually succeeded and you saw the output. ` +
1268
- `Resume now with the NEXT task ${next.id} ("${next.title}"): call task.update {taskId:"${next.id}", state:"in_progress"}, ` +
1269
- `then do the real work with a tool call (fs.writeMany / shell.exec / shell.start), VERIFY it, and mark it done. ` +
1270
- `Continue task by task until EVERY task is actually finished.`,
1443
+ instruction,
1271
1444
  });
1272
1445
  continue;
1273
1446
  }
@@ -1394,14 +1567,25 @@ export async function runAgentLoop(prompt, options = {}) {
1394
1567
  if (aborted || blocked || failed || awaitingPlanApproval)
1395
1568
  break;
1396
1569
  if (group.length === 1) {
1397
- const id = `tool-${++nextToolEventId}`;
1398
- const res = await executeSingleTool(group[0], id, options.signal || new AbortController().signal);
1570
+ const call = group[0];
1571
+ const idx = allCalls.indexOf(call);
1572
+ if (idx >= 0 && !callIds[idx]) {
1573
+ callIds[idx] = `tool-${++nextToolEventId}`;
1574
+ }
1575
+ const id = (idx >= 0 ? callIds[idx] : undefined) ?? `tool-${++nextToolEventId}`;
1576
+ const res = await executeSingleTool(call, id, options.signal || new AbortController().signal);
1399
1577
  recordResult(res);
1400
1578
  }
1401
1579
  else {
1402
1580
  // Concurrent group — assign ids in document order, then push their
1403
1581
  // results in document order for a stable transcript.
1404
- const ids = group.map(() => `tool-${++nextToolEventId}`);
1582
+ const ids = group.map((c) => {
1583
+ const idx = allCalls.indexOf(c);
1584
+ if (idx >= 0 && !callIds[idx]) {
1585
+ callIds[idx] = `tool-${++nextToolEventId}`;
1586
+ }
1587
+ return (idx >= 0 ? callIds[idx] : undefined) ?? `tool-${++nextToolEventId}`;
1588
+ });
1405
1589
  const results = await Promise.all(group.map((c, k) => executeSingleTool(c, ids[k], options.signal || new AbortController().signal)));
1406
1590
  for (const res of results)
1407
1591
  recordResult(res);