@elizaos/plugin-agent-orchestrator 0.2.0 → 0.3.2

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 (79) hide show
  1. package/dist/index.js +495 -72
  2. package/dist/index.js.map +14 -13
  3. package/package.json +2 -2
  4. package/dist/src/actions/coding-task-handlers.d.ts +0 -44
  5. package/dist/src/actions/coding-task-handlers.d.ts.map +0 -1
  6. package/dist/src/actions/coding-task-helpers.d.ts +0 -27
  7. package/dist/src/actions/coding-task-helpers.d.ts.map +0 -1
  8. package/dist/src/actions/finalize-workspace.d.ts +0 -11
  9. package/dist/src/actions/finalize-workspace.d.ts.map +0 -1
  10. package/dist/src/actions/list-agents.d.ts +0 -11
  11. package/dist/src/actions/list-agents.d.ts.map +0 -1
  12. package/dist/src/actions/manage-issues.d.ts +0 -11
  13. package/dist/src/actions/manage-issues.d.ts.map +0 -1
  14. package/dist/src/actions/provision-workspace.d.ts +0 -11
  15. package/dist/src/actions/provision-workspace.d.ts.map +0 -1
  16. package/dist/src/actions/send-to-agent.d.ts +0 -11
  17. package/dist/src/actions/send-to-agent.d.ts.map +0 -1
  18. package/dist/src/actions/spawn-agent.d.ts +0 -11
  19. package/dist/src/actions/spawn-agent.d.ts.map +0 -1
  20. package/dist/src/actions/start-coding-task.d.ts +0 -17
  21. package/dist/src/actions/start-coding-task.d.ts.map +0 -1
  22. package/dist/src/actions/stop-agent.d.ts +0 -11
  23. package/dist/src/actions/stop-agent.d.ts.map +0 -1
  24. package/dist/src/api/agent-routes.d.ts +0 -18
  25. package/dist/src/api/agent-routes.d.ts.map +0 -1
  26. package/dist/src/api/coordinator-routes.d.ts +0 -22
  27. package/dist/src/api/coordinator-routes.d.ts.map +0 -1
  28. package/dist/src/api/issue-routes.d.ts +0 -17
  29. package/dist/src/api/issue-routes.d.ts.map +0 -1
  30. package/dist/src/api/routes.d.ts +0 -36
  31. package/dist/src/api/routes.d.ts.map +0 -1
  32. package/dist/src/api/workspace-routes.d.ts +0 -17
  33. package/dist/src/api/workspace-routes.d.ts.map +0 -1
  34. package/dist/src/index.d.ts +0 -32
  35. package/dist/src/index.d.ts.map +0 -1
  36. package/dist/src/providers/action-examples.d.ts +0 -13
  37. package/dist/src/providers/action-examples.d.ts.map +0 -1
  38. package/dist/src/providers/active-workspace-context.d.ts +0 -13
  39. package/dist/src/providers/active-workspace-context.d.ts.map +0 -1
  40. package/dist/src/services/agent-metrics.d.ts +0 -28
  41. package/dist/src/services/agent-metrics.d.ts.map +0 -1
  42. package/dist/src/services/agent-selection.d.ts +0 -53
  43. package/dist/src/services/agent-selection.d.ts.map +0 -1
  44. package/dist/src/services/ansi-utils.d.ts +0 -48
  45. package/dist/src/services/ansi-utils.d.ts.map +0 -1
  46. package/dist/src/services/pty-auto-response.d.ts +0 -30
  47. package/dist/src/services/pty-auto-response.d.ts.map +0 -1
  48. package/dist/src/services/pty-init.d.ts +0 -43
  49. package/dist/src/services/pty-init.d.ts.map +0 -1
  50. package/dist/src/services/pty-service.d.ts +0 -92
  51. package/dist/src/services/pty-service.d.ts.map +0 -1
  52. package/dist/src/services/pty-session-io.d.ts +0 -46
  53. package/dist/src/services/pty-session-io.d.ts.map +0 -1
  54. package/dist/src/services/pty-spawn.d.ts +0 -50
  55. package/dist/src/services/pty-spawn.d.ts.map +0 -1
  56. package/dist/src/services/pty-types.d.ts +0 -80
  57. package/dist/src/services/pty-types.d.ts.map +0 -1
  58. package/dist/src/services/stall-classifier.d.ts +0 -44
  59. package/dist/src/services/stall-classifier.d.ts.map +0 -1
  60. package/dist/src/services/swarm-coordinator-prompts.d.ts +0 -62
  61. package/dist/src/services/swarm-coordinator-prompts.d.ts.map +0 -1
  62. package/dist/src/services/swarm-coordinator.d.ts +0 -163
  63. package/dist/src/services/swarm-coordinator.d.ts.map +0 -1
  64. package/dist/src/services/swarm-decision-loop.d.ts +0 -39
  65. package/dist/src/services/swarm-decision-loop.d.ts.map +0 -1
  66. package/dist/src/services/swarm-idle-watchdog.d.ts +0 -22
  67. package/dist/src/services/swarm-idle-watchdog.d.ts.map +0 -1
  68. package/dist/src/services/workspace-git-ops.d.ts +0 -28
  69. package/dist/src/services/workspace-git-ops.d.ts.map +0 -1
  70. package/dist/src/services/workspace-github.d.ts +0 -58
  71. package/dist/src/services/workspace-github.d.ts.map +0 -1
  72. package/dist/src/services/workspace-lifecycle.d.ts +0 -18
  73. package/dist/src/services/workspace-lifecycle.d.ts.map +0 -1
  74. package/dist/src/services/workspace-service.d.ts +0 -84
  75. package/dist/src/services/workspace-service.d.ts.map +0 -1
  76. package/dist/src/services/workspace-types.d.ts +0 -81
  77. package/dist/src/services/workspace-types.d.ts.map +0 -1
  78. package/dist/tsconfig.build.tsbuildinfo +0 -1
  79. package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/index.js CHANGED
@@ -114,11 +114,31 @@ function buildTurnCompletePrompt(taskCtx, turnOutput, decisionHistory) {
114
114
 
115
115
  ` + `Guidelines:\n` + `- BEFORE choosing "complete", enumerate each objective from the original task and verify ` + `evidence in the output. If ANY objective lacks evidence, use "respond" with the missing work.\n` + `- A PR being created does NOT mean the task is done \u2014 check that the PR covers ALL requested changes.
116
116
  ` + `- If the task mentions multiple features/fixes, verify EACH one is addressed, not just the first.\n` + `- If the agent only analyzed code or read files, it hasn't done the actual work yet \u2014 send a follow-up.
117
- ` + `- If the agent wrote code but didn't test it and testing seems appropriate, ask it to run tests.\n` + `- If the output shows errors or failed tests, send a follow-up to fix them.\n` + `- IMPORTANT: If the working directory is a git repository clone (not a scratch dir), the agent ` + `MUST commit its changes, push them, and create a pull request before the task can be "complete". ` + `If the output only shows code edits with no git commit or PR, respond with "Now commit your changes, push, and create a pull request".\n` + `- CRITICAL: Creating a PR is NEVER the final step. After you see "Created pull request" or a PR URL ` + `in the output, you MUST respond with "Review your PR, run each test plan item to verify it works, ` + `update the PR to check off each item, then confirm all items pass". NEVER mark as "complete" on the ` + `same turn that a PR was created \u2014 always send this follow-up first.
118
- ` + `- Only mark as "complete" AFTER the agent has confirmed it verified the test plan items ` + `(look for output like "all items pass", "verified", "checked off", or similar confirmation).\n` + `- Keep follow-up instructions concise and specific.\n` + `- Default to "respond" \u2014 only use "complete" when you're certain ALL work is done.
117
+ ` + `- If the agent wrote code but didn't test it and testing seems appropriate, ask it to run tests.\n` + `- If the output shows errors or failed tests, send a follow-up to fix them.\n` + `- IMPORTANT: If the working directory is a git repository clone (not a scratch dir), the agent ` + `MUST commit its changes, push them, and create a pull request before the task can be "complete". ` + `If the output only shows code edits with no git commit or PR, respond with "Now commit your changes, push, and create a pull request".\n` + `- IMPORTANT: Creating a PR is NOT the final step. If this is the turn where the PR was created ` + `(i.e. "Created pull request" or a PR URL appears for the FIRST time and no previous decision ` + `already sent a review follow-up), respond with "Review your PR, run each test plan item to verify ` + `it works, update the PR to check off each item, then confirm all items pass".\n` + `- If a previous decision ALREADY sent a review/verification follow-up (check the decision history), ` + `and the agent has now responded with its review results, you MAY mark "complete" if the agent ` + `indicates the work is done (e.g. "Done", "verified", "all checks pass", "Here's what I did", ` + `or a clear summary of completed work). Do NOT require exact phrases \u2014 use judgment.
118
+ ` + `- Keep follow-up instructions concise and specific.\n` + `- When asking agents to verify work, prefer CLI tools (gh, curl, cat, git diff, etc.) over ` + `browser automation. Browser tools may not be available in headless environments and can cause delays.\n` + `- Default to "respond" \u2014 only use "complete" when you're certain ALL work is done.
119
119
 
120
120
  ` + `Respond with ONLY a JSON object:\n` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
121
121
  }
122
+ function buildBlockedEventMessage(taskCtx, promptText, recentOutput, decisionHistory) {
123
+ const historySection = decisionHistory.length > 0 ? `\nPrevious decisions:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
124
+ return `[Coding Agent Event] A ${taskCtx.agentType} agent ("${taskCtx.label}") is blocked and waiting for input.\n\n` + `Task: "${taskCtx.originalTask}"\n` + `Workdir: ${taskCtx.workdir}\n` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nRecent terminal output:\n---\n${recentOutput.slice(-3000)}\n---\n\n` + `Blocking prompt: "${promptText}"\n\n` + `Decide how to handle this. Options:\n` + `- "respond" \u2014 send text or keys to unblock the agent
125
+ ` + `- "complete" \u2014 the task is fully done
126
+ ` + `- "escalate" \u2014 you need the user's input
127
+ ` + `- "ignore" \u2014 not actually blocking
128
+
129
+ ` + `Guidelines:\n` + `- For tool approvals / Y/n that align with the task, respond "y" or keys:["enter"].\n` + `- If the prompt asks for info NOT in the original task, escalate.\n` + `- Decline access to paths outside ${taskCtx.workdir}.\n` + `- If a PR was just created, respond to review & verify test plan items before completing.\n` + `- When in doubt, escalate.\n\n` + `Include a JSON action block at the end of your response:\n` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}\n` + "```";
130
+ }
131
+ function buildTurnCompleteEventMessage(taskCtx, turnOutput, decisionHistory) {
132
+ const historySection = decisionHistory.length > 0 ? `\nPrevious decisions:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
133
+ return `[Coding Agent Event] A ${taskCtx.agentType} agent ("${taskCtx.label}") just finished a turn and is idle.\n\n` + `Task: "${taskCtx.originalTask}"\n` + `Workdir: ${taskCtx.workdir}\n` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nTurn output:\n---\n${turnOutput.slice(-3000)}\n---\n\n` + `Decide if the overall task is done or if the agent needs more work.\n\n` + `Options:\n` + `- "respond" \u2014 send a follow-up instruction (DEFAULT for intermediate steps)
134
+ ` + `- "complete" \u2014 ALL task objectives met (code written, committed, PR created & verified)
135
+ ` + `- "escalate" \u2014 something looks wrong, ask the user
136
+ ` + `- "ignore" \u2014 should not normally be used here
137
+
138
+ ` + `Guidelines:\n` + `- Verify evidence for EVERY objective before using "complete".\n` + `- If code was written but not committed/pushed/PR'd, respond with next step.\n` + `- If a PR was just created, respond to review & verify test plan items.\n` + `- When asking agents to verify work, prefer CLI tools (gh, curl, cat, etc.) over browser automation.\n` + `- Default to "respond" \u2014 only "complete" when certain ALL work is done.
139
+
140
+ ` + `Include a JSON action block at the end of your response:\n` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}\n` + "```";
141
+ }
122
142
  function parseCoordinationResponse(llmOutput) {
123
143
  const jsonMatch = llmOutput.match(/\{[\s\S]*\}/);
124
144
  if (!jsonMatch)
@@ -148,6 +168,123 @@ function parseCoordinationResponse(llmOutput) {
148
168
  }
149
169
  }
150
170
 
171
+ // src/services/swarm-event-triage.ts
172
+ import {ModelType as ModelType2} from "@elizaos/core";
173
+ function classifyByHeuristic(ctx) {
174
+ if (ctx.promptType) {
175
+ if (ROUTINE_PROMPT_TYPES.has(ctx.promptType))
176
+ return "routine";
177
+ if (CREATIVE_PROMPT_TYPES.has(ctx.promptType))
178
+ return "creative";
179
+ }
180
+ if (ctx.eventType === "blocked" && ctx.promptText) {
181
+ const hasRoutine = ROUTINE_PATTERNS.some((r) => r.test(ctx.promptText));
182
+ const hasCreative = CREATIVE_PATTERNS.some((r) => r.test(ctx.promptText));
183
+ if (hasRoutine && !hasCreative)
184
+ return "routine";
185
+ if (hasCreative && !hasRoutine)
186
+ return "creative";
187
+ if (hasCreative)
188
+ return "creative";
189
+ }
190
+ if (ctx.eventType === "turn_complete" && ctx.recentOutput) {
191
+ const isTerminal = TERMINAL_OUTPUT_PATTERNS.some((r) => r.test(ctx.recentOutput));
192
+ const isIntermediate = INTERMEDIATE_OUTPUT_PATTERNS.some((r) => r.test(ctx.recentOutput));
193
+ if (isTerminal || isIntermediate)
194
+ return "routine";
195
+ }
196
+ return null;
197
+ }
198
+ function buildTriagePrompt(ctx) {
199
+ const eventDesc = ctx.eventType === "blocked" ? `BLOCKED prompt: "${ctx.promptText.slice(0, 300)}"` : `TURN COMPLETE. Recent output:\n${(ctx.recentOutput ?? "").slice(-500)}`;
200
+ return `Classify this coding agent event as "routine" or "creative".\n\n` + `Task: ${ctx.originalTask.slice(0, 200)}\n` + `Event: ${eventDesc}\n\n` + `"routine" = simple approval, permission, config, yes/no, tool consent, obvious pass/fail.\n` + `"creative" = needs task context, error recovery, design choice, ambiguous situation, approach selection.\n\n` + `Respond with ONLY a JSON object: {"tier": "routine"} or {"tier": "creative"}`;
201
+ }
202
+ function parseTriageResponse(llmOutput) {
203
+ const matches = llmOutput.matchAll(/\{[\s\S]*?\}/g);
204
+ for (const match of matches) {
205
+ try {
206
+ const parsed = JSON.parse(match[0]);
207
+ if (parsed.tier === "routine" || parsed.tier === "creative") {
208
+ return parsed.tier;
209
+ }
210
+ } catch {
211
+ }
212
+ }
213
+ return null;
214
+ }
215
+ async function classifyEventTier(runtime, ctx, log) {
216
+ const heuristicResult = classifyByHeuristic(ctx);
217
+ if (heuristicResult) {
218
+ log(`Triage: heuristic \u2192 ${heuristicResult}`);
219
+ return heuristicResult;
220
+ }
221
+ try {
222
+ const prompt = buildTriagePrompt(ctx);
223
+ const result = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt });
224
+ const tier = parseTriageResponse(result);
225
+ if (tier) {
226
+ log(`Triage: LLM \u2192 ${tier}`);
227
+ return tier;
228
+ }
229
+ log(`Triage: LLM returned unparseable response \u2014 defaulting to creative`);
230
+ } catch (err) {
231
+ log(`Triage: LLM classifier failed: ${err} \u2014 defaulting to creative`);
232
+ }
233
+ return "creative";
234
+ }
235
+ var ROUTINE_PROMPT_TYPES, CREATIVE_PROMPT_TYPES, ROUTINE_PATTERNS, CREATIVE_PATTERNS, TERMINAL_OUTPUT_PATTERNS, INTERMEDIATE_OUTPUT_PATTERNS;
236
+ var init_swarm_event_triage = __esm(() => {
237
+ ROUTINE_PROMPT_TYPES = new Set([
238
+ "permission",
239
+ "config",
240
+ "tos",
241
+ "tool_wait"
242
+ ]);
243
+ CREATIVE_PROMPT_TYPES = new Set(["project_select", "model_select"]);
244
+ ROUTINE_PATTERNS = [
245
+ /\bAllow\s+tool\b/i,
246
+ /\(Y\/n\)/,
247
+ /\(y\/N\)/,
248
+ /\bTrust\s+(this\s+)?directory\b/i,
249
+ /\bProceed\?/i,
250
+ /\boverwrite\?/i,
251
+ /\bDo you trust\b/i,
252
+ /\bAllow access\b/i,
253
+ /\bGrant permission\b/i,
254
+ /\bAccept\?/i,
255
+ /\bContinue\?/i,
256
+ /\bPermit\b.*\?/i,
257
+ /\bApprove\b.*\?/i
258
+ ];
259
+ CREATIVE_PATTERNS = [
260
+ /\bWhich approach\b/i,
261
+ /\bHow should\b/i,
262
+ /\btests? failing\b/i,
263
+ /\bchoose between\b/i,
264
+ /\bpick (one|a|an)\b/i,
265
+ /\bWhat do you (want|think)\b/i,
266
+ /\berror recover/i,
267
+ /\bfailed with\b/i,
268
+ /\bcompilation error/i,
269
+ /\bbuild failed\b/i,
270
+ /\btype error/i,
271
+ /\bmerge conflict/i
272
+ ];
273
+ TERMINAL_OUTPUT_PATTERNS = [
274
+ /All \d+ tests? pass/i,
275
+ /Tests?:\s+\d+ passed/i,
276
+ /✓ All checks passed/i,
277
+ /https:\/\/github\.com\/[^\s]+\/pull\/\d+/,
278
+ /Successfully created PR/i,
279
+ /Commit [a-f0-9]{7,40}/i
280
+ ];
281
+ INTERMEDIATE_OUTPUT_PATTERNS = [
282
+ /^Running tests?\.\.\./im,
283
+ /^Building\.\.\./im,
284
+ /^Installing dependencies/im
285
+ ];
286
+ });
287
+
151
288
  // src/services/swarm-decision-loop.ts
152
289
  var exports_swarm_decision_loop = {};
153
290
  __export(exports_swarm_decision_loop, {
@@ -157,10 +294,11 @@ __export(exports_swarm_decision_loop, {
157
294
  handleConfirmDecision: () => handleConfirmDecision,
158
295
  handleBlocked: () => handleBlocked,
159
296
  handleAutonomousDecision: () => handleAutonomousDecision,
160
- executeDecision: () => executeDecision
297
+ executeDecision: () => executeDecision,
298
+ checkAllTasksComplete: () => checkAllTasksComplete
161
299
  });
162
300
  import * as path from "node:path";
163
- import {ModelType as ModelType2} from "@elizaos/core";
301
+ import {ModelType as ModelType3} from "@elizaos/core";
164
302
  function toContextSummary(taskCtx) {
165
303
  return {
166
304
  sessionId: taskCtx.sessionId,
@@ -203,6 +341,40 @@ function isOutOfScopeAccess(promptText, workdir) {
203
341
  return !resolved.startsWith(resolvedWorkdir + path.sep) && resolved !== resolvedWorkdir;
204
342
  });
205
343
  }
344
+ function checkAllTasksComplete(ctx) {
345
+ const tasks = Array.from(ctx.tasks.values());
346
+ if (tasks.length === 0)
347
+ return;
348
+ const terminalStates = new Set(["completed", "stopped", "error"]);
349
+ const allDone = tasks.every((t) => terminalStates.has(t.status));
350
+ if (!allDone)
351
+ return;
352
+ const completed = tasks.filter((t) => t.status === "completed");
353
+ const stopped = tasks.filter((t) => t.status === "stopped");
354
+ const errored = tasks.filter((t) => t.status === "error");
355
+ const parts = [];
356
+ if (completed.length > 0) {
357
+ parts.push(`${completed.length} completed`);
358
+ }
359
+ if (stopped.length > 0) {
360
+ parts.push(`${stopped.length} stopped`);
361
+ }
362
+ if (errored.length > 0) {
363
+ parts.push(`${errored.length} errored`);
364
+ }
365
+ ctx.sendChatMessage(`All ${tasks.length} coding agents finished (${parts.join(", ")}). Review their work when you're ready.`, "coding-agent");
366
+ ctx.broadcast({
367
+ type: "swarm_complete",
368
+ sessionId: "",
369
+ timestamp: Date.now(),
370
+ data: {
371
+ total: tasks.length,
372
+ completed: completed.length,
373
+ stopped: stopped.length,
374
+ errored: errored.length
375
+ }
376
+ });
377
+ }
206
378
  async function fetchRecentOutput(ctx, sessionId, lines = 50) {
207
379
  if (!ctx.ptyService)
208
380
  return "";
@@ -215,7 +387,7 @@ async function fetchRecentOutput(ctx, sessionId, lines = 50) {
215
387
  async function makeCoordinationDecision(ctx, taskCtx, promptText, recentOutput) {
216
388
  const prompt = buildCoordinationPrompt(toContextSummary(taskCtx), promptText, recentOutput, toDecisionHistory(taskCtx));
217
389
  try {
218
- const result = await ctx.runtime.useModel(ModelType2.TEXT_SMALL, {
390
+ const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
219
391
  prompt
220
392
  });
221
393
  return parseCoordinationResponse(result);
@@ -253,9 +425,10 @@ async function executeDecision(ctx, sessionId, decision) {
253
425
  } catch {
254
426
  }
255
427
  ctx.sendChatMessage(summary ? `Finished "${taskCtx?.label ?? sessionId}".\n\n${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
256
- ctx.ptyService.stopSession(sessionId).catch((err) => {
428
+ ctx.ptyService.stopSession(sessionId, true).catch((err) => {
257
429
  ctx.log(`Failed to stop session after LLM-detected completion: ${err}`);
258
430
  });
431
+ checkAllTasksComplete(ctx);
259
432
  break;
260
433
  }
261
434
  case "escalate":
@@ -296,7 +469,7 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
296
469
  });
297
470
  ctx.sendChatMessage(`[${taskCtx.label}] WARNING: Auto-approved access to path outside workspace (${taskCtx.workdir}). ` + `Prompt: "${promptText.slice(0, 150)}". Stopping session for safety.`, "coding-agent");
298
471
  taskCtx.status = "error";
299
- ctx.ptyService?.stopSession(sessionId).catch((err) => {
472
+ ctx.ptyService?.stopSession(sessionId, true).catch((err) => {
300
473
  ctx.log(`Failed to stop session after out-of-scope auto-approval: ${err}`);
301
474
  });
302
475
  return;
@@ -357,10 +530,10 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
357
530
  }
358
531
  switch (ctx.getSupervisionLevel()) {
359
532
  case "autonomous":
360
- await handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, "");
533
+ await handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, "", eventData.promptInfo?.type);
361
534
  break;
362
535
  case "confirm":
363
- await handleConfirmDecision(ctx, sessionId, taskCtx, promptText, "");
536
+ await handleConfirmDecision(ctx, sessionId, taskCtx, promptText, "", eventData.promptInfo?.type);
364
537
  break;
365
538
  case "notify":
366
539
  taskCtx.decisions.push({
@@ -387,21 +560,54 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
387
560
  const raw = await fetchRecentOutput(ctx, sessionId);
388
561
  turnOutput = cleanForChat(raw);
389
562
  }
390
- const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
563
+ const agentDecisionCb = ctx.getAgentDecisionCallback();
391
564
  let decision = null;
392
- try {
393
- const result = await ctx.runtime.useModel(ModelType2.TEXT_SMALL, {
394
- prompt
395
- });
396
- decision = parseCoordinationResponse(result);
397
- } catch (err) {
398
- ctx.log(`Turn-complete LLM call failed: ${err}`);
565
+ let decisionFromPipeline = false;
566
+ const triageCtx = {
567
+ eventType: "turn_complete",
568
+ promptText: "",
569
+ recentOutput: turnOutput,
570
+ originalTask: taskCtx.originalTask
571
+ };
572
+ const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
573
+ if (tier === "routine") {
574
+ const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
575
+ try {
576
+ const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
577
+ prompt
578
+ });
579
+ decision = parseCoordinationResponse(result);
580
+ } catch (err) {
581
+ ctx.log(`Turn-complete LLM call failed: ${err}`);
582
+ }
583
+ } else {
584
+ if (agentDecisionCb) {
585
+ const eventMessage = buildTurnCompleteEventMessage(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
586
+ try {
587
+ decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
588
+ if (decision)
589
+ decisionFromPipeline = true;
590
+ } catch (err) {
591
+ ctx.log(`Agent decision callback failed for turn-complete: ${err} \u2014 falling back to small LLM`);
592
+ }
593
+ }
594
+ if (!decision) {
595
+ const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
596
+ try {
597
+ const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
598
+ prompt
599
+ });
600
+ decision = parseCoordinationResponse(result);
601
+ } catch (err) {
602
+ ctx.log(`Turn-complete LLM fallback call failed: ${err}`);
603
+ }
604
+ }
399
605
  }
400
606
  if (!decision) {
401
- ctx.log(`Turn-complete for "${taskCtx.label}": LLM invalid response \u2014 defaulting to complete`);
607
+ ctx.log(`Turn-complete for "${taskCtx.label}": all decision paths failed \u2014 escalating`);
402
608
  decision = {
403
- action: "complete",
404
- reasoning: "LLM returned invalid response \u2014 defaulting to complete"
609
+ action: "escalate",
610
+ reasoning: "All decision paths returned invalid response \u2014 escalating for human review"
405
611
  };
406
612
  }
407
613
  ctx.log(`Turn assessment for "${taskCtx.label}": ${decision.action}${decision.action === "respond" ? ` \u2192 "${(decision.response ?? "").slice(0, 80)}"` : ""} \u2014 ${decision.reasoning.slice(0, 120)}`);
@@ -422,19 +628,21 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
422
628
  reasoning: decision.reasoning
423
629
  }
424
630
  });
425
- if (decision.action === "respond") {
426
- const instruction = decision.response ?? "";
427
- const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
428
- ctx.sendChatMessage(`[${taskCtx.label}] Turn done, continuing: ${preview}`, "coding-agent");
429
- } else if (decision.action === "escalate") {
430
- ctx.sendChatMessage(`[${taskCtx.label}] Turn finished \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
631
+ if (!decisionFromPipeline) {
632
+ if (decision.action === "respond") {
633
+ const instruction = decision.response ?? "";
634
+ const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
635
+ ctx.sendChatMessage(`[${taskCtx.label}] Turn done, continuing: ${preview}`, "coding-agent");
636
+ } else if (decision.action === "escalate") {
637
+ ctx.sendChatMessage(`[${taskCtx.label}] Turn finished \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
638
+ }
431
639
  }
432
640
  await executeDecision(ctx, sessionId, decision);
433
641
  } finally {
434
642
  ctx.inFlightDecisions.delete(sessionId);
435
643
  }
436
644
  }
437
- async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, recentOutput) {
645
+ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
438
646
  if (ctx.inFlightDecisions.has(sessionId)) {
439
647
  ctx.log(`Skipping duplicate decision for ${sessionId} (in-flight)`);
440
648
  return;
@@ -445,14 +653,41 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
445
653
  if (!output) {
446
654
  output = await fetchRecentOutput(ctx, sessionId);
447
655
  }
448
- let decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
656
+ const agentDecisionCb = ctx.getAgentDecisionCallback();
657
+ let decision = null;
658
+ let decisionFromPipeline = false;
659
+ const triageCtx = {
660
+ eventType: "blocked",
661
+ promptText,
662
+ promptType,
663
+ recentOutput: output,
664
+ originalTask: taskCtx.originalTask
665
+ };
666
+ const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
667
+ if (tier === "routine") {
668
+ decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
669
+ } else {
670
+ if (agentDecisionCb) {
671
+ const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
672
+ try {
673
+ decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
674
+ if (decision)
675
+ decisionFromPipeline = true;
676
+ } catch (err) {
677
+ ctx.log(`Agent decision callback failed: ${err} \u2014 falling back to small LLM`);
678
+ }
679
+ }
680
+ if (!decision) {
681
+ decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
682
+ }
683
+ }
449
684
  if (!decision) {
450
685
  taskCtx.decisions.push({
451
686
  timestamp: Date.now(),
452
687
  event: "blocked",
453
688
  promptText,
454
689
  decision: "escalate",
455
- reasoning: "LLM returned invalid coordination response"
690
+ reasoning: "All decision paths returned invalid coordination response"
456
691
  });
457
692
  ctx.broadcast({
458
693
  type: "escalation",
@@ -494,19 +729,21 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
494
729
  reasoning: decision.reasoning
495
730
  }
496
731
  });
497
- if (decision.action === "respond") {
498
- const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
499
- const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
500
- ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc} \u2014 ${reasonExcerpt}`, "coding-agent");
501
- } else if (decision.action === "escalate") {
502
- ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
732
+ if (!decisionFromPipeline) {
733
+ if (decision.action === "respond") {
734
+ const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
735
+ const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
736
+ ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc} \u2014 ${reasonExcerpt}`, "coding-agent");
737
+ } else if (decision.action === "escalate") {
738
+ ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
739
+ }
503
740
  }
504
741
  await executeDecision(ctx, sessionId, decision);
505
742
  } finally {
506
743
  ctx.inFlightDecisions.delete(sessionId);
507
744
  }
508
745
  }
509
- async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recentOutput) {
746
+ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
510
747
  if (ctx.inFlightDecisions.has(sessionId))
511
748
  return;
512
749
  ctx.inFlightDecisions.add(sessionId);
@@ -515,7 +752,34 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
515
752
  if (!output) {
516
753
  output = await fetchRecentOutput(ctx, sessionId);
517
754
  }
518
- const decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
755
+ const agentDecisionCb = ctx.getAgentDecisionCallback();
756
+ let decision = null;
757
+ let decisionFromPipeline = false;
758
+ const triageCtx = {
759
+ eventType: "blocked",
760
+ promptText,
761
+ promptType,
762
+ recentOutput: output,
763
+ originalTask: taskCtx.originalTask
764
+ };
765
+ const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
766
+ if (tier === "routine") {
767
+ decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
768
+ } else {
769
+ if (agentDecisionCb) {
770
+ const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
771
+ try {
772
+ decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
773
+ if (decision)
774
+ decisionFromPipeline = true;
775
+ } catch (err) {
776
+ ctx.log(`Agent decision callback failed (confirm): ${err} \u2014 falling back to small LLM`);
777
+ }
778
+ }
779
+ if (!decision) {
780
+ decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
781
+ }
782
+ }
519
783
  if (!decision) {
520
784
  ctx.pendingDecisions.set(sessionId, {
521
785
  sessionId,
@@ -523,7 +787,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
523
787
  recentOutput: output,
524
788
  llmDecision: {
525
789
  action: "escalate",
526
- reasoning: "LLM returned invalid response \u2014 needs human review"
790
+ reasoning: "All decision paths returned invalid response \u2014 needs human review"
527
791
  },
528
792
  taskContext: taskCtx,
529
793
  createdAt: Date.now()
@@ -546,7 +810,8 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
546
810
  prompt: promptText,
547
811
  suggestedAction: decision?.action,
548
812
  suggestedResponse: decision?.response,
549
- reasoning: decision?.reasoning
813
+ reasoning: decision?.reasoning,
814
+ fromPipeline: decisionFromPipeline
550
815
  }
551
816
  });
552
817
  } finally {
@@ -556,6 +821,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
556
821
  var MAX_AUTO_RESPONSES = 10;
557
822
  var init_swarm_decision_loop = __esm(() => {
558
823
  init_ansi_utils();
824
+ init_swarm_event_triage();
559
825
  });
560
826
 
561
827
  // src/actions/finalize-workspace.ts
@@ -1714,6 +1980,14 @@ isBun,
1714
1980
  PTYManager,
1715
1981
  ShellAdapter
1716
1982
  } from "pty-manager";
1983
+ function forwardReadyAsTaskComplete(ctx, session) {
1984
+ if (!ctx.hasActiveTask?.(session.id) || !ctx.hasTaskActivity?.(session.id)) {
1985
+ return;
1986
+ }
1987
+ const response = ctx.taskResponseMarkers.has(session.id) ? captureTaskResponse(session.id, ctx.sessionOutputBuffers, ctx.taskResponseMarkers) : "";
1988
+ ctx.log(`session_ready for active task ${session.id} \u2014 forwarding as task_complete (stall classifier path, response: ${response.length} chars)`);
1989
+ ctx.emitEvent(session.id, "task_complete", { session, response });
1990
+ }
1717
1991
  async function initializePTYManager(ctx) {
1718
1992
  const usingBunWorker = isBun();
1719
1993
  if (usingBunWorker) {
@@ -1730,6 +2004,7 @@ async function initializePTYManager(ctx) {
1730
2004
  bunManager.on("session_ready", (session) => {
1731
2005
  ctx.log(`session_ready event received for ${session.id} (type: ${session.type}, status: ${session.status})`);
1732
2006
  ctx.emitEvent(session.id, "ready", { session });
2007
+ forwardReadyAsTaskComplete(ctx, session);
1733
2008
  });
1734
2009
  bunManager.on("session_exit", (id, code) => {
1735
2010
  ctx.emitEvent(id, "stopped", { reason: `exit code ${code}` });
@@ -1808,6 +2083,7 @@ async function initializePTYManager(ctx) {
1808
2083
  }
1809
2084
  nodeManager.on("session_ready", (session) => {
1810
2085
  ctx.emitEvent(session.id, "ready", { session });
2086
+ forwardReadyAsTaskComplete(ctx, session);
1811
2087
  });
1812
2088
  nodeManager.on("blocking_prompt", (session, promptInfo, autoResponded) => {
1813
2089
  ctx.emitEvent(session.id, "blocked", { promptInfo, autoResponded });
@@ -1875,26 +2151,40 @@ async function sendKeysToSession(ctx, sessionId, keys) {
1875
2151
  ptySession.sendKeys(keys);
1876
2152
  }
1877
2153
  }
1878
- async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log) {
1879
- const session = ctx.manager.get(sessionId);
1880
- if (!session) {
1881
- throw new Error(`Session ${sessionId} not found`);
1882
- }
1883
- if (ctx.usingBunWorker) {
1884
- await ctx.manager.kill(sessionId);
1885
- } else {
1886
- await ctx.manager.stop(sessionId);
1887
- }
1888
- const unsubscribe = ctx.outputUnsubscribers.get(sessionId);
1889
- if (unsubscribe) {
1890
- unsubscribe();
2154
+ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log, force = false) {
2155
+ try {
2156
+ const session = ctx.manager.get(sessionId);
2157
+ if (!session) {
2158
+ throw new Error(`Session ${sessionId} not found`);
2159
+ }
2160
+ if (ctx.usingBunWorker) {
2161
+ if (force) {
2162
+ await ctx.manager.kill(sessionId, "SIGKILL");
2163
+ } else {
2164
+ await ctx.manager.kill(sessionId);
2165
+ }
2166
+ } else {
2167
+ if (force) {
2168
+ await ctx.manager.stop(sessionId, { force: true });
2169
+ } else {
2170
+ await ctx.manager.stop(sessionId);
2171
+ }
2172
+ }
2173
+ } finally {
2174
+ try {
2175
+ const unsubscribe = ctx.outputUnsubscribers.get(sessionId);
2176
+ if (unsubscribe) {
2177
+ unsubscribe();
2178
+ }
2179
+ } catch {
2180
+ }
1891
2181
  ctx.outputUnsubscribers.delete(sessionId);
2182
+ sessionMetadata.delete(sessionId);
2183
+ sessionWorkdirs.delete(sessionId);
2184
+ ctx.sessionOutputBuffers.delete(sessionId);
2185
+ ctx.taskResponseMarkers.delete(sessionId);
2186
+ log(`Stopped session ${sessionId}`);
1892
2187
  }
1893
- sessionMetadata.delete(sessionId);
1894
- sessionWorkdirs.delete(sessionId);
1895
- ctx.sessionOutputBuffers.delete(sessionId);
1896
- ctx.taskResponseMarkers.delete(sessionId);
1897
- log(`Stopped session ${sessionId}`);
1898
2188
  }
1899
2189
  function subscribeToOutput(ctx, sessionId, callback) {
1900
2190
  if (ctx.usingBunWorker) {
@@ -2228,12 +2518,35 @@ import {logger} from "@elizaos/core";
2228
2518
  // src/services/swarm-idle-watchdog.ts
2229
2519
  init_ansi_utils();
2230
2520
  init_swarm_decision_loop();
2231
- import {ModelType as ModelType3} from "@elizaos/core";
2521
+ import {ModelType as ModelType4} from "@elizaos/core";
2232
2522
  async function scanIdleSessions(ctx) {
2233
2523
  const now = Date.now();
2234
2524
  for (const taskCtx of ctx.tasks.values()) {
2235
2525
  if (taskCtx.status !== "active")
2236
2526
  continue;
2527
+ if (ctx.ptyService) {
2528
+ const session = ctx.ptyService.getSession(taskCtx.sessionId);
2529
+ if (!session) {
2530
+ ctx.log(`Idle watchdog: "${taskCtx.label}" \u2014 PTY session no longer exists, marking as stopped`);
2531
+ taskCtx.status = "stopped";
2532
+ taskCtx.decisions.push({
2533
+ timestamp: now,
2534
+ event: "idle_watchdog",
2535
+ promptText: "PTY session no longer exists",
2536
+ decision: "stopped",
2537
+ reasoning: "Underlying PTY process is gone (likely killed during restart)"
2538
+ });
2539
+ ctx.broadcast({
2540
+ type: "stopped",
2541
+ sessionId: taskCtx.sessionId,
2542
+ timestamp: now,
2543
+ data: { reason: "pty_session_gone" }
2544
+ });
2545
+ ctx.sendChatMessage(`[${taskCtx.label}] Session lost \u2014 the agent process is no longer running (likely killed during a restart).`, "coding-agent");
2546
+ checkAllTasksComplete(ctx);
2547
+ continue;
2548
+ }
2549
+ }
2237
2550
  const idleMs = now - taskCtx.lastActivityAt;
2238
2551
  if (idleMs < IDLE_THRESHOLD_MS)
2239
2552
  continue;
@@ -2241,7 +2554,8 @@ async function scanIdleSessions(ctx) {
2241
2554
  continue;
2242
2555
  if (ctx.ptyService) {
2243
2556
  try {
2244
- const currentOutput = await ctx.ptyService.getSessionOutput(taskCtx.sessionId, 20);
2557
+ const rawOutput = await ctx.ptyService.getSessionOutput(taskCtx.sessionId, 20);
2558
+ const currentOutput = stripAnsi(rawOutput).trim();
2245
2559
  const lastSeen = ctx.lastSeenOutput.get(taskCtx.sessionId) ?? "";
2246
2560
  ctx.lastSeenOutput.set(taskCtx.sessionId, currentOutput);
2247
2561
  if (currentOutput !== lastSeen) {
@@ -2256,17 +2570,18 @@ async function scanIdleSessions(ctx) {
2256
2570
  taskCtx.idleCheckCount++;
2257
2571
  const idleMinutes = Math.round(idleMs / 60000);
2258
2572
  ctx.log(`Idle watchdog: "${taskCtx.label}" idle for ${idleMinutes}m (check ${taskCtx.idleCheckCount}/${MAX_IDLE_CHECKS})`);
2259
- if (taskCtx.idleCheckCount > MAX_IDLE_CHECKS) {
2260
- ctx.log(`Idle watchdog: force-escalating "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
2573
+ if (taskCtx.idleCheckCount >= MAX_IDLE_CHECKS) {
2574
+ ctx.log(`Idle watchdog: force-stopping "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
2575
+ taskCtx.status = "stopped";
2261
2576
  taskCtx.decisions.push({
2262
2577
  timestamp: now,
2263
2578
  event: "idle_watchdog",
2264
2579
  promptText: `Session idle for ${idleMinutes} minutes`,
2265
- decision: "escalate",
2266
- reasoning: `Force-escalated after ${MAX_IDLE_CHECKS} idle checks with no activity`
2580
+ decision: "stopped",
2581
+ reasoning: `Force-stopped after ${MAX_IDLE_CHECKS} idle checks with no activity`
2267
2582
  });
2268
2583
  ctx.broadcast({
2269
- type: "escalation",
2584
+ type: "stopped",
2270
2585
  sessionId: taskCtx.sessionId,
2271
2586
  timestamp: now,
2272
2587
  data: {
@@ -2275,7 +2590,22 @@ async function scanIdleSessions(ctx) {
2275
2590
  idleCheckCount: taskCtx.idleCheckCount
2276
2591
  }
2277
2592
  });
2278
- ctx.sendChatMessage(`[${taskCtx.label}] Session has been idle for ${idleMinutes} minutes with no progress. Needs your attention.`, "coding-agent");
2593
+ ctx.sendChatMessage(`[${taskCtx.label}] Session stopped \u2014 idle for ${idleMinutes} minutes with no progress.`, "coding-agent");
2594
+ if (ctx.ptyService) {
2595
+ try {
2596
+ await ctx.ptyService.stopSession(taskCtx.sessionId, true);
2597
+ } catch (err) {
2598
+ ctx.log(`Idle watchdog: failed to stop session ${taskCtx.sessionId}: ${err}`);
2599
+ taskCtx.status = "error";
2600
+ ctx.broadcast({
2601
+ type: "error",
2602
+ sessionId: taskCtx.sessionId,
2603
+ timestamp: now,
2604
+ data: { message: `Failed to stop idle session: ${err}` }
2605
+ });
2606
+ }
2607
+ }
2608
+ checkAllTasksComplete(ctx);
2279
2609
  continue;
2280
2610
  }
2281
2611
  await handleIdleCheck(ctx, taskCtx, idleMinutes);
@@ -2311,7 +2641,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
2311
2641
  const prompt = buildIdleCheckPrompt(contextSummary, recentOutput, idleMinutes, taskCtx.idleCheckCount, MAX_IDLE_CHECKS, decisionHistory);
2312
2642
  let decision = null;
2313
2643
  try {
2314
- const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
2644
+ const result = await ctx.runtime.useModel(ModelType4.TEXT_SMALL, {
2315
2645
  prompt
2316
2646
  });
2317
2647
  decision = parseCoordinationResponse(result);
@@ -2362,6 +2692,7 @@ var MAX_IDLE_CHECKS = 4;
2362
2692
  // src/services/swarm-coordinator.ts
2363
2693
  var UNREGISTERED_BUFFER_MS = 2000;
2364
2694
  var IDLE_SCAN_INTERVAL_MS = 60 * 1000;
2695
+ var PAUSE_TIMEOUT_MS = 30000;
2365
2696
 
2366
2697
  class SwarmCoordinator {
2367
2698
  static serviceType = "SWARM_COORDINATOR";
@@ -2375,10 +2706,14 @@ class SwarmCoordinator {
2375
2706
  inFlightDecisions = new Set;
2376
2707
  chatCallback = null;
2377
2708
  wsBroadcast = null;
2709
+ agentDecisionCb = null;
2378
2710
  unregisteredBuffer = new Map;
2379
2711
  idleWatchdogTimer = null;
2380
2712
  lastSeenOutput = new Map;
2381
2713
  lastToolNotification = new Map;
2714
+ _paused = false;
2715
+ pauseBuffer = [];
2716
+ pauseTimeout = null;
2382
2717
  constructor(runtime) {
2383
2718
  this.runtime = runtime;
2384
2719
  }
@@ -2390,6 +2725,13 @@ class SwarmCoordinator {
2390
2725
  this.wsBroadcast = cb;
2391
2726
  this.log("WS broadcast callback wired");
2392
2727
  }
2728
+ setAgentDecisionCallback(cb) {
2729
+ this.agentDecisionCb = cb;
2730
+ this.log("Agent decision callback wired \u2014 events will route through Milaidy");
2731
+ }
2732
+ getAgentDecisionCallback() {
2733
+ return this.agentDecisionCb;
2734
+ }
2393
2735
  sendChatMessage(text, source) {
2394
2736
  if (!this.chatCallback)
2395
2737
  return;
@@ -2432,8 +2774,49 @@ class SwarmCoordinator {
2432
2774
  this.unregisteredBuffer.clear();
2433
2775
  this.lastSeenOutput.clear();
2434
2776
  this.lastToolNotification.clear();
2777
+ this.agentDecisionCb = null;
2778
+ this._paused = false;
2779
+ if (this.pauseTimeout) {
2780
+ clearTimeout(this.pauseTimeout);
2781
+ this.pauseTimeout = null;
2782
+ }
2783
+ this.pauseBuffer = [];
2435
2784
  this.log("SwarmCoordinator stopped");
2436
2785
  }
2786
+ get isPaused() {
2787
+ return this._paused;
2788
+ }
2789
+ pause() {
2790
+ if (this._paused)
2791
+ return;
2792
+ this._paused = true;
2793
+ this.log("Coordinator paused \u2014 buffering LLM decisions until user message is processed");
2794
+ this.broadcast({ type: "coordinator_paused", sessionId: "", timestamp: Date.now(), data: {} });
2795
+ this.pauseTimeout = setTimeout(() => {
2796
+ if (this._paused) {
2797
+ this.log("Coordinator auto-resuming after timeout");
2798
+ this.resume();
2799
+ }
2800
+ }, PAUSE_TIMEOUT_MS);
2801
+ }
2802
+ resume() {
2803
+ if (!this._paused)
2804
+ return;
2805
+ this._paused = false;
2806
+ if (this.pauseTimeout) {
2807
+ clearTimeout(this.pauseTimeout);
2808
+ this.pauseTimeout = null;
2809
+ }
2810
+ this.log(`Coordinator resumed \u2014 replaying ${this.pauseBuffer.length} buffered events`);
2811
+ this.broadcast({ type: "coordinator_resumed", sessionId: "", timestamp: Date.now(), data: {} });
2812
+ const buffered = [...this.pauseBuffer];
2813
+ this.pauseBuffer = [];
2814
+ for (const entry of buffered) {
2815
+ this.handleSessionEvent(entry.sessionId, entry.event, entry.data).catch((err) => {
2816
+ this.log(`Error replaying buffered event: ${err}`);
2817
+ });
2818
+ }
2819
+ }
2437
2820
  registerTask(sessionId, context) {
2438
2821
  this.tasks.set(sessionId, {
2439
2822
  sessionId,
@@ -2560,6 +2943,20 @@ class SwarmCoordinator {
2560
2943
  }
2561
2944
  taskCtx.lastActivityAt = Date.now();
2562
2945
  taskCtx.idleCheckCount = 0;
2946
+ if (this._paused && (event === "blocked" || event === "task_complete")) {
2947
+ const eventData = data;
2948
+ if (!(event === "blocked" && eventData.autoResponded)) {
2949
+ this.broadcast({
2950
+ type: event === "blocked" ? "blocked_buffered" : "turn_complete_buffered",
2951
+ sessionId,
2952
+ timestamp: Date.now(),
2953
+ data
2954
+ });
2955
+ this.pauseBuffer.push({ sessionId, event, data });
2956
+ this.log(`Buffered "${event}" for ${taskCtx.label} (coordinator paused)`);
2957
+ return;
2958
+ }
2959
+ }
2563
2960
  switch (event) {
2564
2961
  case "blocked":
2565
2962
  await handleBlocked(this, sessionId, taskCtx, data);
@@ -2584,6 +2981,7 @@ class SwarmCoordinator {
2584
2981
  });
2585
2982
  const errorMsg = data.message ?? "unknown error";
2586
2983
  this.sendChatMessage(`"${taskCtx.label}" hit an error: ${errorMsg}`, "coding-agent");
2984
+ checkAllTasksComplete(this);
2587
2985
  break;
2588
2986
  }
2589
2987
  case "stopped":
@@ -2595,6 +2993,7 @@ class SwarmCoordinator {
2595
2993
  timestamp: Date.now(),
2596
2994
  data
2597
2995
  });
2996
+ checkAllTasksComplete(this);
2598
2997
  break;
2599
2998
  case "ready":
2600
2999
  this.broadcast({
@@ -2615,6 +3014,10 @@ class SwarmCoordinator {
2615
3014
  });
2616
3015
  const toolData = data;
2617
3016
  const now = Date.now();
3017
+ const STARTUP_GRACE_MS = 1e4;
3018
+ if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
3019
+ break;
3020
+ }
2618
3021
  const lastNotif = this.lastToolNotification.get(sessionId) ?? 0;
2619
3022
  if (now - lastNotif > 30000) {
2620
3023
  this.lastToolNotification.set(sessionId, now);
@@ -2650,6 +3053,9 @@ class SwarmCoordinator {
2650
3053
  async executeDecision(sessionId, decision) {
2651
3054
  return executeDecision(this, sessionId, decision);
2652
3055
  }
3056
+ async executeEventDecision(sessionId, decision) {
3057
+ return executeDecision(this, sessionId, decision);
3058
+ }
2653
3059
  setSupervisionLevel(level) {
2654
3060
  this.supervisionLevel = level;
2655
3061
  this.broadcast({
@@ -2794,7 +3200,21 @@ class PTYService {
2794
3200
  metricsTracker: this.metricsTracker,
2795
3201
  traceEntries: this.traceEntries,
2796
3202
  maxTraceEntries: PTYService.MAX_TRACE_ENTRIES,
2797
- log: (msg) => this.log(msg)
3203
+ log: (msg) => this.log(msg),
3204
+ hasActiveTask: (sessionId) => {
3205
+ const coordinator = this.coordinator;
3206
+ if (!coordinator)
3207
+ return false;
3208
+ const taskCtx = coordinator.getTaskContext(sessionId);
3209
+ return taskCtx?.status === "active";
3210
+ },
3211
+ hasTaskActivity: (sessionId) => {
3212
+ const coordinator = this.coordinator;
3213
+ if (!coordinator)
3214
+ return false;
3215
+ const taskCtx = coordinator.getTaskContext(sessionId);
3216
+ return (taskCtx?.decisions.length ?? 0) > 0;
3217
+ }
2798
3218
  });
2799
3219
  this.manager = result.manager;
2800
3220
  this.usingBunWorker = result.usingBunWorker;
@@ -2962,10 +3382,10 @@ class PTYService {
2962
3382
  throw new Error("PTYService not initialized");
2963
3383
  return sendKeysToSession(this.ioContext(), sessionId, keys);
2964
3384
  }
2965
- async stopSession(sessionId) {
3385
+ async stopSession(sessionId, force = false) {
2966
3386
  if (!this.manager)
2967
3387
  throw new Error("PTYService not initialized");
2968
- return stopSession(this.ioContext(), sessionId, this.sessionMetadata, this.sessionWorkdirs, (msg) => this.log(msg));
3388
+ return stopSession(this.ioContext(), sessionId, this.sessionMetadata, this.sessionWorkdirs, (msg) => this.log(msg), force);
2969
3389
  }
2970
3390
  get defaultApprovalPreset() {
2971
3391
  const fromEnv = this.runtime.getSetting("PARALLAX_DEFAULT_APPROVAL_PRESET");
@@ -3435,7 +3855,7 @@ function registerSessionEvents(ptyService, runtime, sessionId, label, scratchDir
3435
3855
  text: preview ? `Agent "${label}" completed the task.\n\n${preview}` : `Agent "${label}" completed the task.`
3436
3856
  });
3437
3857
  }
3438
- ptyService.stopSession(sessionId).catch((err) => {
3858
+ ptyService.stopSession(sessionId, true).catch((err) => {
3439
3859
  logger4.warn(`[START_CODING_TASK] Failed to stop session for "${label}" after task complete: ${err}`);
3440
3860
  });
3441
3861
  }
@@ -5248,7 +5668,8 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
5248
5668
  return true;
5249
5669
  }
5250
5670
  if (method === "GET" && subPath === "/status") {
5251
- const tasks = coordinator.getAllTaskContexts();
5671
+ const allTasks = coordinator.getAllTaskContexts();
5672
+ const tasks = allTasks.filter((t) => t.status !== "stopped" && t.status !== "completed" && t.status !== "error");
5252
5673
  sendJson(res, {
5253
5674
  supervisionLevel: coordinator.getSupervisionLevel(),
5254
5675
  taskCount: tasks.length,
@@ -5652,10 +6073,12 @@ export {
5652
6073
  src_default as default,
5653
6074
  createCodingAgentRouteHandler,
5654
6075
  codingAgentPlugin,
6076
+ buildTurnCompleteEventMessage,
6077
+ buildBlockedEventMessage,
5655
6078
  SwarmCoordinator,
5656
6079
  PTYService,
5657
6080
  CodingWorkspaceService
5658
6081
  };
5659
6082
 
5660
- //# debugId=7D1565B17FF6D63964756E2164756E21
6083
+ //# debugId=BA5A6C5F800D868764756E2164756E21
5661
6084
  //# sourceMappingURL=index.js.map