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,414 @@
1
+ /**
2
+ * Lifecycle Hook System - Event-driven extensibility framework
3
+ *
4
+ * Distilled from OpenClaw's hook system (vendor/openclaw/src/hooks/internal-hooks.ts)
5
+ * Re-implemented as an enterprise "business process automation" hook system
6
+ *
7
+ * Features:
8
+ * - Type-safe event registration and triggering
9
+ * - Wildcard and specific event:action subscriptions
10
+ * - Error isolation (one handler failure doesn't block others)
11
+ * - Hook priority ordering
12
+ * - Async handler support with timeout protection
13
+ * - Debug introspection for registered hooks
14
+ */
15
+
16
+ /**
17
+ * Hook event types — the broad categories of lifecycle events
18
+ */
19
+ export const HookEventType = {
20
+ AGENT: 'agent',
21
+ TASK: 'task',
22
+ REQUIREMENT: 'requirement',
23
+ DEPARTMENT: 'department',
24
+ SYSTEM: 'system',
25
+ MESSAGE: 'message',
26
+ LLM: 'llm',
27
+ };
28
+
29
+ /**
30
+ * Pre-defined hook event keys (type:action combinations)
31
+ */
32
+ export const HookEvent = {
33
+ // Agent lifecycle
34
+ AGENT_CREATED: 'agent:created',
35
+ AGENT_TASK_START: 'agent:task_start',
36
+ AGENT_TASK_END: 'agent:task_end',
37
+ AGENT_DISMISSED: 'agent:dismissed',
38
+ AGENT_ERROR: 'agent:error',
39
+
40
+ // Task lifecycle
41
+ TASK_ASSIGNED: 'task:assigned',
42
+ TASK_PROGRESS: 'task:progress',
43
+ TASK_COMPLETED: 'task:completed',
44
+ TASK_FAILED: 'task:failed',
45
+ TASK_REVIEW: 'task:review',
46
+
47
+ // Requirement lifecycle
48
+ REQ_CREATED: 'requirement:created',
49
+ REQ_TEAM_FORMED: 'requirement:team_formed',
50
+ REQ_PHASE_START: 'requirement:phase_start',
51
+ REQ_PHASE_END: 'requirement:phase_end',
52
+ REQ_COMPLETED: 'requirement:completed',
53
+ REQ_CANCELLED: 'requirement:cancelled',
54
+
55
+ // Department lifecycle
56
+ DEPT_CREATED: 'department:created',
57
+ DEPT_MEMBER_ADDED: 'department:member_added',
58
+ DEPT_MEMBER_REMOVED: 'department:member_removed',
59
+
60
+ // System events
61
+ SYSTEM_STARTUP: 'system:startup',
62
+ SYSTEM_SHUTDOWN: 'system:shutdown',
63
+ SYSTEM_HEALTH_CHECK: 'system:health_check',
64
+ SYSTEM_CONFIG_CHANGE: 'system:config_change',
65
+
66
+ // Message events
67
+ MESSAGE_RECEIVED: 'message:received',
68
+ MESSAGE_SENT: 'message:sent',
69
+ MESSAGE_BROADCAST: 'message:broadcast',
70
+
71
+ // LLM interaction events
72
+ LLM_REQUEST_START: 'llm:request_start',
73
+ LLM_REQUEST_END: 'llm:request_end',
74
+ LLM_TOKEN_USAGE: 'llm:token_usage',
75
+ LLM_ERROR: 'llm:error',
76
+ };
77
+
78
+ /**
79
+ * Default handler timeout (ms)
80
+ */
81
+ const DEFAULT_TIMEOUT = 10000;
82
+
83
+ /**
84
+ * Internal handler entry with metadata
85
+ * @typedef {object} HandlerEntry
86
+ * @property {string} id - Unique handler ID
87
+ * @property {Function} handler - The actual handler function
88
+ * @property {number} priority - Execution priority (lower = first)
89
+ * @property {string} source - Who registered this handler (plugin ID, module name, etc.)
90
+ * @property {number} timeout - Max execution time in ms
91
+ * @property {Date} registeredAt - When this handler was registered
92
+ */
93
+
94
+ /**
95
+ * Hook Event Payload - passed to every handler
96
+ * @typedef {object} HookEventPayload
97
+ * @property {string} type - Event type (agent, task, etc.)
98
+ * @property {string} action - Specific action (created, completed, etc.)
99
+ * @property {string} eventKey - Full event key (type:action)
100
+ * @property {object} context - Event-specific context data
101
+ * @property {Date} timestamp - When the event occurred
102
+ * @property {string[]} messages - Handlers can push messages here for feedback
103
+ * @property {object} meta - Mutable metadata that handlers can enrich
104
+ */
105
+
106
+ let handlerIdCounter = 0;
107
+
108
+ /**
109
+ * Hook Registry - Manages all lifecycle hooks
110
+ *
111
+ * Uses a singleton pattern (similar to OpenClaw's globalThis approach)
112
+ * to ensure hooks work across dynamic imports.
113
+ */
114
+ class HookRegistry {
115
+ constructor() {
116
+ /** @type {Map<string, HandlerEntry[]>} */
117
+ this.handlers = new Map();
118
+
119
+ /** @type {Map<string, number>} - Track how many times each event has fired */
120
+ this.eventCounts = new Map();
121
+
122
+ /** @type {Array<{eventKey: string, timestamp: Date, handlerCount: number, errors: number}>} */
123
+ this.recentFires = [];
124
+ this.maxRecentFires = 100;
125
+ }
126
+
127
+ /**
128
+ * Register a hook handler for a specific event key
129
+ *
130
+ * @param {string} eventKey - Event type (e.g., 'agent') or specific (e.g., 'agent:created')
131
+ * @param {Function} handler - async (event: HookEventPayload) => void
132
+ * @param {object} options
133
+ * @param {number} options.priority - Execution priority (default: 100, lower = first)
134
+ * @param {string} options.source - Who registered this (for debugging)
135
+ * @param {number} options.timeout - Handler timeout in ms
136
+ * @returns {string} Handler ID for later removal
137
+ *
138
+ * @example
139
+ * // Listen to all agent events
140
+ * hookRegistry.on('agent', async (event) => {
141
+ * console.log('Agent event:', event.action);
142
+ * });
143
+ *
144
+ * // Listen to specific task completion
145
+ * hookRegistry.on('task:completed', async (event) => {
146
+ * await notifyStakeholders(event.context);
147
+ * });
148
+ */
149
+ on(eventKey, handler, options = {}) {
150
+ const id = `hook_${++handlerIdCounter}`;
151
+ const entry = {
152
+ id,
153
+ handler,
154
+ priority: options.priority ?? 100,
155
+ source: options.source || 'unknown',
156
+ timeout: options.timeout ?? DEFAULT_TIMEOUT,
157
+ registeredAt: new Date(),
158
+ };
159
+
160
+ if (!this.handlers.has(eventKey)) {
161
+ this.handlers.set(eventKey, []);
162
+ }
163
+
164
+ const list = this.handlers.get(eventKey);
165
+ list.push(entry);
166
+
167
+ // Keep sorted by priority (lower first)
168
+ list.sort((a, b) => a.priority - b.priority);
169
+
170
+ return id;
171
+ }
172
+
173
+ /**
174
+ * Remove a specific handler by its ID
175
+ * @param {string} handlerId
176
+ * @returns {boolean} Whether the handler was found and removed
177
+ */
178
+ off(handlerId) {
179
+ for (const [eventKey, entries] of this.handlers) {
180
+ const index = entries.findIndex(e => e.id === handlerId);
181
+ if (index !== -1) {
182
+ entries.splice(index, 1);
183
+ if (entries.length === 0) {
184
+ this.handlers.delete(eventKey);
185
+ }
186
+ return true;
187
+ }
188
+ }
189
+ return false;
190
+ }
191
+
192
+ /**
193
+ * Remove all handlers for an event key
194
+ * @param {string} eventKey
195
+ */
196
+ offAll(eventKey) {
197
+ this.handlers.delete(eventKey);
198
+ }
199
+
200
+ /**
201
+ * Remove all handlers from a specific source
202
+ * @param {string} source
203
+ * @returns {number} Number of handlers removed
204
+ */
205
+ offBySource(source) {
206
+ let removed = 0;
207
+ for (const [eventKey, entries] of this.handlers) {
208
+ const before = entries.length;
209
+ const filtered = entries.filter(e => e.source !== source);
210
+ if (filtered.length !== before) {
211
+ removed += before - filtered.length;
212
+ if (filtered.length === 0) {
213
+ this.handlers.delete(eventKey);
214
+ } else {
215
+ this.handlers.set(eventKey, filtered);
216
+ }
217
+ }
218
+ }
219
+ return removed;
220
+ }
221
+
222
+ /**
223
+ * Trigger a hook event
224
+ *
225
+ * Calls all handlers registered for:
226
+ * 1. The general event type (e.g., 'agent')
227
+ * 2. The specific event:action combination (e.g., 'agent:created')
228
+ *
229
+ * Handlers execute in priority order. Errors are caught and logged
230
+ * but don't prevent other handlers from running.
231
+ *
232
+ * @param {string} eventKey - The event key (e.g., 'agent:created')
233
+ * @param {object} context - Event-specific context data
234
+ * @returns {Promise<{messages: string[], errors: Array<{handlerId: string, source: string, error: string}>, meta: object}>}
235
+ */
236
+ async trigger(eventKey, context = {}) {
237
+ const [type, action] = eventKey.includes(':')
238
+ ? eventKey.split(':', 2)
239
+ : [eventKey, 'unknown'];
240
+
241
+ const event = {
242
+ type,
243
+ action,
244
+ eventKey,
245
+ context,
246
+ timestamp: new Date(),
247
+ messages: [],
248
+ meta: {},
249
+ };
250
+
251
+ // Collect handlers: type-level + specific event key
252
+ const typeHandlers = this.handlers.get(type) || [];
253
+ const specificHandlers = eventKey !== type
254
+ ? (this.handlers.get(eventKey) || [])
255
+ : [];
256
+
257
+ // Merge and sort by priority
258
+ const allHandlers = [...typeHandlers, ...specificHandlers]
259
+ .sort((a, b) => a.priority - b.priority);
260
+
261
+ // Track event firing
262
+ this.eventCounts.set(eventKey, (this.eventCounts.get(eventKey) || 0) + 1);
263
+
264
+ const errors = [];
265
+
266
+ if (allHandlers.length === 0) {
267
+ this._recordFire(eventKey, 0, 0);
268
+ return { messages: event.messages, errors, meta: event.meta };
269
+ }
270
+
271
+ // Execute handlers sequentially (in priority order)
272
+ for (const entry of allHandlers) {
273
+ try {
274
+ const result = await this._executeWithTimeout(
275
+ entry.handler,
276
+ event,
277
+ entry.timeout,
278
+ );
279
+ } catch (err) {
280
+ const message = err instanceof Error ? err.message : String(err);
281
+ console.error(`[Hooks] Error in handler "${entry.id}" (${entry.source}) for ${eventKey}: ${message}`);
282
+ errors.push({
283
+ handlerId: entry.id,
284
+ source: entry.source,
285
+ error: message,
286
+ });
287
+ }
288
+ }
289
+
290
+ this._recordFire(eventKey, allHandlers.length, errors.length);
291
+
292
+ return { messages: event.messages, errors, meta: event.meta };
293
+ }
294
+
295
+ /**
296
+ * Execute a handler with timeout protection
297
+ * @param {Function} handler
298
+ * @param {object} event
299
+ * @param {number} timeoutMs
300
+ * @returns {Promise<void>}
301
+ */
302
+ async _executeWithTimeout(handler, event, timeoutMs) {
303
+ return Promise.race([
304
+ handler(event),
305
+ new Promise((_, reject) =>
306
+ setTimeout(
307
+ () => reject(new Error(`Hook handler timed out after ${timeoutMs}ms`)),
308
+ timeoutMs,
309
+ ),
310
+ ),
311
+ ]);
312
+ }
313
+
314
+ /**
315
+ * Record a fire event for introspection
316
+ */
317
+ _recordFire(eventKey, handlerCount, errors) {
318
+ this.recentFires.push({
319
+ eventKey,
320
+ timestamp: new Date(),
321
+ handlerCount,
322
+ errors,
323
+ });
324
+ if (this.recentFires.length > this.maxRecentFires) {
325
+ this.recentFires.shift();
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Clear all registered hooks (useful for testing)
331
+ */
332
+ clear() {
333
+ this.handlers.clear();
334
+ this.eventCounts.clear();
335
+ this.recentFires = [];
336
+ }
337
+
338
+ /**
339
+ * Get all registered event keys (for debugging)
340
+ * @returns {string[]}
341
+ */
342
+ getRegisteredKeys() {
343
+ return Array.from(this.handlers.keys());
344
+ }
345
+
346
+ /**
347
+ * Get handler count for a specific event key
348
+ * @param {string} eventKey
349
+ * @returns {number}
350
+ */
351
+ getHandlerCount(eventKey) {
352
+ return (this.handlers.get(eventKey) || []).length;
353
+ }
354
+
355
+ /**
356
+ * Get full diagnostic summary
357
+ * @returns {object}
358
+ */
359
+ getSummary() {
360
+ const keys = this.getRegisteredKeys();
361
+ const handlersByKey = {};
362
+ let totalHandlers = 0;
363
+
364
+ for (const key of keys) {
365
+ const entries = this.handlers.get(key) || [];
366
+ handlersByKey[key] = entries.map(e => ({
367
+ id: e.id,
368
+ source: e.source,
369
+ priority: e.priority,
370
+ }));
371
+ totalHandlers += entries.length;
372
+ }
373
+
374
+ return {
375
+ totalHandlers,
376
+ registeredKeys: keys.length,
377
+ handlersByKey,
378
+ eventCounts: Object.fromEntries(this.eventCounts),
379
+ recentFires: this.recentFires.slice(-20),
380
+ };
381
+ }
382
+ }
383
+
384
+ // ============================================================================
385
+ // Convenience factory for creating hook event payloads
386
+ // ============================================================================
387
+
388
+ /**
389
+ * Create a hook event payload with common fields filled in
390
+ *
391
+ * @param {string} type - Event type
392
+ * @param {string} action - Action within that type
393
+ * @param {object} context - Additional context
394
+ * @returns {HookEventPayload}
395
+ */
396
+ export function createHookEvent(type, action, context = {}) {
397
+ return {
398
+ type,
399
+ action,
400
+ eventKey: `${type}:${action}`,
401
+ context,
402
+ timestamp: new Date(),
403
+ messages: [],
404
+ meta: {},
405
+ };
406
+ }
407
+
408
+ // ============================================================================
409
+ // Global singleton
410
+ // ============================================================================
411
+
412
+ export const hookRegistry = new HookRegistry();
413
+
414
+ export { HookRegistry };
@@ -0,0 +1,134 @@
1
+
2
+ 'use client';
3
+
4
+ import { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
5
+ import en from '@/locales/en';
6
+ import zh from '@/locales/zh';
7
+ import ja from '@/locales/ja';
8
+ import ko from '@/locales/ko';
9
+ import es from '@/locales/es';
10
+ import fr from '@/locales/fr';
11
+ import de from '@/locales/de';
12
+
13
+ const translations = { en, zh, ja, ko, es, fr, de };
14
+
15
+ export const LANGUAGES = [
16
+ { code: 'en', label: 'English', flag: '🇺🇸' },
17
+ { code: 'zh', label: '中文', flag: '🇨🇳' },
18
+ { code: 'ja', label: '日本語', flag: '🇯🇵' },
19
+ { code: 'ko', label: '한국어', flag: '🇰🇷' },
20
+ { code: 'es', label: 'Español', flag: '🇪🇸' },
21
+ { code: 'fr', label: 'Français', flag: '🇫🇷' },
22
+ { code: 'de', label: 'Deutsch', flag: '🇩🇪' },
23
+ ];
24
+
25
+ const DEFAULT_LANG = 'en';
26
+
27
+ const I18nContext = createContext(null);
28
+
29
+ function getNestedValue(obj, path) {
30
+ return path.split('.').reduce((o, k) => o?.[k], obj);
31
+ }
32
+
33
+ export function I18nProvider({ children }) {
34
+ const [lang, setLangState] = useState(DEFAULT_LANG);
35
+
36
+ useEffect(() => {
37
+ const saved = localStorage.getItem('idea-unlimited-lang');
38
+ if (saved && translations[saved]) {
39
+ setLangState(saved);
40
+ }
41
+ }, []);
42
+
43
+ const setLang = useCallback((code) => {
44
+ if (translations[code]) {
45
+ setLangState(code);
46
+ localStorage.setItem('idea-unlimited-lang', code);
47
+ document.documentElement.lang = code === 'zh' ? 'zh-CN' : code;
48
+ }
49
+ }, []);
50
+
51
+ const t = useCallback((key, params) => {
52
+ let str = getNestedValue(translations[lang], key)
53
+ || getNestedValue(translations[DEFAULT_LANG], key)
54
+ || key;
55
+ if (params) {
56
+ Object.entries(params).forEach(([k, v]) => {
57
+ str = str.replace(new RegExp(`\\{${k}\\}`, 'g'), v);
58
+ });
59
+ }
60
+ return str;
61
+ }, [lang]);
62
+
63
+ return (
64
+ <I18nContext.Provider value={{ lang, setLang, t }}>
65
+ {children}
66
+ </I18nContext.Provider>
67
+ );
68
+ }
69
+
70
+ export function useI18n() {
71
+ const ctx = useContext(I18nContext);
72
+ if (!ctx) throw new Error('useI18n must be used within I18nProvider');
73
+ return ctx;
74
+ }
75
+
76
+ export function LanguageSelector({ className = '', direction = 'up' }) {
77
+ const { lang, setLang } = useI18n();
78
+ const [open, setOpen] = useState(false);
79
+ const btnRef = useRef(null);
80
+ const [menuPos, setMenuPos] = useState({ bottom: 0, left: 0, top: 0 });
81
+ const current = LANGUAGES.find(l => l.code === lang) || LANGUAGES[0];
82
+
83
+ useEffect(() => {
84
+ if (open && btnRef.current) {
85
+ const rect = btnRef.current.getBoundingClientRect();
86
+ setMenuPos({
87
+ bottom: window.innerHeight - rect.top + 4,
88
+ left: rect.left,
89
+ top: rect.bottom + 4,
90
+ });
91
+ }
92
+ }, [open]);
93
+
94
+ const menuStyle = direction === 'down'
95
+ ? { top: menuPos.top, left: menuPos.left }
96
+ : { bottom: menuPos.bottom, left: menuPos.left };
97
+
98
+ return (
99
+ <div className={`relative ${className}`}>
100
+ <button
101
+ ref={btnRef}
102
+ onClick={() => setOpen(!open)}
103
+ className="flex items-center gap-1.5 px-2 py-1 rounded-lg text-xs bg-white/5 hover:bg-white/10 transition-all text-[var(--muted)] hover:text-white"
104
+ >
105
+ <span>{current.flag}</span>
106
+ <span>{current.label}</span>
107
+ <span className="text-[10px]">▾</span>
108
+ </button>
109
+ {open && (
110
+ <>
111
+ <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
112
+ <div
113
+ className="fixed bg-[var(--card)] border border-[var(--border)] rounded-lg shadow-xl z-50 py-1 min-w-[140px]"
114
+ style={menuStyle}
115
+ >
116
+ {LANGUAGES.map(l => (
117
+ <button
118
+ key={l.code}
119
+ onClick={() => { setLang(l.code); setOpen(false); }}
120
+ className={`w-full text-left px-3 py-1.5 text-xs flex items-center gap-2 hover:bg-white/5 transition-all ${
121
+ lang === l.code ? 'text-[var(--accent)]' : 'text-[var(--muted)]'
122
+ }`}
123
+ >
124
+ <span>{l.flag}</span>
125
+ <span>{l.label}</span>
126
+ {lang === l.code && <span className="ml-auto">✓</span>}
127
+ </button>
128
+ ))}
129
+ </div>
130
+ </>
131
+ )}
132
+ </div>
133
+ );
134
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Centralized path configuration
3
+ *
4
+ * In Electron packaged mode, data and workspace directories are set via
5
+ * environment variables (IDEACO_DATA_DIR / IDEACO_WORKSPACE_DIR) to point
6
+ * to the user's persistent userData folder, so that app updates don't
7
+ * overwrite user data.
8
+ *
9
+ * In dev mode or plain `next dev`, falls back to process.cwd() based paths.
10
+ */
11
+ import path from 'path';
12
+
13
+ export const DATA_DIR = process.env.IDEACO_DATA_DIR
14
+ || path.resolve(process.cwd(), 'data');
15
+
16
+ export const WORKSPACE_DIR = process.env.IDEACO_WORKSPACE_DIR
17
+ || path.resolve(process.cwd(), 'workspace');
18
+
19
+ export const MEMORY_DIR = path.join(DATA_DIR, 'memories');
20
+ export const CHATS_DIR = path.join(DATA_DIR, 'chats');
21
+ export const AUDIT_DIR = path.join(DATA_DIR, 'audit');
22
+ export const STATE_FILE = path.join(DATA_DIR, 'company-state.json');
23
+ export const BACKUP_FILE = path.join(DATA_DIR, 'company-state.backup.json');
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Global State Management - Server-side Singleton
3
+ * Shares Company instance across Next.js API Routes
4
+ *
5
+ * Persistence:
6
+ * - Auto-restores from data/company-state.json on startup
7
+ * - Auto-saves to disk on state changes
8
+ * - Uses globalThis to prevent state loss during hot reload
9
+ */
10
+ import { loadState, saveState, clearState } from '@/core/organization/persistence.js';
11
+ import { Company } from '@/core/index.js';
12
+ import { initPluginRuntime } from '@/core/system/plugin.js';
13
+ import { sessionManager } from '@/core/agent/session.js';
14
+ import { cronScheduler } from '@/core/system/cron.js';
15
+ import { knowledgeManager } from '@/core/employee/knowledge.js';
16
+ import { llmClient } from '@/core/agent/llm-agent/client.js';
17
+
18
+ const globalStore = globalThis;
19
+
20
+ if (!globalStore.__aiEnterprise) {
21
+ globalStore.__aiEnterprise = {
22
+ company: null,
23
+ loaded: false,
24
+ };
25
+
26
+ // Initialize plugin runtime: inject real singletons into the plugin system
27
+ initPluginRuntime({
28
+ sessionManager,
29
+ cronScheduler,
30
+ knowledgeManager,
31
+ llmClient,
32
+ messageBus: null, // messageBus is created after Company, injected lazily
33
+ });
34
+
35
+ // Try restoring from disk on first startup
36
+ try {
37
+ const savedData = loadState();
38
+ if (savedData) {
39
+ globalStore.__aiEnterprise.company = Company.deserialize(savedData);
40
+ // Inject messageBus after restoration
41
+ if (globalStore.__aiEnterprise.company?.messageBus) {
42
+ initPluginRuntime({ messageBus: globalStore.__aiEnterprise.company.messageBus });
43
+ }
44
+ console.log('🔄 Company state restored from disk successfully');
45
+ }
46
+ } catch (e) {
47
+ console.error('⚠️ Failed to restore state, starting with empty state:', e.message);
48
+ globalStore.__aiEnterprise.company = null;
49
+ }
50
+ globalStore.__aiEnterprise.loaded = true;
51
+ }
52
+
53
+ export function getCompany() {
54
+ return globalStore.__aiEnterprise.company;
55
+ }
56
+
57
+ export function setCompany(company) {
58
+ globalStore.__aiEnterprise.company = company;
59
+ // Inject messageBus into plugin runtime (only available after Company is created)
60
+ if (company && company.messageBus) {
61
+ initPluginRuntime({ messageBus: company.messageBus });
62
+ }
63
+ // Save to disk immediately
64
+ if (company) {
65
+ saveState(company);
66
+ }
67
+ }
68
+
69
+ export function resetCompany() {
70
+ globalStore.__aiEnterprise.company = null;
71
+ clearState();
72
+ }