@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.
@@ -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
 
@@ -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(worktreePath, hasExplicitWorktreePath) {
4262
- return Boolean(worktreePath) && !hasExplicitWorktreePath;
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.worktreePath, hasExplicitWorktreePath)) {
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 prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock);
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 and removes it after the run finishes.
10
- - Set `worktreePath` only when you explicitly want a persistent worktree outside the run lifecycle.
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`.