@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.
@@ -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
- requirement.title
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 prdWorkflowDashboardTimeline(workflows = []) {
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 key = String(entry?.key || [entry?.source, entry?.kind, entry?.id].filter(Boolean).join(":"));
3748
- if (!key || seen.has(key)) continue;
3749
- seen.add(key);
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(key) || {
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 = entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.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.workflowCount += 1;
3777
- if (workflow?.state === "completed") current.completedCount += 1;
3778
- if (workflow?.state === "blocked") current.blockedCount += 1;
3779
- current.workflowIds.push(workflowId);
3780
- buckets.set(key, current);
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
- return prdWorkflowDashboardSummary(record, materialized, userCtx);
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 = listFlowsJson(root, { ...userCtx, includeWorkspaceFlows: true })
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
  }