@elizaos/plugin-agent-orchestrator 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/actions/coding-task-handlers.d.ts.map +1 -0
  2. package/dist/actions/coding-task-helpers.d.ts.map +1 -0
  3. package/dist/actions/finalize-workspace.d.ts.map +1 -0
  4. package/dist/actions/list-agents.d.ts.map +1 -0
  5. package/dist/actions/manage-issues.d.ts.map +1 -0
  6. package/dist/actions/provision-workspace.d.ts.map +1 -0
  7. package/dist/actions/send-to-agent.d.ts.map +1 -0
  8. package/dist/actions/spawn-agent.d.ts.map +1 -0
  9. package/dist/actions/start-coding-task.d.ts.map +1 -0
  10. package/dist/actions/stop-agent.d.ts.map +1 -0
  11. package/dist/api/agent-routes.d.ts.map +1 -0
  12. package/dist/api/coordinator-routes.d.ts.map +1 -0
  13. package/dist/api/issue-routes.d.ts.map +1 -0
  14. package/dist/api/routes.d.ts.map +1 -0
  15. package/dist/api/workspace-routes.d.ts.map +1 -0
  16. package/dist/{src/index.d.ts → index.d.ts} +2 -1
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +451 -49
  19. package/dist/index.js.map +12 -11
  20. package/dist/providers/action-examples.d.ts.map +1 -0
  21. package/dist/providers/active-workspace-context.d.ts.map +1 -0
  22. package/dist/services/agent-metrics.d.ts.map +1 -0
  23. package/dist/services/agent-selection.d.ts.map +1 -0
  24. package/dist/services/ansi-utils.d.ts.map +1 -0
  25. package/dist/services/pty-auto-response.d.ts.map +1 -0
  26. package/dist/{src/services → services}/pty-init.d.ts +2 -0
  27. package/dist/services/pty-init.d.ts.map +1 -0
  28. package/dist/services/pty-service.d.ts.map +1 -0
  29. package/dist/services/pty-session-io.d.ts.map +1 -0
  30. package/dist/services/pty-spawn.d.ts.map +1 -0
  31. package/dist/services/pty-types.d.ts.map +1 -0
  32. package/dist/services/stall-classifier.d.ts.map +1 -0
  33. package/dist/{src/services → services}/swarm-coordinator-prompts.d.ts +13 -0
  34. package/dist/services/swarm-coordinator-prompts.d.ts.map +1 -0
  35. package/dist/{src/services → services}/swarm-coordinator.d.ts +33 -0
  36. package/dist/services/swarm-coordinator.d.ts.map +1 -0
  37. package/dist/{src/services → services}/swarm-decision-loop.d.ts +7 -2
  38. package/dist/services/swarm-decision-loop.d.ts.map +1 -0
  39. package/dist/services/swarm-event-triage.d.ts +49 -0
  40. package/dist/services/swarm-event-triage.d.ts.map +1 -0
  41. package/dist/services/swarm-idle-watchdog.d.ts.map +1 -0
  42. package/dist/services/workspace-git-ops.d.ts.map +1 -0
  43. package/dist/services/workspace-github.d.ts.map +1 -0
  44. package/dist/services/workspace-lifecycle.d.ts.map +1 -0
  45. package/dist/services/workspace-service.d.ts.map +1 -0
  46. package/dist/services/workspace-types.d.ts.map +1 -0
  47. package/package.json +2 -2
  48. package/dist/src/actions/coding-task-handlers.d.ts.map +0 -1
  49. package/dist/src/actions/coding-task-helpers.d.ts.map +0 -1
  50. package/dist/src/actions/finalize-workspace.d.ts.map +0 -1
  51. package/dist/src/actions/list-agents.d.ts.map +0 -1
  52. package/dist/src/actions/manage-issues.d.ts.map +0 -1
  53. package/dist/src/actions/provision-workspace.d.ts.map +0 -1
  54. package/dist/src/actions/send-to-agent.d.ts.map +0 -1
  55. package/dist/src/actions/spawn-agent.d.ts.map +0 -1
  56. package/dist/src/actions/start-coding-task.d.ts.map +0 -1
  57. package/dist/src/actions/stop-agent.d.ts.map +0 -1
  58. package/dist/src/api/agent-routes.d.ts.map +0 -1
  59. package/dist/src/api/coordinator-routes.d.ts.map +0 -1
  60. package/dist/src/api/issue-routes.d.ts.map +0 -1
  61. package/dist/src/api/routes.d.ts.map +0 -1
  62. package/dist/src/api/workspace-routes.d.ts.map +0 -1
  63. package/dist/src/index.d.ts.map +0 -1
  64. package/dist/src/providers/action-examples.d.ts.map +0 -1
  65. package/dist/src/providers/active-workspace-context.d.ts.map +0 -1
  66. package/dist/src/services/agent-metrics.d.ts.map +0 -1
  67. package/dist/src/services/agent-selection.d.ts.map +0 -1
  68. package/dist/src/services/ansi-utils.d.ts.map +0 -1
  69. package/dist/src/services/pty-auto-response.d.ts.map +0 -1
  70. package/dist/src/services/pty-init.d.ts.map +0 -1
  71. package/dist/src/services/pty-service.d.ts.map +0 -1
  72. package/dist/src/services/pty-session-io.d.ts.map +0 -1
  73. package/dist/src/services/pty-spawn.d.ts.map +0 -1
  74. package/dist/src/services/pty-types.d.ts.map +0 -1
  75. package/dist/src/services/stall-classifier.d.ts.map +0 -1
  76. package/dist/src/services/swarm-coordinator-prompts.d.ts.map +0 -1
  77. package/dist/src/services/swarm-coordinator.d.ts.map +0 -1
  78. package/dist/src/services/swarm-decision-loop.d.ts.map +0 -1
  79. package/dist/src/services/swarm-idle-watchdog.d.ts.map +0 -1
  80. package/dist/src/services/workspace-git-ops.d.ts.map +0 -1
  81. package/dist/src/services/workspace-github.d.ts.map +0 -1
  82. package/dist/src/services/workspace-lifecycle.d.ts.map +0 -1
  83. package/dist/src/services/workspace-service.d.ts.map +0 -1
  84. package/dist/src/services/workspace-types.d.ts.map +0 -1
  85. package/dist/tsconfig.build.tsbuildinfo +0 -1
  86. package/dist/tsconfig.tsbuildinfo +0 -1
  87. /package/dist/{src/actions → actions}/coding-task-handlers.d.ts +0 -0
  88. /package/dist/{src/actions → actions}/coding-task-helpers.d.ts +0 -0
  89. /package/dist/{src/actions → actions}/finalize-workspace.d.ts +0 -0
  90. /package/dist/{src/actions → actions}/list-agents.d.ts +0 -0
  91. /package/dist/{src/actions → actions}/manage-issues.d.ts +0 -0
  92. /package/dist/{src/actions → actions}/provision-workspace.d.ts +0 -0
  93. /package/dist/{src/actions → actions}/send-to-agent.d.ts +0 -0
  94. /package/dist/{src/actions → actions}/spawn-agent.d.ts +0 -0
  95. /package/dist/{src/actions → actions}/start-coding-task.d.ts +0 -0
  96. /package/dist/{src/actions → actions}/stop-agent.d.ts +0 -0
  97. /package/dist/{src/api → api}/agent-routes.d.ts +0 -0
  98. /package/dist/{src/api → api}/coordinator-routes.d.ts +0 -0
  99. /package/dist/{src/api → api}/issue-routes.d.ts +0 -0
  100. /package/dist/{src/api → api}/routes.d.ts +0 -0
  101. /package/dist/{src/api → api}/workspace-routes.d.ts +0 -0
  102. /package/dist/{src/providers → providers}/action-examples.d.ts +0 -0
  103. /package/dist/{src/providers → providers}/active-workspace-context.d.ts +0 -0
  104. /package/dist/{src/services → services}/agent-metrics.d.ts +0 -0
  105. /package/dist/{src/services → services}/agent-selection.d.ts +0 -0
  106. /package/dist/{src/services → services}/ansi-utils.d.ts +0 -0
  107. /package/dist/{src/services → services}/pty-auto-response.d.ts +0 -0
  108. /package/dist/{src/services → services}/pty-service.d.ts +0 -0
  109. /package/dist/{src/services → services}/pty-session-io.d.ts +0 -0
  110. /package/dist/{src/services → services}/pty-spawn.d.ts +0 -0
  111. /package/dist/{src/services → services}/pty-types.d.ts +0 -0
  112. /package/dist/{src/services → services}/stall-classifier.d.ts +0 -0
  113. /package/dist/{src/services → services}/swarm-idle-watchdog.d.ts +0 -0
  114. /package/dist/{src/services → services}/workspace-git-ops.d.ts +0 -0
  115. /package/dist/{src/services → services}/workspace-github.d.ts +0 -0
  116. /package/dist/{src/services → services}/workspace-lifecycle.d.ts +0 -0
  117. /package/dist/{src/services → services}/workspace-service.d.ts +0 -0
  118. /package/dist/{src/services → services}/workspace-types.d.ts +0 -0
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` + `- 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` + `- 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);
@@ -256,6 +428,7 @@ async function executeDecision(ctx, sessionId, decision) {
256
428
  ctx.ptyService.stopSession(sessionId).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":
@@ -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
@@ -1730,6 +1996,11 @@ async function initializePTYManager(ctx) {
1730
1996
  bunManager.on("session_ready", (session) => {
1731
1997
  ctx.log(`session_ready event received for ${session.id} (type: ${session.type}, status: ${session.status})`);
1732
1998
  ctx.emitEvent(session.id, "ready", { session });
1999
+ if (ctx.hasActiveTask?.(session.id) && ctx.taskResponseMarkers.has(session.id)) {
2000
+ const response = captureTaskResponse(session.id, ctx.sessionOutputBuffers, ctx.taskResponseMarkers);
2001
+ ctx.log(`session_ready for active task ${session.id} \u2014 forwarding as task_complete (stall classifier path)`);
2002
+ ctx.emitEvent(session.id, "task_complete", { session, response });
2003
+ }
1733
2004
  });
1734
2005
  bunManager.on("session_exit", (id, code) => {
1735
2006
  ctx.emitEvent(id, "stopped", { reason: `exit code ${code}` });
@@ -1808,6 +2079,11 @@ async function initializePTYManager(ctx) {
1808
2079
  }
1809
2080
  nodeManager.on("session_ready", (session) => {
1810
2081
  ctx.emitEvent(session.id, "ready", { session });
2082
+ if (ctx.hasActiveTask?.(session.id) && ctx.taskResponseMarkers.has(session.id)) {
2083
+ const response = captureTaskResponse(session.id, ctx.sessionOutputBuffers, ctx.taskResponseMarkers);
2084
+ ctx.log(`session_ready for active task ${session.id} \u2014 forwarding as task_complete (stall classifier path)`);
2085
+ ctx.emitEvent(session.id, "task_complete", { session, response });
2086
+ }
1811
2087
  });
1812
2088
  nodeManager.on("blocking_prompt", (session, promptInfo, autoResponded) => {
1813
2089
  ctx.emitEvent(session.id, "blocked", { promptInfo, autoResponded });
@@ -2228,12 +2504,35 @@ import {logger} from "@elizaos/core";
2228
2504
  // src/services/swarm-idle-watchdog.ts
2229
2505
  init_ansi_utils();
2230
2506
  init_swarm_decision_loop();
2231
- import {ModelType as ModelType3} from "@elizaos/core";
2507
+ import {ModelType as ModelType4} from "@elizaos/core";
2232
2508
  async function scanIdleSessions(ctx) {
2233
2509
  const now = Date.now();
2234
2510
  for (const taskCtx of ctx.tasks.values()) {
2235
2511
  if (taskCtx.status !== "active")
2236
2512
  continue;
2513
+ if (ctx.ptyService) {
2514
+ const session = ctx.ptyService.getSession(taskCtx.sessionId);
2515
+ if (!session) {
2516
+ ctx.log(`Idle watchdog: "${taskCtx.label}" \u2014 PTY session no longer exists, marking as stopped`);
2517
+ taskCtx.status = "stopped";
2518
+ taskCtx.decisions.push({
2519
+ timestamp: now,
2520
+ event: "idle_watchdog",
2521
+ promptText: "PTY session no longer exists",
2522
+ decision: "stopped",
2523
+ reasoning: "Underlying PTY process is gone (likely killed during restart)"
2524
+ });
2525
+ ctx.broadcast({
2526
+ type: "stopped",
2527
+ sessionId: taskCtx.sessionId,
2528
+ timestamp: now,
2529
+ data: { reason: "pty_session_gone" }
2530
+ });
2531
+ ctx.sendChatMessage(`[${taskCtx.label}] Session lost \u2014 the agent process is no longer running (likely killed during a restart).`, "coding-agent");
2532
+ checkAllTasksComplete(ctx);
2533
+ continue;
2534
+ }
2535
+ }
2237
2536
  const idleMs = now - taskCtx.lastActivityAt;
2238
2537
  if (idleMs < IDLE_THRESHOLD_MS)
2239
2538
  continue;
@@ -2241,7 +2540,8 @@ async function scanIdleSessions(ctx) {
2241
2540
  continue;
2242
2541
  if (ctx.ptyService) {
2243
2542
  try {
2244
- const currentOutput = await ctx.ptyService.getSessionOutput(taskCtx.sessionId, 20);
2543
+ const rawOutput = await ctx.ptyService.getSessionOutput(taskCtx.sessionId, 20);
2544
+ const currentOutput = stripAnsi(rawOutput).trim();
2245
2545
  const lastSeen = ctx.lastSeenOutput.get(taskCtx.sessionId) ?? "";
2246
2546
  ctx.lastSeenOutput.set(taskCtx.sessionId, currentOutput);
2247
2547
  if (currentOutput !== lastSeen) {
@@ -2256,17 +2556,18 @@ async function scanIdleSessions(ctx) {
2256
2556
  taskCtx.idleCheckCount++;
2257
2557
  const idleMinutes = Math.round(idleMs / 60000);
2258
2558
  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`);
2559
+ if (taskCtx.idleCheckCount >= MAX_IDLE_CHECKS) {
2560
+ ctx.log(`Idle watchdog: force-stopping "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
2561
+ taskCtx.status = "stopped";
2261
2562
  taskCtx.decisions.push({
2262
2563
  timestamp: now,
2263
2564
  event: "idle_watchdog",
2264
2565
  promptText: `Session idle for ${idleMinutes} minutes`,
2265
- decision: "escalate",
2266
- reasoning: `Force-escalated after ${MAX_IDLE_CHECKS} idle checks with no activity`
2566
+ decision: "stopped",
2567
+ reasoning: `Force-stopped after ${MAX_IDLE_CHECKS} idle checks with no activity`
2267
2568
  });
2268
2569
  ctx.broadcast({
2269
- type: "escalation",
2570
+ type: "stopped",
2270
2571
  sessionId: taskCtx.sessionId,
2271
2572
  timestamp: now,
2272
2573
  data: {
@@ -2275,7 +2576,22 @@ async function scanIdleSessions(ctx) {
2275
2576
  idleCheckCount: taskCtx.idleCheckCount
2276
2577
  }
2277
2578
  });
2278
- ctx.sendChatMessage(`[${taskCtx.label}] Session has been idle for ${idleMinutes} minutes with no progress. Needs your attention.`, "coding-agent");
2579
+ ctx.sendChatMessage(`[${taskCtx.label}] Session stopped \u2014 idle for ${idleMinutes} minutes with no progress.`, "coding-agent");
2580
+ if (ctx.ptyService) {
2581
+ try {
2582
+ await ctx.ptyService.stopSession(taskCtx.sessionId);
2583
+ } catch (err) {
2584
+ ctx.log(`Idle watchdog: failed to stop session ${taskCtx.sessionId}: ${err}`);
2585
+ taskCtx.status = "error";
2586
+ ctx.broadcast({
2587
+ type: "error",
2588
+ sessionId: taskCtx.sessionId,
2589
+ timestamp: now,
2590
+ data: { message: `Failed to stop idle session: ${err}` }
2591
+ });
2592
+ }
2593
+ }
2594
+ checkAllTasksComplete(ctx);
2279
2595
  continue;
2280
2596
  }
2281
2597
  await handleIdleCheck(ctx, taskCtx, idleMinutes);
@@ -2311,7 +2627,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
2311
2627
  const prompt = buildIdleCheckPrompt(contextSummary, recentOutput, idleMinutes, taskCtx.idleCheckCount, MAX_IDLE_CHECKS, decisionHistory);
2312
2628
  let decision = null;
2313
2629
  try {
2314
- const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
2630
+ const result = await ctx.runtime.useModel(ModelType4.TEXT_SMALL, {
2315
2631
  prompt
2316
2632
  });
2317
2633
  decision = parseCoordinationResponse(result);
@@ -2362,6 +2678,7 @@ var MAX_IDLE_CHECKS = 4;
2362
2678
  // src/services/swarm-coordinator.ts
2363
2679
  var UNREGISTERED_BUFFER_MS = 2000;
2364
2680
  var IDLE_SCAN_INTERVAL_MS = 60 * 1000;
2681
+ var PAUSE_TIMEOUT_MS = 30000;
2365
2682
 
2366
2683
  class SwarmCoordinator {
2367
2684
  static serviceType = "SWARM_COORDINATOR";
@@ -2375,10 +2692,14 @@ class SwarmCoordinator {
2375
2692
  inFlightDecisions = new Set;
2376
2693
  chatCallback = null;
2377
2694
  wsBroadcast = null;
2695
+ agentDecisionCb = null;
2378
2696
  unregisteredBuffer = new Map;
2379
2697
  idleWatchdogTimer = null;
2380
2698
  lastSeenOutput = new Map;
2381
2699
  lastToolNotification = new Map;
2700
+ _paused = false;
2701
+ pauseBuffer = [];
2702
+ pauseTimeout = null;
2382
2703
  constructor(runtime) {
2383
2704
  this.runtime = runtime;
2384
2705
  }
@@ -2390,6 +2711,13 @@ class SwarmCoordinator {
2390
2711
  this.wsBroadcast = cb;
2391
2712
  this.log("WS broadcast callback wired");
2392
2713
  }
2714
+ setAgentDecisionCallback(cb) {
2715
+ this.agentDecisionCb = cb;
2716
+ this.log("Agent decision callback wired \u2014 events will route through Milaidy");
2717
+ }
2718
+ getAgentDecisionCallback() {
2719
+ return this.agentDecisionCb;
2720
+ }
2393
2721
  sendChatMessage(text, source) {
2394
2722
  if (!this.chatCallback)
2395
2723
  return;
@@ -2432,8 +2760,49 @@ class SwarmCoordinator {
2432
2760
  this.unregisteredBuffer.clear();
2433
2761
  this.lastSeenOutput.clear();
2434
2762
  this.lastToolNotification.clear();
2763
+ this.agentDecisionCb = null;
2764
+ this._paused = false;
2765
+ if (this.pauseTimeout) {
2766
+ clearTimeout(this.pauseTimeout);
2767
+ this.pauseTimeout = null;
2768
+ }
2769
+ this.pauseBuffer = [];
2435
2770
  this.log("SwarmCoordinator stopped");
2436
2771
  }
2772
+ get isPaused() {
2773
+ return this._paused;
2774
+ }
2775
+ pause() {
2776
+ if (this._paused)
2777
+ return;
2778
+ this._paused = true;
2779
+ this.log("Coordinator paused \u2014 buffering LLM decisions until user message is processed");
2780
+ this.broadcast({ type: "coordinator_paused", sessionId: "", timestamp: Date.now(), data: {} });
2781
+ this.pauseTimeout = setTimeout(() => {
2782
+ if (this._paused) {
2783
+ this.log("Coordinator auto-resuming after timeout");
2784
+ this.resume();
2785
+ }
2786
+ }, PAUSE_TIMEOUT_MS);
2787
+ }
2788
+ resume() {
2789
+ if (!this._paused)
2790
+ return;
2791
+ this._paused = false;
2792
+ if (this.pauseTimeout) {
2793
+ clearTimeout(this.pauseTimeout);
2794
+ this.pauseTimeout = null;
2795
+ }
2796
+ this.log(`Coordinator resumed \u2014 replaying ${this.pauseBuffer.length} buffered events`);
2797
+ this.broadcast({ type: "coordinator_resumed", sessionId: "", timestamp: Date.now(), data: {} });
2798
+ const buffered = [...this.pauseBuffer];
2799
+ this.pauseBuffer = [];
2800
+ for (const entry of buffered) {
2801
+ this.handleSessionEvent(entry.sessionId, entry.event, entry.data).catch((err) => {
2802
+ this.log(`Error replaying buffered event: ${err}`);
2803
+ });
2804
+ }
2805
+ }
2437
2806
  registerTask(sessionId, context) {
2438
2807
  this.tasks.set(sessionId, {
2439
2808
  sessionId,
@@ -2560,6 +2929,20 @@ class SwarmCoordinator {
2560
2929
  }
2561
2930
  taskCtx.lastActivityAt = Date.now();
2562
2931
  taskCtx.idleCheckCount = 0;
2932
+ if (this._paused && (event === "blocked" || event === "task_complete")) {
2933
+ const eventData = data;
2934
+ if (!(event === "blocked" && eventData.autoResponded)) {
2935
+ this.broadcast({
2936
+ type: event === "blocked" ? "blocked_buffered" : "turn_complete_buffered",
2937
+ sessionId,
2938
+ timestamp: Date.now(),
2939
+ data
2940
+ });
2941
+ this.pauseBuffer.push({ sessionId, event, data });
2942
+ this.log(`Buffered "${event}" for ${taskCtx.label} (coordinator paused)`);
2943
+ return;
2944
+ }
2945
+ }
2563
2946
  switch (event) {
2564
2947
  case "blocked":
2565
2948
  await handleBlocked(this, sessionId, taskCtx, data);
@@ -2584,6 +2967,7 @@ class SwarmCoordinator {
2584
2967
  });
2585
2968
  const errorMsg = data.message ?? "unknown error";
2586
2969
  this.sendChatMessage(`"${taskCtx.label}" hit an error: ${errorMsg}`, "coding-agent");
2970
+ checkAllTasksComplete(this);
2587
2971
  break;
2588
2972
  }
2589
2973
  case "stopped":
@@ -2595,6 +2979,7 @@ class SwarmCoordinator {
2595
2979
  timestamp: Date.now(),
2596
2980
  data
2597
2981
  });
2982
+ checkAllTasksComplete(this);
2598
2983
  break;
2599
2984
  case "ready":
2600
2985
  this.broadcast({
@@ -2615,6 +3000,10 @@ class SwarmCoordinator {
2615
3000
  });
2616
3001
  const toolData = data;
2617
3002
  const now = Date.now();
3003
+ const STARTUP_GRACE_MS = 1e4;
3004
+ if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
3005
+ break;
3006
+ }
2618
3007
  const lastNotif = this.lastToolNotification.get(sessionId) ?? 0;
2619
3008
  if (now - lastNotif > 30000) {
2620
3009
  this.lastToolNotification.set(sessionId, now);
@@ -2650,6 +3039,9 @@ class SwarmCoordinator {
2650
3039
  async executeDecision(sessionId, decision) {
2651
3040
  return executeDecision(this, sessionId, decision);
2652
3041
  }
3042
+ async executeEventDecision(sessionId, decision) {
3043
+ return executeDecision(this, sessionId, decision);
3044
+ }
2653
3045
  setSupervisionLevel(level) {
2654
3046
  this.supervisionLevel = level;
2655
3047
  this.broadcast({
@@ -2794,7 +3186,14 @@ class PTYService {
2794
3186
  metricsTracker: this.metricsTracker,
2795
3187
  traceEntries: this.traceEntries,
2796
3188
  maxTraceEntries: PTYService.MAX_TRACE_ENTRIES,
2797
- log: (msg) => this.log(msg)
3189
+ log: (msg) => this.log(msg),
3190
+ hasActiveTask: (sessionId) => {
3191
+ const coordinator = this.coordinator;
3192
+ if (!coordinator)
3193
+ return false;
3194
+ const taskCtx = coordinator.getTaskContext(sessionId);
3195
+ return taskCtx?.status === "active";
3196
+ }
2798
3197
  });
2799
3198
  this.manager = result.manager;
2800
3199
  this.usingBunWorker = result.usingBunWorker;
@@ -5248,7 +5647,8 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
5248
5647
  return true;
5249
5648
  }
5250
5649
  if (method === "GET" && subPath === "/status") {
5251
- const tasks = coordinator.getAllTaskContexts();
5650
+ const allTasks = coordinator.getAllTaskContexts();
5651
+ const tasks = allTasks.filter((t) => t.status !== "stopped" && t.status !== "completed" && t.status !== "error");
5252
5652
  sendJson(res, {
5253
5653
  supervisionLevel: coordinator.getSupervisionLevel(),
5254
5654
  taskCount: tasks.length,
@@ -5652,10 +6052,12 @@ export {
5652
6052
  src_default as default,
5653
6053
  createCodingAgentRouteHandler,
5654
6054
  codingAgentPlugin,
6055
+ buildTurnCompleteEventMessage,
6056
+ buildBlockedEventMessage,
5655
6057
  SwarmCoordinator,
5656
6058
  PTYService,
5657
6059
  CodingWorkspaceService
5658
6060
  };
5659
6061
 
5660
- //# debugId=7D1565B17FF6D63964756E2164756E21
6062
+ //# debugId=0100F6538A0A9D6264756E2164756E21
5661
6063
  //# sourceMappingURL=index.js.map