@fieldwangai/agentflow 0.1.113 → 0.1.115

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.
@@ -126,6 +126,7 @@ import {
126
126
  readRunLedgerEvents,
127
127
  runLedgerId,
128
128
  } from "./run-ledger.mjs";
129
+ import { readAdminRunDetail } from "./admin-run-detail.mjs";
129
130
  import {
130
131
  appendWorkspaceRunLogEvent,
131
132
  createWorkspaceRunLogSession,
@@ -7723,6 +7724,78 @@ function prdWorkflowReviewPaths(scopedRoot, tapdId, reviewId) {
7723
7724
  };
7724
7725
  }
7725
7726
 
7727
+ function prdWorkflowReviewIndexPath(tapdId, reviewId) {
7728
+ return path.join(
7729
+ getAgentflowDataRoot(),
7730
+ "prd-workflow-review-index",
7731
+ prdWorkflowSafeStateId(tapdId),
7732
+ `${prdWorkflowSafeStateId(reviewId)}.json`,
7733
+ );
7734
+ }
7735
+
7736
+ function prdWorkflowWriteReviewIndex(ownerId, tapdId, reviewId) {
7737
+ const indexPath = prdWorkflowReviewIndexPath(tapdId, reviewId);
7738
+ const tempPath = `${indexPath}.${process.pid}.${Date.now()}.tmp`;
7739
+ fs.mkdirSync(path.dirname(indexPath), { recursive: true });
7740
+ fs.writeFileSync(tempPath, JSON.stringify({
7741
+ version: 1,
7742
+ tapdId: String(tapdId || ""),
7743
+ reviewId: prdWorkflowSafeStateId(reviewId),
7744
+ ownerId: String(ownerId || "").trim(),
7745
+ updatedAt: new Date().toISOString(),
7746
+ }, null, 2) + "\n", "utf-8");
7747
+ fs.renameSync(tempPath, indexPath);
7748
+ }
7749
+
7750
+ function prdWorkflowReadReviewIndex(tapdId, reviewId) {
7751
+ try {
7752
+ const parsed = JSON.parse(fs.readFileSync(prdWorkflowReviewIndexPath(tapdId, reviewId), "utf-8"));
7753
+ if (
7754
+ String(parsed?.tapdId || "") !== String(tapdId || "")
7755
+ || prdWorkflowSafeStateId(parsed?.reviewId) !== prdWorkflowSafeStateId(reviewId)
7756
+ ) {
7757
+ return null;
7758
+ }
7759
+ return parsed;
7760
+ } catch {
7761
+ return null;
7762
+ }
7763
+ }
7764
+
7765
+ function prdWorkflowReviewFileExists(paths) {
7766
+ try {
7767
+ return fs.existsSync(paths.markdownPath) && fs.statSync(paths.markdownPath).isFile();
7768
+ } catch {
7769
+ return false;
7770
+ }
7771
+ }
7772
+
7773
+ function prdWorkflowResolveReviewPaths(scopedRoot, tapdId, reviewId) {
7774
+ const direct = prdWorkflowReviewPaths(scopedRoot, tapdId, reviewId);
7775
+ if (prdWorkflowReviewFileExists(direct)) return direct;
7776
+
7777
+ const indexed = prdWorkflowReadReviewIndex(tapdId, reviewId);
7778
+ if (indexed) {
7779
+ const indexedPaths = prdWorkflowReviewPaths(
7780
+ getAgentflowUserDataRoot(indexed.ownerId || ""),
7781
+ tapdId,
7782
+ reviewId,
7783
+ );
7784
+ if (prdWorkflowReviewFileExists(indexedPaths)) return indexedPaths;
7785
+ }
7786
+
7787
+ const candidateOwners = ["", ...listAgentflowUserIds()];
7788
+ for (const ownerId of candidateOwners) {
7789
+ const candidate = prdWorkflowReviewPaths(getAgentflowUserDataRoot(ownerId), tapdId, reviewId);
7790
+ if (!prdWorkflowReviewFileExists(candidate)) continue;
7791
+ try {
7792
+ prdWorkflowWriteReviewIndex(ownerId, tapdId, reviewId);
7793
+ } catch (_) {}
7794
+ return candidate;
7795
+ }
7796
+ return direct;
7797
+ }
7798
+
7726
7799
  function prdWorkflowMigrateLegacyState(legacyRoot, stateRoot, tapdId) {
7727
7800
  const sourceRoot = path.resolve(legacyRoot || "");
7728
7801
  const destinationRoot = path.resolve(stateRoot || "");
@@ -7752,6 +7825,75 @@ function prdWorkflowMigrateLegacyState(legacyRoot, stateRoot, tapdId) {
7752
7825
  } catch (_) {}
7753
7826
  }
7754
7827
 
7828
+ function prdWorkflowReviewShortLinkDir(root) {
7829
+ return path.join(path.resolve(root || process.cwd()), ".workspace", "prd-flow", "review-short-links");
7830
+ }
7831
+
7832
+ function prdWorkflowReviewShortLinkPath(root, shortCode) {
7833
+ return path.join(
7834
+ prdWorkflowReviewShortLinkDir(root),
7835
+ `${String(shortCode || "").trim()}.json`,
7836
+ );
7837
+ }
7838
+
7839
+ function prdWorkflowReadReviewShortLink(root, shortCode) {
7840
+ const code = String(shortCode || "").trim();
7841
+ if (!/^[A-Za-z0-9_-]{8,32}$/.test(code)) return null;
7842
+ try {
7843
+ const filePath = prdWorkflowReviewShortLinkPath(root, code);
7844
+ if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) return null;
7845
+ const link = JSON.parse(fs.readFileSync(filePath, "utf-8"));
7846
+ const targetPath = String(link?.targetPath || "").trim();
7847
+ if (!targetPath.startsWith("/api/prd-workflow/review/") || /[\r\n]/.test(targetPath)) return null;
7848
+ return {
7849
+ ...link,
7850
+ shortCode: code,
7851
+ targetPath,
7852
+ filePath,
7853
+ };
7854
+ } catch {
7855
+ return null;
7856
+ }
7857
+ }
7858
+
7859
+ function prdWorkflowCreateReviewShortLink(root, reviewUrl, review = {}) {
7860
+ let parsed;
7861
+ try {
7862
+ parsed = new URL(String(reviewUrl || ""));
7863
+ } catch {
7864
+ return null;
7865
+ }
7866
+ const targetPath = `${parsed.pathname}${parsed.search}`;
7867
+ if (!targetPath.startsWith("/api/prd-workflow/review/")) return null;
7868
+ const digest = crypto.createHash("sha256").update(targetPath).digest("base64url");
7869
+ const dir = prdWorkflowReviewShortLinkDir(root);
7870
+ fs.mkdirSync(dir, { recursive: true });
7871
+ for (let length = 8; length <= 24; length += 2) {
7872
+ const shortCode = digest.slice(0, length);
7873
+ const existing = prdWorkflowReadReviewShortLink(root, shortCode);
7874
+ if (existing && existing.targetPath !== targetPath) continue;
7875
+ const link = {
7876
+ shortCode,
7877
+ targetPath,
7878
+ tapdId: String(review?.tapdId || ""),
7879
+ reviewId: String(review?.id || ""),
7880
+ durability: String(review?.durability || "temporary"),
7881
+ expiresAt: String(review?.expiresAt || ""),
7882
+ createdAt: String(review?.createdAt || new Date().toISOString()),
7883
+ };
7884
+ fs.writeFileSync(
7885
+ prdWorkflowReviewShortLinkPath(root, shortCode),
7886
+ JSON.stringify(link, null, 2) + "\n",
7887
+ "utf-8",
7888
+ );
7889
+ return {
7890
+ ...link,
7891
+ shortUrl: `${parsed.origin}/r/${shortCode}`,
7892
+ };
7893
+ }
7894
+ throw new Error("Unable to allocate a unique review short code");
7895
+ }
7896
+
7755
7897
  function prdWorkflowPruneReviews(scopedRoot, tapdId, maxReviews = 200) {
7756
7898
  try {
7757
7899
  const dir = prdWorkflowReviewDir(scopedRoot, tapdId);
@@ -7772,10 +7914,12 @@ function prdWorkflowPruneReviews(scopedRoot, tapdId, maxReviews = 200) {
7772
7914
  for (const entry of entries.filter((item) => Number.isFinite(item.expiresMs) && item.expiresMs < now)) {
7773
7915
  try { fs.unlinkSync(entry.abs); } catch (_) {}
7774
7916
  try { fs.unlinkSync(path.join(dir, `${entry.id}.md`)); } catch (_) {}
7917
+ try { fs.unlinkSync(prdWorkflowReviewIndexPath(tapdId, entry.id)); } catch (_) {}
7775
7918
  }
7776
7919
  for (const entry of entries.filter((item) => !(Number.isFinite(item.expiresMs) && item.expiresMs < now)).slice(maxReviews)) {
7777
7920
  try { fs.unlinkSync(entry.abs); } catch (_) {}
7778
7921
  try { fs.unlinkSync(path.join(dir, `${entry.id}.md`)); } catch (_) {}
7922
+ try { fs.unlinkSync(prdWorkflowReviewIndexPath(tapdId, entry.id)); } catch (_) {}
7779
7923
  }
7780
7924
  } catch (_) {}
7781
7925
  }
@@ -7804,6 +7948,30 @@ function prdWorkflowReviewSplitFrontmatter(markdown) {
7804
7948
  return { frontmatter: match[1].trim(), body: text.slice(match[0].length) };
7805
7949
  }
7806
7950
 
7951
+ function prdWorkflowReviewStripLegacyMetadata(body) {
7952
+ const lines = String(body || "").replace(/\r\n/g, "\n").split("\n");
7953
+ const visible = [];
7954
+ let fenced = false;
7955
+ let hidden = false;
7956
+ for (const line of lines) {
7957
+ if (!hidden && /^\s*```/.test(line)) {
7958
+ fenced = !fenced;
7959
+ visible.push(line);
7960
+ continue;
7961
+ }
7962
+ if (!fenced && !hidden && /<!--\s*prd-flow-start\b/.test(line)) {
7963
+ hidden = !/prd-flow-end\s*-->/.test(line);
7964
+ continue;
7965
+ }
7966
+ if (hidden) {
7967
+ if (/prd-flow-end\s*-->/.test(line)) hidden = false;
7968
+ continue;
7969
+ }
7970
+ visible.push(line);
7971
+ }
7972
+ return visible.join("\n");
7973
+ }
7974
+
7807
7975
  function prdWorkflowReviewRenderFrontmatter(frontmatter) {
7808
7976
  if (!String(frontmatter || "").trim()) return "";
7809
7977
  const rows = [];
@@ -8050,20 +8218,24 @@ export function prdWorkflowReviewMarkdownToHtml(markdown) {
8050
8218
  const { frontmatter, body } = prdWorkflowReviewSplitFrontmatter(markdown);
8051
8219
  return [
8052
8220
  prdWorkflowReviewRenderFrontmatter(frontmatter),
8053
- prdWorkflowReviewBodyToHtml(body),
8221
+ prdWorkflowReviewBodyToHtml(prdWorkflowReviewStripLegacyMetadata(body)),
8054
8222
  ].filter(Boolean).join("\n");
8055
8223
  }
8056
8224
 
8057
8225
  function prdWorkflowReviewExtractPageTitle(markdown, fallbackTitle) {
8058
8226
  const { frontmatter, body } = prdWorkflowReviewSplitFrontmatter(markdown);
8059
- const lines = String(body || "").replace(/\r\n/g, "\n").split("\n");
8227
+ const visibleBody = prdWorkflowReviewStripLegacyMetadata(body);
8228
+ const lines = String(visibleBody || "").replace(/\r\n/g, "\n").split("\n");
8060
8229
  const firstContentIndex = lines.findIndex((line) => String(line || "").trim());
8061
8230
  const heading = firstContentIndex >= 0
8062
8231
  ? String(lines[firstContentIndex] || "").trim().match(/^#\s+(.+)$/)
8063
8232
  : null;
8064
8233
  if (!heading) {
8065
8234
  return {
8066
- markdown: String(markdown || ""),
8235
+ markdown: [
8236
+ frontmatter ? `---\n${frontmatter}\n---` : "",
8237
+ visibleBody,
8238
+ ].filter(Boolean).join("\n\n"),
8067
8239
  title: String(fallbackTitle || "PRD Workflow Review"),
8068
8240
  };
8069
8241
  }
@@ -8275,7 +8447,7 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
8275
8447
  </html>`;
8276
8448
  }
8277
8449
 
8278
- function prdWorkflowCreateReview(scopedRoot, tapdId, payload = {}, urlBase = "") {
8450
+ function prdWorkflowCreateReview(scopedRoot, tapdId, payload = {}, urlBase = "", ownerId = "") {
8279
8451
  const content = String(payload.markdown || payload.content || payload.rawOutput || "").slice(0, 500000);
8280
8452
  if (!content.trim()) throw new Error("Missing review markdown");
8281
8453
  const title = String(payload.title || payload.label || "PRD Workflow Review").trim().slice(0, 160) || "PRD Workflow Review";
@@ -8311,6 +8483,7 @@ function prdWorkflowCreateReview(scopedRoot, tapdId, payload = {}, urlBase = "")
8311
8483
  fs.mkdirSync(paths.dir, { recursive: true });
8312
8484
  fs.writeFileSync(paths.markdownPath, content.trimEnd() + "\n", "utf-8");
8313
8485
  fs.writeFileSync(paths.metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
8486
+ prdWorkflowWriteReviewIndex(ownerId, tapdId, paths.id);
8314
8487
  prdWorkflowPruneReviews(scopedRoot, tapdId);
8315
8488
  prdWorkflowAppendAudit(scopedRoot, tapdId, {
8316
8489
  type: "review-created",
@@ -10698,6 +10871,7 @@ function normalizeContextInstanceIds(raw) {
10698
10871
  * @param {string} opts.workspaceRoot
10699
10872
  * @param {number} opts.port
10700
10873
  * @param {boolean} [opts.hideCommunityLinks]
10874
+ * @param {boolean} [opts.enableWorkspaceScheduler]
10701
10875
  * @param {string} [opts.staticDir] 默认 PACKAGE_ROOT/builtin/web-ui/dist(npm run build 产出)
10702
10876
  * @returns {Promise<import('http').Server>}
10703
10877
  */
@@ -10706,6 +10880,7 @@ export function startUiServer({
10706
10880
  port,
10707
10881
  host = "127.0.0.1",
10708
10882
  hideCommunityLinks = false,
10883
+ enableWorkspaceScheduler = true,
10709
10884
  staticDir = path.join(PACKAGE_ROOT, "builtin", "web-ui", "dist"),
10710
10885
  }) {
10711
10886
  const root = path.resolve(workspaceRoot);
@@ -11095,7 +11270,19 @@ export function startUiServer({
11095
11270
  baseSnapshot,
11096
11271
  getSessionTokenFromRequest(req) || "",
11097
11272
  );
11098
- json(res, 200, { ok: true, snapshot });
11273
+ const workflowShare = workflowScope.collaboration?.shareToken
11274
+ ? prdWorkflowShareLinkSummary(
11275
+ workflowScope.collaboration,
11276
+ workflowScope.collaboration.shareToken,
11277
+ serverPublicBaseUrl(req, host, uiPort),
11278
+ userCtx.userId,
11279
+ )
11280
+ : null;
11281
+ json(res, 200, {
11282
+ ok: true,
11283
+ snapshot,
11284
+ ...(workflowShare ? { workflowShare, shareUrl: workflowShare.url } : {}),
11285
+ });
11099
11286
  } catch (e) {
11100
11287
  json(res, 500, { error: (e && e.message) || String(e) });
11101
11288
  }
@@ -11120,6 +11307,22 @@ export function startUiServer({
11120
11307
  json(res, 400, { error: "Missing tapdId" });
11121
11308
  return;
11122
11309
  }
11310
+ const rawSnapshot = payload.snapshot && typeof payload.snapshot === "object" && !Array.isArray(payload.snapshot)
11311
+ ? payload.snapshot
11312
+ : payload.prd || payload.next ? payload : null;
11313
+ if (!rawSnapshot) {
11314
+ json(res, 400, { error: "Missing snapshot" });
11315
+ return;
11316
+ }
11317
+ const existingCollaboration = getPrdWorkflowCollaborationForUser(tapdId, userCtx.userId);
11318
+ const existingAccess = prdWorkflowCollaborationAccess(existingCollaboration, userCtx.userId);
11319
+ const shareResult = existingCollaboration && existingAccess.role !== "owner"
11320
+ ? { record: existingCollaboration, created: false }
11321
+ : ensurePrdWorkflowShareLink({ tapdId, userId: userCtx.userId });
11322
+ if (shareResult.error) {
11323
+ json(res, shareResult.status || 400, { error: shareResult.error });
11324
+ return;
11325
+ }
11123
11326
  const flowId = String(payload.flowId || "").trim();
11124
11327
  const flowSource = String(payload.flowSource || "user").trim() || "user";
11125
11328
  const archived = payload.archived === true || payload.flowArchived === true;
@@ -11136,13 +11339,6 @@ export function startUiServer({
11136
11339
  }
11137
11340
  const scopedRoot = workflowScope.stateRoot;
11138
11341
  prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
11139
- const rawSnapshot = payload.snapshot && typeof payload.snapshot === "object" && !Array.isArray(payload.snapshot)
11140
- ? payload.snapshot
11141
- : payload.prd || payload.next ? payload : null;
11142
- if (!rawSnapshot) {
11143
- json(res, 400, { error: "Missing snapshot" });
11144
- return;
11145
- }
11146
11342
  const normalizedSnapshot = {
11147
11343
  ...prdWorkflowSnapshotFromParsed(scopedRoot, tapdId, rawSnapshot, userCtx, { flowSource, flowId }),
11148
11344
  clientReportedAt: new Date().toISOString(),
@@ -11262,8 +11458,20 @@ export function startUiServer({
11262
11458
  }
11263
11459
  const materialized = prdWorkflowMaterializeSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId });
11264
11460
  const withDiagnostic = prdWorkflowWithAgentflowTokenDiagnostic(materialized, getSessionTokenFromRequest(req) || "");
11461
+ const workflowShare = shareResult.record?.shareToken
11462
+ ? prdWorkflowShareLinkSummary(
11463
+ shareResult.record,
11464
+ shareResult.record.shareToken,
11465
+ serverPublicBaseUrl(req, host, uiPort, payload),
11466
+ userCtx.userId,
11467
+ )
11468
+ : null;
11265
11469
  prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "snapshot-report", tapdId, snapshot: withDiagnostic });
11266
- json(res, 200, { ok: true, snapshot: withDiagnostic });
11470
+ json(res, 200, {
11471
+ ok: true,
11472
+ snapshot: withDiagnostic,
11473
+ ...(workflowShare ? { workflowShare, shareUrl: workflowShare.url } : {}),
11474
+ });
11267
11475
  } catch (e) {
11268
11476
  json(res, 500, { error: (e && e.message) || String(e) });
11269
11477
  }
@@ -11966,8 +12174,26 @@ export function startUiServer({
11966
12174
  }
11967
12175
  const scopedRoot = workflowScope.stateRoot;
11968
12176
  prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
11969
- const review = prdWorkflowCreateReview(scopedRoot, tapdId, payload, serverPublicBaseUrl(req, host, uiPort, payload));
11970
- const reviewUrl = review.url;
12177
+ const review = prdWorkflowCreateReview(
12178
+ scopedRoot,
12179
+ tapdId,
12180
+ payload,
12181
+ serverPublicBaseUrl(req, host, uiPort, payload),
12182
+ workflowScope.ownerId,
12183
+ );
12184
+ const query = new URLSearchParams();
12185
+ if (flowId) query.set("flowId", flowId);
12186
+ if (flowId && flowSource && flowSource !== "user") query.set("flowSource", flowSource);
12187
+ if (archived) query.set("archived", "1");
12188
+ const reviewUrl = query.toString() ? `${review.url}?${query.toString()}` : review.url;
12189
+ let shortLink = null;
12190
+ try {
12191
+ shortLink = prdWorkflowCreateReviewShortLink(root, reviewUrl, review);
12192
+ } catch (e) {
12193
+ log.debug(`[prd-workflow] review short link failed: ${(e && e.message) || String(e)}`);
12194
+ }
12195
+ const shortUrl = shortLink?.shortUrl || "";
12196
+ const displayUrl = shortUrl || reviewUrl;
11971
12197
  const durability = review.durability || "temporary";
11972
12198
  const reviewSource = review.source && typeof review.source === "object" && !Array.isArray(review.source)
11973
12199
  ? review.source
@@ -11979,7 +12205,9 @@ export function startUiServer({
11979
12205
  durability,
11980
12206
  source: reviewSource,
11981
12207
  confirmed: payload.confirmed === true || payload.confirmed === "1",
11982
- url: reviewUrl,
12208
+ url: displayUrl,
12209
+ canonicalUrl: reviewUrl,
12210
+ shortUrl,
11983
12211
  expiresAt: review.expiresAt || "",
11984
12212
  };
11985
12213
  const event = prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, {
@@ -11998,15 +12226,35 @@ export function startUiServer({
11998
12226
  sourceArtifact: reviewSource,
11999
12227
  expiresAt: review.expiresAt || "",
12000
12228
  artifacts: [artifact],
12001
- links: [{ label: "Markdown Review", url: reviewUrl, persistence: "runtime", durability, source: reviewSource, expiresAt: review.expiresAt || "" }],
12229
+ links: [{
12230
+ label: "Markdown Review",
12231
+ url: displayUrl,
12232
+ canonicalUrl: reviewUrl,
12233
+ shortUrl,
12234
+ persistence: "runtime",
12235
+ durability,
12236
+ source: reviewSource,
12237
+ expiresAt: review.expiresAt || "",
12238
+ }],
12002
12239
  reviewId: review.id,
12240
+ reviewShortCode: shortLink?.shortCode || "",
12003
12241
  });
12004
12242
  const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
12005
12243
  await prdWorkflowSnapshot(workflowScope.executionRoot, scopedRoot, tapdId, userCtx, { flowSource, flowId }),
12006
12244
  getSessionTokenFromRequest(req) || "",
12007
12245
  );
12008
12246
  prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "review-link", tapdId, event, snapshot });
12009
- json(res, 200, { ok: true, review: { ...review, url: reviewUrl }, event, snapshot });
12247
+ json(res, 200, {
12248
+ ok: true,
12249
+ review: {
12250
+ ...review,
12251
+ url: reviewUrl,
12252
+ shortUrl,
12253
+ shortCode: shortLink?.shortCode || "",
12254
+ },
12255
+ event,
12256
+ snapshot,
12257
+ });
12010
12258
  } catch (e) {
12011
12259
  json(res, 500, { error: (e && e.message) || String(e) });
12012
12260
  }
@@ -12053,6 +12301,41 @@ export function startUiServer({
12053
12301
  return;
12054
12302
  }
12055
12303
 
12304
+ if (req.method === "GET" && url.pathname.startsWith("/r/")) {
12305
+ try {
12306
+ const parts = url.pathname.split("/").filter(Boolean);
12307
+ const shortCode = decodeURIComponent(parts[1] || "");
12308
+ if (parts.length !== 2) {
12309
+ res.writeHead(404);
12310
+ res.end("Not found");
12311
+ return;
12312
+ }
12313
+ const link = prdWorkflowReadReviewShortLink(root, shortCode);
12314
+ if (!link) {
12315
+ res.writeHead(404);
12316
+ res.end("Not found");
12317
+ return;
12318
+ }
12319
+ const expiresMs = Date.parse(link.expiresAt || "");
12320
+ if (Number.isFinite(expiresMs) && expiresMs < Date.now()) {
12321
+ try { fs.unlinkSync(link.filePath); } catch (_) {}
12322
+ res.writeHead(410, { "Content-Type": "text/plain; charset=utf-8" });
12323
+ res.end("Review link expired");
12324
+ return;
12325
+ }
12326
+ res.writeHead(302, {
12327
+ Location: link.targetPath,
12328
+ "Cache-Control": "no-store",
12329
+ "Referrer-Policy": "no-referrer",
12330
+ });
12331
+ res.end();
12332
+ } catch {
12333
+ res.writeHead(404);
12334
+ res.end("Not found");
12335
+ }
12336
+ return;
12337
+ }
12338
+
12056
12339
  if (req.method === "GET" && url.pathname.startsWith("/api/prd-workflow/review/")) {
12057
12340
  try {
12058
12341
  const parts = url.pathname.split("/").filter(Boolean);
@@ -12081,8 +12364,8 @@ export function startUiServer({
12081
12364
  }
12082
12365
  const scopedRoot = workflowScope.stateRoot;
12083
12366
  prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
12084
- const paths = prdWorkflowReviewPaths(scopedRoot, tapdId, reviewId);
12085
- if (!fs.existsSync(paths.markdownPath) || !fs.statSync(paths.markdownPath).isFile()) {
12367
+ const paths = prdWorkflowResolveReviewPaths(scopedRoot, tapdId, reviewId);
12368
+ if (!prdWorkflowReviewFileExists(paths)) {
12086
12369
  res.writeHead(404);
12087
12370
  res.end("Not found");
12088
12371
  return;
@@ -12364,6 +12647,35 @@ export function startUiServer({
12364
12647
  return;
12365
12648
  }
12366
12649
 
12650
+ if (req.method === "GET" && url.pathname === "/api/admin/run-detail") {
12651
+ if (!authUser?.isAdmin) {
12652
+ json(res, 403, { error: "Admin permission required" });
12653
+ return;
12654
+ }
12655
+ const input = {
12656
+ runType: url.searchParams.get("runType") || "pipeline",
12657
+ userId: url.searchParams.get("userId") || "",
12658
+ flowId: url.searchParams.get("flowId") || "",
12659
+ flowSource: url.searchParams.get("flowSource") || "user",
12660
+ runId: url.searchParams.get("runId") || "",
12661
+ };
12662
+ if (!input.userId || !input.flowId || !input.runId) {
12663
+ json(res, 400, { error: "Missing userId, flowId or runId" });
12664
+ return;
12665
+ }
12666
+ try {
12667
+ const detail = readAdminRunDetail(root, input);
12668
+ if (!detail) {
12669
+ json(res, 404, { error: "Run detail not found" });
12670
+ return;
12671
+ }
12672
+ json(res, 200, detail);
12673
+ } catch (e) {
12674
+ json(res, 500, { error: (e && e.message) || String(e) });
12675
+ }
12676
+ return;
12677
+ }
12678
+
12367
12679
  if (url.pathname === "/api/admin/user-allowlist") {
12368
12680
  if (!authUser?.isAdmin) {
12369
12681
  json(res, 403, { error: "Admin permission required" });
@@ -16639,24 +16951,26 @@ finishedAt: "${new Date().toISOString()}"
16639
16951
  res.end(data);
16640
16952
  });
16641
16953
 
16642
- const workspaceScheduleTimer = setInterval(() => {
16643
- try {
16644
- pollWorkspaceSchedules(root);
16645
- } catch (e) {
16646
- log.debug(`[workspace-scheduler] poll failed: ${(e && e.message) || String(e)}`);
16647
- }
16648
- }, WORKSPACE_SCHEDULE_POLL_MS);
16649
- try {
16650
- workspaceScheduleTimer.unref?.();
16651
- } catch (_) {}
16652
- server.on("close", () => clearInterval(workspaceScheduleTimer));
16653
- setTimeout(() => {
16954
+ if (enableWorkspaceScheduler) {
16955
+ const workspaceScheduleTimer = setInterval(() => {
16956
+ try {
16957
+ pollWorkspaceSchedules(root);
16958
+ } catch (e) {
16959
+ log.debug(`[workspace-scheduler] poll failed: ${(e && e.message) || String(e)}`);
16960
+ }
16961
+ }, WORKSPACE_SCHEDULE_POLL_MS);
16654
16962
  try {
16655
- pollWorkspaceSchedules(root);
16656
- } catch (e) {
16657
- log.debug(`[workspace-scheduler] initial poll failed: ${(e && e.message) || String(e)}`);
16658
- }
16659
- }, 1000).unref?.();
16963
+ workspaceScheduleTimer.unref?.();
16964
+ } catch (_) {}
16965
+ server.on("close", () => clearInterval(workspaceScheduleTimer));
16966
+ setTimeout(() => {
16967
+ try {
16968
+ pollWorkspaceSchedules(root);
16969
+ } catch (e) {
16970
+ log.debug(`[workspace-scheduler] initial poll failed: ${(e && e.message) || String(e)}`);
16971
+ }
16972
+ }, 1000).unref?.();
16973
+ }
16660
16974
 
16661
16975
  return new Promise((resolve, reject) => {
16662
16976
  server.once("error", reject);