principles-disciple 1.11.0 → 1.13.0
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/CHANGELOG.md +26 -0
- package/README.md +66 -0
- package/esbuild.config.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -3
- package/run-nocturnal.mjs +30 -0
- package/scripts/db-migrate.mjs +170 -0
- package/scripts/sync-plugin.mjs +250 -6
- package/src/commands/archive-impl.ts +136 -0
- package/src/commands/capabilities.ts +4 -2
- package/src/commands/context.ts +5 -1
- package/src/commands/disable-impl.ts +151 -0
- package/src/commands/evolution-status.ts +64 -19
- package/src/commands/export.ts +8 -6
- package/src/commands/focus.ts +8 -20
- package/src/commands/nocturnal-review.ts +5 -7
- package/src/commands/nocturnal-rollout.ts +1 -12
- package/src/commands/nocturnal-train.ts +21 -47
- package/src/commands/pain.ts +10 -5
- package/src/commands/principle-rollback.ts +4 -2
- package/src/commands/promote-impl.ts +274 -0
- package/src/commands/rollback-impl.ts +234 -0
- package/src/commands/rollback.ts +6 -3
- package/src/commands/samples.ts +2 -0
- package/src/commands/thinking-os.ts +3 -4
- package/src/commands/workflow-debug.ts +2 -1
- package/src/config/errors.ts +1 -0
- package/src/core/AGENTS.md +34 -0
- package/src/core/adaptive-thresholds.ts +4 -3
- package/src/core/code-implementation-storage.ts +241 -0
- package/src/core/config.ts +5 -2
- package/src/core/control-ui-db.ts +29 -10
- package/src/core/detection-funnel.ts +12 -7
- package/src/core/diagnostician-task-store.ts +156 -0
- package/src/core/dictionary.ts +4 -4
- package/src/core/empathy-keyword-matcher.ts +7 -3
- package/src/core/empathy-types.ts +13 -2
- package/src/core/event-log.ts +14 -6
- package/src/core/evolution-engine.ts +27 -31
- package/src/core/evolution-logger.ts +3 -2
- package/src/core/evolution-reducer.ts +110 -31
- package/src/core/evolution-types.ts +10 -0
- package/src/core/external-training-contract.ts +1 -0
- package/src/core/focus-history.ts +38 -24
- package/src/core/hygiene/tracker.ts +10 -6
- package/src/core/init.ts +5 -2
- package/src/core/migration.ts +3 -3
- package/src/core/model-deployment-registry.ts +6 -4
- package/src/core/model-training-registry.ts +5 -3
- package/src/core/nocturnal-arbiter.ts +13 -14
- package/src/core/nocturnal-artifact-lineage.ts +117 -0
- package/src/core/nocturnal-artificer.ts +257 -0
- package/src/core/nocturnal-candidate-scoring.ts +4 -2
- package/src/core/nocturnal-compliance.ts +67 -19
- package/src/core/nocturnal-dataset.ts +95 -2
- package/src/core/nocturnal-executability.ts +2 -3
- package/src/core/nocturnal-export.ts +6 -3
- package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
- package/src/core/nocturnal-trajectory-extractor.ts +10 -3
- package/src/core/nocturnal-trinity.ts +319 -61
- package/src/core/pain-context-extractor.ts +29 -15
- package/src/core/pain.ts +7 -5
- package/src/core/path-resolver.ts +16 -15
- package/src/core/paths.ts +2 -1
- package/src/core/pd-task-reconciler.ts +463 -0
- package/src/core/pd-task-service.ts +42 -0
- package/src/core/pd-task-store.ts +77 -0
- package/src/core/pd-task-types.ts +128 -0
- package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
- package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
- package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
- package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
- package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
- package/src/core/principle-training-state.ts +95 -370
- package/src/core/principle-tree-ledger.ts +733 -0
- package/src/core/principle-tree-migration.ts +195 -0
- package/src/core/profile.ts +3 -1
- package/src/core/promotion-gate.ts +14 -18
- package/src/core/replay-engine.ts +562 -0
- package/src/core/risk-calculator.ts +6 -4
- package/src/core/rule-host-helpers.ts +39 -0
- package/src/core/rule-host-types.ts +82 -0
- package/src/core/rule-host.ts +245 -0
- package/src/core/rule-implementation-runtime.ts +38 -0
- package/src/core/schema/db-types.ts +16 -0
- package/src/core/schema/index.ts +26 -0
- package/src/core/schema/migration-runner.ts +207 -0
- package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
- package/src/core/schema/migrations/002-init-central.ts +122 -0
- package/src/core/schema/migrations/003-init-workflow.ts +55 -0
- package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
- package/src/core/schema/migrations/index.ts +31 -0
- package/src/core/schema/schema-definitions.ts +650 -0
- package/src/core/session-tracker.ts +6 -4
- package/src/core/shadow-observation-registry.ts +6 -3
- package/src/core/system-logger.ts +2 -2
- package/src/core/thinking-models.ts +182 -46
- package/src/core/thinking-os-parser.ts +156 -0
- package/src/core/training-program.ts +7 -7
- package/src/core/trajectory.ts +42 -36
- package/src/core/workspace-context.ts +77 -11
- package/src/core/workspace-dir-validation.ts +152 -0
- package/src/hooks/AGENTS.md +31 -0
- package/src/hooks/bash-risk.ts +3 -1
- package/src/hooks/edit-verification.ts +9 -5
- package/src/hooks/gate-block-helper.ts +5 -1
- package/src/hooks/gate.ts +152 -5
- package/src/hooks/gfi-gate.ts +9 -2
- package/src/hooks/lifecycle-routing.ts +124 -0
- package/src/hooks/lifecycle.ts +12 -12
- package/src/hooks/llm.ts +17 -109
- package/src/hooks/message-sanitize.ts +5 -3
- package/src/hooks/pain.ts +19 -15
- package/src/hooks/progressive-trust-gate.ts +7 -1
- package/src/hooks/prompt.ts +169 -60
- package/src/hooks/subagent.ts +5 -4
- package/src/hooks/thinking-checkpoint.ts +2 -0
- package/src/hooks/trajectory-collector.ts +15 -12
- package/src/http/principles-console-route.ts +31 -68
- package/src/i18n/commands.ts +2 -2
- package/src/index.ts +130 -40
- package/src/service/central-database.ts +131 -43
- package/src/service/central-health-service.ts +47 -0
- package/src/service/central-overview-service.ts +135 -0
- package/src/service/central-sync-service.ts +87 -0
- package/src/service/control-ui-query-service.ts +46 -36
- package/src/service/event-log-auditor.ts +261 -0
- package/src/service/evolution-query-service.ts +23 -22
- package/src/service/evolution-worker.ts +565 -261
- package/src/service/health-query-service.ts +213 -36
- package/src/service/nocturnal-runtime.ts +8 -4
- package/src/service/nocturnal-service.ts +503 -59
- package/src/service/nocturnal-target-selector.ts +5 -7
- package/src/service/runtime-summary-service.ts +2 -1
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
- package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
- package/src/service/subagent-workflow/index.ts +2 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
- package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
- package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
- package/src/service/subagent-workflow/types.ts +9 -4
- package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
- package/src/service/subagent-workflow/workflow-store.ts +71 -11
- package/src/service/trajectory-service.ts +2 -1
- package/src/tools/critique-prompt.ts +1 -1
- package/src/tools/deep-reflect.ts +175 -209
- package/src/tools/model-index.ts +2 -1
- package/src/types/event-types.ts +2 -2
- package/src/types/principle-tree-schema.ts +29 -23
- package/src/utils/file-lock.ts +5 -3
- package/src/utils/io.ts +5 -2
- package/src/utils/nlp.ts +5 -46
- package/src/utils/node-vm-polyfill.ts +11 -0
- package/src/utils/plugin-logger.ts +2 -0
- package/src/utils/retry.ts +572 -0
- package/src/utils/subagent-probe.ts +1 -1
- package/templates/langs/en/core/AGENTS.md +0 -13
- package/templates/langs/en/core/SOUL.md +1 -31
- package/templates/langs/en/core/TOOLS.md +0 -4
- package/templates/langs/en/principles/THINKING_OS.md +77 -0
- package/templates/langs/en/skills/admin/SKILL.md +0 -1
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
- package/templates/langs/zh/core/AGENTS.md +0 -22
- package/templates/langs/zh/core/SOUL.md +1 -31
- package/templates/langs/zh/core/TOOLS.md +0 -4
- package/templates/langs/zh/principles/THINKING_OS.md +77 -0
- package/templates/langs/zh/skills/admin/SKILL.md +0 -1
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
- package/tests/commands/evolution-status.test.ts +119 -0
- package/tests/commands/implementation-lifecycle.test.ts +362 -0
- package/tests/core/code-implementation-storage.test.ts +398 -0
- package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
- package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
- package/tests/core/nocturnal-artificer.test.ts +241 -0
- package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
- package/tests/core/pd-task-store.test.ts +126 -0
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
- package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
- package/tests/core/principle-training-state.test.ts +228 -1
- package/tests/core/principle-tree-ledger.test.ts +423 -0
- package/tests/core/regression-v1-9-1.test.ts +265 -0
- package/tests/core/replay-engine.test.ts +234 -0
- package/tests/core/rule-host-helpers.test.ts +120 -0
- package/tests/core/rule-host.test.ts +389 -0
- package/tests/core/rule-implementation-runtime.test.ts +64 -0
- package/tests/core/workspace-context.test.ts +53 -0
- package/tests/core/workspace-dir-validation.test.ts +272 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
- package/tests/hooks/pain.test.ts +74 -10
- package/tests/hooks/prompt.test.ts +63 -1
- package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
- package/tests/service/data-endpoints-regression.test.ts +834 -0
- package/tests/service/evolution-worker.test.ts +0 -123
- package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
- package/tests/utils/nlp.test.ts +1 -19
- package/tests/utils/retry.test.ts +327 -0
- package/ui/src/App.tsx +1 -1
- package/ui/src/api.ts +4 -0
- package/ui/src/charts.tsx +366 -0
- package/ui/src/components/WorkspaceConfig.tsx +107 -75
- package/ui/src/i18n/ui.ts +89 -31
- package/ui/src/pages/EvolutionPage.tsx +1 -1
- package/ui/src/pages/OverviewPage.tsx +441 -81
- package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
- package/ui/src/styles.css +43 -0
- package/ui/src/types.ts +17 -1
- package/src/agents/nocturnal-dreamer.md +0 -152
- package/src/agents/nocturnal-philosopher.md +0 -138
- package/src/agents/nocturnal-reflector.md +0 -126
- package/src/agents/nocturnal-scribe.md +0 -164
- package/templates/workspace/.principles/00-kernel.md +0 -51
- package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
- package/templates/workspace/.principles/PRINCIPLES.md +0 -20
- package/templates/workspace/.principles/PROFILE.json +0 -54
- package/templates/workspace/.principles/PROFILE.schema.json +0 -56
- package/templates/workspace/.principles/THINKING_OS.md +0 -64
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
- package/templates/workspace/.principles/models/_INDEX.md +0 -27
- package/templates/workspace/.principles/models/first_principles.md +0 -62
- package/templates/workspace/.principles/models/marketing_4p.md +0 -52
- package/templates/workspace/.principles/models/porter_five.md +0 -63
- package/templates/workspace/.principles/models/swot.md +0 -60
- package/templates/workspace/.principles/models/user_story_map.md +0 -63
- package/templates/workspace/.state/WORKBOARD.json +0 -4
|
@@ -21,6 +21,12 @@ export class CentralDatabase {
|
|
|
21
21
|
private readonly dbPath: string;
|
|
22
22
|
private readonly db: Database.Database;
|
|
23
23
|
private readonly workspaces: WorkspaceInfo[] = [];
|
|
24
|
+
private _closed = false;
|
|
25
|
+
|
|
26
|
+
/** Whether this connection has been closed. Used by the singleton to auto-reopen. */
|
|
27
|
+
get isClosed(): boolean {
|
|
28
|
+
return this._closed;
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
constructor() {
|
|
26
32
|
const openClawDir = os.homedir();
|
|
@@ -39,9 +45,10 @@ export class CentralDatabase {
|
|
|
39
45
|
|
|
40
46
|
dispose(): void {
|
|
41
47
|
this.db.close();
|
|
48
|
+
this._closed = true;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
private tableExists(db: Database.Database, tableName: string): boolean {
|
|
51
|
+
private static tableExists(db: Database.Database, tableName: string): boolean {
|
|
45
52
|
const result = db.prepare(`
|
|
46
53
|
SELECT name FROM sqlite_master WHERE type='table' AND name=?
|
|
47
54
|
`).get(tableName);
|
|
@@ -211,7 +218,7 @@ export class CentralDatabase {
|
|
|
211
218
|
// Sync sessions
|
|
212
219
|
const sessions = sourceDb.prepare(`
|
|
213
220
|
SELECT session_id, started_at, updated_at FROM sessions
|
|
214
|
-
`).all() as
|
|
221
|
+
`).all() as {session_id: string; started_at: string; updated_at: string}[];
|
|
215
222
|
|
|
216
223
|
const insertSession = this.db.prepare(`
|
|
217
224
|
INSERT OR REPLACE INTO aggregated_sessions (session_id, workspace, started_at, updated_at)
|
|
@@ -227,11 +234,11 @@ export class CentralDatabase {
|
|
|
227
234
|
const toolCalls = sourceDb.prepare(`
|
|
228
235
|
SELECT session_id, tool_name, outcome, duration_ms, error_type, error_message, created_at
|
|
229
236
|
FROM tool_calls
|
|
230
|
-
`).all() as
|
|
237
|
+
`).all() as {
|
|
231
238
|
session_id: string; tool_name: string; outcome: string;
|
|
232
239
|
duration_ms: number | null; error_type: string | null;
|
|
233
240
|
error_message: string | null; created_at: string
|
|
234
|
-
}
|
|
241
|
+
}[];
|
|
235
242
|
|
|
236
243
|
const insertTool = this.db.prepare(`
|
|
237
244
|
INSERT INTO aggregated_tool_calls
|
|
@@ -250,10 +257,10 @@ export class CentralDatabase {
|
|
|
250
257
|
// Sync pain_events
|
|
251
258
|
const painEvents = sourceDb.prepare(`
|
|
252
259
|
SELECT session_id, source, score, reason, created_at FROM pain_events
|
|
253
|
-
`).all() as
|
|
260
|
+
`).all() as {
|
|
254
261
|
session_id: string; source: string; score: number;
|
|
255
262
|
reason: string | null; created_at: string
|
|
256
|
-
}
|
|
263
|
+
}[];
|
|
257
264
|
|
|
258
265
|
const insertPain = this.db.prepare(`
|
|
259
266
|
INSERT INTO aggregated_pain_events (workspace, session_id, source, score, reason, created_at)
|
|
@@ -269,9 +276,9 @@ export class CentralDatabase {
|
|
|
269
276
|
const corrections = sourceDb.prepare(`
|
|
270
277
|
SELECT session_id, correction_cue, created_at FROM user_turns
|
|
271
278
|
WHERE correction_detected = 1
|
|
272
|
-
`).all() as
|
|
279
|
+
`).all() as {
|
|
273
280
|
session_id: string; correction_cue: string | null; created_at: string
|
|
274
|
-
}
|
|
281
|
+
}[];
|
|
275
282
|
|
|
276
283
|
const insertCorr = this.db.prepare(`
|
|
277
284
|
INSERT INTO aggregated_user_corrections (workspace, session_id, correction_cue, created_at)
|
|
@@ -286,9 +293,9 @@ export class CentralDatabase {
|
|
|
286
293
|
// Sync principle_events
|
|
287
294
|
const principles = sourceDb.prepare(`
|
|
288
295
|
SELECT principle_id, event_type, created_at FROM principle_events
|
|
289
|
-
`).all() as
|
|
296
|
+
`).all() as {
|
|
290
297
|
principle_id: string | null; event_type: string; created_at: string
|
|
291
|
-
}
|
|
298
|
+
}[];
|
|
292
299
|
|
|
293
300
|
const insertPrinciple = this.db.prepare(`
|
|
294
301
|
INSERT INTO aggregated_principle_events (workspace, principle_id, event_type, created_at)
|
|
@@ -301,12 +308,12 @@ export class CentralDatabase {
|
|
|
301
308
|
}
|
|
302
309
|
|
|
303
310
|
// Sync thinking_model_events (may not exist in older workspaces)
|
|
304
|
-
if (
|
|
311
|
+
if (CentralDatabase.tableExists(sourceDb, 'thinking_model_events')) {
|
|
305
312
|
const thinking = sourceDb.prepare(`
|
|
306
313
|
SELECT session_id, model_id, matched_pattern, created_at FROM thinking_model_events
|
|
307
|
-
`).all() as
|
|
314
|
+
`).all() as {
|
|
308
315
|
session_id: string; model_id: string; matched_pattern: string; created_at: string
|
|
309
|
-
}
|
|
316
|
+
}[];
|
|
310
317
|
|
|
311
318
|
const insertThinking = this.db.prepare(`
|
|
312
319
|
INSERT INTO aggregated_thinking_events (workspace, session_id, model_id, matched_pattern, created_at)
|
|
@@ -323,10 +330,10 @@ export class CentralDatabase {
|
|
|
323
330
|
const samples = sourceDb.prepare(`
|
|
324
331
|
SELECT sample_id, session_id, bad_assistant_turn_id, quality_score, review_status, created_at
|
|
325
332
|
FROM correction_samples
|
|
326
|
-
`).all() as
|
|
333
|
+
`).all() as {
|
|
327
334
|
sample_id: string; session_id: string; bad_assistant_turn_id: number;
|
|
328
335
|
quality_score: number | null; review_status: string | null; created_at: string
|
|
329
|
-
}
|
|
336
|
+
}[];
|
|
330
337
|
|
|
331
338
|
const insertSample = this.db.prepare(`
|
|
332
339
|
INSERT OR REPLACE INTO aggregated_correction_samples
|
|
@@ -345,9 +352,9 @@ export class CentralDatabase {
|
|
|
345
352
|
// Sync task_outcomes
|
|
346
353
|
const outcomes = sourceDb.prepare(`
|
|
347
354
|
SELECT session_id, task_id, outcome, created_at FROM task_outcomes
|
|
348
|
-
`).all() as
|
|
355
|
+
`).all() as {
|
|
349
356
|
session_id: string; task_id: string | null; outcome: string; created_at: string
|
|
350
|
-
}
|
|
357
|
+
}[];
|
|
351
358
|
|
|
352
359
|
const insertOutcome = this.db.prepare(`
|
|
353
360
|
INSERT INTO aggregated_task_outcomes (workspace, session_id, task_id, outcome, created_at)
|
|
@@ -475,7 +482,7 @@ export class CentralDatabase {
|
|
|
475
482
|
|
|
476
483
|
const workspaces = this.db.prepare(`
|
|
477
484
|
SELECT name FROM workspaces ORDER BY name
|
|
478
|
-
`).all() as
|
|
485
|
+
`).all() as { name: string }[];
|
|
479
486
|
|
|
480
487
|
const enabledConfigs = this.getWorkspaceConfigs().filter(c => c.enabled && c.syncEnabled);
|
|
481
488
|
const enabledWorkspaceNames = enabledConfigs.map(c => c.workspaceName);
|
|
@@ -501,16 +508,16 @@ export class CentralDatabase {
|
|
|
501
508
|
/**
|
|
502
509
|
* Get daily trend data
|
|
503
510
|
*/
|
|
504
|
-
getDailyTrend(days
|
|
511
|
+
getDailyTrend(days = 7): {
|
|
505
512
|
day: string;
|
|
506
513
|
toolCalls: number;
|
|
507
514
|
failures: number;
|
|
508
515
|
userCorrections: number;
|
|
509
516
|
thinkingTurns: number;
|
|
510
|
-
}
|
|
517
|
+
}[] {
|
|
511
518
|
const cutoffDate = new Date();
|
|
512
519
|
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
513
|
-
const cutoffStr = cutoffDate.toISOString().split('T')
|
|
520
|
+
const [cutoffStr] = cutoffDate.toISOString().split('T');
|
|
514
521
|
|
|
515
522
|
const toolDaily = this.db.prepare(`
|
|
516
523
|
SELECT
|
|
@@ -521,9 +528,9 @@ export class CentralDatabase {
|
|
|
521
528
|
WHERE substr(created_at, 1, 10) >= ?
|
|
522
529
|
GROUP BY substr(created_at, 1, 10)
|
|
523
530
|
ORDER BY day
|
|
524
|
-
`).all(cutoffStr) as
|
|
531
|
+
`).all(cutoffStr) as {
|
|
525
532
|
day: string; tool_calls: number; failures: number
|
|
526
|
-
}
|
|
533
|
+
}[];
|
|
527
534
|
|
|
528
535
|
const correctionsDaily = this.db.prepare(`
|
|
529
536
|
SELECT
|
|
@@ -532,9 +539,9 @@ export class CentralDatabase {
|
|
|
532
539
|
FROM aggregated_user_corrections
|
|
533
540
|
WHERE substr(created_at, 1, 10) >= ?
|
|
534
541
|
GROUP BY substr(created_at, 1, 10)
|
|
535
|
-
`).all(cutoffStr) as
|
|
542
|
+
`).all(cutoffStr) as {
|
|
536
543
|
day: string; corrections: number
|
|
537
|
-
}
|
|
544
|
+
}[];
|
|
538
545
|
|
|
539
546
|
const thinkingDaily = this.db.prepare(`
|
|
540
547
|
SELECT
|
|
@@ -543,9 +550,9 @@ export class CentralDatabase {
|
|
|
543
550
|
FROM aggregated_thinking_events
|
|
544
551
|
WHERE substr(created_at, 1, 10) >= ?
|
|
545
552
|
GROUP BY substr(created_at, 1, 10)
|
|
546
|
-
`).all(cutoffStr) as
|
|
553
|
+
`).all(cutoffStr) as {
|
|
547
554
|
day: string; thinking_turns: number
|
|
548
|
-
}
|
|
555
|
+
}[];
|
|
549
556
|
|
|
550
557
|
// Merge all trends
|
|
551
558
|
const dayMap = new Map<string, {
|
|
@@ -602,11 +609,11 @@ export class CentralDatabase {
|
|
|
602
609
|
/**
|
|
603
610
|
* Get top regressions
|
|
604
611
|
*/
|
|
605
|
-
getTopRegressions(limit
|
|
612
|
+
getTopRegressions(limit = 5): {
|
|
606
613
|
toolName: string;
|
|
607
614
|
errorType: string;
|
|
608
615
|
occurrences: number;
|
|
609
|
-
}
|
|
616
|
+
}[] {
|
|
610
617
|
return this.db.prepare(`
|
|
611
618
|
SELECT
|
|
612
619
|
tool_name as toolName,
|
|
@@ -617,11 +624,11 @@ export class CentralDatabase {
|
|
|
617
624
|
GROUP BY tool_name, error_type
|
|
618
625
|
ORDER BY occurrences DESC
|
|
619
626
|
LIMIT ?
|
|
620
|
-
`).all(limit) as
|
|
627
|
+
`).all(limit) as {
|
|
621
628
|
toolName: string;
|
|
622
629
|
errorType: string;
|
|
623
630
|
occurrences: number;
|
|
624
|
-
}
|
|
631
|
+
}[];
|
|
625
632
|
}
|
|
626
633
|
|
|
627
634
|
/**
|
|
@@ -630,11 +637,11 @@ export class CentralDatabase {
|
|
|
630
637
|
getThinkingModelStats(): {
|
|
631
638
|
totalModels: number;
|
|
632
639
|
activeModels: number;
|
|
633
|
-
models:
|
|
640
|
+
models: {
|
|
634
641
|
modelId: string;
|
|
635
642
|
hits: number;
|
|
636
643
|
coverageRate: number;
|
|
637
|
-
}
|
|
644
|
+
}[];
|
|
638
645
|
} {
|
|
639
646
|
const totalModels = this.db.prepare(`
|
|
640
647
|
SELECT COUNT(DISTINCT model_id) as count FROM aggregated_thinking_events
|
|
@@ -661,11 +668,7 @@ export class CentralDatabase {
|
|
|
661
668
|
FROM aggregated_thinking_events
|
|
662
669
|
GROUP BY model_id
|
|
663
670
|
ORDER BY hits DESC
|
|
664
|
-
`).all() as
|
|
665
|
-
|
|
666
|
-
const coverageRate = totalToolCalls.count > 0
|
|
667
|
-
? models.reduce((sum, m) => sum + m.hits, 0) / totalToolCalls.count
|
|
668
|
-
: 0;
|
|
671
|
+
`).all() as { modelId: string; hits: number }[];
|
|
669
672
|
|
|
670
673
|
return {
|
|
671
674
|
totalModels: totalModels.count,
|
|
@@ -686,22 +689,22 @@ export class CentralDatabase {
|
|
|
686
689
|
`).all() as WorkspaceInfo[];
|
|
687
690
|
}
|
|
688
691
|
|
|
689
|
-
getWorkspaceConfigs():
|
|
692
|
+
getWorkspaceConfigs(): {
|
|
690
693
|
workspaceName: string;
|
|
691
694
|
enabled: boolean;
|
|
692
695
|
displayName: string | null;
|
|
693
696
|
syncEnabled: boolean;
|
|
694
|
-
}
|
|
697
|
+
}[] {
|
|
695
698
|
const configs = this.db.prepare(`
|
|
696
699
|
SELECT workspace_name, enabled, display_name, sync_enabled
|
|
697
700
|
FROM workspace_config
|
|
698
701
|
ORDER BY workspace_name
|
|
699
|
-
`).all() as
|
|
702
|
+
`).all() as {
|
|
700
703
|
workspace_name: string;
|
|
701
704
|
enabled: number;
|
|
702
705
|
display_name: string | null;
|
|
703
706
|
sync_enabled: number;
|
|
704
|
-
}
|
|
707
|
+
}[];
|
|
705
708
|
|
|
706
709
|
return configs.map(c => ({
|
|
707
710
|
workspaceName: c.workspace_name,
|
|
@@ -818,13 +821,98 @@ export class CentralDatabase {
|
|
|
818
821
|
DELETE FROM sync_log;
|
|
819
822
|
`);
|
|
820
823
|
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Get total task outcomes count across enabled workspaces (D-02)
|
|
827
|
+
*/
|
|
828
|
+
getTaskOutcomes(): number {
|
|
829
|
+
const filter = this.getEnabledWorkspaceFilter();
|
|
830
|
+
const row = this.db.prepare(`
|
|
831
|
+
SELECT COUNT(*) as count FROM aggregated_task_outcomes
|
|
832
|
+
WHERE workspace IN (${filter})
|
|
833
|
+
`).get() as { count: number } | undefined;
|
|
834
|
+
return row?.count ?? 0;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Get total principle events count across enabled workspaces (D-03)
|
|
839
|
+
*/
|
|
840
|
+
getPrincipleEventCount(): number {
|
|
841
|
+
const filter = this.getEnabledWorkspaceFilter();
|
|
842
|
+
const row = this.db.prepare(`
|
|
843
|
+
SELECT COUNT(*) as count FROM aggregated_principle_events
|
|
844
|
+
WHERE workspace IN (${filter})
|
|
845
|
+
`).get() as { count: number } | undefined;
|
|
846
|
+
return row?.count ?? 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Get sample counts grouped by review_status across enabled workspaces (D-06)
|
|
851
|
+
*/
|
|
852
|
+
getSampleCountersByStatus(): Record<string, number> {
|
|
853
|
+
const filter = this.getEnabledWorkspaceFilter();
|
|
854
|
+
const rows = this.db.prepare(`
|
|
855
|
+
SELECT review_status, COUNT(*) as count
|
|
856
|
+
FROM aggregated_correction_samples
|
|
857
|
+
WHERE workspace IN (${filter})
|
|
858
|
+
GROUP BY review_status
|
|
859
|
+
`).all() as { review_status: string; count: number }[];
|
|
860
|
+
return Object.fromEntries(rows.map(r => [r.review_status, r.count]));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* Get top N most recent pending/approved samples across all enabled workspaces (D-04)
|
|
865
|
+
*/
|
|
866
|
+
getSamplePreview(limit = 5): {
|
|
867
|
+
sampleId: string;
|
|
868
|
+
sessionId: string;
|
|
869
|
+
workspace: string;
|
|
870
|
+
qualityScore: number;
|
|
871
|
+
reviewStatus: string;
|
|
872
|
+
createdAt: string;
|
|
873
|
+
}[] {
|
|
874
|
+
const filter = this.getEnabledWorkspaceFilter();
|
|
875
|
+
const rows = this.db.prepare(`
|
|
876
|
+
SELECT sample_id, session_id, workspace, quality_score, review_status, created_at
|
|
877
|
+
FROM aggregated_correction_samples
|
|
878
|
+
WHERE workspace IN (${filter})
|
|
879
|
+
AND review_status IN ('pending', 'approved')
|
|
880
|
+
ORDER BY created_at DESC
|
|
881
|
+
LIMIT ?
|
|
882
|
+
`).all(limit) as {
|
|
883
|
+
sample_id: string;
|
|
884
|
+
session_id: string;
|
|
885
|
+
workspace: string;
|
|
886
|
+
quality_score: number;
|
|
887
|
+
review_status: string;
|
|
888
|
+
created_at: string;
|
|
889
|
+
}[];
|
|
890
|
+
return rows.map(r => ({
|
|
891
|
+
sampleId: r.sample_id,
|
|
892
|
+
sessionId: r.session_id,
|
|
893
|
+
workspace: r.workspace,
|
|
894
|
+
qualityScore: r.quality_score ?? 0,
|
|
895
|
+
reviewStatus: r.review_status ?? 'pending',
|
|
896
|
+
createdAt: r.created_at,
|
|
897
|
+
}));
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Get the most recent lastSync timestamp across all workspaces (D-05)
|
|
902
|
+
*/
|
|
903
|
+
getMostRecentSync(): string | null {
|
|
904
|
+
const row = this.db.prepare(`
|
|
905
|
+
SELECT MAX(last_sync) as lastSync FROM workspaces
|
|
906
|
+
`).get() as { lastSync: string | null } | undefined;
|
|
907
|
+
return row?.lastSync ?? null;
|
|
908
|
+
}
|
|
821
909
|
}
|
|
822
910
|
|
|
823
911
|
// Singleton instance
|
|
824
912
|
let centralDbInstance: CentralDatabase | null = null;
|
|
825
913
|
|
|
826
914
|
export function getCentralDatabase(): CentralDatabase {
|
|
827
|
-
if (!centralDbInstance) {
|
|
915
|
+
if (!centralDbInstance || centralDbInstance.isClosed) {
|
|
828
916
|
centralDbInstance = new CentralDatabase();
|
|
829
917
|
}
|
|
830
918
|
return centralDbInstance;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getCentralDatabase } from './central-database.js';
|
|
2
|
+
import { HealthQueryService } from './health-query-service.js';
|
|
3
|
+
|
|
4
|
+
export interface WorkspaceHealthEntry {
|
|
5
|
+
workspaceName: string;
|
|
6
|
+
health: ReturnType<HealthQueryService['getOverviewHealth']>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CentralHealthResponse {
|
|
10
|
+
workspaces: WorkspaceHealthEntry[];
|
|
11
|
+
generatedAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Aggregates health data across all enabled workspaces.
|
|
16
|
+
* Each workspace gets its own HealthQueryService instance so GFI, Trust,
|
|
17
|
+
* Evolution, Principles, and Queue stats are workspace-specific.
|
|
18
|
+
*/
|
|
19
|
+
export class CentralHealthService {
|
|
20
|
+
/* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: utility method that doesn't need instance state */
|
|
21
|
+
getAllWorkspaceHealth(): CentralHealthResponse {
|
|
22
|
+
const centralDb = getCentralDatabase();
|
|
23
|
+
const workspaces: WorkspaceHealthEntry[] = [];
|
|
24
|
+
const enabled = centralDb.getEnabledWorkspaces();
|
|
25
|
+
|
|
26
|
+
for (const ws of enabled) {
|
|
27
|
+
try {
|
|
28
|
+
const hqs = new HealthQueryService(ws.path);
|
|
29
|
+
try {
|
|
30
|
+
const health = hqs.getOverviewHealth();
|
|
31
|
+
workspaces.push({ workspaceName: ws.name, health });
|
|
32
|
+
} finally {
|
|
33
|
+
hqs.dispose();
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.warn(
|
|
37
|
+
`[CentralHealthService] Could not get health for workspace "${ws.name}": ${String(error)}`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
workspaces,
|
|
44
|
+
generatedAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { getCentralDatabase, type CentralDatabase } from './central-database.js';
|
|
2
|
+
import { getThinkingModelDefinitions } from '../core/thinking-models.js';
|
|
3
|
+
import type { OverviewResponse } from './control-ui-query-service.js';
|
|
4
|
+
|
|
5
|
+
export { OverviewResponse };
|
|
6
|
+
|
|
7
|
+
export interface CentralOverviewResponse
|
|
8
|
+
extends Omit<OverviewResponse, 'dataSource' | 'runtimeControlPlaneSource'> {
|
|
9
|
+
dataSource: string;
|
|
10
|
+
runtimeControlPlaneSource: string;
|
|
11
|
+
centralInfo: {
|
|
12
|
+
workspaceCount: number;
|
|
13
|
+
enabledWorkspaceCount: number;
|
|
14
|
+
workspaces: string[];
|
|
15
|
+
enabledWorkspaces: string[];
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class CentralOverviewService {
|
|
20
|
+
private readonly centralDb: CentralDatabase;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this.centralDb = getCentralDatabase();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: intentionally no-op, centralDb is a process-wide singleton, see comment below */
|
|
27
|
+
dispose(): void {
|
|
28
|
+
// Do NOT dispose centralDb — it's a singleton shared across all requests.
|
|
29
|
+
// Individual services that open per-request connections (e.g. HealthQueryService)
|
|
30
|
+
// must dispose their own connections, but the central aggregated DB lives for
|
|
31
|
+
// the lifetime of the process.
|
|
32
|
+
}
|
|
33
|
+
/* eslint-enable @typescript-eslint/class-methods-use-this */
|
|
34
|
+
|
|
35
|
+
getOverview(days = 30): CentralOverviewResponse {
|
|
36
|
+
const stats = this.centralDb.getOverviewStats();
|
|
37
|
+
const trend = this.centralDb.getDailyTrend(days);
|
|
38
|
+
const regressions = this.centralDb.getTopRegressions(5);
|
|
39
|
+
const thinkingStats = this.centralDb.getThinkingModelStats();
|
|
40
|
+
const samplePreviewRows = this.centralDb.getSamplePreview(5);
|
|
41
|
+
const mostRecentSync = this.centralDb.getMostRecentSync();
|
|
42
|
+
|
|
43
|
+
// D-02: Query aggregated_task_outcomes for real taskOutcomes (not hardcoded 0)
|
|
44
|
+
let taskOutcomes = 0;
|
|
45
|
+
try {
|
|
46
|
+
taskOutcomes = this.centralDb.getTaskOutcomes();
|
|
47
|
+
} catch {
|
|
48
|
+
console.warn('[CentralOverviewService] Could not query aggregated_task_outcomes, defaulting taskOutcomes to 0');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// D-03: Query aggregated_principle_events for principleEventCount
|
|
52
|
+
// gate_blocks has no equivalent in central DB -- hardcode to 0 with warning
|
|
53
|
+
let principleEventCount = 0;
|
|
54
|
+
let gateBlocks = 0;
|
|
55
|
+
try {
|
|
56
|
+
principleEventCount = this.centralDb.getPrincipleEventCount();
|
|
57
|
+
} catch {
|
|
58
|
+
console.warn('[CentralOverviewService] Could not query aggregated_principle_events, defaulting principleEventCount to 0');
|
|
59
|
+
}
|
|
60
|
+
// gate_blocks: no equivalent in aggregated DB schema; hardcode to 0
|
|
61
|
+
|
|
62
|
+
// D-06: sampleQueue.counters from aggregated_correction_samples GROUP BY review_status
|
|
63
|
+
// eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in try/catch
|
|
64
|
+
let sampleCounters: Record<string, number> = {};
|
|
65
|
+
try {
|
|
66
|
+
sampleCounters = this.centralDb.getSampleCountersByStatus();
|
|
67
|
+
} catch {
|
|
68
|
+
// Fallback to stats-based counters if query fails
|
|
69
|
+
sampleCounters = {
|
|
70
|
+
pending: stats.pendingSamples,
|
|
71
|
+
approved: stats.approvedSamples,
|
|
72
|
+
rejected: stats.rejectedSamples,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// D-04: sampleQueue.preview from samplePreviewRows (not [])
|
|
77
|
+
// D-05: dataFreshness from mostRecentSync (not workspaces[0])
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
workspaceDir: 'central',
|
|
81
|
+
generatedAt: new Date().toISOString(),
|
|
82
|
+
dataFreshness: mostRecentSync,
|
|
83
|
+
dataSource: 'central_aggregated_db',
|
|
84
|
+
runtimeControlPlaneSource: 'all_workspaces',
|
|
85
|
+
summary: {
|
|
86
|
+
repeatErrorRate: stats.totalToolCalls > 0
|
|
87
|
+
? stats.totalFailures / stats.totalToolCalls
|
|
88
|
+
: 0,
|
|
89
|
+
userCorrectionRate: stats.totalToolCalls > 0
|
|
90
|
+
? stats.totalCorrections / stats.totalToolCalls
|
|
91
|
+
: 0,
|
|
92
|
+
pendingSamples: stats.pendingSamples,
|
|
93
|
+
approvedSamples: stats.approvedSamples,
|
|
94
|
+
thinkingCoverageRate: stats.totalToolCalls > 0
|
|
95
|
+
? stats.totalThinkingEvents / stats.totalToolCalls
|
|
96
|
+
: 0,
|
|
97
|
+
painEvents: stats.totalPainEvents,
|
|
98
|
+
principleEventCount,
|
|
99
|
+
gateBlocks,
|
|
100
|
+
taskOutcomes,
|
|
101
|
+
},
|
|
102
|
+
dailyTrend: trend,
|
|
103
|
+
topRegressions: regressions,
|
|
104
|
+
sampleQueue: {
|
|
105
|
+
counters: sampleCounters,
|
|
106
|
+
preview: samplePreviewRows.map(row => ({
|
|
107
|
+
sampleId: row.sampleId,
|
|
108
|
+
sessionId: row.sessionId,
|
|
109
|
+
qualityScore: Number(row.qualityScore),
|
|
110
|
+
reviewStatus: row.reviewStatus,
|
|
111
|
+
createdAt: row.createdAt,
|
|
112
|
+
})),
|
|
113
|
+
},
|
|
114
|
+
thinkingSummary: {
|
|
115
|
+
activeModels: thinkingStats.activeModels,
|
|
116
|
+
dormantModels: thinkingStats.totalModels - thinkingStats.activeModels,
|
|
117
|
+
effectiveModels: thinkingStats.models.filter(m => m.coverageRate > 0.1).length,
|
|
118
|
+
coverageRate: stats.totalToolCalls > 0
|
|
119
|
+
? stats.totalThinkingEvents / stats.totalToolCalls
|
|
120
|
+
: 0,
|
|
121
|
+
modelBreakdown: thinkingStats.models.map(m => ({
|
|
122
|
+
modelId: m.modelId,
|
|
123
|
+
hits: m.hits,
|
|
124
|
+
})),
|
|
125
|
+
modelDefinitions: getThinkingModelDefinitions(),
|
|
126
|
+
},
|
|
127
|
+
centralInfo: {
|
|
128
|
+
workspaceCount: stats.workspaceCount,
|
|
129
|
+
enabledWorkspaceCount: stats.enabledWorkspaceCount,
|
|
130
|
+
workspaces: stats.workspaceNames,
|
|
131
|
+
enabledWorkspaces: stats.enabledWorkspaceNames,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CentralSyncService - Periodically sync workspace data to central database.
|
|
3
|
+
*
|
|
4
|
+
* Ensures thinking_model_events and other workspace data are aggregated
|
|
5
|
+
* into the central database for cross-workspace queries and WebUI display.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { OpenClawPluginService, OpenClawPluginServiceContext, PluginLogger } from '../openclaw-sdk.js';
|
|
9
|
+
import { CentralDatabase } from './central-database.js';
|
|
10
|
+
|
|
11
|
+
let syncInterval: ReturnType<typeof setInterval> | null = null;
|
|
12
|
+
let logger: PluginLogger | undefined = undefined;
|
|
13
|
+
let centralDb: CentralDatabase | null = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default sync interval: 5 minutes.
|
|
17
|
+
* Can be overridden via config: intervals.central_sync_ms
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
async function runSyncCycle(): Promise<void> {
|
|
22
|
+
if (!centralDb) {
|
|
23
|
+
logger?.warn?.('[PD:CentralSync] CentralDatabase not initialized, skipping sync');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const results = centralDb.syncAll();
|
|
29
|
+
const totalSynced = Array.from(results.values()).reduce((sum, count) => sum + count, 0);
|
|
30
|
+
const workspacesSynced = Array.from(results.entries())
|
|
31
|
+
.filter(([, count]) => count > 0)
|
|
32
|
+
.map(([name, count]) => `${name}:${count}`)
|
|
33
|
+
.join(', ');
|
|
34
|
+
|
|
35
|
+
if (totalSynced > 0) {
|
|
36
|
+
logger?.info?.(`[PD:CentralSync] Synced ${totalSynced} records from workspaces: ${workspacesSynced}`);
|
|
37
|
+
} else {
|
|
38
|
+
logger?.debug?.(`[PD:CentralSync] No new records to sync`);
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
logger?.error?.(`[PD:CentralSync] Sync failed: ${String(err)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const CentralSyncService: OpenClawPluginService = {
|
|
46
|
+
id: 'principles-central-sync',
|
|
47
|
+
|
|
48
|
+
async start(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
49
|
+
const { logger: ctxLogger, config } = ctx;
|
|
50
|
+
logger = ctxLogger;
|
|
51
|
+
|
|
52
|
+
const { intervals } = config as { intervals?: { central_sync_ms?: number } };
|
|
53
|
+
const intervalMs = intervals?.central_sync_ms ?? DEFAULT_SYNC_INTERVAL_MS;
|
|
54
|
+
|
|
55
|
+
// Initialize CentralDatabase
|
|
56
|
+
centralDb = new CentralDatabase();
|
|
57
|
+
|
|
58
|
+
// Initial sync on start
|
|
59
|
+
logger?.info?.(`[PD:CentralSync] Starting with interval ${intervalMs}ms`);
|
|
60
|
+
await runSyncCycle();
|
|
61
|
+
|
|
62
|
+
// Schedule periodic sync
|
|
63
|
+
syncInterval = setInterval(runSyncCycle, intervalMs);
|
|
64
|
+
|
|
65
|
+
logger?.info?.(`[PD:CentralSync] Service started, syncing every ${intervalMs / 1000}s`);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async stop(ctx: OpenClawPluginServiceContext): Promise<void> {
|
|
69
|
+
if (syncInterval) {
|
|
70
|
+
clearInterval(syncInterval);
|
|
71
|
+
syncInterval = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Final sync on stop
|
|
75
|
+
if (centralDb) {
|
|
76
|
+
try {
|
|
77
|
+
centralDb.syncAll();
|
|
78
|
+
ctx.logger?.info?.(`[PD:CentralSync] Final sync completed`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
ctx.logger?.error?.(`[PD:CentralSync] Final sync failed: ${String(err)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
centralDb = null;
|
|
85
|
+
ctx.logger?.info?.(`[PD:CentralSync] Service stopped`);
|
|
86
|
+
},
|
|
87
|
+
};
|