@elizaos/plugin-agent-orchestrator 0.1.0 → 0.2.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/index.js +465 -539
- package/dist/index.js.map +10 -10
- package/dist/src/actions/coding-task-handlers.d.ts +44 -0
- package/dist/src/actions/coding-task-handlers.d.ts.map +1 -0
- package/dist/src/actions/coding-task-helpers.d.ts +27 -0
- package/dist/src/actions/coding-task-helpers.d.ts.map +1 -0
- package/dist/src/actions/finalize-workspace.d.ts +11 -0
- package/dist/src/actions/finalize-workspace.d.ts.map +1 -0
- package/dist/src/actions/list-agents.d.ts +11 -0
- package/dist/src/actions/list-agents.d.ts.map +1 -0
- package/dist/src/actions/manage-issues.d.ts +11 -0
- package/dist/src/actions/manage-issues.d.ts.map +1 -0
- package/dist/src/actions/provision-workspace.d.ts +11 -0
- package/dist/src/actions/provision-workspace.d.ts.map +1 -0
- package/dist/src/actions/send-to-agent.d.ts +11 -0
- package/dist/src/actions/send-to-agent.d.ts.map +1 -0
- package/dist/src/actions/spawn-agent.d.ts +11 -0
- package/dist/src/actions/spawn-agent.d.ts.map +1 -0
- package/dist/src/actions/start-coding-task.d.ts +17 -0
- package/dist/src/actions/start-coding-task.d.ts.map +1 -0
- package/dist/src/actions/stop-agent.d.ts +11 -0
- package/dist/src/actions/stop-agent.d.ts.map +1 -0
- package/dist/src/api/agent-routes.d.ts +18 -0
- package/dist/src/api/agent-routes.d.ts.map +1 -0
- package/dist/src/api/coordinator-routes.d.ts +22 -0
- package/dist/src/api/coordinator-routes.d.ts.map +1 -0
- package/dist/src/api/issue-routes.d.ts +17 -0
- package/dist/src/api/issue-routes.d.ts.map +1 -0
- package/dist/src/api/routes.d.ts +36 -0
- package/dist/src/api/routes.d.ts.map +1 -0
- package/dist/src/api/workspace-routes.d.ts +17 -0
- package/dist/src/api/workspace-routes.d.ts.map +1 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/providers/action-examples.d.ts +13 -0
- package/dist/src/providers/action-examples.d.ts.map +1 -0
- package/dist/src/providers/active-workspace-context.d.ts +13 -0
- package/dist/src/providers/active-workspace-context.d.ts.map +1 -0
- package/dist/src/services/agent-metrics.d.ts +28 -0
- package/dist/src/services/agent-metrics.d.ts.map +1 -0
- package/dist/src/services/agent-selection.d.ts +53 -0
- package/dist/src/services/agent-selection.d.ts.map +1 -0
- package/dist/src/services/ansi-utils.d.ts +48 -0
- package/dist/src/services/ansi-utils.d.ts.map +1 -0
- package/dist/src/services/pty-auto-response.d.ts +30 -0
- package/dist/src/services/pty-auto-response.d.ts.map +1 -0
- package/dist/src/services/pty-init.d.ts +43 -0
- package/dist/src/services/pty-init.d.ts.map +1 -0
- package/dist/src/services/pty-service.d.ts +92 -0
- package/dist/src/services/pty-service.d.ts.map +1 -0
- package/dist/src/services/pty-session-io.d.ts +46 -0
- package/dist/src/services/pty-session-io.d.ts.map +1 -0
- package/dist/src/services/pty-spawn.d.ts +50 -0
- package/dist/src/services/pty-spawn.d.ts.map +1 -0
- package/dist/src/services/pty-types.d.ts +80 -0
- package/dist/src/services/pty-types.d.ts.map +1 -0
- package/dist/src/services/stall-classifier.d.ts +44 -0
- package/dist/src/services/stall-classifier.d.ts.map +1 -0
- package/dist/src/services/swarm-coordinator-prompts.d.ts +62 -0
- package/dist/src/services/swarm-coordinator-prompts.d.ts.map +1 -0
- package/dist/src/services/swarm-coordinator.d.ts +163 -0
- package/dist/src/services/swarm-coordinator.d.ts.map +1 -0
- package/dist/src/services/swarm-decision-loop.d.ts +39 -0
- package/dist/src/services/swarm-decision-loop.d.ts.map +1 -0
- package/dist/src/services/swarm-idle-watchdog.d.ts +22 -0
- package/dist/src/services/swarm-idle-watchdog.d.ts.map +1 -0
- package/dist/src/services/workspace-git-ops.d.ts +28 -0
- package/dist/src/services/workspace-git-ops.d.ts.map +1 -0
- package/dist/src/services/workspace-github.d.ts +58 -0
- package/dist/src/services/workspace-github.d.ts.map +1 -0
- package/dist/src/services/workspace-lifecycle.d.ts +18 -0
- package/dist/src/services/workspace-lifecycle.d.ts.map +1 -0
- package/dist/src/services/workspace-service.d.ts +84 -0
- package/dist/src/services/workspace-service.d.ts.map +1 -0
- package/dist/src/services/workspace-types.d.ts +81 -0
- package/dist/src/services/workspace-types.d.ts.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +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,34 @@ 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.
|
|
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.
|
|
174
104
|
|
|
175
|
-
` + `
|
|
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".
|
|
176
106
|
|
|
177
|
-
` + `
|
|
178
|
-
|
|
179
|
-
` + `4. "ignore" — The agent is still actively working (e.g. compiling, running tests, ` + `generating code). The idle period is expected and it will produce output soon.
|
|
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.
|
|
207
|
-
|
|
208
|
-
` + `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".
|
|
209
|
-
|
|
210
|
-
` + `Your options:
|
|
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.
|
|
211
112
|
|
|
212
|
-
` + `
|
|
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.
|
|
213
114
|
|
|
214
|
-
` + `
|
|
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` + `- CRITICAL: Creating a PR is NEVER the final step. After you see "Created pull request" or a PR URL ` + `in the output, you MUST respond with "Review your PR, run each test plan item to verify it works, ` + `update the PR to check off each item, then confirm all items pass". NEVER mark as "complete" on the ` + `same turn that a PR was created \u2014 always send this follow-up first.
|
|
118
|
+
` + `- Only mark as "complete" AFTER the agent has confirmed it verified the test plan items ` + `(look for output like "all items pass", "verified", "checked off", or similar confirmation).\n` + `- Keep follow-up instructions concise and specific.\n` + `- Default to "respond" \u2014 only use "complete" when you're certain ALL work is done.
|
|
215
119
|
|
|
216
|
-
` + `
|
|
217
|
-
|
|
218
|
-
` + `4. "ignore" — Should not normally be used here.
|
|
219
|
-
|
|
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.
|
|
232
|
-
|
|
233
|
-
` + `Respond with ONLY a JSON object:
|
|
234
|
-
` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
120
|
+
` + `Respond with ONLY a JSON object:\n` + `{"action": "respond|complete|escalate|ignore", "response": "...", "useKeys": false, "keys": [], "reasoning": "..."}`;
|
|
235
121
|
}
|
|
236
122
|
function parseCoordinationResponse(llmOutput) {
|
|
237
123
|
const jsonMatch = llmOutput.match(/\{[\s\S]*\}/);
|
|
@@ -266,20 +152,23 @@ function parseCoordinationResponse(llmOutput) {
|
|
|
266
152
|
var exports_swarm_decision_loop = {};
|
|
267
153
|
__export(exports_swarm_decision_loop, {
|
|
268
154
|
makeCoordinationDecision: () => makeCoordinationDecision,
|
|
155
|
+
isOutOfScopeAccess: () => isOutOfScopeAccess,
|
|
269
156
|
handleTurnComplete: () => handleTurnComplete,
|
|
270
157
|
handleConfirmDecision: () => handleConfirmDecision,
|
|
271
158
|
handleBlocked: () => handleBlocked,
|
|
272
159
|
handleAutonomousDecision: () => handleAutonomousDecision,
|
|
273
160
|
executeDecision: () => executeDecision
|
|
274
161
|
});
|
|
275
|
-
import
|
|
162
|
+
import * as path from "node:path";
|
|
163
|
+
import {ModelType as ModelType2} from "@elizaos/core";
|
|
276
164
|
function toContextSummary(taskCtx) {
|
|
277
165
|
return {
|
|
278
166
|
sessionId: taskCtx.sessionId,
|
|
279
167
|
agentType: taskCtx.agentType,
|
|
280
168
|
label: taskCtx.label,
|
|
281
169
|
originalTask: taskCtx.originalTask,
|
|
282
|
-
workdir: taskCtx.workdir
|
|
170
|
+
workdir: taskCtx.workdir,
|
|
171
|
+
repo: taskCtx.repo
|
|
283
172
|
};
|
|
284
173
|
}
|
|
285
174
|
function toDecisionHistory(taskCtx) {
|
|
@@ -296,6 +185,24 @@ function formatDecisionResponse(decision) {
|
|
|
296
185
|
return;
|
|
297
186
|
return decision.useKeys ? `keys:${decision.keys?.join(",")}` : decision.response;
|
|
298
187
|
}
|
|
188
|
+
function isOutOfScopeAccess(promptText, workdir) {
|
|
189
|
+
const stripped = promptText.replace(/https?:\/\/\S+/g, "");
|
|
190
|
+
const multiSegment = /\/[\w.-]+(?:\/[\w.-]+)+/g;
|
|
191
|
+
const sensitiveRoots = /\b\/(etc|tmp|var|usr|opt|sys|proc|root)\b/g;
|
|
192
|
+
const homeTilde = /~\/[\w.-]+/g;
|
|
193
|
+
const matches = [
|
|
194
|
+
...stripped.match(multiSegment) ?? [],
|
|
195
|
+
...(stripped.match(sensitiveRoots) ?? []).map((m) => m.trimStart()),
|
|
196
|
+
...(stripped.match(homeTilde) ?? []).map((m) => m.replace("~", process.env.HOME ?? "/home/user"))
|
|
197
|
+
];
|
|
198
|
+
if (matches.length === 0)
|
|
199
|
+
return false;
|
|
200
|
+
const resolvedWorkdir = path.resolve(workdir);
|
|
201
|
+
return matches.some((p) => {
|
|
202
|
+
const resolved = path.resolve(p);
|
|
203
|
+
return !resolved.startsWith(resolvedWorkdir + path.sep) && resolved !== resolvedWorkdir;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
299
206
|
async function fetchRecentOutput(ctx, sessionId, lines = 50) {
|
|
300
207
|
if (!ctx.ptyService)
|
|
301
208
|
return "";
|
|
@@ -343,10 +250,9 @@ async function executeDecision(ctx, sessionId, decision) {
|
|
|
343
250
|
try {
|
|
344
251
|
const rawOutput = await ctx.ptyService.getSessionOutput(sessionId, 50);
|
|
345
252
|
summary = extractCompletionSummary(rawOutput);
|
|
346
|
-
} catch {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
ctx.sendChatMessage(summary ? `Finished "${taskCtx?.label ?? sessionId}".\n\n${summary}` : `Finished "${taskCtx?.label ?? sessionId}".`, "coding-agent");
|
|
350
256
|
ctx.ptyService.stopSession(sessionId).catch((err) => {
|
|
351
257
|
ctx.log(`Failed to stop session after LLM-detected completion: ${err}`);
|
|
352
258
|
});
|
|
@@ -370,6 +276,31 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
370
276
|
const eventData = data;
|
|
371
277
|
const promptText = eventData.promptInfo?.prompt ?? eventData.promptInfo?.instructions ?? "";
|
|
372
278
|
if (eventData.autoResponded) {
|
|
279
|
+
if (isOutOfScopeAccess(promptText, taskCtx.workdir)) {
|
|
280
|
+
taskCtx.decisions.push({
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
event: "blocked",
|
|
283
|
+
promptText,
|
|
284
|
+
decision: "escalate",
|
|
285
|
+
reasoning: `SECURITY: Auto-response approved access outside workspace (${taskCtx.workdir}). Session stopped.`
|
|
286
|
+
});
|
|
287
|
+
ctx.broadcast({
|
|
288
|
+
type: "escalation",
|
|
289
|
+
sessionId,
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
data: {
|
|
292
|
+
prompt: promptText,
|
|
293
|
+
reason: "out_of_scope_auto_approved",
|
|
294
|
+
workdir: taskCtx.workdir
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
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");
|
|
298
|
+
taskCtx.status = "error";
|
|
299
|
+
ctx.ptyService?.stopSession(sessionId).catch((err) => {
|
|
300
|
+
ctx.log(`Failed to stop session after out-of-scope auto-approval: ${err}`);
|
|
301
|
+
});
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
373
304
|
taskCtx.autoResolvedCount++;
|
|
374
305
|
taskCtx.decisions.push({
|
|
375
306
|
timestamp: Date.now(),
|
|
@@ -437,7 +368,7 @@ async function handleBlocked(ctx, sessionId, taskCtx, data) {
|
|
|
437
368
|
event: "blocked",
|
|
438
369
|
promptText,
|
|
439
370
|
decision: "escalate",
|
|
440
|
-
reasoning: "Supervision level is notify
|
|
371
|
+
reasoning: "Supervision level is notify \u2014 broadcasting only"
|
|
441
372
|
});
|
|
442
373
|
break;
|
|
443
374
|
}
|
|
@@ -449,7 +380,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
449
380
|
}
|
|
450
381
|
ctx.inFlightDecisions.add(sessionId);
|
|
451
382
|
try {
|
|
452
|
-
ctx.log(`Turn complete for "${taskCtx.label}"
|
|
383
|
+
ctx.log(`Turn complete for "${taskCtx.label}" \u2014 assessing whether task is done`);
|
|
453
384
|
const rawResponse = data.response ?? "";
|
|
454
385
|
let turnOutput = cleanForChat(rawResponse);
|
|
455
386
|
if (!turnOutput) {
|
|
@@ -467,13 +398,13 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
467
398
|
ctx.log(`Turn-complete LLM call failed: ${err}`);
|
|
468
399
|
}
|
|
469
400
|
if (!decision) {
|
|
470
|
-
ctx.log(`Turn-complete for "${taskCtx.label}": LLM invalid response
|
|
401
|
+
ctx.log(`Turn-complete for "${taskCtx.label}": LLM invalid response \u2014 defaulting to complete`);
|
|
471
402
|
decision = {
|
|
472
403
|
action: "complete",
|
|
473
|
-
reasoning: "LLM returned invalid response
|
|
404
|
+
reasoning: "LLM returned invalid response \u2014 defaulting to complete"
|
|
474
405
|
};
|
|
475
406
|
}
|
|
476
|
-
ctx.log(`Turn assessment for "${taskCtx.label}": ${decision.action}${decision.action === "respond" ? `
|
|
407
|
+
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
408
|
taskCtx.decisions.push({
|
|
478
409
|
timestamp: Date.now(),
|
|
479
410
|
event: "turn_complete",
|
|
@@ -496,7 +427,7 @@ async function handleTurnComplete(ctx, sessionId, taskCtx, data) {
|
|
|
496
427
|
const preview = instruction.length > 120 ? `${instruction.slice(0, 120)}...` : instruction;
|
|
497
428
|
ctx.sendChatMessage(`[${taskCtx.label}] Turn done, continuing: ${preview}`, "coding-agent");
|
|
498
429
|
} else if (decision.action === "escalate") {
|
|
499
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Turn finished
|
|
430
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Turn finished \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
500
431
|
}
|
|
501
432
|
await executeDecision(ctx, sessionId, decision);
|
|
502
433
|
} finally {
|
|
@@ -514,7 +445,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
514
445
|
if (!output) {
|
|
515
446
|
output = await fetchRecentOutput(ctx, sessionId);
|
|
516
447
|
}
|
|
517
|
-
|
|
448
|
+
let decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
|
|
518
449
|
if (!decision) {
|
|
519
450
|
taskCtx.decisions.push({
|
|
520
451
|
timestamp: Date.now(),
|
|
@@ -534,6 +465,14 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
534
465
|
});
|
|
535
466
|
return;
|
|
536
467
|
}
|
|
468
|
+
if (decision.action === "respond" && isOutOfScopeAccess(promptText, taskCtx.workdir)) {
|
|
469
|
+
decision = {
|
|
470
|
+
action: "respond",
|
|
471
|
+
response: `No \u2014 that path is outside your workspace. Use ${taskCtx.workdir} instead. Create any files or directories you need there.`,
|
|
472
|
+
reasoning: `Declined out-of-scope access (outside ${taskCtx.workdir}) and redirected agent to workspace.`
|
|
473
|
+
};
|
|
474
|
+
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");
|
|
475
|
+
}
|
|
537
476
|
taskCtx.decisions.push({
|
|
538
477
|
timestamp: Date.now(),
|
|
539
478
|
event: "blocked",
|
|
@@ -558,7 +497,7 @@ async function handleAutonomousDecision(ctx, sessionId, taskCtx, promptText, rec
|
|
|
558
497
|
if (decision.action === "respond") {
|
|
559
498
|
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : decision.response ? `Responded: ${decision.response.length > 100 ? `${decision.response.slice(0, 100)}...` : decision.response}` : "Responded";
|
|
560
499
|
const reasonExcerpt = decision.reasoning.length > 150 ? `${decision.reasoning.slice(0, 150)}...` : decision.reasoning;
|
|
561
|
-
ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc}
|
|
500
|
+
ctx.sendChatMessage(`[${taskCtx.label}] ${actionDesc} \u2014 ${reasonExcerpt}`, "coding-agent");
|
|
562
501
|
} else if (decision.action === "escalate") {
|
|
563
502
|
ctx.sendChatMessage(`[${taskCtx.label}] Needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
564
503
|
}
|
|
@@ -584,7 +523,7 @@ async function handleConfirmDecision(ctx, sessionId, taskCtx, promptText, recent
|
|
|
584
523
|
recentOutput: output,
|
|
585
524
|
llmDecision: {
|
|
586
525
|
action: "escalate",
|
|
587
|
-
reasoning: "LLM returned invalid response
|
|
526
|
+
reasoning: "LLM returned invalid response \u2014 needs human review"
|
|
588
527
|
},
|
|
589
528
|
taskContext: taskCtx,
|
|
590
529
|
createdAt: Date.now()
|
|
@@ -706,9 +645,7 @@ var finalizeWorkspaceAction = {
|
|
|
706
645
|
data: { workspaceId, status }
|
|
707
646
|
};
|
|
708
647
|
}
|
|
709
|
-
const commitMessage = content.commitMessage ?? `feat: automated changes from coding agent
|
|
710
|
-
|
|
711
|
-
Generated by Milady coding agent plugin.`;
|
|
648
|
+
const commitMessage = content.commitMessage ?? `feat: automated changes from coding agent\n\nGenerated by Milady coding agent plugin.`;
|
|
712
649
|
const commitHash = await workspaceService.commit(workspaceId, {
|
|
713
650
|
message: commitMessage,
|
|
714
651
|
all: true
|
|
@@ -717,15 +654,7 @@ Generated by Milady coding agent plugin.`;
|
|
|
717
654
|
let prInfo = null;
|
|
718
655
|
if (!content.skipPR) {
|
|
719
656
|
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*`;
|
|
657
|
+
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
658
|
prInfo = await workspaceService.createPR(workspaceId, {
|
|
730
659
|
title: prTitle,
|
|
731
660
|
body: prBody,
|
|
@@ -736,14 +665,11 @@ Automated changes generated by Milady coding agent.
|
|
|
736
665
|
if (callback) {
|
|
737
666
|
if (prInfo) {
|
|
738
667
|
await callback({
|
|
739
|
-
text: `Workspace finalized
|
|
740
|
-
` + `Commit: ${commitHash.slice(0, 8)}
|
|
741
|
-
` + `PR #${prInfo.number}: ${prInfo.url}`
|
|
668
|
+
text: `Workspace finalized!\n` + `Commit: ${commitHash.slice(0, 8)}\n` + `PR #${prInfo.number}: ${prInfo.url}`
|
|
742
669
|
});
|
|
743
670
|
} else {
|
|
744
671
|
await callback({
|
|
745
|
-
text: `Workspace changes committed and pushed.
|
|
746
|
-
` + `Commit: ${commitHash.slice(0, 8)}`
|
|
672
|
+
text: `Workspace changes committed and pushed.\n` + `Commit: ${commitHash.slice(0, 8)}`
|
|
747
673
|
});
|
|
748
674
|
}
|
|
749
675
|
}
|
|
@@ -887,23 +813,18 @@ var listAgentsAction = {
|
|
|
887
813
|
}));
|
|
888
814
|
const lines = sessions.map((session, index) => {
|
|
889
815
|
const statusEmoji = {
|
|
890
|
-
running: "
|
|
891
|
-
idle: "
|
|
892
|
-
blocked: "
|
|
893
|
-
completed: "
|
|
894
|
-
error: "
|
|
895
|
-
}[session.status] ?? "
|
|
816
|
+
running: "\u25B6\uFE0F",
|
|
817
|
+
idle: "\u23F8\uFE0F",
|
|
818
|
+
blocked: "\u26A0\uFE0F",
|
|
819
|
+
completed: "\u2705",
|
|
820
|
+
error: "\u274C"
|
|
821
|
+
}[session.status] ?? "\u2753";
|
|
896
822
|
return `${index + 1}. ${statusEmoji} ${session.agentType} (${session.id.slice(0, 8)}...)
|
|
897
|
-
\uD83D\uDCC1 ${session.workdir}
|
|
898
|
-
Status: ${session.status}`;
|
|
823
|
+
\uD83D\uDCC1 ${session.workdir}\n Status: ${session.status}`;
|
|
899
824
|
});
|
|
900
825
|
if (callback) {
|
|
901
826
|
await callback({
|
|
902
|
-
text: `Active coding agents
|
|
903
|
-
|
|
904
|
-
${lines.join(`
|
|
905
|
-
|
|
906
|
-
`)}`
|
|
827
|
+
text: `Active coding agents:\n\n${lines.join("\n\n")}`
|
|
907
828
|
});
|
|
908
829
|
}
|
|
909
830
|
return {
|
|
@@ -916,150 +837,6 @@ ${lines.join(`
|
|
|
916
837
|
};
|
|
917
838
|
|
|
918
839
|
// 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
840
|
async function handleOperation(service, repo, operation, params, originalText, callback) {
|
|
1064
841
|
try {
|
|
1065
842
|
switch (operation.toLowerCase()) {
|
|
@@ -1080,12 +857,9 @@ async function handleOperation(service, repo, operation, params, originalText, c
|
|
|
1080
857
|
created.push(issue2);
|
|
1081
858
|
}
|
|
1082
859
|
if (callback) {
|
|
1083
|
-
const summary = created.map((i) => `#${i.number}: ${i.title}
|
|
1084
|
-
${i.url}`).join(`
|
|
1085
|
-
`);
|
|
860
|
+
const summary = created.map((i) => `#${i.number}: ${i.title}\n ${i.url}`).join("\n");
|
|
1086
861
|
await callback({
|
|
1087
|
-
text: `Created ${created.length} issues
|
|
1088
|
-
${summary}`
|
|
862
|
+
text: `Created ${created.length} issues:\n${summary}`
|
|
1089
863
|
});
|
|
1090
864
|
}
|
|
1091
865
|
return { success: true, data: { issues: created } };
|
|
@@ -1102,8 +876,7 @@ ${summary}`
|
|
|
1102
876
|
});
|
|
1103
877
|
if (callback) {
|
|
1104
878
|
await callback({
|
|
1105
|
-
text: `Created issue #${issue.number}: ${issue.title}
|
|
1106
|
-
${issue.url}`
|
|
879
|
+
text: `Created issue #${issue.number}: ${issue.title}\n${issue.url}`
|
|
1107
880
|
});
|
|
1108
881
|
}
|
|
1109
882
|
return { success: true, data: { issue } };
|
|
@@ -1121,10 +894,8 @@ ${issue.url}`
|
|
|
1121
894
|
text: `No ${stateFilter} issues found in ${repo}.`
|
|
1122
895
|
});
|
|
1123
896
|
} 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}` });
|
|
897
|
+
const summary = issues.map((i) => `#${i.number} [${i.state}] ${i.title}${i.labels.length > 0 ? ` (${i.labels.join(", ")})` : ""}`).join("\n");
|
|
898
|
+
await callback({ text: `Issues in ${repo}:\n${summary}` });
|
|
1128
899
|
}
|
|
1129
900
|
}
|
|
1130
901
|
return { success: true, data: { issues } };
|
|
@@ -1139,12 +910,7 @@ ${summary}` });
|
|
|
1139
910
|
const issue = await service.getIssue(repo, issueNumber);
|
|
1140
911
|
if (callback) {
|
|
1141
912
|
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}`
|
|
913
|
+
text: `Issue #${issue.number}: ${issue.title} [${issue.state}]\n\n${issue.body}\n\nLabels: ${issue.labels.join(", ") || "none"}\n${issue.url}`
|
|
1148
914
|
});
|
|
1149
915
|
}
|
|
1150
916
|
return { success: true, data: { issue } };
|
|
@@ -1313,6 +1079,145 @@ function parseLabels(input) {
|
|
|
1313
1079
|
return input.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1314
1080
|
return [];
|
|
1315
1081
|
}
|
|
1082
|
+
var manageIssuesAction = {
|
|
1083
|
+
name: "MANAGE_ISSUES",
|
|
1084
|
+
similes: [
|
|
1085
|
+
"CREATE_ISSUE",
|
|
1086
|
+
"LIST_ISSUES",
|
|
1087
|
+
"CLOSE_ISSUE",
|
|
1088
|
+
"COMMENT_ISSUE",
|
|
1089
|
+
"UPDATE_ISSUE",
|
|
1090
|
+
"GET_ISSUE"
|
|
1091
|
+
],
|
|
1092
|
+
description: "Manage GitHub issues for a repository. " + "Supports creating issues, listing issues, getting issue details, " + "adding comments, updating, closing, and reopening issues.",
|
|
1093
|
+
examples: [
|
|
1094
|
+
[
|
|
1095
|
+
{
|
|
1096
|
+
name: "{{user1}}",
|
|
1097
|
+
content: {
|
|
1098
|
+
text: "Create an issue on the testbed repo to add a login page"
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: "{{agentName}}",
|
|
1103
|
+
content: {
|
|
1104
|
+
text: "I'll create that issue for you.",
|
|
1105
|
+
action: "MANAGE_ISSUES"
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
],
|
|
1109
|
+
[
|
|
1110
|
+
{
|
|
1111
|
+
name: "{{user1}}",
|
|
1112
|
+
content: {
|
|
1113
|
+
text: "List the open issues on HaruHunab1320/git-workspace-service-testbed"
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
{
|
|
1117
|
+
name: "{{agentName}}",
|
|
1118
|
+
content: {
|
|
1119
|
+
text: "Let me check the open issues for that repo.",
|
|
1120
|
+
action: "MANAGE_ISSUES"
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
],
|
|
1124
|
+
[
|
|
1125
|
+
{
|
|
1126
|
+
name: "{{user1}}",
|
|
1127
|
+
content: { text: "Close issue #3 on the testbed repo" }
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "{{agentName}}",
|
|
1131
|
+
content: {
|
|
1132
|
+
text: "I'll close that issue.",
|
|
1133
|
+
action: "MANAGE_ISSUES"
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
]
|
|
1137
|
+
],
|
|
1138
|
+
validate: async (runtime, _message) => {
|
|
1139
|
+
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
1140
|
+
return workspaceService != null;
|
|
1141
|
+
},
|
|
1142
|
+
handler: async (runtime, message, _state, options, callback) => {
|
|
1143
|
+
const workspaceService = runtime.getService("CODING_WORKSPACE_SERVICE");
|
|
1144
|
+
if (!workspaceService) {
|
|
1145
|
+
if (callback) {
|
|
1146
|
+
await callback({ text: "Workspace Service is not available." });
|
|
1147
|
+
}
|
|
1148
|
+
return { success: false, error: "SERVICE_UNAVAILABLE" };
|
|
1149
|
+
}
|
|
1150
|
+
workspaceService.setAuthPromptCallback((prompt) => {
|
|
1151
|
+
if (callback) {
|
|
1152
|
+
callback({
|
|
1153
|
+
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...`
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
const params = options?.parameters;
|
|
1158
|
+
const content = message.content;
|
|
1159
|
+
const text = content.text ?? "";
|
|
1160
|
+
const operation = params?.operation ?? content.operation ?? inferOperation(text);
|
|
1161
|
+
const repo = params?.repo ?? content.repo;
|
|
1162
|
+
if (!repo) {
|
|
1163
|
+
const urlMatch = text?.match(/(?:https?:\/\/github\.com\/)?([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
|
|
1164
|
+
if (!urlMatch) {
|
|
1165
|
+
if (callback) {
|
|
1166
|
+
await callback({
|
|
1167
|
+
text: "Please specify a repository (e.g., owner/repo or a GitHub URL)."
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
return { success: false, error: "MISSING_REPO" };
|
|
1171
|
+
}
|
|
1172
|
+
return handleOperation(workspaceService, urlMatch[1], operation, params ?? content, text, callback);
|
|
1173
|
+
}
|
|
1174
|
+
return handleOperation(workspaceService, repo, operation, params ?? content, text, callback);
|
|
1175
|
+
},
|
|
1176
|
+
parameters: [
|
|
1177
|
+
{
|
|
1178
|
+
name: "operation",
|
|
1179
|
+
description: "The operation to perform: create, list, get, update, comment, close, reopen, add_labels",
|
|
1180
|
+
required: true,
|
|
1181
|
+
schema: { type: "string" }
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
name: "repo",
|
|
1185
|
+
description: "Repository in owner/repo format or full GitHub URL.",
|
|
1186
|
+
required: true,
|
|
1187
|
+
schema: { type: "string" }
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
name: "title",
|
|
1191
|
+
description: "Issue title (for create operation).",
|
|
1192
|
+
required: false,
|
|
1193
|
+
schema: { type: "string" }
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
name: "body",
|
|
1197
|
+
description: "Issue body/description (for create or comment operations).",
|
|
1198
|
+
required: false,
|
|
1199
|
+
schema: { type: "string" }
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
name: "issueNumber",
|
|
1203
|
+
description: "Issue number (for get, update, comment, close, reopen operations).",
|
|
1204
|
+
required: false,
|
|
1205
|
+
schema: { type: "number" }
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
name: "labels",
|
|
1209
|
+
description: "Labels to add (comma-separated string or array).",
|
|
1210
|
+
required: false,
|
|
1211
|
+
schema: { type: "string" }
|
|
1212
|
+
},
|
|
1213
|
+
{
|
|
1214
|
+
name: "state",
|
|
1215
|
+
description: "Filter by state: open, closed, or all (for list operation).",
|
|
1216
|
+
required: false,
|
|
1217
|
+
schema: { type: "string" }
|
|
1218
|
+
}
|
|
1219
|
+
]
|
|
1220
|
+
};
|
|
1316
1221
|
|
|
1317
1222
|
// src/actions/provision-workspace.ts
|
|
1318
1223
|
var provisionWorkspaceAction = {
|
|
@@ -1425,9 +1330,7 @@ var provisionWorkspaceAction = {
|
|
|
1425
1330
|
}
|
|
1426
1331
|
if (callback) {
|
|
1427
1332
|
await callback({
|
|
1428
|
-
text: `Created workspace at ${workspace.path}
|
|
1429
|
-
` + `Branch: ${workspace.branch}
|
|
1430
|
-
` + `Type: ${workspace.isWorktree ? "worktree" : "clone"}`
|
|
1333
|
+
text: `Created workspace at ${workspace.path}\n` + `Branch: ${workspace.branch}\n` + `Type: ${workspace.isWorktree ? "worktree" : "clone"}`
|
|
1431
1334
|
});
|
|
1432
1335
|
}
|
|
1433
1336
|
return {
|
|
@@ -1634,19 +1537,21 @@ var sendToAgentAction = {
|
|
|
1634
1537
|
|
|
1635
1538
|
// src/actions/spawn-agent.ts
|
|
1636
1539
|
import * as os from "node:os";
|
|
1637
|
-
import * as
|
|
1540
|
+
import * as path2 from "node:path";
|
|
1638
1541
|
import {
|
|
1639
|
-
|
|
1542
|
+
logger as logger3
|
|
1640
1543
|
} from "@elizaos/core";
|
|
1641
1544
|
|
|
1642
1545
|
// src/services/pty-service.ts
|
|
1643
|
-
import {
|
|
1546
|
+
import {mkdir, readFile, writeFile} from "node:fs/promises";
|
|
1547
|
+
import {dirname, join} from "node:path";
|
|
1548
|
+
import {logger as logger2} from "@elizaos/core";
|
|
1644
1549
|
import {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1550
|
+
checkAdapters,
|
|
1551
|
+
createAdapter,
|
|
1552
|
+
generateApprovalConfig
|
|
1648
1553
|
} from "coding-agent-adapters";
|
|
1649
|
-
import {
|
|
1554
|
+
import {PTYConsoleBridge} from "pty-console";
|
|
1650
1555
|
|
|
1651
1556
|
// src/services/agent-metrics.ts
|
|
1652
1557
|
class AgentMetricsTracker {
|
|
@@ -1702,7 +1607,6 @@ function computeAgentScore(metrics) {
|
|
|
1702
1607
|
const speedPenalty = Math.min(avgCompletionMs / 300000, 1) * 0.1;
|
|
1703
1608
|
return Math.max(0, successRate - stallPenalty - speedPenalty);
|
|
1704
1609
|
}
|
|
1705
|
-
var DEFAULT_ORDER = ["claude", "gemini", "codex", "aider"];
|
|
1706
1610
|
function selectAgentType(ctx) {
|
|
1707
1611
|
if (ctx.config.strategy === "fixed") {
|
|
1708
1612
|
return ctx.config.fixedAgentType;
|
|
@@ -1724,6 +1628,7 @@ function selectAgentType(ctx) {
|
|
|
1724
1628
|
}
|
|
1725
1629
|
return bestAgent;
|
|
1726
1630
|
}
|
|
1631
|
+
var DEFAULT_ORDER = ["claude", "gemini", "codex", "aider"];
|
|
1727
1632
|
|
|
1728
1633
|
// src/services/pty-auto-response.ts
|
|
1729
1634
|
async function pushDefaultRules(ctx, sessionId, agentType) {
|
|
@@ -1801,19 +1706,14 @@ async function handleGeminiAuth(ctx, sessionId, sendKeysToSession) {
|
|
|
1801
1706
|
|
|
1802
1707
|
// src/services/pty-init.ts
|
|
1803
1708
|
init_ansi_utils();
|
|
1804
|
-
import {
|
|
1805
|
-
import {
|
|
1709
|
+
import {createRequire as createRequire2} from "node:module";
|
|
1710
|
+
import {createAllAdapters} from "coding-agent-adapters";
|
|
1806
1711
|
import {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1712
|
+
BunCompatiblePTYManager,
|
|
1713
|
+
isBun,
|
|
1714
|
+
PTYManager,
|
|
1715
|
+
ShellAdapter
|
|
1811
1716
|
} 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
1717
|
async function initializePTYManager(ctx) {
|
|
1818
1718
|
const usingBunWorker = isBun();
|
|
1819
1719
|
if (usingBunWorker) {
|
|
@@ -1856,7 +1756,7 @@ async function initializePTYManager(ctx) {
|
|
|
1856
1756
|
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
1857
1757
|
});
|
|
1858
1758
|
bunManager.on("tool_running", (session, info) => {
|
|
1859
|
-
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? `
|
|
1759
|
+
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? ` \u2014 ${info.description}` : ""}`);
|
|
1860
1760
|
ctx.emitEvent(session.id, "tool_running", { session, ...info });
|
|
1861
1761
|
});
|
|
1862
1762
|
bunManager.on("message", (message) => {
|
|
@@ -1926,7 +1826,7 @@ async function initializePTYManager(ctx) {
|
|
|
1926
1826
|
ctx.emitEvent(session.id, "task_complete", { session, response });
|
|
1927
1827
|
});
|
|
1928
1828
|
nodeManager.on("tool_running", (session, info) => {
|
|
1929
|
-
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? `
|
|
1829
|
+
ctx.log(`tool_running for ${session.id}: ${info.toolName}${info.description ? ` \u2014 ${info.description}` : ""}`);
|
|
1930
1830
|
ctx.emitEvent(session.id, "tool_running", { session, ...info });
|
|
1931
1831
|
});
|
|
1932
1832
|
nodeManager.on("session_stopped", (session, reason) => {
|
|
@@ -1940,6 +1840,12 @@ async function initializePTYManager(ctx) {
|
|
|
1940
1840
|
});
|
|
1941
1841
|
return { manager: nodeManager, usingBunWorker: false };
|
|
1942
1842
|
}
|
|
1843
|
+
var _require = createRequire2(import.meta.url);
|
|
1844
|
+
var resolvedAdapterModule = "coding-agent-adapters";
|
|
1845
|
+
try {
|
|
1846
|
+
resolvedAdapterModule = _require.resolve("coding-agent-adapters");
|
|
1847
|
+
} catch {
|
|
1848
|
+
}
|
|
1943
1849
|
|
|
1944
1850
|
// src/services/pty-session-io.ts
|
|
1945
1851
|
async function sendToSession(ctx, sessionId, input) {
|
|
@@ -2011,8 +1917,7 @@ async function getSessionOutput(ctx, sessionId, lines) {
|
|
|
2011
1917
|
if (!buffer)
|
|
2012
1918
|
return "";
|
|
2013
1919
|
const tail = lines ?? buffer.length;
|
|
2014
|
-
return buffer.slice(-tail).join(
|
|
2015
|
-
`);
|
|
1920
|
+
return buffer.slice(-tail).join("\n");
|
|
2016
1921
|
}
|
|
2017
1922
|
const output = [];
|
|
2018
1923
|
for await (const line of ctx.manager.logs(sessionId, {
|
|
@@ -2020,17 +1925,24 @@ async function getSessionOutput(ctx, sessionId, lines) {
|
|
|
2020
1925
|
})) {
|
|
2021
1926
|
output.push(line);
|
|
2022
1927
|
}
|
|
2023
|
-
return output.join(
|
|
2024
|
-
`);
|
|
1928
|
+
return output.join("\n");
|
|
2025
1929
|
}
|
|
2026
1930
|
|
|
2027
1931
|
// src/services/pty-spawn.ts
|
|
1932
|
+
function buildSanitizedBaseEnv() {
|
|
1933
|
+
const env = {};
|
|
1934
|
+
for (const key of ENV_ALLOWLIST) {
|
|
1935
|
+
const val = process.env[key];
|
|
1936
|
+
if (val)
|
|
1937
|
+
env[key] = val;
|
|
1938
|
+
}
|
|
1939
|
+
return env;
|
|
1940
|
+
}
|
|
2028
1941
|
function setupOutputBuffer(ctx, sessionId) {
|
|
2029
1942
|
const buffer = [];
|
|
2030
1943
|
ctx.sessionOutputBuffers.set(sessionId, buffer);
|
|
2031
1944
|
const unsubscribe = ctx.manager.onSessionData(sessionId, (data) => {
|
|
2032
|
-
const lines = data.split(
|
|
2033
|
-
`);
|
|
1945
|
+
const lines = data.split("\n");
|
|
2034
1946
|
buffer.push(...lines);
|
|
2035
1947
|
while (buffer.length > (ctx.serviceConfig.maxLogLines ?? 1000)) {
|
|
2036
1948
|
buffer.shift();
|
|
@@ -2053,17 +1965,17 @@ function setupDeferredTaskDelivery(ctx, session, task, agentType) {
|
|
|
2053
1965
|
const sendTaskWithRetry = (attempt) => {
|
|
2054
1966
|
const buffer = ctx.sessionOutputBuffers.get(sid);
|
|
2055
1967
|
const baselineLength = buffer?.length ?? 0;
|
|
2056
|
-
ctx.log(`Session ${sid}
|
|
1968
|
+
ctx.log(`Session ${sid} \u2014 sending task (attempt ${attempt + 1}, ${settleMs}ms settle, baseline ${baselineLength} lines)`);
|
|
2057
1969
|
ctx.sendToSession(sid, task).catch((err) => ctx.log(`Failed to send deferred task to ${sid}: ${err}`));
|
|
2058
1970
|
if (attempt < MAX_RETRIES) {
|
|
2059
1971
|
setTimeout(() => {
|
|
2060
1972
|
const currentLength = buffer?.length ?? 0;
|
|
2061
1973
|
const newLines = currentLength - baselineLength;
|
|
2062
1974
|
if (newLines < MIN_NEW_LINES) {
|
|
2063
|
-
ctx.log(`Session ${sid}
|
|
1975
|
+
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
1976
|
sendTaskWithRetry(attempt + 1);
|
|
2065
1977
|
} else {
|
|
2066
|
-
ctx.log(`Session ${sid}
|
|
1978
|
+
ctx.log(`Session ${sid} \u2014 task accepted (${newLines} new lines after ${VERIFY_DELAY_MS}ms)`);
|
|
2067
1979
|
}
|
|
2068
1980
|
}, VERIFY_DELAY_MS);
|
|
2069
1981
|
}
|
|
@@ -2114,7 +2026,8 @@ function buildSpawnConfig(sessionId, options, workdir) {
|
|
|
2114
2026
|
name: options.name,
|
|
2115
2027
|
type: options.agentType,
|
|
2116
2028
|
workdir,
|
|
2117
|
-
|
|
2029
|
+
inheritProcessEnv: false,
|
|
2030
|
+
env: { ...buildSanitizedBaseEnv(), ...options.env, ...modelEnv },
|
|
2118
2031
|
...options.skipAdapterAutoResponse ? { skipAdapterAutoResponse: true } : {},
|
|
2119
2032
|
adapterConfig: {
|
|
2120
2033
|
...options.credentials,
|
|
@@ -2126,6 +2039,21 @@ function buildSpawnConfig(sessionId, options, workdir) {
|
|
|
2126
2039
|
}
|
|
2127
2040
|
};
|
|
2128
2041
|
}
|
|
2042
|
+
var ENV_ALLOWLIST = [
|
|
2043
|
+
"PATH",
|
|
2044
|
+
"HOME",
|
|
2045
|
+
"USER",
|
|
2046
|
+
"SHELL",
|
|
2047
|
+
"LANG",
|
|
2048
|
+
"LC_ALL",
|
|
2049
|
+
"LC_CTYPE",
|
|
2050
|
+
"TERM",
|
|
2051
|
+
"TZ",
|
|
2052
|
+
"TMPDIR",
|
|
2053
|
+
"XDG_RUNTIME_DIR",
|
|
2054
|
+
"NODE_OPTIONS",
|
|
2055
|
+
"BUN_INSTALL"
|
|
2056
|
+
];
|
|
2129
2057
|
|
|
2130
2058
|
// src/services/pty-types.ts
|
|
2131
2059
|
var PI_AGENT_ALIASES = new Set([
|
|
@@ -2170,39 +2098,17 @@ var toPiCommand = (task) => {
|
|
|
2170
2098
|
|
|
2171
2099
|
// src/services/stall-classifier.ts
|
|
2172
2100
|
init_ansi_utils();
|
|
2173
|
-
import {
|
|
2101
|
+
import {ModelType} from "@elizaos/core";
|
|
2174
2102
|
import {
|
|
2175
|
-
|
|
2176
|
-
|
|
2103
|
+
buildTaskCompletionTimeline,
|
|
2104
|
+
extractTaskCompletionTraceRecords
|
|
2177
2105
|
} from "pty-manager";
|
|
2178
2106
|
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.
|
|
2107
|
+
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
2108
|
|
|
2194
|
-
` + `4. "error"
|
|
2109
|
+
` + `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
2110
|
|
|
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": "..."}`;
|
|
2111
|
+
` + `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
2112
|
}
|
|
2207
2113
|
async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveOutput, buffers, traceEntries, log) {
|
|
2208
2114
|
try {
|
|
@@ -2212,8 +2118,7 @@ async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveO
|
|
|
2212
2118
|
const snapshotDir = path.join(os.homedir(), ".milady", "debug");
|
|
2213
2119
|
fs.mkdirSync(snapshotDir, { recursive: true });
|
|
2214
2120
|
const ourBuffer = buffers.get(sessionId);
|
|
2215
|
-
const ourTail = ourBuffer ? ourBuffer.slice(-100).join(
|
|
2216
|
-
`) : "(no buffer)";
|
|
2121
|
+
const ourTail = ourBuffer ? ourBuffer.slice(-100).join("\n") : "(no buffer)";
|
|
2217
2122
|
let traceTimeline = "(no trace entries)";
|
|
2218
2123
|
try {
|
|
2219
2124
|
const records = extractTaskCompletionTraceRecords(traceEntries);
|
|
@@ -2236,15 +2141,14 @@ async function writeStallSnapshot(sessionId, agentType, recentOutput, effectiveO
|
|
|
2236
2141
|
traceTimeline,
|
|
2237
2142
|
``,
|
|
2238
2143
|
`--- raw trace entries (last 20 of ${traceEntries.length}) ---`,
|
|
2239
|
-
traceEntries.slice(-20).join(
|
|
2240
|
-
`),
|
|
2144
|
+
traceEntries.slice(-20).join("\n"),
|
|
2241
2145
|
``
|
|
2242
|
-
].join(
|
|
2243
|
-
`);
|
|
2146
|
+
].join("\n");
|
|
2244
2147
|
const snapshotPath = path.join(snapshotDir, `stall-snapshot-${sessionId}.txt`);
|
|
2245
2148
|
fs.writeFileSync(snapshotPath, snapshot);
|
|
2246
|
-
log(`Stall snapshot
|
|
2247
|
-
} catch (_) {
|
|
2149
|
+
log(`Stall snapshot \u2192 ${snapshotPath}`);
|
|
2150
|
+
} catch (_) {
|
|
2151
|
+
}
|
|
2248
2152
|
}
|
|
2249
2153
|
async function classifyStallOutput(ctx) {
|
|
2250
2154
|
const {
|
|
@@ -2263,8 +2167,7 @@ async function classifyStallOutput(ctx) {
|
|
|
2263
2167
|
if (!recentOutput || recentOutput.trim().length < 200) {
|
|
2264
2168
|
const ourBuffer = buffers.get(sessionId);
|
|
2265
2169
|
if (ourBuffer && ourBuffer.length > 0) {
|
|
2266
|
-
const rawTail = ourBuffer.slice(-100).join(
|
|
2267
|
-
`);
|
|
2170
|
+
const rawTail = ourBuffer.slice(-100).join("\n");
|
|
2268
2171
|
const stripped = stripAnsi(rawTail);
|
|
2269
2172
|
if (stripped.length > effectiveOutput.length) {
|
|
2270
2173
|
effectiveOutput = stripped;
|
|
@@ -2304,7 +2207,7 @@ async function classifyStallOutput(ctx) {
|
|
|
2304
2207
|
prompt: parsed.prompt,
|
|
2305
2208
|
suggestedResponse: parsed.suggestedResponse
|
|
2306
2209
|
};
|
|
2307
|
-
log(`Stall classification for ${sessionId}: ${classification.state}${classification.suggestedResponse ? `
|
|
2210
|
+
log(`Stall classification for ${sessionId}: ${classification.state}${classification.suggestedResponse ? ` \u2192 "${classification.suggestedResponse}"` : ""}`);
|
|
2308
2211
|
if (classification.state === "task_complete") {
|
|
2309
2212
|
const session = manager?.get(sessionId);
|
|
2310
2213
|
const durationMs = session?.startedAt ? Date.now() - new Date(session.startedAt).getTime() : 0;
|
|
@@ -2320,14 +2223,12 @@ async function classifyStallOutput(ctx) {
|
|
|
2320
2223
|
// src/services/swarm-coordinator.ts
|
|
2321
2224
|
init_ansi_utils();
|
|
2322
2225
|
init_swarm_decision_loop();
|
|
2323
|
-
import {
|
|
2226
|
+
import {logger} from "@elizaos/core";
|
|
2324
2227
|
|
|
2325
2228
|
// src/services/swarm-idle-watchdog.ts
|
|
2326
2229
|
init_ansi_utils();
|
|
2327
2230
|
init_swarm_decision_loop();
|
|
2328
|
-
import {
|
|
2329
|
-
var IDLE_THRESHOLD_MS = 3 * 60 * 1000;
|
|
2330
|
-
var MAX_IDLE_CHECKS = 3;
|
|
2231
|
+
import {ModelType as ModelType3} from "@elizaos/core";
|
|
2331
2232
|
async function scanIdleSessions(ctx) {
|
|
2332
2233
|
const now = Date.now();
|
|
2333
2234
|
for (const taskCtx of ctx.tasks.values()) {
|
|
@@ -2346,10 +2247,11 @@ async function scanIdleSessions(ctx) {
|
|
|
2346
2247
|
if (currentOutput !== lastSeen) {
|
|
2347
2248
|
taskCtx.lastActivityAt = now;
|
|
2348
2249
|
taskCtx.idleCheckCount = 0;
|
|
2349
|
-
ctx.log(`Idle watchdog: "${taskCtx.label}" has fresh PTY output
|
|
2250
|
+
ctx.log(`Idle watchdog: "${taskCtx.label}" has fresh PTY output \u2014 not idle`);
|
|
2350
2251
|
continue;
|
|
2351
2252
|
}
|
|
2352
|
-
} catch {
|
|
2253
|
+
} catch {
|
|
2254
|
+
}
|
|
2353
2255
|
}
|
|
2354
2256
|
taskCtx.idleCheckCount++;
|
|
2355
2257
|
const idleMinutes = Math.round(idleMs / 60000);
|
|
@@ -2417,8 +2319,8 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
2417
2319
|
ctx.log(`Idle check LLM call failed: ${err}`);
|
|
2418
2320
|
}
|
|
2419
2321
|
if (!decision) {
|
|
2420
|
-
ctx.log(`Idle check for "${taskCtx.label}": LLM returned invalid response
|
|
2421
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Session idle for ${idleMinutes}m
|
|
2322
|
+
ctx.log(`Idle check for "${taskCtx.label}": LLM returned invalid response \u2014 escalating`);
|
|
2323
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Session idle for ${idleMinutes}m \u2014 couldn't determine status. Needs your attention.`, "coding-agent");
|
|
2422
2324
|
return;
|
|
2423
2325
|
}
|
|
2424
2326
|
taskCtx.decisions.push({
|
|
@@ -2440,19 +2342,22 @@ async function handleIdleCheck(ctx, taskCtx, idleMinutes) {
|
|
|
2440
2342
|
reasoning: decision.reasoning
|
|
2441
2343
|
}
|
|
2442
2344
|
});
|
|
2443
|
-
if (decision.action === "complete") {
|
|
2345
|
+
if (decision.action === "complete") {
|
|
2346
|
+
} else if (decision.action === "respond") {
|
|
2444
2347
|
const actionDesc = decision.useKeys ? `Sent keys: ${decision.keys?.join(", ")}` : `Nudged: ${decision.response ?? ""}`;
|
|
2445
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m
|
|
2348
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m \u2014 ${actionDesc}`, "coding-agent");
|
|
2446
2349
|
} else if (decision.action === "escalate") {
|
|
2447
|
-
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m
|
|
2350
|
+
ctx.sendChatMessage(`[${taskCtx.label}] Idle for ${idleMinutes}m \u2014 needs your attention: ${decision.reasoning}`, "coding-agent");
|
|
2448
2351
|
} else if (decision.action === "ignore") {
|
|
2449
|
-
ctx.log(`Idle check for "${taskCtx.label}": LLM says still working
|
|
2352
|
+
ctx.log(`Idle check for "${taskCtx.label}": LLM says still working \u2014 ${decision.reasoning}`);
|
|
2450
2353
|
}
|
|
2451
2354
|
await executeDecision(ctx, sessionId, decision);
|
|
2452
2355
|
} finally {
|
|
2453
2356
|
ctx.inFlightDecisions.delete(sessionId);
|
|
2454
2357
|
}
|
|
2455
2358
|
}
|
|
2359
|
+
var IDLE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
2360
|
+
var MAX_IDLE_CHECKS = 4;
|
|
2456
2361
|
|
|
2457
2362
|
// src/services/swarm-coordinator.ts
|
|
2458
2363
|
var UNREGISTERED_BUFFER_MS = 2000;
|
|
@@ -2536,6 +2441,7 @@ class SwarmCoordinator {
|
|
|
2536
2441
|
label: context.label,
|
|
2537
2442
|
originalTask: context.originalTask,
|
|
2538
2443
|
workdir: context.workdir,
|
|
2444
|
+
repo: context.repo,
|
|
2539
2445
|
status: "active",
|
|
2540
2446
|
decisions: [],
|
|
2541
2447
|
autoResolvedCount: 0,
|
|
@@ -2563,6 +2469,15 @@ class SwarmCoordinator {
|
|
|
2563
2469
|
}
|
|
2564
2470
|
}
|
|
2565
2471
|
}
|
|
2472
|
+
getLastUsedRepo() {
|
|
2473
|
+
let latest;
|
|
2474
|
+
for (const task of this.tasks.values()) {
|
|
2475
|
+
if (task.repo && (!latest || task.registeredAt > latest.registeredAt)) {
|
|
2476
|
+
latest = task;
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
return latest?.repo;
|
|
2480
|
+
}
|
|
2566
2481
|
getTaskContext(sessionId) {
|
|
2567
2482
|
return this.tasks.get(sessionId);
|
|
2568
2483
|
}
|
|
@@ -2604,10 +2519,9 @@ class SwarmCoordinator {
|
|
|
2604
2519
|
}
|
|
2605
2520
|
writeSseEvent(res, event) {
|
|
2606
2521
|
try {
|
|
2607
|
-
res.write(`data: ${JSON.stringify(event)}
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
} catch {}
|
|
2522
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
2523
|
+
} catch {
|
|
2524
|
+
}
|
|
2611
2525
|
}
|
|
2612
2526
|
async handleSessionEvent(sessionId, event, data) {
|
|
2613
2527
|
const taskCtx = this.tasks.get(sessionId);
|
|
@@ -2626,7 +2540,8 @@ class SwarmCoordinator {
|
|
|
2626
2540
|
if (ctx) {
|
|
2627
2541
|
this.unregisteredBuffer.delete(sessionId);
|
|
2628
2542
|
for (const entry of stillBuffered) {
|
|
2629
|
-
this.handleSessionEvent(sessionId, entry.event, entry.data).catch(() => {
|
|
2543
|
+
this.handleSessionEvent(sessionId, entry.event, entry.data).catch(() => {
|
|
2544
|
+
});
|
|
2630
2545
|
}
|
|
2631
2546
|
} else {
|
|
2632
2547
|
this.unregisteredBuffer.delete(sessionId);
|
|
@@ -2637,6 +2552,12 @@ class SwarmCoordinator {
|
|
|
2637
2552
|
}
|
|
2638
2553
|
return;
|
|
2639
2554
|
}
|
|
2555
|
+
if (taskCtx.status === "stopped" || taskCtx.status === "error" || taskCtx.status === "completed") {
|
|
2556
|
+
if (event !== "stopped" && event !== "error") {
|
|
2557
|
+
this.log(`Ignoring "${event}" for ${taskCtx.label} (status: ${taskCtx.status})`);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2640
2561
|
taskCtx.lastActivityAt = Date.now();
|
|
2641
2562
|
taskCtx.idleCheckCount = 0;
|
|
2642
2563
|
switch (event) {
|
|
@@ -2667,6 +2588,7 @@ class SwarmCoordinator {
|
|
|
2667
2588
|
}
|
|
2668
2589
|
case "stopped":
|
|
2669
2590
|
taskCtx.status = "stopped";
|
|
2591
|
+
this.inFlightDecisions.delete(sessionId);
|
|
2670
2592
|
this.broadcast({
|
|
2671
2593
|
type: "stopped",
|
|
2672
2594
|
sessionId,
|
|
@@ -2705,9 +2627,10 @@ class SwarmCoordinator {
|
|
|
2705
2627
|
if (devUrl) {
|
|
2706
2628
|
urlSuffix = ` Dev server running at ${devUrl}`;
|
|
2707
2629
|
}
|
|
2708
|
-
} catch {
|
|
2630
|
+
} catch {
|
|
2631
|
+
}
|
|
2709
2632
|
}
|
|
2710
|
-
this.sendChatMessage(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal
|
|
2633
|
+
this.sendChatMessage(`[${taskCtx.label}] Running ${toolDesc}.${urlSuffix} The agent is working outside the terminal \u2014 I'll let it finish.`, "coding-agent");
|
|
2711
2634
|
}
|
|
2712
2635
|
break;
|
|
2713
2636
|
}
|
|
@@ -2847,6 +2770,7 @@ class PTYService {
|
|
|
2847
2770
|
const coordinator = new SwarmCoordinator(runtime);
|
|
2848
2771
|
coordinator.start(service);
|
|
2849
2772
|
service.coordinator = coordinator;
|
|
2773
|
+
runtime.services.set("SWARM_COORDINATOR", [coordinator]);
|
|
2850
2774
|
logger2.info("[PTYService] SwarmCoordinator wired and started");
|
|
2851
2775
|
} catch (err) {
|
|
2852
2776
|
logger2.error(`[PTYService] Failed to wire SwarmCoordinator: ${err}`);
|
|
@@ -2887,6 +2811,7 @@ class PTYService {
|
|
|
2887
2811
|
async stop() {
|
|
2888
2812
|
if (this.coordinator) {
|
|
2889
2813
|
this.coordinator.stop();
|
|
2814
|
+
this.runtime.services.delete("SWARM_COORDINATOR");
|
|
2890
2815
|
this.coordinator = null;
|
|
2891
2816
|
}
|
|
2892
2817
|
if (this.consoleBridge) {
|
|
@@ -2954,6 +2879,24 @@ class PTYService {
|
|
|
2954
2879
|
this.log(`Failed to write approval config: ${err}`);
|
|
2955
2880
|
}
|
|
2956
2881
|
}
|
|
2882
|
+
if (resolvedAgentType === "claude") {
|
|
2883
|
+
try {
|
|
2884
|
+
const settingsPath = join(workdir, ".claude", "settings.json");
|
|
2885
|
+
let settings = {};
|
|
2886
|
+
try {
|
|
2887
|
+
settings = JSON.parse(await readFile(settingsPath, "utf-8"));
|
|
2888
|
+
} catch {
|
|
2889
|
+
}
|
|
2890
|
+
const permissions = settings.permissions ?? {};
|
|
2891
|
+
permissions.allowedDirectories = [workdir];
|
|
2892
|
+
settings.permissions = permissions;
|
|
2893
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
2894
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
2895
|
+
this.log(`Wrote allowedDirectories [${workdir}] to ${settingsPath}`);
|
|
2896
|
+
} catch (err) {
|
|
2897
|
+
this.log(`Failed to write allowedDirectories: ${err}`);
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2957
2900
|
const spawnConfig = buildSpawnConfig(sessionId, {
|
|
2958
2901
|
...options,
|
|
2959
2902
|
agentType: resolvedAgentType,
|
|
@@ -2963,7 +2906,8 @@ class PTYService {
|
|
|
2963
2906
|
this.sessionMetadata.set(session.id, {
|
|
2964
2907
|
...options.metadata,
|
|
2965
2908
|
requestedType: options.metadata?.requestedType ?? options.agentType,
|
|
2966
|
-
agentType: resolvedAgentType
|
|
2909
|
+
agentType: resolvedAgentType,
|
|
2910
|
+
coordinatorManaged: !!options.skipAdapterAutoResponse
|
|
2967
2911
|
});
|
|
2968
2912
|
const ctx = {
|
|
2969
2913
|
manager: this.manager,
|
|
@@ -3096,7 +3040,7 @@ class PTYService {
|
|
|
3096
3040
|
async classifyStall(sessionId, recentOutput) {
|
|
3097
3041
|
const meta = this.sessionMetadata.get(sessionId);
|
|
3098
3042
|
const agentType = meta?.agentType ?? "unknown";
|
|
3099
|
-
|
|
3043
|
+
const classification = await classifyStallOutput({
|
|
3100
3044
|
sessionId,
|
|
3101
3045
|
recentOutput,
|
|
3102
3046
|
agentType,
|
|
@@ -3108,6 +3052,11 @@ class PTYService {
|
|
|
3108
3052
|
debugSnapshots: this.serviceConfig.debug === true,
|
|
3109
3053
|
log: (msg) => this.log(msg)
|
|
3110
3054
|
});
|
|
3055
|
+
if (classification && meta?.coordinatorManaged && classification.suggestedResponse) {
|
|
3056
|
+
this.log(`Suppressing stall auto-response for coordinator-managed session ${sessionId} ` + `(would have sent: "${classification.suggestedResponse}")`);
|
|
3057
|
+
classification.suggestedResponse = undefined;
|
|
3058
|
+
}
|
|
3059
|
+
return classification;
|
|
3111
3060
|
}
|
|
3112
3061
|
getAdapter(agentType) {
|
|
3113
3062
|
let adapter = this.adapterCache.get(agentType);
|
|
@@ -3267,13 +3216,13 @@ var spawnAgentAction = {
|
|
|
3267
3216
|
}
|
|
3268
3217
|
return { success: false, error: "NO_WORKSPACE" };
|
|
3269
3218
|
}
|
|
3270
|
-
const resolvedWorkdir =
|
|
3271
|
-
const workspaceBaseDir =
|
|
3219
|
+
const resolvedWorkdir = path2.resolve(workdir);
|
|
3220
|
+
const workspaceBaseDir = path2.join(os.homedir(), ".milady", "workspaces");
|
|
3272
3221
|
const allowedPrefixes = [
|
|
3273
|
-
|
|
3274
|
-
|
|
3222
|
+
path2.resolve(workspaceBaseDir),
|
|
3223
|
+
path2.resolve(process.cwd())
|
|
3275
3224
|
];
|
|
3276
|
-
const isAllowed = allowedPrefixes.some((prefix) => resolvedWorkdir.startsWith(prefix +
|
|
3225
|
+
const isAllowed = allowedPrefixes.some((prefix) => resolvedWorkdir.startsWith(prefix + path2.sep) || resolvedWorkdir === prefix);
|
|
3277
3226
|
if (!isAllowed) {
|
|
3278
3227
|
if (callback) {
|
|
3279
3228
|
await callback({
|
|
@@ -3309,9 +3258,7 @@ var spawnAgentAction = {
|
|
|
3309
3258
|
if (preflight && !preflight.installed) {
|
|
3310
3259
|
if (callback) {
|
|
3311
3260
|
await callback({
|
|
3312
|
-
text: `${preflight.adapter} CLI is not installed.
|
|
3313
|
-
` + `Install with: ${preflight.installCommand}
|
|
3314
|
-
` + `Docs: ${preflight.docsUrl}`
|
|
3261
|
+
text: `${preflight.adapter} CLI is not installed.\n` + `Install with: ${preflight.installCommand}\n` + `Docs: ${preflight.docsUrl}`
|
|
3315
3262
|
});
|
|
3316
3263
|
}
|
|
3317
3264
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
@@ -3437,21 +3384,21 @@ var spawnAgentAction = {
|
|
|
3437
3384
|
|
|
3438
3385
|
// src/actions/coding-task-handlers.ts
|
|
3439
3386
|
import {
|
|
3440
|
-
|
|
3387
|
+
logger as logger5
|
|
3441
3388
|
} from "@elizaos/core";
|
|
3442
3389
|
|
|
3443
3390
|
// src/actions/coding-task-helpers.ts
|
|
3444
|
-
import {
|
|
3391
|
+
import {randomUUID} from "node:crypto";
|
|
3445
3392
|
import * as fs from "node:fs";
|
|
3446
3393
|
import * as os2 from "node:os";
|
|
3447
|
-
import * as
|
|
3394
|
+
import * as path3 from "node:path";
|
|
3448
3395
|
import {
|
|
3449
|
-
|
|
3396
|
+
logger as logger4
|
|
3450
3397
|
} from "@elizaos/core";
|
|
3451
3398
|
function createScratchDir() {
|
|
3452
|
-
const baseDir =
|
|
3399
|
+
const baseDir = path3.join(os2.homedir(), ".milady", "workspaces");
|
|
3453
3400
|
const scratchId = randomUUID();
|
|
3454
|
-
const scratchDir =
|
|
3401
|
+
const scratchDir = path3.join(baseDir, scratchId);
|
|
3455
3402
|
fs.mkdirSync(scratchDir, { recursive: true });
|
|
3456
3403
|
return scratchDir;
|
|
3457
3404
|
}
|
|
@@ -3485,9 +3432,7 @@ function registerSessionEvents(ptyService, runtime, sessionId, label, scratchDir
|
|
|
3485
3432
|
const response = data.response ?? "";
|
|
3486
3433
|
const preview = response.length > 500 ? `${response.slice(0, 500)}...` : response;
|
|
3487
3434
|
callback({
|
|
3488
|
-
text: preview ? `Agent "${label}" completed the task
|
|
3489
|
-
|
|
3490
|
-
${preview}` : `Agent "${label}" completed the task.`
|
|
3435
|
+
text: preview ? `Agent "${label}" completed the task.\n\n${preview}` : `Agent "${label}" completed the task.`
|
|
3491
3436
|
});
|
|
3492
3437
|
}
|
|
3493
3438
|
ptyService.stopSession(sessionId).catch((err) => {
|
|
@@ -3512,7 +3457,6 @@ ${preview}` : `Agent "${label}" completed the task.`
|
|
|
3512
3457
|
}
|
|
3513
3458
|
|
|
3514
3459
|
// src/actions/coding-task-handlers.ts
|
|
3515
|
-
var MAX_CONCURRENT_AGENTS = 8;
|
|
3516
3460
|
async function handleMultiAgent(ctx, agentsParam) {
|
|
3517
3461
|
const {
|
|
3518
3462
|
runtime,
|
|
@@ -3653,7 +3597,8 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
3653
3597
|
agentType: specAgentType,
|
|
3654
3598
|
label: specLabel,
|
|
3655
3599
|
originalTask: specTask,
|
|
3656
|
-
workdir
|
|
3600
|
+
workdir,
|
|
3601
|
+
repo
|
|
3657
3602
|
});
|
|
3658
3603
|
}
|
|
3659
3604
|
results.push({
|
|
@@ -3692,8 +3637,7 @@ async function handleMultiAgent(ctx, agentsParam) {
|
|
|
3692
3637
|
`Launched ${succeeded.length}/${agentSpecs.length} agents${repo ? ` on ${repo}` : ""}:`,
|
|
3693
3638
|
...succeeded.map((r) => ` - "${r.label}" (${r.agentType}) [session: ${r.sessionId}]`),
|
|
3694
3639
|
...failed.length > 0 ? [`Failed: ${failed.map((r) => `"${r.label}": ${r.error}`).join(", ")}`] : []
|
|
3695
|
-
].join(
|
|
3696
|
-
`);
|
|
3640
|
+
].join("\n");
|
|
3697
3641
|
if (callback) {
|
|
3698
3642
|
await callback({ text: summary });
|
|
3699
3643
|
}
|
|
@@ -3774,9 +3718,7 @@ async function handleSingleAgent(ctx, task) {
|
|
|
3774
3718
|
logger5.warn(`[START_CODING_TASK] ${preflight.adapter} CLI not installed`);
|
|
3775
3719
|
if (callback) {
|
|
3776
3720
|
await callback({
|
|
3777
|
-
text: `${preflight.adapter} CLI is not installed.
|
|
3778
|
-
Install with: ${preflight.installCommand}
|
|
3779
|
-
Docs: ${preflight.docsUrl}`
|
|
3721
|
+
text: `${preflight.adapter} CLI is not installed.\nInstall with: ${preflight.installCommand}\nDocs: ${preflight.docsUrl}`
|
|
3780
3722
|
});
|
|
3781
3723
|
}
|
|
3782
3724
|
return { success: false, error: "AGENT_NOT_INSTALLED" };
|
|
@@ -3815,7 +3757,8 @@ Docs: ${preflight.docsUrl}`
|
|
|
3815
3757
|
agentType,
|
|
3816
3758
|
label,
|
|
3817
3759
|
originalTask: task,
|
|
3818
|
-
workdir
|
|
3760
|
+
workdir,
|
|
3761
|
+
repo
|
|
3819
3762
|
});
|
|
3820
3763
|
}
|
|
3821
3764
|
if (state) {
|
|
@@ -3828,8 +3771,7 @@ Docs: ${preflight.docsUrl}`
|
|
|
3828
3771
|
}
|
|
3829
3772
|
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
3773
|
if (callback) {
|
|
3831
|
-
await callback({ text: `${summary}
|
|
3832
|
-
Session ID: ${session.id}` });
|
|
3774
|
+
await callback({ text: `${summary}\nSession ID: ${session.id}` });
|
|
3833
3775
|
}
|
|
3834
3776
|
return {
|
|
3835
3777
|
success: true,
|
|
@@ -3855,6 +3797,7 @@ Session ID: ${session.id}` });
|
|
|
3855
3797
|
return { success: false, error: errorMessage };
|
|
3856
3798
|
}
|
|
3857
3799
|
}
|
|
3800
|
+
var MAX_CONCURRENT_AGENTS = 8;
|
|
3858
3801
|
|
|
3859
3802
|
// src/actions/start-coding-task.ts
|
|
3860
3803
|
var startCodingTaskAction = {
|
|
@@ -3866,7 +3809,7 @@ var startCodingTaskAction = {
|
|
|
3866
3809
|
"SPAWN_AND_PROVISION",
|
|
3867
3810
|
"CODE_THIS"
|
|
3868
3811
|
],
|
|
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.",
|
|
3812
|
+
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
3813
|
examples: [
|
|
3871
3814
|
[
|
|
3872
3815
|
{
|
|
@@ -3928,6 +3871,13 @@ var startCodingTaskAction = {
|
|
|
3928
3871
|
repo = urlMatch[0];
|
|
3929
3872
|
}
|
|
3930
3873
|
}
|
|
3874
|
+
if (!repo) {
|
|
3875
|
+
const coordinator = getCoordinator(runtime);
|
|
3876
|
+
const lastRepo = coordinator?.getLastUsedRepo();
|
|
3877
|
+
if (lastRepo) {
|
|
3878
|
+
repo = lastRepo;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3931
3881
|
const customCredentialKeys = runtime.getSetting("CUSTOM_CREDENTIAL_KEYS");
|
|
3932
3882
|
let customCredentials;
|
|
3933
3883
|
if (customCredentialKeys) {
|
|
@@ -3971,7 +3921,7 @@ var startCodingTaskAction = {
|
|
|
3971
3921
|
parameters: [
|
|
3972
3922
|
{
|
|
3973
3923
|
name: "repo",
|
|
3974
|
-
description: "Git repository URL to clone (e.g. https://github.com/owner/repo). " + "
|
|
3924
|
+
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
3925
|
required: false,
|
|
3976
3926
|
schema: { type: "string" }
|
|
3977
3927
|
},
|
|
@@ -4019,7 +3969,7 @@ var startCodingTaskAction = {
|
|
|
4019
3969
|
|
|
4020
3970
|
// src/actions/stop-agent.ts
|
|
4021
3971
|
import {
|
|
4022
|
-
|
|
3972
|
+
logger as logger6
|
|
4023
3973
|
} from "@elizaos/core";
|
|
4024
3974
|
var stopAgentAction = {
|
|
4025
3975
|
name: "STOP_CODING_AGENT",
|
|
@@ -4182,6 +4132,15 @@ var stopAgentAction = {
|
|
|
4182
4132
|
};
|
|
4183
4133
|
|
|
4184
4134
|
// src/providers/action-examples.ts
|
|
4135
|
+
function formatExample(ex) {
|
|
4136
|
+
const actionTags = ex.actions.map((a) => ` <action>${a}</action>`).join("\n");
|
|
4137
|
+
const paramBlocks = Object.entries(ex.params ?? {}).map(([actionName, params]) => {
|
|
4138
|
+
const inner = Object.entries(params).map(([k, v]) => ` <${k}>${v}</${k}>`).join("\n");
|
|
4139
|
+
return ` <${actionName}>\n${inner}\n </${actionName}>`;
|
|
4140
|
+
}).join("\n");
|
|
4141
|
+
const paramsSection = paramBlocks ? `\n<params>\n${paramBlocks}\n</params>` : "";
|
|
4142
|
+
return `User: ${ex.user}\nAssistant:\n<actions>\n${actionTags}\n</actions>${paramsSection}`;
|
|
4143
|
+
}
|
|
4185
4144
|
var CODING_AGENT_EXAMPLES = [
|
|
4186
4145
|
{
|
|
4187
4146
|
user: "Can you set up a workspace for https://github.com/acme/my-app and have Claude fix the login bug?",
|
|
@@ -4190,7 +4149,7 @@ var CODING_AGENT_EXAMPLES = [
|
|
|
4190
4149
|
START_CODING_TASK: {
|
|
4191
4150
|
repo: "https://github.com/acme/my-app",
|
|
4192
4151
|
agentType: "claude",
|
|
4193
|
-
task: "Fix the login bug in src/auth.ts
|
|
4152
|
+
task: "Fix the login bug in src/auth.ts \u2014 users are getting 401 errors after token refresh"
|
|
4194
4153
|
}
|
|
4195
4154
|
}
|
|
4196
4155
|
},
|
|
@@ -4243,28 +4202,7 @@ var CODING_AGENT_EXAMPLES = [
|
|
|
4243
4202
|
}
|
|
4244
4203
|
}
|
|
4245
4204
|
];
|
|
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
|
|
4205
|
+
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
4206
|
Assistant:
|
|
4269
4207
|
<actions>
|
|
4270
4208
|
<action>REPLY</action>
|
|
@@ -4273,7 +4211,7 @@ Assistant:
|
|
|
4273
4211
|
<params>
|
|
4274
4212
|
<START_CODING_TASK>
|
|
4275
4213
|
<repo>https://github.com/acme/app</repo>
|
|
4276
|
-
<agents>Fix the authentication bug in src/auth.ts
|
|
4214
|
+
<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
4215
|
</START_CODING_TASK>
|
|
4278
4216
|
</params>`;
|
|
4279
4217
|
var codingAgentExamplesProvider = {
|
|
@@ -4281,14 +4219,12 @@ var codingAgentExamplesProvider = {
|
|
|
4281
4219
|
description: "Structured examples showing how to use coding agent actions with parameters",
|
|
4282
4220
|
position: -1,
|
|
4283
4221
|
get: async (_runtime, _message, _state) => {
|
|
4284
|
-
const examples = CODING_AGENT_EXAMPLES.map(formatExample).join(
|
|
4285
|
-
|
|
4286
|
-
`);
|
|
4222
|
+
const examples = CODING_AGENT_EXAMPLES.map(formatExample).join("\n\n");
|
|
4287
4223
|
const text = [
|
|
4288
4224
|
"# Coding Agent Action Call Examples",
|
|
4289
4225
|
"When the user asks you to work on code, clone repos, spawn agents, or run agent tasks,",
|
|
4290
4226
|
"you MUST select the appropriate actions and include parameters. Do NOT just describe",
|
|
4291
|
-
"what you would do
|
|
4227
|
+
"what you would do \u2014 actually select the actions.",
|
|
4292
4228
|
"",
|
|
4293
4229
|
"IMPORTANT: Use START_CODING_TASK to launch coding agents. It handles workspace setup",
|
|
4294
4230
|
"automatically. If a repo URL is provided, it clones it first. If no repo, the agent",
|
|
@@ -4306,8 +4242,7 @@ var codingAgentExamplesProvider = {
|
|
|
4306
4242
|
"and unique identifiers so their work is clearly differentiated.",
|
|
4307
4243
|
"",
|
|
4308
4244
|
MULTI_AGENT_EXAMPLE
|
|
4309
|
-
].join(
|
|
4310
|
-
`);
|
|
4245
|
+
].join("\n");
|
|
4311
4246
|
return {
|
|
4312
4247
|
data: { codingAgentExamples: CODING_AGENT_EXAMPLES },
|
|
4313
4248
|
values: { codingAgentExamples: text },
|
|
@@ -4340,7 +4275,7 @@ function formatWorkspaceLine(ws, sessions) {
|
|
|
4340
4275
|
const label = ws.label || ws.id.slice(0, 8);
|
|
4341
4276
|
const agents = sessions.filter((s) => s.workdir === ws.path);
|
|
4342
4277
|
const agentSummary = agents.length > 0 ? agents.map((a) => `${a.agentType}:${formatStatus(a.status)}`).join(", ") : "no agents";
|
|
4343
|
-
return ` - "${label}"
|
|
4278
|
+
return ` - "${label}" \u2192 ${ws.repo} (branch: ${ws.branch}, ${agentSummary})`;
|
|
4344
4279
|
}
|
|
4345
4280
|
var activeWorkspaceContextProvider = {
|
|
4346
4281
|
name: "ACTIVE_WORKSPACE_CONTEXT",
|
|
@@ -4355,7 +4290,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4355
4290
|
try {
|
|
4356
4291
|
sessions = await Promise.race([
|
|
4357
4292
|
ptyService.listSessions(),
|
|
4358
|
-
new Promise((
|
|
4293
|
+
new Promise((resolve3) => setTimeout(() => resolve3([]), 2000))
|
|
4359
4294
|
]);
|
|
4360
4295
|
} catch {
|
|
4361
4296
|
sessions = [];
|
|
@@ -4369,8 +4304,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4369
4304
|
"# Active Workspaces & Agents",
|
|
4370
4305
|
"No active workspaces or coding agent sessions.",
|
|
4371
4306
|
"Use START_CODING_TASK to launch a new coding agent."
|
|
4372
|
-
].join(
|
|
4373
|
-
`);
|
|
4307
|
+
].join("\n");
|
|
4374
4308
|
return {
|
|
4375
4309
|
data: { activeWorkspaces: [], activeSessions: [] },
|
|
4376
4310
|
values: { activeWorkspaceContext: text2 },
|
|
@@ -4400,9 +4334,9 @@ var activeWorkspaceContextProvider = {
|
|
|
4400
4334
|
const supervisionLevel = coordinator.getSupervisionLevel();
|
|
4401
4335
|
if (pending.length > 0) {
|
|
4402
4336
|
lines.push("");
|
|
4403
|
-
lines.push(`## Pending Confirmations (${pending.length})
|
|
4337
|
+
lines.push(`## Pending Confirmations (${pending.length}) \u2014 supervision: ${supervisionLevel}`);
|
|
4404
4338
|
for (const p of pending) {
|
|
4405
|
-
lines.push(` - "${p.taskContext.label}" blocked: "${p.promptText}"
|
|
4339
|
+
lines.push(` - "${p.taskContext.label}" blocked: "${p.promptText}" \u2192 suggested: ${p.llmDecision.action}`);
|
|
4406
4340
|
}
|
|
4407
4341
|
} else if (supervisionLevel !== "autonomous") {
|
|
4408
4342
|
lines.push("");
|
|
@@ -4413,8 +4347,7 @@ var activeWorkspaceContextProvider = {
|
|
|
4413
4347
|
lines.push("");
|
|
4414
4348
|
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
4349
|
}
|
|
4416
|
-
const text = lines.join(
|
|
4417
|
-
`);
|
|
4350
|
+
const text = lines.join("\n");
|
|
4418
4351
|
return {
|
|
4419
4352
|
data: {
|
|
4420
4353
|
activeWorkspaces: workspaces.map((ws) => ({
|
|
@@ -4440,18 +4373,18 @@ var activeWorkspaceContextProvider = {
|
|
|
4440
4373
|
|
|
4441
4374
|
// src/services/workspace-service.ts
|
|
4442
4375
|
import * as os3 from "node:os";
|
|
4443
|
-
import * as
|
|
4376
|
+
import * as path5 from "node:path";
|
|
4444
4377
|
import {
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4378
|
+
CredentialService,
|
|
4379
|
+
GitHubPatClient as GitHubPatClient2,
|
|
4380
|
+
MemoryTokenStore,
|
|
4381
|
+
WorkspaceService
|
|
4449
4382
|
} from "git-workspace-service";
|
|
4450
4383
|
|
|
4451
4384
|
// src/services/workspace-github.ts
|
|
4452
4385
|
import {
|
|
4453
|
-
|
|
4454
|
-
|
|
4386
|
+
GitHubPatClient,
|
|
4387
|
+
OAuthDeviceFlow
|
|
4455
4388
|
} from "git-workspace-service";
|
|
4456
4389
|
function parseOwnerRepo(repo) {
|
|
4457
4390
|
const match = repo.match(/(?:github\.com\/)?([^/]+)\/([^/.]+)/);
|
|
@@ -4507,9 +4440,7 @@ async function performOAuthFlow(ctx, clientId) {
|
|
|
4507
4440
|
expiresIn: deviceCode.expiresIn
|
|
4508
4441
|
});
|
|
4509
4442
|
} else {
|
|
4510
|
-
console.log(`
|
|
4511
|
-
[GitHub Auth] Go to ${deviceCode.verificationUri} and enter code: ${deviceCode.userCode}
|
|
4512
|
-
`);
|
|
4443
|
+
console.log(`\n[GitHub Auth] Go to ${deviceCode.verificationUri} and enter code: ${deviceCode.userCode}\n`);
|
|
4513
4444
|
}
|
|
4514
4445
|
const token = await oauth.pollForToken(deviceCode);
|
|
4515
4446
|
const client = new GitHubPatClient({ token: token.accessToken });
|
|
@@ -4578,8 +4509,7 @@ async function getStatus(workspacePath) {
|
|
|
4578
4509
|
cwd: workspacePath,
|
|
4579
4510
|
encoding: "utf-8"
|
|
4580
4511
|
}).trim();
|
|
4581
|
-
const lines = statusOutput.split(
|
|
4582
|
-
`).filter(Boolean);
|
|
4512
|
+
const lines = statusOutput.split("\n").filter(Boolean);
|
|
4583
4513
|
const modified = [];
|
|
4584
4514
|
const staged = [];
|
|
4585
4515
|
const untracked = [];
|
|
@@ -4654,11 +4584,11 @@ async function createPR(workspaceService, workspace, workspaceId, options, log)
|
|
|
4654
4584
|
|
|
4655
4585
|
// src/services/workspace-lifecycle.ts
|
|
4656
4586
|
import * as fs2 from "node:fs";
|
|
4657
|
-
import * as
|
|
4587
|
+
import * as path4 from "node:path";
|
|
4658
4588
|
async function removeScratchDir(dirPath, baseDir, log) {
|
|
4659
|
-
const resolved =
|
|
4660
|
-
const resolvedBase =
|
|
4661
|
-
if (!resolved.startsWith(resolvedBase) && resolved !==
|
|
4589
|
+
const resolved = path4.resolve(dirPath);
|
|
4590
|
+
const resolvedBase = path4.resolve(baseDir) + path4.sep;
|
|
4591
|
+
if (!resolved.startsWith(resolvedBase) && resolved !== path4.resolve(baseDir)) {
|
|
4662
4592
|
console.warn(`[CodingWorkspaceService] Refusing to remove dir outside base: ${resolved}`);
|
|
4663
4593
|
return;
|
|
4664
4594
|
}
|
|
@@ -4690,7 +4620,7 @@ async function gcOrphanedWorkspaces(baseDir, workspaceTtlMs, trackedWorkspaceIds
|
|
|
4690
4620
|
skipped++;
|
|
4691
4621
|
continue;
|
|
4692
4622
|
}
|
|
4693
|
-
const dirPath =
|
|
4623
|
+
const dirPath = path4.join(baseDir, entry.name);
|
|
4694
4624
|
try {
|
|
4695
4625
|
const stat = await fs2.promises.stat(dirPath);
|
|
4696
4626
|
const age = now - stat.mtimeMs;
|
|
@@ -4727,7 +4657,7 @@ class CodingWorkspaceService {
|
|
|
4727
4657
|
constructor(runtime, config = {}) {
|
|
4728
4658
|
this.runtime = runtime;
|
|
4729
4659
|
this.serviceConfig = {
|
|
4730
|
-
baseDir: config.baseDir ??
|
|
4660
|
+
baseDir: config.baseDir ?? path5.join(os3.homedir(), ".milady", "workspaces"),
|
|
4731
4661
|
branchPrefix: config.branchPrefix ?? "milady",
|
|
4732
4662
|
debug: config.debug ?? false,
|
|
4733
4663
|
workspaceTtlMs: config.workspaceTtlMs ?? 24 * 60 * 60 * 1000
|
|
@@ -4989,7 +4919,7 @@ class CodingWorkspaceService {
|
|
|
4989
4919
|
}
|
|
4990
4920
|
// src/api/agent-routes.ts
|
|
4991
4921
|
import * as os4 from "node:os";
|
|
4992
|
-
import * as
|
|
4922
|
+
import * as path6 from "node:path";
|
|
4993
4923
|
async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
4994
4924
|
const method = req.method?.toUpperCase();
|
|
4995
4925
|
if (method === "GET" && pathname === "/api/coding-agents/preflight") {
|
|
@@ -5116,15 +5046,15 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
5116
5046
|
customCredentials,
|
|
5117
5047
|
metadata
|
|
5118
5048
|
} = body;
|
|
5119
|
-
const workspaceBaseDir =
|
|
5049
|
+
const workspaceBaseDir = path6.join(os4.homedir(), ".milady", "workspaces");
|
|
5120
5050
|
const allowedPrefixes = [
|
|
5121
|
-
|
|
5122
|
-
|
|
5051
|
+
path6.resolve(workspaceBaseDir),
|
|
5052
|
+
path6.resolve(process.cwd())
|
|
5123
5053
|
];
|
|
5124
5054
|
let workdir = rawWorkdir;
|
|
5125
5055
|
if (workdir) {
|
|
5126
|
-
const resolved =
|
|
5127
|
-
const isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 +
|
|
5056
|
+
const resolved = path6.resolve(workdir);
|
|
5057
|
+
const isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 + path6.sep));
|
|
5128
5058
|
if (!isAllowed) {
|
|
5129
5059
|
sendError(res, "workdir must be within workspace base directory or cwd", 403);
|
|
5130
5060
|
return true;
|
|
@@ -5287,7 +5217,6 @@ async function handleAgentRoutes(req, res, pathname, ctx) {
|
|
|
5287
5217
|
}
|
|
5288
5218
|
|
|
5289
5219
|
// src/api/coordinator-routes.ts
|
|
5290
|
-
var COORDINATOR_PREFIX = "/api/coding-agents/coordinator";
|
|
5291
5220
|
async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
5292
5221
|
if (!pathname.startsWith(COORDINATOR_PREFIX)) {
|
|
5293
5222
|
return false;
|
|
@@ -5305,9 +5234,7 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5305
5234
|
"Cache-Control": "no-cache",
|
|
5306
5235
|
Connection: "keep-alive"
|
|
5307
5236
|
});
|
|
5308
|
-
res.write(
|
|
5309
|
-
|
|
5310
|
-
`);
|
|
5237
|
+
res.write(":ok\n\n");
|
|
5311
5238
|
const unsubscribe = coordinator.addSseClient(res);
|
|
5312
5239
|
req.on("close", unsubscribe);
|
|
5313
5240
|
const keepAlive = setInterval(() => {
|
|
@@ -5315,9 +5242,7 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5315
5242
|
clearInterval(keepAlive);
|
|
5316
5243
|
return;
|
|
5317
5244
|
}
|
|
5318
|
-
res.write(
|
|
5319
|
-
|
|
5320
|
-
`);
|
|
5245
|
+
res.write(":ping\n\n");
|
|
5321
5246
|
}, 30000);
|
|
5322
5247
|
req.on("close", () => clearInterval(keepAlive));
|
|
5323
5248
|
return true;
|
|
@@ -5401,6 +5326,7 @@ async function handleCoordinatorRoutes(req, res, pathname, ctx) {
|
|
|
5401
5326
|
}
|
|
5402
5327
|
return false;
|
|
5403
5328
|
}
|
|
5329
|
+
var COORDINATOR_PREFIX = "/api/coding-agents/coordinator";
|
|
5404
5330
|
|
|
5405
5331
|
// src/api/issue-routes.ts
|
|
5406
5332
|
async function handleIssueRoutes(req, res, pathname, ctx) {
|
|
@@ -5632,9 +5558,8 @@ async function handleWorkspaceRoutes(req, res, pathname, ctx) {
|
|
|
5632
5558
|
}
|
|
5633
5559
|
|
|
5634
5560
|
// src/api/routes.ts
|
|
5635
|
-
var MAX_BODY_SIZE = 1024 * 1024;
|
|
5636
5561
|
async function parseBody(req) {
|
|
5637
|
-
return new Promise((
|
|
5562
|
+
return new Promise((resolve5, reject) => {
|
|
5638
5563
|
let body = "";
|
|
5639
5564
|
let size = 0;
|
|
5640
5565
|
req.on("data", (chunk) => {
|
|
@@ -5648,7 +5573,7 @@ async function parseBody(req) {
|
|
|
5648
5573
|
});
|
|
5649
5574
|
req.on("end", () => {
|
|
5650
5575
|
try {
|
|
5651
|
-
|
|
5576
|
+
resolve5(body ? JSON.parse(body) : {});
|
|
5652
5577
|
} catch {
|
|
5653
5578
|
reject(new Error("Invalid JSON body"));
|
|
5654
5579
|
}
|
|
@@ -5689,6 +5614,7 @@ function createCodingAgentRouteHandler(runtime, coordinator) {
|
|
|
5689
5614
|
};
|
|
5690
5615
|
return (req, res, pathname) => handleCodingAgentRoutes(req, res, pathname, ctx);
|
|
5691
5616
|
}
|
|
5617
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
5692
5618
|
|
|
5693
5619
|
// src/index.ts
|
|
5694
5620
|
var codingAgentPlugin = {
|
|
@@ -5731,5 +5657,5 @@ export {
|
|
|
5731
5657
|
CodingWorkspaceService
|
|
5732
5658
|
};
|
|
5733
5659
|
|
|
5734
|
-
//# debugId=
|
|
5660
|
+
//# debugId=7D1565B17FF6D63964756E2164756E21
|
|
5735
5661
|
//# sourceMappingURL=index.js.map
|