@principles/pd-cli 1.108.0 → 1.109.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/commands/__tests__/runtime-probe-config.test.d.ts +20 -0
- package/dist/commands/__tests__/runtime-probe-config.test.d.ts.map +1 -0
- package/dist/commands/__tests__/runtime-probe-config.test.js +388 -0
- package/dist/commands/__tests__/runtime-probe-config.test.js.map +1 -0
- package/dist/commands/config-doctor.d.ts.map +1 -1
- package/dist/commands/config-doctor.js +4 -0
- package/dist/commands/config-doctor.js.map +1 -1
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +139 -13
- package/dist/commands/runtime.js.map +1 -1
- package/dist/index.js +2 -19
- package/dist/index.js.map +1 -1
- package/dist/services/config-doctor.d.ts +2 -0
- package/dist/services/config-doctor.d.ts.map +1 -1
- package/dist/services/config-doctor.js +3 -0
- package/dist/services/config-doctor.js.map +1 -1
- package/dist/services/mainline-snapshot-assembler.d.ts.map +1 -1
- package/dist/services/mainline-snapshot-assembler.js +22 -4
- package/dist/services/mainline-snapshot-assembler.js.map +1 -1
- package/dist/services/pd-config-loader.d.ts +4 -0
- package/dist/services/pd-config-loader.d.ts.map +1 -1
- package/dist/services/pd-config-loader.js +12 -0
- package/dist/services/pd-config-loader.js.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.d.ts +11 -0
- package/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
- package/dist/services/resolve-runtime-from-pd-config.js +31 -1
- package/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/__tests__/runtime-probe-config.test.ts +431 -0
- package/src/commands/config-doctor.ts +4 -0
- package/src/commands/runtime.ts +133 -13
- package/src/index.ts +2 -19
- package/src/services/config-doctor.ts +6 -0
- package/src/services/mainline-snapshot-assembler.ts +23 -3
- package/src/services/pd-config-loader.ts +17 -0
- package/src/services/resolve-runtime-from-pd-config.ts +41 -0
- package/tests/commands/config-doctor.test.ts +15 -0
- package/tests/services/pd-config-loader.test.ts +21 -0
package/src/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { handleContextBuild } from './commands/context.js';
|
|
|
24
24
|
import { handleLegacyImportOpenClaw } from './commands/legacy-import.js';
|
|
25
25
|
import { handleLegacyCleanup } from './commands/legacy-cleanup.js';
|
|
26
26
|
import { handleDiagnoseStatus, handleDiagnoseRun } from './commands/diagnose.js';
|
|
27
|
-
import {
|
|
27
|
+
import { registerRuntimeProbeCommand } from './commands/runtime.js';
|
|
28
28
|
import { handleFlowShow } from './commands/flow.js';
|
|
29
29
|
import { handleTraceShow } from './commands/trace.js';
|
|
30
30
|
import { handlePruningReport, handlePruningExplain, handlePruningReview, handlePruningRollback, handlePruningOrphans } from './commands/runtime-pruning.js';
|
|
@@ -430,24 +430,7 @@ demoCmd
|
|
|
430
430
|
});
|
|
431
431
|
});
|
|
432
432
|
|
|
433
|
-
runtimeCmd
|
|
434
|
-
.command('probe')
|
|
435
|
-
.description('Probe runtime health and capabilities (HG-01 HARD GATE)')
|
|
436
|
-
.requiredOption('-r, --runtime <kind>', "Runtime kind: 'openclaw-cli' or 'pi-ai'")
|
|
437
|
-
.option('--openclaw-local', 'Use local OpenClaw (mutually exclusive with --openclaw-gateway)')
|
|
438
|
-
.option('--openclaw-gateway', 'Use gateway OpenClaw (mutually exclusive with --openclaw-local)')
|
|
439
|
-
.option('-a, --agent <agentId>', 'Agent ID to probe')
|
|
440
|
-
.option('--provider <name>', 'LLM provider (e.g., openrouter) 鈥?for pi-ai, falls back to --workspace workflows.yaml')
|
|
441
|
-
.option('--model <id>', 'Model ID (e.g., anthropic/claude-sonnet-4) 鈥?for pi-ai, falls back to --workspace workflows.yaml')
|
|
442
|
-
.option('--apiKeyEnv <name>', 'Env var name for API key (e.g., OPENROUTER_API_KEY) 鈥?for pi-ai, falls back to --workspace workflows.yaml')
|
|
443
|
-
.option('--baseUrl <url>', 'Custom base URL for OpenAI-compatible providers 鈥?for pi-ai, falls back to --workspace workflows.yaml')
|
|
444
|
-
.option('--maxRetries <n>', 'Max retry attempts for LLM failures', parseInt)
|
|
445
|
-
.option('--timeoutMs <ms>', 'Timeout in milliseconds for probe', parseInt)
|
|
446
|
-
.option('-w, --workspace <path>', 'Workspace directory 鈥?loads pi-ai policy from .state/workflows.yaml')
|
|
447
|
-
.option('--json', 'Output raw JSON')
|
|
448
|
-
.action(async (opts) => {
|
|
449
|
-
await handleRuntimeProbe(opts);
|
|
450
|
-
});
|
|
433
|
+
registerRuntimeProbeCommand(runtimeCmd);
|
|
451
434
|
|
|
452
435
|
const flowCmd = runtimeCmd
|
|
453
436
|
.command('flow')
|
|
@@ -110,6 +110,8 @@ export interface DoctorOutput {
|
|
|
110
110
|
nextActions: string[];
|
|
111
111
|
/** Legacy files detected (informational only, not used for resolution) */
|
|
112
112
|
legacyFilesDetected: string[];
|
|
113
|
+
/** PRI-404: Next actions for removing legacy files */
|
|
114
|
+
legacyFileNextActions: string[];
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -450,6 +452,9 @@ export async function buildDoctorOutput(input: BuildDoctorInput): Promise<Doctor
|
|
|
450
452
|
warnings.push(...loadResult.warnings);
|
|
451
453
|
warnings.push(...flags.warnings);
|
|
452
454
|
|
|
455
|
+
// PRI-404: Add legacy file nextActions (separate field, not mixed into general nextActions)
|
|
456
|
+
const { legacyFileNextActions } = loadResult;
|
|
457
|
+
|
|
453
458
|
if (!loadResult.ok) {
|
|
454
459
|
for (const err of loadResult.errors) {
|
|
455
460
|
warnings.push(`Config error at ${err.path}: ${err.reason}`);
|
|
@@ -567,6 +572,7 @@ export async function buildDoctorOutput(input: BuildDoctorInput): Promise<Doctor
|
|
|
567
572
|
warnings,
|
|
568
573
|
nextActions: nextActions.length > 0 ? nextActions : ['All checks passed — configuration is valid'],
|
|
569
574
|
legacyFilesDetected: loadResult.legacyFilesDetected,
|
|
575
|
+
legacyFileNextActions,
|
|
570
576
|
};
|
|
571
577
|
|
|
572
578
|
if (reason) out.reason = reason;
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
assertMainlineContract,
|
|
21
21
|
EMPTY_CONTEXT_SENTINEL,
|
|
22
22
|
hydratePITaskRecord,
|
|
23
|
+
resolveAgentRuntimeBinding,
|
|
23
24
|
type MainlineSnapshot,
|
|
24
25
|
type RuntimeReadinessSnapshot,
|
|
25
26
|
type DiagnosisTaskSnapshot,
|
|
@@ -140,13 +141,32 @@ function buildDefaultReadiness(workspaceDir: string, warnings: string[]): Runtim
|
|
|
140
141
|
warnings.push(...configLoadResult.warnings);
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
// PRI-402: resolve real runtimeProbeProfile from .pd/config.yaml
|
|
145
|
+
// so smoke's config_source_alignment + diagnostician_readiness can judge correctly
|
|
146
|
+
let runtimeProbeProfile: string | null = null;
|
|
147
|
+
let diagnosticianReady = false;
|
|
148
|
+
let diagnosticianReadinessReason = 'No readiness snapshot provided; run "pd runtime probe" first.';
|
|
149
|
+
|
|
150
|
+
if (configLoadResult.ok) {
|
|
151
|
+
const bindingResult = resolveAgentRuntimeBinding(configLoadResult.effective, 'diagnostician');
|
|
152
|
+
if (bindingResult.ok) {
|
|
153
|
+
runtimeProbeProfile = bindingResult.profileId;
|
|
154
|
+
// Profile exists and is bound — readiness is at least "configured"
|
|
155
|
+
// Actual connectivity is unknown without probe, but config alignment is satisfied
|
|
156
|
+
diagnosticianReady = true;
|
|
157
|
+
diagnosticianReadinessReason = `Profile '${bindingResult.profileId}' resolved from .pd/config.yaml (connectivity not probed)`;
|
|
158
|
+
} else {
|
|
159
|
+
diagnosticianReadinessReason = bindingResult.reason;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
143
163
|
return {
|
|
144
164
|
configDoctorProfile: defaultProfile,
|
|
145
|
-
runtimeProbeProfile
|
|
165
|
+
runtimeProbeProfile,
|
|
146
166
|
configSource: '.pd/config.yaml',
|
|
147
167
|
probeConfigSource: '.pd/config.yaml',
|
|
148
|
-
diagnosticianReady
|
|
149
|
-
diagnosticianReadinessReason
|
|
168
|
+
diagnosticianReady,
|
|
169
|
+
diagnosticianReadinessReason,
|
|
150
170
|
};
|
|
151
171
|
}
|
|
152
172
|
|
|
@@ -46,6 +46,8 @@ export interface PdConfigLoadResultOk {
|
|
|
46
46
|
warnings: string[];
|
|
47
47
|
/** If legacy files were detected */
|
|
48
48
|
legacyFilesDetected: string[];
|
|
49
|
+
/** PRI-404: Next actions for removing legacy files (empty if none detected) */
|
|
50
|
+
legacyFileNextActions: string[];
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface PdConfigLoadResultErr {
|
|
@@ -58,6 +60,8 @@ export interface PdConfigLoadResultErr {
|
|
|
58
60
|
/** Warnings from config resolution */
|
|
59
61
|
warnings: string[];
|
|
60
62
|
legacyFilesDetected: string[];
|
|
63
|
+
/** PRI-404: Next actions for removing legacy files (empty if none detected) */
|
|
64
|
+
legacyFileNextActions: string[];
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
export type PdConfigLoadResult = PdConfigLoadResultOk | PdConfigLoadResultErr;
|
|
@@ -84,6 +88,13 @@ function detectLegacyFiles(workspaceDir: string): string[] {
|
|
|
84
88
|
return detected;
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
/** PRI-404: Generate nextAction suggestions for removing legacy files. */
|
|
92
|
+
function buildLegacyFileNextActions(legacyFiles: string[]): string[] {
|
|
93
|
+
return legacyFiles.map(f => {
|
|
94
|
+
return `Run 'Remove-Item ${f} -Force' to remove legacy config`;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
// ── Load PD Config ───────────────────────────────────────────────────────────
|
|
88
99
|
|
|
89
100
|
/**
|
|
@@ -98,6 +109,7 @@ function detectLegacyFiles(workspaceDir: string): string[] {
|
|
|
98
109
|
export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
99
110
|
const configPath = getPdConfigPath(workspaceDir);
|
|
100
111
|
const legacyFilesDetected = detectLegacyFiles(workspaceDir);
|
|
112
|
+
const legacyFileNextActions = buildLegacyFileNextActions(legacyFilesDetected);
|
|
101
113
|
|
|
102
114
|
// 1) Config file missing → use defaults
|
|
103
115
|
if (!fs.existsSync(configPath)) {
|
|
@@ -114,6 +126,7 @@ export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
|
114
126
|
: []),
|
|
115
127
|
],
|
|
116
128
|
legacyFilesDetected,
|
|
129
|
+
legacyFileNextActions,
|
|
117
130
|
};
|
|
118
131
|
}
|
|
119
132
|
|
|
@@ -132,6 +145,7 @@ export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
|
132
145
|
warnings: [],
|
|
133
146
|
defaults: effective,
|
|
134
147
|
legacyFilesDetected,
|
|
148
|
+
legacyFileNextActions,
|
|
135
149
|
};
|
|
136
150
|
}
|
|
137
151
|
|
|
@@ -150,6 +164,7 @@ export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
|
150
164
|
warnings: [],
|
|
151
165
|
defaults: effective,
|
|
152
166
|
legacyFilesDetected,
|
|
167
|
+
legacyFileNextActions,
|
|
153
168
|
};
|
|
154
169
|
}
|
|
155
170
|
|
|
@@ -170,6 +185,7 @@ export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
|
170
185
|
warnings: [],
|
|
171
186
|
defaults: effective,
|
|
172
187
|
legacyFilesDetected,
|
|
188
|
+
legacyFileNextActions,
|
|
173
189
|
};
|
|
174
190
|
}
|
|
175
191
|
|
|
@@ -188,6 +204,7 @@ export function loadPdConfig(workspaceDir: string): PdConfigLoadResult {
|
|
|
188
204
|
: []),
|
|
189
205
|
],
|
|
190
206
|
legacyFilesDetected,
|
|
207
|
+
legacyFileNextActions,
|
|
191
208
|
};
|
|
192
209
|
}
|
|
193
210
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import {
|
|
17
17
|
resolveRuntimeConfigFromPdConfig,
|
|
18
18
|
isRuntimeConfigError,
|
|
19
|
+
resolveAgentRuntimeBinding,
|
|
19
20
|
} from '@principles/core/runtime-v2';
|
|
20
21
|
import type {
|
|
21
22
|
RuntimeConfigResult,
|
|
@@ -34,6 +35,33 @@ export interface ResolvedRuntimeFromPdConfig {
|
|
|
34
35
|
configLoadResult: PdConfigLoadResult;
|
|
35
36
|
/** Canonical config source label, e.g. ".pd/config.yaml". */
|
|
36
37
|
configSource: string;
|
|
38
|
+
/**
|
|
39
|
+
* PRI-402: Resolved runtime profile ID for the diagnostician agent.
|
|
40
|
+
* e.g. "pi-ai.lmstudio". null when config resolution fails or profile is not found.
|
|
41
|
+
*/
|
|
42
|
+
runtimeProfileId: string | null;
|
|
43
|
+
/**
|
|
44
|
+
* PRI-402: Human-readable runtime profile label for the diagnostician agent.
|
|
45
|
+
* e.g. "pi-ai: lmstudio/qwen3.6-27b-mtp". null when config resolution fails.
|
|
46
|
+
* Matches the label format used by `pd config doctor`.
|
|
47
|
+
*/
|
|
48
|
+
runtimeProfileLabel: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build a profile label matching the format used by `pd config doctor`.
|
|
53
|
+
* Mirrors `buildProfileLabel` in `pd-config-redaction.ts` (core).
|
|
54
|
+
*/
|
|
55
|
+
function buildProfileLabel(profileId: string, profile: { type: string; provider?: string; model?: string; source?: string }): string {
|
|
56
|
+
if (profile.type === 'openclaw') {
|
|
57
|
+
const parts: string[] = ['openclaw'];
|
|
58
|
+
if (profile.provider) parts.push(profile.provider);
|
|
59
|
+
if (profile.model) parts.push(profile.model);
|
|
60
|
+
if (profile.source && !profile.provider && !profile.model) parts.push(profile.source);
|
|
61
|
+
return parts.join(': ');
|
|
62
|
+
}
|
|
63
|
+
// pi-ai
|
|
64
|
+
return `pi-ai: ${profile.provider ?? 'unknown'}/${profile.model ?? 'unknown'}`;
|
|
37
65
|
}
|
|
38
66
|
|
|
39
67
|
/**
|
|
@@ -77,11 +105,22 @@ export function resolveRuntimeFromPdConfig(
|
|
|
77
105
|
legacyWarnings,
|
|
78
106
|
configLoadResult,
|
|
79
107
|
configSource: '.pd/config.yaml',
|
|
108
|
+
runtimeProfileId: null,
|
|
109
|
+
runtimeProfileLabel: null,
|
|
80
110
|
};
|
|
81
111
|
}
|
|
82
112
|
|
|
83
113
|
const result = resolveRuntimeConfigFromPdConfig(configLoadResult.effective, getEnvVar);
|
|
84
114
|
|
|
115
|
+
// PRI-402: Extract profile ID and label for probe output alignment with doctor
|
|
116
|
+
let runtimeProfileId: string | null = null;
|
|
117
|
+
let runtimeProfileLabel: string | null = null;
|
|
118
|
+
const bindingResult = resolveAgentRuntimeBinding(configLoadResult.effective, 'diagnostician');
|
|
119
|
+
if (bindingResult.ok) {
|
|
120
|
+
runtimeProfileId = bindingResult.profileId;
|
|
121
|
+
runtimeProfileLabel = buildProfileLabel(bindingResult.profileId, bindingResult.profile);
|
|
122
|
+
}
|
|
123
|
+
|
|
85
124
|
const legacyWarnings = configLoadResult.legacyFilesDetected.length > 0
|
|
86
125
|
? [
|
|
87
126
|
`Legacy config files detected: ${configLoadResult.legacyFilesDetected.join(', ')}. ` +
|
|
@@ -94,6 +133,8 @@ export function resolveRuntimeFromPdConfig(
|
|
|
94
133
|
legacyWarnings,
|
|
95
134
|
configLoadResult,
|
|
96
135
|
configSource: '.pd/config.yaml',
|
|
136
|
+
runtimeProfileId,
|
|
137
|
+
runtimeProfileLabel,
|
|
97
138
|
};
|
|
98
139
|
}
|
|
99
140
|
|
|
@@ -239,6 +239,21 @@ describe('Legacy file detection', () => {
|
|
|
239
239
|
expect(output.legacyFilesDetected[0]).toContain('feature-flags.yaml');
|
|
240
240
|
} finally { rmTmpDir(tmp); }
|
|
241
241
|
});
|
|
242
|
+
|
|
243
|
+
it('PRI-404: legacyFileNextActions contains Remove-Item commands and does not pollute nextActions', async () => {
|
|
244
|
+
const tmp = mkTmpDir();
|
|
245
|
+
const stateDir = path.join(tmp, '.state');
|
|
246
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
247
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1\n', 'utf8');
|
|
248
|
+
try {
|
|
249
|
+
const output = await buildDoctorOutput({ workspaceDir: tmp });
|
|
250
|
+
expect(output.legacyFileNextActions.length).toBeGreaterThan(0);
|
|
251
|
+
expect(output.legacyFileNextActions[0]).toContain('Remove-Item');
|
|
252
|
+
expect(output.legacyFileNextActions[0]).toContain('workflows.yaml');
|
|
253
|
+
// Regression: legacyFileNextActions must NOT appear in general nextActions
|
|
254
|
+
expect(output.nextActions.some(na => na.includes('Remove-Item') && na.includes('workflows.yaml'))).toBe(false);
|
|
255
|
+
} finally { rmTmpDir(tmp); }
|
|
256
|
+
});
|
|
242
257
|
});
|
|
243
258
|
|
|
244
259
|
// ── Feature flags from config.yaml ───────────────────────────────────────────
|
|
@@ -445,6 +445,27 @@ describe('Legacy file detection', () => {
|
|
|
445
445
|
expect(result.warnings.some(w => w.includes('Legacy config files detected'))).toBe(true);
|
|
446
446
|
} finally { rmTmpDir(tmp); }
|
|
447
447
|
});
|
|
448
|
+
|
|
449
|
+
it('PRI-404: includes legacyFileNextActions with Remove-Item command when legacy files detected', () => {
|
|
450
|
+
const tmp = mkTmpDir();
|
|
451
|
+
const stateDir = path.join(tmp, '.state');
|
|
452
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
453
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1\n', 'utf8');
|
|
454
|
+
try {
|
|
455
|
+
const result = loadPdConfig(tmp);
|
|
456
|
+
expect(result.legacyFileNextActions.length).toBeGreaterThan(0);
|
|
457
|
+
expect(result.legacyFileNextActions[0]).toContain('Remove-Item');
|
|
458
|
+
expect(result.legacyFileNextActions[0]).toContain('workflows.yaml');
|
|
459
|
+
} finally { rmTmpDir(tmp); }
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('PRI-404: legacyFileNextActions is empty when no legacy files', () => {
|
|
463
|
+
const tmp = mkTmpDir();
|
|
464
|
+
try {
|
|
465
|
+
const result = loadPdConfig(tmp);
|
|
466
|
+
expect(result.legacyFileNextActions).toEqual([]);
|
|
467
|
+
} finally { rmTmpDir(tmp); }
|
|
468
|
+
});
|
|
448
469
|
});
|
|
449
470
|
|
|
450
471
|
// ── JSON purity ──────────────────────────────────────────────────────────────
|