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