@fieldwangai/agentflow 0.1.33 → 0.1.35
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/bin/lib/agent-runners.mjs +17 -0
- package/bin/lib/composer-agent.mjs +2 -0
- package/bin/lib/composer-skill-router.mjs +23 -4
- package/bin/lib/git-worktree.mjs +15 -0
- package/bin/lib/locales/en.json +3 -7
- package/bin/lib/locales/zh.json +2 -6
- package/bin/lib/paths.mjs +0 -1
- package/bin/lib/scheduler.mjs +8 -3
- package/bin/lib/skill-registry.mjs +46 -2
- package/bin/lib/ui-server.mjs +896 -22
- package/bin/pipeline/build-node-prompt.mjs +27 -1
- package/bin/pipeline/pre-process-node.mjs +70 -34
- package/builtin/nodes/agent_subAgent.md +2 -0
- package/builtin/nodes/control_agent_toBool.md +4 -0
- package/builtin/nodes/control_cancelled.md +8 -4
- package/builtin/nodes/control_cd_workspace.md +2 -0
- package/builtin/nodes/control_delay.md +9 -0
- package/builtin/nodes/control_toBool.md +6 -2
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +9 -0
- package/builtin/nodes/provide_bool.md +2 -0
- package/builtin/nodes/provide_file.md +2 -0
- package/builtin/nodes/provide_str.md +2 -0
- package/builtin/nodes/tool_get_env.md +4 -0
- package/builtin/nodes/tool_git_checkout.md +6 -1
- package/builtin/nodes/tool_git_worktree_load.md +7 -2
- package/builtin/nodes/tool_git_worktree_unload.md +5 -5
- package/builtin/nodes/tool_load_key.md +4 -0
- package/builtin/nodes/tool_nodejs.md +4 -0
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +4 -0
- package/builtin/nodes/tool_user_ask.md +3 -1
- package/builtin/nodes/tool_user_check.md +3 -1
- package/builtin/web-ui/dist/assets/index-BzmhleR9.css +1 -0
- package/builtin/web-ui/dist/assets/index-DEeZI5V6.js +214 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/nodes/control_deadline.md +0 -32
- package/builtin/web-ui/dist/assets/index-BWAb27N0.js +0 -198
- package/builtin/web-ui/dist/assets/index-DgfSfcjH.css +0 -1
|
@@ -711,6 +711,14 @@ function truncateComposerLine(s) {
|
|
|
711
711
|
return t.slice(0, COMPOSER_STATUS_MAX - 1) + "…";
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
+
const RAW_TRACE_MAX_CHARS = 4096;
|
|
715
|
+
|
|
716
|
+
function rawTraceText(value) {
|
|
717
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
718
|
+
if (!text) return "";
|
|
719
|
+
return text.length > RAW_TRACE_MAX_CHARS ? text.slice(0, RAW_TRACE_MAX_CHARS) + "\n...[truncated]" : text;
|
|
720
|
+
}
|
|
721
|
+
|
|
714
722
|
function normalizeStreamTextChunk(t) {
|
|
715
723
|
if (!t || typeof t !== "string") return "";
|
|
716
724
|
return t.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
|
@@ -728,6 +736,7 @@ function extractCursorStreamNlText(event) {
|
|
|
728
736
|
}
|
|
729
737
|
if (typeof event.text === "string" && event.text.trim()) return normalizeStreamTextChunk(event.text);
|
|
730
738
|
if (typeof event.thinking === "string" && event.thinking.trim()) return normalizeStreamTextChunk(event.thinking);
|
|
739
|
+
if (typeof event.delta === "string" && event.delta.trim()) return normalizeStreamTextChunk(event.delta);
|
|
731
740
|
return "";
|
|
732
741
|
}
|
|
733
742
|
|
|
@@ -864,6 +873,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
864
873
|
for (const line of lines) {
|
|
865
874
|
try {
|
|
866
875
|
const event = JSON.parse(line);
|
|
876
|
+
emit({ type: "raw", source: "cursor", stream: "stdout", eventType: event?.type || "unknown", text: rawTraceText(event) });
|
|
867
877
|
if (event.type === "assistant" && event.message?.content) {
|
|
868
878
|
const text = extractCursorStreamNlText(event);
|
|
869
879
|
if (text) {
|
|
@@ -903,6 +913,7 @@ export function runCursorAgentWithPrompt(cliWorkspace, promptText, options = {})
|
|
|
903
913
|
emit({ type: "status", line: `${t("runner.event_label")}: ${event.type ?? "unknown"}` });
|
|
904
914
|
}
|
|
905
915
|
} catch (_) {
|
|
916
|
+
emit({ type: "raw", source: "cursor", stream: "stdout", eventType: "line", text: rawTraceText(line) });
|
|
906
917
|
if (line.includes('"type":"tool_call"') || line.includes('"type": "tool_call"')) {
|
|
907
918
|
let subtype = "?";
|
|
908
919
|
try {
|
|
@@ -1017,6 +1028,7 @@ export function runOpenCodeAgentWithPrompt(cliWorkspace, promptText, options = {
|
|
|
1017
1028
|
const line = outBuf.slice(0, idx);
|
|
1018
1029
|
outBuf = outBuf.slice(idx + 1);
|
|
1019
1030
|
if (line) {
|
|
1031
|
+
emit({ type: "raw", source: "opencode", stream: "stdout", eventType: "line", text: rawTraceText(line) });
|
|
1020
1032
|
tryEmitOpenCodeLineAsNatural(line, emit);
|
|
1021
1033
|
emit({ type: "status", line: `[stdout] ${truncateComposerLine(line)}` });
|
|
1022
1034
|
}
|
|
@@ -1032,6 +1044,7 @@ export function runOpenCodeAgentWithPrompt(cliWorkspace, promptText, options = {
|
|
|
1032
1044
|
const line = errBuf.slice(0, idx);
|
|
1033
1045
|
errBuf = errBuf.slice(idx + 1);
|
|
1034
1046
|
if (line) {
|
|
1047
|
+
emit({ type: "raw", source: "opencode", stream: "stderr", eventType: "line", text: rawTraceText(line) });
|
|
1035
1048
|
tryEmitOpenCodeLineAsNatural(line, emit);
|
|
1036
1049
|
emit({ type: "status", line: `[stderr] ${truncateComposerLine(line)}` });
|
|
1037
1050
|
}
|
|
@@ -1051,10 +1064,12 @@ export function runOpenCodeAgentWithPrompt(cliWorkspace, promptText, options = {
|
|
|
1051
1064
|
child.stderr.removeAllListeners();
|
|
1052
1065
|
child.removeAllListeners();
|
|
1053
1066
|
if (outBuf.trim()) {
|
|
1067
|
+
emit({ type: "raw", source: "opencode", stream: "stdout", eventType: "tail", text: rawTraceText(outBuf.trim()) });
|
|
1054
1068
|
tryEmitOpenCodeLineAsNatural(outBuf.trim(), emit);
|
|
1055
1069
|
emit({ type: "status", line: truncateComposerLine(outBuf.trim()) });
|
|
1056
1070
|
}
|
|
1057
1071
|
if (errBuf.trim()) {
|
|
1072
|
+
emit({ type: "raw", source: "opencode", stream: "stderr", eventType: "tail", text: rawTraceText(errBuf.trim()) });
|
|
1058
1073
|
tryEmitOpenCodeLineAsNatural(errBuf.trim(), emit);
|
|
1059
1074
|
emit({ type: "status", line: `[opencode_stderr] ${truncateComposerLine(errBuf.trim())}` });
|
|
1060
1075
|
}
|
|
@@ -1151,6 +1166,7 @@ export function runClaudeCodeAgentWithPrompt(cliWorkspace, promptText, options =
|
|
|
1151
1166
|
for (const line of lines) {
|
|
1152
1167
|
try {
|
|
1153
1168
|
const event = JSON.parse(line);
|
|
1169
|
+
emit({ type: "raw", source: "claude-code", stream: "stdout", eventType: event?.type || "unknown", text: rawTraceText(event) });
|
|
1154
1170
|
if (event.type === "assistant" && event.message && Array.isArray(event.message.content)) {
|
|
1155
1171
|
for (const block of event.message.content) {
|
|
1156
1172
|
if (!block || typeof block !== "object") continue;
|
|
@@ -1196,6 +1212,7 @@ export function runClaudeCodeAgentWithPrompt(cliWorkspace, promptText, options =
|
|
|
1196
1212
|
emit({ type: "status", line: `${t("runner.event_label")}: ${event.type ?? "unknown"}` });
|
|
1197
1213
|
}
|
|
1198
1214
|
} catch (_) {
|
|
1215
|
+
emit({ type: "raw", source: "claude-code", stream: "stdout", eventType: "line", text: rawTraceText(line) });
|
|
1199
1216
|
emit({ type: "status", line: truncateComposerLine(line) });
|
|
1200
1217
|
}
|
|
1201
1218
|
}
|
|
@@ -106,6 +106,7 @@ export function buildScriptContentBlockForInstances(flowYamlAbs, instanceIds) {
|
|
|
106
106
|
* @param {string} [opts.modelKey]
|
|
107
107
|
* @param {boolean} [opts.force]
|
|
108
108
|
* @param {(ev: object) => void} [opts.onStreamEvent]
|
|
109
|
+
* @param {(subtype: string, toolName: string) => void} [opts.onToolCall]
|
|
109
110
|
* @returns {{ child: import('child_process').ChildProcess, finished: Promise<void> }}
|
|
110
111
|
*/
|
|
111
112
|
export function startComposerAgent(opts) {
|
|
@@ -123,6 +124,7 @@ export function startComposerAgent(opts) {
|
|
|
123
124
|
|
|
124
125
|
const common = {
|
|
125
126
|
onStreamEvent: opts.onStreamEvent,
|
|
127
|
+
onToolCall: opts.onToolCall,
|
|
126
128
|
force: Boolean(opts.force),
|
|
127
129
|
env,
|
|
128
130
|
};
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import path from "path";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
listSkills as registryListSkills,
|
|
18
|
+
listUniqueSkills as registryListUniqueSkills,
|
|
19
|
+
readSkillDetail as registryReadSkillDetail,
|
|
20
|
+
} from "./skill-registry.mjs";
|
|
17
21
|
|
|
18
22
|
// ─── 意图模式定义 ─────────────────────────────────────────────────────────
|
|
19
23
|
|
|
@@ -126,7 +130,7 @@ function readFileCached(absPath) {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
export function listComposerSkills(packageRoot, workspaceRoot) {
|
|
129
|
-
return
|
|
133
|
+
return registryListUniqueSkills(packageRoot, workspaceRoot).map(({ body, content, ...skill }) => skill);
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
export function readComposerSkillDetail(packageRoot, workspaceRoot, keyOrName) {
|
|
@@ -140,9 +144,24 @@ export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot)
|
|
|
140
144
|
const wanted = new Set(skillKeys.map((x) => String(x || "").trim()).filter(Boolean));
|
|
141
145
|
if (wanted.size === 0) return { skills: [], references: [], skillsHint: "", hasContext: false };
|
|
142
146
|
|
|
143
|
-
const
|
|
144
|
-
|
|
147
|
+
const exactByKey = new Map(registryListSkills(packageRoot, workspaceRoot).map((item) => [item.key, item]));
|
|
148
|
+
const candidateItems = [];
|
|
149
|
+
const seenKeys = new Set();
|
|
150
|
+
for (const item of registryListUniqueSkills(packageRoot, workspaceRoot)) {
|
|
145
151
|
if (!wanted.has(item.key) && !wanted.has(item.name)) continue;
|
|
152
|
+
candidateItems.push(item);
|
|
153
|
+
seenKeys.add(item.key);
|
|
154
|
+
}
|
|
155
|
+
for (const key of wanted) {
|
|
156
|
+
const exact = exactByKey.get(key);
|
|
157
|
+
if (exact && !seenKeys.has(exact.key)) {
|
|
158
|
+
candidateItems.push(exact);
|
|
159
|
+
seenKeys.add(exact.key);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const skills = [];
|
|
164
|
+
for (const item of candidateItems) {
|
|
146
165
|
skills.push({
|
|
147
166
|
id: item.name,
|
|
148
167
|
content: item.body,
|
package/bin/lib/git-worktree.mjs
CHANGED
|
@@ -112,6 +112,21 @@ export function resolveGitRepoRoot(repoPath) {
|
|
|
112
112
|
return path.resolve(gitOrThrow(["rev-parse", "--show-toplevel"], abs, "git rev-parse"));
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export function inferGitRepoRootFromWorktree(worktreePath) {
|
|
116
|
+
const raw = String(worktreePath || "").trim();
|
|
117
|
+
if (!raw) throw new Error("worktreePath is required");
|
|
118
|
+
const abs = path.resolve(raw);
|
|
119
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
|
|
120
|
+
throw new Error(`worktreePath directory not found: ${abs}`);
|
|
121
|
+
}
|
|
122
|
+
const commonDir = gitOrThrow(["rev-parse", "--git-common-dir"], abs, "git rev-parse common dir");
|
|
123
|
+
const commonAbs = path.resolve(abs, commonDir);
|
|
124
|
+
if (path.basename(commonAbs) === ".git") {
|
|
125
|
+
return path.dirname(commonAbs);
|
|
126
|
+
}
|
|
127
|
+
return resolveGitRepoRoot(abs);
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
export function currentGitBranch(repoRoot) {
|
|
116
131
|
const branch = gitOrThrow(["rev-parse", "--abbrev-ref", "HEAD"], repoRoot, "git rev-parse branch");
|
|
117
132
|
return branch === "HEAD" ? "" : branch;
|
package/bin/lib/locales/en.json
CHANGED
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"description": "Continues to next when any upstream input is ready"
|
|
239
239
|
},
|
|
240
240
|
"control_toBool": {
|
|
241
|
-
"displayName": "
|
|
241
|
+
"displayName": "Code ToBool",
|
|
242
242
|
"description": "Execute script to produce true/false prediction. Like tool_nodejs but enforces bool output. Extensible inputs."
|
|
243
243
|
},
|
|
244
244
|
"control_delay": {
|
|
@@ -249,13 +249,9 @@
|
|
|
249
249
|
"displayName": "Wait Until",
|
|
250
250
|
"description": "Persistently wait until an absolute time, then scheduler resumes this run."
|
|
251
251
|
},
|
|
252
|
-
"control_deadline": {
|
|
253
|
-
"displayName": "Deadline",
|
|
254
|
-
"description": "Check whether the deadline has expired and output expired as bool."
|
|
255
|
-
},
|
|
256
252
|
"control_cancelled": {
|
|
257
|
-
"displayName": "
|
|
258
|
-
"description": "Check whether the current run
|
|
253
|
+
"displayName": "Cancel Check",
|
|
254
|
+
"description": "Check whether the current wait/run has been cancelled and output cancelled as bool."
|
|
259
255
|
},
|
|
260
256
|
"control_interval_loop": {
|
|
261
257
|
"displayName": "Interval Loop",
|
package/bin/lib/locales/zh.json
CHANGED
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"description": "当任一上游输入就绪时,沿 next 继续执行"
|
|
239
239
|
},
|
|
240
240
|
"control_toBool": {
|
|
241
|
-
"displayName": "
|
|
241
|
+
"displayName": "代码转布尔",
|
|
242
242
|
"description": "执行 script 脚本输出 true/false 到 prediction,类似 tool_nodejs 但强制 bool 输出,可扩展输入"
|
|
243
243
|
},
|
|
244
244
|
"control_delay": {
|
|
@@ -249,13 +249,9 @@
|
|
|
249
249
|
"displayName": "等待到时间",
|
|
250
250
|
"description": "持久化等待到指定时间,到点后由 scheduler 唤醒当前 run 继续执行"
|
|
251
251
|
},
|
|
252
|
-
"control_deadline": {
|
|
253
|
-
"displayName": "截止判断",
|
|
254
|
-
"description": "判断当前时间是否已超过截止时间,输出 expired 布尔值"
|
|
255
|
-
},
|
|
256
252
|
"control_cancelled": {
|
|
257
253
|
"displayName": "取消判断",
|
|
258
|
-
"description": "判断当前 run
|
|
254
|
+
"description": "判断当前 wait/run 是否已被取消,输出 cancelled 布尔值"
|
|
259
255
|
},
|
|
260
256
|
"control_interval_loop": {
|
|
261
257
|
"displayName": "间隔循环",
|
package/bin/lib/paths.mjs
CHANGED
package/bin/lib/scheduler.mjs
CHANGED
|
@@ -136,7 +136,7 @@ function readJsonObject(filePath) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
function waitStateKey(state) {
|
|
139
|
-
return String((state && (state.id || state.instanceId)) || "");
|
|
139
|
+
return String((state && (state.waitId || state.id || state.instanceId)) || "");
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
function persistedWaitState(state) {
|
|
@@ -181,17 +181,22 @@ function writeWaitState(waitState, patch = {}) {
|
|
|
181
181
|
...(patch && typeof patch === "object" ? patch : {}),
|
|
182
182
|
updatedAt: new Date().toISOString(),
|
|
183
183
|
};
|
|
184
|
+
const key = waitStateKey(next);
|
|
185
|
+
if (key) {
|
|
186
|
+
next.waitId = String(next.waitId || key);
|
|
187
|
+
next.id = String(next.id || next.waitId);
|
|
188
|
+
}
|
|
184
189
|
const runDir = next.runDir || (next.waitPath ? path.dirname(next.waitPath) : "");
|
|
185
190
|
if (!runDir) return;
|
|
186
191
|
|
|
187
192
|
const legacyPath = next.legacyPath || next.waitPath || path.join(runDir, WAIT_STATE_FILENAME);
|
|
188
193
|
const registryPath = next.registryPath || path.join(runDir, WAIT_STATES_FILENAME);
|
|
189
194
|
const persisted = persistedWaitState(next);
|
|
190
|
-
const
|
|
195
|
+
const persistedKey = waitStateKey(persisted);
|
|
191
196
|
|
|
192
197
|
const registry = readJsonObject(registryPath);
|
|
193
198
|
if (registry && Array.isArray(registry.waits)) {
|
|
194
|
-
const waits = registry.waits.filter((w) => waitStateKey(w) !==
|
|
199
|
+
const waits = registry.waits.filter((w) => waitStateKey(w) !== persistedKey);
|
|
195
200
|
waits.push(persisted);
|
|
196
201
|
fs.writeFileSync(registryPath, JSON.stringify({ ...registry, updatedAt: new Date().toISOString(), waits }, null, 2) + "\n", "utf-8");
|
|
197
202
|
}
|
|
@@ -5,6 +5,15 @@ import yaml from "js-yaml";
|
|
|
5
5
|
|
|
6
6
|
const fileCache = new Map();
|
|
7
7
|
const CACHE_TTL_MS = 60_000;
|
|
8
|
+
const SOURCE_PRIORITY = new Map([
|
|
9
|
+
["workspace-agents", 100],
|
|
10
|
+
["workspace-codex", 95],
|
|
11
|
+
["workspace-cursor", 90],
|
|
12
|
+
["builtin", 80],
|
|
13
|
+
["global-agents", 70],
|
|
14
|
+
["global-codex", 65],
|
|
15
|
+
["global-cursor", 60],
|
|
16
|
+
]);
|
|
8
17
|
|
|
9
18
|
function readFileCached(absPath) {
|
|
10
19
|
const now = Date.now();
|
|
@@ -62,7 +71,15 @@ export function listSkillFiles(dir) {
|
|
|
62
71
|
try {
|
|
63
72
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return [];
|
|
64
73
|
return fs.readdirSync(dir, { withFileTypes: true })
|
|
65
|
-
.filter((entry) =>
|
|
74
|
+
.filter((entry) => {
|
|
75
|
+
if (entry.isDirectory()) return true;
|
|
76
|
+
if (!entry.isSymbolicLink()) return false;
|
|
77
|
+
try {
|
|
78
|
+
return fs.statSync(path.join(dir, entry.name)).isDirectory();
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
})
|
|
66
83
|
.map((entry) => path.join(dir, entry.name, "SKILL.md"))
|
|
67
84
|
.filter((skillPath) => fs.existsSync(skillPath));
|
|
68
85
|
} catch {
|
|
@@ -132,14 +149,41 @@ export function listSkillsFromSources(sources, opts = {}) {
|
|
|
132
149
|
return { skills, warnings };
|
|
133
150
|
}
|
|
134
151
|
|
|
152
|
+
function skillPriority(skill) {
|
|
153
|
+
return SOURCE_PRIORITY.get(String(skill?.source || "")) ?? 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function dedupeSkillsByName(skills) {
|
|
157
|
+
const byName = new Map();
|
|
158
|
+
for (const skill of Array.isArray(skills) ? skills : []) {
|
|
159
|
+
const name = String(skill?.name || skill?.id || "").trim();
|
|
160
|
+
if (!name) continue;
|
|
161
|
+
const existing = byName.get(name);
|
|
162
|
+
if (!existing || skillPriority(skill) > skillPriority(existing)) {
|
|
163
|
+
byName.set(name, skill);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return Array.from(byName.values()).sort((a, b) => {
|
|
167
|
+
const bySource = String(a.sourceLabel || "").localeCompare(String(b.sourceLabel || ""));
|
|
168
|
+
if (bySource !== 0) return bySource;
|
|
169
|
+
return String(a.name || "").localeCompare(String(b.name || ""));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
135
173
|
export function listSkills(packageRoot, workspaceRoot, opts = {}) {
|
|
136
174
|
return listSkillsFromSources(defaultSkillSources(packageRoot, workspaceRoot), opts).skills;
|
|
137
175
|
}
|
|
138
176
|
|
|
177
|
+
export function listUniqueSkills(packageRoot, workspaceRoot, opts = {}) {
|
|
178
|
+
return dedupeSkillsByName(listSkills(packageRoot, workspaceRoot, opts));
|
|
179
|
+
}
|
|
180
|
+
|
|
139
181
|
export function readSkillDetail(packageRoot, workspaceRoot, keyOrName) {
|
|
140
182
|
const wanted = String(keyOrName || "").trim();
|
|
141
183
|
if (!wanted) return null;
|
|
142
|
-
const
|
|
184
|
+
const all = listSkills(packageRoot, workspaceRoot);
|
|
185
|
+
const item = all.find((skill) => skill.key === wanted)
|
|
186
|
+
|| dedupeSkillsByName(all).find((skill) => skill.name === wanted);
|
|
143
187
|
if (!item) return null;
|
|
144
188
|
return item;
|
|
145
189
|
}
|