@fieldwangai/agentflow 0.1.126 → 0.1.129
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/prd-workflow-collaboration.mjs +8 -0
- package/bin/lib/ui-server.mjs +275 -0
- package/builtin/web-ui/dist/assets/index-DQA8LAlU.css +1 -0
- package/builtin/web-ui/dist/assets/{index-DTXOt8j5.js → index-DgIcjRrA.js} +43 -43
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-BKLuP3ZS.css +0 -1
|
@@ -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);
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -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
|
}
|
|
@@ -8372,6 +8490,111 @@ function prdWorkflowReviewRenderPlannedCode(filePath, codeLines, startLine = 0,
|
|
|
8372
8490
|
</section>`;
|
|
8373
8491
|
}
|
|
8374
8492
|
|
|
8493
|
+
const PRD_WORKFLOW_VERIFICATION_FIELDS = {
|
|
8494
|
+
"入口": "entry",
|
|
8495
|
+
"前置条件": "precondition",
|
|
8496
|
+
"观察": "observation",
|
|
8497
|
+
"通过标准": "passCriteria",
|
|
8498
|
+
};
|
|
8499
|
+
|
|
8500
|
+
function prdWorkflowReviewVerificationBoundary(line) {
|
|
8501
|
+
const trimmed = String(line || "").trim();
|
|
8502
|
+
return /^#verification\s*$/i.test(trimmed)
|
|
8503
|
+
|| /^#change\s+(?:add|modify|remove|move)\s*$/i.test(trimmed)
|
|
8504
|
+
|| /^#file\s+.+$/i.test(trimmed)
|
|
8505
|
+
|| Boolean(prdWorkflowReviewActionLine(line));
|
|
8506
|
+
}
|
|
8507
|
+
|
|
8508
|
+
function prdWorkflowReviewVerificationContent(lines = []) {
|
|
8509
|
+
const source = Array.isArray(lines) ? [...lines] : [];
|
|
8510
|
+
while (source.length && !String(source[0] || "").trim()) source.shift();
|
|
8511
|
+
while (source.length && !String(source[source.length - 1] || "").trim()) source.pop();
|
|
8512
|
+
if (source.length <= 1) return source;
|
|
8513
|
+
return [source[0], ...prdWorkflowReviewDedentPlannedCode(source.slice(1))];
|
|
8514
|
+
}
|
|
8515
|
+
|
|
8516
|
+
function prdWorkflowReviewParseVerificationBlock(lines, startIndex) {
|
|
8517
|
+
if (!/^\s*#verification\s*$/i.test(String(lines[startIndex] || ""))) return null;
|
|
8518
|
+
const fields = {
|
|
8519
|
+
entry: [],
|
|
8520
|
+
precondition: [],
|
|
8521
|
+
observation: [],
|
|
8522
|
+
passCriteria: [],
|
|
8523
|
+
};
|
|
8524
|
+
let activeField = "";
|
|
8525
|
+
let boundaryIndex = lines.length;
|
|
8526
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
8527
|
+
const line = String(lines[i] || "");
|
|
8528
|
+
if (/^\s*#verificationend\s*$/i.test(line)) {
|
|
8529
|
+
return {
|
|
8530
|
+
verification: {
|
|
8531
|
+
type: "verification",
|
|
8532
|
+
entry: prdWorkflowReviewVerificationContent(fields.entry),
|
|
8533
|
+
precondition: prdWorkflowReviewVerificationContent(fields.precondition),
|
|
8534
|
+
observation: prdWorkflowReviewVerificationContent(fields.observation),
|
|
8535
|
+
passCriteria: prdWorkflowReviewVerificationContent(fields.passCriteria),
|
|
8536
|
+
},
|
|
8537
|
+
complete: true,
|
|
8538
|
+
endIndex: i,
|
|
8539
|
+
};
|
|
8540
|
+
}
|
|
8541
|
+
if (prdWorkflowReviewVerificationBoundary(line)) {
|
|
8542
|
+
boundaryIndex = i;
|
|
8543
|
+
break;
|
|
8544
|
+
}
|
|
8545
|
+
const field = line.match(/^\s*-\s*(入口|前置条件|观察|通过标准)\s*[::]\s*(.*)$/);
|
|
8546
|
+
if (field) {
|
|
8547
|
+
activeField = PRD_WORKFLOW_VERIFICATION_FIELDS[field[1]];
|
|
8548
|
+
if (field[2]) fields[activeField].push(field[2]);
|
|
8549
|
+
continue;
|
|
8550
|
+
}
|
|
8551
|
+
if (activeField) fields[activeField].push(line);
|
|
8552
|
+
}
|
|
8553
|
+
return {
|
|
8554
|
+
verification: null,
|
|
8555
|
+
complete: false,
|
|
8556
|
+
endIndex: boundaryIndex - 1,
|
|
8557
|
+
rawLines: lines.slice(startIndex + 1, boundaryIndex),
|
|
8558
|
+
};
|
|
8559
|
+
}
|
|
8560
|
+
|
|
8561
|
+
function prdWorkflowReviewRenderVerificationBlock(verification) {
|
|
8562
|
+
const fields = [
|
|
8563
|
+
["入口", verification.entry],
|
|
8564
|
+
["前置条件", verification.precondition],
|
|
8565
|
+
["观察", verification.observation],
|
|
8566
|
+
["通过标准", verification.passCriteria],
|
|
8567
|
+
];
|
|
8568
|
+
const rows = fields.map(([label, content]) => {
|
|
8569
|
+
const missing = !Array.isArray(content) || !content.some((line) => String(line || "").trim());
|
|
8570
|
+
const value = missing
|
|
8571
|
+
? '<span class="verification-block__missing">未填写</span>'
|
|
8572
|
+
: prdWorkflowReviewMarkdownLinesToHtml(content);
|
|
8573
|
+
return `<div class="verification-block__field${missing ? " is-missing" : ""}">
|
|
8574
|
+
<dt>${htmlEscapeAttribute(label)}</dt>
|
|
8575
|
+
<dd>${value}</dd>
|
|
8576
|
+
</div>`;
|
|
8577
|
+
}).join("");
|
|
8578
|
+
return `<section class="verification-block" data-solution-block="verification">
|
|
8579
|
+
<div class="verification-block__header">
|
|
8580
|
+
<span class="verification-block__badge">验证方案</span>
|
|
8581
|
+
<span>执行与通过标准</span>
|
|
8582
|
+
</div>
|
|
8583
|
+
<dl class="verification-block__fields">${rows}</dl>
|
|
8584
|
+
</section>`;
|
|
8585
|
+
}
|
|
8586
|
+
|
|
8587
|
+
function prdWorkflowReviewRenderIncompleteVerification(rawLines = []) {
|
|
8588
|
+
const body = prdWorkflowReviewMarkdownLinesToHtml(rawLines);
|
|
8589
|
+
return `<section class="verification-block is-incomplete" data-solution-block="verification">
|
|
8590
|
+
<div class="verification-block__header">
|
|
8591
|
+
<span class="verification-block__badge">验证方案</span>
|
|
8592
|
+
<strong>验证块格式不完整</strong>
|
|
8593
|
+
</div>
|
|
8594
|
+
${body ? `<div class="verification-block__fallback">${body}</div>` : ""}
|
|
8595
|
+
</section>`;
|
|
8596
|
+
}
|
|
8597
|
+
|
|
8375
8598
|
function prdWorkflowReviewParseChangeIntent(lines, startIndex) {
|
|
8376
8599
|
const start = String(lines[startIndex] || "")
|
|
8377
8600
|
.trim()
|
|
@@ -8654,6 +8877,17 @@ function prdWorkflowReviewMarkdownLinesToHtml(lines) {
|
|
|
8654
8877
|
flushBlocks();
|
|
8655
8878
|
continue;
|
|
8656
8879
|
}
|
|
8880
|
+
if (/^#verification\s*$/i.test(trimmed)) {
|
|
8881
|
+
const parsed = prdWorkflowReviewParseVerificationBlock(lines, i);
|
|
8882
|
+
if (parsed) {
|
|
8883
|
+
flushBlocks();
|
|
8884
|
+
html.push(parsed.complete
|
|
8885
|
+
? prdWorkflowReviewRenderVerificationBlock(parsed.verification)
|
|
8886
|
+
: prdWorkflowReviewRenderIncompleteVerification(parsed.rawLines));
|
|
8887
|
+
i = parsed.endIndex;
|
|
8888
|
+
continue;
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8657
8891
|
if (/^#change\s+/i.test(trimmed)) {
|
|
8658
8892
|
const parsed = prdWorkflowReviewParseChangeIntent(lines, i);
|
|
8659
8893
|
if (parsed) {
|
|
@@ -9109,6 +9343,24 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
9109
9343
|
.change-intent__proposal-line.is-code { display: grid; grid-template-columns: 2rem minmax(max-content, 1fr); margin: 0 -14px; padding: 0 14px; background: var(--change-code); }
|
|
9110
9344
|
.change-intent__proposal-mark { color: var(--change-add); font-weight: 900; text-align: center; user-select: none; }
|
|
9111
9345
|
.change-intent__proposal-text { white-space: pre; }
|
|
9346
|
+
.verification-block { max-width: 100%; margin: 1rem 0 1.2rem; overflow: hidden; border: 1px solid var(--change-border); border-radius: 12px; background: var(--panel-strong); }
|
|
9347
|
+
.verification-block__header { display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--change-border); background: var(--change-header); padding: 11px 14px; color: var(--heading); font-size: 13px; font-weight: 900; }
|
|
9348
|
+
.verification-block__badge { flex: 0 0 auto; border: 1px solid currentColor; border-radius: 999px; color: var(--change-move); padding: 4px 9px; font-size: 12px; line-height: 1.3; }
|
|
9349
|
+
.verification-block__fields { margin: 0; }
|
|
9350
|
+
.verification-block__field { display: grid; grid-template-columns: minmax(7rem, 9rem) minmax(0, 1fr); border-top: 1px solid var(--border-soft); }
|
|
9351
|
+
.verification-block__field:first-child { border-top: 0; }
|
|
9352
|
+
.verification-block__field dt { background: var(--change-context); color: var(--body); padding: 11px 14px; font-size: 13px; font-weight: 900; }
|
|
9353
|
+
.verification-block__field dd { min-width: 0; margin: 0; padding: 10px 14px 12px; color: var(--body); }
|
|
9354
|
+
.verification-block__field dd > *:first-child { margin-top: 0; }
|
|
9355
|
+
.verification-block__field dd > *:last-child { margin-bottom: 0; }
|
|
9356
|
+
.verification-block__field.is-missing { opacity: .72; }
|
|
9357
|
+
.verification-block__missing { color: var(--muted); font-size: 13px; font-style: italic; }
|
|
9358
|
+
.verification-block.is-incomplete { border-color: var(--pending-border); }
|
|
9359
|
+
.verification-block.is-incomplete .verification-block__header { border-bottom-color: var(--pending-border); background: var(--pending-bg); color: var(--pending-text); }
|
|
9360
|
+
.verification-block.is-incomplete .verification-block__badge { color: var(--pending-text); }
|
|
9361
|
+
.verification-block__fallback { padding: 10px 14px 12px; }
|
|
9362
|
+
.verification-block__fallback > *:first-child { margin-top: 0; }
|
|
9363
|
+
.verification-block__fallback > *:last-child { margin-bottom: 0; }
|
|
9112
9364
|
.syntax-comment { color: var(--syntax-comment); font-style: italic; }
|
|
9113
9365
|
.syntax-keyword { color: var(--syntax-keyword); font-weight: 700; }
|
|
9114
9366
|
.syntax-literal { color: var(--syntax-literal); font-weight: 650; }
|
|
@@ -9154,6 +9406,8 @@ export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
9154
9406
|
main { padding: 28px 14px 48px; }
|
|
9155
9407
|
.change-intent__header { align-items: flex-start; flex-direction: column; }
|
|
9156
9408
|
.change-intent__locator { width: 100%; }
|
|
9409
|
+
.verification-block__field { grid-template-columns: 1fr; }
|
|
9410
|
+
.verification-block__field dt { border-bottom: 1px solid var(--border-soft); padding-bottom: 8px; }
|
|
9157
9411
|
.change-intent__source-line, .planned-code__line { grid-template-columns: 3.25rem minmax(max-content, 1fr); }
|
|
9158
9412
|
header { display: block; }
|
|
9159
9413
|
.toolbar { justify-content: flex-start; margin-top: 14px; }
|
|
@@ -12086,6 +12340,27 @@ export function startUiServer({
|
|
|
12086
12340
|
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
12087
12341
|
return;
|
|
12088
12342
|
}
|
|
12343
|
+
if (req.method === "GET" && url.pathname === "/api/prd-workflows") {
|
|
12344
|
+
if (!authUser?.userId) {
|
|
12345
|
+
json(res, 401, { error: "Unauthorized" });
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
12348
|
+
try {
|
|
12349
|
+
const workflows = listPrdWorkflowCollaborationsForUser(userCtx.userId).map((record) => {
|
|
12350
|
+
const stateRoot = path.resolve(getAgentflowUserDataRoot(record.ownerId));
|
|
12351
|
+
const tapdId = String(record.tapdId || "").trim();
|
|
12352
|
+
const project = prdWorkflowReadProjectState(stateRoot, tapdId);
|
|
12353
|
+
const latestClient = prdWorkflowLatestClientSnapshot(stateRoot, stateRoot, tapdId);
|
|
12354
|
+
const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
|
|
12355
|
+
const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
|
|
12356
|
+
return prdWorkflowDashboardSummary(record, snapshot, userCtx);
|
|
12357
|
+
});
|
|
12358
|
+
json(res, 200, { ok: true, workflows });
|
|
12359
|
+
} catch (error) {
|
|
12360
|
+
json(res, 500, { error: (error && error.message) || String(error) });
|
|
12361
|
+
}
|
|
12362
|
+
return;
|
|
12363
|
+
}
|
|
12089
12364
|
if (req.method === "GET" && url.pathname === "/api/prd-workflow/share") {
|
|
12090
12365
|
const tapdId = String(url.searchParams.get("tapdId") || "").trim();
|
|
12091
12366
|
const shareToken = String(url.searchParams.get("workflowShare") || "").trim();
|