agent-worker 0.4.0 → 0.5.0

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.
@@ -0,0 +1,3 @@
1
+ import { C as getModelForBackend, S as SDK_MODEL_ALIASES, _ as execWithIdleTimeout, a as createMockBackend, b as CODEX_MODEL_MAP, c as CodexBackend, d as codexAdapter, f as createStreamParser, g as IdleTimeoutError, h as formatEvent, i as MockAIBackend, l as ClaudeCodeBackend, m as extractCodexResult, n as createBackend, o as SdkBackend, p as extractClaudeResult, r as listBackends, s as CursorBackend, t as checkBackends, u as claudeAdapter, v as BACKEND_DEFAULT_MODELS, x as CURSOR_MODEL_MAP, y as CLAUDE_MODEL_MAP } from "./backends-CEYiMUgC.mjs";
2
+
3
+ export { listBackends };
@@ -1,5 +1,5 @@
1
1
  import { gateway, generateText } from "ai";
2
- import { ExecaError, execa } from "execa";
2
+ import { execa } from "execa";
3
3
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { stringify } from "yaml";
@@ -297,6 +297,271 @@ function parseModel(model) {
297
297
  };
298
298
  }
299
299
 
300
+ //#endregion
301
+ //#region src/backends/idle-timeout.ts
302
+ /**
303
+ * Idle timeout for CLI subprocess execution
304
+ *
305
+ * Unlike a hard timeout (kill after N ms total), an idle timeout only fires
306
+ * when the process produces no stdout/stderr output for the configured duration.
307
+ * This allows long-running agent tasks to continue as long as they're actively
308
+ * producing output (tool calls, analysis, etc.).
309
+ */
310
+ /**
311
+ * Execute a command with idle timeout.
312
+ *
313
+ * The timeout resets every time the process writes to stdout or stderr.
314
+ * If the process goes silent for longer than `timeout` ms, it's killed.
315
+ */
316
+ /** Minimum idle timeout to prevent accidental instant kills */
317
+ const MIN_TIMEOUT_MS = 1e3;
318
+ async function execWithIdleTimeout(options) {
319
+ const { command, args, cwd, onStdout } = options;
320
+ const timeout = Math.max(options.timeout, MIN_TIMEOUT_MS);
321
+ let idleTimedOut = false;
322
+ let timer;
323
+ let stdout = "";
324
+ let stderr = "";
325
+ const subprocess = execa(command, args, {
326
+ cwd,
327
+ stdin: "ignore",
328
+ buffer: false
329
+ });
330
+ const resetTimer = () => {
331
+ clearTimeout(timer);
332
+ timer = setTimeout(() => {
333
+ idleTimedOut = true;
334
+ subprocess.kill();
335
+ }, timeout);
336
+ };
337
+ subprocess.stdout?.on("data", (chunk) => {
338
+ const text = chunk.toString();
339
+ stdout += text;
340
+ if (onStdout) onStdout(text);
341
+ resetTimer();
342
+ });
343
+ subprocess.stderr?.on("data", (chunk) => {
344
+ stderr += chunk.toString();
345
+ resetTimer();
346
+ });
347
+ resetTimer();
348
+ try {
349
+ await subprocess;
350
+ clearTimeout(timer);
351
+ return {
352
+ stdout: stdout.trimEnd(),
353
+ stderr: stderr.trimEnd()
354
+ };
355
+ } catch (error) {
356
+ clearTimeout(timer);
357
+ if (idleTimedOut) throw new IdleTimeoutError(timeout, stdout, stderr);
358
+ throw error;
359
+ }
360
+ }
361
+ /**
362
+ * Error thrown when a process is killed due to idle timeout
363
+ */
364
+ var IdleTimeoutError = class extends Error {
365
+ timeout;
366
+ stdout;
367
+ stderr;
368
+ constructor(timeout, stdout, stderr) {
369
+ super(`Process idle timed out after ${timeout}ms of inactivity`);
370
+ this.name = "IdleTimeoutError";
371
+ this.timeout = timeout;
372
+ this.stdout = stdout;
373
+ this.stderr = stderr;
374
+ }
375
+ };
376
+
377
+ //#endregion
378
+ //#region src/backends/stream-json.ts
379
+ /**
380
+ * Format a standard StreamEvent into a human-readable progress message.
381
+ * Returns null if the event doesn't need display.
382
+ *
383
+ * This function only knows about StreamEvent — it never touches
384
+ * backend-specific raw JSON. Format-specific conversion is handled
385
+ * by the EventAdapter.
386
+ */
387
+ function formatEvent(event, backendName) {
388
+ switch (event.kind) {
389
+ case "init": {
390
+ const details = [];
391
+ if (event.model) details.push(`model: ${event.model}`);
392
+ if (event.sessionId) details.push(`session: ${event.sessionId}`);
393
+ return `${backendName} initialized${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
394
+ }
395
+ case "tool_call": {
396
+ const truncated = event.args.length > 100 ? event.args.slice(0, 100) + "..." : event.args;
397
+ return `CALL ${event.name}(${truncated})`;
398
+ }
399
+ case "completed": {
400
+ const parts = [backendName, "completed"];
401
+ const details = [];
402
+ if (event.durationMs) details.push(`${(event.durationMs / 1e3).toFixed(1)}s`);
403
+ if (event.costUsd) details.push(`$${event.costUsd.toFixed(4)}`);
404
+ if (event.usage) details.push(`${event.usage.input} in, ${event.usage.output} out`);
405
+ if (details.length > 0) parts.push(`(${details.join(", ")})`);
406
+ return parts.join(" ");
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * Adapter for Claude/Cursor stream-json format.
412
+ *
413
+ * Events:
414
+ * { type: "system", subtype: "init", model: "..." }
415
+ * { type: "assistant", message: { content: [{ type: "tool_use", name, input }] } }
416
+ * { type: "result", duration_ms: N, total_cost_usd: N }
417
+ */
418
+ const claudeAdapter = (raw) => {
419
+ const type = raw.type;
420
+ if (type === "system" && raw.subtype === "init") return {
421
+ kind: "init",
422
+ model: raw.model || void 0,
423
+ sessionId: raw.session_id
424
+ };
425
+ if (type === "assistant") {
426
+ const message = raw.message;
427
+ if (!message?.content) return null;
428
+ const toolCalls = message.content.filter((c) => c.type === "tool_use");
429
+ if (toolCalls.length > 0) {
430
+ const tc = toolCalls[0];
431
+ return {
432
+ kind: "tool_call",
433
+ name: tc.name || "unknown",
434
+ args: formatToolInput(tc.input)
435
+ };
436
+ }
437
+ return null;
438
+ }
439
+ if (type === "result") return {
440
+ kind: "completed",
441
+ durationMs: raw.duration_ms,
442
+ costUsd: raw.total_cost_usd
443
+ };
444
+ return null;
445
+ };
446
+ /**
447
+ * Extract final result from Claude/Cursor stream-json output.
448
+ *
449
+ * Priority:
450
+ * 1. type=result with result field
451
+ * 2. Last assistant message with text content
452
+ * 3. Raw stdout fallback
453
+ */
454
+ function extractClaudeResult(stdout) {
455
+ const lines = stdout.trim().split("\n");
456
+ for (let i = lines.length - 1; i >= 0; i--) try {
457
+ const event = JSON.parse(lines[i]);
458
+ if (event.type === "result" && event.result) return { content: event.result };
459
+ } catch {}
460
+ for (let i = lines.length - 1; i >= 0; i--) try {
461
+ const event = JSON.parse(lines[i]);
462
+ if (event.type === "assistant" && event.message?.content) {
463
+ const textParts = event.message.content.filter((c) => c.type === "text").map((c) => c.text);
464
+ if (textParts.length > 0) return { content: textParts.join("\n") };
465
+ }
466
+ } catch {}
467
+ return { content: stdout.trim() };
468
+ }
469
+ /**
470
+ * Adapter for Codex --json format.
471
+ *
472
+ * Events:
473
+ * { type: "thread.started", thread_id: "..." }
474
+ * { type: "item.completed", item: { type: "function_call", name, arguments } }
475
+ * { type: "item.completed", item: { type: "agent_message", text } } → skipped (result only)
476
+ * { type: "turn.completed", usage: { input_tokens, output_tokens } }
477
+ */
478
+ const codexAdapter = (raw) => {
479
+ const type = raw.type;
480
+ if (type === "thread.started") {
481
+ const threadId = raw.thread_id;
482
+ return {
483
+ kind: "init",
484
+ sessionId: threadId ? `${threadId.slice(0, 8)}...` : void 0
485
+ };
486
+ }
487
+ if (type === "item.completed") {
488
+ const item = raw.item;
489
+ if (!item) return null;
490
+ if (item.type === "function_call") return {
491
+ kind: "tool_call",
492
+ name: item.name || "unknown",
493
+ args: item.arguments ?? ""
494
+ };
495
+ return null;
496
+ }
497
+ if (type === "turn.completed") {
498
+ const usage = raw.usage;
499
+ return {
500
+ kind: "completed",
501
+ usage: usage ? {
502
+ input: usage.input_tokens ?? 0,
503
+ output: usage.output_tokens ?? 0
504
+ } : void 0
505
+ };
506
+ }
507
+ return null;
508
+ };
509
+ /**
510
+ * Extract final result from Codex --json output.
511
+ *
512
+ * Priority:
513
+ * 1. Last item.completed with item.type=agent_message
514
+ * 2. Raw stdout fallback
515
+ */
516
+ function extractCodexResult(stdout) {
517
+ const lines = stdout.trim().split("\n");
518
+ for (let i = lines.length - 1; i >= 0; i--) try {
519
+ const event = JSON.parse(lines[i]);
520
+ if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item?.text) return { content: event.item.text };
521
+ } catch {}
522
+ return { content: stdout.trim() };
523
+ }
524
+ /**
525
+ * Create a line-buffered stream parser.
526
+ *
527
+ * Accumulates stdout chunks, parses each line through the given adapter,
528
+ * and emits formatted progress messages via debugLog.
529
+ *
530
+ * @param debugLog - Callback for progress messages
531
+ * @param backendName - Display name (e.g., "Cursor", "Claude", "Codex")
532
+ * @param adapter - Format-specific adapter to convert raw JSON → StreamEvent
533
+ */
534
+ function createStreamParser(debugLog, backendName, adapter) {
535
+ let lineBuf = "";
536
+ return (chunk) => {
537
+ lineBuf += chunk;
538
+ const lines = lineBuf.split("\n");
539
+ lineBuf = lines.pop() ?? "";
540
+ for (const line of lines) {
541
+ if (!line.trim()) continue;
542
+ try {
543
+ const event = adapter(JSON.parse(line));
544
+ if (event) {
545
+ const progress = formatEvent(event, backendName);
546
+ if (progress) debugLog(progress);
547
+ }
548
+ } catch {}
549
+ }
550
+ };
551
+ }
552
+ /**
553
+ * Format tool call input for display (truncated JSON string)
554
+ */
555
+ function formatToolInput(input) {
556
+ if (!input || typeof input !== "object") return "";
557
+ try {
558
+ const str = JSON.stringify(input);
559
+ return str.length > 100 ? str.slice(0, 100) + "..." : str;
560
+ } catch {
561
+ return "";
562
+ }
563
+ }
564
+
300
565
  //#endregion
301
566
  //#region src/backends/claude-code.ts
302
567
  /**
@@ -332,13 +597,19 @@ var ClaudeCodeBackend = class {
332
597
  async send(message, options) {
333
598
  const args = this.buildArgs(message, options);
334
599
  const cwd = this.options.workspace || this.options.cwd;
600
+ const debugLog = this.options.debugLog;
601
+ const outputFormat = this.options.outputFormat ?? "stream-json";
602
+ const timeout = this.options.timeout ?? 3e5;
335
603
  try {
336
- const { stdout } = await execa("claude", args, {
604
+ const { stdout } = await execWithIdleTimeout({
605
+ command: "claude",
606
+ args,
337
607
  cwd,
338
- stdin: "ignore",
339
- timeout: this.options.timeout
608
+ timeout,
609
+ onStdout: outputFormat === "stream-json" && debugLog ? createStreamParser(debugLog, "Claude", claudeAdapter) : void 0
340
610
  });
341
- if (this.options.outputFormat === "json") try {
611
+ if (outputFormat === "stream-json") return extractClaudeResult(stdout);
612
+ if (outputFormat === "json") try {
342
613
  const parsed = JSON.parse(stdout);
343
614
  return {
344
615
  content: parsed.content || parsed.result || stdout,
@@ -350,7 +621,11 @@ var ClaudeCodeBackend = class {
350
621
  }
351
622
  return { content: stdout.trim() };
352
623
  } catch (error) {
353
- if (error instanceof ExecaError) throw new Error(`claude failed (exit ${error.exitCode}): ${error.stderr || error.shortMessage}`);
624
+ if (error instanceof IdleTimeoutError) throw new Error(`claude timed out after ${timeout}ms of inactivity`);
625
+ if (error && typeof error === "object" && "exitCode" in error) {
626
+ const execError = error;
627
+ throw new Error(`claude failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
628
+ }
354
629
  throw error;
355
630
  }
356
631
  }
@@ -383,7 +658,8 @@ var ClaudeCodeBackend = class {
383
658
  args.push("--append-system-prompt", system);
384
659
  }
385
660
  if (this.options.allowedTools?.length) args.push("--allowed-tools", this.options.allowedTools.join(","));
386
- if (this.options.outputFormat) args.push("--output-format", this.options.outputFormat);
661
+ const outputFormat = this.options.outputFormat ?? "stream-json";
662
+ args.push("--output-format", outputFormat);
387
663
  if (this.options.continue) args.push("--continue");
388
664
  if (this.options.resume) args.push("--resume", this.options.resume);
389
665
  if (this.options.mcpConfigPath) args.push("--mcp-config", this.options.mcpConfigPath);
@@ -401,7 +677,7 @@ var ClaudeCodeBackend = class {
401
677
  //#region src/backends/codex.ts
402
678
  /**
403
679
  * OpenAI Codex CLI backend
404
- * Uses `codex exec` for non-interactive mode
680
+ * Uses `codex exec` for non-interactive mode with JSON event output
405
681
  *
406
682
  * MCP Configuration:
407
683
  * Codex uses project-level MCP config. Use setWorkspace() to set up
@@ -432,28 +708,23 @@ var CodexBackend = class {
432
708
  async send(message, _options) {
433
709
  const args = this.buildArgs(message);
434
710
  const cwd = this.options.workspace || this.options.cwd;
711
+ const debugLog = this.options.debugLog;
712
+ const timeout = this.options.timeout ?? 3e5;
435
713
  try {
436
- const { stdout } = await execa("codex", args, {
714
+ const { stdout } = await execWithIdleTimeout({
715
+ command: "codex",
716
+ args,
437
717
  cwd,
438
- stdin: "ignore",
439
- timeout: this.options.timeout
718
+ timeout,
719
+ onStdout: debugLog ? createStreamParser(debugLog, "Codex", codexAdapter) : void 0
440
720
  });
441
- if (this.options.json) try {
442
- const lines = stdout.trim().split("\n");
443
- const lastLine = lines[lines.length - 1];
444
- if (!lastLine) return { content: stdout.trim() };
445
- const lastEvent = JSON.parse(lastLine);
446
- return {
447
- content: lastEvent.message || lastEvent.content || stdout,
448
- toolCalls: lastEvent.toolCalls,
449
- usage: lastEvent.usage
450
- };
451
- } catch {
452
- return { content: stdout.trim() };
453
- }
454
- return { content: stdout.trim() };
721
+ return extractCodexResult(stdout);
455
722
  } catch (error) {
456
- if (error instanceof ExecaError) throw new Error(`codex failed (exit ${error.exitCode}): ${error.stderr || error.shortMessage}`);
723
+ if (error instanceof IdleTimeoutError) throw new Error(`codex timed out after ${timeout}ms of inactivity`);
724
+ if (error && typeof error === "object" && "exitCode" in error) {
725
+ const execError = error;
726
+ throw new Error(`codex failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
727
+ }
457
728
  throw error;
458
729
  }
459
730
  }
@@ -477,13 +748,12 @@ var CodexBackend = class {
477
748
  buildArgs(message) {
478
749
  const args = [
479
750
  "exec",
480
- "--dangerously-bypass-approvals-and-sandbox",
751
+ "--full-auto",
752
+ "--json",
753
+ "--skip-git-repo-check",
481
754
  message
482
755
  ];
483
756
  if (this.options.model) args.push("--model", this.options.model);
484
- if (this.options.json) args.push("--json");
485
- if (this.options.skipGitRepoCheck) args.push("--skip-git-repo-check");
486
- if (this.options.approvalMode) args.push("--approval-mode", this.options.approvalMode);
487
757
  if (this.options.resume) args.push("--resume", this.options.resume);
488
758
  return args;
489
759
  }
@@ -493,7 +763,8 @@ var CodexBackend = class {
493
763
  //#region src/backends/cursor.ts
494
764
  /**
495
765
  * Cursor CLI backend
496
- * Uses `cursor-agent -p` for non-interactive mode
766
+ * Uses `cursor agent -p` (preferred) or `cursor-agent -p` (fallback)
767
+ * for non-interactive mode with stream-json output
497
768
  *
498
769
  * MCP Configuration:
499
770
  * Cursor uses project-level MCP config via .cursor/mcp.json in the workspace.
@@ -504,9 +775,11 @@ var CodexBackend = class {
504
775
  var CursorBackend = class {
505
776
  type = "cursor";
506
777
  options;
778
+ /** Resolved command: "cursor" (subcommand style) or "cursor-agent" (standalone) */
779
+ resolvedCommand = null;
507
780
  constructor(options = {}) {
508
781
  this.options = {
509
- timeout: 12e4,
782
+ timeout: 3e5,
510
783
  ...options
511
784
  };
512
785
  }
@@ -521,32 +794,30 @@ var CursorBackend = class {
521
794
  writeFileSync(join(cursorDir, "mcp.json"), JSON.stringify(mcpConfig, null, 2));
522
795
  }
523
796
  async send(message, _options) {
524
- const { command, args } = this.buildCommand(message);
797
+ const { command, args } = await this.buildCommand(message);
525
798
  const cwd = this.options.workspace || this.options.cwd;
799
+ const debugLog = this.options.debugLog;
800
+ const timeout = this.options.timeout ?? 3e5;
526
801
  try {
527
- const { stdout } = await execa(command, args, {
802
+ const { stdout } = await execWithIdleTimeout({
803
+ command,
804
+ args,
528
805
  cwd,
529
- stdin: "ignore",
530
- timeout: this.options.timeout
806
+ timeout,
807
+ onStdout: debugLog ? createStreamParser(debugLog, "Cursor", claudeAdapter) : void 0
531
808
  });
532
- return { content: stdout.trim() };
809
+ return extractClaudeResult(stdout);
533
810
  } catch (error) {
534
- if (error instanceof ExecaError) {
535
- if (error.timedOut) throw new Error(`cursor-agent timed out after ${this.options.timeout}ms`);
536
- throw new Error(`cursor-agent failed (exit ${error.exitCode}): ${error.stderr || error.shortMessage}`);
811
+ if (error instanceof IdleTimeoutError) throw new Error(`cursor agent timed out after ${timeout}ms of inactivity`);
812
+ if (error && typeof error === "object" && "exitCode" in error) {
813
+ const execError = error;
814
+ throw new Error(`cursor agent failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
537
815
  }
538
816
  throw error;
539
817
  }
540
818
  }
541
819
  async isAvailable() {
542
- for (const cmd of ["cursor-agent", "agent"]) try {
543
- await execa(cmd, ["--version"], {
544
- stdin: "ignore",
545
- timeout: 2e3
546
- });
547
- return true;
548
- } catch {}
549
- return false;
820
+ return await this.resolveCommand() !== null;
550
821
  }
551
822
  getInfo() {
552
823
  return {
@@ -554,17 +825,48 @@ var CursorBackend = class {
554
825
  model: this.options.model
555
826
  };
556
827
  }
557
- buildCommand(message) {
558
- const args = [
828
+ /**
829
+ * Resolve which cursor command is available.
830
+ * Prefers `cursor agent` (subcommand), falls back to `cursor-agent` (standalone).
831
+ * Result is cached after first resolution.
832
+ */
833
+ async resolveCommand() {
834
+ if (this.resolvedCommand !== null) return this.resolvedCommand;
835
+ try {
836
+ await execa("cursor", ["agent", "--version"], {
837
+ stdin: "ignore",
838
+ timeout: 2e3
839
+ });
840
+ this.resolvedCommand = "cursor";
841
+ return "cursor";
842
+ } catch {}
843
+ try {
844
+ await execa("cursor-agent", ["--version"], {
845
+ stdin: "ignore",
846
+ timeout: 2e3
847
+ });
848
+ this.resolvedCommand = "cursor-agent";
849
+ return "cursor-agent";
850
+ } catch {}
851
+ return null;
852
+ }
853
+ async buildCommand(message) {
854
+ const cmd = await this.resolveCommand();
855
+ const agentArgs = [
559
856
  "-p",
560
857
  "--force",
561
858
  "--approve-mcps",
859
+ "--output-format=stream-json",
562
860
  message
563
861
  ];
564
- if (this.options.model) args.push("--model", this.options.model);
862
+ if (this.options.model) agentArgs.push("--model", this.options.model);
863
+ if (cmd === "cursor") return {
864
+ command: "cursor",
865
+ args: ["agent", ...agentArgs]
866
+ };
565
867
  return {
566
868
  command: "cursor-agent",
567
- args
869
+ args: agentArgs
568
870
  };
569
871
  }
570
872
  };
@@ -732,4 +1034,4 @@ async function listBackends() {
732
1034
  }
733
1035
 
734
1036
  //#endregion
735
- export { FRONTIER_MODELS as _, createMockBackend as a, createModelAsync as b, CodexBackend as c, CLAUDE_MODEL_MAP as d, CODEX_MODEL_MAP as f, parseModel as g, getModelForBackend as h, MockAIBackend as i, ClaudeCodeBackend as l, SDK_MODEL_ALIASES as m, createBackend as n, SdkBackend as o, CURSOR_MODEL_MAP as p, listBackends as r, CursorBackend as s, checkBackends as t, BACKEND_DEFAULT_MODELS as u, SUPPORTED_PROVIDERS as v, getDefaultModel as x, createModel as y };
1037
+ export { getModelForBackend as C, createModel as D, SUPPORTED_PROVIDERS as E, createModelAsync as O, SDK_MODEL_ALIASES as S, FRONTIER_MODELS as T, execWithIdleTimeout as _, createMockBackend as a, CODEX_MODEL_MAP as b, CodexBackend as c, codexAdapter as d, createStreamParser as f, IdleTimeoutError as g, formatEvent as h, MockAIBackend as i, getDefaultModel as k, ClaudeCodeBackend as l, extractCodexResult as m, createBackend as n, SdkBackend as o, extractClaudeResult as p, listBackends as r, CursorBackend as s, checkBackends as t, claudeAdapter as u, BACKEND_DEFAULT_MODELS as v, parseModel as w, CURSOR_MODEL_MAP as x, CLAUDE_MODEL_MAP as y };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { _ as FRONTIER_MODELS, n as createBackend, x as getDefaultModel } from "../backends-DenGdkrj.mjs";
3
- import { a as createSkillsTool, c as createFeedbackTool, l as AgentSession, o as SkillsProvider, s as FEEDBACK_PROMPT, t as SkillImporter } from "../skills-VyC7eQyK.mjs";
2
+ import { T as FRONTIER_MODELS, k as getDefaultModel, n as createBackend } from "../backends-CEYiMUgC.mjs";
3
+ import { a as createSkillsTool, c as createFeedbackTool, l as AgentSession, o as SkillsProvider, s as FEEDBACK_PROMPT, t as SkillImporter } from "../skills-iya7NbH7.mjs";
4
4
  import { jsonSchema, tool } from "ai";
5
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import { dirname, isAbsolute, join, relative } from "node:path";
@@ -2094,7 +2094,7 @@ Examples:
2094
2094
 
2095
2095
  Note: Workflow name is inferred from YAML 'name' field or filename
2096
2096
  `).action(async (file, options) => {
2097
- const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-CaRCNEh6.mjs");
2097
+ const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-K1Zd655A.mjs");
2098
2098
  const tag = options.tag || DEFAULT_TAG;
2099
2099
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
2100
2100
  const workflowName = parsedWorkflow.name;
@@ -2144,7 +2144,7 @@ Examples:
2144
2144
 
2145
2145
  Note: Workflow name is inferred from YAML 'name' field or filename
2146
2146
  `).action(async (file, options) => {
2147
- const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-CaRCNEh6.mjs");
2147
+ const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-K1Zd655A.mjs");
2148
2148
  const tag = options.tag || DEFAULT_TAG;
2149
2149
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
2150
2150
  const workflowName = parsedWorkflow.name;
@@ -2339,7 +2339,7 @@ function registerInfoCommands(program) {
2339
2339
  console.log(`\nDefault: ${defaultModel} (when no model specified)`);
2340
2340
  });
2341
2341
  program.command("backends").description("Check available backends (SDK, CLI tools)").action(async () => {
2342
- const { listBackends } = await import("../backends-D3MAlJBX.mjs");
2342
+ const { listBackends } = await import("../backends-B8rYTNqn.mjs");
2343
2343
  const backends = await listBackends();
2344
2344
  console.log("Backend Status:\n");
2345
2345
  for (const backend of backends) {
@@ -2489,8 +2489,11 @@ Note: Requires agent to be created with --feedback flag
2489
2489
  //#region src/cli/index.ts
2490
2490
  globalThis.AI_SDK_LOG_WARNINGS = false;
2491
2491
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
2492
- process.stderr.write = function(chunk, ...args) {
2493
- if (process.argv.includes("--debug") || process.argv.includes("-d")) return originalStderrWrite(typeof chunk === "string" ? chunk : chunk.toString(), ...args);
2492
+ process.stderr.write = function(chunk, ...rest) {
2493
+ if (process.argv.includes("--debug") || process.argv.includes("-d")) {
2494
+ const message = typeof chunk === "string" ? chunk : chunk.toString();
2495
+ return originalStderrWrite.call(process.stderr, message, ...rest);
2496
+ }
2494
2497
  return true;
2495
2498
  };
2496
2499
  const program = new Command();
package/dist/index.d.mts CHANGED
@@ -467,7 +467,7 @@ interface ClaudeCodeOptions {
467
467
  cwd?: string;
468
468
  /** Workspace directory for agent isolation */
469
469
  workspace?: string;
470
- /** Timeout in milliseconds */
470
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
471
471
  timeout?: number;
472
472
  /** MCP config file path (for workflow context) */
473
473
  mcpConfigPath?: string;
@@ -505,19 +505,13 @@ declare class ClaudeCodeBackend implements Backend {
505
505
  interface CodexOptions {
506
506
  /** Model to use (e.g., 'gpt-5.2-codex') */
507
507
  model?: string;
508
- /** Output as JSON events */
509
- json?: boolean;
510
508
  /** Working directory (defaults to workspace if set) */
511
509
  cwd?: string;
512
510
  /** Workspace directory for agent isolation */
513
511
  workspace?: string;
514
- /** Skip git repo check */
515
- skipGitRepoCheck?: boolean;
516
- /** Approval mode: 'suggest' | 'auto-edit' | 'full-auto' */
517
- approvalMode?: "suggest" | "auto-edit" | "full-auto";
518
512
  /** Resume a previous session */
519
513
  resume?: string;
520
- /** Timeout in milliseconds */
514
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
521
515
  timeout?: number;
522
516
  /** Debug log function (for workflow diagnostics) */
523
517
  debugLog?: (message: string) => void;
@@ -553,7 +547,7 @@ interface CursorOptions {
553
547
  cwd?: string;
554
548
  /** Workspace directory for agent isolation (contains .cursor/mcp.json) */
555
549
  workspace?: string;
556
- /** Timeout in milliseconds */
550
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
557
551
  timeout?: number;
558
552
  /** Debug log function (for workflow diagnostics) */
559
553
  debugLog?: (message: string) => void;
@@ -561,6 +555,8 @@ interface CursorOptions {
561
555
  declare class CursorBackend implements Backend {
562
556
  readonly type: "cursor";
563
557
  private options;
558
+ /** Resolved command: "cursor" (subcommand style) or "cursor-agent" (standalone) */
559
+ private resolvedCommand;
564
560
  constructor(options?: CursorOptions);
565
561
  /**
566
562
  * Set up workspace directory with MCP config
@@ -578,10 +574,16 @@ declare class CursorBackend implements Backend {
578
574
  version?: string;
579
575
  model?: string;
580
576
  };
581
- protected buildCommand(message: string): {
577
+ /**
578
+ * Resolve which cursor command is available.
579
+ * Prefers `cursor agent` (subcommand), falls back to `cursor-agent` (standalone).
580
+ * Result is cached after first resolution.
581
+ */
582
+ private resolveCommand;
583
+ protected buildCommand(message: string): Promise<{
582
584
  command: string;
583
585
  args: string[];
584
- };
586
+ }>;
585
587
  }
586
588
  //#endregion
587
589
  //#region src/backends/sdk.d.ts
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as FRONTIER_MODELS, a as createMockBackend, b as createModelAsync, c as CodexBackend, i as MockAIBackend, l as ClaudeCodeBackend, n as createBackend, o as SdkBackend, r as listBackends, s as CursorBackend, t as checkBackends, v as SUPPORTED_PROVIDERS, y as createModel } from "./backends-DenGdkrj.mjs";
2
- import { a as createSkillsTool, c as createFeedbackTool, i as parseImportSpec, l as AgentSession, n as buildGitUrl, o as SkillsProvider, r as getSpecDisplayName, s as FEEDBACK_PROMPT, t as SkillImporter } from "./skills-VyC7eQyK.mjs";
1
+ import { D as createModel, E as SUPPORTED_PROVIDERS, O as createModelAsync, T as FRONTIER_MODELS, a as createMockBackend, c as CodexBackend, i as MockAIBackend, l as ClaudeCodeBackend, n as createBackend, o as SdkBackend, r as listBackends, s as CursorBackend, t as checkBackends } from "./backends-CEYiMUgC.mjs";
2
+ import { a as createSkillsTool, c as createFeedbackTool, i as parseImportSpec, l as AgentSession, n as buildGitUrl, o as SkillsProvider, r as getSpecDisplayName, s as FEEDBACK_PROMPT, t as SkillImporter } from "./skills-iya7NbH7.mjs";
3
3
  import { jsonSchema, tool } from "ai";
4
4
  import { createBashTool } from "bash-tool";
5
5
 
@@ -1,4 +1,4 @@
1
- import { b as createModelAsync } from "./backends-DenGdkrj.mjs";
1
+ import { O as createModelAsync } from "./backends-CEYiMUgC.mjs";
2
2
  import { ToolLoopAgent, jsonSchema, stepCountIs, tool } from "ai";
3
3
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
4
4
  import { join, normalize } from "node:path";
@@ -1,4 +1,4 @@
1
- import { a as createMockBackend, b as createModelAsync, g as parseModel, n as createBackend } from "./backends-DenGdkrj.mjs";
1
+ import { O as createModelAsync, a as createMockBackend, n as createBackend, w as parseModel } from "./backends-CEYiMUgC.mjs";
2
2
  import { c as CONTEXT_DEFAULTS, i as resolveContextDir, n as createFileContextProvider, t as FileContextProvider } from "./cli/index.mjs";
3
3
  import { a as createMemoryContextProvider, t as createContextMCPServer } from "./mcp-server-DtIApaBD.mjs";
4
4
  import { generateText, jsonSchema, stepCountIs, tool } from "ai";
@@ -860,6 +860,7 @@ function shouldContinue(state) {
860
860
  */
861
861
  function createAgentController(config) {
862
862
  const { name, agent, contextProvider, mcpUrl, workspaceDir, projectDir, backend, onRunComplete, log = () => {}, feedback } = config;
863
+ const infoLog = config.infoLog ?? log;
863
864
  const errorLog = config.errorLog ?? log;
864
865
  const pollInterval = config.pollInterval ?? CONTROLLER_DEFAULTS.pollInterval;
865
866
  const retryConfig = {
@@ -895,7 +896,7 @@ function createAgentController(config) {
895
896
  continue;
896
897
  }
897
898
  const senders = inbox.map((m) => m.entry.from);
898
- log(`Inbox: ${inbox.length} message(s) from [${senders.join(", ")}]`);
899
+ infoLog(`Inbox: ${inbox.length} message(s) from [${senders.join(", ")}]`);
899
900
  for (const msg of inbox) {
900
901
  const preview = msg.entry.content.length > 120 ? msg.entry.content.slice(0, 120) + "..." : msg.entry.content;
901
902
  log(` from @${msg.entry.from}: ${preview}`);
@@ -907,7 +908,7 @@ function createAgentController(config) {
907
908
  while (attempt < retryConfig.maxAttempts && shouldContinue(state)) {
908
909
  attempt++;
909
910
  state = "running";
910
- log(`Running (attempt ${attempt}/${retryConfig.maxAttempts})`);
911
+ infoLog(`Running (attempt ${attempt}/${retryConfig.maxAttempts})`);
911
912
  lastResult = await runAgent(backend, {
912
913
  name,
913
914
  agent,
@@ -922,9 +923,9 @@ function createAgentController(config) {
922
923
  projectDir,
923
924
  retryAttempt: attempt,
924
925
  feedback
925
- }, log);
926
+ }, log, infoLog);
926
927
  if (lastResult.success) {
927
- log(`DONE ${lastResult.steps ? `${lastResult.steps} steps, ${lastResult.toolCalls} tool calls, ${lastResult.duration}ms` : `${lastResult.duration}ms`}`);
928
+ infoLog(`DONE ${lastResult.steps ? `${lastResult.steps} steps, ${lastResult.toolCalls} tool calls, ${lastResult.duration}ms` : `${lastResult.duration}ms`}`);
928
929
  if (lastResult.content) await contextProvider.appendChannel(name, lastResult.content);
929
930
  await contextProvider.ackInbox(name, latestId);
930
931
  break;
@@ -954,7 +955,7 @@ function createAgentController(config) {
954
955
  async start() {
955
956
  if (state !== "stopped") throw new Error(`Controller ${name} is already running`);
956
957
  state = "idle";
957
- log(`Starting`);
958
+ infoLog(`Starting`);
958
959
  runLoop().catch((error) => {
959
960
  errorLog(`ERROR ${error instanceof Error ? error.message : String(error)}`);
960
961
  state = "stopped";
@@ -994,7 +995,8 @@ function createAgentController(config) {
994
995
  * SDK and mock backends get special runners with MCP tool bridge + bash,
995
996
  * because they can't manage tools on their own (unlike CLI backends).
996
997
  */
997
- async function runAgent(backend, ctx, log) {
998
+ async function runAgent(backend, ctx, log, infoLog) {
999
+ const info = infoLog ?? log;
998
1000
  if (backend.type === "mock") return runMockAgent(ctx, (msg) => log(msg));
999
1001
  if (backend.type === "sdk") return runSdkAgent(ctx, (msg) => log(msg));
1000
1002
  const startTime = Date.now();
@@ -1004,7 +1006,7 @@ async function runAgent(backend, ctx, log) {
1004
1006
  backend.setWorkspace(ctx.workspaceDir, mcpConfig);
1005
1007
  }
1006
1008
  const prompt = buildAgentPrompt(ctx);
1007
- log(`Prompt (${prompt.length} chars) → ${backend.type} backend`);
1009
+ info(`Prompt (${prompt.length} chars) → ${backend.type} backend`);
1008
1010
  await backend.send(prompt, { system: ctx.agent.resolvedSystemPrompt });
1009
1011
  return {
1010
1012
  success: true,
@@ -1044,9 +1046,13 @@ async function checkWorkflowIdle(controllers, provider, debounceMs = CONTROLLER_
1044
1046
  */
1045
1047
  function getBackendByType(backendType, options) {
1046
1048
  if (backendType === "mock") return createMockBackend(options?.debugLog);
1049
+ const backendOptions = {};
1050
+ if (options?.timeout) backendOptions.timeout = options.timeout;
1051
+ if (options?.debugLog) backendOptions.debugLog = options.debugLog;
1047
1052
  return createBackend({
1048
1053
  type: backendType,
1049
- model: options?.model
1054
+ model: options?.model,
1055
+ ...Object.keys(backendOptions).length > 0 ? { options: backendOptions } : {}
1050
1056
  });
1051
1057
  }
1052
1058
  /**
@@ -1658,7 +1664,8 @@ async function runWorkflowWithControllers(config) {
1658
1664
  if (createBackend) backend = createBackend(agentName, agentDef);
1659
1665
  else if (agentDef.backend) backend = getBackendByType(agentDef.backend, {
1660
1666
  model: agentDef.model,
1661
- debugLog: backendDebugLog
1667
+ debugLog: backendDebugLog,
1668
+ timeout: agentDef.timeout
1662
1669
  });
1663
1670
  else if (agentDef.model) backend = getBackendForModel(agentDef.model, { debugLog: backendDebugLog });
1664
1671
  else throw new Error(`Agent "${agentName}" requires either a backend or model field`);
@@ -1676,6 +1683,7 @@ async function runWorkflowWithControllers(config) {
1676
1683
  backend,
1677
1684
  pollInterval,
1678
1685
  log: (msg) => controllerLogger.debug(msg),
1686
+ infoLog: (msg) => controllerLogger.info(msg),
1679
1687
  errorLog: (msg) => controllerLogger.error(msg),
1680
1688
  feedback: feedbackEnabled
1681
1689
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-worker",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "SDK and CLI for creating and testing agent workers with Vercel AI SDK",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -1,3 +0,0 @@
1
- import { a as createMockBackend, c as CodexBackend, d as CLAUDE_MODEL_MAP, f as CODEX_MODEL_MAP, h as getModelForBackend, i as MockAIBackend, l as ClaudeCodeBackend, m as SDK_MODEL_ALIASES, n as createBackend, o as SdkBackend, p as CURSOR_MODEL_MAP, r as listBackends, s as CursorBackend, t as checkBackends, u as BACKEND_DEFAULT_MODELS } from "./backends-DenGdkrj.mjs";
2
-
3
- export { listBackends };