@fieldwangai/agentflow 0.1.134 → 0.1.136
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/ui-server.mjs +405 -40
- package/bin/lib/workflow-report.mjs +154 -9
- package/builtin/web-ui/dist/assets/index-HdswcJWY.js +565 -0
- package/builtin/web-ui/dist/assets/index-KIGufzQf.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-cli/SKILL.md +3 -70
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +24 -4
- package/skills/agentflow-cli/scripts/workflow-report-client.mjs +68 -0
- package/skills/agentflow-workflow-report/SKILL.md +100 -0
- package/skills/agentflow-workflow-report/agents/openai.yaml +4 -0
- package/skills/agentflow-workflow-report/references/protocol.md +558 -0
- package/builtin/web-ui/dist/assets/index-CqXKONpd.js +0 -350
- package/builtin/web-ui/dist/assets/index-QDDFbZ_T.css +0 -1
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -176,7 +176,9 @@ import {
|
|
|
176
176
|
} from "./teams.mjs";
|
|
177
177
|
import {
|
|
178
178
|
legacyOverallToGlobalState,
|
|
179
|
+
materializeWorkflowExtensions,
|
|
179
180
|
materializeWorkflowGlobalState,
|
|
181
|
+
materializeWorkflowProjections,
|
|
180
182
|
mergeWorkflowArtifactLists,
|
|
181
183
|
mergeWorkflowArtifacts,
|
|
182
184
|
mergeWorkflowGlobalState,
|
|
@@ -3464,6 +3466,23 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3464
3466
|
|| snapshot?.title
|
|
3465
3467
|
|| "",
|
|
3466
3468
|
).trim();
|
|
3469
|
+
const timeline = Array.isArray(snapshot?.projections?.timeline)
|
|
3470
|
+
? snapshot.projections.timeline
|
|
3471
|
+
.filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry))
|
|
3472
|
+
.map((entry) => ({
|
|
3473
|
+
key: String(entry.key || [entry.source, entry.kind, entry.id].filter(Boolean).join(":")),
|
|
3474
|
+
kind: String(entry.kind || ""),
|
|
3475
|
+
id: String(entry.id || ""),
|
|
3476
|
+
title: String(entry.title || entry.label || entry.id || ""),
|
|
3477
|
+
date: String(entry.date || ""),
|
|
3478
|
+
source: String(entry.source || ""),
|
|
3479
|
+
dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
|
|
3480
|
+
? entry.dimensions
|
|
3481
|
+
: {},
|
|
3482
|
+
order: Number.isFinite(Number(entry.order)) ? Number(entry.order) : 0,
|
|
3483
|
+
}))
|
|
3484
|
+
.filter((entry) => entry.kind && entry.id)
|
|
3485
|
+
: [];
|
|
3467
3486
|
const updatedAtTimestamp = Math.max(
|
|
3468
3487
|
prdWorkflowDashboardTimestamp(record),
|
|
3469
3488
|
prdWorkflowDashboardTimestamp(snapshot),
|
|
@@ -3481,6 +3500,7 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3481
3500
|
platforms,
|
|
3482
3501
|
actionCount: actions.length,
|
|
3483
3502
|
completedActionCount: completedActions,
|
|
3503
|
+
timeline,
|
|
3484
3504
|
latestAction: latestAction
|
|
3485
3505
|
? {
|
|
3486
3506
|
title: String(latestAction.title || latestAction.label || latestAction.action || latestAction.id || "").trim(),
|
|
@@ -3500,6 +3520,72 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3500
3520
|
};
|
|
3501
3521
|
}
|
|
3502
3522
|
|
|
3523
|
+
function prdWorkflowDashboardTimeline(workflows = []) {
|
|
3524
|
+
const buckets = new Map();
|
|
3525
|
+
const assignedWorkflowIds = new Set();
|
|
3526
|
+
const rows = [...(Array.isArray(workflows) ? workflows : [])].sort((left, right) => {
|
|
3527
|
+
const leftAt = Date.parse(String(left?.updatedAt || ""));
|
|
3528
|
+
const rightAt = Date.parse(String(right?.updatedAt || ""));
|
|
3529
|
+
if (Number.isFinite(leftAt) && Number.isFinite(rightAt)) return leftAt - rightAt;
|
|
3530
|
+
if (Number.isFinite(leftAt) !== Number.isFinite(rightAt)) return Number.isFinite(leftAt) ? 1 : -1;
|
|
3531
|
+
return String(left?.id || left?.tapdId || "").localeCompare(String(right?.id || right?.tapdId || ""));
|
|
3532
|
+
});
|
|
3533
|
+
for (const workflow of rows) {
|
|
3534
|
+
const workflowId = String(workflow?.id || workflow?.tapdId || "");
|
|
3535
|
+
const seen = new Set();
|
|
3536
|
+
for (const entry of Array.isArray(workflow?.timeline) ? workflow.timeline : []) {
|
|
3537
|
+
const key = String(entry?.key || [entry?.source, entry?.kind, entry?.id].filter(Boolean).join(":"));
|
|
3538
|
+
if (!key || seen.has(key)) continue;
|
|
3539
|
+
seen.add(key);
|
|
3540
|
+
assignedWorkflowIds.add(workflowId);
|
|
3541
|
+
const current = buckets.get(key) || {
|
|
3542
|
+
key,
|
|
3543
|
+
kind: String(entry.kind || ""),
|
|
3544
|
+
id: String(entry.id || ""),
|
|
3545
|
+
title: String(entry.title || entry.id || ""),
|
|
3546
|
+
date: String(entry.date || ""),
|
|
3547
|
+
source: String(entry.source || ""),
|
|
3548
|
+
dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
|
|
3549
|
+
? entry.dimensions
|
|
3550
|
+
: {},
|
|
3551
|
+
order: Number.isFinite(Number(entry.order)) ? Number(entry.order) : 0,
|
|
3552
|
+
workflowCount: 0,
|
|
3553
|
+
completedCount: 0,
|
|
3554
|
+
blockedCount: 0,
|
|
3555
|
+
workflowIds: [],
|
|
3556
|
+
};
|
|
3557
|
+
current.kind = String(entry.kind || current.kind);
|
|
3558
|
+
current.id = String(entry.id || current.id);
|
|
3559
|
+
current.title = String(entry.title || current.title);
|
|
3560
|
+
current.date = String(entry.date || current.date);
|
|
3561
|
+
current.source = String(entry.source || current.source);
|
|
3562
|
+
current.dimensions = entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
|
|
3563
|
+
? entry.dimensions
|
|
3564
|
+
: current.dimensions;
|
|
3565
|
+
current.order = Number.isFinite(Number(entry.order)) ? Number(entry.order) : current.order;
|
|
3566
|
+
current.workflowCount += 1;
|
|
3567
|
+
if (workflow?.state === "completed") current.completedCount += 1;
|
|
3568
|
+
if (workflow?.state === "blocked") current.blockedCount += 1;
|
|
3569
|
+
current.workflowIds.push(workflowId);
|
|
3570
|
+
buckets.set(key, current);
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
const timeline = Array.from(buckets.values()).sort((left, right) => {
|
|
3574
|
+
const leftAt = Date.parse(String(left.date || ""));
|
|
3575
|
+
const rightAt = Date.parse(String(right.date || ""));
|
|
3576
|
+
const leftValid = Number.isFinite(leftAt);
|
|
3577
|
+
const rightValid = Number.isFinite(rightAt);
|
|
3578
|
+
if (leftValid && rightValid && leftAt !== rightAt) return leftAt - rightAt;
|
|
3579
|
+
if (leftValid !== rightValid) return leftValid ? -1 : 1;
|
|
3580
|
+
if (left.order !== right.order) return left.order - right.order;
|
|
3581
|
+
return left.title.localeCompare(right.title, undefined, { numeric: true, sensitivity: "base" });
|
|
3582
|
+
});
|
|
3583
|
+
return {
|
|
3584
|
+
timeline,
|
|
3585
|
+
unassignedCount: rows.filter((workflow) => !assignedWorkflowIds.has(String(workflow?.id || workflow?.tapdId || ""))).length,
|
|
3586
|
+
};
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3503
3589
|
function workspaceConversationsPath(scopedRoot) {
|
|
3504
3590
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
|
|
3505
3591
|
}
|
|
@@ -7912,7 +7998,17 @@ function prdWorkflowSafeStateId(value) {
|
|
|
7912
7998
|
}
|
|
7913
7999
|
|
|
7914
8000
|
function prdWorkflowReviewIdFromRequest(tapdId, payload = {}, durability = "temporary") {
|
|
7915
|
-
if (durability === "temporary")
|
|
8001
|
+
if (durability === "temporary") {
|
|
8002
|
+
const idempotencyKey = String(payload.idempotencyKey || payload.idempotency_key || "").trim();
|
|
8003
|
+
if (!idempotencyKey) return `r-${crypto.randomBytes(5).toString("hex")}`;
|
|
8004
|
+
const source = String(payload.source || "agentflow-cli").trim().toLowerCase() || "agentflow-cli";
|
|
8005
|
+
const digest = crypto
|
|
8006
|
+
.createHash("sha256")
|
|
8007
|
+
.update(JSON.stringify({ tapdId: String(tapdId || ""), source, idempotencyKey }))
|
|
8008
|
+
.digest("hex")
|
|
8009
|
+
.slice(0, 12);
|
|
8010
|
+
return `r-${digest}`;
|
|
8011
|
+
}
|
|
7916
8012
|
const requested = String(payload.reviewId || payload.review_id || "").trim();
|
|
7917
8013
|
if (!requested) return `review_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
7918
8014
|
const safeRequested = prdWorkflowSafeStateId(requested);
|
|
@@ -10086,6 +10182,89 @@ function prdWorkflowSnapshotMetaFromReport(payload = {}, rawSnapshot = {}, req =
|
|
|
10086
10182
|
};
|
|
10087
10183
|
}
|
|
10088
10184
|
|
|
10185
|
+
function prdWorkflowStoreClientObservation({
|
|
10186
|
+
scopedRoot,
|
|
10187
|
+
tapdId,
|
|
10188
|
+
rawState,
|
|
10189
|
+
payload = {},
|
|
10190
|
+
req = null,
|
|
10191
|
+
userCtx = {},
|
|
10192
|
+
flowSource = "user",
|
|
10193
|
+
flowId = "",
|
|
10194
|
+
}) {
|
|
10195
|
+
const normalizedSnapshot = {
|
|
10196
|
+
...prdWorkflowSnapshotFromParsed(scopedRoot, tapdId, rawState, userCtx, { flowSource, flowId }),
|
|
10197
|
+
clientReportedAt: new Date().toISOString(),
|
|
10198
|
+
sources: {
|
|
10199
|
+
...(rawState.sources && typeof rawState.sources === "object" ? rawState.sources : {}),
|
|
10200
|
+
executionMode: "workflow-report",
|
|
10201
|
+
},
|
|
10202
|
+
};
|
|
10203
|
+
const reportMeta = prdWorkflowSnapshotMetaFromReport(payload, rawState, req, userCtx);
|
|
10204
|
+
const reportSource = {
|
|
10205
|
+
...(normalizedSnapshot.sources && typeof normalizedSnapshot.sources === "object" ? normalizedSnapshot.sources : {}),
|
|
10206
|
+
executionMode: "workflow-report",
|
|
10207
|
+
truth: "observation",
|
|
10208
|
+
authority: "client",
|
|
10209
|
+
persistence: "runtime",
|
|
10210
|
+
clientId: reportMeta.clientId,
|
|
10211
|
+
clientUserId: reportMeta.userId,
|
|
10212
|
+
clientReportedAt: reportMeta.reportedAt,
|
|
10213
|
+
clientObservedAt: reportMeta.observedAt,
|
|
10214
|
+
baseRevision: reportMeta.baseRevision,
|
|
10215
|
+
scope: reportMeta.scope,
|
|
10216
|
+
platform: reportMeta.platform,
|
|
10217
|
+
issueKey: reportMeta.issueKey,
|
|
10218
|
+
stageKey: reportMeta.stageKey,
|
|
10219
|
+
};
|
|
10220
|
+
const existingClientState = prdWorkflowReadClientState(scopedRoot, tapdId);
|
|
10221
|
+
const existingClientId = prdWorkflowSafeStateId(reportMeta.clientId || "anonymous");
|
|
10222
|
+
const previousClientSnapshot = existingClientState.clients?.[existingClientId]?.snapshot || null;
|
|
10223
|
+
const stampedSnapshot = prdWorkflowStampCurrentActionEntryTimes(
|
|
10224
|
+
scopedRoot,
|
|
10225
|
+
tapdId,
|
|
10226
|
+
normalizedSnapshot,
|
|
10227
|
+
existingClientState,
|
|
10228
|
+
reportMeta,
|
|
10229
|
+
);
|
|
10230
|
+
const storedObservationSnapshot = prdWorkflowStoredObservationSnapshot(stampedSnapshot, reportSource);
|
|
10231
|
+
const actionChanges = prdWorkflowSnapshotActionChanges(previousClientSnapshot || {}, storedObservationSnapshot);
|
|
10232
|
+
prdWorkflowWriteClientObservation(scopedRoot, tapdId, reportMeta, storedObservationSnapshot);
|
|
10233
|
+
prdWorkflowAppendAudit(scopedRoot, tapdId, {
|
|
10234
|
+
type: "workflow-report-observation-stored",
|
|
10235
|
+
flowSource,
|
|
10236
|
+
flowId,
|
|
10237
|
+
clientId: reportMeta.clientId,
|
|
10238
|
+
userId: reportMeta.userId,
|
|
10239
|
+
observedAt: reportMeta.observedAt,
|
|
10240
|
+
reportedAt: reportMeta.reportedAt,
|
|
10241
|
+
phase: String(storedObservationSnapshot?.phase || ""),
|
|
10242
|
+
pointer: String(storedObservationSnapshot?.pointer || ""),
|
|
10243
|
+
revision: String(storedObservationSnapshot?.revision || ""),
|
|
10244
|
+
actionCount: prdWorkflowSnapshotActionCount(storedObservationSnapshot),
|
|
10245
|
+
truth: "observation",
|
|
10246
|
+
authority: "client",
|
|
10247
|
+
persistence: "runtime",
|
|
10248
|
+
note: "producer observation accepted through the canonical Workflow Report endpoint",
|
|
10249
|
+
});
|
|
10250
|
+
for (const change of actionChanges) {
|
|
10251
|
+
prdWorkflowAppendAudit(scopedRoot, tapdId, {
|
|
10252
|
+
type: "snapshot-action-change",
|
|
10253
|
+
source: "workflow-report",
|
|
10254
|
+
clientId: reportMeta.clientId,
|
|
10255
|
+
userId: reportMeta.userId,
|
|
10256
|
+
observedAt: reportMeta.observedAt,
|
|
10257
|
+
reportedAt: reportMeta.reportedAt,
|
|
10258
|
+
revision: String(storedObservationSnapshot.revision || ""),
|
|
10259
|
+
previousRevision: String(previousClientSnapshot?.revision || ""),
|
|
10260
|
+
pointer: String(storedObservationSnapshot.pointer || ""),
|
|
10261
|
+
previousPointer: String(previousClientSnapshot?.pointer || ""),
|
|
10262
|
+
...change,
|
|
10263
|
+
});
|
|
10264
|
+
}
|
|
10265
|
+
return { reportMeta, storedObservationSnapshot, previousClientSnapshot };
|
|
10266
|
+
}
|
|
10267
|
+
|
|
10089
10268
|
function prdWorkflowSnapshotReportConflict(existingRecord, incomingSnapshot, meta) {
|
|
10090
10269
|
if (!existingRecord?.snapshot || meta.force) return null;
|
|
10091
10270
|
const current = existingRecord.snapshot;
|
|
@@ -10480,6 +10659,13 @@ function prdWorkflowRuntimeEventCanonicalAction(stage = "") {
|
|
|
10480
10659
|
: "";
|
|
10481
10660
|
}
|
|
10482
10661
|
|
|
10662
|
+
function prdWorkflowRuntimeEventProducer(event = {}) {
|
|
10663
|
+
return String(event.source || event.producer || "agentflow")
|
|
10664
|
+
.trim()
|
|
10665
|
+
.toLowerCase()
|
|
10666
|
+
.slice(0, 120) || "agentflow";
|
|
10667
|
+
}
|
|
10668
|
+
|
|
10483
10669
|
function prdWorkflowRuntimeOwnedArtifacts(values, stage = "") {
|
|
10484
10670
|
if (!Array.isArray(values)) return values;
|
|
10485
10671
|
const ownsOnlyChangedArtifact = /^(?:issue-plan|implementation|bugfix|integration):/.test(String(stage || ""));
|
|
@@ -10503,14 +10689,14 @@ function prdWorkflowRuntimeEventId(event = {}) {
|
|
|
10503
10689
|
const aggregateByStage = event.aggregateByStage !== false && event.aggregate_by_stage !== false;
|
|
10504
10690
|
if ((stage || action) && aggregateByStage) {
|
|
10505
10691
|
const issue = String(event.issueKey || event.issue_key || event.issue || "").trim();
|
|
10506
|
-
const key = [scope, issue, platform, stage || action].filter(Boolean).join(":");
|
|
10692
|
+
const key = [prdWorkflowRuntimeEventProducer(event), scope, issue, platform, stage || action].filter(Boolean).join(":");
|
|
10507
10693
|
return `stage_${prdWorkflowSafeStateId(key)}`;
|
|
10508
10694
|
}
|
|
10509
10695
|
const existing = String(event.id || event.eventId || event.event_id || "").trim();
|
|
10510
10696
|
if (existing) return existing.slice(0, 160);
|
|
10511
10697
|
if (stage || action) {
|
|
10512
10698
|
const issue = String(event.issueKey || event.issue_key || event.issue || "").trim();
|
|
10513
|
-
const key = [scope, issue, platform, stage || action].filter(Boolean).join(":");
|
|
10699
|
+
const key = [prdWorkflowRuntimeEventProducer(event), scope, issue, platform, stage || action].filter(Boolean).join(":");
|
|
10514
10700
|
return `stage_${prdWorkflowSafeStateId(key)}`;
|
|
10515
10701
|
}
|
|
10516
10702
|
return `evt_${Date.now().toString(36)}_${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -10574,8 +10760,13 @@ function prdWorkflowNormalizeRuntimeEvent(tapdId, event = {}) {
|
|
|
10574
10760
|
entry.stage = stage;
|
|
10575
10761
|
entry.stageKey = stage;
|
|
10576
10762
|
}
|
|
10577
|
-
|
|
10578
|
-
|
|
10763
|
+
const attachProducer = (values) => (Array.isArray(values) ? values.map((item) => (
|
|
10764
|
+
item && typeof item === "object" && !Array.isArray(item)
|
|
10765
|
+
? { ...item, producer: String(item.producer || source).trim().toLowerCase() || source }
|
|
10766
|
+
: item
|
|
10767
|
+
)) : values);
|
|
10768
|
+
entry.artifacts = attachProducer(prdWorkflowRuntimeOwnedArtifacts(entry.artifacts, stage));
|
|
10769
|
+
entry.links = attachProducer(prdWorkflowRuntimeOwnedArtifacts(entry.links, stage));
|
|
10579
10770
|
if (scope) entry.scope = scope;
|
|
10580
10771
|
if (platform) entry.platform = platform;
|
|
10581
10772
|
if (!entry.createdAt) entry.createdAt = event.startedAt || now;
|
|
@@ -10674,8 +10865,12 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
|
|
|
10674
10865
|
const current = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId);
|
|
10675
10866
|
const entry = prdWorkflowNormalizeRuntimeEvent(tapdId, event);
|
|
10676
10867
|
const entryIdem = String(entry.idempotencyKey || "").trim();
|
|
10868
|
+
const entryProducer = prdWorkflowRuntimeEventProducer(entry);
|
|
10869
|
+
const entryDedupeKey = prdWorkflowRuntimeEventDedupeKey(entry);
|
|
10677
10870
|
const index = current.events.findIndex((item) => {
|
|
10871
|
+
if (prdWorkflowRuntimeEventProducer(item) !== entryProducer) return false;
|
|
10678
10872
|
if (String(item?.id || "") === entry.id) return true;
|
|
10873
|
+
if (prdWorkflowRuntimeEventDedupeKey(item) === entryDedupeKey) return true;
|
|
10679
10874
|
if (!entryIdem) return false;
|
|
10680
10875
|
if (String(item?.idempotencyKey || "").trim() === entryIdem) return true;
|
|
10681
10876
|
return Array.isArray(item?.idempotencyHistory) && item.idempotencyHistory.includes(entryIdem);
|
|
@@ -10786,36 +10981,43 @@ function prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, event = {}) {
|
|
|
10786
10981
|
}
|
|
10787
10982
|
}
|
|
10788
10983
|
|
|
10789
|
-
function
|
|
10984
|
+
function prdWorkflowFindIdempotencyEvent(scopedRoot, tapdId, idempotencyKey, source = "", completedOnly = true) {
|
|
10790
10985
|
const key = String(idempotencyKey || "").trim();
|
|
10791
10986
|
if (!key) return null;
|
|
10987
|
+
const producer = String(source || "").trim().toLowerCase();
|
|
10792
10988
|
const events = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId).events;
|
|
10793
10989
|
return [...events].reverse().find((event) => (
|
|
10990
|
+
(!producer || prdWorkflowRuntimeEventProducer(event) === producer) &&
|
|
10794
10991
|
(String(event?.idempotencyKey || "") === key || (Array.isArray(event?.idempotencyHistory) && event.idempotencyHistory.includes(key))) &&
|
|
10795
|
-
["done", "success", "completed"].includes(String(event?.status || "").toLowerCase())
|
|
10992
|
+
(!completedOnly || ["done", "success", "completed"].includes(String(event?.status || "").toLowerCase()))
|
|
10796
10993
|
)) || null;
|
|
10797
10994
|
}
|
|
10798
10995
|
|
|
10996
|
+
function prdWorkflowFindCompletedIdempotencyEvent(scopedRoot, tapdId, idempotencyKey, source = "") {
|
|
10997
|
+
return prdWorkflowFindIdempotencyEvent(scopedRoot, tapdId, idempotencyKey, source, true);
|
|
10998
|
+
}
|
|
10999
|
+
|
|
10799
11000
|
function prdWorkflowRuntimeEventDedupeKey(event = {}, index = 0) {
|
|
11001
|
+
const producer = prdWorkflowRuntimeEventProducer(event);
|
|
10800
11002
|
const stage = prdWorkflowRuntimeEventCanonicalStage(event);
|
|
10801
11003
|
const aggregateByStage = event.aggregateByStage !== false && event.aggregate_by_stage !== false;
|
|
10802
11004
|
if (stage) {
|
|
10803
11005
|
const issue = event?.issueKey || event?.issue_key || event?.issue;
|
|
10804
11006
|
const platform = event?.platform;
|
|
10805
11007
|
if (aggregateByStage || issue || platform) {
|
|
10806
|
-
return ["stage", event?.scope, issue, platform, stage]
|
|
11008
|
+
return ["producer", producer, "stage", event?.scope, issue, platform, stage]
|
|
10807
11009
|
.map((value) => String(value || "").trim())
|
|
10808
11010
|
.join(":");
|
|
10809
11011
|
}
|
|
10810
11012
|
}
|
|
10811
11013
|
const id = String(event?.id || event?.eventId || event?.event_id || "").trim();
|
|
10812
|
-
if (id) return `id:${id}`;
|
|
11014
|
+
if (id) return `producer:${producer}:id:${id}`;
|
|
10813
11015
|
if (stage) {
|
|
10814
|
-
return ["stage", event?.scope, event?.issueKey || event?.issue_key || event?.issue, event?.platform, stage]
|
|
11016
|
+
return ["producer", producer, "stage", event?.scope, event?.issueKey || event?.issue_key || event?.issue, event?.platform, stage]
|
|
10815
11017
|
.map((value) => String(value || "").trim())
|
|
10816
11018
|
.join(":");
|
|
10817
11019
|
}
|
|
10818
|
-
return `idx:${index}`;
|
|
11020
|
+
return `producer:${producer}:idx:${index}`;
|
|
10819
11021
|
}
|
|
10820
11022
|
|
|
10821
11023
|
function prdWorkflowMergeRuntimeEventList(snapshotEvents = [], runtimeEvents = []) {
|
|
@@ -11083,13 +11285,25 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
|
|
|
11083
11285
|
const overall = prdWorkflowOverallFromEvents(tapdId, snapshot, runtimeEvents);
|
|
11084
11286
|
const globalState = prdWorkflowGlobalStateFromEvents(tapdId, snapshot, runtimeEvents);
|
|
11085
11287
|
const artifacts = mergeWorkflowArtifacts(snapshot?.artifacts, runtimeEvents);
|
|
11288
|
+
const projections = materializeWorkflowProjections(snapshot, runtimeEvents);
|
|
11289
|
+
const extensions = materializeWorkflowExtensions(snapshot, runtimeEvents);
|
|
11290
|
+
const prdFlowExtension = extensions["prd-flow"] && typeof extensions["prd-flow"] === "object"
|
|
11291
|
+
? extensions["prd-flow"]
|
|
11292
|
+
: {};
|
|
11293
|
+
const prdFlowExtensionView = {};
|
|
11294
|
+
for (const key of ["issues", "issueGroups", "issue_groups", "epics", "epicGroups", "epic_groups", "aiDocs", "ai_docs"]) {
|
|
11295
|
+
if (Object.prototype.hasOwnProperty.call(prdFlowExtension, key)) prdFlowExtensionView[key] = prdFlowExtension[key];
|
|
11296
|
+
}
|
|
11086
11297
|
return {
|
|
11087
11298
|
...snapshot,
|
|
11299
|
+
...prdFlowExtensionView,
|
|
11088
11300
|
workflow: globalState.workflow,
|
|
11089
11301
|
overall,
|
|
11090
11302
|
globalState,
|
|
11091
11303
|
artifacts,
|
|
11092
|
-
|
|
11304
|
+
projections,
|
|
11305
|
+
extensions,
|
|
11306
|
+
runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents, projections, extensions),
|
|
11093
11307
|
runtimeEvents,
|
|
11094
11308
|
events,
|
|
11095
11309
|
sources: {
|
|
@@ -12515,7 +12729,7 @@ export function startUiServer({
|
|
|
12515
12729
|
? getTeamById(requestedTeamId)
|
|
12516
12730
|
: getTeamForUser(userCtx.userId);
|
|
12517
12731
|
if (!team || team.status !== "active") {
|
|
12518
|
-
json(res, 200, { ok: true, view: "team", team: null, workflows: [] });
|
|
12732
|
+
json(res, 200, { ok: true, view: "team", team: null, workflows: [], timeline: [], unassignedCount: 0 });
|
|
12519
12733
|
return;
|
|
12520
12734
|
}
|
|
12521
12735
|
records = listPrdWorkflowCollaborationsForTeam(team.id);
|
|
@@ -12529,9 +12743,17 @@ export function startUiServer({
|
|
|
12529
12743
|
const latestClient = prdWorkflowLatestClientSnapshot(stateRoot, stateRoot, tapdId);
|
|
12530
12744
|
const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
|
|
12531
12745
|
const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
|
|
12532
|
-
|
|
12746
|
+
const materialized = prdWorkflowMergeRuntimeEvents(stateRoot, tapdId, snapshot);
|
|
12747
|
+
return prdWorkflowDashboardSummary(record, materialized, userCtx);
|
|
12748
|
+
});
|
|
12749
|
+
const dashboardTimeline = prdWorkflowDashboardTimeline(workflows);
|
|
12750
|
+
json(res, 200, {
|
|
12751
|
+
ok: true,
|
|
12752
|
+
view: view === "team" ? "team" : "personal",
|
|
12753
|
+
team: teamSummaryWithUsers(team),
|
|
12754
|
+
workflows,
|
|
12755
|
+
...dashboardTimeline,
|
|
12533
12756
|
});
|
|
12534
|
-
json(res, 200, { ok: true, view: view === "team" ? "team" : "personal", team: teamSummaryWithUsers(team), workflows });
|
|
12535
12757
|
} catch (error) {
|
|
12536
12758
|
json(res, 500, { error: (error && error.message) || String(error) });
|
|
12537
12759
|
}
|
|
@@ -12860,6 +13082,8 @@ export function startUiServer({
|
|
|
12860
13082
|
}
|
|
12861
13083
|
|
|
12862
13084
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/snapshot") {
|
|
13085
|
+
res.setHeader("Deprecation", "true");
|
|
13086
|
+
res.setHeader("Link", "</api/workflows/report>; rel=\"successor-version\"");
|
|
12863
13087
|
if (!authUser?.userId) {
|
|
12864
13088
|
json(res, 401, { error: "Authentication required" });
|
|
12865
13089
|
return;
|
|
@@ -13093,6 +13317,10 @@ export function startUiServer({
|
|
|
13093
13317
|
json(res, 200, {
|
|
13094
13318
|
ok: true,
|
|
13095
13319
|
snapshot: withDiagnostic,
|
|
13320
|
+
compatibility: {
|
|
13321
|
+
deprecatedEndpoint: "/api/prd-workflow/snapshot",
|
|
13322
|
+
replacement: "/api/workflows/report with observation.state",
|
|
13323
|
+
},
|
|
13096
13324
|
...(workflowShare ? { workflowShare, shareUrl: workflowShare.shortUrl || workflowShare.url } : {}),
|
|
13097
13325
|
});
|
|
13098
13326
|
} catch (e) {
|
|
@@ -13636,6 +13864,13 @@ export function startUiServer({
|
|
|
13636
13864
|
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
13637
13865
|
return;
|
|
13638
13866
|
}
|
|
13867
|
+
if (!workflowScope.collaboration) {
|
|
13868
|
+
const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
|
|
13869
|
+
if (ensured.error) {
|
|
13870
|
+
json(res, ensured.status || 400, { error: ensured.error });
|
|
13871
|
+
return;
|
|
13872
|
+
}
|
|
13873
|
+
}
|
|
13639
13874
|
const scopedRoot = workflowScope.stateRoot;
|
|
13640
13875
|
prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
|
|
13641
13876
|
const currentSnapshot = prdWorkflowMaterializeSnapshot(
|
|
@@ -13649,6 +13884,24 @@ export function startUiServer({
|
|
|
13649
13884
|
String(currentSnapshot.runtimeRevision || "").trim(),
|
|
13650
13885
|
String(currentSnapshot.revision || "").trim(),
|
|
13651
13886
|
].filter(Boolean));
|
|
13887
|
+
if (report.idempotencyKey) {
|
|
13888
|
+
const existing = prdWorkflowFindCompletedIdempotencyEvent(
|
|
13889
|
+
scopedRoot,
|
|
13890
|
+
tapdId,
|
|
13891
|
+
report.idempotencyKey,
|
|
13892
|
+
report.event.source,
|
|
13893
|
+
);
|
|
13894
|
+
if (existing) {
|
|
13895
|
+
json(res, 200, {
|
|
13896
|
+
ok: true,
|
|
13897
|
+
alreadyApplied: true,
|
|
13898
|
+
report,
|
|
13899
|
+
event: existing,
|
|
13900
|
+
snapshot: currentSnapshot,
|
|
13901
|
+
});
|
|
13902
|
+
return;
|
|
13903
|
+
}
|
|
13904
|
+
}
|
|
13652
13905
|
if (report.expectedRevision && acceptedRevisions.size && !acceptedRevisions.has(report.expectedRevision)) {
|
|
13653
13906
|
json(res, 409, {
|
|
13654
13907
|
error: "Workflow state changed; refresh before reporting",
|
|
@@ -13662,28 +13915,36 @@ export function startUiServer({
|
|
|
13662
13915
|
});
|
|
13663
13916
|
return;
|
|
13664
13917
|
}
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
|
|
13669
|
-
|
|
13670
|
-
|
|
13671
|
-
|
|
13672
|
-
|
|
13673
|
-
|
|
13674
|
-
|
|
13675
|
-
|
|
13676
|
-
|
|
13918
|
+
let observation = null;
|
|
13919
|
+
if (report.observation) {
|
|
13920
|
+
const observationPayload = {
|
|
13921
|
+
...payload,
|
|
13922
|
+
tapdId,
|
|
13923
|
+
clientId: report.observation.clientId || payload.clientId || payload.source || "workflow-reporter",
|
|
13924
|
+
observedAt: report.observation.observedAt || payload.observedAt || "",
|
|
13925
|
+
scope: report.observation.scope || payload.scope || "client",
|
|
13926
|
+
};
|
|
13927
|
+
observation = prdWorkflowStoreClientObservation({
|
|
13928
|
+
scopedRoot,
|
|
13929
|
+
tapdId,
|
|
13930
|
+
rawState: report.observation.state,
|
|
13931
|
+
payload: observationPayload,
|
|
13932
|
+
req,
|
|
13933
|
+
userCtx,
|
|
13934
|
+
flowSource,
|
|
13935
|
+
flowId,
|
|
13936
|
+
});
|
|
13677
13937
|
}
|
|
13678
|
-
const
|
|
13938
|
+
const shouldStoreEvent = report.hasRuntimeUpdate || Boolean(report.idempotencyKey);
|
|
13939
|
+
const event = shouldStoreEvent ? prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, {
|
|
13679
13940
|
...report.event,
|
|
13680
13941
|
tapdId,
|
|
13681
13942
|
actor: {
|
|
13682
13943
|
userId: String(userCtx.userId || ""),
|
|
13683
13944
|
username: String(authUser.username || userCtx.userId || ""),
|
|
13684
13945
|
},
|
|
13685
|
-
});
|
|
13686
|
-
if (!event) throw new Error("Failed to store workflow report");
|
|
13946
|
+
}) : null;
|
|
13947
|
+
if (shouldStoreEvent && !event) throw new Error("Failed to store workflow report");
|
|
13687
13948
|
const snapshot = prdWorkflowWithAgentflowTokenDiagnostic(
|
|
13688
13949
|
prdWorkflowMaterializeSnapshot(
|
|
13689
13950
|
workflowScope.executionRoot,
|
|
@@ -13696,9 +13957,20 @@ export function startUiServer({
|
|
|
13696
13957
|
);
|
|
13697
13958
|
prdWorkflowBroadcast(
|
|
13698
13959
|
prdWorkflowKey(userCtx, flowSource, flowId, tapdId),
|
|
13699
|
-
{ type: "workflow-report", tapdId, workflow: report.workflow, event, snapshot },
|
|
13960
|
+
{ type: "workflow-report", tapdId, workflow: report.workflow, event, observation: Boolean(observation), snapshot },
|
|
13700
13961
|
);
|
|
13701
|
-
json(res, 200, {
|
|
13962
|
+
json(res, 200, {
|
|
13963
|
+
ok: true,
|
|
13964
|
+
report,
|
|
13965
|
+
event,
|
|
13966
|
+
observation: observation ? {
|
|
13967
|
+
accepted: true,
|
|
13968
|
+
clientId: observation.reportMeta.clientId,
|
|
13969
|
+
observedAt: observation.reportMeta.observedAt,
|
|
13970
|
+
schema: report.observation.schema,
|
|
13971
|
+
} : null,
|
|
13972
|
+
snapshot,
|
|
13973
|
+
});
|
|
13702
13974
|
} catch (e) {
|
|
13703
13975
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
13704
13976
|
}
|
|
@@ -13706,6 +13978,8 @@ export function startUiServer({
|
|
|
13706
13978
|
}
|
|
13707
13979
|
|
|
13708
13980
|
if (req.method === "POST" && url.pathname === "/api/prd-workflow/event") {
|
|
13981
|
+
res.setHeader("Deprecation", "true");
|
|
13982
|
+
res.setHeader("Link", "</api/workflows/report>; rel=\"successor-version\"");
|
|
13709
13983
|
if (!authUser?.userId) {
|
|
13710
13984
|
json(res, 401, { error: "Authentication required" });
|
|
13711
13985
|
return;
|
|
@@ -13756,14 +14030,26 @@ export function startUiServer({
|
|
|
13756
14030
|
getSessionTokenFromRequest(req) || "",
|
|
13757
14031
|
);
|
|
13758
14032
|
prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "runtime-event", tapdId, event, snapshot });
|
|
13759
|
-
json(res, 200, {
|
|
14033
|
+
json(res, 200, {
|
|
14034
|
+
ok: true,
|
|
14035
|
+
event,
|
|
14036
|
+
snapshot,
|
|
14037
|
+
compatibility: {
|
|
14038
|
+
deprecatedEndpoint: "/api/prd-workflow/event",
|
|
14039
|
+
replacement: "/api/workflows/report with action/artifacts/extensions",
|
|
14040
|
+
},
|
|
14041
|
+
});
|
|
13760
14042
|
} catch (e) {
|
|
13761
14043
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
13762
14044
|
}
|
|
13763
14045
|
return;
|
|
13764
14046
|
}
|
|
13765
14047
|
|
|
13766
|
-
if (req.method === "POST" &&
|
|
14048
|
+
if (req.method === "POST" && (
|
|
14049
|
+
url.pathname === "/api/workflow-artifacts/publish" ||
|
|
14050
|
+
url.pathname === "/api/prd-workflow/review-link"
|
|
14051
|
+
)) {
|
|
14052
|
+
const legacyReviewEndpoint = url.pathname === "/api/prd-workflow/review-link";
|
|
13767
14053
|
if (!authUser?.userId) {
|
|
13768
14054
|
json(res, 401, { error: "Authentication required" });
|
|
13769
14055
|
return;
|
|
@@ -13776,11 +14062,16 @@ export function startUiServer({
|
|
|
13776
14062
|
return;
|
|
13777
14063
|
}
|
|
13778
14064
|
try {
|
|
13779
|
-
const
|
|
13780
|
-
if (
|
|
13781
|
-
json(res, 400, { error:
|
|
14065
|
+
const workflow = normalizeWorkflowReference(payload);
|
|
14066
|
+
if (workflow.error) {
|
|
14067
|
+
json(res, 400, { error: workflow.error });
|
|
14068
|
+
return;
|
|
14069
|
+
}
|
|
14070
|
+
if (workflow.namespace !== "tapd") {
|
|
14071
|
+
json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
|
|
13782
14072
|
return;
|
|
13783
14073
|
}
|
|
14074
|
+
const tapdId = workflow.id;
|
|
13784
14075
|
const flowId = String(payload.flowId || "").trim();
|
|
13785
14076
|
const flowSource = String(payload.flowSource || "user").trim() || "user";
|
|
13786
14077
|
const archived = payload.archived === true || payload.flowArchived === true;
|
|
@@ -13797,6 +14088,68 @@ export function startUiServer({
|
|
|
13797
14088
|
}
|
|
13798
14089
|
const scopedRoot = workflowScope.stateRoot;
|
|
13799
14090
|
prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
|
|
14091
|
+
const producer = String(payload.source || "agentflow-cli").trim().toLowerCase() || "agentflow-cli";
|
|
14092
|
+
if (!/^[a-z][a-z0-9._-]{0,119}$/.test(producer)) {
|
|
14093
|
+
json(res, 400, { error: "Invalid workflow report source" });
|
|
14094
|
+
return;
|
|
14095
|
+
}
|
|
14096
|
+
const idempotencyKey = String(
|
|
14097
|
+
payload.idempotencyKey || payload.idempotency_key || "",
|
|
14098
|
+
).trim();
|
|
14099
|
+
const currentSnapshot = prdWorkflowMaterializeSnapshot(
|
|
14100
|
+
workflowScope.executionRoot,
|
|
14101
|
+
scopedRoot,
|
|
14102
|
+
tapdId,
|
|
14103
|
+
userCtx,
|
|
14104
|
+
{ flowSource, flowId },
|
|
14105
|
+
);
|
|
14106
|
+
const expectedRevision = String(payload.expectedRevision || payload.expected_revision || "").trim();
|
|
14107
|
+
const acceptedRevisions = new Set([
|
|
14108
|
+
String(currentSnapshot.runtimeRevision || "").trim(),
|
|
14109
|
+
String(currentSnapshot.revision || "").trim(),
|
|
14110
|
+
].filter(Boolean));
|
|
14111
|
+
if (idempotencyKey) {
|
|
14112
|
+
const existing = prdWorkflowFindIdempotencyEvent(
|
|
14113
|
+
scopedRoot,
|
|
14114
|
+
tapdId,
|
|
14115
|
+
idempotencyKey,
|
|
14116
|
+
producer,
|
|
14117
|
+
false,
|
|
14118
|
+
);
|
|
14119
|
+
if (existing) {
|
|
14120
|
+
const artifact = Array.isArray(existing.artifacts) ? existing.artifacts[0] : null;
|
|
14121
|
+
json(res, 200, {
|
|
14122
|
+
ok: true,
|
|
14123
|
+
alreadyApplied: true,
|
|
14124
|
+
workflow,
|
|
14125
|
+
artifact,
|
|
14126
|
+
review: artifact ? {
|
|
14127
|
+
id: existing.reviewId || "",
|
|
14128
|
+
url: artifact.canonicalUrl || artifact.url || "",
|
|
14129
|
+
shortUrl: artifact.shortUrl || "",
|
|
14130
|
+
shortCode: existing.reviewShortCode || "",
|
|
14131
|
+
durability: existing.durability || artifact.durability || "",
|
|
14132
|
+
expiresAt: existing.expiresAt || artifact.expiresAt || "",
|
|
14133
|
+
} : null,
|
|
14134
|
+
event: existing,
|
|
14135
|
+
snapshot: currentSnapshot,
|
|
14136
|
+
});
|
|
14137
|
+
return;
|
|
14138
|
+
}
|
|
14139
|
+
}
|
|
14140
|
+
if (expectedRevision && acceptedRevisions.size && !acceptedRevisions.has(expectedRevision)) {
|
|
14141
|
+
json(res, 409, {
|
|
14142
|
+
error: "Workflow state changed; refresh before publishing",
|
|
14143
|
+
conflict: {
|
|
14144
|
+
type: "workflow-revision-conflict",
|
|
14145
|
+
expectedRevision,
|
|
14146
|
+
currentRevision: currentSnapshot.runtimeRevision || currentSnapshot.revision || "",
|
|
14147
|
+
workflow,
|
|
14148
|
+
},
|
|
14149
|
+
snapshot: currentSnapshot,
|
|
14150
|
+
});
|
|
14151
|
+
return;
|
|
14152
|
+
}
|
|
13800
14153
|
const review = prdWorkflowCreateReview(
|
|
13801
14154
|
scopedRoot,
|
|
13802
14155
|
tapdId,
|
|
@@ -13830,9 +14183,6 @@ export function startUiServer({
|
|
|
13830
14183
|
const reviewSource = review.source && typeof review.source === "object" && !Array.isArray(review.source)
|
|
13831
14184
|
? review.source
|
|
13832
14185
|
: { kind: durability === "durable" ? "ai-doc" : "local-draft", durability };
|
|
13833
|
-
const idempotencyKey = String(
|
|
13834
|
-
payload.idempotencyKey || payload.idempotency_key || "",
|
|
13835
|
-
).trim();
|
|
13836
14186
|
const artifact = {
|
|
13837
14187
|
key: artifactKey,
|
|
13838
14188
|
label: payload.artifactLabel || "Markdown Review",
|
|
@@ -13848,6 +14198,7 @@ export function startUiServer({
|
|
|
13848
14198
|
issueKey: payload.issueKey || payload.issue_key || "",
|
|
13849
14199
|
platform: payload.platform || "",
|
|
13850
14200
|
stageKey: reviewStageKey,
|
|
14201
|
+
producer,
|
|
13851
14202
|
...(reviewMrUrl ? { mrUrl: reviewMrUrl } : {}),
|
|
13852
14203
|
...(reviewMrIid ? { mrIid: reviewMrIid } : {}),
|
|
13853
14204
|
...(reviewCommitSha ? { commitSha: reviewCommitSha } : {}),
|
|
@@ -13855,6 +14206,7 @@ export function startUiServer({
|
|
|
13855
14206
|
const event = prdWorkflowAppendRuntimeEvent(scopedRoot, tapdId, {
|
|
13856
14207
|
id: `review-link:${artifactKey}`,
|
|
13857
14208
|
type: "review-link",
|
|
14209
|
+
source: producer,
|
|
13858
14210
|
auxiliary: true,
|
|
13859
14211
|
aggregateByStage: false,
|
|
13860
14212
|
conflictOnArtifact: false,
|
|
@@ -13886,6 +14238,7 @@ export function startUiServer({
|
|
|
13886
14238
|
persistence: "runtime",
|
|
13887
14239
|
durability,
|
|
13888
14240
|
source: reviewSource,
|
|
14241
|
+
producer,
|
|
13889
14242
|
expiresAt: review.expiresAt || "",
|
|
13890
14243
|
issueKey: artifact.issueKey,
|
|
13891
14244
|
platform: artifact.platform,
|
|
@@ -13902,8 +14255,14 @@ export function startUiServer({
|
|
|
13902
14255
|
getSessionTokenFromRequest(req) || "",
|
|
13903
14256
|
);
|
|
13904
14257
|
prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, tapdId), { type: "review-link", tapdId, event, snapshot });
|
|
14258
|
+
if (legacyReviewEndpoint) {
|
|
14259
|
+
res.setHeader("Deprecation", "true");
|
|
14260
|
+
res.setHeader("Link", "</api/workflow-artifacts/publish>; rel=\"successor-version\"");
|
|
14261
|
+
}
|
|
13905
14262
|
json(res, 200, {
|
|
13906
14263
|
ok: true,
|
|
14264
|
+
workflow,
|
|
14265
|
+
artifact,
|
|
13907
14266
|
review: {
|
|
13908
14267
|
...review,
|
|
13909
14268
|
url: reviewUrl,
|
|
@@ -13912,6 +14271,12 @@ export function startUiServer({
|
|
|
13912
14271
|
},
|
|
13913
14272
|
event,
|
|
13914
14273
|
snapshot,
|
|
14274
|
+
...(legacyReviewEndpoint ? {
|
|
14275
|
+
compatibility: {
|
|
14276
|
+
deprecatedEndpoint: "/api/prd-workflow/review-link",
|
|
14277
|
+
replacement: "/api/workflow-artifacts/publish",
|
|
14278
|
+
},
|
|
14279
|
+
} : {}),
|
|
13915
14280
|
});
|
|
13916
14281
|
} catch (e) {
|
|
13917
14282
|
json(res, 500, { error: (e && e.message) || String(e) });
|