principles-disciple 1.59.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 +2 -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/utils/workspace-resolver.ts +44 -3
- package/tests/commands/pd-reflect.test.ts +1 -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)) {
|
|
@@ -168,7 +169,7 @@ function buildChineseOutput(
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
export function handleEvolutionStatusCommand(ctx: PluginCommandContext): { text: string } {
|
|
171
|
-
const workspaceDir = (ctx
|
|
172
|
+
const workspaceDir = resolvePluginCommandWorkspaceDir(ctx, 'evolution-status');
|
|
172
173
|
const sessionId = (ctx as { sessionId?: string | null }).sessionId ?? null;
|
|
173
174
|
// #207/#210: Use WorkspaceContext to get evolutionReducer with stateDir
|
|
174
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() || '';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Shared helpers for resolving workspace directories across commands and hooks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
7
|
+
import type { OpenClawPluginApi, PluginCommandContext } from '../openclaw-sdk.js';
|
|
8
8
|
import { validateWorkspaceDir, type WorkspaceResolutionContext } from '../core/workspace-dir-validation.js';
|
|
9
9
|
import { resolveWorkspaceDir } from '../core/workspace-dir-service.js';
|
|
10
10
|
import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
|
|
@@ -25,7 +25,10 @@ export function resolveCommandWorkspaceDir(
|
|
|
25
25
|
if (ctx.workspaceDir) {
|
|
26
26
|
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
27
27
|
if (!issue) return ctx.workspaceDir;
|
|
28
|
-
|
|
28
|
+
// Validation failed — fail immediately, do not silently fall back
|
|
29
|
+
const errorMsg = `[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`;
|
|
30
|
+
api.logger.error(errorMsg);
|
|
31
|
+
throw new Error(errorMsg);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
// 2. Official OpenClaw API → env vars → config file
|
|
@@ -34,13 +37,51 @@ export function resolveCommandWorkspaceDir(
|
|
|
34
37
|
|
|
35
38
|
// CRITICAL FAILURE: Cannot determine workspace directory
|
|
36
39
|
const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
|
|
37
|
-
`ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
|
|
40
|
+
`ctx.workspaceDir="${ctx.workspaceDir ?? ''}" is invalid, and all fallbacks failed. ` +
|
|
38
41
|
`Commands will NOT execute to prevent data corruption.`;
|
|
39
42
|
api.logger.error(errorMsg);
|
|
40
43
|
|
|
41
44
|
throw new Error(errorMsg);
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Resolve workspace directory for plugin command execution.
|
|
49
|
+
*
|
|
50
|
+
* Chain: ctx.workspaceDir (canonical) → ctx.config.workspaceDir (dispatcher fallback)
|
|
51
|
+
*
|
|
52
|
+
* CRITICAL: Throws if workspaceDir cannot be resolved. Commands must NEVER silently
|
|
53
|
+
* fall back to process.cwd() as this masks configuration errors and can corrupt
|
|
54
|
+
* the wrong workspace.
|
|
55
|
+
*
|
|
56
|
+
* @param ctx - Plugin command context (has workspaceDir + config properties)
|
|
57
|
+
* @param source - Source label for error messages (e.g. 'evolution-status', 'pain')
|
|
58
|
+
*/
|
|
59
|
+
export function resolvePluginCommandWorkspaceDir(
|
|
60
|
+
ctx: PluginCommandContext,
|
|
61
|
+
source: string,
|
|
62
|
+
): string {
|
|
63
|
+
// 1. Canonical workspaceDir field (set by OpenClaw command dispatcher)
|
|
64
|
+
if (ctx.workspaceDir) {
|
|
65
|
+
const issue = validateWorkspaceDir(ctx.workspaceDir);
|
|
66
|
+
if (!issue) return ctx.workspaceDir;
|
|
67
|
+
throw new Error(`[PD:Command:${source}] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Dispatcher may also put workspaceDir in config (legacy/alternative path)
|
|
71
|
+
const configWorkspaceDir = ctx.config?.workspaceDir as string | undefined;
|
|
72
|
+
if (configWorkspaceDir) {
|
|
73
|
+
const issue = validateWorkspaceDir(configWorkspaceDir);
|
|
74
|
+
if (!issue) return configWorkspaceDir;
|
|
75
|
+
throw new Error(`[PD:Command:${source}] ctx.config.workspaceDir="${configWorkspaceDir}" is invalid: ${issue}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// CRITICAL FAILURE: No workspace directory available
|
|
79
|
+
throw new Error(
|
|
80
|
+
`[PD:Command:${source}] CRITICAL: workspaceDir is not set in ctx.workspaceDir or ctx.config.workspaceDir. ` +
|
|
81
|
+
`Commands cannot execute without a valid workspace. Set OPENCLAW_WORKSPACE_DIR env var or ensure the workspace is properly initialized.`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
44
85
|
/**
|
|
45
86
|
* Resolve workspace directory for tool hook execution (safe version).
|
|
46
87
|
* Returns undefined instead of throwing if resolution fails.
|
|
@@ -20,7 +20,7 @@ describe('pd-reflect command', () => {
|
|
|
20
20
|
it('requires an explicit resolved workspace directory', async () => {
|
|
21
21
|
const result = await handlePdReflect.handler({} as any);
|
|
22
22
|
expect(result.isError).toBe(true);
|
|
23
|
-
expect(result.text).toContain('
|
|
23
|
+
expect(result.text).toContain('workspaceDir is not set');
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('enqueues into the provided active workspace', async () => {
|