@fieldwangai/agentflow 0.1.133 → 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.
- package/bin/lib/prd-workflow-collaboration.mjs +24 -3
- package/bin/lib/teams.mjs +160 -0
- package/bin/lib/ui-server.mjs +297 -5
- package/bin/lib/workflow-report.mjs +88 -4
- package/bin/lib/workspace-collaboration.mjs +52 -2
- package/builtin/web-ui/dist/assets/index-BFQVTav-.css +1 -0
- package/builtin/web-ui/dist/assets/index-CmpbCHAj.js +420 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-cli/SKILL.md +3 -70
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-workflow-report/SKILL.md +77 -0
- package/skills/agentflow-workflow-report/agents/openai.yaml +4 -0
- package/skills/agentflow-workflow-report/references/protocol.md +337 -0
- package/builtin/web-ui/dist/assets/index-CtH_zTEj.js +0 -350
- package/builtin/web-ui/dist/assets/index-DQA8LAlU.css +0 -1
|
@@ -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
|
|
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}`;
|
|
@@ -2,6 +2,7 @@ import crypto from "crypto";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { getAgentflowDataRoot } from "./paths.mjs";
|
|
5
|
+
import { getTeamForUser } from "./teams.mjs";
|
|
5
6
|
|
|
6
7
|
const REGISTRY_VERSION = 1;
|
|
7
8
|
const DEFAULT_INVITE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -67,15 +68,19 @@ function publicWorkspace(record, userId = "") {
|
|
|
67
68
|
if (!record) return null;
|
|
68
69
|
const actorId = normalizeUserId(userId);
|
|
69
70
|
const members = record.members && typeof record.members === "object" ? record.members : {};
|
|
71
|
+
const access = workspaceCollaborationAccess(record, actorId);
|
|
70
72
|
return {
|
|
71
73
|
id: record.id,
|
|
72
74
|
flowId: record.flowId,
|
|
73
75
|
flowSource: record.projectSource || record.flowSource || "workspace",
|
|
74
76
|
archived: record.archived === true,
|
|
75
77
|
ownerId: record.ownerId,
|
|
76
|
-
role:
|
|
78
|
+
role: access.role,
|
|
79
|
+
accessSource: access.source,
|
|
80
|
+
teamId: access.teamId,
|
|
77
81
|
memberCount: Object.keys(members).length,
|
|
78
82
|
members: Object.entries(members).map(([id, role]) => ({ userId: id, role })),
|
|
83
|
+
teamShares: Object.entries(record.teamShares || {}).map(([teamId, role]) => ({ teamId, role })),
|
|
79
84
|
createdAt: record.createdAt || "",
|
|
80
85
|
updatedAt: record.updatedAt || "",
|
|
81
86
|
};
|
|
@@ -98,12 +103,19 @@ export function getWorkspaceCollaborationById(workspaceId) {
|
|
|
98
103
|
export function workspaceCollaborationAccess(record, userId) {
|
|
99
104
|
if (!record) return { allowed: true, role: "" };
|
|
100
105
|
const id = normalizeUserId(userId);
|
|
101
|
-
const
|
|
106
|
+
const directRole = id === record.ownerId ? "owner" : String(record.members?.[id] || "");
|
|
107
|
+
const team = getTeamForUser(id);
|
|
108
|
+
const teamRole = team ? String(record.teamShares?.[team.id] || "") : "";
|
|
109
|
+
const role = directRole === "owner" || directRole === "editor" || teamRole === "editor"
|
|
110
|
+
? directRole === "owner" ? "owner" : "editor"
|
|
111
|
+
: directRole === "viewer" || teamRole === "viewer" ? "viewer" : "";
|
|
102
112
|
return {
|
|
103
113
|
allowed: role === "owner" || role === "editor" || role === "viewer",
|
|
104
114
|
writable: role === "owner" || role === "editor",
|
|
105
115
|
runnable: role === "owner" || role === "editor",
|
|
106
116
|
role,
|
|
117
|
+
source: directRole ? "member" : teamRole ? "team" : "",
|
|
118
|
+
teamId: teamRole ? team?.id || "" : "",
|
|
107
119
|
};
|
|
108
120
|
}
|
|
109
121
|
|
|
@@ -256,6 +268,44 @@ export function addWorkspaceCollaborationMember({
|
|
|
256
268
|
return { workspace: publicWorkspace(record, actorId), memberUserId: targetId };
|
|
257
269
|
}
|
|
258
270
|
|
|
271
|
+
export function setWorkspaceCollaborationTeamShare({
|
|
272
|
+
workspaceId,
|
|
273
|
+
userId,
|
|
274
|
+
teamId,
|
|
275
|
+
role = "viewer",
|
|
276
|
+
}) {
|
|
277
|
+
const registry = readRegistry();
|
|
278
|
+
const record = registry.workspaces[String(workspaceId || "").trim()];
|
|
279
|
+
if (!record) return { error: "Workspace collaboration not found", status: 404 };
|
|
280
|
+
const actorId = normalizeUserId(userId);
|
|
281
|
+
const targetTeamId = String(teamId || "").trim();
|
|
282
|
+
if (workspaceCollaborationAccess(record, actorId).role !== "owner") {
|
|
283
|
+
return { error: "Only the workspace owner can share with a team", status: 403 };
|
|
284
|
+
}
|
|
285
|
+
if (!targetTeamId) return { error: "Missing teamId", status: 400 };
|
|
286
|
+
record.teamShares = record.teamShares && typeof record.teamShares === "object" ? record.teamShares : {};
|
|
287
|
+
record.teamShares[targetTeamId] = role === "editor" ? "editor" : "viewer";
|
|
288
|
+
record.updatedAt = new Date().toISOString();
|
|
289
|
+
writeRegistry(registry);
|
|
290
|
+
return { workspace: publicWorkspace(record, actorId), teamId: targetTeamId };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function removeWorkspaceCollaborationTeamShare({ workspaceId, userId, teamId }) {
|
|
294
|
+
const registry = readRegistry();
|
|
295
|
+
const record = registry.workspaces[String(workspaceId || "").trim()];
|
|
296
|
+
if (!record) return { error: "Workspace collaboration not found", status: 404 };
|
|
297
|
+
const actorId = normalizeUserId(userId);
|
|
298
|
+
const targetTeamId = String(teamId || "").trim();
|
|
299
|
+
if (workspaceCollaborationAccess(record, actorId).role !== "owner") {
|
|
300
|
+
return { error: "Only the workspace owner can revoke a team share", status: 403 };
|
|
301
|
+
}
|
|
302
|
+
const removed = Boolean(record.teamShares?.[targetTeamId]);
|
|
303
|
+
if (removed) delete record.teamShares[targetTeamId];
|
|
304
|
+
record.updatedAt = new Date().toISOString();
|
|
305
|
+
writeRegistry(registry);
|
|
306
|
+
return { workspace: publicWorkspace(record, actorId), teamId: targetTeamId, removed };
|
|
307
|
+
}
|
|
308
|
+
|
|
259
309
|
export function removeWorkspaceCollaborationMember({
|
|
260
310
|
workspaceId,
|
|
261
311
|
userId,
|