principles-disciple 1.71.0 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +10 -5
- package/package.json +17 -19
- package/scripts/acceptance-test.mjs +16 -73
- package/scripts/sync-plugin.mjs +382 -77
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -2
- package/src/commands/context.ts +2 -2
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +16 -16
- package/src/commands/export.ts +12 -67
- package/src/commands/pain.ts +91 -1
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +7 -43
- package/src/commands/rollback-impl.ts +2 -1
- package/src/commands/rollback.ts +2 -1
- package/src/commands/samples.ts +2 -1
- package/src/commands/thinking-os.ts +2 -1
- package/src/config/errors.ts +18 -2
- package/src/constants/diagnostician.ts +2 -2
- package/src/constants/tools.ts +2 -1
- package/src/core/__tests__/focus-history.test.ts +210 -0
- package/src/core/config.ts +1 -1
- package/src/core/confirm-first-gate.ts +255 -0
- package/src/core/correction-cue-learner.ts +2 -136
- package/src/core/correction-types.ts +16 -88
- package/src/core/dictionary.ts +19 -20
- package/src/core/empathy-keyword-matcher.ts +17 -289
- package/src/core/empathy-types.ts +18 -229
- package/src/core/event-log.ts +38 -132
- package/src/core/evolution-reducer.ts +21 -2
- package/src/core/evolution-types.ts +76 -464
- package/src/core/file-store.ts +80 -0
- package/src/core/focus-history.ts +228 -955
- package/src/core/local-worker-routing.ts +34 -314
- package/src/core/merge-gate-audit.ts +0 -195
- package/src/core/pain-diagnostic-gate.ts +154 -0
- package/src/core/pain-signal.ts +21 -138
- package/src/core/pain.ts +15 -88
- package/src/core/pd-task-reconciler.ts +26 -115
- package/src/core/pd-task-service.ts +9 -9
- package/src/core/pd-task-types.ts +23 -127
- package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
- package/src/core/principle-compiler/code-validator.ts +15 -42
- package/src/core/principle-compiler/compiler.ts +100 -15
- package/src/core/principle-compiler/index.ts +5 -2
- package/src/core/principle-compiler/template-generator.ts +4 -104
- package/src/core/principle-injection.ts +10 -202
- package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
- package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
- package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
- package/src/core/principle-tree-ledger-adapter.ts +145 -0
- package/src/core/principle-tree-ledger.ts +8 -6
- package/src/core/reflection/reflection-context.ts +14 -109
- package/src/core/replay-engine.ts +8 -500
- package/src/core/rule-host-helpers.ts +5 -35
- package/src/core/rule-host-types.ts +10 -82
- package/src/core/rule-host.ts +6 -63
- package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
- package/src/core/session-tracker.ts +87 -101
- package/src/core/shadow-observation-registry.ts +19 -48
- package/src/core/trajectory.ts +3 -1
- package/src/core/workflow-funnel-loader.ts +62 -68
- package/src/core/workspace-context.ts +46 -0
- package/src/core/workspace-dir-service.ts +1 -1
- package/src/core/workspace-dir-validation.ts +18 -9
- package/src/hooks/AGENTS.md +1 -1
- package/src/hooks/gate-block-helper.ts +46 -44
- package/src/hooks/gate.ts +207 -7
- package/src/hooks/lifecycle.ts +30 -32
- package/src/hooks/llm.ts +60 -32
- package/src/hooks/pain.ts +297 -103
- package/src/hooks/prompt.ts +469 -339
- package/src/hooks/subagent.ts +2 -29
- package/src/i18n/commands.ts +2 -10
- package/src/index.ts +95 -85
- package/src/openclaw-sdk.ts +311 -0
- package/src/service/central-database.ts +8 -4
- package/src/service/evolution-queue-migration.ts +2 -1
- package/src/service/evolution-worker.ts +163 -1786
- package/src/service/internalization-trigger-adapter.ts +302 -0
- package/src/service/keyword-optimization-service.ts +4 -4
- package/src/service/monitoring-query-service.ts +1 -215
- package/src/service/queue-io.ts +60 -331
- package/src/service/runtime-summary-service.ts +115 -18
- package/src/service/subagent-workflow/index.ts +0 -41
- package/src/service/subagent-workflow/types.ts +9 -120
- package/src/service/subagent-workflow/workflow-store.ts +2 -119
- package/src/service/workflow-watchdog.ts +0 -43
- package/src/types/event-payload.ts +16 -74
- package/src/types/event-types.ts +39 -547
- package/src/types/hygiene-types.ts +7 -30
- package/src/types/principle-tree-schema.ts +20 -222
- package/src/types/queue.ts +15 -70
- package/src/types/runtime-summary.ts +5 -49
- package/src/utils/io.ts +10 -0
- package/src/utils/retry.ts +1 -1
- package/src/utils/shadow-fingerprint.ts +2 -2
- package/src/utils/workspace-resolver.ts +50 -0
- package/templates/langs/en/core/AGENTS.md +2 -2
- package/templates/langs/en/core/BOOT.md +1 -1
- package/templates/langs/en/core/HEARTBEAT.md +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
- package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
- package/templates/langs/zh/core/AGENTS.md +2 -2
- package/templates/langs/zh/core/BOOT.md +1 -1
- package/templates/langs/zh/core/HEARTBEAT.md +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
- package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
- package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
- package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
- package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
- package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
- package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
- package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
- package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
- package/tests/build-artifacts.test.ts +1 -3
- package/tests/commands/evolution-status.test.ts +0 -118
- package/tests/core/bootstrap-rules.test.ts +1 -1
- package/tests/core/config.test.ts +1 -1
- package/tests/core/event-log.test.ts +35 -0
- package/tests/core/evolution-engine.test.ts +610 -0
- package/tests/core/file-store.test.ts +102 -0
- package/tests/core/focus-history.test.ts +203 -11
- package/tests/core/merge-gate-audit.test.ts +2 -169
- package/tests/core/model-deployment-registry.test.ts +7 -1
- package/tests/core/model-training-registry.test.ts +19 -0
- package/tests/core/observability.test.ts +0 -1
- package/tests/core/pain-diagnostic-gate.test.ts +498 -0
- package/tests/core/pain.test.ts +0 -1
- package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
- package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
- package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
- package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
- package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
- package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
- package/tests/core/reflection-context.test.ts +0 -14
- package/tests/core/replay-engine.test.ts +127 -215
- package/tests/core/rule-host-helpers.test.ts +2 -2
- package/tests/core/rule-implementation-runtime.test.ts +0 -27
- package/tests/core/workflow-funnel-loader.test.ts +162 -0
- package/tests/core/workspace-dir-validation.test.ts +8 -1
- package/tests/core-anti-growth.test.ts +192 -0
- package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
- package/tests/hooks/confirm-first-gate.test.ts +333 -0
- package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
- package/tests/hooks/gate-auto-correct.test.ts +665 -0
- package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
- package/tests/hooks/pain.test.ts +269 -12
- package/tests/hooks/prompt-characterization.test.ts +500 -0
- package/tests/hooks/prompt-size-guard.test.ts +329 -0
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
- package/tests/index.test.ts +94 -1
- package/tests/integration/auto-entry-gate.test.ts +248 -0
- package/tests/integration/internalization-trigger-guard.test.ts +69 -0
- package/tests/integration/m8-legacy-paths.test.ts +63 -0
- package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
- package/tests/plugin-config-resolution-cutover.test.ts +359 -0
- package/tests/runtime-v2-discovery-guard.test.ts +154 -0
- package/tests/service/central-database.test.ts +457 -0
- package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
- package/tests/service/evolution-worker.timeout.test.ts +11 -129
- package/tests/service/internalization-trigger-adapter.test.ts +251 -0
- package/tests/service/monitoring-query-service.test.ts +1 -47
- package/tests/service/queue-io.test.ts +1 -62
- package/tests/service/runtime-summary-service.test.ts +184 -3
- package/tests/service/workflow-watchdog.test.ts +0 -91
- package/tests/utils/file-lock.test.ts +5 -3
- package/tests/utils/session-key.test.ts +52 -0
- package/tests/utils/subagent-probe.test.ts +48 -1
- package/vitest.config.ts +4 -11
- package/.planning/codebase/ARCHITECTURE.md +0 -157
- package/.planning/codebase/CONCERNS.md +0 -145
- package/.planning/codebase/CONVENTIONS.md +0 -148
- package/.planning/codebase/INTEGRATIONS.md +0 -81
- package/.planning/codebase/STACK.md +0 -87
- package/.planning/codebase/STRUCTURE.md +0 -193
- package/.planning/codebase/TESTING.md +0 -243
- package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
- package/docs/COMMAND_REFERENCE.md +0 -76
- package/docs/COMMAND_REFERENCE_EN.md +0 -79
- package/scripts/build-web.mjs +0 -46
- package/scripts/diagnose-nocturnal.mjs +0 -537
- package/scripts/seed-nocturnal-scenarios.mjs +0 -384
- package/src/commands/nocturnal-review.ts +0 -322
- package/src/commands/nocturnal-rollout.ts +0 -790
- package/src/commands/nocturnal-train.ts +0 -986
- package/src/commands/pd-reflect.ts +0 -88
- package/src/core/adaptive-thresholds.ts +0 -478
- package/src/core/diagnostician-task-store.ts +0 -192
- package/src/core/nocturnal-arbiter.ts +0 -715
- package/src/core/nocturnal-artifact-lineage.ts +0 -116
- package/src/core/nocturnal-artificer.ts +0 -257
- package/src/core/nocturnal-candidate-scoring.ts +0 -530
- package/src/core/nocturnal-compliance.ts +0 -1146
- package/src/core/nocturnal-dataset.ts +0 -763
- package/src/core/nocturnal-executability.ts +0 -428
- package/src/core/nocturnal-export.ts +0 -499
- package/src/core/nocturnal-paths.ts +0 -240
- package/src/core/nocturnal-reasoning-deriver.ts +0 -343
- package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
- package/src/core/nocturnal-snapshot-contract.ts +0 -99
- package/src/core/nocturnal-trajectory-extractor.ts +0 -512
- package/src/core/nocturnal-trinity-types.ts +0 -218
- package/src/core/nocturnal-trinity.ts +0 -2680
- package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
- package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
- package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
- package/src/http/principles-console-route.ts +0 -709
- package/src/service/central-health-service.ts +0 -49
- package/src/service/central-overview-service.ts +0 -138
- package/src/service/control-ui-query-service.ts +0 -900
- package/src/service/cooldown-strategy.ts +0 -97
- package/src/service/evolution-pain-context.ts +0 -79
- package/src/service/evolution-query-service.ts +0 -407
- package/src/service/health-query-service.ts +0 -1038
- package/src/service/nocturnal-config.ts +0 -214
- package/src/service/nocturnal-runtime.ts +0 -734
- package/src/service/nocturnal-service.ts +0 -1605
- package/src/service/nocturnal-target-selector.ts +0 -545
- package/src/service/sleep-cycle.ts +0 -157
- package/src/service/startup-reconciler.ts +0 -112
- package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
- package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
- package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
- package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
- package/src/tools/write-pain-flag.ts +0 -215
- package/tests/commands/nocturnal-review.test.ts +0 -448
- package/tests/commands/nocturnal-train.test.ts +0 -97
- package/tests/commands/pd-reflect.test.ts +0 -49
- package/tests/core/adaptive-thresholds.test.ts +0 -261
- package/tests/core/nocturnal-arbiter.test.ts +0 -559
- package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
- package/tests/core/nocturnal-artificer.test.ts +0 -241
- package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
- package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
- package/tests/core/nocturnal-compliance.test.ts +0 -646
- package/tests/core/nocturnal-dataset.test.ts +0 -892
- package/tests/core/nocturnal-e2e.test.ts +0 -234
- package/tests/core/nocturnal-executability.test.ts +0 -357
- package/tests/core/nocturnal-export.test.ts +0 -517
- package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
- package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
- package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
- package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
- package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
- package/tests/core/nocturnal-trinity.test.ts +0 -2053
- package/tests/core/pain-auto-repair.test.ts +0 -96
- package/tests/core/pain-integration.test.ts +0 -510
- package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
- package/tests/http/principles-console-route.test.ts +0 -162
- package/tests/integration/chaos-resilience.test.ts +0 -348
- package/tests/integration/empathy-workflow-integration.test.ts +0 -626
- package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
- package/tests/service/control-ui-query-service.test.ts +0 -121
- package/tests/service/cooldown-strategy.test.ts +0 -164
- package/tests/service/data-endpoints-regression.test.ts +0 -834
- package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
- package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
- package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
- package/tests/service/nocturnal-runtime.test.ts +0 -473
- package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
- package/tests/service/nocturnal-target-selector.test.ts +0 -615
- package/tests/service/startup-reconciler.test.ts +0 -148
- package/tests/tools/write-pain-flag.test.ts +0 -358
- package/ui/src/App.tsx +0 -45
- package/ui/src/api.ts +0 -220
- package/ui/src/charts.tsx +0 -955
- package/ui/src/components/ErrorState.tsx +0 -6
- package/ui/src/components/Loading.tsx +0 -13
- package/ui/src/components/ProtectedRoute.tsx +0 -12
- package/ui/src/components/Shell.tsx +0 -91
- package/ui/src/components/WorkspaceConfig.tsx +0 -178
- package/ui/src/components/index.ts +0 -5
- package/ui/src/context/auth.tsx +0 -80
- package/ui/src/context/theme.tsx +0 -66
- package/ui/src/hooks/useAutoRefresh.ts +0 -39
- package/ui/src/i18n/ui.ts +0 -473
- package/ui/src/main.tsx +0 -16
- package/ui/src/pages/EvolutionPage.tsx +0 -333
- package/ui/src/pages/FeedbackPage.tsx +0 -138
- package/ui/src/pages/GateMonitorPage.tsx +0 -136
- package/ui/src/pages/LoginPage.tsx +0 -89
- package/ui/src/pages/OverviewPage.tsx +0 -599
- package/ui/src/pages/SamplesPage.tsx +0 -174
- package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
- package/ui/src/styles.css +0 -2020
- package/ui/src/types.ts +0 -384
- package/ui/src/utils/format.ts +0 -15
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';
|
|
@@ -8,33 +8,29 @@ import { WorkspaceContext } from '../core/workspace-context.js';
|
|
|
8
8
|
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
|
+
import { detectApprovalMarker, setConfirmFirstApproval, setConfirmFirstDirective, hydrateFromStore, pruneStoreStaleRows, setConfirmFirstStore, resetConfirmFirst } from '../core/confirm-first-gate.js';
|
|
11
12
|
import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
|
|
12
|
-
import { EmpathyObserverWorkflowManager, empathyObserverWorkflowSpec, isExpectedSubagentError } from '../service/subagent-workflow/index.js';
|
|
13
13
|
import { PathResolver } from '../core/path-resolver.js';
|
|
14
14
|
import { selectPrinciplesForInjection, DEFAULT_PRINCIPLE_BUDGET } from '../core/principle-injection.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { getCachedMaskedPrincipleSet, WorkflowFunnelLoader, PiAiRuntimeAdapter, EmpathyObserver, AgentScheduler, SqliteConfirmFirstStateStore, SqliteConnection } from '@principles/core/runtime-v2';
|
|
16
|
+
import { truncateInjectionToBudget } from '@principles/core/prompt-builder';
|
|
17
|
+
import { PromptActivationReader, RUNTIME_V2_PRINCIPLE_BUDGET } from '../core/runtime-v2-prompt-activation-reader.js';
|
|
17
18
|
import {
|
|
18
19
|
matchEmpathyKeywords,
|
|
19
20
|
loadKeywordStore,
|
|
20
21
|
saveKeywordStore,
|
|
21
|
-
shouldTriggerOptimization,
|
|
22
22
|
getKeywordStoreSummary,
|
|
23
23
|
} from '../core/empathy-keyword-matcher.js';
|
|
24
24
|
import { severityToPenalty, DEFAULT_EMPATHY_KEYWORD_CONFIG } from '../core/empathy-types.js';
|
|
25
|
+
import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
|
|
26
|
+
import { emitPainDetectedEvent, buildTrajectoryEvidence } from './pain.js';
|
|
25
27
|
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
|
-
}
|
|
28
|
+
import {
|
|
29
|
+
buildAttitudeDirective,
|
|
30
|
+
detectCorrectionCue as coreDetectCorrectionCue,
|
|
31
|
+
extractMessageContent,
|
|
32
|
+
isMinimalTrigger,
|
|
33
|
+
} from '@principles/core/prompt-builder';
|
|
38
34
|
|
|
39
35
|
// ---------------------------------------------------------------------------
|
|
40
36
|
// Static file cache — avoids re-reading rarely-changing files every message
|
|
@@ -81,6 +77,7 @@ function cachedReadFile(filePath: string): string {
|
|
|
81
77
|
// Module-level empathy state — shared across calls to avoid per-turn I/O
|
|
82
78
|
let _empathyTurnCounter = 0;
|
|
83
79
|
let _empathyKeywordCache: { store: ReturnType<typeof loadKeywordStore>; lang: string } | null = null;
|
|
80
|
+
let _confirmFirstHydrationCounter = 0;
|
|
84
81
|
|
|
85
82
|
/**
|
|
86
83
|
* Model configuration with primary model and optional fallback models
|
|
@@ -129,43 +126,7 @@ interface PromptHookApi {
|
|
|
129
126
|
}
|
|
130
127
|
|
|
131
128
|
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;
|
|
129
|
+
return extractMessageContent(message);
|
|
169
130
|
}
|
|
170
131
|
|
|
171
132
|
/**
|
|
@@ -297,64 +258,25 @@ export function getDiagnosticianModel(api: PromptHookApi | null, logger?: Plugin
|
|
|
297
258
|
/**
|
|
298
259
|
* Extract recent user messages for keyword optimization context.
|
|
299
260
|
*/
|
|
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
|
-
}
|
|
261
|
+
|
|
323
262
|
|
|
324
263
|
/**
|
|
325
264
|
* Build prompt for keyword optimization subagent.
|
|
326
265
|
*/
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
`;
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
function ensureConfirmFirstStore(workspaceDir: string): void {
|
|
269
|
+
if (!_confirmFirstStoreInitialized) {
|
|
270
|
+
try {
|
|
271
|
+
const connection = new SqliteConnection({ workspaceDir, readonly: false });
|
|
272
|
+
setConfirmFirstStore(new SqliteConfirmFirstStateStore(connection));
|
|
273
|
+
_confirmFirstStoreInitialized = true;
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.warn(`[PD:ConfirmFirst] Failed to initialize store: ${String(err)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
357
278
|
}
|
|
279
|
+
let _confirmFirstStoreInitialized = false;
|
|
358
280
|
|
|
359
281
|
export async function handleBeforePromptBuild(
|
|
360
282
|
event: PluginHookBeforePromptBuildEvent,
|
|
@@ -364,23 +286,29 @@ export async function handleBeforePromptBuild(
|
|
|
364
286
|
const logger = ctx.api?.logger;
|
|
365
287
|
logger?.info?.(`[PD:Prompt] handleBeforePromptBuild called: workspaceDir=${!!workspaceDir}, trigger=${ctx.trigger}, sessionId=${ctx.sessionId?.substring(0, 20)}`);
|
|
366
288
|
if (!workspaceDir) {
|
|
367
|
-
logger?.warn?.(`[PD:Prompt] workspaceDir is missing — skipping
|
|
289
|
+
logger?.warn?.(`[PD:Prompt] workspaceDir is missing — skipping PD context injection`);
|
|
368
290
|
return;
|
|
369
291
|
}
|
|
370
292
|
|
|
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
293
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
379
|
-
const { trigger, sessionId
|
|
294
|
+
const { trigger, sessionId } = ctx as { trigger: string | undefined; sessionId: string | undefined };
|
|
295
|
+
const api = ctx.api;
|
|
380
296
|
if (sessionId) {
|
|
381
297
|
wctx.trajectory?.recordSession?.({ sessionId });
|
|
382
298
|
}
|
|
383
299
|
|
|
300
|
+
if (sessionId) {
|
|
301
|
+
ensureConfirmFirstStore(workspaceDir);
|
|
302
|
+
hydrateFromStore(sessionId);
|
|
303
|
+
_confirmFirstHydrationCounter++;
|
|
304
|
+
if (_confirmFirstHydrationCounter % 100 === 0) {
|
|
305
|
+
const pruned = pruneStoreStaleRows();
|
|
306
|
+
if (pruned > 0) {
|
|
307
|
+
logger?.info?.(`[PD:ConfirmFirst] Pruned ${pruned} stale rows from confirm_first_state`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
384
312
|
if (sessionId && trigger === 'user' && Array.isArray(event.messages) && event.messages.length > 0) {
|
|
385
313
|
const latestUserIndex = [...event.messages]
|
|
386
314
|
.map((message, index) => ({ message, index }))
|
|
@@ -389,6 +317,26 @@ export async function handleBeforePromptBuild(
|
|
|
389
317
|
|
|
390
318
|
if (latestUserIndex) {
|
|
391
319
|
const userText = getTextContent(latestUserIndex.message);
|
|
320
|
+
|
|
321
|
+
// ── Confirm-first approval detection ──
|
|
322
|
+
// If user sends approval language, mark session as approved for confirm-first gate
|
|
323
|
+
if (sessionId && detectApprovalMarker(userText)) {
|
|
324
|
+
setConfirmFirstApproval(sessionId);
|
|
325
|
+
// P2: Emit approval telemetry for observability (ERR-002)
|
|
326
|
+
try {
|
|
327
|
+
wctx.eventLog.recordConfirmFirstGateApproved({
|
|
328
|
+
sessionId,
|
|
329
|
+
workspaceDir: wctx.workspaceDir,
|
|
330
|
+
toolName: '(approval)',
|
|
331
|
+
reason: 'user_approval_detected',
|
|
332
|
+
principleId: 'confirm-first',
|
|
333
|
+
nextAction: 'mutating tools now permitted',
|
|
334
|
+
});
|
|
335
|
+
} catch (logErr) {
|
|
336
|
+
logger?.warn?.(`[PD:ConfirmFirst] Failed to emit approval event: ${String(logErr)}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
392
340
|
// Use CorrectionCueLearner for detection — supports learned keywords, not just hardcoded list
|
|
393
341
|
let correctionCue: string | null = null;
|
|
394
342
|
try {
|
|
@@ -406,7 +354,7 @@ export async function handleBeforePromptBuild(
|
|
|
406
354
|
}
|
|
407
355
|
} catch (learnerErr) {
|
|
408
356
|
// Fallback to hardcoded detection if learner fails — log for observability
|
|
409
|
-
correctionCue =
|
|
357
|
+
correctionCue = coreDetectCorrectionCue(userText);
|
|
410
358
|
logger?.warn?.(`[PD:Prompt] CorrectionCueLearner.match() failed (${String(learnerErr)}), fallback=${correctionCue ? `matched="${correctionCue}"` : 'no-match'}`);
|
|
411
359
|
}
|
|
412
360
|
let referencesAssistantTurnId: number | null = null;
|
|
@@ -432,10 +380,10 @@ export async function handleBeforePromptBuild(
|
|
|
432
380
|
}
|
|
433
381
|
|
|
434
382
|
// Load context injection configuration
|
|
435
|
-
const contextConfig = loadContextInjectionConfig(workspaceDir);
|
|
383
|
+
const contextConfig = loadContextInjectionConfig(wctx.workspaceDir);
|
|
436
384
|
|
|
437
385
|
// Minimal mode: heartbeat and subagents skip most context to reduce tokens
|
|
438
|
-
const isMinimalMode = trigger
|
|
386
|
+
const isMinimalMode = isMinimalTrigger(trigger as string | undefined, sessionId as string | undefined);
|
|
439
387
|
|
|
440
388
|
const session = sessionId ? getSession(sessionId) : undefined;
|
|
441
389
|
|
|
@@ -449,6 +397,8 @@ export async function handleBeforePromptBuild(
|
|
|
449
397
|
let prependSystemContext: string;
|
|
450
398
|
let prependContext = '';
|
|
451
399
|
let appendSystemContext = '';
|
|
400
|
+
// Tracks pending diagnostician task count for diagnostician-priority mode in size guard
|
|
401
|
+
let pendingDiagTaskCount = 0;
|
|
452
402
|
|
|
453
403
|
// ──── 0. Manual Pain Clearance ────
|
|
454
404
|
if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
|
|
@@ -533,7 +483,9 @@ The empathy observer subagent handles pain detection independently.
|
|
|
533
483
|
|
|
534
484
|
const isUserInteraction = trigger === 'user' || trigger === 'api' || !trigger;
|
|
535
485
|
|
|
486
|
+
// Empathy Observer: keyword fast-path + optional LLM deep analysis (zero latency async dispatch)
|
|
536
487
|
const empathyEnabled = wctx.config.get('empathy_engine.enabled') !== false;
|
|
488
|
+
|
|
537
489
|
logger?.info?.(`[PD:Empathy] Conditions: enabled=${empathyEnabled}, isUser=${isUserInteraction}, sessionId=${!!sessionId}, api=${!!api}, !agentToAgent=${!isAgentToAgent}, workspaceDir=${!!workspaceDir}, hasMessage=${!!latestUserMessage}`);
|
|
538
490
|
|
|
539
491
|
// Track if we should inject behavioral constraints (will be added to appendSystemContext later)
|
|
@@ -562,23 +514,6 @@ The empathy observer subagent handles pain detection independently.
|
|
|
562
514
|
_empathyTurnCounter++;
|
|
563
515
|
const turnCount = _empathyTurnCounter;
|
|
564
516
|
|
|
565
|
-
// Decision: should we call subagent?
|
|
566
|
-
let shouldCallSubagent = false;
|
|
567
|
-
let samplingReason = '';
|
|
568
|
-
|
|
569
|
-
if (matchResult.score >= 0.8) {
|
|
570
|
-
// High confidence — keyword match is reliable, no subagent needed
|
|
571
|
-
shouldCallSubagent = false;
|
|
572
|
-
} else if (matchResult.score >= 0.3) {
|
|
573
|
-
// Boundary case — 30% sampling for subagent verification
|
|
574
|
-
shouldCallSubagent = Math.random() < 0.3;
|
|
575
|
-
samplingReason = 'boundary_verification';
|
|
576
|
-
} else {
|
|
577
|
-
// No keyword hit — 5% random sampling to discover new expressions
|
|
578
|
-
shouldCallSubagent = Math.random() < 0.05;
|
|
579
|
-
samplingReason = 'random_discovery';
|
|
580
|
-
}
|
|
581
|
-
|
|
582
517
|
if (matchResult.matched) {
|
|
583
518
|
const penalty = severityToPenalty(matchResult.severity, DEFAULT_EMPATHY_KEYWORD_CONFIG);
|
|
584
519
|
// trackFriction signature: (sessionId, deltaF: number, hash: string, workspaceDir?, options?)
|
|
@@ -586,93 +521,194 @@ The empathy observer subagent handles pain detection independently.
|
|
|
586
521
|
source: 'user_empathy',
|
|
587
522
|
});
|
|
588
523
|
|
|
589
|
-
logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
subagent: toWorkflowSubagent(runtimeSubagent),
|
|
614
|
-
});
|
|
615
|
-
empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
|
|
616
|
-
parentSessionId: sessionId,
|
|
617
|
-
workspaceDir,
|
|
618
|
-
taskInput: latestUserMessage,
|
|
619
|
-
}).catch((err) => {
|
|
620
|
-
if (!isExpectedSubagentError(err)) {
|
|
621
|
-
api.logger?.warn?.(`[PD:Empathy] subagent sample failed: ${String(err)}`);
|
|
622
|
-
}
|
|
623
|
-
});
|
|
624
|
-
}
|
|
524
|
+
logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}`);
|
|
525
|
+
|
|
526
|
+
const currentSession = getSession(sessionId);
|
|
527
|
+
const currentGfi = currentSession?.currentGfi ?? 0;
|
|
528
|
+
const painTrigger = wctx.config.get('thresholds.pain_trigger') || 40;
|
|
529
|
+
const highGfiThreshold = Math.max(wctx.config.get('severity_thresholds.high') || 70, painTrigger + 30);
|
|
530
|
+
|
|
531
|
+
if (currentGfi >= highGfiThreshold) {
|
|
532
|
+
const gfiPainScore = Math.min(Math.round(currentGfi), 60);
|
|
533
|
+
logger?.info?.(`[PD:Empathy] GFI-TRIGGERED: currentGfi=${currentGfi.toFixed(1)} >= highGfi=${highGfiThreshold}, emitting pain signal (score=${gfiPainScore})`);
|
|
534
|
+
|
|
535
|
+
const gate = evaluatePainDiagnosticGate({
|
|
536
|
+
source: 'user_empathy',
|
|
537
|
+
score: gfiPainScore,
|
|
538
|
+
currentGfi,
|
|
539
|
+
consecutiveErrors: currentSession?.consecutiveErrors ?? 0,
|
|
540
|
+
sessionId,
|
|
541
|
+
errorHash: 'empathy_gfi_threshold',
|
|
542
|
+
thresholds: {
|
|
543
|
+
painTrigger,
|
|
544
|
+
highSeverity: wctx.config.get('severity_thresholds.high') || 70,
|
|
545
|
+
semanticPain: Math.max(painTrigger, 60),
|
|
546
|
+
},
|
|
547
|
+
});
|
|
625
548
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
549
|
+
wctx.eventLog.recordPainSignal(sessionId, {
|
|
550
|
+
score: gfiPainScore,
|
|
551
|
+
source: 'user_empathy',
|
|
552
|
+
reason: `Accumulated GFI (${currentGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Matched: ${matchResult.matchedTerms.join(', ')}`,
|
|
553
|
+
isRisky: false,
|
|
554
|
+
});
|
|
632
555
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
556
|
+
if (gate.shouldDiagnose) {
|
|
557
|
+
logger?.info?.(`[PD:Empathy] Gate approved, calling emitPainDetectedEvent...`);
|
|
558
|
+
try {
|
|
559
|
+
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
560
|
+
await emitPainDetectedEvent(wctx, {
|
|
561
|
+
ts: new Date().toISOString(),
|
|
562
|
+
type: 'pain_detected',
|
|
563
|
+
data: {
|
|
564
|
+
painId: `empathy_gfi_${Date.now()}`,
|
|
565
|
+
painType: 'user_frustration',
|
|
566
|
+
source: 'user_empathy',
|
|
567
|
+
reason: `Accumulated GFI (${currentGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Matched: ${matchResult.matchedTerms.join(', ')}`,
|
|
568
|
+
score: gfiPainScore,
|
|
569
|
+
sessionId,
|
|
570
|
+
agentId: 'main',
|
|
571
|
+
provenance: 'openclaw_context_bound',
|
|
572
|
+
evidence,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
logger?.info?.(`[PD:Empathy] emitPainDetectedEvent completed (GFI-triggered)`);
|
|
576
|
+
} catch (emitErr) {
|
|
577
|
+
console.error(`[PD:Empathy] FAILED to emit GFI-triggered pain event: ${String(emitErr)}`);
|
|
578
|
+
logger?.warn?.(`[PD:Empathy] Failed to emit GFI-triggered pain event: ${String(emitErr)}`);
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
logger?.info?.(`[PD:Empathy] GFI-triggered gate rejected: ${gate.detail}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
637
584
|
|
|
638
|
-
//
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
workspaceDir,
|
|
647
|
-
logger: api.logger ?? console,
|
|
648
|
-
|
|
649
|
-
subagent: toWorkflowSubagent(api.runtime.subagent),
|
|
585
|
+
// Trigger asynchronous background Empathy Observer deep analysis (Zero Latency)
|
|
586
|
+
const observer = resolveEmpathyObserver(wctx, logger);
|
|
587
|
+
if (observer) {
|
|
588
|
+
const scheduler = new AgentScheduler();
|
|
589
|
+
scheduler.register({
|
|
590
|
+
agentId: 'empathy-observer',
|
|
591
|
+
mode: 'realtime',
|
|
592
|
+
runner: observer,
|
|
650
593
|
});
|
|
594
|
+
|
|
595
|
+
logger?.info?.(`[PD:Empathy] Triggering background Empathy Observer deep analysis for message: "${msgPreview}"`);
|
|
651
596
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
597
|
+
void scheduler.dispatch('empathy-observer', { userMessage: latestUserMessage })
|
|
598
|
+
.then(async (result) => {
|
|
599
|
+
if (result.damageDetected) {
|
|
600
|
+
logger?.info?.(`[PD:Empathy] Background Empathy Observer detected damage. Severity: ${result.severity}, Reason: ${result.reason}`);
|
|
601
|
+
|
|
602
|
+
// ── Persistence Contract ──
|
|
603
|
+
const painScore = scoreFromSeverityForSpec(result.severity, wctx);
|
|
604
|
+
|
|
605
|
+
trackFriction(
|
|
606
|
+
sessionId,
|
|
607
|
+
painScore,
|
|
608
|
+
`observer_empathy_${result.severity}`,
|
|
609
|
+
workspaceDir,
|
|
610
|
+
{ source: 'user_empathy' }
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const eventId = `emp_obs_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
614
|
+
wctx.eventLog.recordPainSignal(sessionId, {
|
|
615
|
+
score: painScore,
|
|
616
|
+
source: 'user_empathy',
|
|
617
|
+
reason: result.reason || 'Empathy observer detected likely user frustration.',
|
|
618
|
+
isRisky: false,
|
|
619
|
+
origin: 'system_infer',
|
|
620
|
+
severity: result.severity,
|
|
621
|
+
confidence: result.confidence,
|
|
622
|
+
detection_mode: 'structured',
|
|
623
|
+
deduped: false,
|
|
624
|
+
trigger_text_excerpt: latestUserMessage.substring(0, 120),
|
|
625
|
+
raw_score: painScore,
|
|
626
|
+
calibrated_score: painScore,
|
|
627
|
+
eventId,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
wctx.trajectory?.recordPainEvent?.({
|
|
632
|
+
sessionId,
|
|
633
|
+
source: 'user_empathy',
|
|
634
|
+
score: painScore,
|
|
635
|
+
reason: result.reason || 'Empathy observer detected likely user frustration.',
|
|
636
|
+
severity: result.severity,
|
|
637
|
+
origin: 'system_infer',
|
|
638
|
+
confidence: result.confidence,
|
|
639
|
+
text: latestUserMessage,
|
|
640
|
+
});
|
|
641
|
+
} catch (error) {
|
|
642
|
+
logger?.warn?.(`[PD:Empathy] Failed to persist trajectory: ${String(error)}`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Check if GFI triggers a pain event post-LLM validation
|
|
646
|
+
const freshSession = getSession(sessionId);
|
|
647
|
+
const freshGfi = freshSession?.currentGfi ?? 0;
|
|
648
|
+
if (freshGfi >= highGfiThreshold) {
|
|
649
|
+
const freshGfiPainScore = Math.min(Math.round(freshGfi), 60);
|
|
650
|
+
const gate = evaluatePainDiagnosticGate({
|
|
651
|
+
source: 'user_empathy',
|
|
652
|
+
score: freshGfiPainScore,
|
|
653
|
+
currentGfi: freshGfi,
|
|
654
|
+
consecutiveErrors: freshSession?.consecutiveErrors ?? 0,
|
|
655
|
+
sessionId,
|
|
656
|
+
errorHash: 'empathy_gfi_threshold',
|
|
657
|
+
thresholds: {
|
|
658
|
+
painTrigger,
|
|
659
|
+
highSeverity: wctx.config.get('severity_thresholds.high') || 70,
|
|
660
|
+
semanticPain: Math.max(painTrigger, 60),
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
if (gate.shouldDiagnose) {
|
|
665
|
+
logger?.info?.(`[PD:Empathy] GFI threshold crossed after background observer. Emitting pain signal...`);
|
|
666
|
+
try {
|
|
667
|
+
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
668
|
+
await emitPainDetectedEvent(wctx, {
|
|
669
|
+
ts: new Date().toISOString(),
|
|
670
|
+
type: 'pain_detected',
|
|
671
|
+
data: {
|
|
672
|
+
painId: `empathy_gfi_${Date.now()}`,
|
|
673
|
+
painType: 'user_frustration',
|
|
674
|
+
source: 'user_empathy',
|
|
675
|
+
reason: `Accumulated GFI (${freshGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Verified by Empathy Observer.`,
|
|
676
|
+
score: freshGfiPainScore,
|
|
677
|
+
sessionId,
|
|
678
|
+
agentId: 'main',
|
|
679
|
+
provenance: 'openclaw_context_bound',
|
|
680
|
+
evidence,
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
} catch (emitErr) {
|
|
684
|
+
logger?.error?.(`[PD:Empathy] FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
} else {
|
|
689
|
+
logger?.info?.(`[PD:Empathy] Background Empathy Observer did not detect any damage.`);
|
|
690
|
+
}
|
|
691
|
+
})
|
|
692
|
+
.catch((err) => {
|
|
693
|
+
logger?.warn?.(`[PD:Empathy] Background analysis failed or rejected: ${String(err)}`);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
} else {
|
|
697
|
+
// Log unmatched messages periodically for coverage analysis
|
|
698
|
+
if (turnCount > 0 && turnCount % 50 === 0) {
|
|
699
|
+
const sampleMsg = latestUserMessage.substring(0, 80).replace(/\n/g, ' ');
|
|
700
|
+
logger?.debug?.(`[PD:Empathy] NO_MATCH: "${sampleMsg}" (turn ${turnCount}, keywords_in_store=${Object.keys(keywordStore.terms).length})`);
|
|
665
701
|
}
|
|
666
702
|
}
|
|
667
703
|
|
|
668
704
|
// Periodic summary (every 100 turns)
|
|
669
705
|
if (turnCount > 0 && turnCount % 100 === 0) {
|
|
670
|
-
|
|
706
|
+
const s = getKeywordStoreSummary(keywordStore);
|
|
707
|
+
const highFP = s.highFalsePositiveTerms.slice(0, 5).map(t => `${t.term}(${t.falsePositiveRate.toFixed(2)})`).join(', ');
|
|
708
|
+
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}]`);
|
|
671
709
|
}
|
|
672
710
|
|
|
673
|
-
// Save keyword store on every match
|
|
674
|
-
// Previously used turnCount % 50 gate which caused hitCount loss because
|
|
675
|
-
// module-level state resets on plugin reload before reaching turn 50.
|
|
711
|
+
// Save keyword store on every match
|
|
676
712
|
if (matchResult.matched) {
|
|
677
713
|
saveKeywordStore(wctx.stateDir, keywordStore);
|
|
678
714
|
const {totalHits} = keywordStore.stats;
|
|
@@ -683,21 +719,6 @@ The empathy observer subagent handles pain detection independently.
|
|
|
683
719
|
}
|
|
684
720
|
}
|
|
685
721
|
|
|
686
|
-
// Empathy Observer: analyze user message for frustration signals (legacy, disabled)
|
|
687
|
-
// The keyword matching approach above is now the primary empathy detection method.
|
|
688
|
-
// The subagent-based observer is kept for periodic keyword optimization only.
|
|
689
|
-
// if (workspaceDir) {
|
|
690
|
-
// const empathyManager = new EmpathyObserverWorkflowManager({
|
|
691
|
-
// workspaceDir,
|
|
692
|
-
// logger: api.logger,
|
|
693
|
-
// subagent: api.runtime.subagent as any,
|
|
694
|
-
// });
|
|
695
|
-
// empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
|
|
696
|
-
// parentSessionId: sessionId,
|
|
697
|
-
// workspaceDir,
|
|
698
|
-
// taskInput: latestUserMessage,
|
|
699
|
-
// }).catch((err) => api.logger.warn(`[PD:Empathy] workflow failed: ${String(err)}`));
|
|
700
|
-
// }
|
|
701
722
|
}
|
|
702
723
|
|
|
703
724
|
// ──── 4. Heartbeat-specific checklist (also fires for cron-triggered sessions) ────
|
|
@@ -720,95 +741,18 @@ The empathy observer subagent handles pain detection independently.
|
|
|
720
741
|
const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
|
|
721
742
|
prependContext += `<heartbeat_checklist>
|
|
722
743
|
${heartbeatChecklist}
|
|
723
|
-
|
|
724
|
-
// HEARTBEAT_OK removed - tasks must always be processed
|
|
725
744
|
</heartbeat_checklist>\n`;
|
|
726
745
|
} catch (e) {
|
|
727
746
|
logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
|
|
728
747
|
}
|
|
729
748
|
}
|
|
730
749
|
|
|
731
|
-
// ──── 4b. Inject pending diagnostician tasks ────
|
|
732
|
-
// FIX (#283): 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
|
-
try {
|
|
736
|
-
const pendingTasks = getPendingDiagnosticianTasks(wctx.stateDir);
|
|
737
|
-
if (pendingTasks.length > 0) {
|
|
738
|
-
const taskBlocks = pendingTasks
|
|
739
|
-
.slice(0, 3)
|
|
740
|
-
.map(({ id, task }) => `<diagnostician_task id="${id}">\n${task.prompt}\n</diagnostician_task>`)
|
|
741
|
-
.join('\n\n');
|
|
742
|
-
|
|
743
|
-
const pendingCount = pendingTasks.length;
|
|
744
|
-
const processingNote = pendingCount > 3
|
|
745
|
-
? `\n\nNOTE: ${pendingCount - 3} more tasks are queued. Process these 3 first; remaining tasks will be handled on subsequent heartbeats.`
|
|
746
|
-
: '';
|
|
747
|
-
|
|
748
|
-
prependContext += `<diagnostician_tasks pending="${pendingCount}">
|
|
749
|
-
You are acting as a **Pain Diagnostician**. Process the following task(s) by:
|
|
750
|
-
1. Analyzing the pain signal and its context
|
|
751
|
-
2. Identifying the root cause and violated principles
|
|
752
|
-
3. Writing a completion marker file: .evolution_complete_<TASK_ID>
|
|
753
|
-
4. Writing a diagnostic report: .diagnostician_report_<TASK_ID>.json
|
|
754
|
-
|
|
755
|
-
${taskBlocks}${processingNote}
|
|
756
|
-
</diagnostician_tasks>\n`;
|
|
757
|
-
|
|
758
|
-
logger?.info?.(`[PD:Prompt] Injected ${Math.min(pendingCount, 3)}/${pendingCount} pending diagnostician task(s) into heartbeat prompt`);
|
|
759
|
-
|
|
760
|
-
// C: Record heartbeat_diagnosis event for observability
|
|
761
|
-
try {
|
|
762
|
-
const eventLog = EventLogService.get(wctx.stateDir, logger);
|
|
763
|
-
eventLog.recordHeartbeatDiagnosis({
|
|
764
|
-
taskCount: pendingCount,
|
|
765
|
-
taskIds: pendingTasks.slice(0, 3).map(t => t.id),
|
|
766
|
-
trigger: 'heartbeat',
|
|
767
|
-
});
|
|
768
|
-
} catch (evErr) {
|
|
769
|
-
logger?.warn?.(`[PD:Prompt] Failed to record heartbeat_diagnosis event: ${String(evErr)}`);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
} catch (e) {
|
|
773
|
-
logger?.warn?.(`[PD:Prompt] Failed to read diagnostician tasks: ${String(e)}`);
|
|
774
|
-
}
|
|
775
750
|
}
|
|
776
751
|
|
|
777
752
|
// ──── 6. Dynamic Attitude Matrix (based on GFI) ────
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
let attitudeDirective: string;
|
|
753
|
+
|
|
781
754
|
const currentGfi = session?.currentGfi || 0;
|
|
782
|
-
|
|
783
|
-
if (currentGfi >= 70) {
|
|
784
|
-
attitudeDirective = `
|
|
785
|
-
### 【SYSTEM_MODE: HUMBLE_RECOVERY】
|
|
786
|
-
**CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
|
|
787
|
-
**BEHAVIORAL OVERRIDE**:
|
|
788
|
-
- You have failed to meet expectations. Humility is your primary directive.
|
|
789
|
-
- **STOP** aggressive file modifications.
|
|
790
|
-
- **START** every response with a sincere, non-defensive apology.
|
|
791
|
-
- **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
|
|
792
|
-
`;
|
|
793
|
-
} else if (currentGfi >= 40) {
|
|
794
|
-
attitudeDirective = `
|
|
795
|
-
### 【SYSTEM_MODE: CONCILIATORY】
|
|
796
|
-
**CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
|
|
797
|
-
**BEHAVIORAL OVERRIDE**:
|
|
798
|
-
- User is frustrated. Be more explanatory and cautious.
|
|
799
|
-
- Before executing any tool, clearly state what you intend to do and **WAIT** for implicit or explicit user consent.
|
|
800
|
-
- Avoid technical jargon; focus on the business/project value of your changes.
|
|
801
|
-
`;
|
|
802
|
-
} else {
|
|
803
|
-
attitudeDirective = `
|
|
804
|
-
### 【SYSTEM_MODE: EFFICIENT】
|
|
805
|
-
**CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
|
|
806
|
-
**BEHAVIORAL OVERRIDE**:
|
|
807
|
-
- Maintain peak efficiency.
|
|
808
|
-
- Be concise. Prefer action over long explanations.
|
|
809
|
-
- Follow the "Principles > Directives" rule strictly.
|
|
810
|
-
`;
|
|
811
|
-
}
|
|
755
|
+
const attitudeDirective = buildAttitudeDirective(currentGfi);
|
|
812
756
|
|
|
813
757
|
// ──── 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ────
|
|
814
758
|
// NOTE: Principles is ALWAYS injected (not configurable)
|
|
@@ -884,7 +828,7 @@ ${taskBlocks}${processingNote}
|
|
|
884
828
|
projectContextContent = extractSummary(finalContent, 30);
|
|
885
829
|
} else {
|
|
886
830
|
// Full mode: current version + recent history (3 versions)
|
|
887
|
-
const historyVersions = getHistoryVersions(focusPath, 3);
|
|
831
|
+
const historyVersions = await getHistoryVersions(focusPath, 3);
|
|
888
832
|
if (historyVersions.length > 0) {
|
|
889
833
|
const historySections = historyVersions.map((v, i) =>
|
|
890
834
|
`\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`
|
|
@@ -909,13 +853,33 @@ ${taskBlocks}${processingNote}
|
|
|
909
853
|
const allActive = reducer.getActivePrinciples();
|
|
910
854
|
const allProbation = reducer.getProbationPrinciples();
|
|
911
855
|
|
|
856
|
+
// Pruning mask: exclude principles whose latest review is archive-candidate
|
|
857
|
+
let maskedIds = new Set<string>();
|
|
858
|
+
try {
|
|
859
|
+
maskedIds = getCachedMaskedPrincipleSet(wctx.workspaceDir);
|
|
860
|
+
} catch (err) {
|
|
861
|
+
// Safe degradation: if review log unreadable, inject all principles
|
|
862
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
863
|
+
if (logger?.info) {
|
|
864
|
+
logger.info(`[PD:Pruning] Failed to read review log — all principles injected: ${msg}`);
|
|
865
|
+
} else {
|
|
866
|
+
console.error(`[PD:Pruning] Failed to read review log — all principles injected: ${msg}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
912
870
|
// Budget-aware selection: prioritize P0>P1>P2 and recency
|
|
913
|
-
const activeSelection = selectPrinciplesForInjection(
|
|
871
|
+
const activeSelection = selectPrinciplesForInjection(
|
|
872
|
+
allActive.filter(p => !maskedIds.has(p.id)),
|
|
873
|
+
DEFAULT_PRINCIPLE_BUDGET,
|
|
874
|
+
);
|
|
914
875
|
const active = activeSelection.selected;
|
|
915
876
|
|
|
916
877
|
// Probation principles get a smaller sub-budget (1000 chars)
|
|
917
878
|
const probationBudget = 1000;
|
|
918
|
-
const probationSelection = selectPrinciplesForInjection(
|
|
879
|
+
const probationSelection = selectPrinciplesForInjection(
|
|
880
|
+
allProbation.filter(p => !maskedIds.has(p.id)),
|
|
881
|
+
probationBudget,
|
|
882
|
+
);
|
|
919
883
|
const probation = probationSelection.selected;
|
|
920
884
|
|
|
921
885
|
if (activeSelection.wasTruncated || probationSelection.wasTruncated) {
|
|
@@ -952,13 +916,105 @@ ${taskBlocks}${processingNote}
|
|
|
952
916
|
logger?.warn?.(`[PD:Prompt] Failed to load evolution principles: ${String(e)}`);
|
|
953
917
|
}
|
|
954
918
|
|
|
919
|
+
let runtimeV2PrinciplesContent = '';
|
|
920
|
+
const runtimeV2PrincipleIds = new Set<string>();
|
|
921
|
+
// Hoisted so the owner_approved_behavior_directives section can access them
|
|
922
|
+
let dedupedV2: Array<{ principleId: string; text: string; artifactId: string; activationId: string }> = [];
|
|
923
|
+
try {
|
|
924
|
+
const reader = new PromptActivationReader(wctx.workspaceDir, { logger });
|
|
925
|
+
const v2Result = await reader.readActivatedPrinciples();
|
|
926
|
+
|
|
927
|
+
if (v2Result.warnings.length > 0) {
|
|
928
|
+
logger?.info?.(`[PD:RuntimeV2] Activation read warnings: ${v2Result.warnings.join('; ')}`);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const legacyActiveIds = new Set<string>();
|
|
932
|
+
try {
|
|
933
|
+
const legacyActive = wctx.evolutionReducer.getActivePrinciples();
|
|
934
|
+
for (const p of legacyActive) {
|
|
935
|
+
legacyActiveIds.add(p.id);
|
|
936
|
+
}
|
|
937
|
+
const legacyProbation = wctx.evolutionReducer.getProbationPrinciples();
|
|
938
|
+
for (const p of legacyProbation) {
|
|
939
|
+
legacyActiveIds.add(p.id);
|
|
940
|
+
}
|
|
941
|
+
} catch {
|
|
942
|
+
// best-effort dedup
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
dedupedV2 = v2Result.principles.filter((p) => !legacyActiveIds.has(p.principleId));
|
|
946
|
+
|
|
947
|
+
if (dedupedV2.length > 0) {
|
|
948
|
+
let remaining = RUNTIME_V2_PRINCIPLE_BUDGET;
|
|
949
|
+
const lines: string[] = [];
|
|
950
|
+
lines.push('Runtime V2 activated principles (owner-approved):');
|
|
951
|
+
remaining -= 'Runtime V2 activated principles (owner-approved):'.length;
|
|
952
|
+
|
|
953
|
+
for (const p of dedupedV2) {
|
|
954
|
+
const entry = `- [${escapeXml(p.principleId)}] ${escapeXml(p.text)}`;
|
|
955
|
+
if (remaining < entry.length + 1) {
|
|
956
|
+
logger?.info?.(`[PD:RuntimeV2] Principle budget reached (${RUNTIME_V2_PRINCIPLE_BUDGET}c) — truncating after ${lines.length - 1} principles`);
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
lines.push(entry);
|
|
960
|
+
remaining -= entry.length + 1;
|
|
961
|
+
runtimeV2PrincipleIds.add(p.principleId);
|
|
962
|
+
}
|
|
963
|
+
runtimeV2PrinciplesContent = lines.join('\n');
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// ── Emit structured observability event ──
|
|
967
|
+
try {
|
|
968
|
+
const eventLog = wctx.eventLog;
|
|
969
|
+
eventLog.recordRuntimeV2ActivationsInjected({
|
|
970
|
+
sessionId: sessionId ?? 'unknown',
|
|
971
|
+
workspaceDir: wctx.workspaceDir,
|
|
972
|
+
principleIds: [...runtimeV2PrincipleIds],
|
|
973
|
+
activationIds: dedupedV2.map((p) => p.activationId),
|
|
974
|
+
artifactIds: dedupedV2.map((p) => p.artifactId),
|
|
975
|
+
injectedCount: runtimeV2PrincipleIds.size,
|
|
976
|
+
skippedWarnings: v2Result.warnings,
|
|
977
|
+
injectedCharCount: runtimeV2PrinciplesContent.length,
|
|
978
|
+
budget: RUNTIME_V2_PRINCIPLE_BUDGET,
|
|
979
|
+
...(runtimeV2PrincipleIds.size === 0
|
|
980
|
+
? {
|
|
981
|
+
skipReason: v2Result.principles.length === 0
|
|
982
|
+
? 'no_validated_activations'
|
|
983
|
+
: 'all_deduped_against_legacy',
|
|
984
|
+
nextAction: v2Result.principles.length === 0
|
|
985
|
+
? 'check activations table for prompt channel rows with validated artifacts'
|
|
986
|
+
: 'legacy evolution reducer already contains these principle IDs',
|
|
987
|
+
}
|
|
988
|
+
: {}),
|
|
989
|
+
});
|
|
990
|
+
} catch (logErr) {
|
|
991
|
+
logger?.warn?.(`[PD:RuntimeV2] Failed to emit activation observability event: ${String(logErr)}`);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// ── Set confirm-first directive state for gate enforcement ──
|
|
995
|
+
if (sessionId) {
|
|
996
|
+
const cfPrinciple = dedupedV2.find(
|
|
997
|
+
(p) =>
|
|
998
|
+
p.principleId === 'princ-mvp-acceptance-confirm-first' ||
|
|
999
|
+
(p.text.toLowerCase().includes('confirm requirements') &&
|
|
1000
|
+
p.text.toLowerCase().includes('owner approval')),
|
|
1001
|
+
);
|
|
1002
|
+
setConfirmFirstDirective(sessionId, !!cfPrinciple, cfPrinciple?.principleId);
|
|
1003
|
+
}
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
logger?.warn?.(`[PD:RuntimeV2] Failed to read Runtime V2 prompt activations: ${String(e)}`);
|
|
1006
|
+
if (sessionId) {
|
|
1007
|
+
resetConfirmFirst(sessionId);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
955
1011
|
// Build appendSystemContext with recency effect
|
|
956
1012
|
// Content order (most important last): behavioral_constraints -> project_context -> working_memory -> reflection_log -> thinking_os -> principles
|
|
957
1013
|
const appendParts: string[] = [];
|
|
958
1014
|
|
|
959
1015
|
// 0. Behavioral Constraints (empathy observer coordination)
|
|
960
1016
|
// Injected here (appendSystemContext) instead of prependContext to hide from WebUI users.
|
|
961
|
-
//
|
|
1017
|
+
// Behavioral constraints: empathy observer coordination
|
|
962
1018
|
if (shouldInjectBehavioralConstraints) {
|
|
963
1019
|
appendParts.push(`<behavioral_constraints>
|
|
964
1020
|
${empathySilenceConstraint}
|
|
@@ -980,11 +1036,35 @@ ${empathySilenceConstraint}
|
|
|
980
1036
|
appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
|
|
981
1037
|
}
|
|
982
1038
|
|
|
983
|
-
// 3. Evolution Loop principles (active/probation)
|
|
1039
|
+
// 3. Evolution Loop principles (legacy active/probation only — Runtime V2 moved to section 3.5)
|
|
984
1040
|
if (evolutionPrinciplesContent) {
|
|
985
1041
|
appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
|
|
986
1042
|
}
|
|
987
1043
|
|
|
1044
|
+
// 3.5. Owner-Approved Behavior Directives (Runtime V2 activated principles)
|
|
1045
|
+
// PLACED IN prependSystemContext (before gateway system prompt) for highest LLM attention.
|
|
1046
|
+
// These are owner-reviewed, validated behavior constraints — not background context.
|
|
1047
|
+
if (runtimeV2PrincipleIds.size > 0) {
|
|
1048
|
+
const directiveLines: string[] = [];
|
|
1049
|
+
directiveLines.push('');
|
|
1050
|
+
directiveLines.push('## 【OWNER-APPROVED BEHAVIOR DIRECTIVES】');
|
|
1051
|
+
directiveLines.push('');
|
|
1052
|
+
directiveLines.push('Owner-approved behavior directives are active operating constraints learned from prior owner corrections.');
|
|
1053
|
+
directiveLines.push('These directives are mandatory for this session unless they conflict with safety, security, or higher-priority system policy.');
|
|
1054
|
+
directiveLines.push('For ambiguous coding or file-changing tasks, follow these directives before using mutating tools.');
|
|
1055
|
+
directiveLines.push('');
|
|
1056
|
+
for (const p of dedupedV2) {
|
|
1057
|
+
if (!runtimeV2PrincipleIds.has(p.principleId)) continue;
|
|
1058
|
+
directiveLines.push(`<directive id="${escapeXml(p.principleId)}" source="runtime_v2_activation">`);
|
|
1059
|
+
directiveLines.push(`MANDATORY: ${escapeXml(p.text)}`);
|
|
1060
|
+
directiveLines.push('Apply this as an active behavior constraint. Do not treat this as background context.');
|
|
1061
|
+
directiveLines.push('</directive>');
|
|
1062
|
+
directiveLines.push('');
|
|
1063
|
+
}
|
|
1064
|
+
directiveLines.push('Note: These directives do not override safety, security, or core system policy.');
|
|
1065
|
+
prependSystemContext += directiveLines.join('\n');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
988
1068
|
// Routing Guidance (section 5 — injected between evolution principles and core principles)
|
|
989
1069
|
// Inject delegation guidance when task is bounded + deployment allowed + not high-entropy.
|
|
990
1070
|
// This is a non-authoritative suggestion — the main agent decides whether to follow.
|
|
@@ -1013,7 +1093,6 @@ ${empathySilenceConstraint}
|
|
|
1013
1093
|
const routingInput: RoutingInput = {
|
|
1014
1094
|
taskIntent: toolMatches[0] ?? undefined,
|
|
1015
1095
|
taskDescription: latestUserText.trim(),
|
|
1016
|
-
requestedTools: toolMatches.length > 0 ? toolMatches : undefined,
|
|
1017
1096
|
requestedFiles: fileMatches.length > 0 ? fileMatches : undefined,
|
|
1018
1097
|
};
|
|
1019
1098
|
|
|
@@ -1124,36 +1203,36 @@ ${attitudeDirective}
|
|
|
1124
1203
|
}
|
|
1125
1204
|
|
|
1126
1205
|
// ──── 8. SIZE GUARD ────
|
|
1127
|
-
//
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
const lines = projectContextContent.split('\n');
|
|
1138
|
-
if (lines.length > 20) {
|
|
1139
|
-
const truncated = lines.slice(0, 20).join('\n') + '\n...[truncated]';
|
|
1140
|
-
appendSystemContext = appendSystemContext.replace(
|
|
1141
|
-
`<project_context>\n${projectContextContent}\n</project_context>`,
|
|
1142
|
-
`<project_context>\n${truncated}\n</project_context>`
|
|
1143
|
-
);
|
|
1144
|
-
truncationLog.push('project_context');
|
|
1145
|
-
}
|
|
1206
|
+
// Delegates to @principles/core/prompt-builder/truncateInjectionToBudget
|
|
1207
|
+
// which handles priority stripping: project_context → thinking_os →
|
|
1208
|
+
// evolution_principles → reflection_log → reason: truncation → fallback.
|
|
1209
|
+
const result = truncateInjectionToBudget(
|
|
1210
|
+
prependSystemContext,
|
|
1211
|
+
prependContext,
|
|
1212
|
+
appendSystemContext,
|
|
1213
|
+
{
|
|
1214
|
+
diagnosticianMode: pendingDiagTaskCount > 0,
|
|
1215
|
+
blocks: { projectContextContent, thinkingOsContent, evolutionPrinciplesContent },
|
|
1146
1216
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1217
|
+
);
|
|
1218
|
+
|
|
1219
|
+
prependSystemContext = result.prependSystemContext;
|
|
1220
|
+
prependContext = result.prependContext;
|
|
1221
|
+
appendSystemContext = result.appendSystemContext;
|
|
1222
|
+
|
|
1223
|
+
if (result.truncated) {
|
|
1224
|
+
const logEntry = result.truncationLog.join(', ');
|
|
1225
|
+
if (result.appendSystemContext.includes('[WARNING: Context sections stripped')) {
|
|
1226
|
+
logger?.error(
|
|
1227
|
+
`[PD:Prompt] PROMPT OVER LIMIT AFTER ALL REDUCTIONS — using fallback. ` +
|
|
1228
|
+
`Diagnostician mode: ${pendingDiagTaskCount > 0}. Stripped: ${logEntry}.`
|
|
1229
|
+
);
|
|
1230
|
+
} else {
|
|
1231
|
+
logger?.warn(
|
|
1232
|
+
`[PD:Prompt] Injection size exceeded budget, truncated: ${logEntry || 'none'}, ` +
|
|
1233
|
+
`diagnostician mode: ${pendingDiagTaskCount > 0}`
|
|
1234
|
+
);
|
|
1154
1235
|
}
|
|
1155
|
-
|
|
1156
|
-
logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated: ${truncationLog.join(', ') || 'none'}, new size: ${newSize} chars`);
|
|
1157
1236
|
}
|
|
1158
1237
|
|
|
1159
1238
|
return {
|
|
@@ -1162,3 +1241,54 @@ ${attitudeDirective}
|
|
|
1162
1241
|
appendSystemContext
|
|
1163
1242
|
};
|
|
1164
1243
|
}
|
|
1244
|
+
|
|
1245
|
+
// ── Empathy Observer Hybrid Deep Analysis Helpers (Unified SDK Migration) ──
|
|
1246
|
+
|
|
1247
|
+
function scoreFromSeverityForSpec(severity: string | undefined, wctx: WorkspaceContext): number {
|
|
1248
|
+
if (severity === 'severe') return Number(wctx.config.get('empathy_engine.penalties.severe') ?? 40);
|
|
1249
|
+
if (severity === 'moderate') return Number(wctx.config.get('empathy_engine.penalties.moderate') ?? 25);
|
|
1250
|
+
return Number(wctx.config.get('empathy_engine.penalties.mild') ?? 10);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function resolveEmpathyObserver(wctx: WorkspaceContext, logger?: Pick<PluginLogger, 'info' | 'warn' | 'error' | 'debug'>): EmpathyObserver | null {
|
|
1254
|
+
try {
|
|
1255
|
+
const loader = new WorkflowFunnelLoader(wctx.stateDir);
|
|
1256
|
+
const funnel = loader.getFunnel('pd-empathy-observer');
|
|
1257
|
+
const policy = funnel?.policy;
|
|
1258
|
+
if (!policy || policy.runtimeKind !== 'pi-ai') {
|
|
1259
|
+
logger?.debug?.('[PD:Empathy] workflows.yaml pd-empathy-observer policy not found. Falling back to environment variables.');
|
|
1260
|
+
const provider = process.env.PD_EMPATHY_PROVIDER || 'anthropic';
|
|
1261
|
+
const model = process.env.PD_EMPATHY_MODEL || 'anthropic/claude-3-5-sonnet';
|
|
1262
|
+
const apiKeyEnv = process.env.PD_EMPATHY_API_KEY_ENV || 'ANTHROPIC_API_KEY';
|
|
1263
|
+
const baseUrl = process.env.PD_EMPATHY_BASE_URL;
|
|
1264
|
+
|
|
1265
|
+
if (!process.env[apiKeyEnv]) {
|
|
1266
|
+
logger?.debug?.(`[PD:Empathy] Empathy observer API key env ${apiKeyEnv} is not set. Background analysis disabled.`);
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const adapter = new PiAiRuntimeAdapter({
|
|
1271
|
+
provider,
|
|
1272
|
+
model,
|
|
1273
|
+
apiKeyEnv,
|
|
1274
|
+
baseUrl,
|
|
1275
|
+
workspace: wctx.workspaceDir,
|
|
1276
|
+
});
|
|
1277
|
+
return new EmpathyObserver({ runtimeAdapter: adapter });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const adapter = new PiAiRuntimeAdapter({
|
|
1281
|
+
provider: String(policy.provider),
|
|
1282
|
+
model: String(policy.model),
|
|
1283
|
+
apiKeyEnv: String(policy.apiKeyEnv),
|
|
1284
|
+
maxRetries: policy.maxRetries,
|
|
1285
|
+
timeoutMs: policy.timeoutMs ?? 30_000,
|
|
1286
|
+
baseUrl: policy.baseUrl,
|
|
1287
|
+
workspace: wctx.workspaceDir,
|
|
1288
|
+
});
|
|
1289
|
+
return new EmpathyObserver({ runtimeAdapter: adapter }, { timeoutMs: policy.timeoutMs });
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
logger?.warn?.(`[PD:Empathy] Failed to resolve EmpathyObserver: ${String(err)}`);
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
}
|