@fieldwangai/agentflow 0.1.82 → 0.1.84
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/git-worktree.mjs
CHANGED
|
@@ -225,6 +225,7 @@ export function loadGitWorktree({ repoPath, branch = "", worktreePath = "", pipe
|
|
|
225
225
|
const repoRoot = resolveGitRepoRoot(repoPath);
|
|
226
226
|
const wantedBranch = String(branch || "").trim();
|
|
227
227
|
const target = path.resolve(worktreePath || defaultWorktreePath(pipelineWorkspace || repoRoot, repoRoot, wantedBranch));
|
|
228
|
+
let created = false;
|
|
228
229
|
|
|
229
230
|
if (wantedBranch && !branchExists(repoRoot, wantedBranch)) {
|
|
230
231
|
throw new Error(`branch does not exist in repoPath: ${wantedBranch}`);
|
|
@@ -262,6 +263,7 @@ export function loadGitWorktree({ repoPath, branch = "", worktreePath = "", pipe
|
|
|
262
263
|
if (result.status !== 0) {
|
|
263
264
|
throw new Error(`git worktree add failed: ${result.stderr || result.stdout}`);
|
|
264
265
|
}
|
|
266
|
+
created = true;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
const actualBranch = actualWorktreeBranch(target);
|
|
@@ -271,6 +273,7 @@ export function loadGitWorktree({ repoPath, branch = "", worktreePath = "", pipe
|
|
|
271
273
|
worktreePath: target,
|
|
272
274
|
branch: actualBranch,
|
|
273
275
|
commit,
|
|
276
|
+
created,
|
|
274
277
|
};
|
|
275
278
|
}
|
|
276
279
|
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -4091,6 +4091,76 @@ function workspaceUpstreamMcpBlocks(graph, nodeId, outputs) {
|
|
|
4091
4091
|
return Array.from(new Set(blocks)).join("\n\n---\n\n");
|
|
4092
4092
|
}
|
|
4093
4093
|
|
|
4094
|
+
function workspaceSemanticInputText(graph, nodeId, outputs, name, scopedRoot = "") {
|
|
4095
|
+
const targetName = String(name || "").trim();
|
|
4096
|
+
if (!targetName) return "";
|
|
4097
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
4098
|
+
const edge = edges
|
|
4099
|
+
.filter((item) => String(item?.target || "") === String(nodeId))
|
|
4100
|
+
.find((item) => String(workspaceTargetSlotForEdge(graph, item)?.name || "") === targetName);
|
|
4101
|
+
if (edge) return workspaceOutputSlotValueForEdge(graph, outputs, edge, scopedRoot);
|
|
4102
|
+
const instance = graph?.instances && typeof graph.instances === "object" ? graph.instances[String(nodeId || "")] : null;
|
|
4103
|
+
return workspaceSlotValue(workspaceSlotByName(instance, targetName));
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
|
|
4107
|
+
const raw = String(text || "").trim();
|
|
4108
|
+
if (!raw) return null;
|
|
4109
|
+
const parsed = parseJsonText(raw, null);
|
|
4110
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
4111
|
+
const resolved = workspaceResolvePath(baseCwd || scopedRoot, raw) || raw;
|
|
4112
|
+
return {
|
|
4113
|
+
version: 1,
|
|
4114
|
+
label: "workspace",
|
|
4115
|
+
cwd: resolved,
|
|
4116
|
+
workspaceRoot: resolved,
|
|
4117
|
+
pipelineWorkspace: scopedRoot ? path.resolve(scopedRoot) : "",
|
|
4118
|
+
previous: null,
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
|
|
4123
|
+
const root = scopedRoot ? path.resolve(scopedRoot) : "";
|
|
4124
|
+
const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
|
|
4125
|
+
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot);
|
|
4126
|
+
let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
|
|
4127
|
+
if (!workspaceContext && cwd && root && cwd !== root) {
|
|
4128
|
+
workspaceContext = {
|
|
4129
|
+
version: 1,
|
|
4130
|
+
label: "workspace",
|
|
4131
|
+
cwd,
|
|
4132
|
+
workspaceRoot: cwd,
|
|
4133
|
+
pipelineWorkspace: root,
|
|
4134
|
+
previous: null,
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
const gitContext = normalizeGitContext(workspaceSemanticInputText(graph, nodeId, outputs, "gitContext", scopedRoot));
|
|
4138
|
+
if (!workspaceContext && !gitContext) return "";
|
|
4139
|
+
|
|
4140
|
+
const contextCwd = workspaceContext?.cwd ? path.resolve(String(workspaceContext.cwd)) : "";
|
|
4141
|
+
const workspaceRoot = workspaceContext?.workspaceRoot ? path.resolve(String(workspaceContext.workspaceRoot)) : contextCwd;
|
|
4142
|
+
const pipelineWorkspace = workspaceContext?.pipelineWorkspace ? path.resolve(String(workspaceContext.pipelineWorkspace)) : root;
|
|
4143
|
+
const label = String(workspaceContext?.label || "").trim();
|
|
4144
|
+
const lines = [
|
|
4145
|
+
"## Workspace 上下文",
|
|
4146
|
+
"",
|
|
4147
|
+
"当前 Agent 仍在独立节点目录中运行,文件边界以“文件边界”章节为准。",
|
|
4148
|
+
label ? `- 名称:${label}` : "",
|
|
4149
|
+
contextCwd ? `- 当前工作目录上下文:\`${contextCwd}\`` : "",
|
|
4150
|
+
workspaceRoot && workspaceRoot !== contextCwd ? `- workspaceRoot:\`${workspaceRoot}\`` : "",
|
|
4151
|
+
pipelineWorkspace ? `- 流程目录:\`${pipelineWorkspace}\`` : "",
|
|
4152
|
+
gitContext?.repoPath ? `- Git repoPath:\`${gitContext.repoPath}\`` : "",
|
|
4153
|
+
gitContext?.worktreePath ? `- Git worktreePath:\`${gitContext.worktreePath}\`` : "",
|
|
4154
|
+
gitContext?.branch ? `- Git branch:\`${gitContext.branch}\`` : "",
|
|
4155
|
+
gitContext?.commit ? `- Git commit:\`${gitContext.commit}\`` : "",
|
|
4156
|
+
"",
|
|
4157
|
+
"使用要求:",
|
|
4158
|
+
"- 读取、搜索、分析当前项目或资料时,优先从“当前工作目录上下文”开始;不要把节点的“当前执行目录”误认为项目根目录。",
|
|
4159
|
+
"- 临时文件和正式产物仍必须按“文件边界”写入本节点的 `tmp/` 与 `outputs/`。",
|
|
4160
|
+
].filter((line) => line !== "");
|
|
4161
|
+
return lines.join("\n");
|
|
4162
|
+
}
|
|
4163
|
+
|
|
4094
4164
|
function mergeWorkspaceSkillBlocks(...values) {
|
|
4095
4165
|
const blocks = values
|
|
4096
4166
|
.map((value) => String(value || ""))
|
|
@@ -4206,7 +4276,7 @@ function workspaceUpdateDirectDisplays(graph, sourceId, content, outputs = null,
|
|
|
4206
4276
|
return updated;
|
|
4207
4277
|
}
|
|
4208
4278
|
|
|
4209
|
-
function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock = "", inputValues = {}, nodeTmpDir = "", implementationBlock = "") {
|
|
4279
|
+
function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock = "", inputValues = {}, nodeTmpDir = "", implementationBlock = "", workspaceContextBlock = "") {
|
|
4210
4280
|
const instance = graph.instances[nodeId] || {};
|
|
4211
4281
|
const body = workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim();
|
|
4212
4282
|
const { values: relevantInputValues, placeholders } = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
@@ -4217,6 +4287,7 @@ function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock
|
|
|
4217
4287
|
return [
|
|
4218
4288
|
"你正在执行一个独立任务。只使用本提示中的任务、输入、可用能力和文件边界。",
|
|
4219
4289
|
fileBoundary ? `\n${fileBoundary}` : "",
|
|
4290
|
+
workspaceContextBlock ? `\n${workspaceContextBlock}` : "",
|
|
4220
4291
|
inputBlock ? `\n${inputBlock}` : "",
|
|
4221
4292
|
placeholders.size ? "\n任务只显式引用了上面的输入槽;其它未被 `${...}` 引用的已连接业务输入不要作为分析依据。" : "",
|
|
4222
4293
|
implementationBlock ? `\n${implementationBlock}` : "",
|
|
@@ -4258,8 +4329,11 @@ function workspaceDefaultWorktreePath(runTmpRoot, nodeId, repoPath, branch = "")
|
|
|
4258
4329
|
);
|
|
4259
4330
|
}
|
|
4260
4331
|
|
|
4261
|
-
function workspaceShouldAutoCleanupWorktree(
|
|
4262
|
-
|
|
4332
|
+
function workspaceShouldAutoCleanupWorktree(result, scopedRoot = "") {
|
|
4333
|
+
const worktreePath = String(result?.worktreePath || "").trim();
|
|
4334
|
+
if (!worktreePath) return false;
|
|
4335
|
+
if (result.created === true) return true;
|
|
4336
|
+
return scopedRoot ? workspacePathInside(scopedRoot, worktreePath) : false;
|
|
4263
4337
|
}
|
|
4264
4338
|
|
|
4265
4339
|
function workspaceTrackAutoCleanupWorktree(list, item) {
|
|
@@ -4341,7 +4415,7 @@ function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
|
4341
4415
|
return dir;
|
|
4342
4416
|
}
|
|
4343
4417
|
|
|
4344
|
-
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, task = "", inputValues = {}, skillsBlock = "", mcpBlock = "" } = {}) {
|
|
4418
|
+
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "" } = {}) {
|
|
4345
4419
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
4346
4420
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
4347
4421
|
const outputsDir = path.join(nodeRunDir, "outputs");
|
|
@@ -4357,6 +4431,7 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, task =
|
|
|
4357
4431
|
nodeTmpDir,
|
|
4358
4432
|
outputsDir,
|
|
4359
4433
|
workspaceRoot,
|
|
4434
|
+
executionCwd: cwd ? path.resolve(cwd) : workspaceRoot,
|
|
4360
4435
|
createdAt: new Date().toISOString(),
|
|
4361
4436
|
};
|
|
4362
4437
|
const materializedInputs = workspaceMaterializeNodeInputFiles(nodeRunDir, workspaceRoot, inputValues);
|
|
@@ -4933,13 +5008,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4933
5008
|
const worktreePath = rawWorktreePath
|
|
4934
5009
|
? workspaceResolvePath(cwd, rawWorktreePath)
|
|
4935
5010
|
: (gitContext?.worktreePath ? path.resolve(gitContext.worktreePath) : workspaceDefaultWorktreePath(runTmpRoot, nodeId, repoPath, branch));
|
|
4936
|
-
const hasExplicitWorktreePath = Boolean(rawWorktreePath) || Boolean(gitContext?.worktreePath);
|
|
4937
5011
|
const previousCwd = cwd;
|
|
4938
5012
|
const force = ["true", "1", "yes", "on"].includes(workspaceSlotValue(workspaceSlotByName(instance, "force")).trim().toLowerCase());
|
|
4939
5013
|
const pruneMissingRaw = workspaceSlotValue(workspaceSlotByName(instance, "pruneMissing")).trim().toLowerCase();
|
|
4940
5014
|
const pruneMissing = pruneMissingRaw !== "false";
|
|
4941
5015
|
const result = loadGitWorktree({ repoPath, branch, worktreePath, pipelineWorkspace: scopedRoot, force, pruneMissing });
|
|
4942
|
-
if (workspaceShouldAutoCleanupWorktree(result
|
|
5016
|
+
if (workspaceShouldAutoCleanupWorktree(result, scopedRoot)) {
|
|
4943
5017
|
workspaceTrackAutoCleanupWorktree(autoCleanupWorktrees, {
|
|
4944
5018
|
nodeId,
|
|
4945
5019
|
repoPath: result.repoRoot,
|
|
@@ -5191,7 +5265,8 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5191
5265
|
// Best-effort debug artifact only.
|
|
5192
5266
|
}
|
|
5193
5267
|
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
5194
|
-
const
|
|
5268
|
+
const workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
|
|
5269
|
+
const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock, workspaceContextBlock);
|
|
5195
5270
|
try {
|
|
5196
5271
|
fs.writeFileSync(path.join(runPackage.nodeRunDir, "prompt.md"), prompt.trimEnd() + "\n", "utf-8");
|
|
5197
5272
|
} catch {
|
|
@@ -6,8 +6,10 @@ description: |
|
|
|
6
6
|
- `repoPath` is required unless `gitContext.repoPath` is connected.
|
|
7
7
|
- `workspaceContext` is required so the node can preserve the previous execution context.
|
|
8
8
|
- `branch` is optional. When empty, AgentFlow creates a detached worktree at the current HEAD.
|
|
9
|
-
- `worktreePath` is optional. When empty, AgentFlow creates a temporary worktree under the current run temp directory
|
|
10
|
-
-
|
|
9
|
+
- `worktreePath` is optional. When empty, AgentFlow creates a temporary worktree under the current run temp directory.
|
|
10
|
+
- A worktree created by this node during the current Workspace run is removed when the run finishes or is stopped, even when `worktreePath` is explicitly set.
|
|
11
|
+
- Existing registered worktrees under the current flow workspace are also removed after the run, covering leftovers from previous interrupted runs.
|
|
12
|
+
- Existing registered worktrees outside the current flow workspace are reused and not removed automatically unless this run created them.
|
|
11
13
|
- Existing worktree paths are reused only when they are registered by `git worktree list` for the given repo.
|
|
12
14
|
- `pruneMissing` defaults to true. When Git has a registered worktree whose directory is missing, AgentFlow runs `git worktree prune` before adding it again.
|
|
13
15
|
- `force` defaults to false. When true, AgentFlow passes `--force` to `git worktree add`.
|