principles-disciple 1.7.6 → 1.7.8
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/dist/commands/context.js +5 -15
- package/dist/commands/evolution-status.js +2 -9
- package/dist/commands/export.js +61 -8
- package/dist/commands/nocturnal-review.d.ts +24 -0
- package/dist/commands/nocturnal-review.js +265 -0
- package/dist/commands/nocturnal-rollout.d.ts +27 -0
- package/dist/commands/nocturnal-rollout.js +671 -0
- package/dist/commands/nocturnal-train.d.ts +25 -0
- package/dist/commands/nocturnal-train.js +919 -0
- package/dist/commands/pain.js +8 -21
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +1 -1
- package/dist/core/adaptive-thresholds.d.ts +186 -0
- package/dist/core/adaptive-thresholds.js +300 -0
- package/dist/core/config.d.ts +2 -38
- package/dist/core/config.js +6 -61
- package/dist/core/event-log.d.ts +1 -2
- package/dist/core/event-log.js +0 -3
- package/dist/core/evolution-engine.js +1 -21
- package/dist/core/evolution-reducer.d.ts +7 -1
- package/dist/core/evolution-reducer.js +56 -4
- package/dist/core/evolution-types.d.ts +61 -9
- package/dist/core/evolution-types.js +31 -9
- package/dist/core/external-training-contract.d.ts +276 -0
- package/dist/core/external-training-contract.js +269 -0
- package/dist/core/local-worker-routing.d.ts +175 -0
- package/dist/core/local-worker-routing.js +525 -0
- package/dist/core/model-deployment-registry.d.ts +218 -0
- package/dist/core/model-deployment-registry.js +503 -0
- package/dist/core/model-training-registry.d.ts +295 -0
- package/dist/core/model-training-registry.js +475 -0
- package/dist/core/nocturnal-arbiter.d.ts +159 -0
- package/dist/core/nocturnal-arbiter.js +534 -0
- package/dist/core/nocturnal-candidate-scoring.d.ts +137 -0
- package/dist/core/nocturnal-candidate-scoring.js +266 -0
- package/dist/core/nocturnal-compliance.d.ts +175 -0
- package/dist/core/nocturnal-compliance.js +824 -0
- package/dist/core/nocturnal-dataset.d.ts +224 -0
- package/dist/core/nocturnal-dataset.js +443 -0
- package/dist/core/nocturnal-executability.d.ts +85 -0
- package/dist/core/nocturnal-executability.js +331 -0
- package/dist/core/nocturnal-export.d.ts +124 -0
- package/dist/core/nocturnal-export.js +275 -0
- package/dist/core/nocturnal-paths.d.ts +124 -0
- package/dist/core/nocturnal-paths.js +214 -0
- package/dist/core/nocturnal-trajectory-extractor.d.ts +242 -0
- package/dist/core/nocturnal-trajectory-extractor.js +307 -0
- package/dist/core/nocturnal-trinity.d.ts +311 -0
- package/dist/core/nocturnal-trinity.js +880 -0
- package/dist/core/paths.d.ts +6 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/principle-training-state.d.ts +121 -0
- package/dist/core/principle-training-state.js +321 -0
- package/dist/core/promotion-gate.d.ts +238 -0
- package/dist/core/promotion-gate.js +529 -0
- package/dist/core/session-tracker.d.ts +10 -0
- package/dist/core/session-tracker.js +14 -0
- package/dist/core/shadow-observation-registry.d.ts +217 -0
- package/dist/core/shadow-observation-registry.js +308 -0
- package/dist/core/training-program.d.ts +233 -0
- package/dist/core/training-program.js +433 -0
- package/dist/core/trajectory.d.ts +95 -1
- package/dist/core/trajectory.js +220 -6
- package/dist/core/workspace-context.d.ts +0 -6
- package/dist/core/workspace-context.js +0 -12
- package/dist/hooks/bash-risk.d.ts +6 -6
- package/dist/hooks/bash-risk.js +8 -8
- package/dist/hooks/gate-block-helper.js +1 -1
- package/dist/hooks/gate.d.ts +1 -1
- package/dist/hooks/gate.js +2 -2
- package/dist/hooks/gfi-gate.d.ts +3 -3
- package/dist/hooks/gfi-gate.js +15 -14
- package/dist/hooks/pain.js +6 -9
- package/dist/hooks/progressive-trust-gate.d.ts +21 -49
- package/dist/hooks/progressive-trust-gate.js +51 -204
- package/dist/hooks/prompt.d.ts +11 -11
- package/dist/hooks/prompt.js +158 -72
- package/dist/hooks/subagent.js +43 -6
- package/dist/i18n/commands.js +8 -8
- package/dist/index.js +129 -28
- package/dist/service/evolution-worker.d.ts +42 -4
- package/dist/service/evolution-worker.js +321 -13
- package/dist/service/nocturnal-runtime.d.ts +183 -0
- package/dist/service/nocturnal-runtime.js +352 -0
- package/dist/service/nocturnal-service.d.ts +163 -0
- package/dist/service/nocturnal-service.js +787 -0
- package/dist/service/nocturnal-target-selector.d.ts +145 -0
- package/dist/service/nocturnal-target-selector.js +315 -0
- package/dist/service/phase3-input-filter.d.ts +2 -23
- package/dist/service/phase3-input-filter.js +3 -27
- package/dist/service/runtime-summary-service.d.ts +0 -10
- package/dist/service/runtime-summary-service.js +1 -54
- package/dist/tools/deep-reflect.js +2 -1
- package/dist/types/event-types.d.ts +2 -10
- package/dist/types/runtime-summary.d.ts +1 -8
- package/dist/types.d.ts +0 -3
- package/dist/types.js +0 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/templates/langs/en/skills/pd-mentor/SKILL.md +5 -5
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +5 -5
- package/templates/pain_settings.json +0 -6
- package/dist/commands/trust.d.ts +0 -4
- package/dist/commands/trust.js +0 -78
- package/dist/core/trust-engine.d.ts +0 -96
- package/dist/core/trust-engine.js +0 -286
package/dist/hooks/gfi-gate.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handles Fatigue Index (GFI) based tool blocking with TIER 0-3 classification.
|
|
5
5
|
*
|
|
6
6
|
* **Responsibilities:**
|
|
7
|
-
* - Calculate dynamic GFI thresholds based on
|
|
7
|
+
* - Calculate dynamic GFI thresholds based on EP tier and line changes
|
|
8
8
|
* - Apply tier-based tool blocking:
|
|
9
9
|
* - TIER 0: Read-only tools (never blocked)
|
|
10
10
|
* - TIER 1: Low-risk writes (blocked when GFI >= low_risk_block threshold)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* **Configuration:**
|
|
16
16
|
* - GFI thresholds from config.gfi_gate
|
|
17
|
-
* -
|
|
17
|
+
* - EP tier multipliers for dynamic threshold calculation
|
|
18
18
|
* - Large change adjustments
|
|
19
19
|
*
|
|
20
20
|
* **Block Persistence:**
|
|
@@ -27,6 +27,7 @@ import { analyzeBashCommand, calculateDynamicThreshold } from './bash-risk.js';
|
|
|
27
27
|
import { BASH_TOOLS_SET, HIGH_RISK_TOOLS, LOW_RISK_WRITE_TOOLS, AGENT_TOOLS } from '../constants/tools.js';
|
|
28
28
|
import { AGENT_SPAWN_GFI_THRESHOLD } from '../config/index.js';
|
|
29
29
|
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
30
|
+
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
30
31
|
/**
|
|
31
32
|
* Internal helper to call the shared block helper with gfi-gate source tag.
|
|
32
33
|
*/
|
|
@@ -45,6 +46,9 @@ export function checkGfiGate(event, wctx, sessionId, config, logger) {
|
|
|
45
46
|
}
|
|
46
47
|
const session = getSession(sessionId);
|
|
47
48
|
const currentGfi = session?.currentGfi || 0;
|
|
49
|
+
const getEpTier = () => {
|
|
50
|
+
return getEvolutionEngine(wctx.workspaceDir).getTier();
|
|
51
|
+
};
|
|
48
52
|
// TIER 3: Bash commands
|
|
49
53
|
if (BASH_TOOLS_SET.has(event.toolName)) {
|
|
50
54
|
const command = String(event.params.command || event.params.args || '');
|
|
@@ -57,12 +61,11 @@ export function checkGfiGate(event, wctx, sessionId, config, logger) {
|
|
|
57
61
|
return undefined;
|
|
58
62
|
}
|
|
59
63
|
// normal bash - check GFI threshold
|
|
60
|
-
const
|
|
61
|
-
const stage = trustEngine.getStage();
|
|
64
|
+
const tier = getEpTier();
|
|
62
65
|
const baseThreshold = config.thresholds?.low_risk_block || 70;
|
|
63
|
-
const dynamicThreshold = calculateDynamicThreshold(baseThreshold,
|
|
66
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, tier, 0, {
|
|
64
67
|
large_change_lines: config.large_change_lines || 50,
|
|
65
|
-
|
|
68
|
+
ep_tier_multipliers: config.ep_tier_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5, '5': 2.0 },
|
|
66
69
|
});
|
|
67
70
|
if (currentGfi >= dynamicThreshold) {
|
|
68
71
|
logger?.warn?.(`[PD:GFI_GATE] Bash blocked by GFI: ${currentGfi} >= ${dynamicThreshold}`);
|
|
@@ -72,12 +75,11 @@ export function checkGfiGate(event, wctx, sessionId, config, logger) {
|
|
|
72
75
|
}
|
|
73
76
|
// TIER 2: High-risk tools
|
|
74
77
|
if (HIGH_RISK_TOOLS.has(event.toolName)) {
|
|
75
|
-
const
|
|
76
|
-
const stage = trustEngine.getStage();
|
|
78
|
+
const tier = getEpTier();
|
|
77
79
|
const baseThreshold = config.thresholds?.high_risk_block || 40;
|
|
78
|
-
const dynamicThreshold = calculateDynamicThreshold(baseThreshold,
|
|
80
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, tier, 0, {
|
|
79
81
|
large_change_lines: config.large_change_lines || 50,
|
|
80
|
-
|
|
82
|
+
ep_tier_multipliers: config.ep_tier_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5, '5': 2.0 },
|
|
81
83
|
});
|
|
82
84
|
if (currentGfi >= dynamicThreshold) {
|
|
83
85
|
const filePath = event.params.file_path || event.params.path || event.params.file || event.params.target || 'unknown';
|
|
@@ -87,13 +89,12 @@ export function checkGfiGate(event, wctx, sessionId, config, logger) {
|
|
|
87
89
|
}
|
|
88
90
|
// TIER 1: Low-risk write tools
|
|
89
91
|
if (LOW_RISK_WRITE_TOOLS.has(event.toolName)) {
|
|
90
|
-
const
|
|
91
|
-
const stage = trustEngine.getStage();
|
|
92
|
+
const tier = getEpTier();
|
|
92
93
|
const lineChanges = estimateLineChanges({ toolName: event.toolName, params: event.params });
|
|
93
94
|
const baseThreshold = config.thresholds?.low_risk_block || 70;
|
|
94
|
-
const dynamicThreshold = calculateDynamicThreshold(baseThreshold,
|
|
95
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, tier, lineChanges, {
|
|
95
96
|
large_change_lines: config.large_change_lines || 50,
|
|
96
|
-
|
|
97
|
+
ep_tier_multipliers: config.ep_tier_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5, '5': 2.0 },
|
|
97
98
|
});
|
|
98
99
|
if (currentGfi >= dynamicThreshold) {
|
|
99
100
|
const filePath = event.params.file_path || event.params.path || event.params.file || event.params.target || 'unknown';
|
package/dist/hooks/pain.js
CHANGED
|
@@ -7,6 +7,7 @@ import { denoiseError, computeHash } from '../utils/hashing.js';
|
|
|
7
7
|
import { SystemLogger } from '../core/system-logger.js';
|
|
8
8
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
9
9
|
import { getEvolutionLogger, createTraceId } from '../core/evolution-logger.js';
|
|
10
|
+
import { recordEvolutionSuccess, recordEvolutionFailure } from '../core/evolution-engine.js';
|
|
10
11
|
const WRITE_TOOLS = ['write', 'edit', 'apply_patch', 'write_file', 'edit_file', 'replace'];
|
|
11
12
|
function shouldAttributePrincipleToTool(principle, toolName) {
|
|
12
13
|
return principle.contextTags.includes(toolName) || principle.trigger.includes(toolName);
|
|
@@ -30,7 +31,6 @@ export function handleAfterToolCall(event, ctx, api) {
|
|
|
30
31
|
const wctx = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir: effectiveWorkspaceDir });
|
|
31
32
|
const config = wctx.config;
|
|
32
33
|
const eventLog = wctx.eventLog;
|
|
33
|
-
const trust = wctx.trust;
|
|
34
34
|
const sessionId = ctx.sessionId || 'unknown';
|
|
35
35
|
const sessionState = ctx.sessionId ? getSession(ctx.sessionId) : undefined;
|
|
36
36
|
const gfiBefore = sessionState?.currentGfi ?? 0;
|
|
@@ -106,11 +106,10 @@ export function handleAfterToolCall(event, ctx, api) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
const isRisk = isRisky(relPath, profile.risk_paths);
|
|
109
|
-
|
|
109
|
+
recordEvolutionFailure(effectiveWorkspaceDir, event.toolName, {
|
|
110
|
+
filePath: relPath,
|
|
111
|
+
reason: isRisk ? 'risky' : 'tool',
|
|
110
112
|
sessionId,
|
|
111
|
-
api,
|
|
112
|
-
toolName: event.toolName,
|
|
113
|
-
error: event.error // Pass error for timeout detection
|
|
114
113
|
});
|
|
115
114
|
// Record tool call failure event
|
|
116
115
|
eventLog.recordToolCall(sessionId, {
|
|
@@ -147,11 +146,9 @@ export function handleAfterToolCall(event, ctx, api) {
|
|
|
147
146
|
else {
|
|
148
147
|
// ── SUCCESS BRANCH ──
|
|
149
148
|
const resetState = resetFriction(sessionId, effectiveWorkspaceDir);
|
|
150
|
-
|
|
151
|
-
trust.recordSuccess('tool_success', {
|
|
149
|
+
recordEvolutionSuccess(effectiveWorkspaceDir, event.toolName, {
|
|
152
150
|
sessionId,
|
|
153
|
-
|
|
154
|
-
toolName: event.toolName // 👈 NEW: Pass toolName for classification
|
|
151
|
+
reason: 'tool_success',
|
|
155
152
|
});
|
|
156
153
|
const injectedProbationIds = getInjectedProbationIds(sessionId, effectiveWorkspaceDir);
|
|
157
154
|
for (const id of injectedProbationIds) {
|
|
@@ -1,66 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Progressive
|
|
2
|
+
* Progressive Gate Module (EP-Only Version)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* EP (Evolution Points) 是唯一的门控机制。
|
|
5
5
|
*
|
|
6
|
-
* **
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Apply percentage-based line change limits for large files
|
|
13
|
-
* - Handle plan approval whitelist for Stage 1
|
|
14
|
-
* - Record EP simulation for comparison analysis
|
|
6
|
+
* **EP 门控逻辑:**
|
|
7
|
+
* - Seed (0分): 只读 + 基础文档
|
|
8
|
+
* - Sprout (50分): 单文件编辑
|
|
9
|
+
* - Sapling (200分): 多文件 + 测试 + 子智能体
|
|
10
|
+
* - Tree (500分): 重构 + 风险路径
|
|
11
|
+
* - Forest (1000分): 完全自主
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* - Plan approval patterns and operations
|
|
13
|
+
* **风险路径控制:**
|
|
14
|
+
* - 低等级不能修改风险路径
|
|
15
|
+
* - 高等级解锁风险路径权限
|
|
20
16
|
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
17
|
+
* **不再有:**
|
|
18
|
+
* - Trust Score (30-100) 系统
|
|
19
|
+
* - Stage 1-4 分级
|
|
20
|
+
* - Plan Approval 白名单机制
|
|
21
|
+
* - 基于行数的限制
|
|
24
22
|
*/
|
|
25
23
|
import type { PluginHookBeforeToolCallEvent, PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
|
|
26
24
|
import type { WorkspaceContext } from '../core/workspace-context.js';
|
|
27
25
|
/**
|
|
28
|
-
*
|
|
26
|
+
* Build EP gate rejection reason
|
|
29
27
|
*/
|
|
30
|
-
export
|
|
31
|
-
enabled?: boolean;
|
|
32
|
-
plan_approvals?: {
|
|
33
|
-
enabled?: boolean;
|
|
34
|
-
max_lines_override?: number;
|
|
35
|
-
allowed_patterns?: string[];
|
|
36
|
-
allowed_operations?: string[];
|
|
37
|
-
};
|
|
38
|
-
}
|
|
28
|
+
export declare function buildEvolutionGateReason(tier: number, tierName: string, reason: string): string;
|
|
39
29
|
/**
|
|
40
|
-
*
|
|
41
|
-
*/
|
|
42
|
-
export interface TrustLimits {
|
|
43
|
-
stage_2_max_lines?: number;
|
|
44
|
-
stage_3_max_lines?: number;
|
|
45
|
-
stage_2_max_percentage?: number;
|
|
46
|
-
stage_3_max_percentage?: number;
|
|
47
|
-
min_lines_fallback?: number;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Build line limit rejection reason
|
|
51
|
-
*/
|
|
52
|
-
export declare function buildLineLimitReason(lineChanges: number, effectiveLimit: number, limitType: 'percentage' | 'fixed', targetLineCount: number | null, actualPercentage: number | null, stage: number): string;
|
|
53
|
-
/**
|
|
54
|
-
* Check progressive trust gate based on trust stage and operation context
|
|
30
|
+
* Check EP-based gate
|
|
55
31
|
*
|
|
56
32
|
* @param event - The tool call event
|
|
57
33
|
* @param wctx - Workspace context
|
|
58
34
|
* @param relPath - Relative path to target file
|
|
59
35
|
* @param risky - Whether the path is a risk path
|
|
60
|
-
* @param lineChanges - Estimated line changes
|
|
36
|
+
* @param lineChanges - Estimated line changes (kept for interface compatibility, not used for gating)
|
|
61
37
|
* @param logger - Logger instance
|
|
62
38
|
* @param ctx - Hook context
|
|
63
|
-
* @param profile - Gate profile containing risk_paths
|
|
39
|
+
* @param profile - Gate profile containing risk_paths config
|
|
64
40
|
* @returns PluginHookBeforeToolCallResult to block, or undefined to allow
|
|
65
41
|
*/
|
|
66
42
|
export declare function checkProgressiveTrustGate(event: PluginHookBeforeToolCallEvent, wctx: WorkspaceContext, relPath: string, risky: boolean, lineChanges: number, logger: {
|
|
@@ -72,8 +48,4 @@ export declare function checkProgressiveTrustGate(event: PluginHookBeforeToolCal
|
|
|
72
48
|
sessionId?: string;
|
|
73
49
|
}, profile?: {
|
|
74
50
|
risk_paths: string[];
|
|
75
|
-
progressive_gate?: {
|
|
76
|
-
enabled?: boolean;
|
|
77
|
-
plan_approvals?: any;
|
|
78
|
-
};
|
|
79
51
|
}): PluginHookBeforeToolCallResult | void;
|
|
@@ -1,49 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Progressive
|
|
2
|
+
* Progressive Gate Module (EP-Only Version)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* EP (Evolution Points) 是唯一的门控机制。
|
|
5
5
|
*
|
|
6
|
-
* **
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - Apply percentage-based line change limits for large files
|
|
13
|
-
* - Handle plan approval whitelist for Stage 1
|
|
14
|
-
* - Record EP simulation for comparison analysis
|
|
6
|
+
* **EP 门控逻辑:**
|
|
7
|
+
* - Seed (0分): 只读 + 基础文档
|
|
8
|
+
* - Sprout (50分): 单文件编辑
|
|
9
|
+
* - Sapling (200分): 多文件 + 测试 + 子智能体
|
|
10
|
+
* - Tree (500分): 重构 + 风险路径
|
|
11
|
+
* - Forest (1000分): 完全自主
|
|
15
12
|
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
* - Plan approval patterns and operations
|
|
13
|
+
* **风险路径控制:**
|
|
14
|
+
* - 低等级不能修改风险路径
|
|
15
|
+
* - 高等级解锁风险路径权限
|
|
20
16
|
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
17
|
+
* **不再有:**
|
|
18
|
+
* - Trust Score (30-100) 系统
|
|
19
|
+
* - Stage 1-4 分级
|
|
20
|
+
* - Plan Approval 白名单机制
|
|
21
|
+
* - 基于行数的限制
|
|
24
22
|
*/
|
|
25
|
-
import * as fs from 'fs';
|
|
26
|
-
import * as path from 'path';
|
|
27
|
-
import { planStatus as getPlanStatus } from '../utils/io.js';
|
|
28
|
-
import { matchesAnyPattern } from '../utils/glob-match.js';
|
|
29
|
-
import { assessRiskLevel, getTargetFileLineCount, calculatePercentageThreshold } from '../core/risk-calculator.js';
|
|
30
23
|
import { checkEvolutionGate } from '../core/evolution-engine.js';
|
|
31
|
-
import { EventLogService } from '../core/event-log.js';
|
|
32
24
|
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
33
25
|
/**
|
|
34
|
-
* Build
|
|
26
|
+
* Build EP gate rejection reason
|
|
35
27
|
*/
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
return `Modification too large: ${lineChanges} lines (${actualPercentage}% of ${targetLineCount} lines). ` +
|
|
39
|
-
`Stage ${stage} limit is ${effectiveLimit} lines (${limitType}). ` +
|
|
40
|
-
`Threshold calculation: min(${targetLineCount} × ${actualPercentage}%, ${effectiveLimit} lines).`;
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
return `Modification too large: ${lineChanges} lines. ` +
|
|
44
|
-
`Stage ${stage} limit is ${effectiveLimit} lines (fixed threshold). ` +
|
|
45
|
-
`Note: Could not read target file to calculate percentage-based limit. Check file permissions and encoding.`;
|
|
46
|
-
}
|
|
28
|
+
export function buildEvolutionGateReason(tier, tierName, reason) {
|
|
29
|
+
return `[EP Gate] Tier ${tier} (${tierName}): ${reason}`;
|
|
47
30
|
}
|
|
48
31
|
/**
|
|
49
32
|
* Internal helper to call the shared block helper with progressive-trust-gate source tag.
|
|
@@ -58,185 +41,49 @@ function block(filePath, reason, wctx, toolName, logger, sessionId) {
|
|
|
58
41
|
}, logger);
|
|
59
42
|
}
|
|
60
43
|
/**
|
|
61
|
-
* Check
|
|
44
|
+
* Check EP-based gate
|
|
62
45
|
*
|
|
63
46
|
* @param event - The tool call event
|
|
64
47
|
* @param wctx - Workspace context
|
|
65
48
|
* @param relPath - Relative path to target file
|
|
66
49
|
* @param risky - Whether the path is a risk path
|
|
67
|
-
* @param lineChanges - Estimated line changes
|
|
50
|
+
* @param lineChanges - Estimated line changes (kept for interface compatibility, not used for gating)
|
|
68
51
|
* @param logger - Logger instance
|
|
69
52
|
* @param ctx - Hook context
|
|
70
|
-
* @param profile - Gate profile containing risk_paths
|
|
53
|
+
* @param profile - Gate profile containing risk_paths config
|
|
71
54
|
* @returns PluginHookBeforeToolCallResult to block, or undefined to allow
|
|
72
55
|
*/
|
|
73
56
|
export function checkProgressiveTrustGate(event, wctx, relPath, risky, lineChanges, logger, ctx, profile) {
|
|
74
|
-
//
|
|
75
|
-
if (
|
|
57
|
+
// EP is the only gate now - use actual gate decision
|
|
58
|
+
if (!ctx.workspaceDir) {
|
|
59
|
+
logger.warn?.('[PD_GATE] No workspaceDir, skipping EP gate check');
|
|
76
60
|
return;
|
|
77
61
|
}
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
};
|
|
90
|
-
const riskLevel = assessRiskLevel(relPath, { toolName: event.toolName, params: event.params }, profile?.risk_paths || []);
|
|
91
|
-
const planApprovals = profile?.progressive_gate?.plan_approvals;
|
|
92
|
-
const canUsePlanApproval = Boolean(stage === 1 &&
|
|
93
|
-
planApprovals?.enabled &&
|
|
94
|
-
getPlanStatus(ctx.workspaceDir || '') === 'READY' &&
|
|
95
|
-
planApprovals.allowed_operations?.includes(event.toolName) &&
|
|
96
|
-
matchesAnyPattern(relPath, planApprovals.allowed_patterns || []) &&
|
|
97
|
-
((planApprovals.max_lines_override ?? -1) === -1 || lineChanges <= (planApprovals.max_lines_override ?? -1)));
|
|
98
|
-
logger.info?.(`[PD_GATE] Trust: ${trustScore} (Stage ${stage}), Risk: ${riskLevel}, Path: ${relPath}`);
|
|
99
|
-
// ── EP SIMULATION MODE (M6验证) ──
|
|
100
|
-
// 记录EP系统的模拟决策,但不生效(仅用于对比分析)
|
|
101
|
-
// BUGFIX #90: 移到所有Stage检查之前,确保所有Stage都触发EP simulation记录
|
|
102
|
-
try {
|
|
103
|
-
const epDecision = checkEvolutionGate(ctx.workspaceDir, {
|
|
104
|
-
toolName: event.toolName,
|
|
105
|
-
content: event.params?.content,
|
|
106
|
-
lineCount: lineChanges,
|
|
107
|
-
isRiskPath: risky,
|
|
108
|
-
});
|
|
109
|
-
const epLogEntry = {
|
|
110
|
-
timestamp: new Date().toISOString(),
|
|
111
|
-
toolName: event.toolName,
|
|
112
|
-
filePath: relPath,
|
|
113
|
-
trustEngine: { score: trustScore, stage, decision: 'allow' },
|
|
114
|
-
epSystem: { tier: epDecision.currentTier ?? 'UNKNOWN', allowed: epDecision.allowed, reason: epDecision.reason },
|
|
115
|
-
conflict: epDecision.allowed === false, // Trust允许但EP拒绝(任何阶段)
|
|
116
|
-
};
|
|
117
|
-
const epLogPath = path.join(ctx.workspaceDir, '.state', 'ep_simulation.jsonl');
|
|
118
|
-
// 安全创建目录(如果失败则跳过日志写入,但不影响 Trust Engine 决策)
|
|
119
|
-
let canWriteEpLog = true;
|
|
120
|
-
try {
|
|
121
|
-
fs.mkdirSync(path.dirname(epLogPath), { recursive: true });
|
|
122
|
-
}
|
|
123
|
-
catch (mkdirErr) {
|
|
124
|
-
if (!mkdirErr || mkdirErr.code !== 'EEXIST') {
|
|
125
|
-
logger.warn?.(`[PD_EP_SIM] Failed to create log dir: ${mkdirErr?.message ?? String(mkdirErr)}, skipping log`);
|
|
126
|
-
canWriteEpLog = false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (canWriteEpLog) {
|
|
130
|
-
fs.appendFileSync(epLogPath, JSON.stringify(epLogEntry) + '\n');
|
|
131
|
-
}
|
|
132
|
-
logger.info?.(`[PD_EP_SIM] Tier: ${epDecision.currentTier}, Allowed: ${epDecision.allowed}, Trust: ${trustScore} (Stage ${stage})`);
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
// EP 模拟失败不应该影响 Trust Engine 决策
|
|
136
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
137
|
-
logger.warn?.(`[PD_EP_SIM] Simulation failed: ${errMsg}, continuing with Trust Engine`);
|
|
138
|
-
}
|
|
139
|
-
// Stage 1 (Bankruptcy): Block ALL writes to risk paths, and all medium+ writes
|
|
140
|
-
if (stage === 1) {
|
|
141
|
-
if (canUsePlanApproval) {
|
|
142
|
-
const planStatus = 'READY';
|
|
143
|
-
wctx.eventLog.recordPlanApproval(ctx.sessionId, {
|
|
144
|
-
toolName: event.toolName,
|
|
145
|
-
filePath: relPath,
|
|
146
|
-
pattern: relPath,
|
|
147
|
-
planStatus
|
|
148
|
-
});
|
|
149
|
-
wctx.trajectory?.recordGateBlock?.({
|
|
150
|
-
sessionId: ctx.sessionId,
|
|
151
|
-
toolName: event.toolName,
|
|
152
|
-
filePath: relPath,
|
|
153
|
-
reason: 'plan_approval',
|
|
154
|
-
planStatus,
|
|
155
|
-
});
|
|
156
|
-
logger.info?.(`[PD_GATE] Stage 1 PLAN approval: ${relPath}`);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (risky || riskLevel !== 'LOW') {
|
|
160
|
-
// Block if not approved by whitelist
|
|
161
|
-
return block(relPath, `Trust score too low (${trustScore}). Stage 1 agents cannot modify risk paths or perform non-trivial edits.`, wctx, event.toolName, logger, ctx.sessionId);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Stage 2 (Editor): Block writes to risk paths. Block large changes.
|
|
165
|
-
if (stage === 2) {
|
|
166
|
-
if (risky) {
|
|
167
|
-
return block(relPath, `Stage 2 agents are not authorized to modify risk paths.`, wctx, event.toolName, logger, ctx.sessionId);
|
|
168
|
-
}
|
|
169
|
-
// Percentage-based threshold calculation
|
|
170
|
-
const targetAbsolutePath = typeof event.params.file_path === 'string' && ctx.workspaceDir ? path.join(ctx.workspaceDir, event.params.file_path) : null;
|
|
171
|
-
const targetLineCount = targetAbsolutePath ? getTargetFileLineCount(targetAbsolutePath) : null;
|
|
172
|
-
const minLinesFallback = trustSettings.limits?.min_lines_fallback ?? 20;
|
|
173
|
-
const stage2MaxPercentage = trustSettings.limits?.stage_2_max_percentage ?? 10;
|
|
174
|
-
const stage2FixedLimit = trustSettings.limits?.stage_2_max_lines ?? 50;
|
|
175
|
-
let effectiveLimit;
|
|
176
|
-
let limitType;
|
|
177
|
-
let actualPercentage = null;
|
|
178
|
-
if (targetLineCount !== null && targetLineCount > 0) {
|
|
179
|
-
effectiveLimit = calculatePercentageThreshold(targetLineCount, stage2MaxPercentage, minLinesFallback);
|
|
180
|
-
actualPercentage = Math.round((lineChanges / targetLineCount) * 100);
|
|
181
|
-
limitType = 'percentage';
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
effectiveLimit = stage2FixedLimit;
|
|
185
|
-
limitType = 'fixed';
|
|
186
|
-
}
|
|
187
|
-
if (lineChanges > effectiveLimit) {
|
|
188
|
-
return block(relPath, buildLineLimitReason(lineChanges, effectiveLimit, limitType, targetLineCount, actualPercentage, 2), wctx, event.toolName, logger, ctx.sessionId);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
// Stage 3 (Developer): Allow normal writes. Require READY plan for risk paths.
|
|
192
|
-
if (stage === 3) {
|
|
193
|
-
if (risky) {
|
|
194
|
-
const planStatus = getPlanStatus(ctx.workspaceDir || '');
|
|
195
|
-
if (planStatus !== 'READY') {
|
|
196
|
-
return block(relPath, `No READY plan found. Stage 3 requires a plan for risk path modifications.`, wctx, event.toolName, logger, ctx.sessionId);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Percentage-based threshold calculation
|
|
200
|
-
const targetAbsolutePath = typeof event.params.file_path === 'string' && ctx.workspaceDir ? path.join(ctx.workspaceDir, event.params.file_path) : null;
|
|
201
|
-
const targetLineCount = targetAbsolutePath ? getTargetFileLineCount(targetAbsolutePath) : null;
|
|
202
|
-
const minLinesFallback = trustSettings.limits?.min_lines_fallback ?? 20;
|
|
203
|
-
const stage3MaxPercentage = trustSettings.limits?.stage_3_max_percentage ?? 15;
|
|
204
|
-
const stage3FixedLimit = trustSettings.limits?.stage_3_max_lines ?? 300;
|
|
205
|
-
let effectiveLimit;
|
|
206
|
-
let limitType;
|
|
207
|
-
let actualPercentage = null;
|
|
208
|
-
if (targetLineCount !== null && targetLineCount > 0) {
|
|
209
|
-
effectiveLimit = calculatePercentageThreshold(targetLineCount, stage3MaxPercentage, minLinesFallback);
|
|
210
|
-
actualPercentage = Math.round((lineChanges / targetLineCount) * 100);
|
|
211
|
-
limitType = 'percentage';
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
effectiveLimit = stage3FixedLimit;
|
|
215
|
-
limitType = 'fixed';
|
|
216
|
-
}
|
|
217
|
-
if (lineChanges > effectiveLimit) {
|
|
218
|
-
return block(relPath, buildLineLimitReason(lineChanges, effectiveLimit, limitType, targetLineCount, actualPercentage, 3), wctx, event.toolName, logger, ctx.sessionId);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
// Stage 4 (Architect): Full bypass
|
|
222
|
-
if (stage === 4) {
|
|
223
|
-
logger.info?.(`[PD_GATE] Trusted Architect bypass for ${relPath}`);
|
|
224
|
-
// Audit log for Stage 4 bypass (security traceability)
|
|
225
|
-
try {
|
|
226
|
-
const stateDir = wctx.resolve('STATE_DIR');
|
|
227
|
-
const eventLog = EventLogService.get(stateDir);
|
|
228
|
-
eventLog.recordGateBypass(ctx.sessionId, {
|
|
229
|
-
toolName: event.toolName,
|
|
230
|
-
filePath: relPath,
|
|
231
|
-
bypassType: 'stage4_architect',
|
|
232
|
-
trustScore,
|
|
233
|
-
trustStage: stage,
|
|
234
|
-
});
|
|
235
|
-
logger.info?.(`[PD_GATE] Stage 4 Architect bypass recorded for ${relPath}`);
|
|
236
|
-
}
|
|
237
|
-
catch (auditErr) {
|
|
238
|
-
logger.warn?.(`[PD_GATE] Failed to record Stage 4 bypass audit: ${String(auditErr)}`);
|
|
239
|
-
}
|
|
240
|
-
return;
|
|
62
|
+
// Call EP gate - this is the actual gate, not simulation
|
|
63
|
+
const epDecision = checkEvolutionGate(ctx.workspaceDir, {
|
|
64
|
+
toolName: event.toolName,
|
|
65
|
+
isRiskPath: risky,
|
|
66
|
+
});
|
|
67
|
+
const currentTier = epDecision.currentTier ?? 1;
|
|
68
|
+
const tierName = getTierName(currentTier);
|
|
69
|
+
logger.info?.(`[PD_GATE] EP Gate: Tier ${currentTier} (${tierName}), Tool: ${event.toolName}, Risk: ${risky}, Allowed: ${epDecision.allowed}`);
|
|
70
|
+
if (!epDecision.allowed) {
|
|
71
|
+
const reason = buildEvolutionGateReason(currentTier, tierName, epDecision.reason ?? 'Unknown restriction');
|
|
72
|
+
return block(relPath, reason, wctx, event.toolName, logger, ctx.sessionId);
|
|
241
73
|
}
|
|
74
|
+
// Gate passed - allow
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get tier name from tier number
|
|
79
|
+
*/
|
|
80
|
+
function getTierName(tier) {
|
|
81
|
+
const names = {
|
|
82
|
+
1: 'Seed',
|
|
83
|
+
2: 'Sprout',
|
|
84
|
+
3: 'Sapling',
|
|
85
|
+
4: 'Tree',
|
|
86
|
+
5: 'Forest',
|
|
87
|
+
};
|
|
88
|
+
return names[tier] ?? 'Unknown';
|
|
242
89
|
}
|
package/dist/hooks/prompt.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { PluginHookBeforePromptBuildEvent, PluginHookAgentContext, PluginHo
|
|
|
2
2
|
import { ContextInjectionConfig } from '../types.js';
|
|
3
3
|
import { type EmpathyObserverApi } from '../service/empathy-observer-manager.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Default model configuration for OpenClaw agents
|
|
6
6
|
*/
|
|
7
7
|
interface AgentsDefaultsConfig {
|
|
8
8
|
model?: unknown;
|
|
@@ -23,22 +23,22 @@ interface PromptHookApi {
|
|
|
23
23
|
logger: PluginLogger;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* @internal
|
|
26
|
+
* Resolves model configuration for OpenClaw agents, supporting string and object formats
|
|
27
|
+
* @param modelConfig - Model config: string (e.g. "provider/model") or { primary, fallbacks } object
|
|
28
|
+
* @internal Helper for model configuration resolution
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveModelFromConfig(modelConfig: unknown, logger?: PluginLogger): string | null;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
32
|
+
* Loads context injection config from .principles/PROFILE.json
|
|
33
|
+
* Parses contextInjection configuration from PROFILE.json for context injection
|
|
34
|
+
* @internal Used by evolution engine for context settings
|
|
35
35
|
*/
|
|
36
36
|
export declare function loadContextInjectionConfig(workspaceDir: string): ContextInjectionConfig;
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @internal
|
|
38
|
+
* Gets the diagnostician model - the model used for AI self-diagnosis and reflection
|
|
39
|
+
* Priority: subagents.model > subagents.model > env.OPENCLAW_MODEL
|
|
40
|
+
* Falls back to main model if no diagnostician model is configured
|
|
41
|
+
* @internal Helper for model configuration resolution
|
|
42
42
|
*/
|
|
43
43
|
export declare function getDiagnosticianModel(api: PromptHookApi | null, logger?: PluginLogger): string;
|
|
44
44
|
export declare function handleBeforePromptBuild(event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext & {
|