principles-disciple 1.7.4 → 1.7.6
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/evolution-status.js +32 -44
- package/dist/commands/focus.js +30 -155
- package/dist/config/defaults/runtime.d.ts +40 -0
- package/dist/config/defaults/runtime.js +44 -0
- package/dist/config/errors.d.ts +84 -0
- package/dist/config/errors.js +94 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.js +7 -0
- package/dist/constants/diagnostician.d.ts +12 -0
- package/dist/constants/diagnostician.js +56 -0
- package/dist/constants/tools.d.ts +2 -2
- package/dist/constants/tools.js +1 -1
- package/dist/core/config.d.ts +12 -0
- package/dist/core/config.js +7 -0
- package/dist/core/control-ui-db.d.ts +27 -0
- package/dist/core/control-ui-db.js +18 -0
- package/dist/core/evolution-engine.js +1 -1
- package/dist/core/focus-history.d.ts +92 -0
- package/dist/core/focus-history.js +490 -0
- package/dist/core/init.js +2 -2
- package/dist/core/path-resolver.js +2 -1
- package/dist/core/profile.js +1 -1
- package/dist/core/trajectory.d.ts +60 -0
- package/dist/core/trajectory.js +72 -2
- package/dist/hooks/bash-risk.d.ts +57 -0
- package/dist/hooks/bash-risk.js +137 -0
- package/dist/hooks/edit-verification.d.ts +62 -0
- package/dist/hooks/edit-verification.js +256 -0
- package/dist/hooks/gate-block-helper.d.ts +44 -0
- package/dist/hooks/gate-block-helper.js +119 -0
- package/dist/hooks/gate.d.ts +18 -0
- package/dist/hooks/gate.js +63 -752
- package/dist/hooks/gfi-gate.d.ts +40 -0
- package/dist/hooks/gfi-gate.js +112 -0
- package/dist/hooks/progressive-trust-gate.d.ts +79 -0
- package/dist/hooks/progressive-trust-gate.js +242 -0
- package/dist/hooks/prompt.js +83 -28
- package/dist/hooks/subagent.js +1 -2
- package/dist/hooks/thinking-checkpoint.d.ts +37 -0
- package/dist/hooks/thinking-checkpoint.js +51 -0
- package/dist/http/principles-console-route.d.ts +7 -0
- package/dist/http/principles-console-route.js +255 -3
- package/dist/index.js +0 -2
- package/dist/service/central-database.d.ts +104 -0
- package/dist/service/central-database.js +649 -0
- package/dist/service/control-ui-query-service.d.ts +1 -1
- package/dist/service/control-ui-query-service.js +3 -3
- package/dist/service/evolution-query-service.d.ts +1 -1
- package/dist/service/evolution-query-service.js +5 -5
- package/dist/service/evolution-worker.d.ts +10 -0
- package/dist/service/evolution-worker.js +10 -6
- package/dist/service/phase3-input-filter.d.ts +57 -0
- package/dist/service/phase3-input-filter.js +93 -3
- package/dist/service/runtime-summary-service.d.ts +34 -0
- package/dist/service/runtime-summary-service.js +93 -1
- package/dist/tools/deep-reflect.js +1 -2
- package/dist/types/event-types.d.ts +2 -0
- package/dist/types/runtime-summary.d.ts +54 -0
- package/dist/types/runtime-summary.js +1 -0
- package/dist/utils/subagent-probe.d.ts +11 -0
- package/dist/utils/subagent-probe.js +46 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/templates/langs/en/core/AGENTS.md +1 -1
- package/templates/langs/en/core/TOOLS.md +1 -1
- package/templates/langs/zh/core/AGENTS.md +1 -1
- package/templates/langs/zh/core/TOOLS.md +1 -1
- package/{agents/auditor.md → templates/langs/zh/skills/pd-auditor/SKILL.md} +3 -3
- package/{agents/diagnostician.md → templates/langs/zh/skills/pd-diagnostician/SKILL.md} +39 -29
- package/{agents/explorer.md → templates/langs/zh/skills/pd-explorer/SKILL.md} +3 -3
- package/{agents/implementer.md → templates/langs/zh/skills/pd-implementer/SKILL.md} +3 -3
- package/{agents/planner.md → templates/langs/zh/skills/pd-planner/SKILL.md} +3 -3
- package/{agents/reporter.md → templates/langs/zh/skills/pd-reporter/SKILL.md} +3 -3
- package/{agents/reviewer.md → templates/langs/zh/skills/pd-reviewer/SKILL.md} +3 -3
- package/dist/core/agent-loader.d.ts +0 -44
- package/dist/core/agent-loader.js +0 -147
- package/dist/tools/agent-spawn.d.ts +0 -54
- package/dist/tools/agent-spawn.js +0 -456
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GFI Gate Module
|
|
3
|
+
*
|
|
4
|
+
* Handles Fatigue Index (GFI) based tool blocking with TIER 0-3 classification.
|
|
5
|
+
*
|
|
6
|
+
* **Responsibilities:**
|
|
7
|
+
* - Calculate dynamic GFI thresholds based on trust stage and line changes
|
|
8
|
+
* - Apply tier-based tool blocking:
|
|
9
|
+
* - TIER 0: Read-only tools (never blocked)
|
|
10
|
+
* - TIER 1: Low-risk writes (blocked when GFI >= low_risk_block threshold)
|
|
11
|
+
* - TIER 2: High-risk operations (blocked when GFI >= high_risk_block threshold)
|
|
12
|
+
* - TIER 3: Bash commands (content-dependent blocking)
|
|
13
|
+
* - Prevent subagent spawn at critically high GFI (>=90)
|
|
14
|
+
*
|
|
15
|
+
* **Configuration:**
|
|
16
|
+
* - GFI thresholds from config.gfi_gate
|
|
17
|
+
* - Trust stage multipliers for dynamic threshold calculation
|
|
18
|
+
* - Large change adjustments
|
|
19
|
+
*
|
|
20
|
+
* **Block Persistence:**
|
|
21
|
+
* - Uses shared `recordGateBlockAndReturn` from gate-block-helper.ts
|
|
22
|
+
* - Ensures single authoritative block persistence path
|
|
23
|
+
*/
|
|
24
|
+
import type { WorkspaceContext } from '../core/workspace-context.js';
|
|
25
|
+
import type { PluginHookBeforeToolCallEvent, PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
|
|
26
|
+
export interface GfiGateConfig {
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
thresholds?: {
|
|
29
|
+
low_risk_block?: number;
|
|
30
|
+
high_risk_block?: number;
|
|
31
|
+
};
|
|
32
|
+
large_change_lines?: number;
|
|
33
|
+
trust_stage_multipliers?: Record<string, number>;
|
|
34
|
+
bash_safe_patterns?: string[];
|
|
35
|
+
bash_dangerous_patterns?: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function checkGfiGate(event: PluginHookBeforeToolCallEvent, wctx: WorkspaceContext, sessionId: string | undefined, config: GfiGateConfig, logger?: {
|
|
38
|
+
info?: (message: string) => void;
|
|
39
|
+
warn?: (message: string) => void;
|
|
40
|
+
}): PluginHookBeforeToolCallResult | undefined;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GFI Gate Module
|
|
3
|
+
*
|
|
4
|
+
* Handles Fatigue Index (GFI) based tool blocking with TIER 0-3 classification.
|
|
5
|
+
*
|
|
6
|
+
* **Responsibilities:**
|
|
7
|
+
* - Calculate dynamic GFI thresholds based on trust stage and line changes
|
|
8
|
+
* - Apply tier-based tool blocking:
|
|
9
|
+
* - TIER 0: Read-only tools (never blocked)
|
|
10
|
+
* - TIER 1: Low-risk writes (blocked when GFI >= low_risk_block threshold)
|
|
11
|
+
* - TIER 2: High-risk operations (blocked when GFI >= high_risk_block threshold)
|
|
12
|
+
* - TIER 3: Bash commands (content-dependent blocking)
|
|
13
|
+
* - Prevent subagent spawn at critically high GFI (>=90)
|
|
14
|
+
*
|
|
15
|
+
* **Configuration:**
|
|
16
|
+
* - GFI thresholds from config.gfi_gate
|
|
17
|
+
* - Trust stage multipliers for dynamic threshold calculation
|
|
18
|
+
* - Large change adjustments
|
|
19
|
+
*
|
|
20
|
+
* **Block Persistence:**
|
|
21
|
+
* - Uses shared `recordGateBlockAndReturn` from gate-block-helper.ts
|
|
22
|
+
* - Ensures single authoritative block persistence path
|
|
23
|
+
*/
|
|
24
|
+
import { getSession } from '../core/session-tracker.js';
|
|
25
|
+
import { estimateLineChanges } from '../core/risk-calculator.js';
|
|
26
|
+
import { analyzeBashCommand, calculateDynamicThreshold } from './bash-risk.js';
|
|
27
|
+
import { BASH_TOOLS_SET, HIGH_RISK_TOOLS, LOW_RISK_WRITE_TOOLS, AGENT_TOOLS } from '../constants/tools.js';
|
|
28
|
+
import { AGENT_SPAWN_GFI_THRESHOLD } from '../config/index.js';
|
|
29
|
+
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
30
|
+
/**
|
|
31
|
+
* Internal helper to call the shared block helper with gfi-gate source tag.
|
|
32
|
+
*/
|
|
33
|
+
function block(wctx, filePath, reason, toolName, sessionId, logger) {
|
|
34
|
+
return recordGateBlockAndReturn(wctx, {
|
|
35
|
+
filePath,
|
|
36
|
+
reason,
|
|
37
|
+
toolName,
|
|
38
|
+
sessionId,
|
|
39
|
+
blockSource: 'gfi-gate',
|
|
40
|
+
}, logger || { warn: () => { }, error: () => { } });
|
|
41
|
+
}
|
|
42
|
+
export function checkGfiGate(event, wctx, sessionId, config, logger) {
|
|
43
|
+
if (!config || config.enabled === false || !sessionId) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const session = getSession(sessionId);
|
|
47
|
+
const currentGfi = session?.currentGfi || 0;
|
|
48
|
+
// TIER 3: Bash commands
|
|
49
|
+
if (BASH_TOOLS_SET.has(event.toolName)) {
|
|
50
|
+
const command = String(event.params.command || event.params.args || '');
|
|
51
|
+
const bashRisk = analyzeBashCommand(command, config.bash_safe_patterns || [], config.bash_dangerous_patterns || [], logger);
|
|
52
|
+
if (bashRisk === 'dangerous') {
|
|
53
|
+
logger?.warn?.(`[PD:GFI_GATE] Dangerous bash command blocked: ${command.substring(0, 50)}...`);
|
|
54
|
+
return block(wctx, command.substring(0, 100), `危险命令被拦截。检测到危险命令模式,需要确认执行意图。`, event.toolName, sessionId, logger);
|
|
55
|
+
}
|
|
56
|
+
if (bashRisk === 'safe') {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
// normal bash - check GFI threshold
|
|
60
|
+
const trustEngine = wctx.trust;
|
|
61
|
+
const stage = trustEngine.getStage();
|
|
62
|
+
const baseThreshold = config.thresholds?.low_risk_block || 70;
|
|
63
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, stage, 0, {
|
|
64
|
+
large_change_lines: config.large_change_lines || 50,
|
|
65
|
+
trust_stage_multipliers: config.trust_stage_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5 },
|
|
66
|
+
});
|
|
67
|
+
if (currentGfi >= dynamicThreshold) {
|
|
68
|
+
logger?.warn?.(`[PD:GFI_GATE] Bash blocked by GFI: ${currentGfi} >= ${dynamicThreshold}`);
|
|
69
|
+
return block(wctx, command.substring(0, 100), `疲劳指数过高 (GFI: ${currentGfi}/${dynamicThreshold})。系统进入保护模式。`, event.toolName, sessionId, logger);
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
// TIER 2: High-risk tools
|
|
74
|
+
if (HIGH_RISK_TOOLS.has(event.toolName)) {
|
|
75
|
+
const trustEngine = wctx.trust;
|
|
76
|
+
const stage = trustEngine.getStage();
|
|
77
|
+
const baseThreshold = config.thresholds?.high_risk_block || 40;
|
|
78
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, stage, 0, {
|
|
79
|
+
large_change_lines: config.large_change_lines || 50,
|
|
80
|
+
trust_stage_multipliers: config.trust_stage_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5 },
|
|
81
|
+
});
|
|
82
|
+
if (currentGfi >= dynamicThreshold) {
|
|
83
|
+
const filePath = event.params.file_path || event.params.path || event.params.file || event.params.target || 'unknown';
|
|
84
|
+
logger?.warn?.(`[PD:GFI_GATE] High-risk tool "${event.toolName}" blocked by GFI: ${currentGfi} >= ${dynamicThreshold}`);
|
|
85
|
+
return block(wctx, filePath, `高风险操作被拦截。GFI: ${currentGfi}/${dynamicThreshold}。高风险工具需要更低的阈值。`, event.toolName, sessionId, logger);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// TIER 1: Low-risk write tools
|
|
89
|
+
if (LOW_RISK_WRITE_TOOLS.has(event.toolName)) {
|
|
90
|
+
const trustEngine = wctx.trust;
|
|
91
|
+
const stage = trustEngine.getStage();
|
|
92
|
+
const lineChanges = estimateLineChanges({ toolName: event.toolName, params: event.params });
|
|
93
|
+
const baseThreshold = config.thresholds?.low_risk_block || 70;
|
|
94
|
+
const dynamicThreshold = calculateDynamicThreshold(baseThreshold, stage, lineChanges, {
|
|
95
|
+
large_change_lines: config.large_change_lines || 50,
|
|
96
|
+
trust_stage_multipliers: config.trust_stage_multipliers || { '1': 0.5, '2': 0.75, '3': 1.0, '4': 1.5 },
|
|
97
|
+
});
|
|
98
|
+
if (currentGfi >= dynamicThreshold) {
|
|
99
|
+
const filePath = event.params.file_path || event.params.path || event.params.file || event.params.target || 'unknown';
|
|
100
|
+
logger?.warn?.(`[PD:GFI_GATE] Low-risk tool "${event.toolName}" blocked by GFI: ${currentGfi} >= ${dynamicThreshold}`);
|
|
101
|
+
return block(wctx, filePath, `疲劳指数过高 (GFI: ${currentGfi}/${dynamicThreshold})。系统进入保护模式。`, event.toolName, sessionId, logger);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// AGENT_TOOLS: Block subagent spawn when GFI is critically high
|
|
105
|
+
if (AGENT_TOOLS.has(event.toolName)) {
|
|
106
|
+
if (currentGfi >= AGENT_SPAWN_GFI_THRESHOLD) {
|
|
107
|
+
logger?.warn?.(`[PD:GFI_GATE] Agent tool "${event.toolName}" blocked by GFI: ${currentGfi} >= ${AGENT_SPAWN_GFI_THRESHOLD}`);
|
|
108
|
+
return block(wctx, 'subagent-spawn', `疲劳指数过高,禁止派生子智能体。GFI: ${currentGfi}/${AGENT_SPAWN_GFI_THRESHOLD}`, event.toolName, sessionId, logger);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Trust Gate Module
|
|
3
|
+
*
|
|
4
|
+
* Handles progressive access control based on trust stages (1-4).
|
|
5
|
+
*
|
|
6
|
+
* **Responsibilities:**
|
|
7
|
+
* - Enforce trust stage-based permissions:
|
|
8
|
+
* - Stage 1 (Bankruptcy): Block ALL writes to risk paths, medium+ changes
|
|
9
|
+
* - Stage 2 (Editor): Block risk paths, large changes
|
|
10
|
+
* - Stage 3 (Developer): Require READY plan for risk paths, normal limits
|
|
11
|
+
* - Stage 4 (Architect): Full bypass with audit logging
|
|
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
|
|
15
|
+
*
|
|
16
|
+
* **Configuration:**
|
|
17
|
+
* - Progressive gate settings from profile.progressive_gate
|
|
18
|
+
* - Trust limits from config.trust
|
|
19
|
+
* - Plan approval patterns and operations
|
|
20
|
+
*
|
|
21
|
+
* **Block Persistence:**
|
|
22
|
+
* - Uses shared `recordGateBlockAndReturn` from gate-block-helper.ts
|
|
23
|
+
* - Ensures single authoritative block persistence path
|
|
24
|
+
*/
|
|
25
|
+
import type { PluginHookBeforeToolCallEvent, PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
|
|
26
|
+
import type { WorkspaceContext } from '../core/workspace-context.js';
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for progressive gate behavior
|
|
29
|
+
*/
|
|
30
|
+
export interface ProgressiveGateConfig {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
plan_approvals?: {
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
max_lines_override?: number;
|
|
35
|
+
allowed_patterns?: string[];
|
|
36
|
+
allowed_operations?: string[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Trust stage limits configuration
|
|
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
|
|
55
|
+
*
|
|
56
|
+
* @param event - The tool call event
|
|
57
|
+
* @param wctx - Workspace context
|
|
58
|
+
* @param relPath - Relative path to target file
|
|
59
|
+
* @param risky - Whether the path is a risk path
|
|
60
|
+
* @param lineChanges - Estimated line changes
|
|
61
|
+
* @param logger - Logger instance
|
|
62
|
+
* @param ctx - Hook context
|
|
63
|
+
* @param profile - Gate profile containing risk_paths and progressive_gate config
|
|
64
|
+
* @returns PluginHookBeforeToolCallResult to block, or undefined to allow
|
|
65
|
+
*/
|
|
66
|
+
export declare function checkProgressiveTrustGate(event: PluginHookBeforeToolCallEvent, wctx: WorkspaceContext, relPath: string, risky: boolean, lineChanges: number, logger: {
|
|
67
|
+
warn?: (message: string) => void;
|
|
68
|
+
error?: (message: string) => void;
|
|
69
|
+
info?: (message: string) => void;
|
|
70
|
+
}, ctx: {
|
|
71
|
+
workspaceDir?: string;
|
|
72
|
+
sessionId?: string;
|
|
73
|
+
}, profile?: {
|
|
74
|
+
risk_paths: string[];
|
|
75
|
+
progressive_gate?: {
|
|
76
|
+
enabled?: boolean;
|
|
77
|
+
plan_approvals?: any;
|
|
78
|
+
};
|
|
79
|
+
}): PluginHookBeforeToolCallResult | void;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Trust Gate Module
|
|
3
|
+
*
|
|
4
|
+
* Handles progressive access control based on trust stages (1-4).
|
|
5
|
+
*
|
|
6
|
+
* **Responsibilities:**
|
|
7
|
+
* - Enforce trust stage-based permissions:
|
|
8
|
+
* - Stage 1 (Bankruptcy): Block ALL writes to risk paths, medium+ changes
|
|
9
|
+
* - Stage 2 (Editor): Block risk paths, large changes
|
|
10
|
+
* - Stage 3 (Developer): Require READY plan for risk paths, normal limits
|
|
11
|
+
* - Stage 4 (Architect): Full bypass with audit logging
|
|
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
|
|
15
|
+
*
|
|
16
|
+
* **Configuration:**
|
|
17
|
+
* - Progressive gate settings from profile.progressive_gate
|
|
18
|
+
* - Trust limits from config.trust
|
|
19
|
+
* - Plan approval patterns and operations
|
|
20
|
+
*
|
|
21
|
+
* **Block Persistence:**
|
|
22
|
+
* - Uses shared `recordGateBlockAndReturn` from gate-block-helper.ts
|
|
23
|
+
* - Ensures single authoritative block persistence path
|
|
24
|
+
*/
|
|
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
|
+
import { checkEvolutionGate } from '../core/evolution-engine.js';
|
|
31
|
+
import { EventLogService } from '../core/event-log.js';
|
|
32
|
+
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
33
|
+
/**
|
|
34
|
+
* Build line limit rejection reason
|
|
35
|
+
*/
|
|
36
|
+
export function buildLineLimitReason(lineChanges, effectiveLimit, limitType, targetLineCount, actualPercentage, stage) {
|
|
37
|
+
if (limitType === 'percentage' && targetLineCount !== null && actualPercentage !== null) {
|
|
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
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal helper to call the shared block helper with progressive-trust-gate source tag.
|
|
50
|
+
*/
|
|
51
|
+
function block(filePath, reason, wctx, toolName, logger, sessionId) {
|
|
52
|
+
return recordGateBlockAndReturn(wctx, {
|
|
53
|
+
filePath,
|
|
54
|
+
reason,
|
|
55
|
+
toolName,
|
|
56
|
+
sessionId,
|
|
57
|
+
blockSource: 'progressive-trust-gate',
|
|
58
|
+
}, logger);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check progressive trust gate based on trust stage and operation context
|
|
62
|
+
*
|
|
63
|
+
* @param event - The tool call event
|
|
64
|
+
* @param wctx - Workspace context
|
|
65
|
+
* @param relPath - Relative path to target file
|
|
66
|
+
* @param risky - Whether the path is a risk path
|
|
67
|
+
* @param lineChanges - Estimated line changes
|
|
68
|
+
* @param logger - Logger instance
|
|
69
|
+
* @param ctx - Hook context
|
|
70
|
+
* @param profile - Gate profile containing risk_paths and progressive_gate config
|
|
71
|
+
* @returns PluginHookBeforeToolCallResult to block, or undefined to allow
|
|
72
|
+
*/
|
|
73
|
+
export function checkProgressiveTrustGate(event, wctx, relPath, risky, lineChanges, logger, ctx, profile) {
|
|
74
|
+
// Check if progressive gate is disabled
|
|
75
|
+
if (profile?.progressive_gate?.enabled === false) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const trustEngine = wctx.trust;
|
|
79
|
+
const trustScore = trustEngine.getScore();
|
|
80
|
+
const stage = trustEngine.getStage();
|
|
81
|
+
const trustSettings = wctx.config.get('trust') || {
|
|
82
|
+
limits: {
|
|
83
|
+
stage_2_max_lines: 50,
|
|
84
|
+
stage_3_max_lines: 300,
|
|
85
|
+
stage_2_max_percentage: 10,
|
|
86
|
+
stage_3_max_percentage: 15,
|
|
87
|
+
min_lines_fallback: 20,
|
|
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;
|
|
241
|
+
}
|
|
242
|
+
}
|