agent-worker 0.17.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.
@@ -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 };