@elizaos/plugin-agent-orchestrator 0.3.10 → 0.3.12
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/index.js +519 -57
- package/dist/index.js.map +14 -11
- package/dist/services/debug-capture.d.ts +38 -0
- package/dist/services/debug-capture.d.ts.map +1 -0
- package/dist/services/pty-service.d.ts +13 -0
- package/dist/services/pty-service.d.ts.map +1 -1
- package/dist/services/stall-classifier.d.ts.map +1 -1
- package/dist/services/swarm-coordinator-prompts.d.ts +4 -0
- package/dist/services/swarm-coordinator-prompts.d.ts.map +1 -1
- package/dist/services/swarm-coordinator.d.ts +11 -0
- package/dist/services/swarm-coordinator.d.ts.map +1 -1
- package/dist/services/swarm-decision-loop.d.ts +6 -0
- package/dist/services/swarm-decision-loop.d.ts.map +1 -1
- package/dist/services/swarm-event-triage.d.ts.map +1 -1
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -1
- package/dist/services/trajectory-context.d.ts +70 -0
- package/dist/services/trajectory-context.d.ts.map +1 -0
- package/dist/services/trajectory-feedback.d.ts +53 -0
- package/dist/services/trajectory-feedback.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -98,15 +98,43 @@ var init_ansi_utils = __esm(() => {
|
|
|
98
98
|
STATUS_LINE = /^\s*(?:\d+[smh]\s+\d+s?\s*·|↓\s*[\d.]+k?\s*tokens|·\s*↓|esc\s+to\s+interrupt|[Uu]pdate available|ate available|Run:\s+brew|brew\s+upgrade|\d+\s+files?\s+\+\d+\s+-\d+|ctrl\+\w|\+\d+\s+lines|Wrote\s+\d+\s+lines\s+to|\?\s+for\s+shortcuts|Cooked for|Baked for|Cogitated for)/i;
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
// src/services/trajectory-context.ts
|
|
102
|
+
function setTrajectoryContext(runtime, ctx) {
|
|
103
|
+
runtime[CTX_KEY] = ctx;
|
|
104
|
+
}
|
|
105
|
+
function clearTrajectoryContext(runtime) {
|
|
106
|
+
runtime[CTX_KEY] = undefined;
|
|
107
|
+
}
|
|
108
|
+
async function withTrajectoryContext(runtime, ctx, fn) {
|
|
109
|
+
setTrajectoryContext(runtime, ctx);
|
|
110
|
+
try {
|
|
111
|
+
return await fn();
|
|
112
|
+
} finally {
|
|
113
|
+
clearTrajectoryContext(runtime);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
var CTX_KEY = "__orchestratorTrajectoryCtx";
|
|
117
|
+
|
|
101
118
|
// src/services/swarm-coordinator-prompts.ts
|
|
102
119
|
function buildSiblingSection(siblings) {
|
|
103
120
|
if (!siblings || siblings.length === 0)
|
|
104
121
|
return "";
|
|
122
|
+
const lines = siblings.map((s) => {
|
|
123
|
+
let line = ` - [${s.status}] "${s.label}" (${s.agentType}): ${s.originalTask}`;
|
|
124
|
+
if (s.completionSummary) {
|
|
125
|
+
line += `
|
|
126
|
+
Result: ${s.completionSummary}`;
|
|
127
|
+
} else if (s.lastKeyDecision) {
|
|
128
|
+
line += `
|
|
129
|
+
Latest: ${s.lastKeyDecision}`;
|
|
130
|
+
}
|
|
131
|
+
return line;
|
|
132
|
+
});
|
|
105
133
|
return `
|
|
106
134
|
Other agents in this swarm:
|
|
107
|
-
` +
|
|
135
|
+
` + lines.join(`
|
|
108
136
|
`) + `
|
|
109
|
-
Use this context when the agent asks creative or architectural questions — ` + `your answer should be consistent with what sibling agents are
|
|
137
|
+
Use this context when the agent asks creative or architectural questions — ` + `your answer should be consistent with what sibling agents are doing.
|
|
110
138
|
`;
|
|
111
139
|
}
|
|
112
140
|
function buildSharedDecisionsSection(decisions) {
|
|
@@ -168,6 +196,7 @@ ${recentOutput.slice(-3000)}
|
|
|
168
196
|
` + `- 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.
|
|
169
197
|
` + `- When in doubt, escalate — it's better to ask the human than to make a wrong choice.
|
|
170
198
|
` + `- 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.
|
|
199
|
+
` + `- Look for explicit "DECISION:" markers in the agent's output — these are the agent deliberately ` + `surfacing design choices. Always capture these as keyDecision.
|
|
171
200
|
|
|
172
201
|
` + `Respond with ONLY a JSON object:
|
|
173
202
|
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
@@ -209,6 +238,7 @@ ${recentOutput.slice(-3000)}
|
|
|
209
238
|
` + `- If the agent is clearly mid-operation (build output, test runner, git operations), use "ignore".
|
|
210
239
|
` + `- On check ${idleCheckNumber} of ${maxIdleChecks} — if unsure, lean toward "respond" with a nudge rather than "complete".
|
|
211
240
|
` + `- If the agent's output reveals a significant creative or architectural decision, ` + `include "keyDecision" with a brief one-line summary.
|
|
241
|
+
` + `- Look for explicit "DECISION:" markers in the agent's output — always capture these as keyDecision.
|
|
212
242
|
|
|
213
243
|
` + `Respond with ONLY a JSON object:
|
|
214
244
|
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
@@ -258,6 +288,7 @@ ${turnOutput.slice(-3000)}
|
|
|
258
288
|
` + `- 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.
|
|
259
289
|
` + `- Default to "respond" — only use "complete" when you're certain ALL work is done.
|
|
260
290
|
` + `- 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.
|
|
291
|
+
` + `- Look for explicit "DECISION:" markers in the agent's output — these are the agent deliberately ` + `surfacing design choices. Always capture these as keyDecision.
|
|
261
292
|
|
|
262
293
|
` + `Respond with ONLY a JSON object:
|
|
263
294
|
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}`;
|
|
@@ -295,6 +326,7 @@ ${recentOutput.slice(-3000)}
|
|
|
295
326
|
` + `- When in doubt, escalate.
|
|
296
327
|
|
|
297
328
|
` + `If the agent's output reveals a significant decision that sibling agents should know about, include "keyDecision" with a brief summary.
|
|
329
|
+
` + `Look for explicit "DECISION:" markers in the agent's output — always capture these as keyDecision.
|
|
298
330
|
|
|
299
331
|
` + `Include a JSON action block at the end of your response:
|
|
300
332
|
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}
|
|
@@ -332,6 +364,7 @@ ${turnOutput.slice(-3000)}
|
|
|
332
364
|
` + `- When asking agents to verify work, prefer CLI tools (gh, curl, cat, etc.) over browser automation.
|
|
333
365
|
` + `- Default to "respond" — only "complete" when certain ALL work is done.
|
|
334
366
|
` + `- If the agent's output reveals a significant creative or architectural decision, include "keyDecision" with a brief summary.
|
|
367
|
+
` + `- Look for explicit "DECISION:" markers in the agent's output — always capture these as keyDecision.
|
|
335
368
|
|
|
336
369
|
` + `Include a JSON action block at the end of your response:
|
|
337
370
|
` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "...", "keyDecision": "..."}
|
|
@@ -429,7 +462,7 @@ async function classifyEventTier(runtime, ctx, log) {
|
|
|
429
462
|
}
|
|
430
463
|
try {
|
|
431
464
|
const prompt = buildTriagePrompt(ctx);
|
|
432
|
-
const result = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt });
|
|
465
|
+
const result = await withTrajectoryContext(runtime, { source: "orchestrator", decisionType: "event-triage" }, () => runtime.useModel(ModelType2.TEXT_SMALL, { prompt }));
|
|
433
466
|
const tier = parseTriageResponse(result);
|
|
434
467
|
if (tier) {
|
|
435
468
|
log(`Triage: LLM → ${tier}`);
|
|
@@ -504,7 +537,8 @@ __export(exports_swarm_decision_loop, {
|
|
|
504
537
|
handleBlocked: () => handleBlocked,
|
|
505
538
|
handleAutonomousDecision: () => handleAutonomousDecision,
|
|
506
539
|
executeDecision: () => executeDecision,
|
|
507
|
-
checkAllTasksComplete: () => checkAllTasksComplete
|
|
540
|
+
checkAllTasksComplete: () => checkAllTasksComplete,
|
|
541
|
+
POST_SEND_COOLDOWN_MS: () => POST_SEND_COOLDOWN_MS
|
|
508
542
|
});
|
|
509
543
|
import * as path from "node:path";
|
|
510
544
|
import { ModelType as ModelType3 } from "@elizaos/core";
|
|
@@ -544,11 +578,28 @@ function collectSiblings(ctx, currentSessionId) {
|
|
|
544
578
|
for (const [sid, task] of ctx.tasks) {
|
|
545
579
|
if (sid === currentSessionId)
|
|
546
580
|
continue;
|
|
581
|
+
let lastKeyDecision;
|
|
582
|
+
for (let i = task.decisions.length - 1;i >= 0; i--) {
|
|
583
|
+
const d = task.decisions[i];
|
|
584
|
+
if (d.reasoning && d.decision !== "auto_resolved") {
|
|
585
|
+
lastKeyDecision = d.reasoning;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
for (let i = ctx.sharedDecisions.length - 1;i >= 0; i--) {
|
|
590
|
+
const sd = ctx.sharedDecisions[i];
|
|
591
|
+
if (sd.agentLabel === task.label) {
|
|
592
|
+
lastKeyDecision = sd.summary;
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
547
596
|
siblings.push({
|
|
548
597
|
label: task.label,
|
|
549
598
|
agentType: task.agentType,
|
|
550
599
|
originalTask: task.originalTask,
|
|
551
|
-
status: task.status
|
|
600
|
+
status: task.status,
|
|
601
|
+
lastKeyDecision,
|
|
602
|
+
completionSummary: task.completionSummary
|
|
552
603
|
});
|
|
553
604
|
}
|
|
554
605
|
return siblings;
|
|
@@ -601,6 +652,17 @@ async function drainPendingTurnComplete(ctx, sessionId) {
|
|
|
601
652
|
ctx.log(`Draining buffered turn-complete for "${taskCtx.label}"`);
|
|
602
653
|
await handleTurnComplete(ctx, sessionId, taskCtx, pendingData);
|
|
603
654
|
}
|
|
655
|
+
async function drainPendingBlocked(ctx, sessionId) {
|
|
656
|
+
if (!ctx.pendingBlocked.has(sessionId))
|
|
657
|
+
return;
|
|
658
|
+
const pendingData = ctx.pendingBlocked.get(sessionId);
|
|
659
|
+
ctx.pendingBlocked.delete(sessionId);
|
|
660
|
+
const taskCtx = ctx.tasks.get(sessionId);
|
|
661
|
+
if (!taskCtx || taskCtx.status !== "active")
|
|
662
|
+
return;
|
|
663
|
+
ctx.log(`Draining buffered blocked event for "${taskCtx.label}"`);
|
|
664
|
+
await handleBlocked(ctx, sessionId, taskCtx, pendingData);
|
|
665
|
+
}
|
|
604
666
|
function formatDecisionResponse(decision) {
|
|
605
667
|
if (decision.action !== "respond")
|
|
606
668
|
return;
|
|
@@ -706,9 +768,15 @@ async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
|
706
768
|
async function makeCoordinationDecision(ctx, taskCtx, promptText, recentOutput) {
|
|
707
769
|
const prompt = buildCoordinationPrompt(toContextSummary(taskCtx), promptText, recentOutput, toDecisionHistory(taskCtx), collectSiblings(ctx, taskCtx.sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
708
770
|
try {
|
|
709
|
-
const result = await ctx.runtime
|
|
710
|
-
|
|
711
|
-
|
|
771
|
+
const result = await withTrajectoryContext(ctx.runtime, {
|
|
772
|
+
source: "orchestrator",
|
|
773
|
+
decisionType: "coordination",
|
|
774
|
+
sessionId: taskCtx.sessionId,
|
|
775
|
+
taskLabel: taskCtx.label,
|
|
776
|
+
repo: taskCtx.repo,
|
|
777
|
+
workdir: taskCtx.workdir,
|
|
778
|
+
originalTask: taskCtx.originalTask
|
|
779
|
+
}, () => ctx.runtime.useModel(ModelType3.TEXT_SMALL, { prompt }));
|
|
712
780
|
return parseCoordinationResponse(result);
|
|
713
781
|
} catch (err) {
|
|
714
782
|
ctx.log(`LLM coordination call failed: ${err}`);
|
|
@@ -719,7 +787,8 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
719
787
|
if (!ctx.ptyService)
|
|
720
788
|
return;
|
|
721
789
|
switch (decision.action) {
|
|
722
|
-
case "respond":
|
|
790
|
+
case "respond": {
|
|
791
|
+
const taskCtx = ctx.tasks.get(sessionId);
|
|
723
792
|
if (decision.useKeys && decision.keys) {
|
|
724
793
|
await ctx.ptyService.sendKeysToSession(sessionId, decision.keys);
|
|
725
794
|
} else if (decision.response !== undefined) {
|
|
@@ -729,7 +798,10 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
729
798
|
commitSharedDecisionIndex(ctx, sessionId, snapshotIndex);
|
|
730
799
|
}
|
|
731
800
|
}
|
|
801
|
+
if (taskCtx)
|
|
802
|
+
taskCtx.lastInputSentAt = Date.now();
|
|
732
803
|
break;
|
|
804
|
+
}
|
|
733
805
|
case "complete": {
|
|
734
806
|
const taskCtx = ctx.tasks.get(sessionId);
|
|
735
807
|
if (taskCtx) {
|
|
@@ -826,6 +898,18 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
826
898
|
}
|
|
827
899
|
return;
|
|
828
900
|
}
|
|
901
|
+
const promptFingerprint = promptText.slice(0, 200);
|
|
902
|
+
if (ctx.inFlightDecisions.has(sessionId)) {
|
|
903
|
+
if (ctx.lastBlockedPromptFingerprint.get(sessionId) === promptFingerprint) {
|
|
904
|
+
ctx.log(`Skipping duplicate blocked event for ${taskCtx.label} (decision in-flight, same prompt)`);
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
ctx.log(`New blocked prompt for ${taskCtx.label} while decision in-flight — buffering`);
|
|
908
|
+
ctx.pendingBlocked.set(sessionId, data);
|
|
909
|
+
ctx.lastBlockedPromptFingerprint.set(sessionId, promptFingerprint);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
ctx.lastBlockedPromptFingerprint.set(sessionId, promptFingerprint);
|
|
829
913
|
ctx.broadcast({
|
|
830
914
|
type: "blocked",
|
|
831
915
|
sessionId,
|
|
@@ -879,6 +963,13 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
879
963
|
ctx.pendingTurnComplete.set(sessionId, data);
|
|
880
964
|
return;
|
|
881
965
|
}
|
|
966
|
+
if (taskCtx.lastInputSentAt) {
|
|
967
|
+
const elapsed = Date.now() - taskCtx.lastInputSentAt;
|
|
968
|
+
if (elapsed < POST_SEND_COOLDOWN_MS) {
|
|
969
|
+
ctx.log(`Suppressing turn-complete for "${taskCtx.label}" — ` + `${Math.round(elapsed / 1000)}s since last input (cooldown ${POST_SEND_COOLDOWN_MS / 1000}s)`);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
882
973
|
ctx.inFlightDecisions.add(sessionId);
|
|
883
974
|
try {
|
|
884
975
|
ctx.log(`Turn complete for "${taskCtx.label}" — assessing whether task is done`);
|
|
@@ -892,9 +983,15 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
892
983
|
const decisionFromPipeline = false;
|
|
893
984
|
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx), collectSiblings(ctx, sessionId), ctx.sharedDecisions, ctx.getSwarmContext());
|
|
894
985
|
try {
|
|
895
|
-
const result = await ctx.runtime
|
|
896
|
-
|
|
897
|
-
|
|
986
|
+
const result = await withTrajectoryContext(ctx.runtime, {
|
|
987
|
+
source: "orchestrator",
|
|
988
|
+
decisionType: "turn-complete",
|
|
989
|
+
sessionId,
|
|
990
|
+
taskLabel: taskCtx.label,
|
|
991
|
+
repo: taskCtx.repo,
|
|
992
|
+
workdir: taskCtx.workdir,
|
|
993
|
+
originalTask: taskCtx.originalTask
|
|
994
|
+
}, () => ctx.runtime.useModel(ModelType3.TEXT_SMALL, { prompt }));
|
|
898
995
|
decision = parseCoordinationResponse(result);
|
|
899
996
|
} catch (err) {
|
|
900
997
|
ctx.log(`Turn-complete LLM call failed: ${err}`);
|
|
@@ -938,6 +1035,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
938
1035
|
} finally {
|
|
939
1036
|
ctx.inFlightDecisions.delete(sessionId);
|
|
940
1037
|
await drainPendingTurnComplete(ctx, sessionId);
|
|
1038
|
+
await drainPendingBlocked(ctx, sessionId);
|
|
941
1039
|
}
|
|
942
1040
|
}
|
|
943
1041
|
async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
|
|
@@ -1041,6 +1139,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
1041
1139
|
} finally {
|
|
1042
1140
|
ctx.inFlightDecisions.delete(sessionId);
|
|
1043
1141
|
await drainPendingTurnComplete(ctx, sessionId);
|
|
1142
|
+
await drainPendingBlocked(ctx, sessionId);
|
|
1044
1143
|
}
|
|
1045
1144
|
}
|
|
1046
1145
|
async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
|
|
@@ -1117,9 +1216,10 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
1117
1216
|
} finally {
|
|
1118
1217
|
ctx.inFlightDecisions.delete(sessionId);
|
|
1119
1218
|
await drainPendingTurnComplete(ctx, sessionId);
|
|
1219
|
+
await drainPendingBlocked(ctx, sessionId);
|
|
1120
1220
|
}
|
|
1121
1221
|
}
|
|
1122
|
-
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10;
|
|
1222
|
+
var DECISION_CB_TIMEOUT_MS = 30000, MAX_AUTO_RESPONSES = 10, POST_SEND_COOLDOWN_MS = 15000;
|
|
1123
1223
|
var init_swarm_decision_loop = __esm(() => {
|
|
1124
1224
|
init_ansi_utils();
|
|
1125
1225
|
init_swarm_event_triage();
|
|
@@ -2142,13 +2242,13 @@ var sendToAgentAction = {
|
|
|
2142
2242
|
import * as os from "node:os";
|
|
2143
2243
|
import * as path2 from "node:path";
|
|
2144
2244
|
import {
|
|
2145
|
-
logger as
|
|
2245
|
+
logger as logger4
|
|
2146
2246
|
} from "@elizaos/core";
|
|
2147
2247
|
|
|
2148
2248
|
// src/services/pty-service.ts
|
|
2149
|
-
import { mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2249
|
+
import { appendFile, mkdir, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2150
2250
|
import { dirname, join as join2 } from "node:path";
|
|
2151
|
-
import { logger as
|
|
2251
|
+
import { logger as logger3 } from "@elizaos/core";
|
|
2152
2252
|
import {
|
|
2153
2253
|
checkAdapters,
|
|
2154
2254
|
createAdapter,
|
|
@@ -2882,9 +2982,11 @@ async function classifyStallOutput(ctx) {
|
|
|
2882
2982
|
}
|
|
2883
2983
|
try {
|
|
2884
2984
|
log(`Stall detected for ${sessionId}, asking LLM to classify...`);
|
|
2885
|
-
const result = await runtime
|
|
2886
|
-
|
|
2887
|
-
|
|
2985
|
+
const result = await withTrajectoryContext(runtime, {
|
|
2986
|
+
source: "orchestrator",
|
|
2987
|
+
decisionType: "stall-classification",
|
|
2988
|
+
sessionId
|
|
2989
|
+
}, () => runtime.useModel(ModelType.TEXT_SMALL, { prompt: systemPrompt }));
|
|
2888
2990
|
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
|
2889
2991
|
if (!jsonMatch) {
|
|
2890
2992
|
log(`Stall classification: no JSON in LLM response`);
|
|
@@ -2996,9 +3098,15 @@ async function classifyAndDecideForCoordinator(ctx) {
|
|
|
2996
3098
|
}
|
|
2997
3099
|
try {
|
|
2998
3100
|
log(`Stall detected for coordinator-managed ${sessionId}, combined classify+decide...`);
|
|
2999
|
-
const result = await runtime
|
|
3000
|
-
|
|
3001
|
-
|
|
3101
|
+
const result = await withTrajectoryContext(runtime, {
|
|
3102
|
+
source: "orchestrator",
|
|
3103
|
+
decisionType: "stall-classify-decide",
|
|
3104
|
+
sessionId,
|
|
3105
|
+
taskLabel: taskContext.label,
|
|
3106
|
+
repo: taskContext.repo,
|
|
3107
|
+
workdir: taskContext.workdir,
|
|
3108
|
+
originalTask: taskContext.originalTask
|
|
3109
|
+
}, () => runtime.useModel(ModelType.TEXT_SMALL, { prompt: systemPrompt }));
|
|
3002
3110
|
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
|
3003
3111
|
if (!jsonMatch) {
|
|
3004
3112
|
log(`Combined classify+decide: no JSON in LLM response`);
|
|
@@ -3045,6 +3153,9 @@ async function classifyAndDecideForCoordinator(ctx) {
|
|
|
3045
3153
|
}
|
|
3046
3154
|
}
|
|
3047
3155
|
|
|
3156
|
+
// src/services/pty-service.ts
|
|
3157
|
+
init_swarm_decision_loop();
|
|
3158
|
+
|
|
3048
3159
|
// src/services/swarm-coordinator.ts
|
|
3049
3160
|
init_ansi_utils();
|
|
3050
3161
|
init_swarm_decision_loop();
|
|
@@ -3188,9 +3299,15 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
3188
3299
|
const prompt = buildIdleCheckPrompt(contextSummary, recentOutput, idleMinutes, taskCtx.idleCheckCount, MAX_IDLE_CHECKS, decisionHistory, siblings, ctx.sharedDecisions, ctx.getSwarmContext());
|
|
3189
3300
|
let decision = null;
|
|
3190
3301
|
try {
|
|
3191
|
-
const result = await ctx.runtime
|
|
3192
|
-
|
|
3193
|
-
|
|
3302
|
+
const result = await withTrajectoryContext(ctx.runtime, {
|
|
3303
|
+
source: "orchestrator",
|
|
3304
|
+
decisionType: "idle-check",
|
|
3305
|
+
sessionId,
|
|
3306
|
+
taskLabel: taskCtx.label,
|
|
3307
|
+
repo: taskCtx.repo,
|
|
3308
|
+
workdir: taskCtx.workdir,
|
|
3309
|
+
originalTask: taskCtx.originalTask
|
|
3310
|
+
}, () => ctx.runtime.useModel(ModelType4.TEXT_SMALL, { prompt }));
|
|
3194
3311
|
decision = parseCoordinationResponse(result);
|
|
3195
3312
|
} catch (err) {
|
|
3196
3313
|
ctx.log(`Idle check LLM call failed: ${err}`);
|
|
@@ -3249,6 +3366,8 @@ class SwarmCoordinator {
|
|
|
3249
3366
|
pendingDecisions = new Map;
|
|
3250
3367
|
inFlightDecisions = new Set;
|
|
3251
3368
|
pendingTurnComplete = new Map;
|
|
3369
|
+
lastBlockedPromptFingerprint = new Map;
|
|
3370
|
+
pendingBlocked = new Map;
|
|
3252
3371
|
chatCallback = null;
|
|
3253
3372
|
wsBroadcast = null;
|
|
3254
3373
|
agentDecisionCb = null;
|
|
@@ -3335,6 +3454,8 @@ class SwarmCoordinator {
|
|
|
3335
3454
|
this.pendingDecisions.clear();
|
|
3336
3455
|
this.inFlightDecisions.clear();
|
|
3337
3456
|
this.pendingTurnComplete.clear();
|
|
3457
|
+
this.lastBlockedPromptFingerprint.clear();
|
|
3458
|
+
this.pendingBlocked.clear();
|
|
3338
3459
|
this.unregisteredBuffer.clear();
|
|
3339
3460
|
this.lastSeenOutput.clear();
|
|
3340
3461
|
this.lastToolNotification.clear();
|
|
@@ -3716,6 +3837,65 @@ class SwarmCoordinator {
|
|
|
3716
3837
|
}
|
|
3717
3838
|
}
|
|
3718
3839
|
|
|
3840
|
+
// src/services/debug-capture.ts
|
|
3841
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
3842
|
+
var captureManager = null;
|
|
3843
|
+
var initAttempted = false;
|
|
3844
|
+
function isDebugCaptureEnabled() {
|
|
3845
|
+
return process.env.PARALLAX_DEBUG_CAPTURE === "1";
|
|
3846
|
+
}
|
|
3847
|
+
async function ensureCaptureManager() {
|
|
3848
|
+
if (captureManager)
|
|
3849
|
+
return captureManager;
|
|
3850
|
+
if (initAttempted)
|
|
3851
|
+
return null;
|
|
3852
|
+
initAttempted = true;
|
|
3853
|
+
if (!isDebugCaptureEnabled())
|
|
3854
|
+
return null;
|
|
3855
|
+
try {
|
|
3856
|
+
const mod = await import("pty-state-capture");
|
|
3857
|
+
const { PTYStateCaptureManager } = mod;
|
|
3858
|
+
captureManager = new PTYStateCaptureManager({
|
|
3859
|
+
outputRootDir: ".parallax/pty-captures",
|
|
3860
|
+
defaultRows: 80,
|
|
3861
|
+
defaultCols: 220
|
|
3862
|
+
});
|
|
3863
|
+
logger2.info("[debug-capture] PTY state capture enabled — writing to .parallax/pty-captures/");
|
|
3864
|
+
return captureManager;
|
|
3865
|
+
} catch {
|
|
3866
|
+
logger2.debug("[debug-capture] pty-state-capture not available — capture disabled");
|
|
3867
|
+
return null;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
async function captureSessionOpen(sessionId, agentType) {
|
|
3871
|
+
const mgr = await ensureCaptureManager();
|
|
3872
|
+
if (!mgr)
|
|
3873
|
+
return;
|
|
3874
|
+
try {
|
|
3875
|
+
await mgr.openSession(sessionId, { source: agentType });
|
|
3876
|
+
} catch (err) {
|
|
3877
|
+
logger2.debug(`[debug-capture] Failed to open session ${sessionId}: ${err}`);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
async function captureFeed(sessionId, chunk, direction = "stdout") {
|
|
3881
|
+
if (!captureManager)
|
|
3882
|
+
return;
|
|
3883
|
+
try {
|
|
3884
|
+
await captureManager.feed(sessionId, chunk, direction);
|
|
3885
|
+
} catch (err) {
|
|
3886
|
+
logger2.debug(`[debug-capture] Feed error for ${sessionId}: ${err}`);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
async function captureLifecycle(sessionId, event, detail) {
|
|
3890
|
+
if (!captureManager)
|
|
3891
|
+
return;
|
|
3892
|
+
try {
|
|
3893
|
+
await captureManager.lifecycle(sessionId, event, detail);
|
|
3894
|
+
} catch (err) {
|
|
3895
|
+
logger2.debug(`[debug-capture] Lifecycle error for ${sessionId}: ${err}`);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3719
3899
|
// src/services/pty-service.ts
|
|
3720
3900
|
function getCoordinator(runtime) {
|
|
3721
3901
|
const ptyService = runtime.getService("PTY_SERVICE");
|
|
@@ -3759,16 +3939,16 @@ class PTYService {
|
|
|
3759
3939
|
const existing = servicesMap?.get?.("SWARM_COORDINATOR");
|
|
3760
3940
|
if (existing && existing.length > 0) {
|
|
3761
3941
|
service.coordinator = existing[0];
|
|
3762
|
-
|
|
3942
|
+
logger3.info("[PTYService] SwarmCoordinator already registered, skipping duplicate start");
|
|
3763
3943
|
} else {
|
|
3764
3944
|
try {
|
|
3765
3945
|
const coordinator = new SwarmCoordinator(runtime);
|
|
3766
3946
|
coordinator.start(service);
|
|
3767
3947
|
service.coordinator = coordinator;
|
|
3768
3948
|
servicesMap?.set?.("SWARM_COORDINATOR", [coordinator]);
|
|
3769
|
-
|
|
3949
|
+
logger3.info("[PTYService] SwarmCoordinator wired and started");
|
|
3770
3950
|
} catch (err) {
|
|
3771
|
-
|
|
3951
|
+
logger3.error(`[PTYService] Failed to wire SwarmCoordinator: ${err}`);
|
|
3772
3952
|
}
|
|
3773
3953
|
}
|
|
3774
3954
|
return service;
|
|
@@ -3950,6 +4130,9 @@ class PTYService {
|
|
|
3950
4130
|
this.log(`Failed to write Gemini settings: ${err}`);
|
|
3951
4131
|
}
|
|
3952
4132
|
}
|
|
4133
|
+
if (resolvedAgentType !== "shell" && workdir !== process.cwd()) {
|
|
4134
|
+
await this.ensureOrchestratorGitignore(workdir);
|
|
4135
|
+
}
|
|
3953
4136
|
const spawnConfig = buildSpawnConfig(sessionId, {
|
|
3954
4137
|
...options,
|
|
3955
4138
|
agentType: resolvedAgentType,
|
|
@@ -3989,6 +4172,21 @@ class PTYService {
|
|
|
3989
4172
|
if (this.usingBunWorker) {
|
|
3990
4173
|
setupOutputBuffer(ctx, session.id);
|
|
3991
4174
|
}
|
|
4175
|
+
if (isDebugCaptureEnabled()) {
|
|
4176
|
+
captureSessionOpen(session.id, resolvedAgentType).catch(() => {});
|
|
4177
|
+
if (this.usingBunWorker) {
|
|
4178
|
+
this.manager.onSessionData(session.id, (data) => {
|
|
4179
|
+
captureFeed(session.id, data, "stdout");
|
|
4180
|
+
});
|
|
4181
|
+
} else {
|
|
4182
|
+
const ptySession = this.manager.getSession(session.id);
|
|
4183
|
+
if (ptySession) {
|
|
4184
|
+
ptySession.on("output", (data) => {
|
|
4185
|
+
captureFeed(session.id, data, "stdout");
|
|
4186
|
+
});
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
3992
4190
|
if (resolvedInitialTask) {
|
|
3993
4191
|
setupDeferredTaskDelivery(ctx, session, resolvedInitialTask, resolvedAgentType);
|
|
3994
4192
|
}
|
|
@@ -4016,6 +4214,7 @@ class PTYService {
|
|
|
4016
4214
|
async sendToSession(sessionId, input) {
|
|
4017
4215
|
if (!this.manager)
|
|
4018
4216
|
throw new Error("PTYService not initialized");
|
|
4217
|
+
captureFeed(sessionId, input, "stdin");
|
|
4019
4218
|
return sendToSession(this.ioContext(), sessionId, input);
|
|
4020
4219
|
}
|
|
4021
4220
|
async sendKeysToSession(sessionId, keys) {
|
|
@@ -4026,6 +4225,7 @@ class PTYService {
|
|
|
4026
4225
|
async stopSession(sessionId, force = false) {
|
|
4027
4226
|
if (!this.manager)
|
|
4028
4227
|
throw new Error("PTYService not initialized");
|
|
4228
|
+
captureLifecycle(sessionId, "session_stopped", force ? "force" : undefined);
|
|
4029
4229
|
return stopSession(this.ioContext(), sessionId, this.sessionMetadata, this.sessionWorkdirs, (msg) => this.log(msg), force);
|
|
4030
4230
|
}
|
|
4031
4231
|
get defaultApprovalPreset() {
|
|
@@ -4101,12 +4301,12 @@ class PTYService {
|
|
|
4101
4301
|
handleHookEvent(sessionId, event, data) {
|
|
4102
4302
|
const summary = event === "tool_running" ? `tool=${data.toolName ?? "?"}` : event === "permission_approved" ? `tool=${data.tool ?? "?"}` : JSON.stringify(data);
|
|
4103
4303
|
if (event === "tool_running" || event === "permission_approved") {
|
|
4104
|
-
|
|
4304
|
+
logger3.debug(`[PTYService] Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4105
4305
|
} else {
|
|
4106
4306
|
this.log(`Hook event for ${sessionId}: ${event} ${summary}`);
|
|
4107
4307
|
}
|
|
4108
4308
|
if (this.manager && this.usingBunWorker) {
|
|
4109
|
-
this.manager.notifyHookEvent(sessionId, event).catch((err) =>
|
|
4309
|
+
this.manager.notifyHookEvent(sessionId, event).catch((err) => logger3.debug(`[PTYService] Failed to forward hook event to session: ${err}`));
|
|
4110
4310
|
}
|
|
4111
4311
|
switch (event) {
|
|
4112
4312
|
case "tool_running":
|
|
@@ -4140,6 +4340,13 @@ class PTYService {
|
|
|
4140
4340
|
if (meta?.coordinatorManaged && this.coordinator?.getSupervisionLevel() === "autonomous") {
|
|
4141
4341
|
const taskCtx = this.coordinator.getTaskContext(sessionId);
|
|
4142
4342
|
if (taskCtx) {
|
|
4343
|
+
if (taskCtx.lastInputSentAt) {
|
|
4344
|
+
const elapsed = Date.now() - taskCtx.lastInputSentAt;
|
|
4345
|
+
if (elapsed < POST_SEND_COOLDOWN_MS) {
|
|
4346
|
+
this.log(`Suppressing stall classification for ${sessionId} — ` + `${Math.round(elapsed / 1000)}s since coordinator sent input`);
|
|
4347
|
+
return null;
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4143
4350
|
return classifyAndDecideForCoordinator({
|
|
4144
4351
|
sessionId,
|
|
4145
4352
|
recentOutput,
|
|
@@ -4207,6 +4414,56 @@ class PTYService {
|
|
|
4207
4414
|
async writeMemoryFile(agentType, workspacePath, content, options) {
|
|
4208
4415
|
return this.getAdapter(agentType).writeMemoryFile(workspacePath, content, options);
|
|
4209
4416
|
}
|
|
4417
|
+
static GITIGNORE_MARKER = "# orchestrator-injected (do not commit agent config/memory files)";
|
|
4418
|
+
static gitignoreLocks = new Map;
|
|
4419
|
+
async ensureOrchestratorGitignore(workdir) {
|
|
4420
|
+
const gitignorePath = join2(workdir, ".gitignore");
|
|
4421
|
+
const existing_lock = PTYService.gitignoreLocks.get(gitignorePath);
|
|
4422
|
+
if (existing_lock)
|
|
4423
|
+
await existing_lock;
|
|
4424
|
+
const task = this.doEnsureGitignore(gitignorePath, workdir);
|
|
4425
|
+
PTYService.gitignoreLocks.set(gitignorePath, task);
|
|
4426
|
+
try {
|
|
4427
|
+
await task;
|
|
4428
|
+
} finally {
|
|
4429
|
+
if (PTYService.gitignoreLocks.get(gitignorePath) === task) {
|
|
4430
|
+
PTYService.gitignoreLocks.delete(gitignorePath);
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
async doEnsureGitignore(gitignorePath, workdir) {
|
|
4435
|
+
let existing = "";
|
|
4436
|
+
try {
|
|
4437
|
+
existing = await readFile2(gitignorePath, "utf-8");
|
|
4438
|
+
} catch {}
|
|
4439
|
+
if (existing.includes(PTYService.GITIGNORE_MARKER))
|
|
4440
|
+
return;
|
|
4441
|
+
const entries = [
|
|
4442
|
+
"",
|
|
4443
|
+
PTYService.GITIGNORE_MARKER,
|
|
4444
|
+
"CLAUDE.md",
|
|
4445
|
+
".claude/",
|
|
4446
|
+
"GEMINI.md",
|
|
4447
|
+
".gemini/",
|
|
4448
|
+
".aider*"
|
|
4449
|
+
];
|
|
4450
|
+
try {
|
|
4451
|
+
if (existing.length === 0) {
|
|
4452
|
+
await writeFile2(gitignorePath, entries.join(`
|
|
4453
|
+
`) + `
|
|
4454
|
+
`, "utf-8");
|
|
4455
|
+
} else {
|
|
4456
|
+
const separator = existing.endsWith(`
|
|
4457
|
+
`) ? "" : `
|
|
4458
|
+
`;
|
|
4459
|
+
await appendFile(gitignorePath, separator + entries.join(`
|
|
4460
|
+
`) + `
|
|
4461
|
+
`, "utf-8");
|
|
4462
|
+
}
|
|
4463
|
+
} catch (err) {
|
|
4464
|
+
this.log(`Failed to update .gitignore in ${workdir}: ${err}`);
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4210
4467
|
onSessionEvent(callback) {
|
|
4211
4468
|
this.eventCallbacks.push(callback);
|
|
4212
4469
|
return () => {
|
|
@@ -4254,7 +4511,7 @@ class PTYService {
|
|
|
4254
4511
|
return this.metricsTracker.getAll();
|
|
4255
4512
|
}
|
|
4256
4513
|
log(message) {
|
|
4257
|
-
|
|
4514
|
+
logger3.debug(`[PTYService] ${message}`);
|
|
4258
4515
|
}
|
|
4259
4516
|
}
|
|
4260
4517
|
|
|
@@ -4300,7 +4557,7 @@ var spawnAgentAction = {
|
|
|
4300
4557
|
validate: async (runtime, _message) => {
|
|
4301
4558
|
const ptyService = runtime.getService("PTY_SERVICE");
|
|
4302
4559
|
if (!ptyService) {
|
|
4303
|
-
|
|
4560
|
+
logger4.warn("[SPAWN_CODING_AGENT] PTYService not available");
|
|
4304
4561
|
return false;
|
|
4305
4562
|
}
|
|
4306
4563
|
return true;
|
|
@@ -4413,7 +4670,7 @@ var spawnAgentAction = {
|
|
|
4413
4670
|
ptyService.onSessionEvent((sessionId, event, data) => {
|
|
4414
4671
|
if (sessionId !== session.id)
|
|
4415
4672
|
return;
|
|
4416
|
-
|
|
4673
|
+
logger4.debug(`[Session ${sessionId}] ${event}: ${JSON.stringify(data)}`);
|
|
4417
4674
|
if (!coordinator) {
|
|
4418
4675
|
if (event === "blocked" && callback) {
|
|
4419
4676
|
callback({
|
|
@@ -4465,7 +4722,7 @@ var spawnAgentAction = {
|
|
|
4465
4722
|
};
|
|
4466
4723
|
} catch (error) {
|
|
4467
4724
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4468
|
-
|
|
4725
|
+
logger4.error("[SPAWN_CODING_AGENT] Failed to spawn agent:", errorMessage);
|
|
4469
4726
|
if (callback) {
|
|
4470
4727
|
await callback({
|
|
4471
4728
|
text: `Failed to spawn coding agent: ${errorMessage}`
|
|
@@ -4513,9 +4770,170 @@ var spawnAgentAction = {
|
|
|
4513
4770
|
|
|
4514
4771
|
// src/actions/coding-task-handlers.ts
|
|
4515
4772
|
import {
|
|
4516
|
-
logger as
|
|
4773
|
+
logger as logger6,
|
|
4517
4774
|
ModelType as ModelType5
|
|
4518
4775
|
} from "@elizaos/core";
|
|
4776
|
+
// src/services/trajectory-feedback.ts
|
|
4777
|
+
var QUERY_TIMEOUT_MS = 5000;
|
|
4778
|
+
function withTimeout2(promise, ms) {
|
|
4779
|
+
return Promise.race([
|
|
4780
|
+
promise,
|
|
4781
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Trajectory query timed out after ${ms}ms`)), ms))
|
|
4782
|
+
]);
|
|
4783
|
+
}
|
|
4784
|
+
function getTrajectoryLogger(runtime) {
|
|
4785
|
+
const runtimeAny = runtime;
|
|
4786
|
+
if (typeof runtimeAny.getService === "function") {
|
|
4787
|
+
const svc = runtimeAny.getService("trajectory_logger");
|
|
4788
|
+
if (svc && typeof svc === "object" && hasListMethod(svc)) {
|
|
4789
|
+
return svc;
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
if (typeof runtimeAny.getServicesByType === "function") {
|
|
4793
|
+
const services = runtimeAny.getServicesByType("trajectory_logger");
|
|
4794
|
+
if (Array.isArray(services)) {
|
|
4795
|
+
for (const svc of services) {
|
|
4796
|
+
if (svc && typeof svc === "object" && hasListMethod(svc)) {
|
|
4797
|
+
return svc;
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
return null;
|
|
4803
|
+
}
|
|
4804
|
+
function hasListMethod(obj) {
|
|
4805
|
+
const candidate = obj;
|
|
4806
|
+
return typeof candidate.listTrajectories === "function" && typeof candidate.getTrajectoryDetail === "function";
|
|
4807
|
+
}
|
|
4808
|
+
function extractInsights(response, purpose) {
|
|
4809
|
+
const insights = [];
|
|
4810
|
+
const decisionPattern = /DECISION:\s*(.+?)(?:\n|$)/gi;
|
|
4811
|
+
let match;
|
|
4812
|
+
while ((match = decisionPattern.exec(response)) !== null) {
|
|
4813
|
+
insights.push(match[1].trim());
|
|
4814
|
+
}
|
|
4815
|
+
const keyDecisionPattern = /"keyDecision"\s*:\s*"([^"]+)"/g;
|
|
4816
|
+
while ((match = keyDecisionPattern.exec(response)) !== null) {
|
|
4817
|
+
insights.push(match[1].trim());
|
|
4818
|
+
}
|
|
4819
|
+
if ((purpose === "turn-complete" || purpose === "coordination") && insights.length === 0) {
|
|
4820
|
+
const reasoningPattern = /"reasoning"\s*:\s*"([^"]{20,200})"/;
|
|
4821
|
+
const reasoningMatch = response.match(reasoningPattern);
|
|
4822
|
+
if (reasoningMatch) {
|
|
4823
|
+
insights.push(reasoningMatch[1].trim());
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return insights;
|
|
4827
|
+
}
|
|
4828
|
+
function isRelevant(experience, taskDescription) {
|
|
4829
|
+
if (!taskDescription)
|
|
4830
|
+
return true;
|
|
4831
|
+
const taskWords = new Set(taskDescription.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 3));
|
|
4832
|
+
const insightWords = experience.insight.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 3);
|
|
4833
|
+
let overlap = 0;
|
|
4834
|
+
for (const word of insightWords) {
|
|
4835
|
+
if (taskWords.has(word))
|
|
4836
|
+
overlap++;
|
|
4837
|
+
if (overlap >= 2)
|
|
4838
|
+
return true;
|
|
4839
|
+
}
|
|
4840
|
+
return false;
|
|
4841
|
+
}
|
|
4842
|
+
async function queryPastExperience(runtime, options = {}) {
|
|
4843
|
+
const {
|
|
4844
|
+
maxTrajectories = 30,
|
|
4845
|
+
maxEntries = 8,
|
|
4846
|
+
lookbackHours = 48,
|
|
4847
|
+
taskDescription,
|
|
4848
|
+
repo
|
|
4849
|
+
} = options;
|
|
4850
|
+
const logger5 = getTrajectoryLogger(runtime);
|
|
4851
|
+
if (!logger5)
|
|
4852
|
+
return [];
|
|
4853
|
+
const startDate = new Date(Date.now() - lookbackHours * 60 * 60 * 1000).toISOString();
|
|
4854
|
+
try {
|
|
4855
|
+
const result = await withTimeout2(logger5.listTrajectories({
|
|
4856
|
+
source: "orchestrator",
|
|
4857
|
+
limit: maxTrajectories,
|
|
4858
|
+
startDate
|
|
4859
|
+
}), QUERY_TIMEOUT_MS);
|
|
4860
|
+
if (!result.trajectories || result.trajectories.length === 0)
|
|
4861
|
+
return [];
|
|
4862
|
+
const experiences = [];
|
|
4863
|
+
const maxScans = Math.min(result.trajectories.length, maxTrajectories);
|
|
4864
|
+
for (let scanIdx = 0;scanIdx < maxScans; scanIdx++) {
|
|
4865
|
+
const summary = result.trajectories[scanIdx];
|
|
4866
|
+
const detail = await withTimeout2(logger5.getTrajectoryDetail(summary.id), QUERY_TIMEOUT_MS).catch(() => null);
|
|
4867
|
+
if (!detail?.steps)
|
|
4868
|
+
continue;
|
|
4869
|
+
const metadata = detail.metadata;
|
|
4870
|
+
const decisionType = metadata?.orchestrator?.decisionType ?? "unknown";
|
|
4871
|
+
const taskLabel = metadata?.orchestrator?.taskLabel ?? "";
|
|
4872
|
+
const trajectoryRepo = metadata?.orchestrator?.repo;
|
|
4873
|
+
if (repo && (!trajectoryRepo || trajectoryRepo !== repo))
|
|
4874
|
+
continue;
|
|
4875
|
+
for (const step of detail.steps) {
|
|
4876
|
+
if (!step.llmCalls)
|
|
4877
|
+
continue;
|
|
4878
|
+
for (const call of step.llmCalls) {
|
|
4879
|
+
if (!call.response)
|
|
4880
|
+
continue;
|
|
4881
|
+
const insights = extractInsights(call.response, call.purpose ?? decisionType);
|
|
4882
|
+
for (const insight of insights) {
|
|
4883
|
+
experiences.push({
|
|
4884
|
+
timestamp: call.timestamp ?? summary.startTime,
|
|
4885
|
+
decisionType: call.purpose ?? decisionType,
|
|
4886
|
+
taskLabel,
|
|
4887
|
+
insight
|
|
4888
|
+
});
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
let filtered = taskDescription ? experiences.filter((e) => isRelevant(e, taskDescription)) : experiences;
|
|
4894
|
+
if (filtered.length === 0 && experiences.length > 0) {
|
|
4895
|
+
filtered = experiences;
|
|
4896
|
+
}
|
|
4897
|
+
const seen = new Map;
|
|
4898
|
+
for (const exp of filtered) {
|
|
4899
|
+
const key = exp.insight.toLowerCase();
|
|
4900
|
+
const existing = seen.get(key);
|
|
4901
|
+
if (!existing || exp.timestamp > existing.timestamp) {
|
|
4902
|
+
seen.set(key, exp);
|
|
4903
|
+
}
|
|
4904
|
+
}
|
|
4905
|
+
return Array.from(seen.values()).sort((a, b) => b.timestamp - a.timestamp).slice(0, maxEntries);
|
|
4906
|
+
} catch (err) {
|
|
4907
|
+
console.error("[trajectory-feedback] Failed to query past experience:", err);
|
|
4908
|
+
return [];
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
function formatPastExperience(experiences) {
|
|
4912
|
+
if (experiences.length === 0)
|
|
4913
|
+
return "";
|
|
4914
|
+
const lines = experiences.map((e) => {
|
|
4915
|
+
const age = formatAge(e.timestamp);
|
|
4916
|
+
const label = e.taskLabel ? ` [${e.taskLabel}]` : "";
|
|
4917
|
+
return `- ${e.insight}${label} (${age})`;
|
|
4918
|
+
});
|
|
4919
|
+
return `# Past Experience
|
|
4920
|
+
|
|
4921
|
+
` + `The following decisions and insights were captured from recent agent sessions. ` + `Use them to avoid repeating mistakes and to stay consistent with established patterns.
|
|
4922
|
+
|
|
4923
|
+
` + `${lines.join(`
|
|
4924
|
+
`)}
|
|
4925
|
+
`;
|
|
4926
|
+
}
|
|
4927
|
+
function formatAge(timestamp) {
|
|
4928
|
+
const diffMs = Date.now() - timestamp;
|
|
4929
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
4930
|
+
if (hours < 1)
|
|
4931
|
+
return "just now";
|
|
4932
|
+
if (hours < 24)
|
|
4933
|
+
return `${hours}h ago`;
|
|
4934
|
+
const days = Math.floor(hours / 24);
|
|
4935
|
+
return `${days}d ago`;
|
|
4936
|
+
}
|
|
4519
4937
|
|
|
4520
4938
|
// src/actions/coding-task-helpers.ts
|
|
4521
4939
|
import { randomUUID } from "node:crypto";
|
|
@@ -4523,7 +4941,7 @@ import * as fs from "node:fs";
|
|
|
4523
4941
|
import * as os2 from "node:os";
|
|
4524
4942
|
import * as path3 from "node:path";
|
|
4525
4943
|
import {
|
|
4526
|
-
logger as
|
|
4944
|
+
logger as logger5
|
|
4527
4945
|
} from "@elizaos/core";
|
|
4528
4946
|
function createScratchDir() {
|
|
4529
4947
|
const baseDir = path3.join(os2.homedir(), ".milady", "workspaces");
|
|
@@ -4568,7 +4986,7 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
4568
4986
|
});
|
|
4569
4987
|
}
|
|
4570
4988
|
ptyService.stopSession(sessionId, true).catch((err) => {
|
|
4571
|
-
|
|
4989
|
+
logger5.warn(`[START_CODING_TASK] Failed to stop session for "${label}" after task complete: ${err}`);
|
|
4572
4990
|
});
|
|
4573
4991
|
}
|
|
4574
4992
|
if (event === "error" && callback) {
|
|
@@ -4581,7 +4999,7 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
4581
4999
|
const wsService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
4582
5000
|
if (wsService) {
|
|
4583
5001
|
wsService.removeScratchDir(scratchDir).catch((err) => {
|
|
4584
|
-
|
|
5002
|
+
logger5.warn(`[START_CODING_TASK] Failed to cleanup scratch dir for "${label}": ${err}`);
|
|
4585
5003
|
});
|
|
4586
5004
|
}
|
|
4587
5005
|
}
|
|
@@ -4617,6 +5035,26 @@ function stripAgentPrefix(spec) {
|
|
|
4617
5035
|
}
|
|
4618
5036
|
return spec;
|
|
4619
5037
|
}
|
|
5038
|
+
function buildSwarmMemoryInstructions(agentLabel, agentTask, allSubtasks, agentIndex) {
|
|
5039
|
+
const siblingTasks = allSubtasks.filter((_, i) => i !== agentIndex).map((t, i) => ` ${i + 1}. ${t}`).join(`
|
|
5040
|
+
`);
|
|
5041
|
+
return `# Swarm Coordination
|
|
5042
|
+
|
|
5043
|
+
` + `You are agent "${agentLabel}" in a multi-agent swarm of ${allSubtasks.length} agents.
|
|
5044
|
+
` + `Your task: ${agentTask}
|
|
5045
|
+
|
|
5046
|
+
` + `Other agents are working on:
|
|
5047
|
+
${siblingTasks}
|
|
5048
|
+
|
|
5049
|
+
` + `## Coordination Rules
|
|
5050
|
+
|
|
5051
|
+
` + `- **Follow the Shared Context exactly.** The planning brief above contains ` + `concrete decisions (names, file paths, APIs, conventions). Use them as-is.
|
|
5052
|
+
` + `- **Surface design decisions.** If you need to make a creative or architectural ` + `choice not covered by the Shared Context (naming something, choosing a library, ` + `designing an interface, picking an approach), state your decision clearly in your ` + `output so the orchestrator can share it with sibling agents. Write it as:
|
|
5053
|
+
` + ` "DECISION: [brief description of what you decided and why]"
|
|
5054
|
+
` + `- **Don't contradict sibling work.** If the orchestrator tells you about decisions ` + `other agents have made, align with them.
|
|
5055
|
+
` + `- **Ask when uncertain.** If your task depends on another agent's output and you ` + `don't have enough context, ask rather than guessing.
|
|
5056
|
+
`;
|
|
5057
|
+
}
|
|
4620
5058
|
async function generateSwarmContext(runtime, subtasks, userRequest) {
|
|
4621
5059
|
const taskList = subtasks.map((t, i) => ` ${i + 1}. ${t}`).join(`
|
|
4622
5060
|
`);
|
|
@@ -4627,25 +5065,28 @@ async function generateSwarmContext(runtime, subtasks, userRequest) {
|
|
|
4627
5065
|
` + `Subtasks being assigned:
|
|
4628
5066
|
${taskList}
|
|
4629
5067
|
|
|
4630
|
-
` + `Generate a concise shared context brief (3-
|
|
5068
|
+
` + `Generate a concise shared context brief (3-10 bullet points) covering:
|
|
4631
5069
|
` + `- Project intent and overall goal
|
|
4632
5070
|
` + `- Key constraints or preferences from the user's request
|
|
4633
5071
|
` + `- Conventions all agents should follow (naming, style, patterns, tone)
|
|
4634
5072
|
` + `- How subtasks relate to each other (dependencies, shared interfaces, etc.)
|
|
4635
5073
|
` + `- Any decisions that should be consistent across all agents
|
|
4636
5074
|
|
|
4637
|
-
` + `
|
|
5075
|
+
` + `CRITICAL — Concrete Decisions:
|
|
5076
|
+
` + `If any subtask involves creative choices (naming a feature, choosing an approach, ` + `designing an API, picking a concept), YOU must make those decisions NOW in this brief. ` + `Do NOT leave creative choices to individual agents — they run in parallel and will ` + `each make different choices, causing inconsistency.
|
|
5077
|
+
` + `For example: if one agent builds a feature and another writes tests for it, ` + `decide the feature name, file paths, function signatures, and key design choices here ` + `so both agents use the same names and structure.
|
|
5078
|
+
|
|
5079
|
+
` + `Only include what's relevant — skip categories that don't apply. ` + `Be specific and actionable, not generic. Be as detailed as the task requires — ` + `a trivial task needs a few bullets, a complex task deserves a thorough roadmap.
|
|
4638
5080
|
|
|
4639
5081
|
` + `Output ONLY the bullet points, no preamble.`;
|
|
4640
5082
|
try {
|
|
4641
|
-
const result = await runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
5083
|
+
const result = await withTrajectoryContext(runtime, { source: "orchestrator", decisionType: "swarm-context-generation" }, () => runtime.useModel(ModelType5.TEXT_SMALL, {
|
|
4642
5084
|
prompt,
|
|
4643
|
-
maxTokens: 400,
|
|
4644
5085
|
temperature: 0.3
|
|
4645
|
-
});
|
|
5086
|
+
}));
|
|
4646
5087
|
return result?.trim() || "";
|
|
4647
5088
|
} catch (err) {
|
|
4648
|
-
|
|
5089
|
+
logger6.warn(`Swarm context generation failed: ${err}`);
|
|
4649
5090
|
return "";
|
|
4650
5091
|
}
|
|
4651
5092
|
}
|
|
@@ -4703,6 +5144,13 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
4703
5144
|
const coordinator = getCoordinator(runtime);
|
|
4704
5145
|
coordinator?.setSwarmContext(swarmContext);
|
|
4705
5146
|
}
|
|
5147
|
+
const pastExperience = await queryPastExperience(runtime, {
|
|
5148
|
+
taskDescription: userRequest,
|
|
5149
|
+
lookbackHours: 48,
|
|
5150
|
+
maxEntries: 8,
|
|
5151
|
+
repo
|
|
5152
|
+
});
|
|
5153
|
+
const pastExperienceBlock = formatPastExperience(pastExperience);
|
|
4706
5154
|
const results = [];
|
|
4707
5155
|
for (const [i, spec] of agentSpecs.entries()) {
|
|
4708
5156
|
let specAgentType = defaultAgentType;
|
|
@@ -4759,12 +5207,16 @@ ${swarmContext}
|
|
|
4759
5207
|
--- End Shared Context ---` : specTask;
|
|
4760
5208
|
const initialTask = specPiRequested ? toPiCommand(taskWithContext) : taskWithContext;
|
|
4761
5209
|
const displayType = specPiRequested ? "pi" : specAgentType;
|
|
5210
|
+
const swarmMemory = agentSpecs.length > 1 && swarmContext ? buildSwarmMemoryInstructions(specLabel, specTask, cleanSubtasks, i) : undefined;
|
|
5211
|
+
const agentMemory = [memoryContent, swarmMemory, pastExperienceBlock].filter(Boolean).join(`
|
|
5212
|
+
|
|
5213
|
+
`) || undefined;
|
|
4762
5214
|
const session = await ptyService.spawnSession({
|
|
4763
5215
|
name: `coding-${Date.now()}-${i}`,
|
|
4764
5216
|
agentType: specAgentType,
|
|
4765
5217
|
workdir,
|
|
4766
5218
|
initialTask,
|
|
4767
|
-
memoryContent,
|
|
5219
|
+
memoryContent: agentMemory,
|
|
4768
5220
|
credentials,
|
|
4769
5221
|
approvalPreset: approvalPreset ?? ptyService.defaultApprovalPreset,
|
|
4770
5222
|
customCredentials,
|
|
@@ -4806,7 +5258,7 @@ ${swarmContext}
|
|
|
4806
5258
|
}
|
|
4807
5259
|
} catch (error) {
|
|
4808
5260
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4809
|
-
|
|
5261
|
+
logger6.error(`[START_CODING_TASK] Failed to spawn agent ${i + 1}:`, errorMessage);
|
|
4810
5262
|
results.push({
|
|
4811
5263
|
sessionId: "",
|
|
4812
5264
|
agentType: specAgentType,
|
|
@@ -4838,7 +5290,7 @@ ${swarmContext}
|
|
|
4838
5290
|
};
|
|
4839
5291
|
}
|
|
4840
5292
|
async function handleSingleAgent(ctx, task) {
|
|
4841
|
-
|
|
5293
|
+
logger6.debug(`[START_CODING_TASK] handleSingleAgent called, agentType=${ctx.defaultAgentType}, task=${task ? "yes" : "none"}, repo=${ctx.repo ?? "none"}`);
|
|
4842
5294
|
const {
|
|
4843
5295
|
runtime,
|
|
4844
5296
|
ptyService,
|
|
@@ -4898,14 +5350,14 @@ async function handleSingleAgent(ctx, task) {
|
|
|
4898
5350
|
} else {
|
|
4899
5351
|
workdir = createScratchDir();
|
|
4900
5352
|
}
|
|
4901
|
-
|
|
5353
|
+
logger6.debug(`[START_CODING_TASK] Spawning ${agentType} agent, task: ${task ? `"${task.slice(0, 80)}..."` : "(none)"}, workdir: ${workdir}`);
|
|
4902
5354
|
try {
|
|
4903
5355
|
if (agentType !== "shell" && agentType !== "pi") {
|
|
4904
5356
|
const [preflight] = await ptyService.checkAvailableAgents([
|
|
4905
5357
|
agentType
|
|
4906
5358
|
]);
|
|
4907
5359
|
if (preflight && !preflight.installed) {
|
|
4908
|
-
|
|
5360
|
+
logger6.warn(`[START_CODING_TASK] ${preflight.adapter} CLI not installed`);
|
|
4909
5361
|
if (callback) {
|
|
4910
5362
|
await callback({
|
|
4911
5363
|
text: `${preflight.adapter} CLI is not installed.
|
|
@@ -4915,19 +5367,29 @@ Docs: ${preflight.docsUrl}`
|
|
|
4915
5367
|
}
|
|
4916
5368
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
4917
5369
|
}
|
|
4918
|
-
|
|
5370
|
+
logger6.debug(`[START_CODING_TASK] Preflight OK: ${preflight?.adapter} installed`);
|
|
4919
5371
|
}
|
|
4920
5372
|
const piRequested = isPiAgentType(rawAgentType);
|
|
4921
5373
|
const initialTask = piRequested ? toPiCommand(task) : task;
|
|
4922
5374
|
const displayType = piRequested ? "pi" : agentType;
|
|
5375
|
+
const pastExperience = await queryPastExperience(runtime, {
|
|
5376
|
+
taskDescription: task,
|
|
5377
|
+
lookbackHours: 48,
|
|
5378
|
+
maxEntries: 6,
|
|
5379
|
+
repo
|
|
5380
|
+
});
|
|
5381
|
+
const pastExperienceBlock = formatPastExperience(pastExperience);
|
|
5382
|
+
const agentMemory = [memoryContent, pastExperienceBlock].filter(Boolean).join(`
|
|
5383
|
+
|
|
5384
|
+
`) || undefined;
|
|
4923
5385
|
const coordinator = getCoordinator(runtime);
|
|
4924
|
-
|
|
5386
|
+
logger6.debug(`[START_CODING_TASK] Calling spawnSession (${agentType}, coordinator=${!!coordinator})`);
|
|
4925
5387
|
const session = await ptyService.spawnSession({
|
|
4926
5388
|
name: `coding-${Date.now()}`,
|
|
4927
5389
|
agentType,
|
|
4928
5390
|
workdir,
|
|
4929
5391
|
initialTask,
|
|
4930
|
-
memoryContent,
|
|
5392
|
+
memoryContent: agentMemory,
|
|
4931
5393
|
credentials,
|
|
4932
5394
|
approvalPreset: approvalPreset ?? ptyService.defaultApprovalPreset,
|
|
4933
5395
|
customCredentials,
|
|
@@ -4940,7 +5402,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
4940
5402
|
label
|
|
4941
5403
|
}
|
|
4942
5404
|
});
|
|
4943
|
-
|
|
5405
|
+
logger6.debug(`[START_CODING_TASK] Session spawned: ${session.id} (${session.status})`);
|
|
4944
5406
|
const isScratchWorkspace = !repo;
|
|
4945
5407
|
const scratchDir = isScratchWorkspace ? workdir : null;
|
|
4946
5408
|
registerSessionEvents(ptyService, runtime, session.id, label, scratchDir, callback, !!coordinator);
|
|
@@ -4981,7 +5443,7 @@ Session ID: ${session.id}` });
|
|
|
4981
5443
|
};
|
|
4982
5444
|
} catch (error) {
|
|
4983
5445
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4984
|
-
|
|
5446
|
+
logger6.error("[START_CODING_TASK] Failed to spawn agent:", errorMessage);
|
|
4985
5447
|
if (callback) {
|
|
4986
5448
|
await callback({
|
|
4987
5449
|
text: `Failed to start coding agent: ${errorMessage}`
|
|
@@ -5162,7 +5624,7 @@ var startCodingTaskAction = {
|
|
|
5162
5624
|
|
|
5163
5625
|
// src/actions/stop-agent.ts
|
|
5164
5626
|
import {
|
|
5165
|
-
logger as
|
|
5627
|
+
logger as logger7
|
|
5166
5628
|
} from "@elizaos/core";
|
|
5167
5629
|
var stopAgentAction = {
|
|
5168
5630
|
name: "STOP_CODING_AGENT",
|
|
@@ -5241,7 +5703,7 @@ var stopAgentAction = {
|
|
|
5241
5703
|
try {
|
|
5242
5704
|
await ptyService.stopSession(session2.id);
|
|
5243
5705
|
} catch (err) {
|
|
5244
|
-
|
|
5706
|
+
logger7.error(`Failed to stop session ${session2.id}: ${err}`);
|
|
5245
5707
|
}
|
|
5246
5708
|
}
|
|
5247
5709
|
if (state?.codingSession) {
|
|
@@ -7006,5 +7468,5 @@ export {
|
|
|
7006
7468
|
CodingWorkspaceService
|
|
7007
7469
|
};
|
|
7008
7470
|
|
|
7009
|
-
//# debugId=
|
|
7471
|
+
//# debugId=4383E51DF7A1F4A764756E2164756E21
|
|
7010
7472
|
//# sourceMappingURL=index.js.map
|