@fieldwangai/agentflow 0.1.163 → 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/ui-server.mjs +63 -9
- package/bin/lib/workspace-routes.mjs +220 -16
- package/bin/lib/workspace-run-logs.mjs +2 -0
- package/bin/lib/workspace-server.mjs +335 -22
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-DmhIgoD7.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/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 || {});
|
|
@@ -4668,12 +4930,13 @@ function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
|
4668
4930
|
return dir;
|
|
4669
4931
|
}
|
|
4670
4932
|
|
|
4671
|
-
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 } = {}) {
|
|
4672
4934
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
4673
4935
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
4674
4936
|
const legacyOutputsDir = path.join(nodeRunDir, "outputs");
|
|
4675
|
-
const workspaceRoot = path.resolve(scopedRoot);
|
|
4676
|
-
const
|
|
4937
|
+
const workspaceRoot = path.resolve(sourceRoot || scopedRoot);
|
|
4938
|
+
const outputWorkspaceRoot = path.resolve(scopedRoot);
|
|
4939
|
+
const workspaceOutputsDir = path.join(outputWorkspaceRoot, "outputs");
|
|
4677
4940
|
const nodePart = workspaceSanitizeTmpSegment(nodeId || "node", "node");
|
|
4678
4941
|
const outputsRel = durableOutputs ? path.posix.join("outputs", nodePart) : "outputs";
|
|
4679
4942
|
const outputsDir = durableOutputs ? path.join(workspaceOutputsDir, nodePart) : legacyOutputsDir;
|
|
@@ -4703,6 +4966,7 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, cwd = "
|
|
|
4703
4966
|
outputsRel,
|
|
4704
4967
|
directWorkspaceOutputs: durableOutputs,
|
|
4705
4968
|
workspaceRoot,
|
|
4969
|
+
outputWorkspaceRoot,
|
|
4706
4970
|
workspaceOutputsDir,
|
|
4707
4971
|
executionCwd: cwd ? path.resolve(cwd) : workspaceRoot,
|
|
4708
4972
|
resultFileRel,
|
|
@@ -5277,22 +5541,23 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5277
5541
|
emit({ type: "status", nodeId, line: `Timing ${label}: ${elapsedMs}ms`, timing: { label, elapsedMs, ...extra } });
|
|
5278
5542
|
};
|
|
5279
5543
|
let cwd = scopedRoot;
|
|
5544
|
+
const runtimeStorageRoot = path.resolve(opts.runtimeRoot || scopedRoot);
|
|
5280
5545
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
5281
5546
|
const runEnv = {};
|
|
5282
5547
|
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
5283
5548
|
const autoCleanupWorktrees = [];
|
|
5284
|
-
const runTmpRoot = workspaceCreateRunTmpRoot(
|
|
5549
|
+
const runTmpRoot = workspaceCreateRunTmpRoot(runtimeStorageRoot, runNodeId);
|
|
5285
5550
|
const runtimeRunId = String(opts.runId || payload?.runId || "").trim() || runLedgerId("workspace-execution");
|
|
5286
5551
|
const ownsRunManifest = !(Array.isArray(opts?.subflowCallStack) && opts.subflowCallStack.length);
|
|
5287
5552
|
const persistRunManifest = (status, extra = {}) => {
|
|
5288
5553
|
if (!ownsRunManifest) return null;
|
|
5289
|
-
return workspaceWriteRunManifest(
|
|
5554
|
+
return workspaceWriteRunManifest(runtimeStorageRoot, runtimeRunId, {
|
|
5290
5555
|
flowId: String(payload?.flowId || ""),
|
|
5291
5556
|
flowSource: String(payload?.flowSource || "user"),
|
|
5292
5557
|
runNodeId,
|
|
5293
5558
|
status,
|
|
5294
5559
|
runtimeRoot: runTmpRoot,
|
|
5295
|
-
artifactRoot: path.join(
|
|
5560
|
+
artifactRoot: path.join(runtimeStorageRoot, "outputs"),
|
|
5296
5561
|
worktrees: autoCleanupWorktrees.map((entry) => ({ ...entry, removed: false })),
|
|
5297
5562
|
...extra,
|
|
5298
5563
|
});
|
|
@@ -5705,7 +5970,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
5705
5970
|
});
|
|
5706
5971
|
}
|
|
5707
5972
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, `${nodeId}-iteration-${iteration}`, {
|
|
5708
|
-
scopedRoot,
|
|
5973
|
+
scopedRoot: runtimeStorageRoot,
|
|
5974
|
+
sourceRoot: scopedRoot,
|
|
5709
5975
|
cwd,
|
|
5710
5976
|
task: stepScript || stepScriptRef,
|
|
5711
5977
|
inputValues: iterationInputs,
|
|
@@ -6283,7 +6549,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6283
6549
|
const prepareStartedAt = Date.now();
|
|
6284
6550
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
6285
6551
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6286
|
-
scopedRoot,
|
|
6552
|
+
scopedRoot: runtimeStorageRoot,
|
|
6553
|
+
sourceRoot: scopedRoot,
|
|
6287
6554
|
cwd,
|
|
6288
6555
|
task: String(instance.script || instance.scriptRef || instance.body || "").trim(),
|
|
6289
6556
|
inputValues,
|
|
@@ -6357,7 +6624,8 @@ export async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {},
|
|
|
6357
6624
|
);
|
|
6358
6625
|
const resultOutputSpec = workspaceResultOutputSpec(graph, nodeId);
|
|
6359
6626
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
6360
|
-
scopedRoot,
|
|
6627
|
+
scopedRoot: runtimeStorageRoot,
|
|
6628
|
+
sourceRoot: scopedRoot,
|
|
6361
6629
|
cwd,
|
|
6362
6630
|
task: workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim() || upstreamText,
|
|
6363
6631
|
inputValues: relevantInputs.values,
|
|
@@ -6767,6 +7035,9 @@ export function upsertWorkspaceDeferredRun(meta = {}, deferred = {}) {
|
|
|
6767
7035
|
label: String(meta.label || previous.label || "Workspace Run"),
|
|
6768
7036
|
plannedNodeIds: Array.isArray(meta.plannedNodeIds) ? meta.plannedNodeIds.map(String) : (previous.plannedNodeIds || []),
|
|
6769
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 || ""),
|
|
6770
7041
|
marketplaceResources: Array.isArray(meta.marketplaceResources)
|
|
6771
7042
|
? meta.marketplaceResources.map((item) => ({
|
|
6772
7043
|
kind: String(item?.kind || ""),
|
|
@@ -6969,7 +7240,8 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6969
7240
|
if (scoped.error || !scoped.root) continue;
|
|
6970
7241
|
let graph;
|
|
6971
7242
|
try {
|
|
6972
|
-
graph =
|
|
7243
|
+
graph = readWorkspaceStableRelease(scoped.root, root)?.graph
|
|
7244
|
+
|| readWorkspaceGraph(scoped.root, root).graph;
|
|
6973
7245
|
} catch {
|
|
6974
7246
|
continue;
|
|
6975
7247
|
}
|
|
@@ -6999,6 +7271,9 @@ export function listWorkspaceScheduleStatuses(root, userCtx = {}) {
|
|
|
6999
7271
|
rows.push({
|
|
7000
7272
|
kind: "workspace",
|
|
7001
7273
|
key,
|
|
7274
|
+
registered: Boolean(registry.schedules?.[key]),
|
|
7275
|
+
ownerUserId: scheduleUserId,
|
|
7276
|
+
ownerUsername: String(current.username || scheduleUserId),
|
|
7002
7277
|
flowId,
|
|
7003
7278
|
flowSource,
|
|
7004
7279
|
workspaceId: String(flow.collaboration?.id || scoped.workspaceId || ""),
|
|
@@ -7062,11 +7337,12 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
|
|
|
7062
7337
|
writeWorkspaceScheduleRegistry({ version: 1, schedules });
|
|
7063
7338
|
return [];
|
|
7064
7339
|
}
|
|
7065
|
-
const
|
|
7340
|
+
const effectiveGraph = readWorkspaceStableRelease(scoped?.root || "", root)?.graph || graph;
|
|
7341
|
+
const instances = effectiveGraph?.instances && typeof effectiveGraph.instances === "object" ? effectiveGraph.instances : {};
|
|
7066
7342
|
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
7067
7343
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
7068
7344
|
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
7069
|
-
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(
|
|
7345
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(effectiveGraph, scheduleNodeId, config);
|
|
7070
7346
|
const key = workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId);
|
|
7071
7347
|
const previous = registry.schedules?.[key] && typeof registry.schedules[key] === "object" ? registry.schedules[key] : {};
|
|
7072
7348
|
const previousNext = Number(previous.nextRunAt || 0);
|
|
@@ -7187,7 +7463,15 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7187
7463
|
});
|
|
7188
7464
|
return;
|
|
7189
7465
|
}
|
|
7190
|
-
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
|
+
);
|
|
7191
7475
|
const scheduleNodeId = String(entry.scheduleNodeId || entry.key?.split(":").pop() || "");
|
|
7192
7476
|
const instance = graph.instances?.[scheduleNodeId];
|
|
7193
7477
|
const config = normalizeWorkspaceScheduledRunConfig(instance?.body || "");
|
|
@@ -7221,7 +7505,7 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7221
7505
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "scheduler-triggered", scheduleNodeId, runNodeId: targetRunNodeId, cron: config.cron, timezone: config.timezone });
|
|
7222
7506
|
let plan;
|
|
7223
7507
|
try {
|
|
7224
|
-
plan = workspaceRunPlan(graph, targetRunNodeId,
|
|
7508
|
+
plan = workspaceRunPlan(graph, targetRunNodeId, executionRoot);
|
|
7225
7509
|
} catch (e) {
|
|
7226
7510
|
const error = (e && e.message) || String(e);
|
|
7227
7511
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "error", error });
|
|
@@ -7274,8 +7558,17 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7274
7558
|
startedAt: Date.now(),
|
|
7275
7559
|
scheduled: true,
|
|
7276
7560
|
workspaceRoot: root,
|
|
7277
|
-
|
|
7561
|
+
executionRoot,
|
|
7562
|
+
releaseId: stableRelease?.release?.id || "",
|
|
7563
|
+
designRevision: stableRelease?.release?.designRevision || workspaceDesignRevision(graph),
|
|
7564
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, plannedNodeIds),
|
|
7278
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
|
+
});
|
|
7279
7572
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
7280
7573
|
appendWorkspaceRunStarted(runEntry);
|
|
7281
7574
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -7292,12 +7585,13 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7292
7585
|
runControl.setChild(child, childOptions);
|
|
7293
7586
|
};
|
|
7294
7587
|
try {
|
|
7295
|
-
const result = await runWorkspaceGraph(root,
|
|
7588
|
+
const result = await runWorkspaceGraph(root, executionRoot, {
|
|
7296
7589
|
flowId: entry.flowId,
|
|
7297
7590
|
flowSource: entry.flowSource || "user",
|
|
7298
7591
|
runNodeId: targetRunNodeId,
|
|
7299
7592
|
graph,
|
|
7300
7593
|
}, userCtx, {
|
|
7594
|
+
runtimeRoot: scoped.root,
|
|
7301
7595
|
signal: controller.signal,
|
|
7302
7596
|
onActiveChild: setActiveChild,
|
|
7303
7597
|
onEvent: (event) => appendWorkspaceRunLogEvent(runLog.runId, event),
|
|
@@ -7305,7 +7599,12 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7305
7599
|
});
|
|
7306
7600
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
7307
7601
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
7308
|
-
|
|
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);
|
|
7309
7608
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
7310
7609
|
if (result.deferred) {
|
|
7311
7610
|
const waiting = upsertWorkspaceDeferredRun({
|
|
@@ -7333,12 +7632,14 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7333
7632
|
...runEntry,
|
|
7334
7633
|
endedAt,
|
|
7335
7634
|
durationMs: endedAt - runEntry.startedAt,
|
|
7336
|
-
marketplaceResources: marketplaceResourcesForRun(
|
|
7635
|
+
marketplaceResources: marketplaceResourcesForRun(executionRoot, graph, result.order || Array.from(touchedIds)),
|
|
7337
7636
|
}, "success");
|
|
7338
7637
|
finishWorkspaceRunLogSession(runLog.runId, "success", {
|
|
7339
7638
|
endedAt,
|
|
7340
7639
|
durationMs: endedAt - runEntry.startedAt,
|
|
7341
7640
|
runNodeId: targetRunNodeId,
|
|
7641
|
+
releaseId: runEntry.releaseId,
|
|
7642
|
+
designRevision: runEntry.designRevision,
|
|
7342
7643
|
});
|
|
7343
7644
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
7344
7645
|
nextRunAt: computeNext(config),
|
|
@@ -7359,6 +7660,8 @@ export async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
7359
7660
|
endedAt,
|
|
7360
7661
|
durationMs: endedAt - runEntry.startedAt,
|
|
7361
7662
|
runNodeId: targetRunNodeId,
|
|
7663
|
+
releaseId: runEntry.releaseId,
|
|
7664
|
+
designRevision: runEntry.designRevision,
|
|
7362
7665
|
error: stopped ? "" : error,
|
|
7363
7666
|
});
|
|
7364
7667
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
@@ -7386,6 +7689,8 @@ function finishWorkspaceDeferredRun(entry, status, patch = {}) {
|
|
|
7386
7689
|
endedAt,
|
|
7387
7690
|
durationMs: Math.max(0, endedAt - Number(entry.startedAt || endedAt)),
|
|
7388
7691
|
runNodeId: entry.runNodeId || "",
|
|
7692
|
+
releaseId: entry.releaseId || "",
|
|
7693
|
+
designRevision: entry.designRevision || "",
|
|
7389
7694
|
error: String(patch.error || ""),
|
|
7390
7695
|
});
|
|
7391
7696
|
if (entry.scheduleKey) {
|
|
@@ -7431,13 +7736,16 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7431
7736
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
7432
7737
|
};
|
|
7433
7738
|
try {
|
|
7434
|
-
const
|
|
7435
|
-
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, {
|
|
7436
7743
|
flowId: claimed.flowId,
|
|
7437
7744
|
flowSource: claimed.flowSource || "user",
|
|
7438
7745
|
runNodeId: claimed.runNodeId,
|
|
7439
7746
|
graph,
|
|
7440
7747
|
}, userCtx, {
|
|
7748
|
+
runtimeRoot: scoped.root,
|
|
7441
7749
|
signal: controller.signal,
|
|
7442
7750
|
onActiveChild: (child, options = {}) => runControl.setChild(child, options),
|
|
7443
7751
|
onEvent: (event) => appendWorkspaceRunLogEvent(claimed.runId, event),
|
|
@@ -7445,7 +7753,12 @@ async function runWorkspaceDeferredEntry(root, claimed) {
|
|
|
7445
7753
|
});
|
|
7446
7754
|
const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
7447
7755
|
const touchedIds = workspaceRunTouchedNodeIds(result);
|
|
7448
|
-
|
|
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);
|
|
7449
7762
|
writeWorkspaceGraph(scoped.root, mergedGraph, root);
|
|
7450
7763
|
if (result.deferred) {
|
|
7451
7764
|
const waiting = upsertWorkspaceDeferredRun({ ...claimed, deferredKey: claimed.key }, result.deferred);
|