agent-worker 0.14.0 → 0.15.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,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { A as parseModel, I as getDefaultModel, P as createModelAsync, a as createMockBackend, j as FRONTIER_MODELS, k as normalizeBackendType, n as createBackend } from "../backends-C6WBIn9H.mjs";
3
- import { generateText, jsonSchema, stepCountIs, tool } from "ai";
2
+ import { A as parseModel, I as getDefaultModel, L as isAutoProvider, P as createModelAsync, R as resolveModelFallback, a as createMockBackend, j as FRONTIER_MODELS, k as normalizeBackendType, n as createBackend } from "../backends-C7pQwuAx.mjs";
3
+ import { t as createTool } from "../create-tool-gcUuI1FD.mjs";
4
+ import { generateText, stepCountIs, tool } from "ai";
4
5
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
5
6
  import { dirname, isAbsolute, join, relative } from "node:path";
6
7
  import { appendFile, mkdir, open, readFile, readdir, stat, unlink, writeFile } from "node:fs/promises";
8
+ import { stringify } from "yaml";
7
9
  import { z } from "zod";
8
10
  import { homedir } from "node:os";
9
11
  import { execSync, spawn } from "node:child_process";
@@ -156,11 +158,11 @@ var MemoryStateStore = class {
156
158
  * Auto-detects runtime: Bun.serve() when available, @hono/node-server otherwise.
157
159
  */
158
160
  async function startHttpServer(app, options) {
159
- if (typeof globalThis.Bun !== "undefined") return startBun(app, options);
161
+ if ("Bun" in globalThis) return startBun(app, options);
160
162
  return startNode(app, options);
161
163
  }
162
164
  function startBun(app, options) {
163
- const server = Bun.serve({
165
+ const server = globalThis.Bun.serve({
164
166
  fetch: app.fetch,
165
167
  port: options.port,
166
168
  hostname: options.hostname
@@ -396,7 +398,7 @@ function registerInboxTools(server, ctx, options) {
396
398
  server.tool("my_status_set", "Update your status and current task. Call when starting or completing work.", {
397
399
  task: z.string().optional().describe("Current task description (what you're working on)"),
398
400
  state: z.enum(["idle", "running"]).optional().describe("Agent state (running = working, idle = available)"),
399
- metadata: z.record(z.unknown()).optional().describe("Additional metadata (e.g., PR number, file path)")
401
+ metadata: z.record(z.string(), z.unknown()).optional().describe("Additional metadata (e.g., PR number, file path)")
400
402
  }, async (args, extra) => {
401
403
  const agent = getAgentId(extra) || "anonymous";
402
404
  logTool("my_status_set", agent, args);
@@ -573,7 +575,7 @@ function registerProposalTools(server, ctx, proposalManager) {
573
575
  }, async (params, extra) => {
574
576
  const createdBy = getAgentId(extra) || "anonymous";
575
577
  try {
576
- const proposal = proposalManager.create({
578
+ const proposal = await proposalManager.create({
577
579
  type: params.type,
578
580
  title: params.title,
579
581
  description: params.description,
@@ -614,7 +616,7 @@ function registerProposalTools(server, ctx, proposalManager) {
614
616
  reason: z.string().optional().describe("Optional reason for your vote")
615
617
  }, async ({ proposal: proposalId, choice, reason }, extra) => {
616
618
  const voter = getAgentId(extra) || "anonymous";
617
- const result = proposalManager.vote({
619
+ const result = await proposalManager.vote({
618
620
  proposalId,
619
621
  voter,
620
622
  choice,
@@ -647,7 +649,7 @@ function registerProposalTools(server, ctx, proposalManager) {
647
649
  });
648
650
  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 }) => {
649
651
  if (proposalId) {
650
- const proposal = proposalManager.get(proposalId);
652
+ const proposal = await proposalManager.get(proposalId);
651
653
  if (!proposal) return { content: [{
652
654
  type: "text",
653
655
  text: JSON.stringify({
@@ -660,7 +662,7 @@ function registerProposalTools(server, ctx, proposalManager) {
660
662
  text: formatProposal(proposal)
661
663
  }] };
662
664
  }
663
- const activeProposals = proposalManager.list("active");
665
+ const activeProposals = await proposalManager.list("active");
664
666
  return { content: [{
665
667
  type: "text",
666
668
  text: activeProposals.length > 0 ? formatProposalList(activeProposals) : "(no active proposals)"
@@ -668,7 +670,7 @@ function registerProposalTools(server, ctx, proposalManager) {
668
670
  });
669
671
  server.tool("team_proposal_cancel", "Cancel a proposal you created.", { proposal: z.string().describe("Proposal ID to cancel") }, async ({ proposal: proposalId }, extra) => {
670
672
  const cancelledBy = getAgentId(extra) || "anonymous";
671
- const result = proposalManager.cancel(proposalId, cancelledBy);
673
+ const result = await proposalManager.cancel(proposalId, cancelledBy);
672
674
  if (!result.success) return { content: [{
673
675
  type: "text",
674
676
  text: JSON.stringify({
@@ -836,7 +838,7 @@ function createContextMCPServer(options) {
836
838
  //#endregion
837
839
  //#region src/workflow/context/types.ts
838
840
  /** Resource ID prefix */
839
- const RESOURCE_PREFIX = "res_";
841
+ const RESOURCE_PREFIX$1 = "res_";
840
842
  /** Resource URI scheme */
841
843
  const RESOURCE_SCHEME = "resource:";
842
844
  /** Message length threshold for channel messages - content longer than this should use resources or documents */
@@ -845,7 +847,7 @@ const MESSAGE_LENGTH_THRESHOLD = 1200;
845
847
  * Generate a unique resource ID
846
848
  */
847
849
  function generateResourceId() {
848
- return `${RESOURCE_PREFIX}${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
850
+ return `${RESOURCE_PREFIX$1}${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
849
851
  }
850
852
  /**
851
853
  * Create resource reference for use in markdown
@@ -894,98 +896,34 @@ function calculatePriority(msg) {
894
896
  //#endregion
895
897
  //#region src/workflow/context/provider.ts
896
898
  /**
897
- * Context Provider interface + unified implementation
898
- * Domain logic for channel, inbox, document, and resource operations.
899
- * Storage I/O is delegated to a StorageBackend.
900
- */
901
- /** Logical storage key layout */
902
- const KEYS = {
903
- channel: "channel.jsonl",
904
- inboxState: "_state/inbox.json",
905
- agentStatus: "_state/agent-status.json",
906
- documentPrefix: "documents/",
907
- resourcePrefix: "resources/"
908
- };
909
- /**
910
- * Unified ContextProvider implementation.
911
- * All domain logic lives here; storage I/O goes through StorageBackend.
899
+ * Composite ContextProvider delegates to domain-specific stores.
912
900
  *
913
- * Channel format: JSONL (one JSON object per line)
914
- * Documents: raw content strings
915
- * Resources: content-addressed blobs
916
- * Inbox state: JSON cursor file (ID-based)
901
+ * Each store owns one concern:
902
+ * - ChannelStore: append-only JSONL message log
903
+ * - InboxStore: filtered view of channel with per-agent cursors
904
+ * - DocumentStore: raw text documents
905
+ * - ResourceStore: content-addressed blobs
906
+ * - StatusStore: agent status tracking
907
+ *
908
+ * smartSend is the only cross-store orchestration (channel + resource).
917
909
  */
918
910
  var ContextProviderImpl = class {
919
- /** Cached parsed channel entries incrementally synced from storage */
920
- channelEntries = [];
921
- /** Storage byte offset up to which the cache is synced */
922
- channelOffset = 0;
923
- /** Guards concurrent syncChannel calls (share one in-flight read) */
924
- syncPromise = null;
925
- /** Run epoch: channel entry count at run start. Inbox ignores entries before this index. */
926
- runStartIndex = 0;
927
- constructor(storage, validAgents) {
928
- this.storage = storage;
911
+ constructor(channel, inbox, documents, resources, status, validAgents) {
912
+ this.channel = channel;
913
+ this.inbox = inbox;
914
+ this.documents = documents;
915
+ this.resources = resources;
916
+ this.status = status;
929
917
  this.validAgents = validAgents;
930
918
  }
931
- /** Expose storage backend (for ProposalManager, testing, etc.) */
932
- getStorage() {
933
- return this.storage;
919
+ appendChannel(from, content, options) {
920
+ return this.channel.append(from, content, options);
934
921
  }
935
- /**
936
- * Sync cached entries from storage via incremental read.
937
- * Only parses newly appended JSONL lines since last sync.
938
- * Concurrent callers share the same in-flight read to avoid duplicate pushes.
939
- */
940
- syncChannel() {
941
- if (!this.syncPromise) this.syncPromise = this.doSyncChannel().finally(() => {
942
- this.syncPromise = null;
943
- });
944
- return this.syncPromise;
922
+ readChannel(options) {
923
+ return this.channel.read(options);
945
924
  }
946
- async doSyncChannel() {
947
- const result = await this.storage.readFrom(KEYS.channel, this.channelOffset);
948
- if (result.content) {
949
- this.channelEntries.push(...parseJsonl(result.content));
950
- this.channelOffset = result.offset;
951
- }
952
- return this.channelEntries;
953
- }
954
- async appendChannel(from, content, options) {
955
- const msg = {
956
- id: nanoid(),
957
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
958
- from,
959
- content,
960
- mentions: extractMentions(content, this.validAgents)
961
- };
962
- if (options?.to) msg.to = options.to;
963
- if (options?.kind) msg.kind = options.kind;
964
- if (options?.toolCall) msg.toolCall = options.toolCall;
965
- const line = JSON.stringify(msg) + "\n";
966
- await this.storage.append(KEYS.channel, line);
967
- return msg;
968
- }
969
- async readChannel(options) {
970
- let entries = await this.syncChannel();
971
- if (options?.agent) {
972
- const agent = options.agent;
973
- entries = entries.filter((e) => {
974
- if (e.kind === "system" || e.kind === "debug" || e.kind === "output") return false;
975
- if (e.to) return e.to === agent || e.from === agent;
976
- return true;
977
- });
978
- }
979
- if (options?.since) entries = entries.filter((e) => e.timestamp > options.since);
980
- if (options?.limit && options.limit > 0) entries = entries.slice(-options.limit);
981
- return entries;
982
- }
983
- async tailChannel(cursor) {
984
- const entries = await this.syncChannel();
985
- return {
986
- entries: entries.slice(cursor),
987
- cursor: entries.length
988
- };
925
+ tailChannel(cursor) {
926
+ return this.channel.tail(cursor);
989
927
  }
990
928
  /**
991
929
  * Smart send: automatically converts long messages to resources
@@ -996,164 +934,60 @@ var ContextProviderImpl = class {
996
934
  * 3. Logs the full content in debug channel for visibility
997
935
  */
998
936
  async smartSend(from, content, options) {
999
- if (!shouldUseResource(content)) return this.appendChannel(from, content, options);
937
+ if (!shouldUseResource(content)) return this.channel.append(from, content, options);
1000
938
  const resourceType = content.startsWith("```") || content.includes("\n```") ? "markdown" : "text";
1001
- const resource = await this.createResource(content, from, resourceType);
1002
- await this.appendChannel("system", `Created resource ${resource.id} (${content.length} chars) for @${from}:\n${content}`, { kind: "debug" });
939
+ const resource = await this.resources.create(content, from, resourceType);
940
+ await this.channel.append("system", `Created resource ${resource.id} (${content.length} chars) for @${from}:\n${content}`, { kind: "debug" });
1003
941
  const mentions = extractMentions(content, this.validAgents);
1004
942
  const shortMessage = `${mentions.length > 0 ? mentions.map((m) => `@${m}`).join(" ") + " " : ""}[Long content stored as resource]\n\nRead the full content: resource_read("${resource.id}")\n\nReference: ${resource.ref}`;
1005
- return this.appendChannel(from, shortMessage, options);
943
+ return this.channel.append(from, shortMessage, options);
1006
944
  }
1007
- async getInbox(agent) {
1008
- const state = await this.loadInboxState();
1009
- const lastAckId = state.readCursors[agent];
1010
- const lastSeenId = state.seenCursors?.[agent];
1011
- let entries = await this.syncChannel();
1012
- if (this.runStartIndex > 0) entries = entries.slice(this.runStartIndex);
1013
- if (lastAckId) {
1014
- const ackIdx = entries.findIndex((e) => e.id === lastAckId);
1015
- if (ackIdx >= 0) entries = entries.slice(ackIdx + 1);
1016
- }
1017
- let seenIdx = -1;
1018
- if (lastSeenId) seenIdx = entries.findIndex((e) => e.id === lastSeenId);
1019
- return entries.filter((e) => {
1020
- if (e.kind === "system" || e.kind === "debug" || e.kind === "output" || e.kind === "tool_call") return false;
1021
- if (e.from === agent) return false;
1022
- return e.mentions.includes(agent) || e.to === agent;
1023
- }).map((entry) => {
1024
- const entryIdx = entries.indexOf(entry);
1025
- return {
1026
- entry,
1027
- priority: calculatePriority(entry),
1028
- seen: seenIdx >= 0 && entryIdx <= seenIdx
1029
- };
1030
- });
945
+ getInbox(agent) {
946
+ return this.inbox.getInbox(agent);
1031
947
  }
1032
- async markInboxSeen(agent, untilId) {
1033
- const state = await this.loadInboxState();
1034
- if (!state.seenCursors) state.seenCursors = {};
1035
- state.seenCursors[agent] = untilId;
1036
- await this.storage.write(KEYS.inboxState, JSON.stringify(state, null, 2));
948
+ markInboxSeen(agent, untilId) {
949
+ return this.inbox.markSeen(agent, untilId);
1037
950
  }
1038
- async ackInbox(agent, untilId) {
1039
- const state = await this.loadInboxState();
1040
- state.readCursors[agent] = untilId;
1041
- await this.storage.write(KEYS.inboxState, JSON.stringify(state, null, 2));
951
+ ackInbox(agent, untilId) {
952
+ return this.inbox.ack(agent, untilId);
1042
953
  }
1043
- async loadInboxState() {
1044
- const raw = await this.storage.read(KEYS.inboxState);
1045
- if (!raw) return { readCursors: {} };
1046
- try {
1047
- const data = JSON.parse(raw);
1048
- return {
1049
- readCursors: data.readCursors || {},
1050
- seenCursors: data.seenCursors
1051
- };
1052
- } catch {
1053
- return { readCursors: {} };
1054
- }
954
+ readDocument(file) {
955
+ return this.documents.read(file);
1055
956
  }
1056
- docKey(file) {
1057
- return KEYS.documentPrefix + (file || CONTEXT_DEFAULTS.document);
957
+ writeDocument(content, file) {
958
+ return this.documents.write(content, file);
1058
959
  }
1059
- async readDocument(file) {
1060
- return await this.storage.read(this.docKey(file)) ?? "";
960
+ appendDocument(content, file) {
961
+ return this.documents.append(content, file);
1061
962
  }
1062
- async writeDocument(content, file) {
1063
- await this.storage.write(this.docKey(file), content);
963
+ listDocuments() {
964
+ return this.documents.list();
1064
965
  }
1065
- async appendDocument(content, file) {
1066
- await this.storage.append(this.docKey(file), content);
966
+ createDocument(file, content) {
967
+ return this.documents.create(file, content);
1067
968
  }
1068
- async listDocuments() {
1069
- return (await this.storage.list(KEYS.documentPrefix)).filter((f) => f.endsWith(".md")).sort();
969
+ createResource(content, createdBy, type) {
970
+ return this.resources.create(content, createdBy, type);
1070
971
  }
1071
- async createDocument(file, content) {
1072
- const key = this.docKey(file);
1073
- if (await this.storage.exists(key)) throw new Error(`Document already exists: ${file}`);
1074
- await this.storage.write(key, content);
972
+ readResource(id) {
973
+ return this.resources.read(id);
1075
974
  }
1076
- async createResource(content, _createdBy, type = "text") {
1077
- const id = generateResourceId();
1078
- const ext = type === "json" ? "json" : type === "diff" ? "diff" : "md";
1079
- const key = `${KEYS.resourcePrefix}${id}.${ext}`;
1080
- await this.storage.write(key, content);
1081
- return {
1082
- id,
1083
- ref: createResourceRef(id)
1084
- };
975
+ setAgentStatus(agent, status) {
976
+ return this.status.set(agent, status);
1085
977
  }
1086
- async readResource(id) {
1087
- for (const ext of [
1088
- "md",
1089
- "json",
1090
- "diff",
1091
- "txt"
1092
- ]) {
1093
- const key = `${KEYS.resourcePrefix}${id}.${ext}`;
1094
- const content = await this.storage.read(key);
1095
- if (content !== null) return content;
1096
- }
1097
- return null;
978
+ getAgentStatus(agent) {
979
+ return this.status.get(agent);
1098
980
  }
1099
- async loadAgentStatus() {
1100
- const raw = await this.storage.read(KEYS.agentStatus);
1101
- if (!raw) return {};
1102
- try {
1103
- return JSON.parse(raw);
1104
- } catch {
1105
- return {};
1106
- }
1107
- }
1108
- async saveAgentStatus(statuses) {
1109
- await this.storage.write(KEYS.agentStatus, JSON.stringify(statuses, null, 2));
1110
- }
1111
- async setAgentStatus(agent, status) {
1112
- const statuses = await this.loadAgentStatus();
1113
- const existing = statuses[agent] || {
1114
- state: "idle",
1115
- lastUpdate: (/* @__PURE__ */ new Date()).toISOString()
1116
- };
1117
- statuses[agent] = {
1118
- ...existing,
1119
- ...status,
1120
- lastUpdate: (/* @__PURE__ */ new Date()).toISOString()
1121
- };
1122
- if (status.state === "running" && existing.state !== "running") statuses[agent].startedAt = (/* @__PURE__ */ new Date()).toISOString();
1123
- if (status.state === "idle") {
1124
- statuses[agent].startedAt = void 0;
1125
- statuses[agent].task = void 0;
1126
- }
1127
- await this.saveAgentStatus(statuses);
1128
- }
1129
- async getAgentStatus(agent) {
1130
- return (await this.loadAgentStatus())[agent] || null;
1131
- }
1132
- async listAgentStatus() {
1133
- return this.loadAgentStatus();
981
+ listAgentStatus() {
982
+ return this.status.list();
1134
983
  }
1135
984
  async markRunStart() {
1136
- this.runStartIndex = (await this.syncChannel()).length;
985
+ await this.inbox.markRunStart();
1137
986
  }
1138
987
  async destroy() {
1139
- await this.storage.delete(KEYS.inboxState);
988
+ await this.inbox.destroy();
1140
989
  }
1141
990
  };
1142
- /**
1143
- * Parse JSONL content into an array of objects.
1144
- * Skips empty lines and lines that fail to parse.
1145
- */
1146
- function parseJsonl(content) {
1147
- const results = [];
1148
- for (const line of content.split("\n")) {
1149
- const trimmed = line.trim();
1150
- if (!trimmed) continue;
1151
- try {
1152
- results.push(JSON.parse(trimmed));
1153
- } catch {}
1154
- }
1155
- return results;
1156
- }
1157
991
 
1158
992
  //#endregion
1159
993
  //#region src/workflow/context/storage.ts
@@ -1311,11 +1145,287 @@ var FileStorage = class {
1311
1145
  }
1312
1146
  };
1313
1147
 
1148
+ //#endregion
1149
+ //#region src/workflow/context/stores/channel.ts
1150
+ /**
1151
+ * Channel Store
1152
+ * Append-only JSONL message log with incremental sync and visibility filtering.
1153
+ */
1154
+ const CHANNEL_KEY = "channel.jsonl";
1155
+ /**
1156
+ * JSONL-backed channel store.
1157
+ * Incrementally syncs from a StorageBackend using byte offsets.
1158
+ */
1159
+ var DefaultChannelStore = class {
1160
+ entries = [];
1161
+ offset = 0;
1162
+ syncPromise = null;
1163
+ constructor(storage, validAgents) {
1164
+ this.storage = storage;
1165
+ this.validAgents = validAgents;
1166
+ }
1167
+ sync() {
1168
+ if (!this.syncPromise) this.syncPromise = this.doSync().finally(() => {
1169
+ this.syncPromise = null;
1170
+ });
1171
+ return this.syncPromise;
1172
+ }
1173
+ async doSync() {
1174
+ const result = await this.storage.readFrom(CHANNEL_KEY, this.offset);
1175
+ if (result.content) {
1176
+ this.entries.push(...parseJsonl(result.content));
1177
+ this.offset = result.offset;
1178
+ }
1179
+ return this.entries;
1180
+ }
1181
+ length() {
1182
+ return this.entries.length;
1183
+ }
1184
+ async append(from, content, options) {
1185
+ const msg = {
1186
+ id: nanoid(),
1187
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1188
+ from,
1189
+ content,
1190
+ mentions: extractMentions(content, this.validAgents)
1191
+ };
1192
+ if (options?.to) msg.to = options.to;
1193
+ if (options?.kind) msg.kind = options.kind;
1194
+ if (options?.toolCall) msg.toolCall = options.toolCall;
1195
+ const line = JSON.stringify(msg) + "\n";
1196
+ await this.storage.append(CHANNEL_KEY, line);
1197
+ return msg;
1198
+ }
1199
+ async read(options) {
1200
+ let entries = await this.sync();
1201
+ if (options?.agent) {
1202
+ const agent = options.agent;
1203
+ entries = entries.filter((e) => {
1204
+ if (e.kind === "system" || e.kind === "debug" || e.kind === "output") return false;
1205
+ if (e.to) return e.to === agent || e.from === agent;
1206
+ return true;
1207
+ });
1208
+ }
1209
+ if (options?.since) entries = entries.filter((e) => e.timestamp > options.since);
1210
+ if (options?.limit && options.limit > 0) entries = entries.slice(-options.limit);
1211
+ return entries;
1212
+ }
1213
+ async tail(cursor) {
1214
+ const entries = await this.sync();
1215
+ return {
1216
+ entries: entries.slice(cursor),
1217
+ cursor: entries.length
1218
+ };
1219
+ }
1220
+ };
1221
+ /**
1222
+ * Parse JSONL content into an array of objects.
1223
+ * Skips empty lines and lines that fail to parse.
1224
+ */
1225
+ function parseJsonl(content) {
1226
+ const results = [];
1227
+ for (const line of content.split("\n")) {
1228
+ const trimmed = line.trim();
1229
+ if (!trimmed) continue;
1230
+ try {
1231
+ results.push(JSON.parse(trimmed));
1232
+ } catch {}
1233
+ }
1234
+ return results;
1235
+ }
1236
+
1237
+ //#endregion
1238
+ //#region src/workflow/context/stores/inbox.ts
1239
+ const INBOX_STATE_KEY = "_state/inbox.json";
1240
+ /**
1241
+ * Default inbox store backed by channel + JSON cursor file.
1242
+ * Inbox is a filtered view of the channel, not a separate log.
1243
+ */
1244
+ var DefaultInboxStore = class {
1245
+ runStartIndex = 0;
1246
+ constructor(channel, storage) {
1247
+ this.channel = channel;
1248
+ this.storage = storage;
1249
+ }
1250
+ async getInbox(agent) {
1251
+ const state = await this.loadState();
1252
+ const lastAckId = state.readCursors[agent];
1253
+ const lastSeenId = state.seenCursors?.[agent];
1254
+ let entries = await this.channel.sync();
1255
+ if (this.runStartIndex > 0) entries = entries.slice(this.runStartIndex);
1256
+ if (lastAckId) {
1257
+ const ackIdx = entries.findIndex((e) => e.id === lastAckId);
1258
+ if (ackIdx >= 0) entries = entries.slice(ackIdx + 1);
1259
+ }
1260
+ let seenIdx = -1;
1261
+ if (lastSeenId) seenIdx = entries.findIndex((e) => e.id === lastSeenId);
1262
+ return entries.filter((e) => {
1263
+ if (e.kind === "system" || e.kind === "debug" || e.kind === "output" || e.kind === "tool_call") return false;
1264
+ if (e.from === agent) return false;
1265
+ return e.mentions.includes(agent) || e.to === agent;
1266
+ }).map((entry) => {
1267
+ const entryIdx = entries.indexOf(entry);
1268
+ return {
1269
+ entry,
1270
+ priority: calculatePriority(entry),
1271
+ seen: seenIdx >= 0 && entryIdx <= seenIdx
1272
+ };
1273
+ });
1274
+ }
1275
+ async markSeen(agent, untilId) {
1276
+ const state = await this.loadState();
1277
+ if (!state.seenCursors) state.seenCursors = {};
1278
+ state.seenCursors[agent] = untilId;
1279
+ await this.storage.write(INBOX_STATE_KEY, JSON.stringify(state, null, 2));
1280
+ }
1281
+ async ack(agent, untilId) {
1282
+ const state = await this.loadState();
1283
+ state.readCursors[agent] = untilId;
1284
+ await this.storage.write(INBOX_STATE_KEY, JSON.stringify(state, null, 2));
1285
+ }
1286
+ async markRunStart() {
1287
+ this.runStartIndex = (await this.channel.sync()).length;
1288
+ }
1289
+ async destroy() {
1290
+ await this.storage.delete(INBOX_STATE_KEY);
1291
+ }
1292
+ async loadState() {
1293
+ const raw = await this.storage.read(INBOX_STATE_KEY);
1294
+ if (!raw) return { readCursors: {} };
1295
+ try {
1296
+ const data = JSON.parse(raw);
1297
+ return {
1298
+ readCursors: data.readCursors || {},
1299
+ seenCursors: data.seenCursors
1300
+ };
1301
+ } catch {
1302
+ return { readCursors: {} };
1303
+ }
1304
+ }
1305
+ };
1306
+
1307
+ //#endregion
1308
+ //#region src/workflow/context/stores/document.ts
1309
+ const DOCUMENT_PREFIX = "documents/";
1310
+ /**
1311
+ * Default document store backed by a StorageBackend.
1312
+ * Documents are stored as raw text under a key prefix.
1313
+ */
1314
+ var DefaultDocumentStore = class {
1315
+ constructor(storage) {
1316
+ this.storage = storage;
1317
+ }
1318
+ key(file) {
1319
+ return DOCUMENT_PREFIX + (file || CONTEXT_DEFAULTS.document);
1320
+ }
1321
+ async read(file) {
1322
+ return await this.storage.read(this.key(file)) ?? "";
1323
+ }
1324
+ async write(content, file) {
1325
+ await this.storage.write(this.key(file), content);
1326
+ }
1327
+ async append(content, file) {
1328
+ await this.storage.append(this.key(file), content);
1329
+ }
1330
+ async list() {
1331
+ return (await this.storage.list(DOCUMENT_PREFIX)).filter((f) => f.endsWith(".md")).sort();
1332
+ }
1333
+ async create(file, content) {
1334
+ const key = this.key(file);
1335
+ if (await this.storage.exists(key)) throw new Error(`Document already exists: ${file}`);
1336
+ await this.storage.write(key, content);
1337
+ }
1338
+ };
1339
+
1340
+ //#endregion
1341
+ //#region src/workflow/context/stores/resource.ts
1342
+ const RESOURCE_PREFIX = "resources/";
1343
+ /**
1344
+ * Default resource store backed by a StorageBackend.
1345
+ * Resources are keyed by generated ID with type-based extensions.
1346
+ */
1347
+ var DefaultResourceStore = class {
1348
+ constructor(storage) {
1349
+ this.storage = storage;
1350
+ }
1351
+ async create(content, _createdBy, type = "text") {
1352
+ const id = generateResourceId();
1353
+ const key = `${RESOURCE_PREFIX}${id}.${type === "json" ? "json" : type === "diff" ? "diff" : "md"}`;
1354
+ await this.storage.write(key, content);
1355
+ return {
1356
+ id,
1357
+ ref: createResourceRef(id)
1358
+ };
1359
+ }
1360
+ async read(id) {
1361
+ for (const ext of [
1362
+ "md",
1363
+ "json",
1364
+ "diff",
1365
+ "txt"
1366
+ ]) {
1367
+ const key = `${RESOURCE_PREFIX}${id}.${ext}`;
1368
+ const content = await this.storage.read(key);
1369
+ if (content !== null) return content;
1370
+ }
1371
+ return null;
1372
+ }
1373
+ };
1374
+
1375
+ //#endregion
1376
+ //#region src/workflow/context/stores/status.ts
1377
+ const STATUS_KEY = "_state/agent-status.json";
1378
+ /**
1379
+ * Default status store backed by a JSON file via StorageBackend.
1380
+ * All agent statuses are stored in a single JSON object.
1381
+ */
1382
+ var DefaultStatusStore = class {
1383
+ constructor(storage) {
1384
+ this.storage = storage;
1385
+ }
1386
+ async set(agent, status) {
1387
+ const statuses = await this.loadAll();
1388
+ const existing = statuses[agent] || {
1389
+ state: "idle",
1390
+ lastUpdate: (/* @__PURE__ */ new Date()).toISOString()
1391
+ };
1392
+ statuses[agent] = {
1393
+ ...existing,
1394
+ ...status,
1395
+ lastUpdate: (/* @__PURE__ */ new Date()).toISOString()
1396
+ };
1397
+ if (status.state === "running" && existing.state !== "running") statuses[agent].startedAt = (/* @__PURE__ */ new Date()).toISOString();
1398
+ if (status.state === "idle") {
1399
+ statuses[agent].startedAt = void 0;
1400
+ statuses[agent].task = void 0;
1401
+ }
1402
+ await this.save(statuses);
1403
+ }
1404
+ async get(agent) {
1405
+ return (await this.loadAll())[agent] || null;
1406
+ }
1407
+ async list() {
1408
+ return this.loadAll();
1409
+ }
1410
+ async loadAll() {
1411
+ const raw = await this.storage.read(STATUS_KEY);
1412
+ if (!raw) return {};
1413
+ try {
1414
+ return JSON.parse(raw);
1415
+ } catch {
1416
+ return {};
1417
+ }
1418
+ }
1419
+ async save(statuses) {
1420
+ await this.storage.write(STATUS_KEY, JSON.stringify(statuses, null, 2));
1421
+ }
1422
+ };
1423
+
1314
1424
  //#endregion
1315
1425
  //#region src/workflow/context/file-provider.ts
1316
1426
  /**
1317
1427
  * File Context Provider
1318
- * Thin wrapper around ContextProviderImpl + FileStorage.
1428
+ * Composes default stores with FileStorage backend.
1319
1429
  * Includes instance lock to prevent concurrent access to the same context directory.
1320
1430
  */
1321
1431
  var file_provider_exports = /* @__PURE__ */ __exportAll({
@@ -1328,8 +1438,7 @@ var file_provider_exports = /* @__PURE__ */ __exportAll({
1328
1438
  const LOCK_FILE = "_state/instance.lock";
1329
1439
  /**
1330
1440
  * File-based ContextProvider.
1331
- * All domain logic is in ContextProviderImpl;
1332
- * FileStorage handles I/O.
1441
+ * Creates default stores backed by a shared FileStorage.
1333
1442
  *
1334
1443
  * Adds instance locking: only one process can hold the lock at a time.
1335
1444
  * Stale locks (from crashed processes) are automatically cleaned up.
@@ -1337,7 +1446,12 @@ const LOCK_FILE = "_state/instance.lock";
1337
1446
  var FileContextProvider = class extends ContextProviderImpl {
1338
1447
  lockPath;
1339
1448
  constructor(storage, validAgents, contextDir) {
1340
- super(storage, validAgents);
1449
+ const channel = new DefaultChannelStore(storage, validAgents);
1450
+ const inbox = new DefaultInboxStore(channel, storage);
1451
+ const documents = new DefaultDocumentStore(storage);
1452
+ const resources = new DefaultResourceStore(storage);
1453
+ const status = new DefaultStatusStore(storage);
1454
+ super(channel, inbox, documents, resources, status, validAgents);
1341
1455
  this.contextDir = contextDir;
1342
1456
  this.lockPath = join(contextDir, LOCK_FILE);
1343
1457
  }
@@ -1390,16 +1504,16 @@ var FileContextProvider = class extends ContextProviderImpl {
1390
1504
  *
1391
1505
  * Supports:
1392
1506
  * - ${{ workflow.name }} — substituted with workflowName
1393
- * - ${{ instance }} — substituted with instance
1507
+ * - ${{ workflow.tag }} — substituted with tag
1394
1508
  * - ~ expansion to home directory
1395
1509
  * - Relative paths resolved against baseDir (or cwd if not provided)
1396
1510
  * - Absolute paths used as-is
1397
1511
  */
1398
1512
  function resolveContextDir(dirTemplate, opts) {
1399
- const workflow = opts.workflow ?? opts.workflowName ?? opts.instance ?? "global";
1513
+ const workflow = opts.workflow ?? opts.workflowName ?? "global";
1400
1514
  const workflowName = opts.workflowName ?? workflow;
1401
1515
  const tag = opts.tag ?? "main";
1402
- let dir = dirTemplate.replace("${{ workflow.name }}", workflowName).replace("${{ workflow.tag }}", tag).replace("${{ instance }}", opts.instance ?? workflow);
1516
+ let dir = dirTemplate.replace("${{ workflow.name }}", workflowName).replace("${{ workflow.tag }}", tag);
1403
1517
  if (dir.startsWith("~/")) dir = join(homedir(), dir.slice(2));
1404
1518
  else if (dir === "~") dir = homedir();
1405
1519
  else if (!isAbsolute(dir)) dir = join(opts.baseDir ?? process.cwd(), dir);
@@ -1410,10 +1524,9 @@ function resolveContextDir(dirTemplate, opts) {
1410
1524
  * Shorthand for the common case.
1411
1525
  * @param workflow Workflow name (defaults to "global")
1412
1526
  * @param tag Workflow instance tag (defaults to "main")
1413
- * @param instanceOrWorkflowName (deprecated) Legacy parameter for backward compatibility
1414
1527
  */
1415
- function getDefaultContextDir(workflow, tag, instanceOrWorkflowName) {
1416
- const wf = workflow ?? instanceOrWorkflowName ?? "global";
1528
+ function getDefaultContextDir(workflow, tag) {
1529
+ const wf = workflow ?? "global";
1417
1530
  const t = tag ?? "main";
1418
1531
  return resolveContextDir(CONTEXT_DEFAULTS.dir, {
1419
1532
  workflow: wf,
@@ -1569,9 +1682,9 @@ async function runWithHttp(options) {
1569
1682
  }
1570
1683
 
1571
1684
  //#endregion
1572
- //#region src/workflow/controller/types.ts
1573
- /** Default controller configuration values */
1574
- const CONTROLLER_DEFAULTS = {
1685
+ //#region src/workflow/loop/types.ts
1686
+ /** Default loop configuration values */
1687
+ const LOOP_DEFAULTS = {
1575
1688
  pollInterval: 5e3,
1576
1689
  retry: {
1577
1690
  maxAttempts: 3,
@@ -1583,7 +1696,7 @@ const CONTROLLER_DEFAULTS = {
1583
1696
  };
1584
1697
 
1585
1698
  //#endregion
1586
- //#region src/workflow/controller/prompt.ts
1699
+ //#region src/workflow/loop/prompt.ts
1587
1700
  /**
1588
1701
  * Format inbox messages for display
1589
1702
  */
@@ -1669,7 +1782,18 @@ function buildAgentPrompt(ctx) {
1669
1782
  }
1670
1783
 
1671
1784
  //#endregion
1672
- //#region src/workflow/controller/mcp-config.ts
1785
+ //#region src/workflow/loop/mcp-config.ts
1786
+ /**
1787
+ * Workflow MCP Config Generation & Writing
1788
+ *
1789
+ * Two responsibilities:
1790
+ * 1. Generate MCP config for workflow HTTP transport
1791
+ * 2. Write backend-specific MCP config files to workspace
1792
+ *
1793
+ * Writing lives here (not in backends) because it's workspace infrastructure,
1794
+ * not a backend concern. Backends only need their cwd set — they don't
1795
+ * need to know about MCP config file layout.
1796
+ */
1673
1797
  /**
1674
1798
  * Generate MCP config for workflow context server.
1675
1799
  *
@@ -1683,6 +1807,62 @@ function generateWorkflowMCPConfig(mcpUrl, agentName) {
1683
1807
  url
1684
1808
  } } };
1685
1809
  }
1810
+ /**
1811
+ * Write MCP config to a workspace directory in the format expected by a backend.
1812
+ *
1813
+ * Each CLI backend reads MCP config from a different location:
1814
+ * - claude: {workspace}/mcp-config.json (passed via --mcp-config flag)
1815
+ * - cursor: {workspace}/.cursor/mcp.json (auto-discovered by cursor)
1816
+ * - codex: {workspace}/.codex/config.yaml (auto-discovered by codex)
1817
+ * - opencode: {workspace}/opencode.json (auto-discovered by opencode)
1818
+ * - default/mock: no config file needed (MCP handled by loop via SDK)
1819
+ */
1820
+ function writeBackendMcpConfig(backendType, workspaceDir, mcpConfig) {
1821
+ ensureDir(workspaceDir);
1822
+ switch (backendType) {
1823
+ case "claude":
1824
+ writeJsonConfig(join(workspaceDir, "mcp-config.json"), mcpConfig);
1825
+ break;
1826
+ case "cursor": {
1827
+ const cursorDir = join(workspaceDir, ".cursor");
1828
+ ensureDir(cursorDir);
1829
+ writeJsonConfig(join(cursorDir, "mcp.json"), mcpConfig);
1830
+ break;
1831
+ }
1832
+ case "codex": {
1833
+ const codexDir = join(workspaceDir, ".codex");
1834
+ ensureDir(codexDir);
1835
+ const codexConfig = { mcp_servers: mcpConfig.mcpServers };
1836
+ writeFileSync(join(codexDir, "config.yaml"), stringify(codexConfig));
1837
+ break;
1838
+ }
1839
+ case "opencode": {
1840
+ const opencodeMcp = {};
1841
+ for (const [name, config] of Object.entries(mcpConfig.mcpServers)) {
1842
+ const serverConfig = config;
1843
+ if (serverConfig.type === "http") opencodeMcp[name] = serverConfig;
1844
+ else opencodeMcp[name] = {
1845
+ type: "local",
1846
+ command: [serverConfig.command, ...serverConfig.args || []],
1847
+ enabled: true,
1848
+ ...serverConfig.env ? { environment: serverConfig.env } : {}
1849
+ };
1850
+ }
1851
+ const opencodeConfig = {
1852
+ $schema: "https://opencode.ai/config.json",
1853
+ mcp: opencodeMcp
1854
+ };
1855
+ writeJsonConfig(join(workspaceDir, "opencode.json"), opencodeConfig);
1856
+ break;
1857
+ }
1858
+ }
1859
+ }
1860
+ function ensureDir(dir) {
1861
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1862
+ }
1863
+ function writeJsonConfig(path, data) {
1864
+ writeFileSync(path, JSON.stringify(data, null, 2));
1865
+ }
1686
1866
 
1687
1867
  //#endregion
1688
1868
  //#region src/daemon/cron.ts
@@ -1779,14 +1959,14 @@ function msUntilNextCron(expr, from = /* @__PURE__ */ new Date()) {
1779
1959
  }
1780
1960
 
1781
1961
  //#endregion
1782
- //#region src/workflow/controller/mock-runner.ts
1962
+ //#region src/workflow/loop/mock-runner.ts
1783
1963
  /**
1784
1964
  * Mock Agent Runner
1785
1965
  *
1786
1966
  * Orchestrates mock agent execution for workflow integration testing.
1787
1967
  * Uses AI SDK generateText with MockLanguageModelV3 and real MCP tool calls.
1788
1968
  *
1789
- * This lives in the controller layer (not backends) because it does orchestration:
1969
+ * This lives in the loop layer (not backends) because it does orchestration:
1790
1970
  * connecting to MCP, building prompts, managing tool loops.
1791
1971
  * The mock backend itself is just a simple send() adapter.
1792
1972
  */
@@ -1804,9 +1984,9 @@ async function createMCPToolBridge$1(mcpUrl, agentName) {
1804
1984
  const aiTools = {};
1805
1985
  for (const mcpTool of mcpTools) {
1806
1986
  const toolName = mcpTool.name;
1807
- aiTools[toolName] = tool({
1987
+ aiTools[toolName] = createTool({
1808
1988
  description: mcpTool.description || toolName,
1809
- inputSchema: jsonSchema(mcpTool.inputSchema),
1989
+ schema: mcpTool.inputSchema,
1810
1990
  execute: async (args) => {
1811
1991
  return (await client.callTool({
1812
1992
  name: toolName,
@@ -1823,7 +2003,7 @@ async function createMCPToolBridge$1(mcpUrl, agentName) {
1823
2003
  /**
1824
2004
  * Run a mock agent with AI SDK and real MCP tools.
1825
2005
  *
1826
- * Used by the controller when backend.type === 'mock'.
2006
+ * Used by the loop when backend.type === 'mock'.
1827
2007
  * Unlike real backends that just send(), the mock runner needs to:
1828
2008
  * 1. Connect to MCP server for real tool execution
1829
2009
  * 2. Generate scripted tool calls via MockLanguageModelV3
@@ -1915,7 +2095,7 @@ async function runMockAgent(ctx, debugLog) {
1915
2095
  }
1916
2096
 
1917
2097
  //#endregion
1918
- //#region src/workflow/controller/sdk-runner.ts
2098
+ //#region src/workflow/loop/sdk-runner.ts
1919
2099
  /**
1920
2100
  * SDK Agent Runner
1921
2101
  *
@@ -1968,9 +2148,9 @@ async function createMCPToolBridge(mcpUrl, agentName) {
1968
2148
  const aiTools = {};
1969
2149
  for (const mcpTool of mcpTools) {
1970
2150
  const toolName = mcpTool.name;
1971
- aiTools[toolName] = tool({
2151
+ aiTools[toolName] = createTool({
1972
2152
  description: mcpTool.description || toolName,
1973
- inputSchema: jsonSchema(mcpTool.inputSchema),
2153
+ schema: mcpTool.inputSchema,
1974
2154
  execute: async (args) => {
1975
2155
  return (await client.callTool({
1976
2156
  name: toolName,
@@ -1985,17 +2165,18 @@ async function createMCPToolBridge(mcpUrl, agentName) {
1985
2165
  };
1986
2166
  }
1987
2167
  function createBashTool() {
1988
- return tool({
2168
+ return createTool({
1989
2169
  description: "Execute a shell command and return stdout/stderr.",
1990
- inputSchema: jsonSchema({
2170
+ schema: {
1991
2171
  type: "object",
1992
2172
  properties: { command: {
1993
2173
  type: "string",
1994
2174
  description: "The shell command to execute"
1995
2175
  } },
1996
2176
  required: ["command"]
1997
- }),
1998
- execute: async ({ command }) => {
2177
+ },
2178
+ execute: async (args) => {
2179
+ const command = args.command;
1999
2180
  try {
2000
2181
  return execSync(command, {
2001
2182
  encoding: "utf-8",
@@ -2010,7 +2191,7 @@ function createBashTool() {
2010
2191
  /**
2011
2192
  * Run an SDK agent with real model + MCP tools + bash.
2012
2193
  *
2013
- * Used by the controller when backend.type === 'default'.
2194
+ * Used by the loop when backend.type === 'default'.
2014
2195
  * Unlike the simple SdkBackend.send() (text-only), this runner:
2015
2196
  * 1. Connects to MCP server for context tools (channel, document)
2016
2197
  * 2. Adds bash tool for shell access
@@ -2074,30 +2255,30 @@ async function runSdkAgent(ctx, debugLog) {
2074
2255
  }
2075
2256
 
2076
2257
  //#endregion
2077
- //#region src/workflow/controller/controller.ts
2078
- /** Check if controller should continue running */
2258
+ //#region src/workflow/loop/loop.ts
2259
+ /** Check if loop should continue running */
2079
2260
  function shouldContinue(state) {
2080
2261
  return state !== "stopped";
2081
2262
  }
2082
2263
  /**
2083
- * Create an agent controller
2264
+ * Create an agent loop
2084
2265
  *
2085
- * The controller:
2266
+ * The loop:
2086
2267
  * 1. Polls for inbox messages on an interval
2087
2268
  * 2. Runs the agent when messages are found
2088
2269
  * 3. Acknowledges inbox only on successful run
2089
2270
  * 4. Retries with exponential backoff on failure
2090
2271
  * 5. Can be woken early via wake()
2091
2272
  */
2092
- function createAgentController(config) {
2273
+ function createAgentLoop(config) {
2093
2274
  const { name, agent, contextProvider, eventLog, mcpUrl, workspaceDir, projectDir, backend, onRunComplete, log = () => {}, feedback } = config;
2094
2275
  const infoLog = config.infoLog ?? log;
2095
2276
  const errorLog = config.errorLog ?? log;
2096
- const pollInterval = config.pollInterval ?? CONTROLLER_DEFAULTS.pollInterval;
2277
+ const pollInterval = config.pollInterval ?? LOOP_DEFAULTS.pollInterval;
2097
2278
  const retryConfig = {
2098
- maxAttempts: config.retry?.maxAttempts ?? CONTROLLER_DEFAULTS.retry.maxAttempts,
2099
- backoffMs: config.retry?.backoffMs ?? CONTROLLER_DEFAULTS.retry.backoffMs,
2100
- backoffMultiplier: config.retry?.backoffMultiplier ?? CONTROLLER_DEFAULTS.retry.backoffMultiplier
2279
+ maxAttempts: config.retry?.maxAttempts ?? LOOP_DEFAULTS.retry.maxAttempts,
2280
+ backoffMs: config.retry?.backoffMs ?? LOOP_DEFAULTS.retry.backoffMs,
2281
+ backoffMultiplier: config.retry?.backoffMultiplier ?? LOOP_DEFAULTS.retry.backoffMultiplier
2101
2282
  };
2102
2283
  let state = "stopped";
2103
2284
  let wakeResolver = null;
@@ -2125,7 +2306,7 @@ function createAgentController(config) {
2125
2306
  });
2126
2307
  }
2127
2308
  /**
2128
- * Main controller loop
2309
+ * Main poll loop
2129
2310
  */
2130
2311
  async function runLoop() {
2131
2312
  while (shouldContinue(state)) {
@@ -2175,7 +2356,7 @@ function createAgentController(config) {
2175
2356
  agent,
2176
2357
  inbox,
2177
2358
  recentChannel: await contextProvider.readChannel({
2178
- limit: CONTROLLER_DEFAULTS.recentChannelLimit,
2359
+ limit: LOOP_DEFAULTS.recentChannelLimit,
2179
2360
  agent: name
2180
2361
  }),
2181
2362
  documentContent: await contextProvider.readDocument(),
@@ -2219,7 +2400,7 @@ function createAgentController(config) {
2219
2400
  return state;
2220
2401
  },
2221
2402
  async start() {
2222
- if (state !== "stopped") throw new Error(`Controller ${name} is already running`);
2403
+ if (state !== "stopped") throw new Error(`Loop ${name} is already running`);
2223
2404
  state = "idle";
2224
2405
  lastActivityTime = Date.now();
2225
2406
  await contextProvider.setAgentStatus(name, { state: "idle" });
@@ -2281,7 +2462,7 @@ function createAgentController(config) {
2281
2462
  agent,
2282
2463
  inbox,
2283
2464
  recentChannel: await contextProvider.readChannel({
2284
- limit: CONTROLLER_DEFAULTS.recentChannelLimit,
2465
+ limit: LOOP_DEFAULTS.recentChannelLimit,
2285
2466
  agent: name
2286
2467
  }),
2287
2468
  documentContent: await contextProvider.readDocument(),
@@ -2312,7 +2493,7 @@ function createAgentController(config) {
2312
2493
  /**
2313
2494
  * Run an agent: build prompt, configure workspace, call backend.send()
2314
2495
  *
2315
- * This is the single orchestration function that the controller calls.
2496
+ * This is the single orchestration function that the loop calls.
2316
2497
  * All the "how to run an agent" logic lives here — backends just send().
2317
2498
  *
2318
2499
  * SDK and mock backends get special runners with MCP tool bridge + bash,
@@ -2324,10 +2505,8 @@ async function runAgent(backend, ctx, log, infoLog) {
2324
2505
  if (backend.type === "default") return runSdkAgent(ctx, (msg) => log(msg));
2325
2506
  const startTime = Date.now();
2326
2507
  try {
2327
- if (backend.setWorkspace) {
2328
- const mcpConfig = generateWorkflowMCPConfig(ctx.mcpUrl, ctx.name);
2329
- backend.setWorkspace(ctx.workspaceDir, mcpConfig);
2330
- }
2508
+ const mcpConfig = generateWorkflowMCPConfig(ctx.mcpUrl, ctx.name);
2509
+ writeBackendMcpConfig(backend.type, ctx.workspaceDir, mcpConfig);
2331
2510
  const prompt = buildAgentPrompt(ctx);
2332
2511
  info(`Prompt (${prompt.length} chars) → ${backend.type} backend`);
2333
2512
  const response = await backend.send(prompt, { system: ctx.agent.resolvedSystemPrompt });
@@ -2353,15 +2532,15 @@ function sleep(ms) {
2353
2532
  /**
2354
2533
  * Check if workflow is complete (all agents idle, no pending work)
2355
2534
  */
2356
- async function checkWorkflowIdle(controllers, provider, debounceMs = CONTROLLER_DEFAULTS.idleDebounceMs) {
2357
- if (![...controllers.values()].every((c) => c.state === "idle")) return false;
2358
- for (const [name] of controllers) if ((await provider.getInbox(name)).length > 0) return false;
2535
+ async function checkWorkflowIdle(loops, provider, debounceMs = LOOP_DEFAULTS.idleDebounceMs) {
2536
+ if (![...loops.values()].every((c) => c.state === "idle")) return false;
2537
+ for (const [name] of loops) if ((await provider.getInbox(name)).length > 0) return false;
2359
2538
  await sleep(debounceMs);
2360
- return [...controllers.values()].every((c) => c.state === "idle");
2539
+ return [...loops.values()].every((c) => c.state === "idle");
2361
2540
  }
2362
2541
 
2363
2542
  //#endregion
2364
- //#region src/workflow/controller/backend.ts
2543
+ //#region src/workflow/loop/backend.ts
2365
2544
  /**
2366
2545
  * Get backend by explicit backend type
2367
2546
  *
@@ -2373,6 +2552,7 @@ function getBackendByType(backendType, options) {
2373
2552
  const backendOptions = {};
2374
2553
  if (options?.timeout) backendOptions.timeout = options.timeout;
2375
2554
  if (options?.streamCallbacks) backendOptions.streamCallbacks = options.streamCallbacks;
2555
+ if (options?.workspace) backendOptions.workspace = options.workspace;
2376
2556
  return createBackend({
2377
2557
  type: backendType,
2378
2558
  model: options?.model,
@@ -2392,21 +2572,18 @@ function getBackendForModel(model, options) {
2392
2572
  model
2393
2573
  });
2394
2574
  const { provider } = parseModel(model);
2395
- switch (provider) {
2396
- case "anthropic": return getBackendByType("default", {
2397
- ...options,
2398
- model
2399
- });
2400
- case "claude": return getBackendByType("claude", {
2401
- ...options,
2402
- model
2403
- });
2404
- case "codex": return getBackendByType("codex", {
2405
- ...options,
2406
- model
2407
- });
2408
- default: throw new Error(`Unknown provider: ${provider}. Specify backend explicitly.`);
2409
- }
2575
+ if (provider === "claude") return getBackendByType("claude", {
2576
+ ...options,
2577
+ model
2578
+ });
2579
+ if (provider === "codex") return getBackendByType("codex", {
2580
+ ...options,
2581
+ model
2582
+ });
2583
+ return getBackendByType("default", {
2584
+ ...options,
2585
+ model
2586
+ });
2410
2587
  }
2411
2588
 
2412
2589
  //#endregion
@@ -2483,19 +2660,19 @@ function formatArg(arg) {
2483
2660
  * These functions are the building blocks that both runner.ts (CLI direct)
2484
2661
  * and daemon.ts (service) use to create workflow infrastructure.
2485
2662
  *
2486
- * Extracted from the monolithic runWorkflowWithControllers() so that
2663
+ * Extracted from the monolithic runWorkflowWithLoops() so that
2487
2664
  * the daemon can create and manage workflow components independently.
2488
2665
  *
2489
2666
  * Usage:
2490
2667
  * 1. createMinimalRuntime() — context + MCP + event log (the "workspace")
2491
- * 2. createWiredController() — backend + workspace dir + controller (per agent)
2492
- * 3. Caller manages lifecycle — start/stop controllers, send kickoff, shutdown
2668
+ * 2. createWiredLoop() — backend + workspace dir + loop (per agent)
2669
+ * 3. Caller manages lifecycle — start/stop loops, send kickoff, shutdown
2493
2670
  */
2494
2671
  /**
2495
2672
  * Create a minimal workflow runtime.
2496
2673
  *
2497
2674
  * Sets up the shared infrastructure (context + MCP + event log) without
2498
- * creating controllers or backends. The daemon can use this to create
2675
+ * creating loops or backends. The daemon can use this to create
2499
2676
  * workflow infrastructure for both standalone and multi-agent workflows.
2500
2677
  *
2501
2678
  * For standalone agents created via `POST /agents`, this gives them
@@ -2559,47 +2736,64 @@ async function createMinimalRuntime(config) {
2559
2736
  };
2560
2737
  }
2561
2738
  /**
2562
- * Create a fully-wired agent controller.
2739
+ * Create a fully-wired agent loop.
2563
2740
  *
2564
2741
  * This handles the full setup:
2565
2742
  * 1. Create backend from agent definition (or use custom factory)
2566
2743
  * 2. Create isolated workspace directory
2567
2744
  * 3. Configure stream callbacks for structured event logging
2568
- * 4. Create the AgentController with all wiring
2745
+ * 4. Create the AgentLoop with all wiring
2569
2746
  *
2570
- * Extracted from runWorkflowWithControllers() so both runner.ts and
2571
- * daemon.ts can create controllers with the same quality.
2747
+ * Extracted from runWorkflowWithLoops() so both runner.ts and
2748
+ * daemon.ts can create loops with the same quality.
2572
2749
  */
2573
- function createWiredController(config) {
2750
+ function createWiredLoop(config) {
2574
2751
  const { name, agent, runtime, pollInterval, feedback: feedbackEnabled } = config;
2575
2752
  const logger = config.logger ?? createSilentLogger();
2753
+ const workspaceDir = join(runtime.contextDir, "workspaces", name);
2754
+ if (!existsSync(workspaceDir)) mkdirSync(workspaceDir, { recursive: true });
2576
2755
  const streamCallbacks = {
2577
2756
  debugLog: (msg) => logger.debug(msg),
2578
2757
  outputLog: (msg) => runtime.eventLog.output(name, msg),
2579
2758
  toolCallLog: (toolName, args) => runtime.eventLog.toolCall(name, toolName, args, "backend"),
2580
2759
  mcpToolNames: runtime.mcpToolNames
2581
2760
  };
2761
+ let effectiveModel;
2762
+ let effectiveProvider = agent.provider;
2763
+ if (isAutoProvider(agent.model) || isAutoProvider(agent.provider)) {
2764
+ const resolved = resolveModelFallback({
2765
+ model: agent.model,
2766
+ provider: typeof agent.provider === "string" ? agent.provider : void 0
2767
+ });
2768
+ effectiveModel = resolved.model;
2769
+ effectiveProvider = resolved.provider;
2770
+ logger.info(`Model resolved: ${effectiveModel}`);
2771
+ } else effectiveModel = agent.model;
2582
2772
  let backend;
2583
2773
  if (config.createBackend) backend = config.createBackend(name, agent);
2584
2774
  else if (agent.backend) backend = getBackendByType(agent.backend, {
2585
- model: agent.model,
2586
- provider: agent.provider,
2775
+ model: effectiveModel,
2776
+ provider: effectiveProvider,
2587
2777
  debugLog: (msg) => logger.debug(msg),
2588
2778
  streamCallbacks,
2589
- timeout: agent.timeout
2779
+ timeout: agent.timeout,
2780
+ workspace: workspaceDir
2590
2781
  });
2591
- else if (agent.model) backend = getBackendForModel(agent.model, {
2592
- provider: agent.provider,
2782
+ else if (effectiveModel) backend = getBackendForModel(effectiveModel, {
2783
+ provider: effectiveProvider,
2593
2784
  debugLog: (msg) => logger.debug(msg),
2594
- streamCallbacks
2785
+ streamCallbacks,
2786
+ workspace: workspaceDir
2595
2787
  });
2596
2788
  else throw new Error(`Agent "${name}" requires either a backend or model field`);
2597
- const workspaceDir = join(runtime.contextDir, "workspaces", name);
2598
- if (!existsSync(workspaceDir)) mkdirSync(workspaceDir, { recursive: true });
2599
2789
  return {
2600
- controller: createAgentController({
2790
+ loop: createAgentLoop({
2601
2791
  name,
2602
- agent,
2792
+ agent: effectiveModel !== agent.model || effectiveProvider !== agent.provider ? {
2793
+ ...agent,
2794
+ model: effectiveModel,
2795
+ provider: effectiveProvider
2796
+ } : agent,
2603
2797
  contextProvider: runtime.contextProvider,
2604
2798
  eventLog: runtime.eventLog,
2605
2799
  mcpUrl: runtime.mcpUrl,
@@ -2621,14 +2815,14 @@ function createWiredController(config) {
2621
2815
  /**
2622
2816
  * Daemon — Centralized agent coordinator.
2623
2817
  *
2624
- * Architecture: Interface → Daemon → Controller (three layers)
2818
+ * Architecture: Interface → Daemon → Loop (three layers)
2625
2819
  * Interface: CLI/REST/MCP clients talk to daemon via HTTP
2626
- * Daemon: This module — owns lifecycle, creates workflows + controllers
2627
- * Controller: AgentController + Backend — executes agent reasoning
2820
+ * Daemon: This module — owns lifecycle, creates workflows + loops
2821
+ * Loop: AgentLoop + Backend — executes agent reasoning
2628
2822
  *
2629
2823
  * Data ownership:
2630
2824
  * Registry (configs) — what agents exist and their configuration
2631
- * Workflows (workflows) — running workflow instances with controllers + context
2825
+ * Workflows (workflows) — running workflow instances with loops + context
2632
2826
  *
2633
2827
  * Key principle: every agent lives in a workflow. Standalone agents created via
2634
2828
  * POST /agents get a 1-agent workflow (created lazily on first /run or /serve).
@@ -2684,28 +2878,28 @@ function configToResolvedAgent(cfg) {
2684
2878
  };
2685
2879
  }
2686
2880
  /**
2687
- * Find an agent's controller across all workflows.
2688
- * Returns the controller if the agent exists in any workflow.
2881
+ * Find an agent's loop across all workflows.
2882
+ * Returns the loop if the agent exists in any workflow.
2689
2883
  */
2690
- function findController(s, agentName) {
2884
+ function findLoop(s, agentName) {
2691
2885
  for (const wf of s.workflows.values()) {
2692
- const ctrl = wf.controllers.get(agentName);
2693
- if (ctrl) return {
2694
- controller: ctrl,
2886
+ const l = wf.loops.get(agentName);
2887
+ if (l) return {
2888
+ loop: l,
2695
2889
  workflow: wf
2696
2890
  };
2697
2891
  }
2698
2892
  return null;
2699
2893
  }
2700
2894
  /**
2701
- * Ensure a standalone agent has a workflow + controller.
2895
+ * Ensure a standalone agent has a workflow + loop.
2702
2896
  * Creates the infrastructure lazily on first call (starts MCP server, etc.).
2703
2897
  *
2704
2898
  * This is the bridge between POST /agents (stores config only) and
2705
- * POST /run or /serve (needs a controller to execute).
2899
+ * POST /run or /serve (needs a loop to execute).
2706
2900
  */
2707
- async function ensureAgentController(s, agentName) {
2708
- const existing = findController(s, agentName);
2901
+ async function ensureAgentLoop(s, agentName) {
2902
+ const existing = findLoop(s, agentName);
2709
2903
  if (existing) return existing;
2710
2904
  const cfg = s.configs.get(agentName);
2711
2905
  if (!cfg) throw new Error(`Agent not found: ${agentName}`);
@@ -2716,9 +2910,9 @@ async function ensureAgentController(s, agentName) {
2716
2910
  tag: cfg.tag,
2717
2911
  agentNames: [agentName]
2718
2912
  });
2719
- let controller;
2913
+ let loop;
2720
2914
  try {
2721
- ({controller} = createWiredController({
2915
+ ({loop} = createWiredLoop({
2722
2916
  name: agentName,
2723
2917
  agent: agentDef,
2724
2918
  runtime
@@ -2732,11 +2926,11 @@ async function ensureAgentController(s, agentName) {
2732
2926
  tag: cfg.tag,
2733
2927
  key: wfKey,
2734
2928
  agents: [agentName],
2735
- controllers: new Map([[agentName, controller]]),
2929
+ loops: new Map([[agentName, loop]]),
2736
2930
  contextProvider: runtime.contextProvider,
2737
2931
  shutdown: async () => {
2738
2932
  try {
2739
- await controller.stop();
2933
+ await loop.stop();
2740
2934
  } finally {
2741
2935
  await runtime.shutdown();
2742
2936
  }
@@ -2745,15 +2939,21 @@ async function ensureAgentController(s, agentName) {
2745
2939
  };
2746
2940
  s.workflows.set(wfKey, handle);
2747
2941
  return {
2748
- controller,
2942
+ loop,
2749
2943
  workflow: handle
2750
2944
  };
2751
2945
  }
2752
- function createDaemonApp(optionsOrGetState) {
2753
- const { getState, token } = typeof optionsOrGetState === "function" ? {
2754
- getState: optionsOrGetState,
2755
- token: void 0
2756
- } : optionsOrGetState;
2946
+ /**
2947
+ * Create the Hono app with all daemon routes.
2948
+ *
2949
+ * Accepts a state getter so the app can be used both in production
2950
+ * (module-level state set by startDaemon) and in tests (injected state).
2951
+ *
2952
+ * When a token is provided, all endpoints require `Authorization: Bearer <token>`.
2953
+ * This prevents cross-origin attacks from malicious websites.
2954
+ */
2955
+ function createDaemonApp(options) {
2956
+ const { getState, token } = options;
2757
2957
  const app = new Hono();
2758
2958
  if (token) app.use("*", async (c, next) => {
2759
2959
  if (c.req.header("authorization") !== `Bearer ${token}`) return c.json({ error: "Unauthorized" }, 401);
@@ -2790,7 +2990,7 @@ function createDaemonApp(optionsOrGetState) {
2790
2990
  const s = getState();
2791
2991
  if (!s) return c.json({ error: "Not ready" }, 503);
2792
2992
  const standaloneAgents = [...s.configs.values()].map((cfg) => {
2793
- const ctrl = findController(s, cfg.name);
2993
+ const found = findLoop(s, cfg.name);
2794
2994
  return {
2795
2995
  name: cfg.name,
2796
2996
  model: cfg.model,
@@ -2799,11 +2999,11 @@ function createDaemonApp(optionsOrGetState) {
2799
2999
  tag: cfg.tag,
2800
3000
  createdAt: cfg.createdAt,
2801
3001
  source: "standalone",
2802
- state: ctrl?.controller.state
3002
+ state: found?.loop.state
2803
3003
  };
2804
3004
  });
2805
3005
  const workflowAgents = [...s.workflows.values()].flatMap((wf) => wf.agents.map((agentName) => {
2806
- const controller = wf.controllers.get(agentName);
3006
+ const loop = wf.loops.get(agentName);
2807
3007
  return {
2808
3008
  name: agentName,
2809
3009
  model: "",
@@ -2812,7 +3012,7 @@ function createDaemonApp(optionsOrGetState) {
2812
3012
  tag: wf.tag,
2813
3013
  createdAt: wf.startedAt,
2814
3014
  source: "workflow",
2815
- state: controller?.state ?? "unknown"
3015
+ state: loop?.state ?? "unknown"
2816
3016
  };
2817
3017
  }));
2818
3018
  return c.json({ agents: [...standaloneAgents, ...workflowAgents] });
@@ -2884,20 +3084,20 @@ function createDaemonApp(optionsOrGetState) {
2884
3084
  if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
2885
3085
  const { agent: agentName, message } = body;
2886
3086
  if (!agentName || !message) return c.json({ error: "agent and message required" }, 400);
2887
- let controller;
2888
- const controllerResult = findController(s, agentName);
2889
- if (controllerResult) controller = controllerResult.controller;
3087
+ let loop;
3088
+ const loopResult = findLoop(s, agentName);
3089
+ if (loopResult) loop = loopResult.loop;
2890
3090
  else if (s.configs.has(agentName)) try {
2891
- controller = (await ensureAgentController(s, agentName)).controller;
3091
+ loop = (await ensureAgentLoop(s, agentName)).loop;
2892
3092
  } catch (error) {
2893
3093
  const msg = error instanceof Error ? error.message : String(error);
2894
3094
  return c.json({ error: `Failed to create agent runtime: ${msg}` }, 500);
2895
3095
  }
2896
- if (!controller) return c.json({ error: `Agent not found: ${agentName}` }, 404);
2897
- const ctrl = controller;
3096
+ if (!loop) return c.json({ error: `Agent not found: ${agentName}` }, 404);
3097
+ const agentLoop = loop;
2898
3098
  return streamSSE(c, async (stream) => {
2899
3099
  try {
2900
- const result = await ctrl.sendDirect(message);
3100
+ const result = await agentLoop.sendDirect(message);
2901
3101
  if (result.success) {
2902
3102
  if (result.content) await stream.writeSSE({
2903
3103
  event: "chunk",
@@ -2930,18 +3130,18 @@ function createDaemonApp(optionsOrGetState) {
2930
3130
  if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
2931
3131
  const { agent: agentName, message } = body;
2932
3132
  if (!agentName || !message) return c.json({ error: "agent and message required" }, 400);
2933
- let controller;
2934
- const controllerResult = findController(s, agentName);
2935
- if (controllerResult) controller = controllerResult.controller;
3133
+ let loop;
3134
+ const loopResult = findLoop(s, agentName);
3135
+ if (loopResult) loop = loopResult.loop;
2936
3136
  else if (s.configs.has(agentName)) try {
2937
- controller = (await ensureAgentController(s, agentName)).controller;
3137
+ loop = (await ensureAgentLoop(s, agentName)).loop;
2938
3138
  } catch (error) {
2939
3139
  const msg = error instanceof Error ? error.message : String(error);
2940
3140
  return c.json({ error: msg }, 500);
2941
3141
  }
2942
- if (!controller) return c.json({ error: `Agent not found: ${agentName}` }, 404);
3142
+ if (!loop) return c.json({ error: `Agent not found: ${agentName}` }, 404);
2943
3143
  try {
2944
- const result = await controller.sendDirect(message);
3144
+ const result = await loop.sendDirect(message);
2945
3145
  if (!result.success) return c.json({ error: result.error }, 500);
2946
3146
  return c.json({
2947
3147
  content: result.content ?? "",
@@ -2974,7 +3174,7 @@ function createDaemonApp(optionsOrGetState) {
2974
3174
  const agentCfg = s.configs.get(agentName);
2975
3175
  const workflow = agentCfg?.workflow ?? "global";
2976
3176
  const tag = agentCfg?.tag ?? "main";
2977
- const existingWf = findController(s, agentName)?.workflow ?? s.workflows.get(`${workflow}:${tag}`);
3177
+ const existingWf = findLoop(s, agentName)?.workflow ?? s.workflows.get(`${workflow}:${tag}`);
2978
3178
  const workflowAgents = getWorkflowAgentNames(workflow, tag);
2979
3179
  const allNames = [...new Set([
2980
3180
  ...workflowAgents,
@@ -3015,14 +3215,14 @@ function createDaemonApp(optionsOrGetState) {
3015
3215
  if (!s) return c.json({ error: "Not ready" }, 503);
3016
3216
  const body = await parseJsonBody(c);
3017
3217
  if (!body || typeof body !== "object") return c.json({ error: "Invalid JSON body" }, 400);
3018
- const { workflow, tag = "main", feedback, pollInterval } = body;
3218
+ const { workflow, tag = "main", feedback, pollInterval, params } = body;
3019
3219
  if (!workflow || !workflow.agents) return c.json({ error: "workflow (parsed YAML) required" }, 400);
3020
3220
  const workflowName = workflow.name || "global";
3021
3221
  const key = `${workflowName}:${tag}`;
3022
3222
  if (s.workflows.has(key)) return c.json({ error: `Workflow already running: ${key}` }, 409);
3023
3223
  try {
3024
- const { runWorkflowWithControllers } = await import("../runner-DV86expc.mjs");
3025
- const result = await runWorkflowWithControllers({
3224
+ const { runWorkflowWithLoops } = await import("../runner-DB-b57iZ.mjs");
3225
+ const result = await runWorkflowWithLoops({
3026
3226
  workflow,
3027
3227
  workflowName,
3028
3228
  tag,
@@ -3030,6 +3230,7 @@ function createDaemonApp(optionsOrGetState) {
3030
3230
  headless: true,
3031
3231
  feedback,
3032
3232
  pollInterval,
3233
+ params,
3033
3234
  log: () => {}
3034
3235
  });
3035
3236
  if (!result.success) return c.json({ error: result.error || "Workflow failed to start" }, 500);
@@ -3038,7 +3239,7 @@ function createDaemonApp(optionsOrGetState) {
3038
3239
  tag,
3039
3240
  key,
3040
3241
  agents: Object.keys(workflow.agents),
3041
- controllers: result.controllers,
3242
+ loops: result.loops,
3042
3243
  contextProvider: result.contextProvider,
3043
3244
  shutdown: result.shutdown,
3044
3245
  workflowPath: workflow.filePath,
@@ -3061,7 +3262,7 @@ function createDaemonApp(optionsOrGetState) {
3061
3262
  if (!s) return c.json({ error: "Not ready" }, 503);
3062
3263
  const workflows = [...s.workflows.values()].map((wf) => {
3063
3264
  const agentStates = {};
3064
- for (const [name, controller] of wf.controllers) agentStates[name] = controller.state;
3265
+ for (const [name, loop] of wf.loops) agentStates[name] = loop.state;
3065
3266
  return {
3066
3267
  name: wf.name,
3067
3268
  tag: wf.tag,
@@ -3654,28 +3855,49 @@ function buildDisplay(agent, workflow, tag) {
3654
3855
  //#endregion
3655
3856
  //#region src/cli/commands/workflow.ts
3656
3857
  function registerWorkflowCommands(program) {
3657
- program.command("run <file>").description("Execute workflow and exit when complete").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("--json", "Output results as JSON").addHelpText("after", `
3858
+ program.command("run <file>").description("Execute workflow and exit when complete").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("--json", "Output results as JSON").allowExcessArguments().addHelpText("after", `
3658
3859
  Examples:
3659
3860
  $ agent-worker run review.yaml # Run review:main
3660
3861
  $ agent-worker run review.yaml --tag pr-123 # Run review:pr-123
3661
3862
  $ agent-worker run review.yaml --json | jq .document # Machine-readable output
3863
+ $ agent-worker run review.yaml -- --target main -n 3 # With workflow params
3864
+
3865
+ Remote workflows (github:owner/repo@ref/path):
3866
+ $ agent-worker run github:acme/workflows/review.yml # Default branch
3867
+ $ agent-worker run github:acme/workflows@v1.0/review.yml # Pinned version
3868
+ $ agent-worker run github:acme/workflows#review # Shorthand
3869
+ $ agent-worker run github:acme/workflows#review -- --target main # With params
3662
3870
 
3663
- Note: Workflow name is inferred from YAML 'name' field or filename
3871
+ Note: Workflow name is inferred from YAML 'name' field or filename.
3872
+ Workflow-defined params (see 'params:' in YAML) are passed after '--'.
3873
+ Set GITHUB_TOKEN env var to access private repositories.
3664
3874
  `).action(async (file, options) => {
3665
- const { parseWorkflowFile, runWorkflowWithControllers } = await import("../workflow-DogkVjOs.mjs");
3875
+ const { parseWorkflowFile, parseWorkflowParams, formatParamHelp, runWorkflowWithLoops } = await import("../workflow-DQ6Eju4n.mjs");
3666
3876
  const tag = options.tag || DEFAULT_TAG;
3667
3877
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
3668
3878
  const workflowName = parsedWorkflow.name;
3669
- let controllers;
3879
+ let params;
3880
+ if (parsedWorkflow.params && parsedWorkflow.params.length > 0) {
3881
+ const extraArgs = getArgsAfterSeparator();
3882
+ try {
3883
+ params = parseWorkflowParams(parsedWorkflow.params, extraArgs);
3884
+ } catch (error) {
3885
+ const msg = error instanceof Error ? error.message : String(error);
3886
+ console.error(`Error: ${msg}`);
3887
+ console.error(formatParamHelp(parsedWorkflow.params));
3888
+ process.exit(1);
3889
+ }
3890
+ }
3891
+ let loops;
3670
3892
  let isCleaningUp = false;
3671
3893
  const cleanup = async () => {
3672
3894
  if (isCleaningUp) return;
3673
3895
  isCleaningUp = true;
3674
3896
  console.log("\nInterrupted, cleaning up...");
3675
- if (controllers) {
3676
- const { shutdownControllers } = await import("../workflow-DogkVjOs.mjs");
3897
+ if (loops) {
3898
+ const { shutdownLoops } = await import("../workflow-DQ6Eju4n.mjs");
3677
3899
  const { createSilentLogger } = await Promise.resolve().then(() => logger_exports);
3678
- await shutdownControllers(controllers, createSilentLogger());
3900
+ await shutdownLoops(loops, createSilentLogger());
3679
3901
  }
3680
3902
  process.exit(130);
3681
3903
  };
@@ -3683,19 +3905,19 @@ Note: Workflow name is inferred from YAML 'name' field or filename
3683
3905
  process.on("SIGTERM", cleanup);
3684
3906
  try {
3685
3907
  const log = options.json ? console.error : console.log;
3686
- const result = await runWorkflowWithControllers({
3908
+ const result = await runWorkflowWithLoops({
3687
3909
  workflow: parsedWorkflow,
3688
3910
  workflowName,
3689
3911
  tag,
3690
3912
  workflowPath: file,
3691
- instance: `${workflowName}:${tag}`,
3692
3913
  debug: options.debug,
3693
3914
  log,
3694
3915
  mode: "run",
3695
3916
  feedback: options.feedback,
3696
- prettyDisplay: !options.debug && !options.json
3917
+ prettyDisplay: !options.debug && !options.json,
3918
+ params
3697
3919
  });
3698
- controllers = result.controllers;
3920
+ loops = result.loops;
3699
3921
  process.off("SIGINT", cleanup);
3700
3922
  process.off("SIGTERM", cleanup);
3701
3923
  if (!result.success) {
@@ -3711,7 +3933,7 @@ Note: Workflow name is inferred from YAML 'name' field or filename
3711
3933
  feedback: result.feedback
3712
3934
  }, null, 2));
3713
3935
  else if (!options.debug) {
3714
- const { showWorkflowSummary } = await import("../display-pretty-BCJq5v9d.mjs");
3936
+ const { showWorkflowSummary } = await import("../display-pretty-Kyd40DEF.mjs");
3715
3937
  showWorkflowSummary({
3716
3938
  duration: result.duration,
3717
3939
  document: finalDoc,
@@ -3735,27 +3957,42 @@ Note: Workflow name is inferred from YAML 'name' field or filename
3735
3957
  process.exit(1);
3736
3958
  }
3737
3959
  });
3738
- 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", `
3960
+ 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").allowExcessArguments().addHelpText("after", `
3739
3961
  Examples:
3740
3962
  $ agent-worker start review.yaml # Start review:main (Ctrl+C to stop)
3741
3963
  $ agent-worker start review.yaml --tag pr-123 # Start review:pr-123
3964
+ $ agent-worker start review.yaml -- --target main # With workflow params
3742
3965
 
3743
3966
  Workflow runs inside the daemon. Use ls/stop to manage:
3744
3967
  $ agent-worker ls # List all agents
3745
3968
  $ agent-worker stop @review:pr-123 # Stop workflow
3746
3969
 
3747
- Note: Workflow name is inferred from YAML 'name' field or filename
3970
+ Note: Workflow name is inferred from YAML 'name' field or filename.
3971
+ Workflow-defined params (see 'params:' in YAML) are passed after '--'.
3748
3972
  `).action(async (file, options) => {
3749
- const { parseWorkflowFile } = await import("../workflow-DogkVjOs.mjs");
3973
+ const { parseWorkflowFile, parseWorkflowParams, formatParamHelp } = await import("../workflow-DQ6Eju4n.mjs");
3750
3974
  const { ensureDaemon } = await Promise.resolve().then(() => agent_exports);
3751
3975
  const tag = options.tag || DEFAULT_TAG;
3752
3976
  const parsedWorkflow = await parseWorkflowFile(file, { tag });
3753
3977
  const workflowName = parsedWorkflow.name;
3978
+ let params;
3979
+ if (parsedWorkflow.params && parsedWorkflow.params.length > 0) {
3980
+ const extraArgs = getArgsAfterSeparator();
3981
+ try {
3982
+ params = parseWorkflowParams(parsedWorkflow.params, extraArgs);
3983
+ } catch (error) {
3984
+ const msg = error instanceof Error ? error.message : String(error);
3985
+ console.error(`Error: ${msg}`);
3986
+ console.error(formatParamHelp(parsedWorkflow.params));
3987
+ process.exit(1);
3988
+ }
3989
+ }
3754
3990
  await ensureDaemon();
3755
3991
  const res = await startWorkflow({
3756
3992
  workflow: parsedWorkflow,
3757
3993
  tag,
3758
- feedback: options.feedback
3994
+ feedback: options.feedback,
3995
+ params
3759
3996
  });
3760
3997
  if (res.error) {
3761
3998
  console.error("Error:", res.error);
@@ -3791,6 +4028,15 @@ Note: Workflow name is inferred from YAML 'name' field or filename
3791
4028
  await new Promise(() => {});
3792
4029
  });
3793
4030
  }
4031
+ /**
4032
+ * Get arguments after the '--' separator.
4033
+ * Standard Unix convention: everything after '--' is passed through
4034
+ * without being interpreted as options by the CLI framework.
4035
+ */
4036
+ function getArgsAfterSeparator() {
4037
+ const idx = process.argv.indexOf("--");
4038
+ return idx === -1 ? [] : process.argv.slice(idx + 1);
4039
+ }
3794
4040
 
3795
4041
  //#endregion
3796
4042
  //#region src/cli/commands/send.ts
@@ -3918,10 +4164,12 @@ function registerInfoCommands(program) {
3918
4164
  console.log(` Gateway format: provider/model (e.g., ${gatewayExample})`);
3919
4165
  console.log(` Direct format: provider:model (e.g., ${directExample})`);
3920
4166
  console.log(` Custom endpoint: --provider anthropic --base-url <url> --api-key '$KEY'`);
4167
+ console.log(` Auto-detect: model: auto (scan env vars, pick best available)`);
3921
4168
  console.log(`\nDefault: ${defaultModel} (when no model specified)`);
4169
+ console.log(`Auto: AGENT_DEFAULT_MODELS="deepseek-chat, anthropic/claude-sonnet-4-5"`);
3922
4170
  });
3923
4171
  program.command("backends").description("Check available backends (SDK, CLI tools)").action(async () => {
3924
- const { listBackends } = await import("../backends-Cv0oM9Ru.mjs");
4172
+ const { listBackends } = await import("../backends-BYWmuyF9.mjs");
3925
4173
  const backends = await listBackends();
3926
4174
  console.log("Backend Status:\n");
3927
4175
  for (const backend of backends) {
@@ -3951,7 +4199,7 @@ Examples:
3951
4199
  $ agent-worker doc read @review:pr-123 # Read specific workflow:tag document
3952
4200
  `).action(async (targetInput) => {
3953
4201
  const dir = await resolveDir(targetInput);
3954
- const { createFileContextProvider } = await import("../context-CzqQeThq.mjs");
4202
+ const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
3955
4203
  const content = await createFileContextProvider(dir, []).readDocument();
3956
4204
  console.log(content || "(empty document)");
3957
4205
  });
@@ -3969,7 +4217,7 @@ Examples:
3969
4217
  process.exit(1);
3970
4218
  }
3971
4219
  const dir = await resolveDir(targetInput);
3972
- const { createFileContextProvider } = await import("../context-CzqQeThq.mjs");
4220
+ const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
3973
4221
  await createFileContextProvider(dir, []).writeDocument(content);
3974
4222
  console.log("Document written");
3975
4223
  });
@@ -3987,7 +4235,7 @@ Examples:
3987
4235
  process.exit(1);
3988
4236
  }
3989
4237
  const dir = await resolveDir(targetInput);
3990
- const { createFileContextProvider } = await import("../context-CzqQeThq.mjs");
4238
+ const { createFileContextProvider } = await import("../context-CdcZpO-0.mjs");
3991
4239
  await createFileContextProvider(dir, []).appendDocument(content);
3992
4240
  console.log("Content appended");
3993
4241
  });
@@ -4001,18 +4249,25 @@ async function resolveDir(targetInput) {
4001
4249
 
4002
4250
  //#endregion
4003
4251
  //#region package.json
4004
- var version = "0.14.0";
4252
+ var version = "0.15.0";
4005
4253
 
4006
4254
  //#endregion
4007
4255
  //#region src/cli/index.ts
4008
4256
  globalThis.AI_SDK_LOG_WARNINGS = false;
4009
4257
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
4010
- process.stderr.write = function(chunk, ...rest) {
4011
- if (process.argv.includes("--debug") || process.argv.includes("-d")) {
4012
- const message = typeof chunk === "string" ? chunk : chunk.toString();
4013
- return originalStderrWrite.call(process.stderr, message, ...rest);
4014
- }
4015
- return true;
4258
+ const isDebugMode = process.argv.includes("--debug") || process.argv.includes("-d");
4259
+ const SDK_NOISE_PATTERNS = [
4260
+ "specificationVersion",
4261
+ "AI_SDK",
4262
+ "ai-sdk",
4263
+ "deprecated",
4264
+ "ExperimentalWarning"
4265
+ ];
4266
+ process.stderr.write = function(chunk, encodingOrCb, cb) {
4267
+ const message = typeof chunk === "string" ? chunk : chunk.toString();
4268
+ if (isDebugMode) return originalStderrWrite(message, encodingOrCb, cb);
4269
+ if (SDK_NOISE_PATTERNS.some((pattern) => message.includes(pattern))) return true;
4270
+ return originalStderrWrite(message, encodingOrCb, cb);
4016
4271
  };
4017
4272
  const program = new Command();
4018
4273
  program.name("agent-worker").description("CLI for creating and managing AI agents").version(version);
@@ -4024,4 +4279,4 @@ registerDocCommands(program);
4024
4279
  program.parse();
4025
4280
 
4026
4281
  //#endregion
4027
- export { extractMentions as A, EventLog as B, CONTEXT_DEFAULTS as C, RESOURCE_SCHEME as D, RESOURCE_PREFIX as E, formatProposalList as F, createLogTool as I, formatInbox$1 as L, shouldUseResource as M, createContextMCPServer as N, calculatePriority as O, formatProposal as P, formatToolParams as R, ContextProviderImpl as S, MESSAGE_LENGTH_THRESHOLD as T, createFileContextProvider as _, getBackendByType as a, FileStorage as b, createAgentController as c, generateWorkflowMCPConfig as d, buildAgentPrompt as f, FileContextProvider as g, runWithHttp as h, createSilentLogger as i, generateResourceId as j, createResourceRef as k, runSdkAgent as l, CONTROLLER_DEFAULTS as m, createWiredController as n, getBackendForModel as o, formatInbox as p, createChannelLogger as r, checkWorkflowIdle as s, createMinimalRuntime as t, runMockAgent as u, getDefaultContextDir as v, MENTION_PATTERN as w, MemoryStorage as x, resolveContextDir as y, getAgentId as z };
4282
+ export { MENTION_PATTERN as A, formatProposal as B, DefaultDocumentStore as C, MemoryStorage as D, FileStorage as E, createResourceRef as F, getAgentId as G, createLogTool as H, extractMentions as I, EventLog as K, generateResourceId as L, RESOURCE_PREFIX$1 as M, RESOURCE_SCHEME as N, ContextProviderImpl as O, calculatePriority as P, shouldUseResource as R, DefaultResourceStore as S, DefaultChannelStore as T, formatInbox$1 as U, formatProposalList as V, formatToolParams as W, FileContextProvider as _, getBackendByType as a, resolveContextDir as b, createAgentLoop as c, generateWorkflowMCPConfig as d, writeBackendMcpConfig as f, runWithHttp as g, LOOP_DEFAULTS as h, createSilentLogger as i, MESSAGE_LENGTH_THRESHOLD as j, CONTEXT_DEFAULTS as k, runSdkAgent as l, formatInbox as m, createWiredLoop as n, getBackendForModel as o, buildAgentPrompt as p, createChannelLogger as r, checkWorkflowIdle as s, createMinimalRuntime as t, runMockAgent as u, createFileContextProvider as v, DefaultInboxStore as w, DefaultStatusStore as x, getDefaultContextDir as y, createContextMCPServer as z };