@fieldwangai/agentflow 0.1.163 → 0.1.165
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/marketplace-usage.mjs +4 -3
- package/bin/lib/ui-server.mjs +63 -9
- package/bin/lib/workspace-routes.mjs +428 -28
- package/bin/lib/workspace-run-logs.mjs +2 -0
- package/bin/lib/workspace-server.mjs +364 -36
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DmhIgoD7.js → WorkflowAssistantThread-pVdrZ-Rl.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BQeq5tdj.css +1 -0
- package/builtin/web-ui/dist/assets/index-Czutb6ai.js +873 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-CI9J6Unt.js +0 -873
- package/builtin/web-ui/dist/assets/index-DY5vE7v1.css +0 -1
|
@@ -42,9 +42,10 @@ import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
|
42
42
|
import { sendWecomAppMarkdown, sendWecomGroupMarkdown } from "./wecom.mjs";
|
|
43
43
|
import { getWorkspaceCollaborationByFlow, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, workspaceCollaborationAccess, workspaceCollaborationSummary } from "./workspace-collaboration.mjs";
|
|
44
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";
|
|
45
46
|
import { createWorkspaceRunController, terminateWorkspaceChild } from "./workspace-run-controller.mjs";
|
|
46
47
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession } from "./workspace-run-logs.mjs";
|
|
47
|
-
import { splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
48
|
+
import { mergeWorkspaceState, splitWorkspaceGraph } from "./workspace-state.mjs";
|
|
48
49
|
import { isWorkspaceDraftDir } from "./workspace-draft.mjs";
|
|
49
50
|
import { getPipelineFiles } from "./workspace-tree.mjs";
|
|
50
51
|
import { spawn } from "child_process";
|
|
@@ -708,6 +709,249 @@ export function readWorkspaceGraph(workspaceRoot, marketplaceRoot = "") {
|
|
|
708
709
|
return { path: designPath, graph };
|
|
709
710
|
}
|
|
710
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
|
+
|
|
711
955
|
const DISPLAY_SHARE_FILENAME = "display-shares.json";
|
|
712
956
|
|
|
713
957
|
const DISPLAY_SHARE_ALLOWED_EXPIRY_DAYS = new Set([1, 7, 30, 90, 365]);
|
|
@@ -1252,6 +1496,24 @@ export function mergeWorkspaceRunGraph(currentGraph, runGraph, touchedIds) {
|
|
|
1252
1496
|
};
|
|
1253
1497
|
}
|
|
1254
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
|
+
|
|
1255
1517
|
export function mergeWorkspacePersistentNodeRefs(incomingGraph, currentGraph) {
|
|
1256
1518
|
const incoming = normalizeWorkspaceGraphPayload(incomingGraph || {});
|
|
1257
1519
|
const current = normalizeWorkspaceGraphPayload(currentGraph || {});
|
|
@@ -1489,30 +1751,45 @@ export function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {})
|
|
|
1489
1751
|
if (opts.isAdmin !== true) {
|
|
1490
1752
|
return { root: "", error: "Admin permission required", status: 403 };
|
|
1491
1753
|
}
|
|
1492
|
-
if (
|
|
1493
|
-
return { root: "", error: "Admin read-only review only supports user
|
|
1754
|
+
if (!["user", "workspace"].includes(flowSource)) {
|
|
1755
|
+
return { root: "", error: "Admin read-only review only supports user or shared Workspaces", status: 400 };
|
|
1494
1756
|
}
|
|
1495
|
-
const
|
|
1757
|
+
const collaboration = flowSource === "workspace"
|
|
1758
|
+
? getWorkspaceCollaborationByFlow(flowId, archived)
|
|
1759
|
+
: null;
|
|
1760
|
+
const owner = adminWorkspaceOwnerSummary(collaboration?.ownerId || adminOwnerId);
|
|
1496
1761
|
if (!owner) {
|
|
1497
1762
|
return { root: "", error: "Workspace owner not found", status: 404 };
|
|
1498
1763
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1764
|
+
let targetPath = "";
|
|
1765
|
+
let physicalFlowSource = flowSource;
|
|
1766
|
+
if (flowSource === "user") {
|
|
1767
|
+
const targetFlow = listFlowsJson(workspaceRoot, { userId: owner.userId })
|
|
1768
|
+
.find((flow) => (
|
|
1769
|
+
flow.id === flowId
|
|
1770
|
+
&& (flow.source || "user") === "user"
|
|
1771
|
+
&& Boolean(flow.archived) === archived
|
|
1772
|
+
));
|
|
1773
|
+
targetPath = targetFlow?.path || "";
|
|
1774
|
+
} else {
|
|
1775
|
+
physicalFlowSource = collaboration?.projectSource || collaboration?.flowSource || "workspace";
|
|
1776
|
+
const result = getPipelineFiles(workspaceRoot, flowId, physicalFlowSource, archived, {
|
|
1777
|
+
...opts,
|
|
1778
|
+
userId: owner.userId,
|
|
1779
|
+
});
|
|
1780
|
+
targetPath = result?.path || "";
|
|
1781
|
+
}
|
|
1782
|
+
if (!targetPath) {
|
|
1506
1783
|
return { root: "", error: "Pipeline workspace not found", status: 404 };
|
|
1507
1784
|
}
|
|
1508
1785
|
return {
|
|
1509
|
-
root: path.resolve(
|
|
1786
|
+
root: path.resolve(targetPath),
|
|
1510
1787
|
flowId,
|
|
1511
|
-
flowSource:
|
|
1788
|
+
flowSource: physicalFlowSource,
|
|
1512
1789
|
requestedFlowSource: flowSource,
|
|
1513
|
-
workspaceId: "",
|
|
1790
|
+
workspaceId: collaboration?.id || "",
|
|
1514
1791
|
archived,
|
|
1515
|
-
collaboration
|
|
1792
|
+
collaboration,
|
|
1516
1793
|
collaborationAccess: {
|
|
1517
1794
|
allowed: true,
|
|
1518
1795
|
writable: false,
|
|
@@ -4668,12 +4945,13 @@ function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
|
4668
4945
|
return dir;
|
|
4669
4946
|
}
|
|
4670
4947
|
|
|
4671
|
-
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {}, durableOutputs = false } = {}) {
|
|
4948
|
+
function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, sourceRoot = "", cwd = "", task = "", inputValues = {}, skillsBlock = "", mcpBlock = "", resultFile = "", outParamFiles = {}, durableOutputs = false } = {}) {
|
|
4672
4949
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
4673
4950
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
4674
4951
|
const legacyOutputsDir = path.join(nodeRunDir, "outputs");
|
|
4675
|
-
const workspaceRoot = path.resolve(scopedRoot);
|
|
4676
|
-
const
|
|
4952
|
+
const workspaceRoot = path.resolve(sourceRoot || scopedRoot);
|
|
4953
|
+
const outputWorkspaceRoot = path.resolve(scopedRoot);
|
|
4954
|
+
const workspaceOutputsDir = path.join(outputWorkspaceRoot, "outputs");
|
|
4677
4955
|
const nodePart = workspaceSanitizeTmpSegment(nodeId || "node", "node");
|
|
4678
4956
|
const outputsRel = durableOutputs ? path.posix.join("outputs", nodePart) : "outputs";
|
|
4679
4957
|
const outputsDir = durableOutputs ? path.join(workspaceOutputsDir, nodePart) : legacyOutputsDir;
|
|
@@ -4703,6 +4981,7 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
4703
4981
|
outputsRel,
|
|
4704
4982
|
directWorkspaceOutputs: durableOutputs,
|
|
4705
4983
|
workspaceRoot,
|
|
4984
|
+
outputWorkspaceRoot,
|
|
4706
4985
|
workspaceOutputsDir,
|
|
4707
4986
|
executionCwd: cwd ? path.resolve(cwd) : workspaceRoot,
|
|
4708
4987
|
resultFileRel,
|
|
@@ -5277,22 +5556,23 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5277
5556
|
emit({ type: "status", nodeId, line: `Timing ${label}: ${elapsedMs}ms`, timing: { label, elapsedMs, ...extra } });
|
|
5278
5557
|
};
|
|
5279
5558
|
let cwd = scopedRoot;
|
|
5559
|
+
const runtimeStorageRoot = path.resolve(opts.runtimeRoot || scopedRoot);
|
|
5280
5560
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
5281
5561
|
const runEnv = {};
|
|
5282
5562
|
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
5283
5563
|
const autoCleanupWorktrees = [];
|
|
5284
|
-
const runTmpRoot = workspaceCreateRunTmpRoot(
|
|
5564
|
+
const runTmpRoot = workspaceCreateRunTmpRoot(runtimeStorageRoot, runNodeId);
|
|
5285
5565
|
const runtimeRunId = String(opts.runId || payload?.runId || "").trim() || runLedgerId("workspace-execution");
|
|
5286
5566
|
const ownsRunManifest = !(Array.isArray(opts?.subflowCallStack) && opts.subflowCallStack.length);
|
|
5287
5567
|
const persistRunManifest = (status, extra = {}) => {
|
|
5288
5568
|
if (!ownsRunManifest) return null;
|
|
5289
|
-
return workspaceWriteRunManifest(
|
|
5569
|
+
return workspaceWriteRunManifest(runtimeStorageRoot, runtimeRunId, {
|
|
5290
5570
|
flowId: String(payload?.flowId || ""),
|
|
5291
5571
|
flowSource: String(payload?.flowSource || "user"),
|
|
5292
5572
|
runNodeId,
|
|
5293
5573
|
status,
|
|
5294
5574
|
runtimeRoot: runTmpRoot,
|
|
5295
|
-
artifactRoot: path.join(
|
|
5575
|
+
artifactRoot: path.join(runtimeStorageRoot, "outputs"),
|
|
5296
5576
|
worktrees: autoCleanupWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
5297
5577
|
...extra,
|
|
5298
5578
|
});
|
|
@@ -5705,7 +5985,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5705
5985
|
});
|
|
5706
5986
|
}
|
|
5707
5987
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, `${nodeId}-iteration-${iteration}`, {
|
|
5708
|
-
scopedRoot,
|
|
5988
|
+
scopedRoot: runtimeStorageRoot,
|
|
5989
|
+
sourceRoot: scopedRoot,
|
|
5709
5990
|
cwd,
|
|
5710
5991
|
task: stepScript || stepScriptRef,
|
|
5711
5992
|
inputValues: iterationInputs,
|
|
@@ -6283,7 +6564,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6283
6564
|
const prepareStartedAt = Date.now();
|
|
6284
6565
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
6285
6566
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6286
|
-
scopedRoot,
|
|
6567
|
+
scopedRoot: runtimeStorageRoot,
|
|
6568
|
+
sourceRoot: scopedRoot,
|
|
6287
6569
|
cwd,
|
|
6288
6570
|
task: String(instance.script || instance.scriptRef || instance.body || "").trim(),
|
|
6289
6571
|
inputValues,
|
|
@@ -6357,7 +6639,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6357
6639
|
);
|
|
6358
6640
|
const resultOutputSpec = workspaceResultOutputSpec(graph, nodeId);
|
|
6359
6641
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6360
|
-
scopedRoot,
|
|
6642
|
+
scopedRoot: runtimeStorageRoot,
|
|
6643
|
+
sourceRoot: scopedRoot,
|
|
6361
6644
|
cwd,
|
|
6362
6645
|
task: workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim() || upstreamText,
|
|
6363
6646
|
inputValues: relevantInputs.values,
|
|
@@ -6767,6 +7050,9 @@ export function upsertWorkspaceDeferredRun(meta = {}, deferred = {}) {
|
|
|
6767
7050
|
label: String(meta.label || previous.label || "Workspace Run"),
|
|
6768
7051
|
plannedNodeIds: Array.isArray(meta.plannedNodeIds) ? meta.plannedNodeIds.map(String) : (previous.plannedNodeIds || []),
|
|
6769
7052
|
workspaceRoot: String(meta.workspaceRoot || previous.workspaceRoot || ""),
|
|
7053
|
+
executionRoot: String(meta.executionRoot || previous.executionRoot || ""),
|
|
7054
|
+
releaseId: String(meta.releaseId || previous.releaseId || ""),
|
|
7055
|
+
designRevision: String(meta.designRevision || previous.designRevision || ""),
|
|
6770
7056
|
marketplaceResources: Array.isArray(meta.marketplaceResources)
|
|
6771
7057
|
? meta.marketplaceResources.map((item) => ({
|
|
6772
7058
|
kind: String(item?.kind || ""),
|
|
@@ -6969,7 +7255,8 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6969
7255
|
if (scoped.error || !scoped.root) continue;
|
|
6970
7256
|
let graph;
|
|
6971
7257
|
try {
|
|
6972
|
-
graph =
|
|
7258
|
+
graph = readWorkspaceStableRelease(scoped.root, root)?.graph
|
|
7259
|
+
|| readWorkspaceGraph(scoped.root, root).graph;
|
|
6973
7260
|
} catch {
|
|
6974
7261
|
continue;
|
|
6975
7262
|
}
|
|
@@ -6999,6 +7286,9 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6999
7286
|
rows.push({
|
|
7000
7287
|
kind: "workspace",
|
|
7001
7288
|
key,
|
|
7289
|
+
registered: Boolean(registry.schedules?.[key]),
|
|
7290
|
+
ownerUserId: scheduleUserId,
|
|
7291
|
+
ownerUsername: String(current.username || scheduleUserId),
|
|
7002
7292
|
flowId,
|
|
7003
7293
|
flowSource,
|
|
7004
7294
|
workspaceId: String(flow.collaboration?.id || scoped.workspaceId || ""),
|
|
@@ -7062,11 +7352,12 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
|
|
|
7062
7352
|
writeWorkspaceScheduleRegistry({ version: 1, schedules });
|
|
7063
7353
|
return [];
|
|
7064
7354
|
}
|
|
7065
|
-
const
|
|
7355
|
+
const effectiveGraph = readWorkspaceStableRelease(scoped?.root || "", root)?.graph || graph;
|
|
7356
|
+
const instances = effectiveGraph?.instances && typeof effectiveGraph.instances === "object" ? effectiveGraph.instances : {};
|
|
7066
7357
|
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
7067
7358
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
7068
7359
|
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
7069
|
-
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(
|
|
7360
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(effectiveGraph, scheduleNodeId, config);
|
|
7070
7361
|
const key = workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId);
|
|
7071
7362
|
const previous = registry.schedules?.[key] && typeof registry.schedules[key] === "object" ? registry.schedules[key] : {};
|
|
7072
7363
|
const previousNext = Number(previous.nextRunAt || 0);
|
|
@@ -7187,7 +7478,15 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7187
7478
|
});
|
|
7188
7479
|
return;
|
|
7189
7480
|
}
|
|
7190
|
-
const
|
|
7481
|
+
const stableRelease = readWorkspaceStableRelease(scoped.root, root);
|
|
7482
|
+
const executionRoot = stableRelease?.root || scoped.root;
|
|
7483
|
+
const executionScoped = stableRelease ? { ...scoped, root: executionRoot } : scoped;
|
|
7484
|
+
const graph = hydrateWorkspaceGraphForRuntime(
|
|
7485
|
+
root,
|
|
7486
|
+
executionScoped,
|
|
7487
|
+
stableRelease?.graph || readWorkspaceGraph(scoped.root, root).graph,
|
|
7488
|
+
userCtx,
|
|
7489
|
+
);
|
|
7191
7490
|
const scheduleNodeId = String(entry.scheduleNodeId || entry.key?.split(":").pop() || "");
|
|
7192
7491
|
const instance = graph.instances?.[scheduleNodeId];
|
|
7193
7492
|
const config = normalizeWorkspaceScheduledRunConfig(instance?.body || "");
|
|
@@ -7221,7 +7520,7 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7221
7520
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "scheduler-triggered", scheduleNodeId, runNodeId: targetRunNodeId, cron: config.cron, timezone: config.timezone });
|
|
7222
7521
|
let plan;
|
|
7223
7522
|
try {
|
|
7224
|
-
plan = workspaceRunPlan(graph, targetRunNodeId,
|
|
7523
|
+
plan = workspaceRunPlan(graph, targetRunNodeId, executionRoot);
|
|
7225
7524
|
} catch (e) {
|
|
7226
7525
|
const error = (e && e.message) || String(e);
|
|
7227
7526
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "error", error });
|
|
@@ -7274,8 +7573,17 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7274
7573
|
startedAt: Date.now(),
|
|
7275
7574
|
scheduled: true,
|
|
7276
7575
|
workspaceRoot: root,
|
|
7277
|
-
|
|
7576
|
+
executionRoot,
|
|
7577
|
+
releaseId: stableRelease?.release?.id || "",
|
|
7578
|
+
designRevision: stableRelease?.release?.designRevision || workspaceDesignRevision(graph),
|
|
7579
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, plannedNodeIds),
|
|
7278
7580
|
};
|
|
7581
|
+
appendWorkspaceRunLogEvent(runLog.runId, {
|
|
7582
|
+
type: "release-resolved",
|
|
7583
|
+
releaseId: runEntry.releaseId || "legacy-current",
|
|
7584
|
+
revision: runEntry.designRevision,
|
|
7585
|
+
source: stableRelease ? "stable" : "legacy-current",
|
|
7586
|
+
});
|
|
7279
7587
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
7280
7588
|
appendWorkspaceRunStarted(runEntry);
|
|
7281
7589
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -7292,12 +7600,13 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7292
7600
|
runControl.setChild(child, childOptions);
|
|
7293
7601
|
};
|
|
7294
7602
|
try {
|
|
7295
|
-
const result = await runWorkspaceGraph(root,
|
|
7603
|
+
const result = await runWorkspaceGraph(root, executionRoot, {
|
|
7296
7604
|
flowId: entry.flowId,
|
|
7297
7605
|
flowSource: entry.flowSource || "user",
|
|
7298
7606
|
runNodeId: targetRunNodeId,
|
|
7299
7607
|
graph,
|
|
7300
7608
|
}, userCtx, {
|
|
7609
|
+
runtimeRoot: scoped.root,
|
|
7301
7610
|
signal: controller.signal,
|
|
7302
7611
|
onActiveChild: setActiveChild,
|
|
7303
7612
|
onEvent: (event) => appendWorkspaceRunLogEvent(runLog.runId, event),
|
|
@@ -7305,7 +7614,12 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7305
7614
|
});
|
|
7306
7615
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
7307
7616
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
7308
|
-
|
|
7617
|
+
if (stableRelease?.release?.id) {
|
|
7618
|
+
writeWorkspaceReleaseRuntimeState(scoped.root, stableRelease.release.id, result.graph);
|
|
7619
|
+
}
|
|
7620
|
+
const mergedGraph = stableRelease
|
|
7621
|
+
? mergeWorkspaceRunState(currentGraph, result.graph, touchedIds)
|
|
7622
|
+
: mergeWorkspaceRunGraph(currentGraph, result.graph, touchedIds);
|
|
7309
7623
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
7310
7624
|
if (result.deferred) {
|
|
7311
7625
|
const waiting = upsertWorkspaceDeferredRun({
|
|
@@ -7333,12 +7647,14 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7333
7647
|
...runEntry,
|
|
7334
7648
|
endedAt,
|
|
7335
7649
|
durationMs: endedAt - runEntry.startedAt,
|
|
7336
|
-
marketplaceResources: marketplaceResourcesForRun(
|
|
7650
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, result.order || Array.from(touchedIds)),
|
|
7337
7651
|
}, "success");
|
|
7338
7652
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
7339
7653
|
endedAt,
|
|
7340
7654
|
durationMs: endedAt - runEntry.startedAt,
|
|
7341
7655
|
runNodeId: targetRunNodeId,
|
|
7656
|
+
releaseId: runEntry.releaseId,
|
|
7657
|
+
designRevision: runEntry.designRevision,
|
|
7342
7658
|
});
|
|
7343
7659
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
7344
7660
|
nextRunAt: computeNext(config),
|
|
@@ -7359,6 +7675,8 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7359
7675
|
endedAt,
|
|
7360
7676
|
durationMs: endedAt - runEntry.startedAt,
|
|
7361
7677
|
runNodeId: targetRunNodeId,
|
|
7678
|
+
releaseId: runEntry.releaseId,
|
|
7679
|
+
designRevision: runEntry.designRevision,
|
|
7362
7680
|
error: stopped ? "" : error,
|
|
7363
7681
|
});
|
|
7364
7682
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -7386,6 +7704,8 @@ function finishWorkspaceDeferredRun(entry, status, patch = {}) {
|
|
|
7386
7704
|
endedAt,
|
|
7387
7705
|
durationMs: Math.max(0, endedAt - Number(entry.startedAt || endedAt)),
|
|
7388
7706
|
runNodeId: entry.runNodeId || "",
|
|
7707
|
+
releaseId: entry.releaseId || "",
|
|
7708
|
+
designRevision: entry.designRevision || "",
|
|
7389
7709
|
error: String(patch.error || ""),
|
|
7390
7710
|
});
|
|
7391
7711
|
if (entry.scheduleKey) {
|
|
@@ -7431,13 +7751,16 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7431
7751
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
7432
7752
|
};
|
|
7433
7753
|
try {
|
|
7434
|
-
const
|
|
7435
|
-
const
|
|
7754
|
+
const executionRoot = String(claimed.executionRoot || "").trim() || scoped.root;
|
|
7755
|
+
const executionScoped = executionRoot === scoped.root ? scoped : { ...scoped, root: executionRoot };
|
|
7756
|
+
const graph = hydrateWorkspaceGraphForRuntime(root, executionScoped, readWorkspaceGraph(executionRoot, root).graph, userCtx);
|
|
7757
|
+
const result = await runWorkspaceGraph(root, executionRoot, {
|
|
7436
7758
|
flowId: claimed.flowId,
|
|
7437
7759
|
flowSource: claimed.flowSource || "user",
|
|
7438
7760
|
runNodeId: claimed.runNodeId,
|
|
7439
7761
|
graph,
|
|
7440
7762
|
}, userCtx, {
|
|
7763
|
+
runtimeRoot: scoped.root,
|
|
7441
7764
|
signal: controller.signal,
|
|
7442
7765
|
onActiveChild: (child, options = {}) => runControl.setChild(child, options),
|
|
7443
7766
|
onEvent: (event) => appendWorkspaceRunLogEvent(claimed.runId, event),
|
|
@@ -7445,7 +7768,12 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7445
7768
|
});
|
|
7446
7769
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
7447
7770
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
7448
|
-
|
|
7771
|
+
if (claimed.releaseId) {
|
|
7772
|
+
writeWorkspaceReleaseRuntimeState(scoped.root, claimed.releaseId, result.graph);
|
|
7773
|
+
}
|
|
7774
|
+
const mergedGraph = claimed.releaseId
|
|
7775
|
+
? mergeWorkspaceRunState(currentGraph, result.graph, touchedIds)
|
|
7776
|
+
: mergeWorkspaceRunGraph(currentGraph, result.graph, touchedIds);
|
|
7449
7777
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
7450
7778
|
if (result.deferred) {
|
|
7451
7779
|
const waiting = upsertWorkspaceDeferredRun({ ...claimed, deferredKey: claimed.key }, result.deferred);
|