|
1 | 1 | // Class bootstrap belongs to `daemon.mjs`; this consumed class relies on global Neo. |
2 | | -import fs from 'fs-extra'; |
3 | | -import {spawn} from 'child_process'; |
4 | | -import net from 'net'; |
5 | | -import path from 'path'; |
6 | | -import Base from '../../../src/core/Base.mjs'; |
7 | | -import ClassSystemUtil from '../../../src/util/ClassSystem.mjs'; |
8 | | -import AiConfig from '../../config.mjs'; |
9 | | -import neuralLinkConfig from '../../mcp/server/neural-link/config.mjs'; |
10 | | -import { |
11 | | - buildLmsPreloadConfig, |
12 | | - buildOllamaReadinessConfig |
13 | | -} from '../../services/graph/providerReadinessHelper.mjs'; |
14 | | -import HealthService from '../../services/memory-core/HealthService.mjs'; |
15 | | -import SQLite from '../../graph/storage/SQLite.mjs'; |
| 2 | +import fs from 'fs-extra'; |
| 3 | +import {spawn} from 'child_process'; |
| 4 | +import net from 'net'; |
| 5 | +import path from 'path'; |
| 6 | +import Base from '../../../src/core/Base.mjs'; |
| 7 | +import ClassSystemUtil from '../../../src/util/ClassSystem.mjs'; |
| 8 | +import AiConfig from '../../config.mjs'; |
| 9 | +import HealthService from '../../services/memory-core/HealthService.mjs'; |
| 10 | +import SQLite from '../../graph/storage/SQLite.mjs'; |
16 | 11 | import MaintenanceBackpressureService, { |
17 | 12 | DEFAULT_HEAVY_MAINTENANCE_TASK_NAMES, |
18 | 13 | DEFAULT_GOLDEN_PATH_DEPENDENCY_TASK_NAMES |
19 | 14 | } from './services/MaintenanceBackpressureService.mjs'; |
20 | | -import PrimaryRepoSyncService from './services/PrimaryRepoSyncService.mjs'; |
21 | | -import TenantRepoSyncService from './services/TenantRepoSyncService.mjs'; |
22 | | -import {getDueTask as summaryGetDueTaskImport} from './scheduling/summary.mjs'; |
23 | | -import {getDueTask as backupGetDueTaskImport} from './scheduling/backup.mjs'; |
24 | | -import {getDueTask as graphLogCompactionGetDueTaskImport} from './scheduling/graphLogCompaction.mjs'; |
25 | | -import {getDueTask as primaryDevSyncGetDueTaskImport} from './scheduling/primaryDevSync.mjs'; |
26 | | -import {getDueTask as goldenPathGetDueTaskImport} from './scheduling/goldenPath.mjs'; |
27 | | -import {getDueTask as dreamGetDueTaskImport} from './scheduling/dream.mjs'; |
28 | | -import {getDueTask as embedDrainLivenessWatchdogGetDueTaskImport} from './scheduling/embedDrainLivenessWatchdog.mjs'; |
29 | | -import memoryCoreConfig from '../../mcp/server/memory-core/config.mjs'; |
30 | | -import MailboxService from '../../services/memory-core/MailboxService.mjs'; |
31 | | -import WakeSubscriptionService from '../../services/memory-core/WakeSubscriptionService.mjs'; |
32 | | -import RequestContextService from '../../mcp/server/shared/services/RequestContextService.mjs'; |
33 | | -import {normalizeAgentIdentityNodeId} from '../../scripts/lifecycle/resumeHarness.mjs'; |
34 | | -import TaskStateService from './services/TaskStateService.mjs'; |
35 | | -import ProcessSupervisorService from './services/ProcessSupervisorService.mjs'; |
36 | | -import DreamService from './services/DreamService.mjs'; |
37 | | -import SwarmHeartbeatService from './services/SwarmHeartbeatService.mjs'; |
38 | | -import GoldenPathSynthesizer from '../../services/graph/GoldenPathSynthesizer.mjs'; |
39 | | -import {getDueTask as tenantRepoSyncGetDueTaskImport} from './scheduling/tenantRepoSync.mjs'; |
40 | | -import {TASK_REGISTRY} from './scheduling/registry.mjs'; |
| 15 | +import {buildConfiguredTaskDefinitions as buildConfiguredTaskDefinitionsImport} from './services/ConfiguredTaskDefinitionsService.mjs'; |
| 16 | +import PrimaryRepoSyncService from './services/PrimaryRepoSyncService.mjs'; |
| 17 | +import TenantRepoSyncService from './services/TenantRepoSyncService.mjs'; |
| 18 | +import {getDueTask as summaryGetDueTaskImport} from './scheduling/summary.mjs'; |
| 19 | +import {getDueTask as backupGetDueTaskImport} from './scheduling/backup.mjs'; |
| 20 | +import {getDueTask as graphLogCompactionGetDueTaskImport} from './scheduling/graphLogCompaction.mjs'; |
| 21 | +import {getDueTask as primaryDevSyncGetDueTaskImport} from './scheduling/primaryDevSync.mjs'; |
| 22 | +import {getDueTask as goldenPathGetDueTaskImport} from './scheduling/goldenPath.mjs'; |
| 23 | +import {getDueTask as dreamGetDueTaskImport} from './scheduling/dream.mjs'; |
| 24 | +import {getDueTask as embedDrainLivenessWatchdogGetDueTaskImport} from './scheduling/embedDrainLivenessWatchdog.mjs'; |
| 25 | +import memoryCoreConfig from '../../mcp/server/memory-core/config.mjs'; |
| 26 | +import MailboxService from '../../services/memory-core/MailboxService.mjs'; |
| 27 | +import WakeSubscriptionService from '../../services/memory-core/WakeSubscriptionService.mjs'; |
| 28 | +import RequestContextService from '../../mcp/server/shared/services/RequestContextService.mjs'; |
| 29 | +import {normalizeAgentIdentityNodeId} from '../../scripts/lifecycle/resumeHarness.mjs'; |
| 30 | +import TaskStateService from './services/TaskStateService.mjs'; |
| 31 | +import ProcessSupervisorService from './services/ProcessSupervisorService.mjs'; |
| 32 | +import DreamService from './services/DreamService.mjs'; |
| 33 | +import SwarmHeartbeatService from './services/SwarmHeartbeatService.mjs'; |
| 34 | +import GoldenPathSynthesizer from '../../services/graph/GoldenPathSynthesizer.mjs'; |
| 35 | +import {getDueTask as tenantRepoSyncGetDueTaskImport} from './scheduling/tenantRepoSync.mjs'; |
| 36 | +import {TASK_REGISTRY} from './scheduling/registry.mjs'; |
41 | 37 | import { |
42 | 38 | buildOrchestratorSchedulingOptions, |
43 | 39 | runSchedulingPipeline |
44 | 40 | } from './scheduling/pipeline.mjs'; |
45 | 41 | import { |
46 | 42 | DEFAULT_DB_PATH, |
47 | 43 | DEFAULT_DATA_DIR, |
48 | | - DEFAULT_SCRIPT_DIR, |
49 | | - buildOllamaServeEnv, |
50 | | - getMaxOllamaContextLength, |
51 | | - resolveOllamaHostPort, |
52 | | - buildTaskDefinitions |
| 44 | + DEFAULT_SCRIPT_DIR |
53 | 45 | } from './taskDefinitions.mjs'; |
54 | 46 |
|
55 | 47 | /** @summary Opens/creates the orchestrator sqlite DB via the shared Memory Core schema bootstrap. */ |
@@ -135,6 +127,7 @@ export class Orchestrator extends Base { |
135 | 127 | dreamGetDueTask = dreamGetDueTaskImport |
136 | 128 | goldenPathGetDueTask = goldenPathGetDueTaskImport |
137 | 129 | embedDrainLivenessWatchdogGetDueTask = embedDrainLivenessWatchdogGetDueTaskImport |
| 130 | + buildConfiguredTaskDefinitionsService = buildConfiguredTaskDefinitionsImport |
138 | 131 |
|
139 | 132 | isPolling = false |
140 | 133 | pollHandle = null |
@@ -350,217 +343,18 @@ export class Orchestrator extends Base { |
350 | 343 | get neuralLinkBridgeLivenessTimeoutMs() { return AiConfig.orchestrator.neuralLinkBridge.livenessProbeTimeoutMs; } |
351 | 344 |
|
352 | 345 | /** |
353 | | - * @summary Builds the supervised task table from the daemon entrypoint's config authorities. |
| 346 | + * @summary Delegates config-backed task-table construction to the orchestrator task builder. |
354 | 347 | * @param {Object} options |
355 | 348 | * @param {String} options.scriptDir Script directory. |
356 | 349 | * @param {String} options.nodeBin Node executable. |
357 | 350 | * @returns {Object} |
358 | 351 | */ |
359 | 352 | buildConfiguredTaskDefinitions({scriptDir, nodeBin}) { |
360 | | - const tasks = buildTaskDefinitions({ |
| 353 | + return this.buildConfiguredTaskDefinitionsService({ |
361 | 354 | scriptDir, |
362 | 355 | nodeBin, |
363 | | - chromaPort : AiConfig.engines.chroma.port, |
364 | | - devServerPort : AiConfig.orchestrator.devServer.port, |
365 | | - devServerLivenessTimeoutMs : AiConfig.orchestrator.devServer.livenessProbeTimeoutMs, |
366 | | - neuralLinkBridgePort : neuralLinkConfig.port, |
367 | 356 | neuralLinkBridgeLivenessTimeoutMs: this.neuralLinkBridgeLivenessTimeoutMs |
368 | 357 | }); |
369 | | - |
370 | | - this.applyConfiguredGraphLogCompaction(tasks); |
371 | | - this.applyConfiguredMlxTask(tasks, {scriptDir}); |
372 | | - this.applyConfiguredLmsTask(tasks); |
373 | | - this.applyConfiguredOllamaTask(tasks); |
374 | | - |
375 | | - return tasks; |
376 | | - } |
377 | | - |
378 | | - /** |
379 | | - * @summary Applies the graph-log compaction flags owned by AiConfig. |
380 | | - * @param {Object} tasks Task table. |
381 | | - * @returns {void} |
382 | | - */ |
383 | | - applyConfiguredGraphLogCompaction(tasks) { |
384 | | - if (AiConfig.orchestrator.graphLogCompaction.vacuum) { |
385 | | - tasks['graphlog-compaction'].args.push('--vacuum'); |
386 | | - } |
387 | | - } |
388 | | - |
389 | | - /** |
390 | | - * @summary Adds the orchestrator-owned MLX server task when enabled. |
391 | | - * @param {Object} tasks Task table. |
392 | | - * @param {Object} options |
393 | | - * @param {String} options.scriptDir Script directory. |
394 | | - * @returns {void} |
395 | | - */ |
396 | | - applyConfiguredMlxTask(tasks, {scriptDir}) { |
397 | | - if (!AiConfig.orchestrator.mlx.enabled) { |
398 | | - return; |
399 | | - } |
400 | | - |
401 | | - tasks.mlx = { |
402 | | - label : 'mlx inference', |
403 | | - command: path.resolve(scriptDir, '../mcp/server/memory-core/.venv/bin/python'), |
404 | | - args : [ |
405 | | - '-m', |
406 | | - 'mlx_lm.server', |
407 | | - '--model', |
408 | | - AiConfig.orchestrator.mlx.model, |
409 | | - '--port', |
410 | | - String(AiConfig.orchestrator.mlx.port) |
411 | | - ], |
412 | | - pidFileName : 'mlx.pid', |
413 | | - expectedCommand: 'mlx_lm.server' |
414 | | - }; |
415 | | - } |
416 | | - |
417 | | - /** |
418 | | - * @summary Adds the orchestrator-owned LM Studio server task when enabled. |
419 | | - * @param {Object} tasks Task table. |
420 | | - * @returns {void} |
421 | | - */ |
422 | | - applyConfiguredLmsTask(tasks) { |
423 | | - if (!AiConfig.orchestrator.lms.enabled) { |
424 | | - return; |
425 | | - } |
426 | | - |
427 | | - const preloadConfig = buildLmsPreloadConfig(AiConfig), |
428 | | - requiredModels = Array.isArray(preloadConfig.models) |
429 | | - ? [...new Set(preloadConfig.models.filter(Boolean))] |
430 | | - : [AiConfig.orchestrator.lms.model].filter(Boolean); |
431 | | - |
432 | | - tasks.lms = { |
433 | | - label : 'lms server (LM Studio CLI)', |
434 | | - command : 'lms', |
435 | | - args : ['server', 'start', '--port', String(AiConfig.orchestrator.lms.port)], |
436 | | - pidFileName : 'lms.pid', |
437 | | - expectedCommand: 'lms server', |
438 | | - requiredModels, |
439 | | - // `lms server start` is fire-and-exit: it wakes the LM Studio service and returns, so |
440 | | - // the launched server never matches `expectedCommand` for the supervisor's |
441 | | - // process-liveness check (`!state.running` stays permanently true). Liveness is the |
442 | | - // HTTP endpoint instead — the supervisor gates the restart on this probe so a healthy |
443 | | - // server is a silent no-op rather than a re-spawn loop. |
444 | | - livenessProbe : async () => { |
445 | | - const {fetchOpenAiCompatibleModelIds} = await import('../../services/graph/providerReadinessHelper.mjs'); |
446 | | - |
447 | | - try { |
448 | | - await fetchOpenAiCompatibleModelIds({ |
449 | | - host : AiConfig.openAiCompatible.host, |
450 | | - timeoutMs: AiConfig.orchestrator.providerReadiness.timeoutMs ?? 2000 |
451 | | - }); |
452 | | - return true; |
453 | | - } catch { |
454 | | - return false; |
455 | | - } |
456 | | - }, |
457 | | - postSpawn : async () => { |
458 | | - const {ensureLmsModelsLoaded} = await import('../../services/graph/providerReadinessHelper.mjs'); |
459 | | - |
460 | | - if (requiredModels.length === 0) { |
461 | | - return { |
462 | | - ready : true, |
463 | | - loadedModels : [], |
464 | | - requiredModels, |
465 | | - availableModels: [], |
466 | | - attempts : 0, |
467 | | - skipped : true, |
468 | | - reason : 'no-openai-compatible-local-roles' |
469 | | - }; |
470 | | - } |
471 | | - |
472 | | - return ensureLmsModelsLoaded({ |
473 | | - host : AiConfig.openAiCompatible.host, |
474 | | - models : requiredModels, |
475 | | - contextLengths: preloadConfig.contextLengths, |
476 | | - parallels : preloadConfig.parallels, |
477 | | - allowPartial : true, |
478 | | - attempts : AiConfig.orchestrator.providerReadiness.attempts, |
479 | | - delayMs : AiConfig.orchestrator.providerReadiness.delayMs, |
480 | | - timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs |
481 | | - }); |
482 | | - } |
483 | | - }; |
484 | | - } |
485 | | - |
486 | | - /** |
487 | | - * @summary Adds the orchestrator-owned native Ollama server task when enabled. |
488 | | - * @param {Object} tasks Task table. |
489 | | - * @returns {void} |
490 | | - */ |
491 | | - applyConfiguredOllamaTask(tasks) { |
492 | | - if (!AiConfig.orchestrator.ollama.enabled) { |
493 | | - return; |
494 | | - } |
495 | | - |
496 | | - const readinessConfig = buildOllamaReadinessConfig(AiConfig), |
497 | | - roles = Array.isArray(readinessConfig.roles) |
498 | | - ? readinessConfig.roles.filter(role => role.model) |
499 | | - : [], |
500 | | - requiredModels = [...new Set(roles.map(role => role.model))]; |
501 | | - |
502 | | - if (requiredModels.length === 0) { |
503 | | - return; |
504 | | - } |
505 | | - |
506 | | - tasks.ollama = { |
507 | | - label : 'ollama server', |
508 | | - command : 'ollama', |
509 | | - args : ['serve'], |
510 | | - pidFileName : 'ollama.pid', |
511 | | - expectedCommand : 'ollama serve', |
512 | | - requiredModels, |
513 | | - singletonPort : resolveOllamaHostPort(readinessConfig.host), |
514 | | - duplicateListenerPolicy: 'defer', |
515 | | - env : buildOllamaServeEnv({ |
516 | | - host : readinessConfig.host, |
517 | | - keepAlive : readinessConfig.keepAlive, |
518 | | - contextLength : getMaxOllamaContextLength(roles), |
519 | | - requireParallelModels: readinessConfig.requireParallelModels |
520 | | - }), |
521 | | - livenessProbe : async () => { |
522 | | - const {ensureOllamaModelsReady} = await import('../../services/graph/providerReadinessHelper.mjs'); |
523 | | - |
524 | | - try { |
525 | | - const result = await ensureOllamaModelsReady({ |
526 | | - host : readinessConfig.host, |
527 | | - roles, |
528 | | - keepAlive : readinessConfig.keepAlive, |
529 | | - requireParallelModels: readinessConfig.requireParallelModels, |
530 | | - allowPartial : true, |
531 | | - attempts : AiConfig.orchestrator.providerReadiness.attempts, |
532 | | - delayMs : AiConfig.orchestrator.providerReadiness.delayMs, |
533 | | - timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs |
534 | | - }); |
535 | | - |
536 | | - if ( |
537 | | - result.error && |
538 | | - result.availableModels?.length === 0 && |
539 | | - result.attemptedModels?.length === 0 |
540 | | - ) { |
541 | | - return false; |
542 | | - } |
543 | | - |
544 | | - return true; |
545 | | - } catch { |
546 | | - return false; |
547 | | - } |
548 | | - }, |
549 | | - postSpawn : async () => { |
550 | | - const {ensureOllamaModelsReady} = await import('../../services/graph/providerReadinessHelper.mjs'); |
551 | | - |
552 | | - return ensureOllamaModelsReady({ |
553 | | - host : readinessConfig.host, |
554 | | - roles, |
555 | | - keepAlive : readinessConfig.keepAlive, |
556 | | - requireParallelModels: readinessConfig.requireParallelModels, |
557 | | - allowPartial : true, |
558 | | - attempts : AiConfig.orchestrator.providerReadiness.attempts, |
559 | | - delayMs : AiConfig.orchestrator.providerReadiness.delayMs, |
560 | | - timeoutMs : AiConfig.orchestrator.providerReadiness.timeoutMs |
561 | | - }); |
562 | | - } |
563 | | - }; |
564 | 358 | } |
565 | 359 |
|
566 | 360 | /** @summary Starts the orchestrator timer loop after the wrapper selects this process. */ |
|
0 commit comments