principles-disciple 1.71.0 → 1.73.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/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/confirm-first-gate.ts +255 -0
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +38 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +46 -44
- package/src/hooks/gate.ts +207 -7
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +469 -339
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +115 -18
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +39 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +10 -0
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +329 -0
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +184 -3
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
|
@@ -31,7 +31,9 @@ function writeSession(workspace: string, sessionId: string, payload: Record<stri
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function writeEvents(workspace: string, entries: unknown[]): void {
|
|
34
|
-
|
|
34
|
+
// Write to today's daily event file (events_YYYY-MM-DD.jsonl)
|
|
35
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
36
|
+
const filePath = path.join(workspace, '.state', 'logs', `events_${today}.jsonl`);
|
|
35
37
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
36
38
|
const content = entries.map((entry) => JSON.stringify(entry)).join('\n');
|
|
37
39
|
fs.writeFileSync(filePath, content ? `${content}\n` : '', 'utf8');
|
|
@@ -471,11 +473,12 @@ describe('RuntimeSummaryService', () => {
|
|
|
471
473
|
trust_score: 59,
|
|
472
474
|
last_updated: '2026-03-20T10:00:00Z',
|
|
473
475
|
});
|
|
476
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
474
477
|
fs.writeFileSync(
|
|
475
|
-
path.join(workspace, '.state', 'logs',
|
|
478
|
+
path.join(workspace, '.state', 'logs', `events_${today}.jsonl`),
|
|
476
479
|
[
|
|
477
480
|
JSON.stringify({
|
|
478
|
-
ts:
|
|
481
|
+
ts: `${today}T10:00:01Z`,
|
|
479
482
|
type: 'pain_signal',
|
|
480
483
|
category: 'detected',
|
|
481
484
|
sessionId: 's1',
|
|
@@ -916,4 +919,182 @@ describe('RuntimeSummaryService', () => {
|
|
|
916
919
|
expect(summary.phase3.directiveIgnoredReason).toBe('queue is only truth source');
|
|
917
920
|
});
|
|
918
921
|
});
|
|
922
|
+
|
|
923
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
924
|
+
// Phase A: Event log daily-file compatibility + stalled diagnostician warning
|
|
925
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
926
|
+
|
|
927
|
+
describe('Event log daily-file format (events_YYYY-MM-DD.jsonl)', () => {
|
|
928
|
+
it('reads today events_YYYY-MM-DD.jsonl and does NOT warn about missing events.jsonl', () => {
|
|
929
|
+
const workspace = makeWorkspace();
|
|
930
|
+
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|
|
931
|
+
trust_score: 59,
|
|
932
|
+
last_updated: '2026-03-20T10:00:00Z',
|
|
933
|
+
});
|
|
934
|
+
// Write daily file (today's date)
|
|
935
|
+
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
936
|
+
const dailyFilePath = path.join(workspace, '.state', 'logs', `events_${today}.jsonl`);
|
|
937
|
+
fs.mkdirSync(path.dirname(dailyFilePath), { recursive: true });
|
|
938
|
+
fs.writeFileSync(
|
|
939
|
+
dailyFilePath,
|
|
940
|
+
JSON.stringify({
|
|
941
|
+
ts: `${today}T10:00:01Z`,
|
|
942
|
+
type: 'pain_signal',
|
|
943
|
+
category: 'detected',
|
|
944
|
+
sessionId: 's1',
|
|
945
|
+
data: { source: 'tool_failure', score: 10, reason: 'write failed' },
|
|
946
|
+
}) + '\n',
|
|
947
|
+
'utf8'
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
const summary = RuntimeSummaryService.getSummary(workspace);
|
|
951
|
+
|
|
952
|
+
// Must have read the daily file successfully
|
|
953
|
+
expect(summary.pain.lastSignal?.source).toBe('tool_failure');
|
|
954
|
+
// Must NOT warn about "No events.jsonl file"
|
|
955
|
+
expect(summary.metadata.warnings.join('\n')).not.toContain('No events.jsonl file');
|
|
956
|
+
expect(summary.metadata.warnings.join('\n')).not.toContain('No event log file');
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
it('falls back to most recent daily file when today file does not exist', () => {
|
|
960
|
+
const workspace = makeWorkspace();
|
|
961
|
+
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|
|
962
|
+
trust_score: 59,
|
|
963
|
+
last_updated: '2026-03-20T10:00:00Z',
|
|
964
|
+
});
|
|
965
|
+
// Write only an older daily file
|
|
966
|
+
const oldDate = '2026-03-18';
|
|
967
|
+
const oldFilePath = path.join(workspace, '.state', 'logs', `events_${oldDate}.jsonl`);
|
|
968
|
+
fs.mkdirSync(path.dirname(oldFilePath), { recursive: true });
|
|
969
|
+
fs.writeFileSync(
|
|
970
|
+
oldFilePath,
|
|
971
|
+
JSON.stringify({
|
|
972
|
+
ts: `${oldDate}T10:00:01Z`,
|
|
973
|
+
type: 'gate_block',
|
|
974
|
+
category: 'risk',
|
|
975
|
+
sessionId: 's2',
|
|
976
|
+
data: { reason: 'old gate event' },
|
|
977
|
+
}) + '\n',
|
|
978
|
+
'utf8'
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const summary = RuntimeSummaryService.getSummary(workspace);
|
|
982
|
+
|
|
983
|
+
// Must have read the old file
|
|
984
|
+
expect(summary.gate.recentBlocks).toBeGreaterThan(0);
|
|
985
|
+
// Must NOT warn about "No event log file"
|
|
986
|
+
expect(summary.metadata.warnings.join('\n')).not.toContain('No event log file');
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
it('warns only when no daily event file exists at all', () => {
|
|
990
|
+
const workspace = makeWorkspace();
|
|
991
|
+
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|
|
992
|
+
trust_score: 59,
|
|
993
|
+
last_updated: '2026-03-20T10:00:00Z',
|
|
994
|
+
});
|
|
995
|
+
// Deliberately leave no event log files
|
|
996
|
+
|
|
997
|
+
const summary = RuntimeSummaryService.getSummary(workspace);
|
|
998
|
+
|
|
999
|
+
expect(summary.metadata.warnings.join('\n')).toContain('No event log file');
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
// M8: Runtime v2 uses SQLite task store for pending tasks; legacy diagnostician_tasks.json
|
|
1004
|
+
// is no longer read for pendingTasks count. This test verified stale behavior.
|
|
1005
|
+
describe.skip('Stalled diagnostician warning', () => {
|
|
1006
|
+
it('raises a high-signal warning when tasks are injected but no reports are written', () => {
|
|
1007
|
+
const workspace = makeWorkspace();
|
|
1008
|
+
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|
|
1009
|
+
trust_score: 59,
|
|
1010
|
+
last_updated: '2026-03-20T10:00:00Z',
|
|
1011
|
+
});
|
|
1012
|
+
writeJson(path.join(workspace, '.state', 'evolution_queue.json'), []);
|
|
1013
|
+
|
|
1014
|
+
// Simulate: pending tasks exist, heartbeats are injecting, but no reports written.
|
|
1015
|
+
// The pending task store is checked via getPendingDiagnosticianTasks which uses
|
|
1016
|
+
// the real filesystem path. We need to write diagnostician_tasks.json directly.
|
|
1017
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1018
|
+
fs.mkdirSync(path.join(workspace, '.state'), { recursive: true });
|
|
1019
|
+
fs.writeFileSync(
|
|
1020
|
+
path.join(workspace, '.state', 'diagnostician_tasks.json'),
|
|
1021
|
+
JSON.stringify({
|
|
1022
|
+
tasks: {
|
|
1023
|
+
'stalled-task-1': {
|
|
1024
|
+
prompt: 'Diagnose: tool_failure score=80',
|
|
1025
|
+
createdAt: `${today}T09:00:00Z`,
|
|
1026
|
+
status: 'pending',
|
|
1027
|
+
},
|
|
1028
|
+
},
|
|
1029
|
+
}),
|
|
1030
|
+
'utf8'
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
// Write daily-stats.json with heartbeats > 0 but reportsWritten = 0
|
|
1034
|
+
writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
|
|
1035
|
+
[today]: {
|
|
1036
|
+
evolution: {
|
|
1037
|
+
diagnosisTasksWritten: 3,
|
|
1038
|
+
diagnosticianReportsWritten: 0,
|
|
1039
|
+
reportsMissingJson: 0,
|
|
1040
|
+
reportsIncompleteFields: 0,
|
|
1041
|
+
principleCandidatesCreated: 0,
|
|
1042
|
+
heartbeatsInjected: 5,
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// Also create a valid daily event file so readEvents() does not emit
|
|
1048
|
+
// "No event log file" warning that would pollute warnings[] and mask
|
|
1049
|
+
// the stall detection logic we are actually testing.
|
|
1050
|
+
fs.mkdirSync(path.join(workspace, '.state', 'logs'), { recursive: true });
|
|
1051
|
+
fs.writeFileSync(
|
|
1052
|
+
path.join(workspace, '.state', 'logs', `events_${today}.jsonl`),
|
|
1053
|
+
JSON.stringify({ ts: `${today}T09:00:00Z`, type: 'heartbeat_diagnosis', category: 'task_injected', sessionId: 'test', data: {} }) + '\n',
|
|
1054
|
+
'utf8'
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
const summary = RuntimeSummaryService.getSummary(workspace);
|
|
1058
|
+
|
|
1059
|
+
const warningText = summary.metadata.warnings.join('\n');
|
|
1060
|
+
expect(warningText).toContain('Diagnostician appears stalled');
|
|
1061
|
+
expect(warningText).toContain('5'); // heartbeatsInjected
|
|
1062
|
+
expect(warningText).toContain('reports are being written'); // confirms it says "0"
|
|
1063
|
+
expect(warningText).toContain('1 task(s) remain pending');
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
it('does NOT raise the stalled warning when reports are being written', () => {
|
|
1067
|
+
const workspace = makeWorkspace();
|
|
1068
|
+
writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
|
|
1069
|
+
trust_score: 59,
|
|
1070
|
+
last_updated: '2026-03-20T10:00:00Z',
|
|
1071
|
+
});
|
|
1072
|
+
writeJson(path.join(workspace, '.state', 'evolution_queue.json'), []);
|
|
1073
|
+
|
|
1074
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1075
|
+
fs.mkdirSync(path.join(workspace, '.state'), { recursive: true });
|
|
1076
|
+
fs.writeFileSync(
|
|
1077
|
+
path.join(workspace, '.state', 'diagnostician_tasks.json'),
|
|
1078
|
+
JSON.stringify({ tasks: {} }), // No pending tasks
|
|
1079
|
+
'utf8'
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
|
|
1083
|
+
[today]: {
|
|
1084
|
+
evolution: {
|
|
1085
|
+
diagnosisTasksWritten: 3,
|
|
1086
|
+
diagnosticianReportsWritten: 3, // Reports ARE being written
|
|
1087
|
+
reportsMissingJson: 0,
|
|
1088
|
+
reportsIncompleteFields: 0,
|
|
1089
|
+
principleCandidatesCreated: 1,
|
|
1090
|
+
heartbeatsInjected: 5,
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
const summary = RuntimeSummaryService.getSummary(workspace);
|
|
1096
|
+
|
|
1097
|
+
expect(summary.metadata.warnings.join('\n')).not.toContain('Diagnostician appears stalled');
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
919
1100
|
});
|
|
@@ -254,74 +254,6 @@ describe('runWorkflowWatchdog', () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
-
// ── BUG-03: Nocturnal snapshot validation ─────────────────────────────
|
|
258
|
-
|
|
259
|
-
describe('BUG-03: nocturnal snapshot validation', () => {
|
|
260
|
-
it('detects fallback_snapshot when nocturnal workflow uses pain_context_fallback', async () => {
|
|
261
|
-
const now = Date.now();
|
|
262
|
-
|
|
263
|
-
mockListWorkflows.mockReturnValue([
|
|
264
|
-
createWorkflow({
|
|
265
|
-
workflow_id: 'wf-nocturnal-001',
|
|
266
|
-
workflow_type: 'nocturnal',
|
|
267
|
-
state: 'completed',
|
|
268
|
-
created_at: now - (60 * 60 * 1000),
|
|
269
|
-
metadata_json: JSON.stringify({
|
|
270
|
-
snapshot: {
|
|
271
|
-
_dataSource: 'pain_context_fallback',
|
|
272
|
-
stats: { totalToolCalls: 0, totalGateBlocks: 0, failureCount: 0 },
|
|
273
|
-
},
|
|
274
|
-
}),
|
|
275
|
-
}),
|
|
276
|
-
]);
|
|
277
|
-
|
|
278
|
-
const result = await runWorkflowWatchdog(
|
|
279
|
-
{ workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
|
|
280
|
-
mockApi,
|
|
281
|
-
mockLogger,
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
expect(result.details).toContainEqual(
|
|
285
|
-
expect.stringContaining('fallback_snapshot: nocturnal workflow wf-nocturnal-001 uses pain-context fallback'),
|
|
286
|
-
);
|
|
287
|
-
expect(result.details).toContainEqual(
|
|
288
|
-
expect.stringContaining('fallback_snapshot_stats: nocturnal workflow wf-nocturnal-001 has empty fallback stats'),
|
|
289
|
-
);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('does not flag fallback_snapshot_stats when nocturnal workflow has real stats', async () => {
|
|
293
|
-
const now = Date.now();
|
|
294
|
-
|
|
295
|
-
mockListWorkflows.mockReturnValue([
|
|
296
|
-
createWorkflow({
|
|
297
|
-
workflow_id: 'wf-nocturnal-002',
|
|
298
|
-
workflow_type: 'nocturnal',
|
|
299
|
-
state: 'completed',
|
|
300
|
-
created_at: now - (60 * 60 * 1000),
|
|
301
|
-
metadata_json: JSON.stringify({
|
|
302
|
-
snapshot: {
|
|
303
|
-
_dataSource: 'pain_context_fallback',
|
|
304
|
-
stats: { totalToolCalls: 5, totalGateBlocks: 2, failureCount: 1 },
|
|
305
|
-
},
|
|
306
|
-
}),
|
|
307
|
-
}),
|
|
308
|
-
]);
|
|
309
|
-
|
|
310
|
-
const result = await runWorkflowWatchdog(
|
|
311
|
-
{ workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
|
|
312
|
-
mockApi,
|
|
313
|
-
mockLogger,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
expect(result.details).toContainEqual(
|
|
317
|
-
expect.stringContaining('fallback_snapshot: nocturnal workflow wf-nocturnal-002 uses pain-context fallback'),
|
|
318
|
-
);
|
|
319
|
-
expect(result.details).not.toContainEqual(
|
|
320
|
-
expect.stringContaining('fallback_snapshot_stats'),
|
|
321
|
-
);
|
|
322
|
-
});
|
|
323
|
-
});
|
|
324
|
-
|
|
325
257
|
// ── General behavior ───────────────────────────────────────────────────
|
|
326
258
|
|
|
327
259
|
describe('general behavior', () => {
|
|
@@ -345,28 +277,5 @@ describe('runWorkflowWatchdog', () => {
|
|
|
345
277
|
expect(result.anomalies).toBe(0);
|
|
346
278
|
expect(result.details).toHaveLength(0);
|
|
347
279
|
});
|
|
348
|
-
|
|
349
|
-
it('handles malformed metadata_json gracefully', async () => {
|
|
350
|
-
const now = Date.now();
|
|
351
|
-
|
|
352
|
-
mockListWorkflows.mockReturnValue([
|
|
353
|
-
createWorkflow({
|
|
354
|
-
workflow_id: 'wf-malformed-001',
|
|
355
|
-
workflow_type: 'nocturnal',
|
|
356
|
-
state: 'completed',
|
|
357
|
-
created_at: now - (60 * 60 * 1000),
|
|
358
|
-
metadata_json: 'not valid json {{{',
|
|
359
|
-
}),
|
|
360
|
-
]);
|
|
361
|
-
|
|
362
|
-
const result = await runWorkflowWatchdog(
|
|
363
|
-
{ workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
|
|
364
|
-
mockApi,
|
|
365
|
-
mockLogger,
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
expect(result.anomalies).toBe(1);
|
|
369
|
-
expect(result.details.some((d: string) => d.includes('malformed_metadata'))).toBe(true);
|
|
370
|
-
});
|
|
371
280
|
});
|
|
372
281
|
});
|
|
@@ -36,7 +36,9 @@ function cleanup(filePath: string): void {
|
|
|
36
36
|
try {
|
|
37
37
|
const dir = path.dirname(filePath);
|
|
38
38
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
39
|
-
} catch {
|
|
39
|
+
} catch {
|
|
40
|
+
// expected on some platforms — cleanup is best-effort
|
|
41
|
+
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function getLockPath(filePath: string): string {
|
|
@@ -357,8 +359,8 @@ describe('File Lock', () => {
|
|
|
357
359
|
const elapsed = Date.now() - start;
|
|
358
360
|
const avgMs = elapsed / iterations;
|
|
359
361
|
|
|
360
|
-
// 平均每次锁操作应该 <
|
|
361
|
-
expect(avgMs).toBeLessThan(
|
|
362
|
+
// 平均每次锁操作应该 < 50ms (宽松阈值避免 CI/满负载 flaky)
|
|
363
|
+
expect(avgMs).toBeLessThan(50);
|
|
362
364
|
});
|
|
363
365
|
});
|
|
364
366
|
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { extractAgentIdFromSessionKey } from '../../src/utils/session-key';
|
|
3
|
+
|
|
4
|
+
describe('extractAgentIdFromSessionKey', () => {
|
|
5
|
+
test('returns undefined for undefined input', () => {
|
|
6
|
+
expect(extractAgentIdFromSessionKey(undefined)).toBeUndefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('returns undefined for empty string', () => {
|
|
10
|
+
expect(extractAgentIdFromSessionKey('')).toBeUndefined();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('returns agentId from 3-part key (agent:{id}:{type}:{uuid})', () => {
|
|
14
|
+
expect(extractAgentIdFromSessionKey('agent:main:session:abc-123')).toBe('main');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('returns agentId from 2-part key (agent:{id}:{uuid})', () => {
|
|
18
|
+
expect(extractAgentIdFromSessionKey('agent:worker-1:def-456')).toBe('worker-1');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('returns undefined for non-matching format', () => {
|
|
22
|
+
expect(extractAgentIdFromSessionKey('user:main:session:abc')).toBeUndefined();
|
|
23
|
+
expect(extractAgentIdFromSessionKey('session:abc-123')).toBeUndefined();
|
|
24
|
+
expect(extractAgentIdFromSessionKey('random-string')).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('trims whitespace from agentId', () => {
|
|
28
|
+
expect(extractAgentIdFromSessionKey('agent: main :session:abc')).toBe('main');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('returns undefined when agentId is whitespace-only', () => {
|
|
32
|
+
expect(extractAgentIdFromSessionKey('agent: :session:abc')).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('handles agentId with special characters', () => {
|
|
36
|
+
expect(extractAgentIdFromSessionKey('agent:my-agent_v2:session:abc')).toBe('my-agent_v2');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('returns undefined for whitespace-only agentId', () => {
|
|
40
|
+
// String.trim() removes ASCII whitespace (\\t, \\n, \\r, space) AND fullwidth space \\u3000 from edges
|
|
41
|
+
expect(extractAgentIdFromSessionKey('agent:\t:session:abc')).toBeUndefined(); // tab → empty after trim
|
|
42
|
+
expect(extractAgentIdFromSessionKey('agent:\r:session:abc')).toBeUndefined(); // CR → empty after trim
|
|
43
|
+
expect(extractAgentIdFromSessionKey('agent:\u3000:session:abc')).toBeUndefined(); // fullwidth space trimmed → empty
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('handles mixed whitespace within agentId parts', () => {
|
|
47
|
+
// Tab is internal (not at trim edge), so 'a\tb'.trim() → 'a\tb' (tab preserved in middle)
|
|
48
|
+
expect(extractAgentIdFromSessionKey('agent:a\tb:session:abc')).toBe('a\tb');
|
|
49
|
+
// CR\\n is internal, not at edge, so 'a\r\nb'.trim() → 'a\r\nb'
|
|
50
|
+
expect(extractAgentIdFromSessionKey('agent:a\r\nb:session:abc')).toBe('a\r\nb');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach, afterAll } from 'vitest';
|
|
2
2
|
import {
|
|
3
3
|
getSubagentRuntimeAvailability,
|
|
4
4
|
isSubagentRuntimeAvailable,
|
|
5
|
+
getAvailableSubagentRuntime,
|
|
5
6
|
} from '../../src/utils/subagent-probe.js';
|
|
6
7
|
|
|
7
8
|
describe('subagent-probe', () => {
|
|
@@ -30,3 +31,49 @@ describe('subagent-probe', () => {
|
|
|
30
31
|
});
|
|
31
32
|
});
|
|
32
33
|
});
|
|
34
|
+
|
|
35
|
+
describe('getAvailableSubagentRuntime', () => {
|
|
36
|
+
const validRuntime = { run: () => Promise.resolve({ runId: 'run-1' }) };
|
|
37
|
+
|
|
38
|
+
it('returns the passed subagent when it is available', () => {
|
|
39
|
+
expect(getAvailableSubagentRuntime(validRuntime)).toBe(validRuntime);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('returns undefined when passed undefined and no global gateway exists', () => {
|
|
43
|
+
expect(getAvailableSubagentRuntime(undefined)).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('returns undefined when passed unavailable subagent and no global gateway', () => {
|
|
47
|
+
expect(getAvailableSubagentRuntime({} as any)).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('with global gateway fallback', () => {
|
|
51
|
+
const symbol = Symbol.for('openclaw.plugin.gatewaySubagentRuntime');
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
(globalThis as any)[symbol] = { subagent: validRuntime };
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
delete (globalThis as any)[symbol];
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterAll(() => {
|
|
62
|
+
// Safety net: ensure cleanup even if a test crashes before afterEach
|
|
63
|
+
delete (globalThis as any)[symbol];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('falls back to global gateway when passed subagent is unavailable', () => {
|
|
67
|
+
expect(getAvailableSubagentRuntime(undefined)).toBe(validRuntime);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('falls back to global gateway when passed subagent has no run', () => {
|
|
71
|
+
expect(getAvailableSubagentRuntime({} as any)).toBe(validRuntime);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('prefers passed subagent over global gateway when both available', () => {
|
|
75
|
+
const localRuntime = { run: () => Promise.resolve({ runId: 'local' }) };
|
|
76
|
+
expect(getAvailableSubagentRuntime(localRuntime)).toBe(localRuntime);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -23,18 +23,11 @@ const integrationTests = [
|
|
|
23
23
|
// Core DB tests
|
|
24
24
|
'tests/core/control-ui-db.test.ts',
|
|
25
25
|
'tests/core/evolution-logger.test.ts',
|
|
26
|
-
'tests/core/nocturnal-e2e.test.ts',
|
|
27
|
-
'tests/core/nocturnal-trajectory-extractor.test.ts',
|
|
28
26
|
'tests/core/replay-engine.test.ts',
|
|
29
27
|
'tests/core/trajectory.test.ts',
|
|
30
28
|
'tests/core/workspace-context.test.ts',
|
|
31
29
|
// Service tests with DB dependencies
|
|
32
|
-
'tests/service/nocturnal-service-code-candidate.test.ts',
|
|
33
|
-
'tests/service/nocturnal-target-selector.test.ts',
|
|
34
|
-
'tests/service/evolution-worker.nocturnal.test.ts',
|
|
35
30
|
'tests/service/evolution-worker.timeout.test.ts',
|
|
36
|
-
'tests/service/data-endpoints-regression.test.ts',
|
|
37
|
-
'tests/service/control-ui-query-service.test.ts',
|
|
38
31
|
'tests/service/keyword-optimization-service.test.ts',
|
|
39
32
|
// Hook tests with DB dependencies
|
|
40
33
|
'tests/hooks/subagent.test.ts',
|
|
@@ -59,10 +52,10 @@ export default defineConfig({
|
|
|
59
52
|
reporter: ['text', 'html'],
|
|
60
53
|
exclude: ['tests/**'],
|
|
61
54
|
thresholds: {
|
|
62
|
-
lines:
|
|
63
|
-
functions:
|
|
64
|
-
branches:
|
|
65
|
-
statements:
|
|
55
|
+
lines: 58,
|
|
56
|
+
functions: 65,
|
|
57
|
+
branches: 45,
|
|
58
|
+
statements: 57,
|
|
66
59
|
},
|
|
67
60
|
},
|
|
68
61
|
},
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
**Analysis Date:** 2026-04-15
|
|
4
|
-
|
|
5
|
-
## Pattern Overview
|
|
6
|
-
|
|
7
|
-
**Overall:** Event-driven plugin architecture with hook-based interception
|
|
8
|
-
|
|
9
|
-
**Key Characteristics:**
|
|
10
|
-
- OpenClaw plugin framework with hook-based integration
|
|
11
|
-
- Background worker services for async evolution processing
|
|
12
|
-
- SQLite-based trajectory and state persistence
|
|
13
|
-
- Multi-layer security gates (GFI gate, empathy engine, pain tracking)
|
|
14
|
-
- Workflow managers for complex subagent orchestration
|
|
15
|
-
|
|
16
|
-
## Layers
|
|
17
|
-
|
|
18
|
-
**Plugin Entry Point:**
|
|
19
|
-
- Purpose: Initialize plugin, register hooks, commands, tools, and services
|
|
20
|
-
- Location: `src/index.ts`
|
|
21
|
-
- Contains: Plugin registration, hook handlers, command registration
|
|
22
|
-
- Depends on: OpenClaw SDK, all core modules
|
|
23
|
-
|
|
24
|
-
**Hooks Layer:**
|
|
25
|
-
- Purpose: Intercept and modify OpenClaw agent behavior
|
|
26
|
-
- Location: `src/hooks/`
|
|
27
|
-
- Contains: `prompt.ts` (before_prompt_build), `gate.ts` (before_tool_call), `pain.ts` (after_tool_call), `llm.ts` (llm_output), `lifecycle.ts` (before_reset, compaction), `subagent.ts` (subagent lifecycle)
|
|
28
|
-
- Depends on: Core services, workspace context
|
|
29
|
-
- Used by: OpenClaw event system
|
|
30
|
-
|
|
31
|
-
**Core Services:**
|
|
32
|
-
- Purpose: Business logic for evolution, pain tracking, trajectory, nocturnal training
|
|
33
|
-
- Location: `src/core/` and `src/service/`
|
|
34
|
-
- Contains: `evolution-engine.ts`, `trajectory.ts`, `nocturnal-trinity.ts`, `pain.ts`, `training-program.ts`, `pd-task-service.ts`
|
|
35
|
-
- Depends on: Database, workspace context, config
|
|
36
|
-
|
|
37
|
-
**Command Handlers:**
|
|
38
|
-
- Purpose: Implement slash commands (e.g., `/pd-init`, `/pd-status`, `/pd-nocturnal-review`)
|
|
39
|
-
- Location: `src/commands/`
|
|
40
|
-
- Contains: 20+ command implementations
|
|
41
|
-
- Depends on: Core services, workspace context
|
|
42
|
-
|
|
43
|
-
**Workflow Managers (Subagent Workflow):**
|
|
44
|
-
- Purpose: Orchestrate complex subagent workflows
|
|
45
|
-
- Location: `src/service/subagent-workflow/`
|
|
46
|
-
- Contains: `nocturnal-workflow-manager.ts`, `deep-reflect-workflow-manager.ts`, `empathy-observer-workflow-manager.ts`, `correction-observer-workflow-manager.ts`
|
|
47
|
-
- Depends on: Evolution worker, core services
|
|
48
|
-
|
|
49
|
-
**Database Layer:**
|
|
50
|
-
- Purpose: Persist trajectory data, evolution state, workflow state
|
|
51
|
-
- Location: `src/core/schema/` (migrations), `src/service/central-database.ts`
|
|
52
|
-
- Contains: SQLite schema, migration runner, query services
|
|
53
|
-
- Depends on: better-sqlite3
|
|
54
|
-
|
|
55
|
-
**UI Layer:**
|
|
56
|
-
- Purpose: React-based plugin UI for monitoring and control
|
|
57
|
-
- Location: `ui/src/`
|
|
58
|
-
- Contains: Pages (Overview, Evolution, Feedback, GateMonitor, Samples, ThinkingModels), components (Shell, ProtectedRoute), context (auth, theme)
|
|
59
|
-
- Depends on: React, React Router, lucide-react
|
|
60
|
-
|
|
61
|
-
**Utils:**
|
|
62
|
-
- Purpose: Shared utilities (I/O, logging, retry, hashing, file locking)
|
|
63
|
-
- Location: `src/utils/`
|
|
64
|
-
- Contains: `io.ts` (atomic writes), `plugin-logger.ts`, `retry.ts`, `hashing.ts`, `file-lock.ts`
|
|
65
|
-
|
|
66
|
-
## Data Flow
|
|
67
|
-
|
|
68
|
-
**Agent Execution Flow:**
|
|
69
|
-
1. OpenClaw fires `before_prompt_build` hook
|
|
70
|
-
2. `hooks/prompt.ts` builds context injection (principles, thinking OS, focus, reflection log)
|
|
71
|
-
3. OpenClaw generates response
|
|
72
|
-
4. `hooks/llm.ts` analyzes LLM output for signals
|
|
73
|
-
5. User triggers tool call
|
|
74
|
-
6. `hooks/gate.ts` intercepts `before_tool_call` - evaluates risk, may block
|
|
75
|
-
7. Tool executes
|
|
76
|
-
8. `hooks/pain.ts` intercepts `after_tool_call` - tracks pain signals, empathy penalties
|
|
77
|
-
|
|
78
|
-
**Evolution Flow:**
|
|
79
|
-
1. `EvolutionWorkerService` polls `pain_candidates.json` periodically
|
|
80
|
-
2. For each candidate, `evolution-engine.ts` evaluates and derives improvements
|
|
81
|
-
3. `trajectory.ts` records behavior patterns
|
|
82
|
-
4. Nocturnal training (`nocturnal-trinity.ts`) synthesizes improvements into rule updates
|
|
83
|
-
5. `principle-tree-ledger.ts` manages principle lifecycle
|
|
84
|
-
|
|
85
|
-
**Nocturnal Training Flow:**
|
|
86
|
-
1. Candidate scoring via `nocturnal-candidate-scoring.ts`
|
|
87
|
-
2. Target selection via `nocturnal-target-selector.ts`
|
|
88
|
-
3. Rule implementation validation via `nocturnal-rule-implementation-validator.ts`
|
|
89
|
-
4. Promotion via `promotion-gate.ts`
|
|
90
|
-
|
|
91
|
-
## Key Abstractions
|
|
92
|
-
|
|
93
|
-
**WorkspaceContext:**
|
|
94
|
-
- Purpose: Encapsulates all state and services for a single workspace
|
|
95
|
-
- Examples: `src/core/workspace-context.ts`
|
|
96
|
-
- Pattern: Factory method `WorkspaceContext.fromHookContext()`
|
|
97
|
-
|
|
98
|
-
**EvolutionEngine:**
|
|
99
|
-
- Purpose: Core evolution processing logic
|
|
100
|
-
- Examples: `src/core/evolution-engine.ts`
|
|
101
|
-
- Pattern: Reducer pattern with typed actions
|
|
102
|
-
|
|
103
|
-
**NocturnalTrinity:**
|
|
104
|
-
- Purpose: Three-phase nocturnal training (dataset, training, evaluation)
|
|
105
|
-
- Examples: `src/core/nocturnal-trinity.ts`
|
|
106
|
-
- Pattern: Phase orchestration with contract validation
|
|
107
|
-
|
|
108
|
-
**PainConfig:**
|
|
109
|
-
- Purpose: Plugin configuration management
|
|
110
|
-
- Examples: `src/core/config.ts`
|
|
111
|
-
- Pattern: Singleton per workspace with file persistence
|
|
112
|
-
|
|
113
|
-
**RuleHost:**
|
|
114
|
-
- Purpose: Secure rule execution environment
|
|
115
|
-
- Examples: `src/core/rule-host.ts`
|
|
116
|
-
- Pattern: Sandboxed evaluation with permission checks
|
|
117
|
-
|
|
118
|
-
## Entry Points
|
|
119
|
-
|
|
120
|
-
**Plugin Entry:**
|
|
121
|
-
- Location: `src/index.ts`
|
|
122
|
-
- Triggers: OpenClaw loads plugin
|
|
123
|
-
- Responsibilities: Register all hooks, commands, tools, services; initialize workspace
|
|
124
|
-
|
|
125
|
-
**Command Entry:**
|
|
126
|
-
- Location: `src/commands/*.ts`
|
|
127
|
-
- Triggers: User invokes slash command (e.g., `/pd-init`)
|
|
128
|
-
- Responsibilities: Validate input, delegate to core services, return formatted result
|
|
129
|
-
|
|
130
|
-
**Service Entry:**
|
|
131
|
-
- Location: `src/service/evolution-worker.ts`
|
|
132
|
-
- Triggers: `before_prompt_build` fires (starts background worker)
|
|
133
|
-
- Responsibilities: Process evolution queue, track pain signals, trigger training
|
|
134
|
-
|
|
135
|
-
## Error Handling
|
|
136
|
-
|
|
137
|
-
**Strategy:** Graceful degradation with logging
|
|
138
|
-
|
|
139
|
-
**Patterns:**
|
|
140
|
-
- Try-catch blocks in all hook handlers with error logging
|
|
141
|
-
- `SystemLogger.log()` for critical errors
|
|
142
|
-
- Workspace context error recording via `eventLog.recordHookExecution()`
|
|
143
|
-
- Non-critical errors caught silently (trajectory collection)
|
|
144
|
-
|
|
145
|
-
## Cross-Cutting Concerns
|
|
146
|
-
|
|
147
|
-
**Logging:** `src/utils/plugin-logger.ts` - OpenClaw logger abstraction
|
|
148
|
-
|
|
149
|
-
**Validation:** Schema validation via `@sinclair/typebox` for config and event types
|
|
150
|
-
|
|
151
|
-
**Authentication:** OpenClaw session-based, agent ID extracted from session key
|
|
152
|
-
|
|
153
|
-
**I/O Safety:** Atomic file writes via `atomicWriteFileSync()` in `src/utils/io.ts`
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
*Architecture analysis: 2026-04-15*
|