ideaco 1.1.5
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/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- package/tailwind.config.mjs +11 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { getCompany } from '@/lib/store';
|
|
3
|
+
import { getApiT } from '@/lib/api-i18n';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Teams Management API
|
|
7
|
+
* GET /api/teams - List all teams
|
|
8
|
+
* GET /api/teams?id=xxx - Get team detail
|
|
9
|
+
* GET /api/teams?departmentId=xxx - List teams by department
|
|
10
|
+
* GET /api/teams?teamId=xxx&sprintId=yyy - Get sprint detail
|
|
11
|
+
* POST /api/teams - Team/Sprint operations
|
|
12
|
+
* DELETE /api/teams?id=xxx - Delete team
|
|
13
|
+
*/
|
|
14
|
+
export async function GET(request) {
|
|
15
|
+
const t = getApiT(request);
|
|
16
|
+
try {
|
|
17
|
+
const company = getCompany();
|
|
18
|
+
if (!company) {
|
|
19
|
+
return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!company.teamManager) {
|
|
23
|
+
const { TeamManager: TM } = await import('@/core/organization/team.js');
|
|
24
|
+
company.teamManager = new TM();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const url = new URL(request.url);
|
|
28
|
+
const id = url.searchParams.get('id');
|
|
29
|
+
const departmentId = url.searchParams.get('departmentId');
|
|
30
|
+
const teamId = url.searchParams.get('teamId');
|
|
31
|
+
const sprintId = url.searchParams.get('sprintId');
|
|
32
|
+
|
|
33
|
+
// Get sprint detail
|
|
34
|
+
if (teamId && sprintId) {
|
|
35
|
+
const team = company.teamManager.get(teamId);
|
|
36
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
37
|
+
const sprint = team.getSprint(sprintId);
|
|
38
|
+
if (!sprint) return NextResponse.json({ error: t('api.sprintNotFound') }, { status: 404 });
|
|
39
|
+
|
|
40
|
+
const data = sprint.serialize();
|
|
41
|
+
// serialize() omits groupChat (stored in chatStore files), attach it here
|
|
42
|
+
data.groupChat = sprint.groupChat || [];
|
|
43
|
+
|
|
44
|
+
// Attach member list
|
|
45
|
+
const dept = company.findDepartment(team.departmentId);
|
|
46
|
+
if (dept) {
|
|
47
|
+
data.members = team.memberIds.map(mid => {
|
|
48
|
+
const a = dept.agents.get(mid);
|
|
49
|
+
return a ? { id: a.id, name: a.name, role: a.role, avatar: a.avatar, status: a.status } : null;
|
|
50
|
+
}).filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return NextResponse.json({ data });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get team detail
|
|
57
|
+
if (id) {
|
|
58
|
+
const team = company.teamManager.get(id);
|
|
59
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
60
|
+
|
|
61
|
+
const data = team.serialize();
|
|
62
|
+
|
|
63
|
+
// Enrich member info
|
|
64
|
+
const dept = company.findDepartment(team.departmentId);
|
|
65
|
+
if (dept) {
|
|
66
|
+
data.membersDetail = team.memberIds.map(mid => {
|
|
67
|
+
const a = dept.agents.get(mid);
|
|
68
|
+
if (!a) return null;
|
|
69
|
+
return {
|
|
70
|
+
id: a.id, name: a.name, role: a.role, avatar: a.avatar, status: a.status,
|
|
71
|
+
skills: a.skills, signature: a.signature, gender: a.gender, age: a.age,
|
|
72
|
+
provider: a.getProviderDisplayInfo(),
|
|
73
|
+
};
|
|
74
|
+
}).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Enrich sprint overview (without full groupChat)
|
|
78
|
+
data.sprints = team.listSprints().map(s => ({
|
|
79
|
+
id: s.id,
|
|
80
|
+
title: s.title,
|
|
81
|
+
goal: s.goal,
|
|
82
|
+
status: s.status,
|
|
83
|
+
createdAt: s.createdAt,
|
|
84
|
+
startedAt: s.startedAt,
|
|
85
|
+
completedAt: s.completedAt,
|
|
86
|
+
chatCount: s.groupChat?.length || 0,
|
|
87
|
+
outputCount: s.outputs?.length || 0,
|
|
88
|
+
workflow: s.workflow ? {
|
|
89
|
+
summary: s.workflow.summary,
|
|
90
|
+
nodeCount: s.workflow.nodes?.length || 0,
|
|
91
|
+
completedCount: s.workflow.nodes?.filter(n => n.status === 'completed').length || 0,
|
|
92
|
+
} : null,
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
return NextResponse.json({ data });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// List by department
|
|
99
|
+
if (departmentId) {
|
|
100
|
+
const teams = company.teamManager.listByDepartment(departmentId);
|
|
101
|
+
return NextResponse.json({ data: teams.map(t => t.serialize()) });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// List all
|
|
105
|
+
return NextResponse.json({
|
|
106
|
+
data: company.teamManager.listAll().map(t => ({
|
|
107
|
+
id: t.id, name: t.name, departmentId: t.departmentId, departmentName: t.departmentName,
|
|
108
|
+
memberIds: t.memberIds, leaderId: t.leaderId, leaderName: t.leaderName,
|
|
109
|
+
description: t.description, status: t.status, createdAt: t.createdAt,
|
|
110
|
+
sprintCount: t.sprints.size,
|
|
111
|
+
skills: t.skills,
|
|
112
|
+
})),
|
|
113
|
+
});
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error('[Teams API] GET error:', e);
|
|
116
|
+
return NextResponse.json({ error: e.message || 'Internal server error' }, { status: 500 });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* DELETE /api/teams?id=xxx - Delete team
|
|
122
|
+
*/
|
|
123
|
+
export async function DELETE(request) {
|
|
124
|
+
const t = getApiT(request);
|
|
125
|
+
try {
|
|
126
|
+
const company = getCompany();
|
|
127
|
+
if (!company) {
|
|
128
|
+
return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!company.teamManager) {
|
|
132
|
+
const { TeamManager: TM } = await import('@/core/organization/team.js');
|
|
133
|
+
company.teamManager = new TM();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const url = new URL(request.url);
|
|
137
|
+
const id = url.searchParams.get('id');
|
|
138
|
+
if (!id) return NextResponse.json({ error: t('api.teamDeleteIdRequired') }, { status: 400 });
|
|
139
|
+
|
|
140
|
+
if (!company.teamManager.get(id)) {
|
|
141
|
+
return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
company.teamManager.delete(id);
|
|
145
|
+
company.save();
|
|
146
|
+
|
|
147
|
+
return NextResponse.json({ data: { success: true, id } });
|
|
148
|
+
} catch (e) {
|
|
149
|
+
console.error('[Teams API] DELETE error:', e);
|
|
150
|
+
return NextResponse.json({ error: e.message || 'Internal server error' }, { status: 500 });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* POST /api/teams - Team/Sprint operations
|
|
156
|
+
*/
|
|
157
|
+
export async function POST(request) {
|
|
158
|
+
const t = getApiT(request);
|
|
159
|
+
try {
|
|
160
|
+
const company = getCompany();
|
|
161
|
+
if (!company) {
|
|
162
|
+
return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!company.teamManager) {
|
|
166
|
+
const { TeamManager: TM } = await import('@/core/organization/team.js');
|
|
167
|
+
company.teamManager = new TM();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const body = await request.json();
|
|
171
|
+
const { action } = body;
|
|
172
|
+
|
|
173
|
+
// === Create Team ===
|
|
174
|
+
if (action === 'create') {
|
|
175
|
+
const { departmentId, name, memberIds, leaderId, description } = body;
|
|
176
|
+
if (!departmentId || !name || !memberIds?.length || !leaderId) {
|
|
177
|
+
return NextResponse.json({ error: t('api.teamCreateRequired') }, { status: 400 });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const dept = company.findDepartment(departmentId);
|
|
181
|
+
if (!dept) return NextResponse.json({ error: t('api.deptNotFound') }, { status: 404 });
|
|
182
|
+
|
|
183
|
+
const leader = dept.agents.get(leaderId);
|
|
184
|
+
if (!leader) return NextResponse.json({ error: t('api.leaderNotFound') }, { status: 400 });
|
|
185
|
+
|
|
186
|
+
// Verify all members exist in department
|
|
187
|
+
for (const mid of memberIds) {
|
|
188
|
+
if (!dept.agents.get(mid)) {
|
|
189
|
+
return NextResponse.json({ error: t('api.memberNotFound', { id: mid }) }, { status: 400 });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Ensure leader is in members list
|
|
194
|
+
const allMemberIds = [...new Set([leaderId, ...memberIds])];
|
|
195
|
+
|
|
196
|
+
const team = company.teamManager.create({
|
|
197
|
+
name,
|
|
198
|
+
departmentId: dept.id,
|
|
199
|
+
departmentName: dept.name,
|
|
200
|
+
memberIds: allMemberIds,
|
|
201
|
+
leaderId,
|
|
202
|
+
leaderName: leader.name,
|
|
203
|
+
description: description || '',
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
company.save();
|
|
207
|
+
return NextResponse.json({ data: team.serialize() });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// === Update Team ===
|
|
211
|
+
if (action === 'update') {
|
|
212
|
+
const { teamId, skills, workspacePath, name, description, memberIds, leaderId } = body;
|
|
213
|
+
if (!teamId) return NextResponse.json({ error: t('api.teamIdRequired') }, { status: 400 });
|
|
214
|
+
|
|
215
|
+
const team = company.teamManager.get(teamId);
|
|
216
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
217
|
+
|
|
218
|
+
if (skills !== undefined) team.skills = skills;
|
|
219
|
+
if (workspacePath !== undefined) {
|
|
220
|
+
if (workspacePath) {
|
|
221
|
+
const path = await import('path');
|
|
222
|
+
const { existsSync, mkdirSync } = await import('fs');
|
|
223
|
+
const resolved = path.default.resolve(workspacePath);
|
|
224
|
+
if (!existsSync(resolved)) mkdirSync(resolved, { recursive: true });
|
|
225
|
+
team.workspacePath = resolved;
|
|
226
|
+
} else {
|
|
227
|
+
team.workspacePath = null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (name !== undefined) team.name = name;
|
|
231
|
+
if (description !== undefined) team.description = description;
|
|
232
|
+
if (memberIds !== undefined) team.memberIds = memberIds;
|
|
233
|
+
if (leaderId !== undefined) {
|
|
234
|
+
team.leaderId = leaderId;
|
|
235
|
+
const dept = company.findDepartment(team.departmentId);
|
|
236
|
+
const leader = dept?.agents.get(leaderId);
|
|
237
|
+
if (leader) team.leaderName = leader.name;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
company.save();
|
|
241
|
+
return NextResponse.json({ data: team.serialize() });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// === Create Sprint ===
|
|
245
|
+
if (action === 'create_sprint') {
|
|
246
|
+
const { teamId, title, goal } = body;
|
|
247
|
+
if (!teamId || !title || !goal) {
|
|
248
|
+
return NextResponse.json({ error: t('api.sprintCreateRequired') }, { status: 400 });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const team = company.teamManager.get(teamId);
|
|
252
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
253
|
+
|
|
254
|
+
const { Sprint } = await import('@/core/organization/team.js');
|
|
255
|
+
const sprint = new Sprint({ title, goal, teamId: team.id, teamName: team.name });
|
|
256
|
+
team.addSprint(sprint);
|
|
257
|
+
|
|
258
|
+
// Add system message
|
|
259
|
+
sprint.addGroupMessage(
|
|
260
|
+
{ name: 'System', role: 'system' },
|
|
261
|
+
t('api.sprintCreated', { title, goal }),
|
|
262
|
+
'system'
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
company.save();
|
|
266
|
+
return NextResponse.json({ data: sprint.serialize() });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// === Start Sprint Discussion ===
|
|
270
|
+
if (action === 'discuss_sprint') {
|
|
271
|
+
const { teamId, sprintId } = body;
|
|
272
|
+
if (!teamId || !sprintId) {
|
|
273
|
+
return NextResponse.json({ error: t('api.sprintDiscussRequired') }, { status: 400 });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const team = company.teamManager.get(teamId);
|
|
277
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
278
|
+
|
|
279
|
+
const sprint = team.getSprint(sprintId);
|
|
280
|
+
if (!sprint) return NextResponse.json({ error: t('api.sprintNotFound') }, { status: 404 });
|
|
281
|
+
|
|
282
|
+
const { SprintStatus: SS } = await import('@/core/organization/team.js');
|
|
283
|
+
if (sprint.status !== SS.DRAFT) {
|
|
284
|
+
return NextResponse.json({ error: t('api.sprintNotDraft') }, { status: 400 });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
sprint.status = SS.DISCUSSING;
|
|
288
|
+
company.save();
|
|
289
|
+
|
|
290
|
+
// Return immediately, run discussion async so frontend can poll progress
|
|
291
|
+
const response = NextResponse.json({ data: sprint.serialize() });
|
|
292
|
+
|
|
293
|
+
// Async discussion flow
|
|
294
|
+
const dept = company.findDepartment(team.departmentId);
|
|
295
|
+
const leader = dept?.agents.get(team.leaderId);
|
|
296
|
+
|
|
297
|
+
if (leader) {
|
|
298
|
+
(async () => {
|
|
299
|
+
try {
|
|
300
|
+
const { getTraitStyle, getAgeStyle } = await import('@/core/prompts.js');
|
|
301
|
+
|
|
302
|
+
const members = team.memberIds.map(mid => dept.agents.get(mid)).filter(Boolean);
|
|
303
|
+
const nonLeaderMembers = members.filter(m => m.id !== team.leaderId);
|
|
304
|
+
|
|
305
|
+
const memberInfo = members.map(a =>
|
|
306
|
+
`- ${a.name} (${a.role}): skills=[${a.skills.join(', ')}]`
|
|
307
|
+
).join('\n');
|
|
308
|
+
|
|
309
|
+
// Helper: build personality-aware system prompt for an agent
|
|
310
|
+
const buildAgentPrompt = (agent, scenario) => {
|
|
311
|
+
const p = agent.personality || {};
|
|
312
|
+
const traitStyle = getTraitStyle(p.trait);
|
|
313
|
+
const ageStyle = getAgeStyle(agent.age);
|
|
314
|
+
return `${traitStyle}
|
|
315
|
+
|
|
316
|
+
You are "${agent.name}", ${agent.gender || 'male'}, age ${agent.age || 28}, working as "${agent.role}".
|
|
317
|
+
Tone: ${p.tone || 'professional'}. Quirk: ${p.quirk || 'none'}. Signature: ${agent.signature || ''}.
|
|
318
|
+
|
|
319
|
+
${ageStyle}
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
${scenario}
|
|
324
|
+
|
|
325
|
+
IMPORTANT: Stay in character. Your reply should reflect your personality, age, and speaking style. DO NOT sound like a polite AI assistant. Be natural, opinionated, and real.
|
|
326
|
+
Speak in the same language as the conversation (match the sprint goal language).`;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Helper: get recent chat as context string
|
|
330
|
+
const getChatContext = () => {
|
|
331
|
+
const msgs = (sprint.groupChat || []).filter(m => m.visibility !== 'flow');
|
|
332
|
+
return msgs.slice(-15).map(m => `${m.from?.name || 'System'}: ${m.content}`).join('\n');
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// === Phase 1: Leader opens discussion ===
|
|
336
|
+
sprint.addGroupMessage(
|
|
337
|
+
leader,
|
|
338
|
+
t('api.sprintDiscussionOpening', { title: sprint.title, goal: sprint.goal }),
|
|
339
|
+
'message'
|
|
340
|
+
);
|
|
341
|
+
company.save();
|
|
342
|
+
|
|
343
|
+
// === Phase 2: Leader proposes initial plan ===
|
|
344
|
+
const planResponse = await leader.chat([
|
|
345
|
+
{
|
|
346
|
+
role: 'system',
|
|
347
|
+
content: buildAgentPrompt(leader, `You are the team leader. Your team is starting a sprint discussion.
|
|
348
|
+
Team members:\n${memberInfo}
|
|
349
|
+
|
|
350
|
+
Analyze the sprint goal and propose a concrete initial plan. Include:
|
|
351
|
+
1. Overall approach and architecture
|
|
352
|
+
2. Task breakdown — what each team member should focus on (based on their skills)
|
|
353
|
+
3. Expected deliverables
|
|
354
|
+
4. Potential risks and mitigation
|
|
355
|
+
|
|
356
|
+
Be specific and actionable. This is a DRAFT plan for the team to discuss and improve.`),
|
|
357
|
+
},
|
|
358
|
+
{ role: 'user', content: `Sprint goal: ${sprint.goal}\n\nPropose your initial plan for the team to discuss.` },
|
|
359
|
+
], { temperature: 0.7, maxTokens: 1024 });
|
|
360
|
+
|
|
361
|
+
sprint.plan = planResponse.content;
|
|
362
|
+
|
|
363
|
+
sprint.addGroupMessage(
|
|
364
|
+
leader,
|
|
365
|
+
`📋 Here is my initial plan:\n\n${planResponse.content}\n\nWhat do you think? Please share your feedback and suggestions based on your expertise so we can improve it together.`,
|
|
366
|
+
'message'
|
|
367
|
+
);
|
|
368
|
+
company.save();
|
|
369
|
+
|
|
370
|
+
// Small delay to mimic natural pacing
|
|
371
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
372
|
+
|
|
373
|
+
// === Phase 3: Each member gives feedback (Round 1 — initial opinions) ===
|
|
374
|
+
// Randomize order so it feels natural
|
|
375
|
+
const shuffled = [...nonLeaderMembers].sort(() => Math.random() - 0.5);
|
|
376
|
+
|
|
377
|
+
for (const agent of shuffled) {
|
|
378
|
+
if (!agent.canChat()) continue;
|
|
379
|
+
|
|
380
|
+
// Inner monologue (flow visibility — Boss can peek)
|
|
381
|
+
try {
|
|
382
|
+
const monologueResp = await agent.chat([
|
|
383
|
+
{
|
|
384
|
+
role: 'system',
|
|
385
|
+
content: buildAgentPrompt(agent, `You are in a sprint planning discussion. The leader just proposed a plan. Think about what you really feel about this plan — your honest inner thoughts. This is your PRIVATE inner monologue, not what you'll say out loud.
|
|
386
|
+
|
|
387
|
+
Consider: Does the plan make sense for your role? Any concerns? Anything missing? What would YOU change?`),
|
|
388
|
+
},
|
|
389
|
+
{ role: 'user', content: `Chat context:\n${getChatContext()}\n\nWhat are your honest inner thoughts about this plan? (1-2 sentences, be real)` },
|
|
390
|
+
], { temperature: 0.95, maxTokens: 200 });
|
|
391
|
+
sprint.addGroupMessage(
|
|
392
|
+
{ id: agent.id, name: agent.name, avatar: agent.avatar, role: agent.role },
|
|
393
|
+
monologueResp.content,
|
|
394
|
+
'monologue',
|
|
395
|
+
'flow'
|
|
396
|
+
);
|
|
397
|
+
} catch (e) { /* monologue failed, continue */ }
|
|
398
|
+
|
|
399
|
+
// Actual group message — substantive feedback
|
|
400
|
+
try {
|
|
401
|
+
const feedbackResp = await agent.chat([
|
|
402
|
+
{
|
|
403
|
+
role: 'system',
|
|
404
|
+
content: buildAgentPrompt(agent, `You are in a sprint planning discussion group. The leader proposed an initial plan. Now it's your turn to give feedback.
|
|
405
|
+
|
|
406
|
+
Your expertise: ${agent.role}, skills: [${(agent.skills || []).join(', ')}]
|
|
407
|
+
|
|
408
|
+
REQUIREMENTS for your feedback:
|
|
409
|
+
1. Comment on the parts relevant to YOUR expertise — be specific
|
|
410
|
+
2. Point out any issues, risks, or improvements you see
|
|
411
|
+
3. If you disagree with something, say so clearly and suggest alternatives
|
|
412
|
+
4. If you have additional ideas, propose them
|
|
413
|
+
5. Keep it concise but substantive (3-5 sentences)
|
|
414
|
+
|
|
415
|
+
Other team members:\n${memberInfo}
|
|
416
|
+
|
|
417
|
+
DO NOT just say "looks good" or "I agree". Give REAL, specific feedback.`),
|
|
418
|
+
},
|
|
419
|
+
{ role: 'user', content: `Chat context:\n${getChatContext()}\n\nGive your professional feedback on the plan.` },
|
|
420
|
+
], { temperature: 0.8, maxTokens: 400 });
|
|
421
|
+
|
|
422
|
+
sprint.addGroupMessage(agent, feedbackResp.content, 'message');
|
|
423
|
+
company.save();
|
|
424
|
+
|
|
425
|
+
// Stagger replies
|
|
426
|
+
await new Promise(r => setTimeout(r, 800 + Math.random() * 1200));
|
|
427
|
+
} catch (e) { /* ignore */ }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// === Phase 4: Cross-discussion (Round 2 — members respond to each other) ===
|
|
431
|
+
// Pick 2-3 members who had the most substantive feedback to continue
|
|
432
|
+
const discussants = shuffled
|
|
433
|
+
.filter(a => a.canChat())
|
|
434
|
+
.slice(0, Math.min(3, nonLeaderMembers.length));
|
|
435
|
+
|
|
436
|
+
if (discussants.length > 1) {
|
|
437
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
438
|
+
|
|
439
|
+
for (const agent of discussants) {
|
|
440
|
+
try {
|
|
441
|
+
const crossResp = await agent.chat([
|
|
442
|
+
{
|
|
443
|
+
role: 'system',
|
|
444
|
+
content: buildAgentPrompt(agent, `You are in a sprint planning discussion. Other team members have given their feedback. Now you can:
|
|
445
|
+
1. Respond to a colleague's point you agree or disagree with
|
|
446
|
+
2. Build on someone else's suggestion
|
|
447
|
+
3. Raise a new concern that wasn't mentioned
|
|
448
|
+
4. Propose a specific technical solution
|
|
449
|
+
|
|
450
|
+
Be brief (2-3 sentences). Reference specific colleagues by name when responding to their points. Stay in character.`),
|
|
451
|
+
},
|
|
452
|
+
{ role: 'user', content: `Full discussion so far:\n${getChatContext()}\n\nAdd to the discussion — respond to colleagues or raise new points.` },
|
|
453
|
+
], { temperature: 0.85, maxTokens: 300 });
|
|
454
|
+
|
|
455
|
+
sprint.addGroupMessage(agent, crossResp.content, 'message');
|
|
456
|
+
company.save();
|
|
457
|
+
|
|
458
|
+
await new Promise(r => setTimeout(r, 600 + Math.random() * 800));
|
|
459
|
+
} catch (e) { /* ignore */ }
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// === Phase 5: Leader summarizes and revises plan ===
|
|
464
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
465
|
+
|
|
466
|
+
// Leader inner monologue
|
|
467
|
+
try {
|
|
468
|
+
const leaderMonologue = await leader.chat([
|
|
469
|
+
{
|
|
470
|
+
role: 'system',
|
|
471
|
+
content: buildAgentPrompt(leader, `You are the team leader. Your team has finished discussing the sprint plan. Think about all the feedback — what should you incorporate? What should you push back on? This is your PRIVATE inner thought.`),
|
|
472
|
+
},
|
|
473
|
+
{ role: 'user', content: `Discussion:\n${getChatContext()}\n\nYour honest thoughts on the team's feedback? (2-3 sentences)` },
|
|
474
|
+
], { temperature: 0.8, maxTokens: 200 });
|
|
475
|
+
sprint.addGroupMessage(
|
|
476
|
+
{ id: leader.id, name: leader.name, avatar: leader.avatar, role: leader.role },
|
|
477
|
+
leaderMonologue.content,
|
|
478
|
+
'monologue',
|
|
479
|
+
'flow'
|
|
480
|
+
);
|
|
481
|
+
} catch (e) { /* ignore */ }
|
|
482
|
+
|
|
483
|
+
// Leader summary message in group
|
|
484
|
+
try {
|
|
485
|
+
const summaryResp = await leader.chat([
|
|
486
|
+
{
|
|
487
|
+
role: 'system',
|
|
488
|
+
content: buildAgentPrompt(leader, `You are the team leader wrapping up the sprint plan discussion. You need to:
|
|
489
|
+
1. Acknowledge the team's valuable feedback (mention specific people and their points)
|
|
490
|
+
2. Explain which suggestions you're incorporating and why
|
|
491
|
+
3. Note any concerns you'll monitor during execution
|
|
492
|
+
4. Announce the plan is ready for Boss's approval
|
|
493
|
+
|
|
494
|
+
Be concise, professional, and appreciative. Show you actually listened.`),
|
|
495
|
+
},
|
|
496
|
+
{ role: 'user', content: `Full discussion:\n${getChatContext()}\n\nSummarize the discussion and wrap up.` },
|
|
497
|
+
], { temperature: 0.7, maxTokens: 400 });
|
|
498
|
+
|
|
499
|
+
sprint.addGroupMessage(leader, summaryResp.content, 'message');
|
|
500
|
+
} catch (e) { /* ignore */ }
|
|
501
|
+
|
|
502
|
+
// Leader revises the plan based on all feedback
|
|
503
|
+
try {
|
|
504
|
+
const revisedPlan = await leader.chat([
|
|
505
|
+
{
|
|
506
|
+
role: 'system',
|
|
507
|
+
content: `You are "${leader.name}", team leader. Your team has discussed the sprint plan and given feedback. Now revise the plan incorporating the valid suggestions.
|
|
508
|
+
|
|
509
|
+
Output ONLY the revised plan in markdown format. Keep it concise and actionable.
|
|
510
|
+
Speak in the same language as the original plan.`,
|
|
511
|
+
},
|
|
512
|
+
{ role: 'user', content: `Original plan:\n${sprint.plan}\n\nFull team discussion:\n${getChatContext()}\n\nOutput the REVISED plan incorporating the team's feedback:` },
|
|
513
|
+
], { temperature: 0.5, maxTokens: 2048 });
|
|
514
|
+
|
|
515
|
+
sprint.plan = revisedPlan.content;
|
|
516
|
+
} catch (e) { /* plan revision failed, keep original */ }
|
|
517
|
+
|
|
518
|
+
// Final system message
|
|
519
|
+
sprint.addGroupMessage(
|
|
520
|
+
{ name: 'System', role: 'system' },
|
|
521
|
+
t('api.sprintDiscussionComplete'),
|
|
522
|
+
'system'
|
|
523
|
+
);
|
|
524
|
+
sprint.status = SS.PENDING_APPROVAL;
|
|
525
|
+
company.save();
|
|
526
|
+
|
|
527
|
+
} catch (e) {
|
|
528
|
+
console.error('[Sprint Discussion] Error:', e.message);
|
|
529
|
+
sprint.addGroupMessage(
|
|
530
|
+
{ name: 'System', role: 'system' },
|
|
531
|
+
t('api.sprintDiscussionError', { error: e.message }),
|
|
532
|
+
'system'
|
|
533
|
+
);
|
|
534
|
+
sprint.status = SS.PENDING_APPROVAL;
|
|
535
|
+
company.save();
|
|
536
|
+
}
|
|
537
|
+
})();
|
|
538
|
+
} else {
|
|
539
|
+
sprint.status = SS.PENDING_APPROVAL;
|
|
540
|
+
company.save();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return response;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// === Approve Sprint (Boss approves → create standard requirement) ===
|
|
547
|
+
if (action === 'approve_sprint') {
|
|
548
|
+
const { teamId, sprintId } = body;
|
|
549
|
+
if (!teamId || !sprintId) {
|
|
550
|
+
return NextResponse.json({ error: t('api.sprintDiscussRequired') }, { status: 400 });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const team = company.teamManager.get(teamId);
|
|
554
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
555
|
+
|
|
556
|
+
const sprint = team.getSprint(sprintId);
|
|
557
|
+
if (!sprint) return NextResponse.json({ error: t('api.sprintNotFound') }, { status: 404 });
|
|
558
|
+
|
|
559
|
+
const { SprintStatus: SS } = await import('@/core/organization/team.js');
|
|
560
|
+
if (sprint.status !== SS.PENDING_APPROVAL) {
|
|
561
|
+
return NextResponse.json({ error: t('api.sprintNotPendingApproval') }, { status: 400 });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
sprint.status = SS.IN_PROGRESS;
|
|
565
|
+
sprint.startedAt = new Date();
|
|
566
|
+
|
|
567
|
+
sprint.addGroupMessage(
|
|
568
|
+
{ name: 'Boss', role: 'boss' },
|
|
569
|
+
t('api.sprintApproved'),
|
|
570
|
+
'message'
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
company.save();
|
|
574
|
+
|
|
575
|
+
// Async: Create a standard Requirement from the sprint and execute via the full requirement pipeline
|
|
576
|
+
(async () => {
|
|
577
|
+
try {
|
|
578
|
+
const requirement = await company.assignSprintAsDepartmentTask(sprint, team);
|
|
579
|
+
sprint.addGroupMessage(
|
|
580
|
+
{ name: 'System', role: 'system' },
|
|
581
|
+
t('api.sprintRequirementCreated', { title: requirement.title }),
|
|
582
|
+
'system'
|
|
583
|
+
);
|
|
584
|
+
company.save();
|
|
585
|
+
} catch (e) {
|
|
586
|
+
console.error('Sprint → Requirement failed:', e.message);
|
|
587
|
+
sprint.status = SS.FAILED;
|
|
588
|
+
sprint.addGroupMessage(
|
|
589
|
+
{ name: 'System', role: 'system' },
|
|
590
|
+
t('api.sprintRequirementFailed', { error: e.message }),
|
|
591
|
+
'system'
|
|
592
|
+
);
|
|
593
|
+
company.save();
|
|
594
|
+
}
|
|
595
|
+
})();
|
|
596
|
+
|
|
597
|
+
return NextResponse.json({ data: sprint.serialize() });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// === Boss sends message in sprint group chat ===
|
|
601
|
+
if (action === 'sprint_message') {
|
|
602
|
+
const { teamId, sprintId, message } = body;
|
|
603
|
+
if (!teamId || !sprintId || !message) {
|
|
604
|
+
return NextResponse.json({ error: t('api.sprintMessageRequired') }, { status: 400 });
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const team = company.teamManager.get(teamId);
|
|
608
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
609
|
+
|
|
610
|
+
const sprint = team.getSprint(sprintId);
|
|
611
|
+
if (!sprint) return NextResponse.json({ error: t('api.sprintNotFound') }, { status: 404 });
|
|
612
|
+
|
|
613
|
+
sprint.addGroupMessage(
|
|
614
|
+
{ id: 'boss', name: company.bossName, avatar: company.bossAvatar, role: 'Boss' },
|
|
615
|
+
message,
|
|
616
|
+
'message'
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
company.save();
|
|
620
|
+
|
|
621
|
+
// Trigger leader to respond when Boss sends a message
|
|
622
|
+
const { SprintStatus: SS } = await import('@/core/organization/team.js');
|
|
623
|
+
if (sprint.status === SS.IN_PROGRESS || sprint.status === SS.DISCUSSING || sprint.status === SS.PENDING_APPROVAL) {
|
|
624
|
+
const dept = company.findDepartment(team.departmentId);
|
|
625
|
+
const leader = dept?.agents.get(team.leaderId);
|
|
626
|
+
if (leader && leader.canChat()) {
|
|
627
|
+
try {
|
|
628
|
+
const recentChat = sprint.groupChat.slice(-10).map(m =>
|
|
629
|
+
`${m.from.name}: ${m.content}`
|
|
630
|
+
).join('\n');
|
|
631
|
+
|
|
632
|
+
const systemPrompts = {
|
|
633
|
+
[SS.PENDING_APPROVAL]: `You are "${leader.name}", team leader for sprint "${sprint.title}". The plan has been submitted and is awaiting Boss's approval. Boss is now giving feedback or suggestions on the plan. Listen carefully, acknowledge the feedback, explain how you will adjust the plan accordingly, and update the approach. Be concise and professional. Speak in the same language as the conversation.`,
|
|
634
|
+
[SS.DISCUSSING]: `You are "${leader.name}", team leader for sprint "${sprint.title}". The team is currently discussing the sprint plan. Boss is participating in the discussion. Respond to Boss's input, incorporate suggestions, and coordinate with the team. Be concise and professional. Speak in the same language as the conversation.`,
|
|
635
|
+
[SS.IN_PROGRESS]: `You are "${leader.name}", team leader for sprint "${sprint.title}". Boss just sent a message. Respond briefly and professionally. If Boss is giving instructions, acknowledge and explain how you'll handle it. Speak in the same language as the conversation.`,
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const reply = await leader.chat([
|
|
639
|
+
{
|
|
640
|
+
role: 'system',
|
|
641
|
+
content: systemPrompts[sprint.status] || systemPrompts[SS.IN_PROGRESS],
|
|
642
|
+
},
|
|
643
|
+
{ role: 'user', content: `Recent chat:\n${recentChat}\n\nBoss says: ${message}\n\nRespond as the team leader.` },
|
|
644
|
+
], { temperature: 0.7, maxTokens: 512 });
|
|
645
|
+
|
|
646
|
+
sprint.addGroupMessage(leader, reply.content, 'message');
|
|
647
|
+
company.save();
|
|
648
|
+
|
|
649
|
+
// If pending_approval and Boss gave feedback, update the plan then trigger team discussion
|
|
650
|
+
if (sprint.status === SS.PENDING_APPROVAL && sprint.plan) {
|
|
651
|
+
// Run async so API returns immediately
|
|
652
|
+
(async () => {
|
|
653
|
+
try {
|
|
654
|
+
const { getTraitStyle, getAgeStyle } = await import('@/core/prompts.js');
|
|
655
|
+
|
|
656
|
+
// Leader revises the plan based on Boss feedback
|
|
657
|
+
const planReply = await leader.chat([
|
|
658
|
+
{
|
|
659
|
+
role: 'system',
|
|
660
|
+
content: `You are "${leader.name}", team leader. Boss has given feedback on your sprint plan. Update the plan based on Boss's feedback. Output ONLY the revised plan in the same format (markdown). Keep it concise. Speak in the same language as the original plan.`,
|
|
661
|
+
},
|
|
662
|
+
{ role: 'user', content: `Original plan:\n${sprint.plan}\n\nBoss feedback: ${message}\n\nYour response to Boss: ${reply.content}\n\nNow output the revised plan:` },
|
|
663
|
+
], { temperature: 0.5, maxTokens: 2048 });
|
|
664
|
+
|
|
665
|
+
sprint.plan = planReply.content;
|
|
666
|
+
company.save();
|
|
667
|
+
|
|
668
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
669
|
+
|
|
670
|
+
// Now trigger team members to review and comment on the revised plan
|
|
671
|
+
const members = team.memberIds
|
|
672
|
+
.map(mid => dept.agents.get(mid))
|
|
673
|
+
.filter(a => a && a.id !== team.leaderId && a.canChat());
|
|
674
|
+
|
|
675
|
+
const recentMsgs = sprint.groupChat.slice(-15)
|
|
676
|
+
.filter(m => m.visibility !== 'flow')
|
|
677
|
+
.map(m => `${m.from?.name || 'System'}: ${m.content}`)
|
|
678
|
+
.join('\n');
|
|
679
|
+
|
|
680
|
+
const shuffled = [...members].sort(() => Math.random() - 0.5);
|
|
681
|
+
|
|
682
|
+
for (const agent of shuffled) {
|
|
683
|
+
try {
|
|
684
|
+
const p = agent.personality || {};
|
|
685
|
+
const traitStyle = getTraitStyle(p.trait);
|
|
686
|
+
const ageStyle = getAgeStyle(agent.age);
|
|
687
|
+
|
|
688
|
+
const feedbackResp = await agent.chat([
|
|
689
|
+
{
|
|
690
|
+
role: 'system',
|
|
691
|
+
content: `${traitStyle}\n\nYou are "${agent.name}", ${agent.gender || 'male'}, age ${agent.age || 28}, working as "${agent.role}".\nTone: ${p.tone || 'professional'}. Quirk: ${p.quirk || 'none'}.\n\n${ageStyle}\n\n---\n\nYour team leader just revised the sprint plan based on Boss's feedback. Now it's your turn to review the REVISED plan and give your opinion.\n\nYour expertise: ${agent.role}, skills: [${(agent.skills || []).join(', ')}]\n\nREQUIREMENTS:\n1. Comment on whether the revisions address Boss's concerns\n2. Point out any issues or improvements you see in the NEW plan\n3. If you have additional suggestions, propose them\n4. Keep it concise (2-4 sentences)\n\nDO NOT just say "looks good". Give REAL, specific feedback. Stay in character.\nSpeak in the same language as the conversation.`,
|
|
692
|
+
},
|
|
693
|
+
{ role: 'user', content: `Recent discussion:\n${recentMsgs}\n\nBoss's feedback: ${message}\n\nRevised plan:\n${sprint.plan}\n\nGive your feedback on the revised plan.` },
|
|
694
|
+
], { temperature: 0.8, maxTokens: 400 });
|
|
695
|
+
|
|
696
|
+
sprint.addGroupMessage(agent, feedbackResp.content, 'message');
|
|
697
|
+
company.save();
|
|
698
|
+
|
|
699
|
+
await new Promise(r => setTimeout(r, 800 + Math.random() * 1200));
|
|
700
|
+
} catch (e) { /* ignore individual agent failure */ }
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Leader incorporates team feedback into a final revision
|
|
704
|
+
if (shuffled.length > 0) {
|
|
705
|
+
await new Promise(r => setTimeout(r, 800));
|
|
706
|
+
|
|
707
|
+
const finalChat = sprint.groupChat.slice(-15)
|
|
708
|
+
.filter(m => m.visibility !== 'flow')
|
|
709
|
+
.map(m => `${m.from?.name || 'System'}: ${m.content}`)
|
|
710
|
+
.join('\n');
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const wrapResp = await leader.chat([
|
|
714
|
+
{
|
|
715
|
+
role: 'system',
|
|
716
|
+
content: `You are "${leader.name}", team leader. Boss gave feedback, you revised the plan, and team members have reviewed the revision. Now briefly acknowledge the team's input and confirm the plan is updated. Be concise (2-3 sentences). Speak in the same language as the conversation.`,
|
|
717
|
+
},
|
|
718
|
+
{ role: 'user', content: `Discussion:\n${finalChat}\n\nWrap up briefly.` },
|
|
719
|
+
], { temperature: 0.7, maxTokens: 256 });
|
|
720
|
+
|
|
721
|
+
sprint.addGroupMessage(leader, wrapResp.content, 'message');
|
|
722
|
+
} catch (e) { /* ignore */ }
|
|
723
|
+
|
|
724
|
+
// Final plan revision incorporating team's latest feedback
|
|
725
|
+
try {
|
|
726
|
+
const finalPlan = await leader.chat([
|
|
727
|
+
{
|
|
728
|
+
role: 'system',
|
|
729
|
+
content: `You are "${leader.name}", team leader. Revise the plan one more time incorporating the team's latest feedback. Output ONLY the revised plan in markdown. Keep the same language.`,
|
|
730
|
+
},
|
|
731
|
+
{ role: 'user', content: `Current plan:\n${sprint.plan}\n\nTeam feedback:\n${finalChat}\n\nOutput the final revised plan:` },
|
|
732
|
+
], { temperature: 0.5, maxTokens: 2048 });
|
|
733
|
+
|
|
734
|
+
sprint.plan = finalPlan.content;
|
|
735
|
+
} catch (e) { /* ignore */ }
|
|
736
|
+
|
|
737
|
+
company.save();
|
|
738
|
+
}
|
|
739
|
+
} catch (e) {
|
|
740
|
+
console.error('[Sprint Boss Feedback Discussion] Error:', e.message);
|
|
741
|
+
}
|
|
742
|
+
})();
|
|
743
|
+
} else {
|
|
744
|
+
company.save();
|
|
745
|
+
}
|
|
746
|
+
} catch (e) { /* ignore */ }
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return NextResponse.json({ data: { success: true } });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// === Delete Sprint ===
|
|
754
|
+
if (action === 'delete_sprint') {
|
|
755
|
+
const { teamId, sprintId } = body;
|
|
756
|
+
if (!teamId || !sprintId) {
|
|
757
|
+
return NextResponse.json({ error: t('api.sprintDiscussRequired') }, { status: 400 });
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
const team = company.teamManager.get(teamId);
|
|
761
|
+
if (!team) return NextResponse.json({ error: t('api.teamNotFound') }, { status: 404 });
|
|
762
|
+
|
|
763
|
+
team.sprints.delete(sprintId);
|
|
764
|
+
company.save();
|
|
765
|
+
return NextResponse.json({ data: { success: true } });
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return NextResponse.json({ error: t('api.unknownAction') }, { status: 400 });
|
|
769
|
+
} catch (e) {
|
|
770
|
+
console.error('[Teams API] POST error:', e);
|
|
771
|
+
return NextResponse.json({ error: e.message || 'Internal server error' }, { status: 500 });
|
|
772
|
+
}
|
|
773
|
+
}
|