@fieldwangai/agentflow 0.1.134 → 0.1.135

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.
@@ -177,6 +177,7 @@ import {
177
177
  import {
178
178
  legacyOverallToGlobalState,
179
179
  materializeWorkflowGlobalState,
180
+ materializeWorkflowProjections,
180
181
  mergeWorkflowArtifactLists,
181
182
  mergeWorkflowArtifacts,
182
183
  mergeWorkflowGlobalState,
@@ -3464,6 +3465,23 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
3464
3465
  || snapshot?.title
3465
3466
  || "",
3466
3467
  ).trim();
3468
+ const timeline = Array.isArray(snapshot?.projections?.timeline)
3469
+ ? snapshot.projections.timeline
3470
+ .filter((entry) => entry && typeof entry === "object" && !Array.isArray(entry))
3471
+ .map((entry) => ({
3472
+ key: String(entry.key || [entry.source, entry.kind, entry.id].filter(Boolean).join(":")),
3473
+ kind: String(entry.kind || ""),
3474
+ id: String(entry.id || ""),
3475
+ title: String(entry.title || entry.label || entry.id || ""),
3476
+ date: String(entry.date || ""),
3477
+ source: String(entry.source || ""),
3478
+ dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
3479
+ ? entry.dimensions
3480
+ : {},
3481
+ order: Number.isFinite(Number(entry.order)) ? Number(entry.order) : 0,
3482
+ }))
3483
+ .filter((entry) => entry.kind && entry.id)
3484
+ : [];
3467
3485
  const updatedAtTimestamp = Math.max(
3468
3486
  prdWorkflowDashboardTimestamp(record),
3469
3487
  prdWorkflowDashboardTimestamp(snapshot),
@@ -3481,6 +3499,7 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
3481
3499
  platforms,
3482
3500
  actionCount: actions.length,
3483
3501
  completedActionCount: completedActions,
3502
+ timeline,
3484
3503
  latestAction: latestAction
3485
3504
  ? {
3486
3505
  title: String(latestAction.title || latestAction.label || latestAction.action || latestAction.id || "").trim(),
@@ -3500,6 +3519,72 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
3500
3519
  };
3501
3520
  }
3502
3521
 
3522
+ function prdWorkflowDashboardTimeline(workflows = []) {
3523
+ const buckets = new Map();
3524
+ const assignedWorkflowIds = new Set();
3525
+ const rows = [...(Array.isArray(workflows) ? workflows : [])].sort((left, right) => {
3526
+ const leftAt = Date.parse(String(left?.updatedAt || ""));
3527
+ const rightAt = Date.parse(String(right?.updatedAt || ""));
3528
+ if (Number.isFinite(leftAt) && Number.isFinite(rightAt)) return leftAt - rightAt;
3529
+ if (Number.isFinite(leftAt) !== Number.isFinite(rightAt)) return Number.isFinite(leftAt) ? 1 : -1;
3530
+ return String(left?.id || left?.tapdId || "").localeCompare(String(right?.id || right?.tapdId || ""));
3531
+ });
3532
+ for (const workflow of rows) {
3533
+ const workflowId = String(workflow?.id || workflow?.tapdId || "");
3534
+ const seen = new Set();
3535
+ for (const entry of Array.isArray(workflow?.timeline) ? workflow.timeline : []) {
3536
+ const key = String(entry?.key || [entry?.source, entry?.kind, entry?.id].filter(Boolean).join(":"));
3537
+ if (!key || seen.has(key)) continue;
3538
+ seen.add(key);
3539
+ assignedWorkflowIds.add(workflowId);
3540
+ const current = buckets.get(key) || {
3541
+ key,
3542
+ kind: String(entry.kind || ""),
3543
+ id: String(entry.id || ""),
3544
+ title: String(entry.title || entry.id || ""),
3545
+ date: String(entry.date || ""),
3546
+ source: String(entry.source || ""),
3547
+ dimensions: entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
3548
+ ? entry.dimensions
3549
+ : {},
3550
+ order: Number.isFinite(Number(entry.order)) ? Number(entry.order) : 0,
3551
+ workflowCount: 0,
3552
+ completedCount: 0,
3553
+ blockedCount: 0,
3554
+ workflowIds: [],
3555
+ };
3556
+ current.kind = String(entry.kind || current.kind);
3557
+ current.id = String(entry.id || current.id);
3558
+ current.title = String(entry.title || current.title);
3559
+ current.date = String(entry.date || current.date);
3560
+ current.source = String(entry.source || current.source);
3561
+ current.dimensions = entry.dimensions && typeof entry.dimensions === "object" && !Array.isArray(entry.dimensions)
3562
+ ? entry.dimensions
3563
+ : current.dimensions;
3564
+ current.order = Number.isFinite(Number(entry.order)) ? Number(entry.order) : current.order;
3565
+ current.workflowCount += 1;
3566
+ if (workflow?.state === "completed") current.completedCount += 1;
3567
+ if (workflow?.state === "blocked") current.blockedCount += 1;
3568
+ current.workflowIds.push(workflowId);
3569
+ buckets.set(key, current);
3570
+ }
3571
+ }
3572
+ const timeline = Array.from(buckets.values()).sort((left, right) => {
3573
+ const leftAt = Date.parse(String(left.date || ""));
3574
+ const rightAt = Date.parse(String(right.date || ""));
3575
+ const leftValid = Number.isFinite(leftAt);
3576
+ const rightValid = Number.isFinite(rightAt);
3577
+ if (leftValid && rightValid && leftAt !== rightAt) return leftAt - rightAt;
3578
+ if (leftValid !== rightValid) return leftValid ? -1 : 1;
3579
+ if (left.order !== right.order) return left.order - right.order;
3580
+ return left.title.localeCompare(right.title, undefined, { numeric: true, sensitivity: "base" });
3581
+ });
3582
+ return {
3583
+ timeline,
3584
+ unassignedCount: rows.filter((workflow) => !assignedWorkflowIds.has(String(workflow?.id || workflow?.tapdId || ""))).length,
3585
+ };
3586
+ }
3587
+
3503
3588
  function workspaceConversationsPath(scopedRoot) {
3504
3589
  return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
3505
3590
  }
@@ -11083,13 +11168,15 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
11083
11168
  const overall = prdWorkflowOverallFromEvents(tapdId, snapshot, runtimeEvents);
11084
11169
  const globalState = prdWorkflowGlobalStateFromEvents(tapdId, snapshot, runtimeEvents);
11085
11170
  const artifacts = mergeWorkflowArtifacts(snapshot?.artifacts, runtimeEvents);
11171
+ const projections = materializeWorkflowProjections(snapshot, runtimeEvents);
11086
11172
  return {
11087
11173
  ...snapshot,
11088
11174
  workflow: globalState.workflow,
11089
11175
  overall,
11090
11176
  globalState,
11091
11177
  artifacts,
11092
- runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents),
11178
+ projections,
11179
+ runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents, projections),
11093
11180
  runtimeEvents,
11094
11181
  events,
11095
11182
  sources: {
@@ -12515,7 +12602,7 @@ export function startUiServer({
12515
12602
  ? getTeamById(requestedTeamId)
12516
12603
  : getTeamForUser(userCtx.userId);
12517
12604
  if (!team || team.status !== "active") {
12518
- json(res, 200, { ok: true, view: "team", team: null, workflows: [] });
12605
+ json(res, 200, { ok: true, view: "team", team: null, workflows: [], timeline: [], unassignedCount: 0 });
12519
12606
  return;
12520
12607
  }
12521
12608
  records = listPrdWorkflowCollaborationsForTeam(team.id);
@@ -12529,9 +12616,17 @@ export function startUiServer({
12529
12616
  const latestClient = prdWorkflowLatestClientSnapshot(stateRoot, stateRoot, tapdId);
12530
12617
  const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
12531
12618
  const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
12532
- return prdWorkflowDashboardSummary(record, snapshot, userCtx);
12619
+ const materialized = prdWorkflowMergeRuntimeEvents(stateRoot, tapdId, snapshot);
12620
+ return prdWorkflowDashboardSummary(record, materialized, userCtx);
12621
+ });
12622
+ const dashboardTimeline = prdWorkflowDashboardTimeline(workflows);
12623
+ json(res, 200, {
12624
+ ok: true,
12625
+ view: view === "team" ? "team" : "personal",
12626
+ team: teamSummaryWithUsers(team),
12627
+ workflows,
12628
+ ...dashboardTimeline,
12533
12629
  });
12534
- json(res, 200, { ok: true, view: view === "team" ? "team" : "personal", team: teamSummaryWithUsers(team), workflows });
12535
12630
  } catch (error) {
12536
12631
  json(res, 500, { error: (error && error.message) || String(error) });
12537
12632
  }
@@ -13636,6 +13731,13 @@ export function startUiServer({
13636
13731
  json(res, workflowScope.status || 400, { error: workflowScope.error });
13637
13732
  return;
13638
13733
  }
13734
+ if (!workflowScope.collaboration) {
13735
+ const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId: userCtx.userId });
13736
+ if (ensured.error) {
13737
+ json(res, ensured.status || 400, { error: ensured.error });
13738
+ return;
13739
+ }
13740
+ }
13639
13741
  const scopedRoot = workflowScope.stateRoot;
13640
13742
  prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
13641
13743
  const currentSnapshot = prdWorkflowMaterializeSnapshot(
@@ -21,6 +21,10 @@ function cleanString(value, max = 4000) {
21
21
  return String(value ?? "").trim().slice(0, max);
22
22
  }
23
23
 
24
+ function hasOwn(value, key) {
25
+ return Boolean(value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key));
26
+ }
27
+
24
28
  function stableValue(value) {
25
29
  if (Array.isArray(value)) return value.map((item) => stableValue(item));
26
30
  if (value && typeof value === "object") {
@@ -105,6 +109,67 @@ function normalizeWorkflowArtifact(value, index = 0, defaultScope = "action") {
105
109
  };
106
110
  }
107
111
 
112
+ export function normalizeWorkflowTimelineProjection(value, index = 0) {
113
+ const raw = plainObject(value);
114
+ const kind = cleanString(raw.kind || raw.type, 80).toLowerCase();
115
+ const id = cleanString(raw.id || raw.key, 240);
116
+ if (!kind || !id) return null;
117
+ const source = cleanString(raw.source || raw.namespace, 120).toLowerCase();
118
+ const date = cleanString(raw.date || raw.targetDate || raw.target_date, 80);
119
+ const dimensions = mergeWorkflowGlobalState({}, plainObject(raw.dimensions || raw.facets));
120
+ const key = cleanString(raw.key || [source, kind, id].filter(Boolean).join(":"), 500);
121
+ return {
122
+ ...mergeWorkflowGlobalState({}, raw),
123
+ key: key || `${kind}:${id}`,
124
+ kind,
125
+ id,
126
+ title: cleanString(raw.title || raw.label || id, 500) || id,
127
+ ...(source ? { source } : {}),
128
+ ...(date ? { date } : {}),
129
+ dimensions,
130
+ order: Number.isFinite(Number(raw.order)) ? Number(raw.order) : index,
131
+ };
132
+ }
133
+
134
+ function normalizeWorkflowProjectionState(value = {}) {
135
+ const raw = mergeWorkflowGlobalState({}, plainObject(value));
136
+ if (Array.isArray(value?.timeline)) {
137
+ raw.timeline = value.timeline
138
+ .map((item, index) => normalizeWorkflowTimelineProjection(item, index))
139
+ .filter(Boolean)
140
+ .slice(0, 100);
141
+ } else {
142
+ delete raw.timeline;
143
+ }
144
+ return raw;
145
+ }
146
+
147
+ export function materializeWorkflowProjections(snapshot = {}, runtimeEvents = []) {
148
+ const base =
149
+ snapshot.projections ||
150
+ snapshot.workflowProjections ||
151
+ snapshot.workflow_projections ||
152
+ snapshot.raw?.projections ||
153
+ {};
154
+ let projections = normalizeWorkflowProjectionState(base);
155
+ const events = [...(Array.isArray(runtimeEvents) ? runtimeEvents : [])]
156
+ .sort((left, right) => eventTime(left) - eventTime(right));
157
+ for (const event of events) {
158
+ const next = event?.projections || event?.workflowProjections || event?.workflow_projections;
159
+ if (!next || typeof next !== "object" || Array.isArray(next) || !hasOwn(next, "timeline")) continue;
160
+ projections = {
161
+ ...projections,
162
+ timeline: Array.isArray(next.timeline)
163
+ ? next.timeline
164
+ .map((item, index) => normalizeWorkflowTimelineProjection(item, index))
165
+ .filter(Boolean)
166
+ .slice(0, 100)
167
+ : projections.timeline || [],
168
+ };
169
+ }
170
+ return projections;
171
+ }
172
+
108
173
  export function mergeWorkflowGlobalState(base, patch) {
109
174
  if (!patch || typeof patch !== "object" || Array.isArray(patch)) return plainObject(base);
110
175
  const out = { ...plainObject(base) };
@@ -206,6 +271,22 @@ export function normalizeWorkflowReport(payload = {}) {
206
271
  .filter((item) => item && typeof item === "object" && !Array.isArray(item))
207
272
  .map((item, index) => normalizeWorkflowArtifact(item, index, action ? "action" : "global"));
208
273
 
274
+ const rawProjections = plainObject(payload.projections || payload.workflowProjections || payload.workflow_projections);
275
+ const hasProjections = Object.keys(rawProjections).length > 0 || hasOwn(payload, "projections");
276
+ if (hasProjections && !hasOwn(rawProjections, "timeline")) {
277
+ return { error: "projections requires timeline" };
278
+ }
279
+ if (hasProjections && !Array.isArray(rawProjections.timeline)) {
280
+ return { error: "projections.timeline must be an array" };
281
+ }
282
+ const invalidTimelineIndex = hasProjections
283
+ ? rawProjections.timeline.findIndex((item, index) => !normalizeWorkflowTimelineProjection(item, index))
284
+ : -1;
285
+ if (invalidTimelineIndex >= 0) {
286
+ return { error: `projections.timeline[${invalidTimelineIndex}] requires kind and id` };
287
+ }
288
+ const projections = hasProjections ? normalizeWorkflowProjectionState(rawProjections) : null;
289
+
209
290
  const rawGlobalState = plainObject(payload.globalState || payload.global_state);
210
291
  const hasGlobalState = Object.keys(rawGlobalState).length > 0;
211
292
  const globalStatePatch = plainObject(rawGlobalState.patch);
@@ -215,8 +296,8 @@ export function normalizeWorkflowReport(payload = {}) {
215
296
  if (hasGlobalState && !Object.keys(globalStatePatch).length && !globalStateRemove.length) {
216
297
  return { error: "globalState requires patch or remove" };
217
298
  }
218
- if (!action && !artifacts.length && !hasGlobalState) {
219
- return { error: "Workflow report requires action, artifacts, or globalState" };
299
+ if (!action && !artifacts.length && !hasGlobalState && !hasProjections) {
300
+ return { error: "Workflow report requires action, artifacts, globalState, or projections" };
220
301
  }
221
302
 
222
303
  const idempotencyKey = cleanString(
@@ -257,6 +338,7 @@ export function normalizeWorkflowReport(payload = {}) {
257
338
  scope: "global",
258
339
  }),
259
340
  artifacts,
341
+ ...(hasProjections ? { projections } : {}),
260
342
  ...(hasGlobalState ? {
261
343
  globalStatePatch,
262
344
  globalStateRemove,
@@ -268,6 +350,7 @@ export function normalizeWorkflowReport(payload = {}) {
268
350
  workflow,
269
351
  action,
270
352
  artifacts,
353
+ projections,
271
354
  globalState: hasGlobalState ? {
272
355
  mode: "merge",
273
356
  patch: globalStatePatch,
@@ -448,7 +531,7 @@ export function mergeWorkflowArtifactLists(left = [], right = [], defaultScope =
448
531
  return out;
449
532
  }
450
533
 
451
- export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtimeEvents = []) {
534
+ export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtimeEvents = [], projections = {}) {
452
535
  const events = (Array.isArray(runtimeEvents) ? runtimeEvents : []).map((event) => ({
453
536
  id: event?.id || "",
454
537
  action: event?.action || event?.actionId || "",
@@ -457,10 +540,11 @@ export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtim
457
540
  artifacts: event?.artifacts || [],
458
541
  globalStatePatch: event?.globalStatePatch || {},
459
542
  globalStateRemove: event?.globalStateRemove || [],
543
+ projections: event?.projections || {},
460
544
  }));
461
545
  const hash = crypto
462
546
  .createHash("sha256")
463
- .update(JSON.stringify(stableValue({ globalState, artifacts, events })))
547
+ .update(JSON.stringify(stableValue({ globalState, artifacts, projections, events })))
464
548
  .digest("hex")
465
549
  .slice(0, 24);
466
550
  return `runtime:${hash}`;