@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.
Files changed (80) hide show
  1. package/README.md +29 -6
  2. package/dist/index.js +465 -539
  3. package/dist/index.js.map +10 -10
  4. package/dist/src/actions/coding-task-handlers.d.ts +44 -0
  5. package/dist/src/actions/coding-task-handlers.d.ts.map +1 -0
  6. package/dist/src/actions/coding-task-helpers.d.ts +27 -0
  7. package/dist/src/actions/coding-task-helpers.d.ts.map +1 -0
  8. package/dist/src/actions/finalize-workspace.d.ts +11 -0
  9. package/dist/src/actions/finalize-workspace.d.ts.map +1 -0
  10. package/dist/src/actions/list-agents.d.ts +11 -0
  11. package/dist/src/actions/list-agents.d.ts.map +1 -0
  12. package/dist/src/actions/manage-issues.d.ts +11 -0
  13. package/dist/src/actions/manage-issues.d.ts.map +1 -0
  14. package/dist/src/actions/provision-workspace.d.ts +11 -0
  15. package/dist/src/actions/provision-workspace.d.ts.map +1 -0
  16. package/dist/src/actions/send-to-agent.d.ts +11 -0
  17. package/dist/src/actions/send-to-agent.d.ts.map +1 -0
  18. package/dist/src/actions/spawn-agent.d.ts +11 -0
  19. package/dist/src/actions/spawn-agent.d.ts.map +1 -0
  20. package/dist/src/actions/start-coding-task.d.ts +17 -0
  21. package/dist/src/actions/start-coding-task.d.ts.map +1 -0
  22. package/dist/src/actions/stop-agent.d.ts +11 -0
  23. package/dist/src/actions/stop-agent.d.ts.map +1 -0
  24. package/dist/src/api/agent-routes.d.ts +18 -0
  25. package/dist/src/api/agent-routes.d.ts.map +1 -0
  26. package/dist/src/api/coordinator-routes.d.ts +22 -0
  27. package/dist/src/api/coordinator-routes.d.ts.map +1 -0
  28. package/dist/src/api/issue-routes.d.ts +17 -0
  29. package/dist/src/api/issue-routes.d.ts.map +1 -0
  30. package/dist/src/api/routes.d.ts +36 -0
  31. package/dist/src/api/routes.d.ts.map +1 -0
  32. package/dist/src/api/workspace-routes.d.ts +17 -0
  33. package/dist/src/api/workspace-routes.d.ts.map +1 -0
  34. package/dist/src/index.d.ts +32 -0
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/providers/action-examples.d.ts +13 -0
  37. package/dist/src/providers/action-examples.d.ts.map +1 -0
  38. package/dist/src/providers/active-workspace-context.d.ts +13 -0
  39. package/dist/src/providers/active-workspace-context.d.ts.map +1 -0
  40. package/dist/src/services/agent-metrics.d.ts +28 -0
  41. package/dist/src/services/agent-metrics.d.ts.map +1 -0
  42. package/dist/src/services/agent-selection.d.ts +53 -0
  43. package/dist/src/services/agent-selection.d.ts.map +1 -0
  44. package/dist/src/services/ansi-utils.d.ts +48 -0
  45. package/dist/src/services/ansi-utils.d.ts.map +1 -0
  46. package/dist/src/services/pty-auto-response.d.ts +30 -0
  47. package/dist/src/services/pty-auto-response.d.ts.map +1 -0
  48. package/dist/src/services/pty-init.d.ts +43 -0
  49. package/dist/src/services/pty-init.d.ts.map +1 -0
  50. package/dist/src/services/pty-service.d.ts +92 -0
  51. package/dist/src/services/pty-service.d.ts.map +1 -0
  52. package/dist/src/services/pty-session-io.d.ts +46 -0
  53. package/dist/src/services/pty-session-io.d.ts.map +1 -0
  54. package/dist/src/services/pty-spawn.d.ts +50 -0
  55. package/dist/src/services/pty-spawn.d.ts.map +1 -0
  56. package/dist/src/services/pty-types.d.ts +80 -0
  57. package/dist/src/services/pty-types.d.ts.map +1 -0
  58. package/dist/src/services/stall-classifier.d.ts +44 -0
  59. package/dist/src/services/stall-classifier.d.ts.map +1 -0
  60. package/dist/src/services/swarm-coordinator-prompts.d.ts +62 -0
  61. package/dist/src/services/swarm-coordinator-prompts.d.ts.map +1 -0
  62. package/dist/src/services/swarm-coordinator.d.ts +163 -0
  63. package/dist/src/services/swarm-coordinator.d.ts.map +1 -0
  64. package/dist/src/services/swarm-decision-loop.d.ts +39 -0
  65. package/dist/src/services/swarm-decision-loop.d.ts.map +1 -0
  66. package/dist/src/services/swarm-idle-watchdog.d.ts +22 -0
  67. package/dist/src/services/swarm-idle-watchdog.d.ts.map +1 -0
  68. package/dist/src/services/workspace-git-ops.d.ts +28 -0
  69. package/dist/src/services/workspace-git-ops.d.ts.map +1 -0
  70. package/dist/src/services/workspace-github.d.ts +58 -0
  71. package/dist/src/services/workspace-github.d.ts.map +1 -0
  72. package/dist/src/services/workspace-lifecycle.d.ts +18 -0
  73. package/dist/src/services/workspace-lifecycle.d.ts.map +1 -0
  74. package/dist/src/services/workspace-service.d.ts +84 -0
  75. package/dist/src/services/workspace-service.d.ts.map +1 -0
  76. package/dist/src/services/workspace-types.d.ts +81 -0
  77. package/dist/src/services/workspace-types.d.ts.map +1 -0
  78. package/dist/tsconfig.build.tsbuildinfo +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -0
  80. package/package.json +5 -4
package/dist/index.js CHANGED
@@ -1,20 +1,5 @@
1
- import { createRequire } from "node:module";
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
- Previous decisions for this session:
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
- ` + `2. "complete" 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.
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
- ` + `3. "escalate" The prompt requires human judgment (e.g. design decisions, ` + `ambiguous requirements, security-sensitive actions). Do NOT respond yourself.
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
- Previous decisions for this session:
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
- ` + `2. "respond" The agent appears stuck or waiting for input that wasn't detected ` + `as a blocking prompt. Send a message to nudge it (e.g. "continue", or answer a question ` + `visible in the output).
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
- ` + `3. "escalate" Something looks wrong or unclear. The human should review.
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
- Previous decisions for this session:
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
- ` + `1. "respond" 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 most turns are intermediate steps, not the final result.
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
- ` + `2. "complete" 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).
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
- ` + `3. "escalate" Something looks wrong or you're unsure whether the task is complete. ` + `Let the human decide.
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 { ModelType as ModelType2 } from "@elizaos/core";
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
- ctx.sendChatMessage(summary ? `Finished "${taskCtx?.label ?? sessionId}".
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 broadcasting only"
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}" assessing whether task is done`);
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 defaulting to complete`);
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 defaulting to complete"
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" ? ` "${(decision.response ?? "").slice(0, 80)}"` : ""} ${decision.reasoning.slice(0, 120)}`);
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 needs your attention: ${decision.reasoning}`, "coding-agent");
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
- const decision = await makeCoordinationDecision(ctx, taskCtx, promptText, output);
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} ${reasonExcerpt}`, "coding-agent");
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 needs human review"
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 path from "node:path";
1540
+ import * as path2 from "node:path";
1638
1541
  import {
1639
- logger as logger3
1542
+ logger as logger3
1640
1543
  } from "@elizaos/core";
1641
1544
 
1642
1545
  // src/services/pty-service.ts
1643
- import { logger as logger2 } from "@elizaos/core";
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
- checkAdapters,
1646
- createAdapter,
1647
- generateApprovalConfig
1550
+ checkAdapters,
1551
+ createAdapter,
1552
+ generateApprovalConfig
1648
1553
  } from "coding-agent-adapters";
1649
- import { PTYConsoleBridge } from "pty-console";
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 { createRequire as createRequire2 } from "node:module";
1805
- import { createAllAdapters } from "coding-agent-adapters";
1709
+ import {createRequire as createRequire2} from "node:module";
1710
+ import {createAllAdapters} from "coding-agent-adapters";
1806
1711
  import {
1807
- BunCompatiblePTYManager,
1808
- isBun,
1809
- PTYManager,
1810
- ShellAdapter
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 ? ` ${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 ? ` ${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} sending task (attempt ${attempt + 1}, ${settleMs}ms settle, baseline ${baselineLength} lines)`);
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} task may not have been accepted (only ${newLines} new lines after ${VERIFY_DELAY_MS}ms). Retrying (attempt ${attempt + 2}/${MAX_RETRIES + 1})`);
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} task accepted (${newLines} new lines after ${VERIFY_DELAY_MS}ms)`);
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
- env: { ...options.env, ...modelEnv },
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 { ModelType } from "@elizaos/core";
2101
+ import {ModelType} from "@elizaos/core";
2174
2102
  import {
2175
- buildTaskCompletionTimeline,
2176
- extractTaskCompletionTraceRecords
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 ` + `it has stopped producing output while in a busy state.
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" The agent hit an error state (crash, unrecoverable error, stack trace).
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" 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.
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 ${snapshotPath}`);
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 ? ` "${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 { logger } from "@elizaos/core";
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 { ModelType as ModelType3 } from "@elizaos/core";
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 not idle`);
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 escalating`);
2421
- ctx.sendChatMessage(`[${taskCtx.label}] Session idle for ${idleMinutes}m couldn't determine status. Needs your attention.`, "coding-agent");
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") {} else if (decision.action === "respond") {
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 ${actionDesc}`, "coding-agent");
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 needs your attention: ${decision.reasoning}`, "coding-agent");
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 ${decision.reasoning}`);
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 I'll let it finish.`, "coding-agent");
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
- return classifyStallOutput({
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 = path.resolve(workdir);
3271
- const workspaceBaseDir = path.join(os.homedir(), ".milady", "workspaces");
3219
+ const resolvedWorkdir = path2.resolve(workdir);
3220
+ const workspaceBaseDir = path2.join(os.homedir(), ".milady", "workspaces");
3272
3221
  const allowedPrefixes = [
3273
- path.resolve(workspaceBaseDir),
3274
- path.resolve(process.cwd())
3222
+ path2.resolve(workspaceBaseDir),
3223
+ path2.resolve(process.cwd())
3275
3224
  ];
3276
- const isAllowed = allowedPrefixes.some((prefix) => resolvedWorkdir.startsWith(prefix + path.sep) || resolvedWorkdir === 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
- logger as logger5
3387
+ logger as logger5
3441
3388
  } from "@elizaos/core";
3442
3389
 
3443
3390
  // src/actions/coding-task-helpers.ts
3444
- import { randomUUID } from "node:crypto";
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 path2 from "node:path";
3394
+ import * as path3 from "node:path";
3448
3395
  import {
3449
- logger as logger4
3396
+ logger as logger4
3450
3397
  } from "@elizaos/core";
3451
3398
  function createScratchDir() {
3452
- const baseDir = path2.join(os2.homedir(), ".milady", "workspaces");
3399
+ const baseDir = path3.join(os2.homedir(), ".milady", "workspaces");
3453
3400
  const scratchId = randomUUID();
3454
- const scratchDir = path2.join(baseDir, scratchId);
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). " + "If omitted, the agent runs in an isolated scratch directory.",
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
- logger as logger6
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 users are getting 401 errors after token refresh"
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
- function formatExample(ex) {
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 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>
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 actually select the actions.",
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}" ${ws.repo} (branch: ${ws.branch}, ${agentSummary})`;
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((resolve2) => setTimeout(() => resolve2([]), 2000))
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}) supervision: ${supervisionLevel}`);
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}" suggested: ${p.llmDecision.action}`);
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 path4 from "node:path";
4376
+ import * as path5 from "node:path";
4444
4377
  import {
4445
- CredentialService,
4446
- GitHubPatClient as GitHubPatClient2,
4447
- MemoryTokenStore,
4448
- WorkspaceService
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
- GitHubPatClient,
4454
- OAuthDeviceFlow
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 path3 from "node:path";
4587
+ import * as path4 from "node:path";
4658
4588
  async function removeScratchDir(dirPath, baseDir, log) {
4659
- const resolved = path3.resolve(dirPath);
4660
- const resolvedBase = path3.resolve(baseDir) + path3.sep;
4661
- if (!resolved.startsWith(resolvedBase) && resolved !== path3.resolve(baseDir)) {
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 = path3.join(baseDir, entry.name);
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 ?? path4.join(os3.homedir(), ".milady", "workspaces"),
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 path5 from "node:path";
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 = path5.join(os4.homedir(), ".milady", "workspaces");
5049
+ const workspaceBaseDir = path6.join(os4.homedir(), ".milady", "workspaces");
5120
5050
  const allowedPrefixes = [
5121
- path5.resolve(workspaceBaseDir),
5122
- path5.resolve(process.cwd())
5051
+ path6.resolve(workspaceBaseDir),
5052
+ path6.resolve(process.cwd())
5123
5053
  ];
5124
5054
  let workdir = rawWorkdir;
5125
5055
  if (workdir) {
5126
- const resolved = path5.resolve(workdir);
5127
- const isAllowed = allowedPrefixes.some((prefix2) => resolved === prefix2 || resolved.startsWith(prefix2 + path5.sep));
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(`:ok
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(`:ping
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((resolve4, reject) => {
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
- resolve4(body ? JSON.parse(body) : {});
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=70379FA5545346E764756E2164756E21
5660
+ //# debugId=7D1565B17FF6D63964756E2164756E21
5735
5661
  //# sourceMappingURL=index.js.map