@fieldwangai/agentflow 0.1.103 → 0.1.106

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.
@@ -148,6 +148,15 @@ import {
148
148
  workspaceCollaborationAccess,
149
149
  workspaceCollaborationSummary,
150
150
  } from "./workspace-collaboration.mjs";
151
+ import {
152
+ addPrdWorkflowCollaborationMember,
153
+ ensurePrdWorkflowCollaboration,
154
+ getPrdWorkflowCollaborationById,
155
+ getPrdWorkflowCollaborationForUser,
156
+ prdWorkflowCollaborationAccess,
157
+ prdWorkflowCollaborationSummary,
158
+ removePrdWorkflowCollaborationMember,
159
+ } from "./prd-workflow-collaboration.mjs";
151
160
 
152
161
  const MIME = {
153
162
  ".html": "text/html; charset=utf-8",
@@ -165,6 +174,16 @@ const MIME = {
165
174
  ".ico": "image/x-icon",
166
175
  };
167
176
 
177
+ const UI_SERVER_STARTED_AT = new Date().toISOString();
178
+ const UI_SERVER_APP_VERSION = (() => {
179
+ try {
180
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8"));
181
+ return String(pkg?.version || "0.0.0").trim() || "0.0.0";
182
+ } catch {
183
+ return "0.0.0";
184
+ }
185
+ })();
186
+
168
187
  function execFileBuffered(command, args, options = {}) {
169
188
  return new Promise((resolve, reject) => {
170
189
  execFile(command, args, {
@@ -3194,6 +3213,20 @@ function workspaceCollaborationSummaryWithUsers(record, userId) {
3194
3213
  };
3195
3214
  }
3196
3215
 
3216
+ function prdWorkflowCollaborationSummaryWithUsers(record, userId) {
3217
+ const summary = prdWorkflowCollaborationSummary(record, userId);
3218
+ if (!summary) return null;
3219
+ const users = readAuthUsers();
3220
+ return {
3221
+ ...summary,
3222
+ ownerUsername: String(users[summary.ownerId]?.username || summary.ownerId),
3223
+ members: (summary.members || []).map((member) => ({
3224
+ ...member,
3225
+ username: String(users[member.userId]?.username || member.userId),
3226
+ })),
3227
+ };
3228
+ }
3229
+
3197
3230
  function workspaceConversationsPath(scopedRoot) {
3198
3231
  return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
3199
3232
  }
@@ -7215,14 +7248,56 @@ const WORKSPACE_IMPLEMENTATION_REFERENCE_ENABLED = true;
7215
7248
  const WORKSPACE_IMPLEMENTATION_SUMMARY_ENABLED = false;
7216
7249
  const WORKSPACE_NODE_HISTORY_MAX_CHARS = 80000;
7217
7250
 
7251
+ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capability = "read") {
7252
+ const tapdId = String(params.tapdId || params.tapd_id || "").trim();
7253
+ const flowId = String(params.flowId || "").trim();
7254
+ const flowSource = String(params.flowSource || "user").trim() || "user";
7255
+ const archived = params.archived === true || params.archived === "1" || params.flowArchived === true;
7256
+ const collaboration = tapdId
7257
+ ? getPrdWorkflowCollaborationForUser(tapdId, userCtx?.userId)
7258
+ : null;
7259
+ const access = prdWorkflowCollaborationAccess(collaboration, userCtx?.userId);
7260
+ if (collaboration && !access.allowed) {
7261
+ return { error: "PRD Workflow collaboration permission denied", status: 403 };
7262
+ }
7263
+ if (capability === "write" && collaboration && !access.writable) {
7264
+ return { error: "PRD Workflow collaboration edit permission denied", status: 403 };
7265
+ }
7266
+ const ownerId = String(collaboration?.ownerId || userCtx?.userId || "").trim();
7267
+ const stateRoot = path.resolve(getAgentflowUserDataRoot(ownerId));
7268
+ let executionRoot = path.resolve(workspaceRoot);
7269
+ if (flowId) {
7270
+ const projectScope = resolveWorkspaceScopeRoot(workspaceRoot, {
7271
+ flowId,
7272
+ flowSource,
7273
+ workspaceId: params.workspaceId || "",
7274
+ archived,
7275
+ }, userCtx);
7276
+ if (projectScope.error) {
7277
+ if (!collaboration || capability !== "read") return projectScope;
7278
+ executionRoot = stateRoot;
7279
+ } else {
7280
+ executionRoot = projectScope.root;
7281
+ }
7282
+ }
7283
+ return {
7284
+ tapdId,
7285
+ executionRoot,
7286
+ stateRoot,
7287
+ ownerId,
7288
+ collaboration,
7289
+ collaborationAccess: access,
7290
+ flowId,
7291
+ flowSource,
7292
+ archived,
7293
+ };
7294
+ }
7295
+
7218
7296
  function prdWorkflowKey(userCtx = {}, flowSource = "user", flowId = "", tapdId = "") {
7219
- const actorScope = flowSource === "workspace" ? "shared" : String(userCtx?.userId || "");
7220
- return [
7221
- actorScope,
7222
- String(flowSource || "user"),
7223
- String(flowId || ""),
7224
- String(tapdId || ""),
7225
- ].join("\t");
7297
+ const id = String(tapdId || "").trim();
7298
+ const collaboration = getPrdWorkflowCollaborationForUser(id, userCtx?.userId);
7299
+ const actorScope = `user:${String(collaboration?.ownerId || userCtx?.userId || "")}`;
7300
+ return [actorScope, id].join("\t");
7226
7301
  }
7227
7302
 
7228
7303
  function prdWorkflowBroadcast(key, event = {}) {
@@ -7240,8 +7315,10 @@ function prdWorkflowCollaborationState(userCtx = {}, flowSource = "user", flowId
7240
7315
  const key = prdWorkflowKey(userCtx, flowSource, flowId, tapdId);
7241
7316
  const active = prdWorkflowActionLocks.get(key) || null;
7242
7317
  const subscribers = prdWorkflowSubscribers.get(key);
7318
+ const workflowCollaboration = getPrdWorkflowCollaborationForUser(tapdId, userCtx?.userId);
7243
7319
  return {
7244
7320
  subscribers: subscribers ? subscribers.size : 0,
7321
+ workflow: prdWorkflowCollaborationSummaryWithUsers(workflowCollaboration, userCtx?.userId),
7245
7322
  activeAction: active ? {
7246
7323
  action: String(active.action || ""),
7247
7324
  tapdId: String(active.tapdId || tapdId || ""),
@@ -7417,6 +7494,35 @@ function prdWorkflowReviewPaths(scopedRoot, tapdId, reviewId) {
7417
7494
  };
7418
7495
  }
7419
7496
 
7497
+ function prdWorkflowMigrateLegacyState(legacyRoot, stateRoot, tapdId) {
7498
+ const sourceRoot = path.resolve(legacyRoot || "");
7499
+ const destinationRoot = path.resolve(stateRoot || "");
7500
+ if (!tapdId || sourceRoot === destinationRoot) return;
7501
+ const pairs = [
7502
+ [prdWorkflowStatePath(sourceRoot, tapdId), prdWorkflowStatePath(destinationRoot, tapdId)],
7503
+ [prdWorkflowCachePath(sourceRoot, tapdId), prdWorkflowCachePath(destinationRoot, tapdId)],
7504
+ [prdWorkflowProjectPath(sourceRoot, tapdId), prdWorkflowProjectPath(destinationRoot, tapdId)],
7505
+ [prdWorkflowClientsPath(sourceRoot, tapdId), prdWorkflowClientsPath(destinationRoot, tapdId)],
7506
+ [prdWorkflowEventsPath(sourceRoot, tapdId), prdWorkflowEventsPath(destinationRoot, tapdId)],
7507
+ [prdWorkflowAuditPath(sourceRoot, tapdId), prdWorkflowAuditPath(destinationRoot, tapdId)],
7508
+ ];
7509
+ for (const [source, destination] of pairs) {
7510
+ try {
7511
+ if (!fs.existsSync(source) || fs.existsSync(destination)) continue;
7512
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
7513
+ fs.copyFileSync(source, destination);
7514
+ } catch (_) {}
7515
+ }
7516
+ try {
7517
+ const sourceReviews = prdWorkflowReviewDir(sourceRoot, tapdId);
7518
+ const destinationReviews = prdWorkflowReviewDir(destinationRoot, tapdId);
7519
+ if (fs.existsSync(sourceReviews) && !fs.existsSync(destinationReviews)) {
7520
+ fs.mkdirSync(path.dirname(destinationReviews), { recursive: true });
7521
+ fs.cpSync(sourceReviews, destinationReviews, { recursive: true, errorOnExist: false });
7522
+ }
7523
+ } catch (_) {}
7524
+ }
7525
+
7420
7526
  function prdWorkflowPruneReviews(scopedRoot, tapdId, maxReviews = 200) {
7421
7527
  try {
7422
7528
  const dir = prdWorkflowReviewDir(scopedRoot, tapdId);
@@ -8681,6 +8787,19 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
8681
8787
  let artifactConflict = false;
8682
8788
  const events = [...current.events];
8683
8789
  if (index >= 0) {
8790
+ const previousImplementationMetadata =
8791
+ events[index]?.implementationMetadata || events[index]?.implementation_metadata;
8792
+ const incomingImplementationMetadata =
8793
+ entry?.implementationMetadata || entry?.implementation_metadata;
8794
+ const mergedImplementationMetadata =
8795
+ incomingImplementationMetadata && typeof incomingImplementationMetadata === "object" && !Array.isArray(incomingImplementationMetadata)
8796
+ ? prdWorkflowOverallMerge(
8797
+ previousImplementationMetadata && typeof previousImplementationMetadata === "object" && !Array.isArray(previousImplementationMetadata)
8798
+ ? previousImplementationMetadata
8799
+ : {},
8800
+ incomingImplementationMetadata,
8801
+ )
8802
+ : previousImplementationMetadata;
8684
8803
  const prevArtifact = prdWorkflowRuntimeEventArtifactSignature(events[index]);
8685
8804
  const nextArtifact = prdWorkflowRuntimeEventArtifactSignature(entry);
8686
8805
  artifactConflict = Boolean(prevArtifact && nextArtifact && prevArtifact !== nextArtifact &&
@@ -8703,6 +8822,9 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
8703
8822
  startedAt: events[index].startedAt || entry.startedAt,
8704
8823
  idempotencyHistory: [...new Set(idempotencyHistory)].slice(-50),
8705
8824
  };
8825
+ if (mergedImplementationMetadata && typeof mergedImplementationMetadata === "object" && !Array.isArray(mergedImplementationMetadata)) {
8826
+ events[index].implementationMetadata = mergedImplementationMetadata;
8827
+ }
8706
8828
  if (artifactConflict) {
8707
8829
  events[index] = {
8708
8830
  ...events[index],
@@ -8802,13 +8924,212 @@ function prdWorkflowMergeRuntimeEventList(snapshotEvents = [], runtimeEvents = [
8802
8924
  return out;
8803
8925
  }
8804
8926
 
8927
+ function prdWorkflowOverallPlainObject(value) {
8928
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
8929
+ }
8930
+
8931
+ function prdWorkflowOverallMerge(base, patch) {
8932
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) return base;
8933
+ const out = { ...prdWorkflowOverallPlainObject(base) };
8934
+ for (const [key, value] of Object.entries(patch)) {
8935
+ if (value === undefined) continue;
8936
+ if (value === null) {
8937
+ delete out[key];
8938
+ continue;
8939
+ }
8940
+ if (value && typeof value === "object" && !Array.isArray(value)) {
8941
+ out[key] = prdWorkflowOverallMerge(out[key], value);
8942
+ continue;
8943
+ }
8944
+ out[key] = value;
8945
+ }
8946
+ return out;
8947
+ }
8948
+
8949
+ function prdWorkflowOverallDeletePath(value, rawPath) {
8950
+ const pathParts = String(rawPath || "").split(".").map((part) => part.trim()).filter(Boolean);
8951
+ if (!pathParts.length) return value;
8952
+ const root = prdWorkflowOverallPlainObject(value);
8953
+ let cursor = root;
8954
+ for (const part of pathParts.slice(0, -1)) {
8955
+ if (!cursor[part] || typeof cursor[part] !== "object" || Array.isArray(cursor[part])) return root;
8956
+ cursor = cursor[part];
8957
+ }
8958
+ delete cursor[pathParts[pathParts.length - 1]];
8959
+ return root;
8960
+ }
8961
+
8962
+ function prdWorkflowOverallValueKey(value) {
8963
+ if (typeof value === "string") return value.trim().toLowerCase();
8964
+ try {
8965
+ return JSON.stringify(value);
8966
+ } catch {
8967
+ return String(value);
8968
+ }
8969
+ }
8970
+
8971
+ function prdWorkflowOverallUnique(values = []) {
8972
+ const out = [];
8973
+ const seen = new Set();
8974
+ for (const value of values) {
8975
+ if (value == null || value === "") continue;
8976
+ const key = prdWorkflowOverallValueKey(value);
8977
+ if (!key || seen.has(key)) continue;
8978
+ seen.add(key);
8979
+ out.push(value);
8980
+ }
8981
+ return out;
8982
+ }
8983
+
8984
+ function prdWorkflowOverallFilterValues(filters, key) {
8985
+ if (!filters || typeof filters !== "object" || Array.isArray(filters)) return [];
8986
+ const aliases = {
8987
+ countries: ["countries", "country", "countryFilters", "country_filters"],
8988
+ users: ["users", "user", "uids", "uid", "userFilters", "user_filters"],
8989
+ versions: ["versions", "version", "versionFilters", "version_filters"],
8990
+ };
8991
+ for (const alias of aliases[key] || [key]) {
8992
+ const value = filters[alias];
8993
+ if (Array.isArray(value)) return value;
8994
+ if (value != null && value !== "") return [value];
8995
+ }
8996
+ return [];
8997
+ }
8998
+
8999
+ function prdWorkflowFinalizeOverall(tapdId, value) {
9000
+ const overall = prdWorkflowOverallPlainObject(value);
9001
+ const requirement = {
9002
+ ...prdWorkflowOverallPlainObject(overall.requirement),
9003
+ tapdId: String(overall?.requirement?.tapdId || overall?.requirement?.tapd_id || tapdId || ""),
9004
+ };
9005
+ const platforms = {};
9006
+ for (const [rawPlatform, rawValue] of Object.entries(prdWorkflowOverallPlainObject(overall.platforms))) {
9007
+ const platform = String(rawPlatform || "").trim().toLowerCase();
9008
+ if (!platform) continue;
9009
+ const platformValue = prdWorkflowOverallPlainObject(rawValue);
9010
+ const issues = prdWorkflowOverallPlainObject(platformValue.issues);
9011
+ const implementations = Object.values(issues)
9012
+ .map((issue) => prdWorkflowOverallPlainObject(issue).implementation)
9013
+ .filter((item) => item && typeof item === "object" && !Array.isArray(item));
9014
+ const filters = implementations.map((item) => prdWorkflowOverallPlainObject(item.filters));
9015
+ platforms[platform] = {
9016
+ ...platformValue,
9017
+ tags: prdWorkflowOverallUnique([
9018
+ ...(Array.isArray(platformValue.tags) ? platformValue.tags : []),
9019
+ ...implementations.flatMap((item) => Array.isArray(item.tags) ? item.tags : []),
9020
+ ]),
9021
+ experiments: prdWorkflowOverallUnique([
9022
+ ...(Array.isArray(platformValue.experiments) ? platformValue.experiments : []),
9023
+ ...implementations.flatMap((item) => Array.isArray(item.experiments) ? item.experiments : []),
9024
+ ]),
9025
+ settings: prdWorkflowOverallUnique([
9026
+ ...(Array.isArray(platformValue.settings) ? platformValue.settings : []),
9027
+ ...implementations.flatMap((item) => Array.isArray(item.settings) ? item.settings : []),
9028
+ ]),
9029
+ filters: {
9030
+ ...prdWorkflowOverallPlainObject(platformValue.filters),
9031
+ countries: prdWorkflowOverallUnique([
9032
+ ...prdWorkflowOverallFilterValues(platformValue.filters, "countries"),
9033
+ ...filters.flatMap((item) => prdWorkflowOverallFilterValues(item, "countries")),
9034
+ ]),
9035
+ users: prdWorkflowOverallUnique([
9036
+ ...prdWorkflowOverallFilterValues(platformValue.filters, "users"),
9037
+ ...filters.flatMap((item) => prdWorkflowOverallFilterValues(item, "users")),
9038
+ ]),
9039
+ versions: prdWorkflowOverallUnique([
9040
+ ...prdWorkflowOverallFilterValues(platformValue.filters, "versions"),
9041
+ ...filters.flatMap((item) => prdWorkflowOverallFilterValues(item, "versions")),
9042
+ ]),
9043
+ },
9044
+ rules: prdWorkflowOverallUnique([
9045
+ ...(Array.isArray(platformValue.rules) ? platformValue.rules : []),
9046
+ ...implementations.flatMap((item) => Array.isArray(item.rules) ? item.rules : []),
9047
+ ]),
9048
+ issues,
9049
+ };
9050
+ }
9051
+ return {
9052
+ ...overall,
9053
+ requirement,
9054
+ platforms,
9055
+ };
9056
+ }
9057
+
9058
+ function prdWorkflowOverallFromEvents(tapdId, snapshot = {}, runtimeEvents = []) {
9059
+ const rawOverall =
9060
+ snapshot?.overall ||
9061
+ snapshot?.prdOverall ||
9062
+ snapshot?.prd_overall ||
9063
+ snapshot?.raw?.overall ||
9064
+ snapshot?.raw?.prdOverall ||
9065
+ snapshot?.raw?.prd_overall ||
9066
+ snapshot?.raw?.prd?.overall ||
9067
+ {};
9068
+ let overall = prdWorkflowOverallMerge({}, rawOverall);
9069
+ const events = [...(Array.isArray(runtimeEvents) ? runtimeEvents : [])].sort((left, right) => {
9070
+ const leftAt = Date.parse(left?.updatedAt || left?.completedAt || left?.createdAt || left?.observedAt || "");
9071
+ const rightAt = Date.parse(right?.updatedAt || right?.completedAt || right?.createdAt || right?.observedAt || "");
9072
+ if (!Number.isFinite(leftAt) && !Number.isFinite(rightAt)) return 0;
9073
+ if (!Number.isFinite(leftAt)) return -1;
9074
+ if (!Number.isFinite(rightAt)) return 1;
9075
+ return leftAt - rightAt;
9076
+ });
9077
+ for (const event of events) {
9078
+ if (!event || typeof event !== "object" || Array.isArray(event)) continue;
9079
+ const patch = event.overallPatch || event.overall_patch;
9080
+ if (patch && typeof patch === "object" && !Array.isArray(patch)) {
9081
+ overall = prdWorkflowOverallMerge(overall, patch);
9082
+ }
9083
+ const platform = String(event.platform || "").trim().toLowerCase();
9084
+ const issueKey = String(event.issueKey || event.issue_key || event.issue || "").trim();
9085
+ const actor = prdWorkflowOverallPlainObject(event.actor);
9086
+ if ((event.overallOwnerFromActor === true || event.overall_owner_from_actor === true) && platform && (actor.userId || actor.username)) {
9087
+ overall = prdWorkflowOverallMerge(overall, {
9088
+ platforms: {
9089
+ [platform]: {
9090
+ owner: {
9091
+ userId: String(actor.userId || ""),
9092
+ username: String(actor.username || actor.userId || ""),
9093
+ source: "latest-confirmed-plan",
9094
+ planVersion: event.planVersion || event.plan_version || "",
9095
+ updatedAt: event.updatedAt || event.completedAt || "",
9096
+ },
9097
+ },
9098
+ },
9099
+ });
9100
+ }
9101
+ const implementation = event.implementationMetadata || event.implementation_metadata;
9102
+ if (platform && issueKey && implementation && typeof implementation === "object" && !Array.isArray(implementation)) {
9103
+ overall = prdWorkflowOverallMerge(overall, {
9104
+ platforms: {
9105
+ [platform]: {
9106
+ issues: {
9107
+ [issueKey]: {
9108
+ title: String(event.issueTitle || event.issue_title || event.title || ""),
9109
+ implementation,
9110
+ mr: String(event?.changes?.impl_mr || event?.implMr || event?.impl_mr || ""),
9111
+ updatedAt: event.updatedAt || event.completedAt || "",
9112
+ },
9113
+ },
9114
+ },
9115
+ },
9116
+ });
9117
+ }
9118
+ const removePaths = event.overallRemove || event.overall_remove;
9119
+ for (const removePath of Array.isArray(removePaths) ? removePaths : []) {
9120
+ overall = prdWorkflowOverallDeletePath(overall, removePath);
9121
+ }
9122
+ }
9123
+ return prdWorkflowFinalizeOverall(tapdId, overall);
9124
+ }
9125
+
8805
9126
  function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
8806
9127
  const runtime = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId);
8807
9128
  const runtimeEvents = runtime.events;
8808
- if (!runtimeEvents.length) return snapshot;
8809
9129
  const events = prdWorkflowMergeRuntimeEventList(snapshot?.events, runtimeEvents);
8810
9130
  return {
8811
9131
  ...snapshot,
9132
+ overall: prdWorkflowOverallFromEvents(tapdId, snapshot, runtimeEvents),
8812
9133
  runtimeEvents,
8813
9134
  events,
8814
9135
  sources: {
@@ -9156,6 +9477,9 @@ function prdWorkflowSnapshotFromParsed(scopedRoot, tapdId, parsed = {}, userCtx
9156
9477
  dependencies: prdWorkflowFirstArray(parsed.dependencies),
9157
9478
  optionalGaps,
9158
9479
  sources: parsed.sources && typeof parsed.sources === "object" ? parsed.sources : {},
9480
+ overall: prdWorkflowOverallFromEvents(id, {
9481
+ overall: parsed.overall || parsed.prdOverall || parsed.prd_overall || prd.overall || {},
9482
+ }, []),
9159
9483
  raw: parsed,
9160
9484
  collaboration: prdWorkflowCollaborationState(userCtx, flowSource, flowId, id),
9161
9485
  };
@@ -9570,6 +9894,7 @@ function listWorkspaceScheduleStatuses(root, userCtx = {}) {
9570
9894
  key,
9571
9895
  flowId,
9572
9896
  flowSource,
9897
+ workspaceId: String(flow.collaboration?.id || scoped.workspaceId || ""),
9573
9898
  scheduleNodeId,
9574
9899
  runNodeId: targetRunNodeId,
9575
9900
  label: String(instance.label || "Scheduled Run"),
@@ -10121,6 +10446,15 @@ export function startUiServer({
10121
10446
  return;
10122
10447
  }
10123
10448
 
10449
+ if (url.pathname === "/api/app-version" && req.method === "GET") {
10450
+ res.setHeader("Cache-Control", "no-store, max-age=0");
10451
+ json(res, 200, {
10452
+ version: UI_SERVER_APP_VERSION,
10453
+ startedAt: UI_SERVER_STARTED_AT,
10454
+ });
10455
+ return;
10456
+ }
10457
+
10124
10458
  if (url.pathname === "/api/auth/login" && req.method === "POST") {
10125
10459
  let payload;
10126
10460
  try {
@@ -10166,21 +10500,136 @@ export function startUiServer({
10166
10500
  json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
10167
10501
  return;
10168
10502
  }
10503
+ if (req.method === "GET" && url.pathname === "/api/prd-workflow/collaboration") {
10504
+ const tapdId = String(url.searchParams.get("tapdId") || "").trim();
10505
+ if (!tapdId) {
10506
+ json(res, 400, { error: "Missing tapdId" });
10507
+ return;
10508
+ }
10509
+ const record = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
10510
+ json(res, 200, {
10511
+ ok: true,
10512
+ collaboration: prdWorkflowCollaborationSummaryWithUsers(record, userCtx.userId),
10513
+ });
10514
+ return;
10515
+ }
10516
+ if (req.method === "POST" && url.pathname === "/api/prd-workflow/collaboration/share") {
10517
+ try {
10518
+ const payload = JSON.parse(await readBody(req));
10519
+ const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
10520
+ if (!tapdId) {
10521
+ json(res, 400, { error: "Missing tapdId" });
10522
+ return;
10523
+ }
10524
+ const existing = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
10525
+ if (existing && prdWorkflowCollaborationAccess(existing, userCtx.userId).role !== "owner") {
10526
+ json(res, 403, { error: "仅 Workflow 所有者可以添加成员" });
10527
+ return;
10528
+ }
10529
+ const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
10530
+ if (ensured.error) {
10531
+ json(res, ensured.status || 400, { error: ensured.error });
10532
+ return;
10533
+ }
10534
+ const targetUser = findWorkspaceShareUser(payload?.username || payload?.userId);
10535
+ if (!targetUser) {
10536
+ json(res, 404, { error: "未找到该用户名,请确认对方已经登录或注册 AgentFlow" });
10537
+ return;
10538
+ }
10539
+ if (targetUser.userId === userCtx.userId) {
10540
+ json(res, 400, { error: "无需将 Workflow 分享给自己" });
10541
+ return;
10542
+ }
10543
+ const added = addPrdWorkflowCollaborationMember({
10544
+ workflowId: ensured.workflow.id,
10545
+ userId: userCtx.userId,
10546
+ memberUserId: targetUser.userId,
10547
+ role: payload?.role,
10548
+ });
10549
+ if (added.error) {
10550
+ json(res, added.status || 400, { error: added.error });
10551
+ return;
10552
+ }
10553
+ const scope = resolvePrdWorkflowScope(root, { ...payload, tapdId }, userCtx, "write");
10554
+ if (!scope.error) prdWorkflowMigrateLegacyState(scope.executionRoot, scope.stateRoot, tapdId);
10555
+ const record = getPrdWorkflowCollaborationById(ensured.workflow.id);
10556
+ prdWorkflowBroadcast(prdWorkflowKey(userCtx, "", "", tapdId), {
10557
+ type: "member.added",
10558
+ tapdId,
10559
+ memberUserId: targetUser.userId,
10560
+ });
10561
+ json(res, 200, {
10562
+ ok: true,
10563
+ collaboration: prdWorkflowCollaborationSummaryWithUsers(record, userCtx.userId),
10564
+ member: { userId: targetUser.userId, username: targetUser.username, role: "editor" },
10565
+ });
10566
+ } catch (error) {
10567
+ json(res, 400, { error: (error && error.message) || String(error) });
10568
+ }
10569
+ return;
10570
+ }
10571
+ if (req.method === "DELETE" && url.pathname === "/api/prd-workflow/collaboration/share") {
10572
+ try {
10573
+ const payload = JSON.parse(await readBody(req));
10574
+ const tapdId = String(payload?.tapdId || payload?.tapd_id || "").trim();
10575
+ const record = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
10576
+ if (!record) {
10577
+ json(res, 404, { error: "PRD Workflow collaboration not found" });
10578
+ return;
10579
+ }
10580
+ const requestedUser = String(payload?.username || payload?.memberUserId || "").trim();
10581
+ const targetUser = requestedUser ? findWorkspaceShareUser(requestedUser) : null;
10582
+ if (requestedUser && !targetUser) {
10583
+ json(res, 404, { error: "未找到该用户" });
10584
+ return;
10585
+ }
10586
+ const removed = removePrdWorkflowCollaborationMember({
10587
+ workflowId: record.id,
10588
+ userId: userCtx.userId,
10589
+ memberUserId: targetUser?.userId || userCtx.userId,
10590
+ });
10591
+ if (removed.error) {
10592
+ json(res, removed.status || 400, { error: removed.error });
10593
+ return;
10594
+ }
10595
+ const nextRecord = getPrdWorkflowCollaborationById(record.id);
10596
+ prdWorkflowBroadcast(prdWorkflowKey(userCtx, "", "", tapdId), {
10597
+ type: removed.left ? "member.left" : "member.removed",
10598
+ tapdId,
10599
+ memberUserId: removed.removedUserId || "",
10600
+ });
10601
+ json(res, 200, {
10602
+ ok: true,
10603
+ left: removed.left === true,
10604
+ removedUserId: removed.removedUserId || "",
10605
+ collaboration: removed.left
10606
+ ? null
10607
+ : prdWorkflowCollaborationSummaryWithUsers(nextRecord, userCtx.userId),
10608
+ });
10609
+ } catch (error) {
10610
+ json(res, 400, { error: (error && error.message) || String(error) });
10611
+ }
10612
+ return;
10613
+ }
10169
10614
  if (req.method === "GET" && url.pathname === "/api/prd-workflow/snapshot") {
10170
10615
  try {
10171
10616
  const tapdId = String(url.searchParams.get("tapdId") || "").trim();
10172
10617
  const flowId = String(url.searchParams.get("flowId") || "").trim();
10173
10618
  const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
10174
10619
  const archived = url.searchParams.get("archived") === "1";
10175
- let scopedRoot = root;
10176
- if (flowId) {
10177
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10178
- if (scoped.error) {
10179
- json(res, 400, { error: scoped.error });
10180
- return;
10181
- }
10182
- scopedRoot = scoped.root;
10620
+ const workflowScope = resolvePrdWorkflowScope(root, {
10621
+ tapdId,
10622
+ flowId,
10623
+ flowSource,
10624
+ archived,
10625
+ workspaceId: url.searchParams.get("workspaceId") || "",
10626
+ }, userCtx);
10627
+ if (workflowScope.error) {
10628
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
10629
+ return;
10183
10630
  }
10631
+ const scopedRoot = workflowScope.stateRoot;
10632
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10184
10633
  const useMock = url.searchParams.get("mock") === "1" || parseBool(process.env.AGENTFLOW_PRD_WORKFLOW_MOCK, false);
10185
10634
  const runtimeOnly = url.searchParams.get("runtimeOnly") === "1" ||
10186
10635
  url.searchParams.get("runtime_only") === "1" ||
@@ -10188,8 +10637,8 @@ export function startUiServer({
10188
10637
  const baseSnapshot = useMock
10189
10638
  ? prdWorkflowMockSnapshot(scopedRoot, tapdId || "mock-prd")
10190
10639
  : runtimeOnly
10191
- ? prdWorkflowMaterializeSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId })
10192
- : await prdWorkflowSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId });
10640
+ ? prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId })
10641
+ : await prdWorkflowSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId });
10193
10642
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10194
10643
  baseSnapshot,
10195
10644
  getSessionTokenFromRequest(req) || "",
@@ -10218,15 +10667,19 @@ export function startUiServer({
10218
10667
  const flowId = String(payload.flowId || "").trim();
10219
10668
  const flowSource = String(payload.flowSource || "user").trim() || "user";
10220
10669
  const archived = payload.archived === true || payload.flowArchived === true;
10221
- let scopedRoot = root;
10222
- if (flowId) {
10223
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10224
- if (scoped.error) {
10225
- json(res, 400, { error: scoped.error });
10226
- return;
10227
- }
10228
- scopedRoot = scoped.root;
10670
+ const workflowScope = resolvePrdWorkflowScope(root, {
10671
+ ...payload,
10672
+ tapdId,
10673
+ flowId,
10674
+ flowSource,
10675
+ archived,
10676
+ }, userCtx, "write");
10677
+ if (workflowScope.error) {
10678
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
10679
+ return;
10229
10680
  }
10681
+ const scopedRoot = workflowScope.stateRoot;
10682
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10230
10683
  const rawSnapshot = payload.snapshot && typeof payload.snapshot === "object" && !Array.isArray(payload.snapshot)
10231
10684
  ? payload.snapshot
10232
10685
  : payload.prd || payload.next ? payload : null;
@@ -10311,7 +10764,7 @@ export function startUiServer({
10311
10764
  incomingSnapshot: prdWorkflowCompactRuntimeValue(projectFactSnapshot, 12000),
10312
10765
  });
10313
10766
  const currentSnapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10314
- prdWorkflowMaterializeSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
10767
+ prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
10315
10768
  getSessionTokenFromRequest(req) || "",
10316
10769
  );
10317
10770
  json(res, 409, {
@@ -10351,7 +10804,7 @@ export function startUiServer({
10351
10804
  persistence: projectFactSource.persistence,
10352
10805
  });
10353
10806
  }
10354
- const materialized = prdWorkflowMaterializeSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId });
10807
+ const materialized = prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId });
10355
10808
  const withDiagnostic = prdWorkflowWithAgentflowTokenDiagnostic(materialized, getSessionTokenFromRequest(req) || "");
10356
10809
  prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "snapshot-report", tapdId, snapshot: withDiagnostic });
10357
10810
  json(res, 200, { ok: true, snapshot: withDiagnostic });
@@ -10370,30 +10823,36 @@ export function startUiServer({
10370
10823
  return;
10371
10824
  }
10372
10825
  let actionScopedRoot = root;
10826
+ let actionExecutionRoot = root;
10373
10827
  let normalizedForCatch = null;
10374
10828
  let actionRunId = "";
10375
10829
  try {
10376
10830
  const flowId = String(payload.flowId || "").trim();
10377
10831
  const flowSource = String(payload.flowSource || "user").trim() || "user";
10378
10832
  const archived = payload.archived === true || payload.flowArchived === true;
10379
- let scopedRoot = root;
10380
- if (flowId) {
10381
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10382
- if (scoped.error) {
10383
- json(res, 400, { error: scoped.error });
10384
- return;
10385
- }
10386
- scopedRoot = scoped.root;
10387
- }
10388
- actionScopedRoot = scopedRoot;
10389
10833
  const normalized = normalizePrdWorkflowActionArgs(payload);
10390
10834
  normalizedForCatch = normalized;
10391
10835
  if (normalized.error) {
10392
10836
  json(res, 400, { error: normalized.error });
10393
10837
  return;
10394
10838
  }
10839
+ const workflowScope = resolvePrdWorkflowScope(root, {
10840
+ ...payload,
10841
+ tapdId: normalized.tapdId,
10842
+ flowId,
10843
+ flowSource,
10844
+ archived,
10845
+ }, userCtx, "write");
10846
+ if (workflowScope.error) {
10847
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
10848
+ return;
10849
+ }
10850
+ const scopedRoot = workflowScope.stateRoot;
10851
+ actionScopedRoot = scopedRoot;
10852
+ actionExecutionRoot = workflowScope.executionRoot;
10853
+ prdWorkflowMigrateLegacyState(actionExecutionRoot, scopedRoot, normalized.tapdId);
10395
10854
  const idem = String(normalized.idempotencyKey || "").trim();
10396
- const idemKey = idem ? prdWorkflowKey(userCtx, flowSource, flowId, `${normalized.tapdId}:${idem}`) : "";
10855
+ const idemKey = idem ? `${prdWorkflowKey(userCtx, flowSource, flowId, normalized.tapdId)}\t${idem}` : "";
10397
10856
  if (idemKey && prdWorkflowIdempotency.has(idemKey)) {
10398
10857
  json(res, 200, { ok: true, alreadyApplied: true, ...prdWorkflowIdempotency.get(idemKey)?.result });
10399
10858
  return;
@@ -10401,7 +10860,7 @@ export function startUiServer({
10401
10860
  const completedEvent = prdWorkflowFindCompletedIdempotencyEvent(scopedRoot, normalized.tapdId, idem);
10402
10861
  if (completedEvent) {
10403
10862
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10404
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10863
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10405
10864
  getSessionTokenFromRequest(req) || "",
10406
10865
  );
10407
10866
  const result = {
@@ -10453,7 +10912,7 @@ export function startUiServer({
10453
10912
  const expectedRevision = String(payload?.expectedRevision || "").trim();
10454
10913
  if (expectedRevision) {
10455
10914
  const latestForMarker = prdWorkflowWithAgentflowTokenDiagnostic(
10456
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10915
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10457
10916
  getSessionTokenFromRequest(req) || "",
10458
10917
  );
10459
10918
  const latestRevision = String(latestForMarker?.revision || "").trim();
@@ -10511,7 +10970,7 @@ export function startUiServer({
10511
10970
  links: markerEvent.links,
10512
10971
  });
10513
10972
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10514
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10973
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10515
10974
  getSessionTokenFromRequest(req) || "",
10516
10975
  );
10517
10976
  result = {
@@ -10556,7 +11015,7 @@ export function startUiServer({
10556
11015
  output,
10557
11016
  });
10558
11017
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10559
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
11018
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10560
11019
  getSessionTokenFromRequest(req) || "",
10561
11020
  );
10562
11021
  result = {
@@ -10598,7 +11057,7 @@ export function startUiServer({
10598
11057
  command: output.command,
10599
11058
  });
10600
11059
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10601
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
11060
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10602
11061
  getSessionTokenFromRequest(req) || "",
10603
11062
  );
10604
11063
  result = {
@@ -10619,7 +11078,7 @@ export function startUiServer({
10619
11078
  return;
10620
11079
  }
10621
11080
  const runtimeEventUrl = `${serverPublicBaseUrl(req, host, uiPort)}/api/prd-workflow/event`;
10622
- const commandResult = await runPrdWorkflowCommand(root, scopedRoot, normalized.args, userCtx, {
11081
+ const commandResult = await runPrdWorkflowCommand(actionExecutionRoot, scopedRoot, normalized.args, userCtx, {
10623
11082
  timeout: 300000,
10624
11083
  env: {
10625
11084
  PRD_FLOW_RUNTIME_EVENT_URL: runtimeEventUrl,
@@ -10655,7 +11114,7 @@ export function startUiServer({
10655
11114
  links: Array.isArray(parsed?.links) ? parsed.links : [],
10656
11115
  });
10657
11116
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10658
- await prdWorkflowSnapshot(root, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
11117
+ await prdWorkflowSnapshot(actionExecutionRoot, scopedRoot, normalized.tapdId, userCtx, { flowSource, flowId }),
10659
11118
  getSessionTokenFromRequest(req) || "",
10660
11119
  );
10661
11120
  result = {
@@ -10711,7 +11170,7 @@ export function startUiServer({
10711
11170
  if (status === 409 && tapdId) {
10712
11171
  try {
10713
11172
  latestSnapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10714
- await prdWorkflowSnapshot(root, actionScopedRoot, tapdId, userCtx, { flowSource, flowId }),
11173
+ await prdWorkflowSnapshot(actionExecutionRoot, actionScopedRoot, tapdId, userCtx, { flowSource, flowId }),
10715
11174
  getSessionTokenFromRequest(req) || "",
10716
11175
  );
10717
11176
  } catch (_) {}
@@ -10750,15 +11209,19 @@ export function startUiServer({
10750
11209
  const flowId = String(url.searchParams.get("flowId") || "").trim();
10751
11210
  const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
10752
11211
  const archived = url.searchParams.get("archived") === "1";
10753
- let scopedRoot = root;
10754
- if (flowId) {
10755
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10756
- if (scoped.error) {
10757
- json(res, 400, { error: scoped.error });
10758
- return;
10759
- }
10760
- scopedRoot = scoped.root;
11212
+ const workflowScope = resolvePrdWorkflowScope(root, {
11213
+ tapdId,
11214
+ flowId,
11215
+ flowSource,
11216
+ archived,
11217
+ workspaceId: url.searchParams.get("workspaceId") || "",
11218
+ }, userCtx);
11219
+ if (workflowScope.error) {
11220
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
11221
+ return;
10761
11222
  }
11223
+ const scopedRoot = workflowScope.stateRoot;
11224
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10762
11225
  const event = prdWorkflowFindCompletedIdempotencyEvent(scopedRoot, tapdId, idempotencyKey);
10763
11226
  json(res, 200, {
10764
11227
  ok: true,
@@ -10795,15 +11258,19 @@ export function startUiServer({
10795
11258
  const flowId = String(payload.flowId || "").trim();
10796
11259
  const flowSource = String(payload.flowSource || "user").trim() || "user";
10797
11260
  const archived = payload.archived === true || payload.flowArchived === true;
10798
- let scopedRoot = root;
10799
- if (flowId) {
10800
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10801
- if (scoped.error) {
10802
- json(res, 400, { error: scoped.error });
10803
- return;
10804
- }
10805
- scopedRoot = scoped.root;
11261
+ const workflowScope = resolvePrdWorkflowScope(root, {
11262
+ ...payload,
11263
+ tapdId,
11264
+ flowId,
11265
+ flowSource,
11266
+ archived,
11267
+ }, userCtx, "write");
11268
+ if (workflowScope.error) {
11269
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
11270
+ return;
10806
11271
  }
11272
+ const scopedRoot = workflowScope.stateRoot;
11273
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10807
11274
  const existing = prdWorkflowFindCompletedIdempotencyEvent(scopedRoot, tapdId, idempotencyKey);
10808
11275
  if (existing) {
10809
11276
  json(res, 200, { ok: true, found: true, event: existing, result: existing.output || existing.result || null });
@@ -10854,15 +11321,19 @@ export function startUiServer({
10854
11321
  const flowId = String(payload.flowId || "").trim();
10855
11322
  const flowSource = String(payload.flowSource || "user").trim() || "user";
10856
11323
  const archived = payload.archived === true || payload.flowArchived === true;
10857
- let scopedRoot = root;
10858
- if (flowId) {
10859
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10860
- if (scoped.error) {
10861
- json(res, 400, { error: scoped.error });
10862
- return;
10863
- }
10864
- scopedRoot = scoped.root;
11324
+ const workflowScope = resolvePrdWorkflowScope(root, {
11325
+ ...payload,
11326
+ tapdId,
11327
+ flowId,
11328
+ flowSource,
11329
+ archived,
11330
+ }, userCtx, "write");
11331
+ if (workflowScope.error) {
11332
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
11333
+ return;
10865
11334
  }
11335
+ const scopedRoot = workflowScope.stateRoot;
11336
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10866
11337
  const eventPayload = payload.event && typeof payload.event === "object" && !Array.isArray(payload.event)
10867
11338
  ? payload.event
10868
11339
  : payload;
@@ -10870,9 +11341,13 @@ export function startUiServer({
10870
11341
  ...eventPayload,
10871
11342
  tapdId,
10872
11343
  type: eventPayload.type || "workflow-event",
11344
+ actor: {
11345
+ userId: String(userCtx?.userId || ""),
11346
+ username: String(authUser?.username || userCtx?.userId || ""),
11347
+ },
10873
11348
  });
10874
11349
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10875
- await prdWorkflowSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
11350
+ prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
10876
11351
  getSessionTokenFromRequest(req) || "",
10877
11352
  );
10878
11353
  prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "runtime-event", tapdId, event, snapshot });
@@ -10900,21 +11375,21 @@ export function startUiServer({
10900
11375
  const flowId = String(payload.flowId || "").trim();
10901
11376
  const flowSource = String(payload.flowSource || "user").trim() || "user";
10902
11377
  const archived = payload.archived === true || payload.flowArchived === true;
10903
- let scopedRoot = root;
10904
- if (flowId) {
10905
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
10906
- if (scoped.error) {
10907
- json(res, 400, { error: scoped.error });
10908
- return;
10909
- }
10910
- scopedRoot = scoped.root;
11378
+ const workflowScope = resolvePrdWorkflowScope(root, {
11379
+ ...payload,
11380
+ tapdId,
11381
+ flowId,
11382
+ flowSource,
11383
+ archived,
11384
+ }, userCtx, "write");
11385
+ if (workflowScope.error) {
11386
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
11387
+ return;
10911
11388
  }
11389
+ const scopedRoot = workflowScope.stateRoot;
11390
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
10912
11391
  const review = prdWorkflowCreateReview(scopedRoot, tapdId, payload, serverPublicBaseUrl(req, host, uiPort, payload));
10913
- const query = new URLSearchParams();
10914
- if (flowId) query.set("flowId", flowId);
10915
- if (flowId && flowSource && flowSource !== "user") query.set("flowSource", flowSource);
10916
- if (archived) query.set("archived", "1");
10917
- const reviewUrl = query.toString() ? `${review.url}?${query.toString()}` : review.url;
11392
+ const reviewUrl = review.url;
10918
11393
  const durability = review.durability || "temporary";
10919
11394
  const reviewSource = review.source && typeof review.source === "object" && !Array.isArray(review.source)
10920
11395
  ? review.source
@@ -10949,7 +11424,7 @@ export function startUiServer({
10949
11424
  reviewId: review.id,
10950
11425
  });
10951
11426
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
10952
- await prdWorkflowSnapshot(root, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
11427
+ await prdWorkflowSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
10953
11428
  getSessionTokenFromRequest(req) || "",
10954
11429
  );
10955
11430
  prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "review-link", tapdId, event, snapshot });
@@ -11002,16 +11477,20 @@ export function startUiServer({
11002
11477
  const flowId = String(url.searchParams.get("flowId") || "").trim();
11003
11478
  const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
11004
11479
  const archived = url.searchParams.get("archived") === "1";
11005
- let scopedRoot = root;
11006
- if (flowId) {
11007
- const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource, archived }, userCtx);
11008
- if (scoped.error) {
11009
- res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
11010
- res.end(scoped.error);
11011
- return;
11012
- }
11013
- scopedRoot = scoped.root;
11480
+ const workflowScope = resolvePrdWorkflowScope(root, {
11481
+ tapdId,
11482
+ flowId,
11483
+ flowSource,
11484
+ archived,
11485
+ workspaceId: url.searchParams.get("workspaceId") || "",
11486
+ }, userCtx);
11487
+ if (workflowScope.error) {
11488
+ res.writeHead(workflowScope.status || 400, { "Content-Type": "text/plain; charset=utf-8" });
11489
+ res.end(workflowScope.error);
11490
+ return;
11014
11491
  }
11492
+ const scopedRoot = workflowScope.stateRoot;
11493
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
11015
11494
  const paths = prdWorkflowReviewPaths(scopedRoot, tapdId, reviewId);
11016
11495
  if (!fs.existsSync(paths.markdownPath) || !fs.statSync(paths.markdownPath).isFile()) {
11017
11496
  res.writeHead(404);