@fieldwangai/agentflow 0.1.136 → 0.1.138
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 +218 -17
- package/bin/lib/ui-server.mjs +943 -59
- package/bin/lib/workflow-report.mjs +290 -30
- package/builtin/web-ui/dist/assets/WorkflowAssistantThread-B3thaqH2.js +6 -0
- package/builtin/web-ui/dist/assets/index-COX1zMwq.css +1 -0
- package/builtin/web-ui/dist/assets/index-YS4XOpXF.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 = 3;
|
|
8
8
|
|
|
9
9
|
function registryPath() {
|
|
10
10
|
return path.join(getAgentflowDataRoot(), "collaboration", "prd-workflows.json");
|
|
@@ -49,10 +49,65 @@ 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 normalizeKnowledgeBindings(value) {
|
|
67
|
+
if (!Array.isArray(value)) return [];
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
return value.flatMap((entry) => {
|
|
70
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return [];
|
|
71
|
+
const workspaceId = String(entry.workspaceId || entry.id || "").trim();
|
|
72
|
+
if (!workspaceId || seen.has(workspaceId)) return [];
|
|
73
|
+
seen.add(workspaceId);
|
|
74
|
+
return [{
|
|
75
|
+
workspaceId,
|
|
76
|
+
label: String(entry.label || workspaceId).trim() || workspaceId,
|
|
77
|
+
kind: String(entry.kind || "git").trim().toLowerCase(),
|
|
78
|
+
type: String(entry.type || "knowledge").trim().toLowerCase(),
|
|
79
|
+
branch: String(entry.branch || "").trim(),
|
|
80
|
+
boundBy: normalizeUserId(entry.boundBy),
|
|
81
|
+
boundAt: String(entry.boundAt || "").trim(),
|
|
82
|
+
}];
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function collaborationMembers(record) {
|
|
87
|
+
const ownerId = normalizeUserId(record?.ownerId);
|
|
88
|
+
const explicit = normalizedMemberMap(record?.members);
|
|
89
|
+
const derived = normalizedMemberMap(record?.derivedMembers);
|
|
90
|
+
const rows = new Map();
|
|
91
|
+
if (ownerId) rows.set(ownerId, {
|
|
92
|
+
userId: ownerId,
|
|
93
|
+
role: "owner",
|
|
94
|
+
source: String(record?.ownerSource || "legacy"),
|
|
95
|
+
});
|
|
96
|
+
for (const [userId, role] of Object.entries(derived)) {
|
|
97
|
+
if (userId === ownerId) continue;
|
|
98
|
+
rows.set(userId, { userId, role: role === "reporter" ? "reporter" : "viewer", source: "tapd" });
|
|
99
|
+
}
|
|
100
|
+
for (const [userId, role] of Object.entries(explicit)) {
|
|
101
|
+
if (userId === ownerId) continue;
|
|
102
|
+
rows.set(userId, { userId, role, source: "explicit" });
|
|
103
|
+
}
|
|
104
|
+
return [...rows.values()];
|
|
105
|
+
}
|
|
106
|
+
|
|
52
107
|
function publicWorkflow(record, userId = "") {
|
|
53
108
|
if (!record) return null;
|
|
54
109
|
const actorId = normalizeUserId(userId);
|
|
55
|
-
const members = record
|
|
110
|
+
const members = collaborationMembers(record);
|
|
56
111
|
const access = prdWorkflowCollaborationAccess(record, actorId);
|
|
57
112
|
return {
|
|
58
113
|
id: record.id,
|
|
@@ -61,8 +116,18 @@ function publicWorkflow(record, userId = "") {
|
|
|
61
116
|
role: access.role,
|
|
62
117
|
accessSource: access.source,
|
|
63
118
|
teamId: String(record.teamId || getTeamForUser(record.ownerId)?.id || ""),
|
|
64
|
-
|
|
65
|
-
|
|
119
|
+
ownerSource: String(record.ownerSource || "legacy"),
|
|
120
|
+
memberCount: members.length,
|
|
121
|
+
members,
|
|
122
|
+
authority: record.authority && typeof record.authority === "object" ? {
|
|
123
|
+
type: String(record.authority.type || ""),
|
|
124
|
+
observedAt: String(record.authority.observedAt || ""),
|
|
125
|
+
revision: String(record.authority.revision || ""),
|
|
126
|
+
unresolvedParticipants: Array.isArray(record.authority.unresolvedParticipants)
|
|
127
|
+
? record.authority.unresolvedParticipants.map((value) => String(value || "")).filter(Boolean)
|
|
128
|
+
: [],
|
|
129
|
+
} : null,
|
|
130
|
+
knowledgeBindings: normalizeKnowledgeBindings(record.knowledgeBindings),
|
|
66
131
|
shareActive: Boolean(record.shareToken),
|
|
67
132
|
shareCreatedAt: record.shareCreatedAt || "",
|
|
68
133
|
createdAt: record.createdAt || "",
|
|
@@ -73,16 +138,26 @@ function publicWorkflow(record, userId = "") {
|
|
|
73
138
|
export function prdWorkflowCollaborationAccess(record, userId) {
|
|
74
139
|
if (!record) return { allowed: true, role: "" };
|
|
75
140
|
const actorId = normalizeUserId(userId);
|
|
76
|
-
const
|
|
141
|
+
const explicitRole = normalizeMemberRole(record.members?.[actorId]);
|
|
142
|
+
const derivedRole = normalizeMemberRole(record.derivedMembers?.[actorId]);
|
|
143
|
+
const directRole = actorId === record.ownerId ? "owner" : (explicitRole || derivedRole);
|
|
77
144
|
const actorTeam = getTeamForUser(actorId);
|
|
78
145
|
const recordTeamId = String(record.teamId || getTeamForUser(record.ownerId)?.id || "");
|
|
79
146
|
const teamRole = actorTeam?.status === "active" && actorTeam.id === recordTeamId ? "viewer" : "";
|
|
80
147
|
const role = directRole || teamRole;
|
|
81
148
|
return {
|
|
82
|
-
allowed: role === "owner" || role === "
|
|
83
|
-
writable: role === "owner" || role === "
|
|
149
|
+
allowed: role === "owner" || role === "reporter" || role === "viewer",
|
|
150
|
+
writable: role === "owner" || role === "reporter",
|
|
84
151
|
role,
|
|
85
|
-
source:
|
|
152
|
+
source: actorId === record.ownerId
|
|
153
|
+
? String(record.ownerSource || "legacy")
|
|
154
|
+
: explicitRole
|
|
155
|
+
? "explicit"
|
|
156
|
+
: derivedRole
|
|
157
|
+
? "tapd"
|
|
158
|
+
: teamRole
|
|
159
|
+
? "team"
|
|
160
|
+
: "",
|
|
86
161
|
teamId: teamRole ? recordTeamId : "",
|
|
87
162
|
};
|
|
88
163
|
}
|
|
@@ -91,6 +166,14 @@ export function getPrdWorkflowCollaborationById(workflowId) {
|
|
|
91
166
|
return readRegistry().workflows[String(workflowId || "").trim()] || null;
|
|
92
167
|
}
|
|
93
168
|
|
|
169
|
+
export function getPrdWorkflowCollaborationByTapdId(tapdId) {
|
|
170
|
+
const normalizedTapdId = normalizeTapdId(tapdId);
|
|
171
|
+
if (!normalizedTapdId) return null;
|
|
172
|
+
return Object.values(readRegistry().workflows)
|
|
173
|
+
.filter((record) => record?.tapdId === normalizedTapdId)
|
|
174
|
+
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")))[0] || null;
|
|
175
|
+
}
|
|
176
|
+
|
|
94
177
|
export function getPrdWorkflowCollaborationByShareToken(shareToken) {
|
|
95
178
|
const token = normalizeShareToken(shareToken);
|
|
96
179
|
if (!token) return null;
|
|
@@ -120,7 +203,9 @@ export function listPrdWorkflowCollaborationsForUser(userId) {
|
|
|
120
203
|
if (!actorId) return [];
|
|
121
204
|
return Object.values(readRegistry().workflows)
|
|
122
205
|
.filter((record) => (
|
|
123
|
-
record?.ownerId === actorId
|
|
206
|
+
record?.ownerId === actorId
|
|
207
|
+
|| Boolean(normalizeMemberRole(record?.members?.[actorId]))
|
|
208
|
+
|| Boolean(normalizeMemberRole(record?.derivedMembers?.[actorId]))
|
|
124
209
|
))
|
|
125
210
|
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")));
|
|
126
211
|
}
|
|
@@ -139,18 +224,23 @@ export function ensurePrdWorkflowCollaboration({ tapdId, userId }) {
|
|
|
139
224
|
if (!ownerId) return { error: "Authentication required", status: 401 };
|
|
140
225
|
if (!normalizedTapdId) return { error: "Missing tapdId", status: 400 };
|
|
141
226
|
const registry = readRegistry();
|
|
142
|
-
let record = Object.values(registry.workflows).find((item) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
227
|
+
let record = Object.values(registry.workflows).find((item) => item?.tapdId === normalizedTapdId) || null;
|
|
228
|
+
if (record) {
|
|
229
|
+
const access = prdWorkflowCollaborationAccess(record, ownerId);
|
|
230
|
+
if (!access.allowed) return { error: "Workflow already belongs to another owner", status: 403 };
|
|
231
|
+
return { record, workflow: publicWorkflow(record, ownerId), created: false };
|
|
232
|
+
}
|
|
146
233
|
const now = new Date().toISOString();
|
|
147
234
|
const workflowId = `prd_${crypto.randomBytes(12).toString("hex")}`;
|
|
148
235
|
record = {
|
|
149
236
|
id: workflowId,
|
|
150
237
|
tapdId: normalizedTapdId,
|
|
151
238
|
ownerId,
|
|
239
|
+
ownerSource: "legacy",
|
|
240
|
+
stateOwnerId: ownerId,
|
|
152
241
|
teamId: String(getTeamForUser(ownerId)?.id || ""),
|
|
153
|
-
members: {
|
|
242
|
+
members: {},
|
|
243
|
+
derivedMembers: {},
|
|
154
244
|
createdAt: now,
|
|
155
245
|
updatedAt: now,
|
|
156
246
|
};
|
|
@@ -163,6 +253,31 @@ export function prdWorkflowCollaborationSummary(record, userId) {
|
|
|
163
253
|
return publicWorkflow(record, userId);
|
|
164
254
|
}
|
|
165
255
|
|
|
256
|
+
export function setPrdWorkflowKnowledgeBindings({ tapdId, userId, bindings = [] }) {
|
|
257
|
+
const record = getPrdWorkflowCollaborationForUser(tapdId, userId);
|
|
258
|
+
if (!record) return { error: "PRD Workflow collaboration not found", status: 404 };
|
|
259
|
+
const actorId = normalizeUserId(userId);
|
|
260
|
+
if (prdWorkflowCollaborationAccess(record, actorId).role !== "owner") {
|
|
261
|
+
return { error: "Only the Workflow owner can manage knowledge bindings", status: 403 };
|
|
262
|
+
}
|
|
263
|
+
const registry = readRegistry();
|
|
264
|
+
const stored = registry.workflows[record.id];
|
|
265
|
+
if (!stored) return { error: "PRD Workflow collaboration not found", status: 404 };
|
|
266
|
+
const now = new Date().toISOString();
|
|
267
|
+
stored.knowledgeBindings = normalizeKnowledgeBindings(bindings).map((binding) => ({
|
|
268
|
+
...binding,
|
|
269
|
+
boundBy: actorId,
|
|
270
|
+
boundAt: binding.boundAt || now,
|
|
271
|
+
}));
|
|
272
|
+
stored.updatedAt = now;
|
|
273
|
+
writeRegistry(registry);
|
|
274
|
+
return {
|
|
275
|
+
record: stored,
|
|
276
|
+
workflow: publicWorkflow(stored, actorId),
|
|
277
|
+
knowledgeBindings: normalizeKnowledgeBindings(stored.knowledgeBindings),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
166
281
|
export function ensurePrdWorkflowShareLink({ tapdId, userId }) {
|
|
167
282
|
const ensured = ensurePrdWorkflowCollaboration({ tapdId, userId });
|
|
168
283
|
if (ensured.error) return ensured;
|
|
@@ -219,7 +334,7 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
219
334
|
workflowId,
|
|
220
335
|
userId,
|
|
221
336
|
memberUserId,
|
|
222
|
-
role = "
|
|
337
|
+
role = "reporter",
|
|
223
338
|
}) {
|
|
224
339
|
const registry = readRegistry();
|
|
225
340
|
const record = registry.workflows[String(workflowId || "").trim()];
|
|
@@ -233,6 +348,10 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
233
348
|
if (targetId === record.ownerId) {
|
|
234
349
|
return { workflow: publicWorkflow(record, actorId), unchanged: true };
|
|
235
350
|
}
|
|
351
|
+
const normalizedRole = normalizeMemberRole(role);
|
|
352
|
+
if (!normalizedRole) {
|
|
353
|
+
return { error: "Workflow member role must be reporter or viewer", status: 400 };
|
|
354
|
+
}
|
|
236
355
|
const conflicting = Object.values(registry.workflows).find((item) => (
|
|
237
356
|
item?.id !== record.id
|
|
238
357
|
&& item?.tapdId === record.tapdId
|
|
@@ -242,12 +361,92 @@ export function addPrdWorkflowCollaborationMember({
|
|
|
242
361
|
return { error: "该用户已经加入同一 TAPD ID 的另一个 Workflow 分享", status: 409 };
|
|
243
362
|
}
|
|
244
363
|
record.members = record.members && typeof record.members === "object" ? record.members : {};
|
|
245
|
-
record.members[targetId] =
|
|
364
|
+
record.members[targetId] = normalizedRole;
|
|
246
365
|
record.updatedAt = new Date().toISOString();
|
|
247
366
|
writeRegistry(registry);
|
|
248
367
|
return { workflow: publicWorkflow(record, actorId), memberUserId: targetId };
|
|
249
368
|
}
|
|
250
369
|
|
|
370
|
+
export function syncPrdWorkflowAuthority({
|
|
371
|
+
tapdId,
|
|
372
|
+
userId,
|
|
373
|
+
isAdmin = false,
|
|
374
|
+
authority = "tapd",
|
|
375
|
+
ownerUserId,
|
|
376
|
+
ownerIdentity = "",
|
|
377
|
+
participantUserIds = [],
|
|
378
|
+
participantIdentities = [],
|
|
379
|
+
unresolvedParticipants = [],
|
|
380
|
+
observedAt = "",
|
|
381
|
+
revision = "",
|
|
382
|
+
}) {
|
|
383
|
+
const actorId = normalizeUserId(userId);
|
|
384
|
+
const normalizedTapdId = normalizeTapdId(tapdId);
|
|
385
|
+
const normalizedOwnerId = normalizeUserId(ownerUserId);
|
|
386
|
+
const authorityType = String(authority || "tapd").trim().toLowerCase();
|
|
387
|
+
if (!actorId) return { error: "Authentication required", status: 401 };
|
|
388
|
+
if (!normalizedTapdId) return { error: "Missing tapdId", status: 400 };
|
|
389
|
+
if (authorityType !== "tapd") return { error: `Unsupported Workflow authority: ${authorityType}`, status: 400 };
|
|
390
|
+
if (!normalizedOwnerId) return { error: "TAPD owner must be a registered AgentFlow user", status: 422 };
|
|
391
|
+
const normalizedObservedAt = String(observedAt || "").trim();
|
|
392
|
+
if (normalizedObservedAt && !Number.isFinite(Date.parse(normalizedObservedAt))) {
|
|
393
|
+
return { error: "observedAt must be an ISO-compatible date", status: 400 };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const registry = readRegistry();
|
|
397
|
+
let record = Object.values(registry.workflows).find((item) => item?.tapdId === normalizedTapdId) || null;
|
|
398
|
+
const previousOwnerId = normalizeUserId(record?.ownerId);
|
|
399
|
+
if (!record && actorId !== normalizedOwnerId && isAdmin !== true) {
|
|
400
|
+
return { error: "Only the TAPD owner can initialize Workflow permissions", status: 403 };
|
|
401
|
+
}
|
|
402
|
+
if (record && actorId !== previousOwnerId && isAdmin !== true) {
|
|
403
|
+
return { error: "Only the current Workflow owner or an administrator can synchronize TAPD permissions", status: 403 };
|
|
404
|
+
}
|
|
405
|
+
const storedObservedAt = String(record?.authority?.observedAt || "").trim();
|
|
406
|
+
if (storedObservedAt && normalizedObservedAt && Date.parse(normalizedObservedAt) < Date.parse(storedObservedAt)) {
|
|
407
|
+
return { error: "TAPD permission snapshot is older than the stored snapshot", status: 409 };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const now = new Date().toISOString();
|
|
411
|
+
if (!record) {
|
|
412
|
+
const workflowId = `prd_${crypto.randomBytes(12).toString("hex")}`;
|
|
413
|
+
record = {
|
|
414
|
+
id: workflowId,
|
|
415
|
+
tapdId: normalizedTapdId,
|
|
416
|
+
stateOwnerId: normalizedOwnerId,
|
|
417
|
+
members: {},
|
|
418
|
+
createdAt: now,
|
|
419
|
+
};
|
|
420
|
+
registry.workflows[workflowId] = record;
|
|
421
|
+
}
|
|
422
|
+
const participants = [...new Set(participantUserIds.map(normalizeUserId).filter(Boolean))]
|
|
423
|
+
.filter((id) => id !== normalizedOwnerId);
|
|
424
|
+
record.ownerId = normalizedOwnerId;
|
|
425
|
+
record.stateOwnerId = normalizeUserId(record.stateOwnerId || previousOwnerId || normalizedOwnerId);
|
|
426
|
+
record.ownerSource = "tapd";
|
|
427
|
+
record.teamId = String(getTeamForUser(normalizedOwnerId)?.id || "");
|
|
428
|
+
record.members = normalizedMemberMap(record.members);
|
|
429
|
+
delete record.members[normalizedOwnerId];
|
|
430
|
+
record.derivedMembers = Object.fromEntries(participants.map((id) => [id, "viewer"]));
|
|
431
|
+
record.authority = {
|
|
432
|
+
type: "tapd",
|
|
433
|
+
ownerIdentity: String(ownerIdentity || "").trim(),
|
|
434
|
+
participantIdentities: [...new Set(participantIdentities.map((value) => String(value || "").trim()).filter(Boolean))],
|
|
435
|
+
unresolvedParticipants: [...new Set(unresolvedParticipants.map((value) => String(value || "").trim()).filter(Boolean))],
|
|
436
|
+
observedAt: normalizedObservedAt || now,
|
|
437
|
+
revision: String(revision || "").trim(),
|
|
438
|
+
};
|
|
439
|
+
record.updatedAt = now;
|
|
440
|
+
writeRegistry(registry);
|
|
441
|
+
return {
|
|
442
|
+
record,
|
|
443
|
+
workflow: publicWorkflow(record, actorId),
|
|
444
|
+
created: !previousOwnerId,
|
|
445
|
+
ownerChanged: Boolean(previousOwnerId && previousOwnerId !== normalizedOwnerId),
|
|
446
|
+
previousOwnerId,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
251
450
|
export function removePrdWorkflowCollaborationMember({
|
|
252
451
|
workflowId,
|
|
253
452
|
userId,
|
|
@@ -270,9 +469,11 @@ export function removePrdWorkflowCollaborationMember({
|
|
|
270
469
|
delete record.members[targetId];
|
|
271
470
|
record.updatedAt = new Date().toISOString();
|
|
272
471
|
writeRegistry(registry);
|
|
472
|
+
const selfRemoved = targetId === actorId;
|
|
473
|
+
const stillAllowed = prdWorkflowCollaborationAccess(record, actorId).allowed;
|
|
273
474
|
return {
|
|
274
475
|
workflow: publicWorkflow(record, actorId),
|
|
275
476
|
removedUserId: targetId,
|
|
276
|
-
left:
|
|
477
|
+
left: selfRemoved && !stillAllowed,
|
|
277
478
|
};
|
|
278
479
|
}
|