@fieldwangai/agentflow 0.1.162 → 0.1.164
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 +314 -76
- package/bin/lib/workspace-routes.mjs +396 -5
- package/bin/lib/workspace-run-logs.mjs +2 -0
- package/bin/lib/workspace-server.mjs +698 -48
- 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-CffIx5BY.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CDItaRfX.css +1 -0
- package/builtin/web-ui/dist/assets/index-CWIcfWHO.js +873 -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";
|
|
@@ -41,9 +42,10 @@ import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
|
41
42
|
import { sendWecomAppMarkdown, sendWecomGroupMarkdown } from "./wecom.mjs";
|
|
42
43
|
import { getWorkspaceCollaborationByFlow, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, workspaceCollaborationAccess, workspaceCollaborationSummary } from "./workspace-collaboration.mjs";
|
|
43
44
|
import { FLOW_SOURCE_FILENAME, WORKSPACE_GRAPH_FILENAME, WorkspaceFlowParseError, readWorkspaceGraphFiles, readWorkspaceRunFingerprints, writeWorkspaceGraphFiles } from "./workspace-flow-store.mjs";
|
|
45
|
+
import { workspaceDesignRevision } from "./workspace-graph-merge.mjs";
|
|
44
46
|
import { createWorkspaceRunController, terminateWorkspaceChild } from "./workspace-run-controller.mjs";
|
|
45
47
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession } from "./workspace-run-logs.mjs";
|
|
46
|
-
import { splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
48
|
+
import { mergeWorkspaceState, splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
47
49
|
import { isWorkspaceDraftDir } from "./workspace-draft.mjs";
|
|
48
50
|
import { getPipelineFiles } from "./workspace-tree.mjs";
|
|
49
51
|
import { spawn } from "child_process";
|
|
@@ -707,6 +709,249 @@ export function readWorkspaceGraph(workspaceRoot, marketplaceRoot = "") {
|
|
|
707
709
|
return { path: designPath, graph };
|
|
708
710
|
}
|
|
709
711
|
|
|
712
|
+
const WORKSPACE_RELEASES_REL = path.join(".workspace", "agentflow", "releases");
|
|
713
|
+
const WORKSPACE_RELEASE_REGISTRY = "registry.json";
|
|
714
|
+
const WORKSPACE_RELEASE_MANIFEST = "release.json";
|
|
715
|
+
const WORKSPACE_RELEASE_SKIP_ROOTS = new Set([
|
|
716
|
+
".git",
|
|
717
|
+
"node_modules",
|
|
718
|
+
"outputs",
|
|
719
|
+
"runBuild",
|
|
720
|
+
"workspace.state.json",
|
|
721
|
+
]);
|
|
722
|
+
|
|
723
|
+
function workspaceReleasesRoot(workspaceRoot) {
|
|
724
|
+
return path.join(path.resolve(workspaceRoot), WORKSPACE_RELEASES_REL);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function workspaceReleaseRegistryPath(workspaceRoot) {
|
|
728
|
+
return path.join(workspaceReleasesRoot(workspaceRoot), WORKSPACE_RELEASE_REGISTRY);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function normalizeWorkspaceReleaseRegistry(value = {}) {
|
|
732
|
+
const releases = Array.isArray(value?.releases)
|
|
733
|
+
? value.releases
|
|
734
|
+
.filter((release) => release && /^v[1-9][0-9]*$/.test(String(release.id || "")))
|
|
735
|
+
.map((release) => ({
|
|
736
|
+
id: String(release.id),
|
|
737
|
+
number: Math.max(1, Number(release.number || String(release.id).slice(1)) || 1),
|
|
738
|
+
designRevision: String(release.designRevision || ""),
|
|
739
|
+
createdAt: String(release.createdAt || ""),
|
|
740
|
+
createdBy: String(release.createdBy || ""),
|
|
741
|
+
notes: String(release.notes || ""),
|
|
742
|
+
baseReleaseId: String(release.baseReleaseId || ""),
|
|
743
|
+
}))
|
|
744
|
+
.sort((a, b) => b.number - a.number)
|
|
745
|
+
: [];
|
|
746
|
+
const stableReleaseId = releases.some((release) => release.id === value?.stableReleaseId)
|
|
747
|
+
? String(value.stableReleaseId)
|
|
748
|
+
: "";
|
|
749
|
+
return {
|
|
750
|
+
version: 1,
|
|
751
|
+
stableReleaseId,
|
|
752
|
+
nextNumber: Math.max(
|
|
753
|
+
Number(value?.nextNumber || 1) || 1,
|
|
754
|
+
releases.reduce((max, release) => Math.max(max, release.number + 1), 1),
|
|
755
|
+
),
|
|
756
|
+
releases,
|
|
757
|
+
updatedAt: String(value?.updatedAt || ""),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function readWorkspaceReleaseRegistry(workspaceRoot) {
|
|
762
|
+
const filePath = workspaceReleaseRegistryPath(workspaceRoot);
|
|
763
|
+
try {
|
|
764
|
+
if (!fs.existsSync(filePath)) return normalizeWorkspaceReleaseRegistry();
|
|
765
|
+
return normalizeWorkspaceReleaseRegistry(JSON.parse(fs.readFileSync(filePath, "utf-8")));
|
|
766
|
+
} catch {
|
|
767
|
+
return normalizeWorkspaceReleaseRegistry();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function writeWorkspaceReleaseRegistry(workspaceRoot, registry) {
|
|
772
|
+
const releasesRoot = workspaceReleasesRoot(workspaceRoot);
|
|
773
|
+
fs.mkdirSync(releasesRoot, { recursive: true });
|
|
774
|
+
const filePath = workspaceReleaseRegistryPath(workspaceRoot);
|
|
775
|
+
const tempPath = path.join(releasesRoot, `.registry-${crypto.randomUUID()}.tmp`);
|
|
776
|
+
const normalized = normalizeWorkspaceReleaseRegistry({
|
|
777
|
+
...registry,
|
|
778
|
+
updatedAt: new Date().toISOString(),
|
|
779
|
+
});
|
|
780
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(normalized, null, 2)}\n`, "utf-8");
|
|
781
|
+
fs.renameSync(tempPath, filePath);
|
|
782
|
+
return normalized;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function workspaceReleaseSnapshotRoot(workspaceRoot, releaseId) {
|
|
786
|
+
const id = String(releaseId || "").trim();
|
|
787
|
+
if (!/^v[1-9][0-9]*$/.test(id)) return "";
|
|
788
|
+
return path.join(workspaceReleasesRoot(workspaceRoot), id, "snapshot");
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function workspaceReleaseRuntimeStatePath(workspaceRoot, releaseId) {
|
|
792
|
+
const id = String(releaseId || "").trim();
|
|
793
|
+
if (!/^v[1-9][0-9]*$/.test(id)) return "";
|
|
794
|
+
return path.join(workspaceReleasesRoot(workspaceRoot), id, "runtime", "workspace.state.json");
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function readWorkspaceReleaseRuntimeState(workspaceRoot, releaseId) {
|
|
798
|
+
const filePath = workspaceReleaseRuntimeStatePath(workspaceRoot, releaseId);
|
|
799
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
802
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
803
|
+
} catch {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function writeWorkspaceReleaseRuntimeState(workspaceRoot, releaseId, graph) {
|
|
809
|
+
const filePath = workspaceReleaseRuntimeStatePath(workspaceRoot, releaseId);
|
|
810
|
+
if (!filePath) return;
|
|
811
|
+
const { state } = splitWorkspaceGraph(graph || {});
|
|
812
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
813
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
814
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(state || { version: 1 }, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
815
|
+
fs.renameSync(tempPath, filePath);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function workspaceCopyReleaseSnapshot(sourceRoot, targetRoot, relative = "") {
|
|
819
|
+
const source = relative ? path.join(sourceRoot, relative) : sourceRoot;
|
|
820
|
+
const entries = fs.readdirSync(source, { withFileTypes: true });
|
|
821
|
+
fs.mkdirSync(relative ? path.join(targetRoot, relative) : targetRoot, { recursive: true });
|
|
822
|
+
for (const entry of entries) {
|
|
823
|
+
const nextRelative = relative ? path.join(relative, entry.name) : entry.name;
|
|
824
|
+
const normalized = nextRelative.replace(/\\/g, "/");
|
|
825
|
+
if (!relative && WORKSPACE_RELEASE_SKIP_ROOTS.has(entry.name)) continue;
|
|
826
|
+
if (normalized === ".workspace/agentflow" || normalized.startsWith(".workspace/agentflow/")) continue;
|
|
827
|
+
const sourcePath = path.join(sourceRoot, nextRelative);
|
|
828
|
+
const targetPath = path.join(targetRoot, nextRelative);
|
|
829
|
+
if (entry.isSymbolicLink()) continue;
|
|
830
|
+
if (entry.isDirectory()) {
|
|
831
|
+
workspaceCopyReleaseSnapshot(sourceRoot, targetRoot, nextRelative);
|
|
832
|
+
} else if (entry.isFile()) {
|
|
833
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
834
|
+
try {
|
|
835
|
+
fs.copyFileSync(sourcePath, targetPath, fs.constants.COPYFILE_FICLONE);
|
|
836
|
+
} catch {
|
|
837
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function workspaceReleaseSummary(workspaceRoot, marketplaceRoot = "", graph = null) {
|
|
844
|
+
const registry = readWorkspaceReleaseRegistry(workspaceRoot);
|
|
845
|
+
const currentGraph = graph || readWorkspaceGraph(workspaceRoot, marketplaceRoot).graph;
|
|
846
|
+
const draftRevision = workspaceDesignRevision(currentGraph);
|
|
847
|
+
const stable = registry.releases.find((release) => release.id === registry.stableReleaseId) || null;
|
|
848
|
+
return {
|
|
849
|
+
enabled: Boolean(stable),
|
|
850
|
+
stableReleaseId: stable?.id || "",
|
|
851
|
+
stableRevision: stable?.designRevision || "",
|
|
852
|
+
draftRevision,
|
|
853
|
+
hasDraftChanges: Boolean(stable && stable.designRevision !== draftRevision),
|
|
854
|
+
releases: registry.releases,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export function readWorkspaceReleaseStatus(workspaceRoot, marketplaceRoot = "", graph = null) {
|
|
859
|
+
return workspaceReleaseSummary(workspaceRoot, marketplaceRoot, graph);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export function readWorkspaceStableRelease(workspaceRoot, marketplaceRoot = "") {
|
|
863
|
+
const registry = readWorkspaceReleaseRegistry(workspaceRoot);
|
|
864
|
+
const release = registry.releases.find((item) => item.id === registry.stableReleaseId) || null;
|
|
865
|
+
if (!release) return null;
|
|
866
|
+
const root = workspaceReleaseSnapshotRoot(workspaceRoot, release.id);
|
|
867
|
+
if (!root || !fs.existsSync(root)) return null;
|
|
868
|
+
const designGraph = readWorkspaceGraph(root, marketplaceRoot).graph;
|
|
869
|
+
const graph = mergeWorkspaceState(
|
|
870
|
+
designGraph,
|
|
871
|
+
readWorkspaceReleaseRuntimeState(workspaceRoot, release.id),
|
|
872
|
+
);
|
|
873
|
+
return { release, root, graph };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function publishWorkspaceRelease(workspaceRoot, marketplaceRoot = "", options = {}) {
|
|
877
|
+
const graph = readWorkspaceGraph(workspaceRoot, marketplaceRoot).graph;
|
|
878
|
+
const designRevision = workspaceDesignRevision(graph);
|
|
879
|
+
const expectedRevision = String(options.expectedRevision || "").trim();
|
|
880
|
+
if (expectedRevision && expectedRevision !== designRevision) {
|
|
881
|
+
return {
|
|
882
|
+
error: "Workspace 已更新,请保存并刷新后再发布",
|
|
883
|
+
conflict: "revision-mismatch",
|
|
884
|
+
expectedRevision,
|
|
885
|
+
currentRevision: designRevision,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
const registry = readWorkspaceReleaseRegistry(workspaceRoot);
|
|
889
|
+
const number = registry.nextNumber;
|
|
890
|
+
const releaseId = `v${number}`;
|
|
891
|
+
const releasesRoot = workspaceReleasesRoot(workspaceRoot);
|
|
892
|
+
const releaseRoot = path.join(releasesRoot, releaseId);
|
|
893
|
+
const snapshotRoot = path.join(releaseRoot, "snapshot");
|
|
894
|
+
const tempRoot = path.join(releasesRoot, `.publish-${releaseId}-${crypto.randomUUID()}`);
|
|
895
|
+
const tempSnapshotRoot = path.join(tempRoot, "snapshot");
|
|
896
|
+
const now = new Date().toISOString();
|
|
897
|
+
const release = {
|
|
898
|
+
id: releaseId,
|
|
899
|
+
number,
|
|
900
|
+
designRevision,
|
|
901
|
+
createdAt: now,
|
|
902
|
+
createdBy: String(options.createdBy || ""),
|
|
903
|
+
notes: String(options.notes || "").trim().slice(0, 2000),
|
|
904
|
+
baseReleaseId: registry.stableReleaseId || "",
|
|
905
|
+
};
|
|
906
|
+
let releaseInstalled = false;
|
|
907
|
+
let registryCommitted = false;
|
|
908
|
+
fs.mkdirSync(releasesRoot, { recursive: true });
|
|
909
|
+
try {
|
|
910
|
+
workspaceCopyReleaseSnapshot(path.resolve(workspaceRoot), tempSnapshotRoot);
|
|
911
|
+
const { design } = splitWorkspaceGraph(graph);
|
|
912
|
+
writeWorkspaceGraph(tempSnapshotRoot, design, marketplaceRoot);
|
|
913
|
+
fs.writeFileSync(path.join(tempRoot, WORKSPACE_RELEASE_MANIFEST), `${JSON.stringify(release, null, 2)}\n`, "utf-8");
|
|
914
|
+
if (fs.existsSync(releaseRoot)) throw new Error(`Release already exists: ${releaseId}`);
|
|
915
|
+
fs.renameSync(tempRoot, releaseRoot);
|
|
916
|
+
releaseInstalled = true;
|
|
917
|
+
const nextRegistry = writeWorkspaceReleaseRegistry(workspaceRoot, {
|
|
918
|
+
...registry,
|
|
919
|
+
stableReleaseId: releaseId,
|
|
920
|
+
nextNumber: number + 1,
|
|
921
|
+
releases: [release, ...registry.releases],
|
|
922
|
+
});
|
|
923
|
+
registryCommitted = true;
|
|
924
|
+
return {
|
|
925
|
+
ok: true,
|
|
926
|
+
release,
|
|
927
|
+
status: workspaceReleaseSummary(workspaceRoot, marketplaceRoot, graph),
|
|
928
|
+
registry: nextRegistry,
|
|
929
|
+
snapshotRoot,
|
|
930
|
+
};
|
|
931
|
+
} catch (error) {
|
|
932
|
+
try { fs.rmSync(tempRoot, { recursive: true, force: true }); } catch {}
|
|
933
|
+
if (releaseInstalled && !registryCommitted) {
|
|
934
|
+
try { fs.rmSync(releaseRoot, { recursive: true, force: true }); } catch {}
|
|
935
|
+
}
|
|
936
|
+
throw error;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
export function rollbackWorkspaceRelease(workspaceRoot, releaseId, marketplaceRoot = "") {
|
|
941
|
+
const registry = readWorkspaceReleaseRegistry(workspaceRoot);
|
|
942
|
+
const release = registry.releases.find((item) => item.id === String(releaseId || "").trim()) || null;
|
|
943
|
+
const snapshotRoot = release ? workspaceReleaseSnapshotRoot(workspaceRoot, release.id) : "";
|
|
944
|
+
if (!release || !snapshotRoot || !fs.existsSync(snapshotRoot)) {
|
|
945
|
+
return { error: "Release not found" };
|
|
946
|
+
}
|
|
947
|
+
writeWorkspaceReleaseRegistry(workspaceRoot, { ...registry, stableReleaseId: release.id });
|
|
948
|
+
return {
|
|
949
|
+
ok: true,
|
|
950
|
+
release,
|
|
951
|
+
status: workspaceReleaseSummary(workspaceRoot, marketplaceRoot),
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
710
955
|
const DISPLAY_SHARE_FILENAME = "display-shares.json";
|
|
711
956
|
|
|
712
957
|
const DISPLAY_SHARE_ALLOWED_EXPIRY_DAYS = new Set([1, 7, 30, 90, 365]);
|
|
@@ -796,6 +1041,71 @@ export function readWorkspacesFromPath(p, userCtx = {}) {
|
|
|
796
1041
|
}
|
|
797
1042
|
}
|
|
798
1043
|
|
|
1044
|
+
function legacyUserWorkspacesPath(userCtx = {}) {
|
|
1045
|
+
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function readLegacyAdminWorkspaces(userCtx = {}) {
|
|
1049
|
+
const users = readAuthUsers();
|
|
1050
|
+
const candidates = [];
|
|
1051
|
+
for (const [userId, user] of Object.entries(users || {})) {
|
|
1052
|
+
if (user?.isAdmin) candidates.push(String(userId || ""));
|
|
1053
|
+
}
|
|
1054
|
+
if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
|
|
1055
|
+
const seenPaths = new Set();
|
|
1056
|
+
const seenEntries = new Set();
|
|
1057
|
+
const out = [];
|
|
1058
|
+
for (const userId of candidates) {
|
|
1059
|
+
const p = legacyUserWorkspacesPath({ userId });
|
|
1060
|
+
const resolved = path.resolve(p);
|
|
1061
|
+
if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
|
|
1062
|
+
seenPaths.add(resolved);
|
|
1063
|
+
for (const entry of readWorkspacesFromPath(p, { userId })) {
|
|
1064
|
+
const key = entry.id || entry.path || entry.repoUrl;
|
|
1065
|
+
if (seenEntries.has(key)) continue;
|
|
1066
|
+
seenEntries.add(key);
|
|
1067
|
+
out.push(entry);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return out;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/** The same authenticated Workspace catalog used by GET /api/workspaces. */
|
|
1074
|
+
export function readUserWorkspaces(userCtx = {}) {
|
|
1075
|
+
const globalPath = workspacesPath();
|
|
1076
|
+
const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
|
|
1077
|
+
const adminLegacy = readLegacyAdminWorkspaces(userCtx);
|
|
1078
|
+
if (globalWorkspaces.length || adminLegacy.length) {
|
|
1079
|
+
const seen = new Set();
|
|
1080
|
+
const out = [];
|
|
1081
|
+
for (const entry of [...globalWorkspaces, ...adminLegacy]) {
|
|
1082
|
+
const key = entry.id || entry.path || entry.repoUrl;
|
|
1083
|
+
if (seen.has(key)) continue;
|
|
1084
|
+
seen.add(key);
|
|
1085
|
+
out.push(entry);
|
|
1086
|
+
}
|
|
1087
|
+
return out;
|
|
1088
|
+
}
|
|
1089
|
+
return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
export function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
1093
|
+
const currentRoot = path.resolve(scopedRoot || root);
|
|
1094
|
+
const homeRoot = path.resolve(os.homedir());
|
|
1095
|
+
const builtins = [
|
|
1096
|
+
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
1097
|
+
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
1098
|
+
];
|
|
1099
|
+
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
1100
|
+
const seen = new Set();
|
|
1101
|
+
return [...builtins, ...custom].filter((entry) => {
|
|
1102
|
+
const key = path.resolve(entry.path);
|
|
1103
|
+
if (seen.has(key)) return false;
|
|
1104
|
+
seen.add(key);
|
|
1105
|
+
return true;
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
|
|
799
1109
|
export function workspaceRepoUrlWithCredential(repoUrl = "", credential = "") {
|
|
800
1110
|
const token = String(credential || "").trim();
|
|
801
1111
|
if (!token) return String(repoUrl || "").trim();
|
|
@@ -856,6 +1166,10 @@ export function appendWorkspaceRunFinished(record, status) {
|
|
|
856
1166
|
durationMs: Math.max(0, Number(record.durationMs || (Number(record.endedAt || Date.now()) - Number(record.startedAt || record.at || Date.now())))),
|
|
857
1167
|
status,
|
|
858
1168
|
});
|
|
1169
|
+
recordMarketplaceRunUsage(record.workspaceRoot || record.root || "", record.marketplaceResources || [], {
|
|
1170
|
+
...record,
|
|
1171
|
+
status,
|
|
1172
|
+
});
|
|
859
1173
|
}
|
|
860
1174
|
|
|
861
1175
|
function normalizeWorkspaceUsageRecord(parsed, source = "workspace-run") {
|
|
@@ -1034,7 +1348,7 @@ function normalizeDisplayShareLayout(layout, fallback = "canvas") {
|
|
|
1034
1348
|
return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
|
|
1035
1349
|
}
|
|
1036
1350
|
|
|
1037
|
-
export function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds, expiresMode, expiresInDays, permanent, expiresAt }) {
|
|
1351
|
+
export function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds, expiresMode, expiresInDays, permanent, expiresAt, visibility = "public" }) {
|
|
1038
1352
|
const shares = readDisplayShares();
|
|
1039
1353
|
let id = createDisplayShareId();
|
|
1040
1354
|
while (shares[id]) id = createDisplayShareId();
|
|
@@ -1050,6 +1364,7 @@ export function createDisplayShareRecord({ userId, flowId, flowSource, archived,
|
|
|
1050
1364
|
title: String(title || "").trim() || "AgentFlow Display",
|
|
1051
1365
|
layout: normalizeDisplayShareLayout(layout, "canvas"),
|
|
1052
1366
|
nodeIds: Array.isArray(nodeIds) ? nodeIds : [],
|
|
1367
|
+
visibility: String(visibility || "").trim().toLowerCase() === "private" ? "private" : "public",
|
|
1053
1368
|
createdAt: now,
|
|
1054
1369
|
updatedAt: now,
|
|
1055
1370
|
expiresAt: expiry.expiresAt,
|
|
@@ -1181,6 +1496,24 @@ export function mergeWorkspaceRunGraph(currentGraph, runGraph, touchedIds) {
|
|
|
1181
1496
|
};
|
|
1182
1497
|
}
|
|
1183
1498
|
|
|
1499
|
+
function mergeWorkspaceRunState(currentGraph, runGraph, touchedIds) {
|
|
1500
|
+
const currentSplit = splitWorkspaceGraph(currentGraph || {});
|
|
1501
|
+
const runSplit = splitWorkspaceGraph(runGraph || {});
|
|
1502
|
+
const ids = touchedIds instanceof Set ? touchedIds : new Set(touchedIds || []);
|
|
1503
|
+
const state = JSON.parse(JSON.stringify(currentSplit.state || { version: 1 }));
|
|
1504
|
+
for (const key of ["inputs", "outputs", "displayBodies", "displayReloadKeys", "fingerprints"]) {
|
|
1505
|
+
const currentBucket = state[key] && typeof state[key] === "object" ? state[key] : {};
|
|
1506
|
+
const runBucket = runSplit.state?.[key] && typeof runSplit.state[key] === "object" ? runSplit.state[key] : {};
|
|
1507
|
+
for (const nodeId of ids) {
|
|
1508
|
+
if (Object.prototype.hasOwnProperty.call(runBucket, nodeId)) currentBucket[nodeId] = runBucket[nodeId];
|
|
1509
|
+
else delete currentBucket[nodeId];
|
|
1510
|
+
}
|
|
1511
|
+
if (Object.keys(currentBucket).length) state[key] = currentBucket;
|
|
1512
|
+
else delete state[key];
|
|
1513
|
+
}
|
|
1514
|
+
return mergeWorkspaceState(currentSplit.design, state);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1184
1517
|
export function mergeWorkspacePersistentNodeRefs(incomingGraph, currentGraph) {
|
|
1185
1518
|
const incoming = normalizeWorkspaceGraphPayload(incomingGraph || {});
|
|
1186
1519
|
const current = normalizeWorkspaceGraphPayload(currentGraph || {});
|
|
@@ -3175,7 +3508,7 @@ function workspaceTargetSlotForEdge(graph, edge) {
|
|
|
3175
3508
|
function isWorkspaceSemanticInputSlot(slot) {
|
|
3176
3509
|
const name = String(slot?.name || "");
|
|
3177
3510
|
const type = String(slot?.type || "");
|
|
3178
|
-
return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "knowledgeContext" || name === "workspaceContext" || name === "gitContext";
|
|
3511
|
+
return type === "node" || type === "context" || name === "prev" || name === "next" || name === "context" || name === "skillsContext" || name === "mcpContext" || name === "knowledgeContext" || name === "workspaceContext" || name === "gitContext";
|
|
3179
3512
|
}
|
|
3180
3513
|
|
|
3181
3514
|
function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
|
|
@@ -3230,8 +3563,9 @@ function workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputNames =
|
|
|
3230
3563
|
return workspaceOutputSlotValueForEdge(graph, outputs, contentEdge, scopedRoot);
|
|
3231
3564
|
}
|
|
3232
3565
|
|
|
3233
|
-
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
3566
|
+
function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "", options = {}) {
|
|
3234
3567
|
const values = {};
|
|
3568
|
+
const includeContext = options?.includeContext === true;
|
|
3235
3569
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
3236
3570
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
3237
3571
|
const target = instances[String(nodeId || "")] || {};
|
|
@@ -3241,13 +3575,13 @@ function workspaceInputValues(graph, nodeId, outputs, scopedRoot = "") {
|
|
|
3241
3575
|
const index = workspaceHandleIndex(edge?.targetHandle, "input");
|
|
3242
3576
|
const slot = inputSlots[index] || null;
|
|
3243
3577
|
const name = String(slot?.name || "").trim();
|
|
3244
|
-
if (!name || isWorkspaceSemanticInputSlot(slot)) continue;
|
|
3578
|
+
if (!name || (isWorkspaceSemanticInputSlot(slot) && !(includeContext && name === "context"))) continue;
|
|
3245
3579
|
const value = workspaceOutputSlotValueForEdge(graph, outputs, edge, scopedRoot);
|
|
3246
3580
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3247
3581
|
}
|
|
3248
3582
|
for (const slot of inputSlots) {
|
|
3249
3583
|
const name = String(slot?.name || "").trim();
|
|
3250
|
-
if (!name || isWorkspaceSemanticInputSlot(slot) || Object.prototype.hasOwnProperty.call(values, name)) continue;
|
|
3584
|
+
if (!name || (isWorkspaceSemanticInputSlot(slot) && !(includeContext && name === "context")) || Object.prototype.hasOwnProperty.call(values, name)) continue;
|
|
3251
3585
|
const value = workspaceSlotValue(slot);
|
|
3252
3586
|
if (String(value || "").trim()) values[name] = String(value);
|
|
3253
3587
|
}
|
|
@@ -3944,7 +4278,9 @@ function selectedSkillKeysFromInstance(instance) {
|
|
|
3944
4278
|
|
|
3945
4279
|
function selectedSkillKeysFromConfigSlots(instance) {
|
|
3946
4280
|
const slots = [...(Array.isArray(instance?.input) ? instance.input : []), ...(Array.isArray(instance?.output) ? instance.output : [])];
|
|
3947
|
-
const slot = slots.find((item) => item?.name === "
|
|
4281
|
+
const slot = slots.find((item) => item?.name === "skills") ||
|
|
4282
|
+
slots.find((item) => item?.name === "skillsContext") ||
|
|
4283
|
+
slots.find((item) => item?.name === "skillKeys");
|
|
3948
4284
|
return parseWorkspaceSkillKeys(workspaceSlotValue(slot) || "");
|
|
3949
4285
|
}
|
|
3950
4286
|
|
|
@@ -3998,6 +4334,25 @@ function workspaceSemanticInputText(graph, nodeId, outputs, name, scopedRoot = "
|
|
|
3998
4334
|
return workspaceSlotValue(workspaceSlotByName(instance, targetName));
|
|
3999
4335
|
}
|
|
4000
4336
|
|
|
4337
|
+
function workspaceContextBundleFromText(text) {
|
|
4338
|
+
const parsed = parseJsonText(String(text || "").trim(), null);
|
|
4339
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
4340
|
+
if (Number(parsed.version || 1) !== 1) return {};
|
|
4341
|
+
const out = {};
|
|
4342
|
+
for (const name of ["knowledgeContext", "skillsContext", "workspaceContext", "mcpContext"]) {
|
|
4343
|
+
if (parsed[name] !== undefined && parsed[name] !== null) {
|
|
4344
|
+
out[name] = typeof parsed[name] === "string" ? parsed[name] : JSON.stringify(parsed[name]);
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
return out;
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
function workspaceNodeContextBundle(graph, nodeId, outputs, scopedRoot = "") {
|
|
4351
|
+
return workspaceContextBundleFromText(
|
|
4352
|
+
workspaceSemanticInputText(graph, nodeId, outputs, "context", scopedRoot),
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4001
4356
|
function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
|
|
4002
4357
|
const raw = String(text || "").trim();
|
|
4003
4358
|
if (!raw) return null;
|
|
@@ -4024,20 +4379,30 @@ function workspaceLooksLikeKnowledgePath(value) {
|
|
|
4024
4379
|
}
|
|
4025
4380
|
|
|
4026
4381
|
function workspaceKnowledgeSourceFromObject(source = {}, baseCwd = "", scopedRoot = "") {
|
|
4382
|
+
if (typeof source === "string") {
|
|
4383
|
+
const ref = source.trim();
|
|
4384
|
+
if (!ref) return null;
|
|
4385
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(ref)) {
|
|
4386
|
+
return { id: ref, label: ref, kind: "binding", type: "", path: "", repoPath: "", mountPath: "", repoUrl: "", branch: "", ref, readonly: true };
|
|
4387
|
+
}
|
|
4388
|
+
source = { path: ref };
|
|
4389
|
+
}
|
|
4027
4390
|
if (!source || typeof source !== "object" || Array.isArray(source)) return null;
|
|
4391
|
+
const ref = String(source.ref || source.binding || "").trim();
|
|
4028
4392
|
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;
|
|
4393
|
+
if (!workspaceLooksLikeKnowledgePath(rawPath) && !ref) return null;
|
|
4394
|
+
const resolvedPath = rawPath ? (workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath) : "";
|
|
4031
4395
|
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",
|
|
4396
|
+
id: String(source.id || source.mountPath || source.label || path.basename(resolvedPath) || ref || "").trim(),
|
|
4397
|
+
label: String(source.label || source.id || source.mountPath || path.basename(resolvedPath) || ref || "知识库").trim(),
|
|
4398
|
+
kind: String(source.kind || (source.repoUrl ? "git" : (ref ? "binding" : "local"))).trim() || "local",
|
|
4035
4399
|
type: String(source.type || "").trim(),
|
|
4036
4400
|
path: resolvedPath,
|
|
4037
4401
|
repoPath: resolvedPath,
|
|
4038
4402
|
mountPath: String(source.mountPath || "").trim(),
|
|
4039
4403
|
repoUrl: String(source.repoUrl || "").trim(),
|
|
4040
4404
|
branch: String(source.branch || "").trim(),
|
|
4405
|
+
ref,
|
|
4041
4406
|
readonly: source.readonly !== false,
|
|
4042
4407
|
};
|
|
4043
4408
|
}
|
|
@@ -4073,7 +4438,7 @@ export function workspaceKnowledgeSourcesFromText(text, baseCwd = "", scopedRoot
|
|
|
4073
4438
|
}
|
|
4074
4439
|
|
|
4075
4440
|
function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
4076
|
-
const valid = Array.isArray(sources) ? sources.filter((source) => source?.path || source?.repoPath) : [];
|
|
4441
|
+
const valid = Array.isArray(sources) ? sources.filter((source) => source?.path || source?.repoPath || source?.ref) : [];
|
|
4077
4442
|
if (!valid.length) return "";
|
|
4078
4443
|
const lines = [
|
|
4079
4444
|
"## 知识库上下文",
|
|
@@ -4087,6 +4452,7 @@ function workspaceKnowledgeContextBlockFromSources(sources = []) {
|
|
|
4087
4452
|
lines.push(`${index + 1}. ${label}`);
|
|
4088
4453
|
if (source.kind) lines.push(` - 类型:${source.kind}${source.type ? `/${source.type}` : ""}`);
|
|
4089
4454
|
if (sourcePath) lines.push(` - 路径:\`${sourcePath}\``);
|
|
4455
|
+
if (source.ref) lines.push(` - 绑定:\`${source.ref}\``);
|
|
4090
4456
|
if (source.mountPath) lines.push(` - 挂载目录:${source.mountPath}`);
|
|
4091
4457
|
if (source.repoUrl) lines.push(` - Git URL:${source.repoUrl}`);
|
|
4092
4458
|
if (source.branch) lines.push(` - 分支:${source.branch}`);
|
|
@@ -4100,10 +4466,11 @@ function workspaceDedupeKnowledgeSources(sources = []) {
|
|
|
4100
4466
|
for (const source of Array.isArray(sources) ? sources : []) {
|
|
4101
4467
|
if (!source || typeof source !== "object") continue;
|
|
4102
4468
|
const key = [
|
|
4103
|
-
path.resolve(String(source.path || source.repoPath
|
|
4469
|
+
source.path || source.repoPath ? path.resolve(String(source.path || source.repoPath)) : "",
|
|
4104
4470
|
String(source.mountPath || ""),
|
|
4105
4471
|
String(source.repoUrl || ""),
|
|
4106
4472
|
String(source.branch || ""),
|
|
4473
|
+
String(source.ref || ""),
|
|
4107
4474
|
].join("\n");
|
|
4108
4475
|
if (seen.has(key)) continue;
|
|
4109
4476
|
seen.add(key);
|
|
@@ -4149,16 +4516,18 @@ function workspaceGlobalKnowledgeSources(graph, scopedRoot = "", logicalCwd = ""
|
|
|
4149
4516
|
);
|
|
4150
4517
|
}
|
|
4151
4518
|
|
|
4152
|
-
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
|
|
4519
|
+
function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "", contextBundle = {}) {
|
|
4153
4520
|
const root = scopedRoot ? path.resolve(scopedRoot) : "";
|
|
4154
4521
|
const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
|
|
4155
|
-
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4522
|
+
const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot)
|
|
4523
|
+
|| String(contextBundle?.knowledgeContext || "");
|
|
4156
4524
|
let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || root, scopedRoot);
|
|
4157
4525
|
knowledgeSources = workspaceDedupeKnowledgeSources([
|
|
4158
4526
|
...workspaceGlobalKnowledgeSources(graph, scopedRoot, logicalCwd, nodeId),
|
|
4159
4527
|
...knowledgeSources,
|
|
4160
4528
|
]);
|
|
4161
|
-
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4529
|
+
const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot)
|
|
4530
|
+
|| String(contextBundle?.workspaceContext || "");
|
|
4162
4531
|
let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
|
|
4163
4532
|
if (!knowledgeSources.length && workspaceContext?.cwd) {
|
|
4164
4533
|
knowledgeSources = workspaceKnowledgeSourcesFromText(JSON.stringify([workspaceContext]), cwd || root, scopedRoot);
|
|
@@ -4384,7 +4753,7 @@ function workspaceDefaultGitRepoRoot(scopedRoot, _userCtx = {}) {
|
|
|
4384
4753
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "git-repos");
|
|
4385
4754
|
}
|
|
4386
4755
|
|
|
4387
|
-
function workspaceDefaultWorktreePath(
|
|
4756
|
+
function workspaceDefaultWorktreePath(scopedRoot, runId, nodeId, repoPath, branch = "") {
|
|
4388
4757
|
const repoRoot = path.resolve(repoPath);
|
|
4389
4758
|
const repoName = sanitizeWorktreeName(path.basename(repoRoot));
|
|
4390
4759
|
const branchName = String(branch || "").trim();
|
|
@@ -4402,9 +4771,12 @@ function workspaceDefaultWorktreePath(runTmpRoot, nodeId, repoPath, branch = "")
|
|
|
4402
4771
|
: "HEAD";
|
|
4403
4772
|
}
|
|
4404
4773
|
return path.join(
|
|
4405
|
-
path.resolve(
|
|
4406
|
-
"
|
|
4774
|
+
path.resolve(scopedRoot),
|
|
4775
|
+
".workspace",
|
|
4776
|
+
"agentflow",
|
|
4777
|
+
"run-workspaces",
|
|
4407
4778
|
workspaceSanitizeTmpSegment(nodeId, "node"),
|
|
4779
|
+
workspaceSanitizeTmpSegment(runId, "run"),
|
|
4408
4780
|
repoName,
|
|
4409
4781
|
sanitizeWorktreeName(refLabel),
|
|
4410
4782
|
);
|
|
@@ -4444,15 +4816,18 @@ function workspaceMarkAutoWorktreeCleaned(graph, entry) {
|
|
|
4444
4816
|
return true;
|
|
4445
4817
|
}
|
|
4446
4818
|
|
|
4447
|
-
function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
4819
|
+
function workspaceCleanupAutoWorktrees(list, graph, emit, { force = false } = {}) {
|
|
4820
|
+
const cleaned = [];
|
|
4821
|
+
const preserved = [];
|
|
4448
4822
|
for (const entry of [...list].reverse()) {
|
|
4449
4823
|
try {
|
|
4450
4824
|
const result = unloadGitWorktree({
|
|
4451
4825
|
repoPath: entry.repoPath,
|
|
4452
4826
|
worktreePath: entry.worktreePath,
|
|
4453
|
-
force
|
|
4827
|
+
force,
|
|
4454
4828
|
prune: true,
|
|
4455
4829
|
});
|
|
4830
|
+
cleaned.push(result.worktreePath);
|
|
4456
4831
|
emit({
|
|
4457
4832
|
type: "natural",
|
|
4458
4833
|
kind: "status",
|
|
@@ -4463,15 +4838,74 @@ function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
|
4463
4838
|
emit({ type: "graph", nodeId: entry.nodeId, graph });
|
|
4464
4839
|
}
|
|
4465
4840
|
} catch (e) {
|
|
4841
|
+
preserved.push({ ...entry, reason: e?.message || String(e) });
|
|
4466
4842
|
emit({
|
|
4467
4843
|
type: "natural",
|
|
4468
4844
|
kind: "warning",
|
|
4469
4845
|
nodeId: entry.nodeId,
|
|
4470
|
-
text:
|
|
4846
|
+
text: `运行 worktree 已保留:${entry.worktreePath}\n原因:${e?.message || String(e)}`,
|
|
4471
4847
|
});
|
|
4472
4848
|
}
|
|
4473
4849
|
}
|
|
4474
4850
|
list.splice(0, list.length);
|
|
4851
|
+
return { cleaned, preserved };
|
|
4852
|
+
}
|
|
4853
|
+
|
|
4854
|
+
function workspaceRunManifestPath(scopedRoot, runId) {
|
|
4855
|
+
const id = workspaceSanitizeTmpSegment(runId, "run");
|
|
4856
|
+
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "run-manifests", `${id}.json`);
|
|
4857
|
+
}
|
|
4858
|
+
|
|
4859
|
+
function workspaceWriteRunManifest(scopedRoot, runId, value = {}) {
|
|
4860
|
+
const filePath = workspaceRunManifestPath(scopedRoot, runId);
|
|
4861
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
4862
|
+
const previous = (() => {
|
|
4863
|
+
try {
|
|
4864
|
+
return fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, "utf-8")) : {};
|
|
4865
|
+
} catch {
|
|
4866
|
+
return {};
|
|
4867
|
+
}
|
|
4868
|
+
})();
|
|
4869
|
+
const next = {
|
|
4870
|
+
version: 1,
|
|
4871
|
+
...previous,
|
|
4872
|
+
...value,
|
|
4873
|
+
runId: String(runId || previous.runId || ""),
|
|
4874
|
+
updatedAt: new Date().toISOString(),
|
|
4875
|
+
};
|
|
4876
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
4877
|
+
fs.writeFileSync(tempPath, JSON.stringify(next, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
|
|
4878
|
+
fs.renameSync(tempPath, filePath);
|
|
4879
|
+
return next;
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
export function cleanupWorkspaceRunResources(scopedRoot, runId, { force = false, status = "stopped", emit = () => {} } = {}) {
|
|
4883
|
+
const filePath = workspaceRunManifestPath(scopedRoot, runId);
|
|
4884
|
+
if (!fs.existsSync(filePath)) return { cleaned: [], preserved: [], manifestPath: filePath };
|
|
4885
|
+
let manifest = {};
|
|
4886
|
+
try {
|
|
4887
|
+
manifest = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
4888
|
+
} catch {
|
|
4889
|
+
return { cleaned: [], preserved: [], manifestPath: filePath };
|
|
4890
|
+
}
|
|
4891
|
+
const resources = Array.isArray(manifest.worktrees) ? manifest.worktrees : [];
|
|
4892
|
+
const pending = resources.filter((entry) => entry?.repoPath && entry?.worktreePath && entry.removed !== true);
|
|
4893
|
+
const result = workspaceCleanupAutoWorktrees(pending.map((entry) => ({ ...entry })), null, emit, { force });
|
|
4894
|
+
const cleanedSet = new Set(result.cleaned.map((item) => path.resolve(item)));
|
|
4895
|
+
const preservedByPath = new Map(result.preserved.map((item) => [path.resolve(item.worktreePath), item]));
|
|
4896
|
+
const worktrees = resources.map((entry) => {
|
|
4897
|
+
const target = entry?.worktreePath ? path.resolve(entry.worktreePath) : "";
|
|
4898
|
+
if (target && cleanedSet.has(target)) return { ...entry, removed: true, removedAt: new Date().toISOString(), reason: "" };
|
|
4899
|
+
if (target && preservedByPath.has(target)) return { ...entry, removed: false, reason: preservedByPath.get(target).reason };
|
|
4900
|
+
return entry;
|
|
4901
|
+
});
|
|
4902
|
+
workspaceWriteRunManifest(scopedRoot, runId, {
|
|
4903
|
+
...manifest,
|
|
4904
|
+
status: result.preserved.length ? `${status}:resources-preserved` : status,
|
|
4905
|
+
worktrees,
|
|
4906
|
+
finishedAt: new Date().toISOString(),
|
|
4907
|
+
});
|
|
4908
|
+
return { ...result, manifestPath: filePath };
|
|
4475
4909
|
}
|
|
4476
4910
|
|
|
4477
4911
|
function workspaceSanitizeTmpSegment(value, fallback = "node") {
|
|
@@ -4496,12 +4930,13 @@ function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
|
4496
4930
|
return dir;
|
|
4497
4931
|
}
|
|
4498
4932
|
|
|
4499
|
-
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {}, durableOutputs = false } = {}) {
|
|
4933
|
+
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, sourceRoot = "", cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {}, durableOutputs = false } = {}) {
|
|
4500
4934
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
4501
4935
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
4502
4936
|
const legacyOutputsDir = path.join(nodeRunDir, "outputs");
|
|
4503
|
-
const workspaceRoot = path.resolve(scopedRoot);
|
|
4504
|
-
const
|
|
4937
|
+
const workspaceRoot = path.resolve(sourceRoot || scopedRoot);
|
|
4938
|
+
const outputWorkspaceRoot = path.resolve(scopedRoot);
|
|
4939
|
+
const workspaceOutputsDir = path.join(outputWorkspaceRoot, "outputs");
|
|
4505
4940
|
const nodePart = workspaceSanitizeTmpSegment(nodeId || "node", "node");
|
|
4506
4941
|
const outputsRel = durableOutputs ? path.posix.join("outputs", nodePart) : "outputs";
|
|
4507
4942
|
const outputsDir = durableOutputs ? path.join(workspaceOutputsDir, nodePart) : legacyOutputsDir;
|
|
@@ -4531,6 +4966,7 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
4531
4966
|
outputsRel,
|
|
4532
4967
|
directWorkspaceOutputs: durableOutputs,
|
|
4533
4968
|
workspaceRoot,
|
|
4969
|
+
outputWorkspaceRoot,
|
|
4534
4970
|
workspaceOutputsDir,
|
|
4535
4971
|
executionCwd: cwd ? path.resolve(cwd) : workspaceRoot,
|
|
4536
4972
|
resultFileRel,
|
|
@@ -5105,15 +5541,33 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5105
5541
|
emit({ type: "status", nodeId, line: `Timing ${label}: ${elapsedMs}ms`, timing: { label, elapsedMs, ...extra } });
|
|
5106
5542
|
};
|
|
5107
5543
|
let cwd = scopedRoot;
|
|
5544
|
+
const runtimeStorageRoot = path.resolve(opts.runtimeRoot || scopedRoot);
|
|
5108
5545
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
5109
5546
|
const runEnv = {};
|
|
5110
5547
|
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
5111
5548
|
const autoCleanupWorktrees = [];
|
|
5112
|
-
const runTmpRoot = workspaceCreateRunTmpRoot(
|
|
5549
|
+
const runTmpRoot = workspaceCreateRunTmpRoot(runtimeStorageRoot, runNodeId);
|
|
5550
|
+
const runtimeRunId = String(opts.runId || payload?.runId || "").trim() || runLedgerId("workspace-execution");
|
|
5551
|
+
const ownsRunManifest = !(Array.isArray(opts?.subflowCallStack) && opts.subflowCallStack.length);
|
|
5552
|
+
const persistRunManifest = (status, extra = {}) => {
|
|
5553
|
+
if (!ownsRunManifest) return null;
|
|
5554
|
+
return workspaceWriteRunManifest(runtimeStorageRoot, runtimeRunId, {
|
|
5555
|
+
flowId: String(payload?.flowId || ""),
|
|
5556
|
+
flowSource: String(payload?.flowSource || "user"),
|
|
5557
|
+
runNodeId,
|
|
5558
|
+
status,
|
|
5559
|
+
runtimeRoot: runTmpRoot,
|
|
5560
|
+
artifactRoot: path.join(runtimeStorageRoot, "outputs"),
|
|
5561
|
+
worktrees: autoCleanupWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
5562
|
+
...extra,
|
|
5563
|
+
});
|
|
5564
|
+
};
|
|
5565
|
+
persistRunManifest("running", { startedAt: new Date().toISOString() });
|
|
5113
5566
|
const controlBranches = new Map();
|
|
5114
5567
|
const skippedNodes = new Set();
|
|
5115
5568
|
const runtimePauseNodeIds = [];
|
|
5116
5569
|
let deferred = null;
|
|
5570
|
+
let runFailure = null;
|
|
5117
5571
|
const incomingControlEdgesByTarget = new Map();
|
|
5118
5572
|
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
5119
5573
|
const target = String(edge?.target || "");
|
|
@@ -5177,11 +5631,87 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5177
5631
|
continue;
|
|
5178
5632
|
}
|
|
5179
5633
|
|
|
5634
|
+
if (defId === "context_knowledge") {
|
|
5635
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5636
|
+
const workspaceIds = parseWorkspaceSkillKeys(inputValues.workspaceIds || "[]");
|
|
5637
|
+
if (!workspaceIds.length) throw new Error(`context.knowledge ${nodeId} requires at least one Workspace ID`);
|
|
5638
|
+
const catalog = new Map(listConfiguredWorkspaces(root, scopedRoot, userCtx).map((entry) => [String(entry.id || ""), entry]));
|
|
5639
|
+
const missing = workspaceIds.filter((id) => !catalog.has(id));
|
|
5640
|
+
if (missing.length) throw new Error(`context.knowledge ${nodeId} cannot resolve Workspace IDs: ${missing.join(", ")}`);
|
|
5641
|
+
const unavailable = workspaceIds.filter((id) => catalog.get(id)?.exists === false);
|
|
5642
|
+
if (unavailable.length) throw new Error(`context.knowledge ${nodeId} Workspace paths are not ready: ${unavailable.join(", ")}`);
|
|
5643
|
+
const sources = workspaceIds
|
|
5644
|
+
.map((id, index) => workspaceKnowledgeSourceFromObject({ ...catalog.get(id), role: index === 0 ? "primary" : "context" }, cwd || scopedRoot, scopedRoot))
|
|
5645
|
+
.filter(Boolean);
|
|
5646
|
+
const value = JSON.stringify({ version: 1, sources });
|
|
5647
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "knowledgeContext", value);
|
|
5648
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5649
|
+
emit({ type: "node-done", nodeId, definitionId: defId, sourceCount: sources.length, workspaceIds });
|
|
5650
|
+
continue;
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5653
|
+
if (defId === "context_skills") {
|
|
5654
|
+
const keys = selectedSkillKeysFromConfigSlots(instance);
|
|
5655
|
+
if (!keys.length) throw new Error(`context.skills ${nodeId} requires at least one skill`);
|
|
5656
|
+
const value = loadSkillsBlockForKeys(keys);
|
|
5657
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "skillsContext", value);
|
|
5658
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5659
|
+
emit({ type: "node-done", nodeId, definitionId: defId, skillCount: keys.length });
|
|
5660
|
+
continue;
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
if (defId === "context_workspace") {
|
|
5664
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5665
|
+
const workspaceId = String(inputValues.workspaceId || "current").trim();
|
|
5666
|
+
const access = String(inputValues.access || "read-write").trim().toLowerCase();
|
|
5667
|
+
if (!["read-only", "read-write"].includes(access)) {
|
|
5668
|
+
throw new Error(`context.workspace ${nodeId} access must be read-only or read-write`);
|
|
5669
|
+
}
|
|
5670
|
+
const catalog = new Map(listConfiguredWorkspaces(root, scopedRoot, userCtx).map((entry) => [String(entry.id || ""), entry]));
|
|
5671
|
+
const selected = catalog.get(workspaceId);
|
|
5672
|
+
if (!selected) throw new Error(`context.workspace ${nodeId} cannot resolve Workspace ID ${workspaceId || "(empty)"}`);
|
|
5673
|
+
if (selected.exists === false) throw new Error(`context.workspace ${nodeId} Workspace path is not ready: ${workspaceId}`);
|
|
5674
|
+
const workspaceRoot = path.resolve(selected.path);
|
|
5675
|
+
const value = JSON.stringify({
|
|
5676
|
+
version: 1,
|
|
5677
|
+
workspaceId,
|
|
5678
|
+
access,
|
|
5679
|
+
label: String(selected.label || selected.id || path.basename(workspaceRoot)),
|
|
5680
|
+
cwd: workspaceRoot,
|
|
5681
|
+
workspaceRoot,
|
|
5682
|
+
pipelineWorkspace: path.resolve(scopedRoot),
|
|
5683
|
+
previous: null,
|
|
5684
|
+
});
|
|
5685
|
+
let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", value);
|
|
5686
|
+
graph.instances[nodeId] = nextInstance;
|
|
5687
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5688
|
+
emit({ type: "node-done", nodeId, definitionId: defId, workspaceId, access });
|
|
5689
|
+
continue;
|
|
5690
|
+
}
|
|
5691
|
+
|
|
5692
|
+
if (defId === "context_bundle") {
|
|
5693
|
+
const bundle = {
|
|
5694
|
+
version: 1,
|
|
5695
|
+
knowledgeContext: workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot),
|
|
5696
|
+
skillsContext: workspaceSemanticInputText(graph, nodeId, outputs, "skillsContext", scopedRoot),
|
|
5697
|
+
workspaceContext: workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot),
|
|
5698
|
+
mcpContext: workspaceSemanticInputText(graph, nodeId, outputs, "mcpContext", scopedRoot),
|
|
5699
|
+
};
|
|
5700
|
+
if (![bundle.knowledgeContext, bundle.skillsContext, bundle.workspaceContext, bundle.mcpContext].some((item) => String(item || "").trim())) {
|
|
5701
|
+
throw new Error(`context.bundle ${nodeId} requires at least one connected Context resource`);
|
|
5702
|
+
}
|
|
5703
|
+
const value = JSON.stringify(bundle);
|
|
5704
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(instance, "context", value);
|
|
5705
|
+
publishNodeOutput(nodeId, value, { emitGraph: true });
|
|
5706
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5707
|
+
continue;
|
|
5708
|
+
}
|
|
5709
|
+
|
|
5180
5710
|
if (defId === "control_subflow_call") {
|
|
5181
5711
|
const subflowId = String(instance.subflowId || "").trim();
|
|
5182
5712
|
const subflow = graph?.subflows?.[subflowId];
|
|
5183
5713
|
if (!subflow) throw new Error(`flow.call ${nodeId} references missing subflow ${subflowId || "(empty)"}`);
|
|
5184
|
-
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5714
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot, { includeContext: true });
|
|
5185
5715
|
const { resultValues, callFrameId } = await workspaceRunSubflowFrame({
|
|
5186
5716
|
root,
|
|
5187
5717
|
scopedRoot,
|
|
@@ -5297,6 +5827,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5297
5827
|
|
|
5298
5828
|
if (defId === "control_while") {
|
|
5299
5829
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5830
|
+
const loopContext = workspaceSemanticInputText(graph, nodeId, outputs, "context", scopedRoot);
|
|
5300
5831
|
const config = normalizeControlWhileConfig(inputValues);
|
|
5301
5832
|
const stepScript = String(instance.script || instance.body || "").trim();
|
|
5302
5833
|
const stepScriptRef = String(instance.scriptRef || "").trim();
|
|
@@ -5392,6 +5923,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5392
5923
|
parentDefinitionId: defId,
|
|
5393
5924
|
subflowId: whileSubflows.conditionId,
|
|
5394
5925
|
inputValues: {
|
|
5926
|
+
...(loopContext && whileSubflows.condition.inputs?.context ? { context: loopContext } : {}),
|
|
5395
5927
|
state: stateText,
|
|
5396
5928
|
iteration: String(iteration),
|
|
5397
5929
|
idempotencyKey,
|
|
@@ -5417,6 +5949,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5417
5949
|
parentDefinitionId: defId,
|
|
5418
5950
|
subflowId: whileSubflows.bodyId,
|
|
5419
5951
|
inputValues: {
|
|
5952
|
+
...(loopContext && whileSubflows.body.inputs?.context ? { context: loopContext } : {}),
|
|
5420
5953
|
state: stateText,
|
|
5421
5954
|
iteration: String(iteration),
|
|
5422
5955
|
idempotencyKey,
|
|
@@ -5437,7 +5970,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5437
5970
|
});
|
|
5438
5971
|
}
|
|
5439
5972
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, `${nodeId}-iteration-${iteration}`, {
|
|
5440
|
-
scopedRoot,
|
|
5973
|
+
scopedRoot: runtimeStorageRoot,
|
|
5974
|
+
sourceRoot: scopedRoot,
|
|
5441
5975
|
cwd,
|
|
5442
5976
|
task: stepScript || stepScriptRef,
|
|
5443
5977
|
inputValues: iterationInputs,
|
|
@@ -5811,9 +6345,17 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5811
6345
|
const worktreeInputSlot = (Array.isArray(instance.input) ? instance.input : [])
|
|
5812
6346
|
.find((slot) => String(slot?.name || "") === "worktreePath") || null;
|
|
5813
6347
|
const rawWorktreePath = workspaceSlotValue(worktreeInputSlot || workspaceSlotByName(instance, "worktreePath")).trim();
|
|
6348
|
+
const retainedWorktreePath = workspaceSlotValue(
|
|
6349
|
+
(Array.isArray(instance.output) ? instance.output : [])
|
|
6350
|
+
.find((slot) => String(slot?.name || "") === "worktreePath"),
|
|
6351
|
+
).trim();
|
|
5814
6352
|
const worktreePath = rawWorktreePath
|
|
5815
6353
|
? workspaceResolvePath(cwd, rawWorktreePath)
|
|
5816
|
-
: (gitContext?.worktreePath
|
|
6354
|
+
: (gitContext?.worktreePath
|
|
6355
|
+
? path.resolve(gitContext.worktreePath)
|
|
6356
|
+
: (retainedWorktreePath
|
|
6357
|
+
? path.resolve(retainedWorktreePath)
|
|
6358
|
+
: workspaceDefaultWorktreePath(scopedRoot, runtimeRunId, nodeId, repoPath, branch)));
|
|
5817
6359
|
const previousCwd = cwd;
|
|
5818
6360
|
const force = ["true", "1", "yes", "on"].includes(workspaceSlotValue(workspaceSlotByName(instance, "force")).trim().toLowerCase());
|
|
5819
6361
|
const pruneMissingRaw = workspaceSlotValue(workspaceSlotByName(instance, "pruneMissing")).trim().toLowerCase();
|
|
@@ -5825,6 +6367,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5825
6367
|
repoPath: result.repoRoot,
|
|
5826
6368
|
worktreePath: result.worktreePath,
|
|
5827
6369
|
});
|
|
6370
|
+
persistRunManifest("running");
|
|
5828
6371
|
}
|
|
5829
6372
|
const outGitContext = buildGitContext({
|
|
5830
6373
|
repoPath: result.repoRoot,
|
|
@@ -6006,7 +6549,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6006
6549
|
const prepareStartedAt = Date.now();
|
|
6007
6550
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
6008
6551
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6009
|
-
scopedRoot,
|
|
6552
|
+
scopedRoot: runtimeStorageRoot,
|
|
6553
|
+
sourceRoot: scopedRoot,
|
|
6010
6554
|
cwd,
|
|
6011
6555
|
task: String(instance.script || instance.scriptRef || instance.body || "").trim(),
|
|
6012
6556
|
inputValues,
|
|
@@ -6067,13 +6611,21 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6067
6611
|
const relevantInputs = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
6068
6612
|
workspaceAssertRequiredInputs(instance.body || "", inputValues, nodeId);
|
|
6069
6613
|
const upstreamText = workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputs.placeholders, scopedRoot);
|
|
6070
|
-
const
|
|
6614
|
+
const contextBundle = workspaceNodeContextBundle(graph, nodeId, outputs, scopedRoot);
|
|
6615
|
+
const upstreamSkillBlocks = mergeWorkspaceSkillBlocks(
|
|
6616
|
+
workspaceUpstreamSkillBlocks(graph, nodeId, outputs),
|
|
6617
|
+
String(contextBundle.skillsContext || ""),
|
|
6618
|
+
);
|
|
6071
6619
|
const ownSkillBlock = isContextRunNode ? loadSkillsBlockForKeys(selectedSkillKeysFromConfigSlots(instance)) : "";
|
|
6072
6620
|
const promptSkillsBlock = mergeWorkspaceSkillBlocks(ownSkillBlock, upstreamSkillBlocks);
|
|
6073
|
-
const promptMcpBlock =
|
|
6621
|
+
const promptMcpBlock = mergeWorkspaceSkillBlocks(
|
|
6622
|
+
workspaceUpstreamMcpBlocks(graph, nodeId, outputs),
|
|
6623
|
+
String(contextBundle.mcpContext || ""),
|
|
6624
|
+
);
|
|
6074
6625
|
const resultOutputSpec = workspaceResultOutputSpec(graph, nodeId);
|
|
6075
6626
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6076
|
-
scopedRoot,
|
|
6627
|
+
scopedRoot: runtimeStorageRoot,
|
|
6628
|
+
sourceRoot: scopedRoot,
|
|
6077
6629
|
cwd,
|
|
6078
6630
|
task: workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim() || upstreamText,
|
|
6079
6631
|
inputValues: relevantInputs.values,
|
|
@@ -6095,7 +6647,7 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6095
6647
|
// Best-effort debug artifact only.
|
|
6096
6648
|
}
|
|
6097
6649
|
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
6098
|
-
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
|
|
6650
|
+
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd, contextBundle);
|
|
6099
6651
|
if (!isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true)) {
|
|
6100
6652
|
const defaultWorkspaceBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
|
|
6101
6653
|
if (!workspaceContextBlock) {
|
|
@@ -6229,9 +6781,47 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6229
6781
|
outputFiles: normalizedAgentOutput.outputFiles || [],
|
|
6230
6782
|
});
|
|
6231
6783
|
}
|
|
6784
|
+
} catch (error) {
|
|
6785
|
+
runFailure = error;
|
|
6786
|
+
throw error;
|
|
6232
6787
|
} finally {
|
|
6233
|
-
|
|
6234
|
-
|
|
6788
|
+
const retainForResume = Boolean(deferred || runtimePauseNodeIds.length);
|
|
6789
|
+
const trackedWorktrees = autoCleanupWorktrees.map((entry) => ({ ...entry }));
|
|
6790
|
+
let cleanup = { cleaned: [], preserved: [] };
|
|
6791
|
+
if (retainForResume) {
|
|
6792
|
+
emit({
|
|
6793
|
+
type: "status",
|
|
6794
|
+
line: `Run workspace retained for resume${trackedWorktrees.length ? ` (${trackedWorktrees.length} worktree)` : ""}`,
|
|
6795
|
+
runId: runtimeRunId,
|
|
6796
|
+
});
|
|
6797
|
+
persistRunManifest("waiting", {
|
|
6798
|
+
worktrees: trackedWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
6799
|
+
waitingAt: new Date().toISOString(),
|
|
6800
|
+
});
|
|
6801
|
+
autoCleanupWorktrees.splice(0, autoCleanupWorktrees.length);
|
|
6802
|
+
} else {
|
|
6803
|
+
cleanup = workspaceCleanupAutoWorktrees(autoCleanupWorktrees, graph, emit, { force: false });
|
|
6804
|
+
const cleanedSet = new Set(cleanup.cleaned.map((item) => path.resolve(item)));
|
|
6805
|
+
const preservedByPath = new Map(cleanup.preserved.map((item) => [path.resolve(item.worktreePath), item]));
|
|
6806
|
+
persistRunManifest(runFailure ? "failed" : "completed", {
|
|
6807
|
+
worktrees: trackedWorktrees.map((entry) => {
|
|
6808
|
+
const target = path.resolve(entry.worktreePath);
|
|
6809
|
+
if (cleanedSet.has(target)) return { ...entry, removed: true, removedAt: new Date().toISOString(), reason: "" };
|
|
6810
|
+
if (preservedByPath.has(target)) return { ...entry, removed: false, reason: preservedByPath.get(target).reason };
|
|
6811
|
+
return entry;
|
|
6812
|
+
}),
|
|
6813
|
+
finishedAt: new Date().toISOString(),
|
|
6814
|
+
error: runFailure ? (runFailure?.message || String(runFailure)) : "",
|
|
6815
|
+
resourcesPreserved: cleanup.preserved.length > 0,
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6818
|
+
const protectedWorktrees = retainForResume ? trackedWorktrees : cleanup.preserved;
|
|
6819
|
+
const protectsRunTmpRoot = protectedWorktrees.some((entry) => workspacePathInside(runTmpRoot, entry.worktreePath));
|
|
6820
|
+
if (protectsRunTmpRoot) {
|
|
6821
|
+
emit({ type: "status", line: `Workspace tmp kept because it contains a retained worktree: ${runTmpRoot}` });
|
|
6822
|
+
} else {
|
|
6823
|
+
workspaceCleanupTmpRoot(runTmpRoot, userCtx, emit);
|
|
6824
|
+
}
|
|
6235
6825
|
}
|
|
6236
6826
|
const finalPauseNodeIds = Array.from(new Set([...pauseNodeIds, ...runtimePauseNodeIds]));
|
|
6237
6827
|
if (!deferred && finalPauseNodeIds.length > 0) {
|
|
@@ -6444,6 +7034,17 @@ export function upsertWorkspaceDeferredRun(meta = {}, deferred = {}) {
|
|
|
6444
7034
|
nodeId: String(deferred.nodeId || meta.nodeId || previous.nodeId || ""),
|
|
6445
7035
|
label: String(meta.label || previous.label || "Workspace Run"),
|
|
6446
7036
|
plannedNodeIds: Array.isArray(meta.plannedNodeIds) ? meta.plannedNodeIds.map(String) : (previous.plannedNodeIds || []),
|
|
7037
|
+
workspaceRoot: String(meta.workspaceRoot || previous.workspaceRoot || ""),
|
|
7038
|
+
executionRoot: String(meta.executionRoot || previous.executionRoot || ""),
|
|
7039
|
+
releaseId: String(meta.releaseId || previous.releaseId || ""),
|
|
7040
|
+
designRevision: String(meta.designRevision || previous.designRevision || ""),
|
|
7041
|
+
marketplaceResources: Array.isArray(meta.marketplaceResources)
|
|
7042
|
+
? meta.marketplaceResources.map((item) => ({
|
|
7043
|
+
kind: String(item?.kind || ""),
|
|
7044
|
+
id: String(item?.id || ""),
|
|
7045
|
+
version: String(item?.version || ""),
|
|
7046
|
+
})).filter((item) => item.kind && item.id && item.version)
|
|
7047
|
+
: (previous.marketplaceResources || []),
|
|
6447
7048
|
startedAt: Number(meta.startedAt || previous.startedAt || now),
|
|
6448
7049
|
scheduled: meta.scheduled === true || previous.scheduled === true,
|
|
6449
7050
|
scheduleKey: String(meta.scheduleKey || previous.scheduleKey || ""),
|
|
@@ -6639,7 +7240,8 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6639
7240
|
if (scoped.error || !scoped.root) continue;
|
|
6640
7241
|
let graph;
|
|
6641
7242
|
try {
|
|
6642
|
-
graph =
|
|
7243
|
+
graph = readWorkspaceStableRelease(scoped.root, root)?.graph
|
|
7244
|
+
|| readWorkspaceGraph(scoped.root, root).graph;
|
|
6643
7245
|
} catch {
|
|
6644
7246
|
continue;
|
|
6645
7247
|
}
|
|
@@ -6669,6 +7271,9 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6669
7271
|
rows.push({
|
|
6670
7272
|
kind: "workspace",
|
|
6671
7273
|
key,
|
|
7274
|
+
registered: Boolean(registry.schedules?.[key]),
|
|
7275
|
+
ownerUserId: scheduleUserId,
|
|
7276
|
+
ownerUsername: String(current.username || scheduleUserId),
|
|
6672
7277
|
flowId,
|
|
6673
7278
|
flowSource,
|
|
6674
7279
|
workspaceId: String(flow.collaboration?.id || scoped.workspaceId || ""),
|
|
@@ -6732,11 +7337,12 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
|
|
|
6732
7337
|
writeWorkspaceScheduleRegistry({ version: 1, schedules });
|
|
6733
7338
|
return [];
|
|
6734
7339
|
}
|
|
6735
|
-
const
|
|
7340
|
+
const effectiveGraph = readWorkspaceStableRelease(scoped?.root || "", root)?.graph || graph;
|
|
7341
|
+
const instances = effectiveGraph?.instances && typeof effectiveGraph.instances === "object" ? effectiveGraph.instances : {};
|
|
6736
7342
|
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
6737
7343
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
6738
7344
|
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
6739
|
-
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(
|
|
7345
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(effectiveGraph, scheduleNodeId, config);
|
|
6740
7346
|
const key = workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId);
|
|
6741
7347
|
const previous = registry.schedules?.[key] && typeof registry.schedules[key] === "object" ? registry.schedules[key] : {};
|
|
6742
7348
|
const previousNext = Number(previous.nextRunAt || 0);
|
|
@@ -6857,7 +7463,15 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6857
7463
|
});
|
|
6858
7464
|
return;
|
|
6859
7465
|
}
|
|
6860
|
-
const
|
|
7466
|
+
const stableRelease = readWorkspaceStableRelease(scoped.root, root);
|
|
7467
|
+
const executionRoot = stableRelease?.root || scoped.root;
|
|
7468
|
+
const executionScoped = stableRelease ? { ...scoped, root: executionRoot } : scoped;
|
|
7469
|
+
const graph = hydrateWorkspaceGraphForRuntime(
|
|
7470
|
+
root,
|
|
7471
|
+
executionScoped,
|
|
7472
|
+
stableRelease?.graph || readWorkspaceGraph(scoped.root, root).graph,
|
|
7473
|
+
userCtx,
|
|
7474
|
+
);
|
|
6861
7475
|
const scheduleNodeId = String(entry.scheduleNodeId || entry.key?.split(":").pop() || "");
|
|
6862
7476
|
const instance = graph.instances?.[scheduleNodeId];
|
|
6863
7477
|
const config = normalizeWorkspaceScheduledRunConfig(instance?.body || "");
|
|
@@ -6891,7 +7505,7 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6891
7505
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "scheduler-triggered", scheduleNodeId, runNodeId: targetRunNodeId, cron: config.cron, timezone: config.timezone });
|
|
6892
7506
|
let plan;
|
|
6893
7507
|
try {
|
|
6894
|
-
plan = workspaceRunPlan(graph, targetRunNodeId,
|
|
7508
|
+
plan = workspaceRunPlan(graph, targetRunNodeId, executionRoot);
|
|
6895
7509
|
} catch (e) {
|
|
6896
7510
|
const error = (e && e.message) || String(e);
|
|
6897
7511
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "error", error });
|
|
@@ -6943,7 +7557,18 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6943
7557
|
plannedNodeIds,
|
|
6944
7558
|
startedAt: Date.now(),
|
|
6945
7559
|
scheduled: true,
|
|
7560
|
+
workspaceRoot: root,
|
|
7561
|
+
executionRoot,
|
|
7562
|
+
releaseId: stableRelease?.release?.id || "",
|
|
7563
|
+
designRevision: stableRelease?.release?.designRevision || workspaceDesignRevision(graph),
|
|
7564
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, plannedNodeIds),
|
|
6946
7565
|
};
|
|
7566
|
+
appendWorkspaceRunLogEvent(runLog.runId, {
|
|
7567
|
+
type: "release-resolved",
|
|
7568
|
+
releaseId: runEntry.releaseId || "legacy-current",
|
|
7569
|
+
revision: runEntry.designRevision,
|
|
7570
|
+
source: stableRelease ? "stable" : "legacy-current",
|
|
7571
|
+
});
|
|
6947
7572
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
6948
7573
|
appendWorkspaceRunStarted(runEntry);
|
|
6949
7574
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -6960,12 +7585,13 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6960
7585
|
runControl.setChild(child, childOptions);
|
|
6961
7586
|
};
|
|
6962
7587
|
try {
|
|
6963
|
-
const result = await runWorkspaceGraph(root,
|
|
7588
|
+
const result = await runWorkspaceGraph(root, executionRoot, {
|
|
6964
7589
|
flowId: entry.flowId,
|
|
6965
7590
|
flowSource: entry.flowSource || "user",
|
|
6966
7591
|
runNodeId: targetRunNodeId,
|
|
6967
7592
|
graph,
|
|
6968
7593
|
}, userCtx, {
|
|
7594
|
+
runtimeRoot: scoped.root,
|
|
6969
7595
|
signal: controller.signal,
|
|
6970
7596
|
onActiveChild: setActiveChild,
|
|
6971
7597
|
onEvent: (event) => appendWorkspaceRunLogEvent(runLog.runId, event),
|
|
@@ -6973,7 +7599,12 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6973
7599
|
});
|
|
6974
7600
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
6975
7601
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
6976
|
-
|
|
7602
|
+
if (stableRelease?.release?.id) {
|
|
7603
|
+
writeWorkspaceReleaseRuntimeState(scoped.root, stableRelease.release.id, result.graph);
|
|
7604
|
+
}
|
|
7605
|
+
const mergedGraph = stableRelease
|
|
7606
|
+
? mergeWorkspaceRunState(currentGraph, result.graph, touchedIds)
|
|
7607
|
+
: mergeWorkspaceRunGraph(currentGraph, result.graph, touchedIds);
|
|
6977
7608
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
6978
7609
|
if (result.deferred) {
|
|
6979
7610
|
const waiting = upsertWorkspaceDeferredRun({
|
|
@@ -6997,11 +7628,18 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6997
7628
|
return;
|
|
6998
7629
|
}
|
|
6999
7630
|
const endedAt = Date.now();
|
|
7000
|
-
appendWorkspaceRunFinished({
|
|
7631
|
+
appendWorkspaceRunFinished({
|
|
7632
|
+
...runEntry,
|
|
7633
|
+
endedAt,
|
|
7634
|
+
durationMs: endedAt - runEntry.startedAt,
|
|
7635
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, result.order || Array.from(touchedIds)),
|
|
7636
|
+
}, "success");
|
|
7001
7637
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
7002
7638
|
endedAt,
|
|
7003
7639
|
durationMs: endedAt - runEntry.startedAt,
|
|
7004
7640
|
runNodeId: targetRunNodeId,
|
|
7641
|
+
releaseId: runEntry.releaseId,
|
|
7642
|
+
designRevision: runEntry.designRevision,
|
|
7005
7643
|
});
|
|
7006
7644
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
7007
7645
|
nextRunAt: computeNext(config),
|
|
@@ -7022,6 +7660,8 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7022
7660
|
endedAt,
|
|
7023
7661
|
durationMs: endedAt - runEntry.startedAt,
|
|
7024
7662
|
runNodeId: targetRunNodeId,
|
|
7663
|
+
releaseId: runEntry.releaseId,
|
|
7664
|
+
designRevision: runEntry.designRevision,
|
|
7025
7665
|
error: stopped ? "" : error,
|
|
7026
7666
|
});
|
|
7027
7667
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -7049,6 +7689,8 @@ function finishWorkspaceDeferredRun(entry, status, patch = {}) {
|
|
|
7049
7689
|
endedAt,
|
|
7050
7690
|
durationMs: Math.max(0, endedAt - Number(entry.startedAt || endedAt)),
|
|
7051
7691
|
runNodeId: entry.runNodeId || "",
|
|
7692
|
+
releaseId: entry.releaseId || "",
|
|
7693
|
+
designRevision: entry.designRevision || "",
|
|
7052
7694
|
error: String(patch.error || ""),
|
|
7053
7695
|
});
|
|
7054
7696
|
if (entry.scheduleKey) {
|
|
@@ -7094,13 +7736,16 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7094
7736
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
7095
7737
|
};
|
|
7096
7738
|
try {
|
|
7097
|
-
const
|
|
7098
|
-
const
|
|
7739
|
+
const executionRoot = String(claimed.executionRoot || "").trim() || scoped.root;
|
|
7740
|
+
const executionScoped = executionRoot === scoped.root ? scoped : { ...scoped, root: executionRoot };
|
|
7741
|
+
const graph = hydrateWorkspaceGraphForRuntime(root, executionScoped, readWorkspaceGraph(executionRoot, root).graph, userCtx);
|
|
7742
|
+
const result = await runWorkspaceGraph(root, executionRoot, {
|
|
7099
7743
|
flowId: claimed.flowId,
|
|
7100
7744
|
flowSource: claimed.flowSource || "user",
|
|
7101
7745
|
runNodeId: claimed.runNodeId,
|
|
7102
7746
|
graph,
|
|
7103
7747
|
}, userCtx, {
|
|
7748
|
+
runtimeRoot: scoped.root,
|
|
7104
7749
|
signal: controller.signal,
|
|
7105
7750
|
onActiveChild: (child, options = {}) => runControl.setChild(child, options),
|
|
7106
7751
|
onEvent: (event) => appendWorkspaceRunLogEvent(claimed.runId, event),
|
|
@@ -7108,7 +7753,12 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7108
7753
|
});
|
|
7109
7754
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
7110
7755
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
7111
|
-
|
|
7756
|
+
if (claimed.releaseId) {
|
|
7757
|
+
writeWorkspaceReleaseRuntimeState(scoped.root, claimed.releaseId, result.graph);
|
|
7758
|
+
}
|
|
7759
|
+
const mergedGraph = claimed.releaseId
|
|
7760
|
+
? mergeWorkspaceRunState(currentGraph, result.graph, touchedIds)
|
|
7761
|
+
: mergeWorkspaceRunGraph(currentGraph, result.graph, touchedIds);
|
|
7112
7762
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
7113
7763
|
if (result.deferred) {
|
|
7114
7764
|
const waiting = upsertWorkspaceDeferredRun({ ...claimed, deferredKey: claimed.key }, result.deferred);
|