@fieldwangai/agentflow 0.1.160 → 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/auth.mjs +231 -14
- package/bin/lib/catalog-flows.mjs +2 -0
- package/bin/lib/cli-auth-page.mjs +103 -0
- 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/skill-runtime.mjs +6 -0
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/table.mjs +2 -3
- package/bin/lib/ui-server.mjs +471 -84
- package/bin/lib/workspace-draft.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +484 -4
- package/bin/lib/workspace-server.mjs +373 -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 +12 -5
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/pipelines/new/workspace.flow.js +1 -1
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-Bfo9Ythw.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 +5 -1
- package/shared/slot-types.js +1 -0
- package/skills/agentflow-author-flow/SKILL.md +81 -8
- package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
- package/skills/agentflow-cli/SKILL.md +179 -27
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14879 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +38 -0
- 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_agent_toBool.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +131 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +74 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
- package/skills/agentflow-cli/runtime/package.json +6 -0
- package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +457 -28
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
- package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
- package/skills/agentflow-flow-dsl/SKILL.md +73 -8
- package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -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-flow-edit-node-fields/SKILL.md +3 -3
- package/skills/agentflow-flow-recipes/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +5 -13
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
- package/builtin/web-ui/dist/assets/index-XbeI5foV.js +0 -872
- package/builtin/web-ui/dist/assets/index-e1omCEau.css +0 -1
- package/skills/agentflow-workspace-graph/SKILL.md +0 -85
|
@@ -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";
|
|
@@ -44,6 +45,7 @@ import { FLOW_SOURCE_FILENAME, WORKSPACE_GRAPH_FILENAME, WorkspaceFlowParseError
|
|
|
44
45
|
import { createWorkspaceRunController, terminateWorkspaceChild } from "./workspace-run-controller.mjs";
|
|
45
46
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession } from "./workspace-run-logs.mjs";
|
|
46
47
|
import { splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
48
|
+
import { isWorkspaceDraftDir } from "./workspace-draft.mjs";
|
|
47
49
|
import { getPipelineFiles } from "./workspace-tree.mjs";
|
|
48
50
|
import { spawn } from "child_process";
|
|
49
51
|
import crypto from "crypto";
|
|
@@ -795,6 +797,71 @@ export function readWorkspacesFromPath(p, userCtx = {}) {
|
|
|
795
797
|
}
|
|
796
798
|
}
|
|
797
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
|
+
|
|
798
865
|
export function workspaceRepoUrlWithCredential(repoUrl = "", credential = "") {
|
|
799
866
|
const token = String(credential || "").trim();
|
|
800
867
|
if (!token) return String(repoUrl || "").trim();
|
|
@@ -855,6 +922,10 @@ export function appendWorkspaceRunFinished(record, status) {
|
|
|
855
922
|
durationMs: Math.max(0, Number(record.durationMs || (Number(record.endedAt || Date.now()) - Number(record.startedAt || record.at || Date.now())))),
|
|
856
923
|
status,
|
|
857
924
|
});
|
|
925
|
+
recordMarketplaceRunUsage(record.workspaceRoot || record.root || "", record.marketplaceResources || [], {
|
|
926
|
+
...record,
|
|
927
|
+
status,
|
|
928
|
+
});
|
|
858
929
|
}
|
|
859
930
|
|
|
860
931
|
function normalizeWorkspaceUsageRecord(parsed, source = "workspace-run") {
|
|
@@ -1033,7 +1104,7 @@ function normalizeDisplayShareLayout(layout, fallback = "canvas") {
|
|
|
1033
1104
|
return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
|
|
1034
1105
|
}
|
|
1035
1106
|
|
|
1036
|
-
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" }) {
|
|
1037
1108
|
const shares = readDisplayShares();
|
|
1038
1109
|
let id = createDisplayShareId();
|
|
1039
1110
|
while (shares[id]) id = createDisplayShareId();
|
|
@@ -1049,6 +1120,7 @@ export function createDisplayShareRecord({ userId, flowId, flowSource, archived,
|
|
|
1049
1120
|
title: String(title || "").trim() || "AgentFlow Display",
|
|
1050
1121
|
layout: normalizeDisplayShareLayout(layout, "canvas"),
|
|
1051
1122
|
nodeIds: Array.isArray(nodeIds) ? nodeIds : [],
|
|
1123
|
+
visibility: String(visibility || "").trim().toLowerCase() === "private" ? "private" : "public",
|
|
1052
1124
|
createdAt: now,
|
|
1053
1125
|
updatedAt: now,
|
|
1054
1126
|
expiresAt: expiry.expiresAt,
|
|
@@ -1491,6 +1563,7 @@ export function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {})
|
|
|
1491
1563
|
requestedFlowSource: flowSource,
|
|
1492
1564
|
workspaceId: collaboration?.id || workspaceId,
|
|
1493
1565
|
archived,
|
|
1566
|
+
draft: isWorkspaceDraftDir(result.path),
|
|
1494
1567
|
collaboration,
|
|
1495
1568
|
collaborationAccess: workspaceCollaborationAccess(collaboration, opts.userId),
|
|
1496
1569
|
};
|
|
@@ -3173,7 +3246,7 @@ function workspaceTargetSlotForEdge(graph, edge) {
|
|
|
3173
3246
|
function isWorkspaceSemanticInputSlot(slot) {
|
|
3174
3247
|
const name = String(slot?.name || "");
|
|
3175
3248
|
const type = String(slot?.type || "");
|
|
3176
|
-
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";
|
|
3177
3250
|
}
|
|
3178
3251
|
|
|
3179
3252
|
function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
|
|
@@ -3228,8 +3301,9 @@ function workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputNames =
|
|
|
3228
3301
|
return workspaceOutputSlotValueForEdge(graph, outputs, contentEdge, scopedRoot);
|
|
3229
3302
|
}
|
|
3230
3303
|
|
|
3231
|
-
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
3304
|
+
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "", options = {}) {
|
|
3232
3305
|
const values = {};
|
|
3306
|
+
const includeContext = options?.includeContext === true;
|
|
3233
3307
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
3234
3308
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
3235
3309
|
const target = instances[String(nodeId || "")] || {};
|
|
@@ -3239,13 +3313,13 @@ function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
|
3239
3313
|
const index = workspaceHandleIndex(edge?.targetHandle, "input");
|
|
3240
3314
|
const slot = inputSlots[index] || null;
|
|
3241
3315
|
const name = String(slot?.name || "").trim();
|
|
3242
|
-
if (!name || isWorkspaceSemanticInputSlot(slot)) continue;
|
|
3316
|
+
if (!name || (isWorkspaceSemanticInputSlot(slot) && !(includeContext && name === "context"))) continue;
|
|
3243
3317
|
const value = workspaceOutputSlotValueForEdge(graph, outputs, edge, scopedRoot);
|
|
3244
3318
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3245
3319
|
}
|
|
3246
3320
|
for (const slot of inputSlots) {
|
|
3247
3321
|
const name = String(slot?.name || "").trim();
|
|
3248
|
-
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;
|
|
3249
3323
|
const value = workspaceSlotValue(slot);
|
|
3250
3324
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3251
3325
|
}
|
|
@@ -3942,7 +4016,9 @@ function selectedSkillKeysFromInstance(instance) {
|
|
|
3942
4016
|
|
|
3943
4017
|
function selectedSkillKeysFromConfigSlots(instance) {
|
|
3944
4018
|
const slots = [...(Array.isArray(instance?.input) ? instance.input : []), ...(Array.isArray(instance?.output) ? instance.output : [])];
|
|
3945
|
-
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");
|
|
3946
4022
|
return parseWorkspaceSkillKeys(workspaceSlotValue(slot) || "");
|
|
3947
4023
|
}
|
|
3948
4024
|
|
|
@@ -3996,6 +4072,25 @@ function workspaceSemanticInputText(graph, nodeId, outputs, name, scopedRoot = "
|
|
|
3996
4072
|
return workspaceSlotValue(workspaceSlotByName(instance, targetName));
|
|
3997
4073
|
}
|
|
3998
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
|
+
|
|
3999
4094
|
function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
|
|
4000
4095
|
const raw = String(text || "").trim();
|
|
4001
4096
|
if (!raw) return null;
|
|
@@ -4022,20 +4117,30 @@ function workspaceLooksLikeKnowledgePath(value) {
|
|
|
4022
4117
|
}
|
|
4023
4118
|
|
|
4024
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
|
+
}
|
|
4025
4128
|
if (!source || typeof source !== "object" || Array.isArray(source)) return null;
|
|
4129
|
+
const ref = String(source.ref || source.binding || "").trim();
|
|
4026
4130
|
const rawPath = String(source.path || source.repoPath || source.cwd || source.workspaceRoot || "").trim();
|
|
4027
|
-
if (!workspaceLooksLikeKnowledgePath(rawPath)) return null;
|
|
4028
|
-
const resolvedPath = workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath;
|
|
4131
|
+
if (!workspaceLooksLikeKnowledgePath(rawPath) && !ref) return null;
|
|
4132
|
+
const resolvedPath = rawPath ? (workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath) : "";
|
|
4029
4133
|
return {
|
|
4030
|
-
id: String(source.id || source.mountPath || source.label || path.basename(resolvedPath) || "").trim(),
|
|
4031
|
-
label: String(source.label || source.id || source.mountPath || path.basename(resolvedPath) || "知识库").trim(),
|
|
4032
|
-
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",
|
|
4033
4137
|
type: String(source.type || "").trim(),
|
|
4034
4138
|
path: resolvedPath,
|
|
4035
4139
|
repoPath: resolvedPath,
|
|
4036
4140
|
mountPath: String(source.mountPath || "").trim(),
|
|
4037
4141
|
repoUrl: String(source.repoUrl || "").trim(),
|
|
4038
4142
|
branch: String(source.branch || "").trim(),
|
|
4143
|
+
ref,
|
|
4039
4144
|
readonly: source.readonly !== false,
|
|
4040
4145
|
};
|
|
4041
4146
|
}
|
|
@@ -4071,7 +4176,7 @@ export function workspaceKnowledgeSourcesFromText(text, baseCwd = "", scopedRoot
|
|
|
4071
4176
|
}
|
|
4072
4177
|
|
|
4073
4178
|
function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
4074
|
-
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) : [];
|
|
4075
4180
|
if (!valid.length) return "";
|
|
4076
4181
|
const lines = [
|
|
4077
4182
|
"## 知识库上下文",
|
|
@@ -4085,6 +4190,7 @@ function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
|
4085
4190
|
lines.push(`${index + 1}. ${label}`);
|
|
4086
4191
|
if (source.kind) lines.push(` - 类型:${source.kind}${source.type ? `/${source.type}` : ""}`);
|
|
4087
4192
|
if (sourcePath) lines.push(` - 路径:\`${sourcePath}\``);
|
|
4193
|
+
if (source.ref) lines.push(` - 绑定:\`${source.ref}\``);
|
|
4088
4194
|
if (source.mountPath) lines.push(` - 挂载目录:${source.mountPath}`);
|
|
4089
4195
|
if (source.repoUrl) lines.push(` - Git URL:${source.repoUrl}`);
|
|
4090
4196
|
if (source.branch) lines.push(` - 分支:${source.branch}`);
|
|
@@ -4098,10 +4204,11 @@ function workspaceDedupeKnowledgeSources(sources = []) {
|
|
|
4098
4204
|
for (const source of Array.isArray(sources) ? sources : []) {
|
|
4099
4205
|
if (!source || typeof source !== "object") continue;
|
|
4100
4206
|
const key = [
|
|
4101
|
-
path.resolve(String(source.path || source.repoPath
|
|
4207
|
+
source.path || source.repoPath ? path.resolve(String(source.path || source.repoPath)) : "",
|
|
4102
4208
|
String(source.mountPath || ""),
|
|
4103
4209
|
String(source.repoUrl || ""),
|
|
4104
4210
|
String(source.branch || ""),
|
|
4211
|
+
String(source.ref || ""),
|
|
4105
4212
|
].join("\n");
|
|
4106
4213
|
if (seen.has(key)) continue;
|
|
4107
4214
|
seen.add(key);
|
|
@@ -4147,16 +4254,18 @@ function workspaceGlobalKnowledgeSources(graph, scopedRoot = "", logicalCwd = ""
|
|
|
4147
4254
|
);
|
|
4148
4255
|
}
|
|
4149
4256
|
|
|
4150
|
-
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
|
|
4257
|
+
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "", contextBundle = {}) {
|
|
4151
4258
|
const root = scopedRoot ? path.resolve(scopedRoot) : "";
|
|
4152
4259
|
const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
|
|
4153
|
-
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4260
|
+
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4261
|
+
|| String(contextBundle?.knowledgeContext || "");
|
|
4154
4262
|
let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || root, scopedRoot);
|
|
4155
4263
|
knowledgeSources = workspaceDedupeKnowledgeSources([
|
|
4156
4264
|
...workspaceGlobalKnowledgeSources(graph, scopedRoot, logicalCwd, nodeId),
|
|
4157
4265
|
...knowledgeSources,
|
|
4158
4266
|
]);
|
|
4159
|
-
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4267
|
+
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4268
|
+
|| String(contextBundle?.workspaceContext || "");
|
|
4160
4269
|
let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
|
|
4161
4270
|
if (!knowledgeSources.length && workspaceContext?.cwd) {
|
|
4162
4271
|
knowledgeSources = workspaceKnowledgeSourcesFromText(JSON.stringify([workspaceContext]), cwd || root, scopedRoot);
|
|
@@ -4382,7 +4491,7 @@ function workspaceDefaultGitRepoRoot(scopedRoot, _userCtx = {}) {
|
|
|
4382
4491
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "git-repos");
|
|
4383
4492
|
}
|
|
4384
4493
|
|
|
4385
|
-
function workspaceDefaultWorktreePath(
|
|
4494
|
+
function workspaceDefaultWorktreePath(scopedRoot, runId, nodeId, repoPath, branch = "") {
|
|
4386
4495
|
const repoRoot = path.resolve(repoPath);
|
|
4387
4496
|
const repoName = sanitizeWorktreeName(path.basename(repoRoot));
|
|
4388
4497
|
const branchName = String(branch || "").trim();
|
|
@@ -4400,9 +4509,12 @@ function workspaceDefaultWorktreePath(runTmpRoot, nodeId, repoPath, branch = "")
|
|
|
4400
4509
|
: "HEAD";
|
|
4401
4510
|
}
|
|
4402
4511
|
return path.join(
|
|
4403
|
-
path.resolve(
|
|
4404
|
-
"
|
|
4512
|
+
path.resolve(scopedRoot),
|
|
4513
|
+
".workspace",
|
|
4514
|
+
"agentflow",
|
|
4515
|
+
"run-workspaces",
|
|
4405
4516
|
workspaceSanitizeTmpSegment(nodeId, "node"),
|
|
4517
|
+
workspaceSanitizeTmpSegment(runId, "run"),
|
|
4406
4518
|
repoName,
|
|
4407
4519
|
sanitizeWorktreeName(refLabel),
|
|
4408
4520
|
);
|
|
@@ -4442,15 +4554,18 @@ function workspaceMarkAutoWorktreeCleaned(graph, entry) {
|
|
|
4442
4554
|
return true;
|
|
4443
4555
|
}
|
|
4444
4556
|
|
|
4445
|
-
function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
4557
|
+
function workspaceCleanupAutoWorktrees(list, graph, emit, { force = false } = {}) {
|
|
4558
|
+
const cleaned = [];
|
|
4559
|
+
const preserved = [];
|
|
4446
4560
|
for (const entry of [...list].reverse()) {
|
|
4447
4561
|
try {
|
|
4448
4562
|
const result = unloadGitWorktree({
|
|
4449
4563
|
repoPath: entry.repoPath,
|
|
4450
4564
|
worktreePath: entry.worktreePath,
|
|
4451
|
-
force
|
|
4565
|
+
force,
|
|
4452
4566
|
prune: true,
|
|
4453
4567
|
});
|
|
4568
|
+
cleaned.push(result.worktreePath);
|
|
4454
4569
|
emit({
|
|
4455
4570
|
type: "natural",
|
|
4456
4571
|
kind: "status",
|
|
@@ -4461,15 +4576,74 @@ function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
|
4461
4576
|
emit({ type: "graph", nodeId: entry.nodeId, graph });
|
|
4462
4577
|
}
|
|
4463
4578
|
} catch (e) {
|
|
4579
|
+
preserved.push({ ...entry, reason: e?.message || String(e) });
|
|
4464
4580
|
emit({
|
|
4465
4581
|
type: "natural",
|
|
4466
4582
|
kind: "warning",
|
|
4467
4583
|
nodeId: entry.nodeId,
|
|
4468
|
-
text:
|
|
4584
|
+
text: `运行 worktree 已保留:${entry.worktreePath}\n原因:${e?.message || String(e)}`,
|
|
4469
4585
|
});
|
|
4470
4586
|
}
|
|
4471
4587
|
}
|
|
4472
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 };
|
|
4473
4647
|
}
|
|
4474
4648
|
|
|
4475
4649
|
function workspaceSanitizeTmpSegment(value, fallback = "node") {
|
|
@@ -5108,10 +5282,27 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5108
5282
|
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
5109
5283
|
const autoCleanupWorktrees = [];
|
|
5110
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() });
|
|
5111
5301
|
const controlBranches = new Map();
|
|
5112
5302
|
const skippedNodes = new Set();
|
|
5113
5303
|
const runtimePauseNodeIds = [];
|
|
5114
5304
|
let deferred = null;
|
|
5305
|
+
let runFailure = null;
|
|
5115
5306
|
const incomingControlEdgesByTarget = new Map();
|
|
5116
5307
|
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
5117
5308
|
const target = String(edge?.target || "");
|
|
@@ -5175,11 +5366,87 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5175
5366
|
continue;
|
|
5176
5367
|
}
|
|
5177
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
|
+
|
|
5178
5445
|
if (defId === "control_subflow_call") {
|
|
5179
5446
|
const subflowId = String(instance.subflowId || "").trim();
|
|
5180
5447
|
const subflow = graph?.subflows?.[subflowId];
|
|
5181
5448
|
if (!subflow) throw new Error(`flow.call ${nodeId} references missing subflow ${subflowId || "(empty)"}`);
|
|
5182
|
-
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5449
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot, { includeContext: true });
|
|
5183
5450
|
const { resultValues, callFrameId } = await workspaceRunSubflowFrame({
|
|
5184
5451
|
root,
|
|
5185
5452
|
scopedRoot,
|
|
@@ -5295,6 +5562,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5295
5562
|
|
|
5296
5563
|
if (defId === "control_while") {
|
|
5297
5564
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5565
|
+
const loopContext = workspaceSemanticInputText(graph, nodeId, outputs, "context", scopedRoot);
|
|
5298
5566
|
const config = normalizeControlWhileConfig(inputValues);
|
|
5299
5567
|
const stepScript = String(instance.script || instance.body || "").trim();
|
|
5300
5568
|
const stepScriptRef = String(instance.scriptRef || "").trim();
|
|
@@ -5390,6 +5658,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5390
5658
|
parentDefinitionId: defId,
|
|
5391
5659
|
subflowId: whileSubflows.conditionId,
|
|
5392
5660
|
inputValues: {
|
|
5661
|
+
...(loopContext && whileSubflows.condition.inputs?.context ? { context: loopContext } : {}),
|
|
5393
5662
|
state: stateText,
|
|
5394
5663
|
iteration: String(iteration),
|
|
5395
5664
|
idempotencyKey,
|
|
@@ -5415,6 +5684,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5415
5684
|
parentDefinitionId: defId,
|
|
5416
5685
|
subflowId: whileSubflows.bodyId,
|
|
5417
5686
|
inputValues: {
|
|
5687
|
+
...(loopContext && whileSubflows.body.inputs?.context ? { context: loopContext } : {}),
|
|
5418
5688
|
state: stateText,
|
|
5419
5689
|
iteration: String(iteration),
|
|
5420
5690
|
idempotencyKey,
|
|
@@ -5809,9 +6079,17 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5809
6079
|
const worktreeInputSlot = (Array.isArray(instance.input) ? instance.input : [])
|
|
5810
6080
|
.find((slot) => String(slot?.name || "") === "worktreePath") || null;
|
|
5811
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();
|
|
5812
6086
|
const worktreePath = rawWorktreePath
|
|
5813
6087
|
? workspaceResolvePath(cwd, rawWorktreePath)
|
|
5814
|
-
: (gitContext?.worktreePath
|
|
6088
|
+
: (gitContext?.worktreePath
|
|
6089
|
+
? path.resolve(gitContext.worktreePath)
|
|
6090
|
+
: (retainedWorktreePath
|
|
6091
|
+
? path.resolve(retainedWorktreePath)
|
|
6092
|
+
: workspaceDefaultWorktreePath(scopedRoot, runtimeRunId, nodeId, repoPath, branch)));
|
|
5815
6093
|
const previousCwd = cwd;
|
|
5816
6094
|
const force = ["true", "1", "yes", "on"].includes(workspaceSlotValue(workspaceSlotByName(instance, "force")).trim().toLowerCase());
|
|
5817
6095
|
const pruneMissingRaw = workspaceSlotValue(workspaceSlotByName(instance, "pruneMissing")).trim().toLowerCase();
|
|
@@ -5823,6 +6101,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5823
6101
|
repoPath: result.repoRoot,
|
|
5824
6102
|
worktreePath: result.worktreePath,
|
|
5825
6103
|
});
|
|
6104
|
+
persistRunManifest("running");
|
|
5826
6105
|
}
|
|
5827
6106
|
const outGitContext = buildGitContext({
|
|
5828
6107
|
repoPath: result.repoRoot,
|
|
@@ -6065,10 +6344,17 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6065
6344
|
const relevantInputs = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
6066
6345
|
workspaceAssertRequiredInputs(instance.body || "", inputValues, nodeId);
|
|
6067
6346
|
const upstreamText = workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputs.placeholders, scopedRoot);
|
|
6068
|
-
const
|
|
6347
|
+
const contextBundle = workspaceNodeContextBundle(graph, nodeId, outputs, scopedRoot);
|
|
6348
|
+
const upstreamSkillBlocks = mergeWorkspaceSkillBlocks(
|
|
6349
|
+
workspaceUpstreamSkillBlocks(graph, nodeId, outputs),
|
|
6350
|
+
String(contextBundle.skillsContext || ""),
|
|
6351
|
+
);
|
|
6069
6352
|
const ownSkillBlock = isContextRunNode ? loadSkillsBlockForKeys(selectedSkillKeysFromConfigSlots(instance)) : "";
|
|
6070
6353
|
const promptSkillsBlock = mergeWorkspaceSkillBlocks(ownSkillBlock, upstreamSkillBlocks);
|
|
6071
|
-
const promptMcpBlock =
|
|
6354
|
+
const promptMcpBlock = mergeWorkspaceSkillBlocks(
|
|
6355
|
+
workspaceUpstreamMcpBlocks(graph, nodeId, outputs),
|
|
6356
|
+
String(contextBundle.mcpContext || ""),
|
|
6357
|
+
);
|
|
6072
6358
|
const resultOutputSpec = workspaceResultOutputSpec(graph, nodeId);
|
|
6073
6359
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6074
6360
|
scopedRoot,
|
|
@@ -6093,7 +6379,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6093
6379
|
// Best-effort debug artifact only.
|
|
6094
6380
|
}
|
|
6095
6381
|
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
6096
|
-
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
|
|
6382
|
+
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd, contextBundle);
|
|
6097
6383
|
if (!isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true)) {
|
|
6098
6384
|
const defaultWorkspaceBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
|
|
6099
6385
|
if (!workspaceContextBlock) {
|
|
@@ -6227,9 +6513,47 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6227
6513
|
outputFiles: normalizedAgentOutput.outputFiles || [],
|
|
6228
6514
|
});
|
|
6229
6515
|
}
|
|
6516
|
+
} catch (error) {
|
|
6517
|
+
runFailure = error;
|
|
6518
|
+
throw error;
|
|
6230
6519
|
} finally {
|
|
6231
|
-
|
|
6232
|
-
|
|
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
|
+
}
|
|
6233
6557
|
}
|
|
6234
6558
|
const finalPauseNodeIds = Array.from(new Set([...pauseNodeIds, ...runtimePauseNodeIds]));
|
|
6235
6559
|
if (!deferred && finalPauseNodeIds.length > 0) {
|
|
@@ -6442,6 +6766,14 @@ export function upsertWorkspaceDeferredRun(meta = {}, deferred = {}) {
|
|
|
6442
6766
|
nodeId: String(deferred.nodeId || meta.nodeId || previous.nodeId || ""),
|
|
6443
6767
|
label: String(meta.label || previous.label || "Workspace Run"),
|
|
6444
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 || []),
|
|
6445
6777
|
startedAt: Number(meta.startedAt || previous.startedAt || now),
|
|
6446
6778
|
scheduled: meta.scheduled === true || previous.scheduled === true,
|
|
6447
6779
|
scheduleKey: String(meta.scheduleKey || previous.scheduleKey || ""),
|
|
@@ -6726,6 +7058,10 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
|
|
|
6726
7058
|
);
|
|
6727
7059
|
if (sameSharedFlow || key.startsWith(prefix)) delete schedules[key];
|
|
6728
7060
|
}
|
|
7061
|
+
if (isWorkspaceDraftDir(scoped?.root || "")) {
|
|
7062
|
+
writeWorkspaceScheduleRegistry({ version: 1, schedules });
|
|
7063
|
+
return [];
|
|
7064
|
+
}
|
|
6729
7065
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
6730
7066
|
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
6731
7067
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
@@ -6937,6 +7273,8 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6937
7273
|
plannedNodeIds,
|
|
6938
7274
|
startedAt: Date.now(),
|
|
6939
7275
|
scheduled: true,
|
|
7276
|
+
workspaceRoot: root,
|
|
7277
|
+
marketplaceResources: marketplaceResourcesForRun(scoped.root, graph, plannedNodeIds),
|
|
6940
7278
|
};
|
|
6941
7279
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
6942
7280
|
appendWorkspaceRunStarted(runEntry);
|
|
@@ -6991,7 +7329,12 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6991
7329
|
return;
|
|
6992
7330
|
}
|
|
6993
7331
|
const endedAt = Date.now();
|
|
6994
|
-
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");
|
|
6995
7338
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
6996
7339
|
endedAt,
|
|
6997
7340
|
durationMs: endedAt - runEntry.startedAt,
|