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,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
|
+
}
|