@fieldwangai/agentflow 0.1.141 → 0.1.142

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,72 @@ function prdWorkflowMergeProducerTimeline(report, currentSnapshot = {}) {
11552
11676
  };
11553
11677
  }
11554
11678
 
11679
+ function prdWorkflowAdminVersionRepairIntent(payload = {}, report = {}, userCtx = {}) {
11680
+ const operation = String(
11681
+ payload.adminOperation || payload.admin_operation || payload.administrativeOperation || payload.administrative_operation || "",
11682
+ ).trim().toLowerCase();
11683
+ if (!operation) return { requested: false };
11684
+ if (operation !== "repair-version-membership") {
11685
+ return { requested: true, status: 400, error: `Unsupported admin Workflow operation: ${operation}` };
11686
+ }
11687
+ if (userCtx.isAdmin !== true) {
11688
+ return { requested: true, status: 403, error: "Admin permission required" };
11689
+ }
11690
+ const forbiddenKeys = ["action", "artifacts", "observation", "globalState", "global_state", "extensions", "extension"]
11691
+ .filter((key) => Object.prototype.hasOwnProperty.call(payload, key));
11692
+ if (forbiddenKeys.length) {
11693
+ return {
11694
+ requested: true,
11695
+ status: 400,
11696
+ error: `Admin version repair may only update projections.timeline; remove: ${forbiddenKeys.join(", ")}`,
11697
+ };
11698
+ }
11699
+ const projections = payload.projections;
11700
+ if (!projections || typeof projections !== "object" || Array.isArray(projections) || !Array.isArray(projections.timeline)) {
11701
+ return { requested: true, status: 400, error: "Admin version repair requires projections.timeline" };
11702
+ }
11703
+ const extraProjectionKeys = Object.keys(projections).filter((key) => key !== "timeline");
11704
+ if (extraProjectionKeys.length) {
11705
+ return { requested: true, status: 400, error: "Admin version repair may only update projections.timeline" };
11706
+ }
11707
+ const nonVersionEntry = report?.projections?.timeline?.find((item) => String(item?.kind || "").trim().toLowerCase() !== "version");
11708
+ if (nonVersionEntry) {
11709
+ return { requested: true, status: 400, error: "Admin version repair only accepts timeline entries with kind=version" };
11710
+ }
11711
+ if (!report.idempotencyKey) {
11712
+ return { requested: true, status: 400, error: "Admin version repair requires idempotencyKey" };
11713
+ }
11714
+ if (!report.expectedRevision) {
11715
+ return { requested: true, status: 400, error: "Admin version repair requires expectedRevision" };
11716
+ }
11717
+ return { requested: true, operation };
11718
+ }
11719
+
11720
+ function prdWorkflowMergeAdminVersionTimeline(report, currentSnapshot = {}) {
11721
+ const source = prdWorkflowRuntimeEventProducer(report?.event || {});
11722
+ const current = Array.isArray(currentSnapshot?.projections?.timeline) ? currentSnapshot.projections.timeline : [];
11723
+ const incoming = Array.isArray(report?.projections?.timeline) ? report.projections.timeline : [];
11724
+ const retained = current.filter((item) => (
11725
+ prdWorkflowRuntimeEventProducer(item) !== source
11726
+ || String(item?.kind || "").trim().toLowerCase() !== "version"
11727
+ ));
11728
+ const timeline = [...retained, ...incoming];
11729
+ const administrativeRepair = {
11730
+ kind: "version-attribution",
11731
+ operation: "repair-version-membership",
11732
+ };
11733
+ return {
11734
+ ...report,
11735
+ projections: { ...report.projections, timeline },
11736
+ event: {
11737
+ ...report.event,
11738
+ projections: { ...report.event.projections, timeline },
11739
+ administrativeRepair,
11740
+ administrative_repair: administrativeRepair,
11741
+ },
11742
+ };
11743
+ }
11744
+
11555
11745
  function prdWorkflowRuntimeEventDedupeKey(event = {}, index = 0) {
11556
11746
  const producer = prdWorkflowRuntimeEventProducer(event);
11557
11747
  const operation = prdWorkflowRuntimeEventOperation(event);
@@ -11838,6 +12028,136 @@ function prdWorkflowGlobalStateFromEvents(tapdId, snapshot = {}, runtimeEvents =
11838
12028
  return state;
11839
12029
  }
11840
12030
 
12031
+ function prdWorkflowChecklistActionKey(action = {}) {
12032
+ return String(
12033
+ action?.actionModel?.key ||
12034
+ action?.key ||
12035
+ action?.actionKey ||
12036
+ action?.action_key ||
12037
+ action?.action ||
12038
+ action?.actionId ||
12039
+ action?.action_id ||
12040
+ action?.stageKey ||
12041
+ action?.stage_key ||
12042
+ "",
12043
+ ).trim();
12044
+ }
12045
+
12046
+ function prdWorkflowChecklistResourceKey(producer, actionKey, itemKey) {
12047
+ return `checklist:${String(producer || "").trim().toLowerCase()}:${String(actionKey || "").trim()}:${String(itemKey || "").trim()}`;
12048
+ }
12049
+
12050
+ function prdWorkflowChecklistStateEntries(runtimeEvents = []) {
12051
+ const states = new Map();
12052
+ for (const event of Array.isArray(runtimeEvents) ? runtimeEvents : []) {
12053
+ const state = event?.checklistState || event?.checklist_state;
12054
+ if (!state || typeof state !== "object" || Array.isArray(state)) continue;
12055
+ const producer = String(state.producer || state.source || "").trim().toLowerCase();
12056
+ const actionKey = String(state.actionKey || state.action_key || "").trim();
12057
+ const itemKey = String(state.itemKey || state.item_key || "").trim();
12058
+ if (!producer || !actionKey || !itemKey) continue;
12059
+ const resourceKey = prdWorkflowChecklistResourceKey(producer, actionKey, itemKey);
12060
+ const version = workflowSnapshotResourceVersions({ runtimeEvents: [event] })[resourceKey] || "absent";
12061
+ states.set(resourceKey, { ...state, producer, actionKey, itemKey, resourceKey, version });
12062
+ }
12063
+ return states;
12064
+ }
12065
+
12066
+ function prdWorkflowMaterializeChecklists(snapshot = {}, runtimeEvents = []) {
12067
+ const actionSources = new Map();
12068
+ for (const event of Array.isArray(runtimeEvents) ? runtimeEvents : []) {
12069
+ const actionKey = prdWorkflowChecklistActionKey(event);
12070
+ const producer = String(event?.source || event?.producer || "").trim().toLowerCase();
12071
+ if (!actionKey || !producer || (!event?.checklist && !event?.actionModel?.checklist)) continue;
12072
+ const existing = actionSources.get(actionKey);
12073
+ actionSources.set(actionKey, existing && existing !== producer ? "" : producer);
12074
+ }
12075
+ const stateEntries = prdWorkflowChecklistStateEntries(runtimeEvents);
12076
+ const terminalStatuses = new Set(["passed", "skipped"]);
12077
+ const decorate = (action) => {
12078
+ if (!action || typeof action !== "object" || Array.isArray(action)) return action;
12079
+ const definition = action.checklist || action.actionModel?.checklist;
12080
+ if (!definition || typeof definition !== "object" || Array.isArray(definition)) return action;
12081
+ const actionKey = prdWorkflowChecklistActionKey(action);
12082
+ const producer = String(
12083
+ definition.source || action.source || action.producer || action.actionModel?.source || actionSources.get(actionKey) || "",
12084
+ ).trim().toLowerCase();
12085
+ const items = (Array.isArray(definition.items) ? definition.items : []).map((item) => {
12086
+ const itemKey = String(item?.key || item?.id || "").trim();
12087
+ const resourceKey = producer && actionKey && itemKey
12088
+ ? prdWorkflowChecklistResourceKey(producer, actionKey, itemKey)
12089
+ : "";
12090
+ const stored = resourceKey ? stateEntries.get(resourceKey) : null;
12091
+ const state = stored || {
12092
+ producer,
12093
+ actionKey,
12094
+ itemKey,
12095
+ status: "pending",
12096
+ note: "",
12097
+ evidence: [],
12098
+ resourceKey,
12099
+ version: "absent",
12100
+ };
12101
+ return { ...item, state };
12102
+ });
12103
+ const required = items.filter((item) => item.required !== false);
12104
+ const completed = items.filter((item) => terminalStatuses.has(String(item?.state?.status || "pending"))).length;
12105
+ const requiredCompleted = required.filter((item) => terminalStatuses.has(String(item?.state?.status || "pending"))).length;
12106
+ const completionPolicy = String(definition.completionPolicy || definition.completion_policy || "all_required").trim().toLowerCase();
12107
+ const completionCandidates = required.length ? required : items;
12108
+ const ready = completionPolicy === "manual"
12109
+ ? false
12110
+ : completionPolicy === "any_required"
12111
+ ? completionCandidates.some((item) => terminalStatuses.has(String(item?.state?.status || "pending")))
12112
+ : completionCandidates.length > 0 && completionCandidates.every((item) => terminalStatuses.has(String(item?.state?.status || "pending")));
12113
+ const checklist = {
12114
+ ...definition,
12115
+ source: producer,
12116
+ items,
12117
+ progress: {
12118
+ total: items.length,
12119
+ completed,
12120
+ required: required.length,
12121
+ requiredCompleted,
12122
+ percent: items.length ? Math.round((completed / items.length) * 100) : 0,
12123
+ ready,
12124
+ },
12125
+ };
12126
+ return {
12127
+ ...action,
12128
+ checklist,
12129
+ ...(action.actionModel && typeof action.actionModel === "object" && !Array.isArray(action.actionModel)
12130
+ ? { actionModel: { ...action.actionModel, checklist } }
12131
+ : {}),
12132
+ };
12133
+ };
12134
+ const out = { ...snapshot };
12135
+ for (const key of ["actions", "workflowActions", "workflow_actions", "timeline", "history", "events", "runtimeEvents", "runtime_events"]) {
12136
+ if (Array.isArray(snapshot?.[key])) out[key] = snapshot[key].map(decorate);
12137
+ }
12138
+ out.checklistStates = [...stateEntries.values()];
12139
+ return out;
12140
+ }
12141
+
12142
+ function prdWorkflowFindChecklistAction(snapshot = {}, producer = "", actionKey = "") {
12143
+ const wantedProducer = String(producer || "").trim().toLowerCase();
12144
+ const wantedActionKey = String(actionKey || "").trim();
12145
+ const matches = [];
12146
+ for (const key of ["actions", "workflowActions", "workflow_actions", "timeline", "history", "events", "runtimeEvents", "runtime_events"]) {
12147
+ for (const action of Array.isArray(snapshot?.[key]) ? snapshot[key] : []) {
12148
+ if (!action || typeof action !== "object" || Array.isArray(action)) continue;
12149
+ const checklist = action.checklist || action.actionModel?.checklist;
12150
+ if (!checklist || typeof checklist !== "object" || Array.isArray(checklist)) continue;
12151
+ const resolvedActionKey = prdWorkflowChecklistActionKey(action);
12152
+ const resolvedProducer = String(checklist.source || action.source || action.producer || "").trim().toLowerCase();
12153
+ if (resolvedActionKey !== wantedActionKey) continue;
12154
+ if (wantedProducer && resolvedProducer !== wantedProducer) continue;
12155
+ matches.push({ ...action, checklist, source: resolvedProducer || wantedProducer });
12156
+ }
12157
+ }
12158
+ return matches.at(-1) || null;
12159
+ }
12160
+
11841
12161
  function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11842
12162
  const runtime = prdWorkflowReadRuntimeEvents(scopedRoot, tapdId);
11843
12163
  const runtimeEvents = runtime.events;
@@ -11854,7 +12174,7 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11854
12174
  for (const key of ["issues", "issueGroups", "issue_groups", "epics", "epicGroups", "epic_groups", "aiDocs", "ai_docs"]) {
11855
12175
  if (Object.prototype.hasOwnProperty.call(prdFlowExtension, key)) prdFlowExtensionView[key] = prdFlowExtension[key];
11856
12176
  }
11857
- const materialized = {
12177
+ let materialized = {
11858
12178
  ...snapshot,
11859
12179
  ...prdFlowExtensionView,
11860
12180
  workflow: globalState.workflow,
@@ -11871,6 +12191,7 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11871
12191
  runtimeEventsUpdatedAt: runtime.updatedAt || "",
11872
12192
  },
11873
12193
  };
12194
+ materialized = prdWorkflowMaterializeChecklists(materialized, runtimeEvents);
11874
12195
  materialized.resourceVersions = workflowSnapshotResourceVersions(materialized);
11875
12196
  return materialized;
11876
12197
  }
@@ -13380,7 +13701,18 @@ export function startUiServer({
13380
13701
  ? getTeamById(requestedTeamId)
13381
13702
  : getTeamForUser(userCtx.userId);
13382
13703
  if (!team || team.status !== "active") {
13383
- json(res, 200, { ok: true, view: "team", team: null, workflows: [], timeline: [], unassignedCount: 0 });
13704
+ json(res, 200, {
13705
+ ok: true,
13706
+ view: "team",
13707
+ team: null,
13708
+ workflows: [],
13709
+ timeline: [],
13710
+ unassignedCount: 0,
13711
+ availableCount: 0,
13712
+ selectedTimelineKey: "all",
13713
+ defaultTimelineKey: "all",
13714
+ pagination: { page: 1, pageSize: 20, total: 0, totalPages: 1, hasPrevious: false, hasNext: false },
13715
+ });
13384
13716
  return;
13385
13717
  }
13386
13718
  records = listPrdWorkflowCollaborationsForTeam(team.id);
@@ -13400,11 +13732,19 @@ export function startUiServer({
13400
13732
  return prdWorkflowDashboardSummary(record, materialized, userCtx, projectBindings);
13401
13733
  });
13402
13734
  const dashboardTimeline = prdWorkflowDashboardTimeline(workflows);
13735
+ const dashboardPage = prdWorkflowDashboardPage(workflows, dashboardTimeline, {
13736
+ timelineKey: url.searchParams.has("timelineKey") ? url.searchParams.get("timelineKey") : "",
13737
+ query: url.searchParams.get("q"),
13738
+ scope: url.searchParams.get("scope"),
13739
+ state: url.searchParams.get("state"),
13740
+ page: url.searchParams.get("page"),
13741
+ pageSize: url.searchParams.get("pageSize"),
13742
+ });
13403
13743
  json(res, 200, {
13404
13744
  ok: true,
13405
13745
  view: view === "team" ? "team" : "personal",
13406
13746
  team: teamSummaryWithUsers(team),
13407
- workflows,
13747
+ ...dashboardPage,
13408
13748
  ...dashboardTimeline,
13409
13749
  });
13410
13750
  } catch (error) {
@@ -14046,6 +14386,276 @@ export function startUiServer({
14046
14386
  }
14047
14387
  return;
14048
14388
  }
14389
+ if (req.method === "GET" && url.pathname === "/api/workflows/checklist") {
14390
+ try {
14391
+ const workflow = normalizeWorkflowReference({ workflow: url.searchParams.get("workflow") || "" });
14392
+ if (workflow.error) {
14393
+ json(res, 400, { error: workflow.error });
14394
+ return;
14395
+ }
14396
+ if (workflow.namespace !== "tapd") {
14397
+ json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
14398
+ return;
14399
+ }
14400
+ const source = String(url.searchParams.get("source") || "").trim().toLowerCase();
14401
+ const actionKey = String(url.searchParams.get("actionKey") || url.searchParams.get("action_key") || "").trim();
14402
+ if (!/^[a-z][a-z0-9._-]{0,119}$/.test(source)) {
14403
+ json(res, 400, { error: "Invalid checklist source" });
14404
+ return;
14405
+ }
14406
+ if (!actionKey || actionKey.length > 240 || /[\0\r\n]/.test(actionKey)) {
14407
+ json(res, 400, { error: "Invalid checklist actionKey" });
14408
+ return;
14409
+ }
14410
+ const flowId = String(url.searchParams.get("flowId") || "").trim();
14411
+ const flowSource = String(url.searchParams.get("flowSource") || "user").trim() || "user";
14412
+ const workflowScope = resolvePrdWorkflowScope(root, {
14413
+ tapdId: workflow.id,
14414
+ flowId,
14415
+ flowSource,
14416
+ archived: url.searchParams.get("archived") === "1",
14417
+ workspaceId: url.searchParams.get("workspaceId") || "",
14418
+ workflowShare: url.searchParams.get("workflowShare") || "",
14419
+ }, userCtx, "read");
14420
+ if (workflowScope.error) {
14421
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
14422
+ return;
14423
+ }
14424
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, workflowScope.stateRoot, workflow.id);
14425
+ const snapshot = prdWorkflowMaterializeSnapshot(
14426
+ workflowScope.executionRoot,
14427
+ workflowScope.stateRoot,
14428
+ workflow.id,
14429
+ userCtx,
14430
+ { flowSource, flowId },
14431
+ );
14432
+ const action = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14433
+ if (!action) {
14434
+ json(res, 404, { error: "Workflow Action checklist not found" });
14435
+ return;
14436
+ }
14437
+ const access = workflowScope.collaborationAccess || {};
14438
+ const canWrite = Boolean(authUser?.userId) && !workflowScope.sharedByLink && !workflowScope.adminReadonly && (
14439
+ workflowScope.collaboration ? access.writable === true : true
14440
+ );
14441
+ json(res, 200, {
14442
+ ok: true,
14443
+ workflow,
14444
+ action: {
14445
+ key: actionKey,
14446
+ source,
14447
+ title: String(action.title || action.label || actionKey),
14448
+ status: String(action.status || "pending"),
14449
+ checklist: action.checklist,
14450
+ },
14451
+ canWrite,
14452
+ });
14453
+ } catch (error) {
14454
+ json(res, 500, { error: (error && error.message) || String(error) });
14455
+ }
14456
+ return;
14457
+ }
14458
+ if (req.method === "PATCH" && url.pathname === "/api/workflows/checklist") {
14459
+ if (!authUser?.userId) {
14460
+ json(res, 401, { error: "Authentication required" });
14461
+ return;
14462
+ }
14463
+ let payload;
14464
+ try {
14465
+ payload = JSON.parse(await readBody(req, 1024 * 1024));
14466
+ } catch (error) {
14467
+ json(res, error?.status === 413 ? 413 : 400, { error: error?.status === 413 ? error.message : "Invalid JSON body" });
14468
+ return;
14469
+ }
14470
+ let releaseWorkflowWriteLock = null;
14471
+ try {
14472
+ const workflow = normalizeWorkflowReference(payload);
14473
+ if (workflow.error) {
14474
+ json(res, 400, { error: workflow.error });
14475
+ return;
14476
+ }
14477
+ if (workflow.namespace !== "tapd") {
14478
+ json(res, 400, { error: `Unsupported workflow namespace: ${workflow.namespace}` });
14479
+ return;
14480
+ }
14481
+ const source = String(payload.source || "").trim().toLowerCase();
14482
+ const actionKey = String(payload.actionKey || payload.action_key || "").trim();
14483
+ const itemKey = String(payload.itemKey || payload.item_key || "").trim();
14484
+ if (!/^[a-z][a-z0-9._-]{0,119}$/.test(source)) {
14485
+ json(res, 400, { error: "Invalid checklist source" });
14486
+ return;
14487
+ }
14488
+ if (!actionKey || actionKey.length > 240 || /[\0\r\n]/.test(actionKey)) {
14489
+ json(res, 400, { error: "Invalid checklist actionKey" });
14490
+ return;
14491
+ }
14492
+ if (!itemKey || itemKey.length > 240 || /[\0\r\n]/.test(itemKey)) {
14493
+ json(res, 400, { error: "Invalid checklist itemKey" });
14494
+ return;
14495
+ }
14496
+ const rawStatus = String(payload.status || "pending").trim().toLowerCase();
14497
+ if (!["pending", "passed", "failed", "blocked", "skipped", "done", "complete", "completed", "success", "error", "cancelled", "canceled"].includes(rawStatus)) {
14498
+ json(res, 400, { error: `Invalid checklist item status: ${rawStatus}` });
14499
+ return;
14500
+ }
14501
+ const status = normalizeWorkflowChecklistItemStatus(rawStatus);
14502
+ const note = String(payload.note || "").trim();
14503
+ if (note.length > 4000) {
14504
+ json(res, 400, { error: "Checklist note exceeds 4000 characters" });
14505
+ return;
14506
+ }
14507
+ const rawEvidence = Array.isArray(payload.evidence) ? payload.evidence : [];
14508
+ if (rawEvidence.length > 20) {
14509
+ json(res, 400, { error: "Checklist evidence supports at most 20 entries" });
14510
+ return;
14511
+ }
14512
+ const evidence = [];
14513
+ for (let index = 0; index < rawEvidence.length; index += 1) {
14514
+ const item = rawEvidence[index];
14515
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
14516
+ json(res, 400, { error: `evidence[${index}] must be an object` });
14517
+ return;
14518
+ }
14519
+ const evidenceUrl = String(item.url || item.href || "").trim();
14520
+ if (!evidenceUrl || evidenceUrl.length > 4000 || !isSafeWorkflowUrl(evidenceUrl)) {
14521
+ json(res, 400, { error: `evidence[${index}].url must use http, https, or an absolute application path` });
14522
+ return;
14523
+ }
14524
+ evidence.push({
14525
+ title: String(item.title || item.label || `证据 ${index + 1}`).trim().slice(0, 500),
14526
+ url: evidenceUrl,
14527
+ });
14528
+ }
14529
+ const expectedVersion = String(payload.expectedVersion || payload.expected_version || "").trim();
14530
+ if (!expectedVersion) {
14531
+ json(res, 400, { error: "Checklist update requires expectedVersion" });
14532
+ return;
14533
+ }
14534
+ const flowId = String(payload.flowId || payload.flow_id || "").trim();
14535
+ const flowSource = String(payload.flowSource || payload.flow_source || "user").trim() || "user";
14536
+ const workflowScope = resolvePrdWorkflowScope(root, {
14537
+ ...payload,
14538
+ tapdId: workflow.id,
14539
+ flowId,
14540
+ flowSource,
14541
+ }, userCtx, "write");
14542
+ if (workflowScope.error) {
14543
+ json(res, workflowScope.status || 400, { error: workflowScope.error });
14544
+ return;
14545
+ }
14546
+ if (!workflowScope.collaboration) {
14547
+ const ensured = ensurePrdWorkflowCollaboration({ tapdId: workflow.id, userId: userCtx.userId });
14548
+ if (ensured.error) {
14549
+ json(res, ensured.status || 400, { error: ensured.error });
14550
+ return;
14551
+ }
14552
+ }
14553
+ const scopedRoot = workflowScope.stateRoot;
14554
+ prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, workflow.id);
14555
+ releaseWorkflowWriteLock = await prdWorkflowAcquireWriteLock(`${scopedRoot}\t${workflow.id}`);
14556
+ let snapshot = prdWorkflowMaterializeSnapshot(
14557
+ workflowScope.executionRoot,
14558
+ scopedRoot,
14559
+ workflow.id,
14560
+ userCtx,
14561
+ { flowSource, flowId },
14562
+ );
14563
+ const idempotencyKey = String(payload.idempotencyKey || payload.idempotency_key || "").trim().slice(0, 500);
14564
+ if (idempotencyKey) {
14565
+ const existing = prdWorkflowFindIdempotencyEvent(scopedRoot, workflow.id, idempotencyKey, "agentflow-checklist", false, "checklist.update");
14566
+ if (existing) {
14567
+ json(res, 200, { ok: true, alreadyApplied: true, workflow, checklistState: existing.checklistState, snapshot });
14568
+ return;
14569
+ }
14570
+ }
14571
+ const action = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14572
+ const checklistItem = action?.checklist?.items?.find((item) => String(item?.key || "") === itemKey);
14573
+ if (!action || !checklistItem) {
14574
+ json(res, 404, { error: "Workflow Action checklist item not found" });
14575
+ return;
14576
+ }
14577
+ if (status === "passed" && checklistItem.evidenceRequired === true && evidence.length === 0) {
14578
+ json(res, 400, { error: "Checklist item requires evidence before it can pass" });
14579
+ return;
14580
+ }
14581
+ const resourceKey = prdWorkflowChecklistResourceKey(source, actionKey, itemKey);
14582
+ const currentVersion = String(snapshot.resourceVersions?.[resourceKey] || "absent");
14583
+ if (expectedVersion !== currentVersion) {
14584
+ json(res, 409, {
14585
+ error: "Checklist item changed; refresh it before saving",
14586
+ conflict: { type: "workflow-resource-conflict", conflicts: [{ resourceKey, expectedVersion, currentVersion }], workflow },
14587
+ snapshot,
14588
+ });
14589
+ return;
14590
+ }
14591
+ const now = new Date().toISOString();
14592
+ const checklistState = {
14593
+ producer: source,
14594
+ actionKey,
14595
+ itemKey,
14596
+ status,
14597
+ note,
14598
+ evidence,
14599
+ updatedAt: now,
14600
+ updatedBy: {
14601
+ userId: String(userCtx.userId || ""),
14602
+ username: String(authUser.username || userCtx.userId || ""),
14603
+ },
14604
+ };
14605
+ const event = prdWorkflowAppendRuntimeEvent(scopedRoot, workflow.id, {
14606
+ id: `checklist_state_${prdWorkflowSafeStateId([source, actionKey, itemKey].join(":"))}`,
14607
+ type: "workflow-checklist-update",
14608
+ operation: "checklist.update",
14609
+ source: "agentflow-checklist",
14610
+ auxiliary: true,
14611
+ aggregateByStage: false,
14612
+ status: "done",
14613
+ checklistState,
14614
+ ...(idempotencyKey ? { idempotencyKey } : {}),
14615
+ });
14616
+ if (!event) throw new Error("Failed to store checklist state");
14617
+ snapshot = prdWorkflowMaterializeSnapshot(
14618
+ workflowScope.executionRoot,
14619
+ scopedRoot,
14620
+ workflow.id,
14621
+ userCtx,
14622
+ { flowSource, flowId },
14623
+ );
14624
+ const updatedAction = prdWorkflowFindChecklistAction(snapshot, source, actionKey);
14625
+ const updatedItem = updatedAction?.checklist?.items?.find((item) => String(item?.key || "") === itemKey);
14626
+ prdWorkflowAppendAudit(scopedRoot, workflow.id, {
14627
+ type: "checklist-item-updated",
14628
+ source,
14629
+ actionKey,
14630
+ itemKey,
14631
+ status,
14632
+ resourceKey,
14633
+ actorUserId: String(userCtx.userId || ""),
14634
+ });
14635
+ prdWorkflowBroadcast(prdWorkflowKey(userCtx, flowSource, flowId, workflow.id), {
14636
+ type: "workflow-checklist-updated",
14637
+ tapdId: workflow.id,
14638
+ source,
14639
+ actionKey,
14640
+ itemKey,
14641
+ checklistState: updatedItem?.state || checklistState,
14642
+ snapshot,
14643
+ });
14644
+ json(res, 200, {
14645
+ ok: true,
14646
+ alreadyApplied: false,
14647
+ workflow,
14648
+ checklistState: updatedItem?.state || checklistState,
14649
+ checklist: updatedAction?.checklist || null,
14650
+ snapshot,
14651
+ });
14652
+ } catch (error) {
14653
+ json(res, 500, { error: (error && error.message) || String(error) });
14654
+ } finally {
14655
+ releaseWorkflowWriteLock?.();
14656
+ }
14657
+ return;
14658
+ }
14049
14659
  if (req.method === "GET" && url.pathname === "/api/prd-workflow/snapshot") {
14050
14660
  try {
14051
14661
  const tapdId = String(url.searchParams.get("tapdId") || "").trim();
@@ -14867,6 +15477,11 @@ export function startUiServer({
14867
15477
  json(res, 400, { error: `Unsupported workflow namespace: ${report.workflow.namespace}` });
14868
15478
  return;
14869
15479
  }
15480
+ const adminVersionRepair = prdWorkflowAdminVersionRepairIntent(payload, report, userCtx);
15481
+ if (adminVersionRepair.error) {
15482
+ json(res, adminVersionRepair.status || 400, { error: adminVersionRepair.error });
15483
+ return;
15484
+ }
14870
15485
  const tapdId = report.workflow.id;
14871
15486
  const flowId = report.flowId;
14872
15487
  const flowSource = report.flowSource || "user";
@@ -14877,12 +15492,12 @@ export function startUiServer({
14877
15492
  flowId,
14878
15493
  flowSource,
14879
15494
  archived,
14880
- }, userCtx, "write");
15495
+ }, userCtx, adminVersionRepair.requested ? "admin-version-repair" : "write");
14881
15496
  if (workflowScope.error) {
14882
15497
  json(res, workflowScope.status || 400, { error: workflowScope.error });
14883
15498
  return;
14884
15499
  }
14885
- if (!workflowScope.collaboration) {
15500
+ if (!workflowScope.collaboration && !adminVersionRepair.requested) {
14886
15501
  const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
14887
15502
  if (ensured.error) {
14888
15503
  json(res, ensured.status || 400, { error: ensured.error });
@@ -14990,7 +15605,9 @@ export function startUiServer({
14990
15605
  });
14991
15606
  return;
14992
15607
  }
14993
- report = prdWorkflowMergeProducerTimeline(report, currentSnapshot);
15608
+ report = adminVersionRepair.requested
15609
+ ? prdWorkflowMergeAdminVersionTimeline(report, currentSnapshot)
15610
+ : prdWorkflowMergeProducerTimeline(report, currentSnapshot);
14994
15611
  if (report.error) {
14995
15612
  json(res, 400, { error: report.error });
14996
15613
  return;
@@ -15037,11 +15654,17 @@ export function startUiServer({
15037
15654
  getSessionTokenFromRequest(req) || "",
15038
15655
  );
15039
15656
  prdWorkflowBroadcast(
15040
- prdWorkflowKey(userCtx, flowSource, flowId, tapdId),
15657
+ prdWorkflowKey(
15658
+ adminVersionRepair.requested ? { userId: workflowScope.stateOwnerId } : userCtx,
15659
+ flowSource,
15660
+ flowId,
15661
+ tapdId,
15662
+ ),
15041
15663
  { type: "workflow-report", tapdId, workflow: report.workflow, event, observation: Boolean(observation), snapshot },
15042
15664
  );
15043
15665
  json(res, 200, {
15044
15666
  ok: true,
15667
+ ...(adminVersionRepair.requested ? { administrativeRepair: report.event.administrativeRepair } : {}),
15045
15668
  report,
15046
15669
  resourceKeys,
15047
15670
  event,