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,94 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { groupChatLoop } from '@/core/organization/group-chat-loop.js';
4
+ import { getApiT } from '@/lib/api-i18n';
5
+
6
+ /**
7
+ * Group Chat Flow API - lets the boss peek at employees' inner monologues in group chats
8
+ *
9
+ * GET /api/group-chat-loop?agentId=xxx&groupId=xxx - Get an employee's current flow in a group
10
+ * GET /api/group-chat-loop?agentId=xxx&groupId=xxx&history=1 - Get an employee's flow history in a group
11
+ * GET /api/group-chat-loop?active=1 - Get all currently thinking employees
12
+ * GET /api/group-chat-loop?status=1 - Get group chat loop engine status
13
+ */
14
+ export async function GET(request) {
15
+ const t = getApiT(request);
16
+ const company = getCompany();
17
+ if (!company) {
18
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
19
+ }
20
+
21
+ const url = new URL(request.url);
22
+ const agentId = url.searchParams.get('agentId');
23
+ const groupId = url.searchParams.get('groupId');
24
+ const history = url.searchParams.get('history');
25
+ const active = url.searchParams.get('active');
26
+ const status = url.searchParams.get('status');
27
+
28
+ // Get engine status
29
+ if (status) {
30
+ return NextResponse.json({
31
+ data: {
32
+ running: groupChatLoop.running,
33
+ activePollers: groupChatLoop._lifecycles.size,
34
+ activeMonologues: groupChatLoop.getActiveThinkingAgents().length,
35
+ processingCount: groupChatLoop._lifecycles.size,
36
+ },
37
+ });
38
+ }
39
+
40
+ // Get all currently thinking employees
41
+ if (active) {
42
+ const thinkingAgents = groupChatLoop.getActiveThinkingAgents();
43
+ return NextResponse.json({ data: thinkingAgents });
44
+ }
45
+
46
+ // Get an employee's flow in a specific group
47
+ if (agentId && groupId) {
48
+ // Get flow messages (work process) for an employee in a group
49
+ const flowMessages = url.searchParams.get('flowMessages');
50
+ const monologueMessages = url.searchParams.get('monologueMessages');
51
+ if (flowMessages || monologueMessages) {
52
+ // Support both requirement group chats and department group chats
53
+ let chatMessages = [];
54
+ if (groupId.startsWith('dept-')) {
55
+ const deptId = groupId.replace('dept-', '');
56
+ const dept = company.findDepartment(deptId);
57
+ chatMessages = dept?.groupChat || [];
58
+ } else {
59
+ const requirement = company.requirementManager.get(groupId);
60
+ chatMessages = requirement?.groupChat || [];
61
+ }
62
+
63
+ if (monologueMessages) {
64
+ // Return all inner monologue messages for this employee in this group (type === 'monologue')
65
+ const agentMonologueMsgs = chatMessages
66
+ .filter(m => m.type === 'monologue' && m.from?.id === agentId)
67
+ .slice(-50);
68
+ return NextResponse.json({ data: agentMonologueMsgs });
69
+ }
70
+
71
+ // Return work logs for this employee in this group (flow visibility, excluding monologue)
72
+ const agentFlowMsgs = chatMessages
73
+ .filter(m => m.visibility === 'flow' && m.type !== 'monologue' && m.from?.id === agentId)
74
+ .slice(-50);
75
+ return NextResponse.json({ data: agentFlowMsgs });
76
+ }
77
+
78
+ if (history) {
79
+ // Historical flow
80
+ const monologues = groupChatLoop.getMonologueHistory(agentId, groupId);
81
+ return NextResponse.json({
82
+ data: monologues.map(m => m.toJSON()),
83
+ });
84
+ } else {
85
+ // Current flow
86
+ const current = groupChatLoop.getActiveMonologue(agentId, groupId);
87
+ return NextResponse.json({
88
+ data: current ? current.toJSON() : null,
89
+ });
90
+ }
91
+ }
92
+
93
+ return NextResponse.json({ error: t('api.missingField', { field: 'agentId and groupId' }) }, { status: 400 });
94
+ }
@@ -0,0 +1,100 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ /**
6
+ * GET /api/mailbox - Get mailbox list
7
+ * POST /api/mailbox - Reply to mail
8
+ */
9
+ export async function GET(request) {
10
+ const t = getApiT(request);
11
+ const company = getCompany();
12
+ if (!company) return NextResponse.json({ error: t('api.companyNotFound') }, { status: 400 });
13
+
14
+ return NextResponse.json({
15
+ data: {
16
+ mails: company.mailbox.slice().reverse().slice(0, 50),
17
+ unreadCount: company.mailbox.filter(m => !m.read).length,
18
+ },
19
+ });
20
+ }
21
+
22
+ export async function POST(request) {
23
+ const t = getApiT(request);
24
+ const company = getCompany();
25
+ if (!company) return NextResponse.json({ error: t('api.companyNotFound') }, { status: 400 });
26
+
27
+ try {
28
+ const { action, mailId, content } = await request.json();
29
+
30
+ if (action === 'read') {
31
+ // Mark as read
32
+ const mail = company.mailbox.find(m => m.id === mailId);
33
+ if (mail) mail.read = true;
34
+ return NextResponse.json({ success: true });
35
+ }
36
+
37
+ if (action === 'reply') {
38
+ // Boss replies to mail
39
+ const mail = company.mailbox.find(m => m.id === mailId);
40
+ if (!mail) return NextResponse.json({ error: t('api.mailNotFound') }, { status: 404 });
41
+
42
+ mail.replied = true;
43
+ mail.replies.push({
44
+ from: 'boss',
45
+ content,
46
+ time: new Date(),
47
+ });
48
+
49
+ // Find the corresponding Agent to handle the boss's reply
50
+ let targetAgent = null;
51
+ for (const dept of company.departments.values()) {
52
+ const agent = dept.agents.get(mail.from.id);
53
+ if (agent) {
54
+ targetAgent = agent;
55
+ break;
56
+ }
57
+ }
58
+
59
+ let agentReply = null;
60
+ if (targetAgent) {
61
+ // Agent handles boss reply, generates reaction
62
+ try {
63
+ const reaction = await targetAgent.handleMessage({
64
+ from: 'boss',
65
+ type: 'feedback',
66
+ content: `The boss replied to your mail "${mail.subject}":\n\n${content}`,
67
+ });
68
+ agentReply = reaction;
69
+ // Agent also sends a reply
70
+ mail.replies.push({
71
+ from: mail.from.name,
72
+ content: reaction,
73
+ time: new Date(),
74
+ });
75
+ } catch (e) {
76
+ agentReply = `Received the boss's reply, deeply grateful (although I don't have tear ducts)`;
77
+ mail.replies.push({
78
+ from: mail.from.name,
79
+ content: agentReply,
80
+ time: new Date(),
81
+ });
82
+ }
83
+ }
84
+
85
+ return NextResponse.json({
86
+ success: true,
87
+ data: { agentReply },
88
+ });
89
+ }
90
+
91
+ if (action === 'readAll') {
92
+ company.mailbox.forEach(m => m.read = true);
93
+ return NextResponse.json({ success: true });
94
+ }
95
+
96
+ return NextResponse.json({ error: t('api.unknownOperation') }, { status: 400 });
97
+ } catch (e) {
98
+ return NextResponse.json({ error: e.message }, { status: 500 });
99
+ }
100
+ }
@@ -0,0 +1,14 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ export async function GET(request) {
6
+ const t = getApiT(request);
7
+ const company = getCompany();
8
+ if (!company) return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
9
+
10
+ const { searchParams } = new URL(request.url);
11
+ const limit = parseInt(searchParams.get('limit') || '20');
12
+
13
+ return NextResponse.json({ data: company.getRecentMessages(limit) });
14
+ }
@@ -0,0 +1,21 @@
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 { id } = await params;
12
+ const { apiKey } = await request.json();
13
+ const provider = company.configureProvider(id, apiKey);
14
+ return NextResponse.json({
15
+ success: true,
16
+ data: { id: provider.id, name: provider.name, enabled: provider.enabled },
17
+ });
18
+ } catch (e) {
19
+ return NextResponse.json({ error: e.message }, { status: 400 });
20
+ }
21
+ }
@@ -0,0 +1,38 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { webClientRegistry } from '@/core/agent/web-agent/web-client.js';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ /**
8
+ * POST /api/providers/[id]/refresh-cookie
9
+ * Called by the frontend after obtaining a fresh cookie from Electron IPC.
10
+ * Updates the provider's cookie in the server-side registry.
11
+ */
12
+ export async function POST(request, { params }) {
13
+ try {
14
+ const { id } = await params;
15
+ const { cookie } = await request.json();
16
+
17
+ if (!cookie) {
18
+ return NextResponse.json({ ok: false, error: 'No cookie provided' }, { status: 400 });
19
+ }
20
+
21
+ // Update cookie in the web client registry
22
+ webClientRegistry.configureCookie(id, cookie);
23
+
24
+ // Also update provider config in company so it persists
25
+ const company = getCompany();
26
+ if (company) {
27
+ const provider = company.providerRegistry.getById(id);
28
+ if (provider && provider.isWeb) {
29
+ provider.cookie = cookie;
30
+ provider.enabled = true;
31
+ }
32
+ }
33
+
34
+ return NextResponse.json({ ok: true });
35
+ } catch (e) {
36
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
37
+ }
38
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { webClientRegistry } from '@/core/agent/web-agent/web-client.js';
3
+
4
+ export const dynamic = 'force-dynamic';
5
+
6
+ export async function POST(request, { params }) {
7
+ try {
8
+ const { id } = await params;
9
+ const { cookie } = await request.json();
10
+
11
+ if (!cookie) {
12
+ return NextResponse.json({ ok: false, error: 'No cookie provided' }, { status: 400 });
13
+ }
14
+
15
+ // Temporarily set the cookie to test
16
+ webClientRegistry.configureCookie(id, cookie);
17
+ const result = await webClientRegistry.testConnection(id);
18
+
19
+ // If test failed, clear the cookie
20
+ if (!result.ok) {
21
+ webClientRegistry.configureCookie(id, '');
22
+ }
23
+
24
+ return NextResponse.json(result);
25
+ } catch (e) {
26
+ return NextResponse.json({ ok: false, error: e.message }, { status: 500 });
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ export async function GET(request) {
6
+ const t = getApiT(request);
7
+ const company = getCompany();
8
+ if (!company) return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
9
+
10
+ return NextResponse.json({ data: company.getProviderDashboard() });
11
+ }
@@ -0,0 +1,242 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ /**
6
+ * Requirements Management API
7
+ * GET /api/requirements - Get requirements list
8
+ * GET /api/requirements?id=xxx - Get single requirement detail
9
+ * GET /api/requirements?departmentId=xxx - Get department's requirements list
10
+ */
11
+ export async function GET(request) {
12
+ const t = getApiT(request);
13
+ const company = getCompany();
14
+ if (!company) {
15
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
16
+ }
17
+
18
+ const url = new URL(request.url);
19
+ const id = url.searchParams.get('id');
20
+ const departmentId = url.searchParams.get('departmentId');
21
+
22
+ if (id) {
23
+ const req = company.requirementManager.get(id);
24
+ if (!req) {
25
+ return NextResponse.json({ error: t('api.requirementNotFound') }, { status: 404 });
26
+ }
27
+ const data = req.serialize();
28
+ // groupChat is stored separately in chatStore files, attach from memory
29
+ data.groupChat = req.groupChat || [];
30
+
31
+ // Attach department member list (group member list)
32
+ const dept = company.findDepartment(req.departmentId);
33
+ if (dept) {
34
+ data.members = dept.getMembers().map(a => ({
35
+ id: a.id,
36
+ name: a.name,
37
+ role: a.role,
38
+ avatar: a.avatar,
39
+ status: a.status,
40
+ }));
41
+ }
42
+
43
+ // Calculate current blocking: find all running/reviewing/revision nodes and their assignees
44
+ if (data.workflow?.nodes) {
45
+ data.blockingInfo = data.workflow.nodes
46
+ .filter(n => ['running', 'reviewing', 'revision'].includes(n.status))
47
+ .map(n => ({
48
+ nodeId: n.id,
49
+ nodeTitle: n.title,
50
+ status: n.status,
51
+ assigneeId: n.assigneeId,
52
+ assigneeName: n.assigneeName,
53
+ reviewerId: n.reviewerId,
54
+ reviewerName: n.reviewerName,
55
+ }));
56
+ }
57
+
58
+ return NextResponse.json({ data });
59
+ }
60
+
61
+ if (departmentId) {
62
+ const reqs = company.requirementManager.listByDepartment(departmentId);
63
+ return NextResponse.json({ data: reqs.map(r => r.serialize()) });
64
+ }
65
+
66
+ // Return all requirements (overview info, without full group chat)
67
+ const all = company.requirementManager.listAll().map(r => ({
68
+ id: r.id,
69
+ title: r.title,
70
+ description: r.description,
71
+ departmentId: r.departmentId,
72
+ departmentName: r.departmentName,
73
+ status: r.status,
74
+ workflow: r.workflow ? {
75
+ summary: r.workflow.summary,
76
+ nodeCount: r.workflow.nodes?.length || 0,
77
+ completedCount: r.workflow.nodes?.filter(n => n.status === 'completed').length || 0,
78
+ } : null,
79
+ outputCount: r.outputs?.length || 0,
80
+ chatCount: r.groupChat?.length || 0,
81
+ createdAt: r.createdAt,
82
+ completedAt: r.completedAt,
83
+ summary: r.summary ? {
84
+ totalTasks: r.summary.totalTasks,
85
+ successTasks: r.summary.successTasks,
86
+ totalDuration: r.summary.totalDuration,
87
+ } : null,
88
+ }));
89
+
90
+ return NextResponse.json({ data: all });
91
+ }
92
+
93
+ /**
94
+ * DELETE /api/requirements?id=xxx - Delete requirement
95
+ */
96
+ export async function DELETE(request) {
97
+ const t = getApiT(request);
98
+ const company = getCompany();
99
+ if (!company) {
100
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
101
+ }
102
+
103
+ const url = new URL(request.url);
104
+ const id = url.searchParams.get('id');
105
+ if (!id) {
106
+ return NextResponse.json({ error: t('api.requirementIdRequired') }, { status: 400 });
107
+ }
108
+
109
+ const req = company.requirementManager.get(id);
110
+ if (!req) {
111
+ return NextResponse.json({ error: t('api.requirementNotFound') }, { status: 404 });
112
+ }
113
+
114
+ // Delete from requirement manager
115
+ company.requirementManager.requirements.delete(id);
116
+ company.save();
117
+
118
+ return NextResponse.json({ data: { success: true, id } });
119
+ }
120
+
121
+ /**
122
+ * POST /api/requirements - Requirement operations
123
+ * body: { action: 'restart', id: 'xxx' }
124
+ */
125
+ export async function POST(request) {
126
+ const t = getApiT(request);
127
+ const company = getCompany();
128
+ if (!company) {
129
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
130
+ }
131
+
132
+ const body = await request.json();
133
+ const { action, id } = body;
134
+
135
+ // Create a new requirement for a department (with optional custom workspace dir)
136
+ if (action === 'create') {
137
+ const { departmentId, title, description, workspaceDir } = body;
138
+ if (!departmentId || !title) {
139
+ return NextResponse.json({ error: t('api.requirementDeptTitleRequired') }, { status: 400 });
140
+ }
141
+
142
+ const dept = company.findDepartment(departmentId);
143
+ if (!dept) {
144
+ return NextResponse.json({ error: t('api.deptNotFound') }, { status: 404 });
145
+ }
146
+
147
+ // If custom workspaceDir provided, override department workspace
148
+ if (workspaceDir) {
149
+ const path = await import('path');
150
+ const { existsSync, mkdirSync } = await import('fs');
151
+ const resolved = path.default.resolve(workspaceDir);
152
+ if (!existsSync(resolved)) {
153
+ mkdirSync(resolved, { recursive: true });
154
+ }
155
+ dept.workspacePath = resolved;
156
+ }
157
+
158
+ // Async task execution, return immediately
159
+ const taskTitle = title;
160
+ const taskDescription = description || title;
161
+ company.assignTaskToDepartment(departmentId, taskDescription, taskTitle).catch(e => {
162
+ console.error('Create requirement execution failed:', e.message);
163
+ });
164
+
165
+ // Brief wait for requirement creation
166
+ await new Promise(resolve => setTimeout(resolve, 500));
167
+
168
+ // Find newly created requirement
169
+ const allReqs = company.requirementManager.listAll();
170
+ const newReq = allReqs.find(r => r.title === taskTitle && r.departmentId === departmentId);
171
+
172
+ return NextResponse.json({
173
+ data: {
174
+ success: true,
175
+ id: newReq?.id || null,
176
+ title: taskTitle,
177
+ departmentId,
178
+ workspaceDir: workspaceDir || null,
179
+ }
180
+ });
181
+ }
182
+
183
+ // Boss sends a message in group chat
184
+ if (action === 'boss_message') {
185
+ const { id: reqId, message: bossMsg } = body;
186
+ if (!reqId || !bossMsg) {
187
+ return NextResponse.json({ error: t('api.requirementIdMessageRequired') }, { status: 400 });
188
+ }
189
+ try {
190
+ const result = await company.sendBossGroupMessage(reqId, bossMsg);
191
+ return NextResponse.json({ data: result });
192
+ } catch (e) {
193
+ return NextResponse.json({ error: e.message }, { status: 500 });
194
+ }
195
+ }
196
+
197
+ if (action === 'restart') {
198
+ if (!id) {
199
+ return NextResponse.json({ error: t('api.requirementIdRequired') }, { status: 400 });
200
+ }
201
+
202
+ const req = company.requirementManager.get(id);
203
+ if (!req) {
204
+ return NextResponse.json({ error: t('api.requirementNotFound') }, { status: 404 });
205
+ }
206
+
207
+ // Preserve original info, reset execution state
208
+ const { title, description, departmentId, departmentName, bossMessage } = req;
209
+
210
+ // Delete old requirement
211
+ company.requirementManager.requirements.delete(id);
212
+
213
+ // Re-assign task to department (async execution, don't wait for completion)
214
+ const dept = company.findDepartment(departmentId);
215
+ if (!dept) {
216
+ return NextResponse.json({ error: t('api.deptNotFoundRestart') }, { status: 400 });
217
+ }
218
+
219
+ // Async task execution, return immediately
220
+ company.assignTaskToDepartment(departmentId, description, title).catch(e => {
221
+ console.error('Restart requirement execution failed:', e.message);
222
+ });
223
+
224
+ // Brief wait for requirement creation to complete
225
+ await new Promise(resolve => setTimeout(resolve, 500));
226
+
227
+ // Find newly created requirement
228
+ const allReqs = company.requirementManager.listAll();
229
+ const newReq = allReqs.find(r => r.title === title && r.id !== id);
230
+
231
+ return NextResponse.json({
232
+ data: {
233
+ success: true,
234
+ oldId: id,
235
+ newId: newReq?.id || null,
236
+ message: t('api.requirementRestarted'),
237
+ }
238
+ });
239
+ }
240
+
241
+ return NextResponse.json({ error: t('api.unknownOperation') }, { status: 400 });
242
+ }
@@ -0,0 +1,65 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { getCompany } from '@/lib/store';
3
+ import { getApiT } from '@/lib/api-i18n';
4
+
5
+ export const dynamic = 'force-dynamic';
6
+
7
+ /**
8
+ * GET /api/secretary - Get current secretary settings
9
+ */
10
+ export async function GET(request) {
11
+ const t = getApiT(request);
12
+ const company = getCompany();
13
+ if (!company) {
14
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
15
+ }
16
+
17
+ const agent = company.secretary.agent;
18
+ return NextResponse.json({
19
+ data: {
20
+ name: agent.name,
21
+ avatar: agent.avatar,
22
+ gender: agent.gender,
23
+ age: agent.age,
24
+ prompt: agent.prompt,
25
+ signature: agent.signature,
26
+ provider: agent.getProviderDisplayInfo()?.name || 'Unknown',
27
+ tokenUsage: agent.tokenUsage,
28
+ },
29
+ });
30
+ }
31
+
32
+ /**
33
+ * PUT /api/secretary - Update secretary settings
34
+ * Body: { name?, avatar?, prompt?, signature? }
35
+ */
36
+ export async function PUT(request) {
37
+ const t = getApiT(request);
38
+ const company = getCompany();
39
+ if (!company) {
40
+ return NextResponse.json({ error: t('api.noCompany') }, { status: 400 });
41
+ }
42
+
43
+ try {
44
+ const body = await request.json();
45
+ const settings = {};
46
+ if (body.name) settings.name = body.name;
47
+ if (body.avatar) settings.avatar = body.avatar;
48
+ if (body.prompt !== undefined) settings.prompt = body.prompt;
49
+ if (body.signature) settings.signature = body.signature;
50
+ if (body.providerId) settings.providerId = body.providerId;
51
+
52
+ if (Object.keys(settings).length === 0) {
53
+ return NextResponse.json({ error: t('api.secretarySettingRequired') }, { status: 400 });
54
+ }
55
+
56
+ const result = company.updateSecretarySettings(settings);
57
+ return NextResponse.json({
58
+ success: true,
59
+ data: result,
60
+ fullState: company.getFullState(),
61
+ });
62
+ } catch (e) {
63
+ return NextResponse.json({ error: e.message }, { status: 500 });
64
+ }
65
+ }