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.
@@ -1,15 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { A as getDefaultModel, E as FRONTIER_MODELS, n as createBackend } from "../backends-BOAkfYyL.mjs";
3
- import { a as createSkillsTool, c as createFeedbackTool, l as AgentSession, o as SkillsProvider, s as FEEDBACK_PROMPT, t as SkillImporter } from "../skills-xNmQZf8e.mjs";
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 { createConnection, createServer } from "node:net";
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
- * Session Registry — Directory-per-session design.
37
+ * Daemon Registry
34
38
  *
35
- * Each session is stored as its own file: sessions/{id}.json
36
- * Only the owning daemon process writes to its session file.
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
- * Default session ID is stored in a separate file: default
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
- const DURATION_RE = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/;
45
- /**
46
- * Parse a duration string like "30s", "5m", "2h" into milliseconds.
47
- * Returns null if not a valid duration format.
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
- function readDefault() {
54
+ /** Read daemon.json. Returns null if missing or malformed. */
55
+ function readDaemonInfo() {
111
56
  try {
112
- return readFileSync(DEFAULT_FILE, "utf-8").trim() || void 0;
57
+ return JSON.parse(readFileSync(DAEMON_FILE, "utf-8"));
113
58
  } catch {
114
- return;
59
+ return null;
115
60
  }
116
61
  }
117
- function registerSession(info) {
118
- ensureDirs();
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(sessionFile(info.id));
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
- function isSessionRunning(idOrName) {
168
- const info = getSessionInfo(idOrName);
169
- if (!info) return false;
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 true;
74
+ return info;
173
75
  } catch {
174
- if (existsSync(info.socketPath)) unlinkSync(info.socketPath);
175
- if (existsSync(info.pidFile)) unlinkSync(info.pidFile);
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
- * Wait for a session to be ready (ready file exists)
183
- * Returns session info if ready, null if timeout
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
- async function waitForReady(nameOrId, timeoutMs = 5e3) {
186
- const start = Date.now();
187
- const pollInterval = 50;
188
- while (Date.now() - start < timeoutMs) {
189
- const info = getSessionInfo(nameOrId);
190
- if (info?.readyFile && existsSync(info.readyFile)) return info;
191
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
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
- return null;
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
- * Get all sessions belonging to an instance.
205
+ * Extract agent ID from MCP extra context.
206
+ * Session ID format: "agentName-uuid8chars" — extract agent name.
197
207
  */
198
- function getInstanceAgents(instance) {
199
- return readAllSessions().filter((s) => s.instance === instance);
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
- * Get agent display names (before @) for all agents in an instance.
221
+ * Format inbox messages for JSON display.
203
222
  */
204
- function getInstanceAgentNames(instance) {
205
- return getInstanceAgents(instance).map((s) => {
206
- if (!s.name) return null;
207
- const atIndex = s.name.indexOf("@");
208
- return atIndex === -1 ? s.name : s.name.slice(0, atIndex);
209
- }).filter((n) => n !== null);
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
- * Extract agent display name from session name (part before @).
240
+ * Format tool call parameters as a concise string.
213
241
  */
214
- function getAgentDisplayName(sessionName) {
215
- const atIndex = sessionName.indexOf("@");
216
- return atIndex === -1 ? sessionName : sessionName.slice(0, atIndex);
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
- * Generate next available agent name in sequence: a0, a1, ..., a9, b0, ..., z9.
220
- * Checks existing sessions to avoid collisions.
249
+ * Create a logTool function that records tool calls via EventLog.
221
250
  */
222
- function generateAutoName() {
223
- const sessions = readAllSessions();
224
- const usedNames = /* @__PURE__ */ new Set();
225
- for (const s of sessions) if (s.name) {
226
- const atIndex = s.name.indexOf("@");
227
- usedNames.add(atIndex === -1 ? s.name : s.name.slice(0, atIndex));
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/daemon/cron.ts
238
- /**
239
- * Minimal cron expression parser.
240
- * Supports standard 5-field cron: minute hour day-of-month month day-of-week
241
- *
242
- * Field syntax:
243
- * * every value
244
- * N exact value
245
- * N-M range (inclusive)
246
- * N,M,O list
247
- * * /step every step (e.g. * /15 = 0,15,30,45) [no space — formatting only]
248
- * N-M/step range with step
249
- */
250
- function range(min, max) {
251
- const r = [];
252
- for (let i = min; i <= max; i++) r.push(i);
253
- return r;
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
- function parseIntStrict(s, context) {
256
- const n = parseInt(s, 10);
257
- if (isNaN(n)) throw new Error(`Invalid number "${s}" in ${context}`);
258
- return n;
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
- function parseCronField(field, min, max) {
261
- const values = /* @__PURE__ */ new Set();
262
- for (const part of field.split(",")) if (part === "*") for (const v of range(min, max)) values.add(v);
263
- else if (part.includes("/")) {
264
- const [rangeStr, stepStr] = part.split("/");
265
- const step = parseIntStrict(stepStr, `step "${part}"`);
266
- if (step <= 0) throw new Error(`Invalid step: ${part}`);
267
- let lo = min;
268
- let hi = max;
269
- if (rangeStr !== "*") if (rangeStr.includes("-")) {
270
- const parts = rangeStr.split("-");
271
- lo = parseIntStrict(parts[0], `range "${part}"`);
272
- hi = parseIntStrict(parts[1], `range "${part}"`);
273
- } else {
274
- lo = parseIntStrict(rangeStr, `field "${part}"`);
275
- hi = max;
276
- }
277
- for (let v = lo; v <= hi; v += step) values.add(v);
278
- } else if (part.includes("-")) {
279
- const parts = part.split("-");
280
- const lo = parseIntStrict(parts[0], `range "${part}"`);
281
- const hi = parseIntStrict(parts[1], `range "${part}"`);
282
- for (const v of range(lo, hi)) values.add(v);
283
- } else values.add(parseIntStrict(part, `field "${field}"`));
284
- return values;
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
- * Parse a 5-field cron expression into sets of matching values.
288
- */
289
- function parseCron(expr) {
290
- const parts = expr.trim().split(/\s+/);
291
- if (parts.length !== 5) throw new Error(`Invalid cron expression (expected 5 fields): ${expr}`);
292
- return {
293
- minutes: parseCronField(parts[0], 0, 59),
294
- hours: parseCronField(parts[1], 0, 23),
295
- daysOfMonth: parseCronField(parts[2], 1, 31),
296
- months: parseCronField(parts[3], 1, 12),
297
- daysOfWeek: parseCronField(parts[4], 0, 6)
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
- * Check if a Date matches a parsed cron expression.
483
+ * Format a proposal for display
302
484
  */
303
- function matchesCron(date, fields) {
304
- return fields.minutes.has(date.getMinutes()) && fields.hours.has(date.getHours()) && fields.daysOfMonth.has(date.getDate()) && fields.months.has(date.getMonth() + 1) && fields.daysOfWeek.has(date.getDay());
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
- * Calculate the next occurrence of a cron expression after `from`.
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 nextCronTime(expr, from = /* @__PURE__ */ new Date()) {
312
- const fields = parseCron(expr);
313
- const next = new Date(from);
314
- next.setSeconds(0, 0);
315
- next.setMinutes(next.getMinutes() + 1);
316
- const maxMinutes = 366 * 24 * 60;
317
- for (let i = 0; i < maxMinutes; i++) {
318
- if (matchesCron(next, fields)) return next;
319
- next.setMinutes(next.getMinutes() + 1);
320
- }
321
- throw new Error(`No matching cron time found within 1 year: ${expr}`);
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
- * Calculate ms until the next cron occurrence.
674
+ * Register the feedback_submit tool and return a getter for collected entries.
325
675
  */
326
- function msUntilNextCron(expr, from = /* @__PURE__ */ new Date()) {
327
- return nextCronTime(expr, from).getTime() - from.getTime();
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/daemon/handler.ts
332
- async function handleRequest(getState, req, resetIdleTimer, gracefulShutdown, resetAllTimers) {
333
- const state = getState();
334
- if (!state) return {
335
- success: false,
336
- error: "No active session"
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
- state.pendingRequests++;
339
- resetIdleTimer();
340
- const { session, info } = state;
341
- try {
342
- switch (req.action) {
343
- case "ping": return {
344
- success: true,
345
- data: {
346
- id: info.id,
347
- model: info.model,
348
- backend: info.backend,
349
- name: info.name
350
- }
351
- };
352
- case "send": {
353
- const { message, options, async: isAsync } = req.payload;
354
- if (isAsync) {
355
- session.send(message, options).catch((error) => {
356
- console.error("Background send error:", error);
357
- });
358
- return {
359
- success: true,
360
- data: {
361
- async: true,
362
- message: "Processing in background. Use `peek` to check the response."
363
- }
364
- };
365
- }
366
- return {
367
- success: true,
368
- data: await session.send(message, options)
369
- };
370
- }
371
- case "tool_add": {
372
- const { name, description, parameters, needsApproval } = req.payload;
373
- const t = tool({
374
- description,
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 === "log" || e.kind === "debug" || e.kind === "stream") return false;
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 === "log" || e.kind === "debug" || e.kind === "stream") return false;
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/cli/target.ts
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
- * Target identifier utilities
1426
+ * Daemon Centralized agent coordinator.
1156
1427
  *
1157
- * Format: agent@workflow:tag (inspired by Docker image:tag)
1158
- * - agent: agent name (optional for @workflow references)
1159
- * - workflow: workflow name (optional, defaults to 'global')
1160
- * - tag: workflow instance tag (optional, defaults to 'main')
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
- * Examples:
1163
- * - "alice" → { agent: "alice", workflow: "global", tag: "main", display: "alice" }
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
- * Display rules:
1170
- * - Omit @global (standalone agents): "alice" not "alice@global"
1171
- * - Omit :main (default tag): "alice@review" not "alice@review:main"
1172
- */
1173
- const DEFAULT_WORKFLOW = "global";
1174
- const DEFAULT_TAG = "main";
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
- function parseTarget(input) {
1180
- if (input.startsWith("@")) {
1181
- const workflowPart = input.slice(1);
1182
- const colonIndex = workflowPart.indexOf(":");
1183
- if (colonIndex === -1) {
1184
- const workflow = workflowPart || DEFAULT_WORKFLOW;
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
- agent: void 0,
1187
- workflow,
1188
- tag: DEFAULT_TAG,
1189
- full: `@${workflow}:${DEFAULT_TAG}`,
1190
- display: workflow === DEFAULT_WORKFLOW ? `@${workflow}` : `@${workflow}`
1191
- };
1192
- } else {
1193
- const workflow = workflowPart.slice(0, colonIndex) || DEFAULT_WORKFLOW;
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
- const atIndex = input.indexOf("@");
1205
- if (atIndex === -1) return {
1206
- agent: input,
1207
- workflow: DEFAULT_WORKFLOW,
1208
- tag: DEFAULT_TAG,
1209
- full: `${input}@${DEFAULT_WORKFLOW}:${DEFAULT_TAG}`,
1210
- display: input
1211
- };
1212
- const agent = input.slice(0, atIndex);
1213
- const workflowPart = input.slice(atIndex + 1);
1214
- const colonIndex = workflowPart.indexOf(":");
1215
- if (colonIndex === -1) {
1216
- const workflow = workflowPart || DEFAULT_WORKFLOW;
1217
- return {
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
- full: `${agent}@${workflow}:${tag}`,
1232
- display: buildDisplay(agent, workflow, tag)
1558
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1233
1559
  };
1234
- }
1235
- }
1236
- /**
1237
- * Build display string following display rules:
1238
- * - Omit @global for standalone agents
1239
- * - Omit :main for default tag
1240
- */
1241
- function buildDisplay(agent, workflow, tag) {
1242
- const isGlobal = workflow === DEFAULT_WORKFLOW;
1243
- const isMainTag = tag === DEFAULT_TAG;
1244
- if (agent === void 0) {
1245
- if (isMainTag) return `@${workflow}`;
1246
- return `@${workflow}:${tag}`;
1247
- }
1248
- if (isGlobal && isMainTag) return agent;
1249
- if (isGlobal && !isMainTag) return `${agent}@${workflow}:${tag}`;
1250
- if (!isGlobal && isMainTag) return `${agent}@${workflow}`;
1251
- return `${agent}@${workflow}:${tag}`;
1252
- }
1253
- /**
1254
- * Build full target identifier from parts
1255
- */
1256
- function buildTarget(agent, workflow, tag) {
1257
- const wf = workflow || DEFAULT_WORKFLOW;
1258
- const t = tag || DEFAULT_TAG;
1259
- if (agent === void 0) return `@${wf}:${t}`;
1260
- return `${agent}@${wf}:${t}`;
1261
- }
1262
- /**
1263
- * Build display string from parts (following display rules)
1264
- */
1265
- function buildTargetDisplay(agent, workflow, tag) {
1266
- return buildDisplay(agent, workflow || DEFAULT_WORKFLOW, tag || DEFAULT_TAG);
1267
- }
1268
- /**
1269
- * @deprecated Use DEFAULT_WORKFLOW and DEFAULT_TAG instead
1270
- */
1271
- const DEFAULT_INSTANCE = DEFAULT_WORKFLOW;
1272
-
1273
- //#endregion
1274
- //#region src/daemon/daemon.ts
1275
- const DEFAULT_SKILL_DIRS = [
1276
- ".agents/skills",
1277
- ".claude/skills",
1278
- ".cursor/skills",
1279
- join(homedir(), ".agents/skills"),
1280
- join(homedir(), ".claude/skills")
1281
- ];
1282
- /**
1283
- * Setup skills provider and return Skills tool
1284
- * Supports both local and imported skills
1285
- * Skills are always loaded and provided as tools, regardless of backend
1286
- */
1287
- async function setupSkills(sessionId, skillPaths, skillDirs, importSkills) {
1288
- const provider = new SkillsProvider();
1289
- for (const dir of DEFAULT_SKILL_DIRS) try {
1290
- provider.scanDirectorySync(dir);
1291
- } catch {}
1292
- if (skillDirs) for (const dir of skillDirs) try {
1293
- provider.scanDirectorySync(dir);
1294
- } catch (error) {
1295
- console.error(`Failed to scan skill directory ${dir}:`, error);
1296
- }
1297
- if (skillPaths) for (const skillPath of skillPaths) try {
1298
- provider.addSkillSync(skillPath);
1299
- } catch (error) {
1300
- console.error(`Failed to add skill ${skillPath}:`, error);
1301
- }
1302
- let importer;
1303
- if (importSkills && importSkills.length > 0) {
1304
- importer = new SkillImporter(sessionId);
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 importer.importMultiple(importSkills);
1307
- await provider.addImportedSkills(importer);
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
- console.error("Failed to import skills:", error);
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
- console.log(`\n[wakeup:interval] Waking agent after ${ms / 1e3}s idle`);
1370
- try {
1371
- state.pendingRequests++;
1372
- resetIdleTimer();
1373
- await state.session.send(prompt);
1374
- } catch (error) {
1375
- console.error("[wakeup:interval] Send error:", error);
1376
- } finally {
1377
- if (state) {
1378
- state.pendingRequests--;
1379
- resetIdleTimer();
1380
- resetIntervalSchedule();
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
- }, ms);
1384
- }
1385
- /**
1386
- * Cron-based wakeup: fires at fixed cron times, regardless of activity.
1387
- * NOT reset by external activity — the agent is woken at the scheduled time.
1388
- * After the wakeup completes, schedules the next occurrence.
1389
- */
1390
- function startCronSchedule() {
1391
- if (!state) return;
1392
- if (state.cronTimer) {
1393
- clearTimeout(state.cronTimer);
1394
- state.cronTimer = void 0;
1395
- }
1396
- const schedule = state.info.schedule;
1397
- if (!schedule) return;
1398
- let resolved;
1399
- try {
1400
- resolved = resolveSchedule(schedule);
1401
- } catch {
1402
- return;
1403
- }
1404
- if (resolved.type !== "cron" || !resolved.expr) return;
1405
- const expr = resolved.expr;
1406
- const prompt = resolved.prompt || DEFAULT_SCHEDULE_PROMPT;
1407
- let delay;
1408
- try {
1409
- delay = msUntilNextCron(expr);
1410
- } catch (error) {
1411
- console.error("[wakeup:cron] Invalid cron expression:", error);
1412
- return;
1413
- }
1414
- const nextTime = new Date(Date.now() + delay);
1415
- console.log(`[wakeup:cron] Next at ${nextTime.toISOString()} (in ${Math.round(delay / 1e3)}s)`);
1416
- state.cronTimer = setTimeout(async () => {
1417
- if (!state) return;
1418
- console.log(`\n[wakeup:cron] Waking agent (${expr})`);
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
- state.pendingRequests++;
1421
- resetIdleTimer();
1422
- await state.session.send(prompt);
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
- console.error("[wakeup:cron] Send error:", error);
1425
- } finally {
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
- }, delay);
1433
- }
1434
- /**
1435
- * Reset schedule timers on external activity.
1436
- * - Interval: resets (idle-based)
1437
- * - Cron: NOT reset (fixed schedule)
1438
- */
1439
- function resetScheduleTimers() {
1440
- resetIntervalSchedule();
1441
- }
1442
- const INBOX_POLL_MS = 2e3;
1443
- /**
1444
- * Start inbox polling — checks channel for @mentions and processes them via the LLM.
1445
- * This is how agents receive messages from `send` (which posts to the channel).
1446
- */
1447
- function startInboxPolling() {
1448
- if (!state?.contextProvider || !state?.agentDisplayName) return;
1449
- const provider = state.contextProvider;
1450
- const agentName = state.agentDisplayName;
1451
- state.inboxPollTimer = setInterval(async () => {
1452
- if (!state || state.pendingRequests > 0) return;
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
- const inbox = await provider.getInbox(agentName);
1455
- if (inbox.length === 0) return;
1456
- const latestId = inbox[inbox.length - 1].entry.id;
1457
- const prompt = inbox.map((m) => `[${m.entry.from}]: ${m.entry.content}`).join("\n\n");
1458
- state.pendingRequests++;
1459
- resetIdleTimer();
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
- console.error("[inbox] Poll error:", error instanceof Error ? error.message : error);
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
- state.server.close();
1484
- const maxWait = 1e4;
1485
- const start = Date.now();
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 cleanup() {
1492
- if (state) {
1493
- if (state.idleTimer) clearTimeout(state.idleTimer);
1494
- if (state.scheduleTimer) clearTimeout(state.scheduleTimer);
1495
- if (state.cronTimer) clearTimeout(state.cronTimer);
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
- let getFeedback;
1522
- if (config.feedback) {
1523
- const fb = createFeedbackTool();
1524
- tools.feedback = fb.tool;
1525
- getFeedback = fb.getFeedback;
1526
- }
1527
- const system = config.feedback ? `${config.system}\n\n${FEEDBACK_PROMPT}` : config.system;
1528
- const cliBackend = backendType !== "sdk" ? createBackend({
1529
- type: backendType,
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 effectiveId = session.id;
1539
- const socketPath = join(SESSIONS_DIR, `${effectiveId}.sock`);
1540
- const pidFile = join(SESSIONS_DIR, `${effectiveId}.pid`);
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
- createdAt: session.createdAt,
1567
- idleTimeout: config.idleTimeout,
1568
- schedule: config.schedule
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
- cleanup();
1645
- process.exit(0);
1836
+ gracefulShutdown();
1646
1837
  });
1647
1838
  process.on("SIGTERM", () => {
1648
- cleanup();
1649
- process.exit(0);
1839
+ gracefulShutdown();
1650
1840
  });
1651
1841
  }
1652
1842
 
1653
1843
  //#endregion
1654
1844
  //#region src/cli/client.ts
1655
- function sendRequest(req, targetOrOptions, options) {
1656
- let target;
1657
- let opts = {};
1658
- if (typeof targetOrOptions === "string") {
1659
- target = targetOrOptions;
1660
- opts = options || {};
1661
- } else if (targetOrOptions) {
1662
- opts = targetOrOptions;
1663
- target = opts.target;
1664
- }
1665
- const debug = opts.debug || false;
1666
- return new Promise((resolve, reject) => {
1667
- if (debug) console.error(`[DEBUG] Looking up session: ${target || "(default)"}`);
1668
- const info = getSessionInfo(target);
1669
- if (!info) {
1670
- if (target) resolve({
1671
- success: false,
1672
- error: `Session not found: ${target}`
1673
- });
1674
- else resolve({
1675
- success: false,
1676
- error: "No active session. Start one with: agent-worker session start -m <model>"
1677
- });
1678
- return;
1679
- }
1680
- if (debug) {
1681
- console.error(`[DEBUG] Found session: ${info.id} (${info.backend})`);
1682
- console.error(`[DEBUG] Socket path: ${info.socketPath}`);
1683
- }
1684
- if (!isSessionRunning(target)) {
1685
- resolve({
1686
- success: false,
1687
- error: `Session not running: ${target || info.id}`
1688
- });
1689
- return;
1690
- }
1691
- if (debug) console.error(`[DEBUG] Connecting to socket...`);
1692
- const socket = createConnection(info.socketPath);
1693
- let buffer = "";
1694
- const startTime = Date.now();
1695
- socket.on("connect", () => {
1696
- if (debug) {
1697
- console.error(`[DEBUG] Connected. Sending request:`);
1698
- console.error(`[DEBUG] ${JSON.stringify(req, null, 2)}`);
1699
- }
1700
- socket.write(JSON.stringify(req) + "\n");
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
- socket.on("data", (data) => {
1703
- if (debug) {
1704
- const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
1705
- console.error(`[DEBUG] Received data after ${elapsed}s: ${data.toString().substring(0, 100)}...`);
1706
- }
1707
- buffer += data.toString();
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
- for (const line of lines) {
1711
- if (!line.trim()) continue;
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 res = JSON.parse(line);
1714
- if (debug) {
1715
- const elapsed = ((Date.now() - startTime) / 1e3).toFixed(2);
1716
- console.error(`[DEBUG] Received response after ${elapsed}s`);
1717
- }
1718
- socket.end();
1719
- resolve(res);
1720
- } catch (error) {
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
- socket.on("error", (error) => {
1728
- if (debug) console.error(`[DEBUG] Socket error:`, error);
1729
- reject(error);
1730
- });
1731
- socket.on("timeout", () => {
1732
- if (debug) console.error(`[DEBUG] Socket timeout after 60s`);
1733
- socket.end();
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
- * Check if any session is active, or a specific session
1742
- */
1743
- function isSessionActive(target) {
1744
- return isSessionRunning(target);
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
- async function createAgentAction(name, options) {
1768
- let system = options.system ?? "You are a helpful assistant.";
1769
- if (options.systemFile) system = readFileSync(options.systemFile, "utf-8");
1770
- const backend = options.backend ?? "sdk";
1771
- const model = options.model || getDefaultModel();
1772
- const idleTimeout = parseInt(options.idleTimeout ?? "1800000", 10);
1773
- const workflow = DEFAULT_WORKFLOW;
1774
- const tag = DEFAULT_TAG;
1775
- const instance = DEFAULT_INSTANCE;
1776
- const agentName = name || generateAutoName();
1777
- let fullName;
1778
- if (agentName.includes("@")) fullName = agentName;
1779
- else fullName = buildTarget(agentName, workflow, tag);
1780
- const schedule = options.wakeup ? {
1781
- wakeup: /^\d+$/.test(options.wakeup) ? parseInt(options.wakeup, 10) : options.wakeup,
1782
- prompt: options.wakeupPrompt
1783
- } : void 0;
1784
- if (options.foreground) startDaemon({
1785
- model,
1786
- system,
1787
- name: fullName,
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 listAgentsAction(targetInput, options) {
1844
- let sessions = listSessions();
1845
- if (!options?.all && targetInput) {
1846
- const target = parseTarget(targetInput);
1847
- sessions = sessions.filter((s) => {
1848
- const sessionWorkflow = s.workflow || s.instance || DEFAULT_WORKFLOW;
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
- if (options?.json) {
1856
- outputJson(sessions.map((s) => {
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("sdk")).option("-s, --system <prompt>", "System prompt", "You are a helpful assistant.").option("-f, --system-file <file>", "Read system prompt from file").option("--idle-timeout <ms>", "Idle timeout in ms (0 = no timeout)", "1800000").option("--skill <path...>", "Add individual skill directories").option("--skill-dir <path...>", "Scan directories for skills").option("--import-skill <spec...>", "Import skills from Git (owner/repo:{skill1,skill2})").option("--tool <file>", "Import MCP tools from file (SDK backend only)").option("--feedback", "Enable feedback tool (agent can report tool/workflow observations)").option("--wakeup <value>", "Scheduled wakeup: ms number, duration (30s/5m/2h), or cron expr").option("--wakeup-prompt <prompt>", "Custom prompt for scheduled wakeup").option("--foreground", "Run in foreground").option("--json", "Output as JSON");
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 ls # List global workflow agents (default)
1955
- $ agent-worker ls @review # List review workflow agents
1956
- $ agent-worker ls @review:pr-123 # List specific workflow:tag agents
1957
- $ agent-worker ls --all # List all agents from all workflows
1958
- `).action(listAgentsAction);
1959
- program.command("stop [target]").description("Stop agent(s)").option("--all", "Stop all agents").addHelpText("after", `
1960
- Examples:
1961
- $ agent-worker stop alice # Stop alice in global workflow
1962
- $ agent-worker stop alice@review # Stop alice in review workflow
1963
- $ agent-worker stop @review:pr-123 # Stop all agents in review:pr-123
1964
- $ agent-worker stop --all # Stop all agents
1965
- `).action(stopAgentAction);
1966
- program.command("status [target]").description("Check agent status").option("--json", "Output as JSON").action(async (target, options) => {
1967
- if (!isSessionRunning(target)) {
1968
- if (options.json) outputJson({
1969
- running: false,
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
- else {
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 send alice "analyze this code" # Send to alice@global:main
2065
- $ agent-worker send alice@review "hello" # Send to alice@review:main
2066
- $ agent-worker send alice@review:pr-123 "check this" # Send to specific workflow:tag
2067
- $ agent-worker send @review "team update" # Broadcast to review workflow
2068
- $ agent-worker send @review "@alice @bob discuss this" # @mention within workflow
2069
- `).action(async (targetInput, message, options) => {
2070
- const target = parseTarget(targetInput);
2071
- const entry = await getContextProvider(target.workflow, target.tag, target.workflow).appendChannel("user", message);
2072
- if (options.json) outputJson({
2073
- id: entry.id,
2074
- timestamp: entry.timestamp,
2075
- mentions: entry.mentions,
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(messages);
2116
+ outputJson({ agents });
2097
2117
  return;
2098
2118
  }
2099
- if (messages.length === 0) {
2100
- console.log(options.find ? "No messages found" : "No messages");
2119
+ if (agents.length === 0) {
2120
+ console.log("No agents");
2101
2121
  return;
2102
2122
  }
2103
- for (const msg of messages) if (msg.kind === "log" || msg.kind === "debug") console.log(` ~ ${msg.from}: ${msg.content}`);
2104
- else {
2105
- const mentions = msg.mentions.length > 0 ? ` → @${msg.mentions.join(" @")}` : "";
2106
- console.log(`[${msg.from}]${mentions} ${msg.content}`);
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("stats [target]").description("Show agent statistics").option("--json", "Output as JSON").action(async (target, options) => {
2110
- if (!isSessionActive(target)) {
2111
- console.error(target ? `Agent not found: ${target}` : "No active agent");
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
- const res = await sendRequest({ action: "stats" }, target);
2115
- if (!res.success) {
2116
- console.error("Error:", res.error);
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 stats = res.data;
2120
- if (options.json) outputJson(stats);
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.log(`Messages: ${stats.messageCount}`);
2123
- console.log(`Tokens: ${stats.usage.total} (in: ${stats.usage.input}, out: ${stats.usage.output})`);
2159
+ console.error("Error:", res.error);
2160
+ process.exit(1);
2124
2161
  }
2125
2162
  });
2126
- program.command("export [target]").description("Export agent transcript").action(async (target) => {
2127
- if (!isSessionActive(target)) {
2128
- console.error(target ? `Agent not found: ${target}` : "No active agent");
2129
- process.exit(1);
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 sendRequest({ action: "export" }, target);
2132
- if (!res.success) {
2133
- console.error("Error:", res.error);
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
- console.log(JSON.stringify(res.data, null, 2));
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("clear [target]").description("Clear agent conversation history").action(async (target) => {
2139
- if (!isSessionActive(target)) {
2140
- console.error(target ? `Agent not found: ${target}` : "No active agent");
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 sendRequest({ action: "clear" }, target);
2144
- if (res.success) console.log("History cleared");
2145
- else console.error("Error:", res.error);
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-BGpkJlFb.mjs");
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-BGpkJlFb.mjs");
2172
- const { createSilentLogger } = await import("../logger-C3ekEOzi.mjs");
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-CWoRE9FY.mjs");
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("-d, --debug", "Show debug details (internal logs, MCP traces, idle checks)").option("--feedback", "Enable feedback tool (agents can report tool/workflow observations)").option("--background", "Run in background (daemonize)").addHelpText("after", `
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 # Foreground (Ctrl+C to stop)
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, runWorkflowWithControllers } = await import("../workflow-BGpkJlFb.mjs");
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
- if (options.background) {
2246
- const { getDefaultContextDir } = await Promise.resolve().then(() => file_provider_exports);
2247
- const contextDir = getDefaultContextDir(workflowName, tag);
2248
- const args = [
2249
- process.argv[1] ?? "",
2250
- "start",
2251
- file
2252
- ];
2253
- if (tag !== DEFAULT_TAG) args.push("--tag", tag);
2254
- if (options.feedback) args.push("--feedback");
2255
- const child = spawn(process.execPath, args, {
2256
- detached: true,
2257
- stdio: "ignore"
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
- let shutdownFn;
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
- console.log("\nShutting down...");
2273
- if (shutdownFn) await shutdownFn();
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
- try {
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/approval.ts
2305
- function registerApprovalCommands(program) {
2306
- program.command("pending").description("List pending tool approvals").option("--to <target>", "Target agent").option("--json", "Output as JSON").action(async (options) => {
2307
- const target = options.to;
2308
- if (!isSessionActive(target)) {
2309
- console.error(target ? `Agent not found: ${target}` : "No active agent");
2310
- process.exit(1);
2311
- }
2312
- const res = await sendRequest({ action: "pending" }, target);
2313
- if (!res.success) {
2314
- console.error("Error:", res.error);
2315
- process.exit(1);
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
- console.log(JSON.stringify(pending, null, 2));
2528
+ outputJson(messages);
2320
2529
  return;
2321
2530
  }
2322
- if (pending.length === 0) {
2323
- console.log("No pending approvals");
2531
+ if (messages.length === 0) {
2532
+ console.log(options.find ? "No messages found" : "No messages");
2324
2533
  return;
2325
2534
  }
2326
- for (const p of pending) {
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
- console.error("Error:", res.error);
2370
- process.exit(1);
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-e6gCxRZ9.mjs");
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-dgI2YCGG.mjs");
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-dgI2YCGG.mjs");
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-dgI2YCGG.mjs");
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.10.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 };