claude-overnight 1.25.21 → 1.25.22

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.
@@ -1 +1 @@
1
- export declare const VERSION = "1.25.21";
1
+ export declare const VERSION = "1.25.22";
package/dist/_version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by build — do not edit manually.
2
- export const VERSION = "1.25.21";
2
+ export const VERSION = "1.25.22";
package/dist/index.js CHANGED
@@ -822,14 +822,44 @@ async function main() {
822
822
  // proxy now uses an account pool (`CURSOR_CONFIG_DIRS`) — each parallel
823
823
  // cursor-agent subprocess gets its own config dir, eliminating the race.
824
824
  // See ensureCursorAccountPool() in providers.ts.
825
- const progress = (msg) => process.stdout.write(chalk.dim(` ${msg}\n`));
825
+ //
826
+ // Single in-place status line collapses N parallel progress streams (one
827
+ // per provider) into one tty line updated via `\r` + ANSI clear. Keeps the
828
+ // "window head" calm instead of appending 3 lines per 3s tick.
829
+ const statuses = new Map();
830
+ const isTTY = process.stdout.isTTY;
831
+ let statusLineActive = false;
832
+ const renderStatus = () => {
833
+ if (!isTTY)
834
+ return;
835
+ const parts = [...statuses.entries()].map(([r, s]) => `${r} ${chalk.dim(s)}`);
836
+ process.stdout.write(`\x1B[2K\r`);
837
+ if (parts.length === 0) {
838
+ statusLineActive = false;
839
+ return;
840
+ }
841
+ process.stdout.write(chalk.dim(" " + parts.join(" · ")));
842
+ statusLineActive = true;
843
+ };
844
+ const clearStatusLine = () => {
845
+ if (isTTY && statusLineActive) {
846
+ process.stdout.write(`\x1B[2K\r`);
847
+ statusLineActive = false;
848
+ }
849
+ };
826
850
  /** Cursor agent cold start + thinking-variant model latency can exceed 20s; API providers stay tight. */
827
851
  const preflightMs = (p) => isCursorProxyProvider(p) ? 60_000 : 20_000;
828
- const results = await Promise.all(pending.map(async ([role, p]) => ({
829
- role,
830
- provider: p,
831
- result: await preflightProvider(p, cwd, preflightMs(p), { onProgress: progress }),
832
- })));
852
+ const results = await Promise.all(pending.map(async ([role, p]) => {
853
+ statuses.set(role, "connecting…");
854
+ renderStatus();
855
+ const result = await preflightProvider(p, cwd, preflightMs(p), {
856
+ onProgress: (msg) => { statuses.set(role, msg); renderStatus(); },
857
+ });
858
+ statuses.delete(role);
859
+ renderStatus();
860
+ return { role, provider: p, result };
861
+ }));
862
+ clearStatusLine();
833
863
  for (const { role, provider, result } of results) {
834
864
  if (!result.ok) {
835
865
  console.error(chalk.red(` ✗ ${role} preflight failed: ${chalk.dim(result.error)}`));
@@ -26,12 +26,15 @@ export interface PlannerOpts {
26
26
  /** When set, stream events are appended to <runDir>/transcripts/<name>.ndjson */
27
27
  transcriptName?: string;
28
28
  /**
29
- * Hard cap on conversation turns. Protects against runaway exploration when
30
- * the underlying endpoint ignores `outputFormat` (e.g. the Cursor proxy
31
- * strips json_schema, leaving thinking models with no signal to stop).
32
- * Defaults to 20 — generous for recon, bounded against infinite loops.
29
+ * Hard cap on conversation turns. Defaults to 20.
33
30
  */
34
31
  maxTurns?: number;
32
+ /**
33
+ * Tools the planner agent may use. Defaults to read-only + Write (for outFile
34
+ * resilience). Deliberately excludes Bash/Agent/TodoWrite/WebFetch to prevent
35
+ * the multi-turn tool loops that cause error_max_turns with thinking models.
36
+ */
37
+ tools?: string[];
35
38
  }
36
39
  export declare function setPlannerEnvResolver(fn: ((model?: string) => Record<string, string> | undefined) | undefined): void;
37
40
  export declare function getTotalPlannerCost(): number;
@@ -144,8 +144,8 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
144
144
  options: {
145
145
  cwd: opts.cwd,
146
146
  model: opts.model,
147
- tools: ["Read", "Glob", "Grep", "Write", "Bash", "WebFetch", "WebSearch", "TodoWrite", "Agent"],
148
- allowedTools: ["Read", "Glob", "Grep", "Write", "Bash", "WebFetch", "WebSearch", "TodoWrite", "Agent"],
147
+ tools: opts.tools ?? ["Read", "Glob", "Grep", "Write"],
148
+ allowedTools: opts.tools ?? ["Read", "Glob", "Grep", "Write"],
149
149
  permissionMode: opts.permissionMode,
150
150
  ...(opts.permissionMode === "bypassPermissions" && { allowDangerouslySkipPermissions: true }),
151
151
  persistSession: true,
@@ -156,9 +156,20 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
156
156
  ...(envOverride && { env: envOverride }),
157
157
  },
158
158
  });
159
- let lastLogText = "";
159
+ // Default to "thinking…" so the ticker conveys meaning during the pre-output
160
+ // reasoning phase. Thinking-variant models (e.g. claude-opus-4-7-thinking-*)
161
+ // can sit silent for 60-90s before emitting any tokens, and cursor-agent
162
+ // doesn't forward thinking deltas — without this, the ticker reads "4m 5s"
163
+ // with nothing else for a minute plus.
164
+ let lastLogText = "thinking…";
160
165
  let toolCount = 0;
161
166
  let costUsd = 0;
167
+ const jsonOutput = opts.outputFormat?.type === "json_schema";
168
+ let jsonCharCount = 0;
169
+ // Dedup identical text snippets: cursor-agent with json_schema-ignoring
170
+ // proxies causes the SDK to loop multiple turns, each re-emitting the same
171
+ // final JSON. We don't want to spam the ticker or transcript with it.
172
+ let lastTextSeen = "";
162
173
  const ticker = setInterval(() => {
163
174
  const elapsed = Math.round((Date.now() - startedAt) / 1000);
164
175
  const m = Math.floor(elapsed / 60);
@@ -261,9 +272,18 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
261
272
  : delta?.type === "thinking_delta" ? delta.thinking
262
273
  : undefined;
263
274
  if (typeof raw === "string" && raw) {
264
- const snippet = raw.trim().replace(/[{}"\\,[\]]+/g, " ").replace(/\s+/g, " ").trim();
265
- if (snippet.length > 5)
266
- lastLogText = snippet.slice(-60);
275
+ if (jsonOutput && delta.type === "text_delta") {
276
+ // Don't surface tail-of-JSON as "progress" — it reads as noise
277
+ // like `…ppression and optimistic-update rollback`. Show size
278
+ // growing instead, which is a genuine signal.
279
+ jsonCharCount += raw.length;
280
+ lastLogText = `writing JSON (${jsonCharCount} chars)…`;
281
+ }
282
+ else {
283
+ const snippet = raw.trim().replace(/[{}"\\,[\]]+/g, " ").replace(/\s+/g, " ").trim();
284
+ if (snippet.length > 5)
285
+ lastLogText = snippet.slice(-60);
286
+ }
267
287
  if (tname)
268
288
  writeTranscriptEvent(tname, { kind: delta.type, text: raw });
269
289
  }
@@ -308,9 +328,17 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
308
328
  writeTranscriptEvent(tname, { kind: "thinking", text: part.thinking });
309
329
  }
310
330
  else if (part?.type === "text" && typeof part.text === "string" && part.text) {
311
- const snippet = part.text.trim().replace(/[{}"\\,[\]]+/g, " ").replace(/\s+/g, " ").slice(-60);
312
- if (snippet.length > 5)
313
- lastLogText = snippet;
331
+ if (part.text === lastTextSeen)
332
+ continue; // dedup repeated turns
333
+ lastTextSeen = part.text;
334
+ if (jsonOutput) {
335
+ lastLogText = `writing JSON (${part.text.length} chars)…`;
336
+ }
337
+ else {
338
+ const snippet = part.text.trim().replace(/[{}"\\,[\]]+/g, " ").replace(/\s+/g, " ").slice(-60);
339
+ if (snippet.length > 5)
340
+ lastLogText = snippet;
341
+ }
314
342
  if (tname)
315
343
  writeTranscriptEvent(tname, { kind: "text", text: part.text });
316
344
  }
package/dist/planner.js CHANGED
@@ -158,7 +158,8 @@ export async function planTasks(objective, cwd, plannerModel, workerModel, permi
158
158
  const fileInstruction = outFile ? `\n\nAFTER generating the JSON, also write it to ${outFile} using the Write tool.` : "";
159
159
  let resultText;
160
160
  try {
161
- resultText = await runPlannerQuery(prompt + fileInstruction, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 40 }, onLog);
161
+ resultText = await runPlannerQuery(prompt + fileInstruction, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 40,
162
+ tools: ["Read", "Glob", "Grep", "Write"] }, onLog);
162
163
  }
163
164
  catch (err) {
164
165
  const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
@@ -259,7 +260,8 @@ Respond with ONLY a JSON object (no markdown fences):
259
260
  onLog("Synthesizing...");
260
261
  let resultText;
261
262
  try {
262
- resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 25 }, onLog);
263
+ resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 25,
264
+ tools: ["Write"] }, onLog);
263
265
  }
264
266
  catch (err) {
265
267
  const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.25.21",
3
+ "version": "1.25.22",
4
4
  "description": "Parallel Claude agents in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Provider-agnostic model catalog (Anthropic, Cursor, OpenAI, Gemini, DeepSeek, Llama, Qwen) with capability-based task scoping.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.25.21",
3
+ "version": "1.25.22",
4
4
  "description": "Claude Code skill for understanding, installing, and inspecting claude-overnight runs -- parallel Claude agents in git worktrees with thinking waves, multi-wave steering, and crash-safe resume. Supports Cursor API Proxy, Qwen, OpenRouter.",
5
5
  "author": {
6
6
  "name": "Francesco Fornace"