claude-overnight 1.25.20 → 1.25.21
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/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.25.
|
|
1
|
+
export declare const VERSION = "1.25.21";
|
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.
|
|
2
|
+
export const VERSION = "1.25.21";
|
package/dist/planner-query.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface PlannerOpts {
|
|
|
25
25
|
};
|
|
26
26
|
/** When set, stream events are appended to <runDir>/transcripts/<name>.ndjson */
|
|
27
27
|
transcriptName?: string;
|
|
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.
|
|
33
|
+
*/
|
|
34
|
+
maxTurns?: number;
|
|
28
35
|
}
|
|
29
36
|
export declare function setPlannerEnvResolver(fn: ((model?: string) => Record<string, string> | undefined) | undefined): void;
|
|
30
37
|
export declare function getTotalPlannerCost(): number;
|
package/dist/planner-query.js
CHANGED
|
@@ -2,6 +2,7 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2
2
|
import { readFileSync } from "fs";
|
|
3
3
|
import { NudgeError } from "./types.js";
|
|
4
4
|
import { writeTranscriptEvent } from "./transcripts.js";
|
|
5
|
+
const DEFAULT_MAX_TURNS = 20;
|
|
5
6
|
// ── Shared env resolver (set once at run start, used by every planner query) ──
|
|
6
7
|
//
|
|
7
8
|
// Swarm and planner calls share a model→env map so a custom provider configured
|
|
@@ -149,6 +150,7 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
|
|
|
149
150
|
...(opts.permissionMode === "bypassPermissions" && { allowDangerouslySkipPermissions: true }),
|
|
150
151
|
persistSession: true,
|
|
151
152
|
includePartialMessages: true,
|
|
153
|
+
maxTurns: opts.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
152
154
|
...(isResume && { resume: opts.resumeSessionId }),
|
|
153
155
|
...(opts.outputFormat && { outputFormat: opts.outputFormat }),
|
|
154
156
|
...(envOverride && { env: envOverride }),
|
|
@@ -202,6 +204,11 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
|
|
|
202
204
|
// Track the open tool block so we can re-log with the enriched target once
|
|
203
205
|
// the input arrives, and write a complete transcript entry on block stop.
|
|
204
206
|
let pendingTool = null;
|
|
207
|
+
// Dedup tool_use logging between stream_event path and full-assistant-message
|
|
208
|
+
// path. The Cursor proxy doesn't always relay content_block_* frames through
|
|
209
|
+
// the Claude CLI's stream-json, so we also mine complete `SDKAssistantMessage`
|
|
210
|
+
// content for progress -- without double-counting when both paths fire.
|
|
211
|
+
const seenToolIds = new Set();
|
|
205
212
|
const logTool = (name, input) => {
|
|
206
213
|
const target = extractToolTarget(input);
|
|
207
214
|
lastLogText = target ? `${name} ${target}` : name;
|
|
@@ -220,6 +227,8 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
|
|
|
220
227
|
toolCount++;
|
|
221
228
|
const input = (cb.input ?? {});
|
|
222
229
|
const hasInput = Object.keys(input).length > 0;
|
|
230
|
+
if (cb.id)
|
|
231
|
+
seenToolIds.add(cb.id);
|
|
223
232
|
pendingTool = {
|
|
224
233
|
index: ev.index ?? 0,
|
|
225
234
|
name: cb.name,
|
|
@@ -274,6 +283,40 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
|
|
|
274
283
|
pendingTool = null;
|
|
275
284
|
}
|
|
276
285
|
}
|
|
286
|
+
// Fallback progress surfacing: when stream events are sparse (e.g. the
|
|
287
|
+
// Cursor proxy's heartbeat thinking block doesn't always round-trip
|
|
288
|
+
// through the Claude CLI as partial messages), mine the full assistant
|
|
289
|
+
// turn message for tool_use / thinking / text so the ticker still moves
|
|
290
|
+
// every ~6-15s instead of sitting silent for minutes.
|
|
291
|
+
if (msg.type === "assistant") {
|
|
292
|
+
const content = msg.message?.content;
|
|
293
|
+
if (Array.isArray(content)) {
|
|
294
|
+
for (const part of content) {
|
|
295
|
+
if (part?.type === "tool_use" && part.id && !seenToolIds.has(part.id)) {
|
|
296
|
+
seenToolIds.add(part.id);
|
|
297
|
+
toolCount++;
|
|
298
|
+
const input = (part.input ?? {});
|
|
299
|
+
logTool(part.name, input);
|
|
300
|
+
if (tname)
|
|
301
|
+
writeTranscriptEvent(tname, { kind: "tool_use", tool: part.name, input });
|
|
302
|
+
}
|
|
303
|
+
else if (part?.type === "thinking" && typeof part.thinking === "string" && part.thinking) {
|
|
304
|
+
const snippet = part.thinking.trim().replace(/\s+/g, " ").slice(-60);
|
|
305
|
+
if (snippet)
|
|
306
|
+
lastLogText = snippet;
|
|
307
|
+
if (tname)
|
|
308
|
+
writeTranscriptEvent(tname, { kind: "thinking", text: part.thinking });
|
|
309
|
+
}
|
|
310
|
+
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;
|
|
314
|
+
if (tname)
|
|
315
|
+
writeTranscriptEvent(tname, { kind: "text", text: part.text });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
277
320
|
if (msg.type === "rate_limit_event") {
|
|
278
321
|
const info = msg.rate_limit_info;
|
|
279
322
|
if (info) {
|
package/dist/planner.js
CHANGED
|
@@ -158,7 +158,7 @@ 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 }, onLog);
|
|
161
|
+
resultText = await runPlannerQuery(prompt + fileInstruction, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 40 }, onLog);
|
|
162
162
|
}
|
|
163
163
|
catch (err) {
|
|
164
164
|
const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
|
|
@@ -168,7 +168,7 @@ export async function planTasks(objective, cwd, plannerModel, workerModel, permi
|
|
|
168
168
|
}
|
|
169
169
|
const parsed = await extractTaskJson(resultText, async () => {
|
|
170
170
|
onLog("Retrying...");
|
|
171
|
-
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry
|
|
171
|
+
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry`, maxTurns: 15 }, onLog);
|
|
172
172
|
}, onLog, outFile);
|
|
173
173
|
let tasks = (parsed.tasks || []).map((t, i) => ({
|
|
174
174
|
id: String(i), prompt: typeof t === "string" ? t : t.prompt,
|
|
@@ -188,7 +188,7 @@ Then pick ${count} angles that carve up THIS specific codebase orthogonally. Pre
|
|
|
188
188
|
|
|
189
189
|
Objective: ${objective}
|
|
190
190
|
|
|
191
|
-
Return ONLY a JSON object: {"themes": ["angle description", ...]}`, { cwd, model, permissionMode, outputFormat: THEMES_SCHEMA, transcriptName }, onLog);
|
|
191
|
+
Return ONLY a JSON object: {"themes": ["angle description", ...]}`, { cwd, model, permissionMode, outputFormat: THEMES_SCHEMA, transcriptName, maxTurns: 12 }, onLog);
|
|
192
192
|
const parsed = attemptJsonParse(resultText);
|
|
193
193
|
if (parsed?.themes && Array.isArray(parsed.themes))
|
|
194
194
|
return parsed.themes.slice(0, count);
|
|
@@ -259,7 +259,7 @@ Respond with ONLY a JSON object (no markdown fences):
|
|
|
259
259
|
onLog("Synthesizing...");
|
|
260
260
|
let resultText;
|
|
261
261
|
try {
|
|
262
|
-
resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName }, onLog);
|
|
262
|
+
resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 25 }, onLog);
|
|
263
263
|
}
|
|
264
264
|
catch (err) {
|
|
265
265
|
const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
|
|
@@ -269,7 +269,7 @@ Respond with ONLY a JSON object (no markdown fences):
|
|
|
269
269
|
}
|
|
270
270
|
const parsed = await extractTaskJson(resultText, async () => {
|
|
271
271
|
onLog("Retrying...");
|
|
272
|
-
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry
|
|
272
|
+
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry`, maxTurns: 10 }, onLog);
|
|
273
273
|
}, onLog, outFile);
|
|
274
274
|
let tasks = (parsed.tasks || []).map((t, i) => ({
|
|
275
275
|
id: String(i), prompt: typeof t === "string" ? t : t.prompt,
|
|
@@ -303,10 +303,10 @@ ${scaleNote} ${concurrency} agents run in parallel. Update the plan accordingly.
|
|
|
303
303
|
|
|
304
304
|
Respond with ONLY a JSON object (no markdown):
|
|
305
305
|
{"tasks":[{"prompt":"..."}]}`;
|
|
306
|
-
const resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName }, onLog);
|
|
306
|
+
const resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName, maxTurns: 15 }, onLog);
|
|
307
307
|
const parsed = await extractTaskJson(resultText, async () => {
|
|
308
308
|
onLog("Retrying...");
|
|
309
|
-
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry
|
|
309
|
+
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA, transcriptName: `${transcriptName}-retry`, maxTurns: 8 }, onLog);
|
|
310
310
|
}, onLog);
|
|
311
311
|
let tasks = (parsed.tasks || []).map((t, i) => ({
|
|
312
312
|
id: String(i), prompt: typeof t === "string" ? t : t.prompt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.21",
|
|
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.
|
|
3
|
+
"version": "1.25.21",
|
|
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"
|