principles-disciple 1.72.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 +459 -439
- 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 +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 +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/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/gate.ts
CHANGED
|
@@ -15,9 +15,11 @@ import { normalizePath, planStatus } from '../utils/io.js';
|
|
|
15
15
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
16
16
|
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
17
17
|
import { RuleHost } from '../core/rule-host.js';
|
|
18
|
-
import type { RuleHostInput } from '
|
|
18
|
+
import type { RuleHostInput } from '@principles/core/runtime-v2';
|
|
19
|
+
import { validateCorrectionProposal, validateProposedPathBounds } from '@principles/core/runtime-v2';
|
|
19
20
|
import type { PluginHookBeforeToolCallEvent, PluginHookToolContext, PluginHookBeforeToolCallResult, PluginLogger } from '../openclaw-sdk.js';
|
|
20
21
|
import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js';
|
|
22
|
+
import { evaluateConfirmFirstGateSync } from '../core/confirm-first-gate.js';
|
|
21
23
|
import { getSession, hasRecentThinking } from '../core/session-tracker.js';
|
|
22
24
|
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
23
25
|
import { EventLogService } from '../core/event-log.js';
|
|
@@ -40,12 +42,48 @@ export function handleBeforeToolCall(
|
|
|
40
42
|
|
|
41
43
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
42
44
|
|
|
45
|
+
// 1.5. Confirm-First Gate — runs BEFORE filePath resolution to catch apply_patch/no-path cases
|
|
46
|
+
try {
|
|
47
|
+
const cfResult = evaluateConfirmFirstGateSync(
|
|
48
|
+
ctx.sessionId,
|
|
49
|
+
event.toolName,
|
|
50
|
+
event.params,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (cfResult.action === 'block') {
|
|
54
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
55
|
+
eventLog.recordConfirmFirstGateBlocked({
|
|
56
|
+
sessionId: ctx.sessionId ?? 'unknown',
|
|
57
|
+
workspaceDir: ctx.workspaceDir,
|
|
58
|
+
toolName: event.toolName,
|
|
59
|
+
reason: cfResult.reason ?? 'confirm_first_required',
|
|
60
|
+
principleId: cfResult.principleId ?? 'unknown',
|
|
61
|
+
nextAction: cfResult.nextAction ?? '',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Use safe placeholder when filePath is unavailable (e.g., apply_patch with no path)
|
|
65
|
+
const safePath = (event.params?.file_path || event.params?.path || event.params?.file || event.params?.target)
|
|
66
|
+
?? `<tool:${event.toolName}>`;
|
|
67
|
+
|
|
68
|
+
return recordGateBlockAndReturn(wctx, {
|
|
69
|
+
filePath: typeof safePath === 'string' ? safePath : `<tool:${event.toolName}>`,
|
|
70
|
+
reason: cfResult.reason ?? 'confirm_first_required',
|
|
71
|
+
toolName: event.toolName,
|
|
72
|
+
sessionId: ctx.sessionId,
|
|
73
|
+
blockSource: 'confirm-first-gate',
|
|
74
|
+
}, logger);
|
|
75
|
+
}
|
|
76
|
+
} catch (cfErr) {
|
|
77
|
+
// ERR-002: fail loud — log but do not crash the gate
|
|
78
|
+
logger?.warn?.(`[PD:ConfirmFirst] Gate evaluation failed (non-blocking): ${String(cfErr)}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
// 2. Resolve the target file path
|
|
44
|
-
let filePath = event.params
|
|
82
|
+
let filePath = event.params?.file_path || event.params?.path || event.params?.file || event.params?.target;
|
|
45
83
|
|
|
46
84
|
// Heuristic for bash mutation detection
|
|
47
85
|
if (isBash && !filePath) {
|
|
48
|
-
const command = String(event.params
|
|
86
|
+
const command = String(event.params?.command || event.params?.args || '');
|
|
49
87
|
const mutationMatch = /(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/.exec(command);
|
|
50
88
|
|
|
51
89
|
if (mutationMatch) {
|
|
@@ -67,7 +105,7 @@ export function handleBeforeToolCall(
|
|
|
67
105
|
action: {
|
|
68
106
|
toolName: event.toolName,
|
|
69
107
|
normalizedPath: relPath,
|
|
70
|
-
paramsSummary: _extractParamsSummary(event.params),
|
|
108
|
+
paramsSummary: _extractParamsSummary(event.params ?? {}),
|
|
71
109
|
},
|
|
72
110
|
workspace: {
|
|
73
111
|
isRiskPath: false, // Rule Host determines risk dynamically
|
|
@@ -83,7 +121,7 @@ export function handleBeforeToolCall(
|
|
|
83
121
|
epTier: _getEpTier(wctx.workspaceDir),
|
|
84
122
|
},
|
|
85
123
|
derived: {
|
|
86
|
-
estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params }),
|
|
124
|
+
estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params ?? {} }),
|
|
87
125
|
bashRisk: _getBashRisk(event),
|
|
88
126
|
},
|
|
89
127
|
};
|
|
@@ -153,8 +191,170 @@ export function handleBeforeToolCall(
|
|
|
153
191
|
logger?.warn?.(`[PD_GATE] Failed to record rule_enforced/rulehost_requireApproval: ${String(evErr)}`);
|
|
154
192
|
}
|
|
155
193
|
}
|
|
194
|
+
|
|
195
|
+
if (hostResult?.decision === 'auto_correct' && hostResult.correctionProposal) {
|
|
196
|
+
const proposal = hostResult.correctionProposal;
|
|
197
|
+
let validation: { valid: boolean; errors: string[] };
|
|
198
|
+
try {
|
|
199
|
+
validation = validateCorrectionProposal(proposal);
|
|
200
|
+
} catch (validationError: unknown) {
|
|
201
|
+
validation = { valid: false, errors: [`Validator threw: ${String(validationError)}`] };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
206
|
+
const correctedFields = Array.isArray(proposal.correctedFields)
|
|
207
|
+
? proposal.correctedFields.map((f: unknown) => typeof f === 'object' && f !== null ? String((f as { field?: string }).field) : String(f))
|
|
208
|
+
: [];
|
|
209
|
+
eventLog.recordRuleHostAutoCorrectProposed({
|
|
210
|
+
toolName: event.toolName,
|
|
211
|
+
filePath: relPath,
|
|
212
|
+
ruleId: String(proposal.ruleId ?? 'unknown'),
|
|
213
|
+
principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
|
|
214
|
+
confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
|
|
215
|
+
reason: hostResult.reason,
|
|
216
|
+
applicationMode: proposal.applicationMode === 'live' ? 'live' : 'shadow',
|
|
217
|
+
correctedFields,
|
|
218
|
+
validationValid: validation.valid,
|
|
219
|
+
});
|
|
220
|
+
} catch (evErr) {
|
|
221
|
+
logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_proposed: ${String(evErr)}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (proposal.applicationMode === 'live' && validation.valid) {
|
|
225
|
+
if (!event.params) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const originalParams = { ...event.params };
|
|
229
|
+
const nextParams: Record<string, unknown> = {};
|
|
230
|
+
const appliedFields: Array<{ field: string; original: unknown; applied: unknown }> = [];
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (!Array.isArray(proposal.correctedFields)) {
|
|
234
|
+
throw new Error('proposal.correctedFields is not an array');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!proposal.proposedParams || typeof proposal.proposedParams !== 'object' || Array.isArray(proposal.proposedParams)) {
|
|
238
|
+
throw new Error('proposal.proposedParams must be an object');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const trustedWorkspaceDir = ctx.workspaceDir;
|
|
242
|
+
if (typeof trustedWorkspaceDir === 'string' && trustedWorkspaceDir.trim().length > 0) {
|
|
243
|
+
const pathBoundsResult = validateProposedPathBounds(proposal.proposedParams, trustedWorkspaceDir);
|
|
244
|
+
if (!pathBoundsResult.valid) {
|
|
245
|
+
throw new Error(`Path boundary violation: ${pathBoundsResult.reason}`);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
const hasPathField = Object.keys(proposal.proposedParams).some(k => typeof proposal.proposedParams[k] === 'string' && (k === 'file_path' || k === 'path' || k === 'filePath'));
|
|
249
|
+
if (hasPathField) {
|
|
250
|
+
throw new Error('Cannot apply live auto-correction with path fields: no trusted workspace directory available');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const cf of proposal.correctedFields) {
|
|
255
|
+
if (typeof cf !== 'object' || cf === null || typeof cf.field !== 'string') {
|
|
256
|
+
throw new Error('correctedFields entry must be an object with a string field');
|
|
257
|
+
}
|
|
258
|
+
const field = cf.field;
|
|
259
|
+
if (!Object.hasOwn(event.params, field)) {
|
|
260
|
+
throw new Error(`Field '${field}' not found in event.params`);
|
|
261
|
+
}
|
|
262
|
+
if (!Object.hasOwn(proposal.proposedParams, field)) {
|
|
263
|
+
throw new Error(`Field '${field}' not found in proposal.proposedParams`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const cf of proposal.correctedFields) {
|
|
268
|
+
if (typeof cf === 'object' && cf !== null && typeof cf.field === 'string') {
|
|
269
|
+
const field = cf.field;
|
|
270
|
+
const originalValue = event.params[field];
|
|
271
|
+
const appliedValue = proposal.proposedParams[field];
|
|
272
|
+
nextParams[field] = appliedValue;
|
|
273
|
+
appliedFields.push({
|
|
274
|
+
field,
|
|
275
|
+
original: originalValue,
|
|
276
|
+
applied: appliedValue,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Object.assign(event.params, nextParams);
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
285
|
+
eventLog.recordRuleHostAutoCorrectApplied({
|
|
286
|
+
toolName: event.toolName,
|
|
287
|
+
filePath: relPath,
|
|
288
|
+
ruleId: String(proposal.ruleId ?? 'unknown'),
|
|
289
|
+
principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
|
|
290
|
+
confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
|
|
291
|
+
reason: hostResult.reason || proposal.correctedFields?.[0]?.reason || 'auto-correct applied',
|
|
292
|
+
correctedFields: appliedFields,
|
|
293
|
+
});
|
|
294
|
+
} catch (evErr) {
|
|
295
|
+
logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_applied: ${String(evErr)}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (proposal.notifyAgent === true && appliedFields.length > 0) {
|
|
299
|
+
const messages = appliedFields.map(f =>
|
|
300
|
+
`[PD Auto-Correct] Rule ${proposal.ruleId}: ${proposal.correctedFields?.[0]?.reason || 'correction applied'}. Parameter '${f.field}' was adjusted from ${JSON.stringify(f.original)} to ${JSON.stringify(f.applied)}.`
|
|
301
|
+
);
|
|
302
|
+
return {
|
|
303
|
+
toolArgs: event.toolArgs,
|
|
304
|
+
skipToolCall: false,
|
|
305
|
+
_pdAutoCorrectWarning: messages.join('\n'),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
} catch (applyError: unknown) {
|
|
309
|
+
if (event.params) {
|
|
310
|
+
Object.assign(event.params, originalParams);
|
|
311
|
+
}
|
|
312
|
+
const errorMsg = String(applyError);
|
|
313
|
+
const isPathViolation = errorMsg.includes('Path boundary violation') || errorMsg.includes('no trusted workspace directory');
|
|
314
|
+
if (isPathViolation) {
|
|
315
|
+
logger?.warn?.(`[PD_GATE] Live auto-correction rejected — path out of bounds: ${errorMsg}`);
|
|
316
|
+
try {
|
|
317
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
318
|
+
eventLog.recordRuleHostAutoCorrectProposed({
|
|
319
|
+
toolName: event.toolName,
|
|
320
|
+
filePath: relPath,
|
|
321
|
+
ruleId: String(proposal.ruleId ?? 'unknown'),
|
|
322
|
+
principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
|
|
323
|
+
confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
|
|
324
|
+
reason: `Path boundary rejected: ${errorMsg}`,
|
|
325
|
+
applicationMode: 'shadow',
|
|
326
|
+
correctedFields: Array.isArray(proposal.correctedFields)
|
|
327
|
+
? proposal.correctedFields.map((f: unknown) => typeof f === 'object' && f !== null ? String((f as { field?: string }).field) : String(f))
|
|
328
|
+
: [],
|
|
329
|
+
validationValid: false,
|
|
330
|
+
});
|
|
331
|
+
} catch (evErr) {
|
|
332
|
+
logger?.warn?.(`[PD_GATE] Failed to record path rejection telemetry: ${String(evErr)}`);
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
logger?.warn?.(`[PD_GATE] Failed to apply auto-correction, using original params: ${errorMsg}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} else if (hostResult?.decision === 'auto_correct') {
|
|
340
|
+
// auto_correct without correctionProposal — emit telemetry for observability
|
|
341
|
+
try {
|
|
342
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
343
|
+
eventLog.recordRuleHostAutoCorrectProposed({
|
|
344
|
+
toolName: event.toolName,
|
|
345
|
+
filePath: relPath,
|
|
346
|
+
ruleId: hostResult.ruleId ?? 'unknown',
|
|
347
|
+
confidence: 0,
|
|
348
|
+
reason: hostResult.reason ?? 'auto_correct without correctionProposal',
|
|
349
|
+
applicationMode: 'shadow',
|
|
350
|
+
correctedFields: [],
|
|
351
|
+
validationValid: false,
|
|
352
|
+
});
|
|
353
|
+
} catch (evErr) {
|
|
354
|
+
logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_proposed (no proposal): ${String(evErr)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
156
357
|
} catch (hostError: unknown) {
|
|
157
|
-
// D-08: Conservative degradation — log and allow on Rule Host failure
|
|
158
358
|
logger.warn?.(`[PD_GATE:RULE_HOST] Host evaluation failed, allowing conservatively: ${String(hostError)}`);
|
|
159
359
|
}
|
|
160
360
|
|
|
@@ -227,7 +427,7 @@ function _getEpTier(workspaceDir: string): number {
|
|
|
227
427
|
function _getBashRisk(event: PluginHookBeforeToolCallEvent): 'safe' | 'normal' | 'dangerous' | 'unknown' {
|
|
228
428
|
if (!BASH_TOOLS_SET.has(event.toolName)) return 'unknown';
|
|
229
429
|
try {
|
|
230
|
-
const command = String(event.params
|
|
430
|
+
const command = String(event.params?.command || event.params?.args || '');
|
|
231
431
|
const isDangerous = /\brm\s+-rf\b|\bchmod\b|\bchown\b|>\s*\/dev\//.test(command);
|
|
232
432
|
if (isDangerous) return 'dangerous';
|
|
233
433
|
const isMutation = /(?:>|>>|sed|rm|mv|mkdir|touch|cp|npm|yarn|pnpm|pip|cargo)/.test(command);
|
package/src/hooks/lifecycle.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as readline from 'readline';
|
|
4
|
-
import { recordAndWritePainFlag } from '../core/pain.js';
|
|
5
4
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
6
5
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
7
6
|
import { PD_DIRS } from '../core/paths.js';
|
|
@@ -10,6 +9,7 @@ import {
|
|
|
10
9
|
mergeWorkingMemory,
|
|
11
10
|
} from '../core/focus-history.js';
|
|
12
11
|
import type { PluginHookBeforeResetEvent, PluginHookBeforeCompactionEvent, PluginHookAfterCompactionEvent, PluginHookAgentContext } from '../openclaw-sdk.js';
|
|
12
|
+
import { emitPainDetectedEvent } from './pain.js';
|
|
13
13
|
|
|
14
14
|
export async function handleBeforeReset(
|
|
15
15
|
event: PluginHookBeforeResetEvent,
|
|
@@ -66,7 +66,7 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
|
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
ctx.logger?.info?.(`[Pain Extractor] Scanning session transcript for pain signals: ${sessionFile}`);
|
|
70
70
|
|
|
71
71
|
const fileStream = fs.createReadStream(sessionFile);
|
|
72
72
|
const rl = readline.createInterface({
|
|
@@ -97,13 +97,13 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
|
|
|
97
97
|
|
|
98
98
|
if (msg.openclawAbort?.aborted) {
|
|
99
99
|
const runIdSafe = msg.openclawAbort?.runId || 'unknown';
|
|
100
|
-
|
|
100
|
+
ctx.logger?.info?.(`[Pain Extractor] Detected hard-abort snapshot (runId: ${runIdSafe})`);
|
|
101
101
|
painPoints.push(`[FATAL INTERCEPT] 动作被沙箱防御机制强制击落。大模型被击落前的思考流 (未遂动机): ${text.substring(0, 250)}...`);
|
|
102
102
|
continue;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (msg.__openclaw?.truncated && msg.__openclaw?.reason === 'oversized') {
|
|
106
|
-
|
|
106
|
+
ctx.logger?.info?.(`[Pain Extractor] Detected oversized data truncation placeholder`);
|
|
107
107
|
painPoints.push(`[COGNITIVE OVERLOAD] 大模型尝试读取极大体积的输入,已被底层守护程序抹除/折叠防爆。请反思是否读取了不当的文件或日志: ${text.substring(0, 150)}...`);
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
@@ -154,21 +154,19 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
|
|
|
154
154
|
|
|
155
155
|
const hasFatal = painPoints.some(p => p.includes('[FATAL INTERCEPT]'));
|
|
156
156
|
if (hasFatal) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
session_id: ctx.sessionId || '',
|
|
171
|
-
agent_id: ctx.agentId || '',
|
|
157
|
+
// Emit via the Runtime v2 pain chain — no .pain_flag file written
|
|
158
|
+
emitPainDetectedEvent(wctx, {
|
|
159
|
+
ts: new Date().toISOString(),
|
|
160
|
+
type: 'pain_detected',
|
|
161
|
+
data: {
|
|
162
|
+
painId: `intercept_${Date.now()}`,
|
|
163
|
+
painType: 'tool_failure' as const,
|
|
164
|
+
source: 'intercept_extraction',
|
|
165
|
+
reason: 'Hard intercept detected in session history compaction.',
|
|
166
|
+
score: 100,
|
|
167
|
+
sessionId: ctx.sessionId || 'unknown',
|
|
168
|
+
agentId: ctx.agentId,
|
|
169
|
+
},
|
|
172
170
|
});
|
|
173
171
|
}
|
|
174
172
|
} catch (err) {
|
|
@@ -200,13 +198,14 @@ export async function handleBeforeCompaction(
|
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
// 提取工作记忆(从 sessionFile)
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
const sessionFile = (event as { sessionFile?: string }).sessionFile;
|
|
202
|
+
if (sessionFile) {
|
|
203
|
+
await extractPainFromSessionFile(sessionFile, ctx);
|
|
205
204
|
|
|
206
205
|
// 新增:提取并保存工作记忆
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
await extractAndSaveWorkingMemory(
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
await extractAndSaveWorkingMemory(sessionFile, ctx, wctx);
|
|
210
209
|
}
|
|
211
210
|
}
|
|
212
211
|
|
|
@@ -296,20 +295,18 @@ async function extractAndSaveWorkingMemory(
|
|
|
296
295
|
// 写入文件
|
|
297
296
|
atomicWriteFileSync(focusPath, updatedContent);
|
|
298
297
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
`${snapshot.nextActions.length} next actions to CURRENT_FOCUS.md`);
|
|
303
|
-
}
|
|
298
|
+
ctx.logger?.info?.(`[WorkingMemory] Preserved ${snapshot.artifacts.length} artifacts, ` +
|
|
299
|
+
`${snapshot.activeProblems.length} problems, ` +
|
|
300
|
+
`${snapshot.nextActions.length} next actions to CURRENT_FOCUS.md`);
|
|
304
301
|
} catch (err) {
|
|
305
302
|
ctx.logger?.error?.(`[PD:Lifecycle] Failed to save working memory: ${String(err)}`);
|
|
306
|
-
|
|
303
|
+
|
|
307
304
|
// 尝试恢复备份
|
|
308
305
|
const backupPath = `${focusPath}.wm-backup`;
|
|
309
306
|
if (fs.existsSync(backupPath)) {
|
|
310
307
|
try {
|
|
311
308
|
fs.copyFileSync(backupPath, focusPath);
|
|
312
|
-
|
|
309
|
+
ctx.logger?.warn?.(`[WorkingMemory] Restored from backup after failure`);
|
|
313
310
|
} catch {
|
|
314
311
|
// 忽略恢复错误
|
|
315
312
|
}
|
|
@@ -325,8 +322,9 @@ export async function handleAfterCompaction(
|
|
|
325
322
|
|
|
326
323
|
const [dateStrPost] = new Date().toISOString().split('T');
|
|
327
324
|
const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStrPost}.md`);
|
|
325
|
+
const messageCount = (event as { messageCount?: number }).messageCount ?? 0;
|
|
328
326
|
const log =
|
|
329
|
-
`- Post-Compaction Complete. Reduced active context to ${
|
|
327
|
+
`- Post-Compaction Complete. Reduced active context to ${messageCount} messages.\n`;
|
|
330
328
|
|
|
331
329
|
try {
|
|
332
330
|
fs.appendFileSync(checkpointPath, log, 'utf8');
|
package/src/hooks/llm.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import type { PluginHookLlmOutputEvent, PluginHookAgentContext } from '../openclaw-sdk.js';
|
|
3
|
+
import type { PluginHookLlmOutputEvent, PluginHookAgentContext, TokenUsage } from '../openclaw-sdk.js';
|
|
4
4
|
import { trackLlmOutput, recordThinkingCheckpoint, resetFriction } from '../core/session-tracker.js';
|
|
5
|
-
import { recordAndWritePainFlag } from '../core/pain.js';
|
|
6
5
|
import { normalizeSeverity } from '../core/empathy-types.js';
|
|
7
6
|
import { ControlUiDatabase } from '../core/control-ui-db.js';
|
|
8
7
|
import { DetectionService } from '../core/detection-service.js';
|
|
@@ -10,6 +9,8 @@ import { detectThinkingModelMatches, deriveThinkingScenarios } from '../core/thi
|
|
|
10
9
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
11
10
|
import { sanitizeAssistantText } from './message-sanitize.js';
|
|
12
11
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
12
|
+
import { emitPainDetectedEvent, buildTrajectoryEvidence } from './pain.js';
|
|
13
|
+
import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
|
|
13
14
|
|
|
14
15
|
export interface EmpathySignal {
|
|
15
16
|
detected: boolean;
|
|
@@ -148,7 +149,12 @@ export function handleLlmOutput(
|
|
|
148
149
|
const {eventLog} = wctx;
|
|
149
150
|
|
|
150
151
|
// Track this turn in the core session memory
|
|
151
|
-
const
|
|
152
|
+
const trigger = (event as { trigger?: string }).trigger ?? undefined;
|
|
153
|
+
const usage = event.usage as TokenUsage | undefined;
|
|
154
|
+
const sessionId = (ctx as { sessionId?: string }).sessionId ?? 'unknown';
|
|
155
|
+
const workspaceDir = (ctx as { workspaceDir?: string }).workspaceDir;
|
|
156
|
+
const sessionKey = (ctx as { sessionKey?: string }).sessionKey ?? 'unknown';
|
|
157
|
+
const state = trackLlmOutput(sessionId, usage, config, workspaceDir, sessionKey, trigger);
|
|
152
158
|
|
|
153
159
|
// We need actual assistant text to analyze
|
|
154
160
|
if (!event.assistantTexts || event.assistantTexts.length === 0) return;
|
|
@@ -160,9 +166,9 @@ export function handleLlmOutput(
|
|
|
160
166
|
try {
|
|
161
167
|
assistantTurnId = wctx.trajectory?.recordAssistantTurn?.({
|
|
162
168
|
sessionId: ctx.sessionId,
|
|
163
|
-
runId: event.runId,
|
|
164
|
-
provider: event.provider,
|
|
165
|
-
model: event.model,
|
|
169
|
+
runId: event.runId ?? 'unknown',
|
|
170
|
+
provider: event.provider ?? 'unknown',
|
|
171
|
+
model: event.model ?? 'unknown',
|
|
166
172
|
rawText: text,
|
|
167
173
|
sanitizedText: sanitizeAssistantText(text),
|
|
168
174
|
usageJson: event.usage || {},
|
|
@@ -227,32 +233,33 @@ export function handleLlmOutput(
|
|
|
227
233
|
matchedReason = `Agent is stuck in low-output loops (${state.stuckLoops} consecutive turns with tiny output but huge context), indicating cognitive paralysis.`;
|
|
228
234
|
}
|
|
229
235
|
|
|
230
|
-
// If a pain threshold is crossed,
|
|
236
|
+
// If a semantic pain threshold is crossed, only valuable episodes enter Runtime v2.
|
|
237
|
+
// Lower-signal detections remain in the event log/GFI layer for accumulation.
|
|
231
238
|
const painTriggerThreshold = config.get('thresholds.pain_trigger') || 30;
|
|
232
|
-
if (painScore >= painTriggerThreshold) {
|
|
233
|
-
// Inject the actual text snippet that triggered this for the diagnostician to read later
|
|
234
|
-
const snippet = text.length > 200 ? text.substring(0, 100) + '...' + text.substring(text.length - 100) : text;
|
|
235
239
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
240
|
+
// GFI-triggered pain: when accumulated friction crosses highGfi threshold,
|
|
241
|
+
// emit pain signal even if L1 detection didn't fire.
|
|
242
|
+
const highGfiThreshold = Math.max(config.get('severity_thresholds.high') || 70, painTriggerThreshold + 30);
|
|
243
|
+
if (state.currentGfi >= highGfiThreshold && painScore < painTriggerThreshold) {
|
|
244
|
+
painScore = Math.min(state.currentGfi, 60);
|
|
245
|
+
source = 'user_empathy';
|
|
246
|
+
matchedReason = `Accumulated GFI (${state.currentGfi.toFixed(1)}) crossed highGfi threshold (${highGfiThreshold}). Source: empathy keyword friction.`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (painScore >= painTriggerThreshold) {
|
|
250
|
+
const gate = evaluatePainDiagnosticGate({
|
|
251
|
+
source: source === 'llm_paralysis' ? 'llm_paralysis' : 'semantic',
|
|
252
|
+
score: painScore,
|
|
253
|
+
currentGfi: state.currentGfi,
|
|
254
|
+
consecutiveErrors: state.consecutiveErrors,
|
|
255
|
+
sessionId: ctx.sessionId || 'unknown',
|
|
256
|
+
errorHash: source,
|
|
257
|
+
thresholds: {
|
|
258
|
+
painTrigger: painTriggerThreshold,
|
|
259
|
+
highSeverity: config.get('severity_thresholds.high') || 70,
|
|
260
|
+
semanticPain: Math.max(painTriggerThreshold, 60),
|
|
261
|
+
},
|
|
262
|
+
});
|
|
256
263
|
|
|
257
264
|
eventLog.recordPainSignal(ctx.sessionId, {
|
|
258
265
|
score: painScore,
|
|
@@ -260,6 +267,27 @@ export function handleLlmOutput(
|
|
|
260
267
|
reason: matchedReason,
|
|
261
268
|
isRisky: false
|
|
262
269
|
});
|
|
270
|
+
|
|
271
|
+
if (gate.shouldDiagnose) {
|
|
272
|
+
const evidence = buildTrajectoryEvidence(wctx, ctx.sessionId || 'unknown');
|
|
273
|
+
emitPainDetectedEvent(wctx, {
|
|
274
|
+
ts: new Date().toISOString(),
|
|
275
|
+
type: 'pain_detected',
|
|
276
|
+
data: {
|
|
277
|
+
painId: `llm_${Date.now()}`,
|
|
278
|
+
painType: 'user_frustration' as const,
|
|
279
|
+
source,
|
|
280
|
+
reason: `${matchedReason}; diagnosticGate=${gate.reason}`,
|
|
281
|
+
score: painScore,
|
|
282
|
+
sessionId: ctx.sessionId || 'unknown',
|
|
283
|
+
agentId: ctx.agentId,
|
|
284
|
+
provenance: 'openclaw_context_bound',
|
|
285
|
+
evidence,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
} else {
|
|
289
|
+
ctx.logger?.info?.(`[PD:LLM] Pain signal recorded without Runtime V2 diagnosis: ${gate.detail}`);
|
|
290
|
+
}
|
|
263
291
|
}
|
|
264
292
|
|
|
265
293
|
// ═══ Thinking OS: Mental Model Usage Tracking ═══
|
|
@@ -268,8 +296,8 @@ export function handleLlmOutput(
|
|
|
268
296
|
trackThinkingModelUsage({
|
|
269
297
|
text,
|
|
270
298
|
wctx,
|
|
271
|
-
sessionId: ctx.sessionId,
|
|
272
|
-
runId: event.runId,
|
|
299
|
+
sessionId: ctx.sessionId ?? 'unknown',
|
|
300
|
+
runId: event.runId ?? 'unknown',
|
|
273
301
|
assistantTurnId,
|
|
274
302
|
createdAt,
|
|
275
303
|
logger: ctx.logger,
|