@fieldwangai/agentflow 0.1.134 → 0.1.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,93 @@ 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 normalizeWorkflowExtensions(value = {}) {
148
+ const raw = plainObject(value);
149
+ const out = {};
150
+ for (const [namespace, extension] of Object.entries(raw)) {
151
+ const key = cleanString(namespace, 120).toLowerCase();
152
+ if (!key || !/^[a-z][a-z0-9._-]{0,119}$/.test(key)) continue;
153
+ if (!extension || typeof extension !== "object" || Array.isArray(extension)) continue;
154
+ out[key] = mergeWorkflowGlobalState({}, extension);
155
+ }
156
+ return out;
157
+ }
158
+
159
+ export function materializeWorkflowExtensions(snapshot = {}, runtimeEvents = []) {
160
+ let extensions = normalizeWorkflowExtensions(
161
+ snapshot.extensions || snapshot.workflowExtensions || snapshot.workflow_extensions || {},
162
+ );
163
+ const events = [...(Array.isArray(runtimeEvents) ? runtimeEvents : [])]
164
+ .sort((left, right) => eventTime(left) - eventTime(right));
165
+ for (const event of events) {
166
+ const patch = event?.extensionsPatch || event?.extensions_patch;
167
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) continue;
168
+ extensions = mergeWorkflowGlobalState(extensions, normalizeWorkflowExtensions(patch));
169
+ }
170
+ return extensions;
171
+ }
172
+
173
+ export function materializeWorkflowProjections(snapshot = {}, runtimeEvents = []) {
174
+ const base =
175
+ snapshot.projections ||
176
+ snapshot.workflowProjections ||
177
+ snapshot.workflow_projections ||
178
+ snapshot.raw?.projections ||
179
+ {};
180
+ let projections = normalizeWorkflowProjectionState(base);
181
+ const events = [...(Array.isArray(runtimeEvents) ? runtimeEvents : [])]
182
+ .sort((left, right) => eventTime(left) - eventTime(right));
183
+ for (const event of events) {
184
+ const next = event?.projections || event?.workflowProjections || event?.workflow_projections;
185
+ if (!next || typeof next !== "object" || Array.isArray(next) || !hasOwn(next, "timeline")) continue;
186
+ projections = {
187
+ ...projections,
188
+ timeline: Array.isArray(next.timeline)
189
+ ? next.timeline
190
+ .map((item, index) => normalizeWorkflowTimelineProjection(item, index))
191
+ .filter(Boolean)
192
+ .slice(0, 100)
193
+ : projections.timeline || [],
194
+ };
195
+ }
196
+ return projections;
197
+ }
198
+
108
199
  export function mergeWorkflowGlobalState(base, patch) {
109
200
  if (!patch || typeof patch !== "object" || Array.isArray(patch)) return plainObject(base);
110
201
  const out = { ...plainObject(base) };
@@ -176,6 +267,24 @@ export function normalizeWorkflowReport(payload = {}) {
176
267
  const workflow = normalizeWorkflowReference(payload);
177
268
  if (workflow.error) return workflow;
178
269
  const rawWorkflow = plainObject(payload.workflow);
270
+ const source = cleanString(payload.source || "agentflow-cli", 120).toLowerCase() || "agentflow-cli";
271
+ if (!/^[a-z][a-z0-9._-]{0,119}$/.test(source)) {
272
+ return { error: "Invalid workflow report source" };
273
+ }
274
+
275
+ const rawObservation = plainObject(payload.observation);
276
+ const hasObservation = Object.keys(rawObservation).length > 0;
277
+ const observationState = plainObject(rawObservation.state || rawObservation.facts || rawObservation.value);
278
+ if (hasObservation && !Object.keys(observationState).length) {
279
+ return { error: "observation requires state" };
280
+ }
281
+ const observation = hasObservation ? {
282
+ schema: cleanString(rawObservation.schema || rawObservation.model || "workflow-observation/v1", 160),
283
+ state: mergeWorkflowGlobalState({}, observationState),
284
+ observedAt: cleanString(rawObservation.observedAt || rawObservation.observed_at, 80),
285
+ clientId: cleanString(rawObservation.clientId || rawObservation.client_id, 160),
286
+ scope: cleanString(rawObservation.scope || "client", 80).toLowerCase() || "client",
287
+ } : null;
179
288
 
180
289
  const rawAction = plainObject(payload.action);
181
290
  const hasAction = Object.keys(rawAction).length > 0;
@@ -204,7 +313,33 @@ export function normalizeWorkflowReport(payload = {}) {
204
313
  const rawArtifacts = Array.isArray(payload.artifacts) ? payload.artifacts : [];
205
314
  const artifacts = rawArtifacts
206
315
  .filter((item) => item && typeof item === "object" && !Array.isArray(item))
207
- .map((item, index) => normalizeWorkflowArtifact(item, index, action ? "action" : "global"));
316
+ .map((item, index) => ({
317
+ ...normalizeWorkflowArtifact(item, index, action ? "action" : "global"),
318
+ producer: source,
319
+ }));
320
+
321
+ const rawProjections = plainObject(payload.projections || payload.workflowProjections || payload.workflow_projections);
322
+ const hasProjections = Object.keys(rawProjections).length > 0 || hasOwn(payload, "projections");
323
+ if (hasProjections && !hasOwn(rawProjections, "timeline")) {
324
+ return { error: "projections requires timeline" };
325
+ }
326
+ if (hasProjections && !Array.isArray(rawProjections.timeline)) {
327
+ return { error: "projections.timeline must be an array" };
328
+ }
329
+ const invalidTimelineIndex = hasProjections
330
+ ? rawProjections.timeline.findIndex((item, index) => !normalizeWorkflowTimelineProjection(item, index))
331
+ : -1;
332
+ if (invalidTimelineIndex >= 0) {
333
+ return { error: `projections.timeline[${invalidTimelineIndex}] requires kind and id` };
334
+ }
335
+ const projections = hasProjections ? normalizeWorkflowProjectionState(rawProjections) : null;
336
+
337
+ const rawExtensions = plainObject(payload.extensions);
338
+ const hasExtensions = Object.keys(rawExtensions).length > 0;
339
+ const extensions = hasExtensions ? normalizeWorkflowExtensions(rawExtensions) : null;
340
+ if (hasExtensions && !Object.keys(extensions).length) {
341
+ return { error: "extensions requires at least one valid namespace object" };
342
+ }
208
343
 
209
344
  const rawGlobalState = plainObject(payload.globalState || payload.global_state);
210
345
  const hasGlobalState = Object.keys(rawGlobalState).length > 0;
@@ -215,8 +350,8 @@ export function normalizeWorkflowReport(payload = {}) {
215
350
  if (hasGlobalState && !Object.keys(globalStatePatch).length && !globalStateRemove.length) {
216
351
  return { error: "globalState requires patch or remove" };
217
352
  }
218
- if (!action && !artifacts.length && !hasGlobalState) {
219
- return { error: "Workflow report requires action, artifacts, or globalState" };
353
+ if (!action && !artifacts.length && !hasGlobalState && !hasProjections && !hasExtensions && !hasObservation) {
354
+ return { error: "Workflow report requires observation, action, artifacts, globalState, projections, or extensions" };
220
355
  }
221
356
 
222
357
  const idempotencyKey = cleanString(
@@ -229,7 +364,7 @@ export function normalizeWorkflowReport(payload = {}) {
229
364
  const event = {
230
365
  schemaVersion,
231
366
  type: "workflow-report",
232
- source: cleanString(payload.source || "agentflow-cli", 120) || "agentflow-cli",
367
+ source,
233
368
  workflow,
234
369
  workflowKey: workflow.key,
235
370
  aggregateByStage: Boolean(action),
@@ -257,6 +392,8 @@ export function normalizeWorkflowReport(payload = {}) {
257
392
  scope: "global",
258
393
  }),
259
394
  artifacts,
395
+ ...(hasProjections ? { projections } : {}),
396
+ ...(hasExtensions ? { extensionsPatch: extensions } : {}),
260
397
  ...(hasGlobalState ? {
261
398
  globalStatePatch,
262
399
  globalStateRemove,
@@ -268,6 +405,10 @@ export function normalizeWorkflowReport(payload = {}) {
268
405
  workflow,
269
406
  action,
270
407
  artifacts,
408
+ projections,
409
+ extensions,
410
+ observation,
411
+ hasRuntimeUpdate: Boolean(action || artifacts.length || hasGlobalState || hasProjections || hasExtensions),
271
412
  globalState: hasGlobalState ? {
272
413
  mode: "merge",
273
414
  patch: globalStatePatch,
@@ -408,20 +549,22 @@ export function mergeWorkflowArtifactLists(left = [], right = [], defaultScope =
408
549
  };
409
550
  const artifactAliases = (artifact) => {
410
551
  const aliases = [];
552
+ const producer = cleanString(artifact?.producer || artifact?.reportSource || artifact?.report_source, 120).toLowerCase();
553
+ const ownedAlias = (alias) => producer ? `producer:${producer}:${alias}` : alias;
411
554
  const explicitKey = cleanString(
412
555
  artifact?.key || artifact?.artifactKey || artifact?.artifact_key,
413
556
  500,
414
557
  );
415
- if (explicitKey) aliases.push(`key:${explicitKey}`);
558
+ if (explicitKey) aliases.push(ownedAlias(`key:${explicitKey}`));
416
559
  const url = normalizedUrl(
417
560
  artifact?.canonicalUrl
418
561
  || artifact?.canonical_url
419
562
  || artifact?.href
420
563
  || artifact?.url,
421
564
  );
422
- if (url) aliases.push(`url:${url}`);
565
+ if (url) aliases.push(ownedAlias(`url:${url}`));
423
566
  const artifactPath = cleanString(artifact?.path, 4000);
424
- if (artifactPath) aliases.push(`path:${artifactPath}`);
567
+ if (artifactPath) aliases.push(ownedAlias(`path:${artifactPath}`));
425
568
  return aliases;
426
569
  };
427
570
  const add = (artifact) => {
@@ -448,7 +591,7 @@ export function mergeWorkflowArtifactLists(left = [], right = [], defaultScope =
448
591
  return out;
449
592
  }
450
593
 
451
- export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtimeEvents = []) {
594
+ export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtimeEvents = [], projections = {}, extensions = {}) {
452
595
  const events = (Array.isArray(runtimeEvents) ? runtimeEvents : []).map((event) => ({
453
596
  id: event?.id || "",
454
597
  action: event?.action || event?.actionId || "",
@@ -457,10 +600,12 @@ export function workflowRuntimeRevision(globalState = {}, artifacts = [], runtim
457
600
  artifacts: event?.artifacts || [],
458
601
  globalStatePatch: event?.globalStatePatch || {},
459
602
  globalStateRemove: event?.globalStateRemove || [],
603
+ projections: event?.projections || {},
604
+ extensionsPatch: event?.extensionsPatch || {},
460
605
  }));
461
606
  const hash = crypto
462
607
  .createHash("sha256")
463
- .update(JSON.stringify(stableValue({ globalState, artifacts, events })))
608
+ .update(JSON.stringify(stableValue({ globalState, artifacts, projections, extensions, events })))
464
609
  .digest("hex")
465
610
  .slice(0, 24);
466
611
  return `runtime:${hash}`;