claude-overnight 1.25.35 → 1.25.37

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.35";
1
+ export declare const VERSION = "1.25.37";
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.35";
2
+ export const VERSION = "1.25.37";
package/dist/index.js CHANGED
@@ -839,8 +839,10 @@ async function main() {
839
839
  statusLineActive = false;
840
840
  }
841
841
  };
842
- /** Cursor agent cold start + thinking-variant model latency can exceed 20s; API providers stay tight. */
843
- const preflightMs = (p) => isCursorProxyProvider(p) ? 60_000 : 20_000;
842
+ /** Cursor agent cold start + thinking-variant model latency can exceed 20s, and the cursor
843
+ * preflight now also runs a write-capability probe (see probeCursorWriteCapability) that
844
+ * asks cursor to Bash a marker file — so the total budget must cover auth ping + write turn. */
845
+ const preflightMs = (p) => isCursorProxyProvider(p) ? 90_000 : 20_000;
844
846
  const results = await Promise.all(pending.map(async ([role, p]) => {
845
847
  statuses.set(role, "connecting…");
846
848
  renderStatus();
@@ -454,9 +454,9 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
454
454
  export function postProcess(raw, budget, onLog) {
455
455
  let tasks = raw;
456
456
  const before = tasks.length;
457
- tasks = tasks.filter((t) => t.prompt && t.prompt.trim().split(/\s+/).length >= 3);
457
+ tasks = tasks.filter((t) => t.prompt && t.prompt.trim().length >= 1);
458
458
  if (tasks.length < before)
459
- onLog(`Filtered ${before - tasks.length} task(s) with fewer than 3 words`);
459
+ onLog(`Filtered ${before - tasks.length} task(s) with empty prompt`);
460
460
  // Read-only tasks (verify/audit/user-test) shouldn't get a worktree: they
461
461
  // don't change files, so they'd just create empty swarm branches that show
462
462
  // up as "0 files changed" noise. Run them in the real project directory so
package/dist/providers.js CHANGED
@@ -1,4 +1,5 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, realpathSync, openSync, statSync, readSync, closeSync } from "fs";
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, realpathSync, openSync, statSync, readSync, closeSync, unlinkSync } from "fs";
2
+ import { tmpdir } from "node:os";
2
3
  import { createRequire } from "node:module";
3
4
  import { homedir } from "os";
4
5
  import { join, dirname } from "path";
@@ -147,10 +148,10 @@ export function envFor(p) {
147
148
  // SDK replaces env for subprocesses — force these so nothing inherits a bad CI / skip flag.
148
149
  base.CI = "true";
149
150
  base.CURSOR_SKIP_KEYCHAIN = "1";
150
- // Bridge mode controls the agent behavior: "plan" enables tool use (Read,
151
- // Glob, Grep, Write, Bash), "ask" gives a chat-only assistant. Planner
152
- // agents and workers must use "plan" so they actually interact with the codebase.
153
- base.CURSOR_BRIDGE_MODE = "plan";
151
+ // "agent" omits --mode so cursor-agent runs full agentic mode (Read,
152
+ // Glob, Grep, Write, Bash). Passing --mode plan or ask forces read-only
153
+ // Write/Bash tool calls are silently dropped, exit 0, empty stdout.
154
+ base.CURSOR_BRIDGE_MODE = "agent";
154
155
  // Use system Node.js for agent subprocess to avoid macOS segfaults with
155
156
  // bundled Node.js. Resolve lazily.
156
157
  if (!_cachedAgentNode || !_cachedAgentScript) {
@@ -382,6 +383,10 @@ async function preflightCursorProxyViaHttp(p, timeoutMs, opts) {
382
383
  const headers = { "content-type": "application/json" };
383
384
  if (key)
384
385
  headers["authorization"] = `Bearer ${key}`;
386
+ // Shared deadline: auth ping + write probe split the total timeout budget.
387
+ const overallDeadlineAt = Date.now() + timeoutMs;
388
+ const remaining = () => Math.max(1_000, overallDeadlineAt - Date.now());
389
+ const authBudget = Math.max(5_000, Math.floor(timeoutMs / 2));
385
390
  const controller = new AbortController();
386
391
  let elapsed = 0;
387
392
  const PROGRESS_INTERVAL_MS = 3_000;
@@ -389,7 +394,7 @@ async function preflightCursorProxyViaHttp(p, timeoutMs, opts) {
389
394
  elapsed += PROGRESS_INTERVAL_MS;
390
395
  opts?.onProgress?.(`still waiting… (${(elapsed / 1000).toFixed(1)}s)`);
391
396
  }, PROGRESS_INTERVAL_MS);
392
- const deadline = setTimeout(() => controller.abort(), timeoutMs);
397
+ const deadline = setTimeout(() => controller.abort(), authBudget);
393
398
  try {
394
399
  // max_tokens must accommodate thinking tokens for `*-thinking-*` variants —
395
400
  // 1 token leaves zero reasoning budget and crashes the cursor-agent subprocess
@@ -410,7 +415,6 @@ async function preflightCursorProxyViaHttp(p, timeoutMs, opts) {
410
415
  }
411
416
  // Drain body so the connection closes cleanly; we don't care about content.
412
417
  await res.text().catch(() => "");
413
- return { ok: true };
414
418
  }
415
419
  catch (err) {
416
420
  if (err?.name === "AbortError") {
@@ -422,6 +426,82 @@ async function preflightCursorProxyViaHttp(p, timeoutMs, opts) {
422
426
  clearTimeout(deadline);
423
427
  clearInterval(progressTimer);
424
428
  }
429
+ // Write-capability probe — catches the --mode plan / ask regression where
430
+ // the proxy silently swallows Write/Bash tool calls (exit 0, empty body,
431
+ // no file changes). Ask cursor to write a unique marker file; fail if the
432
+ // file doesn't appear. Keeps the first wave from silently burning budget.
433
+ opts?.onProgress?.(`probing write capability…`);
434
+ const probeErr = await probeCursorWriteCapability(baseURL, key, p.model, remaining(), opts);
435
+ if (probeErr)
436
+ return { ok: false, error: probeErr };
437
+ return { ok: true };
438
+ }
439
+ /**
440
+ * Ask the proxy to create a unique marker file via its Bash tool; verify the
441
+ * file appeared on disk. Returns an error string on failure, null on success.
442
+ *
443
+ * Failure modes caught:
444
+ * - `CURSOR_BRIDGE_MODE=plan|ask` silently drops Write/Bash (regression fixed in
445
+ * cursor-composer-in-claude 0.9.3; this keeps older proxy versions actionable).
446
+ * - Workspace is untrusted or agent is otherwise nonfunctional — exit 0 with
447
+ * no side effects.
448
+ */
449
+ async function probeCursorWriteCapability(baseURL, key, model, timeoutMs, opts) {
450
+ const marker = `co-probe-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
451
+ const probeFile = join(tmpdir(), `${marker}.txt`);
452
+ try {
453
+ unlinkSync(probeFile);
454
+ }
455
+ catch { }
456
+ const prompt = `Run this exact shell command via your Bash tool, then reply with only the word DONE:\n` +
457
+ `printf 'ok' > ${probeFile}`;
458
+ const controller = new AbortController();
459
+ let elapsed = 0;
460
+ const PROGRESS_INTERVAL_MS = 3_000;
461
+ const progressTimer = setInterval(() => {
462
+ elapsed += PROGRESS_INTERVAL_MS;
463
+ opts?.onProgress?.(`write probe… (${(elapsed / 1000).toFixed(1)}s)`);
464
+ }, PROGRESS_INTERVAL_MS);
465
+ const deadline = setTimeout(() => controller.abort(), timeoutMs);
466
+ const headers = { "content-type": "application/json" };
467
+ if (key)
468
+ headers["authorization"] = `Bearer ${key}`;
469
+ try {
470
+ const res = await fetch(`${baseURL}/v1/messages`, {
471
+ method: "POST",
472
+ headers,
473
+ body: JSON.stringify({
474
+ model,
475
+ max_tokens: 4096,
476
+ messages: [{ role: "user", content: prompt }],
477
+ }),
478
+ signal: controller.signal,
479
+ });
480
+ if (!res.ok) {
481
+ const text = await res.text().catch(() => "");
482
+ return `write probe: HTTP ${res.status}: ${text.slice(0, 200)}`;
483
+ }
484
+ await res.text().catch(() => "");
485
+ }
486
+ catch (err) {
487
+ if (err?.name === "AbortError")
488
+ return `write probe: timeout after ${Math.round(timeoutMs / 1000)}s`;
489
+ return `write probe: ${String(err?.message || err).slice(0, 200)}`;
490
+ }
491
+ finally {
492
+ clearTimeout(deadline);
493
+ clearInterval(progressTimer);
494
+ }
495
+ if (!existsSync(probeFile)) {
496
+ return (`write probe: cursor returned without creating the marker file. ` +
497
+ `Most likely cause: CURSOR_BRIDGE_MODE=plan|ask (silent read-only mode). ` +
498
+ `Upgrade cursor-composer-in-claude to ≥0.9.3 and set CURSOR_BRIDGE_MODE=agent (or unset).`);
499
+ }
500
+ try {
501
+ unlinkSync(probeFile);
502
+ }
503
+ catch { }
504
+ return null;
425
505
  }
426
506
  // ── Cursor API Proxy ──
427
507
  export const PROXY_DEFAULT_URL = "http://127.0.0.1:8765";
@@ -924,9 +1004,10 @@ async function startProxyProcess(baseUrl, url, port) {
924
1004
  // the CLI path injects keychain-shim-inject.js via NODE_OPTIONS which no-ops
925
1005
  // /usr/bin/security calls on macOS (cursor-composer/dist/lib/process.js).
926
1006
  CURSOR_BRIDGE_USE_ACP: "0",
927
- // Default bridge mode: "plan" enables tool use (Read, Glob, Grep, Write, Bash).
928
- // "ask" gives a chat-only assistant that doesn't interact with the codebase.
929
- CURSOR_BRIDGE_MODE: "plan",
1007
+ // "agent" omits --mode so cursor-agent runs full agentic mode with
1008
+ // Read/Glob/Grep/Write/Bash. --mode plan and --mode ask are both strictly
1009
+ // read-only — Write/Bash calls exit 0 with empty stdout.
1010
+ CURSOR_BRIDGE_MODE: "agent",
930
1011
  // cursor-composer chat-only mode fakes HOME to a temp dir; on macOS the agent still waits on
931
1012
  // Keychain (~30s) for `cursor-user` despite CURSOR_API_KEY. Use the real workspace profile.
932
1013
  CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE: "false",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.25.35",
3
+ "version": "1.25.37",
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": {
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "@anthropic-ai/claude-agent-sdk": "^0.2.92",
19
19
  "chalk": "^5.4.1",
20
- "cursor-composer-in-claude": "0.9.2",
20
+ "cursor-composer-in-claude": "0.9.3",
21
21
  "jsonwebtoken": "^9.0.2"
22
22
  },
23
23
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.25.35",
3
+ "version": "1.25.37",
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"