claude-overnight 1.16.7 → 1.16.10

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.
@@ -153,8 +153,14 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
153
153
  const ev = msg.event;
154
154
  if (ev?.type === "content_block_start" && ev.content_block?.type === "tool_use") {
155
155
  toolCount++;
156
- lastLogText = ev.content_block.name;
157
- onLog(ev.content_block.name, "event");
156
+ const toolName = ev.content_block.name;
157
+ const input = ev.content_block.input;
158
+ // Enrich event with target file/path for readability
159
+ const target = input?.path ?? input?.file_path ?? input?.command
160
+ ? (typeof input?.command === "string" ? input.command.split(" ").slice(0, 3).join(" ") : "")
161
+ : "";
162
+ lastLogText = target ? `${toolName} ${target}` : toolName;
163
+ onLog(target ? `${toolName} → ${target}` : toolName, "event");
158
164
  }
159
165
  if (ev?.type === "content_block_delta") {
160
166
  const delta = ev.delta;
package/dist/planner.js CHANGED
@@ -212,7 +212,9 @@ export function buildThinkingTasks(objective, themes, designDir, plannerModel, p
212
212
  const prevBlock = previousKnowledge ? `\nKNOWLEDGE FROM PREVIOUS RUNS:\n${previousKnowledge}\n\nBuild on this — don't re-discover what's already known.\n` : "";
213
213
  return themes.map((theme, i) => ({
214
214
  id: `think-${i}`,
215
- prompt: `You are a senior architect exploring a codebase to design a solution.
215
+ prompt: `## Research: ${theme}
216
+
217
+ You are a senior architect exploring a codebase to design a solution.
216
218
 
217
219
  OVERALL OBJECTIVE: ${objective}
218
220
  ${prevBlock}
package/dist/render.d.ts CHANGED
@@ -10,7 +10,7 @@ type RLGetter = () => {
10
10
  windows: Map<string, RateLimitWindow>;
11
11
  resetsAt?: number;
12
12
  };
13
- export declare function renderFrame(swarm: Swarm, showHotkeys: boolean, runInfo?: RunInfo): string;
13
+ export declare function renderFrame(swarm: Swarm, showHotkeys: boolean, runInfo?: RunInfo, selectedAgentId?: number): string;
14
14
  export interface SteeringViewData {
15
15
  /** The ephemeral ticker heartbeat — elapsed, tool count, cost, current reasoning snippet. */
16
16
  statusLine: string;
package/dist/render.js CHANGED
@@ -138,7 +138,7 @@ function renderUsageBars(out, w, swarm) {
138
138
  out.push(` ${chalk.dim("Extra ")}${barStr} ${label}`);
139
139
  }
140
140
  }
141
- export function renderFrame(swarm, showHotkeys, runInfo) {
141
+ export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId) {
142
142
  const w = Math.max((process.stdout.columns ?? 80) || 80, 60);
143
143
  const out = [];
144
144
  const stoppingTag = swarm.aborted ? chalk.yellow("STOPPING") : "";
@@ -175,10 +175,38 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
175
175
  out.push(chalk.gray(" # Status Task" + " ".repeat(Math.max(1, w - 56)) + "Action"));
176
176
  out.push(chalk.gray(" " + "\u2500".repeat(Math.min(w - 4, 100))));
177
177
  for (const a of show)
178
- out.push(fmtRow(a, w));
178
+ out.push(fmtRow(a, w, a.id === (selectedAgentId ?? -1)));
179
179
  if (swarm.pending > 0)
180
180
  out.push(chalk.gray(` ... + ${swarm.pending} queued`));
181
181
  }
182
+ // ── Agent detail (progressive discovery) ──
183
+ const detailAgent = selectedAgentId != null
184
+ ? swarm.agents.find(a => a.id === selectedAgentId)
185
+ : undefined;
186
+ if (detailAgent) {
187
+ out.push("");
188
+ section(out, w, `Agent ${detailAgent.id} detail \u00b7 [d] next \u00b7 [Esc] close`);
189
+ const taskLines = detailAgent.task.prompt.split("\n");
190
+ const maxTaskLines = Math.min(6, taskLines.length);
191
+ for (let i = 0; i < maxTaskLines; i++) {
192
+ out.push(` ${chalk.dim(truncate(taskLines[i].trim(), w - 6))}`);
193
+ }
194
+ if (taskLines.length > maxTaskLines)
195
+ out.push(chalk.dim(` \u2026 + ${taskLines.length - maxTaskLines} more lines`));
196
+ const meta = [];
197
+ if (detailAgent.currentTool)
198
+ meta.push(chalk.yellow(`tool: ${detailAgent.currentTool}`));
199
+ if (detailAgent.lastText)
200
+ meta.push(chalk.dim(truncate(detailAgent.lastText, 60)));
201
+ if (detailAgent.filesChanged != null)
202
+ meta.push(chalk.dim(`${detailAgent.filesChanged} files`));
203
+ if (detailAgent.costUsd != null)
204
+ meta.push(chalk.yellow(`$${detailAgent.costUsd.toFixed(3)}`));
205
+ if (detailAgent.toolCalls > 0)
206
+ meta.push(chalk.dim(`${detailAgent.toolCalls} tools`));
207
+ if (meta.length > 0)
208
+ out.push(` ${meta.join(chalk.dim(" \u00b7 "))}`);
209
+ }
182
210
  // Merge results
183
211
  if (swarm.mergeResults.length > 0) {
184
212
  out.push("");
@@ -192,11 +220,21 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
192
220
  // Event log
193
221
  out.push("");
194
222
  out.push(chalk.gray(" \u2500\u2500\u2500 Events " + "\u2500".repeat(Math.min(w - 16, 90))));
195
- const logN = Math.min(10, swarm.logs.length);
223
+ const logN = Math.min(12, swarm.logs.length);
196
224
  for (const entry of swarm.logs.slice(-logN)) {
197
225
  const t = new Date(entry.time).toLocaleTimeString("en", { hour12: false });
198
226
  const tag = entry.agentId < 0 ? chalk.magenta("[sys]") : chalk.cyan(`[${entry.agentId}]`);
199
- out.push(chalk.gray(` ${t} `) + tag + ` ${colorEvent(truncate(entry.text, w - 22))}`);
227
+ // Tool-use events with target get a secondary detail line
228
+ const arrowIdx = entry.text.indexOf(" \u2192 ");
229
+ if (arrowIdx > 0 && arrowIdx < 20) {
230
+ const toolName = entry.text.slice(0, arrowIdx);
231
+ const target = entry.text.slice(arrowIdx + 3);
232
+ out.push(chalk.gray(` ${t} `) + tag + ` ${chalk.yellow(toolName)}`);
233
+ out.push(chalk.dim(` ${truncate(target, w - 10)}`));
234
+ }
235
+ else {
236
+ out.push(chalk.gray(` ${t} `) + tag + ` ${colorEvent(truncate(entry.text, w - 22))}`);
237
+ }
200
238
  }
201
239
  if (showHotkeys) {
202
240
  const pending = runInfo?.pendingSteer ?? 0;
@@ -204,7 +242,9 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
204
242
  const fixChip = swarm.failed > 0 && swarm.active > 0 ? chalk.yellow(" [f] fix") : "";
205
243
  const retryChip = swarm.rateLimitPaused > 0 ? chalk.yellow(" [r] retry-now") : "";
206
244
  const pauseLabel = swarm.paused ? "[p] resume" : "[p] pause";
207
- out.push(chalk.dim(` [b] budget [t] cap [c] conc [e] extra ${pauseLabel} [s] steer [?] ask [q] stop`) + fixChip + retryChip + chip);
245
+ const detailChip = swarm.active > 0 ? chalk.dim(" [d] detail") : "";
246
+ const selectChip = swarm.active > 0 && running.length <= 10 ? chalk.dim(" [0-9] select") : "";
247
+ out.push(chalk.dim(` [b] budget [t] cap [c] conc [e] extra ${pauseLabel} [s] steer [?] ask [q] stop`) + fixChip + retryChip + chip + detailChip + selectChip);
208
248
  if (swarm.blocked > 0 && swarm.blocked === swarm.active) {
209
249
  out.push(chalk.yellow(` all workers rate-limited — [r] retry-now, [c] reduce concurrency, [p] pause, [q] quit`));
210
250
  }
@@ -317,14 +357,24 @@ export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter) {
317
357
  out.push("");
318
358
  }
319
359
  section(out, w, "Planner activity");
320
- const events = data.events.slice(-10);
360
+ const events = data.events.slice(-15);
321
361
  if (events.length === 0) {
322
362
  out.push(chalk.dim(" (waiting for planner\u2026)"));
323
363
  }
324
364
  else {
325
365
  for (const e of events) {
326
366
  const t = new Date(e.time).toLocaleTimeString("en", { hour12: false });
327
- out.push(chalk.gray(` ${t} `) + chalk.magenta("[plan] ") + colorEvent(truncate(e.text, w - 22)));
367
+ // Tool-use events with target get a secondary detail line
368
+ const arrowIdx = e.text.indexOf(" \u2192 ");
369
+ if (arrowIdx > 0 && arrowIdx < 30) {
370
+ const toolName = e.text.slice(0, arrowIdx);
371
+ const target = e.text.slice(arrowIdx + 3);
372
+ out.push(chalk.gray(` ${t} `) + chalk.magenta("[plan] ") + chalk.yellow(toolName));
373
+ out.push(chalk.dim(` ${truncate(target, w - 10)}`));
374
+ }
375
+ else {
376
+ out.push(chalk.gray(` ${t} `) + chalk.magenta("[plan] ") + colorEvent(truncate(e.text, w - 22)));
377
+ }
328
378
  }
329
379
  }
330
380
  out.push("");
@@ -383,8 +433,8 @@ export function renderSummary(swarm) {
383
433
  return out.join("\n");
384
434
  }
385
435
  // ── Row formatting ──
386
- function fmtRow(a, w) {
387
- const id = String(a.id).padStart(3);
436
+ function fmtRow(a, w, selected = false) {
437
+ const id = selected ? chalk.cyan.bold(String(a.id).padStart(3)) : String(a.id).padStart(3);
388
438
  const elapsed = a.status === "running" && a.startedAt ? " " + chalk.dim(fmtDur(Date.now() - a.startedAt)) : "";
389
439
  const spin = SPINNER[Math.floor(Date.now() / 250) % SPINNER.length];
390
440
  const icon = a.status === "running"
package/dist/swarm.js CHANGED
@@ -629,7 +629,9 @@ export class Swarm {
629
629
  if (cb?.type === "tool_use") {
630
630
  agent.currentTool = cb.name;
631
631
  agent.toolCalls++;
632
- this.log(agent.id, cb.name);
632
+ const input = cb.input;
633
+ const target = input?.path ?? input?.file_path ?? (typeof input?.command === "string" ? input.command.split(" ").slice(0, 3).join(" ") : "");
634
+ this.log(agent.id, target ? `${cb.name} \u2192 ${target}` : cb.name);
633
635
  }
634
636
  }
635
637
  else if (ev.type === "content_block_delta") {
package/dist/ui.d.ts CHANGED
@@ -68,6 +68,9 @@ export declare class RunDisplay {
68
68
  private lastCompleted;
69
69
  private askState?;
70
70
  private askBusy;
71
+ private askTempFile?;
72
+ /** ID of the agent whose detail panel is open; undefined = no detail shown. */
73
+ private selectedAgentId?;
71
74
  private onSteer?;
72
75
  private onAsk?;
73
76
  constructor(runInfo: RunInfo, liveConfig?: LiveConfig, callbacks?: {
@@ -78,6 +81,15 @@ export declare class RunDisplay {
78
81
  setAsk(state: AskState | undefined): void;
79
82
  /** Signal to the UI whether an ask is in progress (prevents duplicate firings). */
80
83
  setAskBusy(busy: boolean): void;
84
+ /** Cycle the selected agent detail to the next running agent (or first running if none selected). */
85
+ cycleSelectedAgent(): void;
86
+ /** Select a specific agent by ID for the detail panel. */
87
+ selectAgent(id: number): void;
88
+ /** Clear the agent detail panel. */
89
+ clearSelectedAgent(): void;
90
+ private clearAskTempFile;
91
+ /** Get the currently selected agent's ID for rendering. */
92
+ getSelectedAgentId(): number | undefined;
81
93
  start(): void;
82
94
  setWave(swarm: Swarm): void;
83
95
  setSteering(rlGetter?: RLGetter, ctx?: SteeringContext): void;
package/dist/ui.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import chalk from "chalk";
2
2
  import { renderFrame, renderSteeringFrame } from "./render.js";
3
3
  import { splitPaste, segmentsToString, renderSegments, appendCharToSegments, appendPasteToSegments, backspaceSegments, } from "./cli.js";
4
+ import { mkdtempSync, writeFileSync, rmSync } from "fs";
5
+ import { tmpdir } from "os";
6
+ import { join } from "path";
7
+ import { execSync } from "child_process";
4
8
  const MAX_STEERING_EVENTS = 60;
5
9
  const MAX_INPUT_LEN = 600;
10
+ const MAX_ASK_LINES = 40;
11
+ let askTempDir;
6
12
  export class RunDisplay {
7
13
  runInfo;
8
14
  liveConfig;
@@ -22,6 +28,9 @@ export class RunDisplay {
22
28
  lastCompleted = -1;
23
29
  askState;
24
30
  askBusy = false;
31
+ askTempFile;
32
+ /** ID of the agent whose detail panel is open; undefined = no detail shown. */
33
+ selectedAgentId;
25
34
  onSteer;
26
35
  onAsk;
27
36
  constructor(runInfo, liveConfig, callbacks) {
@@ -32,9 +41,69 @@ export class RunDisplay {
32
41
  this.isTTY = !!process.stdout.isTTY;
33
42
  }
34
43
  /** Replace the ask state. Called by run.ts as the side query streams and completes. */
35
- setAsk(state) { this.askState = state; }
44
+ setAsk(state) {
45
+ this.askState = state;
46
+ // Clean up previous temp file
47
+ this.clearAskTempFile();
48
+ // Write full answer to temp file when streaming is done and answer is long
49
+ if (state && !state.streaming && !state.error && state.answer) {
50
+ const lines = state.answer.split("\n");
51
+ if (lines.length > MAX_ASK_LINES) {
52
+ try {
53
+ askTempDir = mkdtempSync(join(tmpdir(), "overnight-ask-"));
54
+ this.askTempFile = join(askTempDir, "answer.txt");
55
+ writeFileSync(this.askTempFile, state.answer, "utf8");
56
+ }
57
+ catch { }
58
+ }
59
+ }
60
+ }
36
61
  /** Signal to the UI whether an ask is in progress (prevents duplicate firings). */
37
62
  setAskBusy(busy) { this.askBusy = busy; }
63
+ /** Cycle the selected agent detail to the next running agent (or first running if none selected). */
64
+ cycleSelectedAgent() {
65
+ if (!this.swarm)
66
+ return;
67
+ const running = this.swarm.agents.filter(a => a.status === "running");
68
+ if (running.length === 0) {
69
+ this.selectedAgentId = undefined;
70
+ return;
71
+ }
72
+ if (this.selectedAgentId == null) {
73
+ this.selectedAgentId = running[0].id;
74
+ return;
75
+ }
76
+ const idx = running.findIndex(a => a.id === this.selectedAgentId);
77
+ this.selectedAgentId = running[(idx + 1) % running.length].id;
78
+ }
79
+ /** Select a specific agent by ID for the detail panel. */
80
+ selectAgent(id) {
81
+ if (!this.swarm)
82
+ return;
83
+ const agent = this.swarm.agents.find(a => a.id === id);
84
+ if (agent && agent.status === "running")
85
+ this.selectedAgentId = id;
86
+ }
87
+ /** Clear the agent detail panel. */
88
+ clearSelectedAgent() { this.selectedAgentId = undefined; }
89
+ clearAskTempFile() {
90
+ if (this.askTempFile) {
91
+ try {
92
+ rmSync(this.askTempFile, { force: true });
93
+ }
94
+ catch { }
95
+ if (askTempDir) {
96
+ try {
97
+ rmSync(askTempDir, { recursive: true, force: true });
98
+ }
99
+ catch { }
100
+ }
101
+ this.askTempFile = undefined;
102
+ askTempDir = undefined;
103
+ }
104
+ }
105
+ /** Get the currently selected agent's ID for rendering. */
106
+ getSelectedAgentId() { return this.selectedAgentId; }
38
107
  start() {
39
108
  if (this.started)
40
109
  return;
@@ -102,6 +171,8 @@ export class RunDisplay {
102
171
  process.stdout.write("\x1B[?25h");
103
172
  }
104
173
  catch { }
174
+ // Clean up ask temp file
175
+ this.clearAskTempFile();
105
176
  this.started = false;
106
177
  }
107
178
  resumeInterval() {
@@ -135,7 +206,7 @@ export class RunDisplay {
135
206
  render() {
136
207
  let frame = "";
137
208
  if (this.swarm) {
138
- frame = renderFrame(this.swarm, this.hasHotkeys(), this.runInfo);
209
+ frame = renderFrame(this.swarm, this.hasHotkeys(), this.runInfo, this.selectedAgentId);
139
210
  }
140
211
  else if (this.steeringActive) {
141
212
  frame = renderSteeringFrame(this.runInfo, {
@@ -188,10 +259,18 @@ export class RunDisplay {
188
259
  out.push(` ${chalk.dim("A: " + (a.answer || "thinking..."))}`);
189
260
  }
190
261
  else {
191
- const lines = a.answer.split("\n").slice(0, 20);
192
- out.push(` ${chalk.bold.green("A:")} ${lines[0] || ""}`);
193
- for (const ln of lines.slice(1))
262
+ const allLines = a.answer.split("\n");
263
+ const showLines = allLines.slice(0, MAX_ASK_LINES);
264
+ out.push(` ${chalk.bold.green("A:")} ${showLines[0] || ""}`);
265
+ for (const ln of showLines.slice(1))
194
266
  out.push(` ${ln}`);
267
+ if (allLines.length > MAX_ASK_LINES) {
268
+ const overflow = allLines.length - MAX_ASK_LINES;
269
+ out.push(chalk.dim(` \u2026 + ${overflow} more lines`));
270
+ if (this.askTempFile) {
271
+ out.push(chalk.dim(" \u23CE Enter to reveal full answer in Finder"));
272
+ }
273
+ }
195
274
  }
196
275
  return "\n" + out.join("\n");
197
276
  }
@@ -249,6 +328,18 @@ export class RunDisplay {
249
328
  /** Handle a typed (non-pasted) chunk. Returns true if the frame needs a redraw. */
250
329
  handleTyped(s) {
251
330
  const lc = this.liveConfig;
331
+ // Enter in hotkey mode reveals truncated ask answer in Finder
332
+ if (this.inputMode === "none" && this.askTempFile) {
333
+ for (const ch of s) {
334
+ if (ch === "\r" || ch === "\n") {
335
+ try {
336
+ execSync(`open -R ${JSON.stringify(this.askTempFile)}`);
337
+ }
338
+ catch { }
339
+ return true;
340
+ }
341
+ }
342
+ }
252
343
  if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency" || this.inputMode === "extra") {
253
344
  let dirty = false;
254
345
  for (const ch of s) {
@@ -370,6 +461,7 @@ export class RunDisplay {
370
461
  return false;
371
462
  if (key === "\x1B" && this.askState && !this.askState.streaming) {
372
463
  this.askState = undefined;
464
+ this.clearAskTempFile();
373
465
  return false;
374
466
  }
375
467
  if (key === "b" || key === "B") {
@@ -427,12 +519,35 @@ export class RunDisplay {
427
519
  if (key === "?" && this.onAsk && this.swarm && !this.askBusy) {
428
520
  if (this.askState && !this.askState.streaming) {
429
521
  this.askState = undefined;
522
+ this.clearAskTempFile();
430
523
  return false;
431
524
  }
432
525
  this.inputMode = "ask";
433
526
  this.inputSegs = [];
434
527
  return true;
435
528
  }
529
+ // [d] cycle agent detail panel
530
+ if ((key === "d" || key === "D") && this.swarm && this.swarm.active > 0) {
531
+ if (this.selectedAgentId != null)
532
+ this.cycleSelectedAgent();
533
+ else
534
+ this.cycleSelectedAgent();
535
+ return true;
536
+ }
537
+ // ESC closes detail panel
538
+ if (key === "\x1B" && this.selectedAgentId != null) {
539
+ this.clearSelectedAgent();
540
+ return true;
541
+ }
542
+ // Number keys 0-9 select a specific agent by row index in the visible table
543
+ if (/^[0-9]$/.test(key) && this.swarm) {
544
+ const n = parseInt(key);
545
+ const running = this.swarm.agents.filter(a => a.status === "running");
546
+ if (n < running.length) {
547
+ this.selectAgent(running[n].id);
548
+ return true;
549
+ }
550
+ }
436
551
  if (key === "q" || key === "Q" || key === "\x03") {
437
552
  if (this.swarm) {
438
553
  if (this.swarm.aborted)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.16.7",
3
+ "version": "1.16.10",
4
4
  "description": "Background lane for your Claude Max plan. Parallel Claude Agent SDK sessions in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Opus/Sonnet/Haiku + Qwen/OpenRouter.",
5
5
  "type": "module",
6
6
  "bin": {