agent-worker 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -343
- package/dist/backends-DG5igQii.mjs +3 -0
- package/dist/{backends-BOAkfYyL.mjs → backends-DLaP0rMW.mjs} +45 -30
- package/dist/cli/index.mjs +1647 -1550
- package/dist/context-BqEyt2SF.mjs +4 -0
- package/dist/{display-pretty-CWoRE9FY.mjs → display-pretty-BCJq5v9d.mjs} +3 -3
- package/dist/index.d.mts +42 -30
- package/dist/index.mjs +561 -5
- package/dist/{logger-C3ekEOzi.mjs → logger-Bfdo83xL.mjs} +2 -2
- package/dist/memory-provider-BtLYtdQH.mjs +70 -0
- package/dist/{workflow-BGpkJlFb.mjs → runner-CQJYnM7D.mjs} +38 -277
- package/dist/worker-CJ5_b2_q.mjs +446 -0
- package/dist/workflow-CNlUyGit.mjs +247 -0
- package/package.json +8 -1
- package/dist/backends-e6gCxRZ9.mjs +0 -3
- package/dist/context-dgI2YCGG.mjs +0 -4
- package/dist/mcp-server-BQCQxv2v.mjs +0 -573
- package/dist/skills-xNmQZf8e.mjs +0 -1002
package/dist/cli/index.mjs
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { jsonSchema, tool } from "ai";
|
|
2
|
+
import { D as FRONTIER_MODELS, T as normalizeBackendType, j as getDefaultModel, n as createBackend } from "../backends-DLaP0rMW.mjs";
|
|
3
|
+
import { t as AgentWorker } from "../worker-CJ5_b2_q.mjs";
|
|
5
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
7
6
|
import { appendFile, mkdir, open, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { z } from "zod";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import { Command, Option } from "commander";
|
|
11
|
-
import {
|
|
11
|
+
import { Hono } from "hono";
|
|
12
|
+
import { streamSSE } from "hono/streaming";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
15
|
import { nanoid } from "nanoid";
|
|
16
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
13
17
|
|
|
14
18
|
//#region rolldown:runtime
|
|
15
19
|
var __defProp = Object.defineProperty;
|
|
@@ -30,509 +34,784 @@ var __exportAll = (all, symbols) => {
|
|
|
30
34
|
//#endregion
|
|
31
35
|
//#region src/daemon/registry.ts
|
|
32
36
|
/**
|
|
33
|
-
*
|
|
37
|
+
* Daemon Registry
|
|
34
38
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* No shared mutable state → no locks needed.
|
|
39
|
+
* Discovery: daemon.json = { pid, host, port, startedAt }
|
|
40
|
+
* One daemon process on a fixed port. Clients read daemon.json to find it.
|
|
38
41
|
*
|
|
39
|
-
*
|
|
42
|
+
* Legacy: per-session files in sessions/{id}.json (deprecated, kept for transition)
|
|
40
43
|
*/
|
|
41
44
|
const CONFIG_DIR = join(homedir(), ".agent-worker");
|
|
42
45
|
const SESSIONS_DIR = join(CONFIG_DIR, "sessions");
|
|
46
|
+
const DEFAULT_PORT = 5099;
|
|
47
|
+
const DAEMON_FILE = join(CONFIG_DIR, "daemon.json");
|
|
43
48
|
const DEFAULT_FILE = join(CONFIG_DIR, "default");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*/
|
|
49
|
-
function parseDuration(value) {
|
|
50
|
-
const match = value.match(DURATION_RE);
|
|
51
|
-
if (!match) return null;
|
|
52
|
-
return parseFloat(match[1]) * {
|
|
53
|
-
ms: 1,
|
|
54
|
-
s: 1e3,
|
|
55
|
-
m: 60 * 1e3,
|
|
56
|
-
h: 3600 * 1e3,
|
|
57
|
-
d: 1440 * 60 * 1e3
|
|
58
|
-
}[match[2]];
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Resolve a wakeup value into a typed schedule.
|
|
62
|
-
* - number → interval (ms)
|
|
63
|
-
* - "30s"/"5m"/"2h" → interval (converted to ms)
|
|
64
|
-
* - cron expression → cron
|
|
65
|
-
*/
|
|
66
|
-
function resolveSchedule(config) {
|
|
67
|
-
const { wakeup, prompt } = config;
|
|
68
|
-
if (typeof wakeup === "number") {
|
|
69
|
-
if (wakeup <= 0) throw new Error("Wakeup interval must be positive");
|
|
70
|
-
return {
|
|
71
|
-
type: "interval",
|
|
72
|
-
ms: wakeup,
|
|
73
|
-
prompt
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
const ms = parseDuration(wakeup);
|
|
77
|
-
if (ms !== null) {
|
|
78
|
-
if (ms <= 0) throw new Error("Wakeup duration must be positive");
|
|
79
|
-
return {
|
|
80
|
-
type: "interval",
|
|
81
|
-
ms,
|
|
82
|
-
prompt
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
type: "cron",
|
|
87
|
-
expr: wakeup,
|
|
88
|
-
prompt
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
function ensureDirs() {
|
|
92
|
-
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
93
|
-
}
|
|
94
|
-
/** Path to a session's metadata file */
|
|
95
|
-
function sessionFile(id) {
|
|
96
|
-
return join(SESSIONS_DIR, `${id}.json`);
|
|
97
|
-
}
|
|
98
|
-
/** Read all session metadata files from the sessions directory */
|
|
99
|
-
function readAllSessions() {
|
|
100
|
-
ensureDirs();
|
|
101
|
-
const entries = [];
|
|
102
|
-
for (const file of readdirSync(SESSIONS_DIR)) {
|
|
103
|
-
if (!file.endsWith(".json")) continue;
|
|
104
|
-
try {
|
|
105
|
-
entries.push(JSON.parse(readFileSync(join(SESSIONS_DIR, file), "utf-8")));
|
|
106
|
-
} catch {}
|
|
107
|
-
}
|
|
108
|
-
return entries;
|
|
49
|
+
/** Write daemon.json for client discovery */
|
|
50
|
+
function writeDaemonInfo(info) {
|
|
51
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
52
|
+
writeFileSync(DAEMON_FILE, JSON.stringify(info, null, 2));
|
|
109
53
|
}
|
|
110
|
-
|
|
54
|
+
/** Read daemon.json. Returns null if missing or malformed. */
|
|
55
|
+
function readDaemonInfo() {
|
|
111
56
|
try {
|
|
112
|
-
return readFileSync(
|
|
57
|
+
return JSON.parse(readFileSync(DAEMON_FILE, "utf-8"));
|
|
113
58
|
} catch {
|
|
114
|
-
return;
|
|
59
|
+
return null;
|
|
115
60
|
}
|
|
116
61
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
writeFileSync(sessionFile(info.id), JSON.stringify(info, null, 2));
|
|
120
|
-
if (!readDefault()) writeFileSync(DEFAULT_FILE, info.id);
|
|
121
|
-
}
|
|
122
|
-
function unregisterSession(idOrName) {
|
|
123
|
-
const info = getSessionInfo(idOrName);
|
|
124
|
-
if (!info) return;
|
|
62
|
+
/** Remove daemon.json (on shutdown) */
|
|
63
|
+
function removeDaemonInfo() {
|
|
125
64
|
try {
|
|
126
|
-
unlinkSync(
|
|
65
|
+
unlinkSync(DAEMON_FILE);
|
|
127
66
|
} catch {}
|
|
128
|
-
if (readDefault() === info.id) {
|
|
129
|
-
const remaining = readAllSessions();
|
|
130
|
-
if (remaining.length > 0) writeFileSync(DEFAULT_FILE, remaining[0].id);
|
|
131
|
-
else try {
|
|
132
|
-
unlinkSync(DEFAULT_FILE);
|
|
133
|
-
} catch {}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
function getSessionInfo(idOrName) {
|
|
137
|
-
if (!idOrName) {
|
|
138
|
-
const defaultId = readDefault();
|
|
139
|
-
if (defaultId) return getSessionInfo(defaultId);
|
|
140
|
-
const sessions = readAllSessions();
|
|
141
|
-
if (sessions.length === 1) return sessions[0] ?? null;
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
const filePath = sessionFile(idOrName);
|
|
145
|
-
if (existsSync(filePath)) try {
|
|
146
|
-
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
147
|
-
} catch {
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
const sessions = readAllSessions();
|
|
151
|
-
const byName = sessions.find((s) => s.name === idOrName);
|
|
152
|
-
if (byName) return byName;
|
|
153
|
-
const prefixMatches = sessions.filter((s) => s.id.startsWith(idOrName));
|
|
154
|
-
if (prefixMatches.length === 1) return prefixMatches[0];
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
function listSessions() {
|
|
158
|
-
return readAllSessions();
|
|
159
|
-
}
|
|
160
|
-
function setDefaultSession(idOrName) {
|
|
161
|
-
const info = getSessionInfo(idOrName);
|
|
162
|
-
if (!info) return false;
|
|
163
|
-
ensureDirs();
|
|
164
|
-
writeFileSync(DEFAULT_FILE, info.id);
|
|
165
|
-
return true;
|
|
166
67
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
68
|
+
/** Check if a daemon is already running (daemon.json exists + PID alive) */
|
|
69
|
+
function isDaemonRunning() {
|
|
70
|
+
const info = readDaemonInfo();
|
|
71
|
+
if (!info) return null;
|
|
170
72
|
try {
|
|
171
73
|
process.kill(info.pid, 0);
|
|
172
|
-
return
|
|
74
|
+
return info;
|
|
173
75
|
} catch {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (info.readyFile && existsSync(info.readyFile)) unlinkSync(info.readyFile);
|
|
177
|
-
unregisterSession(info.id);
|
|
178
|
-
return false;
|
|
76
|
+
removeDaemonInfo();
|
|
77
|
+
return null;
|
|
179
78
|
}
|
|
180
79
|
}
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/agent/handle.ts
|
|
181
83
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
84
|
+
* LocalWorker — In-process execution via AgentWorker.
|
|
85
|
+
*
|
|
86
|
+
* Wraps an AgentWorker instance. State lives in the AgentWorker's memory.
|
|
87
|
+
* This is the default for single-machine deployments.
|
|
184
88
|
*/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
89
|
+
var LocalWorker = class {
|
|
90
|
+
engine;
|
|
91
|
+
constructor(config, restore) {
|
|
92
|
+
const backend = config.backend !== "default" ? createBackend({
|
|
93
|
+
type: config.backend,
|
|
94
|
+
model: config.model
|
|
95
|
+
}) : void 0;
|
|
96
|
+
this.engine = new AgentWorker({
|
|
97
|
+
model: config.model,
|
|
98
|
+
system: config.system,
|
|
99
|
+
tools: {},
|
|
100
|
+
backend
|
|
101
|
+
}, restore);
|
|
102
|
+
}
|
|
103
|
+
send(input, options) {
|
|
104
|
+
return this.engine.send(input, options);
|
|
105
|
+
}
|
|
106
|
+
sendStream(input, options) {
|
|
107
|
+
return this.engine.sendStream(input, options);
|
|
108
|
+
}
|
|
109
|
+
getState() {
|
|
110
|
+
return this.engine.getState();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region src/agent/store.ts
|
|
116
|
+
/**
|
|
117
|
+
* In-memory state store. State is lost when the daemon stops.
|
|
118
|
+
* Suitable for development and single-machine deployments.
|
|
119
|
+
*/
|
|
120
|
+
var MemoryStateStore = class {
|
|
121
|
+
states = /* @__PURE__ */ new Map();
|
|
122
|
+
async load(agentId) {
|
|
123
|
+
return this.states.get(agentId) ?? null;
|
|
192
124
|
}
|
|
193
|
-
|
|
125
|
+
async save(agentId, state) {
|
|
126
|
+
this.states.set(agentId, state);
|
|
127
|
+
}
|
|
128
|
+
async delete(agentId) {
|
|
129
|
+
this.states.delete(agentId);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/daemon/serve.ts
|
|
135
|
+
/**
|
|
136
|
+
* Start an HTTP server for a Hono app.
|
|
137
|
+
* Auto-detects runtime: Bun.serve() when available, @hono/node-server otherwise.
|
|
138
|
+
*/
|
|
139
|
+
async function startHttpServer(app, options) {
|
|
140
|
+
if (typeof globalThis.Bun !== "undefined") return startBun(app, options);
|
|
141
|
+
return startNode(app, options);
|
|
142
|
+
}
|
|
143
|
+
function startBun(app, options) {
|
|
144
|
+
const server = Bun.serve({
|
|
145
|
+
fetch: app.fetch,
|
|
146
|
+
port: options.port,
|
|
147
|
+
hostname: options.hostname
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
port: server.port ?? options.port,
|
|
151
|
+
close: async () => server.stop(true)
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
async function startNode(app, options) {
|
|
155
|
+
const mod = await import("@hono/node-server");
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const server = mod.serve({
|
|
158
|
+
fetch: app.fetch,
|
|
159
|
+
port: options.port,
|
|
160
|
+
hostname: options.hostname
|
|
161
|
+
}, (info) => {
|
|
162
|
+
resolve({
|
|
163
|
+
port: info.port,
|
|
164
|
+
close: () => new Promise((r) => server.close(() => r()))
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
server.on("error", reject);
|
|
168
|
+
});
|
|
194
169
|
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/workflow/context/event-log.ts
|
|
173
|
+
var EventLog = class {
|
|
174
|
+
constructor(provider) {
|
|
175
|
+
this.provider = provider;
|
|
176
|
+
}
|
|
177
|
+
/** Record a tool invocation (MCP, SDK, or backend native) */
|
|
178
|
+
toolCall(agent, name, args, source) {
|
|
179
|
+
this.provider.appendChannel(agent, `${name}(${args})`, {
|
|
180
|
+
kind: "tool_call",
|
|
181
|
+
toolCall: {
|
|
182
|
+
name,
|
|
183
|
+
args,
|
|
184
|
+
source
|
|
185
|
+
}
|
|
186
|
+
}).catch(() => {});
|
|
187
|
+
}
|
|
188
|
+
/** Record an operational log (workflow lifecycle, warnings, errors) */
|
|
189
|
+
system(from, message) {
|
|
190
|
+
this.provider.appendChannel(from, message, { kind: "system" }).catch(() => {});
|
|
191
|
+
}
|
|
192
|
+
/** Record backend streaming text output (not tool calls) */
|
|
193
|
+
output(agent, text) {
|
|
194
|
+
this.provider.appendChannel(agent, text, { kind: "output" }).catch(() => {});
|
|
195
|
+
}
|
|
196
|
+
/** Record debug-level detail (only shown with --debug) */
|
|
197
|
+
debug(from, message) {
|
|
198
|
+
this.provider.appendChannel(from, message, { kind: "debug" }).catch(() => {});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/workflow/context/mcp/helpers.ts
|
|
195
204
|
/**
|
|
196
|
-
*
|
|
205
|
+
* Extract agent ID from MCP extra context.
|
|
206
|
+
* Session ID format: "agentName-uuid8chars" — extract agent name.
|
|
197
207
|
*/
|
|
198
|
-
function
|
|
199
|
-
|
|
208
|
+
function getAgentId(extra) {
|
|
209
|
+
if (!extra || typeof extra !== "object") return void 0;
|
|
210
|
+
if ("sessionId" in extra && typeof extra.sessionId === "string") {
|
|
211
|
+
const sid = extra.sessionId;
|
|
212
|
+
const match = sid.match(/^(.+)-[0-9a-f]{8}$/);
|
|
213
|
+
return match ? match[1] : sid;
|
|
214
|
+
}
|
|
215
|
+
if ("meta" in extra && extra.meta && typeof extra.meta === "object") {
|
|
216
|
+
const meta = extra.meta;
|
|
217
|
+
if ("agentId" in meta && typeof meta.agentId === "string") return meta.agentId;
|
|
218
|
+
}
|
|
200
219
|
}
|
|
201
220
|
/**
|
|
202
|
-
*
|
|
221
|
+
* Format inbox messages for JSON display.
|
|
203
222
|
*/
|
|
204
|
-
function
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
function formatInbox(messages) {
|
|
224
|
+
if (messages.length === 0) return JSON.stringify({
|
|
225
|
+
messages: [],
|
|
226
|
+
count: 0
|
|
227
|
+
});
|
|
228
|
+
return JSON.stringify({
|
|
229
|
+
messages: messages.map((m) => ({
|
|
230
|
+
id: m.entry.id,
|
|
231
|
+
from: m.entry.from,
|
|
232
|
+
content: m.entry.content,
|
|
233
|
+
timestamp: m.entry.timestamp,
|
|
234
|
+
priority: m.priority
|
|
235
|
+
})),
|
|
236
|
+
count: messages.length
|
|
237
|
+
});
|
|
210
238
|
}
|
|
211
239
|
/**
|
|
212
|
-
*
|
|
240
|
+
* Format tool call parameters as a concise string.
|
|
213
241
|
*/
|
|
214
|
-
function
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
function formatToolParams(params) {
|
|
243
|
+
return Object.entries(params).filter(([_, v]) => v !== void 0).map(([k, v]) => {
|
|
244
|
+
const val = typeof v === "string" && v.length > 50 ? v.slice(0, 50) + "..." : v;
|
|
245
|
+
return `${k}=${JSON.stringify(val)}`;
|
|
246
|
+
}).join(", ");
|
|
217
247
|
}
|
|
218
248
|
/**
|
|
219
|
-
*
|
|
220
|
-
* Checks existing sessions to avoid collisions.
|
|
249
|
+
* Create a logTool function that records tool calls via EventLog.
|
|
221
250
|
*/
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
for (let letter = 0; letter < 26; letter++) for (let digit = 0; digit < 10; digit++) {
|
|
230
|
-
const name = String.fromCharCode(97 + letter) + digit;
|
|
231
|
-
if (!usedNames.has(name)) return name;
|
|
232
|
-
}
|
|
233
|
-
return `agent-${crypto.randomUUID().slice(0, 6)}`;
|
|
251
|
+
function createLogTool(eventLog) {
|
|
252
|
+
return (tool, agent, params) => {
|
|
253
|
+
if (!agent) return;
|
|
254
|
+
const args = formatToolParams(params);
|
|
255
|
+
eventLog.toolCall(agent, tool, args, "mcp");
|
|
256
|
+
};
|
|
234
257
|
}
|
|
235
258
|
|
|
236
259
|
//#endregion
|
|
237
|
-
//#region src/
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
//#region src/workflow/context/mcp/channel.ts
|
|
261
|
+
const CHANNEL_MSG_LIMIT = 2e3;
|
|
262
|
+
function registerChannelTools(server, ctx, options) {
|
|
263
|
+
const { provider, getAgentId, logTool } = ctx;
|
|
264
|
+
const { onMention } = options ?? {};
|
|
265
|
+
server.tool("channel_send", `Send a message to the shared channel. Use @agent to mention/notify. Use "to" for private DMs. Long messages (> ${CHANNEL_MSG_LIMIT} chars) are automatically converted to resources.`, {
|
|
266
|
+
message: z.string().describe("Message content, can include @mentions like @reviewer or @coder. Long messages are auto-converted to resources."),
|
|
267
|
+
to: z.string().optional().describe("Send as DM to a specific agent (private, only you and recipient see it)")
|
|
268
|
+
}, async ({ message, to }, extra) => {
|
|
269
|
+
const from = getAgentId(extra) || "anonymous";
|
|
270
|
+
logTool("channel_send", from, {
|
|
271
|
+
message,
|
|
272
|
+
to
|
|
273
|
+
});
|
|
274
|
+
const sendOpts = to ? { to } : void 0;
|
|
275
|
+
const msg = await provider.smartSend(from, message, sendOpts);
|
|
276
|
+
for (const target of msg.mentions) onMention?.(from, target, msg);
|
|
277
|
+
if (to && !msg.mentions.includes(to)) onMention?.(from, to, msg);
|
|
278
|
+
return { content: [{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: JSON.stringify({
|
|
281
|
+
status: "sent",
|
|
282
|
+
timestamp: msg.timestamp,
|
|
283
|
+
mentions: msg.mentions,
|
|
284
|
+
to: msg.to
|
|
285
|
+
})
|
|
286
|
+
}] };
|
|
287
|
+
});
|
|
288
|
+
server.tool("channel_read", "Read messages from the shared channel. DMs and logs are automatically filtered based on your identity.", {
|
|
289
|
+
since: z.string().optional().describe("Read entries after this timestamp (ISO format)"),
|
|
290
|
+
limit: z.number().optional().describe("Maximum entries to return")
|
|
291
|
+
}, async ({ since, limit }, extra) => {
|
|
292
|
+
const agent = getAgentId(extra);
|
|
293
|
+
logTool("channel_read", agent, {
|
|
294
|
+
since,
|
|
295
|
+
limit
|
|
296
|
+
});
|
|
297
|
+
const entries = await provider.readChannel({
|
|
298
|
+
since,
|
|
299
|
+
limit,
|
|
300
|
+
agent
|
|
301
|
+
});
|
|
302
|
+
return { content: [{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: JSON.stringify(entries)
|
|
305
|
+
}] };
|
|
306
|
+
});
|
|
254
307
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/workflow/context/mcp/resource.ts
|
|
311
|
+
function registerResourceTools(server, ctx) {
|
|
312
|
+
const { provider, getAgentId, logTool } = ctx;
|
|
313
|
+
server.tool("resource_create", "Store large content as a resource. Returns a reference (resource:id) usable in channel messages or documents.", {
|
|
314
|
+
content: z.string().describe("Content to store as resource"),
|
|
315
|
+
type: z.enum([
|
|
316
|
+
"markdown",
|
|
317
|
+
"json",
|
|
318
|
+
"text",
|
|
319
|
+
"diff"
|
|
320
|
+
]).optional().describe("Content type hint (default: text)")
|
|
321
|
+
}, async ({ content, type }, extra) => {
|
|
322
|
+
const createdBy = getAgentId(extra) || "anonymous";
|
|
323
|
+
logTool("resource_create", createdBy, {
|
|
324
|
+
type,
|
|
325
|
+
contentLen: content.length
|
|
326
|
+
});
|
|
327
|
+
const result = await provider.createResource(content, createdBy, type);
|
|
328
|
+
return { content: [{
|
|
329
|
+
type: "text",
|
|
330
|
+
text: JSON.stringify({
|
|
331
|
+
id: result.id,
|
|
332
|
+
ref: result.ref,
|
|
333
|
+
hint: `Use [description](${result.ref}) in messages or documents`
|
|
334
|
+
})
|
|
335
|
+
}] };
|
|
336
|
+
});
|
|
337
|
+
server.tool("resource_read", "Read resource content by ID. Use when you encounter resource:id references.", { id: z.string().describe("Resource ID (e.g., res_abc123)") }, async ({ id }) => {
|
|
338
|
+
const content = await provider.readResource(id);
|
|
339
|
+
if (content === null) return { content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: JSON.stringify({ error: `Resource not found: ${id}` })
|
|
342
|
+
}] };
|
|
343
|
+
return { content: [{
|
|
344
|
+
type: "text",
|
|
345
|
+
text: content
|
|
346
|
+
}] };
|
|
347
|
+
});
|
|
259
348
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/workflow/context/mcp/inbox.ts
|
|
352
|
+
function registerInboxTools(server, ctx, options) {
|
|
353
|
+
const { provider, getAgentId, logTool } = ctx;
|
|
354
|
+
const { debugLog } = options ?? {};
|
|
355
|
+
server.tool("my_inbox", "Check your unread inbox messages. Does NOT acknowledge — use my_inbox_ack after processing.", {}, async (_args, extra) => {
|
|
356
|
+
const agent = getAgentId(extra) || "anonymous";
|
|
357
|
+
logTool("my_inbox", agent, {});
|
|
358
|
+
const messages = await provider.getInbox(agent);
|
|
359
|
+
if (debugLog && messages.length > 0) debugLog(`[mcp:${agent}] my_inbox → ${messages.length} unread`);
|
|
360
|
+
return { content: [{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: formatInbox(messages)
|
|
363
|
+
}] };
|
|
364
|
+
});
|
|
365
|
+
server.tool("my_inbox_ack", "Acknowledge inbox messages up to a message ID. Call after processing messages.", { until: z.string().describe("Acknowledge messages up to and including this message ID") }, async ({ until }, extra) => {
|
|
366
|
+
const agent = getAgentId(extra) || "anonymous";
|
|
367
|
+
logTool("my_inbox_ack", agent, { until });
|
|
368
|
+
await provider.ackInbox(agent, until);
|
|
369
|
+
return { content: [{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: JSON.stringify({
|
|
372
|
+
status: "acknowledged",
|
|
373
|
+
until
|
|
374
|
+
})
|
|
375
|
+
}] };
|
|
376
|
+
});
|
|
377
|
+
server.tool("my_status_set", "Update your status and current task. Call when starting or completing work.", {
|
|
378
|
+
task: z.string().optional().describe("Current task description (what you're working on)"),
|
|
379
|
+
state: z.enum(["idle", "running"]).optional().describe("Agent state (running = working, idle = available)"),
|
|
380
|
+
metadata: z.record(z.unknown()).optional().describe("Additional metadata (e.g., PR number, file path)")
|
|
381
|
+
}, async (args, extra) => {
|
|
382
|
+
const agent = getAgentId(extra) || "anonymous";
|
|
383
|
+
logTool("my_status_set", agent, args);
|
|
384
|
+
const status = {};
|
|
385
|
+
if (args.task !== void 0) status.task = args.task;
|
|
386
|
+
if (args.state !== void 0) status.state = args.state;
|
|
387
|
+
if (args.metadata !== void 0) status.metadata = args.metadata;
|
|
388
|
+
await provider.setAgentStatus(agent, status);
|
|
389
|
+
return { content: [{
|
|
390
|
+
type: "text",
|
|
391
|
+
text: JSON.stringify({
|
|
392
|
+
status: "updated",
|
|
393
|
+
agent,
|
|
394
|
+
...status
|
|
395
|
+
})
|
|
396
|
+
}] };
|
|
397
|
+
});
|
|
285
398
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
function
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/workflow/context/mcp/team.ts
|
|
402
|
+
function registerTeamTools(server, ctx) {
|
|
403
|
+
const { provider, validAgents, getAgentId, logTool } = ctx;
|
|
404
|
+
server.tool("team_members", "List all agents in this workflow. Use to discover who you can @mention. Optionally includes agent status (state, current task).", { includeStatus: z.boolean().optional().describe("Include agent status information") }, async (args, extra) => {
|
|
405
|
+
const currentAgent = getAgentId(extra) || "anonymous";
|
|
406
|
+
const includeStatus = args.includeStatus ?? false;
|
|
407
|
+
const agents = validAgents.map((name) => ({
|
|
408
|
+
name,
|
|
409
|
+
mention: `@${name}`,
|
|
410
|
+
isYou: name === currentAgent
|
|
411
|
+
}));
|
|
412
|
+
const result = {
|
|
413
|
+
agents,
|
|
414
|
+
count: agents.length,
|
|
415
|
+
hint: "Use @agent in channel_send to mention other agents"
|
|
416
|
+
};
|
|
417
|
+
if (includeStatus) result.status = await provider.listAgentStatus();
|
|
418
|
+
return { content: [{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: JSON.stringify(result)
|
|
421
|
+
}] };
|
|
422
|
+
});
|
|
423
|
+
server.tool("team_doc_read", "Read a shared team document.", { file: z.string().optional().describe("Document file path (default: notes.md)") }, async ({ file }, extra) => {
|
|
424
|
+
logTool("team_doc_read", getAgentId(extra), { file });
|
|
425
|
+
return { content: [{
|
|
426
|
+
type: "text",
|
|
427
|
+
text: await provider.readDocument(file) || "(empty document)"
|
|
428
|
+
}] };
|
|
429
|
+
});
|
|
430
|
+
server.tool("team_doc_write", "Write/replace a shared team document.", {
|
|
431
|
+
content: z.string().describe("New document content (replaces existing)"),
|
|
432
|
+
file: z.string().optional().describe("Document file path (default: notes.md)")
|
|
433
|
+
}, async ({ content, file }, extra) => {
|
|
434
|
+
logTool("team_doc_write", getAgentId(extra), {
|
|
435
|
+
file,
|
|
436
|
+
contentLen: content.length
|
|
437
|
+
});
|
|
438
|
+
await provider.writeDocument(content, file);
|
|
439
|
+
return { content: [{
|
|
440
|
+
type: "text",
|
|
441
|
+
text: `Document ${file || "notes.md"} written successfully`
|
|
442
|
+
}] };
|
|
443
|
+
});
|
|
444
|
+
server.tool("team_doc_append", "Append content to a shared team document.", {
|
|
445
|
+
content: z.string().describe("Content to append to the document"),
|
|
446
|
+
file: z.string().optional().describe("Document file path (default: notes.md)")
|
|
447
|
+
}, async ({ content, file }, extra) => {
|
|
448
|
+
logTool("team_doc_append", getAgentId(extra), {
|
|
449
|
+
file,
|
|
450
|
+
contentLen: content.length
|
|
451
|
+
});
|
|
452
|
+
await provider.appendDocument(content, file);
|
|
453
|
+
return { content: [{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: `Content appended to ${file || "notes.md"}`
|
|
456
|
+
}] };
|
|
457
|
+
});
|
|
458
|
+
server.tool("team_doc_list", "List all shared team document files.", {}, async () => {
|
|
459
|
+
const files = await provider.listDocuments();
|
|
460
|
+
return { content: [{
|
|
461
|
+
type: "text",
|
|
462
|
+
text: JSON.stringify({
|
|
463
|
+
files,
|
|
464
|
+
count: files.length
|
|
465
|
+
})
|
|
466
|
+
}] };
|
|
467
|
+
});
|
|
468
|
+
server.tool("team_doc_create", "Create a new shared team document file.", {
|
|
469
|
+
file: z.string().describe("Document file path (e.g., \"findings/auth.md\")"),
|
|
470
|
+
content: z.string().describe("Initial document content")
|
|
471
|
+
}, async ({ file, content }) => {
|
|
472
|
+
await provider.createDocument(file, content);
|
|
473
|
+
return { content: [{
|
|
474
|
+
type: "text",
|
|
475
|
+
text: `Document ${file} created successfully`
|
|
476
|
+
}] };
|
|
477
|
+
});
|
|
299
478
|
}
|
|
479
|
+
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/workflow/context/proposals.ts
|
|
300
482
|
/**
|
|
301
|
-
*
|
|
483
|
+
* Format a proposal for display
|
|
302
484
|
*/
|
|
303
|
-
function
|
|
304
|
-
|
|
485
|
+
function formatProposal(proposal) {
|
|
486
|
+
const lines = [];
|
|
487
|
+
lines.push(`📋 **${proposal.title}** (${proposal.id})`);
|
|
488
|
+
lines.push(`Type: ${proposal.type} | Status: ${proposal.status}`);
|
|
489
|
+
if (proposal.description) lines.push(`\n${proposal.description}`);
|
|
490
|
+
lines.push("\nOptions:");
|
|
491
|
+
for (const option of proposal.options) {
|
|
492
|
+
const count = proposal.result?.counts[option.id] || 0;
|
|
493
|
+
const marker = proposal.result?.winner === option.id ? "✓ " : " ";
|
|
494
|
+
lines.push(`${marker}- ${option.label} (${option.id}): ${count} votes`);
|
|
495
|
+
}
|
|
496
|
+
if (proposal.result && Object.keys(proposal.result.votes).length > 0) {
|
|
497
|
+
lines.push("\nVotes:");
|
|
498
|
+
for (const [voter, choice] of Object.entries(proposal.result.votes)) lines.push(` @${voter} → ${choice}`);
|
|
499
|
+
}
|
|
500
|
+
if (proposal.status === "active" && proposal.expiresAt) {
|
|
501
|
+
const remaining = new Date(proposal.expiresAt).getTime() - Date.now();
|
|
502
|
+
const minutes = Math.max(0, Math.floor(remaining / 6e4));
|
|
503
|
+
lines.push(`\nExpires in: ${minutes} minutes`);
|
|
504
|
+
}
|
|
505
|
+
if (proposal.status === "resolved" && proposal.result?.winner) {
|
|
506
|
+
const winningOption = proposal.options.find((o) => o.id === proposal.result?.winner);
|
|
507
|
+
lines.push(`\n🏆 Winner: ${winningOption?.label || proposal.result.winner}`);
|
|
508
|
+
}
|
|
509
|
+
return lines.join("\n");
|
|
305
510
|
}
|
|
306
511
|
/**
|
|
307
|
-
*
|
|
308
|
-
* Searches forward minute-by-minute, up to 1 year.
|
|
309
|
-
* Returns the Date of the next match.
|
|
512
|
+
* Format multiple proposals as a summary
|
|
310
513
|
*/
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
514
|
+
function formatProposalList(proposals) {
|
|
515
|
+
if (proposals.length === 0) return "(no proposals)";
|
|
516
|
+
return proposals.map((p) => {
|
|
517
|
+
const votes = Object.keys(p.result?.votes || {}).length;
|
|
518
|
+
return `- ${p.id}: ${p.title} [${p.status}] (${votes} votes)`;
|
|
519
|
+
}).join("\n");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/workflow/context/mcp/proposal.ts
|
|
524
|
+
function registerProposalTools(server, ctx, proposalManager) {
|
|
525
|
+
const { provider, validAgents, getAgentId } = ctx;
|
|
526
|
+
server.tool("team_proposal_create", "Create a new proposal for team voting. Use for decisions, elections, approvals, or assignments.", {
|
|
527
|
+
type: z.enum([
|
|
528
|
+
"election",
|
|
529
|
+
"decision",
|
|
530
|
+
"approval",
|
|
531
|
+
"assignment"
|
|
532
|
+
]).describe("Type of proposal"),
|
|
533
|
+
title: z.string().describe("Brief title for the proposal"),
|
|
534
|
+
description: z.string().optional().describe("Detailed description"),
|
|
535
|
+
options: z.array(z.object({
|
|
536
|
+
id: z.string().describe("Unique option identifier"),
|
|
537
|
+
label: z.string().describe("Display label for the option")
|
|
538
|
+
})).optional().describe("Voting options (required except for approval type)"),
|
|
539
|
+
resolution: z.object({
|
|
540
|
+
type: z.enum([
|
|
541
|
+
"plurality",
|
|
542
|
+
"majority",
|
|
543
|
+
"unanimous"
|
|
544
|
+
]).optional().describe("How to determine winner"),
|
|
545
|
+
quorum: z.number().optional().describe("Minimum votes required"),
|
|
546
|
+
tieBreaker: z.enum([
|
|
547
|
+
"first",
|
|
548
|
+
"random",
|
|
549
|
+
"creator-decides"
|
|
550
|
+
]).optional().describe("How to break ties")
|
|
551
|
+
}).optional().describe("Resolution rules"),
|
|
552
|
+
binding: z.boolean().optional().describe("Whether result is binding (default: true)"),
|
|
553
|
+
timeoutSeconds: z.number().optional().describe("Timeout in seconds (default: 3600)")
|
|
554
|
+
}, async (params, extra) => {
|
|
555
|
+
const createdBy = getAgentId(extra) || "anonymous";
|
|
556
|
+
try {
|
|
557
|
+
const proposal = proposalManager.create({
|
|
558
|
+
type: params.type,
|
|
559
|
+
title: params.title,
|
|
560
|
+
description: params.description,
|
|
561
|
+
options: params.options,
|
|
562
|
+
resolution: params.resolution,
|
|
563
|
+
binding: params.binding,
|
|
564
|
+
timeoutSeconds: params.timeoutSeconds,
|
|
565
|
+
createdBy
|
|
566
|
+
});
|
|
567
|
+
const optionsList = proposal.options.map((o) => `${o.id}: ${o.label}`).join(", ");
|
|
568
|
+
const otherAgents = validAgents.filter((a) => a !== createdBy).map((a) => `@${a}`).join(" ");
|
|
569
|
+
await provider.appendChannel(createdBy, `Created proposal "${proposal.title}" (${proposal.id})\nOptions: ${optionsList}\nUse team_vote tool to cast your vote. ${otherAgents}`);
|
|
570
|
+
return { content: [{
|
|
571
|
+
type: "text",
|
|
572
|
+
text: JSON.stringify({
|
|
573
|
+
status: "created",
|
|
574
|
+
proposal: {
|
|
575
|
+
id: proposal.id,
|
|
576
|
+
title: proposal.title,
|
|
577
|
+
options: proposal.options,
|
|
578
|
+
expiresAt: proposal.expiresAt
|
|
579
|
+
}
|
|
580
|
+
})
|
|
581
|
+
}] };
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return { content: [{
|
|
584
|
+
type: "text",
|
|
585
|
+
text: JSON.stringify({
|
|
586
|
+
status: "error",
|
|
587
|
+
error: error instanceof Error ? error.message : String(error)
|
|
588
|
+
})
|
|
589
|
+
}] };
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
server.tool("team_vote", "Cast your vote on a team proposal.", {
|
|
593
|
+
proposal: z.string().describe("Proposal ID (e.g., prop-1)"),
|
|
594
|
+
choice: z.string().describe("Option ID to vote for"),
|
|
595
|
+
reason: z.string().optional().describe("Optional reason for your vote")
|
|
596
|
+
}, async ({ proposal: proposalId, choice, reason }, extra) => {
|
|
597
|
+
const voter = getAgentId(extra) || "anonymous";
|
|
598
|
+
const result = proposalManager.vote({
|
|
599
|
+
proposalId,
|
|
600
|
+
voter,
|
|
601
|
+
choice,
|
|
602
|
+
reason
|
|
603
|
+
});
|
|
604
|
+
if (!result.success) return { content: [{
|
|
605
|
+
type: "text",
|
|
606
|
+
text: JSON.stringify({
|
|
607
|
+
status: "error",
|
|
608
|
+
error: result.error
|
|
609
|
+
})
|
|
610
|
+
}] };
|
|
611
|
+
const reasonText = reason ? ` (reason: ${reason})` : "";
|
|
612
|
+
await provider.appendChannel(voter, `Voted "${choice}" on ${proposalId}${reasonText}`);
|
|
613
|
+
if (result.resolved && result.proposal) {
|
|
614
|
+
const winnerOption = result.proposal.options.find((o) => o.id === result.proposal.result?.winner);
|
|
615
|
+
const mentions = Object.keys(result.proposal.result?.votes || {}).map((v) => `@${v}`).join(" ");
|
|
616
|
+
await provider.appendChannel("system", `Proposal ${proposalId} resolved! Winner: ${winnerOption?.label || result.proposal.result?.winner || "none"} ${mentions}`);
|
|
617
|
+
}
|
|
618
|
+
return { content: [{
|
|
619
|
+
type: "text",
|
|
620
|
+
text: JSON.stringify({
|
|
621
|
+
status: "voted",
|
|
622
|
+
proposal: proposalId,
|
|
623
|
+
choice,
|
|
624
|
+
resolved: result.resolved,
|
|
625
|
+
winner: result.proposal?.result?.winner
|
|
626
|
+
})
|
|
627
|
+
}] };
|
|
628
|
+
});
|
|
629
|
+
server.tool("team_proposal_status", "Check status of team proposals. Omit proposal ID to see all active proposals.", { proposal: z.string().optional().describe("Proposal ID (omit for all active)") }, async ({ proposal: proposalId }) => {
|
|
630
|
+
if (proposalId) {
|
|
631
|
+
const proposal = proposalManager.get(proposalId);
|
|
632
|
+
if (!proposal) return { content: [{
|
|
633
|
+
type: "text",
|
|
634
|
+
text: JSON.stringify({
|
|
635
|
+
status: "error",
|
|
636
|
+
error: `Proposal not found: ${proposalId}`
|
|
637
|
+
})
|
|
638
|
+
}] };
|
|
639
|
+
return { content: [{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: formatProposal(proposal)
|
|
642
|
+
}] };
|
|
643
|
+
}
|
|
644
|
+
const activeProposals = proposalManager.list("active");
|
|
645
|
+
return { content: [{
|
|
646
|
+
type: "text",
|
|
647
|
+
text: activeProposals.length > 0 ? formatProposalList(activeProposals) : "(no active proposals)"
|
|
648
|
+
}] };
|
|
649
|
+
});
|
|
650
|
+
server.tool("team_proposal_cancel", "Cancel a proposal you created.", { proposal: z.string().describe("Proposal ID to cancel") }, async ({ proposal: proposalId }, extra) => {
|
|
651
|
+
const cancelledBy = getAgentId(extra) || "anonymous";
|
|
652
|
+
const result = proposalManager.cancel(proposalId, cancelledBy);
|
|
653
|
+
if (!result.success) return { content: [{
|
|
654
|
+
type: "text",
|
|
655
|
+
text: JSON.stringify({
|
|
656
|
+
status: "error",
|
|
657
|
+
error: result.error
|
|
658
|
+
})
|
|
659
|
+
}] };
|
|
660
|
+
await provider.appendChannel(cancelledBy, `Cancelled proposal ${proposalId}`);
|
|
661
|
+
return { content: [{
|
|
662
|
+
type: "text",
|
|
663
|
+
text: JSON.stringify({
|
|
664
|
+
status: "cancelled",
|
|
665
|
+
proposal: proposalId
|
|
666
|
+
})
|
|
667
|
+
}] };
|
|
668
|
+
});
|
|
322
669
|
}
|
|
670
|
+
|
|
671
|
+
//#endregion
|
|
672
|
+
//#region src/workflow/context/mcp/feedback.ts
|
|
323
673
|
/**
|
|
324
|
-
*
|
|
674
|
+
* Register the feedback_submit tool and return a getter for collected entries.
|
|
325
675
|
*/
|
|
326
|
-
function
|
|
327
|
-
|
|
676
|
+
function registerFeedbackTool(server, ctx) {
|
|
677
|
+
const { getAgentId, logTool } = ctx;
|
|
678
|
+
const entries = [];
|
|
679
|
+
server.tool("feedback_submit", "Report a workflow improvement need. Use when you hit something inconvenient — a missing tool, an awkward step, or a capability you wished you had.", {
|
|
680
|
+
target: z.string().describe("The area this is about — a tool name, a workflow step, or a general area (e.g. file search, code review)."),
|
|
681
|
+
type: z.enum([
|
|
682
|
+
"missing",
|
|
683
|
+
"friction",
|
|
684
|
+
"suggestion"
|
|
685
|
+
]).describe("missing: a tool or capability you needed but didn't have. friction: something that works but is awkward or slow. suggestion: a concrete improvement idea."),
|
|
686
|
+
description: z.string().describe("What you needed or what could be improved. Be specific."),
|
|
687
|
+
context: z.string().optional().describe("Optional: what you were trying to do when you hit this.")
|
|
688
|
+
}, async ({ target, type, description, context: ctx }, extra) => {
|
|
689
|
+
logTool("feedback_submit", getAgentId(extra) || "anonymous", {
|
|
690
|
+
target,
|
|
691
|
+
type
|
|
692
|
+
});
|
|
693
|
+
const entry = {
|
|
694
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
695
|
+
target,
|
|
696
|
+
type,
|
|
697
|
+
description,
|
|
698
|
+
...ctx ? { context: ctx } : {}
|
|
699
|
+
};
|
|
700
|
+
if (entries.length >= 50) entries.shift();
|
|
701
|
+
entries.push(entry);
|
|
702
|
+
return { content: [{
|
|
703
|
+
type: "text",
|
|
704
|
+
text: JSON.stringify({ status: "recorded" })
|
|
705
|
+
}] };
|
|
706
|
+
});
|
|
707
|
+
return { getFeedback: () => [...entries] };
|
|
328
708
|
}
|
|
329
709
|
|
|
330
710
|
//#endregion
|
|
331
|
-
//#region src/
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
711
|
+
//#region src/workflow/context/mcp/skills.ts
|
|
712
|
+
function registerSkillsTools(server, provider) {
|
|
713
|
+
server.tool("skills_list", "List all available agent skills with their descriptions.", {}, async () => {
|
|
714
|
+
const skills = provider.list();
|
|
715
|
+
if (skills.length === 0) return { content: [{
|
|
716
|
+
type: "text",
|
|
717
|
+
text: JSON.stringify({ message: "No skills available" })
|
|
718
|
+
}] };
|
|
719
|
+
return { content: [{
|
|
720
|
+
type: "text",
|
|
721
|
+
text: JSON.stringify({ skills: skills.map((s) => ({
|
|
722
|
+
name: s.name,
|
|
723
|
+
description: s.description
|
|
724
|
+
})) })
|
|
725
|
+
}] };
|
|
726
|
+
});
|
|
727
|
+
server.tool("skills_view", "Read the complete SKILL.md file for a skill.", { skillName: z.string().describe("Skill name to view") }, async ({ skillName }) => {
|
|
728
|
+
return { content: [{
|
|
729
|
+
type: "text",
|
|
730
|
+
text: await provider.view(skillName)
|
|
731
|
+
}] };
|
|
732
|
+
});
|
|
733
|
+
server.tool("skills_read", "Read a file within a skill directory (e.g., references/, scripts/, assets/).", {
|
|
734
|
+
skillName: z.string().describe("Skill name"),
|
|
735
|
+
filePath: z.string().describe("Relative file path within the skill (e.g., \"references/search-strategies.md\")")
|
|
736
|
+
}, async ({ skillName, filePath }) => {
|
|
737
|
+
return { content: [{
|
|
738
|
+
type: "text",
|
|
739
|
+
text: await provider.readFile(skillName, filePath)
|
|
740
|
+
}] };
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
//#region src/workflow/context/mcp/server.ts
|
|
746
|
+
/**
|
|
747
|
+
* Context MCP Server — thin orchestrator.
|
|
748
|
+
*
|
|
749
|
+
* Creates an McpServer and registers tools from each category.
|
|
750
|
+
* The actual tool implementations live in their own files:
|
|
751
|
+
* channel.ts, resource.ts, inbox.ts, team.ts, proposal.ts,
|
|
752
|
+
* feedback.ts, skills.ts
|
|
753
|
+
*/
|
|
754
|
+
function createContextMCPServer(options) {
|
|
755
|
+
const { provider, validAgents, name = "workflow-context", version = "1.0.0", onMention, proposalManager, feedback: feedbackEnabled, skills, debugLog } = options;
|
|
756
|
+
const server = new McpServer({
|
|
757
|
+
name,
|
|
758
|
+
version
|
|
759
|
+
});
|
|
760
|
+
const eventLog = new EventLog(provider);
|
|
761
|
+
const ctx = {
|
|
762
|
+
provider,
|
|
763
|
+
eventLog,
|
|
764
|
+
validAgents,
|
|
765
|
+
getAgentId,
|
|
766
|
+
logTool: createLogTool(eventLog)
|
|
337
767
|
};
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
inputSchema: jsonSchema(parameters),
|
|
376
|
-
execute: async () => ({ error: "No implementation - use tool mock to set response" })
|
|
377
|
-
});
|
|
378
|
-
session.addTool(name, t);
|
|
379
|
-
if (needsApproval) session.setApproval(name, true);
|
|
380
|
-
return {
|
|
381
|
-
success: true,
|
|
382
|
-
data: { name }
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
case "tool_mock": {
|
|
386
|
-
const { name, response } = req.payload;
|
|
387
|
-
session.setMockResponse(name, response);
|
|
388
|
-
return {
|
|
389
|
-
success: true,
|
|
390
|
-
data: { name }
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
case "tool_list": return {
|
|
394
|
-
success: true,
|
|
395
|
-
data: session.getTools()
|
|
396
|
-
};
|
|
397
|
-
case "tool_import": {
|
|
398
|
-
if (!session.supportsTools) return {
|
|
399
|
-
success: false,
|
|
400
|
-
error: "Tool import not supported for CLI backends"
|
|
401
|
-
};
|
|
402
|
-
const { filePath } = req.payload;
|
|
403
|
-
if (!filePath || typeof filePath !== "string") return {
|
|
404
|
-
success: false,
|
|
405
|
-
error: "File path is required"
|
|
406
|
-
};
|
|
407
|
-
let module;
|
|
408
|
-
try {
|
|
409
|
-
module = await import(filePath);
|
|
410
|
-
} catch (importError) {
|
|
411
|
-
return {
|
|
412
|
-
success: false,
|
|
413
|
-
error: `Failed to import file: ${(importError instanceof Error ? importError.message : String(importError)).replace(filePath, "<file>")}`
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
let toolsRecord = {};
|
|
417
|
-
if (module.default && typeof module.default === "object" && !Array.isArray(module.default)) toolsRecord = module.default;
|
|
418
|
-
else if (typeof module.default === "function") try {
|
|
419
|
-
const result = await module.default();
|
|
420
|
-
if (result && typeof result === "object" && !Array.isArray(result)) toolsRecord = result;
|
|
421
|
-
} catch (factoryError) {
|
|
422
|
-
return {
|
|
423
|
-
success: false,
|
|
424
|
-
error: `Factory function failed: ${factoryError instanceof Error ? factoryError.message : String(factoryError)}`
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
else if (module.tools && typeof module.tools === "object" && !Array.isArray(module.tools)) toolsRecord = module.tools;
|
|
428
|
-
else return {
|
|
429
|
-
success: false,
|
|
430
|
-
error: "No tools found. Export default Record<name, tool()> or named \"tools\" Record."
|
|
431
|
-
};
|
|
432
|
-
const imported = [];
|
|
433
|
-
for (const [name, t] of Object.entries(toolsRecord)) if (t && typeof t === "object") {
|
|
434
|
-
session.addTool(name, t);
|
|
435
|
-
imported.push(name);
|
|
436
|
-
}
|
|
437
|
-
return {
|
|
438
|
-
success: true,
|
|
439
|
-
data: { imported }
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
case "history": return {
|
|
443
|
-
success: true,
|
|
444
|
-
data: session.history()
|
|
445
|
-
};
|
|
446
|
-
case "stats": return {
|
|
447
|
-
success: true,
|
|
448
|
-
data: session.stats()
|
|
449
|
-
};
|
|
450
|
-
case "export": return {
|
|
451
|
-
success: true,
|
|
452
|
-
data: session.export()
|
|
453
|
-
};
|
|
454
|
-
case "clear":
|
|
455
|
-
session.clear();
|
|
456
|
-
return { success: true };
|
|
457
|
-
case "pending": return {
|
|
458
|
-
success: true,
|
|
459
|
-
data: session.getPendingApprovals()
|
|
460
|
-
};
|
|
461
|
-
case "approve": {
|
|
462
|
-
const { id } = req.payload;
|
|
463
|
-
return {
|
|
464
|
-
success: true,
|
|
465
|
-
data: await session.approve(id)
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
case "deny": {
|
|
469
|
-
const { id, reason } = req.payload;
|
|
470
|
-
session.deny(id, reason);
|
|
471
|
-
return { success: true };
|
|
472
|
-
}
|
|
473
|
-
case "feedback_list":
|
|
474
|
-
if (!state.getFeedback) return {
|
|
475
|
-
success: false,
|
|
476
|
-
error: "Feedback not enabled. Start agent with --feedback"
|
|
477
|
-
};
|
|
478
|
-
return {
|
|
479
|
-
success: true,
|
|
480
|
-
data: state.getFeedback()
|
|
481
|
-
};
|
|
482
|
-
case "schedule_get": return {
|
|
483
|
-
success: true,
|
|
484
|
-
data: info.schedule ?? null
|
|
485
|
-
};
|
|
486
|
-
case "schedule_set": {
|
|
487
|
-
const payload = req.payload;
|
|
488
|
-
if (!payload?.wakeup && payload?.wakeup !== 0) return {
|
|
489
|
-
success: false,
|
|
490
|
-
error: "Invalid schedule: provide wakeup (number ms, duration string, or cron expression)"
|
|
491
|
-
};
|
|
492
|
-
const schedule = {
|
|
493
|
-
wakeup: payload.wakeup,
|
|
494
|
-
prompt: payload.prompt
|
|
495
|
-
};
|
|
496
|
-
try {
|
|
497
|
-
const resolved = resolveSchedule(schedule);
|
|
498
|
-
if (resolved.type === "cron") parseCron(resolved.expr);
|
|
499
|
-
} catch (error) {
|
|
500
|
-
return {
|
|
501
|
-
success: false,
|
|
502
|
-
error: error instanceof Error ? error.message : String(error)
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
info.schedule = schedule;
|
|
506
|
-
if (resetAllTimers) resetAllTimers();
|
|
507
|
-
return {
|
|
508
|
-
success: true,
|
|
509
|
-
data: info.schedule
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
case "schedule_clear":
|
|
513
|
-
info.schedule = void 0;
|
|
514
|
-
if (resetAllTimers) resetAllTimers();
|
|
515
|
-
return { success: true };
|
|
516
|
-
case "shutdown":
|
|
517
|
-
state.pendingRequests--;
|
|
518
|
-
setTimeout(() => gracefulShutdown(), 100);
|
|
519
|
-
return {
|
|
520
|
-
success: true,
|
|
521
|
-
data: "Shutting down"
|
|
522
|
-
};
|
|
523
|
-
default: return {
|
|
524
|
-
success: false,
|
|
525
|
-
error: `Unknown action: ${req.action}`
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
} catch (error) {
|
|
529
|
-
return {
|
|
530
|
-
success: false,
|
|
531
|
-
error: error instanceof Error ? error.message : String(error)
|
|
532
|
-
};
|
|
533
|
-
} finally {
|
|
534
|
-
if (getState() && req.action !== "shutdown") state.pendingRequests--;
|
|
768
|
+
const agentConnections = /* @__PURE__ */ new Map();
|
|
769
|
+
const mcpToolNames = new Set([
|
|
770
|
+
"channel_send",
|
|
771
|
+
"channel_read",
|
|
772
|
+
"resource_create",
|
|
773
|
+
"resource_read",
|
|
774
|
+
"my_inbox",
|
|
775
|
+
"my_inbox_ack",
|
|
776
|
+
"my_status_set",
|
|
777
|
+
"team_members",
|
|
778
|
+
"team_doc_read",
|
|
779
|
+
"team_doc_write",
|
|
780
|
+
"team_doc_append",
|
|
781
|
+
"team_doc_list",
|
|
782
|
+
"team_doc_create"
|
|
783
|
+
]);
|
|
784
|
+
registerChannelTools(server, ctx, { onMention });
|
|
785
|
+
registerResourceTools(server, ctx);
|
|
786
|
+
registerInboxTools(server, ctx, { debugLog });
|
|
787
|
+
registerTeamTools(server, ctx);
|
|
788
|
+
if (proposalManager) {
|
|
789
|
+
registerProposalTools(server, ctx, proposalManager);
|
|
790
|
+
mcpToolNames.add("team_proposal_create");
|
|
791
|
+
mcpToolNames.add("team_vote");
|
|
792
|
+
mcpToolNames.add("team_proposal_status");
|
|
793
|
+
mcpToolNames.add("team_proposal_cancel");
|
|
794
|
+
}
|
|
795
|
+
let getFeedback = () => [];
|
|
796
|
+
if (feedbackEnabled) {
|
|
797
|
+
getFeedback = registerFeedbackTool(server, ctx).getFeedback;
|
|
798
|
+
mcpToolNames.add("feedback_submit");
|
|
799
|
+
}
|
|
800
|
+
if (skills) {
|
|
801
|
+
registerSkillsTools(server, skills);
|
|
802
|
+
mcpToolNames.add("skills_list");
|
|
803
|
+
mcpToolNames.add("skills_view");
|
|
804
|
+
mcpToolNames.add("skills_read");
|
|
535
805
|
}
|
|
806
|
+
return {
|
|
807
|
+
server,
|
|
808
|
+
agentConnections,
|
|
809
|
+
validAgents,
|
|
810
|
+
proposalManager,
|
|
811
|
+
getFeedback,
|
|
812
|
+
mcpToolNames,
|
|
813
|
+
eventLog
|
|
814
|
+
};
|
|
536
815
|
}
|
|
537
816
|
|
|
538
817
|
//#endregion
|
|
@@ -673,7 +952,7 @@ var ContextProviderImpl = class {
|
|
|
673
952
|
if (options?.agent) {
|
|
674
953
|
const agent = options.agent;
|
|
675
954
|
entries = entries.filter((e) => {
|
|
676
|
-
if (e.kind === "
|
|
955
|
+
if (e.kind === "system" || e.kind === "debug" || e.kind === "output") return false;
|
|
677
956
|
if (e.to) return e.to === agent || e.from === agent;
|
|
678
957
|
return true;
|
|
679
958
|
});
|
|
@@ -719,7 +998,7 @@ var ContextProviderImpl = class {
|
|
|
719
998
|
let seenIdx = -1;
|
|
720
999
|
if (lastSeenId) seenIdx = entries.findIndex((e) => e.id === lastSeenId);
|
|
721
1000
|
return entries.filter((e) => {
|
|
722
|
-
if (e.kind === "
|
|
1001
|
+
if (e.kind === "system" || e.kind === "debug" || e.kind === "output" || e.kind === "tool_call") return false;
|
|
723
1002
|
if (e.from === agent) return false;
|
|
724
1003
|
return e.mentions.includes(agent) || e.to === agent;
|
|
725
1004
|
}).map((entry) => {
|
|
@@ -1142,610 +1421,597 @@ function createFileContextProvider(contextDir, validAgents) {
|
|
|
1142
1421
|
}
|
|
1143
1422
|
|
|
1144
1423
|
//#endregion
|
|
1145
|
-
//#region src/
|
|
1146
|
-
var target_exports = /* @__PURE__ */ __exportAll({
|
|
1147
|
-
DEFAULT_INSTANCE: () => DEFAULT_INSTANCE,
|
|
1148
|
-
DEFAULT_TAG: () => DEFAULT_TAG,
|
|
1149
|
-
DEFAULT_WORKFLOW: () => DEFAULT_WORKFLOW,
|
|
1150
|
-
buildTarget: () => buildTarget,
|
|
1151
|
-
buildTargetDisplay: () => buildTargetDisplay,
|
|
1152
|
-
parseTarget: () => parseTarget
|
|
1153
|
-
});
|
|
1424
|
+
//#region src/daemon/daemon.ts
|
|
1154
1425
|
/**
|
|
1155
|
-
*
|
|
1426
|
+
* Daemon — Centralized agent coordinator.
|
|
1156
1427
|
*
|
|
1157
|
-
*
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1428
|
+
* Data ownership:
|
|
1429
|
+
* Registry (configs) — what agents exist and their configuration
|
|
1430
|
+
* StateStore (store) — conversation history and usage (pluggable)
|
|
1431
|
+
* WorkerHandle (workers) — execution, local or remote
|
|
1432
|
+
* Workflows (workflows) — running workflow instances with controllers
|
|
1161
1433
|
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
* - "alice@review" → { agent: "alice", workflow: "review", tag: "main", display: "alice@review" }
|
|
1165
|
-
* - "alice@review:pr-123"→ { agent: "alice", workflow: "review", tag: "pr-123", display: "alice@review:pr-123" }
|
|
1166
|
-
* - "@review" → { agent: undefined, workflow: "review", tag: "main", display: "@review" }
|
|
1167
|
-
* - "@review:pr-123" → { agent: undefined, workflow: "review", tag: "pr-123", display: "@review:pr-123" }
|
|
1434
|
+
* The daemon is pure glue: receive request → lookup config →
|
|
1435
|
+
* dispatch to worker → persist state → return response.
|
|
1168
1436
|
*
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
/**
|
|
1176
|
-
* Parse target identifier from string
|
|
1177
|
-
* Supports: "agent", "agent@workflow", "agent@workflow:tag", "@workflow", "@workflow:tag"
|
|
1437
|
+
* HTTP endpoints:
|
|
1438
|
+
* GET /health, POST /shutdown
|
|
1439
|
+
* GET/POST /agents, GET/DELETE /agents/:name
|
|
1440
|
+
* POST /run (SSE), POST /serve
|
|
1441
|
+
* GET/POST /workflows, DELETE /workflows/:name/:tag
|
|
1442
|
+
* ALL /mcp
|
|
1178
1443
|
*/
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1444
|
+
var daemon_exports = /* @__PURE__ */ __exportAll({
|
|
1445
|
+
createDaemonApp: () => createDaemonApp,
|
|
1446
|
+
startDaemon: () => startDaemon
|
|
1447
|
+
});
|
|
1448
|
+
let state = null;
|
|
1449
|
+
let shuttingDown = false;
|
|
1450
|
+
const mcpSessions = /* @__PURE__ */ new Map();
|
|
1451
|
+
async function gracefulShutdown() {
|
|
1452
|
+
if (shuttingDown) return;
|
|
1453
|
+
shuttingDown = true;
|
|
1454
|
+
if (state) {
|
|
1455
|
+
for (const [, wf] of state.workflows) try {
|
|
1456
|
+
await wf.shutdown();
|
|
1457
|
+
} catch {}
|
|
1458
|
+
state.workflows.clear();
|
|
1459
|
+
for (const [name, handle] of state.workers) try {
|
|
1460
|
+
await state.store.save(name, handle.getState());
|
|
1461
|
+
} catch {}
|
|
1462
|
+
if (state.server) await state.server.close();
|
|
1463
|
+
}
|
|
1464
|
+
for (const [, session] of mcpSessions) try {
|
|
1465
|
+
await session.transport.close();
|
|
1466
|
+
} catch {}
|
|
1467
|
+
mcpSessions.clear();
|
|
1468
|
+
removeDaemonInfo();
|
|
1469
|
+
process.exit(0);
|
|
1470
|
+
}
|
|
1471
|
+
/** Safe JSON body parsing — returns null on malformed input */
|
|
1472
|
+
async function parseJsonBody(c) {
|
|
1473
|
+
try {
|
|
1474
|
+
return await c.req.json();
|
|
1475
|
+
} catch {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
function createDaemonApp(optionsOrGetState) {
|
|
1480
|
+
const { getState, token } = typeof optionsOrGetState === "function" ? {
|
|
1481
|
+
getState: optionsOrGetState,
|
|
1482
|
+
token: void 0
|
|
1483
|
+
} : optionsOrGetState;
|
|
1484
|
+
const app = new Hono();
|
|
1485
|
+
if (token) app.use("*", async (c, next) => {
|
|
1486
|
+
if (c.req.header("authorization") !== `Bearer ${token}`) return c.json({ error: "Unauthorized" }, 401);
|
|
1487
|
+
await next();
|
|
1488
|
+
});
|
|
1489
|
+
function getWorkflowAgentNames(workflow, tag) {
|
|
1490
|
+
const s = getState();
|
|
1491
|
+
if (!s) return [];
|
|
1492
|
+
return [...s.configs.values()].filter((c) => c.workflow === workflow && c.tag === tag).map((c) => c.name);
|
|
1493
|
+
}
|
|
1494
|
+
app.get("/health", (c) => {
|
|
1495
|
+
const s = getState();
|
|
1496
|
+
if (!s) return c.json({ status: "unavailable" }, 503);
|
|
1497
|
+
const standaloneAgents = [...s.configs.keys()];
|
|
1498
|
+
const workflowList = [...s.workflows.values()].map((wf) => ({
|
|
1499
|
+
name: wf.name,
|
|
1500
|
+
tag: wf.tag,
|
|
1501
|
+
agents: wf.agents
|
|
1502
|
+
}));
|
|
1503
|
+
return c.json({
|
|
1504
|
+
status: "ok",
|
|
1505
|
+
pid: process.pid,
|
|
1506
|
+
port: s.port,
|
|
1507
|
+
uptime: Date.now() - new Date(s.startedAt).getTime(),
|
|
1508
|
+
agents: standaloneAgents,
|
|
1509
|
+
workflows: workflowList
|
|
1510
|
+
});
|
|
1511
|
+
});
|
|
1512
|
+
app.post("/shutdown", (c) => {
|
|
1513
|
+
setImmediate(() => gracefulShutdown());
|
|
1514
|
+
return c.json({ success: true });
|
|
1515
|
+
});
|
|
1516
|
+
app.get("/agents", (c) => {
|
|
1517
|
+
const s = getState();
|
|
1518
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1519
|
+
const standaloneAgents = [...s.configs.values()].map((cfg) => ({
|
|
1520
|
+
name: cfg.name,
|
|
1521
|
+
model: cfg.model,
|
|
1522
|
+
backend: cfg.backend,
|
|
1523
|
+
workflow: cfg.workflow,
|
|
1524
|
+
tag: cfg.tag,
|
|
1525
|
+
createdAt: cfg.createdAt,
|
|
1526
|
+
source: "standalone"
|
|
1527
|
+
}));
|
|
1528
|
+
const workflowAgents = [...s.workflows.values()].flatMap((wf) => wf.agents.map((agentName) => {
|
|
1529
|
+
const controller = wf.controllers.get(agentName);
|
|
1185
1530
|
return {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
const tag = workflowPart.slice(colonIndex + 1) || DEFAULT_TAG;
|
|
1195
|
-
return {
|
|
1196
|
-
agent: void 0,
|
|
1197
|
-
workflow,
|
|
1198
|
-
tag,
|
|
1199
|
-
full: `@${workflow}:${tag}`,
|
|
1200
|
-
display: buildDisplay(void 0, workflow, tag)
|
|
1531
|
+
name: agentName,
|
|
1532
|
+
model: "",
|
|
1533
|
+
backend: "",
|
|
1534
|
+
workflow: wf.name,
|
|
1535
|
+
tag: wf.tag,
|
|
1536
|
+
createdAt: wf.startedAt,
|
|
1537
|
+
source: "workflow",
|
|
1538
|
+
state: controller?.state ?? "unknown"
|
|
1201
1539
|
};
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
agent,
|
|
1219
|
-
workflow,
|
|
1220
|
-
tag: DEFAULT_TAG,
|
|
1221
|
-
full: `${agent}@${workflow}:${DEFAULT_TAG}`,
|
|
1222
|
-
display: buildDisplay(agent, workflow, DEFAULT_TAG)
|
|
1223
|
-
};
|
|
1224
|
-
} else {
|
|
1225
|
-
const workflow = workflowPart.slice(0, colonIndex) || DEFAULT_WORKFLOW;
|
|
1226
|
-
const tag = workflowPart.slice(colonIndex + 1) || DEFAULT_TAG;
|
|
1227
|
-
return {
|
|
1228
|
-
agent,
|
|
1540
|
+
}));
|
|
1541
|
+
return c.json({ agents: [...standaloneAgents, ...workflowAgents] });
|
|
1542
|
+
});
|
|
1543
|
+
app.post("/agents", async (c) => {
|
|
1544
|
+
const s = getState();
|
|
1545
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1546
|
+
const body = await parseJsonBody(c);
|
|
1547
|
+
if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
|
|
1548
|
+
const { name, model, system, backend = "default", workflow = "global", tag = "main" } = body;
|
|
1549
|
+
if (!name || !model || !system) return c.json({ error: "name, model, system required" }, 400);
|
|
1550
|
+
if (s.configs.has(name)) return c.json({ error: `Agent already exists: ${name}` }, 409);
|
|
1551
|
+
const agentConfig = {
|
|
1552
|
+
name,
|
|
1553
|
+
model,
|
|
1554
|
+
system,
|
|
1555
|
+
backend,
|
|
1229
1556
|
workflow,
|
|
1230
1557
|
tag,
|
|
1231
|
-
|
|
1232
|
-
display: buildDisplay(agent, workflow, tag)
|
|
1558
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1233
1559
|
};
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1560
|
+
const handle = new LocalWorker(agentConfig, await s.store.load(name) ?? void 0);
|
|
1561
|
+
s.configs.set(name, agentConfig);
|
|
1562
|
+
s.workers.set(name, handle);
|
|
1563
|
+
return c.json({
|
|
1564
|
+
name,
|
|
1565
|
+
model,
|
|
1566
|
+
backend,
|
|
1567
|
+
workflow,
|
|
1568
|
+
tag
|
|
1569
|
+
}, 201);
|
|
1570
|
+
});
|
|
1571
|
+
app.get("/agents/:name", (c) => {
|
|
1572
|
+
const s = getState();
|
|
1573
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1574
|
+
const cfg = s.configs.get(c.req.param("name"));
|
|
1575
|
+
if (!cfg) return c.json({ error: "Agent not found" }, 404);
|
|
1576
|
+
return c.json({
|
|
1577
|
+
name: cfg.name,
|
|
1578
|
+
model: cfg.model,
|
|
1579
|
+
backend: cfg.backend,
|
|
1580
|
+
system: cfg.system,
|
|
1581
|
+
workflow: cfg.workflow,
|
|
1582
|
+
tag: cfg.tag,
|
|
1583
|
+
createdAt: cfg.createdAt
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
app.delete("/agents/:name", async (c) => {
|
|
1587
|
+
const s = getState();
|
|
1588
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1589
|
+
const name = c.req.param("name");
|
|
1590
|
+
if (!s.configs.delete(name)) return c.json({ error: "Agent not found" }, 404);
|
|
1591
|
+
const handle = s.workers.get(name);
|
|
1592
|
+
if (handle) try {
|
|
1593
|
+
await s.store.save(name, handle.getState());
|
|
1594
|
+
} catch {}
|
|
1595
|
+
s.workers.delete(name);
|
|
1596
|
+
return c.json({ success: true });
|
|
1597
|
+
});
|
|
1598
|
+
app.post("/run", async (c) => {
|
|
1599
|
+
const s = getState();
|
|
1600
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1601
|
+
const body = await parseJsonBody(c);
|
|
1602
|
+
if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
|
|
1603
|
+
const { agent: agentName, message } = body;
|
|
1604
|
+
if (!agentName || !message) return c.json({ error: "agent and message required" }, 400);
|
|
1605
|
+
const handle = s.workers.get(agentName);
|
|
1606
|
+
if (!handle) return c.json({ error: `Agent not found: ${agentName}` }, 404);
|
|
1607
|
+
return streamSSE(c, async (stream) => {
|
|
1608
|
+
try {
|
|
1609
|
+
const gen = handle.sendStream(message);
|
|
1610
|
+
while (true) {
|
|
1611
|
+
const { value, done } = await gen.next();
|
|
1612
|
+
if (done) {
|
|
1613
|
+
const currentState = getState();
|
|
1614
|
+
if (currentState) await currentState.store.save(agentName, handle.getState());
|
|
1615
|
+
await stream.writeSSE({
|
|
1616
|
+
event: "done",
|
|
1617
|
+
data: JSON.stringify(value)
|
|
1618
|
+
});
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
await stream.writeSSE({
|
|
1622
|
+
event: "chunk",
|
|
1623
|
+
data: JSON.stringify({
|
|
1624
|
+
agent: agentName,
|
|
1625
|
+
text: value
|
|
1626
|
+
})
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1631
|
+
await stream.writeSSE({
|
|
1632
|
+
event: "error",
|
|
1633
|
+
data: JSON.stringify({ error: msg })
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
app.post("/serve", async (c) => {
|
|
1639
|
+
const s = getState();
|
|
1640
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1641
|
+
const body = await parseJsonBody(c);
|
|
1642
|
+
if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
|
|
1643
|
+
const { agent: agentName, message } = body;
|
|
1644
|
+
if (!agentName || !message) return c.json({ error: "agent and message required" }, 400);
|
|
1645
|
+
const handle = s.workers.get(agentName);
|
|
1646
|
+
if (!handle) return c.json({ error: `Agent not found: ${agentName}` }, 404);
|
|
1305
1647
|
try {
|
|
1306
|
-
await
|
|
1307
|
-
await
|
|
1648
|
+
const response = await handle.send(message);
|
|
1649
|
+
await s.store.save(agentName, handle.getState());
|
|
1650
|
+
return c.json(response);
|
|
1308
1651
|
} catch (error) {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
}
|
|
1312
|
-
const skills = provider.list();
|
|
1313
|
-
if (skills.length > 0) {
|
|
1314
|
-
console.log(`Loaded ${skills.length} skill(s):`);
|
|
1315
|
-
for (const skill of skills) console.log(` - ${skill.name}: ${skill.description}`);
|
|
1316
|
-
}
|
|
1317
|
-
const tools = {};
|
|
1318
|
-
if (skills.length > 0) tools.Skills = createSkillsTool(provider);
|
|
1319
|
-
return {
|
|
1320
|
-
tools,
|
|
1321
|
-
importer
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
let state = null;
|
|
1325
|
-
const DEFAULT_IDLE_TIMEOUT = 1800 * 1e3;
|
|
1326
|
-
const DEFAULT_SCHEDULE_PROMPT = "[Scheduled wakeup] You have been idle. Check if there are any pending tasks or updates to process.";
|
|
1327
|
-
function resetIdleTimer() {
|
|
1328
|
-
if (!state) return;
|
|
1329
|
-
state.lastActivity = Date.now();
|
|
1330
|
-
if (state.idleTimer) {
|
|
1331
|
-
clearTimeout(state.idleTimer);
|
|
1332
|
-
state.idleTimer = void 0;
|
|
1333
|
-
}
|
|
1334
|
-
const timeout = state.info.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
1335
|
-
if (timeout > 0) state.idleTimer = setTimeout(() => {
|
|
1336
|
-
if (state && state.pendingRequests === 0) {
|
|
1337
|
-
console.log(`\nSession idle for ${timeout / 1e3}s, shutting down...`);
|
|
1338
|
-
gracefulShutdown();
|
|
1339
|
-
} else resetIdleTimer();
|
|
1340
|
-
}, timeout);
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Interval-based wakeup: fires when agent has been idle for resolved.ms.
|
|
1344
|
-
* Resets on any activity (external send, @mention, etc).
|
|
1345
|
-
* After the wakeup send completes, the timer restarts.
|
|
1346
|
-
*/
|
|
1347
|
-
function resetIntervalSchedule() {
|
|
1348
|
-
if (!state) return;
|
|
1349
|
-
if (state.scheduleTimer) {
|
|
1350
|
-
clearTimeout(state.scheduleTimer);
|
|
1351
|
-
state.scheduleTimer = void 0;
|
|
1352
|
-
}
|
|
1353
|
-
const schedule = state.info.schedule;
|
|
1354
|
-
if (!schedule) return;
|
|
1355
|
-
let resolved;
|
|
1356
|
-
try {
|
|
1357
|
-
resolved = resolveSchedule(schedule);
|
|
1358
|
-
} catch {
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1361
|
-
if (resolved.type !== "interval" || !resolved.ms) return;
|
|
1362
|
-
const ms = resolved.ms;
|
|
1363
|
-
const prompt = resolved.prompt || DEFAULT_SCHEDULE_PROMPT;
|
|
1364
|
-
state.scheduleTimer = setTimeout(async () => {
|
|
1365
|
-
if (!state || state.pendingRequests > 0) {
|
|
1366
|
-
resetIntervalSchedule();
|
|
1367
|
-
return;
|
|
1652
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1653
|
+
return c.json({ error: msg }, 500);
|
|
1368
1654
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
if (
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1655
|
+
});
|
|
1656
|
+
app.all("/mcp", async (c) => {
|
|
1657
|
+
const s = getState();
|
|
1658
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1659
|
+
const req = c.req.raw;
|
|
1660
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
1661
|
+
if (sessionId && mcpSessions.has(sessionId)) {
|
|
1662
|
+
const session = mcpSessions.get(sessionId);
|
|
1663
|
+
if (req.method === "DELETE") {
|
|
1664
|
+
await session.transport.close();
|
|
1665
|
+
mcpSessions.delete(sessionId);
|
|
1666
|
+
return new Response(null, { status: 200 });
|
|
1381
1667
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1668
|
+
return session.transport.handleRequest(req);
|
|
1669
|
+
}
|
|
1670
|
+
if (req.method === "POST") {
|
|
1671
|
+
const body = await req.json();
|
|
1672
|
+
if (!(Array.isArray(body) ? body.some((m) => m?.method === "initialize") : body?.method === "initialize")) return c.json({ error: "Bad request: session required" }, 400);
|
|
1673
|
+
const agentName = new URL(req.url).searchParams.get("agent") || "user";
|
|
1674
|
+
const agentCfg = s.configs.get(agentName);
|
|
1675
|
+
const workflow = agentCfg?.workflow ?? "global";
|
|
1676
|
+
const tag = agentCfg?.tag ?? "main";
|
|
1677
|
+
const workflowAgents = getWorkflowAgentNames(workflow, tag);
|
|
1678
|
+
const allNames = [...new Set([
|
|
1679
|
+
...workflowAgents,
|
|
1680
|
+
agentName,
|
|
1681
|
+
"user"
|
|
1682
|
+
])];
|
|
1683
|
+
const contextDir = getDefaultContextDir(workflow, tag);
|
|
1684
|
+
mkdirSync(contextDir, { recursive: true });
|
|
1685
|
+
const provider = createFileContextProvider(contextDir, allNames);
|
|
1686
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1687
|
+
sessionIdGenerator: () => `${agentName}-${randomUUID().slice(0, 8)}`,
|
|
1688
|
+
onsessioninitialized: (sid) => {
|
|
1689
|
+
mcpSessions.set(sid, {
|
|
1690
|
+
transport,
|
|
1691
|
+
agentId: agentName
|
|
1692
|
+
});
|
|
1693
|
+
},
|
|
1694
|
+
onsessionclosed: (sid) => {
|
|
1695
|
+
mcpSessions.delete(sid);
|
|
1696
|
+
},
|
|
1697
|
+
enableJsonResponse: true
|
|
1698
|
+
});
|
|
1699
|
+
await createContextMCPServer({
|
|
1700
|
+
provider,
|
|
1701
|
+
validAgents: allNames,
|
|
1702
|
+
name: `${workflow}-context`,
|
|
1703
|
+
version: "1.0.0"
|
|
1704
|
+
}).server.connect(transport);
|
|
1705
|
+
return transport.handleRequest(req, { parsedBody: body });
|
|
1706
|
+
}
|
|
1707
|
+
if (req.method === "GET") return c.json({ error: "Session ID required for GET requests" }, 400);
|
|
1708
|
+
return c.json({ error: "Method not allowed" }, 405);
|
|
1709
|
+
});
|
|
1710
|
+
app.post("/workflows", async (c) => {
|
|
1711
|
+
const s = getState();
|
|
1712
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1713
|
+
const body = await parseJsonBody(c);
|
|
1714
|
+
if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
|
|
1715
|
+
const { workflow, tag = "main", feedback, pollInterval } = body;
|
|
1716
|
+
if (!workflow || !workflow.agents) return c.json({ error: "workflow (parsed YAML) required" }, 400);
|
|
1717
|
+
const workflowName = workflow.name || "global";
|
|
1718
|
+
const key = `${workflowName}:${tag}`;
|
|
1719
|
+
if (s.workflows.has(key)) return c.json({ error: `Workflow already running: ${key}` }, 409);
|
|
1419
1720
|
try {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1721
|
+
const { runWorkflowWithControllers } = await import("../runner-CQJYnM7D.mjs");
|
|
1722
|
+
const result = await runWorkflowWithControllers({
|
|
1723
|
+
workflow,
|
|
1724
|
+
workflowName,
|
|
1725
|
+
tag,
|
|
1726
|
+
mode: "start",
|
|
1727
|
+
headless: true,
|
|
1728
|
+
feedback,
|
|
1729
|
+
pollInterval,
|
|
1730
|
+
log: () => {}
|
|
1731
|
+
});
|
|
1732
|
+
if (!result.success) return c.json({ error: result.error || "Workflow failed to start" }, 500);
|
|
1733
|
+
const handle = {
|
|
1734
|
+
name: workflowName,
|
|
1735
|
+
tag,
|
|
1736
|
+
key,
|
|
1737
|
+
agents: Object.keys(workflow.agents),
|
|
1738
|
+
controllers: result.controllers,
|
|
1739
|
+
contextProvider: result.contextProvider,
|
|
1740
|
+
shutdown: result.shutdown,
|
|
1741
|
+
workflowPath: workflow.filePath,
|
|
1742
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
|
+
};
|
|
1744
|
+
s.workflows.set(key, handle);
|
|
1745
|
+
return c.json({
|
|
1746
|
+
key,
|
|
1747
|
+
name: workflowName,
|
|
1748
|
+
tag,
|
|
1749
|
+
agents: handle.agents
|
|
1750
|
+
}, 201);
|
|
1423
1751
|
} catch (error) {
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
if (state) {
|
|
1427
|
-
state.pendingRequests--;
|
|
1428
|
-
resetIdleTimer();
|
|
1429
|
-
startCronSchedule();
|
|
1430
|
-
}
|
|
1752
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1753
|
+
return c.json({ error: `Failed to start workflow: ${msg}` }, 500);
|
|
1431
1754
|
}
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1755
|
+
});
|
|
1756
|
+
app.get("/workflows", (c) => {
|
|
1757
|
+
const s = getState();
|
|
1758
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1759
|
+
const workflows = [...s.workflows.values()].map((wf) => {
|
|
1760
|
+
const agentStates = {};
|
|
1761
|
+
for (const [name, controller] of wf.controllers) agentStates[name] = controller.state;
|
|
1762
|
+
return {
|
|
1763
|
+
name: wf.name,
|
|
1764
|
+
tag: wf.tag,
|
|
1765
|
+
key: wf.key,
|
|
1766
|
+
agents: wf.agents,
|
|
1767
|
+
agentStates,
|
|
1768
|
+
workflowPath: wf.workflowPath,
|
|
1769
|
+
startedAt: wf.startedAt
|
|
1770
|
+
};
|
|
1771
|
+
});
|
|
1772
|
+
return c.json({ workflows });
|
|
1773
|
+
});
|
|
1774
|
+
async function deleteWorkflow(c, name, tag) {
|
|
1775
|
+
const s = getState();
|
|
1776
|
+
if (!s) return c.json({ error: "Not ready" }, 503);
|
|
1777
|
+
const key = `${name}:${tag}`;
|
|
1778
|
+
const handle = s.workflows.get(key);
|
|
1779
|
+
if (!handle) return c.json({ error: `Workflow not found: ${key}` }, 404);
|
|
1453
1780
|
try {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
resetScheduleTimers();
|
|
1461
|
-
const senders = [...new Set(inbox.map((m) => m.entry.from))];
|
|
1462
|
-
await provider.appendChannel(agentName, `read ${inbox.length} message(s) from ${senders.join(", ")}`, { kind: "log" });
|
|
1463
|
-
try {
|
|
1464
|
-
const response = await state.session.send(prompt);
|
|
1465
|
-
await provider.appendChannel(agentName, response.content);
|
|
1466
|
-
await provider.ackInbox(agentName, latestId);
|
|
1467
|
-
} finally {
|
|
1468
|
-
if (state) {
|
|
1469
|
-
state.pendingRequests--;
|
|
1470
|
-
resetIdleTimer();
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1781
|
+
await handle.shutdown();
|
|
1782
|
+
s.workflows.delete(key);
|
|
1783
|
+
return c.json({
|
|
1784
|
+
success: true,
|
|
1785
|
+
key
|
|
1786
|
+
});
|
|
1473
1787
|
} catch (error) {
|
|
1474
|
-
|
|
1788
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1789
|
+
return c.json({ error: `Failed to stop workflow: ${msg}` }, 500);
|
|
1475
1790
|
}
|
|
1476
|
-
}, INBOX_POLL_MS);
|
|
1477
|
-
}
|
|
1478
|
-
async function gracefulShutdown() {
|
|
1479
|
-
if (!state) {
|
|
1480
|
-
process.exit(0);
|
|
1481
|
-
return;
|
|
1482
1791
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
while (state.pendingRequests > 0 && Date.now() - start < maxWait) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1487
|
-
if (state.importer) await state.importer.cleanup();
|
|
1488
|
-
cleanup();
|
|
1489
|
-
process.exit(0);
|
|
1792
|
+
app.delete("/workflows/:name/:tag", (c) => deleteWorkflow(c, c.req.param("name"), c.req.param("tag")));
|
|
1793
|
+
app.delete("/workflows/:name", (c) => deleteWorkflow(c, c.req.param("name"), "main"));
|
|
1794
|
+
return app;
|
|
1490
1795
|
}
|
|
1491
|
-
function
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (state.inboxPollTimer) clearInterval(state.inboxPollTimer);
|
|
1497
|
-
if (existsSync(state.info.socketPath)) unlinkSync(state.info.socketPath);
|
|
1498
|
-
if (existsSync(state.info.pidFile)) unlinkSync(state.info.pidFile);
|
|
1499
|
-
if (existsSync(state.info.readyFile)) unlinkSync(state.info.readyFile);
|
|
1500
|
-
unregisterSession(state.info.id);
|
|
1501
|
-
}
|
|
1502
|
-
}
|
|
1503
|
-
async function startDaemon(config) {
|
|
1504
|
-
ensureDirs();
|
|
1505
|
-
const backendType = config.backend || "sdk";
|
|
1506
|
-
const sessionId = crypto.randomUUID();
|
|
1507
|
-
const workflow = config.workflow ?? config.instance ?? "global";
|
|
1508
|
-
const tag = config.tag ?? "main";
|
|
1509
|
-
const instance = config.instance ?? workflow;
|
|
1510
|
-
const { tools, importer } = await setupSkills(sessionId, config.skills, config.skillDirs, config.importSkills);
|
|
1511
|
-
if (config.tool) {
|
|
1512
|
-
const toolPath = config.tool.startsWith("/") ? config.tool : join(process.cwd(), config.tool);
|
|
1513
|
-
try {
|
|
1514
|
-
const module = await import(toolPath);
|
|
1515
|
-
const toolsList = Array.isArray(module.default) ? module.default : module.default?.tools || [];
|
|
1516
|
-
for (const toolDef of toolsList) if (toolDef && toolDef.name) tools[toolDef.name] = toolDef;
|
|
1517
|
-
} catch (error) {
|
|
1518
|
-
console.error(`Failed to import tools from ${config.tool}:`, error);
|
|
1519
|
-
}
|
|
1796
|
+
async function startDaemon(config = {}) {
|
|
1797
|
+
const existing = isDaemonRunning();
|
|
1798
|
+
if (existing) {
|
|
1799
|
+
console.error(`Daemon already running: pid=${existing.pid} port=${existing.port}`);
|
|
1800
|
+
process.exit(1);
|
|
1520
1801
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
model: config.model
|
|
1531
|
-
}) : void 0;
|
|
1532
|
-
const session = new AgentSession({
|
|
1533
|
-
model: config.model,
|
|
1534
|
-
system,
|
|
1535
|
-
tools,
|
|
1536
|
-
backend: cliBackend
|
|
1802
|
+
const host = config.host ?? "127.0.0.1";
|
|
1803
|
+
const store = config.store ?? new MemoryStateStore();
|
|
1804
|
+
const token = randomUUID();
|
|
1805
|
+
const server = await startHttpServer(createDaemonApp({
|
|
1806
|
+
getState: () => state,
|
|
1807
|
+
token
|
|
1808
|
+
}), {
|
|
1809
|
+
port: config.port ?? DEFAULT_PORT,
|
|
1810
|
+
hostname: host
|
|
1537
1811
|
});
|
|
1538
|
-
const
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
const readyFile = join(SESSIONS_DIR, `${effectiveId}.ready`);
|
|
1542
|
-
if (existsSync(socketPath)) unlinkSync(socketPath);
|
|
1543
|
-
const contextDir = getDefaultContextDir(workflow, tag);
|
|
1544
|
-
mkdirSync(contextDir, { recursive: true });
|
|
1545
|
-
const agentDisplayName = config.name ? getAgentDisplayName(config.name) : effectiveId.slice(0, 8);
|
|
1546
|
-
const existingAgentNames = getInstanceAgentNames(instance);
|
|
1547
|
-
const contextProvider = createFileContextProvider(contextDir, [...new Set([
|
|
1548
|
-
...existingAgentNames,
|
|
1549
|
-
agentDisplayName,
|
|
1550
|
-
"user"
|
|
1551
|
-
])]);
|
|
1552
|
-
const info = {
|
|
1553
|
-
id: effectiveId,
|
|
1554
|
-
name: config.name,
|
|
1555
|
-
workflow,
|
|
1556
|
-
tag,
|
|
1557
|
-
instance,
|
|
1558
|
-
contextDir,
|
|
1559
|
-
model: config.model,
|
|
1560
|
-
system: config.system,
|
|
1561
|
-
backend: backendType,
|
|
1562
|
-
socketPath,
|
|
1563
|
-
pidFile,
|
|
1564
|
-
readyFile,
|
|
1812
|
+
const actualPort = server.port;
|
|
1813
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1814
|
+
writeDaemonInfo({
|
|
1565
1815
|
pid: process.pid,
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
const server = createServer((socket) => {
|
|
1571
|
-
let buffer = "";
|
|
1572
|
-
socket.on("data", async (data) => {
|
|
1573
|
-
buffer += data.toString();
|
|
1574
|
-
const lines = buffer.split("\n");
|
|
1575
|
-
buffer = lines.pop() || "";
|
|
1576
|
-
for (const line of lines) {
|
|
1577
|
-
if (!line.trim()) continue;
|
|
1578
|
-
try {
|
|
1579
|
-
const req = JSON.parse(line);
|
|
1580
|
-
const resetActivity = () => {
|
|
1581
|
-
resetIdleTimer();
|
|
1582
|
-
resetScheduleTimers();
|
|
1583
|
-
};
|
|
1584
|
-
const resetAll = () => {
|
|
1585
|
-
resetIdleTimer();
|
|
1586
|
-
resetIntervalSchedule();
|
|
1587
|
-
startCronSchedule();
|
|
1588
|
-
};
|
|
1589
|
-
const res = await handleRequest(() => state, req, resetActivity, gracefulShutdown, resetAll);
|
|
1590
|
-
socket.write(JSON.stringify(res) + "\n");
|
|
1591
|
-
} catch (error) {
|
|
1592
|
-
socket.write(JSON.stringify({
|
|
1593
|
-
success: false,
|
|
1594
|
-
error: error instanceof Error ? error.message : "Parse error"
|
|
1595
|
-
}) + "\n");
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
});
|
|
1599
|
-
socket.on("error", () => {});
|
|
1600
|
-
});
|
|
1601
|
-
server.listen(socketPath, () => {
|
|
1602
|
-
writeFileSync(pidFile, process.pid.toString());
|
|
1603
|
-
registerSession(info);
|
|
1604
|
-
state = {
|
|
1605
|
-
session,
|
|
1606
|
-
server,
|
|
1607
|
-
info,
|
|
1608
|
-
lastActivity: Date.now(),
|
|
1609
|
-
pendingRequests: 0,
|
|
1610
|
-
importer,
|
|
1611
|
-
getFeedback,
|
|
1612
|
-
contextProvider,
|
|
1613
|
-
agentDisplayName
|
|
1614
|
-
};
|
|
1615
|
-
writeFileSync(readyFile, effectiveId);
|
|
1616
|
-
resetIdleTimer();
|
|
1617
|
-
resetIntervalSchedule();
|
|
1618
|
-
startCronSchedule();
|
|
1619
|
-
startInboxPolling();
|
|
1620
|
-
const nameStr = config.name ? ` (${config.name})` : "";
|
|
1621
|
-
const workflowDisplay = buildTargetDisplay(void 0, workflow, tag);
|
|
1622
|
-
console.log(`Session started: ${effectiveId}${nameStr}`);
|
|
1623
|
-
console.log(`Model: ${config.model}`);
|
|
1624
|
-
console.log(`Backend: ${backendType}`);
|
|
1625
|
-
console.log(`Workflow: ${workflowDisplay}`);
|
|
1626
|
-
if (config.schedule) try {
|
|
1627
|
-
const resolved = resolveSchedule(config.schedule);
|
|
1628
|
-
if (resolved.type === "interval") {
|
|
1629
|
-
console.log(`Wakeup: every ${resolved.ms / 1e3}s when idle`);
|
|
1630
|
-
const idleMs = config.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
1631
|
-
if (idleMs > 0 && resolved.ms > idleMs) console.warn(`Warning: idle timeout (${idleMs / 1e3}s) is shorter than wakeup interval (${resolved.ms / 1e3}s). Session will shut down before wakeup fires. Use --idle-timeout 0 to disable.`);
|
|
1632
|
-
} else console.log(`Wakeup: cron ${resolved.expr}`);
|
|
1633
|
-
} catch (error) {
|
|
1634
|
-
console.error("Invalid wakeup schedule:", error);
|
|
1635
|
-
}
|
|
1636
|
-
});
|
|
1637
|
-
server.on("error", (error) => {
|
|
1638
|
-
console.error("Server error:", error);
|
|
1639
|
-
cleanup();
|
|
1640
|
-
process.exit(1);
|
|
1816
|
+
host,
|
|
1817
|
+
port: actualPort,
|
|
1818
|
+
startedAt,
|
|
1819
|
+
token
|
|
1641
1820
|
});
|
|
1821
|
+
state = {
|
|
1822
|
+
configs: /* @__PURE__ */ new Map(),
|
|
1823
|
+
workers: /* @__PURE__ */ new Map(),
|
|
1824
|
+
workflows: /* @__PURE__ */ new Map(),
|
|
1825
|
+
store,
|
|
1826
|
+
server,
|
|
1827
|
+
port: actualPort,
|
|
1828
|
+
host,
|
|
1829
|
+
startedAt
|
|
1830
|
+
};
|
|
1831
|
+
console.log(`Daemon started: pid=${process.pid}`);
|
|
1832
|
+
console.log(`Listening: http://${host}:${actualPort}`);
|
|
1833
|
+
console.log(`MCP: http://${host}:${actualPort}/mcp`);
|
|
1642
1834
|
process.on("SIGINT", () => {
|
|
1643
1835
|
console.log("\nShutting down...");
|
|
1644
|
-
|
|
1645
|
-
process.exit(0);
|
|
1836
|
+
gracefulShutdown();
|
|
1646
1837
|
});
|
|
1647
1838
|
process.on("SIGTERM", () => {
|
|
1648
|
-
|
|
1649
|
-
process.exit(0);
|
|
1839
|
+
gracefulShutdown();
|
|
1650
1840
|
});
|
|
1651
1841
|
}
|
|
1652
1842
|
|
|
1653
1843
|
//#endregion
|
|
1654
1844
|
//#region src/cli/client.ts
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1845
|
+
/**
|
|
1846
|
+
* CLI client — HTTP client for daemon REST API.
|
|
1847
|
+
*
|
|
1848
|
+
* Talks to the 9-endpoint daemon:
|
|
1849
|
+
* GET /health, POST /shutdown
|
|
1850
|
+
* GET/POST /agents, GET/DELETE /agents/:name
|
|
1851
|
+
* POST /run (SSE), POST /serve
|
|
1852
|
+
* ALL /mcp
|
|
1853
|
+
*/
|
|
1854
|
+
var client_exports = /* @__PURE__ */ __exportAll({
|
|
1855
|
+
createAgent: () => createAgent,
|
|
1856
|
+
deleteAgent: () => deleteAgent,
|
|
1857
|
+
health: () => health,
|
|
1858
|
+
isDaemonActive: () => isDaemonActive,
|
|
1859
|
+
listAgents: () => listAgents,
|
|
1860
|
+
run: () => run,
|
|
1861
|
+
serve: () => serve,
|
|
1862
|
+
shutdown: () => shutdown,
|
|
1863
|
+
startWorkflow: () => startWorkflow,
|
|
1864
|
+
stopWorkflow: () => stopWorkflow
|
|
1865
|
+
});
|
|
1866
|
+
const MAX_RETRIES = 3;
|
|
1867
|
+
const BASE_DELAY_MS = 200;
|
|
1868
|
+
function isRetryableError(error) {
|
|
1869
|
+
if (error instanceof TypeError) return true;
|
|
1870
|
+
if (error instanceof Error) {
|
|
1871
|
+
const code = error.code;
|
|
1872
|
+
return code === "ECONNREFUSED" || code === "ECONNRESET";
|
|
1873
|
+
}
|
|
1874
|
+
return false;
|
|
1875
|
+
}
|
|
1876
|
+
function getDaemonConnection() {
|
|
1877
|
+
const daemon = isDaemonRunning();
|
|
1878
|
+
if (!daemon) return null;
|
|
1879
|
+
return {
|
|
1880
|
+
url: `http://${daemon.host}:${daemon.port}`,
|
|
1881
|
+
token: daemon.token
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
function requireDaemon() {
|
|
1885
|
+
const conn = getDaemonConnection();
|
|
1886
|
+
if (!conn) throw new Error("No daemon running. Start one with: agent-worker daemon");
|
|
1887
|
+
return conn;
|
|
1888
|
+
}
|
|
1889
|
+
/** Build headers with auth token */
|
|
1890
|
+
function authHeaders(token, extra) {
|
|
1891
|
+
const headers = { ...extra };
|
|
1892
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1893
|
+
return headers;
|
|
1894
|
+
}
|
|
1895
|
+
async function request(method, path, body) {
|
|
1896
|
+
const { url: baseUrl, token } = requireDaemon();
|
|
1897
|
+
let lastError;
|
|
1898
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) try {
|
|
1899
|
+
const init = {
|
|
1900
|
+
method,
|
|
1901
|
+
headers: authHeaders(token, body !== void 0 ? { "Content-Type": "application/json" } : void 0),
|
|
1902
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
1903
|
+
signal: AbortSignal.timeout(6e4)
|
|
1904
|
+
};
|
|
1905
|
+
return await (await fetch(`${baseUrl}${path}`, init)).json();
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
lastError = error;
|
|
1908
|
+
if (attempt < MAX_RETRIES && isRetryableError(error)) {
|
|
1909
|
+
const delay = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1910
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1911
|
+
} else break;
|
|
1912
|
+
}
|
|
1913
|
+
return {
|
|
1914
|
+
success: false,
|
|
1915
|
+
error: `Connection failed: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
/** GET /health */
|
|
1919
|
+
function health() {
|
|
1920
|
+
return request("GET", "/health");
|
|
1921
|
+
}
|
|
1922
|
+
/** POST /shutdown */
|
|
1923
|
+
function shutdown() {
|
|
1924
|
+
return request("POST", "/shutdown");
|
|
1925
|
+
}
|
|
1926
|
+
/** GET /agents */
|
|
1927
|
+
function listAgents() {
|
|
1928
|
+
return request("GET", "/agents");
|
|
1929
|
+
}
|
|
1930
|
+
/** POST /agents */
|
|
1931
|
+
function createAgent(body) {
|
|
1932
|
+
return request("POST", "/agents", body);
|
|
1933
|
+
}
|
|
1934
|
+
/** DELETE /agents/:name */
|
|
1935
|
+
function deleteAgent(name) {
|
|
1936
|
+
return request("DELETE", `/agents/${encodeURIComponent(name)}`);
|
|
1937
|
+
}
|
|
1938
|
+
/** POST /serve (sync JSON response) */
|
|
1939
|
+
function serve(body) {
|
|
1940
|
+
return request("POST", "/serve", body);
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* POST /run (SSE stream).
|
|
1944
|
+
* Calls onChunk for each chunk, returns final response.
|
|
1945
|
+
*/
|
|
1946
|
+
async function run(body, onChunk) {
|
|
1947
|
+
let baseUrl;
|
|
1948
|
+
let token;
|
|
1949
|
+
try {
|
|
1950
|
+
const conn = requireDaemon();
|
|
1951
|
+
baseUrl = conn.url;
|
|
1952
|
+
token = conn.token;
|
|
1953
|
+
} catch (error) {
|
|
1954
|
+
return {
|
|
1955
|
+
success: false,
|
|
1956
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
try {
|
|
1960
|
+
const res = await fetch(`${baseUrl}/run`, {
|
|
1961
|
+
method: "POST",
|
|
1962
|
+
headers: authHeaders(token, { "Content-Type": "application/json" }),
|
|
1963
|
+
body: JSON.stringify(body)
|
|
1701
1964
|
});
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1965
|
+
if (!res.ok || !res.body) return await res.json();
|
|
1966
|
+
const reader = res.body.getReader();
|
|
1967
|
+
const decoder = new TextDecoder();
|
|
1968
|
+
let buffer = "";
|
|
1969
|
+
let finalResponse = { success: true };
|
|
1970
|
+
while (true) {
|
|
1971
|
+
const { value, done } = await reader.read();
|
|
1972
|
+
if (done) break;
|
|
1973
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1708
1974
|
const lines = buffer.split("\n");
|
|
1709
|
-
buffer = lines.pop()
|
|
1710
|
-
|
|
1711
|
-
|
|
1975
|
+
buffer = lines.pop() ?? "";
|
|
1976
|
+
let currentEvent = "";
|
|
1977
|
+
for (const line of lines) if (line.startsWith("event: ")) currentEvent = line.slice(7);
|
|
1978
|
+
else if (line.startsWith("data: ")) {
|
|
1979
|
+
const data = line.slice(6);
|
|
1712
1980
|
try {
|
|
1713
|
-
const
|
|
1714
|
-
if (
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
} catch
|
|
1721
|
-
if (debug) console.error(`[DEBUG] Parse error:`, error);
|
|
1722
|
-
socket.end();
|
|
1723
|
-
reject(error);
|
|
1724
|
-
}
|
|
1981
|
+
const parsed = JSON.parse(data);
|
|
1982
|
+
if (currentEvent === "chunk" && onChunk) onChunk(parsed);
|
|
1983
|
+
else if (currentEvent === "done") finalResponse = parsed;
|
|
1984
|
+
else if (currentEvent === "error") return {
|
|
1985
|
+
success: false,
|
|
1986
|
+
error: parsed.error
|
|
1987
|
+
};
|
|
1988
|
+
} catch {}
|
|
1725
1989
|
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
reject(/* @__PURE__ */ new Error("Connection timeout"));
|
|
1735
|
-
});
|
|
1736
|
-
socket.setTimeout(6e4);
|
|
1737
|
-
if (debug) console.error(`[DEBUG] Waiting for response (60s timeout)...`);
|
|
1738
|
-
});
|
|
1990
|
+
}
|
|
1991
|
+
return finalResponse;
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
return {
|
|
1994
|
+
success: false,
|
|
1995
|
+
error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1739
1998
|
}
|
|
1740
|
-
/**
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1999
|
+
/** POST /workflows — start a workflow via daemon */
|
|
2000
|
+
function startWorkflow(body) {
|
|
2001
|
+
return request("POST", "/workflows", body);
|
|
2002
|
+
}
|
|
2003
|
+
/** DELETE /workflows/:name/:tag — stop a workflow */
|
|
2004
|
+
function stopWorkflow(name, tag = "main") {
|
|
2005
|
+
return request("DELETE", `/workflows/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`);
|
|
2006
|
+
}
|
|
2007
|
+
/** Check if daemon is running */
|
|
2008
|
+
function isDaemonActive() {
|
|
2009
|
+
return getDaemonConnection() !== null;
|
|
1745
2010
|
}
|
|
1746
2011
|
|
|
1747
2012
|
//#endregion
|
|
1748
2013
|
//#region src/cli/output.ts
|
|
2014
|
+
var output_exports = /* @__PURE__ */ __exportAll({ outputJson: () => outputJson });
|
|
1749
2015
|
/**
|
|
1750
2016
|
* CLI Output Utilities
|
|
1751
2017
|
*
|
|
@@ -1764,388 +2030,304 @@ function outputJson(data) {
|
|
|
1764
2030
|
|
|
1765
2031
|
//#endregion
|
|
1766
2032
|
//#region src/cli/commands/agent.ts
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
if (
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
workflow,
|
|
1789
|
-
tag,
|
|
1790
|
-
instance,
|
|
1791
|
-
idleTimeout,
|
|
1792
|
-
backend,
|
|
1793
|
-
skills: options.skill,
|
|
1794
|
-
skillDirs: options.skillDir,
|
|
1795
|
-
importSkills: options.importSkill,
|
|
1796
|
-
tool: options.tool,
|
|
1797
|
-
feedback: options.feedback,
|
|
1798
|
-
schedule
|
|
1799
|
-
});
|
|
1800
|
-
else {
|
|
1801
|
-
const args = [
|
|
1802
|
-
process.argv[1] ?? "",
|
|
1803
|
-
"new",
|
|
1804
|
-
agentName,
|
|
1805
|
-
"-m",
|
|
1806
|
-
model,
|
|
1807
|
-
"-b",
|
|
1808
|
-
backend,
|
|
1809
|
-
"-s",
|
|
1810
|
-
system,
|
|
1811
|
-
"--foreground"
|
|
1812
|
-
];
|
|
1813
|
-
args.push("--idle-timeout", String(idleTimeout));
|
|
1814
|
-
if (options.feedback) args.push("--feedback");
|
|
1815
|
-
if (options.skill) for (const skillPath of options.skill) args.push("--skill", skillPath);
|
|
1816
|
-
if (options.skillDir) for (const dir of options.skillDir) args.push("--skill-dir", dir);
|
|
1817
|
-
if (options.importSkill) for (const spec of options.importSkill) args.push("--import-skill", spec);
|
|
1818
|
-
if (options.tool) args.push("--tool", options.tool);
|
|
1819
|
-
if (options.wakeup) args.push("--wakeup", options.wakeup);
|
|
1820
|
-
if (options.wakeupPrompt) args.push("--wakeup-prompt", options.wakeupPrompt);
|
|
1821
|
-
spawn(process.execPath, args, {
|
|
1822
|
-
detached: true,
|
|
1823
|
-
stdio: "ignore"
|
|
1824
|
-
}).unref();
|
|
1825
|
-
const info = await waitForReady(fullName, 5e3);
|
|
1826
|
-
if (info) {
|
|
1827
|
-
const targetDisplay = buildTargetDisplay(agentName, workflow, tag);
|
|
1828
|
-
if (options.json) outputJson({
|
|
1829
|
-
name: agentName,
|
|
1830
|
-
workflow,
|
|
1831
|
-
tag,
|
|
1832
|
-
instance,
|
|
1833
|
-
model: info.model,
|
|
1834
|
-
backend
|
|
1835
|
-
});
|
|
1836
|
-
else console.log(targetDisplay);
|
|
1837
|
-
} else {
|
|
1838
|
-
console.error("Failed to start agent");
|
|
1839
|
-
process.exit(1);
|
|
1840
|
-
}
|
|
2033
|
+
var agent_exports = /* @__PURE__ */ __exportAll({
|
|
2034
|
+
ensureDaemon: () => ensureDaemon,
|
|
2035
|
+
registerAgentCommands: () => registerAgentCommands
|
|
2036
|
+
});
|
|
2037
|
+
/**
|
|
2038
|
+
* Ensure daemon is running. If not, spawn it in background and wait.
|
|
2039
|
+
*/
|
|
2040
|
+
async function ensureDaemon(port, host) {
|
|
2041
|
+
if (isDaemonRunning()) return;
|
|
2042
|
+
const args = [process.argv[1] ?? "", "daemon"];
|
|
2043
|
+
if (port) args.push("--port", String(port));
|
|
2044
|
+
if (host) args.push("--host", host);
|
|
2045
|
+
spawn(process.execPath, args, {
|
|
2046
|
+
detached: true,
|
|
2047
|
+
stdio: "ignore"
|
|
2048
|
+
}).unref();
|
|
2049
|
+
const maxWait = 5e3;
|
|
2050
|
+
const start = Date.now();
|
|
2051
|
+
while (Date.now() - start < maxWait) {
|
|
2052
|
+
if (isDaemonRunning()) return;
|
|
2053
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1841
2054
|
}
|
|
2055
|
+
console.error("Failed to start daemon");
|
|
2056
|
+
process.exit(1);
|
|
1842
2057
|
}
|
|
1843
|
-
function
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
const sessionTag = s.tag || DEFAULT_TAG;
|
|
1850
|
-
return sessionWorkflow === target.workflow && sessionTag === target.tag;
|
|
2058
|
+
function registerAgentCommands(program) {
|
|
2059
|
+
program.command("daemon").description("Start daemon in foreground").option("--port <port>", `HTTP port (default: ${DEFAULT_PORT})`).option("--host <host>", "Host to bind to (default: 127.0.0.1)").action(async (options) => {
|
|
2060
|
+
const { startDaemon } = await Promise.resolve().then(() => daemon_exports);
|
|
2061
|
+
await startDaemon({
|
|
2062
|
+
port: options.port ? parseInt(options.port, 10) : void 0,
|
|
2063
|
+
host: options.host
|
|
1851
2064
|
});
|
|
1852
|
-
} else if (!options?.all && !targetInput) sessions = sessions.filter((s) => {
|
|
1853
|
-
return (s.workflow || s.instance || DEFAULT_WORKFLOW) === DEFAULT_WORKFLOW;
|
|
1854
2065
|
});
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
const parsed = s.name ? parseTarget(s.name) : null;
|
|
1858
|
-
return {
|
|
1859
|
-
id: s.id,
|
|
1860
|
-
name: parsed?.agent ?? null,
|
|
1861
|
-
workflow: s.workflow,
|
|
1862
|
-
tag: s.tag,
|
|
1863
|
-
instance: s.instance,
|
|
1864
|
-
model: s.model,
|
|
1865
|
-
backend: s.backend,
|
|
1866
|
-
running: isSessionRunning(s.id)
|
|
1867
|
-
};
|
|
1868
|
-
}));
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
if (sessions.length === 0) {
|
|
1872
|
-
console.log("No active agents");
|
|
1873
|
-
return;
|
|
1874
|
-
}
|
|
1875
|
-
const byWorkflow = /* @__PURE__ */ new Map();
|
|
1876
|
-
for (const s of sessions) {
|
|
1877
|
-
const key = buildTargetDisplay(void 0, s.workflow || s.instance || DEFAULT_WORKFLOW, s.tag || DEFAULT_TAG);
|
|
1878
|
-
if (!byWorkflow.has(key)) byWorkflow.set(key, []);
|
|
1879
|
-
byWorkflow.get(key).push(s);
|
|
1880
|
-
}
|
|
1881
|
-
for (const [workflowDisplay, agents] of byWorkflow) {
|
|
1882
|
-
if (byWorkflow.size > 1) console.log(`[${workflowDisplay}]`);
|
|
1883
|
-
for (const s of agents) {
|
|
1884
|
-
const status = isSessionRunning(s.id) ? "running" : "stopped";
|
|
1885
|
-
const displayName = s.name ? getAgentDisplayName(s.name) : s.id.slice(0, 8);
|
|
1886
|
-
const prefix = byWorkflow.size > 1 ? " " : "";
|
|
1887
|
-
console.log(`${prefix}${displayName.padEnd(12)} ${s.model.padEnd(30)} [${status}]`);
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
async function stopAgentAction(targetInput, options) {
|
|
1892
|
-
if (options?.all) {
|
|
1893
|
-
const sessions = listSessions();
|
|
1894
|
-
for (const s of sessions) if (isSessionRunning(s.id)) {
|
|
1895
|
-
await sendRequest({ action: "shutdown" }, s.id);
|
|
1896
|
-
const displayName = s.name ? getAgentDisplayName(s.name) : s.id.slice(0, 8);
|
|
1897
|
-
console.log(`Stopped: ${displayName}`);
|
|
1898
|
-
}
|
|
1899
|
-
return;
|
|
1900
|
-
}
|
|
1901
|
-
if (!targetInput) {
|
|
1902
|
-
console.error("Specify target agent or use --all");
|
|
1903
|
-
process.exit(1);
|
|
1904
|
-
}
|
|
1905
|
-
const target = parseTarget(targetInput);
|
|
1906
|
-
if (!target.agent) {
|
|
1907
|
-
let sessions = listSessions();
|
|
1908
|
-
sessions = sessions.filter((s) => {
|
|
1909
|
-
const sessionWorkflow = s.workflow || s.instance || DEFAULT_WORKFLOW;
|
|
1910
|
-
const sessionTag = s.tag || DEFAULT_TAG;
|
|
1911
|
-
return sessionWorkflow === target.workflow && sessionTag === target.tag;
|
|
1912
|
-
});
|
|
1913
|
-
if (sessions.length === 0) {
|
|
1914
|
-
console.error(`No agents found in ${buildTargetDisplay(void 0, target.workflow, target.tag)}`);
|
|
1915
|
-
process.exit(1);
|
|
1916
|
-
}
|
|
1917
|
-
for (const s of sessions) if (isSessionRunning(s.id)) {
|
|
1918
|
-
await sendRequest({ action: "shutdown" }, s.id);
|
|
1919
|
-
const displayName = s.name ? getAgentDisplayName(s.name) : s.id.slice(0, 8);
|
|
1920
|
-
console.log(`Stopped: ${displayName}`);
|
|
1921
|
-
}
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
const fullTarget = buildTarget(target.agent, target.workflow, target.tag);
|
|
1925
|
-
if (!isSessionRunning(fullTarget)) {
|
|
1926
|
-
console.error(`Agent not found: ${targetInput}`);
|
|
1927
|
-
process.exit(1);
|
|
1928
|
-
}
|
|
1929
|
-
const res = await sendRequest({ action: "shutdown" }, fullTarget);
|
|
1930
|
-
if (res.success) console.log("Agent stopped");
|
|
1931
|
-
else console.error("Error:", res.error);
|
|
1932
|
-
}
|
|
1933
|
-
function addNewCommandOptions(cmd) {
|
|
1934
|
-
return cmd.option("-m, --model <model>", `Model identifier (default: ${getDefaultModel()})`).addOption(new Option("-b, --backend <type>", "Backend type").choices([
|
|
2066
|
+
program.command("new <name>").description("Create a new agent").option("-m, --model <model>", `Model identifier (default: ${getDefaultModel()})`).addOption(new Option("-b, --backend <type>", "Backend type").choices([
|
|
2067
|
+
"default",
|
|
1935
2068
|
"sdk",
|
|
1936
2069
|
"claude",
|
|
1937
2070
|
"codex",
|
|
1938
2071
|
"cursor",
|
|
1939
2072
|
"mock"
|
|
1940
|
-
]).default("
|
|
1941
|
-
}
|
|
1942
|
-
function registerAgentCommands(program) {
|
|
1943
|
-
addNewCommandOptions(program.command("new [name]").description("Create a new standalone agent (auto-names if omitted: a0, a1, ...)").addHelpText("after", `
|
|
1944
|
-
Examples:
|
|
1945
|
-
$ agent-worker new alice -m anthropic/claude-sonnet-4-5 # Create standalone agent
|
|
1946
|
-
$ agent-worker new -b mock # Quick testing without API key
|
|
1947
|
-
$ agent-worker new monitor --wakeup 30s # Agent with scheduled wakeup
|
|
1948
|
-
|
|
1949
|
-
Note: Agent Mode creates standalone agents in the global workflow.
|
|
1950
|
-
For coordinated multi-agent workflows, use Workflow Mode (YAML files).
|
|
1951
|
-
`)).action(createAgentAction);
|
|
1952
|
-
program.command("ls [target]").description("List agents (default: global workflow)").option("--json", "Output as JSON").option("--all", "Show agents from all workflows").addHelpText("after", `
|
|
2073
|
+
]).default("default")).option("-s, --system <prompt>", "System prompt", "You are a helpful assistant.").option("-f, --system-file <file>", "Read system prompt from file").option("--workflow <name>", "Workflow name (default: global)").option("--tag <tag>", "Workflow instance tag (default: main)").option("--port <port>", `Daemon port if starting new daemon (default: ${DEFAULT_PORT})`).option("--host <host>", "Daemon host (default: 127.0.0.1)").option("--json", "Output as JSON").addHelpText("after", `
|
|
1953
2074
|
Examples:
|
|
1954
|
-
$ agent-worker
|
|
1955
|
-
$ agent-worker
|
|
1956
|
-
$ agent-worker
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
error: target ? `Not found: ${target}` : "No active agent"
|
|
1971
|
-
});
|
|
1972
|
-
else console.error(target ? `Agent not found: ${target}` : "No active agent");
|
|
1973
|
-
process.exit(1);
|
|
1974
|
-
}
|
|
1975
|
-
const res = await sendRequest({ action: "ping" }, target);
|
|
1976
|
-
if (res.success && res.data) {
|
|
1977
|
-
const data = res.data;
|
|
1978
|
-
if (options.json) outputJson({
|
|
1979
|
-
...data,
|
|
1980
|
-
running: true
|
|
1981
|
-
});
|
|
1982
|
-
else {
|
|
1983
|
-
const nameStr = data.name ? ` (${data.name})` : "";
|
|
1984
|
-
console.log(`Agent: ${data.id}${nameStr}`);
|
|
1985
|
-
console.log(`Model: ${data.model}`);
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
});
|
|
1989
|
-
program.command("use <target>").description("Set default agent").option("--json", "Output as JSON").action((target, options) => {
|
|
1990
|
-
if (setDefaultSession(target)) if (options.json) outputJson({ target });
|
|
1991
|
-
else console.log(`Default agent set to: ${target}`);
|
|
1992
|
-
else {
|
|
1993
|
-
console.error(`Agent not found: ${target}`);
|
|
1994
|
-
process.exit(1);
|
|
1995
|
-
}
|
|
1996
|
-
});
|
|
1997
|
-
const scheduleCmd = program.command("schedule").description("Manage scheduled wakeup for agents").addHelpText("after", `
|
|
1998
|
-
Examples:
|
|
1999
|
-
$ agent-worker schedule alice set 30s # Wake alice every 30 seconds
|
|
2000
|
-
$ agent-worker schedule alice set 5m --prompt "Status?" # With custom prompt
|
|
2001
|
-
$ agent-worker schedule alice get # View current schedule
|
|
2002
|
-
$ agent-worker schedule alice clear # Remove schedule
|
|
2003
|
-
`);
|
|
2004
|
-
scheduleCmd.command("get [target]").description("Show current wakeup schedule").option("--json", "Output as JSON").action(async (target, options) => {
|
|
2005
|
-
const res = await sendRequest({ action: "schedule_get" }, target);
|
|
2006
|
-
if (!res.success) {
|
|
2007
|
-
console.error("Error:", res.error);
|
|
2008
|
-
process.exit(1);
|
|
2009
|
-
}
|
|
2010
|
-
if (options.json) outputJson(res.data);
|
|
2011
|
-
else if (res.data) {
|
|
2012
|
-
const s = res.data;
|
|
2013
|
-
console.log(`Wakeup: ${s.wakeup}`);
|
|
2014
|
-
if (s.prompt) console.log(`Prompt: ${s.prompt}`);
|
|
2015
|
-
else console.log("Prompt: (default)");
|
|
2016
|
-
} else console.log("No wakeup schedule configured");
|
|
2017
|
-
});
|
|
2018
|
-
scheduleCmd.command("set <wakeup>").description("Set wakeup schedule (ms number, duration 30s/5m/2h, or cron expression)").option("-p, --prompt <prompt>", "Custom wakeup prompt").option("--to <target>", "Target agent").option("--json", "Output as JSON").action(async (wakeup, options) => {
|
|
2019
|
-
const wakeupValue = /^\d+$/.test(wakeup) ? parseInt(wakeup, 10) : wakeup;
|
|
2020
|
-
const payload = { wakeup: wakeupValue };
|
|
2021
|
-
if (options.prompt) payload.prompt = options.prompt;
|
|
2022
|
-
const res = await sendRequest({
|
|
2023
|
-
action: "schedule_set",
|
|
2024
|
-
payload
|
|
2025
|
-
}, options.to);
|
|
2026
|
-
if (res.success) if (options.json) outputJson({
|
|
2027
|
-
wakeup: wakeupValue,
|
|
2028
|
-
prompt: options.prompt || null
|
|
2075
|
+
$ agent-worker new alice -m anthropic/claude-sonnet-4-5
|
|
2076
|
+
$ agent-worker new bot -b mock
|
|
2077
|
+
$ agent-worker new reviewer --workflow review --tag pr-123
|
|
2078
|
+
`).action(async (name, options) => {
|
|
2079
|
+
let system = options.system;
|
|
2080
|
+
if (options.systemFile) system = readFileSync(options.systemFile, "utf-8");
|
|
2081
|
+
const backend = normalizeBackendType(options.backend ?? "default");
|
|
2082
|
+
const model = options.model || getDefaultModel();
|
|
2083
|
+
await ensureDaemon(options.port ? parseInt(options.port, 10) : void 0, options.host);
|
|
2084
|
+
const res = await createAgent({
|
|
2085
|
+
name,
|
|
2086
|
+
model,
|
|
2087
|
+
system,
|
|
2088
|
+
backend,
|
|
2089
|
+
workflow: options.workflow,
|
|
2090
|
+
tag: options.tag
|
|
2029
2091
|
});
|
|
2030
|
-
|
|
2031
|
-
console.log(`Wakeup set: ${wakeup}`);
|
|
2032
|
-
if (options.prompt) console.log(`Prompt: ${options.prompt}`);
|
|
2033
|
-
}
|
|
2034
|
-
else {
|
|
2035
|
-
console.error("Error:", res.error);
|
|
2036
|
-
process.exit(1);
|
|
2037
|
-
}
|
|
2038
|
-
});
|
|
2039
|
-
scheduleCmd.command("clear [target]").description("Remove scheduled wakeup").option("--json", "Output as JSON").action(async (target, options) => {
|
|
2040
|
-
const res = await sendRequest({ action: "schedule_clear" }, target);
|
|
2041
|
-
if (res.success) if (options.json) outputJson({ success: true });
|
|
2042
|
-
else console.log("Schedule cleared");
|
|
2043
|
-
else {
|
|
2092
|
+
if (res.error) {
|
|
2044
2093
|
console.error("Error:", res.error);
|
|
2045
2094
|
process.exit(1);
|
|
2046
2095
|
}
|
|
2096
|
+
if (options.json) outputJson(res);
|
|
2097
|
+
else console.log(`${name} (${model})`);
|
|
2047
2098
|
});
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
//#endregion
|
|
2051
|
-
//#region src/cli/commands/send.ts
|
|
2052
|
-
/**
|
|
2053
|
-
* Get a context provider for the given workflow:tag.
|
|
2054
|
-
* Auto-provisions the context directory if it doesn't exist.
|
|
2055
|
-
*/
|
|
2056
|
-
function getContextProvider(workflow, tag, instanceFallback) {
|
|
2057
|
-
const dir = getDefaultContextDir(workflow, tag);
|
|
2058
|
-
mkdirSync(dir, { recursive: true });
|
|
2059
|
-
return createFileContextProvider(dir, [...getInstanceAgentNames(instanceFallback || workflow), "user"]);
|
|
2060
|
-
}
|
|
2061
|
-
function registerSendCommands(program) {
|
|
2062
|
-
program.command("send <target> <message>").description("Send message to agent or workflow. Use @workflow for broadcast or @mentions within workflow.").option("--json", "Output as JSON").addHelpText("after", `
|
|
2099
|
+
program.command("ls").description("List agents").option("--json", "Output as JSON").addHelpText("after", `
|
|
2063
2100
|
Examples:
|
|
2064
|
-
$ agent-worker
|
|
2065
|
-
$ agent-worker
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
target: target.display
|
|
2077
|
-
});
|
|
2078
|
-
else if (entry.mentions.length > 0) console.log(`→ @${entry.mentions.join(" @")}`);
|
|
2079
|
-
else console.log("→ (broadcast)");
|
|
2080
|
-
});
|
|
2081
|
-
program.command("peek [target]").description("View channel messages (default: @global)").option("--json", "Output as JSON").option("--all", "Show all messages").option("-n, --last <count>", "Show last N messages", parseInt).option("--find <text>", "Filter messages containing text (case-insensitive)").addHelpText("after", `
|
|
2082
|
-
Examples:
|
|
2083
|
-
$ agent-worker peek # View @global:main
|
|
2084
|
-
$ agent-worker peek @review # View @review:main
|
|
2085
|
-
$ agent-worker peek @review:pr-123 # View specific workflow:tag
|
|
2086
|
-
`).action(async (targetInput, options) => {
|
|
2087
|
-
const target = parseTarget(targetInput || `@${DEFAULT_WORKFLOW}`);
|
|
2088
|
-
const provider = getContextProvider(target.workflow, target.tag, target.workflow);
|
|
2089
|
-
const limit = options.all ? void 0 : options.last ?? 10;
|
|
2090
|
-
let messages = await provider.readChannel({ limit });
|
|
2091
|
-
if (options.find) {
|
|
2092
|
-
const searchText = options.find.toLowerCase();
|
|
2093
|
-
messages = messages.filter((msg) => msg.content.toLowerCase().includes(searchText));
|
|
2101
|
+
$ agent-worker ls
|
|
2102
|
+
$ agent-worker ls --json
|
|
2103
|
+
`).action(async (options) => {
|
|
2104
|
+
if (!isDaemonActive()) {
|
|
2105
|
+
if (options.json) outputJson({ agents: [] });
|
|
2106
|
+
else console.log("No daemon running");
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const res = await listAgents();
|
|
2110
|
+
if (res.error) {
|
|
2111
|
+
console.error("Error:", res.error);
|
|
2112
|
+
process.exit(1);
|
|
2094
2113
|
}
|
|
2114
|
+
const agents = res.agents ?? [];
|
|
2095
2115
|
if (options.json) {
|
|
2096
|
-
outputJson(
|
|
2116
|
+
outputJson({ agents });
|
|
2097
2117
|
return;
|
|
2098
2118
|
}
|
|
2099
|
-
if (
|
|
2100
|
-
console.log(
|
|
2119
|
+
if (agents.length === 0) {
|
|
2120
|
+
console.log("No agents");
|
|
2101
2121
|
return;
|
|
2102
2122
|
}
|
|
2103
|
-
for (const
|
|
2104
|
-
|
|
2105
|
-
const
|
|
2106
|
-
console.log(
|
|
2123
|
+
for (const a of agents) {
|
|
2124
|
+
const wf = a.tag === "main" ? `@${a.workflow}` : `@${a.workflow}:${a.tag}`;
|
|
2125
|
+
const info = a.model || a.state || "";
|
|
2126
|
+
console.log(`${a.name.padEnd(12)} ${info.padEnd(30)} ${wf}`);
|
|
2107
2127
|
}
|
|
2108
2128
|
});
|
|
2109
|
-
program.command("
|
|
2110
|
-
|
|
2111
|
-
|
|
2129
|
+
program.command("stop [name]").description("Stop agent, workflow, or daemon").option("--all", "Stop daemon (all agents and workflows)").addHelpText("after", `
|
|
2130
|
+
Examples:
|
|
2131
|
+
$ agent-worker stop alice # Stop specific agent
|
|
2132
|
+
$ agent-worker stop @review:pr-123 # Stop workflow
|
|
2133
|
+
$ agent-worker stop @review # Stop workflow (tag defaults to main)
|
|
2134
|
+
$ agent-worker stop --all # Stop daemon (everything)
|
|
2135
|
+
`).action(async (name, options) => {
|
|
2136
|
+
if (!isDaemonActive()) {
|
|
2137
|
+
console.error("No daemon running");
|
|
2112
2138
|
process.exit(1);
|
|
2113
2139
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
console.
|
|
2140
|
+
if (options.all) {
|
|
2141
|
+
const res = await shutdown();
|
|
2142
|
+
if (res.success) console.log("Daemon stopped");
|
|
2143
|
+
else console.error("Error:", res.error);
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (!name) {
|
|
2147
|
+
console.error("Specify agent name, @workflow[:tag], or use --all");
|
|
2117
2148
|
process.exit(1);
|
|
2118
2149
|
}
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2150
|
+
const { parseTarget } = await Promise.resolve().then(() => target_exports);
|
|
2151
|
+
const target = parseTarget(name);
|
|
2152
|
+
let res;
|
|
2153
|
+
if (target.agent === void 0) {
|
|
2154
|
+
const { stopWorkflow: stopWf } = await Promise.resolve().then(() => client_exports);
|
|
2155
|
+
res = await stopWf(target.workflow, target.tag);
|
|
2156
|
+
} else res = await deleteAgent(target.agent);
|
|
2157
|
+
if (res.success) console.log(`Stopped: ${target.display}`);
|
|
2121
2158
|
else {
|
|
2122
|
-
console.
|
|
2123
|
-
|
|
2159
|
+
console.error("Error:", res.error);
|
|
2160
|
+
process.exit(1);
|
|
2124
2161
|
}
|
|
2125
2162
|
});
|
|
2126
|
-
program.command("
|
|
2127
|
-
if (!
|
|
2128
|
-
|
|
2129
|
-
|
|
2163
|
+
program.command("status").description("Show daemon status").option("--json", "Output as JSON").action(async (options) => {
|
|
2164
|
+
if (!isDaemonActive()) {
|
|
2165
|
+
if (options.json) outputJson({ running: false });
|
|
2166
|
+
else console.log("Daemon not running");
|
|
2167
|
+
return;
|
|
2130
2168
|
}
|
|
2131
|
-
const res = await
|
|
2132
|
-
if (
|
|
2133
|
-
|
|
2169
|
+
const res = await health();
|
|
2170
|
+
if (options.json) outputJson(res);
|
|
2171
|
+
else {
|
|
2172
|
+
console.log(`Daemon: pid=${res.pid} port=${res.port}`);
|
|
2173
|
+
const agents = res.agents ?? [];
|
|
2174
|
+
console.log(`Agents: ${agents.length > 0 ? agents.join(", ") : "(none)"}`);
|
|
2175
|
+
const workflows = res.workflows ?? [];
|
|
2176
|
+
if (workflows.length > 0) {
|
|
2177
|
+
console.log(`Workflows:`);
|
|
2178
|
+
for (const wf of workflows) {
|
|
2179
|
+
const display = wf.tag === "main" ? `@${wf.name}` : `@${wf.name}:${wf.tag}`;
|
|
2180
|
+
console.log(` ${display} → ${wf.agents.join(", ")}`);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
if (res.uptime) {
|
|
2184
|
+
const secs = Math.round(res.uptime / 1e3);
|
|
2185
|
+
console.log(`Uptime: ${secs}s`);
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
program.command("ask <agent> <message>").description("Send message to agent (SSE streaming)").option("--json", "Output final response as JSON").addHelpText("after", `
|
|
2190
|
+
Examples:
|
|
2191
|
+
$ agent-worker ask alice "analyze this code"
|
|
2192
|
+
$ agent-worker ask alice "hello" --json
|
|
2193
|
+
`).action(async (agent, message, options) => {
|
|
2194
|
+
if (!isDaemonActive()) {
|
|
2195
|
+
console.error("No daemon running");
|
|
2134
2196
|
process.exit(1);
|
|
2135
2197
|
}
|
|
2136
|
-
|
|
2198
|
+
const res = await run({
|
|
2199
|
+
agent,
|
|
2200
|
+
message
|
|
2201
|
+
}, (chunk) => {
|
|
2202
|
+
if (!options.json) process.stdout.write(chunk.text);
|
|
2203
|
+
});
|
|
2204
|
+
if (options.json) outputJson(res);
|
|
2205
|
+
else console.log();
|
|
2137
2206
|
});
|
|
2138
|
-
program.command("
|
|
2139
|
-
if (!
|
|
2140
|
-
console.error(
|
|
2207
|
+
program.command("serve <agent> <message>").description("Send message to agent (sync response)").option("--json", "Output as JSON").action(async (agent, message, options) => {
|
|
2208
|
+
if (!isDaemonActive()) {
|
|
2209
|
+
console.error("No daemon running");
|
|
2141
2210
|
process.exit(1);
|
|
2142
2211
|
}
|
|
2143
|
-
const res = await
|
|
2144
|
-
|
|
2145
|
-
|
|
2212
|
+
const res = await serve({
|
|
2213
|
+
agent,
|
|
2214
|
+
message
|
|
2215
|
+
});
|
|
2216
|
+
if (options.json) outputJson(res);
|
|
2217
|
+
else if (res.error) {
|
|
2218
|
+
console.error("Error:", res.error);
|
|
2219
|
+
process.exit(1);
|
|
2220
|
+
} else console.log(res.content ?? JSON.stringify(res));
|
|
2146
2221
|
});
|
|
2147
2222
|
}
|
|
2148
2223
|
|
|
2224
|
+
//#endregion
|
|
2225
|
+
//#region src/cli/target.ts
|
|
2226
|
+
var target_exports = /* @__PURE__ */ __exportAll({
|
|
2227
|
+
DEFAULT_TAG: () => DEFAULT_TAG,
|
|
2228
|
+
DEFAULT_WORKFLOW: () => DEFAULT_WORKFLOW,
|
|
2229
|
+
parseTarget: () => parseTarget
|
|
2230
|
+
});
|
|
2231
|
+
/**
|
|
2232
|
+
* Target identifier utilities
|
|
2233
|
+
*
|
|
2234
|
+
* Format: agent@workflow:tag (inspired by Docker image:tag)
|
|
2235
|
+
* - agent: agent name (optional for @workflow references)
|
|
2236
|
+
* - workflow: workflow name (optional, defaults to 'global')
|
|
2237
|
+
* - tag: workflow instance tag (optional, defaults to 'main')
|
|
2238
|
+
*
|
|
2239
|
+
* Examples:
|
|
2240
|
+
* - "alice" → { agent: "alice", workflow: "global", tag: "main", display: "alice" }
|
|
2241
|
+
* - "alice@review" → { agent: "alice", workflow: "review", tag: "main", display: "alice@review" }
|
|
2242
|
+
* - "alice@review:pr-123"→ { agent: "alice", workflow: "review", tag: "pr-123", display: "alice@review:pr-123" }
|
|
2243
|
+
* - "@review" → { agent: undefined, workflow: "review", tag: "main", display: "@review" }
|
|
2244
|
+
* - "@review:pr-123" → { agent: undefined, workflow: "review", tag: "pr-123", display: "@review:pr-123" }
|
|
2245
|
+
*
|
|
2246
|
+
* Display rules:
|
|
2247
|
+
* - Omit @global (standalone agents): "alice" not "alice@global"
|
|
2248
|
+
* - Omit :main (default tag): "alice@review" not "alice@review:main"
|
|
2249
|
+
*/
|
|
2250
|
+
const DEFAULT_WORKFLOW = "global";
|
|
2251
|
+
const DEFAULT_TAG = "main";
|
|
2252
|
+
/**
|
|
2253
|
+
* Parse target identifier from string
|
|
2254
|
+
* Supports: "agent", "agent@workflow", "agent@workflow:tag", "@workflow", "@workflow:tag"
|
|
2255
|
+
*/
|
|
2256
|
+
function parseTarget(input) {
|
|
2257
|
+
if (input.startsWith("@")) {
|
|
2258
|
+
const workflowPart = input.slice(1);
|
|
2259
|
+
const colonIndex = workflowPart.indexOf(":");
|
|
2260
|
+
if (colonIndex === -1) {
|
|
2261
|
+
const workflow = workflowPart || DEFAULT_WORKFLOW;
|
|
2262
|
+
return {
|
|
2263
|
+
agent: void 0,
|
|
2264
|
+
workflow,
|
|
2265
|
+
tag: DEFAULT_TAG,
|
|
2266
|
+
full: `@${workflow}:${DEFAULT_TAG}`,
|
|
2267
|
+
display: workflow === DEFAULT_WORKFLOW ? `@${workflow}` : `@${workflow}`
|
|
2268
|
+
};
|
|
2269
|
+
} else {
|
|
2270
|
+
const workflow = workflowPart.slice(0, colonIndex) || DEFAULT_WORKFLOW;
|
|
2271
|
+
const tag = workflowPart.slice(colonIndex + 1) || DEFAULT_TAG;
|
|
2272
|
+
return {
|
|
2273
|
+
agent: void 0,
|
|
2274
|
+
workflow,
|
|
2275
|
+
tag,
|
|
2276
|
+
full: `@${workflow}:${tag}`,
|
|
2277
|
+
display: buildDisplay(void 0, workflow, tag)
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
const atIndex = input.indexOf("@");
|
|
2282
|
+
if (atIndex === -1) return {
|
|
2283
|
+
agent: input,
|
|
2284
|
+
workflow: DEFAULT_WORKFLOW,
|
|
2285
|
+
tag: DEFAULT_TAG,
|
|
2286
|
+
full: `${input}@${DEFAULT_WORKFLOW}:${DEFAULT_TAG}`,
|
|
2287
|
+
display: input
|
|
2288
|
+
};
|
|
2289
|
+
const agent = input.slice(0, atIndex);
|
|
2290
|
+
const workflowPart = input.slice(atIndex + 1);
|
|
2291
|
+
const colonIndex = workflowPart.indexOf(":");
|
|
2292
|
+
if (colonIndex === -1) {
|
|
2293
|
+
const workflow = workflowPart || DEFAULT_WORKFLOW;
|
|
2294
|
+
return {
|
|
2295
|
+
agent,
|
|
2296
|
+
workflow,
|
|
2297
|
+
tag: DEFAULT_TAG,
|
|
2298
|
+
full: `${agent}@${workflow}:${DEFAULT_TAG}`,
|
|
2299
|
+
display: buildDisplay(agent, workflow, DEFAULT_TAG)
|
|
2300
|
+
};
|
|
2301
|
+
} else {
|
|
2302
|
+
const workflow = workflowPart.slice(0, colonIndex) || DEFAULT_WORKFLOW;
|
|
2303
|
+
const tag = workflowPart.slice(colonIndex + 1) || DEFAULT_TAG;
|
|
2304
|
+
return {
|
|
2305
|
+
agent,
|
|
2306
|
+
workflow,
|
|
2307
|
+
tag,
|
|
2308
|
+
full: `${agent}@${workflow}:${tag}`,
|
|
2309
|
+
display: buildDisplay(agent, workflow, tag)
|
|
2310
|
+
};
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Build display string following display rules:
|
|
2315
|
+
* - Omit @global for standalone agents
|
|
2316
|
+
* - Omit :main for default tag
|
|
2317
|
+
*/
|
|
2318
|
+
function buildDisplay(agent, workflow, tag) {
|
|
2319
|
+
const isGlobal = workflow === DEFAULT_WORKFLOW;
|
|
2320
|
+
const isMainTag = tag === DEFAULT_TAG;
|
|
2321
|
+
if (agent === void 0) {
|
|
2322
|
+
if (isMainTag) return `@${workflow}`;
|
|
2323
|
+
return `@${workflow}:${tag}`;
|
|
2324
|
+
}
|
|
2325
|
+
if (isGlobal && isMainTag) return agent;
|
|
2326
|
+
if (isGlobal && !isMainTag) return `${agent}@${workflow}:${tag}`;
|
|
2327
|
+
if (!isGlobal && isMainTag) return `${agent}@${workflow}`;
|
|
2328
|
+
return `${agent}@${workflow}:${tag}`;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2149
2331
|
//#endregion
|
|
2150
2332
|
//#region src/cli/commands/workflow.ts
|
|
2151
2333
|
function registerWorkflowCommands(program) {
|
|
@@ -2157,7 +2339,7 @@ Examples:
|
|
|
2157
2339
|
|
|
2158
2340
|
Note: Workflow name is inferred from YAML 'name' field or filename
|
|
2159
2341
|
`).action(async (file, options) => {
|
|
2160
|
-
const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-
|
|
2342
|
+
const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-CNlUyGit.mjs");
|
|
2161
2343
|
const tag = options.tag || DEFAULT_TAG;
|
|
2162
2344
|
const parsedWorkflow = await parseWorkflowFile(file, { tag });
|
|
2163
2345
|
const workflowName = parsedWorkflow.name;
|
|
@@ -2168,8 +2350,8 @@ Note: Workflow name is inferred from YAML 'name' field or filename
|
|
|
2168
2350
|
isCleaningUp = true;
|
|
2169
2351
|
console.log("\nInterrupted, cleaning up...");
|
|
2170
2352
|
if (controllers) {
|
|
2171
|
-
const { shutdownControllers } = await import("../workflow-
|
|
2172
|
-
const { createSilentLogger } = await import("../logger-
|
|
2353
|
+
const { shutdownControllers } = await import("../workflow-CNlUyGit.mjs");
|
|
2354
|
+
const { createSilentLogger } = await import("../logger-Bfdo83xL.mjs");
|
|
2173
2355
|
await shutdownControllers(controllers, createSilentLogger());
|
|
2174
2356
|
}
|
|
2175
2357
|
process.exit(130);
|
|
@@ -2206,7 +2388,7 @@ Note: Workflow name is inferred from YAML 'name' field or filename
|
|
|
2206
2388
|
feedback: result.feedback
|
|
2207
2389
|
}, null, 2));
|
|
2208
2390
|
else if (!options.debug) {
|
|
2209
|
-
const { showWorkflowSummary } = await import("../display-pretty-
|
|
2391
|
+
const { showWorkflowSummary } = await import("../display-pretty-BCJq5v9d.mjs");
|
|
2210
2392
|
showWorkflowSummary({
|
|
2211
2393
|
duration: result.duration,
|
|
2212
2394
|
document: finalDoc,
|
|
@@ -2230,144 +2412,130 @@ Note: Workflow name is inferred from YAML 'name' field or filename
|
|
|
2230
2412
|
process.exit(1);
|
|
2231
2413
|
}
|
|
2232
2414
|
});
|
|
2233
|
-
program.command("start <file>").description("Start workflow and keep agents running").option("--tag <tag>", "Workflow instance tag (default: main)", DEFAULT_TAG).option("
|
|
2415
|
+
program.command("start <file>").description("Start workflow via daemon and keep agents running").option("--tag <tag>", "Workflow instance tag (default: main)", DEFAULT_TAG).option("--feedback", "Enable feedback tool (agents can report tool/workflow observations)").option("--json", "Output as JSON").addHelpText("after", `
|
|
2234
2416
|
Examples:
|
|
2235
|
-
$ agent-worker start review.yaml #
|
|
2236
|
-
$ agent-worker start review.yaml --background # Background daemon
|
|
2417
|
+
$ agent-worker start review.yaml # Start review:main (Ctrl+C to stop)
|
|
2237
2418
|
$ agent-worker start review.yaml --tag pr-123 # Start review:pr-123
|
|
2238
2419
|
|
|
2420
|
+
Workflow runs inside the daemon. Use ls/stop to manage:
|
|
2421
|
+
$ agent-worker ls # List all agents
|
|
2422
|
+
$ agent-worker stop @review:pr-123 # Stop workflow
|
|
2423
|
+
|
|
2239
2424
|
Note: Workflow name is inferred from YAML 'name' field or filename
|
|
2240
2425
|
`).action(async (file, options) => {
|
|
2241
|
-
const { parseWorkflowFile
|
|
2426
|
+
const { parseWorkflowFile } = await import("../workflow-CNlUyGit.mjs");
|
|
2427
|
+
const { ensureDaemon } = await Promise.resolve().then(() => agent_exports);
|
|
2242
2428
|
const tag = options.tag || DEFAULT_TAG;
|
|
2243
2429
|
const parsedWorkflow = await parseWorkflowFile(file, { tag });
|
|
2244
2430
|
const workflowName = parsedWorkflow.name;
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2431
|
+
await ensureDaemon();
|
|
2432
|
+
const res = await startWorkflow({
|
|
2433
|
+
workflow: parsedWorkflow,
|
|
2434
|
+
tag,
|
|
2435
|
+
feedback: options.feedback
|
|
2436
|
+
});
|
|
2437
|
+
if (res.error) {
|
|
2438
|
+
console.error("Error:", res.error);
|
|
2439
|
+
process.exit(1);
|
|
2440
|
+
}
|
|
2441
|
+
const agents = res.agents ?? [];
|
|
2442
|
+
if (options.json) {
|
|
2443
|
+
const { outputJson } = await Promise.resolve().then(() => output_exports);
|
|
2444
|
+
outputJson({
|
|
2445
|
+
name: workflowName,
|
|
2446
|
+
tag,
|
|
2447
|
+
agents
|
|
2258
2448
|
});
|
|
2259
|
-
child.unref();
|
|
2260
|
-
console.log(`Workflow: ${workflowName}:${tag}`);
|
|
2261
|
-
console.log(`PID: ${child.pid}`);
|
|
2262
|
-
console.log(`Context: ${contextDir}`);
|
|
2263
|
-
console.log(`\nTo monitor:`);
|
|
2264
|
-
console.log(` agent-worker ls @${workflowName}:${tag}`);
|
|
2265
|
-
console.log(` agent-worker peek @${workflowName}:${tag}`);
|
|
2266
|
-
console.log(`\nTo stop:`);
|
|
2267
|
-
console.log(` agent-worker stop @${workflowName}:${tag}`);
|
|
2268
2449
|
return;
|
|
2269
2450
|
}
|
|
2270
|
-
|
|
2451
|
+
console.log(`Workflow: @${workflowName}${tag !== "main" ? ":" + tag : ""}`);
|
|
2452
|
+
console.log(`Agents: ${agents.join(", ")}`);
|
|
2453
|
+
console.log(`\nTo monitor:`);
|
|
2454
|
+
console.log(` agent-worker ls`);
|
|
2455
|
+
console.log(` agent-worker peek @${workflowName}${tag !== "main" ? ":" + tag : ""}`);
|
|
2456
|
+
console.log(`\nTo stop:`);
|
|
2457
|
+
console.log(` agent-worker stop @${workflowName}${tag !== "main" ? ":" + tag : ""}`);
|
|
2458
|
+
let isCleaningUp = false;
|
|
2271
2459
|
const cleanup = async () => {
|
|
2272
|
-
|
|
2273
|
-
|
|
2460
|
+
if (isCleaningUp) return;
|
|
2461
|
+
isCleaningUp = true;
|
|
2462
|
+
console.log("\nStopping workflow...");
|
|
2463
|
+
await stopWorkflow(workflowName, tag);
|
|
2274
2464
|
process.exit(0);
|
|
2275
2465
|
};
|
|
2276
2466
|
process.on("SIGINT", cleanup);
|
|
2277
2467
|
process.on("SIGTERM", cleanup);
|
|
2278
|
-
|
|
2279
|
-
const result = await runWorkflowWithControllers({
|
|
2280
|
-
workflow: parsedWorkflow,
|
|
2281
|
-
workflowName,
|
|
2282
|
-
tag,
|
|
2283
|
-
instance: `${workflowName}:${tag}`,
|
|
2284
|
-
debug: options.debug,
|
|
2285
|
-
log: console.log,
|
|
2286
|
-
mode: "start",
|
|
2287
|
-
feedback: options.feedback
|
|
2288
|
-
});
|
|
2289
|
-
if (!result.success) {
|
|
2290
|
-
console.error("Workflow failed:", result.error);
|
|
2291
|
-
process.exit(1);
|
|
2292
|
-
}
|
|
2293
|
-
shutdownFn = result.shutdown;
|
|
2294
|
-
await new Promise(() => {});
|
|
2295
|
-
} catch (error) {
|
|
2296
|
-
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
2297
|
-
await cleanup();
|
|
2298
|
-
process.exit(1);
|
|
2299
|
-
}
|
|
2468
|
+
await new Promise(() => {});
|
|
2300
2469
|
});
|
|
2301
2470
|
}
|
|
2302
2471
|
|
|
2303
2472
|
//#endregion
|
|
2304
|
-
//#region src/cli/commands/
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
const
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2473
|
+
//#region src/cli/commands/send.ts
|
|
2474
|
+
/**
|
|
2475
|
+
* Get agent names for a workflow from the daemon.
|
|
2476
|
+
* Falls back to ["user"] if daemon is not running.
|
|
2477
|
+
*/
|
|
2478
|
+
async function getWorkflowAgentNames(workflow, tag) {
|
|
2479
|
+
if (!isDaemonActive()) return ["user"];
|
|
2480
|
+
try {
|
|
2481
|
+
const names = ((await listAgents()).agents ?? []).filter((a) => a.workflow === workflow && a.tag === tag).map((a) => a.name);
|
|
2482
|
+
return [...new Set([...names, "user"])];
|
|
2483
|
+
} catch {
|
|
2484
|
+
return ["user"];
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Get a context provider for the given workflow:tag.
|
|
2489
|
+
*/
|
|
2490
|
+
async function getContextProvider(workflow, tag) {
|
|
2491
|
+
const dir = getDefaultContextDir(workflow, tag);
|
|
2492
|
+
mkdirSync(dir, { recursive: true });
|
|
2493
|
+
return createFileContextProvider(dir, await getWorkflowAgentNames(workflow, tag));
|
|
2494
|
+
}
|
|
2495
|
+
function registerSendCommands(program) {
|
|
2496
|
+
program.command("send <target> <message>").description("Send message to agent or workflow channel").option("--json", "Output as JSON").addHelpText("after", `
|
|
2497
|
+
Examples:
|
|
2498
|
+
$ agent-worker send alice "analyze this code"
|
|
2499
|
+
$ agent-worker send @review "team update"
|
|
2500
|
+
$ agent-worker send @review "@alice @bob discuss this"
|
|
2501
|
+
`).action(async (targetInput, message, options) => {
|
|
2502
|
+
const target = parseTarget(targetInput);
|
|
2503
|
+
const entry = await (await getContextProvider(target.workflow, target.tag)).appendChannel("user", message);
|
|
2504
|
+
if (options.json) outputJson({
|
|
2505
|
+
id: entry.id,
|
|
2506
|
+
timestamp: entry.timestamp,
|
|
2507
|
+
mentions: entry.mentions,
|
|
2508
|
+
target: target.display
|
|
2509
|
+
});
|
|
2510
|
+
else if (entry.mentions.length > 0) console.log(`→ @${entry.mentions.join(" @")}`);
|
|
2511
|
+
else console.log("→ (broadcast)");
|
|
2512
|
+
});
|
|
2513
|
+
program.command("peek [target]").description("View channel messages (default: @global)").option("--json", "Output as JSON").option("--all", "Show all messages").option("-n, --last <count>", "Show last N messages", parseInt).option("--find <text>", "Filter messages containing text").addHelpText("after", `
|
|
2514
|
+
Examples:
|
|
2515
|
+
$ agent-worker peek
|
|
2516
|
+
$ agent-worker peek @review
|
|
2517
|
+
$ agent-worker peek @review:pr-123
|
|
2518
|
+
`).action(async (targetInput, options) => {
|
|
2519
|
+
const target = parseTarget(targetInput || `@${DEFAULT_WORKFLOW}`);
|
|
2520
|
+
const provider = await getContextProvider(target.workflow, target.tag);
|
|
2521
|
+
const limit = options.all ? void 0 : options.last ?? 10;
|
|
2522
|
+
let messages = await provider.readChannel({ limit });
|
|
2523
|
+
if (options.find) {
|
|
2524
|
+
const searchText = options.find.toLowerCase();
|
|
2525
|
+
messages = messages.filter((msg) => msg.content.toLowerCase().includes(searchText));
|
|
2316
2526
|
}
|
|
2317
|
-
const pending = res.data;
|
|
2318
2527
|
if (options.json) {
|
|
2319
|
-
|
|
2528
|
+
outputJson(messages);
|
|
2320
2529
|
return;
|
|
2321
2530
|
}
|
|
2322
|
-
if (
|
|
2323
|
-
console.log("No
|
|
2531
|
+
if (messages.length === 0) {
|
|
2532
|
+
console.log(options.find ? "No messages found" : "No messages");
|
|
2324
2533
|
return;
|
|
2325
2534
|
}
|
|
2326
|
-
for (const
|
|
2327
|
-
console.log(`[${p.id.slice(0, 8)}] ${p.toolName}`);
|
|
2328
|
-
console.log(` Arguments: ${JSON.stringify(p.arguments)}`);
|
|
2329
|
-
}
|
|
2330
|
-
});
|
|
2331
|
-
program.command("approve <id>").description("Approve a pending tool call").option("--to <target>", "Target agent").option("--json", "Output as JSON").action(async (id, options) => {
|
|
2332
|
-
const target = options.to;
|
|
2333
|
-
if (!isSessionActive(target)) {
|
|
2334
|
-
console.error(target ? `Agent not found: ${target}` : "No active agent");
|
|
2335
|
-
process.exit(1);
|
|
2336
|
-
}
|
|
2337
|
-
const res = await sendRequest({
|
|
2338
|
-
action: "approve",
|
|
2339
|
-
payload: { id }
|
|
2340
|
-
}, target);
|
|
2341
|
-
if (!res.success) {
|
|
2342
|
-
console.error("Error:", res.error);
|
|
2343
|
-
process.exit(1);
|
|
2344
|
-
}
|
|
2345
|
-
if (options.json) console.log(JSON.stringify({
|
|
2346
|
-
approved: true,
|
|
2347
|
-
result: res.data
|
|
2348
|
-
}, null, 2));
|
|
2349
|
-
else {
|
|
2350
|
-
console.log("Approved");
|
|
2351
|
-
console.log(`Result: ${JSON.stringify(res.data, null, 2)}`);
|
|
2352
|
-
}
|
|
2353
|
-
});
|
|
2354
|
-
program.command("deny <id>").description("Deny a pending tool call").option("--to <target>", "Target agent").option("-r, --reason <reason>", "Reason for denial").action(async (id, options) => {
|
|
2355
|
-
const target = options.to;
|
|
2356
|
-
if (!isSessionActive(target)) {
|
|
2357
|
-
console.error(target ? `Agent not found: ${target}` : "No active agent");
|
|
2358
|
-
process.exit(1);
|
|
2359
|
-
}
|
|
2360
|
-
const res = await sendRequest({
|
|
2361
|
-
action: "deny",
|
|
2362
|
-
payload: {
|
|
2363
|
-
id,
|
|
2364
|
-
reason: options.reason
|
|
2365
|
-
}
|
|
2366
|
-
}, target);
|
|
2367
|
-
if (res.success) console.log("Denied");
|
|
2535
|
+
for (const msg of messages) if (msg.kind === "system" || msg.kind === "debug") console.log(` ~ ${msg.from}: ${msg.content}`);
|
|
2368
2536
|
else {
|
|
2369
|
-
|
|
2370
|
-
|
|
2537
|
+
const mentions = msg.mentions.length > 0 ? ` → @${msg.mentions.join(" @")}` : "";
|
|
2538
|
+
console.log(`[${msg.from}]${mentions} ${msg.content}`);
|
|
2371
2539
|
}
|
|
2372
2540
|
});
|
|
2373
2541
|
}
|
|
@@ -2433,7 +2601,7 @@ function registerInfoCommands(program) {
|
|
|
2433
2601
|
console.log(`\nDefault: ${defaultModel} (when no model specified)`);
|
|
2434
2602
|
});
|
|
2435
2603
|
program.command("backends").description("Check available backends (SDK, CLI tools)").action(async () => {
|
|
2436
|
-
const { listBackends } = await import("../backends-
|
|
2604
|
+
const { listBackends } = await import("../backends-DG5igQii.mjs");
|
|
2437
2605
|
const backends = await listBackends();
|
|
2438
2606
|
console.log("Backend Status:\n");
|
|
2439
2607
|
for (const backend of backends) {
|
|
@@ -2463,7 +2631,7 @@ Examples:
|
|
|
2463
2631
|
$ agent-worker doc read @review:pr-123 # Read specific workflow:tag document
|
|
2464
2632
|
`).action(async (targetInput) => {
|
|
2465
2633
|
const dir = await resolveDir(targetInput);
|
|
2466
|
-
const { createFileContextProvider } = await import("../context-
|
|
2634
|
+
const { createFileContextProvider } = await import("../context-BqEyt2SF.mjs");
|
|
2467
2635
|
const content = await createFileContextProvider(dir, []).readDocument();
|
|
2468
2636
|
console.log(content || "(empty document)");
|
|
2469
2637
|
});
|
|
@@ -2481,7 +2649,7 @@ Examples:
|
|
|
2481
2649
|
process.exit(1);
|
|
2482
2650
|
}
|
|
2483
2651
|
const dir = await resolveDir(targetInput);
|
|
2484
|
-
const { createFileContextProvider } = await import("../context-
|
|
2652
|
+
const { createFileContextProvider } = await import("../context-BqEyt2SF.mjs");
|
|
2485
2653
|
await createFileContextProvider(dir, []).writeDocument(content);
|
|
2486
2654
|
console.log("Document written");
|
|
2487
2655
|
});
|
|
@@ -2499,7 +2667,7 @@ Examples:
|
|
|
2499
2667
|
process.exit(1);
|
|
2500
2668
|
}
|
|
2501
2669
|
const dir = await resolveDir(targetInput);
|
|
2502
|
-
const { createFileContextProvider } = await import("../context-
|
|
2670
|
+
const { createFileContextProvider } = await import("../context-BqEyt2SF.mjs");
|
|
2503
2671
|
await createFileContextProvider(dir, []).appendDocument(content);
|
|
2504
2672
|
console.log("Content appended");
|
|
2505
2673
|
});
|
|
@@ -2511,77 +2679,9 @@ async function resolveDir(targetInput) {
|
|
|
2511
2679
|
return getDefaultContextDir(target.workflow, target.tag);
|
|
2512
2680
|
}
|
|
2513
2681
|
|
|
2514
|
-
//#endregion
|
|
2515
|
-
//#region src/cli/commands/mock.ts
|
|
2516
|
-
function registerMockCommands(program) {
|
|
2517
|
-
program.command("mock").description("Mock responses for testing").command("tool <name> <response>").description("Set mock response for a tool").option("--to <target>", "Target agent").addHelpText("after", `
|
|
2518
|
-
Examples:
|
|
2519
|
-
$ agent-worker mock tool get_weather '{"temp": 72, "condition": "sunny"}'
|
|
2520
|
-
$ agent-worker mock tool read_file '{"content": "Hello World"}' --to alice
|
|
2521
|
-
`).action(async (name, response, options) => {
|
|
2522
|
-
const target = options.to;
|
|
2523
|
-
if (!isSessionActive(target)) {
|
|
2524
|
-
console.error(target ? `Agent not found: ${target}` : "No active agent");
|
|
2525
|
-
process.exit(1);
|
|
2526
|
-
}
|
|
2527
|
-
try {
|
|
2528
|
-
const res = await sendRequest({
|
|
2529
|
-
action: "tool_mock",
|
|
2530
|
-
payload: {
|
|
2531
|
-
name,
|
|
2532
|
-
response: JSON.parse(response)
|
|
2533
|
-
}
|
|
2534
|
-
}, target);
|
|
2535
|
-
if (res.success) console.log(`Mock set for: ${name}`);
|
|
2536
|
-
else {
|
|
2537
|
-
console.error("Error:", res.error);
|
|
2538
|
-
process.exit(1);
|
|
2539
|
-
}
|
|
2540
|
-
} catch {
|
|
2541
|
-
console.error("Invalid JSON response. The response parameter must be valid JSON.");
|
|
2542
|
-
console.error("Example: agent-worker mock tool my-tool '{\"result\": \"success\"}'");
|
|
2543
|
-
process.exit(1);
|
|
2544
|
-
}
|
|
2545
|
-
});
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
//#endregion
|
|
2549
|
-
//#region src/cli/commands/feedback.ts
|
|
2550
|
-
function registerFeedbackCommand(program) {
|
|
2551
|
-
program.command("feedback [target]").description("View agent feedback and observations").option("--json", "Output as JSON").addHelpText("after", `
|
|
2552
|
-
Examples:
|
|
2553
|
-
$ agent-worker feedback # View default agent feedback
|
|
2554
|
-
$ agent-worker feedback alice # View alice's feedback
|
|
2555
|
-
$ agent-worker feedback --json # JSON output
|
|
2556
|
-
|
|
2557
|
-
Note: Requires agent to be created with --feedback flag
|
|
2558
|
-
`).action(async (target, options) => {
|
|
2559
|
-
if (!isSessionActive(target)) {
|
|
2560
|
-
console.error(target ? `Agent not found: ${target}` : "No active agent");
|
|
2561
|
-
process.exit(1);
|
|
2562
|
-
}
|
|
2563
|
-
const res = await sendRequest({ action: "feedback_list" }, target);
|
|
2564
|
-
if (!res.success) {
|
|
2565
|
-
console.error("Error:", res.error);
|
|
2566
|
-
process.exit(1);
|
|
2567
|
-
}
|
|
2568
|
-
const entries = res.data;
|
|
2569
|
-
if (options.json) {
|
|
2570
|
-
outputJson(entries);
|
|
2571
|
-
return;
|
|
2572
|
-
}
|
|
2573
|
-
if (entries.length === 0) console.log("No feedback yet");
|
|
2574
|
-
else for (const entry of entries) {
|
|
2575
|
-
const icon = entry.type === "missing" ? "[missing]" : entry.type === "friction" ? "[friction]" : "[suggestion]";
|
|
2576
|
-
console.log(` ${icon} ${entry.target}: ${entry.description}`);
|
|
2577
|
-
if (entry.context) console.log(` context: ${entry.context}`);
|
|
2578
|
-
}
|
|
2579
|
-
});
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
2682
|
//#endregion
|
|
2583
2683
|
//#region package.json
|
|
2584
|
-
var version = "0.
|
|
2684
|
+
var version = "0.12.0";
|
|
2585
2685
|
|
|
2586
2686
|
//#endregion
|
|
2587
2687
|
//#region src/cli/index.ts
|
|
@@ -2596,15 +2696,12 @@ process.stderr.write = function(chunk, ...rest) {
|
|
|
2596
2696
|
};
|
|
2597
2697
|
const program = new Command();
|
|
2598
2698
|
program.name("agent-worker").description("CLI for creating and managing AI agents").version(version);
|
|
2699
|
+
registerWorkflowCommands(program);
|
|
2599
2700
|
registerAgentCommands(program);
|
|
2600
2701
|
registerSendCommands(program);
|
|
2601
|
-
registerMockCommands(program);
|
|
2602
|
-
registerFeedbackCommand(program);
|
|
2603
|
-
registerWorkflowCommands(program);
|
|
2604
|
-
registerApprovalCommands(program);
|
|
2605
2702
|
registerInfoCommands(program);
|
|
2606
2703
|
registerDocCommands(program);
|
|
2607
2704
|
program.parse();
|
|
2608
2705
|
|
|
2609
2706
|
//#endregion
|
|
2610
|
-
export { shouldUseResource as _, FileStorage as a, CONTEXT_DEFAULTS as c, RESOURCE_PREFIX as d, RESOURCE_SCHEME as f, generateResourceId as g, extractMentions as h, resolveContextDir as i, MENTION_PATTERN as l, createResourceRef as m, createFileContextProvider as n, MemoryStorage as o, calculatePriority as p, getDefaultContextDir as r, ContextProviderImpl as s, FileContextProvider as t, MESSAGE_LENGTH_THRESHOLD as u };
|
|
2707
|
+
export { formatToolParams as C, formatInbox as S, EventLog as T, shouldUseResource as _, FileStorage as a, formatProposalList as b, CONTEXT_DEFAULTS as c, RESOURCE_PREFIX as d, RESOURCE_SCHEME as f, generateResourceId as g, extractMentions as h, resolveContextDir as i, MENTION_PATTERN as l, createResourceRef as m, createFileContextProvider as n, MemoryStorage as o, calculatePriority as p, getDefaultContextDir as r, ContextProviderImpl as s, FileContextProvider as t, MESSAGE_LENGTH_THRESHOLD as u, createContextMCPServer as v, getAgentId as w, createLogTool as x, formatProposal as y };
|