@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
|
@@ -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
|
|
|
@@ -52,11 +53,14 @@ function publicWorkflow(record, userId = "") {
|
|
|
52
53
|
if (!record) return null;
|
|
53
54
|
const actorId = normalizeUserId(userId);
|
|
54
55
|
const members = record.members && typeof record.members === "object" ? record.members : {};
|
|
56
|
+
const access = prdWorkflowCollaborationAccess(record, actorId);
|
|
55
57
|
return {
|
|
56
58
|
id: record.id,
|
|
57
59
|
tapdId: record.tapdId,
|
|
58
60
|
ownerId: record.ownerId,
|
|
59
|
-
role:
|
|
61
|
+
role: access.role,
|
|
62
|
+
accessSource: access.source,
|
|
63
|
+
teamId: String(record.teamId || getTeamForUser(record.ownerId)?.id || ""),
|
|
60
64
|
memberCount: Object.keys(members).length,
|
|
61
65
|
members: Object.entries(members).map(([id, role]) => ({ userId: id, role })),
|
|
62
66
|
shareActive: Boolean(record.shareToken),
|
|
@@ -69,11 +73,17 @@ function publicWorkflow(record, userId = "") {
|
|
|
69
73
|
export function prdWorkflowCollaborationAccess(record, userId) {
|
|
70
74
|
if (!record) return { allowed: true, role: "" };
|
|
71
75
|
const actorId = normalizeUserId(userId);
|
|
72
|
-
const
|
|
76
|
+
const directRole = actorId === record.ownerId ? "owner" : String(record.members?.[actorId] || "");
|
|
77
|
+
const actorTeam = getTeamForUser(actorId);
|
|
78
|
+
const recordTeamId = String(record.teamId || getTeamForUser(record.ownerId)?.id || "");
|
|
79
|
+
const teamRole = actorTeam?.status === "active" && actorTeam.id === recordTeamId ? "viewer" : "";
|
|
80
|
+
const role = directRole || teamRole;
|
|
73
81
|
return {
|
|
74
82
|
allowed: role === "owner" || role === "editor" || role === "viewer",
|
|
75
83
|
writable: role === "owner" || role === "editor",
|
|
76
84
|
role,
|
|
85
|
+
source: directRole ? "member" : teamRole ? "team" : "",
|
|
86
|
+
teamId: teamRole ? recordTeamId : "",
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -109,7 +119,17 @@ export function listPrdWorkflowCollaborationsForUser(userId) {
|
|
|
109
119
|
const actorId = normalizeUserId(userId);
|
|
110
120
|
if (!actorId) return [];
|
|
111
121
|
return Object.values(readRegistry().workflows)
|
|
112
|
-
.filter((record) =>
|
|
122
|
+
.filter((record) => (
|
|
123
|
+
record?.ownerId === actorId || Boolean(record?.members?.[actorId])
|
|
124
|
+
))
|
|
125
|
+
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function listPrdWorkflowCollaborationsForTeam(teamId) {
|
|
129
|
+
const id = String(teamId || "").trim();
|
|
130
|
+
if (!id) return [];
|
|
131
|
+
return Object.values(readRegistry().workflows)
|
|
132
|
+
.filter((record) => String(record?.teamId || getTeamForUser(record?.ownerId)?.id || "") === id)
|
|
113
133
|
.sort((left, right) => String(right?.updatedAt || "").localeCompare(String(left?.updatedAt || "")));
|
|
114
134
|
}
|
|
115
135
|
|
|
@@ -129,6 +149,7 @@ export function ensurePrdWorkflowCollaboration({ tapdId, userId }) {
|
|
|
129
149
|
id: workflowId,
|
|
130
150
|
tapdId: normalizedTapdId,
|
|
131
151
|
ownerId,
|
|
152
|
+
teamId: String(getTeamForUser(ownerId)?.id || ""),
|
|
132
153
|
members: { [ownerId]: "owner" },
|
|
133
154
|
createdAt: now,
|
|
134
155
|
updatedAt: now,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { getAgentflowDataRoot } from "./paths.mjs";
|
|
5
|
+
|
|
6
|
+
const REGISTRY_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
function registryPath() {
|
|
9
|
+
return path.join(getAgentflowDataRoot(), "organization", "teams.json");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeUserId(value) {
|
|
13
|
+
return String(value || "").trim().toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeTeamId(value) {
|
|
17
|
+
return String(value || "").trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeMembers(value) {
|
|
21
|
+
const members = Array.isArray(value) ? value : [];
|
|
22
|
+
return Array.from(new Set(members.map(normalizeUserId).filter(Boolean)));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readRegistry() {
|
|
26
|
+
try {
|
|
27
|
+
const filePath = registryPath();
|
|
28
|
+
if (!fs.existsSync(filePath)) return { version: REGISTRY_VERSION, teams: {} };
|
|
29
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
30
|
+
return {
|
|
31
|
+
version: REGISTRY_VERSION,
|
|
32
|
+
teams: parsed?.teams && typeof parsed.teams === "object" && !Array.isArray(parsed.teams)
|
|
33
|
+
? parsed.teams
|
|
34
|
+
: {},
|
|
35
|
+
};
|
|
36
|
+
} catch {
|
|
37
|
+
return { version: REGISTRY_VERSION, teams: {} };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeRegistry(registry) {
|
|
42
|
+
const filePath = registryPath();
|
|
43
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
44
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
45
|
+
fs.writeFileSync(tempPath, JSON.stringify({
|
|
46
|
+
version: REGISTRY_VERSION,
|
|
47
|
+
teams: registry?.teams || {},
|
|
48
|
+
}, null, 2) + "\n", "utf-8");
|
|
49
|
+
fs.renameSync(tempPath, filePath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function publicTeam(record) {
|
|
53
|
+
if (!record) return null;
|
|
54
|
+
const members = normalizeMembers(record.members);
|
|
55
|
+
return {
|
|
56
|
+
id: String(record.id || ""),
|
|
57
|
+
name: String(record.name || ""),
|
|
58
|
+
description: String(record.description || ""),
|
|
59
|
+
status: record.status === "inactive" ? "inactive" : "active",
|
|
60
|
+
members,
|
|
61
|
+
memberCount: members.length,
|
|
62
|
+
createdAt: String(record.createdAt || ""),
|
|
63
|
+
updatedAt: String(record.updatedAt || ""),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function listTeams({ includeInactive = true } = {}) {
|
|
68
|
+
return Object.values(readRegistry().teams)
|
|
69
|
+
.map(publicTeam)
|
|
70
|
+
.filter((team) => includeInactive || team.status === "active")
|
|
71
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getTeamById(teamId) {
|
|
75
|
+
return publicTeam(readRegistry().teams[normalizeTeamId(teamId)] || null);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getTeamForUser(userId, { includeInactive = false } = {}) {
|
|
79
|
+
const id = normalizeUserId(userId);
|
|
80
|
+
if (!id) return null;
|
|
81
|
+
return listTeams({ includeInactive }).find((team) => team.members.includes(id)) || null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createTeam({ name, description = "" } = {}) {
|
|
85
|
+
const normalizedName = String(name || "").trim();
|
|
86
|
+
if (!normalizedName) return { error: "团队名称不能为空", status: 400 };
|
|
87
|
+
const registry = readRegistry();
|
|
88
|
+
const duplicate = Object.values(registry.teams).some((team) => (
|
|
89
|
+
String(team?.name || "").trim().toLowerCase() === normalizedName.toLowerCase()
|
|
90
|
+
));
|
|
91
|
+
if (duplicate) return { error: "已存在同名团队", status: 409 };
|
|
92
|
+
const now = new Date().toISOString();
|
|
93
|
+
const id = `team_${crypto.randomBytes(10).toString("hex")}`;
|
|
94
|
+
const record = {
|
|
95
|
+
id,
|
|
96
|
+
name: normalizedName,
|
|
97
|
+
description: String(description || "").trim(),
|
|
98
|
+
status: "active",
|
|
99
|
+
members: [],
|
|
100
|
+
createdAt: now,
|
|
101
|
+
updatedAt: now,
|
|
102
|
+
};
|
|
103
|
+
registry.teams[id] = record;
|
|
104
|
+
writeRegistry(registry);
|
|
105
|
+
return { team: publicTeam(record), created: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function updateTeam(teamId, patch = {}) {
|
|
109
|
+
const id = normalizeTeamId(teamId);
|
|
110
|
+
const registry = readRegistry();
|
|
111
|
+
const record = registry.teams[id];
|
|
112
|
+
if (!record) return { error: "团队不存在", status: 404 };
|
|
113
|
+
if (Object.prototype.hasOwnProperty.call(patch, "name")) {
|
|
114
|
+
const name = String(patch.name || "").trim();
|
|
115
|
+
if (!name) return { error: "团队名称不能为空", status: 400 };
|
|
116
|
+
const duplicate = Object.values(registry.teams).some((team) => (
|
|
117
|
+
team?.id !== id && String(team?.name || "").trim().toLowerCase() === name.toLowerCase()
|
|
118
|
+
));
|
|
119
|
+
if (duplicate) return { error: "已存在同名团队", status: 409 };
|
|
120
|
+
record.name = name;
|
|
121
|
+
}
|
|
122
|
+
if (Object.prototype.hasOwnProperty.call(patch, "description")) {
|
|
123
|
+
record.description = String(patch.description || "").trim();
|
|
124
|
+
}
|
|
125
|
+
if (Object.prototype.hasOwnProperty.call(patch, "status")) {
|
|
126
|
+
record.status = patch.status === "inactive" ? "inactive" : "active";
|
|
127
|
+
}
|
|
128
|
+
record.updatedAt = new Date().toISOString();
|
|
129
|
+
writeRegistry(registry);
|
|
130
|
+
return { team: publicTeam(record) };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function setTeamMembers(teamId, members = []) {
|
|
134
|
+
const id = normalizeTeamId(teamId);
|
|
135
|
+
const registry = readRegistry();
|
|
136
|
+
const record = registry.teams[id];
|
|
137
|
+
if (!record) return { error: "团队不存在", status: 404 };
|
|
138
|
+
const normalized = normalizeMembers(members);
|
|
139
|
+
for (const team of Object.values(registry.teams)) {
|
|
140
|
+
if (!team || team.id === id) continue;
|
|
141
|
+
team.members = normalizeMembers(team.members).filter((memberId) => !normalized.includes(memberId));
|
|
142
|
+
}
|
|
143
|
+
record.members = normalized;
|
|
144
|
+
record.updatedAt = new Date().toISOString();
|
|
145
|
+
writeRegistry(registry);
|
|
146
|
+
return { team: publicTeam(record) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function deleteTeam(teamId) {
|
|
150
|
+
const id = normalizeTeamId(teamId);
|
|
151
|
+
const registry = readRegistry();
|
|
152
|
+
const record = registry.teams[id];
|
|
153
|
+
if (!record) return { error: "团队不存在", status: 404 };
|
|
154
|
+
if (normalizeMembers(record.members).length > 0) {
|
|
155
|
+
return { error: "请先移出团队中的全部成员", status: 409 };
|
|
156
|
+
}
|
|
157
|
+
delete registry.teams[id];
|
|
158
|
+
writeRegistry(registry);
|
|
159
|
+
return { deleted: true, teamId: id };
|
|
160
|
+
}
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -145,6 +145,8 @@ import {
|
|
|
145
145
|
getWorkspaceCollaborationForProject,
|
|
146
146
|
listWorkspaceCollaborationsForUser,
|
|
147
147
|
removeWorkspaceCollaborationMember,
|
|
148
|
+
removeWorkspaceCollaborationTeamShare,
|
|
149
|
+
setWorkspaceCollaborationTeamShare,
|
|
148
150
|
updateWorkspaceCollaborationFlow,
|
|
149
151
|
workspaceCollaborationAccess,
|
|
150
152
|
workspaceCollaborationSummary,
|
|
@@ -157,14 +159,25 @@ import {
|
|
|
157
159
|
getPrdWorkflowCollaborationForUser,
|
|
158
160
|
ensurePrdWorkflowShareLink,
|
|
159
161
|
listPrdWorkflowCollaborationsForUser,
|
|
162
|
+
listPrdWorkflowCollaborationsForTeam,
|
|
160
163
|
prdWorkflowCollaborationAccess,
|
|
161
164
|
prdWorkflowCollaborationSummary,
|
|
162
165
|
removePrdWorkflowCollaborationMember,
|
|
163
166
|
revokePrdWorkflowShareLink,
|
|
164
167
|
} from "./prd-workflow-collaboration.mjs";
|
|
168
|
+
import {
|
|
169
|
+
createTeam,
|
|
170
|
+
deleteTeam,
|
|
171
|
+
getTeamById,
|
|
172
|
+
getTeamForUser,
|
|
173
|
+
listTeams,
|
|
174
|
+
setTeamMembers,
|
|
175
|
+
updateTeam,
|
|
176
|
+
} from "./teams.mjs";
|
|
165
177
|
import {
|
|
166
178
|
legacyOverallToGlobalState,
|
|
167
179
|
materializeWorkflowGlobalState,
|
|
180
|
+
materializeWorkflowProjections,
|
|
168
181
|
mergeWorkflowArtifactLists,
|
|
169
182
|
mergeWorkflowArtifacts,
|
|
170
183
|
mergeWorkflowGlobalState,
|
|
@@ -3305,6 +3318,7 @@ function workspaceCollaborationSummaryWithUsers(record, userId) {
|
|
|
3305
3318
|
const summary = workspaceCollaborationSummary(record, userId);
|
|
3306
3319
|
if (!summary) return null;
|
|
3307
3320
|
const users = readAuthUsers();
|
|
3321
|
+
const teams = new Map(listTeams().map((team) => [team.id, team]));
|
|
3308
3322
|
return {
|
|
3309
3323
|
...summary,
|
|
3310
3324
|
ownerUsername: String(users[summary.ownerId]?.username || summary.ownerId),
|
|
@@ -3312,6 +3326,23 @@ function workspaceCollaborationSummaryWithUsers(record, userId) {
|
|
|
3312
3326
|
...member,
|
|
3313
3327
|
username: String(users[member.userId]?.username || member.userId),
|
|
3314
3328
|
})),
|
|
3329
|
+
teamShares: (summary.teamShares || []).map((share) => ({
|
|
3330
|
+
...share,
|
|
3331
|
+
teamName: String(teams.get(share.teamId)?.name || share.teamId),
|
|
3332
|
+
})),
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
function teamSummaryWithUsers(team) {
|
|
3337
|
+
if (!team) return null;
|
|
3338
|
+
const users = readAuthUsers();
|
|
3339
|
+
return {
|
|
3340
|
+
...team,
|
|
3341
|
+
members: (team.members || []).map((userId) => ({
|
|
3342
|
+
userId,
|
|
3343
|
+
username: String(users[userId]?.username || userId),
|
|
3344
|
+
isAdmin: Boolean(users[userId]?.isAdmin),
|
|
3345
|
+
})),
|
|
3315
3346
|
};
|
|
3316
3347
|
}
|
|
3317
3348
|
|
|
@@ -3434,6 +3465,23 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3434
3465
|
|| snapshot?.title
|
|
3435
3466
|
|| "",
|
|
3436
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
|
+
: [];
|
|
3437
3485
|
const updatedAtTimestamp = Math.max(
|
|
3438
3486
|
prdWorkflowDashboardTimestamp(record),
|
|
3439
3487
|
prdWorkflowDashboardTimestamp(snapshot),
|
|
@@ -3451,6 +3499,7 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3451
3499
|
platforms,
|
|
3452
3500
|
actionCount: actions.length,
|
|
3453
3501
|
completedActionCount: completedActions,
|
|
3502
|
+
timeline,
|
|
3454
3503
|
latestAction: latestAction
|
|
3455
3504
|
? {
|
|
3456
3505
|
title: String(latestAction.title || latestAction.label || latestAction.action || latestAction.id || "").trim(),
|
|
@@ -3462,11 +3511,80 @@ function prdWorkflowDashboardSummary(record, snapshot = {}, userCtx = {}) {
|
|
|
3462
3511
|
ownerId: String(collaboration.ownerId || ""),
|
|
3463
3512
|
ownerUsername: String(collaboration.ownerUsername || collaboration.ownerId || ""),
|
|
3464
3513
|
memberCount: Number(collaboration.memberCount || 0),
|
|
3514
|
+
accessSource: String(collaboration.accessSource || ""),
|
|
3515
|
+
teamId: String(collaboration.teamId || ""),
|
|
3516
|
+
teamName: String(getTeamById(collaboration.teamId)?.name || ""),
|
|
3465
3517
|
shareActive: collaboration.shareActive === true,
|
|
3466
3518
|
updatedAt: updatedAtTimestamp ? new Date(updatedAtTimestamp).toISOString() : String(record?.updatedAt || ""),
|
|
3467
3519
|
};
|
|
3468
3520
|
}
|
|
3469
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
|
+
|
|
3470
3588
|
function workspaceConversationsPath(scopedRoot) {
|
|
3471
3589
|
return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
|
|
3472
3590
|
}
|
|
@@ -11050,13 +11168,15 @@ function prdWorkflowMergeRuntimeEvents(scopedRoot, tapdId, snapshot) {
|
|
|
11050
11168
|
const overall = prdWorkflowOverallFromEvents(tapdId, snapshot, runtimeEvents);
|
|
11051
11169
|
const globalState = prdWorkflowGlobalStateFromEvents(tapdId, snapshot, runtimeEvents);
|
|
11052
11170
|
const artifacts = mergeWorkflowArtifacts(snapshot?.artifacts, runtimeEvents);
|
|
11171
|
+
const projections = materializeWorkflowProjections(snapshot, runtimeEvents);
|
|
11053
11172
|
return {
|
|
11054
11173
|
...snapshot,
|
|
11055
11174
|
workflow: globalState.workflow,
|
|
11056
11175
|
overall,
|
|
11057
11176
|
globalState,
|
|
11058
11177
|
artifacts,
|
|
11059
|
-
|
|
11178
|
+
projections,
|
|
11179
|
+
runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents, projections),
|
|
11060
11180
|
runtimeEvents,
|
|
11061
11181
|
events,
|
|
11062
11182
|
sources: {
|
|
@@ -12473,16 +12593,40 @@ export function startUiServer({
|
|
|
12473
12593
|
return;
|
|
12474
12594
|
}
|
|
12475
12595
|
try {
|
|
12476
|
-
const
|
|
12596
|
+
const view = String(url.searchParams.get("view") || "personal").trim().toLowerCase();
|
|
12597
|
+
let team = null;
|
|
12598
|
+
let records;
|
|
12599
|
+
if (view === "team") {
|
|
12600
|
+
const requestedTeamId = String(url.searchParams.get("teamId") || "").trim();
|
|
12601
|
+
team = requestedTeamId && authUser?.isAdmin
|
|
12602
|
+
? getTeamById(requestedTeamId)
|
|
12603
|
+
: getTeamForUser(userCtx.userId);
|
|
12604
|
+
if (!team || team.status !== "active") {
|
|
12605
|
+
json(res, 200, { ok: true, view: "team", team: null, workflows: [], timeline: [], unassignedCount: 0 });
|
|
12606
|
+
return;
|
|
12607
|
+
}
|
|
12608
|
+
records = listPrdWorkflowCollaborationsForTeam(team.id);
|
|
12609
|
+
} else {
|
|
12610
|
+
records = listPrdWorkflowCollaborationsForUser(userCtx.userId);
|
|
12611
|
+
}
|
|
12612
|
+
const workflows = records.map((record) => {
|
|
12477
12613
|
const stateRoot = path.resolve(getAgentflowUserDataRoot(record.ownerId));
|
|
12478
12614
|
const tapdId = String(record.tapdId || "").trim();
|
|
12479
12615
|
const project = prdWorkflowReadProjectState(stateRoot, tapdId);
|
|
12480
12616
|
const latestClient = prdWorkflowLatestClientSnapshot(stateRoot, stateRoot, tapdId);
|
|
12481
12617
|
const legacy = prdWorkflowReadCachedSnapshot(stateRoot, tapdId);
|
|
12482
12618
|
const snapshot = project?.snapshot || latestClient || legacy?.snapshot || {};
|
|
12483
|
-
|
|
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,
|
|
12484
12629
|
});
|
|
12485
|
-
json(res, 200, { ok: true, workflows });
|
|
12486
12630
|
} catch (error) {
|
|
12487
12631
|
json(res, 500, { error: (error && error.message) || String(error) });
|
|
12488
12632
|
}
|
|
@@ -13587,6 +13731,13 @@ export function startUiServer({
|
|
|
13587
13731
|
json(res, workflowScope.status || 400, { error: workflowScope.error });
|
|
13588
13732
|
return;
|
|
13589
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
|
+
}
|
|
13590
13741
|
const scopedRoot = workflowScope.stateRoot;
|
|
13591
13742
|
prdWorkflowMigrateLegacyState(workflowScope.executionRoot, scopedRoot, tapdId);
|
|
13592
13743
|
const currentSnapshot = prdWorkflowMaterializeSnapshot(
|
|
@@ -14158,6 +14309,62 @@ export function startUiServer({
|
|
|
14158
14309
|
return;
|
|
14159
14310
|
}
|
|
14160
14311
|
|
|
14312
|
+
if (req.method === "GET" && url.pathname === "/api/teams/me") {
|
|
14313
|
+
const team = getTeamForUser(userCtx.userId);
|
|
14314
|
+
json(res, 200, { team: teamSummaryWithUsers(team) });
|
|
14315
|
+
return;
|
|
14316
|
+
}
|
|
14317
|
+
|
|
14318
|
+
if (url.pathname === "/api/admin/teams") {
|
|
14319
|
+
if (!authUser?.isAdmin) {
|
|
14320
|
+
json(res, 403, { error: "Admin permission required" });
|
|
14321
|
+
return;
|
|
14322
|
+
}
|
|
14323
|
+
if (req.method === "GET") {
|
|
14324
|
+
const assigned = new Set(listTeams().flatMap((team) => team.members || []));
|
|
14325
|
+
const users = Object.entries(readAuthUsers()).map(([userId, user]) => ({
|
|
14326
|
+
userId,
|
|
14327
|
+
username: String(user?.username || userId),
|
|
14328
|
+
isAdmin: Boolean(user?.isAdmin),
|
|
14329
|
+
assigned: assigned.has(userId),
|
|
14330
|
+
teamId: getTeamForUser(userId, { includeInactive: true })?.id || "",
|
|
14331
|
+
}));
|
|
14332
|
+
json(res, 200, { teams: listTeams().map(teamSummaryWithUsers), users });
|
|
14333
|
+
return;
|
|
14334
|
+
}
|
|
14335
|
+
let payload;
|
|
14336
|
+
try {
|
|
14337
|
+
payload = JSON.parse(await readBody(req));
|
|
14338
|
+
} catch {
|
|
14339
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
14340
|
+
return;
|
|
14341
|
+
}
|
|
14342
|
+
let result;
|
|
14343
|
+
if (req.method === "POST") {
|
|
14344
|
+
result = createTeam(payload || {});
|
|
14345
|
+
} else if (req.method === "PATCH") {
|
|
14346
|
+
result = updateTeam(payload?.teamId, payload || {});
|
|
14347
|
+
} else if (req.method === "PUT") {
|
|
14348
|
+
const users = readAuthUsers();
|
|
14349
|
+
const memberIds = Array.isArray(payload?.members) ? payload.members.map((value) => String(value || "").trim().toLowerCase()) : [];
|
|
14350
|
+
const unknown = memberIds.find((userId) => !users[userId]);
|
|
14351
|
+
result = unknown
|
|
14352
|
+
? { error: `用户不存在:${unknown}`, status: 404 }
|
|
14353
|
+
: setTeamMembers(payload?.teamId, memberIds);
|
|
14354
|
+
} else if (req.method === "DELETE") {
|
|
14355
|
+
result = deleteTeam(payload?.teamId);
|
|
14356
|
+
} else {
|
|
14357
|
+
json(res, 405, { error: "Method not allowed" });
|
|
14358
|
+
return;
|
|
14359
|
+
}
|
|
14360
|
+
if (result?.error) {
|
|
14361
|
+
json(res, result.status || 400, { error: result.error });
|
|
14362
|
+
return;
|
|
14363
|
+
}
|
|
14364
|
+
json(res, 200, { ok: true, ...result, teams: listTeams().map(teamSummaryWithUsers) });
|
|
14365
|
+
return;
|
|
14366
|
+
}
|
|
14367
|
+
|
|
14161
14368
|
if (url.pathname === "/api/feedback") {
|
|
14162
14369
|
if (req.method === "POST") {
|
|
14163
14370
|
let payload;
|
|
@@ -14345,6 +14552,8 @@ export function startUiServer({
|
|
|
14345
14552
|
if (url.pathname === "/api/flows") {
|
|
14346
14553
|
if (req.method === "GET") {
|
|
14347
14554
|
try {
|
|
14555
|
+
const projectView = String(url.searchParams.get("view") || "all").trim().toLowerCase();
|
|
14556
|
+
const currentTeam = getTeamForUser(userCtx.userId);
|
|
14348
14557
|
const flows = listFlowsJson(root, { ...userCtx, includeWorkspaceFlows: true })
|
|
14349
14558
|
.filter((flow) => (
|
|
14350
14559
|
!workspaceFlowCollaborationGuard(
|
|
@@ -14387,7 +14596,22 @@ export function startUiServer({
|
|
|
14387
14596
|
});
|
|
14388
14597
|
existingCollaborationIds.add(record.id);
|
|
14389
14598
|
}
|
|
14390
|
-
|
|
14599
|
+
const visibleFlows = projectView === "team"
|
|
14600
|
+
? flows.filter((flow) => (
|
|
14601
|
+
currentTeam
|
|
14602
|
+
&& Array.isArray(flow.collaboration?.teamShares)
|
|
14603
|
+
&& flow.collaboration.teamShares.some((share) => share.teamId === currentTeam.id)
|
|
14604
|
+
))
|
|
14605
|
+
: projectView === "personal"
|
|
14606
|
+
? flows.filter((flow) => (
|
|
14607
|
+
!flow.collaboration
|
|
14608
|
+
|| flow.collaboration.ownerId === userCtx.userId
|
|
14609
|
+
|| flow.collaboration.members?.some((member) => member.userId === userCtx.userId)
|
|
14610
|
+
|| flow.source === "builtin"
|
|
14611
|
+
|| flow.source === "admin"
|
|
14612
|
+
))
|
|
14613
|
+
: flows;
|
|
14614
|
+
json(res, 200, visibleFlows);
|
|
14391
14615
|
} catch (e) {
|
|
14392
14616
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
14393
14617
|
}
|
|
@@ -14714,6 +14938,74 @@ export function startUiServer({
|
|
|
14714
14938
|
return;
|
|
14715
14939
|
}
|
|
14716
14940
|
|
|
14941
|
+
if (url.pathname === "/api/workspace/collaboration/team-share" && (req.method === "POST" || req.method === "DELETE")) {
|
|
14942
|
+
try {
|
|
14943
|
+
const payload = JSON.parse(await readBody(req));
|
|
14944
|
+
const flowId = String(payload?.flowId || "").trim();
|
|
14945
|
+
const flowSource = String(payload?.flowSource || "user").trim();
|
|
14946
|
+
const archived = payload?.archived === true || payload?.flowArchived === true;
|
|
14947
|
+
if (!flowId || (flowSource !== "workspace" && flowSource !== "user")) {
|
|
14948
|
+
json(res, 400, { error: "当前 Project 不支持团队分享" });
|
|
14949
|
+
return;
|
|
14950
|
+
}
|
|
14951
|
+
const targetTeam = getTeamById(payload?.teamId);
|
|
14952
|
+
const actorTeam = getTeamForUser(userCtx.userId);
|
|
14953
|
+
if (!targetTeam || targetTeam.status !== "active") {
|
|
14954
|
+
json(res, 404, { error: "团队不存在或已停用" });
|
|
14955
|
+
return;
|
|
14956
|
+
}
|
|
14957
|
+
if (!authUser?.isAdmin && actorTeam?.id !== targetTeam.id) {
|
|
14958
|
+
json(res, 403, { error: "只能分享给自己所在的团队" });
|
|
14959
|
+
return;
|
|
14960
|
+
}
|
|
14961
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
14962
|
+
flowId,
|
|
14963
|
+
flowSource,
|
|
14964
|
+
workspaceId: payload.workspaceId || "",
|
|
14965
|
+
archived,
|
|
14966
|
+
}, userCtx);
|
|
14967
|
+
if (scoped.error) {
|
|
14968
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
14969
|
+
return;
|
|
14970
|
+
}
|
|
14971
|
+
const ensured = ensureWorkspaceCollaboration({
|
|
14972
|
+
flowId,
|
|
14973
|
+
flowSource,
|
|
14974
|
+
archived,
|
|
14975
|
+
userId: userCtx.userId,
|
|
14976
|
+
});
|
|
14977
|
+
if (ensured.error) {
|
|
14978
|
+
json(res, ensured.status || 400, { error: ensured.error });
|
|
14979
|
+
return;
|
|
14980
|
+
}
|
|
14981
|
+
const result = req.method === "POST"
|
|
14982
|
+
? setWorkspaceCollaborationTeamShare({
|
|
14983
|
+
workspaceId: ensured.workspace.id,
|
|
14984
|
+
userId: userCtx.userId,
|
|
14985
|
+
teamId: targetTeam.id,
|
|
14986
|
+
role: payload?.role,
|
|
14987
|
+
})
|
|
14988
|
+
: removeWorkspaceCollaborationTeamShare({
|
|
14989
|
+
workspaceId: ensured.workspace.id,
|
|
14990
|
+
userId: userCtx.userId,
|
|
14991
|
+
teamId: targetTeam.id,
|
|
14992
|
+
});
|
|
14993
|
+
if (result.error) {
|
|
14994
|
+
json(res, result.status || 400, { error: result.error });
|
|
14995
|
+
return;
|
|
14996
|
+
}
|
|
14997
|
+
const record = getWorkspaceCollaborationForProject({ workspaceId: ensured.workspace.id });
|
|
14998
|
+
json(res, 200, {
|
|
14999
|
+
ok: true,
|
|
15000
|
+
workspace: workspaceCollaborationSummaryWithUsers(record, userCtx.userId),
|
|
15001
|
+
team: teamSummaryWithUsers(targetTeam),
|
|
15002
|
+
});
|
|
15003
|
+
} catch (e) {
|
|
15004
|
+
json(res, 400, { error: (e && e.message) || String(e) });
|
|
15005
|
+
}
|
|
15006
|
+
return;
|
|
15007
|
+
}
|
|
15008
|
+
|
|
14717
15009
|
if (req.method === "DELETE" && url.pathname === "/api/workspace/collaboration/share") {
|
|
14718
15010
|
try {
|
|
14719
15011
|
const payload = JSON.parse(await readBody(req));
|