@fieldwangai/agentflow 0.1.162 → 0.1.163
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/flow-dsl/codegen.mjs +23 -2
- package/bin/lib/flow-dsl/lint.mjs +44 -0
- package/bin/lib/flow-dsl/parser.mjs +68 -1
- package/bin/lib/marketplace-usage.mjs +218 -0
- package/bin/lib/marketplace.mjs +183 -3
- package/bin/lib/node-package-manifest.mjs +1 -1
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/ui-server.mjs +251 -67
- package/bin/lib/workspace-routes.mjs +189 -2
- package/bin/lib/workspace-server.mjs +367 -30
- package/bin/lib/workspace-state.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +5 -1
- package/builtin/nodes/context_bundle.md +44 -0
- package/builtin/nodes/context_knowledge.md +29 -0
- package/builtin/nodes/context_skills.md +29 -0
- package/builtin/nodes/context_workspace.md +36 -0
- package/builtin/nodes/control_while.md +7 -0
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CTrXOZ00.js → WorkflowAssistantThread-DmhIgoD7.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CI9J6Unt.js +873 -0
- package/builtin/web-ui/dist/assets/index-DY5vE7v1.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/shared/slot-types.js +1 -0
- package/skills/agentflow-cli/SKILL.md +50 -3
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +120 -6
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +5 -1
- package/skills/agentflow-cli/runtime/builtin/nodes/context_bundle.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_knowledge.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_skills.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_workspace.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +7 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/skills/agentflow-cli/runtime/package.json +1 -1
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +64 -0
- package/skills/agentflow-flow-dsl/SKILL.md +48 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +6 -2
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +34 -7
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/builtin/web-ui/dist/assets/index-5uJFccdX.css +0 -1
- package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +0 -873
|
@@ -33,6 +33,7 @@ import { t } from "./i18n.mjs";
|
|
|
33
33
|
import { advanceJenkinsBuild, createJenkinsHttpInvoker, jenkinsBuildStatePath, normalizeJenkinsBuildConfig, readJenkinsBuildState, writeJenkinsBuildState } from "./jenkins.mjs";
|
|
34
34
|
import { log } from "./log.mjs";
|
|
35
35
|
import { resolveMarketplaceNodePackage } from "./marketplace.mjs";
|
|
36
|
+
import { marketplaceResourcesForRun, recordMarketplaceRunUsage } from "./marketplace-usage.mjs";
|
|
36
37
|
import { PACKAGE_ROOT, getAgentflowDataRoot, getAgentflowUserDataRoot, listAgentflowUserIds } from "./paths.mjs";
|
|
37
38
|
import { appendRunLedgerEvent, readRunLedgerEvents, runLedgerId } from "./run-ledger.mjs";
|
|
38
39
|
import { computeNextRunAt } from "./schedule-config.mjs";
|
|
@@ -796,6 +797,71 @@ export function readWorkspacesFromPath(p, userCtx = {}) {
|
|
|
796
797
|
}
|
|
797
798
|
}
|
|
798
799
|
|
|
800
|
+
function legacyUserWorkspacesPath(userCtx = {}) {
|
|
801
|
+
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function readLegacyAdminWorkspaces(userCtx = {}) {
|
|
805
|
+
const users = readAuthUsers();
|
|
806
|
+
const candidates = [];
|
|
807
|
+
for (const [userId, user] of Object.entries(users || {})) {
|
|
808
|
+
if (user?.isAdmin) candidates.push(String(userId || ""));
|
|
809
|
+
}
|
|
810
|
+
if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
|
|
811
|
+
const seenPaths = new Set();
|
|
812
|
+
const seenEntries = new Set();
|
|
813
|
+
const out = [];
|
|
814
|
+
for (const userId of candidates) {
|
|
815
|
+
const p = legacyUserWorkspacesPath({ userId });
|
|
816
|
+
const resolved = path.resolve(p);
|
|
817
|
+
if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
|
|
818
|
+
seenPaths.add(resolved);
|
|
819
|
+
for (const entry of readWorkspacesFromPath(p, { userId })) {
|
|
820
|
+
const key = entry.id || entry.path || entry.repoUrl;
|
|
821
|
+
if (seenEntries.has(key)) continue;
|
|
822
|
+
seenEntries.add(key);
|
|
823
|
+
out.push(entry);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return out;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/** The same authenticated Workspace catalog used by GET /api/workspaces. */
|
|
830
|
+
export function readUserWorkspaces(userCtx = {}) {
|
|
831
|
+
const globalPath = workspacesPath();
|
|
832
|
+
const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
|
|
833
|
+
const adminLegacy = readLegacyAdminWorkspaces(userCtx);
|
|
834
|
+
if (globalWorkspaces.length || adminLegacy.length) {
|
|
835
|
+
const seen = new Set();
|
|
836
|
+
const out = [];
|
|
837
|
+
for (const entry of [...globalWorkspaces, ...adminLegacy]) {
|
|
838
|
+
const key = entry.id || entry.path || entry.repoUrl;
|
|
839
|
+
if (seen.has(key)) continue;
|
|
840
|
+
seen.add(key);
|
|
841
|
+
out.push(entry);
|
|
842
|
+
}
|
|
843
|
+
return out;
|
|
844
|
+
}
|
|
845
|
+
return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
849
|
+
const currentRoot = path.resolve(scopedRoot || root);
|
|
850
|
+
const homeRoot = path.resolve(os.homedir());
|
|
851
|
+
const builtins = [
|
|
852
|
+
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
853
|
+
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
854
|
+
];
|
|
855
|
+
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
856
|
+
const seen = new Set();
|
|
857
|
+
return [...builtins, ...custom].filter((entry) => {
|
|
858
|
+
const key = path.resolve(entry.path);
|
|
859
|
+
if (seen.has(key)) return false;
|
|
860
|
+
seen.add(key);
|
|
861
|
+
return true;
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
799
865
|
export function workspaceRepoUrlWithCredential(repoUrl = "", credential = "") {
|
|
800
866
|
const token = String(credential || "").trim();
|
|
801
867
|
if (!token) return String(repoUrl || "").trim();
|
|
@@ -856,6 +922,10 @@ export function appendWorkspaceRunFinished(record, status) {
|
|
|
856
922
|
durationMs: Math.max(0, Number(record.durationMs || (Number(record.endedAt || Date.now()) - Number(record.startedAt || record.at || Date.now())))),
|
|
857
923
|
status,
|
|
858
924
|
});
|
|
925
|
+
recordMarketplaceRunUsage(record.workspaceRoot || record.root || "", record.marketplaceResources || [], {
|
|
926
|
+
...record,
|
|
927
|
+
status,
|
|
928
|
+
});
|
|
859
929
|
}
|
|
860
930
|
|
|
861
931
|
function normalizeWorkspaceUsageRecord(parsed, source = "workspace-run") {
|
|
@@ -1034,7 +1104,7 @@ function normalizeDisplayShareLayout(layout, fallback = "canvas") {
|
|
|
1034
1104
|
return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
|
|
1035
1105
|
}
|
|
1036
1106
|
|
|
1037
|
-
export function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds, expiresMode, expiresInDays, permanent, expiresAt }) {
|
|
1107
|
+
export function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds, expiresMode, expiresInDays, permanent, expiresAt, visibility = "public" }) {
|
|
1038
1108
|
const shares = readDisplayShares();
|
|
1039
1109
|
let id = createDisplayShareId();
|
|
1040
1110
|
while (shares[id]) id = createDisplayShareId();
|
|
@@ -1050,6 +1120,7 @@ export function createDisplayShareRecord({ userId, flowId, flowSource, archived,
|
|
|
1050
1120
|
title: String(title || "").trim() || "AgentFlow Display",
|
|
1051
1121
|
layout: normalizeDisplayShareLayout(layout, "canvas"),
|
|
1052
1122
|
nodeIds: Array.isArray(nodeIds) ? nodeIds : [],
|
|
1123
|
+
visibility: String(visibility || "").trim().toLowerCase() === "private" ? "private" : "public",
|
|
1053
1124
|
createdAt: now,
|
|
1054
1125
|
updatedAt: now,
|
|
1055
1126
|
expiresAt: expiry.expiresAt,
|
|
@@ -3175,7 +3246,7 @@ function workspaceTargetSlotForEdge(graph, edge) {
|
|
|
3175
3246
|
function isWorkspaceSemanticInputSlot(slot) {
|
|
3176
3247
|
const name = String(slot?.name || "");
|
|
3177
3248
|
const type = String(slot?.type || "");
|
|
3178
|
-
return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "knowledgeContext" || name === "workspaceContext" || name === "gitContext";
|
|
3249
|
+
return type === "node" || type === "context" || name === "prev" || name === "next" || name === "context" || name === "skillsContext" || name === "mcpContext" || name === "knowledgeContext" || name === "workspaceContext" || name === "gitContext";
|
|
3179
3250
|
}
|
|
3180
3251
|
|
|
3181
3252
|
function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
|
|
@@ -3230,8 +3301,9 @@ function workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputNames =
|
|
|
3230
3301
|
return workspaceOutputSlotValueForEdge(graph, outputs, contentEdge, scopedRoot);
|
|
3231
3302
|
}
|
|
3232
3303
|
|
|
3233
|
-
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
3304
|
+
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "", options = {}) {
|
|
3234
3305
|
const values = {};
|
|
3306
|
+
const includeContext = options?.includeContext === true;
|
|
3235
3307
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
3236
3308
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
3237
3309
|
const target = instances[String(nodeId || "")] || {};
|
|
@@ -3241,13 +3313,13 @@ function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
|
3241
3313
|
const index = workspaceHandleIndex(edge?.targetHandle, "input");
|
|
3242
3314
|
const slot = inputSlots[index] || null;
|
|
3243
3315
|
const name = String(slot?.name || "").trim();
|
|
3244
|
-
if (!name || isWorkspaceSemanticInputSlot(slot)) continue;
|
|
3316
|
+
if (!name || (isWorkspaceSemanticInputSlot(slot) && !(includeContext && name === "context"))) continue;
|
|
3245
3317
|
const value = workspaceOutputSlotValueForEdge(graph, outputs, edge, scopedRoot);
|
|
3246
3318
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3247
3319
|
}
|
|
3248
3320
|
for (const slot of inputSlots) {
|
|
3249
3321
|
const name = String(slot?.name || "").trim();
|
|
3250
|
-
if (!name || isWorkspaceSemanticInputSlot(slot) || Object.prototype.hasOwnProperty.call(values, name)) continue;
|
|
3322
|
+
if (!name || (isWorkspaceSemanticInputSlot(slot) && !(includeContext && name === "context")) || Object.prototype.hasOwnProperty.call(values, name)) continue;
|
|
3251
3323
|
const value = workspaceSlotValue(slot);
|
|
3252
3324
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3253
3325
|
}
|
|
@@ -3944,7 +4016,9 @@ function selectedSkillKeysFromInstance(instance) {
|
|
|
3944
4016
|
|
|
3945
4017
|
function selectedSkillKeysFromConfigSlots(instance) {
|
|
3946
4018
|
const slots = [...(Array.isArray(instance?.input) ? instance.input : []), ...(Array.isArray(instance?.output) ? instance.output : [])];
|
|
3947
|
-
const slot = slots.find((item) => item?.name === "
|
|
4019
|
+
const slot = slots.find((item) => item?.name === "skills") ||
|
|
4020
|
+
slots.find((item) => item?.name === "skillsContext") ||
|
|
4021
|
+
slots.find((item) => item?.name === "skillKeys");
|
|
3948
4022
|
return parseWorkspaceSkillKeys(workspaceSlotValue(slot) || "");
|
|
3949
4023
|
}
|
|
3950
4024
|
|
|
@@ -3998,6 +4072,25 @@ function workspaceSemanticInputText(graph, nodeId, outputs, name, scopedRoot = "
|
|
|
3998
4072
|
return workspaceSlotValue(workspaceSlotByName(instance, targetName));
|
|
3999
4073
|
}
|
|
4000
4074
|
|
|
4075
|
+
function workspaceContextBundleFromText(text) {
|
|
4076
|
+
const parsed = parseJsonText(String(text || "").trim(), null);
|
|
4077
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
4078
|
+
if (Number(parsed.version || 1) !== 1) return {};
|
|
4079
|
+
const out = {};
|
|
4080
|
+
for (const name of ["knowledgeContext", "skillsContext", "workspaceContext", "mcpContext"]) {
|
|
4081
|
+
if (parsed[name] !== undefined && parsed[name] !== null) {
|
|
4082
|
+
out[name] = typeof parsed[name] === "string" ? parsed[name] : JSON.stringify(parsed[name]);
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
return out;
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
function workspaceNodeContextBundle(graph, nodeId, outputs, scopedRoot = "") {
|
|
4089
|
+
return workspaceContextBundleFromText(
|
|
4090
|
+
workspaceSemanticInputText(graph, nodeId, outputs, "context", scopedRoot),
|
|
4091
|
+
);
|
|
4092
|
+
}
|
|
4093
|
+
|
|
4001
4094
|
function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
|
|
4002
4095
|
const raw = String(text || "").trim();
|
|
4003
4096
|
if (!raw) return null;
|
|
@@ -4024,20 +4117,30 @@ function workspaceLooksLikeKnowledgePath(value) {
|
|
|
4024
4117
|
}
|
|
4025
4118
|
|
|
4026
4119
|
function workspaceKnowledgeSourceFromObject(source = {}, baseCwd = "", scopedRoot = "") {
|
|
4120
|
+
if (typeof source === "string") {
|
|
4121
|
+
const ref = source.trim();
|
|
4122
|
+
if (!ref) return null;
|
|
4123
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(ref)) {
|
|
4124
|
+
return { id: ref, label: ref, kind: "binding", type: "", path: "", repoPath: "", mountPath: "", repoUrl: "", branch: "", ref, readonly: true };
|
|
4125
|
+
}
|
|
4126
|
+
source = { path: ref };
|
|
4127
|
+
}
|
|
4027
4128
|
if (!source || typeof source !== "object" || Array.isArray(source)) return null;
|
|
4129
|
+
const ref = String(source.ref || source.binding || "").trim();
|
|
4028
4130
|
const rawPath = String(source.path || source.repoPath || source.cwd || source.workspaceRoot || "").trim();
|
|
4029
|
-
if (!workspaceLooksLikeKnowledgePath(rawPath)) return null;
|
|
4030
|
-
const resolvedPath = workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath;
|
|
4131
|
+
if (!workspaceLooksLikeKnowledgePath(rawPath) && !ref) return null;
|
|
4132
|
+
const resolvedPath = rawPath ? (workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath) : "";
|
|
4031
4133
|
return {
|
|
4032
|
-
id: String(source.id || source.mountPath || source.label || path.basename(resolvedPath) || "").trim(),
|
|
4033
|
-
label: String(source.label || source.id || source.mountPath || path.basename(resolvedPath) || "知识库").trim(),
|
|
4034
|
-
kind: String(source.kind || (source.repoUrl ? "git" : "local")).trim() || "local",
|
|
4134
|
+
id: String(source.id || source.mountPath || source.label || path.basename(resolvedPath) || ref || "").trim(),
|
|
4135
|
+
label: String(source.label || source.id || source.mountPath || path.basename(resolvedPath) || ref || "知识库").trim(),
|
|
4136
|
+
kind: String(source.kind || (source.repoUrl ? "git" : (ref ? "binding" : "local"))).trim() || "local",
|
|
4035
4137
|
type: String(source.type || "").trim(),
|
|
4036
4138
|
path: resolvedPath,
|
|
4037
4139
|
repoPath: resolvedPath,
|
|
4038
4140
|
mountPath: String(source.mountPath || "").trim(),
|
|
4039
4141
|
repoUrl: String(source.repoUrl || "").trim(),
|
|
4040
4142
|
branch: String(source.branch || "").trim(),
|
|
4143
|
+
ref,
|
|
4041
4144
|
readonly: source.readonly !== false,
|
|
4042
4145
|
};
|
|
4043
4146
|
}
|
|
@@ -4073,7 +4176,7 @@ export function workspaceKnowledgeSourcesFromText(text, baseCwd = "", scopedRoot
|
|
|
4073
4176
|
}
|
|
4074
4177
|
|
|
4075
4178
|
function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
4076
|
-
const valid = Array.isArray(sources) ? sources.filter((source) => source?.path || source?.repoPath) : [];
|
|
4179
|
+
const valid = Array.isArray(sources) ? sources.filter((source) => source?.path || source?.repoPath || source?.ref) : [];
|
|
4077
4180
|
if (!valid.length) return "";
|
|
4078
4181
|
const lines = [
|
|
4079
4182
|
"## 知识库上下文",
|
|
@@ -4087,6 +4190,7 @@ function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
|
4087
4190
|
lines.push(`${index + 1}. ${label}`);
|
|
4088
4191
|
if (source.kind) lines.push(` - 类型:${source.kind}${source.type ? `/${source.type}` : ""}`);
|
|
4089
4192
|
if (sourcePath) lines.push(` - 路径:\`${sourcePath}\``);
|
|
4193
|
+
if (source.ref) lines.push(` - 绑定:\`${source.ref}\``);
|
|
4090
4194
|
if (source.mountPath) lines.push(` - 挂载目录:${source.mountPath}`);
|
|
4091
4195
|
if (source.repoUrl) lines.push(` - Git URL:${source.repoUrl}`);
|
|
4092
4196
|
if (source.branch) lines.push(` - 分支:${source.branch}`);
|
|
@@ -4100,10 +4204,11 @@ function workspaceDedupeKnowledgeSources(sources = []) {
|
|
|
4100
4204
|
for (const source of Array.isArray(sources) ? sources : []) {
|
|
4101
4205
|
if (!source || typeof source !== "object") continue;
|
|
4102
4206
|
const key = [
|
|
4103
|
-
path.resolve(String(source.path || source.repoPath
|
|
4207
|
+
source.path || source.repoPath ? path.resolve(String(source.path || source.repoPath)) : "",
|
|
4104
4208
|
String(source.mountPath || ""),
|
|
4105
4209
|
String(source.repoUrl || ""),
|
|
4106
4210
|
String(source.branch || ""),
|
|
4211
|
+
String(source.ref || ""),
|
|
4107
4212
|
].join("\n");
|
|
4108
4213
|
if (seen.has(key)) continue;
|
|
4109
4214
|
seen.add(key);
|
|
@@ -4149,16 +4254,18 @@ function workspaceGlobalKnowledgeSources(graph, scopedRoot = "", logicalCwd = ""
|
|
|
4149
4254
|
);
|
|
4150
4255
|
}
|
|
4151
4256
|
|
|
4152
|
-
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
|
|
4257
|
+
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "", contextBundle = {}) {
|
|
4153
4258
|
const root = scopedRoot ? path.resolve(scopedRoot) : "";
|
|
4154
4259
|
const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
|
|
4155
|
-
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4260
|
+
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4261
|
+
|| String(contextBundle?.knowledgeContext || "");
|
|
4156
4262
|
let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || root, scopedRoot);
|
|
4157
4263
|
knowledgeSources = workspaceDedupeKnowledgeSources([
|
|
4158
4264
|
...workspaceGlobalKnowledgeSources(graph, scopedRoot, logicalCwd, nodeId),
|
|
4159
4265
|
...knowledgeSources,
|
|
4160
4266
|
]);
|
|
4161
|
-
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4267
|
+
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4268
|
+
|| String(contextBundle?.workspaceContext || "");
|
|
4162
4269
|
let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
|
|
4163
4270
|
if (!knowledgeSources.length && workspaceContext?.cwd) {
|
|
4164
4271
|
knowledgeSources = workspaceKnowledgeSourcesFromText(JSON.stringify([workspaceContext]), cwd || root, scopedRoot);
|
|
@@ -4384,7 +4491,7 @@ function workspaceDefaultGitRepoRoot(scopedRoot, _userCtx = {}) {
|
|
|
4384
4491
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "git-repos");
|
|
4385
4492
|
}
|
|
4386
4493
|
|
|
4387
|
-
function workspaceDefaultWorktreePath(
|
|
4494
|
+
function workspaceDefaultWorktreePath(scopedRoot, runId, nodeId, repoPath, branch = "") {
|
|
4388
4495
|
const repoRoot = path.resolve(repoPath);
|
|
4389
4496
|
const repoName = sanitizeWorktreeName(path.basename(repoRoot));
|
|
4390
4497
|
const branchName = String(branch || "").trim();
|
|
@@ -4402,9 +4509,12 @@ function workspaceDefaultWorktreePath(runTmpRoot, nodeId, repoPath, branch = "")
|
|
|
4402
4509
|
: "HEAD";
|
|
4403
4510
|
}
|
|
4404
4511
|
return path.join(
|
|
4405
|
-
path.resolve(
|
|
4406
|
-
"
|
|
4512
|
+
path.resolve(scopedRoot),
|
|
4513
|
+
".workspace",
|
|
4514
|
+
"agentflow",
|
|
4515
|
+
"run-workspaces",
|
|
4407
4516
|
workspaceSanitizeTmpSegment(nodeId, "node"),
|
|
4517
|
+
workspaceSanitizeTmpSegment(runId, "run"),
|
|
4408
4518
|
repoName,
|
|
4409
4519
|
sanitizeWorktreeName(refLabel),
|
|
4410
4520
|
);
|
|
@@ -4444,15 +4554,18 @@ function workspaceMarkAutoWorktreeCleaned(graph, entry) {
|
|
|
4444
4554
|
return true;
|
|
4445
4555
|
}
|
|
4446
4556
|
|
|
4447
|
-
function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
4557
|
+
function workspaceCleanupAutoWorktrees(list, graph, emit, { force = false } = {}) {
|
|
4558
|
+
const cleaned = [];
|
|
4559
|
+
const preserved = [];
|
|
4448
4560
|
for (const entry of [...list].reverse()) {
|
|
4449
4561
|
try {
|
|
4450
4562
|
const result = unloadGitWorktree({
|
|
4451
4563
|
repoPath: entry.repoPath,
|
|
4452
4564
|
worktreePath: entry.worktreePath,
|
|
4453
|
-
force
|
|
4565
|
+
force,
|
|
4454
4566
|
prune: true,
|
|
4455
4567
|
});
|
|
4568
|
+
cleaned.push(result.worktreePath);
|
|
4456
4569
|
emit({
|
|
4457
4570
|
type: "natural",
|
|
4458
4571
|
kind: "status",
|
|
@@ -4463,15 +4576,74 @@ function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
|
4463
4576
|
emit({ type: "graph", nodeId: entry.nodeId, graph });
|
|
4464
4577
|
}
|
|
4465
4578
|
} catch (e) {
|
|
4579
|
+
preserved.push({ ...entry, reason: e?.message || String(e) });
|
|
4466
4580
|
emit({
|
|
4467
4581
|
type: "natural",
|
|
4468
4582
|
kind: "warning",
|
|
4469
4583
|
nodeId: entry.nodeId,
|
|
4470
|
-
text:
|
|
4584
|
+
text: `运行 worktree 已保留:${entry.worktreePath}\n原因:${e?.message || String(e)}`,
|
|
4471
4585
|
});
|
|
4472
4586
|
}
|
|
4473
4587
|
}
|
|
4474
4588
|
list.splice(0, list.length);
|
|
4589
|
+
return { cleaned, preserved };
|
|
4590
|
+
}
|
|
4591
|
+
|
|
4592
|
+
function workspaceRunManifestPath(scopedRoot, runId) {
|
|
4593
|
+
const id = workspaceSanitizeTmpSegment(runId, "run");
|
|
4594
|
+
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "run-manifests", `${id}.json`);
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
function workspaceWriteRunManifest(scopedRoot, runId, value = {}) {
|
|
4598
|
+
const filePath = workspaceRunManifestPath(scopedRoot, runId);
|
|
4599
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
4600
|
+
const previous = (() => {
|
|
4601
|
+
try {
|
|
4602
|
+
return fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, "utf-8")) : {};
|
|
4603
|
+
} catch {
|
|
4604
|
+
return {};
|
|
4605
|
+
}
|
|
4606
|
+
})();
|
|
4607
|
+
const next = {
|
|
4608
|
+
version: 1,
|
|
4609
|
+
...previous,
|
|
4610
|
+
...value,
|
|
4611
|
+
runId: String(runId || previous.runId || ""),
|
|
4612
|
+
updatedAt: new Date().toISOString(),
|
|
4613
|
+
};
|
|
4614
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
4615
|
+
fs.writeFileSync(tempPath, JSON.stringify(next, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
|
|
4616
|
+
fs.renameSync(tempPath, filePath);
|
|
4617
|
+
return next;
|
|
4618
|
+
}
|
|
4619
|
+
|
|
4620
|
+
export function cleanupWorkspaceRunResources(scopedRoot, runId, { force = false, status = "stopped", emit = () => {} } = {}) {
|
|
4621
|
+
const filePath = workspaceRunManifestPath(scopedRoot, runId);
|
|
4622
|
+
if (!fs.existsSync(filePath)) return { cleaned: [], preserved: [], manifestPath: filePath };
|
|
4623
|
+
let manifest = {};
|
|
4624
|
+
try {
|
|
4625
|
+
manifest = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
4626
|
+
} catch {
|
|
4627
|
+
return { cleaned: [], preserved: [], manifestPath: filePath };
|
|
4628
|
+
}
|
|
4629
|
+
const resources = Array.isArray(manifest.worktrees) ? manifest.worktrees : [];
|
|
4630
|
+
const pending = resources.filter((entry) => entry?.repoPath && entry?.worktreePath && entry.removed !== true);
|
|
4631
|
+
const result = workspaceCleanupAutoWorktrees(pending.map((entry) => ({ ...entry })), null, emit, { force });
|
|
4632
|
+
const cleanedSet = new Set(result.cleaned.map((item) => path.resolve(item)));
|
|
4633
|
+
const preservedByPath = new Map(result.preserved.map((item) => [path.resolve(item.worktreePath), item]));
|
|
4634
|
+
const worktrees = resources.map((entry) => {
|
|
4635
|
+
const target = entry?.worktreePath ? path.resolve(entry.worktreePath) : "";
|
|
4636
|
+
if (target && cleanedSet.has(target)) return { ...entry, removed: true, removedAt: new Date().toISOString(), reason: "" };
|
|
4637
|
+
if (target && preservedByPath.has(target)) return { ...entry, removed: false, reason: preservedByPath.get(target).reason };
|
|
4638
|
+
return entry;
|
|
4639
|
+
});
|
|
4640
|
+
workspaceWriteRunManifest(scopedRoot, runId, {
|
|
4641
|
+
...manifest,
|
|
4642
|
+
status: result.preserved.length ? `${status}:resources-preserved` : status,
|
|
4643
|
+
worktrees,
|
|
4644
|
+
finishedAt: new Date().toISOString(),
|
|
4645
|
+
});
|
|
4646
|
+
return { ...result, manifestPath: filePath };
|
|
4475
4647
|
}
|
|
4476
4648
|
|
|
4477
4649
|
function workspaceSanitizeTmpSegment(value, fallback = "node") {
|
|
@@ -5110,10 +5282,27 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5110
5282
|
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
5111
5283
|
const autoCleanupWorktrees = [];
|
|
5112
5284
|
const runTmpRoot = workspaceCreateRunTmpRoot(scopedRoot, runNodeId);
|
|
5285
|
+
const runtimeRunId = String(opts.runId || payload?.runId || "").trim() || runLedgerId("workspace-execution");
|
|
5286
|
+
const ownsRunManifest = !(Array.isArray(opts?.subflowCallStack) && opts.subflowCallStack.length);
|
|
5287
|
+
const persistRunManifest = (status, extra = {}) => {
|
|
5288
|
+
if (!ownsRunManifest) return null;
|
|
5289
|
+
return workspaceWriteRunManifest(scopedRoot, runtimeRunId, {
|
|
5290
|
+
flowId: String(payload?.flowId || ""),
|
|
5291
|
+
flowSource: String(payload?.flowSource || "user"),
|
|
5292
|
+
runNodeId,
|
|
5293
|
+
status,
|
|
5294
|
+
runtimeRoot: runTmpRoot,
|
|
5295
|
+
artifactRoot: path.join(path.resolve(scopedRoot), "outputs"),
|
|
5296
|
+
worktrees: autoCleanupWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
5297
|
+
...extra,
|
|
5298
|
+
});
|
|
5299
|
+
};
|
|
5300
|
+
persistRunManifest("running", { startedAt: new Date().toISOString() });
|
|
5113
5301
|
const controlBranches = new Map();
|
|
5114
5302
|
const skippedNodes = new Set();
|
|
5115
5303
|
const runtimePauseNodeIds = [];
|
|
5116
5304
|
let deferred = null;
|
|
5305
|
+
let runFailure = null;
|
|
5117
5306
|
const incomingControlEdgesByTarget = new Map();
|
|
5118
5307
|
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
5119
5308
|
const target = String(edge?.target || "");
|
|
@@ -5177,11 +5366,87 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5177
5366
|
continue;
|
|
5178
5367
|
}
|
|
5179
5368
|
|
|
5369
|
+
if (defId === "context_knowledge") {
|
|
5370
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5371
|
+
const workspaceIds = parseWorkspaceSkillKeys(inputValues.workspaceIds || "[]");
|
|
5372
|
+
if (!workspaceIds.length) throw new Error(`context.knowledge ${nodeId} requires at least one Workspace ID`);
|
|
5373
|
+
const catalog = new Map(listConfiguredWorkspaces(root, scopedRoot, userCtx).map((entry) => [String(entry.id || ""), entry]));
|
|
5374
|
+
const missing = workspaceIds.filter((id) => !catalog.has(id));
|
|
5375
|
+
if (missing.length) throw new Error(`context.knowledge ${nodeId} cannot resolve Workspace IDs: ${missing.join(", ")}`);
|
|
5376
|
+
const unavailable = workspaceIds.filter((id) => catalog.get(id)?.exists === false);
|
|
5377
|
+
if (unavailable.length) throw new Error(`context.knowledge ${nodeId} Workspace paths are not ready: ${unavailable.join(", ")}`);
|
|
5378
|
+
const sources = workspaceIds
|
|
5379
|
+
.map((id, index) => workspaceKnowledgeSourceFromObject({ ...catalog.get(id), role: index === 0 ? "primary" : "context" }, cwd || scopedRoot, scopedRoot))
|
|
5380
|
+
.filter(Boolean);
|
|
5381
|
+
const value = JSON.stringify({ version: 1, sources });
|
|
5382
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "knowledgeContext", value);
|
|
5383
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5384
|
+
emit({ type: "node-done", nodeId, definitionId: defId, sourceCount: sources.length, workspaceIds });
|
|
5385
|
+
continue;
|
|
5386
|
+
}
|
|
5387
|
+
|
|
5388
|
+
if (defId === "context_skills") {
|
|
5389
|
+
const keys = selectedSkillKeysFromConfigSlots(instance);
|
|
5390
|
+
if (!keys.length) throw new Error(`context.skills ${nodeId} requires at least one skill`);
|
|
5391
|
+
const value = loadSkillsBlockForKeys(keys);
|
|
5392
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "skillsContext", value);
|
|
5393
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5394
|
+
emit({ type: "node-done", nodeId, definitionId: defId, skillCount: keys.length });
|
|
5395
|
+
continue;
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5398
|
+
if (defId === "context_workspace") {
|
|
5399
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5400
|
+
const workspaceId = String(inputValues.workspaceId || "current").trim();
|
|
5401
|
+
const access = String(inputValues.access || "read-write").trim().toLowerCase();
|
|
5402
|
+
if (!["read-only", "read-write"].includes(access)) {
|
|
5403
|
+
throw new Error(`context.workspace ${nodeId} access must be read-only or read-write`);
|
|
5404
|
+
}
|
|
5405
|
+
const catalog = new Map(listConfiguredWorkspaces(root, scopedRoot, userCtx).map((entry) => [String(entry.id || ""), entry]));
|
|
5406
|
+
const selected = catalog.get(workspaceId);
|
|
5407
|
+
if (!selected) throw new Error(`context.workspace ${nodeId} cannot resolve Workspace ID ${workspaceId || "(empty)"}`);
|
|
5408
|
+
if (selected.exists === false) throw new Error(`context.workspace ${nodeId} Workspace path is not ready: ${workspaceId}`);
|
|
5409
|
+
const workspaceRoot = path.resolve(selected.path);
|
|
5410
|
+
const value = JSON.stringify({
|
|
5411
|
+
version: 1,
|
|
5412
|
+
workspaceId,
|
|
5413
|
+
access,
|
|
5414
|
+
label: String(selected.label || selected.id || path.basename(workspaceRoot)),
|
|
5415
|
+
cwd: workspaceRoot,
|
|
5416
|
+
workspaceRoot,
|
|
5417
|
+
pipelineWorkspace: path.resolve(scopedRoot),
|
|
5418
|
+
previous: null,
|
|
5419
|
+
});
|
|
5420
|
+
let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", value);
|
|
5421
|
+
graph.instances[nodeId] = nextInstance;
|
|
5422
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5423
|
+
emit({ type: "node-done", nodeId, definitionId: defId, workspaceId, access });
|
|
5424
|
+
continue;
|
|
5425
|
+
}
|
|
5426
|
+
|
|
5427
|
+
if (defId === "context_bundle") {
|
|
5428
|
+
const bundle = {
|
|
5429
|
+
version: 1,
|
|
5430
|
+
knowledgeContext: workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot),
|
|
5431
|
+
skillsContext: workspaceSemanticInputText(graph, nodeId, outputs, "skillsContext", scopedRoot),
|
|
5432
|
+
workspaceContext: workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot),
|
|
5433
|
+
mcpContext: workspaceSemanticInputText(graph, nodeId, outputs, "mcpContext", scopedRoot),
|
|
5434
|
+
};
|
|
5435
|
+
if (![bundle.knowledgeContext, bundle.skillsContext, bundle.workspaceContext, bundle.mcpContext].some((item) => String(item || "").trim())) {
|
|
5436
|
+
throw new Error(`context.bundle ${nodeId} requires at least one connected Context resource`);
|
|
5437
|
+
}
|
|
5438
|
+
const value = JSON.stringify(bundle);
|
|
5439
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "context", value);
|
|
5440
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5441
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5442
|
+
continue;
|
|
5443
|
+
}
|
|
5444
|
+
|
|
5180
5445
|
if (defId === "control_subflow_call") {
|
|
5181
5446
|
const subflowId = String(instance.subflowId || "").trim();
|
|
5182
5447
|
const subflow = graph?.subflows?.[subflowId];
|
|
5183
5448
|
if (!subflow) throw new Error(`flow.call ${nodeId} references missing subflow ${subflowId || "(empty)"}`);
|
|
5184
|
-
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5449
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot, { includeContext: true });
|
|
5185
5450
|
const { resultValues, callFrameId } = await workspaceRunSubflowFrame({
|
|
5186
5451
|
root,
|
|
5187
5452
|
scopedRoot,
|
|
@@ -5297,6 +5562,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5297
5562
|
|
|
5298
5563
|
if (defId === "control_while") {
|
|
5299
5564
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5565
|
+
const loopContext = workspaceSemanticInputText(graph, nodeId, outputs, "context", scopedRoot);
|
|
5300
5566
|
const config = normalizeControlWhileConfig(inputValues);
|
|
5301
5567
|
const stepScript = String(instance.script || instance.body || "").trim();
|
|
5302
5568
|
const stepScriptRef = String(instance.scriptRef || "").trim();
|
|
@@ -5392,6 +5658,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5392
5658
|
parentDefinitionId: defId,
|
|
5393
5659
|
subflowId: whileSubflows.conditionId,
|
|
5394
5660
|
inputValues: {
|
|
5661
|
+
...(loopContext && whileSubflows.condition.inputs?.context ? { context: loopContext } : {}),
|
|
5395
5662
|
state: stateText,
|
|
5396
5663
|
iteration: String(iteration),
|
|
5397
5664
|
idempotencyKey,
|
|
@@ -5417,6 +5684,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5417
5684
|
parentDefinitionId: defId,
|
|
5418
5685
|
subflowId: whileSubflows.bodyId,
|
|
5419
5686
|
inputValues: {
|
|
5687
|
+
...(loopContext && whileSubflows.body.inputs?.context ? { context: loopContext } : {}),
|
|
5420
5688
|
state: stateText,
|
|
5421
5689
|
iteration: String(iteration),
|
|
5422
5690
|
idempotencyKey,
|
|
@@ -5811,9 +6079,17 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5811
6079
|
const worktreeInputSlot = (Array.isArray(instance.input) ? instance.input : [])
|
|
5812
6080
|
.find((slot) => String(slot?.name || "") === "worktreePath") || null;
|
|
5813
6081
|
const rawWorktreePath = workspaceSlotValue(worktreeInputSlot || workspaceSlotByName(instance, "worktreePath")).trim();
|
|
6082
|
+
const retainedWorktreePath = workspaceSlotValue(
|
|
6083
|
+
(Array.isArray(instance.output) ? instance.output : [])
|
|
6084
|
+
.find((slot) => String(slot?.name || "") === "worktreePath"),
|
|
6085
|
+
).trim();
|
|
5814
6086
|
const worktreePath = rawWorktreePath
|
|
5815
6087
|
? workspaceResolvePath(cwd, rawWorktreePath)
|
|
5816
|
-
: (gitContext?.worktreePath
|
|
6088
|
+
: (gitContext?.worktreePath
|
|
6089
|
+
? path.resolve(gitContext.worktreePath)
|
|
6090
|
+
: (retainedWorktreePath
|
|
6091
|
+
? path.resolve(retainedWorktreePath)
|
|
6092
|
+
: workspaceDefaultWorktreePath(scopedRoot, runtimeRunId, nodeId, repoPath, branch)));
|
|
5817
6093
|
const previousCwd = cwd;
|
|
5818
6094
|
const force = ["true", "1", "yes", "on"].includes(workspaceSlotValue(workspaceSlotByName(instance, "force")).trim().toLowerCase());
|
|
5819
6095
|
const pruneMissingRaw = workspaceSlotValue(workspaceSlotByName(instance, "pruneMissing")).trim().toLowerCase();
|
|
@@ -5825,6 +6101,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5825
6101
|
repoPath: result.repoRoot,
|
|
5826
6102
|
worktreePath: result.worktreePath,
|
|
5827
6103
|
});
|
|
6104
|
+
persistRunManifest("running");
|
|
5828
6105
|
}
|
|
5829
6106
|
const outGitContext = buildGitContext({
|
|
5830
6107
|
repoPath: result.repoRoot,
|
|
@@ -6067,10 +6344,17 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6067
6344
|
const relevantInputs = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
6068
6345
|
workspaceAssertRequiredInputs(instance.body || "", inputValues, nodeId);
|
|
6069
6346
|
const upstreamText = workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputs.placeholders, scopedRoot);
|
|
6070
|
-
const
|
|
6347
|
+
const contextBundle = workspaceNodeContextBundle(graph, nodeId, outputs, scopedRoot);
|
|
6348
|
+
const upstreamSkillBlocks = mergeWorkspaceSkillBlocks(
|
|
6349
|
+
workspaceUpstreamSkillBlocks(graph, nodeId, outputs),
|
|
6350
|
+
String(contextBundle.skillsContext || ""),
|
|
6351
|
+
);
|
|
6071
6352
|
const ownSkillBlock = isContextRunNode ? loadSkillsBlockForKeys(selectedSkillKeysFromConfigSlots(instance)) : "";
|
|
6072
6353
|
const promptSkillsBlock = mergeWorkspaceSkillBlocks(ownSkillBlock, upstreamSkillBlocks);
|
|
6073
|
-
const promptMcpBlock =
|
|
6354
|
+
const promptMcpBlock = mergeWorkspaceSkillBlocks(
|
|
6355
|
+
workspaceUpstreamMcpBlocks(graph, nodeId, outputs),
|
|
6356
|
+
String(contextBundle.mcpContext || ""),
|
|
6357
|
+
);
|
|
6074
6358
|
const resultOutputSpec = workspaceResultOutputSpec(graph, nodeId);
|
|
6075
6359
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6076
6360
|
scopedRoot,
|
|
@@ -6095,7 +6379,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6095
6379
|
// Best-effort debug artifact only.
|
|
6096
6380
|
}
|
|
6097
6381
|
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
6098
|
-
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
|
|
6382
|
+
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd, contextBundle);
|
|
6099
6383
|
if (!isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true)) {
|
|
6100
6384
|
const defaultWorkspaceBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
|
|
6101
6385
|
if (!workspaceContextBlock) {
|
|
@@ -6229,9 +6513,47 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6229
6513
|
outputFiles: normalizedAgentOutput.outputFiles || [],
|
|
6230
6514
|
});
|
|
6231
6515
|
}
|
|
6516
|
+
} catch (error) {
|
|
6517
|
+
runFailure = error;
|
|
6518
|
+
throw error;
|
|
6232
6519
|
} finally {
|
|
6233
|
-
|
|
6234
|
-
|
|
6520
|
+
const retainForResume = Boolean(deferred || runtimePauseNodeIds.length);
|
|
6521
|
+
const trackedWorktrees = autoCleanupWorktrees.map((entry) => ({ ...entry }));
|
|
6522
|
+
let cleanup = { cleaned: [], preserved: [] };
|
|
6523
|
+
if (retainForResume) {
|
|
6524
|
+
emit({
|
|
6525
|
+
type: "status",
|
|
6526
|
+
line: `Run workspace retained for resume${trackedWorktrees.length ? ` (${trackedWorktrees.length} worktree)` : ""}`,
|
|
6527
|
+
runId: runtimeRunId,
|
|
6528
|
+
});
|
|
6529
|
+
persistRunManifest("waiting", {
|
|
6530
|
+
worktrees: trackedWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
6531
|
+
waitingAt: new Date().toISOString(),
|
|
6532
|
+
});
|
|
6533
|
+
autoCleanupWorktrees.splice(0, autoCleanupWorktrees.length);
|
|
6534
|
+
} else {
|
|
6535
|
+
cleanup = workspaceCleanupAutoWorktrees(autoCleanupWorktrees, graph, emit, { force: false });
|
|
6536
|
+
const cleanedSet = new Set(cleanup.cleaned.map((item) => path.resolve(item)));
|
|
6537
|
+
const preservedByPath = new Map(cleanup.preserved.map((item) => [path.resolve(item.worktreePath), item]));
|
|
6538
|
+
persistRunManifest(runFailure ? "failed" : "completed", {
|
|
6539
|
+
worktrees: trackedWorktrees.map((entry) => {
|
|
6540
|
+
const target = path.resolve(entry.worktreePath);
|
|
6541
|
+
if (cleanedSet.has(target)) return { ...entry, removed: true, removedAt: new Date().toISOString(), reason: "" };
|
|
6542
|
+
if (preservedByPath.has(target)) return { ...entry, removed: false, reason: preservedByPath.get(target).reason };
|
|
6543
|
+
return entry;
|
|
6544
|
+
}),
|
|
6545
|
+
finishedAt: new Date().toISOString(),
|
|
6546
|
+
error: runFailure ? (runFailure?.message || String(runFailure)) : "",
|
|
6547
|
+
resourcesPreserved: cleanup.preserved.length > 0,
|
|
6548
|
+
});
|
|
6549
|
+
}
|
|
6550
|
+
const protectedWorktrees = retainForResume ? trackedWorktrees : cleanup.preserved;
|
|
6551
|
+
const protectsRunTmpRoot = protectedWorktrees.some((entry) => workspacePathInside(runTmpRoot, entry.worktreePath));
|
|
6552
|
+
if (protectsRunTmpRoot) {
|
|
6553
|
+
emit({ type: "status", line: `Workspace tmp kept because it contains a retained worktree: ${runTmpRoot}` });
|
|
6554
|
+
} else {
|
|
6555
|
+
workspaceCleanupTmpRoot(runTmpRoot, userCtx, emit);
|
|
6556
|
+
}
|
|
6235
6557
|
}
|
|
6236
6558
|
const finalPauseNodeIds = Array.from(new Set([...pauseNodeIds, ...runtimePauseNodeIds]));
|
|
6237
6559
|
if (!deferred && finalPauseNodeIds.length > 0) {
|
|
@@ -6444,6 +6766,14 @@ export function upsertWorkspaceDeferredRun(meta = {}, deferred = {}) {
|
|
|
6444
6766
|
nodeId: String(deferred.nodeId || meta.nodeId || previous.nodeId || ""),
|
|
6445
6767
|
label: String(meta.label || previous.label || "Workspace Run"),
|
|
6446
6768
|
plannedNodeIds: Array.isArray(meta.plannedNodeIds) ? meta.plannedNodeIds.map(String) : (previous.plannedNodeIds || []),
|
|
6769
|
+
workspaceRoot: String(meta.workspaceRoot || previous.workspaceRoot || ""),
|
|
6770
|
+
marketplaceResources: Array.isArray(meta.marketplaceResources)
|
|
6771
|
+
? meta.marketplaceResources.map((item) => ({
|
|
6772
|
+
kind: String(item?.kind || ""),
|
|
6773
|
+
id: String(item?.id || ""),
|
|
6774
|
+
version: String(item?.version || ""),
|
|
6775
|
+
})).filter((item) => item.kind && item.id && item.version)
|
|
6776
|
+
: (previous.marketplaceResources || []),
|
|
6447
6777
|
startedAt: Number(meta.startedAt || previous.startedAt || now),
|
|
6448
6778
|
scheduled: meta.scheduled === true || previous.scheduled === true,
|
|
6449
6779
|
scheduleKey: String(meta.scheduleKey || previous.scheduleKey || ""),
|
|
@@ -6943,6 +7273,8 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6943
7273
|
plannedNodeIds,
|
|
6944
7274
|
startedAt: Date.now(),
|
|
6945
7275
|
scheduled: true,
|
|
7276
|
+
workspaceRoot: root,
|
|
7277
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, graph, plannedNodeIds),
|
|
6946
7278
|
};
|
|
6947
7279
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
6948
7280
|
appendWorkspaceRunStarted(runEntry);
|
|
@@ -6997,7 +7329,12 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6997
7329
|
return;
|
|
6998
7330
|
}
|
|
6999
7331
|
const endedAt = Date.now();
|
|
7000
|
-
appendWorkspaceRunFinished({
|
|
7332
|
+
appendWorkspaceRunFinished({
|
|
7333
|
+
...runEntry,
|
|
7334
|
+
endedAt,
|
|
7335
|
+
durationMs: endedAt - runEntry.startedAt,
|
|
7336
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, graph, result.order || Array.from(touchedIds)),
|
|
7337
|
+
}, "success");
|
|
7001
7338
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
7002
7339
|
endedAt,
|
|
7003
7340
|
durationMs: endedAt - runEntry.startedAt,
|