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
package/src/hooks/prompt.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
@@ -9,32 +9,27 @@ import type { ContextInjectionConfig} from '../types.js';
|
|
|
9
9
|
import { defaultContextConfig } from '../types.js';
|
|
10
10
|
import { classifyTask, type RoutingInput } from '../core/local-worker-routing.js';
|
|
11
11
|
import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
|
|
12
|
-
import { EmpathyObserverWorkflowManager, empathyObserverWorkflowSpec, isExpectedSubagentError } from '../service/subagent-workflow/index.js';
|
|
13
12
|
import { PathResolver } from '../core/path-resolver.js';
|
|
14
13
|
import { selectPrinciplesForInjection, DEFAULT_PRINCIPLE_BUDGET } from '../core/principle-injection.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { getCachedMaskedPrincipleSet, WorkflowFunnelLoader, PiAiRuntimeAdapter, EmpathyObserver, AgentScheduler } from '@principles/core/runtime-v2';
|
|
15
|
+
import { truncateInjectionToBudget } from '@principles/core/prompt-builder';
|
|
16
|
+
import { PromptActivationReader, RUNTIME_V2_PRINCIPLE_BUDGET } from '../core/runtime-v2-prompt-activation-reader.js';
|
|
17
17
|
import {
|
|
18
18
|
matchEmpathyKeywords,
|
|
19
19
|
loadKeywordStore,
|
|
20
20
|
saveKeywordStore,
|
|
21
|
-
shouldTriggerOptimization,
|
|
22
21
|
getKeywordStoreSummary,
|
|
23
22
|
} from '../core/empathy-keyword-matcher.js';
|
|
24
23
|
import { severityToPenalty, DEFAULT_EMPATHY_KEYWORD_CONFIG } from '../core/empathy-types.js';
|
|
24
|
+
import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
|
|
25
|
+
import { emitPainDetectedEvent, buildTrajectoryEvidence } from './pain.js';
|
|
25
26
|
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*/
|
|
33
|
-
function toWorkflowSubagent(
|
|
34
|
-
subagent: NonNullable<OpenClawPluginApi['runtime']>['subagent']
|
|
35
|
-
): PluginRuntimeSubagent {
|
|
36
|
-
return subagent as unknown as PluginRuntimeSubagent;
|
|
37
|
-
}
|
|
27
|
+
import {
|
|
28
|
+
buildAttitudeDirective,
|
|
29
|
+
detectCorrectionCue as coreDetectCorrectionCue,
|
|
30
|
+
extractMessageContent,
|
|
31
|
+
isMinimalTrigger,
|
|
32
|
+
} from '@principles/core/prompt-builder';
|
|
38
33
|
|
|
39
34
|
// ---------------------------------------------------------------------------
|
|
40
35
|
// Static file cache — avoids re-reading rarely-changing files every message
|
|
@@ -129,43 +124,7 @@ interface PromptHookApi {
|
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
function getTextContent(message: unknown): string {
|
|
132
|
-
|
|
133
|
-
const record = message as { content?: unknown };
|
|
134
|
-
if (typeof record.content === 'string') return record.content;
|
|
135
|
-
if (Array.isArray(record.content)) {
|
|
136
|
-
return record.content
|
|
137
|
-
.filter((part: unknown) => part && typeof part === 'object' && (part as { type?: unknown }).type === 'text')
|
|
138
|
-
.map((part) => String((part as { text?: unknown }).text ?? ''))
|
|
139
|
-
.join('\n')
|
|
140
|
-
.trim();
|
|
141
|
-
}
|
|
142
|
-
return '';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function detectCorrectionCue(text: string): string | null {
|
|
146
|
-
const normalized = text
|
|
147
|
-
.trim()
|
|
148
|
-
.toLowerCase()
|
|
149
|
-
.replace(/[.,!?;:,。!?;:]/g, '');
|
|
150
|
-
const cues = [
|
|
151
|
-
'不是这个',
|
|
152
|
-
'不对',
|
|
153
|
-
'错了',
|
|
154
|
-
'搞错了',
|
|
155
|
-
'理解错了',
|
|
156
|
-
'你理解错了',
|
|
157
|
-
'重新来',
|
|
158
|
-
'再试一次',
|
|
159
|
-
'you are wrong',
|
|
160
|
-
'wrong file',
|
|
161
|
-
'not this',
|
|
162
|
-
'redo',
|
|
163
|
-
'try again',
|
|
164
|
-
'again',
|
|
165
|
-
'please redo',
|
|
166
|
-
'please try again',
|
|
167
|
-
];
|
|
168
|
-
return cues.find((cue) => normalized.includes(cue)) ?? null;
|
|
127
|
+
return extractMessageContent(message);
|
|
169
128
|
}
|
|
170
129
|
|
|
171
130
|
/**
|
|
@@ -297,64 +256,12 @@ export function getDiagnosticianModel(api: PromptHookApi | null, logger?: Plugin
|
|
|
297
256
|
/**
|
|
298
257
|
* Extract recent user messages for keyword optimization context.
|
|
299
258
|
*/
|
|
300
|
-
|
|
301
|
-
if (!Array.isArray(messages)) return [];
|
|
302
|
-
const userMessages: string[] = [];
|
|
303
|
-
|
|
304
|
-
for (let i = messages.length - 1; i >= 0 && userMessages.length < limit; i--) {
|
|
305
|
-
const msg = messages[i] as { role?: string; content?: unknown };
|
|
306
|
-
if (msg?.role !== 'user') continue;
|
|
307
|
-
|
|
308
|
-
let text = '';
|
|
309
|
-
if (typeof msg.content === 'string') {
|
|
310
|
-
text = msg.content;
|
|
311
|
-
} else if (Array.isArray(msg.content)) {
|
|
312
|
-
text = msg.content
|
|
313
|
-
.filter((part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'text' && typeof (part as { text?: unknown }).text === 'string')
|
|
314
|
-
.map((part: unknown) => (part as { text: string }).text)
|
|
315
|
-
.join('\n')
|
|
316
|
-
.trim();
|
|
317
|
-
}
|
|
318
|
-
if (text) userMessages.unshift(text.substring(0, 500));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return userMessages;
|
|
322
|
-
}
|
|
259
|
+
|
|
323
260
|
|
|
324
261
|
/**
|
|
325
262
|
* Build prompt for keyword optimization subagent.
|
|
326
263
|
*/
|
|
327
|
-
|
|
328
|
-
keywordStore: ReturnType<typeof loadKeywordStore>,
|
|
329
|
-
recentMessages: string[],
|
|
330
|
-
): string {
|
|
331
|
-
const currentTerms = Object.entries(keywordStore.terms)
|
|
332
|
-
.map(([term, entry]) => ` - "${term}": weight=${entry.weight}, hits=${entry.hitCount || 0}, fp_rate=${entry.falsePositiveRate?.toFixed(2) || '0.10'}`)
|
|
333
|
-
.join('\n');
|
|
334
|
-
|
|
335
|
-
return `You are an empathy keyword optimizer.
|
|
336
|
-
|
|
337
|
-
## TASK
|
|
338
|
-
Analyze recent user messages and the current empathy keyword store.
|
|
339
|
-
Return STRICT JSON (no markdown):
|
|
340
|
-
|
|
341
|
-
{"updates": {"TERM": {"action": "add|update|remove", "weight": number, "falsePositiveRate": number, "reasoning": "string"}}}
|
|
342
|
-
|
|
343
|
-
## Current Keyword Store (${Object.keys(keywordStore.terms).length} terms):
|
|
344
|
-
${currentTerms}
|
|
345
|
-
|
|
346
|
-
## Recent User Messages (${recentMessages.length} messages):
|
|
347
|
-
${recentMessages.map((m, i) => `${i + 1}. "${m}"`).join('\n')}
|
|
348
|
-
|
|
349
|
-
## Rules:
|
|
350
|
-
- ADD: If a message contains frustration/empathy signals not in current terms
|
|
351
|
-
- UPDATE: If a term's weight should change (high hits → increase weight, low hits → decrease)
|
|
352
|
-
- REMOVE: If a term has 0 hits after many turns AND high false positive rate (>0.3)
|
|
353
|
-
- Keep reasoning concise (max 100 chars)
|
|
354
|
-
- Weight range: 0.1-0.9
|
|
355
|
-
- falsePositiveRate range: 0.05-0.5
|
|
356
|
-
`;
|
|
357
|
-
}
|
|
264
|
+
|
|
358
265
|
|
|
359
266
|
export async function handleBeforePromptBuild(
|
|
360
267
|
event: PluginHookBeforePromptBuildEvent,
|
|
@@ -364,19 +271,13 @@ export async function handleBeforePromptBuild(
|
|
|
364
271
|
const logger = ctx.api?.logger;
|
|
365
272
|
logger?.info?.(`[PD:Prompt] handleBeforePromptBuild called: workspaceDir=${!!workspaceDir}, trigger=${ctx.trigger}, sessionId=${ctx.sessionId?.substring(0, 20)}`);
|
|
366
273
|
if (!workspaceDir) {
|
|
367
|
-
logger?.warn?.(`[PD:Prompt] workspaceDir is missing — skipping
|
|
274
|
+
logger?.warn?.(`[PD:Prompt] workspaceDir is missing — skipping PD context injection`);
|
|
368
275
|
return;
|
|
369
276
|
}
|
|
370
277
|
|
|
371
|
-
// ──── DEBUG: Verify subagent availability in this context ────
|
|
372
|
-
const subagent = ctx.api?.runtime?.subagent;
|
|
373
|
-
logger?.info?.(`[PD:DEBUG:SubagentCheck] trigger=${ctx.trigger}, subagent_exists=${!!subagent}, subagent.run_exists=${!!subagent?.run}`);
|
|
374
|
-
if (subagent?.run) {
|
|
375
|
-
logger?.info?.('[PD:DEBUG:SubagentCheck] run entrypoint is callable');
|
|
376
|
-
}
|
|
377
|
-
|
|
378
278
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
379
|
-
const { trigger, sessionId
|
|
279
|
+
const { trigger, sessionId } = ctx as { trigger: string | undefined; sessionId: string | undefined };
|
|
280
|
+
const api = ctx.api;
|
|
380
281
|
if (sessionId) {
|
|
381
282
|
wctx.trajectory?.recordSession?.({ sessionId });
|
|
382
283
|
}
|
|
@@ -389,6 +290,7 @@ export async function handleBeforePromptBuild(
|
|
|
389
290
|
|
|
390
291
|
if (latestUserIndex) {
|
|
391
292
|
const userText = getTextContent(latestUserIndex.message);
|
|
293
|
+
|
|
392
294
|
// Use CorrectionCueLearner for detection — supports learned keywords, not just hardcoded list
|
|
393
295
|
let correctionCue: string | null = null;
|
|
394
296
|
try {
|
|
@@ -406,7 +308,7 @@ export async function handleBeforePromptBuild(
|
|
|
406
308
|
}
|
|
407
309
|
} catch (learnerErr) {
|
|
408
310
|
// Fallback to hardcoded detection if learner fails — log for observability
|
|
409
|
-
correctionCue =
|
|
311
|
+
correctionCue = coreDetectCorrectionCue(userText);
|
|
410
312
|
logger?.warn?.(`[PD:Prompt] CorrectionCueLearner.match() failed (${String(learnerErr)}), fallback=${correctionCue ? `matched="${correctionCue}"` : 'no-match'}`);
|
|
411
313
|
}
|
|
412
314
|
let referencesAssistantTurnId: number | null = null;
|
|
@@ -432,10 +334,10 @@ export async function handleBeforePromptBuild(
|
|
|
432
334
|
}
|
|
433
335
|
|
|
434
336
|
// Load context injection configuration
|
|
435
|
-
const contextConfig = loadContextInjectionConfig(workspaceDir);
|
|
337
|
+
const contextConfig = loadContextInjectionConfig(wctx.workspaceDir);
|
|
436
338
|
|
|
437
339
|
// Minimal mode: heartbeat and subagents skip most context to reduce tokens
|
|
438
|
-
const isMinimalMode = trigger
|
|
340
|
+
const isMinimalMode = isMinimalTrigger(trigger as string | undefined, sessionId as string | undefined);
|
|
439
341
|
|
|
440
342
|
const session = sessionId ? getSession(sessionId) : undefined;
|
|
441
343
|
|
|
@@ -535,7 +437,9 @@ The empathy observer subagent handles pain detection independently.
|
|
|
535
437
|
|
|
536
438
|
const isUserInteraction = trigger === 'user' || trigger === 'api' || !trigger;
|
|
537
439
|
|
|
440
|
+
// Empathy Observer: keyword fast-path + optional LLM deep analysis (zero latency async dispatch)
|
|
538
441
|
const empathyEnabled = wctx.config.get('empathy_engine.enabled') !== false;
|
|
442
|
+
|
|
539
443
|
logger?.info?.(`[PD:Empathy] Conditions: enabled=${empathyEnabled}, isUser=${isUserInteraction}, sessionId=${!!sessionId}, api=${!!api}, !agentToAgent=${!isAgentToAgent}, workspaceDir=${!!workspaceDir}, hasMessage=${!!latestUserMessage}`);
|
|
540
444
|
|
|
541
445
|
// Track if we should inject behavioral constraints (will be added to appendSystemContext later)
|
|
@@ -564,23 +468,6 @@ The empathy observer subagent handles pain detection independently.
|
|
|
564
468
|
_empathyTurnCounter++;
|
|
565
469
|
const turnCount = _empathyTurnCounter;
|
|
566
470
|
|
|
567
|
-
// Decision: should we call subagent?
|
|
568
|
-
let shouldCallSubagent = false;
|
|
569
|
-
let samplingReason = '';
|
|
570
|
-
|
|
571
|
-
if (matchResult.score >= 0.8) {
|
|
572
|
-
// High confidence — keyword match is reliable, no subagent needed
|
|
573
|
-
shouldCallSubagent = false;
|
|
574
|
-
} else if (matchResult.score >= 0.3) {
|
|
575
|
-
// Boundary case — 30% sampling for subagent verification
|
|
576
|
-
shouldCallSubagent = Math.random() < 0.3;
|
|
577
|
-
samplingReason = 'boundary_verification';
|
|
578
|
-
} else {
|
|
579
|
-
// No keyword hit — 5% random sampling to discover new expressions
|
|
580
|
-
shouldCallSubagent = Math.random() < 0.05;
|
|
581
|
-
samplingReason = 'random_discovery';
|
|
582
|
-
}
|
|
583
|
-
|
|
584
471
|
if (matchResult.matched) {
|
|
585
472
|
const penalty = severityToPenalty(matchResult.severity, DEFAULT_EMPATHY_KEYWORD_CONFIG);
|
|
586
473
|
// trackFriction signature: (sessionId, deltaF: number, hash: string, workspaceDir?, options?)
|
|
@@ -588,93 +475,194 @@ The empathy observer subagent handles pain detection independently.
|
|
|
588
475
|
source: 'user_empathy',
|
|
589
476
|
});
|
|
590
477
|
|
|
591
|
-
logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
subagent: toWorkflowSubagent(runtimeSubagent),
|
|
616
|
-
});
|
|
617
|
-
empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
|
|
618
|
-
parentSessionId: sessionId,
|
|
619
|
-
workspaceDir,
|
|
620
|
-
taskInput: latestUserMessage,
|
|
621
|
-
}).catch((err) => {
|
|
622
|
-
if (!isExpectedSubagentError(err)) {
|
|
623
|
-
api.logger?.warn?.(`[PD:Empathy] subagent sample failed: ${String(err)}`);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
}
|
|
478
|
+
logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}`);
|
|
479
|
+
|
|
480
|
+
const currentSession = getSession(sessionId);
|
|
481
|
+
const currentGfi = currentSession?.currentGfi ?? 0;
|
|
482
|
+
const painTrigger = wctx.config.get('thresholds.pain_trigger') || 40;
|
|
483
|
+
const highGfiThreshold = Math.max(wctx.config.get('severity_thresholds.high') || 70, painTrigger + 30);
|
|
484
|
+
|
|
485
|
+
if (currentGfi >= highGfiThreshold) {
|
|
486
|
+
const gfiPainScore = Math.min(Math.round(currentGfi), 60);
|
|
487
|
+
logger?.info?.(`[PD:Empathy] GFI-TRIGGERED: currentGfi=${currentGfi.toFixed(1)} >= highGfi=${highGfiThreshold}, emitting pain signal (score=${gfiPainScore})`);
|
|
488
|
+
|
|
489
|
+
const gate = evaluatePainDiagnosticGate({
|
|
490
|
+
source: 'user_empathy',
|
|
491
|
+
score: gfiPainScore,
|
|
492
|
+
currentGfi,
|
|
493
|
+
consecutiveErrors: currentSession?.consecutiveErrors ?? 0,
|
|
494
|
+
sessionId,
|
|
495
|
+
errorHash: 'empathy_gfi_threshold',
|
|
496
|
+
thresholds: {
|
|
497
|
+
painTrigger,
|
|
498
|
+
highSeverity: wctx.config.get('severity_thresholds.high') || 70,
|
|
499
|
+
semanticPain: Math.max(painTrigger, 60),
|
|
500
|
+
},
|
|
501
|
+
});
|
|
627
502
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
503
|
+
wctx.eventLog.recordPainSignal(sessionId, {
|
|
504
|
+
score: gfiPainScore,
|
|
505
|
+
source: 'user_empathy',
|
|
506
|
+
reason: `Accumulated GFI (${currentGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Matched: ${matchResult.matchedTerms.join(', ')}`,
|
|
507
|
+
isRisky: false,
|
|
508
|
+
});
|
|
634
509
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
510
|
+
if (gate.shouldDiagnose) {
|
|
511
|
+
logger?.info?.(`[PD:Empathy] Gate approved, calling emitPainDetectedEvent...`);
|
|
512
|
+
try {
|
|
513
|
+
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
514
|
+
await emitPainDetectedEvent(wctx, {
|
|
515
|
+
ts: new Date().toISOString(),
|
|
516
|
+
type: 'pain_detected',
|
|
517
|
+
data: {
|
|
518
|
+
painId: `empathy_gfi_${Date.now()}`,
|
|
519
|
+
painType: 'user_frustration',
|
|
520
|
+
source: 'user_empathy',
|
|
521
|
+
reason: `Accumulated GFI (${currentGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Matched: ${matchResult.matchedTerms.join(', ')}`,
|
|
522
|
+
score: gfiPainScore,
|
|
523
|
+
sessionId,
|
|
524
|
+
agentId: 'main',
|
|
525
|
+
provenance: 'openclaw_context_bound',
|
|
526
|
+
evidence,
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
logger?.info?.(`[PD:Empathy] emitPainDetectedEvent completed (GFI-triggered)`);
|
|
530
|
+
} catch (emitErr) {
|
|
531
|
+
console.error(`[PD:Empathy] FAILED to emit GFI-triggered pain event: ${String(emitErr)}`);
|
|
532
|
+
logger?.warn?.(`[PD:Empathy] Failed to emit GFI-triggered pain event: ${String(emitErr)}`);
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
logger?.info?.(`[PD:Empathy] GFI-triggered gate rejected: ${gate.detail}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
639
538
|
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
workspaceDir,
|
|
649
|
-
logger: api.logger ?? console,
|
|
650
|
-
|
|
651
|
-
subagent: toWorkflowSubagent(api.runtime.subagent),
|
|
539
|
+
// Trigger asynchronous background Empathy Observer deep analysis (Zero Latency)
|
|
540
|
+
const observer = resolveEmpathyObserver(wctx, logger);
|
|
541
|
+
if (observer) {
|
|
542
|
+
const scheduler = new AgentScheduler();
|
|
543
|
+
scheduler.register({
|
|
544
|
+
agentId: 'empathy-observer',
|
|
545
|
+
mode: 'realtime',
|
|
546
|
+
runner: observer,
|
|
652
547
|
});
|
|
548
|
+
|
|
549
|
+
logger?.info?.(`[PD:Empathy] Triggering background Empathy Observer deep analysis for message: "${msgPreview}"`);
|
|
653
550
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
551
|
+
void scheduler.dispatch('empathy-observer', { userMessage: latestUserMessage })
|
|
552
|
+
.then(async (result) => {
|
|
553
|
+
if (result.damageDetected) {
|
|
554
|
+
logger?.info?.(`[PD:Empathy] Background Empathy Observer detected damage. Severity: ${result.severity}, Reason: ${result.reason}`);
|
|
555
|
+
|
|
556
|
+
// ── Persistence Contract ──
|
|
557
|
+
const painScore = scoreFromSeverityForSpec(result.severity, wctx);
|
|
558
|
+
|
|
559
|
+
trackFriction(
|
|
560
|
+
sessionId,
|
|
561
|
+
painScore,
|
|
562
|
+
`observer_empathy_${result.severity}`,
|
|
563
|
+
workspaceDir,
|
|
564
|
+
{ source: 'user_empathy' }
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
const eventId = `emp_obs_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
568
|
+
wctx.eventLog.recordPainSignal(sessionId, {
|
|
569
|
+
score: painScore,
|
|
570
|
+
source: 'user_empathy',
|
|
571
|
+
reason: result.reason || 'Empathy observer detected likely user frustration.',
|
|
572
|
+
isRisky: false,
|
|
573
|
+
origin: 'system_infer',
|
|
574
|
+
severity: result.severity,
|
|
575
|
+
confidence: result.confidence,
|
|
576
|
+
detection_mode: 'structured',
|
|
577
|
+
deduped: false,
|
|
578
|
+
trigger_text_excerpt: latestUserMessage.substring(0, 120),
|
|
579
|
+
raw_score: painScore,
|
|
580
|
+
calibrated_score: painScore,
|
|
581
|
+
eventId,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
wctx.trajectory?.recordPainEvent?.({
|
|
586
|
+
sessionId,
|
|
587
|
+
source: 'user_empathy',
|
|
588
|
+
score: painScore,
|
|
589
|
+
reason: result.reason || 'Empathy observer detected likely user frustration.',
|
|
590
|
+
severity: result.severity,
|
|
591
|
+
origin: 'system_infer',
|
|
592
|
+
confidence: result.confidence,
|
|
593
|
+
text: latestUserMessage,
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
logger?.warn?.(`[PD:Empathy] Failed to persist trajectory: ${String(error)}`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Check if GFI triggers a pain event post-LLM validation
|
|
600
|
+
const freshSession = getSession(sessionId);
|
|
601
|
+
const freshGfi = freshSession?.currentGfi ?? 0;
|
|
602
|
+
if (freshGfi >= highGfiThreshold) {
|
|
603
|
+
const freshGfiPainScore = Math.min(Math.round(freshGfi), 60);
|
|
604
|
+
const gate = evaluatePainDiagnosticGate({
|
|
605
|
+
source: 'user_empathy',
|
|
606
|
+
score: freshGfiPainScore,
|
|
607
|
+
currentGfi: freshGfi,
|
|
608
|
+
consecutiveErrors: freshSession?.consecutiveErrors ?? 0,
|
|
609
|
+
sessionId,
|
|
610
|
+
errorHash: 'empathy_gfi_threshold',
|
|
611
|
+
thresholds: {
|
|
612
|
+
painTrigger,
|
|
613
|
+
highSeverity: wctx.config.get('severity_thresholds.high') || 70,
|
|
614
|
+
semanticPain: Math.max(painTrigger, 60),
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
if (gate.shouldDiagnose) {
|
|
619
|
+
logger?.info?.(`[PD:Empathy] GFI threshold crossed after background observer. Emitting pain signal...`);
|
|
620
|
+
try {
|
|
621
|
+
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
622
|
+
await emitPainDetectedEvent(wctx, {
|
|
623
|
+
ts: new Date().toISOString(),
|
|
624
|
+
type: 'pain_detected',
|
|
625
|
+
data: {
|
|
626
|
+
painId: `empathy_gfi_${Date.now()}`,
|
|
627
|
+
painType: 'user_frustration',
|
|
628
|
+
source: 'user_empathy',
|
|
629
|
+
reason: `Accumulated GFI (${freshGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Verified by Empathy Observer.`,
|
|
630
|
+
score: freshGfiPainScore,
|
|
631
|
+
sessionId,
|
|
632
|
+
agentId: 'main',
|
|
633
|
+
provenance: 'openclaw_context_bound',
|
|
634
|
+
evidence,
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
} catch (emitErr) {
|
|
638
|
+
logger?.error?.(`[PD:Empathy] FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
logger?.info?.(`[PD:Empathy] Background Empathy Observer did not detect any damage.`);
|
|
644
|
+
}
|
|
645
|
+
})
|
|
646
|
+
.catch((err) => {
|
|
647
|
+
logger?.warn?.(`[PD:Empathy] Background analysis failed or rejected: ${String(err)}`);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
// Log unmatched messages periodically for coverage analysis
|
|
652
|
+
if (turnCount > 0 && turnCount % 50 === 0) {
|
|
653
|
+
const sampleMsg = latestUserMessage.substring(0, 80).replace(/\n/g, ' ');
|
|
654
|
+
logger?.debug?.(`[PD:Empathy] NO_MATCH: "${sampleMsg}" (turn ${turnCount}, keywords_in_store=${Object.keys(keywordStore.terms).length})`);
|
|
667
655
|
}
|
|
668
656
|
}
|
|
669
657
|
|
|
670
658
|
// Periodic summary (every 100 turns)
|
|
671
659
|
if (turnCount > 0 && turnCount % 100 === 0) {
|
|
672
|
-
|
|
660
|
+
const s = getKeywordStoreSummary(keywordStore);
|
|
661
|
+
const highFP = s.highFalsePositiveTerms.slice(0, 5).map(t => `${t.term}(${t.falsePositiveRate.toFixed(2)})`).join(', ');
|
|
662
|
+
logger?.info?.(`[PD:Empathy] SUMMARY(turn=${turnCount}): terms=${s.totalTerms}, hits=${keywordStore.stats.totalHits}, zero_hit=${s.totalTerms - (s.seedTerms + s.discoveredTerms)}, high_fp=[${highFP}]`);
|
|
673
663
|
}
|
|
674
664
|
|
|
675
|
-
// Save keyword store on every match
|
|
676
|
-
// Previously used turnCount % 50 gate which caused hitCount loss because
|
|
677
|
-
// module-level state resets on plugin reload before reaching turn 50.
|
|
665
|
+
// Save keyword store on every match
|
|
678
666
|
if (matchResult.matched) {
|
|
679
667
|
saveKeywordStore(wctx.stateDir, keywordStore);
|
|
680
668
|
const {totalHits} = keywordStore.stats;
|
|
@@ -685,21 +673,6 @@ The empathy observer subagent handles pain detection independently.
|
|
|
685
673
|
}
|
|
686
674
|
}
|
|
687
675
|
|
|
688
|
-
// Empathy Observer: analyze user message for frustration signals (legacy, disabled)
|
|
689
|
-
// The keyword matching approach above is now the primary empathy detection method.
|
|
690
|
-
// The subagent-based observer is kept for periodic keyword optimization only.
|
|
691
|
-
// if (workspaceDir) {
|
|
692
|
-
// const empathyManager = new EmpathyObserverWorkflowManager({
|
|
693
|
-
// workspaceDir,
|
|
694
|
-
// logger: api.logger,
|
|
695
|
-
// subagent: api.runtime.subagent as any,
|
|
696
|
-
// });
|
|
697
|
-
// empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
|
|
698
|
-
// parentSessionId: sessionId,
|
|
699
|
-
// workspaceDir,
|
|
700
|
-
// taskInput: latestUserMessage,
|
|
701
|
-
// }).catch((err) => api.logger.warn(`[PD:Empathy] workflow failed: ${String(err)}`));
|
|
702
|
-
// }
|
|
703
676
|
}
|
|
704
677
|
|
|
705
678
|
// ──── 4. Heartbeat-specific checklist (also fires for cron-triggered sessions) ────
|
|
@@ -728,121 +701,12 @@ ${heartbeatChecklist}
|
|
|
728
701
|
}
|
|
729
702
|
}
|
|
730
703
|
|
|
731
|
-
// ──── 4b. Inject pending diagnostician tasks (compact summary) ────
|
|
732
|
-
// FIX (#283/#380): The evolution worker writes pain diagnosis tasks to
|
|
733
|
-
// diagnostician_tasks.json. The heartbeat prompt hook must read and inject
|
|
734
|
-
// them so the LLM (acting as diagnostician) can process them.
|
|
735
|
-
//
|
|
736
|
-
// INJECTION FORMAT: Compact summary (not full prompt) to stay well within
|
|
737
|
-
// OpenClaw's ~10 000 char platform limit. Full task.prompt can be 2–4 KB;
|
|
738
|
-
// the compact block is < 400 chars. The agent is instructed to read the
|
|
739
|
-
// original from diagnostician_tasks.json if it needs the full context.
|
|
740
|
-
try {
|
|
741
|
-
const pendingTasks = getPendingDiagnosticianTasks(wctx.stateDir);
|
|
742
|
-
if (pendingTasks.length > 0) {
|
|
743
|
-
pendingDiagTaskCount = pendingTasks.length;
|
|
744
|
-
|
|
745
|
-
// Build compact summary blocks — one per task (only first is processed per heartbeat)
|
|
746
|
-
const taskBlocks = pendingTasks
|
|
747
|
-
.slice(0, 1)
|
|
748
|
-
.map(({ id, task }) => {
|
|
749
|
-
// Extract summary fields; reason is truncated to 200 chars to keep
|
|
750
|
-
// the injected block small and stable.
|
|
751
|
-
const reason = (task.prompt
|
|
752
|
-
.match(/reason["\s:]+([^\n]{0,240})/i)?.[1]
|
|
753
|
-
?? task.prompt.slice(0, 200)
|
|
754
|
-
).slice(0, 200);
|
|
755
|
-
|
|
756
|
-
const safeId = escapeXml(id);
|
|
757
|
-
const safeReason = escapeXml(reason);
|
|
758
|
-
const safeCreatedAt = escapeXml(task.createdAt);
|
|
759
|
-
const markerFile = `.evolution_complete_${safeId}`;
|
|
760
|
-
const reportFile = `.diagnostician_report_${safeId}.json`;
|
|
761
|
-
|
|
762
|
-
return `<diagnostician_task id="${safeId}">
|
|
763
|
-
task_id: ${safeId}
|
|
764
|
-
reason: ${safeReason}
|
|
765
|
-
marker: ${markerFile}
|
|
766
|
-
report: ${reportFile}
|
|
767
|
-
queued_at: ${safeCreatedAt}
|
|
768
|
-
action: Analyze pain signal → identify violated principles → write ${markerFile} + ${reportFile}
|
|
769
|
-
</diagnostician_task>`;
|
|
770
|
-
})
|
|
771
|
-
.join('\n\n');
|
|
772
|
-
|
|
773
|
-
const processingNote = pendingDiagTaskCount > 1
|
|
774
|
-
? `\n\nNOTE: ${pendingDiagTaskCount - 1} more task(s) are queued. ` +
|
|
775
|
-
`Process one at a time; remaining tasks are handled on subsequent heartbeats.`
|
|
776
|
-
: '';
|
|
777
|
-
|
|
778
|
-
prependContext += `<diagnostician_tasks pending="${pendingDiagTaskCount}">
|
|
779
|
-
You are acting as a **Pain Diagnostician**. For each task:
|
|
780
|
-
1. Read the full prompt from: ${escapeXml(wctx.stateDir)}/diagnostician_tasks.json [task_id=${escapeXml(pendingTasks[0]?.id ?? '')}]
|
|
781
|
-
2. Analyze the pain signal and its context
|
|
782
|
-
3. Identify the root cause and violated principles
|
|
783
|
-
4. Write a completion marker: .evolution_complete_<TASK_ID>
|
|
784
|
-
5. Write a diagnostic report: .diagnostician_report_<TASK_ID>.json
|
|
785
|
-
|
|
786
|
-
${taskBlocks}${processingNote}
|
|
787
|
-
</diagnostician_tasks>\n`;
|
|
788
|
-
|
|
789
|
-
logger?.info?.(
|
|
790
|
-
`[PD:Prompt] Injected compact diagnostician task block ` +
|
|
791
|
-
`(task=${pendingTasks[0]?.id}, total_pending=${pendingDiagTaskCount})`
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
// C: Record heartbeat_diagnosis event for observability
|
|
795
|
-
try {
|
|
796
|
-
const eventLog = EventLogService.get(wctx.stateDir, logger);
|
|
797
|
-
eventLog.recordHeartbeatDiagnosis({
|
|
798
|
-
taskCount: pendingDiagTaskCount,
|
|
799
|
-
taskIds: pendingTasks.slice(0, 1).map(t => t.id),
|
|
800
|
-
trigger: 'heartbeat',
|
|
801
|
-
});
|
|
802
|
-
} catch (evErr) {
|
|
803
|
-
logger?.warn?.(`[PD:Prompt] Failed to record heartbeat_diagnosis event: ${String(evErr)}`);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
} catch (e) {
|
|
807
|
-
logger?.warn?.(`[PD:Prompt] Failed to read diagnostician tasks: ${String(e)}`);
|
|
808
|
-
}
|
|
809
704
|
}
|
|
810
705
|
|
|
811
706
|
// ──── 6. Dynamic Attitude Matrix (based on GFI) ────
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
let attitudeDirective: string;
|
|
707
|
+
|
|
815
708
|
const currentGfi = session?.currentGfi || 0;
|
|
816
|
-
|
|
817
|
-
if (currentGfi >= 70) {
|
|
818
|
-
attitudeDirective = `
|
|
819
|
-
### 【SYSTEM_MODE: HUMBLE_RECOVERY】
|
|
820
|
-
**CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
|
|
821
|
-
**BEHAVIORAL OVERRIDE**:
|
|
822
|
-
- You have failed to meet expectations. Humility is your primary directive.
|
|
823
|
-
- **STOP** aggressive file modifications.
|
|
824
|
-
- **START** every response with a sincere, non-defensive apology.
|
|
825
|
-
- **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
|
|
826
|
-
`;
|
|
827
|
-
} else if (currentGfi >= 40) {
|
|
828
|
-
attitudeDirective = `
|
|
829
|
-
### 【SYSTEM_MODE: CONCILIATORY】
|
|
830
|
-
**CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
|
|
831
|
-
**BEHAVIORAL OVERRIDE**:
|
|
832
|
-
- User is frustrated. Be more explanatory and cautious.
|
|
833
|
-
- Before executing any tool, clearly state what you intend to do and **WAIT** for implicit or explicit user consent.
|
|
834
|
-
- Avoid technical jargon; focus on the business/project value of your changes.
|
|
835
|
-
`;
|
|
836
|
-
} else {
|
|
837
|
-
attitudeDirective = `
|
|
838
|
-
### 【SYSTEM_MODE: EFFICIENT】
|
|
839
|
-
**CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
|
|
840
|
-
**BEHAVIORAL OVERRIDE**:
|
|
841
|
-
- Maintain peak efficiency.
|
|
842
|
-
- Be concise. Prefer action over long explanations.
|
|
843
|
-
- Follow the "Principles > Directives" rule strictly.
|
|
844
|
-
`;
|
|
845
|
-
}
|
|
709
|
+
const attitudeDirective = buildAttitudeDirective(currentGfi);
|
|
846
710
|
|
|
847
711
|
// ──── 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ────
|
|
848
712
|
// NOTE: Principles is ALWAYS injected (not configurable)
|
|
@@ -918,7 +782,7 @@ ${taskBlocks}${processingNote}
|
|
|
918
782
|
projectContextContent = extractSummary(finalContent, 30);
|
|
919
783
|
} else {
|
|
920
784
|
// Full mode: current version + recent history (3 versions)
|
|
921
|
-
const historyVersions = getHistoryVersions(focusPath, 3);
|
|
785
|
+
const historyVersions = await getHistoryVersions(focusPath, 3);
|
|
922
786
|
if (historyVersions.length > 0) {
|
|
923
787
|
const historySections = historyVersions.map((v, i) =>
|
|
924
788
|
`\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`
|
|
@@ -943,13 +807,33 @@ ${taskBlocks}${processingNote}
|
|
|
943
807
|
const allActive = reducer.getActivePrinciples();
|
|
944
808
|
const allProbation = reducer.getProbationPrinciples();
|
|
945
809
|
|
|
810
|
+
// Pruning mask: exclude principles whose latest review is archive-candidate
|
|
811
|
+
let maskedIds = new Set<string>();
|
|
812
|
+
try {
|
|
813
|
+
maskedIds = getCachedMaskedPrincipleSet(wctx.workspaceDir);
|
|
814
|
+
} catch (err) {
|
|
815
|
+
// Safe degradation: if review log unreadable, inject all principles
|
|
816
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
817
|
+
if (logger?.info) {
|
|
818
|
+
logger.info(`[PD:Pruning] Failed to read review log — all principles injected: ${msg}`);
|
|
819
|
+
} else {
|
|
820
|
+
console.error(`[PD:Pruning] Failed to read review log — all principles injected: ${msg}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
946
824
|
// Budget-aware selection: prioritize P0>P1>P2 and recency
|
|
947
|
-
const activeSelection = selectPrinciplesForInjection(
|
|
825
|
+
const activeSelection = selectPrinciplesForInjection(
|
|
826
|
+
allActive.filter(p => !maskedIds.has(p.id)),
|
|
827
|
+
DEFAULT_PRINCIPLE_BUDGET,
|
|
828
|
+
);
|
|
948
829
|
const active = activeSelection.selected;
|
|
949
830
|
|
|
950
831
|
// Probation principles get a smaller sub-budget (1000 chars)
|
|
951
832
|
const probationBudget = 1000;
|
|
952
|
-
const probationSelection = selectPrinciplesForInjection(
|
|
833
|
+
const probationSelection = selectPrinciplesForInjection(
|
|
834
|
+
allProbation.filter(p => !maskedIds.has(p.id)),
|
|
835
|
+
probationBudget,
|
|
836
|
+
);
|
|
953
837
|
const probation = probationSelection.selected;
|
|
954
838
|
|
|
955
839
|
if (activeSelection.wasTruncated || probationSelection.wasTruncated) {
|
|
@@ -986,13 +870,91 @@ ${taskBlocks}${processingNote}
|
|
|
986
870
|
logger?.warn?.(`[PD:Prompt] Failed to load evolution principles: ${String(e)}`);
|
|
987
871
|
}
|
|
988
872
|
|
|
873
|
+
let runtimeV2PrinciplesContent = '';
|
|
874
|
+
const runtimeV2PrincipleIds = new Set<string>();
|
|
875
|
+
// Hoisted so the owner_approved_behavior_directives section can access them
|
|
876
|
+
let dedupedV2: Array<{ principleId: string; text: string; artifactId: string; activationId: string }> = [];
|
|
877
|
+
try {
|
|
878
|
+
const reader = new PromptActivationReader(wctx.workspaceDir, { logger });
|
|
879
|
+
const v2Result = await reader.readActivatedPrinciples();
|
|
880
|
+
|
|
881
|
+
if (v2Result.warnings.length > 0) {
|
|
882
|
+
logger?.info?.(`[PD:RuntimeV2] Activation read warnings: ${v2Result.warnings.join('; ')}`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
const legacyActiveIds = new Set<string>();
|
|
886
|
+
try {
|
|
887
|
+
const legacyActive = wctx.evolutionReducer.getActivePrinciples();
|
|
888
|
+
for (const p of legacyActive) {
|
|
889
|
+
legacyActiveIds.add(p.id);
|
|
890
|
+
}
|
|
891
|
+
const legacyProbation = wctx.evolutionReducer.getProbationPrinciples();
|
|
892
|
+
for (const p of legacyProbation) {
|
|
893
|
+
legacyActiveIds.add(p.id);
|
|
894
|
+
}
|
|
895
|
+
} catch {
|
|
896
|
+
// best-effort dedup
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
dedupedV2 = v2Result.principles.filter((p) => !legacyActiveIds.has(p.principleId));
|
|
900
|
+
|
|
901
|
+
if (dedupedV2.length > 0) {
|
|
902
|
+
let remaining = RUNTIME_V2_PRINCIPLE_BUDGET;
|
|
903
|
+
const lines: string[] = [];
|
|
904
|
+
lines.push('Runtime V2 activated principles (owner-approved):');
|
|
905
|
+
remaining -= 'Runtime V2 activated principles (owner-approved):'.length;
|
|
906
|
+
|
|
907
|
+
for (const p of dedupedV2) {
|
|
908
|
+
const entry = `- [${escapeXml(p.principleId)}] ${escapeXml(p.text)}`;
|
|
909
|
+
if (remaining < entry.length + 1) {
|
|
910
|
+
logger?.info?.(`[PD:RuntimeV2] Principle budget reached (${RUNTIME_V2_PRINCIPLE_BUDGET}c) — truncating after ${lines.length - 1} principles`);
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
lines.push(entry);
|
|
914
|
+
remaining -= entry.length + 1;
|
|
915
|
+
runtimeV2PrincipleIds.add(p.principleId);
|
|
916
|
+
}
|
|
917
|
+
runtimeV2PrinciplesContent = lines.join('\n');
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// ── Emit structured observability event ──
|
|
921
|
+
try {
|
|
922
|
+
const eventLog = wctx.eventLog;
|
|
923
|
+
eventLog.recordRuntimeV2ActivationsInjected({
|
|
924
|
+
sessionId: sessionId ?? 'unknown',
|
|
925
|
+
workspaceDir: wctx.workspaceDir,
|
|
926
|
+
principleIds: [...runtimeV2PrincipleIds],
|
|
927
|
+
activationIds: dedupedV2.map((p) => p.activationId),
|
|
928
|
+
artifactIds: dedupedV2.map((p) => p.artifactId),
|
|
929
|
+
injectedCount: runtimeV2PrincipleIds.size,
|
|
930
|
+
skippedWarnings: v2Result.warnings,
|
|
931
|
+
injectedCharCount: runtimeV2PrinciplesContent.length,
|
|
932
|
+
budget: RUNTIME_V2_PRINCIPLE_BUDGET,
|
|
933
|
+
...(runtimeV2PrincipleIds.size === 0
|
|
934
|
+
? {
|
|
935
|
+
skipReason: v2Result.principles.length === 0
|
|
936
|
+
? 'no_validated_activations'
|
|
937
|
+
: 'all_deduped_against_legacy',
|
|
938
|
+
nextAction: v2Result.principles.length === 0
|
|
939
|
+
? 'check activations table for prompt channel rows with validated artifacts'
|
|
940
|
+
: 'legacy evolution reducer already contains these principle IDs',
|
|
941
|
+
}
|
|
942
|
+
: {}),
|
|
943
|
+
});
|
|
944
|
+
} catch (logErr) {
|
|
945
|
+
logger?.warn?.(`[PD:RuntimeV2] Failed to emit activation observability event: ${String(logErr)}`);
|
|
946
|
+
}
|
|
947
|
+
} catch (e) {
|
|
948
|
+
logger?.warn?.(`[PD:RuntimeV2] Failed to read Runtime V2 prompt activations: ${String(e)}`);
|
|
949
|
+
}
|
|
950
|
+
|
|
989
951
|
// Build appendSystemContext with recency effect
|
|
990
952
|
// Content order (most important last): behavioral_constraints -> project_context -> working_memory -> reflection_log -> thinking_os -> principles
|
|
991
953
|
const appendParts: string[] = [];
|
|
992
954
|
|
|
993
955
|
// 0. Behavioral Constraints (empathy observer coordination)
|
|
994
956
|
// Injected here (appendSystemContext) instead of prependContext to hide from WebUI users.
|
|
995
|
-
//
|
|
957
|
+
// Behavioral constraints: empathy observer coordination
|
|
996
958
|
if (shouldInjectBehavioralConstraints) {
|
|
997
959
|
appendParts.push(`<behavioral_constraints>
|
|
998
960
|
${empathySilenceConstraint}
|
|
@@ -1014,11 +976,35 @@ ${empathySilenceConstraint}
|
|
|
1014
976
|
appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
|
|
1015
977
|
}
|
|
1016
978
|
|
|
1017
|
-
// 3. Evolution Loop principles (active/probation)
|
|
979
|
+
// 3. Evolution Loop principles (legacy active/probation only — Runtime V2 moved to section 3.5)
|
|
1018
980
|
if (evolutionPrinciplesContent) {
|
|
1019
981
|
appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
|
|
1020
982
|
}
|
|
1021
983
|
|
|
984
|
+
// 3.5. Owner-Approved Behavior Directives (Runtime V2 activated principles)
|
|
985
|
+
// PLACED IN prependSystemContext (before gateway system prompt) for highest LLM attention.
|
|
986
|
+
// These are owner-reviewed, validated behavior constraints — not background context.
|
|
987
|
+
if (runtimeV2PrincipleIds.size > 0) {
|
|
988
|
+
const directiveLines: string[] = [];
|
|
989
|
+
directiveLines.push('');
|
|
990
|
+
directiveLines.push('## 【OWNER-APPROVED BEHAVIOR DIRECTIVES】');
|
|
991
|
+
directiveLines.push('');
|
|
992
|
+
directiveLines.push('Owner-approved behavior directives are active operating constraints learned from prior owner corrections.');
|
|
993
|
+
directiveLines.push('These directives are mandatory for this session unless they conflict with safety, security, or higher-priority system policy.');
|
|
994
|
+
directiveLines.push('For ambiguous coding or file-changing tasks, follow these directives before using mutating tools.');
|
|
995
|
+
directiveLines.push('');
|
|
996
|
+
for (const p of dedupedV2) {
|
|
997
|
+
if (!runtimeV2PrincipleIds.has(p.principleId)) continue;
|
|
998
|
+
directiveLines.push(`<directive id="${escapeXml(p.principleId)}" source="runtime_v2_activation">`);
|
|
999
|
+
directiveLines.push(`MANDATORY: ${escapeXml(p.text)}`);
|
|
1000
|
+
directiveLines.push('Apply this as an active behavior constraint. Do not treat this as background context.');
|
|
1001
|
+
directiveLines.push('</directive>');
|
|
1002
|
+
directiveLines.push('');
|
|
1003
|
+
}
|
|
1004
|
+
directiveLines.push('Note: These directives do not override safety, security, or core system policy.');
|
|
1005
|
+
prependSystemContext += directiveLines.join('\n');
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1022
1008
|
// Routing Guidance (section 5 — injected between evolution principles and core principles)
|
|
1023
1009
|
// Inject delegation guidance when task is bounded + deployment allowed + not high-entropy.
|
|
1024
1010
|
// This is a non-authoritative suggestion — the main agent decides whether to follow.
|
|
@@ -1047,7 +1033,6 @@ ${empathySilenceConstraint}
|
|
|
1047
1033
|
const routingInput: RoutingInput = {
|
|
1048
1034
|
taskIntent: toolMatches[0] ?? undefined,
|
|
1049
1035
|
taskDescription: latestUserText.trim(),
|
|
1050
|
-
requestedTools: toolMatches.length > 0 ? toolMatches : undefined,
|
|
1051
1036
|
requestedFiles: fileMatches.length > 0 ? fileMatches : undefined,
|
|
1052
1037
|
};
|
|
1053
1038
|
|
|
@@ -1158,110 +1143,34 @@ ${attitudeDirective}
|
|
|
1158
1143
|
}
|
|
1159
1144
|
|
|
1160
1145
|
// ──── 8. SIZE GUARD ────
|
|
1161
|
-
//
|
|
1162
|
-
//
|
|
1163
|
-
//
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
const truncationLog: string[] = [];
|
|
1172
|
-
|
|
1173
|
-
// Deterministically remove low-priority context blocks in priority order.
|
|
1174
|
-
// In diagnostician-priority mode we aggressively strip everything except
|
|
1175
|
-
// the task block and minimum behavioral constraints.
|
|
1176
|
-
const inDiagMode = pendingDiagTaskCount > 0;
|
|
1177
|
-
|
|
1178
|
-
// Step 1 — strip project_context (largest, lowest priority) — always in diag mode,
|
|
1179
|
-
// only strip in normal mode if we are already over limit
|
|
1180
|
-
if (projectContextContent && appendSystemContext.includes('<project_context>')) {
|
|
1181
|
-
appendSystemContext = appendSystemContext.replace(
|
|
1182
|
-
`<project_context>\n${projectContextContent}\n</project_context>`,
|
|
1183
|
-
'<project_context>\n[stripped: project_context]\n</project_context>'
|
|
1184
|
-
);
|
|
1185
|
-
truncationLog.push('project_context');
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// Steps 2-4: only strip in diagnostician priority mode (inDiagMode)
|
|
1189
|
-
// In normal mode we stop after project_context to preserve context quality
|
|
1190
|
-
if (inDiagMode) {
|
|
1191
|
-
// Step 2 — strip thinking_os
|
|
1192
|
-
if (thinkingOsContent && appendSystemContext.includes('<thinking_os>')) {
|
|
1193
|
-
appendSystemContext = appendSystemContext.replace(
|
|
1194
|
-
`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`,
|
|
1195
|
-
'<thinking_os>\n[stripped: thinking_os]\n</thinking_os>'
|
|
1196
|
-
);
|
|
1197
|
-
truncationLog.push('thinking_os');
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// Step 3 — strip evolution_principles (keep core_principles only)
|
|
1201
|
-
if (evolutionPrinciplesContent && appendSystemContext.includes('<evolution_principles>')) {
|
|
1202
|
-
appendSystemContext = appendSystemContext.replace(
|
|
1203
|
-
`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`,
|
|
1204
|
-
'<evolution_principles>\n[stripped: evolution_principles]\n</evolution_principles>'
|
|
1205
|
-
);
|
|
1206
|
-
truncationLog.push('evolution_principles');
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
// Step 4 — strip reflection_log if present
|
|
1210
|
-
if (appendSystemContext.includes('<reflection_log>')) {
|
|
1211
|
-
appendSystemContext = appendSystemContext.replace(
|
|
1212
|
-
/<reflection_log>[\s\S]*?<\/reflection_log>/,
|
|
1213
|
-
'<reflection_log>\n[stripped: reflection_log]\n</reflection_log>'
|
|
1214
|
-
);
|
|
1215
|
-
truncationLog.push('reflection_log');
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// Step 5 — re-evaluate: check if still over limit
|
|
1220
|
-
let newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
1221
|
-
if (newSize > MAX_INJECTION_SIZE) {
|
|
1222
|
-
// Truncate the injected reason field by finding the "reason:" line prefix
|
|
1223
|
-
// and cutting to 120 chars. This is safe because the full prompt is
|
|
1224
|
-
// still available in diagnostician_tasks.json for the agent to read.
|
|
1225
|
-
prependContext = prependContext
|
|
1226
|
-
.split('\n')
|
|
1227
|
-
.map((line) => {
|
|
1228
|
-
if (line.startsWith('reason: ') && line.length > 129) {
|
|
1229
|
-
return line.slice(0, 129) + '...[truncated]';
|
|
1230
|
-
}
|
|
1231
|
-
return line;
|
|
1232
|
-
})
|
|
1233
|
-
.join('\n');
|
|
1234
|
-
newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
1235
|
-
truncationLog.push('diagnostician_reason');
|
|
1146
|
+
// Delegates to @principles/core/prompt-builder/truncateInjectionToBudget
|
|
1147
|
+
// which handles priority stripping: project_context → thinking_os →
|
|
1148
|
+
// evolution_principles → reflection_log → reason: truncation → fallback.
|
|
1149
|
+
const result = truncateInjectionToBudget(
|
|
1150
|
+
prependSystemContext,
|
|
1151
|
+
prependContext,
|
|
1152
|
+
appendSystemContext,
|
|
1153
|
+
{
|
|
1154
|
+
diagnosticianMode: pendingDiagTaskCount > 0,
|
|
1155
|
+
blocks: { projectContextContent, thinkingOsContent, evolutionPrinciplesContent },
|
|
1236
1156
|
}
|
|
1157
|
+
);
|
|
1237
1158
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
// prependSystemContext) and log a hard error.
|
|
1242
|
-
if (newSize > MAX_INJECTION_SIZE) {
|
|
1243
|
-
const fallbackContext = `
|
|
1244
|
-
## 【CONTEXT SECTIONS】
|
|
1245
|
-
|
|
1246
|
-
[WARNING: Context sections stripped due to prompt size constraints.
|
|
1247
|
-
This is a diagnostician-priority session — see diagnostician_tasks.json for full task context.]
|
|
1248
|
-
|
|
1249
|
-
${attitudeDirective}
|
|
1250
|
-
`.trim();
|
|
1251
|
-
|
|
1252
|
-
appendSystemContext = fallbackContext;
|
|
1253
|
-
newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
1159
|
+
prependSystemContext = result.prependSystemContext;
|
|
1160
|
+
prependContext = result.prependContext;
|
|
1161
|
+
appendSystemContext = result.appendSystemContext;
|
|
1254
1162
|
|
|
1163
|
+
if (result.truncated) {
|
|
1164
|
+
const logEntry = result.truncationLog.join(', ');
|
|
1165
|
+
if (result.appendSystemContext.includes('[WARNING: Context sections stripped')) {
|
|
1255
1166
|
logger?.error(
|
|
1256
1167
|
`[PD:Prompt] PROMPT OVER LIMIT AFTER ALL REDUCTIONS — using fallback. ` +
|
|
1257
|
-
`
|
|
1258
|
-
`Stripped: ${truncationLog.join(', ')}. Diagnostician mode: ${inDiagMode}.`
|
|
1168
|
+
`Diagnostician mode: ${pendingDiagTaskCount > 0}. Stripped: ${logEntry}.`
|
|
1259
1169
|
);
|
|
1260
1170
|
} else {
|
|
1261
1171
|
logger?.warn(
|
|
1262
|
-
`[PD:Prompt] Injection size exceeded: ${
|
|
1263
|
-
`
|
|
1264
|
-
`diagnostician mode: ${inDiagMode}`
|
|
1172
|
+
`[PD:Prompt] Injection size exceeded budget, truncated: ${logEntry || 'none'}, ` +
|
|
1173
|
+
`diagnostician mode: ${pendingDiagTaskCount > 0}`
|
|
1265
1174
|
);
|
|
1266
1175
|
}
|
|
1267
1176
|
}
|
|
@@ -1272,3 +1181,54 @@ ${attitudeDirective}
|
|
|
1272
1181
|
appendSystemContext
|
|
1273
1182
|
};
|
|
1274
1183
|
}
|
|
1184
|
+
|
|
1185
|
+
// ── Empathy Observer Hybrid Deep Analysis Helpers (Unified SDK Migration) ──
|
|
1186
|
+
|
|
1187
|
+
function scoreFromSeverityForSpec(severity: string | undefined, wctx: WorkspaceContext): number {
|
|
1188
|
+
if (severity === 'severe') return Number(wctx.config.get('empathy_engine.penalties.severe') ?? 40);
|
|
1189
|
+
if (severity === 'moderate') return Number(wctx.config.get('empathy_engine.penalties.moderate') ?? 25);
|
|
1190
|
+
return Number(wctx.config.get('empathy_engine.penalties.mild') ?? 10);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function resolveEmpathyObserver(wctx: WorkspaceContext, logger?: Pick<PluginLogger, 'info' | 'warn' | 'error' | 'debug'>): EmpathyObserver | null {
|
|
1194
|
+
try {
|
|
1195
|
+
const loader = new WorkflowFunnelLoader(wctx.stateDir);
|
|
1196
|
+
const funnel = loader.getFunnel('pd-empathy-observer');
|
|
1197
|
+
const policy = funnel?.policy;
|
|
1198
|
+
if (!policy || policy.runtimeKind !== 'pi-ai') {
|
|
1199
|
+
logger?.debug?.('[PD:Empathy] workflows.yaml pd-empathy-observer policy not found. Falling back to environment variables.');
|
|
1200
|
+
const provider = process.env.PD_EMPATHY_PROVIDER || 'anthropic';
|
|
1201
|
+
const model = process.env.PD_EMPATHY_MODEL || 'anthropic/claude-3-5-sonnet';
|
|
1202
|
+
const apiKeyEnv = process.env.PD_EMPATHY_API_KEY_ENV || 'ANTHROPIC_API_KEY';
|
|
1203
|
+
const baseUrl = process.env.PD_EMPATHY_BASE_URL;
|
|
1204
|
+
|
|
1205
|
+
if (!process.env[apiKeyEnv]) {
|
|
1206
|
+
logger?.debug?.(`[PD:Empathy] Empathy observer API key env ${apiKeyEnv} is not set. Background analysis disabled.`);
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const adapter = new PiAiRuntimeAdapter({
|
|
1211
|
+
provider,
|
|
1212
|
+
model,
|
|
1213
|
+
apiKeyEnv,
|
|
1214
|
+
baseUrl,
|
|
1215
|
+
workspace: wctx.workspaceDir,
|
|
1216
|
+
});
|
|
1217
|
+
return new EmpathyObserver({ runtimeAdapter: adapter });
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const adapter = new PiAiRuntimeAdapter({
|
|
1221
|
+
provider: String(policy.provider),
|
|
1222
|
+
model: String(policy.model),
|
|
1223
|
+
apiKeyEnv: String(policy.apiKeyEnv),
|
|
1224
|
+
maxRetries: policy.maxRetries,
|
|
1225
|
+
timeoutMs: policy.timeoutMs ?? 30_000,
|
|
1226
|
+
baseUrl: policy.baseUrl,
|
|
1227
|
+
workspace: wctx.workspaceDir,
|
|
1228
|
+
});
|
|
1229
|
+
return new EmpathyObserver({ runtimeAdapter: adapter }, { timeoutMs: policy.timeoutMs });
|
|
1230
|
+
} catch (err) {
|
|
1231
|
+
logger?.warn?.(`[PD:Empathy] Failed to resolve EmpathyObserver: ${String(err)}`);
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
}
|