principles-disciple 1.58.0 → 1.60.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 +4 -4
- package/package.json +1 -1
- package/src/commands/archive-impl.ts +2 -1
- package/src/commands/capabilities.ts +2 -1
- package/src/commands/context.ts +3 -5
- package/src/commands/disable-impl.ts +2 -1
- package/src/commands/evolution-status.ts +18 -1
- package/src/commands/export.ts +2 -1
- package/src/commands/focus.ts +2 -5
- package/src/commands/nocturnal-review.ts +2 -1
- package/src/commands/nocturnal-rollout.ts +2 -1
- package/src/commands/nocturnal-train.ts +2 -1
- package/src/commands/pain.ts +2 -1
- package/src/commands/pd-reflect.ts +5 -7
- package/src/commands/principle-rollback.ts +2 -1
- package/src/commands/promote-impl.ts +2 -1
- 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/strategy.ts +3 -2
- package/src/commands/thinking-os.ts +2 -1
- package/src/commands/workflow-debug.ts +2 -1
- package/src/core/event-log.ts +42 -3
- package/src/core/init.ts +2 -2
- package/src/core/principle-compiler/ledger-registrar.ts +11 -2
- package/src/core/rule-host-types.ts +4 -0
- package/src/core/rule-host.ts +7 -1
- package/src/hooks/gate.ts +15 -0
- package/src/hooks/prompt.ts +13 -0
- package/src/index.ts +13 -4
- package/src/service/evolution-worker.ts +30 -0
- package/src/service/runtime-summary-service.ts +38 -0
- package/src/tools/critique-prompt.ts +4 -5
- package/src/tools/deep-reflect.ts +4 -3
- package/src/types/event-types.ts +73 -3
- package/src/utils/workspace-resolver.ts +44 -3
- package/tests/commands/pd-reflect.test.ts +1 -1
- package/tests/core/bootstrap-rules.test.ts +14 -0
- package/tests/core/evolution-reducer.compilation-retry.test.ts +2 -1
- package/tests/core/ledger-registrar.test.ts +5 -2
- package/tests/core/principle-compiler.test.ts +4 -2
- package/tests/core/regression-v1-9-1.test.ts +2 -1
- package/tests/integration/gate-real-io.e2e.test.ts +5 -8
- package/tests/integration/pain-id-chain-e2e.test.ts +12 -6
- package/tests/integration/principle-compiler-e2e.test.ts +28 -9
- package/tests/integration/principle-lifecycle.e2e.test.ts +2 -1
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.60.0",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"buildFingerprint": {
|
|
79
|
-
"gitSha": "
|
|
80
|
-
"bundleMd5": "
|
|
81
|
-
"builtAt": "2026-04-
|
|
79
|
+
"gitSha": "b300ef0761c5",
|
|
80
|
+
"bundleMd5": "3c16a5198f3559b097d028b6e987cf5b",
|
|
81
|
+
"builtAt": "2026-04-18T11:02:10.525Z"
|
|
82
82
|
}
|
|
83
83
|
}
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '../core/principle-tree-ledger.js';
|
|
20
20
|
import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
|
|
21
21
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
22
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Get all implementations from the ledger.
|
|
@@ -43,7 +44,7 @@ function canArchive(state: ImplementationLifecycleState): boolean {
|
|
|
43
44
|
* /pd-archive-impl list - List archivable implementations
|
|
44
45
|
*/
|
|
45
46
|
export function handleArchiveImplCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
46
|
-
const workspaceDir = (ctx
|
|
47
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'archive-impl');
|
|
47
48
|
const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
|
|
48
49
|
const lang = (ctx.config?.language as string) || 'en';
|
|
49
50
|
const isZh = lang === 'zh';
|
|
@@ -4,6 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
5
5
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
6
6
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
7
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
7
8
|
|
|
8
9
|
const TOOLS_TO_SCAN = [
|
|
9
10
|
{ name: 'rg', cmd: ['rg', '--version'] },
|
|
@@ -50,7 +51,7 @@ function scanEnvironment(wctx: WorkspaceContext): any {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export function handleBootstrapTools(ctx: PluginCommandContext): PluginCommandResult {
|
|
53
|
-
const workspaceDir = (ctx
|
|
54
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'capabilities');
|
|
54
55
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
55
56
|
|
|
56
57
|
try {
|
package/src/commands/context.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
5
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
5
6
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
6
7
|
import type { ContextInjectionConfig} from '../types.js';
|
|
7
8
|
import { defaultContextConfig } from '../types.js';
|
|
@@ -11,11 +12,8 @@ import { loadContextInjectionConfig } from '../hooks/prompt.js';
|
|
|
11
12
|
* Get workspace directory from context
|
|
12
13
|
*/
|
|
13
14
|
function getWorkspaceDir(ctx: PluginCommandContext): string {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
throw new Error('[PD:Context] workspaceDir is required but not provided');
|
|
17
|
-
}
|
|
18
|
-
return workspaceDir;
|
|
15
|
+
// resolvePluginCommandWorkspaceDir throws on failure — never returns falsy
|
|
16
|
+
return resolvePluginCommandWorkspaceDir(ctx, 'context');
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
/**
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from '../core/principle-tree-ledger.js';
|
|
22
22
|
import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
|
|
23
23
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
24
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Get all implementations from the ledger.
|
|
@@ -133,7 +134,7 @@ function _handleDisableImpl(
|
|
|
133
134
|
*/
|
|
134
135
|
|
|
135
136
|
export function handleDisableImplCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
136
|
-
const workspaceDir = (ctx
|
|
137
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'disable-impl');
|
|
137
138
|
const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
|
|
138
139
|
const lang = (ctx.config?.language as string) || 'en';
|
|
139
140
|
const isZh = lang === 'zh';
|
|
@@ -4,6 +4,7 @@ import { WorkspaceContext } from '../core/workspace-context.js';
|
|
|
4
4
|
import { normalizeLanguage } from '../i18n/commands.js';
|
|
5
5
|
import type { PluginCommandContext } from '../openclaw-sdk.js';
|
|
6
6
|
import { RuntimeSummaryService } from '../service/runtime-summary-service.js';
|
|
7
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
7
8
|
|
|
8
9
|
function formatNumber(value: number | null): string {
|
|
9
10
|
if (value === null || Number.isNaN(value)) {
|
|
@@ -74,6 +75,14 @@ function buildEnglishOutput(
|
|
|
74
75
|
`- Phase 3: ready ${summary.phase3.phase3ShadowEligible ? 'yes' : 'no'}, queueTruthReady ${summary.phase3.queueTruthReady ? 'yes' : 'no'}, eligible ${summary.phase3.evolutionEligible}, reference_only ${summary.phase3.evolutionReferenceOnly}, rejected ${summary.phase3.evolutionRejected}${summary.phase3.evolutionReferenceOnlyReasons.length > 0 ? ` (reference ${summary.phase3.evolutionReferenceOnlyReasons.slice(0, 2).join(', ')})` : ''}${summary.phase3.evolutionRejectedReasons.length > 0 ? ` (${summary.phase3.evolutionRejectedReasons.slice(0, 3).join(', ')})` : ''}`,
|
|
75
76
|
`- Phase 3 Legacy Directive File: ${summary.phase3.directiveStatus} (${summary.phase3.directiveIgnoredReason})`,
|
|
76
77
|
'',
|
|
78
|
+
// D: Heartbeat Diagnostician chain — separated from evolution/nocturnal
|
|
79
|
+
'Heartbeat Diagnostician (Pain → Principle)',
|
|
80
|
+
`- Pending tasks: ${summary.heartbeatDiagnosis.pendingTasks}`,
|
|
81
|
+
`- Tasks written today: ${summary.heartbeatDiagnosis.tasksWrittenToday}`,
|
|
82
|
+
`- Reports written today: ${summary.heartbeatDiagnosis.reportsWrittenToday}`,
|
|
83
|
+
`- Candidates created today: ${summary.heartbeatDiagnosis.candidatesCreatedToday}`,
|
|
84
|
+
`- Heartbeats injected today: ${summary.heartbeatDiagnosis.heartbeatsInjectedToday}`,
|
|
85
|
+
'',
|
|
77
86
|
'Principles',
|
|
78
87
|
`- candidate principles: ${stats.candidateCount}`,
|
|
79
88
|
`- probation principles: ${stats.probationCount}`,
|
|
@@ -127,6 +136,14 @@ function buildChineseOutput(
|
|
|
127
136
|
`- Phase 3: ready ${summary.phase3.phase3ShadowEligible ? 'yes' : 'no'},queueTruthReady ${summary.phase3.queueTruthReady ? 'yes' : 'no'},eligible ${summary.phase3.evolutionEligible},reference_only ${summary.phase3.evolutionReferenceOnly},rejected ${summary.phase3.evolutionRejected}${summary.phase3.evolutionReferenceOnlyReasons.length > 0 ? ` (reference ${summary.phase3.evolutionReferenceOnlyReasons.slice(0, 2).join(', ')})` : ''}${summary.phase3.evolutionRejectedReasons.length > 0 ? ` (${summary.phase3.evolutionRejectedReasons.slice(0, 3).join(', ')})` : ''}`,
|
|
128
137
|
`- Phase 3 Legacy Directive File: ${summary.phase3.directiveStatus} (${summary.phase3.directiveIgnoredReason})`,
|
|
129
138
|
'',
|
|
139
|
+
// D: Heartbeat Diagnostician chain — separated from evolution/nocturnal
|
|
140
|
+
'心跳诊断链路(Pain → 原则)',
|
|
141
|
+
`- 等待处理: ${summary.heartbeatDiagnosis.pendingTasks}`,
|
|
142
|
+
`- 今日写入任务: ${summary.heartbeatDiagnosis.tasksWrittenToday}`,
|
|
143
|
+
`- 今日写入报告: ${summary.heartbeatDiagnosis.reportsWrittenToday}`,
|
|
144
|
+
`- 今日创建候选: ${summary.heartbeatDiagnosis.candidatesCreatedToday}`,
|
|
145
|
+
`- 今日心跳注入: ${summary.heartbeatDiagnosis.heartbeatsInjectedToday}`,
|
|
146
|
+
'',
|
|
130
147
|
'原则统计',
|
|
131
148
|
`- 候选原则: ${stats.candidateCount}`,
|
|
132
149
|
`- 观察期原则: ${stats.probationCount}`,
|
|
@@ -152,7 +169,7 @@ function buildChineseOutput(
|
|
|
152
169
|
}
|
|
153
170
|
|
|
154
171
|
export function handleEvolutionStatusCommand(ctx: PluginCommandContext): { text: string } {
|
|
155
|
-
const workspaceDir = (ctx
|
|
172
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'evolution-status');
|
|
156
173
|
const sessionId = (ctx as { sessionId?: string | null }).sessionId ?? null;
|
|
157
174
|
// #207/#210: Use WorkspaceContext to get evolutionReducer with stateDir
|
|
158
175
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
|
package/src/commands/export.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
3
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
3
4
|
import { exportORPOSamples, listExports } from '../core/nocturnal-export.js';
|
|
4
5
|
|
|
5
6
|
function isZh(ctx: PluginCommandContext): boolean {
|
|
@@ -7,7 +8,7 @@ function isZh(ctx: PluginCommandContext): boolean {
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function handleExportCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
10
|
-
const workspaceDir = (ctx
|
|
11
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'export');
|
|
11
12
|
const zh = isZh(ctx);
|
|
12
13
|
const args = (ctx.args || '').trim();
|
|
13
14
|
const parts = args.split(/\s+/).filter(Boolean);
|
package/src/commands/focus.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as path from 'path';
|
|
|
13
13
|
import type { PluginCommandContext, PluginCommandResult, OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
14
14
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
15
15
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
16
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
16
17
|
import {
|
|
17
18
|
getHistoryDir,
|
|
18
19
|
backupToHistory,
|
|
@@ -28,11 +29,7 @@ import {
|
|
|
28
29
|
* 获取工作区目录
|
|
29
30
|
*/
|
|
30
31
|
function getWorkspaceDir(ctx: PluginCommandContext): string {
|
|
31
|
-
|
|
32
|
-
if (!workspaceDir) {
|
|
33
|
-
throw new Error('[PD:Focus] workspaceDir is required but not provided');
|
|
34
|
-
}
|
|
35
|
-
return workspaceDir;
|
|
32
|
+
return resolvePluginCommandWorkspaceDir(ctx, 'focus');
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
/**
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from '../core/nocturnal-dataset.js';
|
|
35
35
|
import { getPrincipleState, setPrincipleState } from '../core/principle-training-state.js';
|
|
36
36
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
37
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
37
38
|
|
|
38
39
|
function isZh(ctx: PluginCommandContext): boolean {
|
|
39
40
|
return String(ctx.config?.language || 'en').startsWith('zh');
|
|
@@ -91,7 +92,7 @@ function statusLabel(status: NocturnalReviewStatus, zh: boolean): string {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export function handleNocturnalReviewCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
94
|
-
const workspaceDir = (ctx
|
|
95
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'nocturnal-review');
|
|
95
96
|
const zh = isZh(ctx);
|
|
96
97
|
const args = (ctx.args || '').trim();
|
|
97
98
|
const parts = args.split(/\s+/).filter(Boolean);
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
28
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
28
29
|
import {
|
|
29
30
|
evaluatePromotionGate,
|
|
30
31
|
advancePromotion,
|
|
@@ -99,7 +100,7 @@ function formatConstraintCheck(
|
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export function handleNocturnalRolloutCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
102
|
-
const workspaceDir = (ctx
|
|
103
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'nocturnal-rollout');
|
|
103
104
|
const zh = isZh(ctx);
|
|
104
105
|
const args = (ctx.args || '').trim();
|
|
105
106
|
const parts = args.split(/\s+/).filter(Boolean);
|
|
@@ -28,6 +28,7 @@ import { execFileSync, spawn } from 'child_process';
|
|
|
28
28
|
import { fileURLToPath } from 'url';
|
|
29
29
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
30
30
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
31
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
31
32
|
import {
|
|
32
33
|
type TrainerBackendKind,
|
|
33
34
|
type HardwareTier,
|
|
@@ -100,7 +101,7 @@ function formatTrainingRun(run: ReturnType<typeof getTrainingRun>, zh: boolean):
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
export async function handleNocturnalTrainCommand(ctx: PluginCommandContext): Promise<PluginCommandResult> {
|
|
103
|
-
const workspaceDir = (ctx
|
|
104
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'nocturnal-train');
|
|
104
105
|
const zh = isZh(ctx);
|
|
105
106
|
const args = (ctx.args || '').trim();
|
|
106
107
|
const parts = args.split(/\s+/).filter(Boolean);
|
package/src/commands/pain.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resetFriction, getSession } from '../core/session-tracker.js';
|
|
2
2
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
3
3
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
4
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
4
5
|
import type { EmpathyEventStats } from '../types/event-types.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -92,7 +93,7 @@ interface SessionAwareCommandContext extends PluginCommandContext {
|
|
|
92
93
|
* Handles the /pd-status command
|
|
93
94
|
*/
|
|
94
95
|
export function handlePainCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
95
|
-
const workspaceDir = (ctx
|
|
96
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'pain');
|
|
96
97
|
|
|
97
98
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
98
99
|
const lang = (ctx.config?.language as string) || 'en';
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { PluginCommandDefinition, PluginCommandContext, PluginCommandResult, OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
9
9
|
import { acquireQueueLock, EVOLUTION_QUEUE_LOCK_SUFFIX } from '../service/evolution-worker.js';
|
|
10
10
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
11
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
11
12
|
import * as fs from 'fs';
|
|
12
13
|
import * as path from 'path';
|
|
13
14
|
|
|
@@ -23,11 +24,7 @@ export const handlePdReflect: PluginCommandDefinition = {
|
|
|
23
24
|
requireAuth: false,
|
|
24
25
|
handler: async (ctx: PdReflectContext): Promise<PluginCommandResult> => {
|
|
25
26
|
try {
|
|
26
|
-
|
|
27
|
-
const workspaceDir = ctx.workspaceDir;
|
|
28
|
-
if (!workspaceDir) {
|
|
29
|
-
return { text: 'Cannot determine workspace directory. Ensure you are in an active workspace.', isError: true };
|
|
30
|
-
}
|
|
27
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'pd-reflect');
|
|
31
28
|
|
|
32
29
|
const stateDir = path.join(workspaceDir, '.state');
|
|
33
30
|
const queuePath = path.join(stateDir, 'evolution_queue.json');
|
|
@@ -80,9 +77,10 @@ export const handlePdReflect: PluginCommandDefinition = {
|
|
|
80
77
|
return {
|
|
81
78
|
text: `Nocturnal reflection task enqueued: \`${taskId}\`\n\nIt will be processed in the next evolution worker cycle (~15s). Check .state/nocturnal/samples/ for results.`,
|
|
82
79
|
};
|
|
83
|
-
} catch (
|
|
80
|
+
} catch (err: unknown) {
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
82
|
return {
|
|
85
|
-
text: `Failed to trigger reflection: ${
|
|
83
|
+
text: `Failed to trigger reflection: ${message}`,
|
|
86
84
|
isError: true,
|
|
87
85
|
};
|
|
88
86
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import type { PluginCommandContext } from '../openclaw-sdk.js';
|
|
3
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
export function handlePrincipleRollbackCommand(ctx: PluginCommandContext): { text: string } {
|
|
6
|
-
const workspaceDir = (ctx
|
|
7
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'principle-rollback');
|
|
7
8
|
const argText = (ctx.args || '').trim();
|
|
8
9
|
const [principleId = '', ...reasonParts] = argText.split(/\s+/);
|
|
9
10
|
const reason = (reasonParts.join(' ') || 'manual rollback').trim();
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from '../core/principle-tree-ledger.js';
|
|
29
29
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
30
30
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
31
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
31
32
|
import type { Implementation, ImplementationLifecycleState } from '../types/principle-tree-schema.js';
|
|
32
33
|
import { withLock } from '../utils/file-lock.js';
|
|
33
34
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
@@ -250,7 +251,7 @@ function _handlePromoteImpl(options: PromoteImplOptions): PluginCommandResult {
|
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
export function handlePromoteImplCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
253
|
-
const workspaceDir = (ctx
|
|
254
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'promote-impl');
|
|
254
255
|
const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
|
|
255
256
|
const lang = (ctx.config?.language as string) || 'en';
|
|
256
257
|
const isZh = lang === 'zh';
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../core/principle-tree-ledger.js';
|
|
26
26
|
import type { Implementation } from '../types/principle-tree-schema.js';
|
|
27
27
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
28
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Get all implementations from the ledger.
|
|
@@ -44,7 +45,7 @@ function getAllImplementations(stateDir: string): Implementation[] {
|
|
|
44
45
|
*/
|
|
45
46
|
|
|
46
47
|
export function handleRollbackImplCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
47
|
-
const workspaceDir = (ctx
|
|
48
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'rollback-impl');
|
|
48
49
|
const {stateDir} = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir });
|
|
49
50
|
const lang = (ctx.config?.language as string) || 'en';
|
|
50
51
|
const isZh = lang === 'zh';
|
package/src/commands/rollback.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import { resetFriction } from '../core/session-tracker.js';
|
|
3
3
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
4
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Extended context interface that includes sessionId injected by the plugin framework.
|
|
@@ -18,7 +19,7 @@ interface SessionAwareCommandContext extends PluginCommandContext {
|
|
|
18
19
|
* /pd-rollback last - Rollback the last empathy event in current session
|
|
19
20
|
*/
|
|
20
21
|
export function handleRollbackCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
21
|
-
const workspaceDir = (ctx
|
|
22
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'rollback');
|
|
22
23
|
|
|
23
24
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
24
25
|
const lang = (ctx.config?.language as string) || 'en';
|
package/src/commands/samples.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
2
2
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
3
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
3
4
|
|
|
4
5
|
function isZh(ctx: PluginCommandContext): boolean {
|
|
5
6
|
return String(ctx.config?.language || 'en').startsWith('zh');
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function handleSamplesCommand(ctx: PluginCommandContext): PluginCommandResult {
|
|
9
|
-
const workspaceDir = (ctx
|
|
10
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'samples');
|
|
10
11
|
const zh = isZh(ctx);
|
|
11
12
|
const args = (ctx.args || '').trim();
|
|
12
13
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
package/src/commands/strategy.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
2
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
2
3
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
3
4
|
|
|
4
5
|
export function handleInitStrategy(ctx: PluginCommandContext): PluginCommandResult {
|
|
5
|
-
const workspaceDir = (ctx
|
|
6
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'strategy');
|
|
6
7
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
7
8
|
|
|
8
9
|
const okrDir = wctx.resolve('OKR_DIR').replace(workspaceDir, '').replace(/^\/+/, '');
|
|
@@ -20,7 +21,7 @@ export function handleInitStrategy(ctx: PluginCommandContext): PluginCommandResu
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function handleManageOkr(ctx: PluginCommandContext): PluginCommandResult {
|
|
23
|
-
const workspaceDir = (ctx
|
|
24
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'strategy:manageOkr');
|
|
24
25
|
const wctx = WorkspaceContext.fromHookContext({ workspaceDir, ...ctx.config });
|
|
25
26
|
|
|
26
27
|
const focusPath = wctx.resolve('CURRENT_FOCUS').replace(workspaceDir, '').replace(/^\/+/, '');
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import type { PluginCommandContext, PluginCommandResult } from '../openclaw-sdk.js';
|
|
4
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
4
5
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
5
6
|
|
|
6
7
|
function getWorkspaceDir(ctx: PluginCommandContext): string {
|
|
7
|
-
return (ctx
|
|
8
|
+
return resolvePluginCommandWorkspaceDir(ctx, 'thinking-os');
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
function getModels(wctx: WorkspaceContext): Record<string, string> {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WorkflowStore } from '../service/subagent-workflow/workflow-store.js';
|
|
2
2
|
import type { PluginCommandContext } from '../openclaw-sdk.js';
|
|
3
|
+
import { resolvePluginCommandWorkspaceDir } from '../utils/workspace-resolver.js';
|
|
3
4
|
|
|
4
5
|
function formatTimestamp(ts: number | null | undefined): string {
|
|
5
6
|
if (!ts) return '--';
|
|
@@ -85,7 +86,7 @@ function buildOutput(
|
|
|
85
86
|
export function handleWorkflowDebugCommand(
|
|
86
87
|
ctx: PluginCommandContext & { args?: string }
|
|
87
88
|
): { text: string } {
|
|
88
|
-
const workspaceDir = (ctx
|
|
89
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'workflow-debug');
|
|
89
90
|
|
|
90
91
|
// Parse workflow ID from args
|
|
91
92
|
const args = (ctx as { args?: string }).args?.trim() || '';
|
package/src/core/event-log.ts
CHANGED
|
@@ -18,6 +18,12 @@ import type {
|
|
|
18
18
|
EvolutionTaskEventData,
|
|
19
19
|
DeepReflectionEventData,
|
|
20
20
|
EmpathyRollbackEventData,
|
|
21
|
+
// C: New event data types
|
|
22
|
+
DiagnosisTaskEventData,
|
|
23
|
+
HeartbeatDiagnosisEventData,
|
|
24
|
+
DiagnosticianReportEventData,
|
|
25
|
+
PrincipleCandidateEventData,
|
|
26
|
+
RuleEnforcedEventData,
|
|
21
27
|
} from '../types/event-types.js';
|
|
22
28
|
import { createEmptyDailyStats } from '../types/event-types.js';
|
|
23
29
|
import { atomicWriteFileSync } from '../utils/io.js';
|
|
@@ -180,9 +186,28 @@ export class EventLog {
|
|
|
180
186
|
recordWarn(sessionId: string | undefined, message: string, context?: Record<string, unknown>): void {
|
|
181
187
|
this.record('warn', 'failure', sessionId, { message, ...context });
|
|
182
188
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
|
|
190
|
+
// C: Diagnostician heartbeat chain event recorders
|
|
191
|
+
recordDiagnosisTask(data: DiagnosisTaskEventData): void {
|
|
192
|
+
this.record('diagnosis_task', 'written', undefined, data);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
recordHeartbeatDiagnosis(data: HeartbeatDiagnosisEventData): void {
|
|
196
|
+
this.record('heartbeat_diagnosis', 'injected', undefined, data);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
recordDiagnosticianReport(data: DiagnosticianReportEventData): void {
|
|
200
|
+
this.record('diagnostician_report', data.success ? 'completed' : 'failure', undefined, data);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
recordPrincipleCandidate(data: PrincipleCandidateEventData): void {
|
|
204
|
+
this.record('principle_candidate', 'created', undefined, data);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
recordRuleEnforced(data: RuleEnforcedEventData): void {
|
|
208
|
+
this.record('rule_enforced', 'matched', undefined, data);
|
|
209
|
+
}
|
|
210
|
+
|
|
186
211
|
private record(
|
|
187
212
|
type: EventType,
|
|
188
213
|
category: EventCategory,
|
|
@@ -325,6 +350,20 @@ export class EventLog {
|
|
|
325
350
|
stats.evolution.tasksEnqueued++;
|
|
326
351
|
}
|
|
327
352
|
}
|
|
353
|
+
// C: Diagnostician heartbeat chain event counters
|
|
354
|
+
else if (entry.type === 'diagnosis_task') {
|
|
355
|
+
stats.evolution.diagnosisTasksWritten++;
|
|
356
|
+
} else if (entry.type === 'heartbeat_diagnosis') {
|
|
357
|
+
stats.evolution.heartbeatsInjected++;
|
|
358
|
+
} else if (entry.type === 'diagnostician_report') {
|
|
359
|
+
if (entry.category === 'completed') {
|
|
360
|
+
stats.evolution.diagnosticianReportsWritten++;
|
|
361
|
+
}
|
|
362
|
+
} else if (entry.type === 'principle_candidate') {
|
|
363
|
+
stats.evolution.principleCandidatesCreated++;
|
|
364
|
+
} else if (entry.type === 'rule_enforced') {
|
|
365
|
+
stats.evolution.rulesEnforced++;
|
|
366
|
+
}
|
|
328
367
|
}
|
|
329
368
|
|
|
330
369
|
private startFlushTimer(): void {
|
package/src/core/init.ts
CHANGED
|
@@ -192,7 +192,7 @@ export function ensureCorePrinciples(stateDir: string, logger: PluginLogger): bo
|
|
|
192
192
|
for (const model of CORE_THINKING_MODELS) {
|
|
193
193
|
const state: PrincipleTrainingState = {
|
|
194
194
|
principleId: model.id,
|
|
195
|
-
evaluability: '
|
|
195
|
+
evaluability: 'deterministic',
|
|
196
196
|
applicableOpportunityCount: 0,
|
|
197
197
|
observedViolationCount: 0,
|
|
198
198
|
complianceRate: 0,
|
|
@@ -217,7 +217,7 @@ export function ensureCorePrinciples(stateDir: string, logger: PluginLogger): bo
|
|
|
217
217
|
status: 'active',
|
|
218
218
|
priority: 'P1',
|
|
219
219
|
scope: 'general',
|
|
220
|
-
evaluability: '
|
|
220
|
+
evaluability: 'deterministic',
|
|
221
221
|
valueScore: 0,
|
|
222
222
|
adherenceRate: 0,
|
|
223
223
|
painPreventedCount: 0,
|
|
@@ -52,6 +52,11 @@ export function registerCompiledRule(stateDir: string, input: RegisterInput): Re
|
|
|
52
52
|
const now = new Date().toISOString();
|
|
53
53
|
|
|
54
54
|
// Step 1: Create the rule
|
|
55
|
+
// FIX: Auto-generated rules default to 'warn' enforcement (not 'block') until:
|
|
56
|
+
// - replay evaluation passes
|
|
57
|
+
// - coverage confirmation
|
|
58
|
+
// - human approval
|
|
59
|
+
// This prevents P_001-style false positives from blocking normal edits.
|
|
55
60
|
const rule: LedgerRule = {
|
|
56
61
|
id: ruleId,
|
|
57
62
|
version: 1,
|
|
@@ -59,7 +64,7 @@ export function registerCompiledRule(stateDir: string, input: RegisterInput): Re
|
|
|
59
64
|
description: `Automatically compiled gate rule generated from principle ${principleId}`,
|
|
60
65
|
type: 'gate',
|
|
61
66
|
triggerCondition: coversCondition,
|
|
62
|
-
enforcement: '
|
|
67
|
+
enforcement: 'warn',
|
|
63
68
|
action: codeContent,
|
|
64
69
|
principleId,
|
|
65
70
|
status: 'proposed',
|
|
@@ -82,7 +87,11 @@ export function registerCompiledRule(stateDir: string, input: RegisterInput): Re
|
|
|
82
87
|
version: '1',
|
|
83
88
|
coversCondition,
|
|
84
89
|
coveragePercentage: 100,
|
|
85
|
-
|
|
90
|
+
// FIX: Start as 'candidate' instead of 'active'.
|
|
91
|
+
// RuleHost only loads lifecycleState='active' implementations.
|
|
92
|
+
// This means auto-generated rules will NOT block until explicitly
|
|
93
|
+
// promoted to 'active' after replay evaluation + human approval.
|
|
94
|
+
lifecycleState: 'candidate' as const,
|
|
86
95
|
createdAt: now,
|
|
87
96
|
updatedAt: now,
|
|
88
97
|
};
|
|
@@ -66,6 +66,10 @@ export interface RuleHostResult {
|
|
|
66
66
|
matched: boolean;
|
|
67
67
|
reason: string;
|
|
68
68
|
diagnostics?: Record<string, unknown>;
|
|
69
|
+
/** C: Rule ID that produced this result (for observability events) */
|
|
70
|
+
ruleId?: string;
|
|
71
|
+
/** C: Principle ID that this rule implements (for observability events) */
|
|
72
|
+
principleId?: string;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// ---------------------------------------------------------------------------
|
package/src/core/rule-host.ts
CHANGED
|
@@ -234,7 +234,13 @@ export class RuleHost {
|
|
|
234
234
|
meta,
|
|
235
235
|
evaluate: (input: RuleHostInput): RuleHostResult => {
|
|
236
236
|
const frozenHelpers = createRuleHostHelpers(input);
|
|
237
|
-
|
|
237
|
+
const result = rawEvaluate(input, frozenHelpers);
|
|
238
|
+
// C: Enrich result with rule/principle IDs for observability
|
|
239
|
+
if (result.matched && (result.decision === 'block' || result.decision === 'requireApproval')) {
|
|
240
|
+
result.ruleId = impl.ruleId;
|
|
241
|
+
result.principleId = meta.ruleId ?? impl.ruleId;
|
|
242
|
+
}
|
|
243
|
+
return result;
|
|
238
244
|
},
|
|
239
245
|
};
|
|
240
246
|
} catch (compileError: unknown) {
|
package/src/hooks/gate.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from '../constants/tools.js';
|
|
40
40
|
import { getSession, hasRecentThinking } from '../core/session-tracker.js';
|
|
41
41
|
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
42
|
+
import { EventLogService } from '../core/event-log.js';
|
|
42
43
|
|
|
43
44
|
export function handleBeforeToolCall(
|
|
44
45
|
event: PluginHookBeforeToolCallEvent,
|
|
@@ -205,6 +206,20 @@ export function handleBeforeToolCall(
|
|
|
205
206
|
|
|
206
207
|
const hostResult = ruleHost.evaluate(hostInput);
|
|
207
208
|
if (hostResult?.decision === 'block' || hostResult?.decision === 'requireApproval') {
|
|
209
|
+
// C: Record rule_enforced event for matched rules
|
|
210
|
+
try {
|
|
211
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
|
|
212
|
+
eventLog.recordRuleEnforced({
|
|
213
|
+
ruleId: hostResult.ruleId || 'unknown',
|
|
214
|
+
principleId: hostResult.principleId || 'unknown',
|
|
215
|
+
enforcement: hostResult.decision === 'requireApproval' ? 'requireApproval' : 'block',
|
|
216
|
+
toolName: event.toolName,
|
|
217
|
+
filePath: relPath,
|
|
218
|
+
});
|
|
219
|
+
} catch (evErr) {
|
|
220
|
+
logger?.warn?.(`[PD_GATE] Failed to record rule_enforced event: ${String(evErr)}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
208
223
|
const reason = hostResult.decision === 'requireApproval'
|
|
209
224
|
? `[Rule Host] Approval required: ${hostResult.reason}`
|
|
210
225
|
: hostResult.reason;
|
package/src/hooks/prompt.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from '../core/empathy-keyword-matcher.js';
|
|
24
24
|
import { severityToPenalty, DEFAULT_EMPATHY_KEYWORD_CONFIG } from '../core/empathy-types.js';
|
|
25
25
|
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
26
|
+
import { EventLogService } from '../core/event-log.js';
|
|
26
27
|
import type { PluginRuntimeSubagent } from '../service/subagent-workflow/runtime-direct-driver.js';
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -755,6 +756,18 @@ ${taskBlocks}${processingNote}
|
|
|
755
756
|
</diagnostician_tasks>\n`;
|
|
756
757
|
|
|
757
758
|
logger?.info?.(`[PD:Prompt] Injected ${Math.min(pendingCount, 3)}/${pendingCount} pending diagnostician task(s) into heartbeat prompt`);
|
|
759
|
+
|
|
760
|
+
// C: Record heartbeat_diagnosis event for observability
|
|
761
|
+
try {
|
|
762
|
+
const eventLog = EventLogService.get(wctx.stateDir, logger);
|
|
763
|
+
eventLog.recordHeartbeatDiagnosis({
|
|
764
|
+
taskCount: pendingCount,
|
|
765
|
+
taskIds: pendingTasks.slice(0, 3).map(t => t.id),
|
|
766
|
+
trigger: 'heartbeat',
|
|
767
|
+
});
|
|
768
|
+
} catch (evErr) {
|
|
769
|
+
logger?.warn?.(`[PD:Prompt] Failed to record heartbeat_diagnosis event: ${String(evErr)}`);
|
|
770
|
+
}
|
|
758
771
|
}
|
|
759
772
|
} catch (e) {
|
|
760
773
|
logger?.warn?.(`[PD:Prompt] Failed to read diagnostician tasks: ${String(e)}`);
|