@fieldwangai/agentflow 0.1.138 → 0.1.141
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/catalog-flows.mjs +17 -3
- package/bin/lib/composer-node-schema.mjs +4 -0
- package/bin/lib/i18n.mjs +2 -2
- package/bin/lib/jenkins.mjs +380 -0
- package/bin/lib/locales/en.json +22 -0
- package/bin/lib/locales/zh.json +22 -0
- package/bin/lib/paths.mjs +1 -0
- package/bin/lib/prd-workflow-collaboration.mjs +93 -1
- package/bin/lib/recent-runs.mjs +16 -0
- package/bin/lib/run-node-statuses-from-disk.mjs +41 -3
- package/bin/lib/scheduler.mjs +59 -25
- package/bin/lib/ui-server.mjs +363 -63
- package/bin/pipeline/pre-process-node.mjs +122 -11
- package/bin/pipeline/run-log.mjs +2 -2
- package/bin/pipeline/write-result.mjs +4 -4
- package/builtin/nodes/tool_jenkins_build.md +64 -0
- package/builtin/pipelines/jenkins-build-notify/flow.yaml +217 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B3thaqH2.js → WorkflowAssistantThread-ubxHcM7p.js} +1 -1
- package/builtin/web-ui/dist/assets/index-B6TWUomI.css +1 -0
- package/builtin/web-ui/dist/assets/index-DQzcZp7S.js +590 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-workflow-report/SKILL.md +3 -0
- package/skills/agentflow-workflow-report/references/protocol.md +8 -5
- package/builtin/web-ui/dist/assets/index-COX1zMwq.css +0 -1
- package/builtin/web-ui/dist/assets/index-YS4XOpXF.js +0 -590
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
} from "./composer-log.mjs";
|
|
84
84
|
import { runNodeScript } from "./pipeline-scripts.mjs";
|
|
85
85
|
import { computeNextRunAt, readFlowSchedule, writeFlowSchedule } from "./schedule-config.mjs";
|
|
86
|
-
import { listScheduleStatuses } from "./scheduler.mjs";
|
|
86
|
+
import { cancelScheduledRun, listScheduleStatuses } from "./scheduler.mjs";
|
|
87
87
|
import {
|
|
88
88
|
mergeWorkspaceGraphs,
|
|
89
89
|
workspaceDesignRevision,
|
|
@@ -153,6 +153,7 @@ import {
|
|
|
153
153
|
} from "./workspace-collaboration.mjs";
|
|
154
154
|
import {
|
|
155
155
|
addPrdWorkflowCollaborationMember,
|
|
156
|
+
bindPrdWorkflowProject,
|
|
156
157
|
ensurePrdWorkflowCollaboration,
|
|
157
158
|
getPrdWorkflowCollaborationById,
|
|
158
159
|
getPrdWorkflowCollaborationByShareToken,
|
|
@@ -161,12 +162,14 @@ import {
|
|
|
161
162
|
ensurePrdWorkflowShareLink,
|
|
162
163
|
listPrdWorkflowCollaborationsForUser,
|
|
163
164
|
listPrdWorkflowCollaborationsForTeam,
|
|
165
|
+
listPrdWorkflowProjectBindings,
|
|
164
166
|
prdWorkflowCollaborationAccess,
|
|
165
167
|
prdWorkflowCollaborationSummary,
|
|
166
168
|
removePrdWorkflowCollaborationMember,
|
|
167
169
|
revokePrdWorkflowShareLink,
|
|
168
170
|
setPrdWorkflowKnowledgeBindings,
|
|
169
171
|
syncPrdWorkflowAuthority,
|
|
172
|
+
unbindPrdWorkflowProject,
|
|
170
173
|
} from "./prd-workflow-collaboration.mjs";
|
|
171
174
|
import {
|
|
172
175
|
createTeam,
|
|
@@ -3592,6 +3595,95 @@ function prdWorkflowShareLinkSummary(record, shareToken, publicBaseUrl, userId =
|
|
|
3592
3595
|
};
|
|
3593
3596
|
}
|
|
3594
3597
|
|
|
3598
|
+
function listAccessibleProjectFlows(root, userCtx = {}) {
|
|
3599
|
+
const flows = listFlowsJson(root, { ...userCtx, includeWorkspaceFlows: true })
|
|
3600
|
+
.filter((flow) => (
|
|
3601
|
+
!workspaceFlowCollaborationGuard(
|
|
3602
|
+
flow.id,
|
|
3603
|
+
flow.source || "user",
|
|
3604
|
+
flow.archived === true,
|
|
3605
|
+
userCtx,
|
|
3606
|
+
"read",
|
|
3607
|
+
)
|
|
3608
|
+
))
|
|
3609
|
+
.map((flow) => {
|
|
3610
|
+
const source = flow.source || "user";
|
|
3611
|
+
const collaboration = source === "workspace"
|
|
3612
|
+
? getWorkspaceCollaborationByFlow(flow.id, flow.archived === true)
|
|
3613
|
+
: getWorkspaceCollaborationForProject({
|
|
3614
|
+
flowId: flow.id,
|
|
3615
|
+
flowSource: source,
|
|
3616
|
+
archived: flow.archived === true,
|
|
3617
|
+
ownerId: userCtx.userId,
|
|
3618
|
+
});
|
|
3619
|
+
return collaboration
|
|
3620
|
+
? { ...flow, collaboration: workspaceCollaborationSummaryWithUsers(collaboration, userCtx.userId) }
|
|
3621
|
+
: flow;
|
|
3622
|
+
});
|
|
3623
|
+
const existingCollaborationIds = new Set(flows.map((flow) => flow.collaboration?.id).filter(Boolean));
|
|
3624
|
+
for (const record of listWorkspaceCollaborationsForUser(userCtx.userId)) {
|
|
3625
|
+
const source = record.projectSource || record.flowSource || "workspace";
|
|
3626
|
+
if (source !== "user" || record.ownerId === userCtx.userId) continue;
|
|
3627
|
+
if (existingCollaborationIds.has(record.id)) continue;
|
|
3628
|
+
const ownerFlow = listFlowsJson(root, { userId: record.ownerId })
|
|
3629
|
+
.find((flow) => (
|
|
3630
|
+
flow.id === record.flowId
|
|
3631
|
+
&& (flow.source || "user") === "user"
|
|
3632
|
+
&& Boolean(flow.archived) === Boolean(record.archived)
|
|
3633
|
+
));
|
|
3634
|
+
if (!ownerFlow) continue;
|
|
3635
|
+
flows.push({
|
|
3636
|
+
...ownerFlow,
|
|
3637
|
+
collaboration: workspaceCollaborationSummaryWithUsers(record, userCtx.userId),
|
|
3638
|
+
});
|
|
3639
|
+
existingCollaborationIds.add(record.id);
|
|
3640
|
+
}
|
|
3641
|
+
return flows;
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
function workflowProjectBindingRows(bindings = [], accessibleProjects = [], userCtx = {}) {
|
|
3645
|
+
return (Array.isArray(bindings) ? bindings : []).flatMap((binding) => {
|
|
3646
|
+
const workspaceId = String(binding?.workspaceId || "").trim();
|
|
3647
|
+
if (!workspaceId) return [];
|
|
3648
|
+
const project = accessibleProjects.find((flow) => String(flow?.collaboration?.id || "") === workspaceId);
|
|
3649
|
+
if (!project) return [];
|
|
3650
|
+
const role = String(project.collaboration?.role || "");
|
|
3651
|
+
return [{
|
|
3652
|
+
workspaceId,
|
|
3653
|
+
flowId: String(project.id || binding.flowId || ""),
|
|
3654
|
+
flowSource: String(project.source || binding.flowSource || "user"),
|
|
3655
|
+
archived: project.archived === true,
|
|
3656
|
+
label: String(project.id || binding.flowId || "Project"),
|
|
3657
|
+
description: String(project.description || ""),
|
|
3658
|
+
role: role || ((project.source || "user") === "user" ? "owner" : "editor"),
|
|
3659
|
+
canManage: role === "owner" || role === "editor" || (!role && (project.source || "user") === "user"),
|
|
3660
|
+
boundBy: String(binding.boundBy || ""),
|
|
3661
|
+
boundAt: String(binding.boundAt || ""),
|
|
3662
|
+
}];
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
function availableWorkflowBindingProjects(accessibleProjects = [], bindings = []) {
|
|
3667
|
+
const bound = new Set((Array.isArray(bindings) ? bindings : []).map((item) => String(item?.workspaceId || "")).filter(Boolean));
|
|
3668
|
+
return accessibleProjects
|
|
3669
|
+
.filter((project) => {
|
|
3670
|
+
const source = String(project?.source || "user");
|
|
3671
|
+
const role = String(project?.collaboration?.role || "");
|
|
3672
|
+
const workspaceId = String(project?.collaboration?.id || "");
|
|
3673
|
+
return !project?.archived
|
|
3674
|
+
&& (source === "user" || source === "workspace")
|
|
3675
|
+
&& !bound.has(workspaceId)
|
|
3676
|
+
&& (!role || role === "owner" || role === "editor");
|
|
3677
|
+
})
|
|
3678
|
+
.map((project) => ({
|
|
3679
|
+
flowId: String(project.id || ""),
|
|
3680
|
+
flowSource: String(project.source || "user"),
|
|
3681
|
+
workspaceId: String(project.collaboration?.id || ""),
|
|
3682
|
+
label: String(project.id || "Project"),
|
|
3683
|
+
description: String(project.description || ""),
|
|
3684
|
+
}));
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3595
3687
|
function prdWorkflowDashboardActions(snapshot = {}) {
|
|
3596
3688
|
const rows = new Map();
|
|
3597
3689
|
for (const field of ["actions", "workflowActions", "workflow_actions", "timeline", "history"]) {
|
|
@@ -3639,7 +3731,7 @@ function prdWorkflowDashboardTimestamp(item = {}) {
|
|
|
3639
3731
|
return 0;
|
|
3640
3732
|
}
|
|
3641
3733
|
|
|
3642
|
-
function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
3734
|
+
function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}, projectBindings = []) {
|
|
3643
3735
|
const tapdId = String(record?.tapdId || snapshot?.tapdId || snapshot?.tapd_id || "").trim();
|
|
3644
3736
|
const collaboration = prdWorkflowCollaborationSummaryWithUsers(record, userCtx?.userId) || {};
|
|
3645
3737
|
const actions = prdWorkflowDashboardActions(snapshot);
|
|
@@ -3669,11 +3761,11 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3669
3761
|
? snapshot.overall.requirement
|
|
3670
3762
|
: {};
|
|
3671
3763
|
const title = String(
|
|
3672
|
-
|
|
3764
|
+
snapshot?.globalState?.title
|
|
3765
|
+
|| requirement.title
|
|
3673
3766
|
|| requirement.name
|
|
3674
3767
|
|| snapshot?.prd?.title
|
|
3675
3768
|
|| snapshot?.raw?.prd?.title
|
|
3676
|
-
|| snapshot?.title
|
|
3677
3769
|
|| "",
|
|
3678
3770
|
).trim();
|
|
3679
3771
|
const timeline = Array.isArray(snapshot?.projections?.timeline)
|
|
@@ -3727,10 +3819,73 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3727
3819
|
teamName: String(getTeamById(collaboration.teamId)?.name || ""),
|
|
3728
3820
|
shareActive: collaboration.shareActive === true,
|
|
3729
3821
|
updatedAt: updatedAtTimestamp ? new Date(updatedAtTimestamp).toISOString() : String(record?.updatedAt || ""),
|
|
3822
|
+
projectBindings,
|
|
3730
3823
|
};
|
|
3731
3824
|
}
|
|
3732
3825
|
|
|
3733
|
-
function
|
|
3826
|
+
function prdWorkflowDashboardTimelineDimensionValues(value) {
|
|
3827
|
+
return (Array.isArray(value) ? value : [value])
|
|
3828
|
+
.flatMap((item) => Array.isArray(item) ? item : [item])
|
|
3829
|
+
.map((item) => String(item ?? "").trim())
|
|
3830
|
+
.filter(Boolean);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
function prdWorkflowDashboardTimelineIdentity(entry = {}) {
|
|
3834
|
+
const id = String(entry?.id || "").trim();
|
|
3835
|
+
const source = String(entry?.source || "").trim().toLowerCase();
|
|
3836
|
+
const kind = String(entry?.kind || "").trim().toLowerCase();
|
|
3837
|
+
const dimensions = entry?.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
|
|
3838
|
+
? entry.dimensions
|
|
3839
|
+
: {};
|
|
3840
|
+
const legacyPlatformIdentity = id.match(/^(android|ios|all)[:_-](.+)$/i);
|
|
3841
|
+
const declaredPlatforms = [
|
|
3842
|
+
...prdWorkflowDashboardTimelineDimensionValues(dimensions.platform),
|
|
3843
|
+
...prdWorkflowDashboardTimelineDimensionValues(dimensions.platforms),
|
|
3844
|
+
].map((value) => value.toLowerCase());
|
|
3845
|
+
if (
|
|
3846
|
+
source === "prd-flow"
|
|
3847
|
+
&& ["version", "iteration"].includes(kind)
|
|
3848
|
+
&& legacyPlatformIdentity
|
|
3849
|
+
&& declaredPlatforms.includes(legacyPlatformIdentity[1].toLowerCase())
|
|
3850
|
+
) {
|
|
3851
|
+
return legacyPlatformIdentity[2].trim().toLowerCase();
|
|
3852
|
+
}
|
|
3853
|
+
return id.toLowerCase();
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
function prdWorkflowDashboardTimelineGroupKey(entry = {}) {
|
|
3857
|
+
const source = String(entry?.source || "").trim().toLowerCase();
|
|
3858
|
+
const kind = String(entry?.kind || "").trim().toLowerCase();
|
|
3859
|
+
const identity = prdWorkflowDashboardTimelineIdentity(entry);
|
|
3860
|
+
const dimensions = entry?.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
|
|
3861
|
+
? entry.dimensions
|
|
3862
|
+
: {};
|
|
3863
|
+
const nonPlatformDimensions = Object.entries(dimensions)
|
|
3864
|
+
.filter(([key]) => !["platform", "platforms", "client", "clients", "os"].includes(String(key).trim().toLowerCase()))
|
|
3865
|
+
.map(([key, value]) => [
|
|
3866
|
+
String(key).trim().toLowerCase(),
|
|
3867
|
+
prdWorkflowDashboardTimelineDimensionValues(value).map((item) => item.toLowerCase()).sort(),
|
|
3868
|
+
])
|
|
3869
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
3870
|
+
if (identity) return JSON.stringify([source, kind, identity, nonPlatformDimensions]);
|
|
3871
|
+
return String(entry?.key || [entry?.source, entry?.kind, entry?.id].filter(Boolean).join(":"));
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3874
|
+
function prdWorkflowDashboardMergeTimelineDimensions(current = {}, incoming = {}) {
|
|
3875
|
+
const out = {};
|
|
3876
|
+
for (const key of new Set([...Object.keys(current || {}), ...Object.keys(incoming || {})])) {
|
|
3877
|
+
const values = [
|
|
3878
|
+
...prdWorkflowDashboardTimelineDimensionValues(current?.[key]),
|
|
3879
|
+
...prdWorkflowDashboardTimelineDimensionValues(incoming?.[key]),
|
|
3880
|
+
];
|
|
3881
|
+
const unique = Array.from(new Map(values.map((value) => [value.toLowerCase(), value])).values());
|
|
3882
|
+
if (unique.length === 1) out[key] = unique[0];
|
|
3883
|
+
else if (unique.length > 1) out[key] = unique;
|
|
3884
|
+
}
|
|
3885
|
+
return out;
|
|
3886
|
+
}
|
|
3887
|
+
|
|
3888
|
+
export function prdWorkflowDashboardTimeline(workflows = []) {
|
|
3734
3889
|
const buckets = new Map();
|
|
3735
3890
|
const assignedWorkflowIds = new Set();
|
|
3736
3891
|
const rows = [...(Array.isArray(workflows) ? workflows : [])].sort((left, right) => {
|
|
@@ -3744,14 +3899,15 @@ function prdWorkflowDashboardTimeline(workflows = []) {
|
|
|
3744
3899
|
const workflowId = String(workflow?.id || workflow?.tapdId || "");
|
|
3745
3900
|
const seen = new Set();
|
|
3746
3901
|
for (const entry of Array.isArray(workflow?.timeline) ? workflow.timeline : []) {
|
|
3747
|
-
const
|
|
3748
|
-
|
|
3749
|
-
|
|
3902
|
+
const memberKey = String(entry?.key || [entry?.source, entry?.kind, entry?.id].filter(Boolean).join(":"));
|
|
3903
|
+
const identity = prdWorkflowDashboardTimelineIdentity(entry);
|
|
3904
|
+
const groupKey = prdWorkflowDashboardTimelineGroupKey(entry);
|
|
3905
|
+
if (!memberKey || !groupKey) continue;
|
|
3750
3906
|
assignedWorkflowIds.add(workflowId);
|
|
3751
|
-
const current = buckets.get(
|
|
3752
|
-
key,
|
|
3907
|
+
const current = buckets.get(groupKey) || {
|
|
3908
|
+
key: memberKey,
|
|
3753
3909
|
kind: String(entry.kind || ""),
|
|
3754
|
-
id: String(entry.id || ""),
|
|
3910
|
+
id: identity || String(entry.id || ""),
|
|
3755
3911
|
title: String(entry.title || entry.id || ""),
|
|
3756
3912
|
date: String(entry.date || ""),
|
|
3757
3913
|
source: String(entry.source || ""),
|
|
@@ -3763,21 +3919,25 @@ function prdWorkflowDashboardTimeline(workflows = []) {
|
|
|
3763
3919
|
completedCount: 0,
|
|
3764
3920
|
blockedCount: 0,
|
|
3765
3921
|
workflowIds: [],
|
|
3922
|
+
memberKeys: [],
|
|
3766
3923
|
};
|
|
3767
3924
|
current.kind = String(entry.kind || current.kind);
|
|
3768
|
-
current.id = String(entry.id || current.id);
|
|
3769
3925
|
current.title = String(entry.title || current.title);
|
|
3770
3926
|
current.date = String(entry.date || current.date);
|
|
3771
3927
|
current.source = String(entry.source || current.source);
|
|
3772
|
-
current.dimensions =
|
|
3773
|
-
? entry.dimensions
|
|
3774
|
-
: current.dimensions;
|
|
3928
|
+
current.dimensions = prdWorkflowDashboardMergeTimelineDimensions(current.dimensions, entry.dimensions);
|
|
3775
3929
|
current.order = Number.isFinite(Number(entry.order)) ? Number(entry.order) : current.order;
|
|
3776
|
-
current.
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3930
|
+
if (!current.memberKeys.includes(memberKey)) current.memberKeys.push(memberKey);
|
|
3931
|
+
current.memberKeys.sort();
|
|
3932
|
+
current.key = current.memberKeys[0] || memberKey;
|
|
3933
|
+
if (!seen.has(groupKey)) {
|
|
3934
|
+
seen.add(groupKey);
|
|
3935
|
+
current.workflowCount += 1;
|
|
3936
|
+
if (workflow?.state === "completed") current.completedCount += 1;
|
|
3937
|
+
if (workflow?.state === "blocked") current.blockedCount += 1;
|
|
3938
|
+
if (!current.workflowIds.includes(workflowId)) current.workflowIds.push(workflowId);
|
|
3939
|
+
}
|
|
3940
|
+
buckets.set(groupKey, current);
|
|
3781
3941
|
}
|
|
3782
3942
|
}
|
|
3783
3943
|
const timeline = Array.from(buckets.values()).sort((left, right) => {
|
|
@@ -13227,6 +13387,7 @@ export function startUiServer({
|
|
|
13227
13387
|
} else {
|
|
13228
13388
|
records = listPrdWorkflowCollaborationsForUser(userCtx.userId);
|
|
13229
13389
|
}
|
|
13390
|
+
const accessibleProjects = listAccessibleProjectFlows(root, userCtx);
|
|
13230
13391
|
const workflows = records.map((record) => {
|
|
13231
13392
|
const stateRoot = path.resolve(getAgentflowUserDataRoot(record.stateOwnerId || record.ownerId));
|
|
13232
13393
|
const tapdId = String(record.tapdId || "").trim();
|
|
@@ -13235,7 +13396,8 @@ export function startUiServer({
|
|
|
13235
13396
|
const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
|
|
13236
13397
|
const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
|
|
13237
13398
|
const materialized = prdWorkflowMergeRuntimeEvents(stateRoot, tapdId, snapshot);
|
|
13238
|
-
|
|
13399
|
+
const projectBindings = workflowProjectBindingRows(record.projectBindings, accessibleProjects, userCtx);
|
|
13400
|
+
return prdWorkflowDashboardSummary(record, materialized, userCtx, projectBindings);
|
|
13239
13401
|
});
|
|
13240
13402
|
const dashboardTimeline = prdWorkflowDashboardTimeline(workflows);
|
|
13241
13403
|
json(res, 200, {
|
|
@@ -13373,6 +13535,177 @@ export function startUiServer({
|
|
|
13373
13535
|
});
|
|
13374
13536
|
return;
|
|
13375
13537
|
}
|
|
13538
|
+
if (req.method === "GET" && url.pathname === "/api/workflows/project-bindings") {
|
|
13539
|
+
if (!authUser?.userId) {
|
|
13540
|
+
json(res, 401, { error: "Authentication required" });
|
|
13541
|
+
return;
|
|
13542
|
+
}
|
|
13543
|
+
const tapdId = String(url.searchParams.get("tapdId") || url.searchParams.get("id") || "").trim();
|
|
13544
|
+
if (!tapdId) {
|
|
13545
|
+
json(res, 400, { error: "Missing tapdId" });
|
|
13546
|
+
return;
|
|
13547
|
+
}
|
|
13548
|
+
const existing = getPrdWorkflowCollaborationByTapdId(tapdId);
|
|
13549
|
+
const result = listPrdWorkflowProjectBindings({ tapdId, userId: userCtx.userId });
|
|
13550
|
+
if (existing && result.error) {
|
|
13551
|
+
json(res, 403, { error: "PRD Workflow collaboration permission denied" });
|
|
13552
|
+
return;
|
|
13553
|
+
}
|
|
13554
|
+
const accessibleProjects = listAccessibleProjectFlows(root, userCtx);
|
|
13555
|
+
const bindings = workflowProjectBindingRows(result.projectBindings || [], accessibleProjects, userCtx);
|
|
13556
|
+
json(res, 200, {
|
|
13557
|
+
ok: true,
|
|
13558
|
+
bindings,
|
|
13559
|
+
availableProjects: availableWorkflowBindingProjects(accessibleProjects, bindings),
|
|
13560
|
+
});
|
|
13561
|
+
return;
|
|
13562
|
+
}
|
|
13563
|
+
if (req.method === "POST" && url.pathname === "/api/workflows/project-bindings") {
|
|
13564
|
+
if (!authUser?.userId) {
|
|
13565
|
+
json(res, 401, { error: "Authentication required" });
|
|
13566
|
+
return;
|
|
13567
|
+
}
|
|
13568
|
+
let payload;
|
|
13569
|
+
try {
|
|
13570
|
+
payload = JSON.parse(await readBody(req, 128 * 1024));
|
|
13571
|
+
} catch {
|
|
13572
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
13573
|
+
return;
|
|
13574
|
+
}
|
|
13575
|
+
const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
|
|
13576
|
+
const flowId = String(payload?.flowId || "").trim();
|
|
13577
|
+
const flowSource = String(payload?.flowSource || "user").trim() || "user";
|
|
13578
|
+
const workspaceId = String(payload?.workspaceId || "").trim();
|
|
13579
|
+
if (!tapdId || !flowId) {
|
|
13580
|
+
json(res, 400, { error: "Project binding requires tapdId and flowId" });
|
|
13581
|
+
return;
|
|
13582
|
+
}
|
|
13583
|
+
if (flowSource !== "user" && flowSource !== "workspace") {
|
|
13584
|
+
json(res, 400, { error: "Only editable Projects can be bound" });
|
|
13585
|
+
return;
|
|
13586
|
+
}
|
|
13587
|
+
const existingWorkflow = getPrdWorkflowCollaborationByTapdId(tapdId);
|
|
13588
|
+
if (existingWorkflow && !getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId)) {
|
|
13589
|
+
json(res, 403, { error: "PRD Workflow collaboration permission denied" });
|
|
13590
|
+
return;
|
|
13591
|
+
}
|
|
13592
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
13593
|
+
flowId,
|
|
13594
|
+
flowSource,
|
|
13595
|
+
workspaceId,
|
|
13596
|
+
archived: false,
|
|
13597
|
+
}, userCtx);
|
|
13598
|
+
if (scoped.error) {
|
|
13599
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
13600
|
+
return;
|
|
13601
|
+
}
|
|
13602
|
+
if (scoped.archived || (scoped.collaboration && !scoped.collaborationAccess?.writable)) {
|
|
13603
|
+
json(res, 403, { error: "Only Project owners and editors can bind an iteration" });
|
|
13604
|
+
return;
|
|
13605
|
+
}
|
|
13606
|
+
const projectCollaboration = scoped.collaboration
|
|
13607
|
+
? { record: scoped.collaboration, workspace: workspaceCollaborationSummary(scoped.collaboration, userCtx.userId) }
|
|
13608
|
+
: ensureWorkspaceCollaboration({
|
|
13609
|
+
flowId: scoped.flowId,
|
|
13610
|
+
flowSource: scoped.flowSource,
|
|
13611
|
+
archived: false,
|
|
13612
|
+
userId: userCtx.userId,
|
|
13613
|
+
});
|
|
13614
|
+
if (projectCollaboration.error || !projectCollaboration.record?.id) {
|
|
13615
|
+
json(res, projectCollaboration.status || 400, { error: projectCollaboration.error || "Project collaboration is unavailable" });
|
|
13616
|
+
return;
|
|
13617
|
+
}
|
|
13618
|
+
const projectAccess = workspaceCollaborationAccess(projectCollaboration.record, userCtx.userId);
|
|
13619
|
+
if (!projectAccess.writable) {
|
|
13620
|
+
json(res, 403, { error: "Only Project owners and editors can bind an iteration" });
|
|
13621
|
+
return;
|
|
13622
|
+
}
|
|
13623
|
+
const ensuredWorkflow = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
|
|
13624
|
+
if (ensuredWorkflow.error) {
|
|
13625
|
+
json(res, ensuredWorkflow.status || 400, { error: ensuredWorkflow.error });
|
|
13626
|
+
return;
|
|
13627
|
+
}
|
|
13628
|
+
const result = bindPrdWorkflowProject({
|
|
13629
|
+
tapdId,
|
|
13630
|
+
userId: userCtx.userId,
|
|
13631
|
+
project: {
|
|
13632
|
+
workspaceId: projectCollaboration.record.id,
|
|
13633
|
+
flowId: scoped.flowId,
|
|
13634
|
+
flowSource: scoped.flowSource,
|
|
13635
|
+
archived: false,
|
|
13636
|
+
ownerId: projectCollaboration.record.ownerId,
|
|
13637
|
+
},
|
|
13638
|
+
});
|
|
13639
|
+
if (result.error) {
|
|
13640
|
+
json(res, result.status || 400, { error: result.error });
|
|
13641
|
+
return;
|
|
13642
|
+
}
|
|
13643
|
+
const accessibleProjects = listAccessibleProjectFlows(root, userCtx);
|
|
13644
|
+
const bindings = workflowProjectBindingRows(result.projectBindings, accessibleProjects, userCtx);
|
|
13645
|
+
json(res, 200, {
|
|
13646
|
+
ok: true,
|
|
13647
|
+
created: result.created === true,
|
|
13648
|
+
bindings,
|
|
13649
|
+
availableProjects: availableWorkflowBindingProjects(accessibleProjects, bindings),
|
|
13650
|
+
});
|
|
13651
|
+
return;
|
|
13652
|
+
}
|
|
13653
|
+
if (req.method === "DELETE" && url.pathname === "/api/workflows/project-bindings") {
|
|
13654
|
+
if (!authUser?.userId) {
|
|
13655
|
+
json(res, 401, { error: "Authentication required" });
|
|
13656
|
+
return;
|
|
13657
|
+
}
|
|
13658
|
+
let payload;
|
|
13659
|
+
try {
|
|
13660
|
+
payload = JSON.parse(await readBody(req, 128 * 1024));
|
|
13661
|
+
} catch {
|
|
13662
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
13663
|
+
return;
|
|
13664
|
+
}
|
|
13665
|
+
const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
|
|
13666
|
+
const workspaceId = String(payload?.workspaceId || "").trim();
|
|
13667
|
+
if (!tapdId || !workspaceId) {
|
|
13668
|
+
json(res, 400, { error: "Unbinding requires tapdId and workspaceId" });
|
|
13669
|
+
return;
|
|
13670
|
+
}
|
|
13671
|
+
const listed = listPrdWorkflowProjectBindings({ tapdId, userId: userCtx.userId });
|
|
13672
|
+
if (listed.error) {
|
|
13673
|
+
json(res, listed.status || 400, { error: listed.error });
|
|
13674
|
+
return;
|
|
13675
|
+
}
|
|
13676
|
+
const binding = listed.projectBindings.find((item) => item.workspaceId === workspaceId);
|
|
13677
|
+
if (!binding) {
|
|
13678
|
+
json(res, 404, { error: "Project binding not found" });
|
|
13679
|
+
return;
|
|
13680
|
+
}
|
|
13681
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
13682
|
+
flowId: binding.flowId,
|
|
13683
|
+
flowSource: binding.flowSource,
|
|
13684
|
+
workspaceId,
|
|
13685
|
+
archived: binding.archived === true,
|
|
13686
|
+
}, userCtx);
|
|
13687
|
+
if (scoped.error) {
|
|
13688
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
13689
|
+
return;
|
|
13690
|
+
}
|
|
13691
|
+
if (!scoped.collaborationAccess?.writable) {
|
|
13692
|
+
json(res, 403, { error: "Only Project owners and editors can unbind an iteration" });
|
|
13693
|
+
return;
|
|
13694
|
+
}
|
|
13695
|
+
const result = unbindPrdWorkflowProject({ tapdId, userId: userCtx.userId, workspaceId });
|
|
13696
|
+
if (result.error) {
|
|
13697
|
+
json(res, result.status || 400, { error: result.error });
|
|
13698
|
+
return;
|
|
13699
|
+
}
|
|
13700
|
+
const accessibleProjects = listAccessibleProjectFlows(root, userCtx);
|
|
13701
|
+
const bindings = workflowProjectBindingRows(result.projectBindings, accessibleProjects, userCtx);
|
|
13702
|
+
json(res, 200, {
|
|
13703
|
+
ok: true,
|
|
13704
|
+
bindings,
|
|
13705
|
+
availableProjects: availableWorkflowBindingProjects(accessibleProjects, bindings),
|
|
13706
|
+
});
|
|
13707
|
+
return;
|
|
13708
|
+
}
|
|
13376
13709
|
if (req.method === "GET" && url.pathname === "/api/workflows/knowledge-bindings") {
|
|
13377
13710
|
if (!authUser?.userId) {
|
|
13378
13711
|
json(res, 401, { error: "Authentication required" });
|
|
@@ -15701,48 +16034,7 @@ export function startUiServer({
|
|
|
15701
16034
|
try {
|
|
15702
16035
|
const projectView = String(url.searchParams.get("view") || "all").trim().toLowerCase();
|
|
15703
16036
|
const currentTeam = getTeamForUser(userCtx.userId);
|
|
15704
|
-
const flows =
|
|
15705
|
-
.filter((flow) => (
|
|
15706
|
-
!workspaceFlowCollaborationGuard(
|
|
15707
|
-
flow.id,
|
|
15708
|
-
flow.source || "user",
|
|
15709
|
-
flow.archived === true,
|
|
15710
|
-
userCtx,
|
|
15711
|
-
"read",
|
|
15712
|
-
)
|
|
15713
|
-
))
|
|
15714
|
-
.map((flow) => {
|
|
15715
|
-
const source = flow.source || "user";
|
|
15716
|
-
const collaboration = source === "workspace"
|
|
15717
|
-
? getWorkspaceCollaborationByFlow(flow.id, flow.archived === true)
|
|
15718
|
-
: getWorkspaceCollaborationForProject({
|
|
15719
|
-
flowId: flow.id,
|
|
15720
|
-
flowSource: source,
|
|
15721
|
-
archived: flow.archived === true,
|
|
15722
|
-
ownerId: userCtx.userId,
|
|
15723
|
-
});
|
|
15724
|
-
return collaboration
|
|
15725
|
-
? { ...flow, collaboration: workspaceCollaborationSummaryWithUsers(collaboration, userCtx.userId) }
|
|
15726
|
-
: flow;
|
|
15727
|
-
});
|
|
15728
|
-
const existingCollaborationIds = new Set(flows.map((flow) => flow.collaboration?.id).filter(Boolean));
|
|
15729
|
-
for (const record of listWorkspaceCollaborationsForUser(userCtx.userId)) {
|
|
15730
|
-
const source = record.projectSource || record.flowSource || "workspace";
|
|
15731
|
-
if (source !== "user" || record.ownerId === userCtx.userId) continue;
|
|
15732
|
-
if (existingCollaborationIds.has(record.id)) continue;
|
|
15733
|
-
const ownerFlow = listFlowsJson(root, { userId: record.ownerId })
|
|
15734
|
-
.find((flow) => (
|
|
15735
|
-
flow.id === record.flowId
|
|
15736
|
-
&& (flow.source || "user") === "user"
|
|
15737
|
-
&& Boolean(flow.archived) === Boolean(record.archived)
|
|
15738
|
-
));
|
|
15739
|
-
if (!ownerFlow) continue;
|
|
15740
|
-
flows.push({
|
|
15741
|
-
...ownerFlow,
|
|
15742
|
-
collaboration: workspaceCollaborationSummaryWithUsers(record, userCtx.userId),
|
|
15743
|
-
});
|
|
15744
|
-
existingCollaborationIds.add(record.id);
|
|
15745
|
-
}
|
|
16037
|
+
const flows = listAccessibleProjectFlows(root, userCtx);
|
|
15746
16038
|
const visibleFlows = projectView === "team"
|
|
15747
16039
|
? flows.filter((flow) => (
|
|
15748
16040
|
currentTeam
|
|
@@ -19585,6 +19877,7 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
19585
19877
|
return;
|
|
19586
19878
|
}
|
|
19587
19879
|
const flowSource = payload.flowSource || "user";
|
|
19880
|
+
const requestedRunId = typeof payload.runId === "string" ? payload.runId.trim() : "";
|
|
19588
19881
|
const collaborationDenied = workspaceFlowCollaborationGuard(
|
|
19589
19882
|
flowId,
|
|
19590
19883
|
flowSource,
|
|
@@ -19599,6 +19892,13 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
19599
19892
|
const runKey = workspaceRunKey(userCtx, flowSource, flowId);
|
|
19600
19893
|
const entry = activeFlowRuns.get(runKey);
|
|
19601
19894
|
if (!entry || !entry.child) {
|
|
19895
|
+
if (requestedRunId) {
|
|
19896
|
+
const cancelled = cancelScheduledRun(root, flowId, requestedRunId, userCtx);
|
|
19897
|
+
if (cancelled.ok && cancelled.updatedWaits > 0) {
|
|
19898
|
+
json(res, 200, { ok: true, cancelledWaitingRun: true, ...cancelled });
|
|
19899
|
+
return;
|
|
19900
|
+
}
|
|
19901
|
+
}
|
|
19602
19902
|
json(res, 404, { error: "该流水线未在运行" });
|
|
19603
19903
|
return;
|
|
19604
19904
|
}
|