principles-disciple 1.167.1 → 1.168.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/dist/core/rule-host.d.ts +22 -1
- package/dist/core/rule-host.js +107 -18
- package/dist/core/workspace-context.d.ts +4 -0
- package/dist/core/workspace-context.js +13 -0
- package/dist/hooks/gate.js +36 -50
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/core/rule-host.d.ts
CHANGED
|
@@ -30,12 +30,31 @@ export interface RuleHostOptions {
|
|
|
30
30
|
/** Workspace directory for SQLite access. Required for RuleHost to load active code_tool_hook activations. */
|
|
31
31
|
workspaceDir?: string;
|
|
32
32
|
}
|
|
33
|
+
export interface RuleHostObservedDecision extends RuleHostResult {
|
|
34
|
+
readonly activationId: string;
|
|
35
|
+
}
|
|
36
|
+
export interface RuleHostEvaluationReport {
|
|
37
|
+
readonly liveDecision: RuleHostResult | undefined;
|
|
38
|
+
readonly shadowDecisions: readonly RuleHostObservedDecision[];
|
|
39
|
+
}
|
|
33
40
|
export declare class RuleHost {
|
|
34
41
|
private readonly stateDir;
|
|
35
|
-
private
|
|
42
|
+
private logger;
|
|
36
43
|
private readonly workspaceDir;
|
|
37
44
|
private readonly implementationSources;
|
|
45
|
+
private activationFingerprint;
|
|
46
|
+
private cachedImplementations;
|
|
47
|
+
private sqliteConnection;
|
|
38
48
|
constructor(stateDir: string, logger?: RuleHostLogger, options?: RuleHostOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Update the logger sink on a cached RuleHost instance.
|
|
51
|
+
*
|
|
52
|
+
* WorkspaceContext caches the RuleHost singleton, but each gate call may
|
|
53
|
+
* pass a request-level logger. Without this update, warn/unhealthy logs
|
|
54
|
+
* would forever go to the first logger sink, making the new path hard to
|
|
55
|
+
* debug.
|
|
56
|
+
*/
|
|
57
|
+
updateLogger(logger: RuleHostLogger): void;
|
|
39
58
|
/**
|
|
40
59
|
* Evaluate the input against all active code implementations.
|
|
41
60
|
*
|
|
@@ -46,6 +65,8 @@ export declare class RuleHost {
|
|
|
46
65
|
* - { decision: 'requireApproval', ... } when any implementation returns requireApproval
|
|
47
66
|
*/
|
|
48
67
|
evaluate(input: RuleHostInput): RuleHostResult | undefined;
|
|
68
|
+
dispose(): void;
|
|
69
|
+
evaluateDetailed(input: RuleHostInput): RuleHostEvaluationReport;
|
|
49
70
|
/**
|
|
50
71
|
* Load active code implementations from the SQLite activations table.
|
|
51
72
|
*
|
package/dist/core/rule-host.js
CHANGED
|
@@ -48,11 +48,25 @@ export class RuleHost {
|
|
|
48
48
|
logger;
|
|
49
49
|
workspaceDir;
|
|
50
50
|
implementationSources = new Map();
|
|
51
|
+
activationFingerprint = null;
|
|
52
|
+
cachedImplementations = [];
|
|
53
|
+
sqliteConnection = null;
|
|
51
54
|
constructor(stateDir, logger = console, options) {
|
|
52
55
|
this.stateDir = stateDir;
|
|
53
56
|
this.logger = logger;
|
|
54
57
|
this.workspaceDir = options?.workspaceDir ?? null;
|
|
55
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Update the logger sink on a cached RuleHost instance.
|
|
61
|
+
*
|
|
62
|
+
* WorkspaceContext caches the RuleHost singleton, but each gate call may
|
|
63
|
+
* pass a request-level logger. Without this update, warn/unhealthy logs
|
|
64
|
+
* would forever go to the first logger sink, making the new path hard to
|
|
65
|
+
* debug.
|
|
66
|
+
*/
|
|
67
|
+
updateLogger(logger) {
|
|
68
|
+
this.logger = logger;
|
|
69
|
+
}
|
|
56
70
|
/**
|
|
57
71
|
* Evaluate the input against all active code implementations.
|
|
58
72
|
*
|
|
@@ -63,9 +77,36 @@ export class RuleHost {
|
|
|
63
77
|
* - { decision: 'requireApproval', ... } when any implementation returns requireApproval
|
|
64
78
|
*/
|
|
65
79
|
evaluate(input) {
|
|
80
|
+
return this.evaluateDetailed(input).liveDecision;
|
|
81
|
+
}
|
|
82
|
+
dispose() {
|
|
83
|
+
this.sqliteConnection?.close();
|
|
84
|
+
this.sqliteConnection = null;
|
|
85
|
+
this.cachedImplementations = [];
|
|
86
|
+
this.activationFingerprint = null;
|
|
87
|
+
this.implementationSources.clear();
|
|
88
|
+
}
|
|
89
|
+
evaluateDetailed(input) {
|
|
66
90
|
try {
|
|
67
|
-
const activeImpls = this._loadActiveCodeImplementations();
|
|
68
|
-
|
|
91
|
+
const activeImpls = this._loadActiveCodeImplementations(input.context?.version === 2);
|
|
92
|
+
const liveImpls = activeImpls.filter((impl) => impl.activationMode === 'live');
|
|
93
|
+
const shadowImpls = activeImpls.filter((impl) => impl.activationMode === 'shadow');
|
|
94
|
+
const shadowDecisions = [];
|
|
95
|
+
for (const impl of shadowImpls) {
|
|
96
|
+
try {
|
|
97
|
+
const result = impl.evaluate(input);
|
|
98
|
+
shadowDecisions.push({ ...result, activationId: impl.activationId });
|
|
99
|
+
}
|
|
100
|
+
catch (evalError) {
|
|
101
|
+
const reason = `evaluation failed: ${String(evalError)}`;
|
|
102
|
+
this.logger.warn?.(`[RuleHost] Shadow implementation ${impl.implId} ${reason}`);
|
|
103
|
+
const source = this.implementationSources.get(impl.implId);
|
|
104
|
+
if (source) {
|
|
105
|
+
this._recordUnhealthy(source.activationId, source.artifactId, source.ruleId, reason, 'Fix the RuleCode runtime error or return shape, then re-activate the rule');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const liveDecision = mergeDecisions(liveImpls, input, {
|
|
69
110
|
warn: this.logger.warn,
|
|
70
111
|
onImplementationUnhealthy: (impl, reason) => {
|
|
71
112
|
const source = this.implementationSources.get(impl.implId);
|
|
@@ -76,11 +117,12 @@ export class RuleHost {
|
|
|
76
117
|
this._recordUnhealthy(source.activationId, source.artifactId, source.ruleId, reason, 'Fix the RuleCode runtime error or return shape, then re-activate the rule');
|
|
77
118
|
},
|
|
78
119
|
});
|
|
120
|
+
return { liveDecision, shadowDecisions };
|
|
79
121
|
}
|
|
80
122
|
catch (hostError) {
|
|
81
123
|
// Conservative degradation: log and return undefined (D-08)
|
|
82
124
|
this.logger.warn?.(`[RuleHost] Host evaluation failed, degrading conservatively: ${String(hostError)}`);
|
|
83
|
-
return undefined;
|
|
125
|
+
return { liveDecision: undefined, shadowDecisions: [] };
|
|
84
126
|
}
|
|
85
127
|
}
|
|
86
128
|
/**
|
|
@@ -93,12 +135,12 @@ export class RuleHost {
|
|
|
93
135
|
* Source: activations table (code_tool_hook channel, deactivated_at IS NULL)
|
|
94
136
|
* → JOIN pi_artifacts for content_json → extract implementationCode → compile
|
|
95
137
|
*/
|
|
96
|
-
_loadActiveCodeImplementations() {
|
|
138
|
+
_loadActiveCodeImplementations(supportsContextV2) {
|
|
97
139
|
if (!this.workspaceDir) {
|
|
98
140
|
return [];
|
|
99
141
|
}
|
|
100
142
|
try {
|
|
101
|
-
return this._loadFromActivationsTable(this.workspaceDir);
|
|
143
|
+
return this._loadFromActivationsTable(this.workspaceDir, supportsContextV2);
|
|
102
144
|
}
|
|
103
145
|
catch (activationError) {
|
|
104
146
|
this.logger.warn?.(`[RuleHost] Failed to load code_tool_hook activations: ${String(activationError)}`);
|
|
@@ -120,13 +162,13 @@ export class RuleHost {
|
|
|
120
162
|
*
|
|
121
163
|
* All data from SQLite is treated as unknown and validated before use.
|
|
122
164
|
*/
|
|
123
|
-
_loadFromActivationsTable(workspaceDir) {
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
|
|
165
|
+
_loadFromActivationsTable(workspaceDir, supportsContextV2) {
|
|
166
|
+
const sqliteConn = this.sqliteConnection ?? new SqliteConnection(workspaceDir);
|
|
167
|
+
this.sqliteConnection = sqliteConn;
|
|
168
|
+
{
|
|
127
169
|
const db = sqliteConn.getDb();
|
|
128
170
|
const rows = db.prepare(`
|
|
129
|
-
SELECT a.activation_id, a.artifact_id, a.target_ref,
|
|
171
|
+
SELECT a.activation_id, a.artifact_id, a.target_ref, a.action,
|
|
130
172
|
p.content_json, p.source_rule_id
|
|
131
173
|
FROM activations a
|
|
132
174
|
JOIN pi_artifacts p ON a.artifact_id = p.artifact_id
|
|
@@ -137,6 +179,20 @@ export class RuleHost {
|
|
|
137
179
|
this.logger.warn?.('[RuleHost] Activations table query returned non-array, skipping');
|
|
138
180
|
return [];
|
|
139
181
|
}
|
|
182
|
+
const fingerprintParts = [supportsContextV2 ? 'context-v2' : 'context-v1'];
|
|
183
|
+
for (const row of rows) {
|
|
184
|
+
if (!row || typeof row !== 'object' || Array.isArray(row))
|
|
185
|
+
continue;
|
|
186
|
+
const record = row;
|
|
187
|
+
fingerprintParts.push([
|
|
188
|
+
record['activation_id'], record['artifact_id'], record['target_ref'], record['action'], record['content_json'], record['source_rule_id'],
|
|
189
|
+
].map((value) => typeof value === 'string' ? value : '').join('\u0001'));
|
|
190
|
+
}
|
|
191
|
+
const fingerprint = fingerprintParts.join('\u0002');
|
|
192
|
+
if (fingerprint === this.activationFingerprint) {
|
|
193
|
+
return [...this.cachedImplementations];
|
|
194
|
+
}
|
|
195
|
+
this.implementationSources.clear();
|
|
140
196
|
// PRI-436: Group active rows by target_ref to detect duplicates.
|
|
141
197
|
// At most one active activation per rule (target_ref) is allowed.
|
|
142
198
|
// Duplicate groups are skipped entirely (zero executions) and emit
|
|
@@ -199,10 +255,35 @@ export class RuleHost {
|
|
|
199
255
|
const artifactId = typeof r['artifact_id'] === 'string' ? r['artifact_id'] : '';
|
|
200
256
|
const contentJson = typeof r['content_json'] === 'string' ? r['content_json'] : '';
|
|
201
257
|
const sourceRuleId = typeof r['source_rule_id'] === 'string' ? r['source_rule_id'] : null;
|
|
258
|
+
const action = typeof r['action'] === 'string' ? r['action'] : '';
|
|
202
259
|
if (!activationId || !artifactId || !contentJson) {
|
|
203
260
|
this.logger.warn?.(`[RuleHost] Activation row missing required fields, skipping`);
|
|
204
261
|
continue;
|
|
205
262
|
}
|
|
263
|
+
const activationMode = action === 'code_tool_hook_shadow_activate'
|
|
264
|
+
? 'shadow'
|
|
265
|
+
: action === 'code_tool_hook_live_activate'
|
|
266
|
+
? 'live'
|
|
267
|
+
: null;
|
|
268
|
+
if (!activationMode) {
|
|
269
|
+
this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported action ${action || '(missing)'}, skipping. ` +
|
|
270
|
+
'nextAction=deactivate and recreate the activation through RuleHostWriter');
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (activationMode === 'shadow') {
|
|
274
|
+
// CodeRabbit PR #1121: shadow activations are observation-only and do
|
|
275
|
+
// NOT enter mergeDecisions (no block / requireApproval). Historical
|
|
276
|
+
// owner-approved records persisted with action=code_tool_hook_shadow_activate
|
|
277
|
+
// (before RuleHostWriter was fixed to write code_tool_hook_live_activate)
|
|
278
|
+
// silently lost execution after the dual-mode change. Surface a
|
|
279
|
+
// structured reason + nextAction so the degradation is observable
|
|
280
|
+
// (rc-9-no-silent-fallback), not silent. Fires once per fingerprint
|
|
281
|
+
// change (cached), not per evaluation.
|
|
282
|
+
this.logger.warn?.(`[RuleHost] Activation ${activationId}: loaded in shadow (observation-only) mode; ` +
|
|
283
|
+
'it will NOT block or require approval (shadowDecisions only). ' +
|
|
284
|
+
`nextAction=run \`pd runtime activation promote --activation-id ${activationId} --confirm\` to enable live blocking, ` +
|
|
285
|
+
'or leave as-is for shadow observation.');
|
|
286
|
+
}
|
|
206
287
|
try {
|
|
207
288
|
const content = JSON.parse(contentJson);
|
|
208
289
|
if (!content || typeof content !== 'object' || Array.isArray(content)) {
|
|
@@ -210,6 +291,18 @@ export class RuleHost {
|
|
|
210
291
|
continue;
|
|
211
292
|
}
|
|
212
293
|
const contentObj = content;
|
|
294
|
+
if (Object.hasOwn(contentObj, 'requiresContextVersion')) {
|
|
295
|
+
if (contentObj['requiresContextVersion'] !== 2) {
|
|
296
|
+
this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported context version; skipping. ` +
|
|
297
|
+
'nextAction=regenerate the rule artifact with requiresContextVersion: 2 or omit the field for v1');
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (!supportsContextV2) {
|
|
301
|
+
this.logger.warn?.(`[RuleHost] Activation ${activationId}: suspended because rulecode_context_v2 is disabled or unavailable; skipping. ` +
|
|
302
|
+
'nextAction=enable rulecode_context_v2 with valid config or deactivate this activation');
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
213
306
|
const implementationCode = contentObj['implementationCode'];
|
|
214
307
|
if (typeof implementationCode !== 'string' || implementationCode.length === 0) {
|
|
215
308
|
this.logger.warn?.(`[RuleHost] Activation ${activationId}: no implementationCode in artifact, skipping`);
|
|
@@ -244,6 +337,8 @@ export class RuleHost {
|
|
|
244
337
|
implId,
|
|
245
338
|
ruleId,
|
|
246
339
|
meta,
|
|
340
|
+
activationId,
|
|
341
|
+
activationMode,
|
|
247
342
|
evaluate: (input) => {
|
|
248
343
|
const frozenHelpers = createRuleHostHelpers(input);
|
|
249
344
|
// PRI-437: Execute inside vm context with timeout boundary.
|
|
@@ -269,16 +364,10 @@ export class RuleHost {
|
|
|
269
364
|
this._recordUnhealthy(activationId, artifactId, sourceRuleId ?? artifactId, reason, 'Fix the RuleCode syntax/compilation error, then re-activate the rule');
|
|
270
365
|
}
|
|
271
366
|
}
|
|
367
|
+
this.activationFingerprint = fingerprint;
|
|
368
|
+
this.cachedImplementations = loaded;
|
|
272
369
|
return loaded;
|
|
273
370
|
}
|
|
274
|
-
finally {
|
|
275
|
-
try {
|
|
276
|
-
sqliteConn.close();
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
// best-effort cleanup
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
371
|
}
|
|
283
372
|
/**
|
|
284
373
|
* PRI-437: Record an unhealthy activation state to EventLog.
|
|
@@ -8,6 +8,8 @@ import type { TrajectoryDatabase } from './trajectory.js';
|
|
|
8
8
|
import { PrincipleLifecycleService } from './principle-internalization/principle-lifecycle-service.js';
|
|
9
9
|
import { type PrincipleSubtree, type LedgerPrinciple, type PrincipleValueMetrics } from './principle-tree-ledger.js';
|
|
10
10
|
import type { Principle as ActivePrinciple } from './evolution-types.js';
|
|
11
|
+
import { RuleHost } from './rule-host.js';
|
|
12
|
+
import type { RuleHostLogger } from './rule-host.js';
|
|
11
13
|
interface PrincipleTreeLedgerAccessor {
|
|
12
14
|
getPrincipleSubtree(_principleId: string): PrincipleSubtree | undefined;
|
|
13
15
|
updatePrinciple(_principleId: string, updates: Partial<LedgerPrinciple>): LedgerPrinciple;
|
|
@@ -30,6 +32,7 @@ export declare class WorkspaceContext {
|
|
|
30
32
|
private _trajectory?;
|
|
31
33
|
private _principleTreeLedger?;
|
|
32
34
|
private _principleLifecycle?;
|
|
35
|
+
private _ruleHost?;
|
|
33
36
|
private constructor();
|
|
34
37
|
/**
|
|
35
38
|
* Governance configuration for this workspace.
|
|
@@ -55,6 +58,7 @@ export declare class WorkspaceContext {
|
|
|
55
58
|
* Trajectory database for analytics and sample curation.
|
|
56
59
|
*/
|
|
57
60
|
get trajectory(): TrajectoryDatabase;
|
|
61
|
+
getRuleHost(logger: RuleHostLogger): RuleHost;
|
|
58
62
|
/**
|
|
59
63
|
* Locked ledger access for principle tree reads and metric writes in this workspace.
|
|
60
64
|
*/
|
|
@@ -9,6 +9,7 @@ import { EvolutionReducerImpl } from './evolution-reducer.js';
|
|
|
9
9
|
import { TrajectoryRegistry } from './trajectory.js';
|
|
10
10
|
import { PrincipleLifecycleService } from './principle-internalization/principle-lifecycle-service.js';
|
|
11
11
|
import { getPrincipleSubtree, updatePrinciple, updatePrincipleValueMetrics, } from './principle-tree-ledger.js';
|
|
12
|
+
import { RuleHost } from './rule-host.js';
|
|
12
13
|
/**
|
|
13
14
|
* WorkspaceContext - Centralized management of workspace-specific paths and services.
|
|
14
15
|
* Implements a cached singleton pattern per workspace directory.
|
|
@@ -26,6 +27,7 @@ export class WorkspaceContext {
|
|
|
26
27
|
_trajectory;
|
|
27
28
|
_principleTreeLedger;
|
|
28
29
|
_principleLifecycle;
|
|
30
|
+
_ruleHost;
|
|
29
31
|
constructor(workspaceDir, stateDir) {
|
|
30
32
|
this.workspaceDir = workspaceDir;
|
|
31
33
|
this.stateDir = stateDir;
|
|
@@ -84,6 +86,15 @@ export class WorkspaceContext {
|
|
|
84
86
|
}
|
|
85
87
|
return this._trajectory;
|
|
86
88
|
}
|
|
89
|
+
getRuleHost(logger) {
|
|
90
|
+
if (!this._ruleHost) {
|
|
91
|
+
this._ruleHost = new RuleHost(this.stateDir, logger, { workspaceDir: this.workspaceDir });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
this._ruleHost.updateLogger(logger);
|
|
95
|
+
}
|
|
96
|
+
return this._ruleHost;
|
|
97
|
+
}
|
|
87
98
|
/**
|
|
88
99
|
* Locked ledger access for principle tree reads and metric writes in this workspace.
|
|
89
100
|
*/
|
|
@@ -214,6 +225,8 @@ export class WorkspaceContext {
|
|
|
214
225
|
* Resets internal caches for services and paths.
|
|
215
226
|
*/
|
|
216
227
|
invalidate() {
|
|
228
|
+
this._ruleHost?.dispose();
|
|
229
|
+
this._ruleHost = undefined;
|
|
217
230
|
this._config = undefined;
|
|
218
231
|
this._eventLog = undefined;
|
|
219
232
|
this._dictionary = undefined;
|
package/dist/hooks/gate.js
CHANGED
|
@@ -8,11 +8,9 @@
|
|
|
8
8
|
* 1. Early Return: Skip if not write/bash/agent tool or no workspace
|
|
9
9
|
* 2. Rule Host: Dynamic principle-based evaluation (sole gate)
|
|
10
10
|
*/
|
|
11
|
-
import { normalizePath } from '../utils/io.js';
|
|
12
11
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
13
12
|
import { recordGateBlockAndReturn } from './gate-block-helper.js';
|
|
14
|
-
import {
|
|
15
|
-
import { validateCorrectionProposal, validateProposedPathBounds, computeFeatureFlagsFromConfig, UNAVAILABLE_RULE_CONTEXT } from '@principles/core/runtime-v2';
|
|
13
|
+
import { buildRuleHostAction, validateCorrectionProposal, validateProposedPathBounds, computeFeatureFlagsFromConfig, UNAVAILABLE_RULE_CONTEXT } from '@principles/core/runtime-v2';
|
|
16
14
|
import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js';
|
|
17
15
|
import { getSession, hasRecentThinking } from '../core/session-tracker.js';
|
|
18
16
|
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
@@ -30,41 +28,27 @@ export function handleBeforeToolCall(event, ctx) {
|
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
32
30
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
33
|
-
// 2.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
filePath = command;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
// Write tools without a file path must still go through RuleHost evaluation.
|
|
48
|
-
// Use a synthetic path so RuleHost can evaluate and potentially block.
|
|
49
|
-
if (!filePath && isWriteTool) {
|
|
50
|
-
filePath = `<tool:${event.toolName}>`;
|
|
51
|
-
}
|
|
52
|
-
if (typeof filePath !== 'string')
|
|
31
|
+
// 2. Use the same action builder as Golden Trace replay. This is the single
|
|
32
|
+
// path extraction + normalization contract for production and evaluation.
|
|
33
|
+
const action = buildRuleHostAction(event.toolName, event.params ?? {}, ctx.workspaceDir, {
|
|
34
|
+
isBashTool: isBash,
|
|
35
|
+
isWriteTool,
|
|
36
|
+
});
|
|
37
|
+
const relPath = action.normalizedPath;
|
|
38
|
+
// buildRuleHostAction returns null when no path can be extracted (e.g. bash
|
|
39
|
+
// command with no file target and no clear mutation operator). Mirror the
|
|
40
|
+
// legacy guard: no path → no gate evaluation, let the tool through.
|
|
41
|
+
if (relPath === null)
|
|
53
42
|
return;
|
|
54
|
-
const relPath = normalizePath(filePath, ctx.workspaceDir);
|
|
55
43
|
// 3. Rule Host Evaluation — sole gate
|
|
56
44
|
try {
|
|
57
|
-
const ruleHost =
|
|
45
|
+
const ruleHost = wctx.getRuleHost(logger);
|
|
58
46
|
// PRI-483 Phase 4: assemble RuleContextV2 when `rulecode_context_v2` flag is ON.
|
|
59
47
|
// flag OFF → undefined (v1 zero-change, no trajectory access).
|
|
60
48
|
// flag ON → RuleContextV2 (available or unavailable). Never throws (ERR-024).
|
|
61
49
|
const ruleContext = _buildRuleContextIfEnabled(wctx, relPath, ctx.sessionId, logger);
|
|
62
50
|
const hostInput = {
|
|
63
|
-
action
|
|
64
|
-
toolName: event.toolName,
|
|
65
|
-
normalizedPath: relPath,
|
|
66
|
-
paramsSummary: _extractParamsSummary(event.params ?? {}),
|
|
67
|
-
},
|
|
51
|
+
action,
|
|
68
52
|
workspace: {
|
|
69
53
|
isRiskPath: false, // Rule Host determines risk dynamically
|
|
70
54
|
// DEPRECATED (PRI-286): planStatus/hasPlanFile are legacy compatibility fields.
|
|
@@ -88,7 +72,27 @@ export function handleBeforeToolCall(event, ctx) {
|
|
|
88
72
|
},
|
|
89
73
|
context: ruleContext,
|
|
90
74
|
};
|
|
91
|
-
const
|
|
75
|
+
const report = typeof ruleHost.evaluateDetailed === 'function'
|
|
76
|
+
? ruleHost.evaluateDetailed(hostInput)
|
|
77
|
+
: { liveDecision: ruleHost.evaluate(hostInput), shadowDecisions: [] };
|
|
78
|
+
const hostResult = report.liveDecision;
|
|
79
|
+
for (const shadowDecision of report.shadowDecisions) {
|
|
80
|
+
try {
|
|
81
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger);
|
|
82
|
+
eventLog.recordRuleHostEvaluated({
|
|
83
|
+
toolName: event.toolName,
|
|
84
|
+
filePath: relPath,
|
|
85
|
+
matched: shadowDecision.matched,
|
|
86
|
+
decision: shadowDecision.decision,
|
|
87
|
+
ruleId: shadowDecision.ruleId,
|
|
88
|
+
activationId: shadowDecision.activationId,
|
|
89
|
+
activationMode: 'shadow',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (evErr) {
|
|
93
|
+
logger?.warn?.(`[PD_GATE] Failed to record shadow rulehost_evaluated: ${String(evErr)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
92
96
|
// Always emit rulehost_evaluated
|
|
93
97
|
try {
|
|
94
98
|
const eventLog = EventLogService.get(wctx.stateDir, logger);
|
|
@@ -98,6 +102,7 @@ export function handleBeforeToolCall(event, ctx) {
|
|
|
98
102
|
matched: hostResult?.matched ?? false,
|
|
99
103
|
decision: hostResult?.decision ?? 'allow',
|
|
100
104
|
ruleId: hostResult?.ruleId,
|
|
105
|
+
activationMode: 'live',
|
|
101
106
|
});
|
|
102
107
|
}
|
|
103
108
|
catch (evErr) {
|
|
@@ -321,25 +326,6 @@ export function handleBeforeToolCall(event, ctx) {
|
|
|
321
326
|
// ---------------------------------------------------------------------------
|
|
322
327
|
// Private helpers
|
|
323
328
|
// ---------------------------------------------------------------------------
|
|
324
|
-
function _extractParamsSummary(params) {
|
|
325
|
-
const summary = {};
|
|
326
|
-
// NOTE: Do NOT redact here — this feeds into RuleHost.evaluate() which
|
|
327
|
-
// may match against paramsSummary.command. Redaction happens at
|
|
328
|
-
// EventLog.record() before persistence.
|
|
329
|
-
if (params.file_path)
|
|
330
|
-
summary.file_path = params.file_path;
|
|
331
|
-
if (params.path)
|
|
332
|
-
summary.path = params.path;
|
|
333
|
-
if (params.command)
|
|
334
|
-
summary.command = params.command;
|
|
335
|
-
if (params.args)
|
|
336
|
-
summary.args = params.args;
|
|
337
|
-
if (params.old_string)
|
|
338
|
-
summary.old_string = params.old_string;
|
|
339
|
-
if (params.new_string)
|
|
340
|
-
summary.new_string = params.new_string;
|
|
341
|
-
return summary;
|
|
342
|
-
}
|
|
343
329
|
function _getCurrentGfi(sessionId) {
|
|
344
330
|
if (!sessionId)
|
|
345
331
|
return 0;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "principles-disciple",
|
|
3
3
|
"name": "Principles Disciple",
|
|
4
4
|
"description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.168.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|