@fieldwangai/agentflow 0.1.136 → 0.1.137
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 +172 -17
- package/bin/lib/ui-server.mjs +585 -59
- package/bin/lib/workflow-report.mjs +290 -30
- package/builtin/web-ui/dist/assets/index-CQsrSc3u.css +1 -0
- package/builtin/web-ui/dist/assets/index-DQvqqAeQ.js +590 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-cli/SKILL.md +3 -1
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +33 -4
- package/skills/agentflow-cli/scripts/workflow-report-client.mjs +3 -0
- package/skills/agentflow-workflow-report/SKILL.md +17 -17
- package/skills/agentflow-workflow-report/references/protocol.md +118 -45
- package/builtin/web-ui/dist/assets/index-HdswcJWY.js +0 -565
- package/builtin/web-ui/dist/assets/index-KIGufzQf.css +0 -1
|
@@ -4,7 +4,7 @@ import path from "path";
|
|
|
4
4
|
import { getAgentflowDataRoot } from "./paths.mjs";
|
|
5
5
|
import { getTeamForUser } from "./teams.mjs";
|
|
6
6
|
|
|
7
|
-
const REGISTRY_VERSION =
|
|
7
|
+
const REGISTRY_VERSION = 2;
|
|
8
8
|
|
|
9
9
|
function registryPath() {
|
|
10
10
|
return path.join(getAgentflowDataRoot(), "collaboration", "prd-workflows.json");
|
|
@@ -49,10 +49,45 @@ function normalizeShareToken(value) {
|
|
|
49
49
|
return String(value || "").trim();
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function normalizeMemberRole(value) {
|
|
53
|
+
const role = String(value || "").trim().toLowerCase();
|
|
54
|
+
if (role === "reporter" || role === "editor") return "reporter";
|
|
55
|
+
if (role === "viewer") return "viewer";
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizedMemberMap(value) {
|
|
60
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
61
|
+
return Object.fromEntries(Object.entries(value)
|
|
62
|
+
.map(([userId, role]) => [normalizeUserId(userId), normalizeMemberRole(role)])
|
|
63
|
+
.filter(([userId, role]) => userId && role));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function collaborationMembers(record) {
|
|
67
|
+
const ownerId = normalizeUserId(record?.ownerId);
|
|
68
|
+
const explicit = normalizedMemberMap(record?.members);
|
|
69
|
+
const derived = normalizedMemberMap(record?.derivedMembers);
|
|
70
|
+
const rows = new Map();
|
|
71
|
+
if (ownerId) rows.set(ownerId, {
|
|
72
|
+
userId: ownerId,
|
|
73
|
+
role: "owner",
|
|
74
|
+
source: String(record?.ownerSource || "legacy"),
|
|
75
|
+
});
|
|
76
|
+
for (const [userId, role] of Object.entries(derived)) {
|
|
77
|
+
if (userId === ownerId) continue;
|
|
78
|
+
rows.set(userId, { userId, role: role === "reporter" ? "reporter" : "viewer", source: "tapd" });
|
|
79
|
+
}
|
|
80
|
+
for (const [userId, role] of Object.entries(explicit)) {
|
|
81
|
+
if (userId === ownerId) continue;
|
|
82
|
+
rows.set(userId, { userId, role, source: "explicit" });
|
|
83
|
+
}
|
|
84
|
+
return [...rows.values()];
|
|
85
|
+
}
|
|
86
|
+
|
|
52
87
|
function publicWorkflow(record, userId = "") {
|
|
53
88
|
if (!record) return null;
|
|
54
89
|
const actorId = normalizeUserId(userId);
|
|
55
|
-
const members = record
|
|
90
|
+
const members = collaborationMembers(record);
|
|
56
91
|
const access = prdWorkflowCollaborationAccess(record, actorId);
|
|
57
92
|
return {
|
|
58
93
|
id: record.id,
|
|
@@ -61,8 +96,17 @@ function publicWorkflow(record, userId = "") {
|
|
|
61
96
|
role: access.role,
|
|
62
97
|
accessSource: access.source,
|
|
63
98
|
teamId: String(record.teamId || getTeamForUser(record.ownerId)?.id || ""),
|
|
64
|
-
|
|
65
|
-
|
|
99
|
+
ownerSource: String(record.ownerSource || "legacy"),
|
|
100
|
+
memberCount: members.length,
|
|
101
|
+
members,
|
|
102
|
+
authority: record.authority && typeof record.authority === "object" ? {
|
|
103
|
+
type: String(record.authority.type || ""),
|
|
104
|
+
observedAt: String(record.authority.observedAt || ""),
|
|
105
|
+
revision: String(record.authority.revision || ""),
|
|
106
|
+
unresolvedParticipants: Array.isArray(record.authority.unresolvedParticipants)
|
|
107
|
+
? record.authority.unresolvedParticipants.map((value) => String(value || "")).filter(Boolean)
|
|
108
|
+
: [],
|
|
109
|
+
} : null,
|
|
66
110
|
shareActive: Boolean(record.shareToken),
|
|
67
111
|
shareCreatedAt: record.shareCreatedAt || "",
|
|
68
112
|
createdAt: record.createdAt || "",
|
|
@@ -73,16 +117,26 @@ function publicWorkflow(record, userId = "") {
|
|
|
73
117
|
export function prdWorkflowCollaborationAccess(record, userId) {
|
|
74
118
|
if (!record) return { allowed: true, role: "" };
|
|
75
119
|
const actorId = normalizeUserId(userId);
|
|
76
|
-
const
|
|
120
|
+
const explicitRole = normalizeMemberRole(record.members?.[actorId]);
|
|
121
|
+
const derivedRole = normalizeMemberRole(record.derivedMembers?.[actorId]);
|
|
122
|
+
const directRole = actorId === record.ownerId ? "owner" : (explicitRole || derivedRole);
|
|
77
123
|
const actorTeam = getTeamForUser(actorId);
|
|
78
124
|
const recordTeamId = String(record.teamId || getTeamForUser(record.ownerId)?.id || "");
|
|
79
125
|
const teamRole = actorTeam?.status === "active" && actorTeam.id === recordTeamId ? "viewer" : "";
|
|
80
126
|
const role = directRole || teamRole;
|
|
81
127
|
return {
|
|
82
|
-
allowed: role === "owner" || role === "
|
|
83
|
-
writable: role === "owner" || role === "
|
|
128
|
+
allowed: role === "owner" || role === "reporter" || role === "viewer",
|
|
129
|
+
writable: role === "owner" || role === "reporter",
|
|
84
130
|
role,
|
|
85
|
-
source:
|
|
131
|
+
source: actorId === record.ownerId
|
|
132
|
+
? String(record.ownerSource || "legacy")
|
|
133
|
+
: explicitRole
|
|
134
|
+
? "explicit"
|
|
135
|
+
: derivedRole
|
|
136
|
+
? "tapd"
|
|
137
|
+
: teamRole
|
|
138
|
+
? "team"
|
|
139
|
+
: "",
|
|
86
140
|
teamId: teamRole ? recordTeamId : "",
|
|
87
141
|
};
|
|
88
142
|
}
|
|
@@ -91,6 +145,14 @@ export function getPrdWorkflowCollaborationById(workflowId) {
|
|
|
91
145
|
return readRegistry().workflows[String(workflowId || "").trim()] || null;
|
|
92
146
|
}
|
|
93
147
|
|
|
148
|
+
export function getPrdWorkflowCollaborationByTapdId(tapdId) {
|
|
149
|
+
const normalizedTapdId = normalizeTapdId(tapdId);
|
|
150
|
+
if (!normalizedTapdId) return null;
|
|
151
|
+
return Object.values(readRegistry().workflows)
|
|
152
|
+
.filter((record) => record?.tapdId === normalizedTapdId)
|
|
153
|
+
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")))[0] || null;
|
|
154
|
+
}
|
|
155
|
+
|
|
94
156
|
export function getPrdWorkflowCollaborationByShareToken(shareToken) {
|
|
95
157
|
const token = normalizeShareToken(shareToken);
|
|
96
158
|
if (!token) return null;
|
|
@@ -120,7 +182,9 @@ export function listPrdWorkflowCollaborationsForUser(userId) {
|
|
|
120
182
|
if (!actorId) return [];
|
|
121
183
|
return Object.values(readRegistry().workflows)
|
|
122
184
|
.filter((record) => (
|
|
123
|
-
record?.ownerId === actorId
|
|
185
|
+
record?.ownerId === actorId
|
|
186
|
+
|| Boolean(normalizeMemberRole(record?.members?.[actorId]))
|
|
187
|
+
|| Boolean(normalizeMemberRole(record?.derivedMembers?.[actorId]))
|
|
124
188
|
))
|
|
125
189
|
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")));
|
|
126
190
|
}
|
|
@@ -139,18 +203,23 @@ export function ensurePrdWorkflowCollaboration({ tapdId, userId }) {
|
|
|
139
203
|
if (!ownerId) return { error: "Authentication required", status: 401 };
|
|
140
204
|
if (!normalizedTapdId) return { error: "Missing tapdId", status: 400 };
|
|
141
205
|
const registry = readRegistry();
|
|
142
|
-
let record = Object.values(registry.workflows).find((item) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
let record = Object.values(registry.workflows).find((item) => item?.tapdId === normalizedTapdId) || null;
|
|
207
|
+
if (record) {
|
|
208
|
+
const access = prdWorkflowCollaborationAccess(record, ownerId);
|
|
209
|
+
if (!access.allowed) return { error: "Workflow already belongs to another owner", status: 403 };
|
|
210
|
+
return { record, workflow: publicWorkflow(record, ownerId), created: false };
|
|
211
|
+
}
|
|
146
212
|
const now = new Date().toISOString();
|
|
147
213
|
const workflowId = `prd_${crypto.randomBytes(12).toString("hex")}`;
|
|
148
214
|
record = {
|
|
149
215
|
id: workflowId,
|
|
150
216
|
tapdId: normalizedTapdId,
|
|
151
217
|
ownerId,
|
|
218
|
+
ownerSource: "legacy",
|
|
219
|
+
stateOwnerId: ownerId,
|
|
152
220
|
teamId: String(getTeamForUser(ownerId)?.id || ""),
|
|
153
|
-
members: {
|
|
221
|
+
members: {},
|
|
222
|
+
derivedMembers: {},
|
|
154
223
|
createdAt: now,
|
|
155
224
|
updatedAt: now,
|
|
156
225
|
};
|
|
@@ -219,7 +288,7 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
219
288
|
workflowId,
|
|
220
289
|
userId,
|
|
221
290
|
memberUserId,
|
|
222
|
-
role = "
|
|
291
|
+
role = "reporter",
|
|
223
292
|
}) {
|
|
224
293
|
const registry = readRegistry();
|
|
225
294
|
const record = registry.workflows[String(workflowId || "").trim()];
|
|
@@ -233,6 +302,10 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
233
302
|
if (targetId === record.ownerId) {
|
|
234
303
|
return { workflow: publicWorkflow(record, actorId), unchanged: true };
|
|
235
304
|
}
|
|
305
|
+
const normalizedRole = normalizeMemberRole(role);
|
|
306
|
+
if (!normalizedRole) {
|
|
307
|
+
return { error: "Workflow member role must be reporter or viewer", status: 400 };
|
|
308
|
+
}
|
|
236
309
|
const conflicting = Object.values(registry.workflows).find((item) => (
|
|
237
310
|
item?.id !== record.id
|
|
238
311
|
&& item?.tapdId === record.tapdId
|
|
@@ -242,12 +315,92 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
242
315
|
return { error: "该用户已经加入同一 TAPD ID 的另一个 Workflow 分享", status: 409 };
|
|
243
316
|
}
|
|
244
317
|
record.members = record.members && typeof record.members === "object" ? record.members : {};
|
|
245
|
-
record.members[targetId] =
|
|
318
|
+
record.members[targetId] = normalizedRole;
|
|
246
319
|
record.updatedAt = new Date().toISOString();
|
|
247
320
|
writeRegistry(registry);
|
|
248
321
|
return { workflow: publicWorkflow(record, actorId), memberUserId: targetId };
|
|
249
322
|
}
|
|
250
323
|
|
|
324
|
+
export function syncPrdWorkflowAuthority({
|
|
325
|
+
tapdId,
|
|
326
|
+
userId,
|
|
327
|
+
isAdmin = false,
|
|
328
|
+
authority = "tapd",
|
|
329
|
+
ownerUserId,
|
|
330
|
+
ownerIdentity = "",
|
|
331
|
+
participantUserIds = [],
|
|
332
|
+
participantIdentities = [],
|
|
333
|
+
unresolvedParticipants = [],
|
|
334
|
+
observedAt = "",
|
|
335
|
+
revision = "",
|
|
336
|
+
}) {
|
|
337
|
+
const actorId = normalizeUserId(userId);
|
|
338
|
+
const normalizedTapdId = normalizeTapdId(tapdId);
|
|
339
|
+
const normalizedOwnerId = normalizeUserId(ownerUserId);
|
|
340
|
+
const authorityType = String(authority || "tapd").trim().toLowerCase();
|
|
341
|
+
if (!actorId) return { error: "Authentication required", status: 401 };
|
|
342
|
+
if (!normalizedTapdId) return { error: "Missing tapdId", status: 400 };
|
|
343
|
+
if (authorityType !== "tapd") return { error: `Unsupported Workflow authority: ${authorityType}`, status: 400 };
|
|
344
|
+
if (!normalizedOwnerId) return { error: "TAPD owner must be a registered AgentFlow user", status: 422 };
|
|
345
|
+
const normalizedObservedAt = String(observedAt || "").trim();
|
|
346
|
+
if (normalizedObservedAt && !Number.isFinite(Date.parse(normalizedObservedAt))) {
|
|
347
|
+
return { error: "observedAt must be an ISO-compatible date", status: 400 };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const registry = readRegistry();
|
|
351
|
+
let record = Object.values(registry.workflows).find((item) => item?.tapdId === normalizedTapdId) || null;
|
|
352
|
+
const previousOwnerId = normalizeUserId(record?.ownerId);
|
|
353
|
+
if (!record && actorId !== normalizedOwnerId && isAdmin !== true) {
|
|
354
|
+
return { error: "Only the TAPD owner can initialize Workflow permissions", status: 403 };
|
|
355
|
+
}
|
|
356
|
+
if (record && actorId !== previousOwnerId && isAdmin !== true) {
|
|
357
|
+
return { error: "Only the current Workflow owner or an administrator can synchronize TAPD permissions", status: 403 };
|
|
358
|
+
}
|
|
359
|
+
const storedObservedAt = String(record?.authority?.observedAt || "").trim();
|
|
360
|
+
if (storedObservedAt && normalizedObservedAt && Date.parse(normalizedObservedAt) < Date.parse(storedObservedAt)) {
|
|
361
|
+
return { error: "TAPD permission snapshot is older than the stored snapshot", status: 409 };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const now = new Date().toISOString();
|
|
365
|
+
if (!record) {
|
|
366
|
+
const workflowId = `prd_${crypto.randomBytes(12).toString("hex")}`;
|
|
367
|
+
record = {
|
|
368
|
+
id: workflowId,
|
|
369
|
+
tapdId: normalizedTapdId,
|
|
370
|
+
stateOwnerId: normalizedOwnerId,
|
|
371
|
+
members: {},
|
|
372
|
+
createdAt: now,
|
|
373
|
+
};
|
|
374
|
+
registry.workflows[workflowId] = record;
|
|
375
|
+
}
|
|
376
|
+
const participants = [...new Set(participantUserIds.map(normalizeUserId).filter(Boolean))]
|
|
377
|
+
.filter((id) => id !== normalizedOwnerId);
|
|
378
|
+
record.ownerId = normalizedOwnerId;
|
|
379
|
+
record.stateOwnerId = normalizeUserId(record.stateOwnerId || previousOwnerId || normalizedOwnerId);
|
|
380
|
+
record.ownerSource = "tapd";
|
|
381
|
+
record.teamId = String(getTeamForUser(normalizedOwnerId)?.id || "");
|
|
382
|
+
record.members = normalizedMemberMap(record.members);
|
|
383
|
+
delete record.members[normalizedOwnerId];
|
|
384
|
+
record.derivedMembers = Object.fromEntries(participants.map((id) => [id, "viewer"]));
|
|
385
|
+
record.authority = {
|
|
386
|
+
type: "tapd",
|
|
387
|
+
ownerIdentity: String(ownerIdentity || "").trim(),
|
|
388
|
+
participantIdentities: [...new Set(participantIdentities.map((value) => String(value || "").trim()).filter(Boolean))],
|
|
389
|
+
unresolvedParticipants: [...new Set(unresolvedParticipants.map((value) => String(value || "").trim()).filter(Boolean))],
|
|
390
|
+
observedAt: normalizedObservedAt || now,
|
|
391
|
+
revision: String(revision || "").trim(),
|
|
392
|
+
};
|
|
393
|
+
record.updatedAt = now;
|
|
394
|
+
writeRegistry(registry);
|
|
395
|
+
return {
|
|
396
|
+
record,
|
|
397
|
+
workflow: publicWorkflow(record, actorId),
|
|
398
|
+
created: !previousOwnerId,
|
|
399
|
+
ownerChanged: Boolean(previousOwnerId && previousOwnerId !== normalizedOwnerId),
|
|
400
|
+
previousOwnerId,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
251
404
|
export function removePrdWorkflowCollaborationMember({
|
|
252
405
|
workflowId,
|
|
253
406
|
userId,
|
|
@@ -270,9 +423,11 @@ export function removePrdWorkflowCollaborationMember({
|
|
|
270
423
|
delete record.members[targetId];
|
|
271
424
|
record.updatedAt = new Date().toISOString();
|
|
272
425
|
writeRegistry(registry);
|
|
426
|
+
const selfRemoved = targetId === actorId;
|
|
427
|
+
const stillAllowed = prdWorkflowCollaborationAccess(record, actorId).allowed;
|
|
273
428
|
return {
|
|
274
429
|
workflow: publicWorkflow(record, actorId),
|
|
275
430
|
removedUserId: targetId,
|
|
276
|
-
left:
|
|
431
|
+
left: selfRemoved && !stillAllowed,
|
|
277
432
|
};
|
|
278
433
|
}
|