@elizaos/plugin-agent-orchestrator 0.3.8 → 0.3.10
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 -1
- package/dist/api/hook-routes.d.ts +18 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +554 -126
- package/dist/index.js.map +14 -13
- package/dist/services/pty-service.d.ts +10 -0
- package/dist/services/pty-service.d.ts.map +1 -1
- package/dist/services/pty-session-io.d.ts.map +1 -1
- package/dist/services/pty-spawn.d.ts +1 -1
- package/dist/services/pty-spawn.d.ts.map +1 -1
- package/dist/services/swarm-coordinator-prompts.d.ts +23 -5
- package/dist/services/swarm-coordinator-prompts.d.ts.map +1 -1
- package/dist/services/swarm-coordinator.d.ts +51 -1
- package/dist/services/swarm-coordinator.d.ts.map +1 -1
- package/dist/services/swarm-decision-loop.d.ts.map +1 -1
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -99,7 +99,35 @@ var init_ansi_utils = __esm(() => {
|
|
|
99
99
|
});
|
|
100
100
|
|
|
101
101
|
// src/services/swarm-coordinator-prompts.ts
|
|
102
|
-
function
|
|
102
|
+
function buildSiblingSection(siblings) {
|
|
103
|
+
if (!siblings || siblings.length === 0)
|
|
104
|
+
return "";
|
|
105
|
+
return `
|
|
106
|
+
Other agents in this swarm:
|
|
107
|
+
` + siblings.map((s) => ` - [${s.status}] "${s.label}" (${s.agentType}): ${s.originalTask}`).join(`
|
|
108
|
+
`) + `
|
|
109
|
+
Use this context when the agent asks creative or architectural questions — ` + `your answer should be consistent with what sibling agents are working on.
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
function buildSharedDecisionsSection(decisions) {
|
|
113
|
+
if (!decisions || decisions.length === 0)
|
|
114
|
+
return "";
|
|
115
|
+
return `
|
|
116
|
+
Key decisions made by other agents in this swarm:
|
|
117
|
+
` + decisions.slice(-10).map((d) => ` - [${d.agentLabel}] ${d.summary}`).join(`
|
|
118
|
+
`) + `
|
|
119
|
+
Align with these decisions for consistency — don't contradict them unless the task requires it.
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
function buildSwarmContextSection(swarmContext) {
|
|
123
|
+
if (!swarmContext)
|
|
124
|
+
return "";
|
|
125
|
+
return `
|
|
126
|
+
Project context (from planning phase):
|
|
127
|
+
${swarmContext}
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
function buildCoordinationPrompt(taskCtx, promptText, recentOutput, decisionHistory, siblingTasks, sharedDecisions, swarmContext) {
|
|
103
131
|
const historySection = decisionHistory.length > 0 ? `
|
|
104
132
|
Previous decisions for this session:
|
|
105
133
|
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
@@ -110,7 +138,7 @@ ${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.
|
|
|
110
138
|
` + `Original task: "${taskCtx.originalTask}"
|
|
111
139
|
` + `Working directory: ${taskCtx.workdir}
|
|
112
140
|
` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}
|
|
113
|
-
` + historySection + `
|
|
141
|
+
` + buildSwarmContextSection(swarmContext) + buildSiblingSection(siblingTasks) + buildSharedDecisionsSection(sharedDecisions) + historySection + `
|
|
114
142
|
Recent terminal output (last 50 lines):
|
|
115
143
|
` + `---
|
|
116
144
|
${recentOutput.slice(-3000)}
|
|
@@ -139,11 +167,12 @@ ${recentOutput.slice(-3000)}
|
|
|
139
167
|
` + `- Only use "complete" if the agent confirmed it verified ALL test plan items after creating the PR.
|
|
140
168
|
` + `- If the agent is asking for information that was NOT provided in the original task ` + `(e.g. which repository to use, project requirements, credentials), ESCALATE. ` + `The coordinator does not have this information — the human must provide it.
|
|
141
169
|
` + `- When in doubt, escalate — it's better to ask the human than to make a wrong choice.
|
|
170
|
+
` + `- If the agent's output reveals a significant decision that sibling agents should know about ` + `(e.g. chose a library, designed an API shape, picked a UI pattern, established a writing style, ` + `narrowed a research scope, made any choice that affects the shared project), ` + `include "keyDecision" with a brief one-line summary. Skip this for routine tool approvals.
|
|
142
171
|
|
|
143
172
|
` + `Respond with ONLY a JSON object:
|
|
144
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
173
|
+
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
145
174
|
}
|
|
146
|
-
function buildIdleCheckPrompt(taskCtx, recentOutput, idleMinutes, idleCheckNumber, maxIdleChecks, decisionHistory) {
|
|
175
|
+
function buildIdleCheckPrompt(taskCtx, recentOutput, idleMinutes, idleCheckNumber, maxIdleChecks, decisionHistory, siblingTasks, sharedDecisions, swarmContext) {
|
|
147
176
|
const historySection = decisionHistory.length > 0 ? `
|
|
148
177
|
Previous decisions for this session:
|
|
149
178
|
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
@@ -155,7 +184,7 @@ ${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.
|
|
|
155
184
|
` + `Working directory: ${taskCtx.workdir}
|
|
156
185
|
` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}
|
|
157
186
|
` + `Idle check: ${idleCheckNumber} of ${maxIdleChecks} (session will be force-escalated after ${maxIdleChecks})
|
|
158
|
-
` + historySection + `
|
|
187
|
+
` + buildSwarmContextSection(swarmContext) + buildSiblingSection(siblingTasks) + buildSharedDecisionsSection(sharedDecisions) + historySection + `
|
|
159
188
|
Recent terminal output (last 50 lines):
|
|
160
189
|
` + `---
|
|
161
190
|
${recentOutput.slice(-3000)}
|
|
@@ -179,11 +208,12 @@ ${recentOutput.slice(-3000)}
|
|
|
179
208
|
` + `- If the output shows an error or the agent seems stuck in a loop, escalate.
|
|
180
209
|
` + `- If the agent is clearly mid-operation (build output, test runner, git operations), use "ignore".
|
|
181
210
|
` + `- On check ${idleCheckNumber} of ${maxIdleChecks} — if unsure, lean toward "respond" with a nudge rather than "complete".
|
|
211
|
+
` + `- If the agent's output reveals a significant creative or architectural decision, ` + `include "keyDecision" with a brief one-line summary.
|
|
182
212
|
|
|
183
213
|
` + `Respond with ONLY a JSON object:
|
|
184
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
214
|
+
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
185
215
|
}
|
|
186
|
-
function buildTurnCompletePrompt(taskCtx, turnOutput, decisionHistory) {
|
|
216
|
+
function buildTurnCompletePrompt(taskCtx, turnOutput, decisionHistory, siblingTasks, sharedDecisions, swarmContext) {
|
|
187
217
|
const historySection = decisionHistory.length > 0 ? `
|
|
188
218
|
Previous decisions for this session:
|
|
189
219
|
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
@@ -194,7 +224,7 @@ ${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.
|
|
|
194
224
|
` + `Original task: "${taskCtx.originalTask}"
|
|
195
225
|
` + `Working directory: ${taskCtx.workdir}
|
|
196
226
|
` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}
|
|
197
|
-
` + historySection + `
|
|
227
|
+
` + buildSwarmContextSection(swarmContext) + buildSiblingSection(siblingTasks) + buildSharedDecisionsSection(sharedDecisions) + historySection + `
|
|
198
228
|
Output from this turn:
|
|
199
229
|
` + `---
|
|
200
230
|
${turnOutput.slice(-3000)}
|
|
@@ -227,11 +257,12 @@ ${turnOutput.slice(-3000)}
|
|
|
227
257
|
` + `- Keep follow-up instructions concise and specific.
|
|
228
258
|
` + `- When asking agents to verify work, prefer CLI tools (gh, curl, cat, git diff, etc.) over ` + `browser automation. Browser tools may not be available in headless environments and can cause delays.
|
|
229
259
|
` + `- Default to "respond" — only use "complete" when you're certain ALL work is done.
|
|
260
|
+
` + `- If the agent's output reveals a significant decision that sibling agents should know about ` + `(e.g. chose a library, designed an API shape, picked a UI pattern, established a writing style, ` + `narrowed a research scope, made any choice that affects the shared project), ` + `include "keyDecision" with a brief one-line summary. Skip this for routine tool approvals.
|
|
230
261
|
|
|
231
262
|
` + `Respond with ONLY a JSON object:
|
|
232
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
263
|
+
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
233
264
|
}
|
|
234
|
-
function buildBlockedEventMessage(taskCtx, promptText, recentOutput, decisionHistory) {
|
|
265
|
+
function buildBlockedEventMessage(taskCtx, promptText, recentOutput, decisionHistory, siblingTasks, sharedDecisions, swarmContext) {
|
|
235
266
|
const historySection = decisionHistory.length > 0 ? `
|
|
236
267
|
Previous decisions:
|
|
237
268
|
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
@@ -242,7 +273,7 @@ ${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptT
|
|
|
242
273
|
` + `Task: "${taskCtx.originalTask}"
|
|
243
274
|
` + `Workdir: ${taskCtx.workdir}
|
|
244
275
|
` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}
|
|
245
|
-
` + historySection + `
|
|
276
|
+
` + buildSwarmContextSection(swarmContext) + buildSiblingSection(siblingTasks) + buildSharedDecisionsSection(sharedDecisions) + historySection + `
|
|
246
277
|
Recent terminal output:
|
|
247
278
|
---
|
|
248
279
|
${recentOutput.slice(-3000)}
|
|
@@ -263,11 +294,13 @@ ${recentOutput.slice(-3000)}
|
|
|
263
294
|
` + `- If a PR was just created, respond to review & verify test plan items before completing.
|
|
264
295
|
` + `- When in doubt, escalate.
|
|
265
296
|
|
|
297
|
+
` + `If the agent's output reveals a significant decision that sibling agents should know about, include "keyDecision" with a brief summary.
|
|
298
|
+
|
|
266
299
|
` + `Include a JSON action block at the end of your response:
|
|
267
|
-
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}
|
|
300
|
+
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}
|
|
268
301
|
` + "```";
|
|
269
302
|
}
|
|
270
|
-
function buildTurnCompleteEventMessage(taskCtx, turnOutput, decisionHistory) {
|
|
303
|
+
function buildTurnCompleteEventMessage(taskCtx, turnOutput, decisionHistory, siblingTasks, sharedDecisions, swarmContext) {
|
|
271
304
|
const historySection = decisionHistory.length > 0 ? `
|
|
272
305
|
Previous decisions:
|
|
273
306
|
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
@@ -278,7 +311,7 @@ ${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptT
|
|
|
278
311
|
` + `Task: "${taskCtx.originalTask}"
|
|
279
312
|
` + `Workdir: ${taskCtx.workdir}
|
|
280
313
|
` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}
|
|
281
|
-
` + historySection + `
|
|
314
|
+
` + buildSwarmContextSection(swarmContext) + buildSiblingSection(siblingTasks) + buildSharedDecisionsSection(sharedDecisions) + historySection + `
|
|
282
315
|
Turn output:
|
|
283
316
|
---
|
|
284
317
|
${turnOutput.slice(-3000)}
|
|
@@ -298,9 +331,10 @@ ${turnOutput.slice(-3000)}
|
|
|
298
331
|
` + `- If a PR was just created, respond to review & verify test plan items.
|
|
299
332
|
` + `- When asking agents to verify work, prefer CLI tools (gh, curl, cat, etc.) over browser automation.
|
|
300
333
|
` + `- Default to "respond" — only "complete" when certain ALL work is done.
|
|
334
|
+
` + `- If the agent's output reveals a significant creative or architectural decision, include "keyDecision" with a brief summary.
|
|
301
335
|
|
|
302
336
|
` + `Include a JSON action block at the end of your response:
|
|
303
|
-
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}
|
|
337
|
+
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}
|
|
304
338
|
` + "```";
|
|
305
339
|
}
|
|
306
340
|
function parseCoordinationResponse(llmOutput) {
|
|
@@ -326,6 +360,9 @@ function parseCoordinationResponse(llmOutput) {
|
|
|
326
360
|
return null;
|
|
327
361
|
}
|
|
328
362
|
}
|
|
363
|
+
if (typeof parsed.keyDecision === "string" && parsed.keyDecision.trim()) {
|
|
364
|
+
result.keyDecision = parsed.keyDecision.trim().slice(0, 240);
|
|
365
|
+
}
|
|
329
366
|
return result;
|
|
330
367
|
} catch {
|
|
331
368
|
return null;
|
|
@@ -471,6 +508,18 @@ __export(exports_swarm_decision_loop, {
|
|
|
471
508
|
});
|
|
472
509
|
import * as path from "node:path";
|
|
473
510
|
import { ModelType as ModelType3 } from "@elizaos/core";
|
|
511
|
+
function withTimeout(promise, ms, label) {
|
|
512
|
+
return new Promise((resolve2, reject) => {
|
|
513
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
514
|
+
promise.then((val) => {
|
|
515
|
+
clearTimeout(timer);
|
|
516
|
+
resolve2(val);
|
|
517
|
+
}, (err) => {
|
|
518
|
+
clearTimeout(timer);
|
|
519
|
+
reject(err);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
474
523
|
function toContextSummary(taskCtx) {
|
|
475
524
|
return {
|
|
476
525
|
sessionId: taskCtx.sessionId,
|
|
@@ -490,6 +539,57 @@ function toDecisionHistory(taskCtx) {
|
|
|
490
539
|
reasoning: d.reasoning
|
|
491
540
|
}));
|
|
492
541
|
}
|
|
542
|
+
function collectSiblings(ctx, currentSessionId) {
|
|
543
|
+
const siblings = [];
|
|
544
|
+
for (const [sid, task] of ctx.tasks) {
|
|
545
|
+
if (sid === currentSessionId)
|
|
546
|
+
continue;
|
|
547
|
+
siblings.push({
|
|
548
|
+
label: task.label,
|
|
549
|
+
agentType: task.agentType,
|
|
550
|
+
originalTask: task.originalTask,
|
|
551
|
+
status: task.status
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return siblings;
|
|
555
|
+
}
|
|
556
|
+
function enrichWithSharedDecisions(ctx, sessionId, response) {
|
|
557
|
+
const taskCtx = ctx.tasks.get(sessionId);
|
|
558
|
+
if (!taskCtx)
|
|
559
|
+
return { response };
|
|
560
|
+
const allDecisions = ctx.sharedDecisions;
|
|
561
|
+
const lastSeen = taskCtx.lastSeenDecisionIndex;
|
|
562
|
+
const snapshotEnd = allDecisions.length;
|
|
563
|
+
if (lastSeen >= snapshotEnd)
|
|
564
|
+
return { response };
|
|
565
|
+
if (response.length < 20) {
|
|
566
|
+
return { response };
|
|
567
|
+
}
|
|
568
|
+
const unseen = allDecisions.slice(lastSeen, snapshotEnd);
|
|
569
|
+
const contextBlock = unseen.map((d) => `[${d.agentLabel}] ${d.summary}`).join("; ");
|
|
570
|
+
return {
|
|
571
|
+
response: `${response}
|
|
572
|
+
|
|
573
|
+
(Context from other agents: ${contextBlock})`,
|
|
574
|
+
snapshotIndex: snapshotEnd
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function commitSharedDecisionIndex(ctx, sessionId, snapshotIndex) {
|
|
578
|
+
const taskCtx = ctx.tasks.get(sessionId);
|
|
579
|
+
if (taskCtx) {
|
|
580
|
+
taskCtx.lastSeenDecisionIndex = snapshotIndex;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function recordKeyDecision(ctx, agentLabel, decision) {
|
|
584
|
+
if (!decision.keyDecision)
|
|
585
|
+
return;
|
|
586
|
+
ctx.sharedDecisions.push({
|
|
587
|
+
agentLabel,
|
|
588
|
+
summary: decision.keyDecision,
|
|
589
|
+
timestamp: Date.now()
|
|
590
|
+
});
|
|
591
|
+
ctx.log(`Shared decision from "${agentLabel}": ${decision.keyDecision}`);
|
|
592
|
+
}
|
|
493
593
|
async function drainPendingTurnComplete(ctx, sessionId) {
|
|
494
594
|
if (!ctx.pendingTurnComplete.has(sessionId))
|
|
495
595
|
return;
|
|
@@ -530,8 +630,16 @@ function checkAllTasksComplete(ctx) {
|
|
|
530
630
|
return;
|
|
531
631
|
const terminalStates = new Set(["completed", "stopped", "error"]);
|
|
532
632
|
const allDone = tasks.every((t) => terminalStates.has(t.status));
|
|
533
|
-
if (!allDone)
|
|
633
|
+
if (!allDone) {
|
|
634
|
+
const statuses = tasks.map((t) => `${t.label}=${t.status}`).join(", ");
|
|
635
|
+
ctx.log(`checkAllTasksComplete: not all done yet — ${statuses}`);
|
|
534
636
|
return;
|
|
637
|
+
}
|
|
638
|
+
if (ctx.swarmCompleteNotified) {
|
|
639
|
+
ctx.log("checkAllTasksComplete: already notified — skipping");
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
ctx.swarmCompleteNotified = true;
|
|
535
643
|
const completed = tasks.filter((t) => t.status === "completed");
|
|
536
644
|
const stopped = tasks.filter((t) => t.status === "stopped");
|
|
537
645
|
const errored = tasks.filter((t) => t.status === "error");
|
|
@@ -545,7 +653,7 @@ function checkAllTasksComplete(ctx) {
|
|
|
545
653
|
if (errored.length > 0) {
|
|
546
654
|
parts.push(`${errored.length} errored`);
|
|
547
655
|
}
|
|
548
|
-
ctx.
|
|
656
|
+
ctx.log(`checkAllTasksComplete: all ${tasks.length} tasks terminal (${parts.join(", ")}) — firing swarm_complete`);
|
|
549
657
|
ctx.broadcast({
|
|
550
658
|
type: "swarm_complete",
|
|
551
659
|
sessionId: "",
|
|
@@ -557,6 +665,34 @@ function checkAllTasksComplete(ctx) {
|
|
|
557
665
|
errored: errored.length
|
|
558
666
|
}
|
|
559
667
|
});
|
|
668
|
+
const swarmCompleteCb = ctx.getSwarmCompleteCallback();
|
|
669
|
+
const sendFallbackSummary = () => {
|
|
670
|
+
ctx.sendChatMessage(`All ${tasks.length} coding agents finished (${parts.join(", ")}). Review their work when you're ready.`, "coding-agent");
|
|
671
|
+
};
|
|
672
|
+
if (swarmCompleteCb) {
|
|
673
|
+
ctx.log("checkAllTasksComplete: swarm complete callback is wired — calling synthesis");
|
|
674
|
+
const taskSummaries = tasks.map((t) => ({
|
|
675
|
+
sessionId: t.sessionId,
|
|
676
|
+
label: t.label,
|
|
677
|
+
agentType: t.agentType,
|
|
678
|
+
originalTask: t.originalTask,
|
|
679
|
+
status: t.status,
|
|
680
|
+
completionSummary: t.completionSummary ?? ""
|
|
681
|
+
}));
|
|
682
|
+
withTimeout(Promise.resolve().then(() => swarmCompleteCb({
|
|
683
|
+
tasks: taskSummaries,
|
|
684
|
+
total: tasks.length,
|
|
685
|
+
completed: completed.length,
|
|
686
|
+
stopped: stopped.length,
|
|
687
|
+
errored: errored.length
|
|
688
|
+
})), DECISION_CB_TIMEOUT_MS, "swarmCompleteCb").catch((err) => {
|
|
689
|
+
ctx.log(`Swarm complete callback failed: ${err} — falling back to generic summary`);
|
|
690
|
+
sendFallbackSummary();
|
|
691
|
+
});
|
|
692
|
+
} else {
|
|
693
|
+
ctx.log("checkAllTasksComplete: no synthesis callback — sending generic message");
|
|
694
|
+
sendFallbackSummary();
|
|
695
|
+
}
|
|
560
696
|
}
|
|
561
697
|
async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
562
698
|
if (!ctx.ptyService)
|
|
@@ -568,7 +704,7 @@ async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
|
568
704
|
}
|
|
569
705
|
}
|
|
570
706
|
async function makeCoordinationDecision(ctx, taskCtx, promptText, recentOutput) {
|
|
571
|
-
const prompt = buildCoordinationPrompt(toContextSummary(taskCtx), promptText, recentOutput, toDecisionHistory(taskCtx));
|
|
707
|
+
const prompt = buildCoordinationPrompt(toContextSummary(taskCtx), promptText, recentOutput, toDecisionHistory(taskCtx), collectSiblings(ctx, taskCtx.sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
572
708
|
try {
|
|
573
709
|
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
574
710
|
prompt
|
|
@@ -587,7 +723,11 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
587
723
|
if (decision.useKeys && decision.keys) {
|
|
588
724
|
await ctx.ptyService.sendKeysToSession(sessionId, decision.keys);
|
|
589
725
|
} else if (decision.response !== undefined) {
|
|
590
|
-
|
|
726
|
+
const { response: enriched, snapshotIndex } = enrichWithSharedDecisions(ctx, sessionId, decision.response);
|
|
727
|
+
await ctx.ptyService.sendToSession(sessionId, enriched);
|
|
728
|
+
if (snapshotIndex !== undefined) {
|
|
729
|
+
commitSharedDecisionIndex(ctx, sessionId, snapshotIndex);
|
|
730
|
+
}
|
|
591
731
|
}
|
|
592
732
|
break;
|
|
593
733
|
case "complete": {
|
|
@@ -606,6 +746,9 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
606
746
|
const rawOutput = await ctx.ptyService.getSessionOutput(sessionId, 50);
|
|
607
747
|
summary = extractCompletionSummary(rawOutput);
|
|
608
748
|
} catch {}
|
|
749
|
+
if (taskCtx) {
|
|
750
|
+
taskCtx.completionSummary = summary || decision.reasoning || "";
|
|
751
|
+
}
|
|
609
752
|
ctx.sendChatMessage(summary ? `Finished "${taskCtx?.label ?? sessionId}".
|
|
610
753
|
|
|
611
754
|
${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
|
|
@@ -679,7 +822,7 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
679
822
|
const count = taskCtx.autoResolvedCount;
|
|
680
823
|
if (count <= 2 || count % 5 === 0) {
|
|
681
824
|
const excerpt = promptText.length > 120 ? `${promptText.slice(0, 120)}...` : promptText;
|
|
682
|
-
ctx.
|
|
825
|
+
ctx.log(`[${taskCtx.label}] Approved: ${excerpt}`);
|
|
683
826
|
}
|
|
684
827
|
return;
|
|
685
828
|
}
|
|
@@ -745,48 +888,16 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
745
888
|
const raw = await fetchRecentOutput(ctx, sessionId);
|
|
746
889
|
turnOutput = cleanForChat(raw);
|
|
747
890
|
}
|
|
748
|
-
const agentDecisionCb = ctx.getAgentDecisionCallback();
|
|
749
891
|
let decision = null;
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
|
|
760
|
-
try {
|
|
761
|
-
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
762
|
-
prompt
|
|
763
|
-
});
|
|
764
|
-
decision = parseCoordinationResponse(result);
|
|
765
|
-
} catch (err) {
|
|
766
|
-
ctx.log(`Turn-complete LLM call failed: ${err}`);
|
|
767
|
-
}
|
|
768
|
-
} else {
|
|
769
|
-
if (agentDecisionCb) {
|
|
770
|
-
const eventMessage = buildTurnCompleteEventMessage(toContextSummary(taskCtx), turnOutput, 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 for turn-complete: ${err} — falling back to small LLM`);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
if (!decision) {
|
|
780
|
-
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
|
|
781
|
-
try {
|
|
782
|
-
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
783
|
-
prompt
|
|
784
|
-
});
|
|
785
|
-
decision = parseCoordinationResponse(result);
|
|
786
|
-
} catch (err) {
|
|
787
|
-
ctx.log(`Turn-complete LLM fallback call failed: ${err}`);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
892
|
+
const decisionFromPipeline = false;
|
|
893
|
+
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx), collectSiblings(ctx, sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
894
|
+
try {
|
|
895
|
+
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
896
|
+
prompt
|
|
897
|
+
});
|
|
898
|
+
decision = parseCoordinationResponse(result);
|
|
899
|
+
} catch (err) {
|
|
900
|
+
ctx.log(`Turn-complete LLM call failed: ${err}`);
|
|
790
901
|
}
|
|
791
902
|
if (!decision) {
|
|
792
903
|
ctx.log(`Turn-complete for "${taskCtx.label}": all decision paths failed — escalating`);
|
|
@@ -804,6 +915,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
804
915
|
response: formatDecisionResponse(decision),
|
|
805
916
|
reasoning: decision.reasoning
|
|
806
917
|
});
|
|
918
|
+
recordKeyDecision(ctx, taskCtx.label, decision);
|
|
807
919
|
ctx.broadcast({
|
|
808
920
|
type: "turn_assessment",
|
|
809
921
|
sessionId,
|
|
@@ -817,7 +929,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
817
929
|
if (decision.action === "respond") {
|
|
818
930
|
const instruction = decision.response ?? "";
|
|
819
931
|
const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
|
|
820
|
-
ctx.
|
|
932
|
+
ctx.log(`[${taskCtx.label}] Turn done, continuing: ${preview}`);
|
|
821
933
|
} else if (decision.action === "escalate") {
|
|
822
934
|
ctx.sendChatMessage(`[${taskCtx.label}] Turn finished — needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
823
935
|
}
|
|
@@ -854,9 +966,9 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
854
966
|
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
855
967
|
} else {
|
|
856
968
|
if (agentDecisionCb) {
|
|
857
|
-
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
|
|
969
|
+
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx), collectSiblings(ctx, sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
858
970
|
try {
|
|
859
|
-
decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
|
|
971
|
+
decision = await withTimeout(agentDecisionCb(eventMessage, sessionId, taskCtx), DECISION_CB_TIMEOUT_MS, "agentDecisionCb");
|
|
860
972
|
if (decision)
|
|
861
973
|
decisionFromPipeline = true;
|
|
862
974
|
} catch (err) {
|
|
@@ -902,6 +1014,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
902
1014
|
response: formatDecisionResponse(decision),
|
|
903
1015
|
reasoning: decision.reasoning
|
|
904
1016
|
});
|
|
1017
|
+
recordKeyDecision(ctx, taskCtx.label, decision);
|
|
905
1018
|
taskCtx.autoResolvedCount = 0;
|
|
906
1019
|
ctx.broadcast({
|
|
907
1020
|
type: "coordination_decision",
|
|
@@ -919,7 +1032,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
919
1032
|
if (decision.action === "respond") {
|
|
920
1033
|
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
|
|
921
1034
|
const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
|
|
922
|
-
ctx.
|
|
1035
|
+
ctx.log(`[${taskCtx.label}] ${actionDesc} — ${reasonExcerpt}`);
|
|
923
1036
|
} else if (decision.action === "escalate") {
|
|
924
1037
|
ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
925
1038
|
}
|
|
@@ -954,9 +1067,9 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
954
1067
|
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
955
1068
|
} else {
|
|
956
1069
|
if (agentDecisionCb) {
|
|
957
|
-
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
|
|
1070
|
+
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx), collectSiblings(ctx, sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
958
1071
|
try {
|
|
959
|
-
decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
|
|
1072
|
+
decision = await withTimeout(agentDecisionCb(eventMessage, sessionId, taskCtx), DECISION_CB_TIMEOUT_MS, "agentDecisionCb");
|
|
960
1073
|
if (decision)
|
|
961
1074
|
decisionFromPipeline = true;
|
|
962
1075
|
} catch (err) {
|
|
@@ -1006,7 +1119,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
1006
1119
|
await drainPendingTurnComplete(ctx, sessionId);
|
|
1007
1120
|
}
|
|
1008
1121
|
}
|
|
1009
|
-
var MAX_AUTO_RESPONSES = 10;
|
|
1122
|
+
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10;
|
|
1010
1123
|
var init_swarm_decision_loop = __esm(() => {
|
|
1011
1124
|
init_ansi_utils();
|
|
1012
1125
|
init_swarm_event_triage();
|
|
@@ -2033,8 +2146,8 @@ import {
|
|
|
2033
2146
|
} from "@elizaos/core";
|
|
2034
2147
|
|
|
2035
2148
|
// src/services/pty-service.ts
|
|
2036
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2037
|
-
import { dirname, join } from "node:path";
|
|
2149
|
+
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2150
|
+
import { dirname, join as join2 } from "node:path";
|
|
2038
2151
|
import { logger as logger2 } from "@elizaos/core";
|
|
2039
2152
|
import {
|
|
2040
2153
|
checkAdapters,
|
|
@@ -2349,6 +2462,8 @@ async function initializePTYManager(ctx) {
|
|
|
2349
2462
|
}
|
|
2350
2463
|
|
|
2351
2464
|
// src/services/pty-session-io.ts
|
|
2465
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2466
|
+
import { join } from "node:path";
|
|
2352
2467
|
async function sendToSession(ctx, sessionId, input) {
|
|
2353
2468
|
const session = ctx.manager.get(sessionId);
|
|
2354
2469
|
if (!session) {
|
|
@@ -2403,6 +2518,12 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
|
|
|
2403
2518
|
}
|
|
2404
2519
|
} catch {}
|
|
2405
2520
|
ctx.outputUnsubscribers.delete(sessionId);
|
|
2521
|
+
const workdir = sessionWorkdirs.get(sessionId);
|
|
2522
|
+
if (workdir) {
|
|
2523
|
+
try {
|
|
2524
|
+
await cleanupAgentHooks(workdir, log);
|
|
2525
|
+
} catch {}
|
|
2526
|
+
}
|
|
2406
2527
|
sessionMetadata.delete(sessionId);
|
|
2407
2528
|
sessionWorkdirs.delete(sessionId);
|
|
2408
2529
|
ctx.sessionOutputBuffers.delete(sessionId);
|
|
@@ -2410,6 +2531,28 @@ async function stopSession(ctx, sessionId, sessionMetadata, sessionWorkdirs, log
|
|
|
2410
2531
|
log(`Stopped session ${sessionId}`);
|
|
2411
2532
|
}
|
|
2412
2533
|
}
|
|
2534
|
+
async function cleanupAgentHooks(workdir, log) {
|
|
2535
|
+
const settingsPaths = [
|
|
2536
|
+
join(workdir, ".claude", "settings.json"),
|
|
2537
|
+
join(workdir, ".gemini", "settings.json")
|
|
2538
|
+
];
|
|
2539
|
+
for (const settingsPath of settingsPaths) {
|
|
2540
|
+
try {
|
|
2541
|
+
const raw = await readFile(settingsPath, "utf-8");
|
|
2542
|
+
const settings = JSON.parse(raw);
|
|
2543
|
+
if (!settings.hooks)
|
|
2544
|
+
continue;
|
|
2545
|
+
delete settings.hooks;
|
|
2546
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
2547
|
+
log(`Cleaned up hooks from ${settingsPath}`);
|
|
2548
|
+
} catch (err) {
|
|
2549
|
+
const code = err.code;
|
|
2550
|
+
if (code !== "ENOENT") {
|
|
2551
|
+
log(`Failed to clean up hooks from ${settingsPath}: ${err}`);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2413
2556
|
function subscribeToOutput(ctx, sessionId, callback) {
|
|
2414
2557
|
if (ctx.usingBunWorker) {
|
|
2415
2558
|
const unsubscribe2 = ctx.manager.onSessionData(sessionId, callback);
|
|
@@ -2521,7 +2664,7 @@ function setupDeferredTaskDelivery(ctx, session, task, agentType) {
|
|
|
2521
2664
|
taskSent = true;
|
|
2522
2665
|
if (readyTimeout)
|
|
2523
2666
|
clearTimeout(readyTimeout);
|
|
2524
|
-
ctx.markTaskDelivered
|
|
2667
|
+
ctx.markTaskDelivered(sid);
|
|
2525
2668
|
setTimeout(() => sendTaskWithRetry(0), settleMs);
|
|
2526
2669
|
if (ctx.usingBunWorker) {
|
|
2527
2670
|
ctx.manager.removeListener("session_ready", onReady);
|
|
@@ -2570,7 +2713,12 @@ function buildSpawnConfig(sessionId, options, workdir) {
|
|
|
2570
2713
|
type: options.agentType,
|
|
2571
2714
|
workdir,
|
|
2572
2715
|
inheritProcessEnv: false,
|
|
2573
|
-
env: {
|
|
2716
|
+
env: {
|
|
2717
|
+
...buildSanitizedBaseEnv(),
|
|
2718
|
+
...options.env,
|
|
2719
|
+
...modelEnv,
|
|
2720
|
+
PARALLAX_SESSION_ID: sessionId
|
|
2721
|
+
},
|
|
2574
2722
|
...options.skipAdapterAutoResponse ? { skipAdapterAutoResponse: true } : {},
|
|
2575
2723
|
adapterConfig: {
|
|
2576
2724
|
...options.credentials,
|
|
@@ -3026,7 +3174,18 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
3026
3174
|
response: d.response,
|
|
3027
3175
|
reasoning: d.reasoning
|
|
3028
3176
|
}));
|
|
3029
|
-
const
|
|
3177
|
+
const siblings = [];
|
|
3178
|
+
for (const [sid, task] of ctx.tasks) {
|
|
3179
|
+
if (sid === sessionId)
|
|
3180
|
+
continue;
|
|
3181
|
+
siblings.push({
|
|
3182
|
+
label: task.label,
|
|
3183
|
+
agentType: task.agentType,
|
|
3184
|
+
originalTask: task.originalTask,
|
|
3185
|
+
status: task.status
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
const prompt = buildIdleCheckPrompt(contextSummary, recentOutput, idleMinutes, taskCtx.idleCheckCount, MAX_IDLE_CHECKS, decisionHistory, siblings, ctx.sharedDecisions, ctx.getSwarmContext());
|
|
3030
3189
|
let decision = null;
|
|
3031
3190
|
try {
|
|
3032
3191
|
const result = await ctx.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
@@ -3062,7 +3221,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
3062
3221
|
});
|
|
3063
3222
|
if (decision.action === "complete") {} else if (decision.action === "respond") {
|
|
3064
3223
|
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : `Nudged: ${decision.response ?? ""}`;
|
|
3065
|
-
ctx.
|
|
3224
|
+
ctx.log(`[${taskCtx.label}] Idle for ${idleMinutes}m — ${actionDesc}`);
|
|
3066
3225
|
} else if (decision.action === "escalate") {
|
|
3067
3226
|
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m — needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
3068
3227
|
} else if (decision.action === "ignore") {
|
|
@@ -3093,11 +3252,15 @@ class SwarmCoordinator {
|
|
|
3093
3252
|
chatCallback = null;
|
|
3094
3253
|
wsBroadcast = null;
|
|
3095
3254
|
agentDecisionCb = null;
|
|
3255
|
+
swarmCompleteCb = null;
|
|
3096
3256
|
unregisteredBuffer = new Map;
|
|
3097
3257
|
idleWatchdogTimer = null;
|
|
3098
3258
|
lastSeenOutput = new Map;
|
|
3099
3259
|
lastToolNotification = new Map;
|
|
3100
3260
|
_paused = false;
|
|
3261
|
+
sharedDecisions = [];
|
|
3262
|
+
_swarmContext = "";
|
|
3263
|
+
swarmCompleteNotified = false;
|
|
3101
3264
|
pauseBuffer = [];
|
|
3102
3265
|
pauseTimeout = null;
|
|
3103
3266
|
constructor(runtime) {
|
|
@@ -3111,6 +3274,20 @@ class SwarmCoordinator {
|
|
|
3111
3274
|
this.wsBroadcast = cb;
|
|
3112
3275
|
this.log("WS broadcast callback wired");
|
|
3113
3276
|
}
|
|
3277
|
+
setSwarmCompleteCallback(cb) {
|
|
3278
|
+
this.swarmCompleteCb = cb;
|
|
3279
|
+
this.log("Swarm complete callback wired");
|
|
3280
|
+
}
|
|
3281
|
+
getSwarmCompleteCallback() {
|
|
3282
|
+
return this.swarmCompleteCb;
|
|
3283
|
+
}
|
|
3284
|
+
setSwarmContext(context) {
|
|
3285
|
+
this._swarmContext = context;
|
|
3286
|
+
this.log(`Swarm context set (${context.length} chars)`);
|
|
3287
|
+
}
|
|
3288
|
+
getSwarmContext() {
|
|
3289
|
+
return this._swarmContext;
|
|
3290
|
+
}
|
|
3114
3291
|
setAgentDecisionCallback(cb) {
|
|
3115
3292
|
this.agentDecisionCb = cb;
|
|
3116
3293
|
this.log("Agent decision callback wired — events will route through Milaidy");
|
|
@@ -3162,6 +3339,9 @@ class SwarmCoordinator {
|
|
|
3162
3339
|
this.lastSeenOutput.clear();
|
|
3163
3340
|
this.lastToolNotification.clear();
|
|
3164
3341
|
this.agentDecisionCb = null;
|
|
3342
|
+
this.sharedDecisions.length = 0;
|
|
3343
|
+
this._swarmContext = "";
|
|
3344
|
+
this.swarmCompleteNotified = false;
|
|
3165
3345
|
this._paused = false;
|
|
3166
3346
|
if (this.pauseTimeout) {
|
|
3167
3347
|
clearTimeout(this.pauseTimeout);
|
|
@@ -3205,6 +3385,16 @@ class SwarmCoordinator {
|
|
|
3205
3385
|
}
|
|
3206
3386
|
}
|
|
3207
3387
|
registerTask(sessionId, context) {
|
|
3388
|
+
const allPreviousTerminal = this.tasks.size === 0 || Array.from(this.tasks.values()).every((t) => t.status === "completed" || t.status === "stopped" || t.status === "error");
|
|
3389
|
+
if (allPreviousTerminal) {
|
|
3390
|
+
this.swarmCompleteNotified = false;
|
|
3391
|
+
if (this.tasks.size > 0) {
|
|
3392
|
+
this.tasks.clear();
|
|
3393
|
+
this.sharedDecisions.length = 0;
|
|
3394
|
+
this._swarmContext = "";
|
|
3395
|
+
this.log("Cleared stale swarm state for new swarm");
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3208
3398
|
this.tasks.set(sessionId, {
|
|
3209
3399
|
sessionId,
|
|
3210
3400
|
agentType: context.agentType,
|
|
@@ -3218,7 +3408,8 @@ class SwarmCoordinator {
|
|
|
3218
3408
|
registeredAt: Date.now(),
|
|
3219
3409
|
lastActivityAt: Date.now(),
|
|
3220
3410
|
idleCheckCount: 0,
|
|
3221
|
-
taskDelivered: false
|
|
3411
|
+
taskDelivered: false,
|
|
3412
|
+
lastSeenDecisionIndex: 0
|
|
3222
3413
|
});
|
|
3223
3414
|
this.broadcast({
|
|
3224
3415
|
type: "task_registered",
|
|
@@ -3373,7 +3564,9 @@ class SwarmCoordinator {
|
|
|
3373
3564
|
break;
|
|
3374
3565
|
}
|
|
3375
3566
|
case "stopped":
|
|
3376
|
-
taskCtx.status
|
|
3567
|
+
if (taskCtx.status !== "completed" && taskCtx.status !== "error") {
|
|
3568
|
+
taskCtx.status = "stopped";
|
|
3569
|
+
}
|
|
3377
3570
|
this.inFlightDecisions.delete(sessionId);
|
|
3378
3571
|
this.broadcast({
|
|
3379
3572
|
type: "stopped",
|
|
@@ -3401,6 +3594,9 @@ class SwarmCoordinator {
|
|
|
3401
3594
|
data
|
|
3402
3595
|
});
|
|
3403
3596
|
const toolData = data;
|
|
3597
|
+
if (toolData.source === "hook") {
|
|
3598
|
+
break;
|
|
3599
|
+
}
|
|
3404
3600
|
const now = Date.now();
|
|
3405
3601
|
const STARTUP_GRACE_MS = 1e4;
|
|
3406
3602
|
if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
|
|
@@ -3420,7 +3616,7 @@ class SwarmCoordinator {
|
|
|
3420
3616
|
}
|
|
3421
3617
|
} catch {}
|
|
3422
3618
|
}
|
|
3423
|
-
this.
|
|
3619
|
+
this.log(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal.`);
|
|
3424
3620
|
}
|
|
3425
3621
|
break;
|
|
3426
3622
|
}
|
|
@@ -3703,21 +3899,55 @@ class PTYService {
|
|
|
3703
3899
|
this.log(`Failed to write approval config: ${err}`);
|
|
3704
3900
|
}
|
|
3705
3901
|
}
|
|
3902
|
+
const hookUrl = `http://localhost:${this.runtime.getSetting("SERVER_PORT") ?? "2138"}/api/coding-agents/hooks`;
|
|
3706
3903
|
if (resolvedAgentType === "claude") {
|
|
3707
3904
|
try {
|
|
3708
|
-
const settingsPath =
|
|
3905
|
+
const settingsPath = join2(workdir, ".claude", "settings.json");
|
|
3709
3906
|
let settings = {};
|
|
3710
3907
|
try {
|
|
3711
|
-
settings = JSON.parse(await
|
|
3908
|
+
settings = JSON.parse(await readFile2(settingsPath, "utf-8"));
|
|
3712
3909
|
} catch {}
|
|
3713
3910
|
const permissions = settings.permissions ?? {};
|
|
3714
3911
|
permissions.allowedDirectories = [workdir];
|
|
3715
3912
|
settings.permissions = permissions;
|
|
3913
|
+
const adapter = this.getAdapter("claude");
|
|
3914
|
+
const hookProtocol = adapter.getHookTelemetryProtocol({
|
|
3915
|
+
httpUrl: hookUrl,
|
|
3916
|
+
sessionId
|
|
3917
|
+
});
|
|
3918
|
+
if (hookProtocol) {
|
|
3919
|
+
const existingHooks = settings.hooks ?? {};
|
|
3920
|
+
settings.hooks = { ...existingHooks, ...hookProtocol.settingsHooks };
|
|
3921
|
+
this.log(`Injecting HTTP hooks for session ${sessionId}`);
|
|
3922
|
+
}
|
|
3716
3923
|
await mkdir(dirname(settingsPath), { recursive: true });
|
|
3717
|
-
await
|
|
3924
|
+
await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
3718
3925
|
this.log(`Wrote allowedDirectories [${workdir}] to ${settingsPath}`);
|
|
3719
3926
|
} catch (err) {
|
|
3720
|
-
this.log(`Failed to write
|
|
3927
|
+
this.log(`Failed to write Claude settings: ${err}`);
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
if (resolvedAgentType === "gemini") {
|
|
3931
|
+
try {
|
|
3932
|
+
const settingsPath = join2(workdir, ".gemini", "settings.json");
|
|
3933
|
+
let settings = {};
|
|
3934
|
+
try {
|
|
3935
|
+
settings = JSON.parse(await readFile2(settingsPath, "utf-8"));
|
|
3936
|
+
} catch {}
|
|
3937
|
+
const adapter = this.getAdapter("gemini");
|
|
3938
|
+
const hookProtocol = adapter.getHookTelemetryProtocol({
|
|
3939
|
+
httpUrl: hookUrl,
|
|
3940
|
+
sessionId
|
|
3941
|
+
});
|
|
3942
|
+
if (hookProtocol) {
|
|
3943
|
+
const existingHooks = settings.hooks ?? {};
|
|
3944
|
+
settings.hooks = { ...existingHooks, ...hookProtocol.settingsHooks };
|
|
3945
|
+
this.log(`Injecting Gemini CLI hooks for session ${sessionId}`);
|
|
3946
|
+
}
|
|
3947
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
3948
|
+
await writeFile2(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
3949
|
+
} catch (err) {
|
|
3950
|
+
this.log(`Failed to write Gemini settings: ${err}`);
|
|
3721
3951
|
}
|
|
3722
3952
|
}
|
|
3723
3953
|
const spawnConfig = buildSpawnConfig(sessionId, {
|
|
@@ -3861,6 +4091,42 @@ class PTYService {
|
|
|
3861
4091
|
const session = this.getSession(sessionId);
|
|
3862
4092
|
return session?.status === "authenticating";
|
|
3863
4093
|
}
|
|
4094
|
+
findSessionIdByCwd(cwd) {
|
|
4095
|
+
for (const [sessionId, workdir] of this.sessionWorkdirs) {
|
|
4096
|
+
if (workdir === cwd)
|
|
4097
|
+
return sessionId;
|
|
4098
|
+
}
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
handleHookEvent(sessionId, event, data) {
|
|
4102
|
+
const summary = event === "tool_running" ? `tool=${data.toolName ?? "?"}` : event === "permission_approved" ? `tool=${data.tool ?? "?"}` : JSON.stringify(data);
|
|
4103
|
+
if (event === "tool_running" || event === "permission_approved") {
|
|
4104
|
+
logger2.debug(`[PTYService] Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4105
|
+
} else {
|
|
4106
|
+
this.log(`Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4107
|
+
}
|
|
4108
|
+
if (this.manager && this.usingBunWorker) {
|
|
4109
|
+
this.manager.notifyHookEvent(sessionId, event).catch((err) => logger2.debug(`[PTYService] Failed to forward hook event to session: ${err}`));
|
|
4110
|
+
}
|
|
4111
|
+
switch (event) {
|
|
4112
|
+
case "tool_running":
|
|
4113
|
+
this.emitEvent(sessionId, "tool_running", data);
|
|
4114
|
+
break;
|
|
4115
|
+
case "task_complete":
|
|
4116
|
+
this.emitEvent(sessionId, "task_complete", data);
|
|
4117
|
+
break;
|
|
4118
|
+
case "permission_approved":
|
|
4119
|
+
break;
|
|
4120
|
+
case "notification":
|
|
4121
|
+
this.emitEvent(sessionId, "message", data);
|
|
4122
|
+
break;
|
|
4123
|
+
case "session_end":
|
|
4124
|
+
this.emitEvent(sessionId, "stopped", { ...data, reason: "session_end" });
|
|
4125
|
+
break;
|
|
4126
|
+
default:
|
|
4127
|
+
break;
|
|
4128
|
+
}
|
|
4129
|
+
}
|
|
3864
4130
|
async checkAvailableAgents(types) {
|
|
3865
4131
|
const agentTypes = types ?? ["claude", "gemini", "codex", "aider"];
|
|
3866
4132
|
return checkAdapters(agentTypes);
|
|
@@ -3988,9 +4254,7 @@ class PTYService {
|
|
|
3988
4254
|
return this.metricsTracker.getAll();
|
|
3989
4255
|
}
|
|
3990
4256
|
log(message) {
|
|
3991
|
-
|
|
3992
|
-
logger2.debug(`[PTYService] ${message}`);
|
|
3993
|
-
}
|
|
4257
|
+
logger2.debug(`[PTYService] ${message}`);
|
|
3994
4258
|
}
|
|
3995
4259
|
}
|
|
3996
4260
|
|
|
@@ -4249,7 +4513,8 @@ var spawnAgentAction = {
|
|
|
4249
4513
|
|
|
4250
4514
|
// src/actions/coding-task-handlers.ts
|
|
4251
4515
|
import {
|
|
4252
|
-
logger as logger5
|
|
4516
|
+
logger as logger5,
|
|
4517
|
+
ModelType as ModelType5
|
|
4253
4518
|
} from "@elizaos/core";
|
|
4254
4519
|
|
|
4255
4520
|
// src/actions/coding-task-helpers.ts
|
|
@@ -4325,6 +4590,65 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
4325
4590
|
|
|
4326
4591
|
// src/actions/coding-task-handlers.ts
|
|
4327
4592
|
var MAX_CONCURRENT_AGENTS = 8;
|
|
4593
|
+
var KNOWN_AGENT_PREFIXES = [
|
|
4594
|
+
"claude",
|
|
4595
|
+
"claude-code",
|
|
4596
|
+
"claudecode",
|
|
4597
|
+
"codex",
|
|
4598
|
+
"openai",
|
|
4599
|
+
"gemini",
|
|
4600
|
+
"google",
|
|
4601
|
+
"aider",
|
|
4602
|
+
"pi",
|
|
4603
|
+
"pi-ai",
|
|
4604
|
+
"piai",
|
|
4605
|
+
"pi-coding-agent",
|
|
4606
|
+
"picodingagent",
|
|
4607
|
+
"shell",
|
|
4608
|
+
"bash"
|
|
4609
|
+
];
|
|
4610
|
+
function stripAgentPrefix(spec) {
|
|
4611
|
+
const colonIdx = spec.indexOf(":");
|
|
4612
|
+
if (colonIdx <= 0 || colonIdx >= 20)
|
|
4613
|
+
return spec;
|
|
4614
|
+
const prefix = spec.slice(0, colonIdx).trim().toLowerCase();
|
|
4615
|
+
if (KNOWN_AGENT_PREFIXES.includes(prefix)) {
|
|
4616
|
+
return spec.slice(colonIdx + 1).trim();
|
|
4617
|
+
}
|
|
4618
|
+
return spec;
|
|
4619
|
+
}
|
|
4620
|
+
async function generateSwarmContext(runtime, subtasks, userRequest) {
|
|
4621
|
+
const taskList = subtasks.map((t, i) => ` ${i + 1}. ${t}`).join(`
|
|
4622
|
+
`);
|
|
4623
|
+
const prompt = `You are an AI orchestrator about to launch ${subtasks.length} parallel agents. ` + `Before they start, produce a brief shared context document so all agents stay aligned.
|
|
4624
|
+
|
|
4625
|
+
` + `User's request: "${userRequest}"
|
|
4626
|
+
|
|
4627
|
+
` + `Subtasks being assigned:
|
|
4628
|
+
${taskList}
|
|
4629
|
+
|
|
4630
|
+
` + `Generate a concise shared context brief (3-8 bullet points) covering:
|
|
4631
|
+
` + `- Project intent and overall goal
|
|
4632
|
+
` + `- Key constraints or preferences from the user's request
|
|
4633
|
+
` + `- Conventions all agents should follow (naming, style, patterns, tone)
|
|
4634
|
+
` + `- How subtasks relate to each other (dependencies, shared interfaces, etc.)
|
|
4635
|
+
` + `- Any decisions that should be consistent across all agents
|
|
4636
|
+
|
|
4637
|
+
` + `Only include what's relevant — skip categories that don't apply. ` + `Be specific and actionable, not generic. Keep it under 200 words.
|
|
4638
|
+
|
|
4639
|
+
` + `Output ONLY the bullet points, no preamble.`;
|
|
4640
|
+
try {
|
|
4641
|
+
const result = await runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
4642
|
+
prompt,
|
|
4643
|
+
maxTokens: 400,
|
|
4644
|
+
temperature: 0.3
|
|
4645
|
+
});
|
|
4646
|
+
return result?.trim() || "";
|
|
4647
|
+
} catch (err) {
|
|
4648
|
+
logger5.warn(`Swarm context generation failed: ${err}`);
|
|
4649
|
+
return "";
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4328
4652
|
async function handleMultiAgent(ctx, agentsParam) {
|
|
4329
4653
|
const {
|
|
4330
4654
|
runtime,
|
|
@@ -4372,6 +4696,13 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
4372
4696
|
text: `Launching ${agentSpecs.length} agents${repo ? ` on ${repo}` : ""}...`
|
|
4373
4697
|
});
|
|
4374
4698
|
}
|
|
4699
|
+
const cleanSubtasks = agentSpecs.map(stripAgentPrefix);
|
|
4700
|
+
const userRequest = message.content?.text ?? agentsParam;
|
|
4701
|
+
const swarmContext = agentSpecs.length > 1 ? await generateSwarmContext(runtime, cleanSubtasks, userRequest) : "";
|
|
4702
|
+
if (swarmContext) {
|
|
4703
|
+
const coordinator = getCoordinator(runtime);
|
|
4704
|
+
coordinator?.setSwarmContext(swarmContext);
|
|
4705
|
+
}
|
|
4375
4706
|
const results = [];
|
|
4376
4707
|
for (const [i, spec] of agentSpecs.entries()) {
|
|
4377
4708
|
let specAgentType = defaultAgentType;
|
|
@@ -4381,51 +4712,14 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
4381
4712
|
const colonIdx = spec.indexOf(":");
|
|
4382
4713
|
if (ctx.agentSelectionStrategy !== "fixed" && colonIdx > 0 && colonIdx < 20) {
|
|
4383
4714
|
const prefix = spec.slice(0, colonIdx).trim().toLowerCase();
|
|
4384
|
-
|
|
4385
|
-
"claude",
|
|
4386
|
-
"claude-code",
|
|
4387
|
-
"claudecode",
|
|
4388
|
-
"codex",
|
|
4389
|
-
"openai",
|
|
4390
|
-
"gemini",
|
|
4391
|
-
"google",
|
|
4392
|
-
"aider",
|
|
4393
|
-
"pi",
|
|
4394
|
-
"pi-ai",
|
|
4395
|
-
"piai",
|
|
4396
|
-
"pi-coding-agent",
|
|
4397
|
-
"picodingagent",
|
|
4398
|
-
"shell",
|
|
4399
|
-
"bash"
|
|
4400
|
-
];
|
|
4401
|
-
if (knownTypes.includes(prefix)) {
|
|
4715
|
+
if (KNOWN_AGENT_PREFIXES.includes(prefix)) {
|
|
4402
4716
|
specRequestedType = prefix;
|
|
4403
4717
|
specPiRequested = isPiAgentType(prefix);
|
|
4404
4718
|
specAgentType = normalizeAgentType(prefix);
|
|
4405
4719
|
specTask = spec.slice(colonIdx + 1).trim();
|
|
4406
4720
|
}
|
|
4407
4721
|
} else if (ctx.agentSelectionStrategy === "fixed" && colonIdx > 0 && colonIdx < 20) {
|
|
4408
|
-
|
|
4409
|
-
const knownTypes = [
|
|
4410
|
-
"claude",
|
|
4411
|
-
"claude-code",
|
|
4412
|
-
"claudecode",
|
|
4413
|
-
"codex",
|
|
4414
|
-
"openai",
|
|
4415
|
-
"gemini",
|
|
4416
|
-
"google",
|
|
4417
|
-
"aider",
|
|
4418
|
-
"pi",
|
|
4419
|
-
"pi-ai",
|
|
4420
|
-
"piai",
|
|
4421
|
-
"pi-coding-agent",
|
|
4422
|
-
"picodingagent",
|
|
4423
|
-
"shell",
|
|
4424
|
-
"bash"
|
|
4425
|
-
];
|
|
4426
|
-
if (knownTypes.includes(prefix)) {
|
|
4427
|
-
specTask = spec.slice(colonIdx + 1).trim();
|
|
4428
|
-
}
|
|
4722
|
+
specTask = stripAgentPrefix(spec);
|
|
4429
4723
|
}
|
|
4430
4724
|
const specLabel = explicitLabel ? `${explicitLabel}-${i + 1}` : generateLabel(repo, specTask);
|
|
4431
4725
|
try {
|
|
@@ -4458,7 +4752,12 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
4458
4752
|
}
|
|
4459
4753
|
}
|
|
4460
4754
|
const coordinator = getCoordinator(runtime);
|
|
4461
|
-
const
|
|
4755
|
+
const taskWithContext = swarmContext ? `${specTask}
|
|
4756
|
+
|
|
4757
|
+
--- Shared Context (from project planning) ---
|
|
4758
|
+
${swarmContext}
|
|
4759
|
+
--- End Shared Context ---` : specTask;
|
|
4760
|
+
const initialTask = specPiRequested ? toPiCommand(taskWithContext) : taskWithContext;
|
|
4462
4761
|
const displayType = specPiRequested ? "pi" : specAgentType;
|
|
4463
4762
|
const session = await ptyService.spawnSession({
|
|
4464
4763
|
name: `coding-${Date.now()}-${i}`,
|
|
@@ -6247,6 +6546,132 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
6247
6546
|
return false;
|
|
6248
6547
|
}
|
|
6249
6548
|
|
|
6549
|
+
// src/api/hook-routes.ts
|
|
6550
|
+
async function handleHookRoutes(req, res, pathname, ctx) {
|
|
6551
|
+
if (pathname !== "/api/coding-agents/hooks")
|
|
6552
|
+
return false;
|
|
6553
|
+
const method = req.method?.toUpperCase();
|
|
6554
|
+
if (method !== "POST") {
|
|
6555
|
+
sendError(res, "Method not allowed", 405);
|
|
6556
|
+
return true;
|
|
6557
|
+
}
|
|
6558
|
+
if (!ctx.ptyService) {
|
|
6559
|
+
sendError(res, "PTY Service not available", 503);
|
|
6560
|
+
return true;
|
|
6561
|
+
}
|
|
6562
|
+
let body;
|
|
6563
|
+
try {
|
|
6564
|
+
body = await parseBody(req);
|
|
6565
|
+
} catch (err) {
|
|
6566
|
+
sendError(res, err instanceof Error ? err.message : "Failed to parse request body", 400);
|
|
6567
|
+
return true;
|
|
6568
|
+
}
|
|
6569
|
+
const payload = body;
|
|
6570
|
+
const eventName = payload.hook_event_name;
|
|
6571
|
+
if (!eventName) {
|
|
6572
|
+
sendError(res, "Missing hook_event_name", 400);
|
|
6573
|
+
return true;
|
|
6574
|
+
}
|
|
6575
|
+
const toolName = payload.tool_name ?? payload.toolName;
|
|
6576
|
+
const notificationType = payload.notification_type ?? payload.notificationType;
|
|
6577
|
+
const headerSessionId = req.headers["x-parallax-session-id"];
|
|
6578
|
+
const sessionId = headerSessionId ? headerSessionId : payload.cwd ? ctx.ptyService.findSessionIdByCwd(payload.cwd) : undefined;
|
|
6579
|
+
if (!sessionId) {
|
|
6580
|
+
sendJson(res, { status: "ignored", reason: "session_not_found" });
|
|
6581
|
+
return true;
|
|
6582
|
+
}
|
|
6583
|
+
switch (eventName) {
|
|
6584
|
+
case "PermissionRequest": {
|
|
6585
|
+
sendJson(res, {
|
|
6586
|
+
hookSpecificOutput: {
|
|
6587
|
+
hookEventName: "PermissionRequest",
|
|
6588
|
+
decision: { behavior: "allow" }
|
|
6589
|
+
}
|
|
6590
|
+
});
|
|
6591
|
+
ctx.ptyService.handleHookEvent(sessionId, "permission_approved", {
|
|
6592
|
+
tool: toolName
|
|
6593
|
+
});
|
|
6594
|
+
return true;
|
|
6595
|
+
}
|
|
6596
|
+
case "PreToolUse": {
|
|
6597
|
+
ctx.ptyService.handleHookEvent(sessionId, "tool_running", {
|
|
6598
|
+
toolName,
|
|
6599
|
+
source: "hook"
|
|
6600
|
+
});
|
|
6601
|
+
sendJson(res, {
|
|
6602
|
+
hookSpecificOutput: {
|
|
6603
|
+
hookEventName: "PreToolUse",
|
|
6604
|
+
permissionDecision: "allow"
|
|
6605
|
+
}
|
|
6606
|
+
});
|
|
6607
|
+
return true;
|
|
6608
|
+
}
|
|
6609
|
+
case "Stop": {
|
|
6610
|
+
ctx.ptyService.handleHookEvent(sessionId, "task_complete", {
|
|
6611
|
+
source: "hook"
|
|
6612
|
+
});
|
|
6613
|
+
sendJson(res, {});
|
|
6614
|
+
return true;
|
|
6615
|
+
}
|
|
6616
|
+
case "TaskCompleted": {
|
|
6617
|
+
ctx.ptyService.handleHookEvent(sessionId, "task_complete", {
|
|
6618
|
+
source: "hook_task_completed"
|
|
6619
|
+
});
|
|
6620
|
+
sendJson(res, {});
|
|
6621
|
+
return true;
|
|
6622
|
+
}
|
|
6623
|
+
case "BeforeTool": {
|
|
6624
|
+
ctx.ptyService.handleHookEvent(sessionId, "tool_running", {
|
|
6625
|
+
toolName,
|
|
6626
|
+
source: "gemini_hook"
|
|
6627
|
+
});
|
|
6628
|
+
sendJson(res, { decision: "allow", continue: true });
|
|
6629
|
+
return true;
|
|
6630
|
+
}
|
|
6631
|
+
case "AfterTool": {
|
|
6632
|
+
ctx.ptyService.handleHookEvent(sessionId, "notification", {
|
|
6633
|
+
type: "tool_complete",
|
|
6634
|
+
message: `Tool ${toolName ?? "unknown"} finished`
|
|
6635
|
+
});
|
|
6636
|
+
sendJson(res, { continue: true });
|
|
6637
|
+
return true;
|
|
6638
|
+
}
|
|
6639
|
+
case "AfterAgent": {
|
|
6640
|
+
ctx.ptyService.handleHookEvent(sessionId, "task_complete", {
|
|
6641
|
+
source: "gemini_hook"
|
|
6642
|
+
});
|
|
6643
|
+
sendJson(res, { continue: true });
|
|
6644
|
+
return true;
|
|
6645
|
+
}
|
|
6646
|
+
case "SessionEnd": {
|
|
6647
|
+
ctx.ptyService.handleHookEvent(sessionId, "session_end", {
|
|
6648
|
+
source: "hook"
|
|
6649
|
+
});
|
|
6650
|
+
sendJson(res, { continue: true });
|
|
6651
|
+
return true;
|
|
6652
|
+
}
|
|
6653
|
+
case "Notification": {
|
|
6654
|
+
if (notificationType === "ToolPermission") {
|
|
6655
|
+
ctx.ptyService.handleHookEvent(sessionId, "permission_approved", {
|
|
6656
|
+
tool: toolName
|
|
6657
|
+
});
|
|
6658
|
+
sendJson(res, { decision: "allow", continue: true });
|
|
6659
|
+
return true;
|
|
6660
|
+
}
|
|
6661
|
+
ctx.ptyService.handleHookEvent(sessionId, "notification", {
|
|
6662
|
+
type: notificationType,
|
|
6663
|
+
message: payload.message
|
|
6664
|
+
});
|
|
6665
|
+
sendJson(res, { continue: true });
|
|
6666
|
+
return true;
|
|
6667
|
+
}
|
|
6668
|
+
default: {
|
|
6669
|
+
sendJson(res, { status: "ignored", reason: "unknown_event" });
|
|
6670
|
+
return true;
|
|
6671
|
+
}
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
|
|
6250
6675
|
// src/api/issue-routes.ts
|
|
6251
6676
|
async function handleIssueRoutes(req, res, pathname, ctx) {
|
|
6252
6677
|
const method = req.method?.toUpperCase();
|
|
@@ -6509,6 +6934,9 @@ function sendError(res, message, status = 400) {
|
|
|
6509
6934
|
sendJson(res, { error: message }, status);
|
|
6510
6935
|
}
|
|
6511
6936
|
async function handleCodingAgentRoutes(req, res, pathname, ctx) {
|
|
6937
|
+
if (await handleHookRoutes(req, res, pathname, ctx)) {
|
|
6938
|
+
return true;
|
|
6939
|
+
}
|
|
6512
6940
|
if (await handleCoordinatorRoutes(req, res, pathname, ctx)) {
|
|
6513
6941
|
return true;
|
|
6514
6942
|
}
|
|
@@ -6578,5 +7006,5 @@ export {
|
|
|
6578
7006
|
CodingWorkspaceService
|
|
6579
7007
|
};
|
|
6580
7008
|
|
|
6581
|
-
//# debugId=
|
|
7009
|
+
//# debugId=D706621831E2790E64756E2164756E21
|
|
6582
7010
|
//# sourceMappingURL=index.js.map
|