@fieldwangai/agentflow 0.1.141 → 0.1.143

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.
@@ -188,6 +188,8 @@ import {
188
188
  mergeWorkflowArtifactLists,
189
189
  mergeWorkflowArtifacts,
190
190
  mergeWorkflowGlobalState,
191
+ isSafeWorkflowUrl,
192
+ normalizeWorkflowChecklistItemStatus,
191
193
  normalizeWorkflowReference,
192
194
  normalizeWorkflowReport,
193
195
  removeWorkflowGlobalStatePath,
@@ -3777,6 +3779,8 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}, projec
3777
3779
  id: String(entry.id || ""),
3778
3780
  title: String(entry.title || entry.label || entry.id || ""),
3779
3781
  date: String(entry.date || ""),
3782
+ startDate: String(entry.startDate || entry.start_date || entry.start || ""),
3783
+ endDate: String(entry.endDate || entry.end_date || entry.end || entry.date || ""),
3780
3784
  source: String(entry.source || ""),
3781
3785
  dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
3782
3786
  ? entry.dimensions
@@ -3910,6 +3914,8 @@ export function prdWorkflowDashboardTimeline(workflows = []) {
3910
3914
  id: identity || String(entry.id || ""),
3911
3915
  title: String(entry.title || entry.id || ""),
3912
3916
  date: String(entry.date || ""),
3917
+ startDate: String(entry.startDate || ""),
3918
+ endDate: String(entry.endDate || entry.date || ""),
3913
3919
  source: String(entry.source || ""),
3914
3920
  dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
3915
3921
  ? entry.dimensions
@@ -3924,6 +3930,8 @@ export function prdWorkflowDashboardTimeline(workflows = []) {
3924
3930
  current.kind = String(entry.kind || current.kind);
3925
3931
  current.title = String(entry.title || current.title);
3926
3932
  current.date = String(entry.date || current.date);
3933
+ current.startDate = String(entry.startDate || current.startDate);
3934
+ current.endDate = String(entry.endDate || entry.date || current.endDate);
3927
3935
  current.source = String(entry.source || current.source);
3928
3936
  current.dimensions = prdWorkflowDashboardMergeTimelineDimensions(current.dimensions, entry.dimensions);
3929
3937
  current.order = Number.isFinite(Number(entry.order)) ? Number(entry.order) : current.order;
@@ -3956,6 +3964,112 @@ export function prdWorkflowDashboardTimeline(workflows = []) {
3956
3964
  };
3957
3965
  }
3958
3966
 
3967
+ function prdWorkflowTimelineTimestamp(value, endOfDay = false) {
3968
+ const text = String(value || "").trim();
3969
+ if (!text) return Number.NaN;
3970
+ const timestamp = Date.parse(text);
3971
+ if (!Number.isFinite(timestamp)) return Number.NaN;
3972
+ return endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(text)
3973
+ ? timestamp + (24 * 60 * 60 * 1000) - 1
3974
+ : timestamp;
3975
+ }
3976
+
3977
+ export function prdWorkflowDefaultTimelineKey(timeline = [], now = Date.now()) {
3978
+ const rows = (Array.isArray(timeline) ? timeline : [])
3979
+ .map((entry) => ({
3980
+ entry,
3981
+ startAt: prdWorkflowTimelineTimestamp(entry?.startDate || entry?.start),
3982
+ endAt: prdWorkflowTimelineTimestamp(entry?.endDate || entry?.end || entry?.date, true),
3983
+ }))
3984
+ .filter(({ entry, endAt }) => String(entry?.key || "").trim() && Number.isFinite(endAt));
3985
+ const active = rows
3986
+ .filter(({ startAt, endAt }) => Number.isFinite(startAt) && startAt <= now && now <= endAt)
3987
+ .sort((left, right) => left.endAt - right.endAt);
3988
+ if (active.length > 0) return String(active[0].entry.key);
3989
+ const upcoming = rows
3990
+ .filter(({ endAt }) => endAt >= now)
3991
+ .sort((left, right) => left.endAt - right.endAt);
3992
+ if (upcoming.length > 0) return String(upcoming[0].entry.key);
3993
+ const latestPast = rows.sort((left, right) => right.endAt - left.endAt)[0];
3994
+ return latestPast ? String(latestPast.entry.key) : "all";
3995
+ }
3996
+
3997
+ function prdWorkflowDashboardSearchValues(workflow = {}) {
3998
+ return [
3999
+ workflow.tapdId,
4000
+ workflow.title,
4001
+ workflow.pointer,
4002
+ workflow.phase,
4003
+ workflow.ownerUsername,
4004
+ workflow.latestAction?.title,
4005
+ ...(Array.isArray(workflow.timeline) ? workflow.timeline : []).flatMap((entry) => [
4006
+ entry?.title,
4007
+ entry?.kind,
4008
+ entry?.date,
4009
+ ...Object.values(entry?.dimensions || {}).flatMap((value) => Array.isArray(value) ? value : [value]),
4010
+ ]),
4011
+ ];
4012
+ }
4013
+
4014
+ export function prdWorkflowDashboardPage(workflows = [], dashboardTimeline = {}, options = {}) {
4015
+ const allWorkflows = Array.isArray(workflows) ? workflows : [];
4016
+ const timeline = Array.isArray(dashboardTimeline?.timeline) ? dashboardTimeline.timeline : [];
4017
+ const unassignedCount = Number(dashboardTimeline?.unassignedCount || 0);
4018
+ const defaultTimelineKey = prdWorkflowDefaultTimelineKey(timeline, options.now);
4019
+ const requestedTimelineKey = String(options.timelineKey || "").trim();
4020
+ const matchedTimeline = timeline.find((entry) => (
4021
+ entry.key === requestedTimelineKey
4022
+ || (Array.isArray(entry.memberKeys) && entry.memberKeys.includes(requestedTimelineKey))
4023
+ ));
4024
+ const selectedTimelineKey = requestedTimelineKey === "all"
4025
+ ? "all"
4026
+ : requestedTimelineKey === "unassigned" && unassignedCount > 0
4027
+ ? "unassigned"
4028
+ : matchedTimeline?.key || defaultTimelineKey;
4029
+ const selectedTimeline = timeline.find((entry) => entry.key === selectedTimelineKey) || null;
4030
+ const selectedWorkflowIds = new Set(
4031
+ Array.isArray(selectedTimeline?.workflowIds)
4032
+ ? selectedTimeline.workflowIds.map((value) => String(value))
4033
+ : [],
4034
+ );
4035
+ const query = String(options.query || "").trim().toLowerCase();
4036
+ const scope = ["owned", "collaborating"].includes(options.scope) ? options.scope : "all";
4037
+ const state = ["active", "completed", "blocked"].includes(options.state) ? options.state : "all";
4038
+ const filtered = allWorkflows.filter((workflow) => {
4039
+ const workflowId = String(workflow?.id || workflow?.tapdId || "");
4040
+ if (selectedTimelineKey === "unassigned" && (workflow.timeline || []).length > 0) return false;
4041
+ if (selectedTimeline && !selectedWorkflowIds.has(workflowId)) return false;
4042
+ if (scope === "owned" && workflow.role !== "owner") return false;
4043
+ if (scope === "collaborating" && workflow.role === "owner") return false;
4044
+ if (state !== "all" && workflow.state !== state) return false;
4045
+ if (query && !prdWorkflowDashboardSearchValues(workflow).some(
4046
+ (value) => String(value || "").toLowerCase().includes(query),
4047
+ )) return false;
4048
+ return true;
4049
+ });
4050
+ const requestedPageSize = Number.parseInt(String(options.pageSize || "20"), 10);
4051
+ const pageSize = [20, 50, 100].includes(requestedPageSize) ? requestedPageSize : 20;
4052
+ const total = filtered.length;
4053
+ const totalPages = Math.max(1, Math.ceil(total / pageSize));
4054
+ const requestedPage = Number.parseInt(String(options.page || "1"), 10);
4055
+ const page = Math.min(totalPages, Math.max(1, Number.isFinite(requestedPage) ? requestedPage : 1));
4056
+ const start = (page - 1) * pageSize;
4057
+ return {
4058
+ workflows: filtered.slice(start, start + pageSize),
4059
+ availableCount: allWorkflows.length,
4060
+ selectedTimelineKey,
4061
+ defaultTimelineKey,
4062
+ pagination: {
4063
+ page,
4064
+ pageSize,
4065
+ total,
4066
+ totalPages,
4067
+ hasPrevious: page > 1,
4068
+ hasNext: page < totalPages,
4069
+ },
4070
+ };
4071
+ }
4072
+
3959
4073
  function workspaceConversationsPath(scopedRoot) {
3960
4074
  return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
3961
4075
  }
@@ -8185,6 +8299,10 @@ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capab
8185
8299
  return { error: "Admin permission required", status: 403 };
8186
8300
  }
8187
8301
  const adminOwner = adminOwnerId ? adminWorkspaceOwnerSummary(adminOwnerId) : null;
8302
+ const adminVersionRepair = capability === "admin-version-repair";
8303
+ if (adminVersionRepair && userCtx.isAdmin !== true) {
8304
+ return { error: "Admin permission required", status: 403 };
8305
+ }
8188
8306
  if (adminOwnerId && !adminOwner) {
8189
8307
  return { error: "Workspace owner not found", status: 404 };
8190
8308
  }
@@ -8200,19 +8318,24 @@ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capab
8200
8318
  ? getPrdWorkflowCollaborationForUser(tapdId, userCtx?.userId)
8201
8319
  : null;
8202
8320
  const existingCollaboration = tapdId ? getPrdWorkflowCollaborationByTapdId(tapdId) : null;
8203
- if (!adminOwner && !linkCollaboration && existingCollaboration && !memberCollaboration) {
8321
+ if (!adminOwner && !linkCollaboration && existingCollaboration && !memberCollaboration && !adminVersionRepair) {
8204
8322
  return { error: "PRD Workflow collaboration permission denied", status: 403 };
8205
8323
  }
8206
- const collaboration = adminOwner ? null : (linkCollaboration || memberCollaboration);
8324
+ if (adminVersionRepair && !existingCollaboration) {
8325
+ return { error: "PRD Workflow collaboration not found", status: 404 };
8326
+ }
8327
+ const collaboration = adminOwner ? null : (adminVersionRepair ? existingCollaboration : (linkCollaboration || memberCollaboration));
8207
8328
  const access = adminOwner
8208
8329
  ? { allowed: true, writable: false, role: "admin-viewer", via: "admin-review" }
8330
+ : adminVersionRepair
8331
+ ? { allowed: true, writable: true, role: "admin-version-repair", via: "admin-version-repair" }
8209
8332
  : linkCollaboration
8210
8333
  ? { allowed: true, writable: false, role: "viewer", via: "share-link" }
8211
8334
  : prdWorkflowCollaborationAccess(collaboration, userCtx?.userId);
8212
8335
  if (collaboration && !access.allowed) {
8213
8336
  return { error: "PRD Workflow collaboration permission denied", status: 403 };
8214
8337
  }
8215
- if (capability === "write" && (linkCollaboration || (collaboration && !access.writable))) {
8338
+ if ((capability === "write" || adminVersionRepair) && (linkCollaboration || (collaboration && !access.writable))) {
8216
8339
  return { error: "PRD Workflow collaboration edit permission denied", status: 403 };
8217
8340
  }
8218
8341
  const ownerId = String(adminOwner?.userId || collaboration?.ownerId || userCtx?.userId || "").trim();
@@ -8245,6 +8368,7 @@ function resolvePrdWorkflowScope(workspaceRoot, params = {}, userCtx = {}, capab
8245
8368
  shareToken,
8246
8369
  sharedByLink: Boolean(linkCollaboration),
8247
8370
  adminReadonly: Boolean(adminOwner),
8371
+ adminVersionRepair,
8248
8372
  flowId,
8249
8373
  flowSource,
8250
8374
  archived,
@@ -11552,6 +11676,79 @@ function prdWorkflowMergeProducerTimeline(report, currentSnapshot = {}) {
11552
11676
  };
11553
11677
  }
11554
11678
 
11679
+ function prdWorkflowAdminVersionRepairOperation(value = "", userCtx = {}) {
11680
+ const operation = String(value || "").trim().toLowerCase();
11681
+ if (!operation) return { requested: false };
11682
+ if (operation !== "repair-version-membership") {
11683
+ return { requested: true, status: 400, error: `Unsupported admin Workflow operation: ${operation}` };
11684
+ }
11685
+ if (userCtx.isAdmin !== true) {
11686
+ return { requested: true, status: 403, error: "Admin permission required" };
11687
+ }
11688
+ return { requested: true, operation };
11689
+ }
11690
+
11691
+ function prdWorkflowAdminVersionRepairIntent(payload = {}, report = {}, userCtx = {}) {
11692
+ const intent = prdWorkflowAdminVersionRepairOperation(
11693
+ payload.adminOperation || payload.admin_operation || payload.administrativeOperation || payload.administrative_operation || "",
11694
+ userCtx,
11695
+ );
11696
+ if (!intent.requested || intent.error) return intent;
11697
+ const forbiddenKeys = ["action", "artifacts", "observation", "globalState", "global_state", "extensions", "extension"]
11698
+ .filter((key) => Object.prototype.hasOwnProperty.call(payload, key));
11699
+ if (forbiddenKeys.length) {
11700
+ return {
11701
+ requested: true,
11702
+ status: 400,
11703
+ error: `Admin version repair may only update projections.timeline; remove: ${forbiddenKeys.join(", ")}`,
11704
+ };
11705
+ }
11706
+ const projections = payload.projections;
11707
+ if (!projections || typeof projections !== "object" || Array.isArray(projections) || !Array.isArray(projections.timeline)) {
11708
+ return { requested: true, status: 400, error: "Admin version repair requires projections.timeline" };
11709
+ }
11710
+ const extraProjectionKeys = Object.keys(projections).filter((key) => key !== "timeline");
11711
+ if (extraProjectionKeys.length) {
11712
+ return { requested: true, status: 400, error: "Admin version repair may only update projections.timeline" };
11713
+ }
11714
+ const nonVersionEntry = report?.projections?.timeline?.find((item) => String(item?.kind || "").trim().toLowerCase() !== "version");
11715
+ if (nonVersionEntry) {
11716
+ return { requested: true, status: 400, error: "Admin version repair only accepts timeline entries with kind=version" };
11717
+ }
11718
+ if (!report.idempotencyKey) {
11719
+ return { requested: true, status: 400, error: "Admin version repair requires idempotencyKey" };
11720
+ }
11721
+ if (!report.expectedRevision) {
11722
+ return { requested: true, status: 400, error: "Admin version repair requires expectedRevision" };
11723
+ }
11724
+ return intent;
11725
+ }
11726
+
11727
+ function prdWorkflowMergeAdminVersionTimeline(report, currentSnapshot = {}) {
11728
+ const source = prdWorkflowRuntimeEventProducer(report?.event || {});
11729
+ const current = Array.isArray(currentSnapshot?.projections?.timeline) ? currentSnapshot.projections.timeline : [];
11730
+ const incoming = Array.isArray(report?.projections?.timeline) ? report.projections.timeline : [];
11731
+ const retained = current.filter((item) => (
11732
+ prdWorkflowRuntimeEventProducer(item) !== source
11733
+ || String(item?.kind || "").trim().toLowerCase() !== "version"
11734
+ ));
11735
+ const timeline = [...retained, ...incoming];
11736
+ const administrativeRepair = {
11737
+ kind: "version-attribution",
11738
+ operation: "repair-version-membership",
11739
+ };
11740
+ return {
11741
+ ...report,
11742
+ projections: { ...report.projections, timeline },
11743
+ event: {
11744
+ ...report.event,
11745
+ projections: { ...report.event.projections, timeline },
11746
+ administrativeRepair,
11747
+ administrative_repair: administrativeRepair,
11748
+ },
11749
+ };
11750
+ }
11751
+
11555
11752
  function prdWorkflowRuntimeEventDedupeKey(event = {}, index = 0) {
11556
11753
  const producer = prdWorkflowRuntimeEventProducer(event);
11557
11754
  const operation = prdWorkflowRuntimeEventOperation(event);
@@ -11838,6 +12035,136 @@ function prdWorkflowGlobalStateFromEvents(tapdId, snapshot = {}, runtimeEvents =
11838
12035
  return state;
11839
12036
  }
11840
12037
 
12038
+ function prdWorkflowChecklistActionKey(action = {}) {
12039
+ return String(
12040
+ action?.actionModel?.key ||
12041
+ action?.key ||
12042
+ action?.actionKey ||
12043
+ action?.action_key ||
12044
+ action?.action ||
12045
+ action?.actionId ||
12046
+ action?.action_id ||
12047
+ action?.stageKey ||
12048
+ action?.stage_key ||
12049
+ "",
12050
+ ).trim();
12051
+ }
12052
+
12053
+ function prdWorkflowChecklistResourceKey(producer, actionKey, itemKey) {
12054
+ return `checklist:${String(producer || "").trim().toLowerCase()}:${String(actionKey || "").trim()}:${String(itemKey || "").trim()}`;
12055
+ }
12056
+
12057
+ function prdWorkflowChecklistStateEntries(runtimeEvents = []) {
12058
+ const states = new Map();
12059
+ for (const event of Array.isArray(runtimeEvents) ? runtimeEvents : []) {
12060
+ const state = event?.checklistState || event?.checklist_state;
12061
+ if (!state || typeof state !== "object" || Array.isArray(state)) continue;
12062
+ const producer = String(state.producer || state.source || "").trim().toLowerCase();
12063
+ const actionKey = String(state.actionKey || state.action_key || "").trim();
12064
+ const itemKey = String(state.itemKey || state.item_key || "").trim();
12065
+ if (!producer || !actionKey || !itemKey) continue;
12066
+ const resourceKey = prdWorkflowChecklistResourceKey(producer, actionKey, itemKey);
12067
+ const version = workflowSnapshotResourceVersions({ runtimeEvents: [event] })[resourceKey] || "absent";
12068
+ states.set(resourceKey, { ...state, producer, actionKey, itemKey, resourceKey, version });
12069
+ }
12070
+ return states;
12071
+ }
12072
+
12073
+ function prdWorkflowMaterializeChecklists(snapshot = {}, runtimeEvents = []) {
12074
+ const actionSources = new Map();
12075
+ for (const event of Array.isArray(runtimeEvents) ? runtimeEvents : []) {
12076
+ const actionKey = prdWorkflowChecklistActionKey(event);
12077
+ const producer = String(event?.source || event?.producer || "").trim().toLowerCase();
12078
+ if (!actionKey || !producer || (!event?.checklist && !event?.actionModel?.checklist)) continue;
12079
+ const existing = actionSources.get(actionKey);
12080
+ actionSources.set(actionKey, existing && existing !== producer ? "" : producer);
12081
+ }
12082
+ const stateEntries = prdWorkflowChecklistStateEntries(runtimeEvents);
12083
+ const terminalStatuses = new Set(["passed", "skipped"]);
12084
+ const decorate = (action) => {
12085
+ if (!action || typeof action !== "object" || Array.isArray(action)) return action;
12086
+ const definition = action.checklist || action.actionModel?.checklist;
12087
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) return action;
12088
+ const actionKey = prdWorkflowChecklistActionKey(action);
12089
+ const producer = String(
12090
+ definition.source || action.source || action.producer || action.actionModel?.source || actionSources.get(actionKey) || "",
12091
+ ).trim().toLowerCase();
12092
+ const items = (Array.isArray(definition.items) ? definition.items : []).map((item) => {
12093
+ const itemKey = String(item?.key || item?.id || "").trim();
12094
+ const resourceKey = producer && actionKey && itemKey
12095
+ ? prdWorkflowChecklistResourceKey(producer, actionKey, itemKey)
12096
+ : "";
12097
+ const stored = resourceKey ? stateEntries.get(resourceKey) : null;
12098
+ const state = stored || {
12099
+ producer,
12100
+ actionKey,
12101
+ itemKey,
12102
+ status: "pending",
12103
+ note: "",
12104
+ evidence: [],
12105
+ resourceKey,
12106
+ version: "absent",
12107
+ };
12108
+ return { ...item, state };
12109
+ });
12110
+ const required = items.filter((item) => item.required !== false);
12111
+ const completed = items.filter((item) => terminalStatuses.has(String(item?.state?.status || "pending"))).length;
12112
+ const requiredCompleted = required.filter((item) => terminalStatuses.has(String(item?.state?.status || "pending"))).length;
12113
+ const completionPolicy = String(definition.completionPolicy || definition.completion_policy || "all_required").trim().toLowerCase();
12114
+ const completionCandidates = required.length ? required : items;
12115
+ const ready = completionPolicy === "manual"
12116
+ ? false
12117
+ : completionPolicy === "any_required"
12118
+ ? completionCandidates.some((item) => terminalStatuses.has(String(item?.state?.status || "pending")))
12119
+ : completionCandidates.length > 0 && completionCandidates.every((item) => terminalStatuses.has(String(item?.state?.status || "pending")));
12120
+ const checklist = {
12121
+ ...definition,
12122
+ source: producer,
12123
+ items,
12124
+ progress: {
12125
+ total: items.length,
12126
+ completed,
12127
+ required: required.length,
12128
+ requiredCompleted,
12129
+ percent: items.length ? Math.round((completed / items.length) * 100) : 0,
12130
+ ready,
12131
+ },
12132
+ };
12133
+ return {
12134
+ ...action,
12135
+ checklist,
12136
+ ...(action.actionModel && typeof action.actionModel === "object" && !Array.isArray(action.actionModel)
12137
+ ? { actionModel: { ...action.actionModel, checklist } }
12138
+ : {}),
12139
+ };
12140
+ };
12141
+ const out = { ...snapshot };
12142
+ for (const key of ["actions", "workflowActions", "workflow_actions", "timeline", "history", "events", "runtimeEvents", "runtime_events"]) {
12143
+ if (Array.isArray(snapshot?.[key])) out[key] = snapshot[key].map(decorate);
12144
+ }
12145
+ out.checklistStates = [...stateEntries.values()];
12146
+ return out;
12147
+ }
12148
+
12149
+ function prdWorkflowFindChecklistAction(snapshot = {}, producer = "", actionKey = "") {
12150
+ const wantedProducer = String(producer || "").trim().toLowerCase();
12151
+ const wantedActionKey = String(actionKey || "").trim();
12152
+ const matches = [];
12153
+ for (const key of ["actions", "workflowActions", "workflow_actions", "timeline", "history", "events", "runtimeEvents", "runtime_events"]) {
12154
+ for (const action of Array.isArray(snapshot?.[key]) ? snapshot[key] : []) {
12155
+ if (!action || typeof action !== "object" || Array.isArray(action)) continue;
12156
+ const checklist = action.checklist || action.actionModel?.checklist;
12157
+ if (!checklist || typeof checklist !== "object" || Array.isArray(checklist)) continue;
12158
+ const resolvedActionKey = prdWorkflowChecklistActionKey(action);
12159
+ const resolvedProducer = String(checklist.source || action.source || action.producer || "").trim().toLowerCase();
12160
+ if (resolvedActionKey !== wantedActionKey) continue;
12161
+ if (wantedProducer && resolvedProducer !== wantedProducer) continue;
12162
+ matches.push({ ...action, checklist, source: resolvedProducer || wantedProducer });
12163
+ }
12164
+ }
12165
+ return matches.at(-1) || null;
12166
+ }
12167
+
11841
12168
  function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11842
12169
  const runtime = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId);
11843
12170
  const runtimeEvents = runtime.events;
@@ -11854,7 +12181,7 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11854
12181
  for (const key of ["issues", "issueGroups", "issue_groups", "epics", "epicGroups", "epic_groups", "aiDocs", "ai_docs"]) {
11855
12182
  if (Object.prototype.hasOwnProperty.call(prdFlowExtension, key)) prdFlowExtensionView[key] = prdFlowExtension[key];
11856
12183
  }
11857
- const materialized = {
12184
+ let materialized = {
11858
12185
  ...snapshot,
11859
12186
  ...prdFlowExtensionView,
11860
12187
  workflow: globalState.workflow,
@@ -11871,6 +12198,7 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11871
12198
  runtimeEventsUpdatedAt: runtime.updatedAt || "",
11872
12199
  },
11873
12200
  };
12201
+ materialized = prdWorkflowMaterializeChecklists(materialized, runtimeEvents);
11874
12202
  materialized.resourceVersions = workflowSnapshotResourceVersions(materialized);
11875
12203
  return materialized;
11876
12204
  }
@@ -13380,7 +13708,18 @@ export function startUiServer({
13380
13708
  ? getTeamById(requestedTeamId)
13381
13709
  : getTeamForUser(userCtx.userId);
13382
13710
  if (!team || team.status !== "active") {
13383
- json(res, 200, { ok: true, view: "team", team: null, workflows: [], timeline: [], unassignedCount: 0 });
13711
+ json(res, 200, {
13712
+ ok: true,
13713
+ view: "team",
13714
+ team: null,
13715
+ workflows: [],
13716
+ timeline: [],
13717
+ unassignedCount: 0,
13718
+ availableCount: 0,
13719
+ selectedTimelineKey: "all",
13720
+ defaultTimelineKey: "all",
13721
+ pagination: { page: 1, pageSize: 20, total: 0, totalPages: 1, hasPrevious: false, hasNext: false },
13722
+ });
13384
13723
  return;
13385
13724
  }
13386
13725
  records = listPrdWorkflowCollaborationsForTeam(team.id);
@@ -13400,11 +13739,19 @@ export function startUiServer({
13400
13739
  return prdWorkflowDashboardSummary(record, materialized, userCtx, projectBindings);
13401
13740
  });
13402
13741
  const dashboardTimeline = prdWorkflowDashboardTimeline(workflows);
13742
+ const dashboardPage = prdWorkflowDashboardPage(workflows, dashboardTimeline, {
13743
+ timelineKey: url.searchParams.has("timelineKey") ? url.searchParams.get("timelineKey") : "",
13744
+ query: url.searchParams.get("q"),
13745
+ scope: url.searchParams.get("scope"),
13746
+ state: url.searchParams.get("state"),
13747
+ page: url.searchParams.get("page"),
13748
+ pageSize: url.searchParams.get("pageSize"),
13749
+ });
13403
13750
  json(res, 200, {
13404
13751
  ok: true,
13405
13752
  view: view === "team" ? "team" : "personal",
13406
13753
  team: teamSummaryWithUsers(team),
13407
- workflows,
13754
+ ...dashboardPage,
13408
13755
  ...dashboardTimeline,
13409
13756
  });
13410
13757
  } catch (error) {
@@ -14016,6 +14363,14 @@ export function startUiServer({
14016
14363
  }
14017
14364
  const flowId = String(url.searchParams.get("flowId") || "").trim();
14018
14365
  const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
14366
+ const adminVersionRepair = prdWorkflowAdminVersionRepairOperation(
14367
+ url.searchParams.get("adminOperation") || url.searchParams.get("admin_operation") || "",
14368
+ userCtx,
14369
+ );
14370
+ if (adminVersionRepair.error) {
14371
+ json(res, adminVersionRepair.status || 400, { error: adminVersionRepair.error });
14372
+ return;
14373
+ }
14019
14374
  const workflowScope = resolvePrdWorkflowScope(root, {
14020
14375
  tapdId: workflow.id,
14021
14376
  flowId,
@@ -14023,14 +14378,15 @@ export function startUiServer({
14023
14378
  archived: url.searchParams.get("archived") === "1",
14024
14379
  workspaceId: url.searchParams.get("workspaceId") || "",
14025
14380
  workflowShare: url.searchParams.get("workflowShare") || "",
14026
- }, userCtx);
14381
+ }, userCtx, adminVersionRepair.requested ? "admin-version-repair" : "read");
14027
14382
  if (workflowScope.error) {
14028
14383
  json(res, workflowScope.status || 400, { error: workflowScope.error });
14029
14384
  return;
14030
14385
  }
14031
14386
  const scopedRoot = workflowScope.stateRoot;
14032
14387
  prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, workflow.id);
14033
- const runtimeOnly = url.searchParams.get("runtimeOnly") === "1" ||
14388
+ const runtimeOnly = adminVersionRepair.requested ||
14389
+ url.searchParams.get("runtimeOnly") === "1" ||
14034
14390
  url.searchParams.get("runtime_only") === "1" ||
14035
14391
  url.searchParams.get("cached") === "1";
14036
14392
  const baseSnapshot = runtimeOnly
@@ -14046,6 +14402,276 @@ export function startUiServer({
14046
14402
  }
14047
14403
  return;
14048
14404
  }
14405
+ if (req.method === "GET" && url.pathname === "/api/workflows/checklist") {
14406
+ try {
14407
+ const workflow = normalizeWorkflowReference({ workflow: url.searchParams.get("workflow") || "" });
14408
+ if (workflow.error) {
14409
+ json(res, 400, { error: workflow.error });
14410
+ return;
14411
+ }
14412
+ if (workflow.namespace !== "tapd") {
14413
+ json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
14414
+ return;
14415
+ }
14416
+ const source = String(url.searchParams.get("source") || "").trim().toLowerCase();
14417
+ const actionKey = String(url.searchParams.get("actionKey") || url.searchParams.get("action_key") || "").trim();
14418
+ if (!/^[a-z][a-z0-9._-]{0,119}$/.test(source)) {
14419
+ json(res, 400, { error: "Invalid checklist source" });
14420
+ return;
14421
+ }
14422
+ if (!actionKey || actionKey.length > 240 || /[\0\r\n]/.test(actionKey)) {
14423
+ json(res, 400, { error: "Invalid checklist actionKey" });
14424
+ return;
14425
+ }
14426
+ const flowId = String(url.searchParams.get("flowId") || "").trim();
14427
+ const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
14428
+ const workflowScope = resolvePrdWorkflowScope(root, {
14429
+ tapdId: workflow.id,
14430
+ flowId,
14431
+ flowSource,
14432
+ archived: url.searchParams.get("archived") === "1",
14433
+ workspaceId: url.searchParams.get("workspaceId") || "",
14434
+ workflowShare: url.searchParams.get("workflowShare") || "",
14435
+ }, userCtx, "read");
14436
+ if (workflowScope.error) {
14437
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
14438
+ return;
14439
+ }
14440
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, workflowScope.stateRoot, workflow.id);
14441
+ const snapshot = prdWorkflowMaterializeSnapshot(
14442
+ workflowScope.executionRoot,
14443
+ workflowScope.stateRoot,
14444
+ workflow.id,
14445
+ userCtx,
14446
+ { flowSource, flowId },
14447
+ );
14448
+ const action = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14449
+ if (!action) {
14450
+ json(res, 404, { error: "Workflow Action checklist not found" });
14451
+ return;
14452
+ }
14453
+ const access = workflowScope.collaborationAccess || {};
14454
+ const canWrite = Boolean(authUser?.userId) && !workflowScope.sharedByLink && !workflowScope.adminReadonly && (
14455
+ workflowScope.collaboration ? access.writable === true : true
14456
+ );
14457
+ json(res, 200, {
14458
+ ok: true,
14459
+ workflow,
14460
+ action: {
14461
+ key: actionKey,
14462
+ source,
14463
+ title: String(action.title || action.label || actionKey),
14464
+ status: String(action.status || "pending"),
14465
+ checklist: action.checklist,
14466
+ },
14467
+ canWrite,
14468
+ });
14469
+ } catch (error) {
14470
+ json(res, 500, { error: (error && error.message) || String(error) });
14471
+ }
14472
+ return;
14473
+ }
14474
+ if (req.method === "PATCH" && url.pathname === "/api/workflows/checklist") {
14475
+ if (!authUser?.userId) {
14476
+ json(res, 401, { error: "Authentication required" });
14477
+ return;
14478
+ }
14479
+ let payload;
14480
+ try {
14481
+ payload = JSON.parse(await readBody(req, 1024 * 1024));
14482
+ } catch (error) {
14483
+ json(res, error?.status === 413 ? 413 : 400, { error: error?.status === 413 ? error.message : "Invalid JSON body" });
14484
+ return;
14485
+ }
14486
+ let releaseWorkflowWriteLock = null;
14487
+ try {
14488
+ const workflow = normalizeWorkflowReference(payload);
14489
+ if (workflow.error) {
14490
+ json(res, 400, { error: workflow.error });
14491
+ return;
14492
+ }
14493
+ if (workflow.namespace !== "tapd") {
14494
+ json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
14495
+ return;
14496
+ }
14497
+ const source = String(payload.source || "").trim().toLowerCase();
14498
+ const actionKey = String(payload.actionKey || payload.action_key || "").trim();
14499
+ const itemKey = String(payload.itemKey || payload.item_key || "").trim();
14500
+ if (!/^[a-z][a-z0-9._-]{0,119}$/.test(source)) {
14501
+ json(res, 400, { error: "Invalid checklist source" });
14502
+ return;
14503
+ }
14504
+ if (!actionKey || actionKey.length > 240 || /[\0\r\n]/.test(actionKey)) {
14505
+ json(res, 400, { error: "Invalid checklist actionKey" });
14506
+ return;
14507
+ }
14508
+ if (!itemKey || itemKey.length > 240 || /[\0\r\n]/.test(itemKey)) {
14509
+ json(res, 400, { error: "Invalid checklist itemKey" });
14510
+ return;
14511
+ }
14512
+ const rawStatus = String(payload.status || "pending").trim().toLowerCase();
14513
+ if (!["pending", "passed", "failed", "blocked", "skipped", "done", "complete", "completed", "success", "error", "cancelled", "canceled"].includes(rawStatus)) {
14514
+ json(res, 400, { error: `Invalid checklist item status: ${rawStatus}` });
14515
+ return;
14516
+ }
14517
+ const status = normalizeWorkflowChecklistItemStatus(rawStatus);
14518
+ const note = String(payload.note || "").trim();
14519
+ if (note.length > 4000) {
14520
+ json(res, 400, { error: "Checklist note exceeds 4000 characters" });
14521
+ return;
14522
+ }
14523
+ const rawEvidence = Array.isArray(payload.evidence) ? payload.evidence : [];
14524
+ if (rawEvidence.length > 20) {
14525
+ json(res, 400, { error: "Checklist evidence supports at most 20 entries" });
14526
+ return;
14527
+ }
14528
+ const evidence = [];
14529
+ for (let index = 0; index < rawEvidence.length; index += 1) {
14530
+ const item = rawEvidence[index];
14531
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
14532
+ json(res, 400, { error: `evidence[${index}] must be an object` });
14533
+ return;
14534
+ }
14535
+ const evidenceUrl = String(item.url || item.href || "").trim();
14536
+ if (!evidenceUrl || evidenceUrl.length > 4000 || !isSafeWorkflowUrl(evidenceUrl)) {
14537
+ json(res, 400, { error: `evidence[${index}].url must use http, https, or an absolute application path` });
14538
+ return;
14539
+ }
14540
+ evidence.push({
14541
+ title: String(item.title || item.label || `证据 ${index + 1}`).trim().slice(0, 500),
14542
+ url: evidenceUrl,
14543
+ });
14544
+ }
14545
+ const expectedVersion = String(payload.expectedVersion || payload.expected_version || "").trim();
14546
+ if (!expectedVersion) {
14547
+ json(res, 400, { error: "Checklist update requires expectedVersion" });
14548
+ return;
14549
+ }
14550
+ const flowId = String(payload.flowId || payload.flow_id || "").trim();
14551
+ const flowSource = String(payload.flowSource || payload.flow_source || "user").trim() || "user";
14552
+ const workflowScope = resolvePrdWorkflowScope(root, {
14553
+ ...payload,
14554
+ tapdId: workflow.id,
14555
+ flowId,
14556
+ flowSource,
14557
+ }, userCtx, "write");
14558
+ if (workflowScope.error) {
14559
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
14560
+ return;
14561
+ }
14562
+ if (!workflowScope.collaboration) {
14563
+ const ensured = ensurePrdWorkflowCollaboration({ tapdId: workflow.id, userId: userCtx.userId });
14564
+ if (ensured.error) {
14565
+ json(res, ensured.status || 400, { error: ensured.error });
14566
+ return;
14567
+ }
14568
+ }
14569
+ const scopedRoot = workflowScope.stateRoot;
14570
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, workflow.id);
14571
+ releaseWorkflowWriteLock = await prdWorkflowAcquireWriteLock(`${scopedRoot}\t${workflow.id}`);
14572
+ let snapshot = prdWorkflowMaterializeSnapshot(
14573
+ workflowScope.executionRoot,
14574
+ scopedRoot,
14575
+ workflow.id,
14576
+ userCtx,
14577
+ { flowSource, flowId },
14578
+ );
14579
+ const idempotencyKey = String(payload.idempotencyKey || payload.idempotency_key || "").trim().slice(0, 500);
14580
+ if (idempotencyKey) {
14581
+ const existing = prdWorkflowFindIdempotencyEvent(scopedRoot, workflow.id, idempotencyKey, "agentflow-checklist", false, "checklist.update");
14582
+ if (existing) {
14583
+ json(res, 200, { ok: true, alreadyApplied: true, workflow, checklistState: existing.checklistState, snapshot });
14584
+ return;
14585
+ }
14586
+ }
14587
+ const action = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14588
+ const checklistItem = action?.checklist?.items?.find((item) => String(item?.key || "") === itemKey);
14589
+ if (!action || !checklistItem) {
14590
+ json(res, 404, { error: "Workflow Action checklist item not found" });
14591
+ return;
14592
+ }
14593
+ if (status === "passed" && checklistItem.evidenceRequired === true && evidence.length === 0) {
14594
+ json(res, 400, { error: "Checklist item requires evidence before it can pass" });
14595
+ return;
14596
+ }
14597
+ const resourceKey = prdWorkflowChecklistResourceKey(source, actionKey, itemKey);
14598
+ const currentVersion = String(snapshot.resourceVersions?.[resourceKey] || "absent");
14599
+ if (expectedVersion !== currentVersion) {
14600
+ json(res, 409, {
14601
+ error: "Checklist item changed; refresh it before saving",
14602
+ conflict: { type: "workflow-resource-conflict", conflicts: [{ resourceKey, expectedVersion, currentVersion }], workflow },
14603
+ snapshot,
14604
+ });
14605
+ return;
14606
+ }
14607
+ const now = new Date().toISOString();
14608
+ const checklistState = {
14609
+ producer: source,
14610
+ actionKey,
14611
+ itemKey,
14612
+ status,
14613
+ note,
14614
+ evidence,
14615
+ updatedAt: now,
14616
+ updatedBy: {
14617
+ userId: String(userCtx.userId || ""),
14618
+ username: String(authUser.username || userCtx.userId || ""),
14619
+ },
14620
+ };
14621
+ const event = prdWorkflowAppendRuntimeEvent(scopedRoot, workflow.id, {
14622
+ id: `checklist_state_${prdWorkflowSafeStateId([source, actionKey, itemKey].join(":"))}`,
14623
+ type: "workflow-checklist-update",
14624
+ operation: "checklist.update",
14625
+ source: "agentflow-checklist",
14626
+ auxiliary: true,
14627
+ aggregateByStage: false,
14628
+ status: "done",
14629
+ checklistState,
14630
+ ...(idempotencyKey ? { idempotencyKey } : {}),
14631
+ });
14632
+ if (!event) throw new Error("Failed to store checklist state");
14633
+ snapshot = prdWorkflowMaterializeSnapshot(
14634
+ workflowScope.executionRoot,
14635
+ scopedRoot,
14636
+ workflow.id,
14637
+ userCtx,
14638
+ { flowSource, flowId },
14639
+ );
14640
+ const updatedAction = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14641
+ const updatedItem = updatedAction?.checklist?.items?.find((item) => String(item?.key || "") === itemKey);
14642
+ prdWorkflowAppendAudit(scopedRoot, workflow.id, {
14643
+ type: "checklist-item-updated",
14644
+ source,
14645
+ actionKey,
14646
+ itemKey,
14647
+ status,
14648
+ resourceKey,
14649
+ actorUserId: String(userCtx.userId || ""),
14650
+ });
14651
+ prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, workflow.id), {
14652
+ type: "workflow-checklist-updated",
14653
+ tapdId: workflow.id,
14654
+ source,
14655
+ actionKey,
14656
+ itemKey,
14657
+ checklistState: updatedItem?.state || checklistState,
14658
+ snapshot,
14659
+ });
14660
+ json(res, 200, {
14661
+ ok: true,
14662
+ alreadyApplied: false,
14663
+ workflow,
14664
+ checklistState: updatedItem?.state || checklistState,
14665
+ checklist: updatedAction?.checklist || null,
14666
+ snapshot,
14667
+ });
14668
+ } catch (error) {
14669
+ json(res, 500, { error: (error && error.message) || String(error) });
14670
+ } finally {
14671
+ releaseWorkflowWriteLock?.();
14672
+ }
14673
+ return;
14674
+ }
14049
14675
  if (req.method === "GET" && url.pathname === "/api/prd-workflow/snapshot") {
14050
14676
  try {
14051
14677
  const tapdId = String(url.searchParams.get("tapdId") || "").trim();
@@ -14867,6 +15493,11 @@ export function startUiServer({
14867
15493
  json(res, 400, { error: `Unsupported workflow namespace: ${report.workflow.namespace}` });
14868
15494
  return;
14869
15495
  }
15496
+ const adminVersionRepair = prdWorkflowAdminVersionRepairIntent(payload, report, userCtx);
15497
+ if (adminVersionRepair.error) {
15498
+ json(res, adminVersionRepair.status || 400, { error: adminVersionRepair.error });
15499
+ return;
15500
+ }
14870
15501
  const tapdId = report.workflow.id;
14871
15502
  const flowId = report.flowId;
14872
15503
  const flowSource = report.flowSource || "user";
@@ -14877,12 +15508,12 @@ export function startUiServer({
14877
15508
  flowId,
14878
15509
  flowSource,
14879
15510
  archived,
14880
- }, userCtx, "write");
15511
+ }, userCtx, adminVersionRepair.requested ? "admin-version-repair" : "write");
14881
15512
  if (workflowScope.error) {
14882
15513
  json(res, workflowScope.status || 400, { error: workflowScope.error });
14883
15514
  return;
14884
15515
  }
14885
- if (!workflowScope.collaboration) {
15516
+ if (!workflowScope.collaboration && !adminVersionRepair.requested) {
14886
15517
  const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
14887
15518
  if (ensured.error) {
14888
15519
  json(res, ensured.status || 400, { error: ensured.error });
@@ -14990,7 +15621,9 @@ export function startUiServer({
14990
15621
  });
14991
15622
  return;
14992
15623
  }
14993
- report = prdWorkflowMergeProducerTimeline(report, currentSnapshot);
15624
+ report = adminVersionRepair.requested
15625
+ ? prdWorkflowMergeAdminVersionTimeline(report, currentSnapshot)
15626
+ : prdWorkflowMergeProducerTimeline(report, currentSnapshot);
14994
15627
  if (report.error) {
14995
15628
  json(res, 400, { error: report.error });
14996
15629
  return;
@@ -15037,11 +15670,17 @@ export function startUiServer({
15037
15670
  getSessionTokenFromRequest(req) || "",
15038
15671
  );
15039
15672
  prdWorkflowBroadcast(
15040
- prdWorkflowKey(userCtx, flowSource, flowId, tapdId),
15673
+ prdWorkflowKey(
15674
+ adminVersionRepair.requested ? { userId: workflowScope.stateOwnerId } : userCtx,
15675
+ flowSource,
15676
+ flowId,
15677
+ tapdId,
15678
+ ),
15041
15679
  { type: "workflow-report", tapdId, workflow: report.workflow, event, observation: Boolean(observation), snapshot },
15042
15680
  );
15043
15681
  json(res, 200, {
15044
15682
  ok: true,
15683
+ ...(adminVersionRepair.requested ? { administrativeRepair: report.event.administrativeRepair } : {}),
15045
15684
  report,
15046
15685
  resourceKeys,
15047
15686
  event,