@desplega.ai/agent-swarm 1.2.1 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +20 -1
- package/.env.docker.example +22 -1
- package/.env.example +17 -0
- package/.github/workflows/docker-publish.yml +92 -0
- package/CONTRIBUTING.md +270 -0
- package/DEPLOYMENT.md +391 -0
- package/Dockerfile.worker +29 -1
- package/FAQ.md +19 -0
- package/LICENSE +21 -0
- package/MCP.md +249 -0
- package/README.md +103 -207
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/docker-compose.example.yml +137 -0
- package/docker-entrypoint.sh +223 -7
- package/package.json +8 -3
- package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/codebase-analyzer.md +143 -0
- package/plugin/agents/codebase-locator.md +122 -0
- package/plugin/agents/codebase-pattern-finder.md +227 -0
- package/plugin/agents/web-search-researcher.md +109 -0
- package/plugin/commands/create-plan.md +415 -0
- package/plugin/commands/implement-plan.md +89 -0
- package/plugin/commands/research.md +200 -0
- package/plugin/commands/start-leader.md +101 -0
- package/plugin/commands/start-worker.md +56 -0
- package/plugin/commands/swarm-chat.md +78 -0
- package/plugin/commands/todos.md +66 -0
- package/plugin/commands/work-on-task.md +44 -0
- package/plugin/skills/.gitkeep +0 -0
- package/scripts/generate-mcp-docs.ts +415 -0
- package/slack-manifest.json +69 -0
- package/src/be/db.ts +1431 -25
- package/src/cli.tsx +135 -11
- package/src/commands/lead.ts +13 -0
- package/src/commands/runner.ts +255 -0
- package/src/commands/worker.ts +8 -220
- package/src/hooks/hook.ts +102 -14
- package/src/http.ts +361 -5
- package/src/prompts/base-prompt.ts +131 -0
- package/src/server.ts +56 -0
- package/src/slack/app.ts +73 -0
- package/src/slack/commands.ts +88 -0
- package/src/slack/handlers.ts +281 -0
- package/src/slack/index.ts +3 -0
- package/src/slack/responses.ts +175 -0
- package/src/slack/router.ts +170 -0
- package/src/slack/types.ts +20 -0
- package/src/slack/watcher.ts +119 -0
- package/src/tools/create-channel.ts +80 -0
- package/src/tools/get-tasks.ts +54 -21
- package/src/tools/join-swarm.ts +28 -4
- package/src/tools/list-channels.ts +37 -0
- package/src/tools/list-services.ts +110 -0
- package/src/tools/poll-task.ts +46 -3
- package/src/tools/post-message.ts +87 -0
- package/src/tools/read-messages.ts +192 -0
- package/src/tools/register-service.ts +118 -0
- package/src/tools/send-task.ts +80 -7
- package/src/tools/store-progress.ts +9 -3
- package/src/tools/task-action.ts +211 -0
- package/src/tools/unregister-service.ts +110 -0
- package/src/tools/update-profile.ts +105 -0
- package/src/tools/update-service-status.ts +118 -0
- package/src/types.ts +110 -3
- package/src/utils/pretty-print.ts +224 -0
- package/thoughts/shared/plans/.gitkeep +0 -0
- package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
- package/tsconfig.json +3 -1
- package/ui/bun.lock +692 -0
- package/ui/index.html +22 -0
- package/ui/package.json +32 -0
- package/ui/pnpm-lock.yaml +3034 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +43 -0
- package/ui/src/components/ActivityFeed.tsx +415 -0
- package/ui/src/components/AgentDetailPanel.tsx +534 -0
- package/ui/src/components/AgentsPanel.tsx +549 -0
- package/ui/src/components/ChatPanel.tsx +1820 -0
- package/ui/src/components/ConfigModal.tsx +232 -0
- package/ui/src/components/Dashboard.tsx +534 -0
- package/ui/src/components/Header.tsx +168 -0
- package/ui/src/components/ServicesPanel.tsx +612 -0
- package/ui/src/components/StatsBar.tsx +288 -0
- package/ui/src/components/StatusBadge.tsx +124 -0
- package/ui/src/components/TaskDetailPanel.tsx +807 -0
- package/ui/src/components/TasksPanel.tsx +575 -0
- package/ui/src/hooks/queries.ts +170 -0
- package/ui/src/index.css +235 -0
- package/ui/src/lib/api.ts +161 -0
- package/ui/src/lib/config.ts +35 -0
- package/ui/src/lib/theme.ts +214 -0
- package/ui/src/lib/utils.ts +48 -0
- package/ui/src/main.tsx +32 -0
- package/ui/src/types/api.ts +164 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/tailwind.config.js +35 -0
- package/ui/tsconfig.json +31 -0
- package/ui/vite.config.ts +22 -0
- package/cc-plugin/README.md +0 -49
- package/cc-plugin/commands/setup-leader.md +0 -73
- package/cc-plugin/commands/start-worker.md +0 -64
- package/docker-compose.worker.yml +0 -35
- package/example-req-meta.json +0 -24
- /package/{cc-plugin → plugin}/hooks/hooks.json +0 -0
package/src/commands/worker.ts
CHANGED
|
@@ -1,225 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type RunnerConfig, type RunnerOptions, runAgent } from "./runner.ts";
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
prompt?: string;
|
|
5
|
-
yolo?: boolean;
|
|
6
|
-
additionalArgs?: string[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface RunClaudeIterationOptions {
|
|
10
|
-
prompt: string;
|
|
11
|
-
logFile: string;
|
|
12
|
-
additionalArgs?: string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function runClaudeIteration(opts: RunClaudeIterationOptions): Promise<number> {
|
|
16
|
-
const CMD = [
|
|
17
|
-
"claude",
|
|
18
|
-
"--verbose",
|
|
19
|
-
"--output-format",
|
|
20
|
-
"stream-json",
|
|
21
|
-
"--dangerously-skip-permissions",
|
|
22
|
-
"--allow-dangerously-skip-permissions",
|
|
23
|
-
"--permission-mode",
|
|
24
|
-
"bypassPermissions",
|
|
25
|
-
"-p",
|
|
26
|
-
opts.prompt,
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
if (opts.additionalArgs && opts.additionalArgs.length > 0) {
|
|
30
|
-
CMD.push(...opts.additionalArgs);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log(`[worker] Running: claude ... -p "${opts.prompt}"`);
|
|
34
|
-
|
|
35
|
-
const logFileHandle = Bun.file(opts.logFile).writer();
|
|
36
|
-
|
|
37
|
-
// Collect stderr for better error reporting
|
|
38
|
-
let stderrOutput = "";
|
|
39
|
-
|
|
40
|
-
const proc = Bun.spawn(CMD, {
|
|
41
|
-
env: process.env,
|
|
42
|
-
stdout: "pipe",
|
|
43
|
-
stderr: "pipe",
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
console.log(`[worker] Process spawned, PID: ${proc.pid}`);
|
|
47
|
-
console.log(`[worker] Waiting for output streams...`);
|
|
48
|
-
|
|
49
|
-
let stdoutChunks = 0;
|
|
50
|
-
let stderrChunks = 0;
|
|
51
|
-
|
|
52
|
-
// Read stdout and stderr concurrently
|
|
53
|
-
const stdoutPromise = (async () => {
|
|
54
|
-
console.log(`[worker] stdout stream: ${proc.stdout ? "available" : "not available"}`);
|
|
55
|
-
if (proc.stdout) {
|
|
56
|
-
for await (const chunk of proc.stdout) {
|
|
57
|
-
stdoutChunks++;
|
|
58
|
-
const text = new TextDecoder().decode(chunk);
|
|
59
|
-
logFileHandle.write(text);
|
|
60
|
-
console.log(`[worker] stdout chunk #${stdoutChunks} (${chunk.length} bytes)`);
|
|
61
|
-
|
|
62
|
-
// Also parse and log to console for visibility
|
|
63
|
-
const lines = text.split("\n");
|
|
64
|
-
for (const line of lines) {
|
|
65
|
-
if (line.trim() === "") continue;
|
|
66
|
-
try {
|
|
67
|
-
const json = JSON.parse(line.trim());
|
|
68
|
-
// Log a summary of what's happening
|
|
69
|
-
if (json.type === "assistant" && json.message) {
|
|
70
|
-
const preview = json.message.slice(0, 100);
|
|
71
|
-
console.log(
|
|
72
|
-
`[worker] Assistant: ${preview}${json.message.length > 100 ? "..." : ""}`,
|
|
73
|
-
);
|
|
74
|
-
} else if (json.type === "tool_use") {
|
|
75
|
-
console.log(`[worker] Tool: ${json.tool || json.name || "unknown"}`);
|
|
76
|
-
} else if (json.type === "result") {
|
|
77
|
-
// Log result details
|
|
78
|
-
const resultPreview = JSON.stringify(json).slice(0, 200);
|
|
79
|
-
console.log(
|
|
80
|
-
`[worker] Result: ${resultPreview}${JSON.stringify(json).length > 200 ? "..." : ""}`,
|
|
81
|
-
);
|
|
82
|
-
} else if (json.type === "error") {
|
|
83
|
-
console.error(
|
|
84
|
-
`[worker] Error from Claude: ${json.error || json.message || JSON.stringify(json)}`,
|
|
85
|
-
);
|
|
86
|
-
} else if (json.type === "system") {
|
|
87
|
-
// Log system message details
|
|
88
|
-
const msg = json.message || json.content || "";
|
|
89
|
-
const preview =
|
|
90
|
-
typeof msg === "string" ? msg.slice(0, 150) : JSON.stringify(msg).slice(0, 150);
|
|
91
|
-
console.log(`[worker] System: ${preview}${preview.length >= 150 ? "..." : ""}`);
|
|
92
|
-
} else {
|
|
93
|
-
// Log unknown event types with content
|
|
94
|
-
console.log(
|
|
95
|
-
`[worker] Event type: ${json.type} - ${JSON.stringify(json).slice(0, 100)}`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// Non-JSON line, just log it
|
|
100
|
-
if (line.trim()) {
|
|
101
|
-
console.log(`[worker] Raw output: ${line.trim()}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
console.log(`[worker] stdout stream ended (total ${stdoutChunks} chunks)`);
|
|
107
|
-
}
|
|
108
|
-
})();
|
|
109
|
-
|
|
110
|
-
const stderrPromise = (async () => {
|
|
111
|
-
console.log(`[worker] stderr stream: ${proc.stderr ? "available" : "not available"}`);
|
|
112
|
-
if (proc.stderr) {
|
|
113
|
-
for await (const chunk of proc.stderr) {
|
|
114
|
-
stderrChunks++;
|
|
115
|
-
const text = new TextDecoder().decode(chunk);
|
|
116
|
-
stderrOutput += text;
|
|
117
|
-
// Log stderr to console immediately
|
|
118
|
-
console.error(`[worker] stderr chunk #${stderrChunks}: ${text.trim()}`);
|
|
119
|
-
logFileHandle.write(
|
|
120
|
-
JSON.stringify({ type: "stderr", content: text, timestamp: new Date().toISOString() }) +
|
|
121
|
-
"\n",
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
console.log(`[worker] stderr stream ended (total ${stderrChunks} chunks)`);
|
|
125
|
-
}
|
|
126
|
-
})();
|
|
127
|
-
|
|
128
|
-
// Wait for both streams to finish
|
|
129
|
-
console.log(`[worker] Waiting for streams to complete...`);
|
|
130
|
-
await Promise.all([stdoutPromise, stderrPromise]);
|
|
131
|
-
|
|
132
|
-
await logFileHandle.end();
|
|
133
|
-
console.log(`[worker] Waiting for process to exit...`);
|
|
134
|
-
const exitCode = await proc.exited;
|
|
135
|
-
|
|
136
|
-
// Log final status
|
|
137
|
-
console.log(`[worker] Claude exited with code ${exitCode}`);
|
|
138
|
-
console.log(`[worker] Total stdout chunks: ${stdoutChunks}, stderr chunks: ${stderrChunks}`);
|
|
3
|
+
export type WorkerOptions = RunnerOptions;
|
|
139
4
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.warn(`[worker] WARNING: No output received from Claude at all!`);
|
|
146
|
-
console.warn(`[worker] This might indicate Claude failed to start or auth issues.`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return exitCode ?? 1;
|
|
150
|
-
}
|
|
5
|
+
const workerConfig: RunnerConfig = {
|
|
6
|
+
role: "worker",
|
|
7
|
+
defaultPrompt: "/start-worker",
|
|
8
|
+
metadataType: "worker_metadata",
|
|
9
|
+
};
|
|
151
10
|
|
|
152
11
|
export async function runWorker(opts: WorkerOptions) {
|
|
153
|
-
|
|
154
|
-
// WORKER_LOG_DIR env var for Docker, otherwise ./logs
|
|
155
|
-
const baseLogDir = process.env.WORKER_LOG_DIR || "./logs";
|
|
156
|
-
const logDir = `${baseLogDir}/${sessionId}`;
|
|
157
|
-
|
|
158
|
-
// Create log directory
|
|
159
|
-
await mkdir(logDir, { recursive: true });
|
|
160
|
-
|
|
161
|
-
const defaultPrompt = "/start-worker Start or continue the tasks your leader assigned you!";
|
|
162
|
-
const prompt = opts.prompt || defaultPrompt;
|
|
163
|
-
|
|
164
|
-
const isYolo = opts.yolo || process.env.WORKER_YOLO === "true";
|
|
165
|
-
|
|
166
|
-
console.log(`[worker] Starting worker`);
|
|
167
|
-
console.log(`[worker] Session ID: ${sessionId}`);
|
|
168
|
-
console.log(`[worker] Log directory: ${logDir}`);
|
|
169
|
-
console.log(`[worker] YOLO mode: ${isYolo ? "enabled" : "disabled"}`);
|
|
170
|
-
console.log(`[worker] Prompt: ${prompt}`);
|
|
171
|
-
|
|
172
|
-
let iteration = 0;
|
|
173
|
-
|
|
174
|
-
while (true) {
|
|
175
|
-
iteration++;
|
|
176
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
177
|
-
const logFile = `${logDir}/${timestamp}.jsonl`;
|
|
178
|
-
|
|
179
|
-
console.log(`\n[worker] === Iteration ${iteration} ===`);
|
|
180
|
-
console.log(`[worker] Logging to: ${logFile}`);
|
|
181
|
-
|
|
182
|
-
// Write iteration metadata at the start of each log file
|
|
183
|
-
const metadata = {
|
|
184
|
-
type: "worker_metadata",
|
|
185
|
-
sessionId,
|
|
186
|
-
iteration,
|
|
187
|
-
timestamp: new Date().toISOString(),
|
|
188
|
-
prompt,
|
|
189
|
-
yolo: isYolo,
|
|
190
|
-
};
|
|
191
|
-
await Bun.write(logFile, `${JSON.stringify(metadata)}\n`);
|
|
192
|
-
|
|
193
|
-
const exitCode = await runClaudeIteration({
|
|
194
|
-
prompt,
|
|
195
|
-
logFile,
|
|
196
|
-
additionalArgs: opts.additionalArgs,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
if (exitCode !== 0) {
|
|
200
|
-
const errorLog = {
|
|
201
|
-
timestamp: new Date().toISOString(),
|
|
202
|
-
iteration,
|
|
203
|
-
exitCode,
|
|
204
|
-
error: true,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Append to errors.jsonl
|
|
208
|
-
const errorsFile = `${logDir}/errors.jsonl`;
|
|
209
|
-
const existingErrors = (await Bun.file(errorsFile).exists())
|
|
210
|
-
? await Bun.file(errorsFile).text()
|
|
211
|
-
: "";
|
|
212
|
-
await Bun.write(errorsFile, `${existingErrors + JSON.stringify(errorLog)}\n`);
|
|
213
|
-
|
|
214
|
-
if (!isYolo) {
|
|
215
|
-
console.error(`[worker] Claude exited with code ${exitCode}. Stopping.`);
|
|
216
|
-
console.error(`[worker] Error logged to: ${errorsFile}`);
|
|
217
|
-
process.exit(exitCode);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
console.warn(`[worker] Claude exited with code ${exitCode}. YOLO mode - continuing...`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
console.log(`[worker] Iteration ${iteration} complete. Starting next iteration...`);
|
|
224
|
-
}
|
|
12
|
+
return runAgent(workerConfig, opts);
|
|
225
13
|
}
|
package/src/hooks/hook.ts
CHANGED
|
@@ -30,6 +30,26 @@ interface HookMessage {
|
|
|
30
30
|
stop_hook_active?: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
interface MentionPreview {
|
|
34
|
+
channelName: string;
|
|
35
|
+
agentName: string;
|
|
36
|
+
content: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface InboxSummary {
|
|
41
|
+
unreadCount: number;
|
|
42
|
+
mentionsCount: number;
|
|
43
|
+
offeredTasksCount: number;
|
|
44
|
+
poolTasksCount: number;
|
|
45
|
+
inProgressCount: number;
|
|
46
|
+
recentMentions: MentionPreview[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface AgentWithInbox extends Agent {
|
|
50
|
+
inbox?: InboxSummary;
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
/**
|
|
34
54
|
* Main hook handler - processes Claude Code hook events
|
|
35
55
|
*/
|
|
@@ -97,11 +117,11 @@ export async function handleHook(): Promise<void> {
|
|
|
97
117
|
}
|
|
98
118
|
};
|
|
99
119
|
|
|
100
|
-
const getAgentInfo = async (): Promise<
|
|
120
|
+
const getAgentInfo = async (): Promise<AgentWithInbox | undefined> => {
|
|
101
121
|
if (!mcpConfig) return;
|
|
102
122
|
|
|
103
123
|
try {
|
|
104
|
-
const resp = await fetch(`${getBaseUrl()}/me`, {
|
|
124
|
+
const resp = await fetch(`${getBaseUrl()}/me?include=inbox`, {
|
|
105
125
|
method: "GET",
|
|
106
126
|
headers: mcpConfig.headers,
|
|
107
127
|
});
|
|
@@ -110,7 +130,7 @@ export async function handleHook(): Promise<void> {
|
|
|
110
130
|
return;
|
|
111
131
|
}
|
|
112
132
|
|
|
113
|
-
return (await resp.json()) as
|
|
133
|
+
return (await resp.json()) as AgentWithInbox;
|
|
114
134
|
} catch {
|
|
115
135
|
// Silently fail
|
|
116
136
|
}
|
|
@@ -118,18 +138,88 @@ export async function handleHook(): Promise<void> {
|
|
|
118
138
|
return;
|
|
119
139
|
};
|
|
120
140
|
|
|
141
|
+
const formatSystemTray = (inbox: InboxSummary): string | null => {
|
|
142
|
+
const {
|
|
143
|
+
unreadCount,
|
|
144
|
+
mentionsCount,
|
|
145
|
+
offeredTasksCount,
|
|
146
|
+
poolTasksCount,
|
|
147
|
+
inProgressCount,
|
|
148
|
+
recentMentions,
|
|
149
|
+
} = inbox;
|
|
150
|
+
|
|
151
|
+
// If all counts are zero, return null (no tray)
|
|
152
|
+
if (
|
|
153
|
+
unreadCount === 0 &&
|
|
154
|
+
offeredTasksCount === 0 &&
|
|
155
|
+
poolTasksCount === 0 &&
|
|
156
|
+
inProgressCount === 0
|
|
157
|
+
) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const lines: string[] = [];
|
|
162
|
+
|
|
163
|
+
// Main tray line
|
|
164
|
+
const parts: string[] = [];
|
|
165
|
+
|
|
166
|
+
// Messages section
|
|
167
|
+
if (unreadCount > 0) {
|
|
168
|
+
const mentionsSuffix = mentionsCount > 0 ? ` (${mentionsCount} @mention)` : "";
|
|
169
|
+
parts.push(`📬 ${unreadCount} unread${mentionsSuffix}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Tasks section
|
|
173
|
+
const taskParts = [
|
|
174
|
+
`${offeredTasksCount} offered`,
|
|
175
|
+
`${poolTasksCount} pool`,
|
|
176
|
+
`${inProgressCount} active`,
|
|
177
|
+
];
|
|
178
|
+
parts.push(`📋 ${taskParts.join(", ")}`);
|
|
179
|
+
|
|
180
|
+
lines.push(parts.join(" | "));
|
|
181
|
+
|
|
182
|
+
// Inline @mentions (up to 3)
|
|
183
|
+
if (recentMentions && recentMentions.length > 0) {
|
|
184
|
+
for (const mention of recentMentions) {
|
|
185
|
+
lines.push(
|
|
186
|
+
` └─ @mention from ${mention.agentName} in #${mention.channelName}: "${mention.content}"`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Nudge - remind to check inbox
|
|
192
|
+
if (unreadCount > 0 || offeredTasksCount > 0) {
|
|
193
|
+
const actions: string[] = [];
|
|
194
|
+
if (unreadCount > 0) actions.push("read-messages");
|
|
195
|
+
if (offeredTasksCount > 0) actions.push("poll-task");
|
|
196
|
+
lines.push(`→ Use ${actions.join(" or ")} to check`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return lines.join("\n");
|
|
200
|
+
};
|
|
201
|
+
|
|
121
202
|
// Ping the server to indicate activity
|
|
122
203
|
await ping();
|
|
123
204
|
|
|
124
205
|
// Get current agent info
|
|
125
206
|
const agentInfo = await getAgentInfo();
|
|
126
207
|
|
|
127
|
-
// Always output agent status
|
|
208
|
+
// Always output agent status with system tray
|
|
128
209
|
if (agentInfo) {
|
|
210
|
+
// Base status line
|
|
129
211
|
console.log(
|
|
130
|
-
`You are registered as ${agentInfo.isLead ? "lead" : "worker"} agent "${agentInfo.name}"
|
|
212
|
+
`You are registered as ${agentInfo.isLead ? "lead" : "worker"} agent "${agentInfo.name}" (ID: ${agentInfo.id}, status: ${agentInfo.status}).`,
|
|
131
213
|
);
|
|
132
214
|
|
|
215
|
+
// System tray (if there's activity)
|
|
216
|
+
if (agentInfo.inbox) {
|
|
217
|
+
const tray = formatSystemTray(agentInfo.inbox);
|
|
218
|
+
if (tray) {
|
|
219
|
+
console.log(tray);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
133
223
|
if (!agentInfo.isLead && agentInfo.status === "busy") {
|
|
134
224
|
console.log(
|
|
135
225
|
`Remember to call store-progress periodically to update the lead agent on your progress as you are currently marked as busy. The comments you leave will be helpful for the lead agent to monitor your work.`,
|
|
@@ -150,15 +240,7 @@ ${hasAgentIdHeader() ? `You have a pre-defined agent ID via header: ${mcpConfig?
|
|
|
150
240
|
case "SessionStart":
|
|
151
241
|
if (!agentInfo) break;
|
|
152
242
|
|
|
153
|
-
|
|
154
|
-
console.log(
|
|
155
|
-
`As the lead agent, you are responsible for coordinating the swarm to fulfill the user's request efficiently. Use the ${SERVER_NAME} tools to assign tasks to worker agents and monitor their progress.`,
|
|
156
|
-
);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(
|
|
159
|
-
`As a worker agent, you should call the poll-task tool to wait for tasks assigned by the lead agent, unless specified otherwise.`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
243
|
+
// Covered by base system prompt
|
|
162
244
|
break;
|
|
163
245
|
|
|
164
246
|
case "PreCompact":
|
|
@@ -192,6 +274,12 @@ ${hasAgentIdHeader() ? `You have a pre-defined agent ID via header: ${mcpConfig?
|
|
|
192
274
|
break;
|
|
193
275
|
|
|
194
276
|
case "Stop":
|
|
277
|
+
// Save PM2 processes before shutdown (for container restart persistence)
|
|
278
|
+
try {
|
|
279
|
+
await Bun.$`pm2 save`.quiet();
|
|
280
|
+
} catch {
|
|
281
|
+
// PM2 not available or no processes - silently ignore
|
|
282
|
+
}
|
|
195
283
|
// Mark the agent as offline
|
|
196
284
|
await close();
|
|
197
285
|
break;
|