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.
- package/README.md +6 -3
- package/dist/{backends-Cv0oM9Ru.mjs → backends-BYWmuyF9.mjs} +1 -1
- package/dist/{backends-C6WBIn9H.mjs → backends-C7pQwuAx.mjs} +265 -227
- package/dist/cli/index.mjs +648 -393
- package/dist/context-CdcZpO-0.mjs +4 -0
- package/dist/create-tool-gcUuI1FD.mjs +32 -0
- package/dist/index.d.mts +4 -33
- package/dist/index.mjs +21 -20
- package/dist/{memory-provider-0nuDxzYQ.mjs → memory-provider-ZLOKyCxA.mjs} +8 -3
- package/dist/{runner-DV86expc.mjs → runner-DB-b57iZ.mjs} +53 -46
- package/dist/workflow-DQ6Eju4n.mjs +664 -0
- package/package.json +3 -3
- package/dist/context-CzqQeThq.mjs +0 -4
- package/dist/workflow-DogkVjOs.mjs +0 -301
- /package/dist/{display-pretty-BCJq5v9d.mjs → display-pretty-Kyd40DEF.mjs} +0 -0
package/dist/cli/index.mjs
CHANGED
|
@@ -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-
|
|
3
|
-
import {
|
|
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 (
|
|
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
|
-
*
|
|
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
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
return this.storage;
|
|
919
|
+
appendChannel(from, content, options) {
|
|
920
|
+
return this.channel.append(from, content, options);
|
|
934
921
|
}
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
947
|
-
|
|
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.
|
|
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.
|
|
1002
|
-
await this.
|
|
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.
|
|
943
|
+
return this.channel.append(from, shortMessage, options);
|
|
1006
944
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
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
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
1044
|
-
|
|
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
|
-
|
|
1057
|
-
return
|
|
957
|
+
writeDocument(content, file) {
|
|
958
|
+
return this.documents.write(content, file);
|
|
1058
959
|
}
|
|
1059
|
-
|
|
1060
|
-
return
|
|
960
|
+
appendDocument(content, file) {
|
|
961
|
+
return this.documents.append(content, file);
|
|
1061
962
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
963
|
+
listDocuments() {
|
|
964
|
+
return this.documents.list();
|
|
1064
965
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
966
|
+
createDocument(file, content) {
|
|
967
|
+
return this.documents.create(file, content);
|
|
1067
968
|
}
|
|
1068
|
-
|
|
1069
|
-
return
|
|
969
|
+
createResource(content, createdBy, type) {
|
|
970
|
+
return this.resources.create(content, createdBy, type);
|
|
1070
971
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
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
|
-
|
|
1077
|
-
|
|
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
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
1100
|
-
|
|
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
|
-
|
|
985
|
+
await this.inbox.markRunStart();
|
|
1137
986
|
}
|
|
1138
987
|
async destroy() {
|
|
1139
|
-
await this.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
* - ${{
|
|
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 ??
|
|
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)
|
|
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
|
|
1416
|
-
const wf = workflow ??
|
|
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/
|
|
1573
|
-
/** Default
|
|
1574
|
-
const
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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] =
|
|
1987
|
+
aiTools[toolName] = createTool({
|
|
1808
1988
|
description: mcpTool.description || toolName,
|
|
1809
|
-
|
|
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
|
|
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/
|
|
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] =
|
|
2151
|
+
aiTools[toolName] = createTool({
|
|
1972
2152
|
description: mcpTool.description || toolName,
|
|
1973
|
-
|
|
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
|
|
2168
|
+
return createTool({
|
|
1989
2169
|
description: "Execute a shell command and return stdout/stderr.",
|
|
1990
|
-
|
|
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 (
|
|
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
|
|
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/
|
|
2078
|
-
/** Check if
|
|
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
|
|
2264
|
+
* Create an agent loop
|
|
2084
2265
|
*
|
|
2085
|
-
* The
|
|
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
|
|
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 ??
|
|
2277
|
+
const pollInterval = config.pollInterval ?? LOOP_DEFAULTS.pollInterval;
|
|
2097
2278
|
const retryConfig = {
|
|
2098
|
-
maxAttempts: config.retry?.maxAttempts ??
|
|
2099
|
-
backoffMs: config.retry?.backoffMs ??
|
|
2100
|
-
backoffMultiplier: config.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
|
|
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:
|
|
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(`
|
|
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:
|
|
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
|
|
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
|
-
|
|
2328
|
-
|
|
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(
|
|
2357
|
-
if (![...
|
|
2358
|
-
for (const [name] of
|
|
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 [...
|
|
2539
|
+
return [...loops.values()].every((c) => c.state === "idle");
|
|
2361
2540
|
}
|
|
2362
2541
|
|
|
2363
2542
|
//#endregion
|
|
2364
|
-
//#region src/workflow/
|
|
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
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
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
|
|
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.
|
|
2492
|
-
* 3. Caller manages lifecycle — start/stop
|
|
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
|
|
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
|
|
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
|
|
2745
|
+
* 4. Create the AgentLoop with all wiring
|
|
2569
2746
|
*
|
|
2570
|
-
* Extracted from
|
|
2571
|
-
* daemon.ts can create
|
|
2747
|
+
* Extracted from runWorkflowWithLoops() so both runner.ts and
|
|
2748
|
+
* daemon.ts can create loops with the same quality.
|
|
2572
2749
|
*/
|
|
2573
|
-
function
|
|
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:
|
|
2586
|
-
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 (
|
|
2592
|
-
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
|
-
|
|
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 →
|
|
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 +
|
|
2627
|
-
*
|
|
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
|
|
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
|
|
2688
|
-
* Returns the
|
|
2881
|
+
* Find an agent's loop across all workflows.
|
|
2882
|
+
* Returns the loop if the agent exists in any workflow.
|
|
2689
2883
|
*/
|
|
2690
|
-
function
|
|
2884
|
+
function findLoop(s, agentName) {
|
|
2691
2885
|
for (const wf of s.workflows.values()) {
|
|
2692
|
-
const
|
|
2693
|
-
if (
|
|
2694
|
-
|
|
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 +
|
|
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
|
|
2899
|
+
* POST /run or /serve (needs a loop to execute).
|
|
2706
2900
|
*/
|
|
2707
|
-
async function
|
|
2708
|
-
const existing =
|
|
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
|
|
2913
|
+
let loop;
|
|
2720
2914
|
try {
|
|
2721
|
-
({
|
|
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
|
-
|
|
2929
|
+
loops: new Map([[agentName, loop]]),
|
|
2736
2930
|
contextProvider: runtime.contextProvider,
|
|
2737
2931
|
shutdown: async () => {
|
|
2738
2932
|
try {
|
|
2739
|
-
await
|
|
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
|
-
|
|
2942
|
+
loop,
|
|
2749
2943
|
workflow: handle
|
|
2750
2944
|
};
|
|
2751
2945
|
}
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
|
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:
|
|
3002
|
+
state: found?.loop.state
|
|
2803
3003
|
};
|
|
2804
3004
|
});
|
|
2805
3005
|
const workflowAgents = [...s.workflows.values()].flatMap((wf) => wf.agents.map((agentName) => {
|
|
2806
|
-
const
|
|
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:
|
|
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
|
|
2888
|
-
const
|
|
2889
|
-
if (
|
|
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
|
-
|
|
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 (!
|
|
2897
|
-
const
|
|
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
|
|
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
|
|
2934
|
-
const
|
|
2935
|
-
if (
|
|
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
|
-
|
|
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 (!
|
|
3142
|
+
if (!loop) return c.json({ error: `Agent not found: ${agentName}` }, 404);
|
|
2943
3143
|
try {
|
|
2944
|
-
const result = await
|
|
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 =
|
|
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 {
|
|
3025
|
-
const result = await
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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 (
|
|
3676
|
-
const {
|
|
3897
|
+
if (loops) {
|
|
3898
|
+
const { shutdownLoops } = await import("../workflow-DQ6Eju4n.mjs");
|
|
3677
3899
|
const { createSilentLogger } = await Promise.resolve().then(() => logger_exports);
|
|
3678
|
-
await
|
|
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
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
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 {
|
|
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 };
|