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,483 @@
1
+ /**
2
+ * Configuration Validator - Schema-based config validation and safe defaults
3
+ *
4
+ * Distilled from OpenClaw's config system (vendor/openclaw/src/config/validation.ts
5
+ * and vendor/openclaw/src/config/schema.ts)
6
+ * Re-implemented as an enterprise "company policy compliance engine"
7
+ *
8
+ * Features:
9
+ * - Declarative schema definition with type checking
10
+ * - Nested object and array validation
11
+ * - Default value injection
12
+ * - Environment variable interpolation
13
+ * - Validation error aggregation with friendly messages
14
+ * - Runtime config patching with safety checks
15
+ * - Config diffing for change tracking
16
+ */
17
+
18
+ /**
19
+ * Supported config value types
20
+ */
21
+ export const ConfigType = {
22
+ STRING: 'string',
23
+ NUMBER: 'number',
24
+ BOOLEAN: 'boolean',
25
+ ARRAY: 'array',
26
+ OBJECT: 'object',
27
+ ENUM: 'enum',
28
+ };
29
+
30
+ /**
31
+ * Validation error entry
32
+ * @typedef {object} ValidationError
33
+ * @property {string} path - Dot-notation path to the invalid field
34
+ * @property {string} message - Human-readable error message
35
+ * @property {*} value - The invalid value
36
+ * @property {string} rule - Which rule was violated
37
+ */
38
+
39
+ /**
40
+ * Schema field definition
41
+ * @typedef {object} SchemaField
42
+ * @property {string} type - ConfigType value
43
+ * @property {string} description - Field description
44
+ * @property {*} default - Default value if not provided
45
+ * @property {boolean} required - Whether the field is required
46
+ * @property {Array} enum - Allowed values (for 'enum' type)
47
+ * @property {number} min - Minimum value (for 'number') or min length (for 'string'/'array')
48
+ * @property {number} max - Maximum value (for 'number') or max length (for 'string'/'array')
49
+ * @property {RegExp|string} pattern - Regex pattern (for 'string')
50
+ * @property {object} items - Schema for array items
51
+ * @property {object} properties - Schema for object properties
52
+ * @property {string} envVar - Environment variable to read from
53
+ * @property {Function} validate - Custom validation function: (value) => string|null
54
+ */
55
+
56
+ /**
57
+ * Validate a value against a schema field definition
58
+ *
59
+ * @param {*} value - Value to validate
60
+ * @param {SchemaField} field - Schema definition
61
+ * @param {string} path - Current dot-notation path (for error messages)
62
+ * @returns {ValidationError[]}
63
+ */
64
+ function validateField(value, field, path) {
65
+ const errors = [];
66
+
67
+ // Handle required check
68
+ if (value === undefined || value === null) {
69
+ if (field.required) {
70
+ errors.push({ path, message: `Required field "${path}" is missing`, value, rule: 'required' });
71
+ }
72
+ return errors; // No further validation on missing optional fields
73
+ }
74
+
75
+ // Type checking
76
+ switch (field.type) {
77
+ case ConfigType.STRING:
78
+ if (typeof value !== 'string') {
79
+ errors.push({ path, message: `"${path}" must be a string, got ${typeof value}`, value, rule: 'type' });
80
+ return errors;
81
+ }
82
+ if (field.min !== undefined && value.length < field.min) {
83
+ errors.push({ path, message: `"${path}" must be at least ${field.min} characters`, value, rule: 'min' });
84
+ }
85
+ if (field.max !== undefined && value.length > field.max) {
86
+ errors.push({ path, message: `"${path}" must be at most ${field.max} characters`, value, rule: 'max' });
87
+ }
88
+ if (field.pattern) {
89
+ const regex = field.pattern instanceof RegExp ? field.pattern : new RegExp(field.pattern);
90
+ if (!regex.test(value)) {
91
+ errors.push({ path, message: `"${path}" does not match required pattern`, value, rule: 'pattern' });
92
+ }
93
+ }
94
+ break;
95
+
96
+ case ConfigType.NUMBER:
97
+ if (typeof value !== 'number' || isNaN(value)) {
98
+ errors.push({ path, message: `"${path}" must be a number, got ${typeof value}`, value, rule: 'type' });
99
+ return errors;
100
+ }
101
+ if (field.min !== undefined && value < field.min) {
102
+ errors.push({ path, message: `"${path}" must be >= ${field.min}`, value, rule: 'min' });
103
+ }
104
+ if (field.max !== undefined && value > field.max) {
105
+ errors.push({ path, message: `"${path}" must be <= ${field.max}`, value, rule: 'max' });
106
+ }
107
+ break;
108
+
109
+ case ConfigType.BOOLEAN:
110
+ if (typeof value !== 'boolean') {
111
+ errors.push({ path, message: `"${path}" must be a boolean, got ${typeof value}`, value, rule: 'type' });
112
+ }
113
+ break;
114
+
115
+ case ConfigType.ENUM:
116
+ if (!field.enum || !field.enum.includes(value)) {
117
+ errors.push({
118
+ path,
119
+ message: `"${path}" must be one of [${(field.enum || []).join(', ')}], got "${value}"`,
120
+ value,
121
+ rule: 'enum',
122
+ });
123
+ }
124
+ break;
125
+
126
+ case ConfigType.ARRAY:
127
+ if (!Array.isArray(value)) {
128
+ errors.push({ path, message: `"${path}" must be an array, got ${typeof value}`, value, rule: 'type' });
129
+ return errors;
130
+ }
131
+ if (field.min !== undefined && value.length < field.min) {
132
+ errors.push({ path, message: `"${path}" must have at least ${field.min} items`, value, rule: 'min' });
133
+ }
134
+ if (field.max !== undefined && value.length > field.max) {
135
+ errors.push({ path, message: `"${path}" must have at most ${field.max} items`, value, rule: 'max' });
136
+ }
137
+ // Validate array items
138
+ if (field.items) {
139
+ value.forEach((item, index) => {
140
+ errors.push(...validateField(item, field.items, `${path}[${index}]`));
141
+ });
142
+ }
143
+ break;
144
+
145
+ case ConfigType.OBJECT:
146
+ if (typeof value !== 'object' || Array.isArray(value)) {
147
+ errors.push({ path, message: `"${path}" must be an object, got ${Array.isArray(value) ? 'array' : typeof value}`, value, rule: 'type' });
148
+ return errors;
149
+ }
150
+ // Validate nested properties
151
+ if (field.properties) {
152
+ for (const [key, propSchema] of Object.entries(field.properties)) {
153
+ errors.push(...validateField(value[key], propSchema, `${path}.${key}`));
154
+ }
155
+ }
156
+ break;
157
+ }
158
+
159
+ // Custom validation
160
+ if (field.validate && typeof field.validate === 'function') {
161
+ const customError = field.validate(value);
162
+ if (customError) {
163
+ errors.push({ path, message: customError, value, rule: 'custom' });
164
+ }
165
+ }
166
+
167
+ return errors;
168
+ }
169
+
170
+ /**
171
+ * Apply default values to a config object based on schema
172
+ *
173
+ * @param {object} config - The config object (will be mutated)
174
+ * @param {object} schema - Schema definition with properties
175
+ * @returns {object} The config with defaults applied
176
+ */
177
+ function applyDefaults(config, schema) {
178
+ if (!schema || !schema.properties) return config;
179
+
180
+ for (const [key, field] of Object.entries(schema.properties)) {
181
+ if (config[key] === undefined && field.default !== undefined) {
182
+ config[key] = structuredClone(field.default);
183
+ }
184
+
185
+ // Apply env var override
186
+ if (field.envVar && process.env[field.envVar] !== undefined) {
187
+ const envValue = process.env[field.envVar];
188
+ switch (field.type) {
189
+ case ConfigType.NUMBER:
190
+ config[key] = Number(envValue);
191
+ break;
192
+ case ConfigType.BOOLEAN:
193
+ config[key] = envValue === 'true' || envValue === '1';
194
+ break;
195
+ default:
196
+ config[key] = envValue;
197
+ }
198
+ }
199
+
200
+ // Recurse into nested objects
201
+ if (field.type === ConfigType.OBJECT && field.properties && config[key]) {
202
+ applyDefaults(config[key], field);
203
+ }
204
+ }
205
+
206
+ return config;
207
+ }
208
+
209
+ /**
210
+ * Config Validator - Schema-based configuration validation
211
+ */
212
+ export class ConfigValidator {
213
+ /**
214
+ * @param {object} schema - Top-level schema definition
215
+ * @param {object} schema.properties - Map of field names to SchemaField definitions
216
+ */
217
+ constructor(schema) {
218
+ this.schema = schema;
219
+ }
220
+
221
+ /**
222
+ * Validate a config object against the schema
223
+ *
224
+ * @param {object} config
225
+ * @returns {{ valid: boolean, errors: ValidationError[] }}
226
+ */
227
+ validate(config) {
228
+ if (!config || typeof config !== 'object') {
229
+ return {
230
+ valid: false,
231
+ errors: [{ path: '', message: 'Config must be a non-null object', value: config, rule: 'type' }],
232
+ };
233
+ }
234
+
235
+ const errors = [];
236
+ if (this.schema.properties) {
237
+ for (const [key, field] of Object.entries(this.schema.properties)) {
238
+ errors.push(...validateField(config[key], field, key));
239
+ }
240
+ }
241
+
242
+ return { valid: errors.length === 0, errors };
243
+ }
244
+
245
+ /**
246
+ * Validate and apply defaults (non-destructive - returns a new object)
247
+ *
248
+ * @param {object} config
249
+ * @returns {{ valid: boolean, config: object, errors: ValidationError[] }}
250
+ */
251
+ validateAndApplyDefaults(config) {
252
+ const merged = structuredClone(config || {});
253
+ applyDefaults(merged, this.schema);
254
+
255
+ const { valid, errors } = this.validate(merged);
256
+ return { valid, config: merged, errors };
257
+ }
258
+
259
+ /**
260
+ * Apply a runtime config patch with validation
261
+ *
262
+ * Distilled from OpenClaw's merge-patch.ts pattern — prevents prototype pollution
263
+ * and validates the resulting config.
264
+ *
265
+ * @param {object} baseConfig - Current config
266
+ * @param {object} patch - Partial config to merge
267
+ * @returns {{ valid: boolean, config: object, errors: ValidationError[], changes: Array }}
268
+ */
269
+ applyPatch(baseConfig, patch) {
270
+ if (!patch || typeof patch !== 'object') {
271
+ return {
272
+ valid: true,
273
+ config: structuredClone(baseConfig),
274
+ errors: [],
275
+ changes: [],
276
+ };
277
+ }
278
+
279
+ const merged = structuredClone(baseConfig || {});
280
+ const changes = [];
281
+
282
+ // Safe merge (prototype pollution prevention)
283
+ for (const [key, value] of Object.entries(patch)) {
284
+ // Block prototype pollution
285
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
286
+ continue;
287
+ }
288
+
289
+ const oldValue = merged[key];
290
+ if (value === null) {
291
+ // null means "delete this key"
292
+ if (merged[key] !== undefined) {
293
+ changes.push({ path: key, action: 'delete', oldValue: merged[key] });
294
+ delete merged[key];
295
+ }
296
+ } else if (typeof value === 'object' && !Array.isArray(value) && typeof oldValue === 'object' && !Array.isArray(oldValue)) {
297
+ // Deep merge for nested objects
298
+ const nested = this._deepMergeSafe(oldValue, value, key);
299
+ merged[key] = nested.result;
300
+ changes.push(...nested.changes);
301
+ } else {
302
+ // Direct replacement
303
+ merged[key] = structuredClone(value);
304
+ if (JSON.stringify(oldValue) !== JSON.stringify(value)) {
305
+ changes.push({ path: key, action: oldValue === undefined ? 'add' : 'update', oldValue, newValue: value });
306
+ }
307
+ }
308
+ }
309
+
310
+ const { valid, errors } = this.validate(merged);
311
+ return { valid, config: merged, errors, changes };
312
+ }
313
+
314
+ /**
315
+ * Deep merge with prototype pollution protection
316
+ * @private
317
+ */
318
+ _deepMergeSafe(target, source, parentPath) {
319
+ const result = { ...target };
320
+ const changes = [];
321
+
322
+ for (const [key, value] of Object.entries(source)) {
323
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
324
+ continue;
325
+ }
326
+
327
+ const fullPath = `${parentPath}.${key}`;
328
+ const oldValue = result[key];
329
+
330
+ if (value === null) {
331
+ if (result[key] !== undefined) {
332
+ changes.push({ path: fullPath, action: 'delete', oldValue: result[key] });
333
+ delete result[key];
334
+ }
335
+ } else if (typeof value === 'object' && !Array.isArray(value) && typeof oldValue === 'object' && !Array.isArray(oldValue)) {
336
+ const nested = this._deepMergeSafe(oldValue, value, fullPath);
337
+ result[key] = nested.result;
338
+ changes.push(...nested.changes);
339
+ } else {
340
+ result[key] = structuredClone(value);
341
+ if (JSON.stringify(oldValue) !== JSON.stringify(value)) {
342
+ changes.push({ path: fullPath, action: oldValue === undefined ? 'add' : 'update', oldValue, newValue: value });
343
+ }
344
+ }
345
+ }
346
+
347
+ return { result, changes };
348
+ }
349
+
350
+ /**
351
+ * Diff two configs to find changes
352
+ *
353
+ * @param {object} oldConfig
354
+ * @param {object} newConfig
355
+ * @returns {Array<{path: string, action: string, oldValue: *, newValue: *}>}
356
+ */
357
+ diff(oldConfig, newConfig) {
358
+ return this._diffObjects(oldConfig || {}, newConfig || {}, '');
359
+ }
360
+
361
+ /**
362
+ * @private
363
+ */
364
+ _diffObjects(oldObj, newObj, prefix) {
365
+ const changes = [];
366
+ const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
367
+
368
+ for (const key of allKeys) {
369
+ const path = prefix ? `${prefix}.${key}` : key;
370
+ const oldVal = oldObj[key];
371
+ const newVal = newObj[key];
372
+
373
+ if (oldVal === undefined && newVal !== undefined) {
374
+ changes.push({ path, action: 'add', oldValue: undefined, newValue: newVal });
375
+ } else if (oldVal !== undefined && newVal === undefined) {
376
+ changes.push({ path, action: 'delete', oldValue: oldVal, newValue: undefined });
377
+ } else if (
378
+ typeof oldVal === 'object' && oldVal !== null && !Array.isArray(oldVal) &&
379
+ typeof newVal === 'object' && newVal !== null && !Array.isArray(newVal)
380
+ ) {
381
+ changes.push(...this._diffObjects(oldVal, newVal, path));
382
+ } else if (JSON.stringify(oldVal) !== JSON.stringify(newVal)) {
383
+ changes.push({ path, action: 'update', oldValue: oldVal, newValue: newVal });
384
+ }
385
+ }
386
+
387
+ return changes;
388
+ }
389
+ }
390
+
391
+ // ============================================================================
392
+ // Pre-built schema for the enterprise application config
393
+ // ============================================================================
394
+
395
+ /**
396
+ * Enterprise application config schema
397
+ */
398
+ export const enterpriseConfigSchema = {
399
+ properties: {
400
+ company: {
401
+ type: ConfigType.OBJECT,
402
+ description: 'Company configuration',
403
+ required: true,
404
+ properties: {
405
+ name: {
406
+ type: ConfigType.STRING,
407
+ description: 'Company name',
408
+ required: true,
409
+ min: 1,
410
+ max: 100,
411
+ },
412
+ mission: {
413
+ type: ConfigType.STRING,
414
+ description: 'Company mission statement',
415
+ default: '',
416
+ },
417
+ },
418
+ },
419
+ providers: {
420
+ type: ConfigType.ARRAY,
421
+ description: 'LLM provider configurations',
422
+ default: [],
423
+ items: {
424
+ type: ConfigType.OBJECT,
425
+ properties: {
426
+ id: { type: ConfigType.STRING, required: true },
427
+ provider: {
428
+ type: ConfigType.ENUM,
429
+ enum: ['openai', 'anthropic', 'google', 'deepseek', 'qwen', 'zhipu', 'moonshot', 'custom'],
430
+ required: true,
431
+ },
432
+ apiKey: { type: ConfigType.STRING, envVar: 'LLM_API_KEY' },
433
+ model: { type: ConfigType.STRING, default: 'gpt-4o-mini' },
434
+ baseURL: { type: ConfigType.STRING },
435
+ maxTokens: { type: ConfigType.NUMBER, min: 1, max: 200000, default: 4096 },
436
+ },
437
+ },
438
+ },
439
+ session: {
440
+ type: ConfigType.OBJECT,
441
+ description: 'Session management settings',
442
+ default: {},
443
+ properties: {
444
+ maxSessions: { type: ConfigType.NUMBER, min: 1, max: 10000, default: 500 },
445
+ sessionTTL: { type: ConfigType.NUMBER, min: 0, default: 0 },
446
+ idleTimeout: { type: ConfigType.NUMBER, min: 0, default: 1800000 },
447
+ maxTranscriptLength: { type: ConfigType.NUMBER, min: 10, max: 1000, default: 100 },
448
+ },
449
+ },
450
+ security: {
451
+ type: ConfigType.OBJECT,
452
+ description: 'Security settings',
453
+ default: {},
454
+ properties: {
455
+ blockDangerousCommands: { type: ConfigType.BOOLEAN, default: true },
456
+ scanSecrets: { type: ConfigType.BOOLEAN, default: true },
457
+ maxFileSize: { type: ConfigType.NUMBER, min: 1024, default: 1048576 },
458
+ auditLogDir: { type: ConfigType.STRING, default: 'data/audit' },
459
+ },
460
+ },
461
+ cron: {
462
+ type: ConfigType.OBJECT,
463
+ description: 'Scheduler settings',
464
+ default: {},
465
+ properties: {
466
+ enabled: { type: ConfigType.BOOLEAN, default: false },
467
+ tickInterval: { type: ConfigType.NUMBER, min: 1000, default: 60000 },
468
+ },
469
+ },
470
+ hooks: {
471
+ type: ConfigType.OBJECT,
472
+ description: 'Hook system settings',
473
+ default: {},
474
+ properties: {
475
+ enabled: { type: ConfigType.BOOLEAN, default: true },
476
+ handlerTimeout: { type: ConfigType.NUMBER, min: 1000, max: 60000, default: 10000 },
477
+ },
478
+ },
479
+ },
480
+ };
481
+
482
+ // Global singleton with enterprise schema
483
+ export const configValidator = new ConfigValidator(enterpriseConfigSchema);
@@ -0,0 +1,22 @@
1
+
2
+ /**
3
+ * Internationalized time formatting utility
4
+ */
5
+
6
+ export function formatTimeI18n(time, t) {
7
+ if (!time) return '';
8
+ const d = new Date(time);
9
+ const now = new Date();
10
+ const diff = now - d;
11
+
12
+ if (diff < 60 * 1000) return t('time.justNow');
13
+ if (diff < 60 * 60 * 1000) return t('time.minutesAgo', { n: Math.floor(diff / 60000) });
14
+ if (diff < 24 * 60 * 60 * 1000) {
15
+ return d.toLocaleTimeString('en', { hour: '2-digit', minute: '2-digit' });
16
+ }
17
+ if (diff < 7 * 24 * 60 * 60 * 1000) {
18
+ const days = [t('time.sun'), t('time.mon'), t('time.tue'), t('time.wed'), t('time.thu'), t('time.fri'), t('time.sat')];
19
+ return days[d.getDay()];
20
+ }
21
+ return d.toLocaleDateString('en', { month: 'short', day: 'numeric' });
22
+ }