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.
Files changed (159) hide show
  1. package/.dockerignore +33 -0
  2. package/.nvmrc +1 -0
  3. package/ARCHITECTURE.md +394 -0
  4. package/Dockerfile +50 -0
  5. package/LICENSE +29 -0
  6. package/README.md +206 -0
  7. package/bin/i18n.js +46 -0
  8. package/bin/ideaco.js +494 -0
  9. package/deploy.sh +15 -0
  10. package/docker-compose.yml +30 -0
  11. package/electron/main.cjs +986 -0
  12. package/electron/preload.cjs +14 -0
  13. package/electron/web-backends.cjs +854 -0
  14. package/jsconfig.json +8 -0
  15. package/next.config.mjs +34 -0
  16. package/package.json +134 -0
  17. package/postcss.config.mjs +6 -0
  18. package/public/demo/dashboard.png +0 -0
  19. package/public/demo/employee.png +0 -0
  20. package/public/demo/messages.png +0 -0
  21. package/public/demo/office.png +0 -0
  22. package/public/demo/requirement.png +0 -0
  23. package/public/logo.jpeg +0 -0
  24. package/public/logo.png +0 -0
  25. package/scripts/prepare-electron.js +67 -0
  26. package/scripts/release.js +76 -0
  27. package/src/app/api/agents/[agentId]/chat/route.js +70 -0
  28. package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
  29. package/src/app/api/agents/[agentId]/route.js +106 -0
  30. package/src/app/api/avatar/route.js +104 -0
  31. package/src/app/api/browse-dir/route.js +44 -0
  32. package/src/app/api/chat/route.js +265 -0
  33. package/src/app/api/company/factory-reset/route.js +43 -0
  34. package/src/app/api/company/route.js +82 -0
  35. package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
  36. package/src/app/api/departments/route.js +92 -0
  37. package/src/app/api/group-chat-loop/events/route.js +70 -0
  38. package/src/app/api/group-chat-loop/route.js +94 -0
  39. package/src/app/api/mailbox/route.js +100 -0
  40. package/src/app/api/messages/route.js +14 -0
  41. package/src/app/api/providers/[id]/configure/route.js +21 -0
  42. package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
  43. package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
  44. package/src/app/api/providers/route.js +11 -0
  45. package/src/app/api/requirements/route.js +242 -0
  46. package/src/app/api/secretary/route.js +65 -0
  47. package/src/app/api/system/cli-backends/route.js +91 -0
  48. package/src/app/api/system/cron/route.js +110 -0
  49. package/src/app/api/system/knowledge/route.js +104 -0
  50. package/src/app/api/system/plugins/route.js +40 -0
  51. package/src/app/api/system/skills/route.js +46 -0
  52. package/src/app/api/system/status/route.js +46 -0
  53. package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
  54. package/src/app/api/talent-market/[profileId]/route.js +17 -0
  55. package/src/app/api/talent-market/route.js +26 -0
  56. package/src/app/api/teams/route.js +773 -0
  57. package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
  58. package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
  59. package/src/app/globals.css +130 -0
  60. package/src/app/layout.jsx +40 -0
  61. package/src/app/page.jsx +97 -0
  62. package/src/components/AgentChatModal.jsx +164 -0
  63. package/src/components/AgentDetailModal.jsx +425 -0
  64. package/src/components/AgentSpyModal.jsx +481 -0
  65. package/src/components/AvatarGrid.jsx +29 -0
  66. package/src/components/BossProfileModal.jsx +162 -0
  67. package/src/components/CachedAvatar.jsx +77 -0
  68. package/src/components/ChatPanel.jsx +219 -0
  69. package/src/components/ChatShared.jsx +255 -0
  70. package/src/components/DepartmentDetail.jsx +842 -0
  71. package/src/components/DepartmentView.jsx +367 -0
  72. package/src/components/FileReference.jsx +260 -0
  73. package/src/components/FilesView.jsx +465 -0
  74. package/src/components/GroupChatView.jsx +799 -0
  75. package/src/components/Mailbox.jsx +926 -0
  76. package/src/components/MessagesView.jsx +112 -0
  77. package/src/components/OnboardingGuide.jsx +209 -0
  78. package/src/components/OrgTree.jsx +151 -0
  79. package/src/components/Overview.jsx +391 -0
  80. package/src/components/PixelOffice.jsx +2281 -0
  81. package/src/components/ProviderGrid.jsx +551 -0
  82. package/src/components/ProvidersBoard.jsx +16 -0
  83. package/src/components/RequirementDetail.jsx +1279 -0
  84. package/src/components/RequirementsBoard.jsx +187 -0
  85. package/src/components/SecretarySettings.jsx +295 -0
  86. package/src/components/SetupWizard.jsx +388 -0
  87. package/src/components/Sidebar.jsx +169 -0
  88. package/src/components/SystemMonitor.jsx +808 -0
  89. package/src/components/TalentMarket.jsx +183 -0
  90. package/src/components/TeamDetail.jsx +697 -0
  91. package/src/core/agent/base-agent.js +104 -0
  92. package/src/core/agent/chat-store.js +602 -0
  93. package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
  94. package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
  95. package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
  96. package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
  97. package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
  98. package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
  99. package/src/core/agent/cli-agent/backends/index.js +27 -0
  100. package/src/core/agent/cli-agent/backends/registry.js +580 -0
  101. package/src/core/agent/cli-agent/index.js +154 -0
  102. package/src/core/agent/index.js +60 -0
  103. package/src/core/agent/llm-agent/client.js +320 -0
  104. package/src/core/agent/llm-agent/index.js +97 -0
  105. package/src/core/agent/message-bus.js +211 -0
  106. package/src/core/agent/session.js +608 -0
  107. package/src/core/agent/tools.js +596 -0
  108. package/src/core/agent/web-agent/backends/base-backend.js +180 -0
  109. package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
  110. package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
  111. package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
  112. package/src/core/agent/web-agent/backends/index.js +91 -0
  113. package/src/core/agent/web-agent/index.js +278 -0
  114. package/src/core/agent/web-agent/web-client.js +407 -0
  115. package/src/core/employee/base-employee.js +1088 -0
  116. package/src/core/employee/index.js +35 -0
  117. package/src/core/employee/knowledge.js +327 -0
  118. package/src/core/employee/lifecycle.js +990 -0
  119. package/src/core/employee/memory/index.js +642 -0
  120. package/src/core/employee/memory/store.js +143 -0
  121. package/src/core/employee/performance.js +224 -0
  122. package/src/core/employee/secretary.js +625 -0
  123. package/src/core/employee/skills.js +398 -0
  124. package/src/core/index.js +38 -0
  125. package/src/core/organization/company.js +2600 -0
  126. package/src/core/organization/department.js +737 -0
  127. package/src/core/organization/group-chat-loop.js +264 -0
  128. package/src/core/organization/index.js +8 -0
  129. package/src/core/organization/persistence.js +111 -0
  130. package/src/core/organization/team.js +267 -0
  131. package/src/core/organization/workforce/hr.js +377 -0
  132. package/src/core/organization/workforce/providers.js +468 -0
  133. package/src/core/organization/workforce/role-archetypes.js +805 -0
  134. package/src/core/organization/workforce/talent-market.js +205 -0
  135. package/src/core/prompts.js +532 -0
  136. package/src/core/requirement.js +1789 -0
  137. package/src/core/system/audit.js +483 -0
  138. package/src/core/system/cron.js +449 -0
  139. package/src/core/system/index.js +7 -0
  140. package/src/core/system/plugin.js +2183 -0
  141. package/src/core/utils/json-parse.js +188 -0
  142. package/src/core/workspace.js +239 -0
  143. package/src/lib/api-i18n.js +211 -0
  144. package/src/lib/avatar.js +268 -0
  145. package/src/lib/client-store.js +1025 -0
  146. package/src/lib/config-validator.js +483 -0
  147. package/src/lib/format-time.js +22 -0
  148. package/src/lib/hooks.js +414 -0
  149. package/src/lib/i18n.js +134 -0
  150. package/src/lib/paths.js +23 -0
  151. package/src/lib/store.js +72 -0
  152. package/src/locales/de.js +393 -0
  153. package/src/locales/en.js +1054 -0
  154. package/src/locales/es.js +393 -0
  155. package/src/locales/fr.js +393 -0
  156. package/src/locales/ja.js +501 -0
  157. package/src/locales/ko.js +513 -0
  158. package/src/locales/zh.js +828 -0
  159. package/tailwind.config.mjs +11 -0
@@ -0,0 +1,44 @@
1
+ import { NextResponse } from 'next/server';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { getApiT } from '@/lib/api-i18n';
6
+
7
+ /**
8
+ * GET /api/browse-dir?path=xxx - List subdirectories at the given path
9
+ * Without a path param, returns subdirectories in the user's home directory
10
+ */
11
+ export async function GET(request) {
12
+ const t = getApiT(request);
13
+ try {
14
+ const { searchParams } = new URL(request.url);
15
+ const dirPath = searchParams.get('path') || os.homedir();
16
+
17
+ // Safety check: ensure path exists and is a directory
18
+ const resolved = path.resolve(dirPath);
19
+ if (!fs.existsSync(resolved)) {
20
+ return NextResponse.json({ error: t('api.pathNotExist') }, { status: 400 });
21
+ }
22
+ const stat = fs.statSync(resolved);
23
+ if (!stat.isDirectory()) {
24
+ return NextResponse.json({ error: t('api.pathNotDirectory') }, { status: 400 });
25
+ }
26
+
27
+ const entries = fs.readdirSync(resolved, { withFileTypes: true });
28
+ const dirs = entries
29
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
30
+ .map(e => ({
31
+ name: e.name,
32
+ path: path.join(resolved, e.name),
33
+ }))
34
+ .sort((a, b) => a.name.localeCompare(b.name));
35
+
36
+ return NextResponse.json({
37
+ current: resolved,
38
+ parent: path.dirname(resolved) !== resolved ? path.dirname(resolved) : null,
39
+ dirs,
40
+ });
41
+ } catch (e) {
42
+ return NextResponse.json({ error: e.message }, { status: 500 });
43
+ }
44
+ }
@@ -0,0 +1,265 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { chatStore } from '@/core/agent/chat-store.js';
4
+ import { getApiT } from '@/lib/api-i18n';
5
+
6
+ // Store running task states
7
+ const runningTasks = new Map();
8
+
9
+ export async function POST(request) {
10
+ const t = getApiT(request);
11
+ const company = getCompany();
12
+ if (!company) {
13
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
14
+ }
15
+ try {
16
+ const { message } = await request.json();
17
+ if (!message) {
18
+ return NextResponse.json({ error: t('api.messageRequired') }, { status: 400 });
19
+ }
20
+ const reply = await company.chatWithSecretary(message);
21
+
22
+ // If secretary returned a create_department action, auto-trigger department creation flow
23
+ if (reply.action?.type === 'create_department') {
24
+ const taskId = `dept_${Date.now()}`;
25
+ const { departmentName, mission, members } = reply.action;
26
+ const deptName = departmentName || 'New Project Dept';
27
+ const deptMission = mission || message;
28
+
29
+ runningTasks.set(taskId, { status: 'running', type: 'create_department', startedAt: Date.now() });
30
+
31
+ // Async department creation (non-blocking response)
32
+ (async () => {
33
+ try {
34
+ // Secretary already designed the team — create department directly
35
+ const dept = await company.createDepartmentDirect({
36
+ departmentName: deptName,
37
+ mission: deptMission,
38
+ members: members || [],
39
+ });
40
+ console.log(`✅ Department created: ${dept.name}, ${dept.agents.size} people ready`);
41
+
42
+ runningTasks.set(taskId, {
43
+ status: 'completed',
44
+ summary: {
45
+ departmentId: dept.id,
46
+ departmentName: dept.name,
47
+ memberCount: dept.agents.size,
48
+ members: dept.getMembers().map(a => ({ name: a.name, role: a.role })),
49
+ },
50
+ completedAt: Date.now(),
51
+ });
52
+
53
+ // Append a secretary message to notify the boss
54
+ const notifyMsg = {
55
+ role: 'secretary',
56
+ content: `🎉 "${dept.name}" department has been created! Recruited ${dept.agents.size} employees:\n${dept.getMembers().map(a => ` • ${a.name} (${a.role})`).join('\n')}\n\nTeam is ready, awaiting tasks!`,
57
+ action: { type: 'department_created', departmentId: dept.id, departmentName: dept.name },
58
+ time: new Date(),
59
+ };
60
+ company.chatHistory.push(notifyMsg);
61
+ chatStore.appendMessage(company.chatSessionId, notifyMsg);
62
+ company.save();
63
+
64
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
65
+ } catch (err) {
66
+ console.error(`❌ Department creation failed:`, err.message);
67
+ runningTasks.set(taskId, { status: 'failed', error: err.message, failedAt: Date.now() });
68
+
69
+ const errMsg1 = {
70
+ role: 'secretary',
71
+ content: `😥 Encountered a problem creating the department: ${err.message}\n\nShall we try again?`,
72
+ time: new Date(),
73
+ };
74
+ company.chatHistory.push(errMsg1);
75
+ chatStore.appendMessage(company.chatSessionId, errMsg1);
76
+ company.save();
77
+
78
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
79
+ }
80
+ })();
81
+
82
+ reply.action.taskId = taskId;
83
+ reply.action.taskStatus = 'running';
84
+ }
85
+
86
+ // If secretary returned need_new_department action, also auto-trigger create department + assign task
87
+ if (reply.action?.type === 'need_new_department') {
88
+ const taskId = `dept_task_${Date.now()}`;
89
+ const suggestedMission = reply.action.suggestedMission || message;
90
+
91
+ runningTasks.set(taskId, { status: 'running', type: 'create_and_assign', startedAt: Date.now() });
92
+
93
+ (async () => {
94
+ try {
95
+ // Step 1: Create department (will fallback to planDepartment since no members)
96
+ const dept = await company.createDepartmentDirect({
97
+ departmentName: 'New Project Dept',
98
+ mission: suggestedMission,
99
+ members: [],
100
+ });
101
+ console.log(`✅ Auto-created department: ${dept.name}, ${dept.agents.size} people ready`);
102
+
103
+ // Append secretary message
104
+ const autoCreateMsg = {
105
+ role: 'secretary',
106
+ content: `🎉 I've auto-created the "${dept.name}" department (${dept.agents.size} people), now starting task execution...`,
107
+ action: { type: 'department_created', departmentId: dept.id },
108
+ time: new Date(),
109
+ };
110
+ company.chatHistory.push(autoCreateMsg);
111
+ chatStore.appendMessage(company.chatSessionId, autoCreateMsg);
112
+ company.save();
113
+
114
+ // Step 2: Assign task
115
+ const summary = await company.assignTaskToDepartment(dept.id, suggestedMission);
116
+ runningTasks.set(taskId, { status: 'completed', summary, completedAt: Date.now() });
117
+ console.log(`✅ Task execution complete: ${summary.successTasks}/${summary.totalTasks}`);
118
+
119
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
120
+ } catch (err) {
121
+ console.error(`❌ Create department and assign task failed:`, err.message);
122
+ runningTasks.set(taskId, { status: 'failed', error: err.message, failedAt: Date.now() });
123
+
124
+ const errMsg2 = {
125
+ role: 'secretary',
126
+ content: `😥 Encountered a problem creating the department and assigning the task: ${err.message}\n\nPlease try creating the department manually and try again.`,
127
+ time: new Date(),
128
+ };
129
+ company.chatHistory.push(errMsg2);
130
+ chatStore.appendMessage(company.chatSessionId, errMsg2);
131
+ company.save();
132
+
133
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
134
+ }
135
+ })();
136
+
137
+ reply.action.taskId = taskId;
138
+ reply.action.taskStatus = 'running';
139
+ }
140
+
141
+ // If secretary decided to handle the task herself
142
+ if (reply.action?.type === 'secretary_handle') {
143
+ const taskId = `secretary_${Date.now()}`;
144
+ const { taskDescription } = reply.action;
145
+
146
+ runningTasks.set(taskId, { status: 'running', type: 'secretary_handle', startedAt: Date.now() });
147
+
148
+ // Async execution of secretary's own task
149
+ (async () => {
150
+ try {
151
+ const result = await company.secretary.executeTaskDirectly(taskDescription || message, company);
152
+
153
+ runningTasks.set(taskId, {
154
+ status: 'completed',
155
+ summary: { content: result.content, success: result.success },
156
+ completedAt: Date.now(),
157
+ });
158
+
159
+ // Push the result as a secretary message
160
+ const resultMsg = {
161
+ role: 'secretary',
162
+ content: result.content,
163
+ action: { type: 'secretary_task_completed', taskId },
164
+ time: new Date(),
165
+ };
166
+ company.chatHistory.push(resultMsg);
167
+ chatStore.appendMessage(company.chatSessionId, resultMsg);
168
+ company.save();
169
+
170
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
171
+ } catch (err) {
172
+ console.error(`❌ Secretary task failed:`, err.message);
173
+ runningTasks.set(taskId, { status: 'failed', error: err.message, failedAt: Date.now() });
174
+
175
+ const errMsg = {
176
+ role: 'secretary',
177
+ content: `😥 Encountered a problem while handling the task: ${err.message}\n\nWant me to try again?`,
178
+ time: new Date(),
179
+ };
180
+ company.chatHistory.push(errMsg);
181
+ chatStore.appendMessage(company.chatSessionId, errMsg);
182
+ company.save();
183
+
184
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
185
+ }
186
+ })();
187
+
188
+ reply.action.taskId = taskId;
189
+ reply.action.taskStatus = 'running';
190
+ }
191
+
192
+ // If secretary returned task_assigned action, auto-trigger background task execution
193
+ if (reply.action?.type === 'task_assigned' && reply.action.departmentId) {
194
+ const taskId = `task_${Date.now()}`;
195
+ const { departmentId, taskDescription, taskTitle } = reply.action;
196
+ const description = taskDescription || message; // fallback to original message
197
+
198
+ // Mark task as in progress
199
+ runningTasks.set(taskId, { status: 'running', departmentId, startedAt: Date.now() });
200
+
201
+ // Async task execution (non-blocking response)
202
+ company.assignTaskToDepartment(departmentId, description, taskTitle || null)
203
+ .then(summary => {
204
+ runningTasks.set(taskId, { status: 'completed', summary, completedAt: Date.now() });
205
+ console.log(`✅ Task [${taskId}] completed: ${summary.successTasks}/${summary.totalTasks} succeeded`);
206
+ // Clean up after 30 minutes
207
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
208
+ })
209
+ .catch(err => {
210
+ console.error(`❌ Task [${taskId}] failed:`, err.message);
211
+ runningTasks.set(taskId, { status: 'failed', error: err.message, failedAt: Date.now() });
212
+
213
+ // Append secretary message to notify boss of task failure
214
+ const taskFailMsg = {
215
+ role: 'secretary',
216
+ content: `❌ Task execution failed... Error: ${err.message}\n\nShall we try again?`,
217
+ time: new Date(),
218
+ };
219
+ company.chatHistory.push(taskFailMsg);
220
+ chatStore.appendMessage(company.chatSessionId, taskFailMsg);
221
+ company.save();
222
+
223
+ setTimeout(() => runningTasks.delete(taskId), 30 * 60 * 1000);
224
+ });
225
+
226
+ // Attach task ID in reply for frontend polling
227
+ reply.action.taskId = taskId;
228
+ reply.action.taskStatus = 'running';
229
+ }
230
+
231
+ return NextResponse.json({
232
+ success: true,
233
+ data: {
234
+ reply,
235
+ chatHistory: company.chatHistory.slice(-30),
236
+ },
237
+ });
238
+ } catch (e) {
239
+ return NextResponse.json({ error: e.message }, { status: 500 });
240
+ }
241
+ }
242
+
243
+ export async function GET(request) {
244
+ const t = getApiT(request);
245
+ const company = getCompany();
246
+ if (!company) {
247
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
248
+ }
249
+
250
+ // Support task status queries
251
+ const url = new URL(request.url);
252
+ const taskId = url.searchParams.get('taskId');
253
+
254
+ if (taskId) {
255
+ const taskState = runningTasks.get(taskId);
256
+ if (!taskState) {
257
+ return NextResponse.json({ data: { status: 'unknown' } });
258
+ }
259
+ return NextResponse.json({ data: taskState });
260
+ }
261
+
262
+ return NextResponse.json({
263
+ data: company.chatHistory.slice(-50),
264
+ });
265
+ }
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { resetCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+ import fs from 'fs';
5
+ import { DATA_DIR, WORKSPACE_DIR } from '@/lib/paths.js';
6
+
7
+ /**
8
+ * POST /api/company/factory-reset
9
+ * Nuclear option: wipe ALL data (company state, memories, chats, audit, workspace).
10
+ * This is the "restart company" high-risk operation.
11
+ */
12
+ export async function POST(request) {
13
+ const t = getApiT(request);
14
+
15
+ try {
16
+ // 1. Reset in-memory company state + clear state files & memories
17
+ resetCompany();
18
+
19
+ // 2. Recursively remove the entire data directory (chats, audit, etc.)
20
+ if (fs.existsSync(DATA_DIR)) {
21
+ fs.rmSync(DATA_DIR, { recursive: true, force: true });
22
+ console.log(`🗑️ Factory reset: removed DATA_DIR (${DATA_DIR})`);
23
+ }
24
+
25
+ // 3. Recursively remove the entire workspace directory
26
+ if (fs.existsSync(WORKSPACE_DIR)) {
27
+ fs.rmSync(WORKSPACE_DIR, { recursive: true, force: true });
28
+ console.log(`🗑️ Factory reset: removed WORKSPACE_DIR (${WORKSPACE_DIR})`);
29
+ }
30
+
31
+ // 4. Re-create the data directory so the app can restart cleanly
32
+ fs.mkdirSync(DATA_DIR, { recursive: true });
33
+
34
+ return NextResponse.json({
35
+ success: true,
36
+ message: t('api.factoryResetDone'),
37
+ cleared: { dataDir: DATA_DIR, workspaceDir: WORKSPACE_DIR },
38
+ });
39
+ } catch (e) {
40
+ console.error('❌ Factory reset failed:', e);
41
+ return NextResponse.json({ error: e.message }, { status: 500 });
42
+ }
43
+ }
@@ -0,0 +1,82 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany, setCompany, resetCompany } from '@/lib/store';
3
+ import { Company, setPromptLocale, getPromptLocaleCode } from '@/core/index.js';
4
+ import { getApiT } from '@/lib/api-i18n';
5
+
6
+ export async function GET() {
7
+ const company = getCompany();
8
+ return NextResponse.json({ data: company ? company.getFullState() : null, promptLocale: getPromptLocaleCode() });
9
+ }
10
+
11
+ export async function POST(request) {
12
+ const t = getApiT(request);
13
+ try {
14
+ const { companyName, bossName, secretaryConfig, promptLocale } = await request.json();
15
+ if (!companyName) {
16
+ return NextResponse.json({ error: t('api.companyNameRequired') }, { status: 400 });
17
+ }
18
+ // Set prompt locale for AI employee chat (default: 'en')
19
+ if (promptLocale) {
20
+ setPromptLocale(promptLocale);
21
+ }
22
+ const company = new Company(companyName, bossName || 'Boss', {
23
+ ...secretaryConfig,
24
+ secretaryName: secretaryConfig?.secretaryName,
25
+ secretaryAvatar: secretaryConfig?.secretaryAvatar,
26
+ });
27
+ // Set boss avatar if provided during creation
28
+ if (secretaryConfig?.bossAvatar) {
29
+ company.bossAvatar = secretaryConfig.bossAvatar;
30
+ }
31
+ setCompany(company);
32
+ return NextResponse.json({ success: true, data: company.getFullState() });
33
+ } catch (e) {
34
+ return NextResponse.json({ error: e.message }, { status: 500 });
35
+ }
36
+ }
37
+
38
+ /**
39
+ * DELETE /api/company - Reset company (clear persisted data)
40
+ */
41
+ export async function DELETE(request) {
42
+ const t = getApiT(request);
43
+ resetCompany();
44
+ return NextResponse.json({ success: true, message: t('api.companyDissolved') });
45
+ }
46
+
47
+ /**
48
+ * PUT /api/company - Update company settings (e.g., boss avatar)
49
+ */
50
+ export async function PUT(request) {
51
+ const t = getApiT(request);
52
+ const company = getCompany();
53
+ if (!company) {
54
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
55
+ }
56
+ try {
57
+ const body = await request.json();
58
+ // Support prompt locale change via PUT
59
+ if (body.promptLocale) {
60
+ setPromptLocale(body.promptLocale);
61
+ }
62
+ if (typeof company.updateBossProfile !== 'function') {
63
+ // Fallback: server might have a stale instance without this method, apply directly
64
+ if (body.avatar) company.bossAvatar = body.avatar;
65
+ const { saveState } = await import('@/core/organization/persistence.js');
66
+ saveState(company);
67
+ return NextResponse.json({
68
+ success: true,
69
+ data: { bossAvatar: company.bossAvatar },
70
+ fullState: company.getFullState(),
71
+ });
72
+ }
73
+ const result = company.updateBossProfile(body);
74
+ return NextResponse.json({
75
+ success: true,
76
+ data: result,
77
+ fullState: company.getFullState(),
78
+ });
79
+ } catch (e) {
80
+ return NextResponse.json({ error: e.message }, { status: 500 });
81
+ }
82
+ }
@@ -0,0 +1,19 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ export async function POST(request, { params }) {
6
+ const t = getApiT(request);
7
+ const company = getCompany();
8
+ if (!company) return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
9
+
10
+ try {
11
+ const { deptId, agentId } = await params;
12
+ const { reason } = await request.json();
13
+ company.dismissAgent(deptId, agentId, reason || 'Dismissed by boss');
14
+ company._log('Dismiss employee', `Employee dismissed`);
15
+ return NextResponse.json({ success: true, data: company.getFullState() });
16
+ } catch (e) {
17
+ return NextResponse.json({ error: e.message }, { status: 400 });
18
+ }
19
+ }
@@ -0,0 +1,92 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ /**
6
+ * POST /api/departments - Department operations
7
+ * action=undefined: Generate recruitment plan
8
+ * action=confirm: Confirm recruitment plan
9
+ * action=adjust: Generate adjustment plan
10
+ * action=confirmAdjust: Confirm adjustment plan
11
+ * action=disband: Disband department
12
+ */
13
+ export async function POST(request) {
14
+ const t = getApiT(request);
15
+ const company = getCompany();
16
+ if (!company) return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
17
+
18
+ try {
19
+ const url = new URL(request.url);
20
+ const action = url.searchParams.get('action');
21
+
22
+ if (action === 'confirm') {
23
+ // Confirm recruitment plan
24
+ const { planId } = await request.json();
25
+ if (!planId) {
26
+ return NextResponse.json({ error: t('api.planIdRequired') }, { status: 400 });
27
+ }
28
+ const dept = await company.confirmPlan(planId);
29
+ return NextResponse.json({ success: true, data: company.getFullState() });
30
+
31
+ } else if (action === 'adjust') {
32
+ // Generate adjustment plan
33
+ const { departmentId, adjustGoal } = await request.json();
34
+ if (!departmentId || !adjustGoal) {
35
+ return NextResponse.json({ error: t('api.deptIdAdjustGoalRequired') }, { status: 400 });
36
+ }
37
+ const plan = await company.planAdjustment(departmentId, adjustGoal);
38
+ return NextResponse.json({ success: true, data: plan });
39
+
40
+ } else if (action === 'confirmAdjust') {
41
+ // Confirm adjustment plan
42
+ const { planId } = await request.json();
43
+ if (!planId) {
44
+ return NextResponse.json({ error: t('api.adjustPlanIdRequired') }, { status: 400 });
45
+ }
46
+ await company.confirmAdjustment(planId);
47
+ return NextResponse.json({ success: true, data: company.getFullState() });
48
+
49
+ } else if (action === 'disband') {
50
+ // Disband department
51
+ const { departmentId, reason } = await request.json();
52
+ if (!departmentId) {
53
+ return NextResponse.json({ error: t('api.deptIdRequired') }, { status: 400 });
54
+ }
55
+ const result = company.disbandDepartment(departmentId, reason || 'Boss decision');
56
+ return NextResponse.json({ success: true, data: company.getFullState(), result });
57
+
58
+ } else if (action === 'boss_message') {
59
+ // Boss sends message to department group chat
60
+ const { departmentId, message } = await request.json();
61
+ if (!departmentId || !message) {
62
+ return NextResponse.json({ error: t('api.deptIdMessageRequired') }, { status: 400 });
63
+ }
64
+ const result = company.sendBossDeptGroupMessage(departmentId, message);
65
+ return NextResponse.json({ success: true, data: result });
66
+
67
+ } else if (action === 'dept_chat') {
68
+ // Get department group chat messages
69
+ const { departmentId } = await request.json();
70
+ if (!departmentId) {
71
+ return NextResponse.json({ error: t('api.deptIdRequired') }, { status: 400 });
72
+ }
73
+ const dept = company.findDepartment(departmentId);
74
+ if (!dept) {
75
+ return NextResponse.json({ error: t('api.deptNotFound') }, { status: 404 });
76
+ }
77
+ return NextResponse.json({ success: true, data: { groupChat: dept.groupChat || [] } });
78
+
79
+ } else {
80
+ // Generate recruitment plan
81
+ const { name, mission } = await request.json();
82
+ if (!name || !mission) {
83
+ return NextResponse.json({ error: t('api.deptNameMissionRequired') }, { status: 400 });
84
+ }
85
+ const plan = await company.planDepartment(name, mission);
86
+ return NextResponse.json({ success: true, data: plan });
87
+ }
88
+ } catch (e) {
89
+ console.error('Department operation failed:', e);
90
+ return NextResponse.json({ error: e.message }, { status: 500 });
91
+ }
92
+ }
@@ -0,0 +1,70 @@
1
+ import { groupChatLoop } from '@/core/organization/group-chat-loop.js';
2
+
3
+ export const dynamic = 'force-dynamic';
4
+
5
+ /**
6
+ * SSE endpoint for real-time monologue (flow) events.
7
+ *
8
+ * GET /api/group-chat-loop/events
9
+ *
10
+ * Pushes:
11
+ * - monologue:start { agentId, agentName, groupId }
12
+ * - monologue:end { agentId, agentName, groupId, decision, thoughtCount, thoughts, reason }
13
+ * - snapshot (initial full state on connect)
14
+ */
15
+ export async function GET() {
16
+ const encoder = new TextEncoder();
17
+
18
+ const stream = new ReadableStream({
19
+ start(controller) {
20
+ const send = (event, data) => {
21
+ try {
22
+ controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
23
+ } catch { /* client disconnected */ }
24
+ };
25
+
26
+ // Send initial snapshot so client immediately knows who is thinking
27
+ const snapshot = groupChatLoop.getActiveThinkingAgents();
28
+ send('snapshot', snapshot);
29
+
30
+ // Listen to lifecycle events
31
+ const onStart = (data) => send('monologue:start', data);
32
+ const onEnd = (data) => send('monologue:end', data);
33
+
34
+ groupChatLoop.on('monologue:start', onStart);
35
+ groupChatLoop.on('monologue:end', onEnd);
36
+
37
+ // Keep-alive every 30s to prevent proxy/browser timeout
38
+ const keepAlive = setInterval(() => {
39
+ try {
40
+ controller.enqueue(encoder.encode(': keep-alive\n\n'));
41
+ } catch {
42
+ clearInterval(keepAlive);
43
+ }
44
+ }, 30000);
45
+
46
+ // Cleanup when client disconnects
47
+ const cleanup = () => {
48
+ groupChatLoop.off('monologue:start', onStart);
49
+ groupChatLoop.off('monologue:end', onEnd);
50
+ clearInterval(keepAlive);
51
+ };
52
+
53
+ // AbortSignal isn't available here, but we detect enqueue failure above.
54
+ // Store cleanup for the cancel() callback.
55
+ controller._cleanup = cleanup;
56
+ },
57
+ cancel(controller) {
58
+ controller?._cleanup?.();
59
+ },
60
+ });
61
+
62
+ return new Response(stream, {
63
+ headers: {
64
+ 'Content-Type': 'text/event-stream',
65
+ 'Cache-Control': 'no-cache, no-transform',
66
+ 'Connection': 'keep-alive',
67
+ 'X-Accel-Buffering': 'no',
68
+ },
69
+ });
70
+ }