@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.
- package/dist/actions/coding-task-handlers.d.ts.map +1 -0
- package/dist/actions/coding-task-helpers.d.ts.map +1 -0
- package/dist/actions/finalize-workspace.d.ts.map +1 -0
- package/dist/actions/list-agents.d.ts.map +1 -0
- package/dist/actions/manage-issues.d.ts.map +1 -0
- package/dist/actions/provision-workspace.d.ts.map +1 -0
- package/dist/actions/send-to-agent.d.ts.map +1 -0
- package/dist/actions/spawn-agent.d.ts.map +1 -0
- package/dist/actions/start-coding-task.d.ts.map +1 -0
- package/dist/actions/stop-agent.d.ts.map +1 -0
- package/dist/api/agent-routes.d.ts.map +1 -0
- package/dist/api/coordinator-routes.d.ts.map +1 -0
- package/dist/api/issue-routes.d.ts.map +1 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/workspace-routes.d.ts.map +1 -0
- package/dist/{src/index.d.ts → index.d.ts} +2 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +451 -49
- package/dist/index.js.map +12 -11
- package/dist/providers/action-examples.d.ts.map +1 -0
- package/dist/providers/active-workspace-context.d.ts.map +1 -0
- package/dist/services/agent-metrics.d.ts.map +1 -0
- package/dist/services/agent-selection.d.ts.map +1 -0
- package/dist/services/ansi-utils.d.ts.map +1 -0
- package/dist/services/pty-auto-response.d.ts.map +1 -0
- package/dist/{src/services → services}/pty-init.d.ts +2 -0
- package/dist/services/pty-init.d.ts.map +1 -0
- package/dist/services/pty-service.d.ts.map +1 -0
- package/dist/services/pty-session-io.d.ts.map +1 -0
- package/dist/services/pty-spawn.d.ts.map +1 -0
- package/dist/services/pty-types.d.ts.map +1 -0
- package/dist/services/stall-classifier.d.ts.map +1 -0
- package/dist/{src/services → services}/swarm-coordinator-prompts.d.ts +13 -0
- package/dist/services/swarm-coordinator-prompts.d.ts.map +1 -0
- package/dist/{src/services → services}/swarm-coordinator.d.ts +33 -0
- package/dist/services/swarm-coordinator.d.ts.map +1 -0
- package/dist/{src/services → services}/swarm-decision-loop.d.ts +7 -2
- package/dist/services/swarm-decision-loop.d.ts.map +1 -0
- package/dist/services/swarm-event-triage.d.ts +49 -0
- package/dist/services/swarm-event-triage.d.ts.map +1 -0
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -0
- package/dist/services/workspace-git-ops.d.ts.map +1 -0
- package/dist/services/workspace-github.d.ts.map +1 -0
- package/dist/services/workspace-lifecycle.d.ts.map +1 -0
- package/dist/services/workspace-service.d.ts.map +1 -0
- package/dist/services/workspace-types.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/src/actions/coding-task-handlers.d.ts.map +0 -1
- package/dist/src/actions/coding-task-helpers.d.ts.map +0 -1
- package/dist/src/actions/finalize-workspace.d.ts.map +0 -1
- package/dist/src/actions/list-agents.d.ts.map +0 -1
- package/dist/src/actions/manage-issues.d.ts.map +0 -1
- package/dist/src/actions/provision-workspace.d.ts.map +0 -1
- package/dist/src/actions/send-to-agent.d.ts.map +0 -1
- package/dist/src/actions/spawn-agent.d.ts.map +0 -1
- package/dist/src/actions/start-coding-task.d.ts.map +0 -1
- package/dist/src/actions/stop-agent.d.ts.map +0 -1
- package/dist/src/api/agent-routes.d.ts.map +0 -1
- package/dist/src/api/coordinator-routes.d.ts.map +0 -1
- package/dist/src/api/issue-routes.d.ts.map +0 -1
- package/dist/src/api/routes.d.ts.map +0 -1
- package/dist/src/api/workspace-routes.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/providers/action-examples.d.ts.map +0 -1
- package/dist/src/providers/active-workspace-context.d.ts.map +0 -1
- package/dist/src/services/agent-metrics.d.ts.map +0 -1
- package/dist/src/services/agent-selection.d.ts.map +0 -1
- package/dist/src/services/ansi-utils.d.ts.map +0 -1
- package/dist/src/services/pty-auto-response.d.ts.map +0 -1
- package/dist/src/services/pty-init.d.ts.map +0 -1
- package/dist/src/services/pty-service.d.ts.map +0 -1
- package/dist/src/services/pty-session-io.d.ts.map +0 -1
- package/dist/src/services/pty-spawn.d.ts.map +0 -1
- package/dist/src/services/pty-types.d.ts.map +0 -1
- package/dist/src/services/stall-classifier.d.ts.map +0 -1
- package/dist/src/services/swarm-coordinator-prompts.d.ts.map +0 -1
- package/dist/src/services/swarm-coordinator.d.ts.map +0 -1
- package/dist/src/services/swarm-decision-loop.d.ts.map +0 -1
- package/dist/src/services/swarm-idle-watchdog.d.ts.map +0 -1
- package/dist/src/services/workspace-git-ops.d.ts.map +0 -1
- package/dist/src/services/workspace-github.d.ts.map +0 -1
- package/dist/src/services/workspace-lifecycle.d.ts.map +0 -1
- package/dist/src/services/workspace-service.d.ts.map +0 -1
- package/dist/src/services/workspace-types.d.ts.map +0 -1
- package/dist/tsconfig.build.tsbuildinfo +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- /package/dist/{src/actions → actions}/coding-task-handlers.d.ts +0 -0
- /package/dist/{src/actions → actions}/coding-task-helpers.d.ts +0 -0
- /package/dist/{src/actions → actions}/finalize-workspace.d.ts +0 -0
- /package/dist/{src/actions → actions}/list-agents.d.ts +0 -0
- /package/dist/{src/actions → actions}/manage-issues.d.ts +0 -0
- /package/dist/{src/actions → actions}/provision-workspace.d.ts +0 -0
- /package/dist/{src/actions → actions}/send-to-agent.d.ts +0 -0
- /package/dist/{src/actions → actions}/spawn-agent.d.ts +0 -0
- /package/dist/{src/actions → actions}/start-coding-task.d.ts +0 -0
- /package/dist/{src/actions → actions}/stop-agent.d.ts +0 -0
- /package/dist/{src/api → api}/agent-routes.d.ts +0 -0
- /package/dist/{src/api → api}/coordinator-routes.d.ts +0 -0
- /package/dist/{src/api → api}/issue-routes.d.ts +0 -0
- /package/dist/{src/api → api}/routes.d.ts +0 -0
- /package/dist/{src/api → api}/workspace-routes.d.ts +0 -0
- /package/dist/{src/providers → providers}/action-examples.d.ts +0 -0
- /package/dist/{src/providers → providers}/active-workspace-context.d.ts +0 -0
- /package/dist/{src/services → services}/agent-metrics.d.ts +0 -0
- /package/dist/{src/services → services}/agent-selection.d.ts +0 -0
- /package/dist/{src/services → services}/ansi-utils.d.ts +0 -0
- /package/dist/{src/services → services}/pty-auto-response.d.ts +0 -0
- /package/dist/{src/services → services}/pty-service.d.ts +0 -0
- /package/dist/{src/services → services}/pty-session-io.d.ts +0 -0
- /package/dist/{src/services → services}/pty-spawn.d.ts +0 -0
- /package/dist/{src/services → services}/pty-types.d.ts +0 -0
- /package/dist/{src/services → services}/stall-classifier.d.ts +0 -0
- /package/dist/{src/services → services}/swarm-idle-watchdog.d.ts +0 -0
- /package/dist/{src/services → services}/workspace-git-ops.d.ts +0 -0
- /package/dist/{src/services → services}/workspace-github.d.ts +0 -0
- /package/dist/{src/services → services}/workspace-lifecycle.d.ts +0 -0
- /package/dist/{src/services → services}/workspace-service.d.ts +0 -0
- /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` + `-
|
|
118
|
-
` + `-
|
|
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
|
|
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(
|
|
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
|
|
563
|
+
const agentDecisionCb = ctx.getAgentDecisionCallback();
|
|
391
564
|
let decision = null;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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}":
|
|
607
|
+
ctx.log(`Turn-complete for "${taskCtx.label}": all decision paths failed \u2014 escalating`);
|
|
402
608
|
decision = {
|
|
403
|
-
action: "
|
|
404
|
-
reasoning: "
|
|
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 (
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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: "
|
|
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 (
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
2260
|
-
ctx.log(`Idle watchdog: force-
|
|
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: "
|
|
2266
|
-
reasoning: `Force-
|
|
2566
|
+
decision: "stopped",
|
|
2567
|
+
reasoning: `Force-stopped after ${MAX_IDLE_CHECKS} idle checks with no activity`
|
|
2267
2568
|
});
|
|
2268
2569
|
ctx.broadcast({
|
|
2269
|
-
type: "
|
|
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
|
|
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(
|
|
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
|
|
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=
|
|
6062
|
+
//# debugId=0100F6538A0A9D6264756E2164756E21
|
|
5661
6063
|
//# sourceMappingURL=index.js.map
|