opencode-orchestrator 0.5.4 → 0.5.6

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.
Files changed (101) hide show
  1. package/README.md +21 -23
  2. package/dist/agents/definitions.d.ts +1 -1
  3. package/dist/agents/orchestrator.d.ts +1 -1
  4. package/dist/agents/subagents/architect.d.ts +1 -1
  5. package/dist/agents/subagents/builder.d.ts +1 -1
  6. package/dist/agents/subagents/inspector.d.ts +1 -1
  7. package/dist/agents/subagents/recorder.d.ts +1 -1
  8. package/dist/core/agents/concurrency.d.ts +36 -0
  9. package/dist/core/agents/config.d.ts +9 -0
  10. package/dist/core/agents/format.d.ts +9 -0
  11. package/dist/core/agents/index.d.ts +7 -0
  12. package/dist/core/agents/interfaces/index.d.ts +5 -0
  13. package/dist/core/agents/interfaces/launch-input.d.ts +9 -0
  14. package/dist/core/agents/interfaces/parallel-task.d.ts +26 -0
  15. package/dist/core/agents/logger.d.ts +4 -0
  16. package/dist/core/agents/manager.d.ts +76 -0
  17. package/dist/core/agents/task-store.d.ts +28 -0
  18. package/dist/core/agents/types/index.d.ts +4 -0
  19. package/dist/core/agents/types/parallel-task-status.d.ts +4 -0
  20. package/dist/core/commands/index.d.ts +6 -0
  21. package/dist/core/commands/interfaces/background-task.d.ts +20 -0
  22. package/dist/core/commands/interfaces/index.d.ts +5 -0
  23. package/dist/core/commands/interfaces/run-background-options.d.ts +9 -0
  24. package/dist/core/commands/manager.d.ts +27 -0
  25. package/dist/core/commands/types/background-task-status.d.ts +4 -0
  26. package/dist/core/commands/types/index.d.ts +4 -0
  27. package/dist/core/orchestrator/index.d.ts +7 -0
  28. package/dist/core/orchestrator/interfaces/index.d.ts +5 -0
  29. package/dist/core/{state.d.ts → orchestrator/interfaces/session-state.d.ts} +4 -7
  30. package/dist/core/orchestrator/interfaces/task.d.ts +17 -0
  31. package/dist/core/orchestrator/state.d.ts +10 -0
  32. package/dist/core/{tasks.d.ts → orchestrator/task-graph.d.ts} +2 -14
  33. package/dist/core/orchestrator/types/index.d.ts +5 -0
  34. package/dist/core/orchestrator/types/task-status.d.ts +4 -0
  35. package/dist/core/orchestrator/types/task-type.d.ts +4 -0
  36. package/dist/index.d.ts +0 -2
  37. package/dist/index.js +588 -714
  38. package/dist/shared/{contracts/names.d.ts → agent.d.ts} +12 -0
  39. package/dist/shared/constants.d.ts +56 -0
  40. package/dist/tools/background-cmd/check.d.ts +14 -0
  41. package/dist/tools/background-cmd/index.d.ts +7 -0
  42. package/dist/tools/background-cmd/kill.d.ts +12 -0
  43. package/dist/tools/background-cmd/list.d.ts +17 -0
  44. package/dist/tools/background-cmd/run.d.ts +18 -0
  45. package/dist/tools/callAgent.d.ts +0 -0
  46. package/dist/tools/parallel/cancel-task.d.ts +13 -0
  47. package/dist/tools/parallel/delegate-task.d.ts +21 -0
  48. package/dist/tools/parallel/get-task-result.d.ts +13 -0
  49. package/dist/tools/parallel/index.d.ts +7 -0
  50. package/dist/tools/parallel/list-tasks.d.ts +13 -0
  51. package/dist/tools/rust.d.ts +0 -0
  52. package/dist/tools/search.d.ts +2 -9
  53. package/dist/tools/slashCommand.d.ts +0 -0
  54. package/dist/utils/binary.d.ts +0 -0
  55. package/dist/utils/common.d.ts +0 -0
  56. package/package.json +17 -7
  57. package/dist/agents/coder.d.ts +0 -2
  58. package/dist/agents/fixer.d.ts +0 -2
  59. package/dist/agents/names.d.ts +0 -12
  60. package/dist/agents/planner.d.ts +0 -2
  61. package/dist/agents/reviewer.d.ts +0 -2
  62. package/dist/agents/searcher.d.ts +0 -2
  63. package/dist/agents/subagents/coder.d.ts +0 -2
  64. package/dist/agents/subagents/executor.d.ts +0 -2
  65. package/dist/agents/subagents/fixer.d.ts +0 -2
  66. package/dist/agents/subagents/frontend-designer.d.ts +0 -2
  67. package/dist/agents/subagents/memory.d.ts +0 -2
  68. package/dist/agents/subagents/planner.d.ts +0 -2
  69. package/dist/agents/subagents/publisher.d.ts +0 -2
  70. package/dist/agents/subagents/reviewer.d.ts +0 -2
  71. package/dist/agents/subagents/searcher.d.ts +0 -2
  72. package/dist/agents/subagents/strategist.d.ts +0 -2
  73. package/dist/agents/subagents/surgeon.d.ts +0 -2
  74. package/dist/agents/subagents/types.d.ts +0 -7
  75. package/dist/agents/subagents/visualist.d.ts +0 -2
  76. package/dist/agents/types.d.ts +0 -7
  77. package/dist/cli.d.ts +0 -2
  78. package/dist/constants/agent.d.ts +0 -8
  79. package/dist/constants/index.d.ts +0 -7
  80. package/dist/constants/prompts.d.ts +0 -10
  81. package/dist/constants/task.d.ts +0 -12
  82. package/dist/constants/time.d.ts +0 -34
  83. package/dist/context/enforcer.d.ts +0 -47
  84. package/dist/core/async-agent.d.ts +0 -100
  85. package/dist/core/background.d.ts +0 -78
  86. package/dist/core/batch-processor.d.ts +0 -62
  87. package/dist/core/config.d.ts +0 -55
  88. package/dist/core/session-manager.d.ts +0 -39
  89. package/dist/parallel/optimizer.d.ts +0 -47
  90. package/dist/profiler/execution.d.ts +0 -40
  91. package/dist/prompts/shared.d.ts +0 -2
  92. package/dist/shared/contracts/interfaces.d.ts +0 -7
  93. package/dist/tasks.d.ts +0 -29
  94. package/dist/tools/async-agent.d.ts +0 -70
  95. package/dist/tools/background.d.ts +0 -55
  96. package/dist/tools/batch.d.ts +0 -53
  97. package/dist/tools/config.d.ts +0 -60
  98. package/dist/tools/git.d.ts +0 -48
  99. package/dist/utils/formatting.d.ts +0 -13
  100. package/dist/utils/index.d.ts +0 -8
  101. package/dist/utils/task.d.ts +0 -8
package/dist/index.js CHANGED
@@ -4,19 +4,13 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // src/shared/contracts/names.ts
7
+ // src/shared/agent.ts
8
8
  var AGENT_NAMES = {
9
- // Core Agents (5)
10
9
  COMMANDER: "commander",
11
- // Orchestrator - ReAct loop controller
12
10
  ARCHITECT: "architect",
13
- // Planner + Strategist - Plan-and-Execute
14
11
  BUILDER: "builder",
15
- // Coder + Visualist combined (full-stack)
16
12
  INSPECTOR: "inspector",
17
- // Reviewer + Fixer combined (quality + fix)
18
13
  RECORDER: "recorder"
19
- // Persistent context - saves/loads session state
20
14
  };
21
15
 
22
16
  // src/agents/orchestrator.ts
@@ -33,13 +27,6 @@ You are Commander. Complete missions autonomously. Never stop until done.
33
27
  3. Never stop because agent returned nothing
34
28
  4. Always survey environment & codebase BEFORE coding
35
29
  5. Always verify with evidence based on runtime context
36
- 6. LANGUAGE:
37
- - THINK and REASON in English for maximum stability
38
- - FINAL REPORT: Detect the user's language from their request and respond in the SAME language
39
- - If user writes in Korean \u2192 Report in Korean
40
- - If user writes in English \u2192 Report in English
41
- - If user writes in Japanese \u2192 Report in Japanese
42
- - Default to English if language is unclear
43
30
  </core_rules>
44
31
 
45
32
  <phase_0 name="TRIAGE">
@@ -229,7 +216,7 @@ You are Architect. Break complex tasks into atomic pieces.
229
216
  </role>
230
217
 
231
218
  <constraints>
232
- Reasoning MUST be in English for model stability.
219
+
233
220
  If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
234
221
  </constraints>
235
222
 
@@ -291,7 +278,7 @@ You are Builder. Write code that works.
291
278
  </role>
292
279
 
293
280
  <constraints>
294
- Reasoning MUST be in English for model stability.
281
+
295
282
  If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
296
283
  </constraints>
297
284
 
@@ -357,7 +344,7 @@ You are Inspector. Prove failure or success with evidence.
357
344
  </role>
358
345
 
359
346
  <constraints>
360
- Reasoning MUST be in English for model stability.
347
+
361
348
  If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
362
349
  </constraints>
363
350
 
@@ -424,7 +411,7 @@ You are Recorder. Save and load work progress.
424
411
  </role>
425
412
 
426
413
  <constraints>
427
- Reasoning MUST be in English for model stability.
414
+
428
415
  If your reasoning collapses into gibberish, stop and output "ERROR: REASONING_COLLAPSE".
429
416
  </constraints>
430
417
 
@@ -491,7 +478,7 @@ var AGENTS = {
491
478
  [AGENT_NAMES.RECORDER]: recorder
492
479
  };
493
480
 
494
- // src/core/tasks.ts
481
+ // src/core/orchestrator/task-graph.ts
495
482
  var TaskGraph = class _TaskGraph {
496
483
  tasks = /* @__PURE__ */ new Map();
497
484
  constructor(tasks) {
@@ -532,7 +519,7 @@ var TaskGraph = class _TaskGraph {
532
519
  const notCompleted = tasks.filter((t) => t.status !== "completed");
533
520
  let summary = "\u{1F4CB} **Mission Status**\n";
534
521
  if (completed.length > 0) {
535
- summary += `\u2705 Completed: ${completed.length} tasks (Hidden to save tokens)
522
+ summary += `\u2705 Completed: ${completed.length} tasks
536
523
  `;
537
524
  }
538
525
  for (const task of notCompleted) {
@@ -549,18 +536,16 @@ var TaskGraph = class _TaskGraph {
549
536
  try {
550
537
  const tasks = JSON.parse(json2);
551
538
  return new _TaskGraph(tasks);
552
- } catch (e) {
553
- console.error("Failed to parse TaskGraph JSON:", e);
539
+ } catch {
554
540
  return new _TaskGraph();
555
541
  }
556
542
  }
557
543
  };
558
544
 
559
- // src/core/state.ts
545
+ // src/core/orchestrator/state.ts
560
546
  var state = {
561
547
  missionActive: false,
562
548
  maxIterations: 1e3,
563
- // Effectively infinite - "Relentless" mode
564
549
  maxRetries: 3,
565
550
  sessions: /* @__PURE__ */ new Map()
566
551
  };
@@ -13068,10 +13053,6 @@ var COMMANDS = {
13068
13053
  You are Commander. Complete this mission. Never stop until 100% done.
13069
13054
  </role>
13070
13055
 
13071
- <constraints>
13072
- Reasoning MUST be in English for model stability. Final report in Korean.
13073
- </constraints>
13074
-
13075
13056
  <phase_1 name="MANDATORY_ENVIRONMENT_SCAN">
13076
13057
  Before any planning or coding, you MUST understand:
13077
13058
  1. INFRA: OS-native? Container? Docker-compose? Volume-mounted?
@@ -13289,44 +13270,71 @@ var globSearchTool = (directory) => tool({
13289
13270
  }
13290
13271
  });
13291
13272
  var mgrepTool = (directory) => tool({
13292
- description: `Search multiple patterns in parallel (high-performance).
13293
-
13294
- <purpose>
13295
- Search for multiple regex patterns simultaneously using Rust's parallel execution.
13296
- Much faster than running grep multiple times sequentially.
13297
- </purpose>
13298
-
13299
- <examples>
13300
- - patterns: ["useState", "useEffect", "useContext"] \u2192 Find all React hooks usage
13301
- - patterns: ["TODO", "FIXME", "HACK"] \u2192 Find all code annotations
13302
- - patterns: ["import.*lodash", "require.*lodash"] \u2192 Find all lodash imports
13303
- </examples>
13304
-
13305
- <output>
13306
- Returns matches grouped by pattern, with file paths and line numbers.
13307
- </output>`,
13273
+ description: `Search multiple patterns (runs grep for each pattern).`,
13308
13274
  args: {
13309
- patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns to search for"),
13310
- dir: tool.schema.string().optional().describe("Directory to search (defaults to project root)"),
13311
- max_results_per_pattern: tool.schema.number().optional().describe("Max results per pattern (default: 50)")
13275
+ patterns: tool.schema.array(tool.schema.string()).describe("Array of regex patterns"),
13276
+ dir: tool.schema.string().optional().describe("Directory (defaults to project root)")
13312
13277
  },
13313
13278
  async execute(args) {
13314
- return callRustTool("mgrep", {
13315
- patterns: args.patterns,
13316
- directory: args.dir || directory,
13317
- max_results_per_pattern: args.max_results_per_pattern || 50
13318
- });
13279
+ const results = {};
13280
+ const dir = args.dir || directory;
13281
+ for (const pattern of args.patterns) {
13282
+ const result = await callRustTool("grep_search", { pattern, directory: dir });
13283
+ results[pattern] = result;
13284
+ }
13285
+ return JSON.stringify(results, null, 2);
13319
13286
  }
13320
13287
  });
13321
13288
 
13322
- // src/core/background.ts
13289
+ // src/core/commands/manager.ts
13323
13290
  import { spawn as spawn2 } from "child_process";
13324
13291
  import { randomBytes } from "crypto";
13292
+
13293
+ // src/shared/constants.ts
13294
+ var TIME = {
13295
+ SECOND: 1e3,
13296
+ MINUTE: 60 * 1e3,
13297
+ HOUR: 60 * 60 * 1e3
13298
+ };
13299
+ var ID_PREFIX = {
13300
+ /** Parallel agent task ID (e.g., task_a1b2c3d4) */
13301
+ TASK: "task_",
13302
+ /** Background command job ID (e.g., job_a1b2c3d4) */
13303
+ JOB: "job_",
13304
+ /** Session ID prefix */
13305
+ SESSION: "session_"
13306
+ };
13307
+ var PARALLEL_TASK = {
13308
+ TTL_MS: 30 * TIME.MINUTE,
13309
+ CLEANUP_DELAY_MS: 5 * TIME.MINUTE,
13310
+ MIN_STABILITY_MS: 5 * TIME.SECOND,
13311
+ POLL_INTERVAL_MS: 2e3,
13312
+ DEFAULT_CONCURRENCY: 3,
13313
+ MAX_CONCURRENCY: 10
13314
+ };
13315
+ var BACKGROUND_TASK = {
13316
+ DEFAULT_TIMEOUT_MS: 5 * TIME.MINUTE,
13317
+ MAX_OUTPUT_LENGTH: 1e4
13318
+ };
13319
+ var STATUS_EMOJI = {
13320
+ pending: "\u23F3",
13321
+ running: "\u{1F504}",
13322
+ completed: "\u2705",
13323
+ done: "\u2705",
13324
+ error: "\u274C",
13325
+ timeout: "\u23F0",
13326
+ cancelled: "\u{1F6AB}"
13327
+ };
13328
+ function getStatusEmoji(status) {
13329
+ return STATUS_EMOJI[status] ?? "\u2753";
13330
+ }
13331
+
13332
+ // src/core/commands/manager.ts
13325
13333
  var BackgroundTaskManager = class _BackgroundTaskManager {
13326
13334
  static _instance;
13327
13335
  tasks = /* @__PURE__ */ new Map();
13328
- debugMode = true;
13329
- // Enable debug mode
13336
+ debugMode = process.env.DEBUG_BG_TASK === "true";
13337
+ // Disabled by default
13330
13338
  constructor() {
13331
13339
  }
13332
13340
  static get instance() {
@@ -13335,25 +13343,15 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
13335
13343
  }
13336
13344
  return _BackgroundTaskManager._instance;
13337
13345
  }
13338
- /**
13339
- * Generate a unique task ID in the format job_xxxxxxxx
13340
- */
13341
13346
  generateId() {
13342
- const hex3 = randomBytes(4).toString("hex");
13343
- return `job_${hex3}`;
13347
+ return `${ID_PREFIX.JOB}${randomBytes(4).toString("hex")}`;
13344
13348
  }
13345
- /**
13346
- * Debug logging helper
13347
- */
13348
13349
  debug(taskId, message) {
13349
13350
  if (this.debugMode) {
13350
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
13351
- console.log(`[BG-DEBUG ${timestamp}] ${taskId}: ${message}`);
13351
+ const ts = (/* @__PURE__ */ new Date()).toISOString().substring(11, 23);
13352
+ console.log(`[BG ${ts}] ${taskId}: ${message}`);
13352
13353
  }
13353
13354
  }
13354
- /**
13355
- * Run a command in the background
13356
- */
13357
13355
  run(options) {
13358
13356
  const id = this.generateId();
13359
13357
  const { command, cwd = process.cwd(), timeout = 3e5, label } = options;
@@ -13374,7 +13372,7 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
13374
13372
  timeout
13375
13373
  };
13376
13374
  this.tasks.set(id, task);
13377
- this.debug(id, `Starting: ${command} (cwd: ${cwd})`);
13375
+ this.debug(id, `Starting: ${command}`);
13378
13376
  try {
13379
13377
  const proc = spawn2(shell, task.args, {
13380
13378
  cwd,
@@ -13383,22 +13381,17 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
13383
13381
  });
13384
13382
  task.process = proc;
13385
13383
  proc.stdout?.on("data", (data) => {
13386
- const text = data.toString();
13387
- task.output += text;
13388
- this.debug(id, `stdout: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
13384
+ task.output += data.toString();
13389
13385
  });
13390
13386
  proc.stderr?.on("data", (data) => {
13391
- const text = data.toString();
13392
- task.errorOutput += text;
13393
- this.debug(id, `stderr: ${text.substring(0, 100)}${text.length > 100 ? "..." : ""}`);
13387
+ task.errorOutput += data.toString();
13394
13388
  });
13395
13389
  proc.on("close", (code) => {
13396
13390
  task.exitCode = code;
13397
13391
  task.endTime = Date.now();
13398
13392
  task.status = code === 0 ? "done" : "error";
13399
13393
  task.process = void 0;
13400
- const duration3 = ((task.endTime - task.startTime) / 1e3).toFixed(2);
13401
- this.debug(id, `Completed with code ${code} in ${duration3}s`);
13394
+ this.debug(id, `Done (code=${code})`);
13402
13395
  });
13403
13396
  proc.on("error", (err) => {
13404
13397
  task.status = "error";
@@ -13406,47 +13399,31 @@ var BackgroundTaskManager = class _BackgroundTaskManager {
13406
13399
  Process error: ${err.message}`;
13407
13400
  task.endTime = Date.now();
13408
13401
  task.process = void 0;
13409
- this.debug(id, `Error: ${err.message}`);
13410
13402
  });
13411
13403
  setTimeout(() => {
13412
13404
  if (task.status === "running" && task.process) {
13413
- this.debug(id, `Timeout after ${timeout}ms, killing process`);
13414
13405
  task.process.kill("SIGKILL");
13415
13406
  task.status = "timeout";
13416
13407
  task.endTime = Date.now();
13417
- task.errorOutput += `
13418
- Process killed: timeout after ${timeout}ms`;
13408
+ this.debug(id, "Timeout");
13419
13409
  }
13420
13410
  }, timeout);
13421
13411
  } catch (err) {
13422
13412
  task.status = "error";
13423
- task.errorOutput = `Failed to spawn: ${err instanceof Error ? err.message : String(err)}`;
13413
+ task.errorOutput = `Spawn failed: ${err instanceof Error ? err.message : String(err)}`;
13424
13414
  task.endTime = Date.now();
13425
- this.debug(id, `Spawn failed: ${task.errorOutput}`);
13426
13415
  }
13427
13416
  return task;
13428
13417
  }
13429
- /**
13430
- * Get task by ID
13431
- */
13432
13418
  get(taskId) {
13433
13419
  return this.tasks.get(taskId);
13434
13420
  }
13435
- /**
13436
- * Get all tasks
13437
- */
13438
13421
  getAll() {
13439
13422
  return Array.from(this.tasks.values());
13440
13423
  }
13441
- /**
13442
- * Get tasks by status
13443
- */
13444
13424
  getByStatus(status) {
13445
13425
  return this.getAll().filter((t) => t.status === status);
13446
13426
  }
13447
- /**
13448
- * Clear completed/failed tasks
13449
- */
13450
13427
  clearCompleted() {
13451
13428
  let count = 0;
13452
13429
  for (const [id, task] of this.tasks) {
@@ -13457,9 +13434,6 @@ Process killed: timeout after ${timeout}ms`;
13457
13434
  }
13458
13435
  return count;
13459
13436
  }
13460
- /**
13461
- * Kill a running task
13462
- */
13463
13437
  kill(taskId) {
13464
13438
  const task = this.tasks.get(taskId);
13465
13439
  if (task?.process) {
@@ -13467,72 +13441,35 @@ Process killed: timeout after ${timeout}ms`;
13467
13441
  task.status = "error";
13468
13442
  task.errorOutput += "\nKilled by user";
13469
13443
  task.endTime = Date.now();
13470
- this.debug(taskId, "Killed by user");
13471
13444
  return true;
13472
13445
  }
13473
13446
  return false;
13474
13447
  }
13475
- /**
13476
- * Format duration for display
13477
- */
13478
13448
  formatDuration(task) {
13479
13449
  const end = task.endTime || Date.now();
13480
13450
  const seconds = (end - task.startTime) / 1e3;
13481
- if (seconds < 60) {
13482
- return `${seconds.toFixed(1)}s`;
13483
- }
13484
- const minutes = Math.floor(seconds / 60);
13485
- const remainingSeconds = seconds % 60;
13486
- return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
13451
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
13452
+ return `${Math.floor(seconds / 60)}m ${(seconds % 60).toFixed(0)}s`;
13487
13453
  }
13488
- /**
13489
- * Get status emoji
13490
- */
13491
13454
  getStatusEmoji(status) {
13492
- switch (status) {
13493
- case "pending":
13494
- return "\u23F8\uFE0F";
13495
- case "running":
13496
- return "\u23F3";
13497
- case "done":
13498
- return "\u2705";
13499
- case "error":
13500
- return "\u274C";
13501
- case "timeout":
13502
- return "\u23F0";
13503
- default:
13504
- return "\u2753";
13505
- }
13455
+ return getStatusEmoji(status);
13506
13456
  }
13507
13457
  };
13508
13458
  var backgroundTaskManager = BackgroundTaskManager.instance;
13509
13459
 
13510
- // src/tools/background.ts
13460
+ // src/tools/background-cmd/run.ts
13511
13461
  var runBackgroundTool = tool({
13512
13462
  description: `Run a shell command in the background and get a task ID.
13513
13463
 
13514
13464
  <purpose>
13515
- Execute long-running commands (builds, tests, etc.) without blocking.
13516
- The command runs asynchronously - use check_background to get results.
13517
- </purpose>
13518
-
13519
- <examples>
13520
- - "npm run build" \u2192 Build project in background
13521
- - "cargo test" \u2192 Run Rust tests
13522
- - "sleep 10 && echo done" \u2192 Delayed execution
13523
- </examples>
13524
-
13525
- <flow>
13526
- 1. Call run_background with command
13527
- 2. Get task ID immediately (e.g., job_a1b2c3d4)
13528
- 3. Continue other work
13529
- 4. Call check_background with task ID to get results
13530
- </flow>`,
13465
+ Execute long-running commands (builds, tests) without blocking.
13466
+ Use check_background to get results.
13467
+ </purpose>`,
13531
13468
  args: {
13532
13469
  command: tool.schema.string().describe("Shell command to execute"),
13533
- cwd: tool.schema.string().optional().describe("Working directory (default: project root)"),
13534
- timeout: tool.schema.number().optional().describe("Timeout in milliseconds (default: 300000 = 5 min)"),
13535
- label: tool.schema.string().optional().describe("Human-readable label for this task")
13470
+ cwd: tool.schema.string().optional().describe("Working directory"),
13471
+ timeout: tool.schema.number().optional().describe("Timeout in ms (default: 300000)"),
13472
+ label: tool.schema.string().optional().describe("Task label")
13536
13473
  },
13537
13474
  async execute(args) {
13538
13475
  const { command, cwd, timeout, label } = args;
@@ -13544,36 +13481,20 @@ The command runs asynchronously - use check_background to get results.
13544
13481
  });
13545
13482
  const displayLabel = label ? ` (${label})` : "";
13546
13483
  return `\u{1F680} **Background Task Started**${displayLabel}
13547
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13548
- | Property | Value |
13549
- |----------|-------|
13550
- | **Task ID** | \`${task.id}\` |
13551
- | **Command** | \`${command}\` |
13552
- | **Status** | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
13553
- | **Working Dir** | ${task.cwd} |
13554
- | **Timeout** | ${(task.timeout / 1e3).toFixed(0)}s |
13555
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13484
+ | Task ID | \`${task.id}\` |
13485
+ | Command | \`${command}\` |
13486
+ | Status | ${backgroundTaskManager.getStatusEmoji(task.status)} ${task.status} |
13556
13487
 
13557
- \u{1F4CC} **Next Step**: Use \`check_background\` with task ID \`${task.id}\` to get results.`;
13488
+ \u{1F4CC} Use \`check_background({ taskId: "${task.id}" })\` to get results.`;
13558
13489
  }
13559
13490
  });
13560
- var checkBackgroundTool = tool({
13561
- description: `Check the status and output of a background task.
13562
-
13563
- <purpose>
13564
- Retrieve the current status and output of a previously started background task.
13565
- Use this after run_background to get results.
13566
- </purpose>
13567
13491
 
13568
- <output_includes>
13569
- - Status: running/done/error/timeout
13570
- - Exit code (if completed)
13571
- - Duration
13572
- - Full output (stdout + stderr)
13573
- </output_includes>`,
13492
+ // src/tools/background-cmd/check.ts
13493
+ var checkBackgroundTool = tool({
13494
+ description: `Check the status and output of a background task.`,
13574
13495
  args: {
13575
- taskId: tool.schema.string().describe("Task ID from run_background (e.g., job_a1b2c3d4)"),
13576
- tailLines: tool.schema.number().optional().describe("Limit output to last N lines (default: show all)")
13496
+ taskId: tool.schema.string().describe("Task ID from run_background"),
13497
+ tailLines: tool.schema.number().optional().describe("Limit output to last N lines")
13577
13498
  },
13578
13499
  async execute(args) {
13579
13500
  const { taskId, tailLines } = args;
@@ -13586,7 +13507,7 @@ Use this after run_background to get results.
13586
13507
  const taskList = allTasks.map((t) => `- \`${t.id}\`: ${t.command.substring(0, 30)}...`).join("\n");
13587
13508
  return `\u274C Task \`${taskId}\` not found.
13588
13509
 
13589
- **Available tasks:**
13510
+ **Available:**
13590
13511
  ${taskList}`;
13591
13512
  }
13592
13513
  const duration3 = backgroundTaskManager.formatDuration(task);
@@ -13594,64 +13515,41 @@ ${taskList}`;
13594
13515
  let output = task.output;
13595
13516
  let stderr = task.errorOutput;
13596
13517
  if (tailLines && tailLines > 0) {
13597
- const outputLines = output.split("\n");
13598
- const stderrLines = stderr.split("\n");
13599
- output = outputLines.slice(-tailLines).join("\n");
13600
- stderr = stderrLines.slice(-tailLines).join("\n");
13518
+ output = output.split("\n").slice(-tailLines).join("\n");
13519
+ stderr = stderr.split("\n").slice(-tailLines).join("\n");
13601
13520
  }
13602
13521
  const maxLen = 1e4;
13603
- if (output.length > maxLen) {
13604
- output = `[...truncated ${output.length - maxLen} chars...]
13605
- ` + output.substring(output.length - maxLen);
13606
- }
13607
- if (stderr.length > maxLen) {
13608
- stderr = `[...truncated ${stderr.length - maxLen} chars...]
13609
- ` + stderr.substring(stderr.length - maxLen);
13610
- }
13611
- const labelDisplay = task.label ? ` (${task.label})` : "";
13612
- let result = `${statusEmoji} **Task ${task.id}**${labelDisplay}
13613
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13614
- | Property | Value |
13615
- |----------|-------|
13616
- | **Command** | \`${task.command}\` |
13617
- | **Status** | ${statusEmoji} **${task.status.toUpperCase()}** |
13618
- | **Duration** | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
13619
- ${task.exitCode !== null ? `| **Exit Code** | ${task.exitCode} |` : ""}
13620
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501`;
13621
- if (output.trim()) {
13622
- result += `
13623
-
13624
- \u{1F4E4} **Output (stdout)**:
13522
+ if (output.length > maxLen) output = `[...truncated...]\\n` + output.slice(-maxLen);
13523
+ if (stderr.length > maxLen) stderr = `[...truncated...]\\n` + stderr.slice(-maxLen);
13524
+ let result = `${statusEmoji} **Task ${task.id}**${task.label ? ` (${task.label})` : ""}
13525
+ | Command | \`${task.command}\` |
13526
+ | Status | ${statusEmoji} **${task.status.toUpperCase()}** |
13527
+ | Duration | ${duration3}${task.status === "running" ? " (ongoing)" : ""} |
13528
+ ${task.exitCode !== null ? `| Exit Code | ${task.exitCode} |` : ""}`;
13529
+ if (output.trim()) result += `
13530
+
13531
+ \u{1F4E4} **stdout:**
13625
13532
  \`\`\`
13626
13533
  ${output.trim()}
13627
13534
  \`\`\``;
13628
- }
13629
- if (stderr.trim()) {
13630
- result += `
13535
+ if (stderr.trim()) result += `
13631
13536
 
13632
- \u26A0\uFE0F **Errors (stderr)**:
13537
+ \u26A0\uFE0F **stderr:**
13633
13538
  \`\`\`
13634
13539
  ${stderr.trim()}
13635
13540
  \`\`\``;
13636
- }
13637
- if (task.status === "running") {
13638
- result += `
13541
+ if (task.status === "running") result += `
13639
13542
 
13640
- \u23F3 Task still running... Check again later with:
13641
- \`check_background({ taskId: "${task.id}" })\``;
13642
- }
13543
+ \u23F3 Still running... check again.`;
13643
13544
  return result;
13644
13545
  }
13645
13546
  });
13646
- var listBackgroundTool = tool({
13647
- description: `List all background tasks and their current status.
13648
13547
 
13649
- <purpose>
13650
- Get an overview of all running and completed background tasks.
13651
- Useful to check what's in progress before starting new tasks.
13652
- </purpose>`,
13548
+ // src/tools/background-cmd/list.ts
13549
+ var listBackgroundTool = tool({
13550
+ description: `List all background tasks and their status.`,
13653
13551
  args: {
13654
- status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status (default: all)")
13552
+ status: tool.schema.enum(["all", "running", "done", "error"]).optional().describe("Filter by status")
13655
13553
  },
13656
13554
  async execute(args) {
13657
13555
  const { status = "all" } = args;
@@ -13662,91 +13560,102 @@ Useful to check what's in progress before starting new tasks.
13662
13560
  tasks = backgroundTaskManager.getByStatus(status);
13663
13561
  }
13664
13562
  if (tasks.length === 0) {
13665
- return `\u{1F4CB} **No background tasks** ${status !== "all" ? `with status "${status}"` : ""}
13666
-
13667
- Use \`run_background\` to start a new background task.`;
13563
+ return `\u{1F4CB} No background tasks${status !== "all" ? ` with status "${status}"` : ""}`;
13668
13564
  }
13669
13565
  tasks.sort((a, b) => b.startTime - a.startTime);
13670
- const rows = tasks.map((task) => {
13671
- const emoji3 = backgroundTaskManager.getStatusEmoji(task.status);
13672
- const duration3 = backgroundTaskManager.formatDuration(task);
13673
- const cmdShort = task.command.length > 25 ? task.command.substring(0, 22) + "..." : task.command;
13674
- const labelPart = task.label ? ` [${task.label}]` : "";
13675
- return `| \`${task.id}\` | ${emoji3} ${task.status.padEnd(7)} | ${cmdShort.padEnd(25)}${labelPart} | ${duration3.padStart(8)} |`;
13566
+ const rows = tasks.map((t) => {
13567
+ const emoji3 = backgroundTaskManager.getStatusEmoji(t.status);
13568
+ const cmd = t.command.length > 25 ? t.command.slice(0, 22) + "..." : t.command;
13569
+ const label = t.label ? ` [${t.label}]` : "";
13570
+ return `| \`${t.id}\` | ${emoji3} ${t.status} | ${cmd}${label} | ${backgroundTaskManager.formatDuration(t)} |`;
13676
13571
  }).join("\n");
13677
- const runningCount = tasks.filter((t) => t.status === "running").length;
13678
- const doneCount = tasks.filter((t) => t.status === "done").length;
13679
- const errorCount = tasks.filter((t) => t.status === "error" || t.status === "timeout").length;
13680
- return `\u{1F4CB} **Background Tasks** (${tasks.length} total)
13681
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13682
- | \u23F3 Running: ${runningCount} | \u2705 Done: ${doneCount} | \u274C Error/Timeout: ${errorCount} |
13683
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
13684
-
13685
- | Task ID | Status | Command | Duration |
13686
- |---------|--------|---------|----------|
13687
- ${rows}
13572
+ const running = tasks.filter((t) => t.status === "running").length;
13573
+ const done = tasks.filter((t) => t.status === "done").length;
13574
+ const error45 = tasks.filter((t) => t.status === "error" || t.status === "timeout").length;
13575
+ return `\u{1F4CB} **Background Tasks** (${tasks.length})
13576
+ \u23F3 Running: ${running} | \u2705 Done: ${done} | \u274C Error: ${error45}
13688
13577
 
13689
- \u{1F4A1} Use \`check_background({ taskId: "job_xxxxx" })\` to see full output.`;
13578
+ | ID | Status | Command | Duration |
13579
+ |----|--------|---------|----------|
13580
+ ${rows}`;
13690
13581
  }
13691
13582
  });
13692
- var killBackgroundTool = tool({
13693
- description: `Kill a running background task.
13694
13583
 
13695
- <purpose>
13696
- Stop a background task that is taking too long or no longer needed.
13697
- </purpose>`,
13584
+ // src/tools/background-cmd/kill.ts
13585
+ var killBackgroundTool = tool({
13586
+ description: `Kill a running background task.`,
13698
13587
  args: {
13699
- taskId: tool.schema.string().describe("Task ID to kill (e.g., job_a1b2c3d4)")
13588
+ taskId: tool.schema.string().describe("Task ID to kill")
13700
13589
  },
13701
13590
  async execute(args) {
13702
13591
  const { taskId } = args;
13703
13592
  const task = backgroundTaskManager.get(taskId);
13704
- if (!task) {
13705
- return `\u274C Task \`${taskId}\` not found.`;
13706
- }
13707
- if (task.status !== "running") {
13708
- return `\u26A0\uFE0F Task \`${taskId}\` is not running (status: ${task.status}).`;
13709
- }
13593
+ if (!task) return `\u274C Task \`${taskId}\` not found.`;
13594
+ if (task.status !== "running") return `\u26A0\uFE0F Task \`${taskId}\` is not running (${task.status}).`;
13710
13595
  const killed = backgroundTaskManager.kill(taskId);
13711
13596
  if (killed) {
13712
- return `\u{1F6D1} Task \`${taskId}\` has been killed.
13597
+ return `\u{1F6D1} Task \`${taskId}\` killed.
13713
13598
  Command: \`${task.command}\`
13714
- Duration before kill: ${backgroundTaskManager.formatDuration(task)}`;
13599
+ Duration: ${backgroundTaskManager.formatDuration(task)}`;
13715
13600
  }
13716
- return `\u26A0\uFE0F Could not kill task \`${taskId}\`. It may have already finished.`;
13601
+ return `\u26A0\uFE0F Could not kill task \`${taskId}\`.`;
13717
13602
  }
13718
13603
  });
13719
13604
 
13720
- // src/core/async-agent.ts
13721
- var TASK_TTL_MS = 30 * 60 * 1e3;
13722
- var CLEANUP_DELAY_MS = 5 * 60 * 1e3;
13723
- var MIN_STABILITY_MS = 5 * 1e3;
13724
- var POLL_INTERVAL_MS = 2e3;
13725
- var DEFAULT_CONCURRENCY = 3;
13605
+ // src/core/agents/concurrency.ts
13726
13606
  var DEBUG = process.env.DEBUG_PARALLEL_AGENT === "true";
13727
13607
  var log = (...args) => {
13728
- if (DEBUG) console.log("[parallel-agent]", ...args);
13608
+ if (DEBUG) console.log("[concurrency]", ...args);
13729
13609
  };
13730
13610
  var ConcurrencyController = class {
13731
13611
  counts = /* @__PURE__ */ new Map();
13732
13612
  queues = /* @__PURE__ */ new Map();
13733
13613
  limits = /* @__PURE__ */ new Map();
13614
+ config;
13615
+ constructor(config2) {
13616
+ this.config = config2 ?? {};
13617
+ }
13734
13618
  setLimit(key, limit) {
13735
13619
  this.limits.set(key, limit);
13736
13620
  }
13621
+ /**
13622
+ * Get concurrency limit for a key.
13623
+ * Priority: explicit limit > model > provider > agent > default
13624
+ */
13625
+ getConcurrencyLimit(key) {
13626
+ const explicitLimit = this.limits.get(key);
13627
+ if (explicitLimit !== void 0) {
13628
+ return explicitLimit === 0 ? Infinity : explicitLimit;
13629
+ }
13630
+ if (this.config.modelConcurrency?.[key] !== void 0) {
13631
+ const limit = this.config.modelConcurrency[key];
13632
+ return limit === 0 ? Infinity : limit;
13633
+ }
13634
+ const provider = key.split("/")[0];
13635
+ if (this.config.providerConcurrency?.[provider] !== void 0) {
13636
+ const limit = this.config.providerConcurrency[provider];
13637
+ return limit === 0 ? Infinity : limit;
13638
+ }
13639
+ if (this.config.agentConcurrency?.[key] !== void 0) {
13640
+ const limit = this.config.agentConcurrency[key];
13641
+ return limit === 0 ? Infinity : limit;
13642
+ }
13643
+ return this.config.defaultConcurrency ?? PARALLEL_TASK.DEFAULT_CONCURRENCY;
13644
+ }
13645
+ // Backwards compatible alias
13737
13646
  getLimit(key) {
13738
- return this.limits.get(key) ?? DEFAULT_CONCURRENCY;
13647
+ return this.getConcurrencyLimit(key);
13739
13648
  }
13740
13649
  async acquire(key) {
13741
- const limit = this.getLimit(key);
13742
- if (limit === 0) return;
13650
+ const limit = this.getConcurrencyLimit(key);
13651
+ if (limit === Infinity) return;
13743
13652
  const current = this.counts.get(key) ?? 0;
13744
13653
  if (current < limit) {
13745
13654
  this.counts.set(key, current + 1);
13746
- log(`Acquired slot for ${key}: ${current + 1}/${limit}`);
13655
+ log(`Acquired ${key}: ${current + 1}/${limit}`);
13747
13656
  return;
13748
13657
  }
13749
- log(`Queueing for ${key}: ${current}/${limit} (waiting...)`);
13658
+ log(`Queueing ${key}: ${current}/${limit}`);
13750
13659
  return new Promise((resolve) => {
13751
13660
  const queue = this.queues.get(key) ?? [];
13752
13661
  queue.push(resolve);
@@ -13754,41 +13663,168 @@ var ConcurrencyController = class {
13754
13663
  });
13755
13664
  }
13756
13665
  release(key) {
13757
- const limit = this.getLimit(key);
13758
- if (limit === 0) return;
13666
+ const limit = this.getConcurrencyLimit(key);
13667
+ if (limit === Infinity) return;
13759
13668
  const queue = this.queues.get(key);
13760
13669
  if (queue && queue.length > 0) {
13761
13670
  const next = queue.shift();
13762
- log(`Released slot for ${key}: next in queue`);
13671
+ log(`Released ${key}: next in queue`);
13763
13672
  next();
13764
13673
  } else {
13765
13674
  const current = this.counts.get(key) ?? 0;
13766
13675
  if (current > 0) {
13767
13676
  this.counts.set(key, current - 1);
13768
- log(`Released slot for ${key}: ${current - 1}`);
13677
+ log(`Released ${key}: ${current - 1}/${limit}`);
13769
13678
  }
13770
13679
  }
13771
13680
  }
13772
13681
  getQueueLength(key) {
13773
13682
  return this.queues.get(key)?.length ?? 0;
13774
13683
  }
13684
+ getActiveCount(key) {
13685
+ return this.counts.get(key) ?? 0;
13686
+ }
13687
+ /**
13688
+ * Get formatted concurrency info string (e.g., "2/5 slots")
13689
+ */
13690
+ getConcurrencyInfo(key) {
13691
+ const active = this.getActiveCount(key);
13692
+ const limit = this.getConcurrencyLimit(key);
13693
+ if (limit === Infinity) return "";
13694
+ return ` (${active}/${limit} slots)`;
13695
+ }
13775
13696
  };
13776
- var ParallelAgentManager = class _ParallelAgentManager {
13777
- static _instance;
13778
- // Core state
13697
+
13698
+ // src/core/agents/task-store.ts
13699
+ var TaskStore = class {
13779
13700
  tasks = /* @__PURE__ */ new Map();
13780
13701
  pendingByParent = /* @__PURE__ */ new Map();
13781
13702
  notifications = /* @__PURE__ */ new Map();
13782
- // Dependencies
13703
+ set(id, task) {
13704
+ this.tasks.set(id, task);
13705
+ }
13706
+ get(id) {
13707
+ return this.tasks.get(id);
13708
+ }
13709
+ getAll() {
13710
+ return Array.from(this.tasks.values());
13711
+ }
13712
+ getRunning() {
13713
+ return this.getAll().filter((t) => t.status === "running");
13714
+ }
13715
+ getByParent(parentSessionID) {
13716
+ return this.getAll().filter((t) => t.parentSessionID === parentSessionID);
13717
+ }
13718
+ delete(id) {
13719
+ return this.tasks.delete(id);
13720
+ }
13721
+ clear() {
13722
+ this.tasks.clear();
13723
+ this.pendingByParent.clear();
13724
+ this.notifications.clear();
13725
+ }
13726
+ // Pending tracking
13727
+ trackPending(parentSessionID, taskId) {
13728
+ const pending = this.pendingByParent.get(parentSessionID) ?? /* @__PURE__ */ new Set();
13729
+ pending.add(taskId);
13730
+ this.pendingByParent.set(parentSessionID, pending);
13731
+ }
13732
+ untrackPending(parentSessionID, taskId) {
13733
+ const pending = this.pendingByParent.get(parentSessionID);
13734
+ if (pending) {
13735
+ pending.delete(taskId);
13736
+ if (pending.size === 0) {
13737
+ this.pendingByParent.delete(parentSessionID);
13738
+ }
13739
+ }
13740
+ }
13741
+ getPendingCount(parentSessionID) {
13742
+ return this.pendingByParent.get(parentSessionID)?.size ?? 0;
13743
+ }
13744
+ hasPending(parentSessionID) {
13745
+ return this.getPendingCount(parentSessionID) > 0;
13746
+ }
13747
+ // Notifications
13748
+ queueNotification(task) {
13749
+ const queue = this.notifications.get(task.parentSessionID) ?? [];
13750
+ queue.push(task);
13751
+ this.notifications.set(task.parentSessionID, queue);
13752
+ }
13753
+ getNotifications(parentSessionID) {
13754
+ return this.notifications.get(parentSessionID) ?? [];
13755
+ }
13756
+ clearNotifications(parentSessionID) {
13757
+ this.notifications.delete(parentSessionID);
13758
+ }
13759
+ cleanEmptyNotifications() {
13760
+ for (const [sessionID, queue] of this.notifications.entries()) {
13761
+ if (queue.length === 0) {
13762
+ this.notifications.delete(sessionID);
13763
+ }
13764
+ }
13765
+ }
13766
+ /**
13767
+ * Remove a specific task from all notification queues
13768
+ */
13769
+ clearNotificationsForTask(taskId) {
13770
+ for (const [sessionID, tasks] of this.notifications.entries()) {
13771
+ const filtered = tasks.filter((t) => t.id !== taskId);
13772
+ if (filtered.length === 0) {
13773
+ this.notifications.delete(sessionID);
13774
+ } else if (filtered.length !== tasks.length) {
13775
+ this.notifications.set(sessionID, filtered);
13776
+ }
13777
+ }
13778
+ }
13779
+ };
13780
+
13781
+ // src/core/agents/config.ts
13782
+ var CONFIG = {
13783
+ TASK_TTL_MS: PARALLEL_TASK.TTL_MS,
13784
+ CLEANUP_DELAY_MS: PARALLEL_TASK.CLEANUP_DELAY_MS,
13785
+ MIN_STABILITY_MS: PARALLEL_TASK.MIN_STABILITY_MS,
13786
+ POLL_INTERVAL_MS: PARALLEL_TASK.POLL_INTERVAL_MS
13787
+ };
13788
+
13789
+ // src/core/agents/logger.ts
13790
+ var DEBUG2 = process.env.DEBUG_PARALLEL_AGENT === "true";
13791
+ function log2(...args) {
13792
+ if (DEBUG2) console.log("[parallel-agent]", ...args);
13793
+ }
13794
+
13795
+ // src/core/agents/format.ts
13796
+ function formatDuration(start, end) {
13797
+ const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
13798
+ const seconds = Math.floor(duration3 / 1e3);
13799
+ const minutes = Math.floor(seconds / 60);
13800
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
13801
+ return `${seconds}s`;
13802
+ }
13803
+ function buildNotificationMessage(tasks) {
13804
+ const summary = tasks.map((t) => {
13805
+ const status = t.status === "completed" ? "\u2705" : "\u274C";
13806
+ return `${status} \`${t.id}\`: ${t.description}`;
13807
+ }).join("\n");
13808
+ return `<system-notification>
13809
+ **All Parallel Tasks Complete**
13810
+
13811
+ ${summary}
13812
+
13813
+ Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
13814
+ </system-notification>`;
13815
+ }
13816
+
13817
+ // src/core/agents/manager.ts
13818
+ var ParallelAgentManager = class _ParallelAgentManager {
13819
+ static _instance;
13820
+ store = new TaskStore();
13783
13821
  client;
13784
13822
  directory;
13785
- concurrency;
13786
- // Polling
13823
+ concurrency = new ConcurrencyController();
13787
13824
  pollingInterval;
13788
13825
  constructor(client, directory) {
13789
13826
  this.client = client;
13790
13827
  this.directory = directory;
13791
- this.concurrency = new ConcurrencyController();
13792
13828
  }
13793
13829
  static getInstance(client, directory) {
13794
13830
  if (!_ParallelAgentManager._instance) {
@@ -13802,29 +13838,21 @@ var ParallelAgentManager = class _ParallelAgentManager {
13802
13838
  // ========================================================================
13803
13839
  // Public API
13804
13840
  // ========================================================================
13805
- /**
13806
- * Launch an agent in a new session (async, non-blocking)
13807
- */
13808
13841
  async launch(input) {
13809
13842
  const concurrencyKey = input.agent;
13810
13843
  await this.concurrency.acquire(concurrencyKey);
13811
13844
  this.pruneExpiredTasks();
13812
13845
  try {
13813
13846
  const createResult = await this.client.session.create({
13814
- body: {
13815
- parentID: input.parentSessionID,
13816
- title: `Parallel: ${input.description}`
13817
- },
13818
- query: {
13819
- directory: this.directory
13820
- }
13847
+ body: { parentID: input.parentSessionID, title: `Parallel: ${input.description}` },
13848
+ query: { directory: this.directory }
13821
13849
  });
13822
13850
  if (createResult.error) {
13823
13851
  this.concurrency.release(concurrencyKey);
13824
13852
  throw new Error(`Failed to create session: ${createResult.error}`);
13825
13853
  }
13826
13854
  const sessionID = createResult.data.id;
13827
- const taskId = `task_${crypto.randomUUID().slice(0, 8)}`;
13855
+ const taskId = `${ID_PREFIX.TASK}${crypto.randomUUID().slice(0, 8)}`;
13828
13856
  const task = {
13829
13857
  id: taskId,
13830
13858
  sessionID,
@@ -13835,171 +13863,172 @@ var ParallelAgentManager = class _ParallelAgentManager {
13835
13863
  startedAt: /* @__PURE__ */ new Date(),
13836
13864
  concurrencyKey
13837
13865
  };
13838
- this.tasks.set(taskId, task);
13839
- this.trackPending(input.parentSessionID, taskId);
13866
+ this.store.set(taskId, task);
13867
+ this.store.trackPending(input.parentSessionID, taskId);
13840
13868
  this.startPolling();
13841
13869
  this.client.session.prompt({
13842
13870
  path: { id: sessionID },
13843
- body: {
13844
- agent: input.agent,
13845
- parts: [{ type: "text", text: input.prompt }]
13846
- }
13871
+ body: { agent: input.agent, parts: [{ type: "text", text: input.prompt }] }
13847
13872
  }).catch((error45) => {
13848
- log(`Prompt error for ${taskId}:`, error45);
13873
+ log2(`Prompt error for ${taskId}:`, error45);
13849
13874
  this.handleTaskError(taskId, error45);
13850
13875
  });
13851
- log(`Launched ${taskId} in session ${sessionID}`);
13876
+ log2(`Launched ${taskId} in session ${sessionID}`);
13852
13877
  return task;
13853
13878
  } catch (error45) {
13854
13879
  this.concurrency.release(concurrencyKey);
13855
13880
  throw error45;
13856
13881
  }
13857
13882
  }
13858
- /**
13859
- * Get task by ID
13860
- */
13861
13883
  getTask(id) {
13862
- return this.tasks.get(id);
13884
+ return this.store.get(id);
13863
13885
  }
13864
- /**
13865
- * Get all running tasks
13866
- */
13867
13886
  getRunningTasks() {
13868
- return Array.from(this.tasks.values()).filter((t) => t.status === "running");
13887
+ return this.store.getRunning();
13869
13888
  }
13870
- /**
13871
- * Get all tasks
13872
- */
13873
13889
  getAllTasks() {
13874
- return Array.from(this.tasks.values());
13890
+ return this.store.getAll();
13875
13891
  }
13876
- /**
13877
- * Get tasks by parent session
13878
- */
13879
13892
  getTasksByParent(parentSessionID) {
13880
- return Array.from(this.tasks.values()).filter((t) => t.parentSessionID === parentSessionID);
13893
+ return this.store.getByParent(parentSessionID);
13881
13894
  }
13882
- /**
13883
- * Cancel a running task
13884
- */
13885
13895
  async cancelTask(taskId) {
13886
- const task = this.tasks.get(taskId);
13887
- if (!task || task.status !== "running") {
13888
- return false;
13889
- }
13896
+ const task = this.store.get(taskId);
13897
+ if (!task || task.status !== "running") return false;
13890
13898
  task.status = "error";
13891
13899
  task.error = "Cancelled by user";
13892
13900
  task.completedAt = /* @__PURE__ */ new Date();
13893
- if (task.concurrencyKey) {
13894
- this.concurrency.release(task.concurrencyKey);
13895
- }
13896
- this.untrackPending(task.parentSessionID, taskId);
13901
+ if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
13902
+ this.store.untrackPending(task.parentSessionID, taskId);
13897
13903
  try {
13898
- await this.client.session.delete({
13899
- path: { id: task.sessionID }
13900
- });
13901
- console.log(`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... deleted`);
13904
+ await this.client.session.delete({ path: { id: task.sessionID } });
13905
+ log2(`Session ${task.sessionID.slice(0, 8)}... deleted`);
13902
13906
  } catch {
13903
- console.log(`[parallel] \u{1F5D1}\uFE0F Session ${task.sessionID.slice(0, 8)}... already gone`);
13907
+ log2(`Session ${task.sessionID.slice(0, 8)}... already gone`);
13904
13908
  }
13905
13909
  this.scheduleCleanup(taskId);
13906
- console.log(`[parallel] \u{1F6D1} CANCELLED ${taskId}`);
13907
- log(`Cancelled ${taskId}`);
13910
+ log2(`Cancelled ${taskId}`);
13908
13911
  return true;
13909
13912
  }
13910
- /**
13911
- * Get result from completed task
13912
- */
13913
13913
  async getResult(taskId) {
13914
- const task = this.tasks.get(taskId);
13914
+ const task = this.store.get(taskId);
13915
13915
  if (!task) return null;
13916
13916
  if (task.result) return task.result;
13917
13917
  if (task.status === "error") return `Error: ${task.error}`;
13918
13918
  if (task.status === "running") return null;
13919
13919
  try {
13920
- const messagesResult = await this.client.session.messages({
13921
- path: { id: task.sessionID }
13922
- });
13923
- if (messagesResult.error) {
13924
- return `Error: ${messagesResult.error}`;
13925
- }
13926
- const messages = messagesResult.data ?? [];
13927
- const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
13928
- const lastMsg = assistantMsgs[0];
13920
+ const result = await this.client.session.messages({ path: { id: task.sessionID } });
13921
+ if (result.error) return `Error: ${result.error}`;
13922
+ const messages = result.data ?? [];
13923
+ const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
13929
13924
  if (!lastMsg) return "(No response)";
13930
- const textParts = lastMsg.parts?.filter(
13931
- (p) => p.type === "text" || p.type === "reasoning"
13932
- ) ?? [];
13933
- const result = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
13934
- task.result = result;
13935
- return result;
13925
+ const text = lastMsg.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").filter(Boolean).join("\n") ?? "";
13926
+ task.result = text;
13927
+ return text;
13936
13928
  } catch (error45) {
13937
13929
  return `Error: ${error45 instanceof Error ? error45.message : String(error45)}`;
13938
13930
  }
13939
13931
  }
13940
- /**
13941
- * Set concurrency limit for agent type
13942
- */
13943
13932
  setConcurrencyLimit(agentType, limit) {
13944
13933
  this.concurrency.setLimit(agentType, limit);
13945
13934
  }
13946
- /**
13947
- * Get pending notification count
13948
- */
13949
13935
  getPendingCount(parentSessionID) {
13950
- return this.pendingByParent.get(parentSessionID)?.size ?? 0;
13936
+ return this.store.getPendingCount(parentSessionID);
13951
13937
  }
13952
- /**
13953
- * Cleanup all state
13954
- */
13955
13938
  cleanup() {
13956
13939
  this.stopPolling();
13957
- this.tasks.clear();
13958
- this.pendingByParent.clear();
13959
- this.notifications.clear();
13940
+ this.store.clear();
13960
13941
  }
13942
+ formatDuration = formatDuration;
13961
13943
  // ========================================================================
13962
- // Internal: Tracking
13944
+ // Event Handling (from OpenCode hooks)
13963
13945
  // ========================================================================
13964
- trackPending(parentSessionID, taskId) {
13965
- const pending = this.pendingByParent.get(parentSessionID) ?? /* @__PURE__ */ new Set();
13966
- pending.add(taskId);
13967
- this.pendingByParent.set(parentSessionID, pending);
13968
- }
13969
- untrackPending(parentSessionID, taskId) {
13970
- const pending = this.pendingByParent.get(parentSessionID);
13971
- if (pending) {
13972
- pending.delete(taskId);
13973
- if (pending.size === 0) {
13974
- this.pendingByParent.delete(parentSessionID);
13946
+ /**
13947
+ * Handle OpenCode session events for proper resource cleanup.
13948
+ * Call this from your plugin's event hook.
13949
+ */
13950
+ handleEvent(event) {
13951
+ const props = event.properties;
13952
+ if (event.type === "session.idle") {
13953
+ const sessionID = props?.sessionID;
13954
+ if (!sessionID) return;
13955
+ const task = this.findBySession(sessionID);
13956
+ if (!task || task.status !== "running") return;
13957
+ this.handleSessionIdle(task).catch((err) => {
13958
+ log2("Error handling session.idle:", err);
13959
+ });
13960
+ }
13961
+ if (event.type === "session.deleted") {
13962
+ const sessionID = props?.info?.id ?? props?.sessionID;
13963
+ if (!sessionID) return;
13964
+ const task = this.findBySession(sessionID);
13965
+ if (!task) return;
13966
+ log2(`Session deleted event for task ${task.id}`);
13967
+ if (task.status === "running") {
13968
+ task.status = "error";
13969
+ task.error = "Session deleted";
13970
+ task.completedAt = /* @__PURE__ */ new Date();
13971
+ }
13972
+ if (task.concurrencyKey) {
13973
+ this.concurrency.release(task.concurrencyKey);
13974
+ task.concurrencyKey = void 0;
13975
13975
  }
13976
+ this.store.untrackPending(task.parentSessionID, task.id);
13977
+ this.store.clearNotificationsForTask(task.id);
13978
+ this.store.delete(task.id);
13979
+ log2(`Cleaned up deleted session task: ${task.id}`);
13976
13980
  }
13977
13981
  }
13982
+ /**
13983
+ * Find task by session ID
13984
+ */
13985
+ findBySession(sessionID) {
13986
+ return this.store.getAll().find((t) => t.sessionID === sessionID);
13987
+ }
13988
+ /**
13989
+ * Handle session.idle event - validate and complete task
13990
+ */
13991
+ async handleSessionIdle(task) {
13992
+ const elapsed = Date.now() - task.startedAt.getTime();
13993
+ if (elapsed < CONFIG.MIN_STABILITY_MS) {
13994
+ log2(`Session idle but too early for ${task.id}, waiting...`);
13995
+ return;
13996
+ }
13997
+ const hasOutput = await this.validateSessionHasOutput(task.sessionID);
13998
+ if (!hasOutput) {
13999
+ log2(`Session idle but no output for ${task.id}, waiting...`);
14000
+ return;
14001
+ }
14002
+ task.status = "completed";
14003
+ task.completedAt = /* @__PURE__ */ new Date();
14004
+ if (task.concurrencyKey) {
14005
+ this.concurrency.release(task.concurrencyKey);
14006
+ task.concurrencyKey = void 0;
14007
+ }
14008
+ this.store.untrackPending(task.parentSessionID, task.id);
14009
+ this.store.queueNotification(task);
14010
+ await this.notifyParentIfAllComplete(task.parentSessionID);
14011
+ this.scheduleCleanup(task.id);
14012
+ log2(`Task ${task.id} completed via session.idle event (${formatDuration(task.startedAt, task.completedAt)})`);
14013
+ }
13978
14014
  // ========================================================================
13979
- // Internal: Error Handling
14015
+ // Internal
13980
14016
  // ========================================================================
13981
14017
  handleTaskError(taskId, error45) {
13982
- const task = this.tasks.get(taskId);
14018
+ const task = this.store.get(taskId);
13983
14019
  if (!task) return;
13984
14020
  task.status = "error";
13985
14021
  task.error = error45 instanceof Error ? error45.message : String(error45);
13986
14022
  task.completedAt = /* @__PURE__ */ new Date();
13987
- if (task.concurrencyKey) {
13988
- this.concurrency.release(task.concurrencyKey);
13989
- }
13990
- this.untrackPending(task.parentSessionID, taskId);
13991
- this.queueNotification(task);
14023
+ if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
14024
+ this.store.untrackPending(task.parentSessionID, taskId);
14025
+ this.store.queueNotification(task);
13992
14026
  this.notifyParentIfAllComplete(task.parentSessionID);
13993
14027
  this.scheduleCleanup(taskId);
13994
14028
  }
13995
- // ========================================================================
13996
- // Internal: Polling
13997
- // ========================================================================
13998
14029
  startPolling() {
13999
14030
  if (this.pollingInterval) return;
14000
- this.pollingInterval = setInterval(() => {
14001
- this.pollRunningTasks();
14002
- }, POLL_INTERVAL_MS);
14031
+ this.pollingInterval = setInterval(() => this.pollRunningTasks(), CONFIG.POLL_INTERVAL_MS);
14003
14032
  this.pollingInterval.unref();
14004
14033
  }
14005
14034
  stopPolling() {
@@ -14010,211 +14039,183 @@ var ParallelAgentManager = class _ParallelAgentManager {
14010
14039
  }
14011
14040
  async pollRunningTasks() {
14012
14041
  this.pruneExpiredTasks();
14013
- const runningTasks = this.getRunningTasks();
14014
- if (runningTasks.length === 0) {
14042
+ const running = this.store.getRunning();
14043
+ if (running.length === 0) {
14015
14044
  this.stopPolling();
14016
14045
  return;
14017
14046
  }
14018
14047
  try {
14019
14048
  const statusResult = await this.client.session.status();
14020
14049
  const allStatuses = statusResult.data ?? {};
14021
- for (const task of runningTasks) {
14022
- const sessionStatus = allStatuses[task.sessionID];
14023
- if (sessionStatus?.type === "idle") {
14050
+ for (const task of running) {
14051
+ try {
14052
+ const sessionStatus = allStatuses[task.sessionID];
14053
+ if (sessionStatus?.type === "idle") {
14054
+ const elapsed2 = Date.now() - task.startedAt.getTime();
14055
+ if (elapsed2 < CONFIG.MIN_STABILITY_MS) continue;
14056
+ if (!await this.validateSessionHasOutput(task.sessionID)) continue;
14057
+ await this.completeTask(task);
14058
+ continue;
14059
+ }
14060
+ await this.updateTaskProgress(task);
14024
14061
  const elapsed = Date.now() - task.startedAt.getTime();
14025
- if (elapsed < MIN_STABILITY_MS) continue;
14026
- const hasOutput = await this.validateSessionHasOutput(task.sessionID);
14027
- if (!hasOutput) continue;
14028
- task.status = "completed";
14029
- task.completedAt = /* @__PURE__ */ new Date();
14030
- if (task.concurrencyKey) {
14031
- this.concurrency.release(task.concurrencyKey);
14062
+ if (elapsed >= CONFIG.MIN_STABILITY_MS && task.stablePolls && task.stablePolls >= 3) {
14063
+ if (await this.validateSessionHasOutput(task.sessionID)) {
14064
+ log2(`Task ${task.id} stable for 3 polls, completing...`);
14065
+ await this.completeTask(task);
14066
+ }
14032
14067
  }
14033
- this.untrackPending(task.parentSessionID, task.id);
14034
- this.queueNotification(task);
14035
- this.notifyParentIfAllComplete(task.parentSessionID);
14036
- this.scheduleCleanup(task.id);
14037
- const duration3 = this.formatDuration(task.startedAt, task.completedAt);
14038
- console.log(`[parallel] \u2705 COMPLETED ${task.id} \u2192 ${task.agent}: ${task.description} (${duration3})`);
14039
- log(`Completed ${task.id}`);
14068
+ } catch (error45) {
14069
+ log2(`Poll error for task ${task.id}:`, error45);
14040
14070
  }
14041
14071
  }
14042
14072
  } catch (error45) {
14043
- log("Polling error:", error45);
14073
+ log2("Polling error:", error45);
14044
14074
  }
14045
14075
  }
14046
- // ========================================================================
14047
- // Internal: Validation
14048
- // ========================================================================
14076
+ /**
14077
+ * Update task progress and stability tracking
14078
+ */
14079
+ async updateTaskProgress(task) {
14080
+ try {
14081
+ const result = await this.client.session.messages({ path: { id: task.sessionID } });
14082
+ if (result.error) return;
14083
+ const messages = result.data ?? [];
14084
+ const assistantMsgs = messages.filter((m) => m.info?.role === "assistant");
14085
+ let toolCalls = 0;
14086
+ let lastTool;
14087
+ let lastMessage;
14088
+ for (const msg of assistantMsgs) {
14089
+ for (const part of msg.parts ?? []) {
14090
+ if (part.type === "tool_use" || part.tool) {
14091
+ toolCalls++;
14092
+ lastTool = part.tool || part.name;
14093
+ }
14094
+ if (part.type === "text" && part.text) {
14095
+ lastMessage = part.text;
14096
+ }
14097
+ }
14098
+ }
14099
+ task.progress = {
14100
+ toolCalls,
14101
+ lastTool,
14102
+ lastMessage: lastMessage?.slice(0, 100),
14103
+ lastUpdate: /* @__PURE__ */ new Date()
14104
+ };
14105
+ const currentMsgCount = messages.length;
14106
+ if (task.lastMsgCount === currentMsgCount) {
14107
+ task.stablePolls = (task.stablePolls ?? 0) + 1;
14108
+ } else {
14109
+ task.stablePolls = 0;
14110
+ }
14111
+ task.lastMsgCount = currentMsgCount;
14112
+ } catch {
14113
+ }
14114
+ }
14115
+ /**
14116
+ * Complete a task and cleanup
14117
+ */
14118
+ async completeTask(task) {
14119
+ task.status = "completed";
14120
+ task.completedAt = /* @__PURE__ */ new Date();
14121
+ if (task.concurrencyKey) {
14122
+ this.concurrency.release(task.concurrencyKey);
14123
+ task.concurrencyKey = void 0;
14124
+ }
14125
+ this.store.untrackPending(task.parentSessionID, task.id);
14126
+ this.store.queueNotification(task);
14127
+ await this.notifyParentIfAllComplete(task.parentSessionID);
14128
+ this.scheduleCleanup(task.id);
14129
+ log2(`Completed ${task.id} (${formatDuration(task.startedAt, task.completedAt)})`);
14130
+ }
14049
14131
  async validateSessionHasOutput(sessionID) {
14050
14132
  try {
14051
- const response = await this.client.session.messages({
14052
- path: { id: sessionID }
14053
- });
14133
+ const response = await this.client.session.messages({ path: { id: sessionID } });
14054
14134
  const messages = response.data ?? [];
14055
- const hasContent = messages.some((m) => {
14056
- if (m.info?.role !== "assistant") return false;
14057
- const parts = m.parts ?? [];
14058
- return parts.some(
14059
- (p) => p.type === "text" && p.text?.trim() || p.type === "reasoning" && p.text?.trim() || p.type === "tool"
14060
- );
14061
- });
14062
- return hasContent;
14135
+ return messages.some((m) => m.info?.role === "assistant" && m.parts?.some((p) => p.type === "text" && p.text?.trim() || p.type === "tool"));
14063
14136
  } catch {
14064
14137
  return true;
14065
14138
  }
14066
14139
  }
14067
- // ========================================================================
14068
- // Internal: Cleanup & TTL
14069
- // ========================================================================
14070
14140
  pruneExpiredTasks() {
14071
14141
  const now = Date.now();
14072
- for (const [taskId, task] of this.tasks.entries()) {
14142
+ for (const [taskId, task] of this.store.getAll().map((t) => [t.id, t])) {
14073
14143
  const age = now - task.startedAt.getTime();
14074
- if (age > TASK_TTL_MS) {
14075
- log(`Timeout: ${taskId} (${Math.round(age / 1e3)}s)`);
14076
- if (task.status === "running") {
14077
- task.status = "timeout";
14078
- task.error = "Task exceeded 30 minute time limit";
14079
- task.completedAt = /* @__PURE__ */ new Date();
14080
- if (task.concurrencyKey) {
14081
- this.concurrency.release(task.concurrencyKey);
14082
- }
14083
- this.untrackPending(task.parentSessionID, taskId);
14084
- console.log(`[parallel] \u23F1\uFE0F TIMEOUT ${taskId} \u2192 ${task.agent}: ${task.description}`);
14085
- }
14086
- this.client.session.delete({
14087
- path: { id: task.sessionID }
14088
- }).then(() => {
14089
- console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session deleted)`);
14090
- }).catch(() => {
14091
- console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (timeout session already gone)`);
14092
- });
14093
- this.tasks.delete(taskId);
14094
- }
14095
- }
14096
- for (const [sessionID, queue] of this.notifications.entries()) {
14097
- if (queue.length === 0) {
14098
- this.notifications.delete(sessionID);
14099
- }
14144
+ if (age <= CONFIG.TASK_TTL_MS) continue;
14145
+ log2(`Timeout: ${taskId}`);
14146
+ if (task.status === "running") {
14147
+ task.status = "timeout";
14148
+ task.error = "Task exceeded 30 minute time limit";
14149
+ task.completedAt = /* @__PURE__ */ new Date();
14150
+ if (task.concurrencyKey) this.concurrency.release(task.concurrencyKey);
14151
+ this.store.untrackPending(task.parentSessionID, taskId);
14152
+ }
14153
+ this.client.session.delete({ path: { id: task.sessionID } }).catch(() => {
14154
+ });
14155
+ this.store.delete(taskId);
14100
14156
  }
14157
+ this.store.cleanEmptyNotifications();
14101
14158
  }
14102
14159
  scheduleCleanup(taskId) {
14103
- const task = this.tasks.get(taskId);
14160
+ const task = this.store.get(taskId);
14104
14161
  const sessionID = task?.sessionID;
14105
14162
  setTimeout(async () => {
14106
14163
  if (sessionID) {
14107
14164
  try {
14108
- await this.client.session.delete({
14109
- path: { id: sessionID }
14110
- });
14111
- console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session deleted)`);
14112
- log(`Deleted session ${sessionID}`);
14165
+ await this.client.session.delete({ path: { id: sessionID } });
14113
14166
  } catch {
14114
- console.log(`[parallel] \u{1F5D1}\uFE0F CLEANED ${taskId} (session already gone)`);
14115
14167
  }
14116
14168
  }
14117
- this.tasks.delete(taskId);
14118
- log(`Cleaned up ${taskId} from memory`);
14119
- }, CLEANUP_DELAY_MS);
14120
- }
14121
- // ========================================================================
14122
- // Internal: Notifications
14123
- // ========================================================================
14124
- queueNotification(task) {
14125
- const queue = this.notifications.get(task.parentSessionID) ?? [];
14126
- queue.push(task);
14127
- this.notifications.set(task.parentSessionID, queue);
14169
+ this.store.delete(taskId);
14170
+ log2(`Cleaned up ${taskId}`);
14171
+ }, CONFIG.CLEANUP_DELAY_MS);
14128
14172
  }
14129
14173
  async notifyParentIfAllComplete(parentSessionID) {
14130
- const pending = this.pendingByParent.get(parentSessionID);
14131
- if (pending && pending.size > 0) {
14132
- log(`${pending.size} tasks still pending for ${parentSessionID}`);
14133
- return;
14134
- }
14135
- const completedTasks = this.notifications.get(parentSessionID) ?? [];
14136
- if (completedTasks.length === 0) return;
14137
- const summary = completedTasks.map((t) => {
14138
- const status = t.status === "completed" ? "\u2705" : "\u274C";
14139
- return `${status} \`${t.id}\`: ${t.description}`;
14140
- }).join("\n");
14141
- const notification = `<system-notification>
14142
- **All Parallel Tasks Complete**
14143
-
14144
- ${summary}
14145
-
14146
- Use \`get_task_result({ taskId: "task_xxx" })\` to retrieve results.
14147
- </system-notification>`;
14174
+ if (this.store.hasPending(parentSessionID)) return;
14175
+ const notifications = this.store.getNotifications(parentSessionID);
14176
+ if (notifications.length === 0) return;
14177
+ const message = buildNotificationMessage(notifications);
14148
14178
  try {
14149
14179
  await this.client.session.prompt({
14150
14180
  path: { id: parentSessionID },
14151
- body: {
14152
- noReply: true,
14153
- parts: [{ type: "text", text: notification }]
14154
- }
14181
+ body: { noReply: true, parts: [{ type: "text", text: message }] }
14155
14182
  });
14156
- log(`Notified parent ${parentSessionID}: ${completedTasks.length} tasks`);
14183
+ log2(`Notified parent ${parentSessionID}`);
14157
14184
  } catch (error45) {
14158
- log("Notification error:", error45);
14185
+ log2("Notification error:", error45);
14159
14186
  }
14160
- this.notifications.delete(parentSessionID);
14161
- }
14162
- // ========================================================================
14163
- // Internal: Formatting
14164
- // ========================================================================
14165
- formatDuration(start, end) {
14166
- const duration3 = (end ?? /* @__PURE__ */ new Date()).getTime() - start.getTime();
14167
- const seconds = Math.floor(duration3 / 1e3);
14168
- const minutes = Math.floor(seconds / 60);
14169
- if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
14170
- return `${seconds}s`;
14187
+ this.store.clearNotifications(parentSessionID);
14171
14188
  }
14172
14189
  };
14173
14190
  var parallelAgentManager = {
14174
14191
  getInstance: ParallelAgentManager.getInstance.bind(ParallelAgentManager)
14175
14192
  };
14176
14193
 
14177
- // src/tools/async-agent.ts
14194
+ // src/tools/parallel/delegate-task.ts
14178
14195
  var createDelegateTaskTool = (manager, client) => tool({
14179
14196
  description: `Delegate a task to an agent.
14180
14197
 
14181
14198
  <mode>
14182
- - background=true: Non-blocking. Task runs in parallel session. Use get_task_result later.
14183
- - background=false: Blocking. Waits for result. Use when you need output immediately.
14199
+ - background=true: Non-blocking. Returns task ID immediately.
14200
+ - background=false: Blocking. Waits for result.
14184
14201
  </mode>
14185
14202
 
14186
- <when_to_use_background_true>
14187
- - Multiple independent tasks to run in parallel
14188
- - Long-running tasks (build, test, analysis)
14189
- - You have other work to do while waiting
14190
- - Example: "Build module A" + "Test module B" in parallel
14191
- </when_to_use_background_true>
14192
-
14193
- <when_to_use_background_false>
14194
- - You need the result for the very next step
14195
- - Single task with nothing else to do
14196
- - Quick questions that return fast
14197
- </when_to_use_background_false>
14198
-
14199
14203
  <safety>
14200
- - Max 3 background tasks per agent type (extras queue automatically)
14204
+ - Max 3 tasks per agent type
14201
14205
  - Auto-timeout: 30 minutes
14202
- - Auto-cleanup: 5 minutes after completion
14203
14206
  </safety>`,
14204
14207
  args: {
14205
- agent: tool.schema.string().describe("Agent name (e.g., 'builder', 'inspector', 'architect')"),
14206
- description: tool.schema.string().describe("Short task description"),
14207
- prompt: tool.schema.string().describe("Full prompt/instructions for the agent"),
14208
- background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits for result). REQUIRED.")
14208
+ agent: tool.schema.string().describe("Agent name"),
14209
+ description: tool.schema.string().describe("Task description"),
14210
+ prompt: tool.schema.string().describe("Prompt for the agent"),
14211
+ background: tool.schema.boolean().describe("true=async, false=sync")
14209
14212
  },
14210
14213
  async execute(args, context) {
14211
14214
  const { agent, description, prompt, background } = args;
14212
14215
  const ctx = context;
14213
14216
  const sessionClient = client;
14214
14217
  if (background === void 0) {
14215
- return `\u274C 'background' parameter is REQUIRED.
14216
- - background=true: Run in parallel, returns task ID
14217
- - background=false: Wait for result (blocking)`;
14218
+ return `\u274C 'background' parameter is REQUIRED.`;
14218
14219
  }
14219
14220
  if (background === true) {
14220
14221
  try {
@@ -14224,178 +14225,84 @@ var createDelegateTaskTool = (manager, client) => tool({
14224
14225
  prompt,
14225
14226
  parentSessionID: ctx.sessionID
14226
14227
  });
14227
- const runningCount = manager.getRunningTasks().length;
14228
- const pendingCount = manager.getPendingCount(ctx.sessionID);
14229
- console.log(`[parallel] \u{1F680} SPAWNED ${task.id} \u2192 ${agent}: ${description}`);
14230
- return `
14231
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
14232
- \u2551 \u{1F680} BACKGROUND TASK SPAWNED \u2551
14233
- \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
14234
- \u2551 Task ID: ${task.id.padEnd(45)}\u2551
14235
- \u2551 Agent: ${task.agent.padEnd(45)}\u2551
14236
- \u2551 Description: ${task.description.slice(0, 45).padEnd(45)}\u2551
14237
- \u2551 Status: \u23F3 RUNNING (background) \u2551
14238
- \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
14239
- \u2551 Running: ${String(runningCount).padEnd(5)} \u2502 Pending: ${String(pendingCount).padEnd(5)} \u2551
14240
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
14241
-
14242
- \u{1F4CC} Continue your work! System notifies when ALL complete.
14243
- \u{1F50D} Use \`get_task_result({ taskId: "${task.id}" })\` later.`;
14228
+ return `\u{1F680} Task spawned: \`${task.id}\` (${agent})`;
14244
14229
  } catch (error45) {
14245
- const message = error45 instanceof Error ? error45.message : String(error45);
14246
- console.log(`[parallel] \u274C FAILED: ${message}`);
14247
- return `\u274C Failed to spawn background task: ${message}`;
14230
+ return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
14248
14231
  }
14249
14232
  }
14250
- console.log(`[delegate] \u23F3 SYNC ${agent}: ${description}`);
14251
14233
  try {
14252
14234
  const session = sessionClient.session;
14253
- const directory = ".";
14254
14235
  const createResult = await session.create({
14255
- body: {
14256
- parentID: ctx.sessionID,
14257
- title: `Task: ${description}`
14258
- },
14259
- query: { directory }
14236
+ body: { parentID: ctx.sessionID, title: `Task: ${description}` },
14237
+ query: { directory: "." }
14260
14238
  });
14261
14239
  if (createResult.error || !createResult.data?.id) {
14262
- return `\u274C Failed to create session: ${createResult.error || "No session ID"}`;
14240
+ return `\u274C Failed to create session`;
14263
14241
  }
14264
14242
  const sessionID = createResult.data.id;
14265
14243
  const startTime = Date.now();
14266
- try {
14267
- await session.prompt({
14268
- path: { id: sessionID },
14269
- body: {
14270
- agent,
14271
- parts: [{ type: "text", text: prompt }]
14272
- }
14273
- });
14274
- } catch (promptError) {
14275
- const errorMessage = promptError instanceof Error ? promptError.message : String(promptError);
14276
- return `\u274C Failed to send prompt: ${errorMessage}
14277
-
14278
- Session ID: ${sessionID}`;
14279
- }
14280
- const POLL_INTERVAL_MS2 = 500;
14281
- const MAX_POLL_TIME_MS = 10 * 60 * 1e3;
14282
- const MIN_STABILITY_MS2 = 5e3;
14283
- const STABILITY_POLLS_REQUIRED = 3;
14284
- let stablePolls = 0;
14285
- let lastMsgCount = 0;
14286
- while (Date.now() - startTime < MAX_POLL_TIME_MS) {
14287
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS2));
14244
+ await session.prompt({
14245
+ path: { id: sessionID },
14246
+ body: { agent, parts: [{ type: "text", text: prompt }] }
14247
+ });
14248
+ let stablePolls = 0, lastMsgCount = 0;
14249
+ while (Date.now() - startTime < 10 * 60 * 1e3) {
14250
+ await new Promise((r) => setTimeout(r, 500));
14288
14251
  const statusResult = await session.status();
14289
- const allStatuses = statusResult.data ?? {};
14290
- const sessionStatus = allStatuses[sessionID];
14291
- if (sessionStatus?.type !== "idle") {
14252
+ if (statusResult.data?.[sessionID]?.type !== "idle") {
14292
14253
  stablePolls = 0;
14293
- lastMsgCount = 0;
14294
14254
  continue;
14295
14255
  }
14296
- if (Date.now() - startTime < MIN_STABILITY_MS2) continue;
14297
- const messagesResult2 = await session.messages({ path: { id: sessionID } });
14298
- const messages2 = messagesResult2.data ?? [];
14299
- const currentMsgCount = messages2.length;
14300
- if (currentMsgCount === lastMsgCount) {
14256
+ if (Date.now() - startTime < 5e3) continue;
14257
+ const msgs2 = await session.messages({ path: { id: sessionID } });
14258
+ const count = (msgs2.data ?? []).length;
14259
+ if (count === lastMsgCount) {
14301
14260
  stablePolls++;
14302
- if (stablePolls >= STABILITY_POLLS_REQUIRED) break;
14261
+ if (stablePolls >= 3) break;
14303
14262
  } else {
14304
14263
  stablePolls = 0;
14305
- lastMsgCount = currentMsgCount;
14264
+ lastMsgCount = count;
14306
14265
  }
14307
14266
  }
14308
- const messagesResult = await session.messages({ path: { id: sessionID } });
14309
- const messages = messagesResult.data ?? [];
14310
- const assistantMsgs = messages.filter((m) => m.info?.role === "assistant").reverse();
14311
- const lastMsg = assistantMsgs[0];
14312
- if (!lastMsg) {
14313
- return `\u274C No assistant response found.
14267
+ const msgs = await session.messages({ path: { id: sessionID } });
14268
+ const messages = msgs.data ?? [];
14269
+ const lastMsg = messages.filter((m) => m.info?.role === "assistant").reverse()[0];
14270
+ const text = lastMsg?.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text ?? "").join("\n") || "";
14271
+ return `\u2705 Completed (${Math.floor((Date.now() - startTime) / 1e3)}s)
14314
14272
 
14315
- Session ID: ${sessionID}`;
14316
- }
14317
- const textParts = lastMsg.parts?.filter(
14318
- (p) => p.type === "text" || p.type === "reasoning"
14319
- ) ?? [];
14320
- const textContent = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n");
14321
- const duration3 = Math.floor((Date.now() - startTime) / 1e3);
14322
- console.log(`[delegate] \u2705 COMPLETED ${agent}: ${description} (${duration3}s)`);
14323
- return `\u2705 **Task Completed** (${duration3}s)
14324
-
14325
- Agent: ${agent}
14326
- Session ID: ${sessionID}
14327
-
14328
- ---
14329
-
14330
- ${textContent || "(No text output)"}`;
14273
+ ${text || "(No output)"}`;
14331
14274
  } catch (error45) {
14332
- const message = error45 instanceof Error ? error45.message : String(error45);
14333
- console.log(`[delegate] \u274C FAILED: ${message}`);
14334
- return `\u274C Task failed: ${message}`;
14275
+ return `\u274C Failed: ${error45 instanceof Error ? error45.message : String(error45)}`;
14335
14276
  }
14336
14277
  }
14337
14278
  });
14338
- var createGetTaskResultTool = (manager) => tool({
14339
- description: `Get the result from a completed background task.
14340
14279
 
14341
- <note>
14342
- If the task is still running, returns status info.
14343
- Wait for the "All Complete" notification before checking.
14344
- </note>`,
14280
+ // src/tools/parallel/get-task-result.ts
14281
+ var createGetTaskResultTool = (manager) => tool({
14282
+ description: `Get result from a completed background task.`,
14345
14283
  args: {
14346
- taskId: tool.schema.string().describe("Task ID from delegate_task (e.g., 'task_a1b2c3d4')")
14284
+ taskId: tool.schema.string().describe("Task ID")
14347
14285
  },
14348
14286
  async execute(args) {
14349
- const { taskId } = args;
14350
- const task = manager.getTask(taskId);
14351
- if (!task) {
14352
- return `\u274C Task not found: \`${taskId}\`
14353
-
14354
- Use \`list_tasks\` to see available tasks.`;
14355
- }
14356
- if (task.status === "running") {
14357
- const elapsed = Math.floor((Date.now() - task.startedAt.getTime()) / 1e3);
14358
- return `\u23F3 **Task Still Running**
14359
-
14360
- | Property | Value |
14361
- |----------|-------|
14362
- | **Task ID** | \`${taskId}\` |
14363
- | **Agent** | ${task.agent} |
14364
- | **Elapsed** | ${elapsed}s |
14365
-
14366
- Wait for "All Complete" notification, then try again.`;
14367
- }
14368
- const result = await manager.getResult(taskId);
14287
+ const task = manager.getTask(args.taskId);
14288
+ if (!task) return `\u274C Task not found: \`${args.taskId}\``;
14289
+ if (task.status === "running") return `\u23F3 Still running...`;
14290
+ const result = await manager.getResult(args.taskId);
14369
14291
  const duration3 = manager.formatDuration(task.startedAt, task.completedAt);
14370
14292
  if (task.status === "error" || task.status === "timeout") {
14371
- return `\u274C **Task ${task.status === "timeout" ? "Timed Out" : "Failed"}**
14372
-
14373
- | Property | Value |
14374
- |----------|-------|
14375
- | **Task ID** | \`${taskId}\` |
14376
- | **Agent** | ${task.agent} |
14377
- | **Error** | ${task.error} |
14378
- | **Duration** | ${duration3} |`;
14293
+ return `\u274C ${task.status}: ${task.error}`;
14379
14294
  }
14380
- return `\u2705 **Task Completed**
14381
-
14382
- | Property | Value |
14383
- |----------|-------|
14384
- | **Task ID** | \`${taskId}\` |
14385
- | **Agent** | ${task.agent} |
14386
- | **Duration** | ${duration3} |
14387
-
14388
- ---
14389
-
14390
- **Result:**
14295
+ return `\u2705 Completed (${duration3})
14391
14296
 
14392
14297
  ${result || "(No output)"}`;
14393
14298
  }
14394
14299
  });
14300
+
14301
+ // src/tools/parallel/list-tasks.ts
14395
14302
  var createListTasksTool = (manager) => tool({
14396
- description: `List all background tasks and their status.`,
14303
+ description: `List all background tasks.`,
14397
14304
  args: {
14398
- status: tool.schema.string().optional().describe("Filter: 'all', 'running', 'completed', 'error'")
14305
+ status: tool.schema.string().optional().describe("Filter: all, running, completed, error")
14399
14306
  },
14400
14307
  async execute(args) {
14401
14308
  const { status = "all" } = args;
@@ -14413,73 +14320,35 @@ var createListTasksTool = (manager) => tool({
14413
14320
  default:
14414
14321
  tasks = manager.getAllTasks();
14415
14322
  }
14416
- if (tasks.length === 0) {
14417
- return `\u{1F4CB} No background tasks found${status !== "all" ? ` (filter: ${status})` : ""}.
14418
-
14419
- Use \`delegate_task({ ..., background: true })\` to spawn background tasks.`;
14420
- }
14421
- const runningCount = manager.getRunningTasks().length;
14422
- const completedCount = manager.getAllTasks().filter((t) => t.status === "completed").length;
14423
- const errorCount = manager.getAllTasks().filter((t) => t.status === "error" || t.status === "timeout").length;
14424
- const statusIcon = (s) => {
14425
- switch (s) {
14426
- case "running":
14427
- return "\u23F3";
14428
- case "completed":
14429
- return "\u2705";
14430
- case "error":
14431
- return "\u274C";
14432
- case "timeout":
14433
- return "\u23F1\uFE0F";
14434
- default:
14435
- return "\u2753";
14436
- }
14437
- };
14323
+ if (tasks.length === 0) return `\u{1F4CB} No tasks found.`;
14438
14324
  const rows = tasks.map((t) => {
14439
14325
  const elapsed = Math.floor((Date.now() - t.startedAt.getTime()) / 1e3);
14440
- const desc = t.description.length > 25 ? t.description.slice(0, 22) + "..." : t.description;
14441
- return `| \`${t.id}\` | ${statusIcon(t.status)} ${t.status} | ${t.agent} | ${desc} | ${elapsed}s |`;
14326
+ return `| \`${t.id}\` | ${getStatusEmoji(t.status)} ${t.status} | ${t.agent} | ${elapsed}s |`;
14442
14327
  }).join("\n");
14443
- return `\u{1F4CB} **Background Tasks** (${tasks.length} shown)
14444
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
14445
- | \u23F3 Running: ${runningCount} | \u2705 Completed: ${completedCount} | \u274C Error: ${errorCount} |
14446
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
14328
+ return `\u{1F4CB} **Tasks**
14447
14329
 
14448
- | Task ID | Status | Agent | Description | Elapsed |
14449
- |---------|--------|-------|-------------|---------|
14450
- ${rows}
14451
-
14452
- \u{1F4A1} Use \`get_task_result({ taskId: "task_xxx" })\` to get results.
14453
- \u{1F6D1} Use \`cancel_task({ taskId: "task_xxx" })\` to stop a running task.`;
14330
+ | ID | Status | Agent | Time |
14331
+ |----|--------|-------|------|
14332
+ ${rows}`;
14454
14333
  }
14455
14334
  });
14456
- var createCancelTaskTool = (manager) => tool({
14457
- description: `Cancel a running background task.
14458
14335
 
14459
- <purpose>
14460
- Stop a runaway or no-longer-needed task.
14461
- Frees up concurrency slot for other tasks.
14462
- </purpose>`,
14336
+ // src/tools/parallel/cancel-task.ts
14337
+ var createCancelTaskTool = (manager) => tool({
14338
+ description: `Cancel a running task.`,
14463
14339
  args: {
14464
- taskId: tool.schema.string().describe("Task ID to cancel (e.g., 'task_a1b2c3d4')")
14340
+ taskId: tool.schema.string().describe("Task ID to cancel")
14465
14341
  },
14466
14342
  async execute(args) {
14467
- const { taskId } = args;
14468
- const cancelled = await manager.cancelTask(taskId);
14469
- if (cancelled) {
14470
- return `\u{1F6D1} **Task Cancelled**
14471
-
14472
- Task \`${taskId}\` has been stopped. Concurrency slot released.`;
14473
- }
14474
- const task = manager.getTask(taskId);
14475
- if (task) {
14476
- return `\u26A0\uFE0F Cannot cancel: Task \`${taskId}\` is ${task.status} (not running).`;
14477
- }
14478
- return `\u274C Task \`${taskId}\` not found.
14479
-
14480
- Use \`list_tasks\` to see available tasks.`;
14343
+ const cancelled = await manager.cancelTask(args.taskId);
14344
+ if (cancelled) return `\u{1F6D1} Cancelled: \`${args.taskId}\``;
14345
+ const task = manager.getTask(args.taskId);
14346
+ if (task) return `\u26A0\uFE0F Cannot cancel: Task is ${task.status}`;
14347
+ return `\u274C Not found: \`${args.taskId}\``;
14481
14348
  }
14482
14349
  });
14350
+
14351
+ // src/tools/parallel/index.ts
14483
14352
  function createAsyncAgentTools(manager, client) {
14484
14353
  return {
14485
14354
  delegate_task: createDelegateTaskTool(manager, client),
@@ -14978,6 +14847,11 @@ ${stateSession.graph.getTaskSummary()}`;
14978
14847
  // Event handler - cleans up when sessions are deleted
14979
14848
  // -----------------------------------------------------------------
14980
14849
  handler: async ({ event }) => {
14850
+ try {
14851
+ const manager = ParallelAgentManager.getInstance();
14852
+ manager.handleEvent(event);
14853
+ } catch {
14854
+ }
14981
14855
  if (event.type === "session.deleted") {
14982
14856
  const props = event.properties;
14983
14857
  if (props?.info?.id) {