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,737 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { JobTemplates } from './workforce/hr.js';
3
+ import { cliBackendRegistry } from '../agent/cli-agent/backends/index.js';
4
+ import { chatStore } from '../agent/chat-store.js';
5
+ import { robustJSONParse } from '../utils/json-parse.js';
6
+ // Agent instances are passed in from outside; no direct import needed
7
+
8
+ /**
9
+ * Department - A collaborative unit composed of multiple Agents
10
+ * Supports performance evaluation process and member dismissal
11
+ */
12
+ export class Department {
13
+ constructor({ name, mission, company }) {
14
+ this.id = uuidv4();
15
+ this.name = name; // Department name
16
+ this.mission = mission; // Department mission / goal
17
+ this.company = company; // Parent company
18
+ this.agents = new Map(); // Department members (agentId => Agent)
19
+ this.leader = null; // Department leader
20
+ this.orgStructure = null; // Org structure description
21
+ this.tasks = []; // Department task list
22
+ this.status = 'preparing'; // preparing | active | completed | disbanded
23
+ this.createdAt = new Date();
24
+ this.groupChat = []; // Department group chat message list
25
+ }
26
+
27
+ /**
28
+ * Add department group chat message
29
+ * @param {object} from - Sender { id, name, avatar, role }
30
+ * @param {string} content - Message content
31
+ * @param {string} type - Message type: message | system
32
+ * @param {string} visibility - Visibility: 'group' (broadcast) | 'flow' (worklog only)
33
+ */
34
+ addGroupMessage(from, content, type = 'message', visibility = 'group') {
35
+ const msg = {
36
+ id: uuidv4(),
37
+ from: {
38
+ id: from.id || 'system',
39
+ name: from.name || 'System',
40
+ avatar: from.avatar || null,
41
+ role: from.role || null,
42
+ },
43
+ content,
44
+ type,
45
+ visibility,
46
+ time: new Date(),
47
+ };
48
+ this.groupChat.push(msg);
49
+ // Persist to file storage
50
+ try { chatStore.appendGroupMessage(`dept-${this.id}`, msg); } catch {}
51
+ }
52
+
53
+ /**
54
+ * Load group chat from file storage (called during deserialization).
55
+ * Also handles one-time migration of legacy inline groupChat data.
56
+ * @param {Array} [legacyGroupChat] - Legacy inline data to migrate
57
+ */
58
+ loadGroupChatFromStore(legacyGroupChat = null) {
59
+ const groupId = `dept-${this.id}`;
60
+ if (legacyGroupChat && legacyGroupChat.length > 0) {
61
+ chatStore.migrateGroupChat(groupId, legacyGroupChat);
62
+ }
63
+ this.groupChat = chatStore.getGroupMessages(groupId, 500);
64
+ }
65
+
66
+ /** Add an Agent to the department */
67
+ addAgent(agent) {
68
+ agent.department = this.id;
69
+ this.agents.set(agent.id, agent);
70
+ console.log(` ✅ [${agent.name}] (${agent.role}) joined department "${this.name}"`);
71
+ const providerInfo = agent.getProviderDisplayInfo?.() || {};
72
+ console.log(` Model provider: ${providerInfo.name || 'Unknown'} (${providerInfo.provider || 'Unknown'})`);
73
+ return agent;
74
+ }
75
+
76
+ /**
77
+ * Remove an Agent (department-level operation before dismissal)
78
+ * @param {string} agentId - The Agent ID to remove
79
+ * @returns {Agent|null} The removed Agent
80
+ */
81
+ removeAgent(agentId) {
82
+ const agent = this.agents.get(agentId);
83
+ if (!agent) return null;
84
+
85
+ // If this is the leader, clear the leader reference
86
+ if (this.leader === agentId) {
87
+ this.leader = null;
88
+ }
89
+
90
+ // Clean up reporting lines: transfer subordinates to their manager's superior
91
+ const managerId = agent.reportsTo;
92
+ const manager = managerId ? this.agents.get(managerId) : null;
93
+
94
+ // Reassign subordinates to the superior
95
+ for (const subId of agent.subordinates) {
96
+ const sub = this.agents.get(subId);
97
+ if (sub) {
98
+ if (manager) {
99
+ sub.setManager(manager);
100
+ console.log(` 🔄 [${sub.name}] reporting line transferred to [${manager.name}]`);
101
+ } else {
102
+ sub.reportsTo = null;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Clean up superior's subordinate list
108
+ if (manager) {
109
+ manager.subordinates = manager.subordinates.filter(id => id !== agentId);
110
+ }
111
+
112
+ // Remove from department
113
+ this.agents.delete(agentId);
114
+ agent.department = null;
115
+ agent.reportsTo = null;
116
+ agent.subordinates = [];
117
+
118
+ console.log(` 🚪 [${agent.name}] (${agent.role}) left department "${this.name}"`);
119
+ return agent;
120
+ }
121
+
122
+ /** Set the department leader */
123
+ setLeader(agent) {
124
+ this.leader = agent.id;
125
+ console.log(` 👔 [${agent.name}] appointed as leader of department "${this.name}"`);
126
+ }
127
+
128
+ /** Establish reporting line */
129
+ setReportingLine(subordinate, manager) {
130
+ subordinate.setManager(manager);
131
+ console.log(` 📋 Reporting line: [${subordinate.name}] → [${manager.name}]`);
132
+ }
133
+
134
+ /** Get all department members */
135
+ getMembers() {
136
+ return [...this.agents.values()];
137
+ }
138
+
139
+ /** Get the department leader (fallback: promote first member if leader is missing) */
140
+ getLeader() {
141
+ const leader = this.agents.get(this.leader);
142
+ if (leader) return leader;
143
+ // Fallback: if leader is null or removed, promote the first member
144
+ if (this.agents.size > 0) {
145
+ const first = this.agents.values().next().value;
146
+ this.leader = first.id;
147
+ console.log(` ⚠️ Department "${this.name}" had no leader, auto-promoted [${first.name}] as leader`);
148
+ return first;
149
+ }
150
+ return null;
151
+ }
152
+
153
+ /** Get subordinates of a member */
154
+ getSubordinates(agentId) {
155
+ const agent = this.agents.get(agentId);
156
+ if (!agent) return [];
157
+ return agent.subordinates
158
+ .map(subId => this.agents.get(subId))
159
+ .filter(Boolean);
160
+ }
161
+
162
+ /**
163
+ * Execute a project collaboratively, with automatic performance review upon completion
164
+ * @param {object} project - The project
165
+ * @param {PerformanceSystem} [performanceSystem] - Performance system (optional)
166
+ */
167
+ async executeProject(project, performanceSystem = null) {
168
+ console.log(`\n🏢 Department "${this.name}" starts executing project: "${project.name}"`);
169
+ console.log(` Description: ${project.description}`);
170
+ console.log(` Members: ${this.agents.size}\n`);
171
+
172
+ this.status = 'active';
173
+ const results = [];
174
+ // Collect completed tasks per agent for subsequent performance review
175
+ const agentTaskMap = new Map();
176
+
177
+ // Execute by task phases
178
+ for (const phase of project.phases) {
179
+ console.log(`\n📌 Phase: ${phase.name}`);
180
+ console.log(` ${phase.description}`);
181
+
182
+ // Execute tasks within the same phase in parallel
183
+ const phasePromises = phase.tasks.map(async (task) => {
184
+ const assignee = this.agents.get(task.assigneeId);
185
+ if (!assignee) {
186
+ console.log(` ⚠️ Task assignee not found: ${task.assigneeId}`);
187
+ return null;
188
+ }
189
+ const result = await assignee.executeTask(task);
190
+
191
+ // Record tasks completed by the agent
192
+ if (!agentTaskMap.has(task.assigneeId)) {
193
+ agentTaskMap.set(task.assigneeId, []);
194
+ }
195
+ agentTaskMap.get(task.assigneeId).push({
196
+ task,
197
+ result,
198
+ });
199
+
200
+ return result;
201
+ });
202
+
203
+ const phaseResults = await Promise.all(phasePromises);
204
+ results.push({
205
+ phase: phase.name,
206
+ results: phaseResults.filter(Boolean),
207
+ });
208
+
209
+ // Report after phase completion
210
+ const leader = this.getLeader();
211
+ if (leader) {
212
+ console.log(`\n 📊 [${leader.name}] summarizing phase "${phase.name}" results...`);
213
+ }
214
+ }
215
+
216
+ this.status = 'completed';
217
+ console.log(`\n✅ Department "${this.name}" completed project "${project.name}"!`);
218
+
219
+ // Run performance review after project completion
220
+ if (performanceSystem) {
221
+ console.log(`\n📋 Starting project performance review...`);
222
+ await this._runPerformanceReview(performanceSystem, agentTaskMap);
223
+ }
224
+
225
+ return results;
226
+ }
227
+
228
+ /**
229
+ * Run performance review: superiors rate subordinates, employees provide self-reflection
230
+ */
231
+ async _runPerformanceReview(performanceSystem, agentTaskMap) {
232
+ const leader = this.getLeader();
233
+
234
+ for (const [agentId, taskResults] of agentTaskMap) {
235
+ const agent = this.agents.get(agentId);
236
+ if (!agent) continue;
237
+
238
+ // Find the agent's direct supervisor as the reviewer
239
+ let reviewer = null;
240
+ if (agent.reportsTo) {
241
+ reviewer = this.agents.get(agent.reportsTo);
242
+ }
243
+ // If no supervisor, the department leader reviews
244
+ if (!reviewer && leader && leader.id !== agentId) {
245
+ reviewer = leader;
246
+ }
247
+ // Leader self-review (or skip)
248
+ if (!reviewer) continue;
249
+
250
+ // Evaluate each task
251
+ for (const { task } of taskResults) {
252
+ const review = performanceSystem.autoEvaluate({
253
+ agent,
254
+ reviewer,
255
+ taskTitle: task.title,
256
+ });
257
+
258
+ // Employee receives feedback and self-reflects
259
+ agent.receiveFeedback(review);
260
+ }
261
+ }
262
+
263
+ console.log(`\n✅ Performance review completed!`);
264
+ }
265
+
266
+ /** Get the department org chart tree */
267
+ getOrgTree() {
268
+ const leader = this.getLeader();
269
+ if (!leader) return null;
270
+
271
+ const buildTree = (agent) => ({
272
+ name: agent.name,
273
+ role: agent.role,
274
+ provider: agent.getProviderDisplayInfo().name,
275
+ subordinates: this.getSubordinates(agent.id).map(sub => buildTree(sub)),
276
+ });
277
+
278
+ return buildTree(leader);
279
+ }
280
+
281
+ /** Print org chart */
282
+ printOrgChart(node = null, indent = ' ') {
283
+ if (!node) {
284
+ node = this.getOrgTree();
285
+ if (!node) {
286
+ console.log(' (No org structure yet)');
287
+ return;
288
+ }
289
+ console.log(`\n📊 Department "${this.name}" org chart:`);
290
+ }
291
+
292
+ console.log(`${indent}├── 👤 ${node.name} (${node.role}) [${node.provider}]`);
293
+ node.subordinates.forEach(sub => {
294
+ this.printOrgChart(sub, indent + '│ ');
295
+ });
296
+ }
297
+
298
+ // ======================== Team Design ========================
299
+
300
+ /**
301
+ * AI-analyze requirements and design team architecture for this department.
302
+ * @param {string} requirement - The mission/requirement description
303
+ * @param {import('../employee/base-employee.js').Employee} analyst - An LLM-capable employee (e.g. secretary) to perform the analysis
304
+ * @param {import('./workforce/providers.js').ProviderRegistry} providerRegistry - Provider registry for hiring constraints
305
+ * @returns {Promise<object>} Team plan
306
+ */
307
+ async designTeam(requirement, analyst, providerRegistry) {
308
+ console.log(`\n🗂️ [${this.name}] AI-analyzing requirements and designing team architecture...`);
309
+ console.log(` Requirement: "${requirement}"\n`);
310
+
311
+ const isCLI = analyst.agentType === 'cli';
312
+ const canChat = analyst.canChat();
313
+ if (!canChat && !isCLI) {
314
+ throw new Error('Analyst AI is not configured. Please configure a valid API Key or CLI backend for the analyst provider first.');
315
+ }
316
+
317
+ const plan = isCLI && !canChat
318
+ ? await this._cliAnalyzeRequirement(requirement, analyst, providerRegistry)
319
+ : await this._aiAnalyzeRequirement(requirement, analyst, providerRegistry);
320
+
321
+ console.log(`📋 [${this.name}] Team plan:`);
322
+ console.log(` Department: ${plan.departmentName}`);
323
+ console.log(` Mission: ${plan.mission}`);
324
+ console.log(` Team size: ${plan.members.length} people`);
325
+ plan.members.forEach((m, i) => {
326
+ const indent = m.reportsTo !== null ? ' ' : ' ';
327
+ const prefix = m.isLeader ? '👔' : '👤';
328
+ console.log(`${indent}${prefix} ${m.name} - ${m.templateTitle} ${m.reportsTo !== null ? `(reports to: ${plan.members[m.reportsTo].name})` : '(leader)'}`);
329
+ });
330
+
331
+ return plan;
332
+ }
333
+
334
+ async _aiAnalyzeRequirement(requirement, analyst, providerRegistry) {
335
+ const availableRoles = Object.values(JobTemplates).map(t => ({
336
+ id: t.id, title: t.title, category: t.category, skills: t.skills,
337
+ }));
338
+ const enabledProviders = providerRegistry.listEnabled().map(p => ({
339
+ id: p.id, name: p.name, category: p.category, rating: p.rating,
340
+ isCLI: p.isCLI || false, cliBackendId: p.cliBackendId || null,
341
+ }));
342
+ const availableCategories = [...new Set(enabledProviders.map(p => p.category))];
343
+
344
+ const systemPrompt = `You are an experienced corporate secretary skilled at team planning and talent matching.
345
+
346
+ Here are the available job templates (you can only choose from these):
347
+ ${JSON.stringify(availableRoles, null, 2)}
348
+
349
+ ## Currently enabled providers (IMPORTANT - only templates whose category has an enabled provider can be hired!):
350
+ ${JSON.stringify(enabledProviders, null, 2)}
351
+
352
+ Available categories: ${availableCategories.join(', ')}
353
+
354
+ ⚠️ CRITICAL RULES for provider-aware hiring:
355
+ - You can ONLY use templates whose category has at least one enabled provider above.
356
+ - If the boss mentions a specific provider name (e.g. "CodeBuddy", "Claude Code", "Codex"), you MUST use a CLI template (category: "cli") for that position.
357
+ - CLI templates (cli-software-engineer, cli-fullstack-developer, cli-code-reviewer) use local CLI tools as execution engines. They are powerful coding assistants.
358
+ - When CLI providers are available and the task is coding-related, PREFER CLI templates over general templates — they can directly execute code on the local machine.
359
+ - If the boss says something like "hire a CodeBuddy employee" or "add a CodeBuddy developer", choose a cli-* template.
360
+
361
+ Based on the boss's requirements, output a team plan in JSON format as follows:
362
+ {
363
+ "departmentName": "Department name",
364
+ "mission": "Department mission (concise description)",
365
+ "reasoning": "Your analysis rationale (why this configuration)",
366
+ "members": [
367
+ {
368
+ "templateId": "Job template ID",
369
+ "name": "Employee nickname (use creative, fun names)",
370
+ "isLeader": true/false,
371
+ "reportsTo": null or numeric index,
372
+ "reason": "Why this position is needed"
373
+ }
374
+ ]
375
+ }
376
+
377
+ Requirements:
378
+ 1. The first member must be project-leader with isLeader=true
379
+ 2. Other members' reportsTo should be the index of their direct supervisor (0 = project leader)
380
+ 3. Team size should be reasonable, typically 2-6 people, don't pad the roster
381
+ 4. Employee names should be distinctive and fun
382
+ 5. Return JSON only, no other content`;
383
+
384
+ const response = await analyst.chat([
385
+ { role: 'system', content: systemPrompt },
386
+ { role: 'user', content: `Boss's requirement: ${requirement}` },
387
+ ], { temperature: 0.7, maxTokens: 2048, newConversation: true });
388
+
389
+ let aiPlan;
390
+ try {
391
+ aiPlan = Department._extractJSON(response.content);
392
+ } catch (e) {
393
+ console.error(' ❌ Failed to parse AI response:', response.content?.substring(0, 500));
394
+ throw new Error('Failed to parse AI response format');
395
+ }
396
+
397
+ if (!aiPlan.members || aiPlan.members.length === 0) {
398
+ throw new Error('AI did not plan any members');
399
+ }
400
+
401
+ const validTemplateIds = new Set(Object.values(JobTemplates).map(t => t.id));
402
+ aiPlan.members = aiPlan.members.filter(m => validTemplateIds.has(m.templateId));
403
+ if (aiPlan.members.length === 0) throw new Error('AI planned invalid job templates');
404
+
405
+ console.log(` 🧠 AI analysis rationale: ${aiPlan.reasoning || 'N/A'}`);
406
+
407
+ return {
408
+ departmentName: aiPlan.departmentName || 'New Project Dept',
409
+ mission: aiPlan.mission || requirement,
410
+ reasoning: aiPlan.reasoning,
411
+ members: aiPlan.members.map((m, i) => {
412
+ const template = Object.values(JobTemplates).find(t => t.id === m.templateId);
413
+ return {
414
+ templateId: m.templateId,
415
+ templateTitle: template?.title || m.templateId,
416
+ name: m.name || `Employee${i + 1}`,
417
+ isLeader: m.isLeader || false,
418
+ reportsTo: m.reportsTo ?? (i === 0 ? null : 0),
419
+ reason: m.reason,
420
+ };
421
+ }),
422
+ collaborationRules: Department._designCollaboration(aiPlan.members),
423
+ };
424
+ }
425
+
426
+ async _cliAnalyzeRequirement(requirement, analyst, providerRegistry) {
427
+ console.log(` 🖥️ [${this.name}] Using CLI backend for team design: ${analyst.cliBackend}`);
428
+
429
+ const availableRoles = Object.values(JobTemplates).map(t => ({
430
+ id: t.id, title: t.title, category: t.category, skills: t.skills,
431
+ }));
432
+ const enabledProviders = providerRegistry.listEnabled().map(p => ({
433
+ id: p.id, name: p.name, category: p.category, rating: p.rating,
434
+ isCLI: p.isCLI || false, cliBackendId: p.cliBackendId || null,
435
+ }));
436
+ const availableCategories = [...new Set(enabledProviders.map(p => p.category))];
437
+
438
+ const prompt = `You are an experienced corporate secretary skilled at team planning and talent matching.
439
+
440
+ Here are the available job templates (you can only choose from these):
441
+ ${JSON.stringify(availableRoles, null, 2)}
442
+
443
+ ## Currently enabled providers (IMPORTANT - only templates whose category has an enabled provider can be hired!):
444
+ ${JSON.stringify(enabledProviders, null, 2)}
445
+
446
+ Available categories: ${availableCategories.join(', ')}
447
+
448
+ ⚠️ CRITICAL RULES for provider-aware hiring:
449
+ - You can ONLY use templates whose category has at least one enabled provider above.
450
+ - If the boss mentions a specific provider name (e.g. "CodeBuddy", "Claude Code", "Codex"), you MUST use a CLI template (category: "cli") for that position.
451
+ - CLI templates (cli-software-engineer, cli-fullstack-developer, cli-code-reviewer) use local CLI tools as execution engines.
452
+ - When CLI providers are available and the task is coding-related, PREFER CLI templates over general templates.
453
+
454
+ Based on the boss's requirements, output a team plan in JSON format as follows:
455
+ {
456
+ "departmentName": "Department name",
457
+ "mission": "Department mission (concise description)",
458
+ "reasoning": "Your analysis rationale",
459
+ "members": [
460
+ {
461
+ "templateId": "Job template ID",
462
+ "name": "Employee nickname",
463
+ "isLeader": true/false,
464
+ "reportsTo": null or numeric index,
465
+ "reason": "Why this position is needed"
466
+ }
467
+ ]
468
+ }
469
+
470
+ Requirements:
471
+ 1. The first member must be project-leader with isLeader=true
472
+ 2. Other members' reportsTo should be the index of their direct supervisor (0 = project leader)
473
+ 3. Team size should be reasonable, typically 2-6 people
474
+ 4. Employee names should be distinctive and fun
475
+ 5. Return JSON only, no other content
476
+
477
+ Boss's requirement: ${requirement}`;
478
+
479
+ const cliResult = await cliBackendRegistry.executeTask(
480
+ analyst.cliBackend, analyst, { title: 'Team design analysis', description: prompt },
481
+ analyst.toolKit?.workspaceDir || process.cwd(), {}, { timeout: 120000 }
482
+ );
483
+
484
+ const rawOutput = cliResult.output || cliResult.errorOutput || '';
485
+ let aiPlan;
486
+ try { aiPlan = Department._extractJSON(rawOutput); }
487
+ catch (e) { throw new Error(`Failed to parse CLI response for team design: ${e.message}`); }
488
+
489
+ if (!aiPlan.members || aiPlan.members.length === 0) throw new Error('CLI did not plan any members');
490
+
491
+ const validTemplateIds = new Set(Object.values(JobTemplates).map(t => t.id));
492
+ aiPlan.members = aiPlan.members.filter(m => validTemplateIds.has(m.templateId));
493
+ if (aiPlan.members.length === 0) throw new Error('CLI planned invalid job templates');
494
+
495
+ console.log(` 🧠 CLI analysis rationale: ${aiPlan.reasoning || 'N/A'}`);
496
+
497
+ return {
498
+ departmentName: aiPlan.departmentName || 'New Project Dept',
499
+ mission: aiPlan.mission || requirement,
500
+ reasoning: aiPlan.reasoning,
501
+ members: aiPlan.members.map((m, i) => {
502
+ const template = Object.values(JobTemplates).find(t => t.id === m.templateId);
503
+ return {
504
+ templateId: m.templateId,
505
+ templateTitle: template?.title || m.templateId,
506
+ name: m.name || `Employee${i + 1}`,
507
+ isLeader: m.isLeader || false,
508
+ reportsTo: m.reportsTo ?? (i === 0 ? null : 0),
509
+ reason: m.reason,
510
+ };
511
+ }),
512
+ collaborationRules: Department._designCollaboration(aiPlan.members),
513
+ };
514
+ }
515
+
516
+ // ======================== Team Adjustment ========================
517
+
518
+ /**
519
+ * Analyze and plan team adjustment for this department.
520
+ * @param {string} adjustGoal - Adjustment goal description
521
+ * @param {import('../employee/base-employee.js').Employee} analyst - An LLM-capable employee to perform the analysis
522
+ * @param {import('./workforce/providers.js').ProviderRegistry} providerRegistry - Provider registry
523
+ * @returns {Promise<object>} Adjustment plan { reasoning, fires, hires }
524
+ */
525
+ async adjustTeam(adjustGoal, analyst, providerRegistry) {
526
+ console.log(`\n🔧 [${this.name}] Analyzing adjustment plan...`);
527
+ console.log(` Adjustment goal: "${adjustGoal}"\n`);
528
+
529
+ const currentMembers = this.getMembers().map(m => ({
530
+ id: m.id, name: m.name, role: m.role, skills: m.skills,
531
+ avgScore: m.avgScore || null, taskCount: m.taskCount || 0,
532
+ }));
533
+ const availableRoles = Object.values(JobTemplates).map(t => ({
534
+ id: t.id, title: t.title, category: t.category, skills: t.skills,
535
+ }));
536
+
537
+ const isCLI = analyst.agentType === 'cli';
538
+ const canChat = analyst.canChat();
539
+ if (!canChat && !isCLI) {
540
+ throw new Error('Analyst AI is not configured. Please configure a valid API Key or CLI backend for the analyst provider first.');
541
+ }
542
+
543
+ const plan = isCLI && !canChat
544
+ ? await this._cliAnalyzeAdjustment(currentMembers, availableRoles, adjustGoal, analyst, providerRegistry)
545
+ : await this._aiAnalyzeAdjustment(currentMembers, availableRoles, adjustGoal, analyst, providerRegistry);
546
+
547
+ console.log(`📋 [${this.name}] Adjustment plan:`);
548
+ console.log(` Fires: ${plan.fires.length} people, Hires: ${plan.hires.length} people`);
549
+
550
+ return plan;
551
+ }
552
+
553
+ async _aiAnalyzeAdjustment(currentMembers, availableRoles, adjustGoal, analyst, providerRegistry) {
554
+ const enabledProviders = providerRegistry.listEnabled().map(p => ({
555
+ id: p.id, name: p.name, category: p.category, rating: p.rating,
556
+ isCLI: p.isCLI || false, cliBackendId: p.cliBackendId || null,
557
+ }));
558
+ const availableCategories = [...new Set(enabledProviders.map(p => p.category))];
559
+
560
+ const systemPrompt = `You are an experienced corporate secretary skilled at organizational restructuring and HR planning.
561
+
562
+ Current department info:
563
+ - Name: ${this.name}
564
+ - Mission: ${this.mission}
565
+ - Current members: ${JSON.stringify(currentMembers, null, 2)}
566
+
567
+ Available job templates (hiring can only choose from these):
568
+ ${JSON.stringify(availableRoles, null, 2)}
569
+
570
+ ## Currently enabled providers (IMPORTANT - only templates whose category has an enabled provider can be hired!):
571
+ ${JSON.stringify(enabledProviders, null, 2)}
572
+
573
+ Available categories: ${availableCategories.join(', ')}
574
+
575
+ ⚠️ CRITICAL RULES for provider-aware hiring:
576
+ - You can ONLY use templates whose category has at least one enabled provider above.
577
+ - If the boss mentions a specific provider/tool name (e.g. "CodeBuddy", "Claude Code", "Codex"), you MUST use a CLI template (category: "cli") for that position.
578
+ - CLI templates use local CLI tools as execution engines — they are powerful coding assistants that can directly execute code.
579
+ - When CLI providers are available and the task involves coding, PREFER CLI templates over general templates.
580
+
581
+ Based on the boss's adjustment goal, output an adjustment plan in JSON format as follows:
582
+ {
583
+ "reasoning": "Your analysis rationale (why this adjustment)",
584
+ "fires": [
585
+ { "agentId": "Member ID to fire", "name": "Member name", "reason": "Firing reason" }
586
+ ],
587
+ "hires": [
588
+ {
589
+ "templateId": "Job template ID",
590
+ "name": "New employee nickname (use creative, fun names)",
591
+ "isLeader": false,
592
+ "reportsTo": 0,
593
+ "reason": "Why this position is needed"
594
+ }
595
+ ]
596
+ }
597
+
598
+ Requirements:
599
+ 1. Make reasonable decisions based on boss's goal: could be pure layoff, pure hiring, or both
600
+ 2. When firing, prioritize low performers and skill mismatches
601
+ 3. When hiring, fill capability gaps with distinctive names
602
+ 4. hires reportsTo is the index (0-based) in the current member list, or -1 for direct report to leader
603
+ 5. If no firing needed, fires is an empty array; if no hiring needed, hires is an empty array
604
+ 6. Return JSON only, no other content`;
605
+
606
+ const response = await analyst.chat([
607
+ { role: 'system', content: systemPrompt },
608
+ { role: 'user', content: `Boss's adjustment goal: ${adjustGoal}` },
609
+ ], { temperature: 0.7, maxTokens: 2048 });
610
+
611
+ let aiPlan;
612
+ try {
613
+ const jsonStr = response.content.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
614
+ aiPlan = JSON.parse(jsonStr);
615
+ } catch (e) { throw new Error('Failed to parse AI response format'); }
616
+
617
+ const memberIds = new Set(currentMembers.map(m => m.id));
618
+ aiPlan.fires = (aiPlan.fires || []).filter(f => memberIds.has(f.agentId));
619
+
620
+ const validTemplateIds = new Set(Object.values(JobTemplates).map(t => t.id));
621
+ aiPlan.hires = (aiPlan.hires || []).filter(h => validTemplateIds.has(h.templateId));
622
+ aiPlan.hires = aiPlan.hires.map((h, i) => {
623
+ const template = Object.values(JobTemplates).find(t => t.id === h.templateId);
624
+ return { ...h, templateTitle: template?.title || h.templateId, name: h.name || `NewHire${i + 1}` };
625
+ });
626
+
627
+ return { reasoning: aiPlan.reasoning || 'Adjusting based on goal', fires: aiPlan.fires || [], hires: aiPlan.hires || [] };
628
+ }
629
+
630
+ async _cliAnalyzeAdjustment(currentMembers, availableRoles, adjustGoal, analyst, providerRegistry) {
631
+ console.log(` 🖥️ [${this.name}] Using CLI backend for adjustment analysis: ${analyst.cliBackend}`);
632
+
633
+ const enabledProviders = providerRegistry.listEnabled().map(p => ({
634
+ id: p.id, name: p.name, category: p.category, rating: p.rating,
635
+ isCLI: p.isCLI || false, cliBackendId: p.cliBackendId || null,
636
+ }));
637
+ const availableCategories = [...new Set(enabledProviders.map(p => p.category))];
638
+
639
+ const prompt = `You are an experienced corporate secretary skilled at organizational restructuring and HR planning.
640
+
641
+ Current department info:
642
+ - Name: ${this.name}
643
+ - Mission: ${this.mission}
644
+ - Current members: ${JSON.stringify(currentMembers, null, 2)}
645
+
646
+ Available job templates (hiring can only choose from these):
647
+ ${JSON.stringify(availableRoles, null, 2)}
648
+
649
+ ## Currently enabled providers (IMPORTANT - only templates whose category has an enabled provider can be hired!):
650
+ ${JSON.stringify(enabledProviders, null, 2)}
651
+
652
+ Available categories: ${availableCategories.join(', ')}
653
+
654
+ ⚠️ CRITICAL RULES for provider-aware hiring:
655
+ - You can ONLY use templates whose category has at least one enabled provider above.
656
+ - CLI templates use local CLI tools as execution engines — they are powerful coding assistants.
657
+ - When CLI providers are available and the task involves coding, PREFER CLI templates over general templates.
658
+
659
+ Based on the boss's adjustment goal, output an adjustment plan in JSON format as follows:
660
+ {
661
+ "reasoning": "Your analysis rationale",
662
+ "fires": [
663
+ { "agentId": "Member ID to fire", "name": "Member name", "reason": "Firing reason" }
664
+ ],
665
+ "hires": [
666
+ {
667
+ "templateId": "Job template ID",
668
+ "name": "New employee nickname",
669
+ "isLeader": false,
670
+ "reportsTo": 0,
671
+ "reason": "Why this position is needed"
672
+ }
673
+ ]
674
+ }
675
+
676
+ Requirements:
677
+ 1. Make reasonable decisions based on boss's goal
678
+ 2. When firing, prioritize low performers and skill mismatches
679
+ 3. When hiring, fill capability gaps with distinctive names
680
+ 4. hires reportsTo is the index (0-based) in the current member list, or -1 for direct report to leader
681
+ 5. If no firing needed, fires is an empty array; if no hiring needed, hires is an empty array
682
+ 6. Return JSON only, no other content
683
+
684
+ Boss's adjustment goal: ${adjustGoal}`;
685
+
686
+ const cliResult = await cliBackendRegistry.executeTask(
687
+ analyst.cliBackend, analyst,
688
+ { title: 'Department adjustment analysis', description: prompt },
689
+ analyst.toolKit?.workspaceDir || process.cwd(), {}, { timeout: 120000 }
690
+ );
691
+
692
+ const rawOutput = cliResult.output || cliResult.errorOutput || '';
693
+ let aiPlan;
694
+ try { aiPlan = Department._extractJSON(rawOutput); }
695
+ catch (e) { throw new Error(`Failed to parse CLI response for adjustment: ${e.message}`); }
696
+
697
+ const memberIds = new Set(currentMembers.map(m => m.id));
698
+ aiPlan.fires = (aiPlan.fires || []).filter(f => memberIds.has(f.agentId));
699
+
700
+ const validTemplateIds = new Set(Object.values(JobTemplates).map(t => t.id));
701
+ aiPlan.hires = (aiPlan.hires || []).filter(h => validTemplateIds.has(h.templateId));
702
+ aiPlan.hires = aiPlan.hires.map((h, i) => {
703
+ const template = Object.values(JobTemplates).find(t => t.id === h.templateId);
704
+ return { ...h, templateTitle: template?.title || h.templateId, name: h.name || `NewHire${i + 1}` };
705
+ });
706
+
707
+ return { reasoning: aiPlan.reasoning || 'Adjusting based on goal', fires: aiPlan.fires || [], hires: aiPlan.hires || [] };
708
+ }
709
+
710
+ // ======================== Static Helpers ========================
711
+
712
+ static _extractJSON(rawOutput) {
713
+ return robustJSONParse(rawOutput);
714
+ }
715
+
716
+ static _designCollaboration(members) {
717
+ return [
718
+ '1. Project leader coordinates overall operations, assigns tasks and tracks progress',
719
+ '2. Members report to their direct supervisor upon task completion',
720
+ '3. Peers at the same level can collaborate horizontally',
721
+ '4. Project progresses in phases, each with clear deliverables',
722
+ ];
723
+ }
724
+
725
+ /** Get department summary */
726
+ getSummary() {
727
+ return {
728
+ id: this.id,
729
+ name: this.name,
730
+ mission: this.mission,
731
+ status: this.status,
732
+ memberCount: this.agents.size,
733
+ leader: this.getLeader()?.name,
734
+ members: this.getMembers().map(a => a.getSummary()),
735
+ };
736
+ }
737
+ }