principles-disciple 1.72.0 → 1.74.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/INSTALL.md +1 -3
- 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/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 +29 -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/migration.ts +0 -1
- 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/path-resolver.ts +0 -1
- package/src/core/paths.ts +0 -1
- 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 +71 -64
- package/src/hooks/gate.ts +183 -31
- 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 +400 -440
- 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 +59 -16
- 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 +38 -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 +8 -20
- 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 +7 -7
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/principles/THINKING_OS.md +3 -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/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
- 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 +2 -3
- 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 +7 -7
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/principles/THINKING_OS.md +3 -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/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 +4 -4
- 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 +2 -3
- 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/migration.test.ts +7 -7
- 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/path-resolver.test.ts +1 -1
- package/tests/core/paths-refactor.test.ts +0 -22
- 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-context.test.ts +2 -2
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +191 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-removal.test.ts +188 -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-no-path-write-tool.test.ts +172 -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 +32 -17
- 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 +3 -1
- 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/templates/langs/en/skills/plan-script/SKILL.md +0 -32
- package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
- 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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { SystemLogger } from './system-logger.js';
|
|
2
|
+
|
|
3
|
+
const PAIN_DIAGNOSTIC_SOURCES = [
|
|
4
|
+
'manual',
|
|
5
|
+
'tool_failure',
|
|
6
|
+
'dispatch_error',
|
|
7
|
+
'gate_blocked',
|
|
8
|
+
'user_empathy',
|
|
9
|
+
'llm_paralysis',
|
|
10
|
+
'semantic',
|
|
11
|
+
'subagent_error',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type PainDiagnosticSource = typeof PAIN_DIAGNOSTIC_SOURCES[number];
|
|
15
|
+
|
|
16
|
+
export type PainDiagnosticGateReason =
|
|
17
|
+
| 'manual'
|
|
18
|
+
| 'high_gfi'
|
|
19
|
+
| 'repeated_failure'
|
|
20
|
+
| 'semantic_pain'
|
|
21
|
+
| 'llm_paralysis'
|
|
22
|
+
| 'risky_high_score'
|
|
23
|
+
| 'subagent_error'
|
|
24
|
+
| 'gate_blocked'
|
|
25
|
+
| 'cooldown'
|
|
26
|
+
| 'below_gate';
|
|
27
|
+
|
|
28
|
+
export interface PainDiagnosticGateInput {
|
|
29
|
+
source: PainDiagnosticSource | string;
|
|
30
|
+
score: number;
|
|
31
|
+
currentGfi: number;
|
|
32
|
+
consecutiveErrors?: number;
|
|
33
|
+
isRisky?: boolean;
|
|
34
|
+
errorHash?: string;
|
|
35
|
+
sessionId?: string;
|
|
36
|
+
nowMs?: number;
|
|
37
|
+
cooldownMs?: number;
|
|
38
|
+
thresholds?: {
|
|
39
|
+
painTrigger?: number;
|
|
40
|
+
highSeverity?: number;
|
|
41
|
+
highGfi?: number;
|
|
42
|
+
repeatedFailure?: number;
|
|
43
|
+
semanticPain?: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PainDiagnosticGateDecision {
|
|
48
|
+
shouldDiagnose: boolean;
|
|
49
|
+
reason: PainDiagnosticGateReason;
|
|
50
|
+
episodeKey: string;
|
|
51
|
+
detail: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const DEFAULT_COOLDOWN_MS = 15 * 60 * 1000;
|
|
55
|
+
const lastDiagnosedAtByEpisode = new Map<string, number>();
|
|
56
|
+
|
|
57
|
+
function normalizedSource(source: string): PainDiagnosticSource | string {
|
|
58
|
+
if (source.startsWith('llm_') && source !== 'llm_paralysis') {
|
|
59
|
+
return 'semantic';
|
|
60
|
+
}
|
|
61
|
+
if (!(PAIN_DIAGNOSTIC_SOURCES as readonly string[]).includes(source)) {
|
|
62
|
+
SystemLogger.log('', 'GATE_UNKNOWN_SOURCE', `Unknown pain source: "${source}"`);
|
|
63
|
+
}
|
|
64
|
+
return source;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildEpisodeKey(input: PainDiagnosticGateInput): string {
|
|
68
|
+
const source = normalizedSource(input.source);
|
|
69
|
+
const sessionId = input.sessionId || 'unknown';
|
|
70
|
+
const hash = input.errorHash || 'no-hash';
|
|
71
|
+
return `${sessionId}:${source}:${hash}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function withinCooldown(input: PainDiagnosticGateInput, episodeKey: string): boolean {
|
|
75
|
+
const cooldownMs = input.cooldownMs ?? DEFAULT_COOLDOWN_MS;
|
|
76
|
+
if (cooldownMs <= 0) return false;
|
|
77
|
+
|
|
78
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
79
|
+
const last = lastDiagnosedAtByEpisode.get(episodeKey);
|
|
80
|
+
return last !== undefined && nowMs - last < cooldownMs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function markDiagnosed(input: PainDiagnosticGateInput, episodeKey: string): void {
|
|
84
|
+
lastDiagnosedAtByEpisode.set(episodeKey, input.nowMs ?? Date.now());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function resetPainDiagnosticGateForTest(): void {
|
|
88
|
+
lastDiagnosedAtByEpisode.clear();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function evaluatePainDiagnosticGate(input: PainDiagnosticGateInput): PainDiagnosticGateDecision {
|
|
92
|
+
const source = normalizedSource(input.source);
|
|
93
|
+
const episodeKey = buildEpisodeKey(input);
|
|
94
|
+
const painTrigger = input.thresholds?.painTrigger ?? 40;
|
|
95
|
+
const highSeverity = input.thresholds?.highSeverity ?? 70;
|
|
96
|
+
const highGfi = input.thresholds?.highGfi ?? Math.max(highSeverity, painTrigger + 30);
|
|
97
|
+
const repeatedFailure = input.thresholds?.repeatedFailure ?? 4;
|
|
98
|
+
const semanticPain = input.thresholds?.semanticPain ?? Math.max(painTrigger, 60);
|
|
99
|
+
const score = Number.isFinite(input.score) ? input.score : 0;
|
|
100
|
+
const currentGfi = Number.isFinite(input.currentGfi) ? input.currentGfi : 0;
|
|
101
|
+
const consecutiveErrors: number = Number.isFinite(input.consecutiveErrors) ? (input.consecutiveErrors as number) : 0;
|
|
102
|
+
|
|
103
|
+
const approve = (reason: PainDiagnosticGateReason, detail: string): PainDiagnosticGateDecision => {
|
|
104
|
+
if (withinCooldown(input, episodeKey)) {
|
|
105
|
+
return {
|
|
106
|
+
shouldDiagnose: false,
|
|
107
|
+
reason: 'cooldown',
|
|
108
|
+
episodeKey,
|
|
109
|
+
detail: `recently diagnosed; ${detail}`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
markDiagnosed(input, episodeKey);
|
|
113
|
+
return { shouldDiagnose: true, reason, episodeKey, detail };
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
if (source === 'manual') {
|
|
117
|
+
return approve('manual', 'manual pain signal bypasses automatic gate');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (source === 'subagent_error' && score >= painTrigger) {
|
|
121
|
+
return approve('subagent_error', `subagent error score ${score} >= ${painTrigger}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (source === 'llm_paralysis' && score >= painTrigger) {
|
|
125
|
+
return approve('llm_paralysis', `llm paralysis score ${score} >= ${painTrigger}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (source === 'gate_blocked' && score >= painTrigger) {
|
|
129
|
+
return approve('gate_blocked', `gate blocked score ${score} >= ${painTrigger}`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if ((source === 'user_empathy' || source === 'semantic') && score >= semanticPain) {
|
|
133
|
+
return approve('semantic_pain', `semantic pain score ${score} >= ${semanticPain}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (input.isRisky === true && score >= highSeverity) {
|
|
137
|
+
return approve('risky_high_score', `risky operation score ${score} >= ${highSeverity}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (consecutiveErrors >= repeatedFailure) {
|
|
141
|
+
return approve('repeated_failure', `consecutive errors ${consecutiveErrors} >= ${repeatedFailure}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (currentGfi >= highGfi) {
|
|
145
|
+
return approve('high_gfi', `GFI ${currentGfi.toFixed(1)} >= ${highGfi}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
shouldDiagnose: false,
|
|
150
|
+
reason: 'below_gate',
|
|
151
|
+
episodeKey,
|
|
152
|
+
detail: `score=${score}; gfi=${currentGfi.toFixed(1)}; consecutive=${consecutiveErrors}`,
|
|
153
|
+
};
|
|
154
|
+
}
|
package/src/core/pain-signal.ts
CHANGED
|
@@ -1,139 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import {
|
|
2
|
+
type PainSeverity as CorePainSeverity,
|
|
3
|
+
type PainSignal as CorePainSignal,
|
|
4
|
+
type PainSignalValidationResult as CorePainSignalValidationResult,
|
|
5
|
+
PainSeveritySchema,
|
|
6
|
+
PainSignalSchema,
|
|
7
|
+
deriveSeverity,
|
|
8
|
+
validatePainSignal,
|
|
9
|
+
} from '@principles/core/runtime-v2';
|
|
10
|
+
|
|
11
|
+
export type PainSeverity = CorePainSeverity;
|
|
12
|
+
export const PainSeverity = PainSeveritySchema;
|
|
13
|
+
|
|
14
|
+
export type PainSignal = CorePainSignal;
|
|
15
|
+
export type PainSignalValidationResult = CorePainSignalValidationResult;
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
PainSignalSchema,
|
|
19
|
+
deriveSeverity,
|
|
20
|
+
validatePainSignal,
|
|
21
|
+
};
|
|
13
22
|
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// PainSignal Schema
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Severity levels derived from pain score thresholds.
|
|
20
|
-
* - low: 0-39 (minor issue, informational)
|
|
21
|
-
* - medium: 40-69 (moderate error)
|
|
22
|
-
* - high: 70-89 (severe violation)
|
|
23
|
-
* - critical: 90-100 (systemic failure, spiral detected)
|
|
24
|
-
*/
|
|
25
|
-
export const PainSeverity = Type.Union([
|
|
26
|
-
Type.Literal('low'),
|
|
27
|
-
Type.Literal('medium'),
|
|
28
|
-
Type.Literal('high'),
|
|
29
|
-
Type.Literal('critical'),
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
export type PainSeverity = Static<typeof PainSeverity>;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* TypeBox schema for a universal pain signal.
|
|
36
|
-
*
|
|
37
|
-
* Every signal MUST have: source, score, timestamp, reason, sessionId,
|
|
38
|
-
* agentId, traceId, triggerTextPreview. Optional fields (domain, severity,
|
|
39
|
-
* context) default during validation.
|
|
40
|
-
*/
|
|
41
|
-
export const PainSignalSchema = Type.Object({
|
|
42
|
-
/** What triggered this pain signal (e.g., 'tool_failure', 'human_intervention') */
|
|
43
|
-
source: Type.String({ minLength: 1 }),
|
|
44
|
-
/** Pain score 0-100 */
|
|
45
|
-
score: Type.Number({ minimum: 0, maximum: 100 }),
|
|
46
|
-
/** ISO 8601 timestamp */
|
|
47
|
-
timestamp: Type.String({ minLength: 1 }),
|
|
48
|
-
/** Human-readable reason / error description */
|
|
49
|
-
reason: Type.String({ minLength: 1 }),
|
|
50
|
-
/** Session ID — identifies which conversation this happened in */
|
|
51
|
-
sessionId: Type.Optional(Type.String()),
|
|
52
|
-
/** Agent ID — identifies which agent (main, builder, diagnostician, etc.) */
|
|
53
|
-
agentId: Type.Optional(Type.String()),
|
|
54
|
-
/** Correlation trace ID for linking events across the pipeline */
|
|
55
|
-
traceId: Type.Optional(Type.String()),
|
|
56
|
-
/** Preview of the text that triggered this pain */
|
|
57
|
-
triggerTextPreview: Type.String(),
|
|
58
|
-
/** Domain context (e.g., 'coding', 'writing', 'analysis') */
|
|
59
|
-
domain: Type.String({ default: 'coding' }),
|
|
60
|
-
/** Severity level derived from score */
|
|
61
|
-
severity: PainSeverity,
|
|
62
|
-
/** Additional structured context payload */
|
|
63
|
-
context: Type.Record(Type.String(), Type.Unknown()),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
export type PainSignal = Static<typeof PainSignalSchema>;
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// Default Derivation
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Derives severity from a numeric pain score.
|
|
74
|
-
*/
|
|
75
|
-
export function deriveSeverity(score: number): PainSeverity {
|
|
76
|
-
if (score >= 90) return 'critical';
|
|
77
|
-
if (score >= 70) return 'high';
|
|
78
|
-
if (score >= 40) return 'medium';
|
|
79
|
-
return 'low';
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Validation
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export interface PainSignalValidationResult {
|
|
87
|
-
valid: boolean;
|
|
88
|
-
errors: string[];
|
|
89
|
-
signal?: PainSignal;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Validates an arbitrary object against the PainSignal schema.
|
|
94
|
-
*
|
|
95
|
-
* Returns a structured result with:
|
|
96
|
-
* - `valid`: whether the input conforms to the schema
|
|
97
|
-
* - `errors`: human-readable list of validation failures
|
|
98
|
-
* - `signal`: the typed signal (only present when valid)
|
|
99
|
-
*
|
|
100
|
-
* Missing optional fields (domain, severity, context) are filled with defaults
|
|
101
|
-
* before validation so callers get a fully-formed signal back.
|
|
102
|
-
*/
|
|
103
|
-
export function validatePainSignal(input: unknown): PainSignalValidationResult {
|
|
104
|
-
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
105
|
-
return { valid: false, errors: ['Input must be a non-null object'] };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const raw = input as Record<string, unknown>;
|
|
109
|
-
|
|
110
|
-
// Apply defaults for optional fields before validation
|
|
111
|
-
const hydrated = {
|
|
112
|
-
...raw,
|
|
113
|
-
domain: raw.domain ?? 'coding',
|
|
114
|
-
sessionId: raw.sessionId ?? undefined,
|
|
115
|
-
agentId: raw.agentId ?? undefined,
|
|
116
|
-
traceId: raw.traceId ?? undefined,
|
|
117
|
-
severity: raw.severity ?? deriveSeverity(
|
|
118
|
-
typeof raw.score === 'number' ? raw.score : 0,
|
|
119
|
-
),
|
|
120
|
-
context: raw.context ?? {},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// Collect TypeBox errors
|
|
124
|
-
const errors = [...Value.Errors(PainSignalSchema, hydrated)];
|
|
125
|
-
if (errors.length > 0) {
|
|
126
|
-
return {
|
|
127
|
-
valid: false,
|
|
128
|
-
errors: errors.map(
|
|
129
|
-
(e) => `${e.path ? `${e.path}: ` : ''}${e.message}`,
|
|
130
|
-
),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
valid: true,
|
|
136
|
-
errors: [],
|
|
137
|
-
signal: Value.Cast(PainSignalSchema, hydrated) as PainSignal,
|
|
138
|
-
};
|
|
139
|
-
}
|
package/src/core/pain.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
import
|
|
3
|
-
import { serializeKvLines, parseKvLines, atomicWriteFileSync } from '../utils/io.js';
|
|
2
|
+
import { parseKvLines } from '../utils/io.js';
|
|
4
3
|
import { resolvePdPath } from './paths.js';
|
|
5
4
|
import { ConfigService } from './config-service.js';
|
|
6
5
|
import { SystemLogger } from './system-logger.js';
|
|
7
6
|
|
|
8
7
|
// =========================================================================
|
|
9
|
-
// Pain Flag Contract (
|
|
8
|
+
// Legacy Pain Flag Contract (Compatibility Only)
|
|
10
9
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
10
|
+
// Runtime V2 pain diagnosis does NOT write .state/.pain_flag or consume it as
|
|
11
|
+
// an entry point. New pain signals must enter through emitPainDetectedEvent() or
|
|
12
|
+
// `pd pain record`, which both route to PainSignalBridge.
|
|
13
|
+
//
|
|
14
|
+
// The readers below remain only for legacy sleep-reflection context and
|
|
15
|
+
// historical state inspection. They must not trigger the diagnostician.
|
|
14
16
|
// =========================================================================
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -47,15 +49,8 @@ export interface PainFlagContractResult {
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* All callers (hooks/pain.ts, hooks/subagent.ts, hooks/lifecycle.ts,
|
|
53
|
-
* hooks/llm.ts, skills/pain/SKILL.md) must go through this function.
|
|
54
|
-
*
|
|
55
|
-
* Required fields are enforced by TypeScript — you can't compile
|
|
56
|
-
* without providing source, score, time, reason, session_id, agent_id.
|
|
57
|
-
*
|
|
58
|
-
* Optional fields (trace_id, trigger_text_preview) default to empty string.
|
|
52
|
+
* Builds legacy pain flag data for compatibility tests and historical readers.
|
|
53
|
+
* Do not use this to create new Runtime V2 diagnosis requests.
|
|
59
54
|
*/
|
|
60
55
|
export function buildPainFlag(input: {
|
|
61
56
|
source: string;
|
|
@@ -153,71 +148,6 @@ export function painSeverityLabel(painScore: number, isSpiral = false, projectDi
|
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
|
|
156
|
-
export function writePainFlag(projectDir: string, painData: PainFlagData): void {
|
|
157
|
-
const painFlagPath = resolvePdPath(projectDir, 'PAIN_FLAG');
|
|
158
|
-
const dir = path.dirname(painFlagPath);
|
|
159
|
-
if (!fs.existsSync(dir)) {
|
|
160
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
161
|
-
}
|
|
162
|
-
atomicWriteFileSync(painFlagPath, serializeKvLines(painData));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Combined trajectory record + pain flag write.
|
|
167
|
-
*
|
|
168
|
-
* Records the pain event to trajectory first to get the AUTOINCREMENT ID,
|
|
169
|
-
* then builds and writes the pain flag with that ID embedded.
|
|
170
|
-
* This guarantees the pain→principle→compile chain has the exact matching ID.
|
|
171
|
-
*
|
|
172
|
-
* Error handling: if trajectory write fails, continues without pain_event_id.
|
|
173
|
-
* If flag write fails, the error propagates to the caller.
|
|
174
|
-
*/
|
|
175
|
-
export function recordAndWritePainFlag(
|
|
176
|
-
wctx: {
|
|
177
|
-
trajectory?: { recordPainEvent(input: { sessionId: string; source: string; score: number; reason?: string | null; severity?: string | null; origin?: string | null; confidence?: number | null; text?: string }): number } | null;
|
|
178
|
-
workspaceDir: string;
|
|
179
|
-
},
|
|
180
|
-
trajectoryParams: {
|
|
181
|
-
sessionId: string;
|
|
182
|
-
source: string;
|
|
183
|
-
score: number;
|
|
184
|
-
reason?: string | null;
|
|
185
|
-
severity?: string | null;
|
|
186
|
-
origin?: string | null;
|
|
187
|
-
confidence?: number | null;
|
|
188
|
-
text?: string;
|
|
189
|
-
},
|
|
190
|
-
painFlagParams: {
|
|
191
|
-
source: string;
|
|
192
|
-
score: string;
|
|
193
|
-
reason: string;
|
|
194
|
-
session_id?: string;
|
|
195
|
-
agent_id?: string;
|
|
196
|
-
is_risky?: boolean;
|
|
197
|
-
trace_id?: string;
|
|
198
|
-
trigger_text_preview?: string;
|
|
199
|
-
}
|
|
200
|
-
): void {
|
|
201
|
-
const trajectoryPainId = wctx.trajectory?.recordPainEvent({
|
|
202
|
-
sessionId: trajectoryParams.sessionId,
|
|
203
|
-
source: trajectoryParams.source,
|
|
204
|
-
score: trajectoryParams.score,
|
|
205
|
-
reason: trajectoryParams.reason ?? null,
|
|
206
|
-
severity: trajectoryParams.severity ?? null,
|
|
207
|
-
origin: trajectoryParams.origin ?? null,
|
|
208
|
-
confidence: trajectoryParams.confidence ?? null,
|
|
209
|
-
text: trajectoryParams.text,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
const painData = buildPainFlag({
|
|
213
|
-
...painFlagParams,
|
|
214
|
-
pain_event_id:
|
|
215
|
-
trajectoryPainId !== undefined && trajectoryPainId >= 0 ? String(trajectoryPainId) : undefined,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
writePainFlag(wctx.workspaceDir, painData);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
151
|
/**
|
|
222
152
|
* Converts a JSON pain flag object to KV format.
|
|
223
153
|
*/
|
|
@@ -252,10 +182,10 @@ function convertJsonToKv(json: Record<string, unknown>): Record<string, string>
|
|
|
252
182
|
}
|
|
253
183
|
|
|
254
184
|
/**
|
|
255
|
-
* Reads and validates the pain flag file
|
|
185
|
+
* Reads and validates the legacy pain flag file.
|
|
256
186
|
*
|
|
257
187
|
* - If file doesn't exist → returns {}
|
|
258
|
-
* - If file is JSON format
|
|
188
|
+
* - If file is JSON format → converts to KV in memory only
|
|
259
189
|
* - If file is KV format → validates required fields, logs warning if missing
|
|
260
190
|
* - If file has unknown fields → silently ignores them (forward-compatible)
|
|
261
191
|
*/
|
|
@@ -270,7 +200,8 @@ export function readPainFlagData(projectDir: string): Record<string, string> {
|
|
|
270
200
|
return {};
|
|
271
201
|
}
|
|
272
202
|
|
|
273
|
-
// Detect JSON format
|
|
203
|
+
// Detect JSON format. Legacy compatibility only: parse in memory and do not
|
|
204
|
+
// rewrite .pain_flag, because Runtime V2 must not create or repair this file.
|
|
274
205
|
if (content.startsWith('{')) {
|
|
275
206
|
|
|
276
207
|
let json: Record<string, unknown>;
|
|
@@ -281,12 +212,8 @@ export function readPainFlagData(projectDir: string): Record<string, string> {
|
|
|
281
212
|
return {};
|
|
282
213
|
}
|
|
283
214
|
|
|
284
|
-
// Auto-repair: convert JSON to KV format
|
|
285
215
|
const kvData = convertJsonToKv(json);
|
|
286
|
-
|
|
287
|
-
const repaired = serializeKvLines(kvData);
|
|
288
|
-
atomicWriteFileSync(painFlagPath, repaired);
|
|
289
|
-
SystemLogger.log(projectDir, 'PAIN_FLAG_AUTO_REPAIRED', `Auto-repaired pain flag from JSON to KV format (${Object.keys(json).length} fields)`);
|
|
216
|
+
SystemLogger.log(projectDir, 'PAIN_FLAG_LEGACY_JSON_READ', `Read legacy JSON pain flag in memory (${Object.keys(json).length} fields)`);
|
|
290
217
|
return kvData;
|
|
291
218
|
}
|
|
292
219
|
|
|
@@ -306,7 +306,6 @@ export class PathResolver {
|
|
|
306
306
|
'THINKING_OS': workspacePath.join(workspace, '.principles', 'THINKING_OS.md'),
|
|
307
307
|
'DECISION_POLICY': workspacePath.join(workspace, '.principles', 'DECISION_POLICY.json'),
|
|
308
308
|
'MODELS_DIR': workspacePath.join(workspace, '.principles', 'models'),
|
|
309
|
-
'PLAN': workspacePath.join(workspace, 'PLAN.md'),
|
|
310
309
|
'AGENT_SCORECARD': workspacePath.join(state, 'AGENT_SCORECARD.json'),
|
|
311
310
|
'PAIN_FLAG': workspacePath.join(state, '.pain_flag'),
|
|
312
311
|
'EVOLUTION_QUEUE': workspacePath.join(state, 'evolution_queue.json'),
|
package/src/core/paths.ts
CHANGED
|
@@ -80,8 +80,7 @@ export interface ReconcileOptions {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
function readCronStore(logger?: { info?: (_: string) => void; warn?: (_: string) => void }): CronStoreFile {
|
|
85
84
|
if (!fs.existsSync(CRON_STORE_PATH)) {
|
|
86
85
|
logger?.info?.(`[PD:Reconciler] cron/jobs.json not found, starting with empty store`);
|
|
87
86
|
return { version: 1, jobs: [] };
|
|
@@ -178,101 +177,7 @@ function buildCronJob(
|
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
|
|
181
|
-
function buildTaskPrompt(task: PDTaskSpec,
|
|
182
|
-
// Resolve paths dynamically from workspaceDir instead of hardcoding
|
|
183
|
-
const stateDir = path.join(workspaceDir, '.state');
|
|
184
|
-
const empathyKeywordsPath = path.join(stateDir, 'empathy_keywords.json');
|
|
185
|
-
const eventsJsonlPath = path.join(stateDir, 'logs', 'events.jsonl');
|
|
186
|
-
|
|
187
|
-
if (task.id === 'empathy-optimizer') {
|
|
188
|
-
logger?.info?.(`[PD:Reconciler] Building empathy optimizer prompt`);
|
|
189
|
-
return `You are the Principles Disciple Empathy Keyword Optimizer.
|
|
190
|
-
|
|
191
|
-
## TASK
|
|
192
|
-
Analyze the current empathy keyword store and recent user message logs to:
|
|
193
|
-
1. Discover NEW frustration expressions not in the current store
|
|
194
|
-
2. ADJUST weights of existing terms based on actual hit frequency
|
|
195
|
-
3. REMOVE terms that produce too many false positives
|
|
196
|
-
|
|
197
|
-
## WORKFLOW (execute in order)
|
|
198
|
-
|
|
199
|
-
### Step 1: Read current keyword store
|
|
200
|
-
Use read_file to load:
|
|
201
|
-
\`${empathyKeywordsPath}\`
|
|
202
|
-
|
|
203
|
-
Examine the "terms" object. For each term note:
|
|
204
|
-
- weight (0.1-0.9): higher = stronger frustration signal
|
|
205
|
-
- hitCount: how many times it matched
|
|
206
|
-
- falsePositiveRate (0.05-0.5): how often it's a false alarm
|
|
207
|
-
|
|
208
|
-
### Step 2: Read recent message logs
|
|
209
|
-
Use search_file_content to scan:
|
|
210
|
-
\`${eventsJsonlPath}\`
|
|
211
|
-
|
|
212
|
-
Look for user messages containing frustration signals:
|
|
213
|
-
- Negation: "不对", "错了", "不行", "重做"
|
|
214
|
-
- Anger: "垃圾", "蠢", "废物", "白做"
|
|
215
|
-
- Disappointment: "不行啊", "还是不对", "没解决"
|
|
216
|
-
- Escalation: "你到底在干什么", "你确定吗", "what are you doing"
|
|
217
|
-
|
|
218
|
-
### Step 3: Write updated keyword store
|
|
219
|
-
Use write_file to save the updated store back to:
|
|
220
|
-
\`${empathyKeywordsPath}\`
|
|
221
|
-
|
|
222
|
-
The file format is:
|
|
223
|
-
\`\`\`json
|
|
224
|
-
{
|
|
225
|
-
"version": 1,
|
|
226
|
-
"lastUpdated": "ISO timestamp",
|
|
227
|
-
"lastOptimizedAt": "ISO timestamp",
|
|
228
|
-
"terms": {
|
|
229
|
-
"TERM": {
|
|
230
|
-
"weight": 0.5,
|
|
231
|
-
"source": "seed|llm_discovered|user_reported",
|
|
232
|
-
"hitCount": 0,
|
|
233
|
-
"falsePositiveRate": 0.15
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
"stats": {
|
|
237
|
-
"totalHits": 0,
|
|
238
|
-
"totalFalsePositives": 0,
|
|
239
|
-
"optimizationCount": 1
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
\`\`\`
|
|
243
|
-
|
|
244
|
-
**IMPORTANT**: You MUST use the write_file tool. Do NOT just return JSON in your response.
|
|
245
|
-
|
|
246
|
-
### Step 4: Report summary
|
|
247
|
-
After writing the file, reply with a brief summary:
|
|
248
|
-
\`\`\`
|
|
249
|
-
Empathy keyword optimization complete:
|
|
250
|
-
- Added: N new terms (list them)
|
|
251
|
-
- Updated: M terms (list changes)
|
|
252
|
-
- Removed: K terms (list them)
|
|
253
|
-
- Total terms in store: X
|
|
254
|
-
\`\`\`
|
|
255
|
-
|
|
256
|
-
## RULES
|
|
257
|
-
- ADD: If you find frustration expressions in logs NOT in current terms
|
|
258
|
-
- source = "llm_discovered", discoveredAt = current ISO timestamp
|
|
259
|
-
- weight: 0.5-0.7 for new terms (start conservative)
|
|
260
|
-
- falsePositiveRate: 0.2-0.3 (uncertain until validated)
|
|
261
|
-
- UPDATE: Adjust based on evidence
|
|
262
|
-
- High hitCount + low FPR → increase weight
|
|
263
|
-
- Low hitCount + high FPR → decrease weight
|
|
264
|
-
- Keep weight in 0.1-0.9, FPR in 0.05-0.5
|
|
265
|
-
- REMOVE: If hitCount=0 AND falsePositiveRate > 0.3 AND term is clearly generic
|
|
266
|
-
- Don't remove terms that might be valid but rare
|
|
267
|
-
- PRESERVE: Keep existing hitCount, lastHitAt, discoveredAt for existing terms
|
|
268
|
-
- Bump stats.optimizationCount by 1
|
|
269
|
-
- Set lastOptimizedAt to current ISO timestamp
|
|
270
|
-
|
|
271
|
-
## EXAMPLES
|
|
272
|
-
- "不对" has hitCount=50 → increase weight from 0.5 to 0.7
|
|
273
|
-
- "呵呵" has hitCount=0, FPR=0.4, generic term → REMOVE
|
|
274
|
-
- User says "烦死了" in logs, not in store → ADD weight=0.6, FPR=0.25`;
|
|
275
|
-
}
|
|
180
|
+
function buildTaskPrompt(task: PDTaskSpec, _workspaceDir: string, _logger?: { info?: (_: string) => void }): string {
|
|
276
181
|
return task.description;
|
|
277
182
|
}
|
|
278
183
|
|
|
@@ -334,8 +239,10 @@ export async function reconcilePDTasks(
|
|
|
334
239
|
case 'DISABLE':
|
|
335
240
|
if (action.job) {
|
|
336
241
|
if (!dryRun) {
|
|
337
|
-
action.job
|
|
338
|
-
|
|
242
|
+
const idx = cronStore.jobs.indexOf(action.job);
|
|
243
|
+
if (idx >= 0) {
|
|
244
|
+
cronStore.jobs[idx] = { ...action.job, enabled: false, updatedAtMs: nowMs };
|
|
245
|
+
}
|
|
339
246
|
}
|
|
340
247
|
logger.warn?.(`[PD:Reconciler] Disabled job: ${action.job.name}`);
|
|
341
248
|
}
|
|
@@ -381,9 +288,9 @@ function healthCheck(
|
|
|
381
288
|
): PDTaskSpec[] {
|
|
382
289
|
const jobByName = new Map(cronStore.jobs.map((j) => [j.name, j]));
|
|
383
290
|
|
|
384
|
-
|
|
291
|
+
return tasks.map((task) => {
|
|
385
292
|
const job = jobByName.get(task.name);
|
|
386
|
-
if (!job)
|
|
293
|
+
if (!job) return task;
|
|
387
294
|
|
|
388
295
|
const errors = job.state.consecutiveErrors ?? 0;
|
|
389
296
|
const lastError = job.state.lastError ?? '';
|
|
@@ -402,27 +309,31 @@ function healthCheck(
|
|
|
402
309
|
}
|
|
403
310
|
|
|
404
311
|
// Auto-disable only for non-timeout errors after 3 consecutive failures
|
|
405
|
-
|
|
406
|
-
if (!task.meta) task.meta = {};
|
|
407
|
-
task.meta.autoDisabled = true;
|
|
408
|
-
task.meta.autoDisabledAt = Date.now();
|
|
409
|
-
task.meta.autoDisabledReason = `consecutiveErrors=${errors}`;
|
|
410
|
-
logger.warn?.(`[PD:Reconciler] Auto-disabled task '${task.id}' due to ${errors} consecutive errors: ${lastError.substring(0, 80)}`);
|
|
411
|
-
}
|
|
312
|
+
const shouldAutoDisable = errors >= 3 && !isTimeout && !task.meta?.autoDisabled;
|
|
412
313
|
|
|
413
314
|
// Reset consecutiveErrors on non-error runs
|
|
414
315
|
if (errors === 0 && task.meta?.autoDisabled) {
|
|
415
316
|
logger.info?.(`[PD:Reconciler] Task '${task.id}' was previously auto-disabled but has 0 consecutive errors now`);
|
|
416
317
|
}
|
|
417
318
|
|
|
418
|
-
if (task.meta) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
319
|
+
if (shouldAutoDisable || task.meta || job.state.lastRunAtMs) {
|
|
320
|
+
return {
|
|
321
|
+
...task,
|
|
322
|
+
meta: {
|
|
323
|
+
...task.meta,
|
|
324
|
+
...(shouldAutoDisable ? {
|
|
325
|
+
autoDisabled: true,
|
|
326
|
+
autoDisabledAt: Date.now(),
|
|
327
|
+
autoDisabledReason: `consecutiveErrors=${errors}`,
|
|
328
|
+
} : {}),
|
|
329
|
+
...(task.meta ? { consecutiveFailCount: errors } : {}),
|
|
330
|
+
...(job.state.lastRunAtMs ? { lastFailedAtMs: job.state.lastRunAtMs } : {}),
|
|
331
|
+
},
|
|
332
|
+
};
|
|
423
333
|
}
|
|
424
|
-
|
|
425
|
-
|
|
334
|
+
|
|
335
|
+
return task;
|
|
336
|
+
});
|
|
426
337
|
}
|
|
427
338
|
|
|
428
339
|
export async function trigger(
|