agent-worker 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -38
- package/dist/cli/index.mjs +208 -4320
- package/dist/client-DAKkzdOn.mjs +171 -0
- package/dist/daemon-CwaHgxs6.mjs +1071 -0
- package/dist/index.d.mts +249 -849
- package/dist/index.mjs +27 -1102
- package/dist/output-B0mwPqjv.mjs +20 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/target-9yiBRXxa.mjs +105 -0
- package/package.json +25 -37
- package/dist/backends-D7DT0uox.mjs +0 -1484
- package/dist/backends-DUvcm-ce.mjs +0 -3
- package/dist/context-CoRTddGx.mjs +0 -4
- package/dist/create-tool-gcUuI1FD.mjs +0 -32
- package/dist/display-pretty-Kyd40DEF.mjs +0 -190
- package/dist/memory-provider-Z9D8NdwS.mjs +0 -75
- package/dist/runner-BmT0Y8MD.mjs +0 -690
- package/dist/workflow-LOZUlaDo.mjs +0 -744
package/dist/runner-BmT0Y8MD.mjs
DELETED
|
@@ -1,690 +0,0 @@
|
|
|
1
|
-
import "./backends-D7DT0uox.mjs";
|
|
2
|
-
import "./create-tool-gcUuI1FD.mjs";
|
|
3
|
-
import { D as FileContextProvider, E as runWithHttp, O as createFileContextProvider, Y as createContextMCPServer, i as createSilentLogger, n as createWiredLoop, nt as EventLog, r as createChannelLogger, s as checkWorkflowIdle } from "./cli/index.mjs";
|
|
4
|
-
import { n as createMemoryContextProvider } from "./memory-provider-Z9D8NdwS.mjs";
|
|
5
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { tmpdir } from "node:os";
|
|
8
|
-
import { exec } from "node:child_process";
|
|
9
|
-
import { promisify } from "node:util";
|
|
10
|
-
import stringWidth from "string-width";
|
|
11
|
-
import chalk from "chalk";
|
|
12
|
-
import wrapAnsi from "wrap-ansi";
|
|
13
|
-
|
|
14
|
-
//#region src/workflow/interpolate.ts
|
|
15
|
-
const VARIABLE_PATTERN = /\$\{\{\s*([^}]+)\s*\}\}/g;
|
|
16
|
-
/**
|
|
17
|
-
* Interpolate variables in a template string
|
|
18
|
-
* @param warn Optional callback for unresolved variables
|
|
19
|
-
*/
|
|
20
|
-
function interpolate(template, context, warn) {
|
|
21
|
-
return template.replace(VARIABLE_PATTERN, (match, expression) => {
|
|
22
|
-
const value = resolveExpression(expression.trim(), context);
|
|
23
|
-
if (value === void 0 && warn) warn("Unresolved variable: ${{ " + expression.trim() + " }} — no setup task defines it");
|
|
24
|
-
return value ?? match;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolve a variable expression
|
|
29
|
-
*/
|
|
30
|
-
function resolveExpression(expression, context) {
|
|
31
|
-
if (expression.startsWith("env.")) {
|
|
32
|
-
const varName = expression.slice(4);
|
|
33
|
-
return context.env?.[varName] ?? process.env[varName];
|
|
34
|
-
}
|
|
35
|
-
if (expression.startsWith("params.")) {
|
|
36
|
-
const paramName = expression.slice(7);
|
|
37
|
-
return context.params?.[paramName];
|
|
38
|
-
}
|
|
39
|
-
if (expression.startsWith("workflow.")) {
|
|
40
|
-
const field = expression.slice(9);
|
|
41
|
-
if (field === "name") return context.workflow?.name;
|
|
42
|
-
if (field === "tag") return context.workflow?.tag;
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (expression.startsWith("source.")) {
|
|
46
|
-
if (expression.slice(7) === "dir") return context.source?.dir;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const value = context[expression];
|
|
50
|
-
return typeof value === "string" ? value : void 0;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Create a context from workflow metadata
|
|
54
|
-
*/
|
|
55
|
-
function createContext(workflowName, tag, taskOutputs = {}, params, sourceDir) {
|
|
56
|
-
return {
|
|
57
|
-
...taskOutputs,
|
|
58
|
-
env: process.env,
|
|
59
|
-
workflow: {
|
|
60
|
-
name: workflowName,
|
|
61
|
-
tag
|
|
62
|
-
},
|
|
63
|
-
params,
|
|
64
|
-
source: sourceDir ? { dir: sourceDir } : void 0
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
//#endregion
|
|
69
|
-
//#region src/workflow/layout.ts
|
|
70
|
-
/**
|
|
71
|
-
* Adaptive Terminal Layout System
|
|
72
|
-
*
|
|
73
|
-
* Best practices implementation:
|
|
74
|
-
* - Terminal-aware: Auto-detect width and adapt layout
|
|
75
|
-
* - Smart wrapping: Preserve readability on long messages
|
|
76
|
-
* - Background-agnostic: Colors work on any terminal theme
|
|
77
|
-
* - Human-first: Optimize for visual clarity and scanning
|
|
78
|
-
*
|
|
79
|
-
* References:
|
|
80
|
-
* - https://clig.dev/
|
|
81
|
-
* - https://relay.sh/blog/command-line-ux-in-2020/
|
|
82
|
-
*/
|
|
83
|
-
/**
|
|
84
|
-
* Calculate optimal layout based on terminal size and agent names
|
|
85
|
-
*
|
|
86
|
-
* Layout structure: TIME NAME SEP CONTENT
|
|
87
|
-
* Example: "47:08 student │ Message here"
|
|
88
|
-
* └──┬─┘ └───┬──┘ │ └─────┬──────┘
|
|
89
|
-
* 5 8 3 remaining
|
|
90
|
-
*/
|
|
91
|
-
function calculateLayout(options) {
|
|
92
|
-
const { agentNames, compact = false } = options;
|
|
93
|
-
const terminalWidth = options.terminalWidth ?? process.stdout.columns ?? 80;
|
|
94
|
-
const compactTime = compact || isShortSession();
|
|
95
|
-
const timeWidth = compactTime ? 5 : 8;
|
|
96
|
-
const longestName = Math.max(...agentNames.map((n) => stringWidth(n)), 6);
|
|
97
|
-
const nameWidth = Math.min(longestName + 1, 20);
|
|
98
|
-
const separatorWidth = 3;
|
|
99
|
-
const availableWidth = terminalWidth - (timeWidth + 1 + nameWidth + 1 + separatorWidth);
|
|
100
|
-
const requestedMaxContent = options.maxContentWidth ?? 80;
|
|
101
|
-
return {
|
|
102
|
-
terminalWidth,
|
|
103
|
-
timeWidth,
|
|
104
|
-
nameWidth,
|
|
105
|
-
maxContentWidth: Math.max(Math.min(requestedMaxContent, availableWidth), 40),
|
|
106
|
-
compactTime,
|
|
107
|
-
separatorWidth
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Detect if session is likely short (< 1 hour) based on current time
|
|
112
|
-
* Heuristic: if it's early in the day, likely a short session
|
|
113
|
-
*/
|
|
114
|
-
function isShortSession() {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
let lastTimestamp = null;
|
|
118
|
-
/**
|
|
119
|
-
* Format timestamp according to layout config
|
|
120
|
-
*/
|
|
121
|
-
function formatTime(timestamp, layout) {
|
|
122
|
-
const date = new Date(timestamp);
|
|
123
|
-
const current = {
|
|
124
|
-
hour: date.getHours(),
|
|
125
|
-
minute: date.getMinutes(),
|
|
126
|
-
second: date.getSeconds()
|
|
127
|
-
};
|
|
128
|
-
const last = lastTimestamp ? {
|
|
129
|
-
hour: lastTimestamp.getHours(),
|
|
130
|
-
minute: lastTimestamp.getMinutes()
|
|
131
|
-
} : null;
|
|
132
|
-
const hourChanged = !last || last.hour !== current.hour;
|
|
133
|
-
const minuteChanged = !last || last.minute !== current.minute;
|
|
134
|
-
lastTimestamp = date;
|
|
135
|
-
return {
|
|
136
|
-
formatted: layout.compactTime ? `${pad(current.minute)}:${pad(current.second)}` : `${pad(current.hour)}:${pad(current.minute)}:${pad(current.second)}`,
|
|
137
|
-
hourChanged,
|
|
138
|
-
minuteChanged
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Reset time tracking (call when starting new workflow)
|
|
143
|
-
*/
|
|
144
|
-
function resetTimeTracking() {
|
|
145
|
-
lastTimestamp = null;
|
|
146
|
-
}
|
|
147
|
-
function pad(num) {
|
|
148
|
-
return num.toString().padStart(2, "0");
|
|
149
|
-
}
|
|
150
|
-
function createGroupingState() {
|
|
151
|
-
return {
|
|
152
|
-
lastAgent: null,
|
|
153
|
-
lastMinute: null,
|
|
154
|
-
messageCount: 0
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* Check if message should be grouped with previous one
|
|
159
|
-
* Groups consecutive messages from same agent within same minute
|
|
160
|
-
*/
|
|
161
|
-
function shouldGroup(agent, timestamp, state, enableGrouping) {
|
|
162
|
-
if (!enableGrouping) return false;
|
|
163
|
-
const date = new Date(timestamp);
|
|
164
|
-
const minute = date.getHours() * 60 + date.getMinutes();
|
|
165
|
-
const isSameAgent = agent === state.lastAgent;
|
|
166
|
-
const isSameMinute = minute === state.lastMinute;
|
|
167
|
-
state.lastAgent = agent;
|
|
168
|
-
state.lastMinute = minute;
|
|
169
|
-
state.messageCount++;
|
|
170
|
-
return isSameAgent && isSameMinute && state.messageCount > 1;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
//#endregion
|
|
174
|
-
//#region src/workflow/layout-log.ts
|
|
175
|
-
/**
|
|
176
|
-
* Log-oriented layout strategies
|
|
177
|
-
*
|
|
178
|
-
* Optimized for workflow logs where readability > strict alignment
|
|
179
|
-
* Provides alternative formatting styles for different use cases
|
|
180
|
-
*/
|
|
181
|
-
/**
|
|
182
|
-
* Timeline style: Time with colored dot indicator for each agent
|
|
183
|
-
*
|
|
184
|
-
* Example:
|
|
185
|
-
* 01:17:13 ● workflow
|
|
186
|
-
* │ Running workflow: test-simple with a very long message
|
|
187
|
-
* │ that wraps to the next line automatically
|
|
188
|
-
* 01:17:20 ● alice
|
|
189
|
-
* │ @bob What are AI agents?
|
|
190
|
-
* 01:17:25 ● bob
|
|
191
|
-
* │ @alice AI agents are autonomous software entities
|
|
192
|
-
*/
|
|
193
|
-
function formatTimelineLog(entry, layout, showTime = true) {
|
|
194
|
-
const time = formatTime(entry.timestamp, layout).formatted;
|
|
195
|
-
const timeStr = showTime ? chalk.dim(time) : " ".repeat(layout.timeWidth);
|
|
196
|
-
const agentColors = [
|
|
197
|
-
chalk.cyan,
|
|
198
|
-
chalk.yellow,
|
|
199
|
-
chalk.magenta,
|
|
200
|
-
chalk.green,
|
|
201
|
-
chalk.blue,
|
|
202
|
-
chalk.redBright
|
|
203
|
-
];
|
|
204
|
-
let dotColor;
|
|
205
|
-
if (entry.from === "workflow" || entry.from === "system") dotColor = agentColors[0];
|
|
206
|
-
else dotColor = agentColors[entry.from.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) % (agentColors.length - 1) + 1];
|
|
207
|
-
const dot = dotColor("●");
|
|
208
|
-
const separator = chalk.dim("│");
|
|
209
|
-
const prefixWidth = layout.timeWidth + 3;
|
|
210
|
-
const contentWidth = Math.min(layout.terminalWidth - prefixWidth - 3, 80);
|
|
211
|
-
const wrappedLines = wrapAnsi(entry.content, contentWidth, {
|
|
212
|
-
hard: true,
|
|
213
|
-
trim: false
|
|
214
|
-
}).split("\n");
|
|
215
|
-
const result = [];
|
|
216
|
-
result.push(`${timeStr} ${dot} ${chalk.bold(entry.from)}`);
|
|
217
|
-
const indent = " ".repeat(layout.timeWidth + 1) + `${separator} `;
|
|
218
|
-
result.push(...wrappedLines.map((line) => indent + line));
|
|
219
|
-
return result.join("\n");
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Standard log format: Minimal colors for TTY, plain text for pipes
|
|
223
|
-
*
|
|
224
|
-
* Format: TIMESTAMP SOURCE: MESSAGE
|
|
225
|
-
* Example: 2026-02-09T01:17:13Z workflow: Running workflow: test
|
|
226
|
-
*
|
|
227
|
-
* TTY Mode (terminal):
|
|
228
|
-
* - Dim timestamps for less visual weight
|
|
229
|
-
* - Cyan source names for quick scanning
|
|
230
|
-
* - Yellow [WARN] for warnings
|
|
231
|
-
* - Red [ERROR] for errors
|
|
232
|
-
*
|
|
233
|
-
* Pipe Mode (file/grep):
|
|
234
|
-
* - No colors (chalk auto-detects non-TTY)
|
|
235
|
-
* - Pure text for maximum compatibility
|
|
236
|
-
*
|
|
237
|
-
* Designed for --debug mode, CI/CD logs, and piping to other tools
|
|
238
|
-
*/
|
|
239
|
-
function formatStandardLog(entry, includeMillis = false) {
|
|
240
|
-
const timestamp = includeMillis ? entry.timestamp : entry.timestamp.split(".")[0] + "Z";
|
|
241
|
-
const lines = entry.content.split("\n");
|
|
242
|
-
const isWarn = entry.content.includes("[WARN]");
|
|
243
|
-
const isError = entry.content.includes("[ERROR]");
|
|
244
|
-
let contentColor = (s) => s;
|
|
245
|
-
if (isWarn) contentColor = chalk.yellow;
|
|
246
|
-
if (isError) contentColor = chalk.red;
|
|
247
|
-
const result = [`${chalk.dim(timestamp)} ${chalk.cyan(entry.from)}: ${contentColor(lines[0])}`];
|
|
248
|
-
if (lines.length > 1) {
|
|
249
|
-
const indent = " ".repeat(timestamp.length + 1 + entry.from.length + 2);
|
|
250
|
-
result.push(...lines.slice(1).map((line) => indent + contentColor(line)));
|
|
251
|
-
}
|
|
252
|
-
return result.join("\n");
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region src/workflow/display.ts
|
|
257
|
-
/**
|
|
258
|
-
* Create display context for a workflow
|
|
259
|
-
*/
|
|
260
|
-
function createDisplayContext(agentNames, options) {
|
|
261
|
-
return {
|
|
262
|
-
layout: calculateLayout({ agentNames }),
|
|
263
|
-
grouping: createGroupingState(),
|
|
264
|
-
agentNames,
|
|
265
|
-
enableGrouping: options?.enableGrouping ?? true,
|
|
266
|
-
debugMode: options?.debugMode ?? false
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
function sleep$1(ms) {
|
|
270
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Format a channel entry for display
|
|
274
|
-
*
|
|
275
|
-
* Two modes:
|
|
276
|
-
* - Normal mode: Timeline-style layout for visual clarity
|
|
277
|
-
* - Debug mode: Standard log format (timestamp source: message)
|
|
278
|
-
*/
|
|
279
|
-
function formatChannelEntry(entry, context) {
|
|
280
|
-
if (context.debugMode) return formatStandardLog(entry, false);
|
|
281
|
-
const isFirstMessage = !shouldGroup(entry.from, entry.timestamp, context.grouping, false);
|
|
282
|
-
return formatTimelineLog(entry, context.layout, isFirstMessage);
|
|
283
|
-
}
|
|
284
|
-
/**
|
|
285
|
-
* Start watching channel and displaying new entries
|
|
286
|
-
* with adaptive layout and smart formatting
|
|
287
|
-
*/
|
|
288
|
-
function startChannelWatcher(config) {
|
|
289
|
-
const { contextProvider, agentNames, log, showDebug = false, pollInterval = 500, enableGrouping = true } = config;
|
|
290
|
-
resetTimeTracking();
|
|
291
|
-
const context = createDisplayContext(agentNames, {
|
|
292
|
-
enableGrouping: !showDebug && enableGrouping,
|
|
293
|
-
debugMode: showDebug
|
|
294
|
-
});
|
|
295
|
-
let cursor = config.initialCursor ?? 0;
|
|
296
|
-
let running = true;
|
|
297
|
-
const poll = async () => {
|
|
298
|
-
while (running) {
|
|
299
|
-
try {
|
|
300
|
-
const tail = await contextProvider.tailChannel(cursor);
|
|
301
|
-
for (const entry of tail.entries) {
|
|
302
|
-
if (entry.kind === "debug" && !showDebug) continue;
|
|
303
|
-
log(formatChannelEntry(entry, context));
|
|
304
|
-
}
|
|
305
|
-
cursor = tail.cursor;
|
|
306
|
-
} catch {}
|
|
307
|
-
await sleep$1(pollInterval);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
poll();
|
|
311
|
-
return { stop: () => {
|
|
312
|
-
running = false;
|
|
313
|
-
} };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
//#endregion
|
|
317
|
-
//#region src/workflow/runner.ts
|
|
318
|
-
/**
|
|
319
|
-
* Workflow Runner
|
|
320
|
-
*
|
|
321
|
-
* All output flows through the channel:
|
|
322
|
-
* - Operational events (init, setup, connect) → kind="system" (always visible)
|
|
323
|
-
* - Debug details (MCP traces, idle checks) → kind="debug" (visible with --debug)
|
|
324
|
-
* - Agent messages → kind="message" or undefined (always visible)
|
|
325
|
-
* - Tool calls → kind="tool_call" with structured metadata
|
|
326
|
-
* - Backend text output → kind="output" (always visible)
|
|
327
|
-
*
|
|
328
|
-
* The display layer (display.ts) handles filtering and formatting.
|
|
329
|
-
*/
|
|
330
|
-
const execAsync = promisify(exec);
|
|
331
|
-
/**
|
|
332
|
-
* Create context provider and resolve context directory from workflow config.
|
|
333
|
-
* Extracted so the channel logger can be created before full init.
|
|
334
|
-
*/
|
|
335
|
-
function createWorkflowProvider(workflow, _workflowName, tag) {
|
|
336
|
-
const agentNames = Object.keys(workflow.agents);
|
|
337
|
-
if (!workflow.context) throw new Error("Workflow context is disabled. Remove \"context: false\" to enable agent collaboration.");
|
|
338
|
-
const resolvedContext = workflow.context;
|
|
339
|
-
if (resolvedContext.provider === "memory") return {
|
|
340
|
-
contextProvider: createMemoryContextProvider(agentNames),
|
|
341
|
-
contextDir: join(tmpdir(), `agent-worker-${workflow.name}-${tag}`),
|
|
342
|
-
persistent: false
|
|
343
|
-
};
|
|
344
|
-
const fileContext = resolvedContext;
|
|
345
|
-
const contextDir = fileContext.dir;
|
|
346
|
-
const persistent = fileContext.persistent === true;
|
|
347
|
-
if (!existsSync(contextDir)) mkdirSync(contextDir, { recursive: true });
|
|
348
|
-
const fileProvider = createFileContextProvider(contextDir, agentNames);
|
|
349
|
-
fileProvider.acquireLock();
|
|
350
|
-
return {
|
|
351
|
-
contextProvider: fileProvider,
|
|
352
|
-
contextDir,
|
|
353
|
-
persistent
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Initialize workflow runtime
|
|
358
|
-
*
|
|
359
|
-
* This sets up:
|
|
360
|
-
* 1. Context provider (file or memory) — or uses pre-created one
|
|
361
|
-
* 2. Context directory (for file provider)
|
|
362
|
-
* 3. MCP server (HTTP)
|
|
363
|
-
* 4. Runs setup commands
|
|
364
|
-
*/
|
|
365
|
-
async function initWorkflow(config) {
|
|
366
|
-
const { workflow, workflowName: workflowNameParam, tag: tagParam, onMention, debugLog, feedback: feedbackEnabled, params: paramValues } = config;
|
|
367
|
-
const workflowName = workflowNameParam ?? "global";
|
|
368
|
-
const tag = tagParam ?? "main";
|
|
369
|
-
const logger = config.logger ?? createSilentLogger();
|
|
370
|
-
const startTime = Date.now();
|
|
371
|
-
const agentNames = Object.keys(workflow.agents);
|
|
372
|
-
let contextProvider;
|
|
373
|
-
let contextDir;
|
|
374
|
-
let isPersistent = false;
|
|
375
|
-
if (config.contextProvider && config.contextDir) {
|
|
376
|
-
contextProvider = config.contextProvider;
|
|
377
|
-
contextDir = config.contextDir;
|
|
378
|
-
isPersistent = config.persistent ?? false;
|
|
379
|
-
logger.debug("Using pre-created context provider");
|
|
380
|
-
} else {
|
|
381
|
-
const created = createWorkflowProvider(workflow, workflowName, tag);
|
|
382
|
-
contextProvider = created.contextProvider;
|
|
383
|
-
contextDir = created.contextDir;
|
|
384
|
-
isPersistent = created.persistent;
|
|
385
|
-
const mode = isPersistent ? "persistent (bind)" : "ephemeral";
|
|
386
|
-
logger.debug(`Context directory: ${contextDir} [${mode}]`);
|
|
387
|
-
await contextProvider.markRunStart();
|
|
388
|
-
}
|
|
389
|
-
const projectDir = process.cwd();
|
|
390
|
-
let mcpGetFeedback;
|
|
391
|
-
let mcpToolNames = /* @__PURE__ */ new Set();
|
|
392
|
-
const eventLog = new EventLog(contextProvider);
|
|
393
|
-
const createMCPServerInstance = () => {
|
|
394
|
-
const mcp = createContextMCPServer({
|
|
395
|
-
provider: contextProvider,
|
|
396
|
-
validAgents: agentNames,
|
|
397
|
-
name: `${workflow.name}-context`,
|
|
398
|
-
version: "1.0.0",
|
|
399
|
-
onMention,
|
|
400
|
-
feedback: feedbackEnabled,
|
|
401
|
-
debugLog
|
|
402
|
-
});
|
|
403
|
-
mcpGetFeedback = mcp.getFeedback;
|
|
404
|
-
mcpToolNames = mcp.mcpToolNames;
|
|
405
|
-
return mcp.server;
|
|
406
|
-
};
|
|
407
|
-
const httpMcpServer = await runWithHttp({
|
|
408
|
-
createServerInstance: createMCPServerInstance,
|
|
409
|
-
port: 0,
|
|
410
|
-
onConnect: (agentId, sessionId) => {
|
|
411
|
-
logger.debug(`Agent connected: ${agentId} (${sessionId.slice(0, 8)})`);
|
|
412
|
-
},
|
|
413
|
-
onDisconnect: (agentId, sessionId) => {
|
|
414
|
-
logger.debug(`Agent disconnected: ${agentId} (${sessionId.slice(0, 8)})`);
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
logger.debug(`MCP server: ${httpMcpServer.url}`);
|
|
418
|
-
const setupResults = {};
|
|
419
|
-
const context = createContext(workflow.name, tag, setupResults, paramValues, workflow.sourceDir);
|
|
420
|
-
if (workflow.setup && workflow.setup.length > 0) {
|
|
421
|
-
logger.info("Running setup...");
|
|
422
|
-
for (const task of workflow.setup) try {
|
|
423
|
-
const result = await runSetupTask(task, context, logger);
|
|
424
|
-
if (task.as) {
|
|
425
|
-
setupResults[task.as] = result;
|
|
426
|
-
context[task.as] = result;
|
|
427
|
-
}
|
|
428
|
-
} catch (error) {
|
|
429
|
-
if (contextProvider instanceof FileContextProvider) contextProvider.releaseLock();
|
|
430
|
-
await httpMcpServer.close();
|
|
431
|
-
throw new Error(`Setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const interpolatedKickoff = workflow.kickoff ? interpolate(workflow.kickoff, context, (msg) => logger.warn(msg)) : void 0;
|
|
435
|
-
const runtime = {
|
|
436
|
-
name: workflow.name,
|
|
437
|
-
contextDir,
|
|
438
|
-
projectDir,
|
|
439
|
-
contextProvider,
|
|
440
|
-
eventLog,
|
|
441
|
-
httpMcpServer,
|
|
442
|
-
mcpUrl: httpMcpServer.url,
|
|
443
|
-
agentNames,
|
|
444
|
-
mcpToolNames,
|
|
445
|
-
setupResults,
|
|
446
|
-
async sendKickoff() {
|
|
447
|
-
if (!interpolatedKickoff) {
|
|
448
|
-
logger.debug("No kickoff message configured");
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
logger.debug(`Kickoff: ${interpolatedKickoff.slice(0, 100)}...`);
|
|
452
|
-
await contextProvider.smartSend("system", interpolatedKickoff);
|
|
453
|
-
},
|
|
454
|
-
async shutdown() {
|
|
455
|
-
logger.debug("Shutting down...");
|
|
456
|
-
if (isPersistent) {
|
|
457
|
-
if (contextProvider instanceof FileContextProvider) contextProvider.releaseLock();
|
|
458
|
-
} else await contextProvider.destroy();
|
|
459
|
-
await httpMcpServer.close();
|
|
460
|
-
},
|
|
461
|
-
getFeedback: mcpGetFeedback
|
|
462
|
-
};
|
|
463
|
-
logger.debug(`Workflow initialized in ${Date.now() - startTime}ms`);
|
|
464
|
-
logger.debug(`Agents: ${agentNames.join(", ")}`);
|
|
465
|
-
return runtime;
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Run a setup task
|
|
469
|
-
*/
|
|
470
|
-
async function runSetupTask(task, context, logger) {
|
|
471
|
-
const command = interpolate(task.shell, context);
|
|
472
|
-
const displayCmd = command.length > 60 ? command.slice(0, 60) + "..." : command;
|
|
473
|
-
logger.debug(` $ ${displayCmd}`);
|
|
474
|
-
try {
|
|
475
|
-
const { stdout, stderr } = await execAsync(command);
|
|
476
|
-
const result = stdout.trim();
|
|
477
|
-
if (stderr && stderr.trim()) logger.debug(` stderr: ${stderr.trim().slice(0, 100)}${stderr.length > 100 ? "..." : ""}`);
|
|
478
|
-
if (task.as) {
|
|
479
|
-
const displayResult = result.length > 60 ? result.slice(0, 60) + "..." : result;
|
|
480
|
-
logger.debug(` ${task.as} = ${displayResult}`);
|
|
481
|
-
}
|
|
482
|
-
return result;
|
|
483
|
-
} catch (error) {
|
|
484
|
-
throw new Error(`Command failed: ${command}\n${error instanceof Error ? error.message : String(error)}`);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Run a workflow with agent loops
|
|
489
|
-
*
|
|
490
|
-
* All output flows through the channel. The channel watcher (display layer)
|
|
491
|
-
* filters what to show: --debug includes kind="debug" entries.
|
|
492
|
-
*/
|
|
493
|
-
async function runWorkflowWithLoops(config) {
|
|
494
|
-
const { workflow, workflowName: workflowNameParam, tag: tagParam, debug = false, log = console.log, mode = "run", pollInterval = 5e3, createBackend, feedback: feedbackEnabled, params: paramValues } = config;
|
|
495
|
-
const startTime = Date.now();
|
|
496
|
-
const workflowName = workflowNameParam ?? "global";
|
|
497
|
-
const tag = tagParam ?? "main";
|
|
498
|
-
try {
|
|
499
|
-
const { contextProvider, contextDir, persistent } = createWorkflowProvider(workflow, workflowName, tag);
|
|
500
|
-
const { cursor: channelStart } = await contextProvider.tailChannel(0);
|
|
501
|
-
await contextProvider.markRunStart();
|
|
502
|
-
const logger = createChannelLogger({
|
|
503
|
-
provider: contextProvider,
|
|
504
|
-
from: "workflow"
|
|
505
|
-
});
|
|
506
|
-
logger.info(`Running workflow: ${workflow.name}`);
|
|
507
|
-
logger.info(`Agents: ${Object.keys(workflow.agents).join(", ")}`);
|
|
508
|
-
logger.debug("Starting workflow with loops", {
|
|
509
|
-
mode,
|
|
510
|
-
pollInterval
|
|
511
|
-
});
|
|
512
|
-
const loops = /* @__PURE__ */ new Map();
|
|
513
|
-
logger.debug("Initializing workflow runtime...");
|
|
514
|
-
const runtime = await initWorkflow({
|
|
515
|
-
workflow,
|
|
516
|
-
startAgent: async () => {},
|
|
517
|
-
logger,
|
|
518
|
-
contextProvider,
|
|
519
|
-
contextDir,
|
|
520
|
-
persistent,
|
|
521
|
-
onMention: (from, target, entry) => {
|
|
522
|
-
const loop = loops.get(target);
|
|
523
|
-
if (loop) {
|
|
524
|
-
const preview = entry.content.length > 80 ? entry.content.slice(0, 80) + "..." : entry.content;
|
|
525
|
-
logger.debug(`@mention: ${from} → @${target} (state=${loop.state}): ${preview}`);
|
|
526
|
-
loop.wake();
|
|
527
|
-
} else logger.debug(`@mention: ${from} → @${target} (no loop found!)`);
|
|
528
|
-
},
|
|
529
|
-
debugLog: (msg) => {
|
|
530
|
-
logger.debug(msg);
|
|
531
|
-
},
|
|
532
|
-
feedback: feedbackEnabled,
|
|
533
|
-
params: paramValues
|
|
534
|
-
});
|
|
535
|
-
logger.debug("Runtime initialized", {
|
|
536
|
-
agentNames: runtime.agentNames,
|
|
537
|
-
mcpUrl: runtime.mcpUrl
|
|
538
|
-
});
|
|
539
|
-
logger.info("Starting agents...");
|
|
540
|
-
for (const agentName of runtime.agentNames) {
|
|
541
|
-
const agentDef = workflow.agents[agentName];
|
|
542
|
-
logger.debug(`Creating loop for ${agentName}`, {
|
|
543
|
-
backend: agentDef.backend,
|
|
544
|
-
model: agentDef.model
|
|
545
|
-
});
|
|
546
|
-
const { loop, backend } = createWiredLoop({
|
|
547
|
-
name: agentName,
|
|
548
|
-
agent: agentDef,
|
|
549
|
-
runtime,
|
|
550
|
-
pollInterval,
|
|
551
|
-
feedback: feedbackEnabled,
|
|
552
|
-
createBackend,
|
|
553
|
-
logger: logger.child(agentName)
|
|
554
|
-
});
|
|
555
|
-
logger.debug(`Using backend: ${backend.type} for ${agentName}`);
|
|
556
|
-
loops.set(agentName, loop);
|
|
557
|
-
await loop.start();
|
|
558
|
-
logger.debug(`Loop started: ${agentName}`);
|
|
559
|
-
}
|
|
560
|
-
logger.debug("Sending kickoff message...");
|
|
561
|
-
await runtime.sendKickoff();
|
|
562
|
-
logger.debug("Kickoff sent");
|
|
563
|
-
let channelWatcher;
|
|
564
|
-
if (!config.headless) if (config.prettyDisplay) {
|
|
565
|
-
const { startPrettyDisplay } = await import("./display-pretty-Kyd40DEF.mjs");
|
|
566
|
-
channelWatcher = startPrettyDisplay({
|
|
567
|
-
contextProvider: runtime.contextProvider,
|
|
568
|
-
agentNames: runtime.agentNames,
|
|
569
|
-
workflowName,
|
|
570
|
-
tag,
|
|
571
|
-
workflowPath: config.workflowPath,
|
|
572
|
-
initialCursor: channelStart,
|
|
573
|
-
pollInterval: 250
|
|
574
|
-
});
|
|
575
|
-
} else channelWatcher = startChannelWatcher({
|
|
576
|
-
contextProvider: runtime.contextProvider,
|
|
577
|
-
agentNames: runtime.agentNames,
|
|
578
|
-
log,
|
|
579
|
-
showDebug: debug,
|
|
580
|
-
initialCursor: channelStart,
|
|
581
|
-
pollInterval: 250
|
|
582
|
-
});
|
|
583
|
-
if (mode === "run") {
|
|
584
|
-
logger.debug("Running in \"run\" mode, waiting for completion...");
|
|
585
|
-
let idleCheckCount = 0;
|
|
586
|
-
while (true) {
|
|
587
|
-
const isIdle = await checkWorkflowIdle(loops, runtime.contextProvider);
|
|
588
|
-
idleCheckCount++;
|
|
589
|
-
if (idleCheckCount % 10 === 0) {
|
|
590
|
-
const states = [...loops.entries()].map(([n, c]) => `${n}=${c.state}`).join(", ");
|
|
591
|
-
logger.debug(`Idle check #${idleCheckCount}: ${states}`);
|
|
592
|
-
for (const [agentName] of loops) {
|
|
593
|
-
const inbox = await runtime.contextProvider.getInbox(agentName);
|
|
594
|
-
if (inbox.length > 0) {
|
|
595
|
-
const unseenCount = inbox.filter((m) => !m.seen).length;
|
|
596
|
-
const seenCount = inbox.filter((m) => m.seen).length;
|
|
597
|
-
const parts = [];
|
|
598
|
-
if (seenCount > 0) parts.push(`${seenCount} processing`);
|
|
599
|
-
if (unseenCount > 0) parts.push(`${unseenCount} unread`);
|
|
600
|
-
logger.debug(` ${agentName} inbox: ${parts.join(", ")} from [${inbox.map((m) => m.entry.from).join(", ")}]`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
if (isIdle) {
|
|
605
|
-
const failedAgents = [...loops.entries()].filter(([, loop]) => loop.hasFailures).map(([agentName, loop]) => ({
|
|
606
|
-
name: agentName,
|
|
607
|
-
error: loop.lastError
|
|
608
|
-
}));
|
|
609
|
-
if (failedAgents.length > 0) {
|
|
610
|
-
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
611
|
-
const details = failedAgents.map((a) => `${a.name}: ${a.error}`).join("; ");
|
|
612
|
-
logger.info(`Workflow failed (${elapsed}s): ${details}`);
|
|
613
|
-
channelWatcher?.stop();
|
|
614
|
-
await shutdownLoops(loops, logger);
|
|
615
|
-
await runtime.shutdown();
|
|
616
|
-
return {
|
|
617
|
-
success: false,
|
|
618
|
-
error: details,
|
|
619
|
-
setupResults: runtime.setupResults,
|
|
620
|
-
duration: Date.now() - startTime,
|
|
621
|
-
contextProvider: runtime.contextProvider,
|
|
622
|
-
feedback: runtime.getFeedback?.()
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
626
|
-
logger.info(`Workflow complete (${elapsed}s)`);
|
|
627
|
-
break;
|
|
628
|
-
}
|
|
629
|
-
await sleep(1e3);
|
|
630
|
-
}
|
|
631
|
-
channelWatcher?.stop();
|
|
632
|
-
await shutdownLoops(loops, logger);
|
|
633
|
-
await runtime.shutdown();
|
|
634
|
-
logger.debug(`Workflow finished in ${Date.now() - startTime}ms`);
|
|
635
|
-
return {
|
|
636
|
-
success: true,
|
|
637
|
-
setupResults: runtime.setupResults,
|
|
638
|
-
duration: Date.now() - startTime,
|
|
639
|
-
mcpUrl: runtime.mcpUrl,
|
|
640
|
-
contextProvider: runtime.contextProvider,
|
|
641
|
-
feedback: runtime.getFeedback?.()
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
logger.debug("Running in \"start\" mode, returning control to caller");
|
|
645
|
-
return {
|
|
646
|
-
success: true,
|
|
647
|
-
setupResults: runtime.setupResults,
|
|
648
|
-
duration: Date.now() - startTime,
|
|
649
|
-
mcpUrl: runtime.mcpUrl,
|
|
650
|
-
contextProvider: runtime.contextProvider,
|
|
651
|
-
loops,
|
|
652
|
-
shutdown: async () => {
|
|
653
|
-
channelWatcher?.stop();
|
|
654
|
-
await shutdownLoops(loops, logger);
|
|
655
|
-
await runtime.shutdown();
|
|
656
|
-
},
|
|
657
|
-
getFeedback: runtime.getFeedback
|
|
658
|
-
};
|
|
659
|
-
} catch (error) {
|
|
660
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
661
|
-
log(`Error: ${errorMsg}`);
|
|
662
|
-
return {
|
|
663
|
-
success: false,
|
|
664
|
-
error: errorMsg,
|
|
665
|
-
setupResults: {},
|
|
666
|
-
duration: Date.now() - startTime
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Gracefully shutdown all loops
|
|
672
|
-
*/
|
|
673
|
-
async function shutdownLoops(loops, logger) {
|
|
674
|
-
logger.debug("Stopping loops...");
|
|
675
|
-
const stopPromises = [...loops.values()].map(async (loop) => {
|
|
676
|
-
await loop.stop();
|
|
677
|
-
logger.debug(`Stopped loop: ${loop.name}`);
|
|
678
|
-
});
|
|
679
|
-
await Promise.all(stopPromises);
|
|
680
|
-
logger.debug("All loops stopped");
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* Sleep helper
|
|
684
|
-
*/
|
|
685
|
-
function sleep(ms) {
|
|
686
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
//#endregion
|
|
690
|
-
export { createWorkflowProvider, initWorkflow, interpolate as n, runWorkflowWithLoops, shutdownLoops, createContext as t };
|