agent-worker 0.4.0 → 0.6.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.
@@ -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,335 @@ 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
+ * Execute a command with idle timeout and return abort controller
363
+ * This version returns both the promise and an abort function for external control
364
+ */
365
+ function execWithIdleTimeoutAbortable(options) {
366
+ const { command, args, cwd, onStdout } = options;
367
+ const timeout = Math.max(options.timeout, MIN_TIMEOUT_MS);
368
+ let idleTimedOut = false;
369
+ let timer;
370
+ let stdout = "";
371
+ let stderr = "";
372
+ let isAborted = false;
373
+ const subprocess = execa(command, args, {
374
+ cwd,
375
+ stdin: "ignore",
376
+ buffer: false
377
+ });
378
+ const resetTimer = () => {
379
+ clearTimeout(timer);
380
+ timer = setTimeout(() => {
381
+ idleTimedOut = true;
382
+ subprocess.kill();
383
+ }, timeout);
384
+ };
385
+ subprocess.stdout?.on("data", (chunk) => {
386
+ const text = chunk.toString();
387
+ stdout += text;
388
+ if (onStdout) onStdout(text);
389
+ resetTimer();
390
+ });
391
+ subprocess.stderr?.on("data", (chunk) => {
392
+ stderr += chunk.toString();
393
+ resetTimer();
394
+ });
395
+ resetTimer();
396
+ const abort = () => {
397
+ if (!isAborted) {
398
+ isAborted = true;
399
+ clearTimeout(timer);
400
+ subprocess.kill("SIGTERM");
401
+ setTimeout(() => {
402
+ if (!subprocess.killed) subprocess.kill("SIGKILL");
403
+ }, 1e3);
404
+ }
405
+ };
406
+ return {
407
+ promise: (async () => {
408
+ try {
409
+ await subprocess;
410
+ clearTimeout(timer);
411
+ return {
412
+ stdout: stdout.trimEnd(),
413
+ stderr: stderr.trimEnd()
414
+ };
415
+ } catch (error) {
416
+ clearTimeout(timer);
417
+ if (isAborted) throw new Error("Process aborted by user");
418
+ if (idleTimedOut) throw new IdleTimeoutError(timeout, stdout, stderr);
419
+ throw error;
420
+ }
421
+ })(),
422
+ abort
423
+ };
424
+ }
425
+ /**
426
+ * Error thrown when a process is killed due to idle timeout
427
+ */
428
+ var IdleTimeoutError = class extends Error {
429
+ timeout;
430
+ stdout;
431
+ stderr;
432
+ constructor(timeout, stdout, stderr) {
433
+ super(`Process idle timed out after ${timeout}ms of inactivity`);
434
+ this.name = "IdleTimeoutError";
435
+ this.timeout = timeout;
436
+ this.stdout = stdout;
437
+ this.stderr = stderr;
438
+ }
439
+ };
440
+
441
+ //#endregion
442
+ //#region src/backends/stream-json.ts
443
+ /**
444
+ * Format a standard StreamEvent into a human-readable progress message.
445
+ * Returns null if the event doesn't need display.
446
+ *
447
+ * This function only knows about StreamEvent — it never touches
448
+ * backend-specific raw JSON. Format-specific conversion is handled
449
+ * by the EventAdapter.
450
+ */
451
+ function formatEvent(event, backendName) {
452
+ switch (event.kind) {
453
+ case "init": {
454
+ const details = [];
455
+ if (event.model) details.push(`model: ${event.model}`);
456
+ if (event.sessionId) details.push(`session: ${event.sessionId}`);
457
+ return `${backendName} initialized${details.length > 0 ? ` (${details.join(", ")})` : ""}`;
458
+ }
459
+ case "tool_call": {
460
+ const truncated = event.args.length > 100 ? event.args.slice(0, 100) + "..." : event.args;
461
+ return `CALL ${event.name}(${truncated})`;
462
+ }
463
+ case "completed": {
464
+ const parts = [backendName, "completed"];
465
+ const details = [];
466
+ if (event.durationMs) details.push(`${(event.durationMs / 1e3).toFixed(1)}s`);
467
+ if (event.costUsd) details.push(`$${event.costUsd.toFixed(4)}`);
468
+ if (event.usage) details.push(`${event.usage.input} in, ${event.usage.output} out`);
469
+ if (details.length > 0) parts.push(`(${details.join(", ")})`);
470
+ return parts.join(" ");
471
+ }
472
+ }
473
+ }
474
+ /**
475
+ * Adapter for Claude/Cursor stream-json format.
476
+ *
477
+ * Events:
478
+ * { type: "system", subtype: "init", model: "..." }
479
+ * { type: "assistant", message: { content: [{ type: "tool_use", name, input }] } }
480
+ * { type: "result", duration_ms: N, total_cost_usd: N }
481
+ */
482
+ const claudeAdapter = (raw) => {
483
+ const type = raw.type;
484
+ if (type === "system" && raw.subtype === "init") return {
485
+ kind: "init",
486
+ model: raw.model || void 0,
487
+ sessionId: raw.session_id
488
+ };
489
+ if (type === "assistant") {
490
+ const message = raw.message;
491
+ if (!message?.content) return null;
492
+ const toolCalls = message.content.filter((c) => c.type === "tool_use");
493
+ if (toolCalls.length > 0) {
494
+ const tc = toolCalls[0];
495
+ return {
496
+ kind: "tool_call",
497
+ name: tc.name || "unknown",
498
+ args: formatToolInput(tc.input)
499
+ };
500
+ }
501
+ return null;
502
+ }
503
+ if (type === "result") return {
504
+ kind: "completed",
505
+ durationMs: raw.duration_ms,
506
+ costUsd: raw.total_cost_usd
507
+ };
508
+ return null;
509
+ };
510
+ /**
511
+ * Extract final result from Claude/Cursor stream-json output.
512
+ *
513
+ * Priority:
514
+ * 1. type=result with result field
515
+ * 2. Last assistant message with text content
516
+ * 3. Raw stdout fallback
517
+ */
518
+ function extractClaudeResult(stdout) {
519
+ const lines = stdout.trim().split("\n");
520
+ for (let i = lines.length - 1; i >= 0; i--) try {
521
+ const event = JSON.parse(lines[i]);
522
+ if (event.type === "result" && event.result) return { content: event.result };
523
+ } catch {}
524
+ for (let i = lines.length - 1; i >= 0; i--) try {
525
+ const event = JSON.parse(lines[i]);
526
+ if (event.type === "assistant" && event.message?.content) {
527
+ const textParts = event.message.content.filter((c) => c.type === "text").map((c) => c.text);
528
+ if (textParts.length > 0) return { content: textParts.join("\n") };
529
+ }
530
+ } catch {}
531
+ return { content: stdout.trim() };
532
+ }
533
+ /**
534
+ * Adapter for Codex --json format.
535
+ *
536
+ * Events:
537
+ * { type: "thread.started", thread_id: "..." }
538
+ * { type: "item.completed", item: { type: "function_call", name, arguments } }
539
+ * { type: "item.completed", item: { type: "agent_message", text } } → skipped (result only)
540
+ * { type: "turn.completed", usage: { input_tokens, output_tokens } }
541
+ */
542
+ const codexAdapter = (raw) => {
543
+ const type = raw.type;
544
+ if (type === "thread.started") {
545
+ const threadId = raw.thread_id;
546
+ return {
547
+ kind: "init",
548
+ sessionId: threadId ? `${threadId.slice(0, 8)}...` : void 0
549
+ };
550
+ }
551
+ if (type === "item.completed") {
552
+ const item = raw.item;
553
+ if (!item) return null;
554
+ if (item.type === "function_call") return {
555
+ kind: "tool_call",
556
+ name: item.name || "unknown",
557
+ args: item.arguments ?? ""
558
+ };
559
+ return null;
560
+ }
561
+ if (type === "turn.completed") {
562
+ const usage = raw.usage;
563
+ return {
564
+ kind: "completed",
565
+ usage: usage ? {
566
+ input: usage.input_tokens ?? 0,
567
+ output: usage.output_tokens ?? 0
568
+ } : void 0
569
+ };
570
+ }
571
+ return null;
572
+ };
573
+ /**
574
+ * Extract final result from Codex --json output.
575
+ *
576
+ * Priority:
577
+ * 1. Last item.completed with item.type=agent_message
578
+ * 2. Raw stdout fallback
579
+ */
580
+ function extractCodexResult(stdout) {
581
+ const lines = stdout.trim().split("\n");
582
+ for (let i = lines.length - 1; i >= 0; i--) try {
583
+ const event = JSON.parse(lines[i]);
584
+ if (event.type === "item.completed" && event.item?.type === "agent_message" && event.item?.text) return { content: event.item.text };
585
+ } catch {}
586
+ return { content: stdout.trim() };
587
+ }
588
+ /**
589
+ * Create a line-buffered stream parser.
590
+ *
591
+ * Accumulates stdout chunks, parses each line through the given adapter,
592
+ * and emits formatted progress messages via debugLog.
593
+ *
594
+ * @param debugLog - Callback for progress messages
595
+ * @param backendName - Display name (e.g., "Cursor", "Claude", "Codex")
596
+ * @param adapter - Format-specific adapter to convert raw JSON → StreamEvent
597
+ */
598
+ function createStreamParser(debugLog, backendName, adapter) {
599
+ let lineBuf = "";
600
+ return (chunk) => {
601
+ lineBuf += chunk;
602
+ const lines = lineBuf.split("\n");
603
+ lineBuf = lines.pop() ?? "";
604
+ for (const line of lines) {
605
+ if (!line.trim()) continue;
606
+ try {
607
+ const event = adapter(JSON.parse(line));
608
+ if (event) {
609
+ const progress = formatEvent(event, backendName);
610
+ if (progress) debugLog(progress);
611
+ }
612
+ } catch {}
613
+ }
614
+ };
615
+ }
616
+ /**
617
+ * Format tool call input for display (truncated JSON string)
618
+ */
619
+ function formatToolInput(input) {
620
+ if (!input || typeof input !== "object") return "";
621
+ try {
622
+ const str = JSON.stringify(input);
623
+ return str.length > 100 ? str.slice(0, 100) + "..." : str;
624
+ } catch {
625
+ return "";
626
+ }
627
+ }
628
+
300
629
  //#endregion
301
630
  //#region src/backends/claude-code.ts
302
631
  /**
@@ -312,6 +641,7 @@ function parseModel(model) {
312
641
  var ClaudeCodeBackend = class {
313
642
  type = "claude";
314
643
  options;
644
+ currentAbort;
315
645
  constructor(options = {}) {
316
646
  this.options = {
317
647
  timeout: 3e5,
@@ -332,13 +662,22 @@ var ClaudeCodeBackend = class {
332
662
  async send(message, options) {
333
663
  const args = this.buildArgs(message, options);
334
664
  const cwd = this.options.workspace || this.options.cwd;
665
+ const debugLog = this.options.debugLog;
666
+ const outputFormat = this.options.outputFormat ?? "stream-json";
667
+ const timeout = this.options.timeout ?? 3e5;
335
668
  try {
336
- const { stdout } = await execa("claude", args, {
669
+ const { promise, abort } = execWithIdleTimeoutAbortable({
670
+ command: "claude",
671
+ args,
337
672
  cwd,
338
- stdin: "ignore",
339
- timeout: this.options.timeout
673
+ timeout,
674
+ onStdout: outputFormat === "stream-json" && debugLog ? createStreamParser(debugLog, "Claude", claudeAdapter) : void 0
340
675
  });
341
- if (this.options.outputFormat === "json") try {
676
+ this.currentAbort = abort;
677
+ const { stdout } = await promise;
678
+ this.currentAbort = void 0;
679
+ if (outputFormat === "stream-json") return extractClaudeResult(stdout);
680
+ if (outputFormat === "json") try {
342
681
  const parsed = JSON.parse(stdout);
343
682
  return {
344
683
  content: parsed.content || parsed.result || stdout,
@@ -350,7 +689,12 @@ var ClaudeCodeBackend = class {
350
689
  }
351
690
  return { content: stdout.trim() };
352
691
  } catch (error) {
353
- if (error instanceof ExecaError) throw new Error(`claude failed (exit ${error.exitCode}): ${error.stderr || error.shortMessage}`);
692
+ this.currentAbort = void 0;
693
+ if (error instanceof IdleTimeoutError) throw new Error(`claude timed out after ${timeout}ms of inactivity`);
694
+ if (error && typeof error === "object" && "exitCode" in error) {
695
+ const execError = error;
696
+ throw new Error(`claude failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
697
+ }
354
698
  throw error;
355
699
  }
356
700
  }
@@ -383,7 +727,9 @@ var ClaudeCodeBackend = class {
383
727
  args.push("--append-system-prompt", system);
384
728
  }
385
729
  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);
730
+ const outputFormat = this.options.outputFormat ?? "stream-json";
731
+ args.push("--output-format", outputFormat);
732
+ if (outputFormat === "stream-json") args.push("--verbose");
387
733
  if (this.options.continue) args.push("--continue");
388
734
  if (this.options.resume) args.push("--resume", this.options.resume);
389
735
  if (this.options.mcpConfigPath) args.push("--mcp-config", this.options.mcpConfigPath);
@@ -395,13 +741,22 @@ var ClaudeCodeBackend = class {
395
741
  setMcpConfigPath(path) {
396
742
  this.options.mcpConfigPath = path;
397
743
  }
744
+ /**
745
+ * Abort any running claude process
746
+ */
747
+ abort() {
748
+ if (this.currentAbort) {
749
+ this.currentAbort();
750
+ this.currentAbort = void 0;
751
+ }
752
+ }
398
753
  };
399
754
 
400
755
  //#endregion
401
756
  //#region src/backends/codex.ts
402
757
  /**
403
758
  * OpenAI Codex CLI backend
404
- * Uses `codex exec` for non-interactive mode
759
+ * Uses `codex exec` for non-interactive mode with JSON event output
405
760
  *
406
761
  * MCP Configuration:
407
762
  * Codex uses project-level MCP config. Use setWorkspace() to set up
@@ -432,28 +787,23 @@ var CodexBackend = class {
432
787
  async send(message, _options) {
433
788
  const args = this.buildArgs(message);
434
789
  const cwd = this.options.workspace || this.options.cwd;
790
+ const debugLog = this.options.debugLog;
791
+ const timeout = this.options.timeout ?? 3e5;
435
792
  try {
436
- const { stdout } = await execa("codex", args, {
793
+ const { stdout } = await execWithIdleTimeout({
794
+ command: "codex",
795
+ args,
437
796
  cwd,
438
- stdin: "ignore",
439
- timeout: this.options.timeout
797
+ timeout,
798
+ onStdout: debugLog ? createStreamParser(debugLog, "Codex", codexAdapter) : void 0
440
799
  });
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() };
800
+ return extractCodexResult(stdout);
455
801
  } catch (error) {
456
- if (error instanceof ExecaError) throw new Error(`codex failed (exit ${error.exitCode}): ${error.stderr || error.shortMessage}`);
802
+ if (error instanceof IdleTimeoutError) throw new Error(`codex timed out after ${timeout}ms of inactivity`);
803
+ if (error && typeof error === "object" && "exitCode" in error) {
804
+ const execError = error;
805
+ throw new Error(`codex failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
806
+ }
457
807
  throw error;
458
808
  }
459
809
  }
@@ -477,13 +827,12 @@ var CodexBackend = class {
477
827
  buildArgs(message) {
478
828
  const args = [
479
829
  "exec",
480
- "--dangerously-bypass-approvals-and-sandbox",
830
+ "--full-auto",
831
+ "--json",
832
+ "--skip-git-repo-check",
481
833
  message
482
834
  ];
483
835
  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
836
  if (this.options.resume) args.push("--resume", this.options.resume);
488
837
  return args;
489
838
  }
@@ -493,7 +842,8 @@ var CodexBackend = class {
493
842
  //#region src/backends/cursor.ts
494
843
  /**
495
844
  * Cursor CLI backend
496
- * Uses `cursor-agent -p` for non-interactive mode
845
+ * Uses `cursor agent -p` (preferred) or `cursor-agent -p` (fallback)
846
+ * for non-interactive mode with stream-json output
497
847
  *
498
848
  * MCP Configuration:
499
849
  * Cursor uses project-level MCP config via .cursor/mcp.json in the workspace.
@@ -504,9 +854,11 @@ var CodexBackend = class {
504
854
  var CursorBackend = class {
505
855
  type = "cursor";
506
856
  options;
857
+ /** Resolved command: "cursor" (subcommand style) or "cursor-agent" (standalone) */
858
+ resolvedCommand = null;
507
859
  constructor(options = {}) {
508
860
  this.options = {
509
- timeout: 12e4,
861
+ timeout: 3e5,
510
862
  ...options
511
863
  };
512
864
  }
@@ -521,32 +873,30 @@ var CursorBackend = class {
521
873
  writeFileSync(join(cursorDir, "mcp.json"), JSON.stringify(mcpConfig, null, 2));
522
874
  }
523
875
  async send(message, _options) {
524
- const { command, args } = this.buildCommand(message);
876
+ const { command, args } = await this.buildCommand(message);
525
877
  const cwd = this.options.workspace || this.options.cwd;
878
+ const debugLog = this.options.debugLog;
879
+ const timeout = this.options.timeout ?? 3e5;
526
880
  try {
527
- const { stdout } = await execa(command, args, {
881
+ const { stdout } = await execWithIdleTimeout({
882
+ command,
883
+ args,
528
884
  cwd,
529
- stdin: "ignore",
530
- timeout: this.options.timeout
885
+ timeout,
886
+ onStdout: debugLog ? createStreamParser(debugLog, "Cursor", claudeAdapter) : void 0
531
887
  });
532
- return { content: stdout.trim() };
888
+ return extractClaudeResult(stdout);
533
889
  } 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}`);
890
+ if (error instanceof IdleTimeoutError) throw new Error(`cursor agent timed out after ${timeout}ms of inactivity`);
891
+ if (error && typeof error === "object" && "exitCode" in error) {
892
+ const execError = error;
893
+ throw new Error(`cursor agent failed (exit ${execError.exitCode}): ${execError.stderr || execError.shortMessage}`);
537
894
  }
538
895
  throw error;
539
896
  }
540
897
  }
541
898
  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;
899
+ return await this.resolveCommand() !== null;
550
900
  }
551
901
  getInfo() {
552
902
  return {
@@ -554,17 +904,48 @@ var CursorBackend = class {
554
904
  model: this.options.model
555
905
  };
556
906
  }
557
- buildCommand(message) {
558
- const args = [
907
+ /**
908
+ * Resolve which cursor command is available.
909
+ * Prefers `cursor agent` (subcommand), falls back to `cursor-agent` (standalone).
910
+ * Result is cached after first resolution.
911
+ */
912
+ async resolveCommand() {
913
+ if (this.resolvedCommand !== null) return this.resolvedCommand;
914
+ try {
915
+ await execa("cursor", ["agent", "--version"], {
916
+ stdin: "ignore",
917
+ timeout: 2e3
918
+ });
919
+ this.resolvedCommand = "cursor";
920
+ return "cursor";
921
+ } catch {}
922
+ try {
923
+ await execa("cursor-agent", ["--version"], {
924
+ stdin: "ignore",
925
+ timeout: 2e3
926
+ });
927
+ this.resolvedCommand = "cursor-agent";
928
+ return "cursor-agent";
929
+ } catch {}
930
+ return null;
931
+ }
932
+ async buildCommand(message) {
933
+ const cmd = await this.resolveCommand();
934
+ const agentArgs = [
559
935
  "-p",
560
936
  "--force",
561
937
  "--approve-mcps",
938
+ "--output-format=stream-json",
562
939
  message
563
940
  ];
564
- if (this.options.model) args.push("--model", this.options.model);
941
+ if (this.options.model) agentArgs.push("--model", this.options.model);
942
+ if (cmd === "cursor") return {
943
+ command: "cursor",
944
+ args: ["agent", ...agentArgs]
945
+ };
565
946
  return {
566
947
  command: "cursor-agent",
567
- args
948
+ args: agentArgs
568
949
  };
569
950
  }
570
951
  };
@@ -732,4 +1113,4 @@ async function listBackends() {
732
1113
  }
733
1114
 
734
1115
  //#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 };
1116
+ 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 };
@@ -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-BJyk6zvs.mjs";
2
+
3
+ export { listBackends };
@@ -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-BJyk6zvs.mjs";
3
+ import { a as createSkillsTool, c as createFeedbackTool, l as AgentSession, o as SkillsProvider, s as FEEDBACK_PROMPT, t as SkillImporter } from "../skills-CuAfAkHU.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";
@@ -541,6 +541,8 @@ async function handleRequest(getState, req, resetIdleTimer, gracefulShutdown, re
541
541
  const RESOURCE_PREFIX = "res_";
542
542
  /** Resource URI scheme */
543
543
  const RESOURCE_SCHEME = "resource:";
544
+ /** Message length threshold for channel messages - content longer than this should use resources or documents */
545
+ const MESSAGE_LENGTH_THRESHOLD = 1200;
544
546
  /**
545
547
  * Generate a unique resource ID
546
548
  */
@@ -554,6 +556,12 @@ function generateResourceId() {
554
556
  function createResourceRef(id) {
555
557
  return `${RESOURCE_SCHEME}${id}`;
556
558
  }
559
+ /**
560
+ * Check if content should be stored as a resource instead of inline
561
+ */
562
+ function shouldUseResource(content) {
563
+ return content.length > MESSAGE_LENGTH_THRESHOLD;
564
+ }
557
565
  /** Default context configuration values */
558
566
  const CONTEXT_DEFAULTS = {
559
567
  dir: "~/.agent-worker/workflows/${{ workflow.name }}/${{ workflow.tag }}/",
@@ -679,6 +687,23 @@ var ContextProviderImpl = class {
679
687
  cursor: entries.length
680
688
  };
681
689
  }
690
+ /**
691
+ * Smart send: automatically converts long messages to resources
692
+ *
693
+ * If content exceeds MESSAGE_LENGTH_THRESHOLD:
694
+ * 1. Creates a resource with the full content
695
+ * 2. Sends a short message referencing the resource
696
+ * 3. Logs the full content in debug channel for visibility
697
+ */
698
+ async smartSend(from, content, options) {
699
+ if (!shouldUseResource(content)) return this.appendChannel(from, content, options);
700
+ const resourceType = content.startsWith("```") || content.includes("\n```") ? "markdown" : "text";
701
+ const resource = await this.createResource(content, from, resourceType);
702
+ await this.appendChannel("system", `Created resource ${resource.id} (${content.length} chars) for @${from}:\n${content}`, { kind: "debug" });
703
+ const mentions = extractMentions(content, this.validAgents);
704
+ const shortMessage = `${mentions.length > 0 ? mentions.map((m) => `@${m}`).join(" ") + " " : ""}[Long content stored as resource]\n\nRead the full content: resource_read("${resource.id}")\n\nReference: ${resource.ref}`;
705
+ return this.appendChannel(from, shortMessage, options);
706
+ }
682
707
  async getInbox(agent) {
683
708
  const state = await this.loadInboxState();
684
709
  const lastAckId = state.readCursors[agent];
@@ -2094,10 +2119,25 @@ Examples:
2094
2119
 
2095
2120
  Note: Workflow name is inferred from YAML 'name' field or filename
2096
2121
  `).action(async (file, options) => {
2097
- const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-CaRCNEh6.mjs");
2122
+ const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-C26F4PpH.mjs");
2098
2123
  const tag = options.tag || DEFAULT_TAG;
2099
2124
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
2100
2125
  const workflowName = parsedWorkflow.name;
2126
+ let controllers;
2127
+ let isCleaningUp = false;
2128
+ const cleanup = async () => {
2129
+ if (isCleaningUp) return;
2130
+ isCleaningUp = true;
2131
+ console.log("\nInterrupted, cleaning up...");
2132
+ if (controllers) {
2133
+ const { shutdownControllers } = await import("../workflow-C26F4PpH.mjs");
2134
+ const { createSilentLogger } = await import("../logger-L9AtbIPS.mjs");
2135
+ await shutdownControllers(controllers, createSilentLogger());
2136
+ }
2137
+ process.exit(130);
2138
+ };
2139
+ process.on("SIGINT", cleanup);
2140
+ process.on("SIGTERM", cleanup);
2101
2141
  try {
2102
2142
  const log = options.json ? console.error : console.log;
2103
2143
  const result = await runWorkflowWithControllers({
@@ -2110,6 +2150,9 @@ Note: Workflow name is inferred from YAML 'name' field or filename
2110
2150
  mode: "run",
2111
2151
  feedback: options.feedback
2112
2152
  });
2153
+ controllers = result.controllers;
2154
+ process.off("SIGINT", cleanup);
2155
+ process.off("SIGTERM", cleanup);
2113
2156
  if (!result.success) {
2114
2157
  console.error("Workflow failed:", result.error);
2115
2158
  process.exit(1);
@@ -2132,6 +2175,8 @@ Note: Workflow name is inferred from YAML 'name' field or filename
2132
2175
  for (const entry of result.feedback) console.log(` [${entry.type}] ${entry.target}: ${entry.description}`);
2133
2176
  }
2134
2177
  } catch (error) {
2178
+ process.off("SIGINT", cleanup);
2179
+ process.off("SIGTERM", cleanup);
2135
2180
  console.error("Error:", error instanceof Error ? error.message : String(error));
2136
2181
  process.exit(1);
2137
2182
  }
@@ -2144,7 +2189,7 @@ Examples:
2144
2189
 
2145
2190
  Note: Workflow name is inferred from YAML 'name' field or filename
2146
2191
  `).action(async (file, options) => {
2147
- const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-CaRCNEh6.mjs");
2192
+ const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-C26F4PpH.mjs");
2148
2193
  const tag = options.tag || DEFAULT_TAG;
2149
2194
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
2150
2195
  const workflowName = parsedWorkflow.name;
@@ -2339,7 +2384,7 @@ function registerInfoCommands(program) {
2339
2384
  console.log(`\nDefault: ${defaultModel} (when no model specified)`);
2340
2385
  });
2341
2386
  program.command("backends").description("Check available backends (SDK, CLI tools)").action(async () => {
2342
- const { listBackends } = await import("../backends-D3MAlJBX.mjs");
2387
+ const { listBackends } = await import("../backends-fLwD_6G_.mjs");
2343
2388
  const backends = await listBackends();
2344
2389
  console.log("Backend Status:\n");
2345
2390
  for (const backend of backends) {
@@ -2369,7 +2414,7 @@ Examples:
2369
2414
  $ agent-worker doc read @review:pr-123 # Read specific workflow:tag document
2370
2415
  `).action(async (targetInput) => {
2371
2416
  const dir = await resolveDir(targetInput);
2372
- const { createFileContextProvider } = await import("../context-C7nBmU5D.mjs");
2417
+ const { createFileContextProvider } = await import("../context-DW7xrslO.mjs");
2373
2418
  const content = await createFileContextProvider(dir, []).readDocument();
2374
2419
  console.log(content || "(empty document)");
2375
2420
  });
@@ -2387,7 +2432,7 @@ Examples:
2387
2432
  process.exit(1);
2388
2433
  }
2389
2434
  const dir = await resolveDir(targetInput);
2390
- const { createFileContextProvider } = await import("../context-C7nBmU5D.mjs");
2435
+ const { createFileContextProvider } = await import("../context-DW7xrslO.mjs");
2391
2436
  await createFileContextProvider(dir, []).writeDocument(content);
2392
2437
  console.log("Document written");
2393
2438
  });
@@ -2405,7 +2450,7 @@ Examples:
2405
2450
  process.exit(1);
2406
2451
  }
2407
2452
  const dir = await resolveDir(targetInput);
2408
- const { createFileContextProvider } = await import("../context-C7nBmU5D.mjs");
2453
+ const { createFileContextProvider } = await import("../context-DW7xrslO.mjs");
2409
2454
  await createFileContextProvider(dir, []).appendDocument(content);
2410
2455
  console.log("Content appended");
2411
2456
  });
@@ -2485,16 +2530,23 @@ Note: Requires agent to be created with --feedback flag
2485
2530
  });
2486
2531
  }
2487
2532
 
2533
+ //#endregion
2534
+ //#region package.json
2535
+ var version = "0.6.0";
2536
+
2488
2537
  //#endregion
2489
2538
  //#region src/cli/index.ts
2490
2539
  globalThis.AI_SDK_LOG_WARNINGS = false;
2491
2540
  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);
2541
+ process.stderr.write = function(chunk, ...rest) {
2542
+ if (process.argv.includes("--debug") || process.argv.includes("-d")) {
2543
+ const message = typeof chunk === "string" ? chunk : chunk.toString();
2544
+ return originalStderrWrite.call(process.stderr, message, ...rest);
2545
+ }
2494
2546
  return true;
2495
2547
  };
2496
2548
  const program = new Command();
2497
- program.name("agent-worker").description("CLI for creating and managing AI agents").version("0.0.1");
2549
+ program.name("agent-worker").description("CLI for creating and managing AI agents").version(version);
2498
2550
  registerAgentCommands(program);
2499
2551
  registerSendCommands(program);
2500
2552
  registerMockCommands(program);
@@ -2506,4 +2558,4 @@ registerDocCommands(program);
2506
2558
  program.parse();
2507
2559
 
2508
2560
  //#endregion
2509
- export { FileStorage as a, CONTEXT_DEFAULTS as c, RESOURCE_SCHEME as d, calculatePriority as f, generateResourceId as h, resolveContextDir as i, MENTION_PATTERN as l, extractMentions as m, createFileContextProvider as n, MemoryStorage as o, createResourceRef as p, getDefaultContextDir as r, ContextProviderImpl as s, FileContextProvider as t, RESOURCE_PREFIX as u };
2561
+ export { shouldUseResource as _, FileStorage as a, CONTEXT_DEFAULTS as c, RESOURCE_PREFIX as d, RESOURCE_SCHEME as f, generateResourceId as g, extractMentions as h, resolveContextDir as i, MENTION_PATTERN as l, createResourceRef as m, createFileContextProvider as n, MemoryStorage as o, calculatePriority as p, getDefaultContextDir as r, ContextProviderImpl as s, FileContextProvider as t, MESSAGE_LENGTH_THRESHOLD as u };
@@ -0,0 +1,4 @@
1
+ import { _ as shouldUseResource, a as FileStorage, c as CONTEXT_DEFAULTS, d as RESOURCE_PREFIX, f as RESOURCE_SCHEME, g as generateResourceId, h as extractMentions, i as resolveContextDir, l as MENTION_PATTERN, m as createResourceRef, n as createFileContextProvider, o as MemoryStorage, p as calculatePriority, r as getDefaultContextDir, s as ContextProviderImpl, t as FileContextProvider, u as MESSAGE_LENGTH_THRESHOLD } from "./cli/index.mjs";
2
+ import { a as createMemoryContextProvider, i as MemoryContextProvider, n as formatProposal, r as formatProposalList, t as createContextMCPServer } from "./mcp-server-ONSDOpt5.mjs";
3
+
4
+ export { createFileContextProvider };
package/dist/index.d.mts CHANGED
@@ -175,6 +175,8 @@ interface Backend {
175
175
  setWorkspace?(workspaceDir: string, mcpConfig: {
176
176
  mcpServers: Record<string, unknown>;
177
177
  }): void;
178
+ /** Abort any running operations and cleanup resources */
179
+ abort?(): void;
178
180
  }
179
181
  //#endregion
180
182
  //#region src/agent/session.d.ts
@@ -467,7 +469,7 @@ interface ClaudeCodeOptions {
467
469
  cwd?: string;
468
470
  /** Workspace directory for agent isolation */
469
471
  workspace?: string;
470
- /** Timeout in milliseconds */
472
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
471
473
  timeout?: number;
472
474
  /** MCP config file path (for workflow context) */
473
475
  mcpConfigPath?: string;
@@ -477,6 +479,7 @@ interface ClaudeCodeOptions {
477
479
  declare class ClaudeCodeBackend implements Backend {
478
480
  readonly type: "claude";
479
481
  private options;
482
+ private currentAbort?;
480
483
  constructor(options?: ClaudeCodeOptions);
481
484
  /**
482
485
  * Set up workspace directory with MCP config
@@ -499,25 +502,23 @@ declare class ClaudeCodeBackend implements Backend {
499
502
  * Set MCP config path (for workflow integration)
500
503
  */
501
504
  setMcpConfigPath(path: string): void;
505
+ /**
506
+ * Abort any running claude process
507
+ */
508
+ abort(): void;
502
509
  }
503
510
  //#endregion
504
511
  //#region src/backends/codex.d.ts
505
512
  interface CodexOptions {
506
513
  /** Model to use (e.g., 'gpt-5.2-codex') */
507
514
  model?: string;
508
- /** Output as JSON events */
509
- json?: boolean;
510
515
  /** Working directory (defaults to workspace if set) */
511
516
  cwd?: string;
512
517
  /** Workspace directory for agent isolation */
513
518
  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
519
  /** Resume a previous session */
519
520
  resume?: string;
520
- /** Timeout in milliseconds */
521
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
521
522
  timeout?: number;
522
523
  /** Debug log function (for workflow diagnostics) */
523
524
  debugLog?: (message: string) => void;
@@ -553,7 +554,7 @@ interface CursorOptions {
553
554
  cwd?: string;
554
555
  /** Workspace directory for agent isolation (contains .cursor/mcp.json) */
555
556
  workspace?: string;
556
- /** Timeout in milliseconds */
557
+ /** Idle timeout in milliseconds — kills process if no output for this duration */
557
558
  timeout?: number;
558
559
  /** Debug log function (for workflow diagnostics) */
559
560
  debugLog?: (message: string) => void;
@@ -561,6 +562,8 @@ interface CursorOptions {
561
562
  declare class CursorBackend implements Backend {
562
563
  readonly type: "cursor";
563
564
  private options;
565
+ /** Resolved command: "cursor" (subcommand style) or "cursor-agent" (standalone) */
566
+ private resolvedCommand;
564
567
  constructor(options?: CursorOptions);
565
568
  /**
566
569
  * Set up workspace directory with MCP config
@@ -578,10 +581,16 @@ declare class CursorBackend implements Backend {
578
581
  version?: string;
579
582
  model?: string;
580
583
  };
581
- protected buildCommand(message: string): {
584
+ /**
585
+ * Resolve which cursor command is available.
586
+ * Prefers `cursor agent` (subcommand), falls back to `cursor-agent` (standalone).
587
+ * Result is cached after first resolution.
588
+ */
589
+ private resolveCommand;
590
+ protected buildCommand(message: string): Promise<{
582
591
  command: string;
583
592
  args: string[];
584
- };
593
+ }>;
585
594
  }
586
595
  //#endregion
587
596
  //#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-BJyk6zvs.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-CuAfAkHU.mjs";
3
3
  import { jsonSchema, tool } from "ai";
4
4
  import { createBashTool } from "bash-tool";
5
5
 
@@ -0,0 +1,63 @@
1
+ //#region src/workflow/logger.ts
2
+ /**
3
+ * Create a silent logger (no output)
4
+ */
5
+ function createSilentLogger() {
6
+ const noop = () => {};
7
+ return {
8
+ debug: noop,
9
+ info: noop,
10
+ warn: noop,
11
+ error: noop,
12
+ isDebug: () => false,
13
+ child: () => createSilentLogger()
14
+ };
15
+ }
16
+ /**
17
+ * Create a logger that writes to the channel.
18
+ *
19
+ * - info/warn/error → channel entry with kind="log" (always shown to user)
20
+ * - debug → channel entry with kind="debug" (only shown with --debug)
21
+ *
22
+ * The display layer handles formatting and filtering.
23
+ */
24
+ function createChannelLogger(config) {
25
+ const { provider, from = "system" } = config;
26
+ const formatContent = (level, message, args) => {
27
+ const argsStr = args.length > 0 ? " " + args.map(formatArg).join(" ") : "";
28
+ if (level === "warn") return `[WARN] ${message}${argsStr}`;
29
+ if (level === "error") return `[ERROR] ${message}${argsStr}`;
30
+ return `${message}${argsStr}`;
31
+ };
32
+ const write = (level, message, args) => {
33
+ const content = formatContent(level, message, args);
34
+ const kind = level === "debug" ? "debug" : "log";
35
+ provider.appendChannel(from, content, { kind }).catch(() => {});
36
+ };
37
+ return {
38
+ debug: (message, ...args) => write("debug", message, args),
39
+ info: (message, ...args) => write("info", message, args),
40
+ warn: (message, ...args) => write("warn", message, args),
41
+ error: (message, ...args) => write("error", message, args),
42
+ isDebug: () => true,
43
+ child: (childPrefix) => {
44
+ return createChannelLogger({
45
+ provider,
46
+ from: from ? `${from}:${childPrefix}` : childPrefix
47
+ });
48
+ }
49
+ };
50
+ }
51
+ /** Format an argument for logging */
52
+ function formatArg(arg) {
53
+ if (arg === null || arg === void 0) return String(arg);
54
+ if (typeof arg === "object") try {
55
+ return JSON.stringify(arg);
56
+ } catch {
57
+ return String(arg);
58
+ }
59
+ return String(arg);
60
+ }
61
+
62
+ //#endregion
63
+ export { createChannelLogger, createSilentLogger };
@@ -169,9 +169,8 @@ function createContextMCPServer(options) {
169
169
  version
170
170
  });
171
171
  const agentConnections = /* @__PURE__ */ new Map();
172
- const CHANNEL_MSG_LIMIT = 2e3;
173
- server.tool("channel_send", `Send a message to the shared channel. Use @agent to mention/notify. Use "to" for private DMs. Max ${CHANNEL_MSG_LIMIT} chars — for longer content, use resource_create first then reference the resource ID in your message.`, {
174
- message: z.string().describe("Message content, can include @mentions like @reviewer or @coder"),
172
+ server.tool("channel_send", `Send a message to the shared channel. Use @agent to mention/notify. Use "to" for private DMs. Long messages (> 2000 chars) are automatically converted to resources.`, {
173
+ message: z.string().describe("Message content, can include @mentions like @reviewer or @coder. Long messages are auto-converted to resources."),
175
174
  to: z.string().optional().describe("Send as DM to a specific agent (private, only you and recipient see it)")
176
175
  }, async ({ message, to }, extra) => {
177
176
  const from = getAgentId(extra) || "anonymous";
@@ -179,15 +178,8 @@ function createContextMCPServer(options) {
179
178
  message,
180
179
  to
181
180
  });
182
- if (message.length > CHANNEL_MSG_LIMIT) return {
183
- isError: true,
184
- content: [{
185
- type: "text",
186
- text: `Message too long (${message.length} chars, max ${CHANNEL_MSG_LIMIT}). Use resource_create to store the full content, then send a short message referencing the resource ID.`
187
- }]
188
- };
189
181
  const sendOpts = to ? { to } : void 0;
190
- const msg = await provider.appendChannel(from, message, sendOpts);
182
+ const msg = await provider.smartSend(from, message, sendOpts);
191
183
  for (const target of msg.mentions) if (onMention) onMention(from, target, msg);
192
184
  if (to && !msg.mentions.includes(to) && onMention) onMention(from, to, msg);
193
185
  return { content: [{
@@ -1,4 +1,4 @@
1
- import { b as createModelAsync } from "./backends-DenGdkrj.mjs";
1
+ import { O as createModelAsync } from "./backends-BJyk6zvs.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,6 +1,7 @@
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-BJyk6zvs.mjs";
2
2
  import { c as CONTEXT_DEFAULTS, i as resolveContextDir, n as createFileContextProvider, t as FileContextProvider } from "./cli/index.mjs";
3
- import { a as createMemoryContextProvider, t as createContextMCPServer } from "./mcp-server-DtIApaBD.mjs";
3
+ import { a as createMemoryContextProvider, t as createContextMCPServer } from "./mcp-server-ONSDOpt5.mjs";
4
+ import { createChannelLogger, createSilentLogger } from "./logger-L9AtbIPS.mjs";
4
5
  import { generateText, jsonSchema, stepCountIs, tool } from "ai";
5
6
  import { existsSync, mkdirSync, readFileSync } from "node:fs";
6
7
  import { basename, dirname, join, resolve } from "node:path";
@@ -459,16 +460,6 @@ function formatInbox(inbox) {
459
460
  }).join("\n");
460
461
  }
461
462
  /**
462
- * Format channel messages for display
463
- */
464
- function formatChannel(entries) {
465
- if (entries.length === 0) return "(no messages)";
466
- return entries.map((e) => {
467
- const dm = e.to ? ` [DM→@${e.to}]` : "";
468
- return `[${e.timestamp.slice(11, 19)}] @${e.from}${dm}: ${e.content}`;
469
- }).join("\n");
470
- }
471
- /**
472
463
  * Build the complete agent prompt from run context
473
464
  */
474
465
  function buildAgentPrompt(ctx) {
@@ -479,8 +470,8 @@ function buildAgentPrompt(ctx) {
479
470
  sections.push(`## Inbox (${ctx.inbox.length} message${ctx.inbox.length === 1 ? "" : "s"} for you)`);
480
471
  sections.push(formatInbox(ctx.inbox));
481
472
  sections.push("");
482
- sections.push(`## Recent Activity (last ${ctx.recentChannel.length} messages)`);
483
- sections.push(formatChannel(ctx.recentChannel));
473
+ sections.push("## Recent Activity");
474
+ sections.push("Use channel_read tool to view recent channel messages and conversation context if needed.");
484
475
  if (ctx.documentContent) {
485
476
  sections.push("");
486
477
  sections.push("## Shared Document");
@@ -860,6 +851,7 @@ function shouldContinue(state) {
860
851
  */
861
852
  function createAgentController(config) {
862
853
  const { name, agent, contextProvider, mcpUrl, workspaceDir, projectDir, backend, onRunComplete, log = () => {}, feedback } = config;
854
+ const infoLog = config.infoLog ?? log;
863
855
  const errorLog = config.errorLog ?? log;
864
856
  const pollInterval = config.pollInterval ?? CONTROLLER_DEFAULTS.pollInterval;
865
857
  const retryConfig = {
@@ -895,7 +887,7 @@ function createAgentController(config) {
895
887
  continue;
896
888
  }
897
889
  const senders = inbox.map((m) => m.entry.from);
898
- log(`Inbox: ${inbox.length} message(s) from [${senders.join(", ")}]`);
890
+ infoLog(`Inbox: ${inbox.length} message(s) from [${senders.join(", ")}]`);
899
891
  for (const msg of inbox) {
900
892
  const preview = msg.entry.content.length > 120 ? msg.entry.content.slice(0, 120) + "..." : msg.entry.content;
901
893
  log(` from @${msg.entry.from}: ${preview}`);
@@ -907,7 +899,7 @@ function createAgentController(config) {
907
899
  while (attempt < retryConfig.maxAttempts && shouldContinue(state)) {
908
900
  attempt++;
909
901
  state = "running";
910
- log(`Running (attempt ${attempt}/${retryConfig.maxAttempts})`);
902
+ infoLog(`Running (attempt ${attempt}/${retryConfig.maxAttempts})`);
911
903
  lastResult = await runAgent(backend, {
912
904
  name,
913
905
  agent,
@@ -922,9 +914,9 @@ function createAgentController(config) {
922
914
  projectDir,
923
915
  retryAttempt: attempt,
924
916
  feedback
925
- }, log);
917
+ }, log, infoLog);
926
918
  if (lastResult.success) {
927
- log(`DONE ${lastResult.steps ? `${lastResult.steps} steps, ${lastResult.toolCalls} tool calls, ${lastResult.duration}ms` : `${lastResult.duration}ms`}`);
919
+ infoLog(`DONE ${lastResult.steps ? `${lastResult.steps} steps, ${lastResult.toolCalls} tool calls, ${lastResult.duration}ms` : `${lastResult.duration}ms`}`);
928
920
  if (lastResult.content) await contextProvider.appendChannel(name, lastResult.content);
929
921
  await contextProvider.ackInbox(name, latestId);
930
922
  break;
@@ -954,7 +946,7 @@ function createAgentController(config) {
954
946
  async start() {
955
947
  if (state !== "stopped") throw new Error(`Controller ${name} is already running`);
956
948
  state = "idle";
957
- log(`Starting`);
949
+ infoLog(`Starting`);
958
950
  runLoop().catch((error) => {
959
951
  errorLog(`ERROR ${error instanceof Error ? error.message : String(error)}`);
960
952
  state = "stopped";
@@ -963,6 +955,7 @@ function createAgentController(config) {
963
955
  async stop() {
964
956
  log(`Stopping`);
965
957
  state = "stopped";
958
+ if (backend.abort) backend.abort();
966
959
  if (pollTimeout) {
967
960
  clearTimeout(pollTimeout);
968
961
  pollTimeout = null;
@@ -994,7 +987,8 @@ function createAgentController(config) {
994
987
  * SDK and mock backends get special runners with MCP tool bridge + bash,
995
988
  * because they can't manage tools on their own (unlike CLI backends).
996
989
  */
997
- async function runAgent(backend, ctx, log) {
990
+ async function runAgent(backend, ctx, log, infoLog) {
991
+ const info = infoLog ?? log;
998
992
  if (backend.type === "mock") return runMockAgent(ctx, (msg) => log(msg));
999
993
  if (backend.type === "sdk") return runSdkAgent(ctx, (msg) => log(msg));
1000
994
  const startTime = Date.now();
@@ -1004,7 +998,7 @@ async function runAgent(backend, ctx, log) {
1004
998
  backend.setWorkspace(ctx.workspaceDir, mcpConfig);
1005
999
  }
1006
1000
  const prompt = buildAgentPrompt(ctx);
1007
- log(`Prompt (${prompt.length} chars) → ${backend.type} backend`);
1001
+ info(`Prompt (${prompt.length} chars) → ${backend.type} backend`);
1008
1002
  await backend.send(prompt, { system: ctx.agent.resolvedSystemPrompt });
1009
1003
  return {
1010
1004
  success: true,
@@ -1044,9 +1038,13 @@ async function checkWorkflowIdle(controllers, provider, debounceMs = CONTROLLER_
1044
1038
  */
1045
1039
  function getBackendByType(backendType, options) {
1046
1040
  if (backendType === "mock") return createMockBackend(options?.debugLog);
1041
+ const backendOptions = {};
1042
+ if (options?.timeout) backendOptions.timeout = options.timeout;
1043
+ if (options?.debugLog) backendOptions.debugLog = options.debugLog;
1047
1044
  return createBackend({
1048
1045
  type: backendType,
1049
- model: options?.model
1046
+ model: options?.model,
1047
+ ...Object.keys(backendOptions).length > 0 ? { options: backendOptions } : {}
1050
1048
  });
1051
1049
  }
1052
1050
  /**
@@ -1362,68 +1360,6 @@ function startChannelWatcher(config) {
1362
1360
  } };
1363
1361
  }
1364
1362
 
1365
- //#endregion
1366
- //#region src/workflow/logger.ts
1367
- /**
1368
- * Create a silent logger (no output)
1369
- */
1370
- function createSilentLogger() {
1371
- const noop = () => {};
1372
- return {
1373
- debug: noop,
1374
- info: noop,
1375
- warn: noop,
1376
- error: noop,
1377
- isDebug: () => false,
1378
- child: () => createSilentLogger()
1379
- };
1380
- }
1381
- /**
1382
- * Create a logger that writes to the channel.
1383
- *
1384
- * - info/warn/error → channel entry with kind="log" (always shown to user)
1385
- * - debug → channel entry with kind="debug" (only shown with --debug)
1386
- *
1387
- * The display layer handles formatting and filtering.
1388
- */
1389
- function createChannelLogger(config) {
1390
- const { provider, from = "system" } = config;
1391
- const formatContent = (level, message, args) => {
1392
- const argsStr = args.length > 0 ? " " + args.map(formatArg).join(" ") : "";
1393
- if (level === "warn") return `[WARN] ${message}${argsStr}`;
1394
- if (level === "error") return `[ERROR] ${message}${argsStr}`;
1395
- return `${message}${argsStr}`;
1396
- };
1397
- const write = (level, message, args) => {
1398
- const content = formatContent(level, message, args);
1399
- const kind = level === "debug" ? "debug" : "log";
1400
- provider.appendChannel(from, content, { kind }).catch(() => {});
1401
- };
1402
- return {
1403
- debug: (message, ...args) => write("debug", message, args),
1404
- info: (message, ...args) => write("info", message, args),
1405
- warn: (message, ...args) => write("warn", message, args),
1406
- error: (message, ...args) => write("error", message, args),
1407
- isDebug: () => true,
1408
- child: (childPrefix) => {
1409
- return createChannelLogger({
1410
- provider,
1411
- from: from ? `${from}:${childPrefix}` : childPrefix
1412
- });
1413
- }
1414
- };
1415
- }
1416
- /** Format an argument for logging */
1417
- function formatArg(arg) {
1418
- if (arg === null || arg === void 0) return String(arg);
1419
- if (typeof arg === "object") try {
1420
- return JSON.stringify(arg);
1421
- } catch {
1422
- return String(arg);
1423
- }
1424
- return String(arg);
1425
- }
1426
-
1427
1363
  //#endregion
1428
1364
  //#region src/workflow/runner.ts
1429
1365
  /**
@@ -1554,7 +1490,7 @@ async function initWorkflow(config) {
1554
1490
  return;
1555
1491
  }
1556
1492
  logger.debug(`Kickoff: ${interpolatedKickoff.slice(0, 100)}...`);
1557
- await contextProvider.appendChannel("system", interpolatedKickoff);
1493
+ await contextProvider.smartSend("system", interpolatedKickoff);
1558
1494
  },
1559
1495
  async shutdown() {
1560
1496
  logger.debug("Shutting down...");
@@ -1658,7 +1594,8 @@ async function runWorkflowWithControllers(config) {
1658
1594
  if (createBackend) backend = createBackend(agentName, agentDef);
1659
1595
  else if (agentDef.backend) backend = getBackendByType(agentDef.backend, {
1660
1596
  model: agentDef.model,
1661
- debugLog: backendDebugLog
1597
+ debugLog: backendDebugLog,
1598
+ timeout: agentDef.timeout
1662
1599
  });
1663
1600
  else if (agentDef.model) backend = getBackendForModel(agentDef.model, { debugLog: backendDebugLog });
1664
1601
  else throw new Error(`Agent "${agentName}" requires either a backend or model field`);
@@ -1676,6 +1613,7 @@ async function runWorkflowWithControllers(config) {
1676
1613
  backend,
1677
1614
  pollInterval,
1678
1615
  log: (msg) => controllerLogger.debug(msg),
1616
+ infoLog: (msg) => controllerLogger.info(msg),
1679
1617
  errorLog: (msg) => controllerLogger.error(msg),
1680
1618
  feedback: feedbackEnabled
1681
1619
  });
@@ -1781,4 +1719,4 @@ function sleep(ms) {
1781
1719
  }
1782
1720
 
1783
1721
  //#endregion
1784
- export { parseWorkflowFile, runWorkflowWithControllers };
1722
+ export { parseWorkflowFile, runWorkflowWithControllers, shutdownControllers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-worker",
3
- "version": "0.4.0",
3
+ "version": "0.6.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 };
@@ -1,4 +0,0 @@
1
- import { a as FileStorage, c as CONTEXT_DEFAULTS, d as RESOURCE_SCHEME, f as calculatePriority, h as generateResourceId, i as resolveContextDir, l as MENTION_PATTERN, m as extractMentions, n as createFileContextProvider, o as MemoryStorage, p as createResourceRef, r as getDefaultContextDir, s as ContextProviderImpl, t as FileContextProvider, u as RESOURCE_PREFIX } from "./cli/index.mjs";
2
- import { a as createMemoryContextProvider, i as MemoryContextProvider, n as formatProposal, r as formatProposalList, t as createContextMCPServer } from "./mcp-server-DtIApaBD.mjs";
3
-
4
- export { createFileContextProvider };