@fieldwangai/agentflow 0.1.125 → 0.1.127

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.
@@ -105,6 +105,14 @@ export function getPrdWorkflowCollaborationForUser(tapdId, userId) {
105
105
  return records.find((record) => record.ownerId === actorId) || records[0] || null;
106
106
  }
107
107
 
108
+ export function listPrdWorkflowCollaborationsForUser(userId) {
109
+ const actorId = normalizeUserId(userId);
110
+ if (!actorId) return [];
111
+ return Object.values(readRegistry().workflows)
112
+ .filter((record) => prdWorkflowCollaborationAccess(record, actorId).allowed)
113
+ .sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")));
114
+ }
115
+
108
116
  export function ensurePrdWorkflowCollaboration({ tapdId, userId }) {
109
117
  const ownerId = normalizeUserId(userId);
110
118
  const normalizedTapdId = normalizeTapdId(tapdId);
@@ -156,6 +156,7 @@ import {
156
156
  getPrdWorkflowCollaborationByShareToken,
157
157
  getPrdWorkflowCollaborationForUser,
158
158
  ensurePrdWorkflowShareLink,
159
+ listPrdWorkflowCollaborationsForUser,
159
160
  prdWorkflowCollaborationAccess,
160
161
  prdWorkflowCollaborationSummary,
161
162
  removePrdWorkflowCollaborationMember,
@@ -3349,6 +3350,123 @@ function prdWorkflowShareLinkSummary(record, shareToken, publicBaseUrl, userId =
3349
3350
  };
3350
3351
  }
3351
3352
 
3353
+ function prdWorkflowDashboardActions(snapshot = {}) {
3354
+ const rows = new Map();
3355
+ for (const field of ["actions", "workflowActions", "workflow_actions", "timeline", "history"]) {
3356
+ for (const item of Array.isArray(snapshot?.[field]) ? snapshot[field] : []) {
3357
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
3358
+ const key = String(
3359
+ item.stageKey
3360
+ || item.stage_key
3361
+ || item.actionId
3362
+ || item.action_id
3363
+ || item.id
3364
+ || item.action
3365
+ || "",
3366
+ ).trim();
3367
+ if (!key) continue;
3368
+ rows.set(key, { ...(rows.get(key) || {}), ...item });
3369
+ }
3370
+ }
3371
+ return Array.from(rows.values());
3372
+ }
3373
+
3374
+ function prdWorkflowDashboardTimestamp(item = {}) {
3375
+ for (const value of [
3376
+ item.updatedAt,
3377
+ item.updated_at,
3378
+ item.observedAt,
3379
+ item.observed_at,
3380
+ item.completedAt,
3381
+ item.completed_at,
3382
+ item.stageEnteredAt,
3383
+ item.stage_entered_at,
3384
+ item.actionAt,
3385
+ item.action_at,
3386
+ item.startedAt,
3387
+ item.started_at,
3388
+ item.reportedAt,
3389
+ item.reported_at,
3390
+ item.createdAt,
3391
+ item.created_at,
3392
+ item.at,
3393
+ ]) {
3394
+ const timestamp = Date.parse(String(value || ""));
3395
+ if (Number.isFinite(timestamp)) return timestamp;
3396
+ }
3397
+ return 0;
3398
+ }
3399
+
3400
+ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
3401
+ const tapdId = String(record?.tapdId || snapshot?.tapdId || snapshot?.tapd_id || "").trim();
3402
+ const collaboration = prdWorkflowCollaborationSummaryWithUsers(record, userCtx?.userId) || {};
3403
+ const actions = prdWorkflowDashboardActions(snapshot);
3404
+ const doneStatuses = new Set(["done", "complete", "success", "completed", "passed", "observed"]);
3405
+ const completedActions = actions.filter((item) => (
3406
+ doneStatuses.has(String(item?.status || "").trim().toLowerCase())
3407
+ )).length;
3408
+ const latestAction = [...actions].sort((left, right) => (
3409
+ prdWorkflowDashboardTimestamp(right) - prdWorkflowDashboardTimestamp(left)
3410
+ ))[0] || null;
3411
+ const issues = Array.isArray(snapshot?.issues)
3412
+ ? snapshot.issues
3413
+ : Array.isArray(snapshot?.raw?.prd?.issues)
3414
+ ? snapshot.raw.prd.issues
3415
+ : [];
3416
+ const platforms = Array.from(new Set(issues
3417
+ .map((issue) => String(issue?.platform || "").trim().toLowerCase())
3418
+ .filter(Boolean)));
3419
+ const phase = String(snapshot?.phase || "").trim();
3420
+ const phaseKey = phase.toLowerCase();
3421
+ const state = /blocked|failed|error|conflict/.test(phaseKey)
3422
+ ? "blocked"
3423
+ : /done|completed|released|closed/.test(phaseKey) || (actions.length > 0 && completedActions === actions.length)
3424
+ ? "completed"
3425
+ : "active";
3426
+ const requirement = snapshot?.overall?.requirement && typeof snapshot.overall.requirement === "object"
3427
+ ? snapshot.overall.requirement
3428
+ : {};
3429
+ const title = String(
3430
+ requirement.title
3431
+ || requirement.name
3432
+ || snapshot?.prd?.title
3433
+ || snapshot?.raw?.prd?.title
3434
+ || snapshot?.title
3435
+ || "",
3436
+ ).trim();
3437
+ const updatedAtTimestamp = Math.max(
3438
+ prdWorkflowDashboardTimestamp(record),
3439
+ prdWorkflowDashboardTimestamp(snapshot),
3440
+ prdWorkflowDashboardTimestamp(latestAction || {}),
3441
+ );
3442
+ return {
3443
+ id: String(record?.id || ""),
3444
+ tapdId,
3445
+ title,
3446
+ phase,
3447
+ state,
3448
+ pointer: String(snapshot?.pointer || "").trim(),
3449
+ revision: String(snapshot?.revision || "").trim(),
3450
+ issueCount: issues.length,
3451
+ platforms,
3452
+ actionCount: actions.length,
3453
+ completedActionCount: completedActions,
3454
+ latestAction: latestAction
3455
+ ? {
3456
+ title: String(latestAction.title || latestAction.label || latestAction.action || latestAction.id || "").trim(),
3457
+ status: String(latestAction.status || "").trim(),
3458
+ at: prdWorkflowDashboardTimestamp(latestAction) ? new Date(prdWorkflowDashboardTimestamp(latestAction)).toISOString() : "",
3459
+ }
3460
+ : null,
3461
+ role: String(collaboration.role || ""),
3462
+ ownerId: String(collaboration.ownerId || ""),
3463
+ ownerUsername: String(collaboration.ownerUsername || collaboration.ownerId || ""),
3464
+ memberCount: Number(collaboration.memberCount || 0),
3465
+ shareActive: collaboration.shareActive === true,
3466
+ updatedAt: updatedAtTimestamp ? new Date(updatedAtTimestamp).toISOString() : String(record?.updatedAt || ""),
3467
+ };
3468
+ }
3469
+
3352
3470
  function workspaceConversationsPath(scopedRoot) {
3353
3471
  return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
3354
3472
  }
@@ -8141,7 +8259,7 @@ function prdWorkflowReviewRenderFrontmatter(frontmatter) {
8141
8259
  }
8142
8260
  }
8143
8261
  flush();
8144
- return `<details class="frontmatter" open><summary>文档元数据</summary><table>${rows}</table></details>`;
8262
+ return `<details class="frontmatter"><summary>文档元数据</summary><table><colgroup><col class="frontmatter-key-column"><col></colgroup>${rows}</table></details>`;
8145
8263
  }
8146
8264
 
8147
8265
  function prdWorkflowReviewRenderTable(lines) {
@@ -8614,7 +8732,7 @@ function prdWorkflowReviewRenderChangeIntent(change) {
8614
8732
  function prdWorkflowReviewMarkdownLinesToHtml(lines) {
8615
8733
  const html = [];
8616
8734
  let paragraph = [];
8617
- let list = [];
8735
+ let list = null;
8618
8736
  let code = null;
8619
8737
  const flushParagraph = () => {
8620
8738
  if (!paragraph.length) return;
@@ -8622,9 +8740,11 @@ function prdWorkflowReviewMarkdownLinesToHtml(lines) {
8622
8740
  paragraph = [];
8623
8741
  };
8624
8742
  const flushList = () => {
8625
- if (!list.length) return;
8626
- html.push(`<ul>${list.map((item) => `<li>${item}</li>`).join("")}</ul>`);
8627
- list = [];
8743
+ if (!list?.items?.length) return;
8744
+ const tag = list.ordered ? "ol" : "ul";
8745
+ const start = list.ordered && list.start !== 1 ? ` start="${list.start}"` : "";
8746
+ html.push(`<${tag}${start}>${list.items.map((item) => `<li>${item}</li>`).join("")}</${tag}>`);
8747
+ list = null;
8628
8748
  };
8629
8749
  const flushBlocks = () => {
8630
8750
  flushParagraph();
@@ -8713,13 +8833,31 @@ function prdWorkflowReviewMarkdownLinesToHtml(lines) {
8713
8833
  html.push(`<blockquote>${prdWorkflowReviewInlineMarkdown(quote[1])}</blockquote>`);
8714
8834
  continue;
8715
8835
  }
8716
- const bullet = trimmed.match(/^[-*]\s+(?:\[( |x|X)\]\s+)?(.+)$/);
8717
- if (bullet) {
8836
+ const listItem = line.match(/^(\s*)([-*]|\d+[.)])\s+(?:\[( |x|X)\]\s+)?(.+)$/);
8837
+ if (listItem) {
8718
8838
  flushParagraph();
8719
- const checked = bullet[1] ? `<input type="checkbox" disabled${bullet[1].toLowerCase() === "x" ? " checked" : ""}> ` : "";
8720
- list.push(`${checked}${prdWorkflowReviewInlineMarkdown(bullet[2])}`);
8839
+ const ordered = /^\d/.test(listItem[2]);
8840
+ if (list && list.ordered !== ordered) flushList();
8841
+ if (!list) {
8842
+ list = {
8843
+ ordered,
8844
+ start: ordered ? Number.parseInt(listItem[2], 10) || 1 : 1,
8845
+ indent: listItem[1].length,
8846
+ items: [],
8847
+ };
8848
+ }
8849
+ const checked = listItem[3]
8850
+ ? `<input type="checkbox" disabled${listItem[3].toLowerCase() === "x" ? " checked" : ""}> `
8851
+ : "";
8852
+ list.items.push(`${checked}${prdWorkflowReviewInlineMarkdown(listItem[4])}`);
8853
+ continue;
8854
+ }
8855
+ if (list?.items?.length && /^\s{2,}\S/.test(line)) {
8856
+ const lastIndex = list.items.length - 1;
8857
+ list.items[lastIndex] += ` ${prdWorkflowReviewInlineMarkdown(trimmed)}`;
8721
8858
  continue;
8722
8859
  }
8860
+ if (list) flushList();
8723
8861
  paragraph.push(trimmed);
8724
8862
  }
8725
8863
  if (code) html.push(`<pre><code>${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(code.lines.join("\n")))}</code></pre>`);
@@ -9015,18 +9153,20 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
9015
9153
  .raw:hover, .theme-toggle:hover { border-color: var(--interactive); color: var(--link); }
9016
9154
  .theme-toggle { cursor: pointer; font-family: inherit; }
9017
9155
  .lifecycle { margin-top: 10px; display: inline-flex; max-width: 100%; border: 1px solid var(--border); border-radius: 999px; background: var(--button); color: var(--muted); padding: 6px 10px; font-size: 12px; font-weight: 800; line-height: 1.35; overflow-wrap: anywhere; }
9018
- article { min-width: 0; border: 1px solid var(--border); border-radius: 14px; background: var(--panel); box-shadow: 0 18px 50px var(--shadow); padding: clamp(20px, 4vw, 34px); }
9156
+ article { min-width: 0; border: 0; border-radius: 14px; background: var(--panel); box-shadow: 0 18px 50px var(--shadow); padding: clamp(20px, 4vw, 34px); }
9019
9157
  article > *:first-child { margin-top: 0; }
9020
9158
  article > *:last-child { margin-bottom: 0; }
9021
9159
  h2, h3, h4, h5, h6 { margin: 1.7em 0 .65em; line-height: 1.25; letter-spacing: 0; color: var(--heading); overflow-wrap: anywhere; }
9022
9160
  h2 { padding-bottom: .4rem; border-bottom: 1px solid var(--border-soft); font-size: 1.5rem; }
9023
9161
  h3 { font-size: 1.2rem; }
9024
- p, li, td, th, blockquote { font-size: 15px; line-height: 1.8; overflow-wrap: anywhere; word-break: break-word; }
9162
+ p, li, td, th, blockquote { font-size: 15px; line-height: 1.75; overflow-wrap: anywhere; word-break: break-word; }
9025
9163
  p { margin: .75rem 0; color: var(--body); }
9026
- ul { margin: .65rem 0 1rem; padding-left: 1.35rem; }
9027
- li { margin: .28rem 0; color: var(--body); }
9164
+ ul, ol { margin: .75rem 0 1.1rem; padding-left: 1.55rem; }
9165
+ ol { padding-left: 1.8rem; }
9166
+ li { margin: .48rem 0; padding-left: .12rem; color: var(--body); }
9167
+ li::marker { color: var(--interactive); font-weight: 750; }
9028
9168
  li input { margin-right: .38rem; transform: translateY(1px); }
9029
- code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius: 6px; background: var(--code-bg); color: var(--code-inline); padding: .1rem .34rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: .92em; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
9169
+ code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius: 4px; background: color-mix(in srgb, var(--interactive) 8%, transparent); color: var(--code-inline); padding: .06rem .24rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: .9em; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
9030
9170
  pre { max-width: 100%; overflow: auto; border: 1px solid var(--border); border-radius: 10px; background: var(--code-block); padding: 16px; line-height: 1.65; }
9031
9171
  pre code { border: 0; background: transparent; color: var(--code-block-text); padding: 0; white-space: pre; overflow-wrap: normal; word-break: normal; }
9032
9172
  .planned-code { max-width: 100%; margin: 1rem 0 1.2rem; overflow: hidden; border: 1px solid var(--planned-border); border-radius: 10px; background: var(--code-block); }
@@ -9108,7 +9248,8 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
9108
9248
  .frontmatter { margin: 0 0 1.35rem; border: 1px solid var(--border-soft); border-radius: 10px; background: var(--panel-strong); padding: .75rem .9rem; }
9109
9249
  .frontmatter summary { cursor: pointer; color: var(--body); font-weight: 800; }
9110
9250
  .frontmatter table { min-width: 0; margin-top: .7rem; }
9111
- .frontmatter th { width: min(34%, 12rem); background: var(--panel-soft); color: var(--body); }
9251
+ .frontmatter-key-column { width: clamp(10rem, 22%, 16rem); }
9252
+ .frontmatter th { background: var(--panel-soft); color: var(--body); }
9112
9253
  .frontmatter-list { margin: 0; padding-left: 1.1rem; }
9113
9254
  .action-index { position: sticky; top: 10px; z-index: 4; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 1rem 0 1.25rem; border: 1px solid var(--border); border-radius: 12px; background: var(--panel-strong); box-shadow: 0 8px 22px var(--shadow); padding: 10px 12px; }
9114
9255
  .action-index__label { margin-right: 2px; color: var(--muted); font-size: 12px; font-weight: 800; }
@@ -12063,6 +12204,27 @@ export function startUiServer({
12063
12204
  json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
12064
12205
  return;
12065
12206
  }
12207
+ if (req.method === "GET" && url.pathname === "/api/prd-workflows") {
12208
+ if (!authUser?.userId) {
12209
+ json(res, 401, { error: "Unauthorized" });
12210
+ return;
12211
+ }
12212
+ try {
12213
+ const workflows = listPrdWorkflowCollaborationsForUser(userCtx.userId).map((record) => {
12214
+ const stateRoot = path.resolve(getAgentflowUserDataRoot(record.ownerId));
12215
+ const tapdId = String(record.tapdId || "").trim();
12216
+ const project = prdWorkflowReadProjectState(stateRoot, tapdId);
12217
+ const latestClient = prdWorkflowLatestClientSnapshot(stateRoot, stateRoot, tapdId);
12218
+ const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
12219
+ const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
12220
+ return prdWorkflowDashboardSummary(record, snapshot, userCtx);
12221
+ });
12222
+ json(res, 200, { ok: true, workflows });
12223
+ } catch (error) {
12224
+ json(res, 500, { error: (error && error.message) || String(error) });
12225
+ }
12226
+ return;
12227
+ }
12066
12228
  if (req.method === "GET" && url.pathname === "/api/prd-workflow/share") {
12067
12229
  const tapdId = String(url.searchParams.get("tapdId") || "").trim();
12068
12230
  const shareToken = String(url.searchParams.get("workflowShare") || "").trim();