@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.
@@ -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: actorId === record.ownerId ? "owner" : String(members[actorId] || ""),
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 role = actorId === record.ownerId ? "owner" : String(record.members?.[actorId] || "");
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) => prdWorkflowCollaborationAccess(record, actorId).allowed)
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
+ }
@@ -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
- runtimeRevision: workflowRuntimeRevision(globalState, artifacts, runtimeEvents),
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 workflows = listPrdWorkflowCollaborationsForUser(userCtx.userId).map((record) => {
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
- return prdWorkflowDashboardSummary(record, snapshot, userCtx);
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
- json(res, 200, flows);
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));