@elizaos/plugin-agent-orchestrator 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/dist/actions/coding-task-handlers.d.ts +44 -0
- package/dist/actions/coding-task-handlers.d.ts.map +1 -0
- package/dist/actions/coding-task-helpers.d.ts +27 -0
- package/dist/actions/coding-task-helpers.d.ts.map +1 -0
- package/dist/actions/finalize-workspace.d.ts +11 -0
- package/dist/actions/finalize-workspace.d.ts.map +1 -0
- package/dist/actions/list-agents.d.ts +11 -0
- package/dist/actions/list-agents.d.ts.map +1 -0
- package/dist/actions/manage-issues.d.ts +11 -0
- package/dist/actions/manage-issues.d.ts.map +1 -0
- package/dist/actions/provision-workspace.d.ts +11 -0
- package/dist/actions/provision-workspace.d.ts.map +1 -0
- package/dist/actions/send-to-agent.d.ts +11 -0
- package/dist/actions/send-to-agent.d.ts.map +1 -0
- package/dist/actions/spawn-agent.d.ts +11 -0
- package/dist/actions/spawn-agent.d.ts.map +1 -0
- package/dist/actions/start-coding-task.d.ts +17 -0
- package/dist/actions/start-coding-task.d.ts.map +1 -0
- package/dist/actions/stop-agent.d.ts +11 -0
- package/dist/actions/stop-agent.d.ts.map +1 -0
- package/dist/api/agent-routes.d.ts +18 -0
- package/dist/api/agent-routes.d.ts.map +1 -0
- package/dist/api/coordinator-routes.d.ts +22 -0
- package/dist/api/coordinator-routes.d.ts.map +1 -0
- package/dist/api/issue-routes.d.ts +17 -0
- package/dist/api/issue-routes.d.ts.map +1 -0
- package/dist/api/routes.d.ts +36 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/workspace-routes.d.ts +17 -0
- package/dist/api/workspace-routes.d.ts.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +976 -648
- package/dist/index.js.map +15 -14
- package/dist/providers/action-examples.d.ts +13 -0
- package/dist/providers/action-examples.d.ts.map +1 -0
- package/dist/providers/active-workspace-context.d.ts +13 -0
- package/dist/providers/active-workspace-context.d.ts.map +1 -0
- package/dist/services/agent-metrics.d.ts +28 -0
- package/dist/services/agent-metrics.d.ts.map +1 -0
- package/dist/services/agent-selection.d.ts +53 -0
- package/dist/services/agent-selection.d.ts.map +1 -0
- package/dist/services/ansi-utils.d.ts +48 -0
- package/dist/services/ansi-utils.d.ts.map +1 -0
- package/dist/services/pty-auto-response.d.ts +30 -0
- package/dist/services/pty-auto-response.d.ts.map +1 -0
- package/dist/services/pty-init.d.ts +45 -0
- package/dist/services/pty-init.d.ts.map +1 -0
- package/dist/services/pty-service.d.ts +92 -0
- package/dist/services/pty-service.d.ts.map +1 -0
- package/dist/services/pty-session-io.d.ts +46 -0
- package/dist/services/pty-session-io.d.ts.map +1 -0
- package/dist/services/pty-spawn.d.ts +50 -0
- package/dist/services/pty-spawn.d.ts.map +1 -0
- package/dist/services/pty-types.d.ts +80 -0
- package/dist/services/pty-types.d.ts.map +1 -0
- package/dist/services/stall-classifier.d.ts +44 -0
- package/dist/services/stall-classifier.d.ts.map +1 -0
- package/dist/services/swarm-coordinator-prompts.d.ts +75 -0
- package/dist/services/swarm-coordinator-prompts.d.ts.map +1 -0
- package/dist/services/swarm-coordinator.d.ts +196 -0
- package/dist/services/swarm-coordinator.d.ts.map +1 -0
- package/dist/services/swarm-decision-loop.d.ts +44 -0
- package/dist/services/swarm-decision-loop.d.ts.map +1 -0
- package/dist/services/swarm-event-triage.d.ts +49 -0
- package/dist/services/swarm-event-triage.d.ts.map +1 -0
- package/dist/services/swarm-idle-watchdog.d.ts +22 -0
- package/dist/services/swarm-idle-watchdog.d.ts.map +1 -0
- package/dist/services/workspace-git-ops.d.ts +28 -0
- package/dist/services/workspace-git-ops.d.ts.map +1 -0
- package/dist/services/workspace-github.d.ts +58 -0
- package/dist/services/workspace-github.d.ts.map +1 -0
- package/dist/services/workspace-lifecycle.d.ts +18 -0
- package/dist/services/workspace-lifecycle.d.ts.map +1 -0
- package/dist/services/workspace-service.d.ts +84 -0
- package/dist/services/workspace-service.d.ts.map +1 -0
- package/dist/services/workspace-types.d.ts +81 -0
- package/dist/services/workspace-types.d.ts.map +1 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
1
|
+
import {createRequire} from "node:module";
|
|
4
2
|
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
-
for (let key of __getOwnPropNames(mod))
|
|
11
|
-
if (!__hasOwnProp.call(to, key))
|
|
12
|
-
__defProp(to, key, {
|
|
13
|
-
get: () => mod[key],
|
|
14
|
-
enumerable: true
|
|
15
|
-
});
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
3
|
var __export = (target, all) => {
|
|
19
4
|
for (var name in all)
|
|
20
5
|
__defProp(target, name, {
|
|
@@ -36,8 +21,7 @@ function stripAnsi(raw) {
|
|
|
36
21
|
}
|
|
37
22
|
function cleanForChat(raw) {
|
|
38
23
|
const stripped = applyAnsiStrip(raw);
|
|
39
|
-
return stripped.replace(TUI_DECORATIVE, " ").replace(/\xa0/g, " ").split(
|
|
40
|
-
`).filter((line) => {
|
|
24
|
+
return stripped.replace(TUI_DECORATIVE, " ").replace(/\xa0/g, " ").split("\n").filter((line) => {
|
|
41
25
|
const trimmed = line.trim();
|
|
42
26
|
if (!trimmed)
|
|
43
27
|
return false;
|
|
@@ -48,10 +32,7 @@ function cleanForChat(raw) {
|
|
|
48
32
|
if (!/[a-zA-Z0-9]/.test(trimmed))
|
|
49
33
|
return false;
|
|
50
34
|
return true;
|
|
51
|
-
}).map((line) => line.replace(/ {2,}/g, " ").trim()).filter((line) => line.length > 0).join(
|
|
52
|
-
`).replace(/\n{3,}/g, `
|
|
53
|
-
|
|
54
|
-
`).trim();
|
|
35
|
+
}).map((line) => line.replace(/ {2,}/g, " ").trim()).filter((line) => line.length > 0).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
55
36
|
}
|
|
56
37
|
function extractCompletionSummary(raw) {
|
|
57
38
|
const stripped = applyAnsiStrip(raw);
|
|
@@ -76,8 +57,7 @@ function extractCompletionSummary(raw) {
|
|
|
76
57
|
for (const m of diffStat)
|
|
77
58
|
lines.push(m.trim());
|
|
78
59
|
}
|
|
79
|
-
return lines.join(
|
|
80
|
-
`);
|
|
60
|
+
return lines.join("\n");
|
|
81
61
|
}
|
|
82
62
|
function extractDevServerUrl(raw) {
|
|
83
63
|
const stripped = applyAnsiStrip(raw);
|
|
@@ -91,8 +71,7 @@ function captureTaskResponse(sessionId, buffers, markers) {
|
|
|
91
71
|
return "";
|
|
92
72
|
const responseLines = buffer.slice(marker);
|
|
93
73
|
markers.delete(sessionId);
|
|
94
|
-
return cleanForChat(responseLines.join(
|
|
95
|
-
`));
|
|
74
|
+
return cleanForChat(responseLines.join("\n"));
|
|
96
75
|
}
|
|
97
76
|
var CURSOR_MOVEMENT, CURSOR_POSITION, ERASE, OSC, ALL_ANSI, CONTROL_CHARS, ORPHAN_SGR, LONG_SPACES, TUI_DECORATIVE, LOADING_LINE, STATUS_LINE;
|
|
98
77
|
var init_ansi_utils = __esm(() => {
|
|
@@ -111,127 +90,54 @@ var init_ansi_utils = __esm(() => {
|
|
|
111
90
|
|
|
112
91
|
// src/services/swarm-coordinator-prompts.ts
|
|
113
92
|
function buildCoordinationPrompt(taskCtx, promptText, recentOutput, decisionHistory) {
|
|
114
|
-
const historySection = decisionHistory.length > 0 ? `
|
|
115
|
-
|
|
116
|
-
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
117
|
-
`)}
|
|
118
|
-
` : "";
|
|
119
|
-
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `is blocked and waiting for input.
|
|
120
|
-
|
|
121
|
-
` + `Original task: "${taskCtx.originalTask}"
|
|
122
|
-
` + `Working directory: ${taskCtx.workdir}
|
|
123
|
-
` + historySection + `
|
|
124
|
-
Recent terminal output (last 50 lines):
|
|
125
|
-
` + `---
|
|
126
|
-
${recentOutput.slice(-3000)}
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
` + `The agent is showing this blocking prompt:
|
|
130
|
-
` + `"${promptText}"
|
|
131
|
-
|
|
132
|
-
` + `Decide how to respond. Your options:
|
|
133
|
-
|
|
134
|
-
` + `1. "respond" — Send a response to unblock the agent. For text prompts (Y/n, questions), ` + `set "response" to the text to send. For TUI menus or interactive prompts that need ` + `special keys, set "useKeys": true and "keys" to the key sequence ` + `(e.g. ["enter"], ["down","enter"], ["y","enter"]).
|
|
93
|
+
const historySection = decisionHistory.length > 0 ? `\nPrevious decisions for this session:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
|
|
94
|
+
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `is blocked and waiting for input.\n\n` + `Original task: "${taskCtx.originalTask}"\n` + `Working directory: ${taskCtx.workdir}\n` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nRecent terminal output (last 50 lines):\n` + `---\n${recentOutput.slice(-3000)}\n---\n\n` + `The agent is showing this blocking prompt:\n` + `"${promptText}"\n\n` + `Decide how to respond. Your options:\n\n` + `1. "respond" \u2014 Send a response to unblock the agent. For text prompts (Y/n, questions), ` + `set "response" to the text to send. For TUI menus or interactive prompts that need ` + `special keys, set "useKeys": true and "keys" to the key sequence ` + `(e.g. ["enter"], ["down","enter"], ["y","enter"]).\n\n` + `2. "complete" \u2014 The original task has been fulfilled. The agent has finished its work ` + `(e.g. code written, PR created, tests passed) and is back at the idle prompt. ` + `Use this when the terminal output shows the task objectives have been met.\n\n` + `3. "escalate" \u2014 The prompt requires human judgment (e.g. design decisions, ` + `ambiguous requirements, security-sensitive actions). Do NOT respond yourself.\n\n` + `4. "ignore" \u2014 The prompt is not actually blocking or is already being handled.
|
|
135
95
|
|
|
136
|
-
` + `
|
|
96
|
+
` + `Guidelines:\n` + `- IMPORTANT: If the prompt asks to approve access to files or directories OUTSIDE the working ` + `directory (${taskCtx.workdir}), DECLINE the request and REDIRECT the agent. Do NOT approve ` + `access to paths like /etc, ~/.ssh, ~/, /tmp, or any path that doesn't start with the working ` + `directory. Instead, respond with "n" (or the decline option) and tell the agent: ` + `"That path is outside your workspace. Use ${taskCtx.workdir} instead \u2014 ` + `create any files or directories you need there." This keeps the agent moving without ` + `granting out-of-scope access. The coordinator will also notify the human in case ` + `broader access was intended.\n` + `- For tool approval prompts (file writes, shell commands, etc.), respond "y" or use keys:["enter"] to approve.\n` + `- For Y/n confirmations that align with the original task, respond "y".\n` + `- For design questions or choices that could go either way, escalate.\n` + `- For error recovery prompts, try to respond if the path forward is clear.\n` + `- If the output shows a PR was just created (e.g. "Created pull request #N"), do NOT use "complete" yet. ` + `Instead respond with "Review your PR, run each test plan item to verify it works, update the PR to check off each item, then confirm all items pass".\n` + `- Only use "complete" if the agent confirmed it verified ALL test plan items after creating the PR.\n` + `- 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 \u2014 the human must provide it.
|
|
97
|
+
` + `- When in doubt, escalate \u2014 it's better to ask the human than to make a wrong choice.
|
|
137
98
|
|
|
138
|
-
` + `
|
|
139
|
-
|
|
140
|
-
` + `4. "ignore" — The prompt is not actually blocking or is already being handled.
|
|
141
|
-
|
|
142
|
-
` + `Guidelines:
|
|
143
|
-
` + `- For tool approval prompts (file writes, shell commands, etc.), respond "y" or use keys:["enter"] to approve.
|
|
144
|
-
` + `- For Y/n confirmations that align with the original task, respond "y".
|
|
145
|
-
` + `- For design questions or choices that could go either way, escalate.
|
|
146
|
-
` + `- For error recovery prompts, try to respond if the path forward is clear.
|
|
147
|
-
` + `- If the output shows a PR was just created (e.g. "Created pull request #N"), do NOT use "complete" yet. ` + `Instead respond with "Review your PR, run each test plan item to verify it works, update the PR to check off each item, then confirm all items pass".
|
|
148
|
-
` + `- Only use "complete" if the agent confirmed it verified ALL test plan items after creating the PR.
|
|
149
|
-
` + `- When in doubt, escalate — it's better to ask the human than to make a wrong choice.
|
|
150
|
-
|
|
151
|
-
` + `Respond with ONLY a JSON object:
|
|
152
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
99
|
+
` + `Respond with ONLY a JSON object:\n` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
153
100
|
}
|
|
154
101
|
function buildIdleCheckPrompt(taskCtx, recentOutput, idleMinutes, idleCheckNumber, maxIdleChecks, decisionHistory) {
|
|
155
|
-
const historySection = decisionHistory.length > 0 ? `
|
|
156
|
-
|
|
157
|
-
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
158
|
-
`)}
|
|
159
|
-
` : "";
|
|
160
|
-
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `has been idle for ${idleMinutes} minutes with no events or output changes.
|
|
161
|
-
|
|
162
|
-
` + `Original task: "${taskCtx.originalTask}"
|
|
163
|
-
` + `Working directory: ${taskCtx.workdir}
|
|
164
|
-
` + `Idle check: ${idleCheckNumber} of ${maxIdleChecks} (session will be force-escalated after ${maxIdleChecks})
|
|
165
|
-
` + historySection + `
|
|
166
|
-
Recent terminal output (last 50 lines):
|
|
167
|
-
` + `---
|
|
168
|
-
${recentOutput.slice(-3000)}
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
` + `The session has gone silent. Analyze the terminal output and decide:
|
|
172
|
-
|
|
173
|
-
` + `1. "complete" — The task is done. The output shows the objectives were met ` + `(e.g. PR created, code written, tests passed) and the agent is back at the idle prompt.
|
|
174
|
-
|
|
175
|
-
` + `2. "respond" — The agent appears stuck or waiting for input that wasn't detected ` + `as a blocking prompt. Send a message to nudge it (e.g. "continue", or answer a question ` + `visible in the output).
|
|
102
|
+
const historySection = decisionHistory.length > 0 ? `\nPrevious decisions for this session:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
|
|
103
|
+
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `has been idle for ${idleMinutes} minutes with no events or output changes.\n\n` + `Original task: "${taskCtx.originalTask}"\n` + `Working directory: ${taskCtx.workdir}\n` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}\n` + `Idle check: ${idleCheckNumber} of ${maxIdleChecks} (session will be force-escalated after ${maxIdleChecks})\n` + historySection + `\nRecent terminal output (last 50 lines):\n` + `---\n${recentOutput.slice(-3000)}\n---\n\n` + `The session has gone silent. Analyze the terminal output and decide:\n\n` + `1. "complete" \u2014 The task is FULLY done. ALL objectives in the original task were met ` + `AND the final deliverable is visible in the output (e.g. a PR URL was printed, or the ` + `task explicitly did not require a PR). The agent is back at the idle prompt.\n\n` + `2. "respond" \u2014 The agent appears stuck or waiting for input that wasn't detected ` + `as a blocking prompt. Send a message to nudge it (e.g. "continue", or answer a question ` + `visible in the output). If code was committed but no PR was created yet, respond with ` + `"please create a pull request with your changes" or similar.\n\n` + `3. "escalate" \u2014 Something looks wrong or unclear. The human should review.
|
|
176
104
|
|
|
177
|
-
` + `
|
|
105
|
+
` + `4. "ignore" \u2014 The agent is still actively working (e.g. compiling, running tests, ` + `pushing to remote, creating a PR). The idle period is expected and it will produce output soon.\n\n` + `Guidelines:\n` + `- IMPORTANT: Do NOT mark "complete" if the original task involves creating a PR and no PR URL ` + `(e.g. github.com/...pull/...) appears in the output. Instead use "respond" to nudge the agent ` + `to create the PR.\n` + `- Do NOT mark "complete" just because code was committed \u2014 commits alone don't finish a task ` + `that requires a PR.\n` + `- Network operations (git push, gh pr create, API calls) can cause several minutes of silence \u2014 ` + `prefer "ignore" for early idle checks if the agent was mid-workflow.\n` + `- If the output ends with a command prompt (\$ or >) and ALL task objectives are confirmed met, use "complete".\n` + `- If the output shows an error or the agent seems stuck in a loop, escalate.\n` + `- If the agent is clearly mid-operation (build output, test runner, git operations), use "ignore".\n` + `- On check ${idleCheckNumber} of ${maxIdleChecks} \u2014 if unsure, lean toward "respond" with a nudge rather than "complete".
|
|
178
106
|
|
|
179
|
-
` + `
|
|
180
|
-
|
|
181
|
-
` + `Guidelines:
|
|
182
|
-
` + `- If the output ends with a command prompt ($ or >) and the task objectives are met, use "complete".
|
|
183
|
-
` + `- If the output shows an error or the agent seems stuck in a loop, escalate.
|
|
184
|
-
` + `- If the agent is clearly mid-operation (build output, test runner), use "ignore".
|
|
185
|
-
` + `- On check ${idleCheckNumber} of ${maxIdleChecks} — if unsure, lean toward "escalate" rather than "ignore".
|
|
186
|
-
|
|
187
|
-
` + `Respond with ONLY a JSON object:
|
|
188
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
107
|
+
` + `Respond with ONLY a JSON object:\n` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
189
108
|
}
|
|
190
109
|
function buildTurnCompletePrompt(taskCtx, turnOutput, decisionHistory) {
|
|
191
|
-
const historySection = decisionHistory.length > 0 ? `
|
|
192
|
-
|
|
193
|
-
${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" → ${d.action}${d.response ? ` ("${d.response}")` : ""} — ${d.reasoning}`).join(`
|
|
194
|
-
`)}
|
|
195
|
-
` : "";
|
|
196
|
-
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `just finished a turn and is back at the idle prompt waiting for input.
|
|
197
|
-
|
|
198
|
-
` + `Original task: "${taskCtx.originalTask}"
|
|
199
|
-
` + `Working directory: ${taskCtx.workdir}
|
|
200
|
-
` + historySection + `
|
|
201
|
-
Output from this turn:
|
|
202
|
-
` + `---
|
|
203
|
-
${turnOutput.slice(-3000)}
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
` + `The agent completed a turn. Decide if the OVERALL task is done or if more work is needed.
|
|
110
|
+
const historySection = decisionHistory.length > 0 ? `\nPrevious decisions for this session:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] prompt="${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
|
|
111
|
+
return `You are Milady, an AI orchestrator managing a swarm of coding agents. ` + `A ${taskCtx.agentType} coding agent ("${taskCtx.label}", session: ${taskCtx.sessionId}) ` + `just finished a turn and is back at the idle prompt waiting for input.\n\n` + `Original task: "${taskCtx.originalTask}"\n` + `Working directory: ${taskCtx.workdir}\n` + `Repository: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nOutput from this turn:\n` + `---\n${turnOutput.slice(-3000)}\n---\n\n` + `The agent completed a turn. Decide if the OVERALL task is done or if more work is needed.\n\n` + `IMPORTANT: Coding agents work in multiple turns. A single turn completing does NOT mean ` + `the task is done. You must verify that EVERY objective in the original task has been addressed ` + `in the output before declaring "complete".\n\n` + `Your options:\n\n` + `1. "respond" \u2014 The agent finished a step but the overall task is NOT done yet. ` + `Send a follow-up instruction to continue. Set "response" to the next instruction ` + `(e.g. "Now run the tests", "Create a PR with these changes", "Continue with the next part"). ` + `THIS IS THE DEFAULT \u2014 most turns are intermediate steps, not the final result.
|
|
207
112
|
|
|
208
|
-
` + `
|
|
113
|
+
` + `2. "complete" \u2014 The original task objectives have ALL been fully met. For repo-based tasks, ` + `this means code was written, changes were committed, pushed, AND a pull request was created. ` + `Only use this when you can point to specific evidence in the output for EVERY objective ` + `(e.g. "Created pull request #N" in the output).\n\n` + `3. "escalate" \u2014 Something looks wrong or you're unsure whether the task is complete. ` + `Let the human decide.\n\n` + `4. "ignore" \u2014 Should not normally be used here.
|
|
209
114
|
|
|
210
|
-
` + `
|
|
115
|
+
` + `Guidelines:\n` + `- BEFORE choosing "complete", enumerate each objective from the original task and verify ` + `evidence in the output. If ANY objective lacks evidence, use "respond" with the missing work.\n` + `- A PR being created does NOT mean the task is done \u2014 check that the PR covers ALL requested changes.
|
|
116
|
+
` + `- If the task mentions multiple features/fixes, verify EACH one is addressed, not just the first.\n` + `- If the agent only analyzed code or read files, it hasn't done the actual work yet \u2014 send a follow-up.
|
|
117
|
+
` + `- If the agent wrote code but didn't test it and testing seems appropriate, ask it to run tests.\n` + `- If the output shows errors or failed tests, send a follow-up to fix them.\n` + `- IMPORTANT: If the working directory is a git repository clone (not a scratch dir), the agent ` + `MUST commit its changes, push them, and create a pull request before the task can be "complete". ` + `If the output only shows code edits with no git commit or PR, respond with "Now commit your changes, push, and create a pull request".\n` + `- IMPORTANT: Creating a PR is NOT the final step. If this is the turn where the PR was created ` + `(i.e. "Created pull request" or a PR URL appears for the FIRST time and no previous decision ` + `already sent a review follow-up), respond with "Review your PR, run each test plan item to verify ` + `it works, update the PR to check off each item, then confirm all items pass".\n` + `- If a previous decision ALREADY sent a review/verification follow-up (check the decision history), ` + `and the agent has now responded with its review results, you MAY mark "complete" if the agent ` + `indicates the work is done (e.g. "Done", "verified", "all checks pass", "Here's what I did", ` + `or a clear summary of completed work). Do NOT require exact phrases \u2014 use judgment.
|
|
118
|
+
` + `- Keep follow-up instructions concise and specific.\n` + `- Default to "respond" \u2014 only use "complete" when you're certain ALL work is done.
|
|
211
119
|
|
|
212
|
-
` + `
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
` + `
|
|
120
|
+
` + `Respond with ONLY a JSON object:\n` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
121
|
+
}
|
|
122
|
+
function buildBlockedEventMessage(taskCtx, promptText, recentOutput, decisionHistory) {
|
|
123
|
+
const historySection = decisionHistory.length > 0 ? `\nPrevious decisions:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
|
|
124
|
+
return `[Coding Agent Event] A ${taskCtx.agentType} agent ("${taskCtx.label}") is blocked and waiting for input.\n\n` + `Task: "${taskCtx.originalTask}"\n` + `Workdir: ${taskCtx.workdir}\n` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nRecent terminal output:\n---\n${recentOutput.slice(-3000)}\n---\n\n` + `Blocking prompt: "${promptText}"\n\n` + `Decide how to handle this. Options:\n` + `- "respond" \u2014 send text or keys to unblock the agent
|
|
125
|
+
` + `- "complete" \u2014 the task is fully done
|
|
126
|
+
` + `- "escalate" \u2014 you need the user's input
|
|
127
|
+
` + `- "ignore" \u2014 not actually blocking
|
|
217
128
|
|
|
218
|
-
` + `
|
|
129
|
+
` + `Guidelines:\n` + `- For tool approvals / Y/n that align with the task, respond "y" or keys:["enter"].\n` + `- If the prompt asks for info NOT in the original task, escalate.\n` + `- Decline access to paths outside ${taskCtx.workdir}.\n` + `- If a PR was just created, respond to review & verify test plan items before completing.\n` + `- When in doubt, escalate.\n\n` + `Include a JSON action block at the end of your response:\n` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}\n` + "```";
|
|
130
|
+
}
|
|
131
|
+
function buildTurnCompleteEventMessage(taskCtx, turnOutput, decisionHistory) {
|
|
132
|
+
const historySection = decisionHistory.length > 0 ? `\nPrevious decisions:\n${decisionHistory.slice(-5).map((d, i) => ` ${i + 1}. [${d.event}] "${d.promptText}" \u2192 ${d.action}${d.response ? ` ("${d.response}")` : ""} \u2014 ${d.reasoning}`).join("\n")}\n` : "";
|
|
133
|
+
return `[Coding Agent Event] A ${taskCtx.agentType} agent ("${taskCtx.label}") just finished a turn and is idle.\n\n` + `Task: "${taskCtx.originalTask}"\n` + `Workdir: ${taskCtx.workdir}\n` + `Repo: ${taskCtx.repo ?? "none (scratch directory)"}\n` + historySection + `\nTurn output:\n---\n${turnOutput.slice(-3000)}\n---\n\n` + `Decide if the overall task is done or if the agent needs more work.\n\n` + `Options:\n` + `- "respond" \u2014 send a follow-up instruction (DEFAULT for intermediate steps)
|
|
134
|
+
` + `- "complete" \u2014 ALL task objectives met (code written, committed, PR created & verified)
|
|
135
|
+
` + `- "escalate" \u2014 something looks wrong, ask the user
|
|
136
|
+
` + `- "ignore" \u2014 should not normally be used here
|
|
219
137
|
|
|
220
|
-
` + `Guidelines
|
|
221
|
-
` + `- BEFORE choosing "complete", enumerate each objective from the original task and verify ` + `evidence in the output. If ANY objective lacks evidence, use "respond" with the missing work.
|
|
222
|
-
` + `- A PR being created does NOT mean the task is done — check that the PR covers ALL requested changes.
|
|
223
|
-
` + `- If the task mentions multiple features/fixes, verify EACH one is addressed, not just the first.
|
|
224
|
-
` + `- If the agent only analyzed code or read files, it hasn't done the actual work yet — send a follow-up.
|
|
225
|
-
` + `- If the agent wrote code but didn't test it and testing seems appropriate, ask it to run tests.
|
|
226
|
-
` + `- If the output shows errors or failed tests, send a follow-up to fix them.
|
|
227
|
-
` + `- IMPORTANT: If the working directory is a git repository clone (not a scratch dir), the agent ` + `MUST commit its changes, push them, and create a pull request before the task can be "complete". ` + `If the output only shows code edits with no git commit or PR, respond with "Now commit your changes, push, and create a pull request".
|
|
228
|
-
` + `- CRITICAL: Creating a PR is NEVER the final step. After you see "Created pull request" or a PR URL ` + `in the output, you MUST respond with "Review your PR, run each test plan item to verify it works, ` + `update the PR to check off each item, then confirm all items pass". NEVER mark as "complete" on the ` + `same turn that a PR was created — always send this follow-up first.
|
|
229
|
-
` + `- Only mark as "complete" AFTER the agent has confirmed it verified the test plan items ` + `(look for output like "all items pass", "verified", "checked off", or similar confirmation).
|
|
230
|
-
` + `- Keep follow-up instructions concise and specific.
|
|
231
|
-
` + `- Default to "respond" — only use "complete" when you're certain ALL work is done.
|
|
138
|
+
` + `Guidelines:\n` + `- Verify evidence for EVERY objective before using "complete".\n` + `- If code was written but not committed/pushed/PR'd, respond with next step.\n` + `- If a PR was just created, respond to review & verify test plan items.\n` + `- Default to "respond" \u2014 only "complete" when certain ALL work is done.
|
|
232
139
|
|
|
233
|
-
` + `
|
|
234
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
140
|
+
` + `Include a JSON action block at the end of your response:\n` + "```json\n" + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}\n` + "```";
|
|
235
141
|
}
|
|
236
142
|
function parseCoordinationResponse(llmOutput) {
|
|
237
143
|
const jsonMatch = llmOutput.match(/\{[\s\S]*\}/);
|
|
@@ -262,24 +168,145 @@ function parseCoordinationResponse(llmOutput) {
|
|
|
262
168
|
}
|
|
263
169
|
}
|
|
264
170
|
|
|
171
|
+
// src/services/swarm-event-triage.ts
|
|
172
|
+
import {ModelType as ModelType2} from "@elizaos/core";
|
|
173
|
+
function classifyByHeuristic(ctx) {
|
|
174
|
+
if (ctx.promptType) {
|
|
175
|
+
if (ROUTINE_PROMPT_TYPES.has(ctx.promptType))
|
|
176
|
+
return "routine";
|
|
177
|
+
if (CREATIVE_PROMPT_TYPES.has(ctx.promptType))
|
|
178
|
+
return "creative";
|
|
179
|
+
}
|
|
180
|
+
if (ctx.eventType === "blocked" && ctx.promptText) {
|
|
181
|
+
const hasRoutine = ROUTINE_PATTERNS.some((r) => r.test(ctx.promptText));
|
|
182
|
+
const hasCreative = CREATIVE_PATTERNS.some((r) => r.test(ctx.promptText));
|
|
183
|
+
if (hasRoutine && !hasCreative)
|
|
184
|
+
return "routine";
|
|
185
|
+
if (hasCreative && !hasRoutine)
|
|
186
|
+
return "creative";
|
|
187
|
+
if (hasCreative)
|
|
188
|
+
return "creative";
|
|
189
|
+
}
|
|
190
|
+
if (ctx.eventType === "turn_complete" && ctx.recentOutput) {
|
|
191
|
+
const isTerminal = TERMINAL_OUTPUT_PATTERNS.some((r) => r.test(ctx.recentOutput));
|
|
192
|
+
const isIntermediate = INTERMEDIATE_OUTPUT_PATTERNS.some((r) => r.test(ctx.recentOutput));
|
|
193
|
+
if (isTerminal || isIntermediate)
|
|
194
|
+
return "routine";
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function buildTriagePrompt(ctx) {
|
|
199
|
+
const eventDesc = ctx.eventType === "blocked" ? `BLOCKED prompt: "${ctx.promptText.slice(0, 300)}"` : `TURN COMPLETE. Recent output:\n${(ctx.recentOutput ?? "").slice(-500)}`;
|
|
200
|
+
return `Classify this coding agent event as "routine" or "creative".\n\n` + `Task: ${ctx.originalTask.slice(0, 200)}\n` + `Event: ${eventDesc}\n\n` + `"routine" = simple approval, permission, config, yes/no, tool consent, obvious pass/fail.\n` + `"creative" = needs task context, error recovery, design choice, ambiguous situation, approach selection.\n\n` + `Respond with ONLY a JSON object: {"tier": "routine"} or {"tier": "creative"}`;
|
|
201
|
+
}
|
|
202
|
+
function parseTriageResponse(llmOutput) {
|
|
203
|
+
const matches = llmOutput.matchAll(/\{[\s\S]*?\}/g);
|
|
204
|
+
for (const match of matches) {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = JSON.parse(match[0]);
|
|
207
|
+
if (parsed.tier === "routine" || parsed.tier === "creative") {
|
|
208
|
+
return parsed.tier;
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
async function classifyEventTier(runtime, ctx, log) {
|
|
216
|
+
const heuristicResult = classifyByHeuristic(ctx);
|
|
217
|
+
if (heuristicResult) {
|
|
218
|
+
log(`Triage: heuristic \u2192 ${heuristicResult}`);
|
|
219
|
+
return heuristicResult;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const prompt = buildTriagePrompt(ctx);
|
|
223
|
+
const result = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt });
|
|
224
|
+
const tier = parseTriageResponse(result);
|
|
225
|
+
if (tier) {
|
|
226
|
+
log(`Triage: LLM \u2192 ${tier}`);
|
|
227
|
+
return tier;
|
|
228
|
+
}
|
|
229
|
+
log(`Triage: LLM returned unparseable response \u2014 defaulting to creative`);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log(`Triage: LLM classifier failed: ${err} \u2014 defaulting to creative`);
|
|
232
|
+
}
|
|
233
|
+
return "creative";
|
|
234
|
+
}
|
|
235
|
+
var ROUTINE_PROMPT_TYPES, CREATIVE_PROMPT_TYPES, ROUTINE_PATTERNS, CREATIVE_PATTERNS, TERMINAL_OUTPUT_PATTERNS, INTERMEDIATE_OUTPUT_PATTERNS;
|
|
236
|
+
var init_swarm_event_triage = __esm(() => {
|
|
237
|
+
ROUTINE_PROMPT_TYPES = new Set([
|
|
238
|
+
"permission",
|
|
239
|
+
"config",
|
|
240
|
+
"tos",
|
|
241
|
+
"tool_wait"
|
|
242
|
+
]);
|
|
243
|
+
CREATIVE_PROMPT_TYPES = new Set(["project_select", "model_select"]);
|
|
244
|
+
ROUTINE_PATTERNS = [
|
|
245
|
+
/\bAllow\s+tool\b/i,
|
|
246
|
+
/\(Y\/n\)/,
|
|
247
|
+
/\(y\/N\)/,
|
|
248
|
+
/\bTrust\s+(this\s+)?directory\b/i,
|
|
249
|
+
/\bProceed\?/i,
|
|
250
|
+
/\boverwrite\?/i,
|
|
251
|
+
/\bDo you trust\b/i,
|
|
252
|
+
/\bAllow access\b/i,
|
|
253
|
+
/\bGrant permission\b/i,
|
|
254
|
+
/\bAccept\?/i,
|
|
255
|
+
/\bContinue\?/i,
|
|
256
|
+
/\bPermit\b.*\?/i,
|
|
257
|
+
/\bApprove\b.*\?/i
|
|
258
|
+
];
|
|
259
|
+
CREATIVE_PATTERNS = [
|
|
260
|
+
/\bWhich approach\b/i,
|
|
261
|
+
/\bHow should\b/i,
|
|
262
|
+
/\btests? failing\b/i,
|
|
263
|
+
/\bchoose between\b/i,
|
|
264
|
+
/\bpick (one|a|an)\b/i,
|
|
265
|
+
/\bWhat do you (want|think)\b/i,
|
|
266
|
+
/\berror recover/i,
|
|
267
|
+
/\bfailed with\b/i,
|
|
268
|
+
/\bcompilation error/i,
|
|
269
|
+
/\bbuild failed\b/i,
|
|
270
|
+
/\btype error/i,
|
|
271
|
+
/\bmerge conflict/i
|
|
272
|
+
];
|
|
273
|
+
TERMINAL_OUTPUT_PATTERNS = [
|
|
274
|
+
/All \d+ tests? pass/i,
|
|
275
|
+
/Tests?:\s+\d+ passed/i,
|
|
276
|
+
/✓ All checks passed/i,
|
|
277
|
+
/https:\/\/github\.com\/[^\s]+\/pull\/\d+/,
|
|
278
|
+
/Successfully created PR/i,
|
|
279
|
+
/Commit [a-f0-9]{7,40}/i
|
|
280
|
+
];
|
|
281
|
+
INTERMEDIATE_OUTPUT_PATTERNS = [
|
|
282
|
+
/^Running tests?\.\.\./im,
|
|
283
|
+
/^Building\.\.\./im,
|
|
284
|
+
/^Installing dependencies/im
|
|
285
|
+
];
|
|
286
|
+
});
|
|
287
|
+
|
|
265
288
|
// src/services/swarm-decision-loop.ts
|
|
266
289
|
var exports_swarm_decision_loop = {};
|
|
267
290
|
__export(exports_swarm_decision_loop, {
|
|
268
291
|
makeCoordinationDecision: () => makeCoordinationDecision,
|
|
292
|
+
isOutOfScopeAccess: () => isOutOfScopeAccess,
|
|
269
293
|
handleTurnComplete: () => handleTurnComplete,
|
|
270
294
|
handleConfirmDecision: () => handleConfirmDecision,
|
|
271
295
|
handleBlocked: () => handleBlocked,
|
|
272
296
|
handleAutonomousDecision: () => handleAutonomousDecision,
|
|
273
|
-
executeDecision: () => executeDecision
|
|
297
|
+
executeDecision: () => executeDecision,
|
|
298
|
+
checkAllTasksComplete: () => checkAllTasksComplete
|
|
274
299
|
});
|
|
275
|
-
import
|
|
300
|
+
import * as path from "node:path";
|
|
301
|
+
import {ModelType as ModelType3} from "@elizaos/core";
|
|
276
302
|
function toContextSummary(taskCtx) {
|
|
277
303
|
return {
|
|
278
304
|
sessionId: taskCtx.sessionId,
|
|
279
305
|
agentType: taskCtx.agentType,
|
|
280
306
|
label: taskCtx.label,
|
|
281
307
|
originalTask: taskCtx.originalTask,
|
|
282
|
-
workdir: taskCtx.workdir
|
|
308
|
+
workdir: taskCtx.workdir,
|
|
309
|
+
repo: taskCtx.repo
|
|
283
310
|
};
|
|
284
311
|
}
|
|
285
312
|
function toDecisionHistory(taskCtx) {
|
|
@@ -296,6 +323,58 @@ function formatDecisionResponse(decision) {
|
|
|
296
323
|
return;
|
|
297
324
|
return decision.useKeys ? `keys:${decision.keys?.join(",")}` : decision.response;
|
|
298
325
|
}
|
|
326
|
+
function isOutOfScopeAccess(promptText, workdir) {
|
|
327
|
+
const stripped = promptText.replace(/https?:\/\/\S+/g, "");
|
|
328
|
+
const multiSegment = /\/[\w.-]+(?:\/[\w.-]+)+/g;
|
|
329
|
+
const sensitiveRoots = /\b\/(etc|tmp|var|usr|opt|sys|proc|root)\b/g;
|
|
330
|
+
const homeTilde = /~\/[\w.-]+/g;
|
|
331
|
+
const matches = [
|
|
332
|
+
...stripped.match(multiSegment) ?? [],
|
|
333
|
+
...(stripped.match(sensitiveRoots) ?? []).map((m) => m.trimStart()),
|
|
334
|
+
...(stripped.match(homeTilde) ?? []).map((m) => m.replace("~", process.env.HOME ?? "/home/user"))
|
|
335
|
+
];
|
|
336
|
+
if (matches.length === 0)
|
|
337
|
+
return false;
|
|
338
|
+
const resolvedWorkdir = path.resolve(workdir);
|
|
339
|
+
return matches.some((p) => {
|
|
340
|
+
const resolved = path.resolve(p);
|
|
341
|
+
return !resolved.startsWith(resolvedWorkdir + path.sep) && resolved !== resolvedWorkdir;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function checkAllTasksComplete(ctx) {
|
|
345
|
+
const tasks = Array.from(ctx.tasks.values());
|
|
346
|
+
if (tasks.length === 0)
|
|
347
|
+
return;
|
|
348
|
+
const terminalStates = new Set(["completed", "stopped", "error"]);
|
|
349
|
+
const allDone = tasks.every((t) => terminalStates.has(t.status));
|
|
350
|
+
if (!allDone)
|
|
351
|
+
return;
|
|
352
|
+
const completed = tasks.filter((t) => t.status === "completed");
|
|
353
|
+
const stopped = tasks.filter((t) => t.status === "stopped");
|
|
354
|
+
const errored = tasks.filter((t) => t.status === "error");
|
|
355
|
+
const parts = [];
|
|
356
|
+
if (completed.length > 0) {
|
|
357
|
+
parts.push(`${completed.length} completed`);
|
|
358
|
+
}
|
|
359
|
+
if (stopped.length > 0) {
|
|
360
|
+
parts.push(`${stopped.length} stopped`);
|
|
361
|
+
}
|
|
362
|
+
if (errored.length > 0) {
|
|
363
|
+
parts.push(`${errored.length} errored`);
|
|
364
|
+
}
|
|
365
|
+
ctx.sendChatMessage(`All ${tasks.length} coding agents finished (${parts.join(", ")}). Review their work when you're ready.`, "coding-agent");
|
|
366
|
+
ctx.broadcast({
|
|
367
|
+
type: "swarm_complete",
|
|
368
|
+
sessionId: "",
|
|
369
|
+
timestamp: Date.now(),
|
|
370
|
+
data: {
|
|
371
|
+
total: tasks.length,
|
|
372
|
+
completed: completed.length,
|
|
373
|
+
stopped: stopped.length,
|
|
374
|
+
errored: errored.length
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
299
378
|
async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
300
379
|
if (!ctx.ptyService)
|
|
301
380
|
return "";
|
|
@@ -308,7 +387,7 @@ async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
|
308
387
|
async function makeCoordinationDecision(ctx, taskCtx, promptText, recentOutput) {
|
|
309
388
|
const prompt = buildCoordinationPrompt(toContextSummary(taskCtx), promptText, recentOutput, toDecisionHistory(taskCtx));
|
|
310
389
|
try {
|
|
311
|
-
const result = await ctx.runtime.useModel(
|
|
390
|
+
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
312
391
|
prompt
|
|
313
392
|
});
|
|
314
393
|
return parseCoordinationResponse(result);
|
|
@@ -343,13 +422,13 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
343
422
|
try {
|
|
344
423
|
const rawOutput = await ctx.ptyService.getSessionOutput(sessionId, 50);
|
|
345
424
|
summary = extractCompletionSummary(rawOutput);
|
|
346
|
-
} catch {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
ctx.sendChatMessage(summary ? `Finished "${taskCtx?.label ?? sessionId}".\n\n${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
|
|
350
428
|
ctx.ptyService.stopSession(sessionId).catch((err) => {
|
|
351
429
|
ctx.log(`Failed to stop session after LLM-detected completion: ${err}`);
|
|
352
430
|
});
|
|
431
|
+
checkAllTasksComplete(ctx);
|
|
353
432
|
break;
|
|
354
433
|
}
|
|
355
434
|
case "escalate":
|
|
@@ -370,6 +449,31 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
370
449
|
const eventData = data;
|
|
371
450
|
const promptText = eventData.promptInfo?.prompt ?? eventData.promptInfo?.instructions ?? "";
|
|
372
451
|
if (eventData.autoResponded) {
|
|
452
|
+
if (isOutOfScopeAccess(promptText, taskCtx.workdir)) {
|
|
453
|
+
taskCtx.decisions.push({
|
|
454
|
+
timestamp: Date.now(),
|
|
455
|
+
event: "blocked",
|
|
456
|
+
promptText,
|
|
457
|
+
decision: "escalate",
|
|
458
|
+
reasoning: `SECURITY: Auto-response approved access outside workspace (${taskCtx.workdir}). Session stopped.`
|
|
459
|
+
});
|
|
460
|
+
ctx.broadcast({
|
|
461
|
+
type: "escalation",
|
|
462
|
+
sessionId,
|
|
463
|
+
timestamp: Date.now(),
|
|
464
|
+
data: {
|
|
465
|
+
prompt: promptText,
|
|
466
|
+
reason: "out_of_scope_auto_approved",
|
|
467
|
+
workdir: taskCtx.workdir
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
ctx.sendChatMessage(`[${taskCtx.label}] WARNING: Auto-approved access to path outside workspace (${taskCtx.workdir}). ` + `Prompt: "${promptText.slice(0, 150)}". Stopping session for safety.`, "coding-agent");
|
|
471
|
+
taskCtx.status = "error";
|
|
472
|
+
ctx.ptyService?.stopSession(sessionId).catch((err) => {
|
|
473
|
+
ctx.log(`Failed to stop session after out-of-scope auto-approval: ${err}`);
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
373
477
|
taskCtx.autoResolvedCount++;
|
|
374
478
|
taskCtx.decisions.push({
|
|
375
479
|
timestamp: Date.now(),
|
|
@@ -426,10 +530,10 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
426
530
|
}
|
|
427
531
|
switch (ctx.getSupervisionLevel()) {
|
|
428
532
|
case "autonomous":
|
|
429
|
-
await handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, "");
|
|
533
|
+
await handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, "", eventData.promptInfo?.type);
|
|
430
534
|
break;
|
|
431
535
|
case "confirm":
|
|
432
|
-
await handleConfirmDecision(ctx, sessionId, taskCtx, promptText, "");
|
|
536
|
+
await handleConfirmDecision(ctx, sessionId, taskCtx, promptText, "", eventData.promptInfo?.type);
|
|
433
537
|
break;
|
|
434
538
|
case "notify":
|
|
435
539
|
taskCtx.decisions.push({
|
|
@@ -437,7 +541,7 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
437
541
|
event: "blocked",
|
|
438
542
|
promptText,
|
|
439
543
|
decision: "escalate",
|
|
440
|
-
reasoning: "Supervision level is notify
|
|
544
|
+
reasoning: "Supervision level is notify \u2014 broadcasting only"
|
|
441
545
|
});
|
|
442
546
|
break;
|
|
443
547
|
}
|
|
@@ -449,31 +553,64 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
449
553
|
}
|
|
450
554
|
ctx.inFlightDecisions.add(sessionId);
|
|
451
555
|
try {
|
|
452
|
-
ctx.log(`Turn complete for "${taskCtx.label}"
|
|
556
|
+
ctx.log(`Turn complete for "${taskCtx.label}" \u2014 assessing whether task is done`);
|
|
453
557
|
const rawResponse = data.response ?? "";
|
|
454
558
|
let turnOutput = cleanForChat(rawResponse);
|
|
455
559
|
if (!turnOutput) {
|
|
456
560
|
const raw = await fetchRecentOutput(ctx, sessionId);
|
|
457
561
|
turnOutput = cleanForChat(raw);
|
|
458
562
|
}
|
|
459
|
-
const
|
|
563
|
+
const agentDecisionCb = ctx.getAgentDecisionCallback();
|
|
460
564
|
let decision = null;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
565
|
+
let decisionFromPipeline = false;
|
|
566
|
+
const triageCtx = {
|
|
567
|
+
eventType: "turn_complete",
|
|
568
|
+
promptText: "",
|
|
569
|
+
recentOutput: turnOutput,
|
|
570
|
+
originalTask: taskCtx.originalTask
|
|
571
|
+
};
|
|
572
|
+
const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
|
|
573
|
+
if (tier === "routine") {
|
|
574
|
+
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
|
|
575
|
+
try {
|
|
576
|
+
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
577
|
+
prompt
|
|
578
|
+
});
|
|
579
|
+
decision = parseCoordinationResponse(result);
|
|
580
|
+
} catch (err) {
|
|
581
|
+
ctx.log(`Turn-complete LLM call failed: ${err}`);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
if (agentDecisionCb) {
|
|
585
|
+
const eventMessage = buildTurnCompleteEventMessage(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
|
|
586
|
+
try {
|
|
587
|
+
decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
|
|
588
|
+
if (decision)
|
|
589
|
+
decisionFromPipeline = true;
|
|
590
|
+
} catch (err) {
|
|
591
|
+
ctx.log(`Agent decision callback failed for turn-complete: ${err} \u2014 falling back to small LLM`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (!decision) {
|
|
595
|
+
const prompt = buildTurnCompletePrompt(toContextSummary(taskCtx), turnOutput, toDecisionHistory(taskCtx));
|
|
596
|
+
try {
|
|
597
|
+
const result = await ctx.runtime.useModel(ModelType3.TEXT_SMALL, {
|
|
598
|
+
prompt
|
|
599
|
+
});
|
|
600
|
+
decision = parseCoordinationResponse(result);
|
|
601
|
+
} catch (err) {
|
|
602
|
+
ctx.log(`Turn-complete LLM fallback call failed: ${err}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
468
605
|
}
|
|
469
606
|
if (!decision) {
|
|
470
|
-
ctx.log(`Turn-complete for "${taskCtx.label}":
|
|
607
|
+
ctx.log(`Turn-complete for "${taskCtx.label}": all decision paths failed \u2014 escalating`);
|
|
471
608
|
decision = {
|
|
472
|
-
action: "
|
|
473
|
-
reasoning: "
|
|
609
|
+
action: "escalate",
|
|
610
|
+
reasoning: "All decision paths returned invalid response \u2014 escalating for human review"
|
|
474
611
|
};
|
|
475
612
|
}
|
|
476
|
-
ctx.log(`Turn assessment for "${taskCtx.label}": ${decision.action}${decision.action === "respond" ? `
|
|
613
|
+
ctx.log(`Turn assessment for "${taskCtx.label}": ${decision.action}${decision.action === "respond" ? ` \u2192 "${(decision.response ?? "").slice(0, 80)}"` : ""} \u2014 ${decision.reasoning.slice(0, 120)}`);
|
|
477
614
|
taskCtx.decisions.push({
|
|
478
615
|
timestamp: Date.now(),
|
|
479
616
|
event: "turn_complete",
|
|
@@ -491,19 +628,21 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
491
628
|
reasoning: decision.reasoning
|
|
492
629
|
}
|
|
493
630
|
});
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
631
|
+
if (!decisionFromPipeline) {
|
|
632
|
+
if (decision.action === "respond") {
|
|
633
|
+
const instruction = decision.response ?? "";
|
|
634
|
+
const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
|
|
635
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Turn done, continuing: ${preview}`, "coding-agent");
|
|
636
|
+
} else if (decision.action === "escalate") {
|
|
637
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Turn finished \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
638
|
+
}
|
|
500
639
|
}
|
|
501
640
|
await executeDecision(ctx, sessionId, decision);
|
|
502
641
|
} finally {
|
|
503
642
|
ctx.inFlightDecisions.delete(sessionId);
|
|
504
643
|
}
|
|
505
644
|
}
|
|
506
|
-
async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, recentOutput) {
|
|
645
|
+
async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
|
|
507
646
|
if (ctx.inFlightDecisions.has(sessionId)) {
|
|
508
647
|
ctx.log(`Skipping duplicate decision for ${sessionId} (in-flight)`);
|
|
509
648
|
return;
|
|
@@ -514,14 +653,41 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
514
653
|
if (!output) {
|
|
515
654
|
output = await fetchRecentOutput(ctx, sessionId);
|
|
516
655
|
}
|
|
517
|
-
const
|
|
656
|
+
const agentDecisionCb = ctx.getAgentDecisionCallback();
|
|
657
|
+
let decision = null;
|
|
658
|
+
let decisionFromPipeline = false;
|
|
659
|
+
const triageCtx = {
|
|
660
|
+
eventType: "blocked",
|
|
661
|
+
promptText,
|
|
662
|
+
promptType,
|
|
663
|
+
recentOutput: output,
|
|
664
|
+
originalTask: taskCtx.originalTask
|
|
665
|
+
};
|
|
666
|
+
const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
|
|
667
|
+
if (tier === "routine") {
|
|
668
|
+
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
669
|
+
} else {
|
|
670
|
+
if (agentDecisionCb) {
|
|
671
|
+
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
|
|
672
|
+
try {
|
|
673
|
+
decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
|
|
674
|
+
if (decision)
|
|
675
|
+
decisionFromPipeline = true;
|
|
676
|
+
} catch (err) {
|
|
677
|
+
ctx.log(`Agent decision callback failed: ${err} \u2014 falling back to small LLM`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (!decision) {
|
|
681
|
+
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
518
684
|
if (!decision) {
|
|
519
685
|
taskCtx.decisions.push({
|
|
520
686
|
timestamp: Date.now(),
|
|
521
687
|
event: "blocked",
|
|
522
688
|
promptText,
|
|
523
689
|
decision: "escalate",
|
|
524
|
-
reasoning: "
|
|
690
|
+
reasoning: "All decision paths returned invalid coordination response"
|
|
525
691
|
});
|
|
526
692
|
ctx.broadcast({
|
|
527
693
|
type: "escalation",
|
|
@@ -534,6 +700,14 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
534
700
|
});
|
|
535
701
|
return;
|
|
536
702
|
}
|
|
703
|
+
if (decision.action === "respond" && isOutOfScopeAccess(promptText, taskCtx.workdir)) {
|
|
704
|
+
decision = {
|
|
705
|
+
action: "respond",
|
|
706
|
+
response: `No \u2014 that path is outside your workspace. Use ${taskCtx.workdir} instead. Create any files or directories you need there.`,
|
|
707
|
+
reasoning: `Declined out-of-scope access (outside ${taskCtx.workdir}) and redirected agent to workspace.`
|
|
708
|
+
};
|
|
709
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Declined out-of-scope access and redirected to workspace (${taskCtx.workdir}). If you intended broader access, send the agent an override.`, "coding-agent");
|
|
710
|
+
}
|
|
537
711
|
taskCtx.decisions.push({
|
|
538
712
|
timestamp: Date.now(),
|
|
539
713
|
event: "blocked",
|
|
@@ -555,19 +729,21 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
555
729
|
reasoning: decision.reasoning
|
|
556
730
|
}
|
|
557
731
|
});
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
732
|
+
if (!decisionFromPipeline) {
|
|
733
|
+
if (decision.action === "respond") {
|
|
734
|
+
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
|
|
735
|
+
const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
|
|
736
|
+
ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc} \u2014 ${reasonExcerpt}`, "coding-agent");
|
|
737
|
+
} else if (decision.action === "escalate") {
|
|
738
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
739
|
+
}
|
|
564
740
|
}
|
|
565
741
|
await executeDecision(ctx, sessionId, decision);
|
|
566
742
|
} finally {
|
|
567
743
|
ctx.inFlightDecisions.delete(sessionId);
|
|
568
744
|
}
|
|
569
745
|
}
|
|
570
|
-
async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recentOutput) {
|
|
746
|
+
async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recentOutput, promptType) {
|
|
571
747
|
if (ctx.inFlightDecisions.has(sessionId))
|
|
572
748
|
return;
|
|
573
749
|
ctx.inFlightDecisions.add(sessionId);
|
|
@@ -576,7 +752,34 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
576
752
|
if (!output) {
|
|
577
753
|
output = await fetchRecentOutput(ctx, sessionId);
|
|
578
754
|
}
|
|
579
|
-
const
|
|
755
|
+
const agentDecisionCb = ctx.getAgentDecisionCallback();
|
|
756
|
+
let decision = null;
|
|
757
|
+
let decisionFromPipeline = false;
|
|
758
|
+
const triageCtx = {
|
|
759
|
+
eventType: "blocked",
|
|
760
|
+
promptText,
|
|
761
|
+
promptType,
|
|
762
|
+
recentOutput: output,
|
|
763
|
+
originalTask: taskCtx.originalTask
|
|
764
|
+
};
|
|
765
|
+
const tier = agentDecisionCb ? await classifyEventTier(ctx.runtime, triageCtx, ctx.log) : "routine";
|
|
766
|
+
if (tier === "routine") {
|
|
767
|
+
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
768
|
+
} else {
|
|
769
|
+
if (agentDecisionCb) {
|
|
770
|
+
const eventMessage = buildBlockedEventMessage(toContextSummary(taskCtx), promptText, output, toDecisionHistory(taskCtx));
|
|
771
|
+
try {
|
|
772
|
+
decision = await agentDecisionCb(eventMessage, sessionId, taskCtx);
|
|
773
|
+
if (decision)
|
|
774
|
+
decisionFromPipeline = true;
|
|
775
|
+
} catch (err) {
|
|
776
|
+
ctx.log(`Agent decision callback failed (confirm): ${err} \u2014 falling back to small LLM`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!decision) {
|
|
780
|
+
decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
580
783
|
if (!decision) {
|
|
581
784
|
ctx.pendingDecisions.set(sessionId, {
|
|
582
785
|
sessionId,
|
|
@@ -584,7 +787,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
584
787
|
recentOutput: output,
|
|
585
788
|
llmDecision: {
|
|
586
789
|
action: "escalate",
|
|
587
|
-
reasoning: "
|
|
790
|
+
reasoning: "All decision paths returned invalid response \u2014 needs human review"
|
|
588
791
|
},
|
|
589
792
|
taskContext: taskCtx,
|
|
590
793
|
createdAt: Date.now()
|
|
@@ -607,7 +810,8 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
607
810
|
prompt: promptText,
|
|
608
811
|
suggestedAction: decision?.action,
|
|
609
812
|
suggestedResponse: decision?.response,
|
|
610
|
-
reasoning: decision?.reasoning
|
|
813
|
+
reasoning: decision?.reasoning,
|
|
814
|
+
fromPipeline: decisionFromPipeline
|
|
611
815
|
}
|
|
612
816
|
});
|
|
613
817
|
} finally {
|
|
@@ -617,6 +821,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
617
821
|
var MAX_AUTO_RESPONSES = 10;
|
|
618
822
|
var init_swarm_decision_loop = __esm(() => {
|
|
619
823
|
init_ansi_utils();
|
|
824
|
+
init_swarm_event_triage();
|
|
620
825
|
});
|
|
621
826
|
|
|
622
827
|
// src/actions/finalize-workspace.ts
|
|
@@ -706,9 +911,7 @@ var finalizeWorkspaceAction = {
|
|
|
706
911
|
data: { workspaceId, status }
|
|
707
912
|
};
|
|
708
913
|
}
|
|
709
|
-
const commitMessage = content.commitMessage ?? `feat: automated changes from coding agent
|
|
710
|
-
|
|
711
|
-
Generated by Milady coding agent plugin.`;
|
|
914
|
+
const commitMessage = content.commitMessage ?? `feat: automated changes from coding agent\n\nGenerated by Milady coding agent plugin.`;
|
|
712
915
|
const commitHash = await workspaceService.commit(workspaceId, {
|
|
713
916
|
message: commitMessage,
|
|
714
917
|
all: true
|
|
@@ -717,15 +920,7 @@ Generated by Milady coding agent plugin.`;
|
|
|
717
920
|
let prInfo = null;
|
|
718
921
|
if (!content.skipPR) {
|
|
719
922
|
const prTitle = content.prTitle ?? `[Milady] ${workspace.branch}`;
|
|
720
|
-
const prBody = content.prBody ?? `## Summary
|
|
721
|
-
|
|
722
|
-
Automated changes generated by Milady coding agent.
|
|
723
|
-
|
|
724
|
-
` + `**Branch:** ${workspace.branch}
|
|
725
|
-
` + `**Commit:** ${commitHash}
|
|
726
|
-
|
|
727
|
-
` + `---
|
|
728
|
-
*Generated by @elizaos/plugin-agent-orchestrator*`;
|
|
923
|
+
const prBody = content.prBody ?? `## Summary\n\nAutomated changes generated by Milady coding agent.\n\n` + `**Branch:** ${workspace.branch}\n` + `**Commit:** ${commitHash}\n\n` + `---\n*Generated by @elizaos/plugin-agent-orchestrator*`;
|
|
729
924
|
prInfo = await workspaceService.createPR(workspaceId, {
|
|
730
925
|
title: prTitle,
|
|
731
926
|
body: prBody,
|
|
@@ -736,14 +931,11 @@ Automated changes generated by Milady coding agent.
|
|
|
736
931
|
if (callback) {
|
|
737
932
|
if (prInfo) {
|
|
738
933
|
await callback({
|
|
739
|
-
text: `Workspace finalized
|
|
740
|
-
` + `Commit: ${commitHash.slice(0, 8)}
|
|
741
|
-
` + `PR #${prInfo.number}: ${prInfo.url}`
|
|
934
|
+
text: `Workspace finalized!\n` + `Commit: ${commitHash.slice(0, 8)}\n` + `PR #${prInfo.number}: ${prInfo.url}`
|
|
742
935
|
});
|
|
743
936
|
} else {
|
|
744
937
|
await callback({
|
|
745
|
-
text: `Workspace changes committed and pushed.
|
|
746
|
-
` + `Commit: ${commitHash.slice(0, 8)}`
|
|
938
|
+
text: `Workspace changes committed and pushed.\n` + `Commit: ${commitHash.slice(0, 8)}`
|
|
747
939
|
});
|
|
748
940
|
}
|
|
749
941
|
}
|
|
@@ -887,23 +1079,18 @@ var listAgentsAction = {
|
|
|
887
1079
|
}));
|
|
888
1080
|
const lines = sessions.map((session, index) => {
|
|
889
1081
|
const statusEmoji = {
|
|
890
|
-
running: "
|
|
891
|
-
idle: "
|
|
892
|
-
blocked: "
|
|
893
|
-
completed: "
|
|
894
|
-
error: "
|
|
895
|
-
}[session.status] ?? "
|
|
1082
|
+
running: "\u25B6\uFE0F",
|
|
1083
|
+
idle: "\u23F8\uFE0F",
|
|
1084
|
+
blocked: "\u26A0\uFE0F",
|
|
1085
|
+
completed: "\u2705",
|
|
1086
|
+
error: "\u274C"
|
|
1087
|
+
}[session.status] ?? "\u2753";
|
|
896
1088
|
return `${index + 1}. ${statusEmoji} ${session.agentType} (${session.id.slice(0, 8)}...)
|
|
897
|
-
\uD83D\uDCC1 ${session.workdir}
|
|
898
|
-
Status: ${session.status}`;
|
|
1089
|
+
\uD83D\uDCC1 ${session.workdir}\n Status: ${session.status}`;
|
|
899
1090
|
});
|
|
900
1091
|
if (callback) {
|
|
901
1092
|
await callback({
|
|
902
|
-
text: `Active coding agents
|
|
903
|
-
|
|
904
|
-
${lines.join(`
|
|
905
|
-
|
|
906
|
-
`)}`
|
|
1093
|
+
text: `Active coding agents:\n\n${lines.join("\n\n")}`
|
|
907
1094
|
});
|
|
908
1095
|
}
|
|
909
1096
|
return {
|
|
@@ -916,150 +1103,6 @@ ${lines.join(`
|
|
|
916
1103
|
};
|
|
917
1104
|
|
|
918
1105
|
// src/actions/manage-issues.ts
|
|
919
|
-
var manageIssuesAction = {
|
|
920
|
-
name: "MANAGE_ISSUES",
|
|
921
|
-
similes: [
|
|
922
|
-
"CREATE_ISSUE",
|
|
923
|
-
"LIST_ISSUES",
|
|
924
|
-
"CLOSE_ISSUE",
|
|
925
|
-
"COMMENT_ISSUE",
|
|
926
|
-
"UPDATE_ISSUE",
|
|
927
|
-
"GET_ISSUE"
|
|
928
|
-
],
|
|
929
|
-
description: "Manage GitHub issues for a repository. " + "Supports creating issues, listing issues, getting issue details, " + "adding comments, updating, closing, and reopening issues.",
|
|
930
|
-
examples: [
|
|
931
|
-
[
|
|
932
|
-
{
|
|
933
|
-
name: "{{user1}}",
|
|
934
|
-
content: {
|
|
935
|
-
text: "Create an issue on the testbed repo to add a login page"
|
|
936
|
-
}
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
name: "{{agentName}}",
|
|
940
|
-
content: {
|
|
941
|
-
text: "I'll create that issue for you.",
|
|
942
|
-
action: "MANAGE_ISSUES"
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
],
|
|
946
|
-
[
|
|
947
|
-
{
|
|
948
|
-
name: "{{user1}}",
|
|
949
|
-
content: {
|
|
950
|
-
text: "List the open issues on HaruHunab1320/git-workspace-service-testbed"
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
name: "{{agentName}}",
|
|
955
|
-
content: {
|
|
956
|
-
text: "Let me check the open issues for that repo.",
|
|
957
|
-
action: "MANAGE_ISSUES"
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
],
|
|
961
|
-
[
|
|
962
|
-
{
|
|
963
|
-
name: "{{user1}}",
|
|
964
|
-
content: { text: "Close issue #3 on the testbed repo" }
|
|
965
|
-
},
|
|
966
|
-
{
|
|
967
|
-
name: "{{agentName}}",
|
|
968
|
-
content: {
|
|
969
|
-
text: "I'll close that issue.",
|
|
970
|
-
action: "MANAGE_ISSUES"
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
]
|
|
974
|
-
],
|
|
975
|
-
validate: async (runtime, _message) => {
|
|
976
|
-
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
977
|
-
return workspaceService != null;
|
|
978
|
-
},
|
|
979
|
-
handler: async (runtime, message, _state, options, callback) => {
|
|
980
|
-
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
981
|
-
if (!workspaceService) {
|
|
982
|
-
if (callback) {
|
|
983
|
-
await callback({ text: "Workspace Service is not available." });
|
|
984
|
-
}
|
|
985
|
-
return { success: false, error: "SERVICE_UNAVAILABLE" };
|
|
986
|
-
}
|
|
987
|
-
workspaceService.setAuthPromptCallback((prompt) => {
|
|
988
|
-
if (callback) {
|
|
989
|
-
callback({
|
|
990
|
-
text: `I need GitHub access to manage issues. Please authorize me:
|
|
991
|
-
|
|
992
|
-
` + `Go to: ${prompt.verificationUri}
|
|
993
|
-
` + `Enter code: **${prompt.userCode}**
|
|
994
|
-
|
|
995
|
-
` + `This code expires in ${Math.floor(prompt.expiresIn / 60)} minutes. ` + `I'll wait for you to complete authorization...`
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
});
|
|
999
|
-
const params = options?.parameters;
|
|
1000
|
-
const content = message.content;
|
|
1001
|
-
const text = content.text ?? "";
|
|
1002
|
-
const operation = params?.operation ?? content.operation ?? inferOperation(text);
|
|
1003
|
-
const repo = params?.repo ?? content.repo;
|
|
1004
|
-
if (!repo) {
|
|
1005
|
-
const urlMatch = text?.match(/(?:https?:\/\/github\.com\/)?([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
|
|
1006
|
-
if (!urlMatch) {
|
|
1007
|
-
if (callback) {
|
|
1008
|
-
await callback({
|
|
1009
|
-
text: "Please specify a repository (e.g., owner/repo or a GitHub URL)."
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
return { success: false, error: "MISSING_REPO" };
|
|
1013
|
-
}
|
|
1014
|
-
return handleOperation(workspaceService, urlMatch[1], operation, params ?? content, text, callback);
|
|
1015
|
-
}
|
|
1016
|
-
return handleOperation(workspaceService, repo, operation, params ?? content, text, callback);
|
|
1017
|
-
},
|
|
1018
|
-
parameters: [
|
|
1019
|
-
{
|
|
1020
|
-
name: "operation",
|
|
1021
|
-
description: "The operation to perform: create, list, get, update, comment, close, reopen, add_labels",
|
|
1022
|
-
required: true,
|
|
1023
|
-
schema: { type: "string" }
|
|
1024
|
-
},
|
|
1025
|
-
{
|
|
1026
|
-
name: "repo",
|
|
1027
|
-
description: "Repository in owner/repo format or full GitHub URL.",
|
|
1028
|
-
required: true,
|
|
1029
|
-
schema: { type: "string" }
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
name: "title",
|
|
1033
|
-
description: "Issue title (for create operation).",
|
|
1034
|
-
required: false,
|
|
1035
|
-
schema: { type: "string" }
|
|
1036
|
-
},
|
|
1037
|
-
{
|
|
1038
|
-
name: "body",
|
|
1039
|
-
description: "Issue body/description (for create or comment operations).",
|
|
1040
|
-
required: false,
|
|
1041
|
-
schema: { type: "string" }
|
|
1042
|
-
},
|
|
1043
|
-
{
|
|
1044
|
-
name: "issueNumber",
|
|
1045
|
-
description: "Issue number (for get, update, comment, close, reopen operations).",
|
|
1046
|
-
required: false,
|
|
1047
|
-
schema: { type: "number" }
|
|
1048
|
-
},
|
|
1049
|
-
{
|
|
1050
|
-
name: "labels",
|
|
1051
|
-
description: "Labels to add (comma-separated string or array).",
|
|
1052
|
-
required: false,
|
|
1053
|
-
schema: { type: "string" }
|
|
1054
|
-
},
|
|
1055
|
-
{
|
|
1056
|
-
name: "state",
|
|
1057
|
-
description: "Filter by state: open, closed, or all (for list operation).",
|
|
1058
|
-
required: false,
|
|
1059
|
-
schema: { type: "string" }
|
|
1060
|
-
}
|
|
1061
|
-
]
|
|
1062
|
-
};
|
|
1063
1106
|
async function handleOperation(service, repo, operation, params, originalText, callback) {
|
|
1064
1107
|
try {
|
|
1065
1108
|
switch (operation.toLowerCase()) {
|
|
@@ -1080,12 +1123,9 @@ async function handleOperation(service, repo, operation, params, originalText, c
|
|
|
1080
1123
|
created.push(issue2);
|
|
1081
1124
|
}
|
|
1082
1125
|
if (callback) {
|
|
1083
|
-
const summary = created.map((i) => `#${i.number}: ${i.title}
|
|
1084
|
-
${i.url}`).join(`
|
|
1085
|
-
`);
|
|
1126
|
+
const summary = created.map((i) => `#${i.number}: ${i.title}\n ${i.url}`).join("\n");
|
|
1086
1127
|
await callback({
|
|
1087
|
-
text: `Created ${created.length} issues
|
|
1088
|
-
${summary}`
|
|
1128
|
+
text: `Created ${created.length} issues:\n${summary}`
|
|
1089
1129
|
});
|
|
1090
1130
|
}
|
|
1091
1131
|
return { success: true, data: { issues: created } };
|
|
@@ -1102,8 +1142,7 @@ ${summary}`
|
|
|
1102
1142
|
});
|
|
1103
1143
|
if (callback) {
|
|
1104
1144
|
await callback({
|
|
1105
|
-
text: `Created issue #${issue.number}: ${issue.title}
|
|
1106
|
-
${issue.url}`
|
|
1145
|
+
text: `Created issue #${issue.number}: ${issue.title}\n${issue.url}`
|
|
1107
1146
|
});
|
|
1108
1147
|
}
|
|
1109
1148
|
return { success: true, data: { issue } };
|
|
@@ -1121,10 +1160,8 @@ ${issue.url}`
|
|
|
1121
1160
|
text: `No ${stateFilter} issues found in ${repo}.`
|
|
1122
1161
|
});
|
|
1123
1162
|
} else {
|
|
1124
|
-
const summary = issues.map((i) => `#${i.number} [${i.state}] ${i.title}${i.labels.length > 0 ? ` (${i.labels.join(", ")})` : ""}`).join(
|
|
1125
|
-
`);
|
|
1126
|
-
await callback({ text: `Issues in ${repo}:
|
|
1127
|
-
${summary}` });
|
|
1163
|
+
const summary = issues.map((i) => `#${i.number} [${i.state}] ${i.title}${i.labels.length > 0 ? ` (${i.labels.join(", ")})` : ""}`).join("\n");
|
|
1164
|
+
await callback({ text: `Issues in ${repo}:\n${summary}` });
|
|
1128
1165
|
}
|
|
1129
1166
|
}
|
|
1130
1167
|
return { success: true, data: { issues } };
|
|
@@ -1139,12 +1176,7 @@ ${summary}` });
|
|
|
1139
1176
|
const issue = await service.getIssue(repo, issueNumber);
|
|
1140
1177
|
if (callback) {
|
|
1141
1178
|
await callback({
|
|
1142
|
-
text: `Issue #${issue.number}: ${issue.title} [${issue.state}]
|
|
1143
|
-
|
|
1144
|
-
${issue.body}
|
|
1145
|
-
|
|
1146
|
-
Labels: ${issue.labels.join(", ") || "none"}
|
|
1147
|
-
${issue.url}`
|
|
1179
|
+
text: `Issue #${issue.number}: ${issue.title} [${issue.state}]\n\n${issue.body}\n\nLabels: ${issue.labels.join(", ") || "none"}\n${issue.url}`
|
|
1148
1180
|
});
|
|
1149
1181
|
}
|
|
1150
1182
|
return { success: true, data: { issue } };
|
|
@@ -1233,86 +1265,225 @@ ${issue.url}`
|
|
|
1233
1265
|
}
|
|
1234
1266
|
return { success: true };
|
|
1235
1267
|
}
|
|
1236
|
-
default:
|
|
1268
|
+
default:
|
|
1269
|
+
if (callback) {
|
|
1270
|
+
await callback({
|
|
1271
|
+
text: `Unknown operation: ${operation}. Use: create, list, get, update, comment, close, reopen, add_labels`
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
return { success: false, error: "UNKNOWN_OPERATION" };
|
|
1275
|
+
}
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1278
|
+
if (callback) {
|
|
1279
|
+
await callback({ text: `Issue operation failed: ${errorMessage}` });
|
|
1280
|
+
}
|
|
1281
|
+
return { success: false, error: errorMessage };
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function extractBulkItems(text) {
|
|
1285
|
+
if (!text)
|
|
1286
|
+
return [];
|
|
1287
|
+
const numberedPattern = /(?:^|\s)(\d+)[).:-]\s*(.+?)(?=(?:\s+\d+[).:-]\s)|$)/gs;
|
|
1288
|
+
const items = [];
|
|
1289
|
+
for (const match of text.matchAll(numberedPattern)) {
|
|
1290
|
+
const raw = match[2].trim();
|
|
1291
|
+
if (raw.length > 0) {
|
|
1292
|
+
items.push({ title: raw });
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (items.length >= 2)
|
|
1296
|
+
return items;
|
|
1297
|
+
const bulletPattern = /(?:^|\n)\s*[-*•]\s+(.+)/g;
|
|
1298
|
+
const bulletItems = [];
|
|
1299
|
+
for (const match of text.matchAll(bulletPattern)) {
|
|
1300
|
+
const raw = match[1].trim();
|
|
1301
|
+
if (raw.length > 0) {
|
|
1302
|
+
bulletItems.push({ title: raw });
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (bulletItems.length >= 2)
|
|
1306
|
+
return bulletItems;
|
|
1307
|
+
return [];
|
|
1308
|
+
}
|
|
1309
|
+
function inferOperation(text) {
|
|
1310
|
+
const lower = text.toLowerCase();
|
|
1311
|
+
if (/\b(create|open|file|submit|make|add)\b.*\bissue/.test(lower))
|
|
1312
|
+
return "create";
|
|
1313
|
+
if (/\bissue.*\b(create|open|file|submit|make)\b/.test(lower))
|
|
1314
|
+
return "create";
|
|
1315
|
+
if (/\b(close|resolve)\b.*\bissue/.test(lower))
|
|
1316
|
+
return "close";
|
|
1317
|
+
if (/\bissue.*\b(close|resolve)\b/.test(lower))
|
|
1318
|
+
return "close";
|
|
1319
|
+
if (/\b(reopen|re-open)\b.*\bissue/.test(lower))
|
|
1320
|
+
return "reopen";
|
|
1321
|
+
if (/\b(comment|reply)\b.*\bissue/.test(lower))
|
|
1322
|
+
return "comment";
|
|
1323
|
+
if (/\bissue.*\b(comment|reply)\b/.test(lower))
|
|
1324
|
+
return "comment";
|
|
1325
|
+
if (/\b(update|edit|modify)\b.*\bissue/.test(lower))
|
|
1326
|
+
return "update";
|
|
1327
|
+
if (/\bissue.*\b(update|edit|modify)\b/.test(lower))
|
|
1328
|
+
return "update";
|
|
1329
|
+
if (/\b(label|tag)\b.*\bissue/.test(lower))
|
|
1330
|
+
return "add_labels";
|
|
1331
|
+
if (/\bget\b.*\bissue\s*#?\d/.test(lower))
|
|
1332
|
+
return "get";
|
|
1333
|
+
if (/\bissue\s*#?\d/.test(lower) && !/\b(list|show|all)\b/.test(lower))
|
|
1334
|
+
return "get";
|
|
1335
|
+
if (/\b(list|show|check|what are)\b.*\bissue/.test(lower))
|
|
1336
|
+
return "list";
|
|
1337
|
+
return "list";
|
|
1338
|
+
}
|
|
1339
|
+
function parseLabels(input) {
|
|
1340
|
+
if (!input)
|
|
1341
|
+
return [];
|
|
1342
|
+
if (Array.isArray(input))
|
|
1343
|
+
return input.map(String);
|
|
1344
|
+
if (typeof input === "string")
|
|
1345
|
+
return input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1346
|
+
return [];
|
|
1347
|
+
}
|
|
1348
|
+
var manageIssuesAction = {
|
|
1349
|
+
name: "MANAGE_ISSUES",
|
|
1350
|
+
similes: [
|
|
1351
|
+
"CREATE_ISSUE",
|
|
1352
|
+
"LIST_ISSUES",
|
|
1353
|
+
"CLOSE_ISSUE",
|
|
1354
|
+
"COMMENT_ISSUE",
|
|
1355
|
+
"UPDATE_ISSUE",
|
|
1356
|
+
"GET_ISSUE"
|
|
1357
|
+
],
|
|
1358
|
+
description: "Manage GitHub issues for a repository. " + "Supports creating issues, listing issues, getting issue details, " + "adding comments, updating, closing, and reopening issues.",
|
|
1359
|
+
examples: [
|
|
1360
|
+
[
|
|
1361
|
+
{
|
|
1362
|
+
name: "{{user1}}",
|
|
1363
|
+
content: {
|
|
1364
|
+
text: "Create an issue on the testbed repo to add a login page"
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
name: "{{agentName}}",
|
|
1369
|
+
content: {
|
|
1370
|
+
text: "I'll create that issue for you.",
|
|
1371
|
+
action: "MANAGE_ISSUES"
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
],
|
|
1375
|
+
[
|
|
1376
|
+
{
|
|
1377
|
+
name: "{{user1}}",
|
|
1378
|
+
content: {
|
|
1379
|
+
text: "List the open issues on HaruHunab1320/git-workspace-service-testbed"
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
name: "{{agentName}}",
|
|
1384
|
+
content: {
|
|
1385
|
+
text: "Let me check the open issues for that repo.",
|
|
1386
|
+
action: "MANAGE_ISSUES"
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
],
|
|
1390
|
+
[
|
|
1391
|
+
{
|
|
1392
|
+
name: "{{user1}}",
|
|
1393
|
+
content: { text: "Close issue #3 on the testbed repo" }
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
name: "{{agentName}}",
|
|
1397
|
+
content: {
|
|
1398
|
+
text: "I'll close that issue.",
|
|
1399
|
+
action: "MANAGE_ISSUES"
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
]
|
|
1403
|
+
],
|
|
1404
|
+
validate: async (runtime, _message) => {
|
|
1405
|
+
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
1406
|
+
return workspaceService != null;
|
|
1407
|
+
},
|
|
1408
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
1409
|
+
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
1410
|
+
if (!workspaceService) {
|
|
1411
|
+
if (callback) {
|
|
1412
|
+
await callback({ text: "Workspace Service is not available." });
|
|
1413
|
+
}
|
|
1414
|
+
return { success: false, error: "SERVICE_UNAVAILABLE" };
|
|
1415
|
+
}
|
|
1416
|
+
workspaceService.setAuthPromptCallback((prompt) => {
|
|
1417
|
+
if (callback) {
|
|
1418
|
+
callback({
|
|
1419
|
+
text: `I need GitHub access to manage issues. Please authorize me:\n\n` + `Go to: ${prompt.verificationUri}\n` + `Enter code: **${prompt.userCode}**\n\n` + `This code expires in ${Math.floor(prompt.expiresIn / 60)} minutes. ` + `I'll wait for you to complete authorization...`
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
const params = options?.parameters;
|
|
1424
|
+
const content = message.content;
|
|
1425
|
+
const text = content.text ?? "";
|
|
1426
|
+
const operation = params?.operation ?? content.operation ?? inferOperation(text);
|
|
1427
|
+
const repo = params?.repo ?? content.repo;
|
|
1428
|
+
if (!repo) {
|
|
1429
|
+
const urlMatch = text?.match(/(?:https?:\/\/github\.com\/)?([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
|
|
1430
|
+
if (!urlMatch) {
|
|
1237
1431
|
if (callback) {
|
|
1238
1432
|
await callback({
|
|
1239
|
-
text:
|
|
1433
|
+
text: "Please specify a repository (e.g., owner/repo or a GitHub URL)."
|
|
1240
1434
|
});
|
|
1241
1435
|
}
|
|
1242
|
-
return { success: false, error: "
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1246
|
-
if (callback) {
|
|
1247
|
-
await callback({ text: `Issue operation failed: ${errorMessage}` });
|
|
1248
|
-
}
|
|
1249
|
-
return { success: false, error: errorMessage };
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
function extractBulkItems(text) {
|
|
1253
|
-
if (!text)
|
|
1254
|
-
return [];
|
|
1255
|
-
const numberedPattern = /(?:^|\s)(\d+)[).:-]\s*(.+?)(?=(?:\s+\d+[).:-]\s)|$)/gs;
|
|
1256
|
-
const items = [];
|
|
1257
|
-
for (const match of text.matchAll(numberedPattern)) {
|
|
1258
|
-
const raw = match[2].trim();
|
|
1259
|
-
if (raw.length > 0) {
|
|
1260
|
-
items.push({ title: raw });
|
|
1436
|
+
return { success: false, error: "MISSING_REPO" };
|
|
1437
|
+
}
|
|
1438
|
+
return handleOperation(workspaceService, urlMatch[1], operation, params ?? content, text, callback);
|
|
1261
1439
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1440
|
+
return handleOperation(workspaceService, repo, operation, params ?? content, text, callback);
|
|
1441
|
+
},
|
|
1442
|
+
parameters: [
|
|
1443
|
+
{
|
|
1444
|
+
name: "operation",
|
|
1445
|
+
description: "The operation to perform: create, list, get, update, comment, close, reopen, add_labels",
|
|
1446
|
+
required: true,
|
|
1447
|
+
schema: { type: "string" }
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
name: "repo",
|
|
1451
|
+
description: "Repository in owner/repo format or full GitHub URL.",
|
|
1452
|
+
required: true,
|
|
1453
|
+
schema: { type: "string" }
|
|
1454
|
+
},
|
|
1455
|
+
{
|
|
1456
|
+
name: "title",
|
|
1457
|
+
description: "Issue title (for create operation).",
|
|
1458
|
+
required: false,
|
|
1459
|
+
schema: { type: "string" }
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
name: "body",
|
|
1463
|
+
description: "Issue body/description (for create or comment operations).",
|
|
1464
|
+
required: false,
|
|
1465
|
+
schema: { type: "string" }
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
name: "issueNumber",
|
|
1469
|
+
description: "Issue number (for get, update, comment, close, reopen operations).",
|
|
1470
|
+
required: false,
|
|
1471
|
+
schema: { type: "number" }
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
name: "labels",
|
|
1475
|
+
description: "Labels to add (comma-separated string or array).",
|
|
1476
|
+
required: false,
|
|
1477
|
+
schema: { type: "string" }
|
|
1478
|
+
},
|
|
1479
|
+
{
|
|
1480
|
+
name: "state",
|
|
1481
|
+
description: "Filter by state: open, closed, or all (for list operation).",
|
|
1482
|
+
required: false,
|
|
1483
|
+
schema: { type: "string" }
|
|
1271
1484
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
return bulletItems;
|
|
1275
|
-
return [];
|
|
1276
|
-
}
|
|
1277
|
-
function inferOperation(text) {
|
|
1278
|
-
const lower = text.toLowerCase();
|
|
1279
|
-
if (/\b(create|open|file|submit|make|add)\b.*\bissue/.test(lower))
|
|
1280
|
-
return "create";
|
|
1281
|
-
if (/\bissue.*\b(create|open|file|submit|make)\b/.test(lower))
|
|
1282
|
-
return "create";
|
|
1283
|
-
if (/\b(close|resolve)\b.*\bissue/.test(lower))
|
|
1284
|
-
return "close";
|
|
1285
|
-
if (/\bissue.*\b(close|resolve)\b/.test(lower))
|
|
1286
|
-
return "close";
|
|
1287
|
-
if (/\b(reopen|re-open)\b.*\bissue/.test(lower))
|
|
1288
|
-
return "reopen";
|
|
1289
|
-
if (/\b(comment|reply)\b.*\bissue/.test(lower))
|
|
1290
|
-
return "comment";
|
|
1291
|
-
if (/\bissue.*\b(comment|reply)\b/.test(lower))
|
|
1292
|
-
return "comment";
|
|
1293
|
-
if (/\b(update|edit|modify)\b.*\bissue/.test(lower))
|
|
1294
|
-
return "update";
|
|
1295
|
-
if (/\bissue.*\b(update|edit|modify)\b/.test(lower))
|
|
1296
|
-
return "update";
|
|
1297
|
-
if (/\b(label|tag)\b.*\bissue/.test(lower))
|
|
1298
|
-
return "add_labels";
|
|
1299
|
-
if (/\bget\b.*\bissue\s*#?\d/.test(lower))
|
|
1300
|
-
return "get";
|
|
1301
|
-
if (/\bissue\s*#?\d/.test(lower) && !/\b(list|show|all)\b/.test(lower))
|
|
1302
|
-
return "get";
|
|
1303
|
-
if (/\b(list|show|check|what are)\b.*\bissue/.test(lower))
|
|
1304
|
-
return "list";
|
|
1305
|
-
return "list";
|
|
1306
|
-
}
|
|
1307
|
-
function parseLabels(input) {
|
|
1308
|
-
if (!input)
|
|
1309
|
-
return [];
|
|
1310
|
-
if (Array.isArray(input))
|
|
1311
|
-
return input.map(String);
|
|
1312
|
-
if (typeof input === "string")
|
|
1313
|
-
return input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1314
|
-
return [];
|
|
1315
|
-
}
|
|
1485
|
+
]
|
|
1486
|
+
};
|
|
1316
1487
|
|
|
1317
1488
|
// src/actions/provision-workspace.ts
|
|
1318
1489
|
var provisionWorkspaceAction = {
|
|
@@ -1425,9 +1596,7 @@ var provisionWorkspaceAction = {
|
|
|
1425
1596
|
}
|
|
1426
1597
|
if (callback) {
|
|
1427
1598
|
await callback({
|
|
1428
|
-
text: `Created workspace at ${workspace.path}
|
|
1429
|
-
` + `Branch: ${workspace.branch}
|
|
1430
|
-
` + `Type: ${workspace.isWorktree ? "worktree" : "clone"}`
|
|
1599
|
+
text: `Created workspace at ${workspace.path}\n` + `Branch: ${workspace.branch}\n` + `Type: ${workspace.isWorktree ? "worktree" : "clone"}`
|
|
1431
1600
|
});
|
|
1432
1601
|
}
|
|
1433
1602
|
return {
|
|
@@ -1634,19 +1803,21 @@ var sendToAgentAction = {
|
|
|
1634
1803
|
|
|
1635
1804
|
// src/actions/spawn-agent.ts
|
|
1636
1805
|
import * as os from "node:os";
|
|
1637
|
-
import * as
|
|
1806
|
+
import * as path2 from "node:path";
|
|
1638
1807
|
import {
|
|
1639
|
-
|
|
1808
|
+
logger as logger3
|
|
1640
1809
|
} from "@elizaos/core";
|
|
1641
1810
|
|
|
1642
1811
|
// src/services/pty-service.ts
|
|
1643
|
-
import {
|
|
1812
|
+
import {mkdir, readFile, writeFile} from "node:fs/promises";
|
|
1813
|
+
import {dirname, join} from "node:path";
|
|
1814
|
+
import {logger as logger2} from "@elizaos/core";
|
|
1644
1815
|
import {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1816
|
+
checkAdapters,
|
|
1817
|
+
createAdapter,
|
|
1818
|
+
generateApprovalConfig
|
|
1648
1819
|
} from "coding-agent-adapters";
|
|
1649
|
-
import {
|
|
1820
|
+
import {PTYConsoleBridge} from "pty-console";
|
|
1650
1821
|
|
|
1651
1822
|
// src/services/agent-metrics.ts
|
|
1652
1823
|
class AgentMetricsTracker {
|
|
@@ -1702,7 +1873,6 @@ function computeAgentScore(metrics) {
|
|
|
1702
1873
|
const speedPenalty = Math.min(avgCompletionMs / 300000, 1) * 0.1;
|
|
1703
1874
|
return Math.max(0, successRate - stallPenalty - speedPenalty);
|
|
1704
1875
|
}
|
|
1705
|
-
var DEFAULT_ORDER = ["claude", "gemini", "codex", "aider"];
|
|
1706
1876
|
function selectAgentType(ctx) {
|
|
1707
1877
|
if (ctx.config.strategy === "fixed") {
|
|
1708
1878
|
return ctx.config.fixedAgentType;
|
|
@@ -1724,6 +1894,7 @@ function selectAgentType(ctx) {
|
|
|
1724
1894
|
}
|
|
1725
1895
|
return bestAgent;
|
|
1726
1896
|
}
|
|
1897
|
+
var DEFAULT_ORDER = ["claude", "gemini", "codex", "aider"];
|
|
1727
1898
|
|
|
1728
1899
|
// src/services/pty-auto-response.ts
|
|
1729
1900
|
async function pushDefaultRules(ctx, sessionId, agentType) {
|
|
@@ -1801,19 +1972,14 @@ async function handleGeminiAuth(ctx, sessionId, sendKeysToSession) {
|
|
|
1801
1972
|
|
|
1802
1973
|
// src/services/pty-init.ts
|
|
1803
1974
|
init_ansi_utils();
|
|
1804
|
-
import {
|
|
1805
|
-
import {
|
|
1975
|
+
import {createRequire as createRequire2} from "node:module";
|
|
1976
|
+
import {createAllAdapters} from "coding-agent-adapters";
|
|
1806
1977
|
import {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1978
|
+
BunCompatiblePTYManager,
|
|
1979
|
+
isBun,
|
|
1980
|
+
PTYManager,
|
|
1981
|
+
ShellAdapter
|
|
1811
1982
|
} from "pty-manager";
|
|
1812
|
-
var _require = createRequire2(import.meta.url);
|
|
1813
|
-
var resolvedAdapterModule = "coding-agent-adapters";
|
|
1814
|
-
try {
|
|
1815
|
-
resolvedAdapterModule = _require.resolve("coding-agent-adapters");
|
|
1816
|
-
} catch {}
|
|
1817
1983
|
async function initializePTYManager(ctx) {
|
|
1818
1984
|
const usingBunWorker = isBun();
|
|
1819
1985
|
if (usingBunWorker) {
|
|
@@ -1830,6 +1996,11 @@ async function initializePTYManager(ctx) {
|
|
|
1830
1996
|
bunManager.on("session_ready", (session) => {
|
|
1831
1997
|
ctx.log(`session_ready event received for ${session.id} (type: ${session.type}, status: ${session.status})`);
|
|
1832
1998
|
ctx.emitEvent(session.id, "ready", { session });
|
|
1999
|
+
if (ctx.hasActiveTask?.(session.id) && ctx.taskResponseMarkers.has(session.id)) {
|
|
2000
|
+
const response = captureTaskResponse(session.id, ctx.sessionOutputBuffers, ctx.taskResponseMarkers);
|
|
2001
|
+
ctx.log(`session_ready for active task ${session.id} \u2014 forwarding as task_complete (stall classifier path)`);
|
|
2002
|
+
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
2003
|
+
}
|
|
1833
2004
|
});
|
|
1834
2005
|
bunManager.on("session_exit", (id, code) => {
|
|
1835
2006
|
ctx.emitEvent(id, "stopped", { reason: `exit code ${code}` });
|
|
@@ -1856,7 +2027,7 @@ async function initializePTYManager(ctx) {
|
|
|
1856
2027
|
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
1857
2028
|
});
|
|
1858
2029
|
bunManager.on("tool_running", (session, info) => {
|
|
1859
|
-
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? `
|
|
2030
|
+
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? ` \u2014 ${info.description}` : ""}`);
|
|
1860
2031
|
ctx.emitEvent(session.id, "tool_running", { session, ...info });
|
|
1861
2032
|
});
|
|
1862
2033
|
bunManager.on("message", (message) => {
|
|
@@ -1908,6 +2079,11 @@ async function initializePTYManager(ctx) {
|
|
|
1908
2079
|
}
|
|
1909
2080
|
nodeManager.on("session_ready", (session) => {
|
|
1910
2081
|
ctx.emitEvent(session.id, "ready", { session });
|
|
2082
|
+
if (ctx.hasActiveTask?.(session.id) && ctx.taskResponseMarkers.has(session.id)) {
|
|
2083
|
+
const response = captureTaskResponse(session.id, ctx.sessionOutputBuffers, ctx.taskResponseMarkers);
|
|
2084
|
+
ctx.log(`session_ready for active task ${session.id} \u2014 forwarding as task_complete (stall classifier path)`);
|
|
2085
|
+
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
2086
|
+
}
|
|
1911
2087
|
});
|
|
1912
2088
|
nodeManager.on("blocking_prompt", (session, promptInfo, autoResponded) => {
|
|
1913
2089
|
ctx.emitEvent(session.id, "blocked", { promptInfo, autoResponded });
|
|
@@ -1926,7 +2102,7 @@ async function initializePTYManager(ctx) {
|
|
|
1926
2102
|
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
1927
2103
|
});
|
|
1928
2104
|
nodeManager.on("tool_running", (session, info) => {
|
|
1929
|
-
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? `
|
|
2105
|
+
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? ` \u2014 ${info.description}` : ""}`);
|
|
1930
2106
|
ctx.emitEvent(session.id, "tool_running", { session, ...info });
|
|
1931
2107
|
});
|
|
1932
2108
|
nodeManager.on("session_stopped", (session, reason) => {
|
|
@@ -1940,6 +2116,12 @@ async function initializePTYManager(ctx) {
|
|
|
1940
2116
|
});
|
|
1941
2117
|
return { manager: nodeManager, usingBunWorker: false };
|
|
1942
2118
|
}
|
|
2119
|
+
var _require = createRequire2(import.meta.url);
|
|
2120
|
+
var resolvedAdapterModule = "coding-agent-adapters";
|
|
2121
|
+
try {
|
|
2122
|
+
resolvedAdapterModule = _require.resolve("coding-agent-adapters");
|
|
2123
|
+
} catch {
|
|
2124
|
+
}
|
|
1943
2125
|
|
|
1944
2126
|
// src/services/pty-session-io.ts
|
|
1945
2127
|
async function sendToSession(ctx, sessionId, input) {
|
|
@@ -2011,8 +2193,7 @@ async function getSessionOutput(ctx, sessionId, lines) {
|
|
|
2011
2193
|
if (!buffer)
|
|
2012
2194
|
return "";
|
|
2013
2195
|
const tail = lines ?? buffer.length;
|
|
2014
|
-
return buffer.slice(-tail).join(
|
|
2015
|
-
`);
|
|
2196
|
+
return buffer.slice(-tail).join("\n");
|
|
2016
2197
|
}
|
|
2017
2198
|
const output = [];
|
|
2018
2199
|
for await (const line of ctx.manager.logs(sessionId, {
|
|
@@ -2020,17 +2201,24 @@ async function getSessionOutput(ctx, sessionId, lines) {
|
|
|
2020
2201
|
})) {
|
|
2021
2202
|
output.push(line);
|
|
2022
2203
|
}
|
|
2023
|
-
return output.join(
|
|
2024
|
-
`);
|
|
2204
|
+
return output.join("\n");
|
|
2025
2205
|
}
|
|
2026
2206
|
|
|
2027
2207
|
// src/services/pty-spawn.ts
|
|
2208
|
+
function buildSanitizedBaseEnv() {
|
|
2209
|
+
const env = {};
|
|
2210
|
+
for (const key of ENV_ALLOWLIST) {
|
|
2211
|
+
const val = process.env[key];
|
|
2212
|
+
if (val)
|
|
2213
|
+
env[key] = val;
|
|
2214
|
+
}
|
|
2215
|
+
return env;
|
|
2216
|
+
}
|
|
2028
2217
|
function setupOutputBuffer(ctx, sessionId) {
|
|
2029
2218
|
const buffer = [];
|
|
2030
2219
|
ctx.sessionOutputBuffers.set(sessionId, buffer);
|
|
2031
2220
|
const unsubscribe = ctx.manager.onSessionData(sessionId, (data) => {
|
|
2032
|
-
const lines = data.split(
|
|
2033
|
-
`);
|
|
2221
|
+
const lines = data.split("\n");
|
|
2034
2222
|
buffer.push(...lines);
|
|
2035
2223
|
while (buffer.length > (ctx.serviceConfig.maxLogLines ?? 1000)) {
|
|
2036
2224
|
buffer.shift();
|
|
@@ -2053,17 +2241,17 @@ function setupDeferredTaskDelivery(ctx, session, task, agentType) {
|
|
|
2053
2241
|
const sendTaskWithRetry = (attempt) => {
|
|
2054
2242
|
const buffer = ctx.sessionOutputBuffers.get(sid);
|
|
2055
2243
|
const baselineLength = buffer?.length ?? 0;
|
|
2056
|
-
ctx.log(`Session ${sid}
|
|
2244
|
+
ctx.log(`Session ${sid} \u2014 sending task (attempt ${attempt + 1}, ${settleMs}ms settle, baseline ${baselineLength} lines)`);
|
|
2057
2245
|
ctx.sendToSession(sid, task).catch((err) => ctx.log(`Failed to send deferred task to ${sid}: ${err}`));
|
|
2058
2246
|
if (attempt < MAX_RETRIES) {
|
|
2059
2247
|
setTimeout(() => {
|
|
2060
2248
|
const currentLength = buffer?.length ?? 0;
|
|
2061
2249
|
const newLines = currentLength - baselineLength;
|
|
2062
2250
|
if (newLines < MIN_NEW_LINES) {
|
|
2063
|
-
ctx.log(`Session ${sid}
|
|
2251
|
+
ctx.log(`Session ${sid} \u2014 task may not have been accepted (only ${newLines} new lines after ${VERIFY_DELAY_MS}ms). Retrying (attempt ${attempt + 2}/${MAX_RETRIES + 1})`);
|
|
2064
2252
|
sendTaskWithRetry(attempt + 1);
|
|
2065
2253
|
} else {
|
|
2066
|
-
ctx.log(`Session ${sid}
|
|
2254
|
+
ctx.log(`Session ${sid} \u2014 task accepted (${newLines} new lines after ${VERIFY_DELAY_MS}ms)`);
|
|
2067
2255
|
}
|
|
2068
2256
|
}, VERIFY_DELAY_MS);
|
|
2069
2257
|
}
|
|
@@ -2114,7 +2302,8 @@ function buildSpawnConfig(sessionId, options, workdir) {
|
|
|
2114
2302
|
name: options.name,
|
|
2115
2303
|
type: options.agentType,
|
|
2116
2304
|
workdir,
|
|
2117
|
-
|
|
2305
|
+
inheritProcessEnv: false,
|
|
2306
|
+
env: { ...buildSanitizedBaseEnv(), ...options.env, ...modelEnv },
|
|
2118
2307
|
...options.skipAdapterAutoResponse ? { skipAdapterAutoResponse: true } : {},
|
|
2119
2308
|
adapterConfig: {
|
|
2120
2309
|
...options.credentials,
|
|
@@ -2126,6 +2315,21 @@ function buildSpawnConfig(sessionId, options, workdir) {
|
|
|
2126
2315
|
}
|
|
2127
2316
|
};
|
|
2128
2317
|
}
|
|
2318
|
+
var ENV_ALLOWLIST = [
|
|
2319
|
+
"PATH",
|
|
2320
|
+
"HOME",
|
|
2321
|
+
"USER",
|
|
2322
|
+
"SHELL",
|
|
2323
|
+
"LANG",
|
|
2324
|
+
"LC_ALL",
|
|
2325
|
+
"LC_CTYPE",
|
|
2326
|
+
"TERM",
|
|
2327
|
+
"TZ",
|
|
2328
|
+
"TMPDIR",
|
|
2329
|
+
"XDG_RUNTIME_DIR",
|
|
2330
|
+
"NODE_OPTIONS",
|
|
2331
|
+
"BUN_INSTALL"
|
|
2332
|
+
];
|
|
2129
2333
|
|
|
2130
2334
|
// src/services/pty-types.ts
|
|
2131
2335
|
var PI_AGENT_ALIASES = new Set([
|
|
@@ -2170,39 +2374,17 @@ var toPiCommand = (task) => {
|
|
|
2170
2374
|
|
|
2171
2375
|
// src/services/stall-classifier.ts
|
|
2172
2376
|
init_ansi_utils();
|
|
2173
|
-
import {
|
|
2377
|
+
import {ModelType} from "@elizaos/core";
|
|
2174
2378
|
import {
|
|
2175
|
-
|
|
2176
|
-
|
|
2379
|
+
buildTaskCompletionTimeline,
|
|
2380
|
+
extractTaskCompletionTraceRecords
|
|
2177
2381
|
} from "pty-manager";
|
|
2178
2382
|
function buildStallClassificationPrompt(agentType, sessionId, output) {
|
|
2179
|
-
return `You are Milady, an AI orchestrator managing coding agent sessions. ` + `A ${agentType} coding agent (session: ${sessionId}) appears to have stalled
|
|
2180
|
-
|
|
2181
|
-
` + `Here is the recent terminal output:
|
|
2182
|
-
` + `---
|
|
2183
|
-
${output.slice(-1500)}
|
|
2184
|
-
---
|
|
2185
|
-
|
|
2186
|
-
` + `Classify what's happening. Read the output carefully and choose the MOST specific match:
|
|
2187
|
-
|
|
2188
|
-
` + `1. "task_complete" — The agent FINISHED its task and returned to its idle prompt. ` + `Strong indicators: a summary of completed work ("Done", "All done", "Here's what was completed"), ` + `timing info ("Baked for", "Churned for", "Crunched for", "Cooked for", "Worked for"), ` + `or the agent's main prompt symbol (❯) appearing AFTER completion output. ` + `If the output contains evidence of completed work followed by an idle prompt, this is ALWAYS task_complete, ` + `even though the agent is technically "waiting" — it is waiting for a NEW task, not asking a question.
|
|
2189
|
-
|
|
2190
|
-
` + `2. "waiting_for_input" — The agent is MID-TASK and blocked on a specific question or permission prompt. ` + `The agent has NOT finished its work — it needs a response to continue. ` + `Examples: Y/n confirmation, file permission dialogs, "Do you want to proceed?", ` + `tool approval prompts, or interactive menus. ` + `This is NOT the same as the agent sitting at its idle prompt after finishing work.
|
|
2191
|
-
|
|
2192
|
-
` + `3. "still_working" — The agent is actively processing (API call, compilation, thinking, etc.) ` + `and has not produced final output yet. No prompt or completion summary visible.
|
|
2383
|
+
return `You are Milady, an AI orchestrator managing coding agent sessions. ` + `A ${agentType} coding agent (session: ${sessionId}) appears to have stalled \u2014 ` + `it has stopped producing output while in a busy state.\n\n` + `Here is the recent terminal output:\n` + `---\n${output.slice(-1500)}\n---\n\n` + `Classify what's happening. Read the output carefully and choose the MOST specific match:\n\n` + `1. "task_complete" \u2014 The agent FINISHED its task and returned to its idle prompt. ` + `Strong indicators: a summary of completed work ("Done", "All done", "Here's what was completed"), ` + `timing info ("Baked for", "Churned for", "Crunched for", "Cooked for", "Worked for"), ` + `or the agent's main prompt symbol (\u276F) appearing AFTER completion output. ` + `If the output contains evidence of completed work followed by an idle prompt, this is ALWAYS task_complete, ` + `even though the agent is technically "waiting" \u2014 it is waiting for a NEW task, not asking a question.
|
|
2193
2384
|
|
|
2194
|
-
` + `4. "error"
|
|
2385
|
+
` + `2. "waiting_for_input" \u2014 The agent is MID-TASK and blocked on a specific question or permission prompt. ` + `The agent has NOT finished its work \u2014 it needs a response to continue. ` + `Examples: Y/n confirmation, file permission dialogs, "Do you want to proceed?", ` + `tool approval prompts, or interactive menus. ` + `This is NOT the same as the agent sitting at its idle prompt after finishing work.\n\n` + `3. "still_working" \u2014 The agent is actively processing (API call, compilation, thinking, etc.) ` + `and has not produced final output yet. No prompt or completion summary visible.\n\n` + `4. "error" \u2014 The agent hit an error state (crash, unrecoverable error, stack trace).
|
|
2195
2386
|
|
|
2196
|
-
` + `5. "tool_running"
|
|
2197
|
-
|
|
2198
|
-
` + `IMPORTANT: If you see BOTH completed work output AND an idle prompt (❯), choose "task_complete". ` + `Only choose "waiting_for_input" if the agent is clearly asking a question mid-task.
|
|
2199
|
-
|
|
2200
|
-
` + `If "waiting_for_input", also provide:
|
|
2201
|
-
` + `- "prompt": the text of what it's asking
|
|
2202
|
-
` + `- "suggestedResponse": what to type/send. Use "keys:enter" for TUI menu confirmation, ` + `"keys:down,enter" to select a non-default option, or plain text like "y" for text prompts.
|
|
2203
|
-
|
|
2204
|
-
` + `Respond with ONLY a JSON object:
|
|
2205
|
-
` + `{"state": "...", "prompt": "...", "suggestedResponse": "..."}`;
|
|
2387
|
+
` + `5. "tool_running" \u2014 The agent is using an external tool (browser automation, ` + `MCP tool, etc.). Indicators: "Claude in Chrome", "javascript_tool", ` + `"computer_tool", "screenshot", "navigate", tool execution output. ` + `The agent is actively working but the terminal may be quiet.\n\n` + `IMPORTANT: If you see BOTH completed work output AND an idle prompt (\u276F), choose "task_complete". ` + `Only choose "waiting_for_input" if the agent is clearly asking a question mid-task.\n\n` + `If "waiting_for_input", also provide:\n` + `- "prompt": the text of what it's asking\n` + `- "suggestedResponse": what to type/send. Use "keys:enter" for TUI menu confirmation, ` + `"keys:down,enter" to select a non-default option, or plain text like "y" for text prompts.\n\n` + `Respond with ONLY a JSON object:\n` + `{"state": "...", "prompt": "...", "suggestedResponse": "..."}`;
|
|
2206
2388
|
}
|
|
2207
2389
|
async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveOutput, buffers, traceEntries, log) {
|
|
2208
2390
|
try {
|
|
@@ -2212,8 +2394,7 @@ async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveO
|
|
|
2212
2394
|
const snapshotDir = path.join(os.homedir(), ".milady", "debug");
|
|
2213
2395
|
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
2214
2396
|
const ourBuffer = buffers.get(sessionId);
|
|
2215
|
-
const ourTail = ourBuffer ? ourBuffer.slice(-100).join(
|
|
2216
|
-
`) : "(no buffer)";
|
|
2397
|
+
const ourTail = ourBuffer ? ourBuffer.slice(-100).join("\n") : "(no buffer)";
|
|
2217
2398
|
let traceTimeline = "(no trace entries)";
|
|
2218
2399
|
try {
|
|
2219
2400
|
const records = extractTaskCompletionTraceRecords(traceEntries);
|
|
@@ -2236,15 +2417,14 @@ async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveO
|
|
|
2236
2417
|
traceTimeline,
|
|
2237
2418
|
``,
|
|
2238
2419
|
`--- raw trace entries (last 20 of ${traceEntries.length}) ---`,
|
|
2239
|
-
traceEntries.slice(-20).join(
|
|
2240
|
-
`),
|
|
2420
|
+
traceEntries.slice(-20).join("\n"),
|
|
2241
2421
|
``
|
|
2242
|
-
].join(
|
|
2243
|
-
`);
|
|
2422
|
+
].join("\n");
|
|
2244
2423
|
const snapshotPath = path.join(snapshotDir, `stall-snapshot-${sessionId}.txt`);
|
|
2245
2424
|
fs.writeFileSync(snapshotPath, snapshot);
|
|
2246
|
-
log(`Stall snapshot
|
|
2247
|
-
} catch (_) {
|
|
2425
|
+
log(`Stall snapshot \u2192 ${snapshotPath}`);
|
|
2426
|
+
} catch (_) {
|
|
2427
|
+
}
|
|
2248
2428
|
}
|
|
2249
2429
|
async function classifyStallOutput(ctx) {
|
|
2250
2430
|
const {
|
|
@@ -2263,8 +2443,7 @@ async function classifyStallOutput(ctx) {
|
|
|
2263
2443
|
if (!recentOutput || recentOutput.trim().length < 200) {
|
|
2264
2444
|
const ourBuffer = buffers.get(sessionId);
|
|
2265
2445
|
if (ourBuffer && ourBuffer.length > 0) {
|
|
2266
|
-
const rawTail = ourBuffer.slice(-100).join(
|
|
2267
|
-
`);
|
|
2446
|
+
const rawTail = ourBuffer.slice(-100).join("\n");
|
|
2268
2447
|
const stripped = stripAnsi(rawTail);
|
|
2269
2448
|
if (stripped.length > effectiveOutput.length) {
|
|
2270
2449
|
effectiveOutput = stripped;
|
|
@@ -2304,7 +2483,7 @@ async function classifyStallOutput(ctx) {
|
|
|
2304
2483
|
prompt: parsed.prompt,
|
|
2305
2484
|
suggestedResponse: parsed.suggestedResponse
|
|
2306
2485
|
};
|
|
2307
|
-
log(`Stall classification for ${sessionId}: ${classification.state}${classification.suggestedResponse ? `
|
|
2486
|
+
log(`Stall classification for ${sessionId}: ${classification.state}${classification.suggestedResponse ? ` \u2192 "${classification.suggestedResponse}"` : ""}`);
|
|
2308
2487
|
if (classification.state === "task_complete") {
|
|
2309
2488
|
const session = manager?.get(sessionId);
|
|
2310
2489
|
const durationMs = session?.startedAt ? Date.now() - new Date(session.startedAt).getTime() : 0;
|
|
@@ -2320,19 +2499,40 @@ async function classifyStallOutput(ctx) {
|
|
|
2320
2499
|
// src/services/swarm-coordinator.ts
|
|
2321
2500
|
init_ansi_utils();
|
|
2322
2501
|
init_swarm_decision_loop();
|
|
2323
|
-
import {
|
|
2502
|
+
import {logger} from "@elizaos/core";
|
|
2324
2503
|
|
|
2325
2504
|
// src/services/swarm-idle-watchdog.ts
|
|
2326
2505
|
init_ansi_utils();
|
|
2327
2506
|
init_swarm_decision_loop();
|
|
2328
|
-
import {
|
|
2329
|
-
var IDLE_THRESHOLD_MS = 3 * 60 * 1000;
|
|
2330
|
-
var MAX_IDLE_CHECKS = 3;
|
|
2507
|
+
import {ModelType as ModelType4} from "@elizaos/core";
|
|
2331
2508
|
async function scanIdleSessions(ctx) {
|
|
2332
2509
|
const now = Date.now();
|
|
2333
2510
|
for (const taskCtx of ctx.tasks.values()) {
|
|
2334
2511
|
if (taskCtx.status !== "active")
|
|
2335
2512
|
continue;
|
|
2513
|
+
if (ctx.ptyService) {
|
|
2514
|
+
const session = ctx.ptyService.getSession(taskCtx.sessionId);
|
|
2515
|
+
if (!session) {
|
|
2516
|
+
ctx.log(`Idle watchdog: "${taskCtx.label}" \u2014 PTY session no longer exists, marking as stopped`);
|
|
2517
|
+
taskCtx.status = "stopped";
|
|
2518
|
+
taskCtx.decisions.push({
|
|
2519
|
+
timestamp: now,
|
|
2520
|
+
event: "idle_watchdog",
|
|
2521
|
+
promptText: "PTY session no longer exists",
|
|
2522
|
+
decision: "stopped",
|
|
2523
|
+
reasoning: "Underlying PTY process is gone (likely killed during restart)"
|
|
2524
|
+
});
|
|
2525
|
+
ctx.broadcast({
|
|
2526
|
+
type: "stopped",
|
|
2527
|
+
sessionId: taskCtx.sessionId,
|
|
2528
|
+
timestamp: now,
|
|
2529
|
+
data: { reason: "pty_session_gone" }
|
|
2530
|
+
});
|
|
2531
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Session lost \u2014 the agent process is no longer running (likely killed during a restart).`, "coding-agent");
|
|
2532
|
+
checkAllTasksComplete(ctx);
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2336
2536
|
const idleMs = now - taskCtx.lastActivityAt;
|
|
2337
2537
|
if (idleMs < IDLE_THRESHOLD_MS)
|
|
2338
2538
|
continue;
|
|
@@ -2340,31 +2540,34 @@ async function scanIdleSessions(ctx) {
|
|
|
2340
2540
|
continue;
|
|
2341
2541
|
if (ctx.ptyService) {
|
|
2342
2542
|
try {
|
|
2343
|
-
const
|
|
2543
|
+
const rawOutput = await ctx.ptyService.getSessionOutput(taskCtx.sessionId, 20);
|
|
2544
|
+
const currentOutput = stripAnsi(rawOutput).trim();
|
|
2344
2545
|
const lastSeen = ctx.lastSeenOutput.get(taskCtx.sessionId) ?? "";
|
|
2345
2546
|
ctx.lastSeenOutput.set(taskCtx.sessionId, currentOutput);
|
|
2346
2547
|
if (currentOutput !== lastSeen) {
|
|
2347
2548
|
taskCtx.lastActivityAt = now;
|
|
2348
2549
|
taskCtx.idleCheckCount = 0;
|
|
2349
|
-
ctx.log(`Idle watchdog: "${taskCtx.label}" has fresh PTY output
|
|
2550
|
+
ctx.log(`Idle watchdog: "${taskCtx.label}" has fresh PTY output \u2014 not idle`);
|
|
2350
2551
|
continue;
|
|
2351
2552
|
}
|
|
2352
|
-
} catch {
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2353
2555
|
}
|
|
2354
2556
|
taskCtx.idleCheckCount++;
|
|
2355
2557
|
const idleMinutes = Math.round(idleMs / 60000);
|
|
2356
2558
|
ctx.log(`Idle watchdog: "${taskCtx.label}" idle for ${idleMinutes}m (check ${taskCtx.idleCheckCount}/${MAX_IDLE_CHECKS})`);
|
|
2357
|
-
if (taskCtx.idleCheckCount
|
|
2358
|
-
ctx.log(`Idle watchdog: force-
|
|
2559
|
+
if (taskCtx.idleCheckCount >= MAX_IDLE_CHECKS) {
|
|
2560
|
+
ctx.log(`Idle watchdog: force-stopping "${taskCtx.label}" after ${MAX_IDLE_CHECKS} checks`);
|
|
2561
|
+
taskCtx.status = "stopped";
|
|
2359
2562
|
taskCtx.decisions.push({
|
|
2360
2563
|
timestamp: now,
|
|
2361
2564
|
event: "idle_watchdog",
|
|
2362
2565
|
promptText: `Session idle for ${idleMinutes} minutes`,
|
|
2363
|
-
decision: "
|
|
2364
|
-
reasoning: `Force-
|
|
2566
|
+
decision: "stopped",
|
|
2567
|
+
reasoning: `Force-stopped after ${MAX_IDLE_CHECKS} idle checks with no activity`
|
|
2365
2568
|
});
|
|
2366
2569
|
ctx.broadcast({
|
|
2367
|
-
type: "
|
|
2570
|
+
type: "stopped",
|
|
2368
2571
|
sessionId: taskCtx.sessionId,
|
|
2369
2572
|
timestamp: now,
|
|
2370
2573
|
data: {
|
|
@@ -2373,7 +2576,22 @@ async function scanIdleSessions(ctx) {
|
|
|
2373
2576
|
idleCheckCount: taskCtx.idleCheckCount
|
|
2374
2577
|
}
|
|
2375
2578
|
});
|
|
2376
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Session
|
|
2579
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Session stopped \u2014 idle for ${idleMinutes} minutes with no progress.`, "coding-agent");
|
|
2580
|
+
if (ctx.ptyService) {
|
|
2581
|
+
try {
|
|
2582
|
+
await ctx.ptyService.stopSession(taskCtx.sessionId);
|
|
2583
|
+
} catch (err) {
|
|
2584
|
+
ctx.log(`Idle watchdog: failed to stop session ${taskCtx.sessionId}: ${err}`);
|
|
2585
|
+
taskCtx.status = "error";
|
|
2586
|
+
ctx.broadcast({
|
|
2587
|
+
type: "error",
|
|
2588
|
+
sessionId: taskCtx.sessionId,
|
|
2589
|
+
timestamp: now,
|
|
2590
|
+
data: { message: `Failed to stop idle session: ${err}` }
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
checkAllTasksComplete(ctx);
|
|
2377
2595
|
continue;
|
|
2378
2596
|
}
|
|
2379
2597
|
await handleIdleCheck(ctx, taskCtx, idleMinutes);
|
|
@@ -2409,7 +2627,7 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
2409
2627
|
const prompt = buildIdleCheckPrompt(contextSummary, recentOutput, idleMinutes, taskCtx.idleCheckCount, MAX_IDLE_CHECKS, decisionHistory);
|
|
2410
2628
|
let decision = null;
|
|
2411
2629
|
try {
|
|
2412
|
-
const result = await ctx.runtime.useModel(
|
|
2630
|
+
const result = await ctx.runtime.useModel(ModelType4.TEXT_SMALL, {
|
|
2413
2631
|
prompt
|
|
2414
2632
|
});
|
|
2415
2633
|
decision = parseCoordinationResponse(result);
|
|
@@ -2417,8 +2635,8 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
2417
2635
|
ctx.log(`Idle check LLM call failed: ${err}`);
|
|
2418
2636
|
}
|
|
2419
2637
|
if (!decision) {
|
|
2420
|
-
ctx.log(`Idle check for "${taskCtx.label}": LLM returned invalid response
|
|
2421
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Session idle for ${idleMinutes}m
|
|
2638
|
+
ctx.log(`Idle check for "${taskCtx.label}": LLM returned invalid response \u2014 escalating`);
|
|
2639
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Session idle for ${idleMinutes}m \u2014 couldn't determine status. Needs your attention.`, "coding-agent");
|
|
2422
2640
|
return;
|
|
2423
2641
|
}
|
|
2424
2642
|
taskCtx.decisions.push({
|
|
@@ -2440,23 +2658,27 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
2440
2658
|
reasoning: decision.reasoning
|
|
2441
2659
|
}
|
|
2442
2660
|
});
|
|
2443
|
-
if (decision.action === "complete") {
|
|
2661
|
+
if (decision.action === "complete") {
|
|
2662
|
+
} else if (decision.action === "respond") {
|
|
2444
2663
|
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : `Nudged: ${decision.response ?? ""}`;
|
|
2445
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m
|
|
2664
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m \u2014 ${actionDesc}`, "coding-agent");
|
|
2446
2665
|
} else if (decision.action === "escalate") {
|
|
2447
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m
|
|
2666
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
2448
2667
|
} else if (decision.action === "ignore") {
|
|
2449
|
-
ctx.log(`Idle check for "${taskCtx.label}": LLM says still working
|
|
2668
|
+
ctx.log(`Idle check for "${taskCtx.label}": LLM says still working \u2014 ${decision.reasoning}`);
|
|
2450
2669
|
}
|
|
2451
2670
|
await executeDecision(ctx, sessionId, decision);
|
|
2452
2671
|
} finally {
|
|
2453
2672
|
ctx.inFlightDecisions.delete(sessionId);
|
|
2454
2673
|
}
|
|
2455
2674
|
}
|
|
2675
|
+
var IDLE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
2676
|
+
var MAX_IDLE_CHECKS = 4;
|
|
2456
2677
|
|
|
2457
2678
|
// src/services/swarm-coordinator.ts
|
|
2458
2679
|
var UNREGISTERED_BUFFER_MS = 2000;
|
|
2459
2680
|
var IDLE_SCAN_INTERVAL_MS = 60 * 1000;
|
|
2681
|
+
var PAUSE_TIMEOUT_MS = 30000;
|
|
2460
2682
|
|
|
2461
2683
|
class SwarmCoordinator {
|
|
2462
2684
|
static serviceType = "SWARM_COORDINATOR";
|
|
@@ -2470,10 +2692,14 @@ class SwarmCoordinator {
|
|
|
2470
2692
|
inFlightDecisions = new Set;
|
|
2471
2693
|
chatCallback = null;
|
|
2472
2694
|
wsBroadcast = null;
|
|
2695
|
+
agentDecisionCb = null;
|
|
2473
2696
|
unregisteredBuffer = new Map;
|
|
2474
2697
|
idleWatchdogTimer = null;
|
|
2475
2698
|
lastSeenOutput = new Map;
|
|
2476
2699
|
lastToolNotification = new Map;
|
|
2700
|
+
_paused = false;
|
|
2701
|
+
pauseBuffer = [];
|
|
2702
|
+
pauseTimeout = null;
|
|
2477
2703
|
constructor(runtime) {
|
|
2478
2704
|
this.runtime = runtime;
|
|
2479
2705
|
}
|
|
@@ -2485,6 +2711,13 @@ class SwarmCoordinator {
|
|
|
2485
2711
|
this.wsBroadcast = cb;
|
|
2486
2712
|
this.log("WS broadcast callback wired");
|
|
2487
2713
|
}
|
|
2714
|
+
setAgentDecisionCallback(cb) {
|
|
2715
|
+
this.agentDecisionCb = cb;
|
|
2716
|
+
this.log("Agent decision callback wired \u2014 events will route through Milaidy");
|
|
2717
|
+
}
|
|
2718
|
+
getAgentDecisionCallback() {
|
|
2719
|
+
return this.agentDecisionCb;
|
|
2720
|
+
}
|
|
2488
2721
|
sendChatMessage(text, source) {
|
|
2489
2722
|
if (!this.chatCallback)
|
|
2490
2723
|
return;
|
|
@@ -2527,8 +2760,49 @@ class SwarmCoordinator {
|
|
|
2527
2760
|
this.unregisteredBuffer.clear();
|
|
2528
2761
|
this.lastSeenOutput.clear();
|
|
2529
2762
|
this.lastToolNotification.clear();
|
|
2763
|
+
this.agentDecisionCb = null;
|
|
2764
|
+
this._paused = false;
|
|
2765
|
+
if (this.pauseTimeout) {
|
|
2766
|
+
clearTimeout(this.pauseTimeout);
|
|
2767
|
+
this.pauseTimeout = null;
|
|
2768
|
+
}
|
|
2769
|
+
this.pauseBuffer = [];
|
|
2530
2770
|
this.log("SwarmCoordinator stopped");
|
|
2531
2771
|
}
|
|
2772
|
+
get isPaused() {
|
|
2773
|
+
return this._paused;
|
|
2774
|
+
}
|
|
2775
|
+
pause() {
|
|
2776
|
+
if (this._paused)
|
|
2777
|
+
return;
|
|
2778
|
+
this._paused = true;
|
|
2779
|
+
this.log("Coordinator paused \u2014 buffering LLM decisions until user message is processed");
|
|
2780
|
+
this.broadcast({ type: "coordinator_paused", sessionId: "", timestamp: Date.now(), data: {} });
|
|
2781
|
+
this.pauseTimeout = setTimeout(() => {
|
|
2782
|
+
if (this._paused) {
|
|
2783
|
+
this.log("Coordinator auto-resuming after timeout");
|
|
2784
|
+
this.resume();
|
|
2785
|
+
}
|
|
2786
|
+
}, PAUSE_TIMEOUT_MS);
|
|
2787
|
+
}
|
|
2788
|
+
resume() {
|
|
2789
|
+
if (!this._paused)
|
|
2790
|
+
return;
|
|
2791
|
+
this._paused = false;
|
|
2792
|
+
if (this.pauseTimeout) {
|
|
2793
|
+
clearTimeout(this.pauseTimeout);
|
|
2794
|
+
this.pauseTimeout = null;
|
|
2795
|
+
}
|
|
2796
|
+
this.log(`Coordinator resumed \u2014 replaying ${this.pauseBuffer.length} buffered events`);
|
|
2797
|
+
this.broadcast({ type: "coordinator_resumed", sessionId: "", timestamp: Date.now(), data: {} });
|
|
2798
|
+
const buffered = [...this.pauseBuffer];
|
|
2799
|
+
this.pauseBuffer = [];
|
|
2800
|
+
for (const entry of buffered) {
|
|
2801
|
+
this.handleSessionEvent(entry.sessionId, entry.event, entry.data).catch((err) => {
|
|
2802
|
+
this.log(`Error replaying buffered event: ${err}`);
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2532
2806
|
registerTask(sessionId, context) {
|
|
2533
2807
|
this.tasks.set(sessionId, {
|
|
2534
2808
|
sessionId,
|
|
@@ -2536,6 +2810,7 @@ class SwarmCoordinator {
|
|
|
2536
2810
|
label: context.label,
|
|
2537
2811
|
originalTask: context.originalTask,
|
|
2538
2812
|
workdir: context.workdir,
|
|
2813
|
+
repo: context.repo,
|
|
2539
2814
|
status: "active",
|
|
2540
2815
|
decisions: [],
|
|
2541
2816
|
autoResolvedCount: 0,
|
|
@@ -2563,6 +2838,15 @@ class SwarmCoordinator {
|
|
|
2563
2838
|
}
|
|
2564
2839
|
}
|
|
2565
2840
|
}
|
|
2841
|
+
getLastUsedRepo() {
|
|
2842
|
+
let latest;
|
|
2843
|
+
for (const task of this.tasks.values()) {
|
|
2844
|
+
if (task.repo && (!latest || task.registeredAt > latest.registeredAt)) {
|
|
2845
|
+
latest = task;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
return latest?.repo;
|
|
2849
|
+
}
|
|
2566
2850
|
getTaskContext(sessionId) {
|
|
2567
2851
|
return this.tasks.get(sessionId);
|
|
2568
2852
|
}
|
|
@@ -2604,10 +2888,9 @@ class SwarmCoordinator {
|
|
|
2604
2888
|
}
|
|
2605
2889
|
writeSseEvent(res, event) {
|
|
2606
2890
|
try {
|
|
2607
|
-
res.write(`data: ${JSON.stringify(event)}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
} catch {}
|
|
2891
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
2892
|
+
} catch {
|
|
2893
|
+
}
|
|
2611
2894
|
}
|
|
2612
2895
|
async handleSessionEvent(sessionId, event, data) {
|
|
2613
2896
|
const taskCtx = this.tasks.get(sessionId);
|
|
@@ -2626,7 +2909,8 @@ class SwarmCoordinator {
|
|
|
2626
2909
|
if (ctx) {
|
|
2627
2910
|
this.unregisteredBuffer.delete(sessionId);
|
|
2628
2911
|
for (const entry of stillBuffered) {
|
|
2629
|
-
this.handleSessionEvent(sessionId, entry.event, entry.data).catch(() => {
|
|
2912
|
+
this.handleSessionEvent(sessionId, entry.event, entry.data).catch(() => {
|
|
2913
|
+
});
|
|
2630
2914
|
}
|
|
2631
2915
|
} else {
|
|
2632
2916
|
this.unregisteredBuffer.delete(sessionId);
|
|
@@ -2637,8 +2921,28 @@ class SwarmCoordinator {
|
|
|
2637
2921
|
}
|
|
2638
2922
|
return;
|
|
2639
2923
|
}
|
|
2924
|
+
if (taskCtx.status === "stopped" || taskCtx.status === "error" || taskCtx.status === "completed") {
|
|
2925
|
+
if (event !== "stopped" && event !== "error") {
|
|
2926
|
+
this.log(`Ignoring "${event}" for ${taskCtx.label} (status: ${taskCtx.status})`);
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2640
2930
|
taskCtx.lastActivityAt = Date.now();
|
|
2641
2931
|
taskCtx.idleCheckCount = 0;
|
|
2932
|
+
if (this._paused && (event === "blocked" || event === "task_complete")) {
|
|
2933
|
+
const eventData = data;
|
|
2934
|
+
if (!(event === "blocked" && eventData.autoResponded)) {
|
|
2935
|
+
this.broadcast({
|
|
2936
|
+
type: event === "blocked" ? "blocked_buffered" : "turn_complete_buffered",
|
|
2937
|
+
sessionId,
|
|
2938
|
+
timestamp: Date.now(),
|
|
2939
|
+
data
|
|
2940
|
+
});
|
|
2941
|
+
this.pauseBuffer.push({ sessionId, event, data });
|
|
2942
|
+
this.log(`Buffered "${event}" for ${taskCtx.label} (coordinator paused)`);
|
|
2943
|
+
return;
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2642
2946
|
switch (event) {
|
|
2643
2947
|
case "blocked":
|
|
2644
2948
|
await handleBlocked(this, sessionId, taskCtx, data);
|
|
@@ -2663,16 +2967,19 @@ class SwarmCoordinator {
|
|
|
2663
2967
|
});
|
|
2664
2968
|
const errorMsg = data.message ?? "unknown error";
|
|
2665
2969
|
this.sendChatMessage(`"${taskCtx.label}" hit an error: ${errorMsg}`, "coding-agent");
|
|
2970
|
+
checkAllTasksComplete(this);
|
|
2666
2971
|
break;
|
|
2667
2972
|
}
|
|
2668
2973
|
case "stopped":
|
|
2669
2974
|
taskCtx.status = "stopped";
|
|
2975
|
+
this.inFlightDecisions.delete(sessionId);
|
|
2670
2976
|
this.broadcast({
|
|
2671
2977
|
type: "stopped",
|
|
2672
2978
|
sessionId,
|
|
2673
2979
|
timestamp: Date.now(),
|
|
2674
2980
|
data
|
|
2675
2981
|
});
|
|
2982
|
+
checkAllTasksComplete(this);
|
|
2676
2983
|
break;
|
|
2677
2984
|
case "ready":
|
|
2678
2985
|
this.broadcast({
|
|
@@ -2693,6 +3000,10 @@ class SwarmCoordinator {
|
|
|
2693
3000
|
});
|
|
2694
3001
|
const toolData = data;
|
|
2695
3002
|
const now = Date.now();
|
|
3003
|
+
const STARTUP_GRACE_MS = 1e4;
|
|
3004
|
+
if (now - taskCtx.registeredAt < STARTUP_GRACE_MS) {
|
|
3005
|
+
break;
|
|
3006
|
+
}
|
|
2696
3007
|
const lastNotif = this.lastToolNotification.get(sessionId) ?? 0;
|
|
2697
3008
|
if (now - lastNotif > 30000) {
|
|
2698
3009
|
this.lastToolNotification.set(sessionId, now);
|
|
@@ -2705,9 +3016,10 @@ class SwarmCoordinator {
|
|
|
2705
3016
|
if (devUrl) {
|
|
2706
3017
|
urlSuffix = ` Dev server running at ${devUrl}`;
|
|
2707
3018
|
}
|
|
2708
|
-
} catch {
|
|
3019
|
+
} catch {
|
|
3020
|
+
}
|
|
2709
3021
|
}
|
|
2710
|
-
this.sendChatMessage(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal
|
|
3022
|
+
this.sendChatMessage(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal \u2014 I'll let it finish.`, "coding-agent");
|
|
2711
3023
|
}
|
|
2712
3024
|
break;
|
|
2713
3025
|
}
|
|
@@ -2727,6 +3039,9 @@ class SwarmCoordinator {
|
|
|
2727
3039
|
async executeDecision(sessionId, decision) {
|
|
2728
3040
|
return executeDecision(this, sessionId, decision);
|
|
2729
3041
|
}
|
|
3042
|
+
async executeEventDecision(sessionId, decision) {
|
|
3043
|
+
return executeDecision(this, sessionId, decision);
|
|
3044
|
+
}
|
|
2730
3045
|
setSupervisionLevel(level) {
|
|
2731
3046
|
this.supervisionLevel = level;
|
|
2732
3047
|
this.broadcast({
|
|
@@ -2847,6 +3162,7 @@ class PTYService {
|
|
|
2847
3162
|
const coordinator = new SwarmCoordinator(runtime);
|
|
2848
3163
|
coordinator.start(service);
|
|
2849
3164
|
service.coordinator = coordinator;
|
|
3165
|
+
runtime.services.set("SWARM_COORDINATOR", [coordinator]);
|
|
2850
3166
|
logger2.info("[PTYService] SwarmCoordinator wired and started");
|
|
2851
3167
|
} catch (err) {
|
|
2852
3168
|
logger2.error(`[PTYService] Failed to wire SwarmCoordinator: ${err}`);
|
|
@@ -2870,7 +3186,14 @@ class PTYService {
|
|
|
2870
3186
|
metricsTracker: this.metricsTracker,
|
|
2871
3187
|
traceEntries: this.traceEntries,
|
|
2872
3188
|
maxTraceEntries: PTYService.MAX_TRACE_ENTRIES,
|
|
2873
|
-
log: (msg) => this.log(msg)
|
|
3189
|
+
log: (msg) => this.log(msg),
|
|
3190
|
+
hasActiveTask: (sessionId) => {
|
|
3191
|
+
const coordinator = this.coordinator;
|
|
3192
|
+
if (!coordinator)
|
|
3193
|
+
return false;
|
|
3194
|
+
const taskCtx = coordinator.getTaskContext(sessionId);
|
|
3195
|
+
return taskCtx?.status === "active";
|
|
3196
|
+
}
|
|
2874
3197
|
});
|
|
2875
3198
|
this.manager = result.manager;
|
|
2876
3199
|
this.usingBunWorker = result.usingBunWorker;
|
|
@@ -2887,6 +3210,7 @@ class PTYService {
|
|
|
2887
3210
|
async stop() {
|
|
2888
3211
|
if (this.coordinator) {
|
|
2889
3212
|
this.coordinator.stop();
|
|
3213
|
+
this.runtime.services.delete("SWARM_COORDINATOR");
|
|
2890
3214
|
this.coordinator = null;
|
|
2891
3215
|
}
|
|
2892
3216
|
if (this.consoleBridge) {
|
|
@@ -2954,6 +3278,24 @@ class PTYService {
|
|
|
2954
3278
|
this.log(`Failed to write approval config: ${err}`);
|
|
2955
3279
|
}
|
|
2956
3280
|
}
|
|
3281
|
+
if (resolvedAgentType === "claude") {
|
|
3282
|
+
try {
|
|
3283
|
+
const settingsPath = join(workdir, ".claude", "settings.json");
|
|
3284
|
+
let settings = {};
|
|
3285
|
+
try {
|
|
3286
|
+
settings = JSON.parse(await readFile(settingsPath, "utf-8"));
|
|
3287
|
+
} catch {
|
|
3288
|
+
}
|
|
3289
|
+
const permissions = settings.permissions ?? {};
|
|
3290
|
+
permissions.allowedDirectories = [workdir];
|
|
3291
|
+
settings.permissions = permissions;
|
|
3292
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
3293
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
3294
|
+
this.log(`Wrote allowedDirectories [${workdir}] to ${settingsPath}`);
|
|
3295
|
+
} catch (err) {
|
|
3296
|
+
this.log(`Failed to write allowedDirectories: ${err}`);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
2957
3299
|
const spawnConfig = buildSpawnConfig(sessionId, {
|
|
2958
3300
|
...options,
|
|
2959
3301
|
agentType: resolvedAgentType,
|
|
@@ -2963,7 +3305,8 @@ class PTYService {
|
|
|
2963
3305
|
this.sessionMetadata.set(session.id, {
|
|
2964
3306
|
...options.metadata,
|
|
2965
3307
|
requestedType: options.metadata?.requestedType ?? options.agentType,
|
|
2966
|
-
agentType: resolvedAgentType
|
|
3308
|
+
agentType: resolvedAgentType,
|
|
3309
|
+
coordinatorManaged: !!options.skipAdapterAutoResponse
|
|
2967
3310
|
});
|
|
2968
3311
|
const ctx = {
|
|
2969
3312
|
manager: this.manager,
|
|
@@ -3096,7 +3439,7 @@ class PTYService {
|
|
|
3096
3439
|
async classifyStall(sessionId, recentOutput) {
|
|
3097
3440
|
const meta = this.sessionMetadata.get(sessionId);
|
|
3098
3441
|
const agentType = meta?.agentType ?? "unknown";
|
|
3099
|
-
|
|
3442
|
+
const classification = await classifyStallOutput({
|
|
3100
3443
|
sessionId,
|
|
3101
3444
|
recentOutput,
|
|
3102
3445
|
agentType,
|
|
@@ -3108,6 +3451,11 @@ class PTYService {
|
|
|
3108
3451
|
debugSnapshots: this.serviceConfig.debug === true,
|
|
3109
3452
|
log: (msg) => this.log(msg)
|
|
3110
3453
|
});
|
|
3454
|
+
if (classification && meta?.coordinatorManaged && classification.suggestedResponse) {
|
|
3455
|
+
this.log(`Suppressing stall auto-response for coordinator-managed session ${sessionId} ` + `(would have sent: "${classification.suggestedResponse}")`);
|
|
3456
|
+
classification.suggestedResponse = undefined;
|
|
3457
|
+
}
|
|
3458
|
+
return classification;
|
|
3111
3459
|
}
|
|
3112
3460
|
getAdapter(agentType) {
|
|
3113
3461
|
let adapter = this.adapterCache.get(agentType);
|
|
@@ -3267,13 +3615,13 @@ var spawnAgentAction = {
|
|
|
3267
3615
|
}
|
|
3268
3616
|
return { success: false, error: "NO_WORKSPACE" };
|
|
3269
3617
|
}
|
|
3270
|
-
const resolvedWorkdir =
|
|
3271
|
-
const workspaceBaseDir =
|
|
3618
|
+
const resolvedWorkdir = path2.resolve(workdir);
|
|
3619
|
+
const workspaceBaseDir = path2.join(os.homedir(), ".milady", "workspaces");
|
|
3272
3620
|
const allowedPrefixes = [
|
|
3273
|
-
|
|
3274
|
-
|
|
3621
|
+
path2.resolve(workspaceBaseDir),
|
|
3622
|
+
path2.resolve(process.cwd())
|
|
3275
3623
|
];
|
|
3276
|
-
const isAllowed = allowedPrefixes.some((prefix) => resolvedWorkdir.startsWith(prefix +
|
|
3624
|
+
const isAllowed = allowedPrefixes.some((prefix) => resolvedWorkdir.startsWith(prefix + path2.sep) || resolvedWorkdir === prefix);
|
|
3277
3625
|
if (!isAllowed) {
|
|
3278
3626
|
if (callback) {
|
|
3279
3627
|
await callback({
|
|
@@ -3309,9 +3657,7 @@ var spawnAgentAction = {
|
|
|
3309
3657
|
if (preflight && !preflight.installed) {
|
|
3310
3658
|
if (callback) {
|
|
3311
3659
|
await callback({
|
|
3312
|
-
text: `${preflight.adapter} CLI is not installed.
|
|
3313
|
-
` + `Install with: ${preflight.installCommand}
|
|
3314
|
-
` + `Docs: ${preflight.docsUrl}`
|
|
3660
|
+
text: `${preflight.adapter} CLI is not installed.\n` + `Install with: ${preflight.installCommand}\n` + `Docs: ${preflight.docsUrl}`
|
|
3315
3661
|
});
|
|
3316
3662
|
}
|
|
3317
3663
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
@@ -3437,21 +3783,21 @@ var spawnAgentAction = {
|
|
|
3437
3783
|
|
|
3438
3784
|
// src/actions/coding-task-handlers.ts
|
|
3439
3785
|
import {
|
|
3440
|
-
|
|
3786
|
+
logger as logger5
|
|
3441
3787
|
} from "@elizaos/core";
|
|
3442
3788
|
|
|
3443
3789
|
// src/actions/coding-task-helpers.ts
|
|
3444
|
-
import {
|
|
3790
|
+
import {randomUUID} from "node:crypto";
|
|
3445
3791
|
import * as fs from "node:fs";
|
|
3446
3792
|
import * as os2 from "node:os";
|
|
3447
|
-
import * as
|
|
3793
|
+
import * as path3 from "node:path";
|
|
3448
3794
|
import {
|
|
3449
|
-
|
|
3795
|
+
logger as logger4
|
|
3450
3796
|
} from "@elizaos/core";
|
|
3451
3797
|
function createScratchDir() {
|
|
3452
|
-
const baseDir =
|
|
3798
|
+
const baseDir = path3.join(os2.homedir(), ".milady", "workspaces");
|
|
3453
3799
|
const scratchId = randomUUID();
|
|
3454
|
-
const scratchDir =
|
|
3800
|
+
const scratchDir = path3.join(baseDir, scratchId);
|
|
3455
3801
|
fs.mkdirSync(scratchDir, { recursive: true });
|
|
3456
3802
|
return scratchDir;
|
|
3457
3803
|
}
|
|
@@ -3485,9 +3831,7 @@ function registerSessionEvents(ptyService, runtime, sessionId, label, scratchDir
|
|
|
3485
3831
|
const response = data.response ?? "";
|
|
3486
3832
|
const preview = response.length > 500 ? `${response.slice(0, 500)}...` : response;
|
|
3487
3833
|
callback({
|
|
3488
|
-
text: preview ? `Agent "${label}" completed the task
|
|
3489
|
-
|
|
3490
|
-
${preview}` : `Agent "${label}" completed the task.`
|
|
3834
|
+
text: preview ? `Agent "${label}" completed the task.\n\n${preview}` : `Agent "${label}" completed the task.`
|
|
3491
3835
|
});
|
|
3492
3836
|
}
|
|
3493
3837
|
ptyService.stopSession(sessionId).catch((err) => {
|
|
@@ -3512,7 +3856,6 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
3512
3856
|
}
|
|
3513
3857
|
|
|
3514
3858
|
// src/actions/coding-task-handlers.ts
|
|
3515
|
-
var MAX_CONCURRENT_AGENTS = 8;
|
|
3516
3859
|
async function handleMultiAgent(ctx, agentsParam) {
|
|
3517
3860
|
const {
|
|
3518
3861
|
runtime,
|
|
@@ -3653,7 +3996,8 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
3653
3996
|
agentType: specAgentType,
|
|
3654
3997
|
label: specLabel,
|
|
3655
3998
|
originalTask: specTask,
|
|
3656
|
-
workdir
|
|
3999
|
+
workdir,
|
|
4000
|
+
repo
|
|
3657
4001
|
});
|
|
3658
4002
|
}
|
|
3659
4003
|
results.push({
|
|
@@ -3692,8 +4036,7 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
3692
4036
|
`Launched ${succeeded.length}/${agentSpecs.length} agents${repo ? ` on ${repo}` : ""}:`,
|
|
3693
4037
|
...succeeded.map((r) => ` - "${r.label}" (${r.agentType}) [session: ${r.sessionId}]`),
|
|
3694
4038
|
...failed.length > 0 ? [`Failed: ${failed.map((r) => `"${r.label}": ${r.error}`).join(", ")}`] : []
|
|
3695
|
-
].join(
|
|
3696
|
-
`);
|
|
4039
|
+
].join("\n");
|
|
3697
4040
|
if (callback) {
|
|
3698
4041
|
await callback({ text: summary });
|
|
3699
4042
|
}
|
|
@@ -3774,9 +4117,7 @@ async function handleSingleAgent(ctx, task) {
|
|
|
3774
4117
|
logger5.warn(`[START_CODING_TASK] ${preflight.adapter} CLI not installed`);
|
|
3775
4118
|
if (callback) {
|
|
3776
4119
|
await callback({
|
|
3777
|
-
text: `${preflight.adapter} CLI is not installed.
|
|
3778
|
-
Install with: ${preflight.installCommand}
|
|
3779
|
-
Docs: ${preflight.docsUrl}`
|
|
4120
|
+
text: `${preflight.adapter} CLI is not installed.\nInstall with: ${preflight.installCommand}\nDocs: ${preflight.docsUrl}`
|
|
3780
4121
|
});
|
|
3781
4122
|
}
|
|
3782
4123
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
@@ -3815,7 +4156,8 @@ Docs: ${preflight.docsUrl}`
|
|
|
3815
4156
|
agentType,
|
|
3816
4157
|
label,
|
|
3817
4158
|
originalTask: task,
|
|
3818
|
-
workdir
|
|
4159
|
+
workdir,
|
|
4160
|
+
repo
|
|
3819
4161
|
});
|
|
3820
4162
|
}
|
|
3821
4163
|
if (state) {
|
|
@@ -3828,8 +4170,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
3828
4170
|
}
|
|
3829
4171
|
const summary = repo ? `Cloned ${repo} and started ${displayType} agent as "${label}"${task ? ` with task: "${task}"` : ""}` : `Started ${displayType} agent as "${label}" in scratch workspace${task ? ` with task: "${task}"` : ""}`;
|
|
3830
4172
|
if (callback) {
|
|
3831
|
-
await callback({ text: `${summary}
|
|
3832
|
-
Session ID: ${session.id}` });
|
|
4173
|
+
await callback({ text: `${summary}\nSession ID: ${session.id}` });
|
|
3833
4174
|
}
|
|
3834
4175
|
return {
|
|
3835
4176
|
success: true,
|
|
@@ -3855,6 +4196,7 @@ Session ID: ${session.id}` });
|
|
|
3855
4196
|
return { success: false, error: errorMessage };
|
|
3856
4197
|
}
|
|
3857
4198
|
}
|
|
4199
|
+
var MAX_CONCURRENT_AGENTS = 8;
|
|
3858
4200
|
|
|
3859
4201
|
// src/actions/start-coding-task.ts
|
|
3860
4202
|
var startCodingTaskAction = {
|
|
@@ -3866,7 +4208,7 @@ var startCodingTaskAction = {
|
|
|
3866
4208
|
"SPAWN_AND_PROVISION",
|
|
3867
4209
|
"CODE_THIS"
|
|
3868
4210
|
],
|
|
3869
|
-
description: "Start a coding task: optionally clone a repo, then spawn a coding agent (Claude Code, Codex, Gemini, Aider, Pi) " + "to work on it. If no repo is provided, the agent runs in a safe scratch directory. " + "Use this whenever the user asks to work on code, research something with an agent, or run any agent task.",
|
|
4211
|
+
description: "Start a coding task: optionally clone a repo, then spawn a coding agent (Claude Code, Codex, Gemini, Aider, Pi) " + "to work on it. If no repo is provided, the agent runs in a safe scratch directory. " + "Use this whenever the user asks to work on code, research something with an agent, or run any agent task. " + "IMPORTANT: If the user references a repository from conversation history (e.g. 'in the same repo', " + "'on that project', 'add a feature to it'), you MUST include the repo URL in the `repo` parameter. " + "If the task involves code changes to a real project but you don't know the repo URL, ASK the user for it " + "before calling this action \u2014 do not default to a scratch directory for real project work.",
|
|
3870
4212
|
examples: [
|
|
3871
4213
|
[
|
|
3872
4214
|
{
|
|
@@ -3928,6 +4270,13 @@ var startCodingTaskAction = {
|
|
|
3928
4270
|
repo = urlMatch[0];
|
|
3929
4271
|
}
|
|
3930
4272
|
}
|
|
4273
|
+
if (!repo) {
|
|
4274
|
+
const coordinator = getCoordinator(runtime);
|
|
4275
|
+
const lastRepo = coordinator?.getLastUsedRepo();
|
|
4276
|
+
if (lastRepo) {
|
|
4277
|
+
repo = lastRepo;
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
3931
4280
|
const customCredentialKeys = runtime.getSetting("CUSTOM_CREDENTIAL_KEYS");
|
|
3932
4281
|
let customCredentials;
|
|
3933
4282
|
if (customCredentialKeys) {
|
|
@@ -3971,7 +4320,7 @@ var startCodingTaskAction = {
|
|
|
3971
4320
|
parameters: [
|
|
3972
4321
|
{
|
|
3973
4322
|
name: "repo",
|
|
3974
|
-
description: "Git repository URL to clone (e.g. https://github.com/owner/repo). " + "
|
|
4323
|
+
description: "Git repository URL to clone (e.g. https://github.com/owner/repo). " + "ALWAYS provide this when the user is working on a real project or references a repo from context. " + "Only omit for pure research/scratch tasks with no target repository. " + "If unsure which repo, ask the user before spawning.",
|
|
3975
4324
|
required: false,
|
|
3976
4325
|
schema: { type: "string" }
|
|
3977
4326
|
},
|
|
@@ -4019,7 +4368,7 @@ var startCodingTaskAction = {
|
|
|
4019
4368
|
|
|
4020
4369
|
// src/actions/stop-agent.ts
|
|
4021
4370
|
import {
|
|
4022
|
-
|
|
4371
|
+
logger as logger6
|
|
4023
4372
|
} from "@elizaos/core";
|
|
4024
4373
|
var stopAgentAction = {
|
|
4025
4374
|
name: "STOP_CODING_AGENT",
|
|
@@ -4182,6 +4531,15 @@ var stopAgentAction = {
|
|
|
4182
4531
|
};
|
|
4183
4532
|
|
|
4184
4533
|
// src/providers/action-examples.ts
|
|
4534
|
+
function formatExample(ex) {
|
|
4535
|
+
const actionTags = ex.actions.map((a) => ` <action>${a}</action>`).join("\n");
|
|
4536
|
+
const paramBlocks = Object.entries(ex.params ?? {}).map(([actionName, params]) => {
|
|
4537
|
+
const inner = Object.entries(params).map(([k, v]) => ` <${k}>${v}</${k}>`).join("\n");
|
|
4538
|
+
return ` <${actionName}>\n${inner}\n </${actionName}>`;
|
|
4539
|
+
}).join("\n");
|
|
4540
|
+
const paramsSection = paramBlocks ? `\n<params>\n${paramBlocks}\n</params>` : "";
|
|
4541
|
+
return `User: ${ex.user}\nAssistant:\n<actions>\n${actionTags}\n</actions>${paramsSection}`;
|
|
4542
|
+
}
|
|
4185
4543
|
var CODING_AGENT_EXAMPLES = [
|
|
4186
4544
|
{
|
|
4187
4545
|
user: "Can you set up a workspace for https://github.com/acme/my-app and have Claude fix the login bug?",
|
|
@@ -4190,7 +4548,7 @@ var CODING_AGENT_EXAMPLES = [
|
|
|
4190
4548
|
START_CODING_TASK: {
|
|
4191
4549
|
repo: "https://github.com/acme/my-app",
|
|
4192
4550
|
agentType: "claude",
|
|
4193
|
-
task: "Fix the login bug in src/auth.ts
|
|
4551
|
+
task: "Fix the login bug in src/auth.ts \u2014 users are getting 401 errors after token refresh"
|
|
4194
4552
|
}
|
|
4195
4553
|
}
|
|
4196
4554
|
},
|
|
@@ -4243,28 +4601,7 @@ var CODING_AGENT_EXAMPLES = [
|
|
|
4243
4601
|
}
|
|
4244
4602
|
}
|
|
4245
4603
|
];
|
|
4246
|
-
|
|
4247
|
-
const actionTags = ex.actions.map((a) => ` <action>${a}</action>`).join(`
|
|
4248
|
-
`);
|
|
4249
|
-
const paramBlocks = Object.entries(ex.params ?? {}).map(([actionName, params]) => {
|
|
4250
|
-
const inner = Object.entries(params).map(([k, v]) => ` <${k}>${v}</${k}>`).join(`
|
|
4251
|
-
`);
|
|
4252
|
-
return ` <${actionName}>
|
|
4253
|
-
${inner}
|
|
4254
|
-
</${actionName}>`;
|
|
4255
|
-
}).join(`
|
|
4256
|
-
`);
|
|
4257
|
-
const paramsSection = paramBlocks ? `
|
|
4258
|
-
<params>
|
|
4259
|
-
${paramBlocks}
|
|
4260
|
-
</params>` : "";
|
|
4261
|
-
return `User: ${ex.user}
|
|
4262
|
-
Assistant:
|
|
4263
|
-
<actions>
|
|
4264
|
-
${actionTags}
|
|
4265
|
-
</actions>${paramsSection}`;
|
|
4266
|
-
}
|
|
4267
|
-
var MULTI_AGENT_EXAMPLE = `User: Spin up 3 agents on https://github.com/acme/app — one to fix auth, one to write tests, one to update docs
|
|
4604
|
+
var MULTI_AGENT_EXAMPLE = `User: Spin up 3 agents on https://github.com/acme/app \u2014 one to fix auth, one to write tests, one to update docs
|
|
4268
4605
|
Assistant:
|
|
4269
4606
|
<actions>
|
|
4270
4607
|
<action>REPLY</action>
|
|
@@ -4273,7 +4610,7 @@ Assistant:
|
|
|
4273
4610
|
<params>
|
|
4274
4611
|
<START_CODING_TASK>
|
|
4275
4612
|
<repo>https://github.com/acme/app</repo>
|
|
4276
|
-
<agents>Fix the authentication bug in src/auth.ts
|
|
4613
|
+
<agents>Fix the authentication bug in src/auth.ts \u2014 users get 401 after token refresh. Your unique identifier is "alpha". | Write comprehensive unit tests for the auth module in src/auth.ts. Your unique identifier is "beta". | Update the API documentation in docs/ to reflect the new auth flow. Your unique identifier is "gamma".</agents>
|
|
4277
4614
|
</START_CODING_TASK>
|
|
4278
4615
|
</params>`;
|
|
4279
4616
|
var codingAgentExamplesProvider = {
|
|
@@ -4281,14 +4618,12 @@ var codingAgentExamplesProvider = {
|
|
|
4281
4618
|
description: "Structured examples showing how to use coding agent actions with parameters",
|
|
4282
4619
|
position: -1,
|
|
4283
4620
|
get: async (_runtime, _message, _state) => {
|
|
4284
|
-
const examples = CODING_AGENT_EXAMPLES.map(formatExample).join(
|
|
4285
|
-
|
|
4286
|
-
`);
|
|
4621
|
+
const examples = CODING_AGENT_EXAMPLES.map(formatExample).join("\n\n");
|
|
4287
4622
|
const text = [
|
|
4288
4623
|
"# Coding Agent Action Call Examples",
|
|
4289
4624
|
"When the user asks you to work on code, clone repos, spawn agents, or run agent tasks,",
|
|
4290
4625
|
"you MUST select the appropriate actions and include parameters. Do NOT just describe",
|
|
4291
|
-
"what you would do
|
|
4626
|
+
"what you would do \u2014 actually select the actions.",
|
|
4292
4627
|
"",
|
|
4293
4628
|
"IMPORTANT: Use START_CODING_TASK to launch coding agents. It handles workspace setup",
|
|
4294
4629
|
"automatically. If a repo URL is provided, it clones it first. If no repo, the agent",
|
|
@@ -4306,8 +4641,7 @@ var codingAgentExamplesProvider = {
|
|
|
4306
4641
|
"and unique identifiers so their work is clearly differentiated.",
|
|
4307
4642
|
"",
|
|
4308
4643
|
MULTI_AGENT_EXAMPLE
|
|
4309
|
-
].join(
|
|
4310
|
-
`);
|
|
4644
|
+
].join("\n");
|
|
4311
4645
|
return {
|
|
4312
4646
|
data: { codingAgentExamples: CODING_AGENT_EXAMPLES },
|
|
4313
4647
|
values: { codingAgentExamples: text },
|
|
@@ -4340,7 +4674,7 @@ function formatWorkspaceLine(ws, sessions) {
|
|
|
4340
4674
|
const label = ws.label || ws.id.slice(0, 8);
|
|
4341
4675
|
const agents = sessions.filter((s) => s.workdir === ws.path);
|
|
4342
4676
|
const agentSummary = agents.length > 0 ? agents.map((a) => `${a.agentType}:${formatStatus(a.status)}`).join(", ") : "no agents";
|
|
4343
|
-
return ` - "${label}"
|
|
4677
|
+
return ` - "${label}" \u2192 ${ws.repo} (branch: ${ws.branch}, ${agentSummary})`;
|
|
4344
4678
|
}
|
|
4345
4679
|
var activeWorkspaceContextProvider = {
|
|
4346
4680
|
name: "ACTIVE_WORKSPACE_CONTEXT",
|
|
@@ -4355,7 +4689,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4355
4689
|
try {
|
|
4356
4690
|
sessions = await Promise.race([
|
|
4357
4691
|
ptyService.listSessions(),
|
|
4358
|
-
new Promise((
|
|
4692
|
+
new Promise((resolve3) => setTimeout(() => resolve3([]), 2000))
|
|
4359
4693
|
]);
|
|
4360
4694
|
} catch {
|
|
4361
4695
|
sessions = [];
|
|
@@ -4369,8 +4703,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4369
4703
|
"# Active Workspaces & Agents",
|
|
4370
4704
|
"No active workspaces or coding agent sessions.",
|
|
4371
4705
|
"Use START_CODING_TASK to launch a new coding agent."
|
|
4372
|
-
].join(
|
|
4373
|
-
`);
|
|
4706
|
+
].join("\n");
|
|
4374
4707
|
return {
|
|
4375
4708
|
data: { activeWorkspaces: [], activeSessions: [] },
|
|
4376
4709
|
values: { activeWorkspaceContext: text2 },
|
|
@@ -4400,9 +4733,9 @@ var activeWorkspaceContextProvider = {
|
|
|
4400
4733
|
const supervisionLevel = coordinator.getSupervisionLevel();
|
|
4401
4734
|
if (pending.length > 0) {
|
|
4402
4735
|
lines.push("");
|
|
4403
|
-
lines.push(`## Pending Confirmations (${pending.length})
|
|
4736
|
+
lines.push(`## Pending Confirmations (${pending.length}) \u2014 supervision: ${supervisionLevel}`);
|
|
4404
4737
|
for (const p of pending) {
|
|
4405
|
-
lines.push(` - "${p.taskContext.label}" blocked: "${p.promptText}"
|
|
4738
|
+
lines.push(` - "${p.taskContext.label}" blocked: "${p.promptText}" \u2192 suggested: ${p.llmDecision.action}`);
|
|
4406
4739
|
}
|
|
4407
4740
|
} else if (supervisionLevel !== "autonomous") {
|
|
4408
4741
|
lines.push("");
|
|
@@ -4413,8 +4746,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4413
4746
|
lines.push("");
|
|
4414
4747
|
lines.push("You can interact with agents using SEND_TO_CODING_AGENT (pass sessionId), " + "stop them with STOP_CODING_AGENT, or finalize their work with FINALIZE_WORKSPACE.");
|
|
4415
4748
|
}
|
|
4416
|
-
const text = lines.join(
|
|
4417
|
-
`);
|
|
4749
|
+
const text = lines.join("\n");
|
|
4418
4750
|
return {
|
|
4419
4751
|
data: {
|
|
4420
4752
|
activeWorkspaces: workspaces.map((ws) => ({
|
|
@@ -4440,18 +4772,18 @@ var activeWorkspaceContextProvider = {
|
|
|
4440
4772
|
|
|
4441
4773
|
// src/services/workspace-service.ts
|
|
4442
4774
|
import * as os3 from "node:os";
|
|
4443
|
-
import * as
|
|
4775
|
+
import * as path5 from "node:path";
|
|
4444
4776
|
import {
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4777
|
+
CredentialService,
|
|
4778
|
+
GitHubPatClient as GitHubPatClient2,
|
|
4779
|
+
MemoryTokenStore,
|
|
4780
|
+
WorkspaceService
|
|
4449
4781
|
} from "git-workspace-service";
|
|
4450
4782
|
|
|
4451
4783
|
// src/services/workspace-github.ts
|
|
4452
4784
|
import {
|
|
4453
|
-
|
|
4454
|
-
|
|
4785
|
+
GitHubPatClient,
|
|
4786
|
+
OAuthDeviceFlow
|
|
4455
4787
|
} from "git-workspace-service";
|
|
4456
4788
|
function parseOwnerRepo(repo) {
|
|
4457
4789
|
const match = repo.match(/(?:github\.com\/)?([^/]+)\/([^/.]+)/);
|
|
@@ -4507,9 +4839,7 @@ async function performOAuthFlow(ctx, clientId) {
|
|
|
4507
4839
|
expiresIn: deviceCode.expiresIn
|
|
4508
4840
|
});
|
|
4509
4841
|
} else {
|
|
4510
|
-
console.log(`
|
|
4511
|
-
[GitHub Auth] Go to ${deviceCode.verificationUri} and enter code: ${deviceCode.userCode}
|
|
4512
|
-
`);
|
|
4842
|
+
console.log(`\n[GitHub Auth] Go to ${deviceCode.verificationUri} and enter code: ${deviceCode.userCode}\n`);
|
|
4513
4843
|
}
|
|
4514
4844
|
const token = await oauth.pollForToken(deviceCode);
|
|
4515
4845
|
const client = new GitHubPatClient({ token: token.accessToken });
|
|
@@ -4578,8 +4908,7 @@ async function getStatus(workspacePath) {
|
|
|
4578
4908
|
cwd: workspacePath,
|
|
4579
4909
|
encoding: "utf-8"
|
|
4580
4910
|
}).trim();
|
|
4581
|
-
const lines = statusOutput.split(
|
|
4582
|
-
`).filter(Boolean);
|
|
4911
|
+
const lines = statusOutput.split("\n").filter(Boolean);
|
|
4583
4912
|
const modified = [];
|
|
4584
4913
|
const staged = [];
|
|
4585
4914
|
const untracked = [];
|
|
@@ -4654,11 +4983,11 @@ async function createPR(workspaceService, workspace, workspaceId, options, log)
|
|
|
4654
4983
|
|
|
4655
4984
|
// src/services/workspace-lifecycle.ts
|
|
4656
4985
|
import * as fs2 from "node:fs";
|
|
4657
|
-
import * as
|
|
4986
|
+
import * as path4 from "node:path";
|
|
4658
4987
|
async function removeScratchDir(dirPath, baseDir, log) {
|
|
4659
|
-
const resolved =
|
|
4660
|
-
const resolvedBase =
|
|
4661
|
-
if (!resolved.startsWith(resolvedBase) && resolved !==
|
|
4988
|
+
const resolved = path4.resolve(dirPath);
|
|
4989
|
+
const resolvedBase = path4.resolve(baseDir) + path4.sep;
|
|
4990
|
+
if (!resolved.startsWith(resolvedBase) && resolved !== path4.resolve(baseDir)) {
|
|
4662
4991
|
console.warn(`[CodingWorkspaceService] Refusing to remove dir outside base: ${resolved}`);
|
|
4663
4992
|
return;
|
|
4664
4993
|
}
|
|
@@ -4690,7 +5019,7 @@ async function gcOrphanedWorkspaces(baseDir, workspaceTtlMs, trackedWorkspaceIds
|
|
|
4690
5019
|
skipped++;
|
|
4691
5020
|
continue;
|
|
4692
5021
|
}
|
|
4693
|
-
const dirPath =
|
|
5022
|
+
const dirPath = path4.join(baseDir, entry.name);
|
|
4694
5023
|
try {
|
|
4695
5024
|
const stat = await fs2.promises.stat(dirPath);
|
|
4696
5025
|
const age = now - stat.mtimeMs;
|
|
@@ -4727,7 +5056,7 @@ class CodingWorkspaceService {
|
|
|
4727
5056
|
constructor(runtime, config = {}) {
|
|
4728
5057
|
this.runtime = runtime;
|
|
4729
5058
|
this.serviceConfig = {
|
|
4730
|
-
baseDir: config.baseDir ??
|
|
5059
|
+
baseDir: config.baseDir ?? path5.join(os3.homedir(), ".milady", "workspaces"),
|
|
4731
5060
|
branchPrefix: config.branchPrefix ?? "milady",
|
|
4732
5061
|
debug: config.debug ?? false,
|
|
4733
5062
|
workspaceTtlMs: config.workspaceTtlMs ?? 24 * 60 * 60 * 1000
|
|
@@ -4989,7 +5318,7 @@ class CodingWorkspaceService {
|
|
|
4989
5318
|
}
|
|
4990
5319
|
// src/api/agent-routes.ts
|
|
4991
5320
|
import * as os4 from "node:os";
|
|
4992
|
-
import * as
|
|
5321
|
+
import * as path6 from "node:path";
|
|
4993
5322
|
async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
4994
5323
|
const method = req.method?.toUpperCase();
|
|
4995
5324
|
if (method === "GET" && pathname === "/api/coding-agents/preflight") {
|
|
@@ -5116,15 +5445,15 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
5116
5445
|
customCredentials,
|
|
5117
5446
|
metadata
|
|
5118
5447
|
} = body;
|
|
5119
|
-
const workspaceBaseDir =
|
|
5448
|
+
const workspaceBaseDir = path6.join(os4.homedir(), ".milady", "workspaces");
|
|
5120
5449
|
const allowedPrefixes = [
|
|
5121
|
-
|
|
5122
|
-
|
|
5450
|
+
path6.resolve(workspaceBaseDir),
|
|
5451
|
+
path6.resolve(process.cwd())
|
|
5123
5452
|
];
|
|
5124
5453
|
let workdir = rawWorkdir;
|
|
5125
5454
|
if (workdir) {
|
|
5126
|
-
const resolved =
|
|
5127
|
-
const isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 +
|
|
5455
|
+
const resolved = path6.resolve(workdir);
|
|
5456
|
+
const isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 + path6.sep));
|
|
5128
5457
|
if (!isAllowed) {
|
|
5129
5458
|
sendError(res, "workdir must be within workspace base directory or cwd", 403);
|
|
5130
5459
|
return true;
|
|
@@ -5287,7 +5616,6 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
5287
5616
|
}
|
|
5288
5617
|
|
|
5289
5618
|
// src/api/coordinator-routes.ts
|
|
5290
|
-
var COORDINATOR_PREFIX = "/api/coding-agents/coordinator";
|
|
5291
5619
|
async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
5292
5620
|
if (!pathname.startsWith(COORDINATOR_PREFIX)) {
|
|
5293
5621
|
return false;
|
|
@@ -5305,9 +5633,7 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5305
5633
|
"Cache-Control": "no-cache",
|
|
5306
5634
|
Connection: "keep-alive"
|
|
5307
5635
|
});
|
|
5308
|
-
res.write(
|
|
5309
|
-
|
|
5310
|
-
`);
|
|
5636
|
+
res.write(":ok\n\n");
|
|
5311
5637
|
const unsubscribe = coordinator.addSseClient(res);
|
|
5312
5638
|
req.on("close", unsubscribe);
|
|
5313
5639
|
const keepAlive = setInterval(() => {
|
|
@@ -5315,15 +5641,14 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5315
5641
|
clearInterval(keepAlive);
|
|
5316
5642
|
return;
|
|
5317
5643
|
}
|
|
5318
|
-
res.write(
|
|
5319
|
-
|
|
5320
|
-
`);
|
|
5644
|
+
res.write(":ping\n\n");
|
|
5321
5645
|
}, 30000);
|
|
5322
5646
|
req.on("close", () => clearInterval(keepAlive));
|
|
5323
5647
|
return true;
|
|
5324
5648
|
}
|
|
5325
5649
|
if (method === "GET" && subPath === "/status") {
|
|
5326
|
-
const
|
|
5650
|
+
const allTasks = coordinator.getAllTaskContexts();
|
|
5651
|
+
const tasks = allTasks.filter((t) => t.status !== "stopped" && t.status !== "completed" && t.status !== "error");
|
|
5327
5652
|
sendJson(res, {
|
|
5328
5653
|
supervisionLevel: coordinator.getSupervisionLevel(),
|
|
5329
5654
|
taskCount: tasks.length,
|
|
@@ -5401,6 +5726,7 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5401
5726
|
}
|
|
5402
5727
|
return false;
|
|
5403
5728
|
}
|
|
5729
|
+
var COORDINATOR_PREFIX = "/api/coding-agents/coordinator";
|
|
5404
5730
|
|
|
5405
5731
|
// src/api/issue-routes.ts
|
|
5406
5732
|
async function handleIssueRoutes(req, res, pathname, ctx) {
|
|
@@ -5632,9 +5958,8 @@ async function handleWorkspaceRoutes(req, res, pathname, ctx) {
|
|
|
5632
5958
|
}
|
|
5633
5959
|
|
|
5634
5960
|
// src/api/routes.ts
|
|
5635
|
-
var MAX_BODY_SIZE = 1024 * 1024;
|
|
5636
5961
|
async function parseBody(req) {
|
|
5637
|
-
return new Promise((
|
|
5962
|
+
return new Promise((resolve5, reject) => {
|
|
5638
5963
|
let body = "";
|
|
5639
5964
|
let size = 0;
|
|
5640
5965
|
req.on("data", (chunk) => {
|
|
@@ -5648,7 +5973,7 @@ async function parseBody(req) {
|
|
|
5648
5973
|
});
|
|
5649
5974
|
req.on("end", () => {
|
|
5650
5975
|
try {
|
|
5651
|
-
|
|
5976
|
+
resolve5(body ? JSON.parse(body) : {});
|
|
5652
5977
|
} catch {
|
|
5653
5978
|
reject(new Error("Invalid JSON body"));
|
|
5654
5979
|
}
|
|
@@ -5689,6 +6014,7 @@ function createCodingAgentRouteHandler(runtime, coordinator) {
|
|
|
5689
6014
|
};
|
|
5690
6015
|
return (req, res, pathname) => handleCodingAgentRoutes(req, res, pathname, ctx);
|
|
5691
6016
|
}
|
|
6017
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
5692
6018
|
|
|
5693
6019
|
// src/index.ts
|
|
5694
6020
|
var codingAgentPlugin = {
|
|
@@ -5726,10 +6052,12 @@ export {
|
|
|
5726
6052
|
src_default as default,
|
|
5727
6053
|
createCodingAgentRouteHandler,
|
|
5728
6054
|
codingAgentPlugin,
|
|
6055
|
+
buildTurnCompleteEventMessage,
|
|
6056
|
+
buildBlockedEventMessage,
|
|
5729
6057
|
SwarmCoordinator,
|
|
5730
6058
|
PTYService,
|
|
5731
6059
|
CodingWorkspaceService
|
|
5732
6060
|
};
|
|
5733
6061
|
|
|
5734
|
-
//# debugId=
|
|
6062
|
+
//# debugId=0100F6538A0A9D6264756E2164756E21
|
|
5735
6063
|
//# sourceMappingURL=index.js.map
|