@principles/pd-cli 1.142.3 → 1.142.4
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__/telemetry-flag-wiring.test.d.ts +9 -0
- package/dist/commands/__tests__/telemetry-flag-wiring.test.d.ts.map +1 -0
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js +76 -0
- package/dist/commands/__tests__/telemetry-flag-wiring.test.js.map +1 -0
- package/dist/commands/__tests__/version.test.d.ts +2 -0
- package/dist/commands/__tests__/version.test.d.ts.map +1 -0
- package/dist/commands/__tests__/version.test.js +134 -0
- package/dist/commands/__tests__/version.test.js.map +1 -0
- package/dist/commands/console.d.ts.map +1 -1
- package/dist/commands/console.js +29 -10
- package/dist/commands/console.js.map +1 -1
- package/dist/commands/pain-record.d.ts.map +1 -1
- package/dist/commands/pain-record.js +6 -15
- package/dist/commands/pain-record.js.map +1 -1
- package/dist/commands/runtime-activation.d.ts +29 -0
- package/dist/commands/runtime-activation.d.ts.map +1 -1
- package/dist/commands/runtime-activation.js +70 -31
- package/dist/commands/runtime-activation.js.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.d.ts.map +1 -1
- package/dist/commands/runtime-internalization-enqueue-successors.js +2 -2
- package/dist/commands/runtime-internalization-enqueue-successors.js.map +1 -1
- package/dist/commands/telemetry.d.ts +82 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +268 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/version.d.ts +11 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +48 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/index.js +35 -2
- package/dist/index.js.map +1 -1
- package/dist/services/version-report.d.ts +44 -0
- package/dist/services/version-report.d.ts.map +1 -0
- package/dist/services/version-report.js +152 -0
- package/dist/services/version-report.js.map +1 -0
- package/package.json +2 -1
- package/src/commands/__tests__/telemetry-flag-wiring.test.ts +84 -0
- package/src/commands/__tests__/version.test.ts +141 -0
- package/src/commands/console.ts +34 -11
- package/src/commands/pain-record.ts +6 -13
- package/src/commands/runtime-activation.ts +74 -22
- package/src/commands/runtime-internalization-enqueue-successors.ts +3 -2
- package/src/commands/telemetry.ts +349 -0
- package/src/commands/version.ts +49 -0
- package/src/index.ts +37 -2
- package/src/services/version-report.ts +203 -0
- package/tests/commands/console-open.test.ts +55 -5
- package/tests/commands/pain-record.test.ts +26 -0
- package/tests/commands/runtime-activation-shadow-telemetry.test.ts +130 -0
- package/tests/commands/runtime-activation.test.ts +67 -1
- package/tests/commands/runtime-internalization-enqueue-successors.test.ts +6 -1
- package/tests/commands/telemetry.test.ts +190 -0
- package/tests/e2e/cli-full-flow.test.ts +38 -3
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical version report builder (SPEC §12).
|
|
3
|
+
*
|
|
4
|
+
* `pd --version` prints one stable short text line; `pd version --json`
|
|
5
|
+
* exposes the full canonical report: productVersion, releaseId, components,
|
|
6
|
+
* bootstrapVersion, channel, source, generation, health, and the last
|
|
7
|
+
* transaction. The canonical product identity comes from the installation
|
|
8
|
+
* state under ~/.pd — never from a checkout's package.json.
|
|
9
|
+
*
|
|
10
|
+
* The record shapes here mirror the canonical contracts owned by
|
|
11
|
+
* create-principles-disciple/src/update/ (the deep ReleaseManager module);
|
|
12
|
+
* this reader is deliberately thin: it READS installation state and never
|
|
13
|
+
* performs update logic.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'node:fs';
|
|
16
|
+
import * as os from 'node:os';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
export class VersionReportError extends Error {
|
|
19
|
+
reason;
|
|
20
|
+
nextAction;
|
|
21
|
+
constructor(reason, message, nextAction) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'VersionReportError';
|
|
24
|
+
this.reason = reason;
|
|
25
|
+
this.nextAction = nextAction;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isPlainObject(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function readJsonIfPresent(filePath) {
|
|
32
|
+
if (!fs.existsSync(filePath))
|
|
33
|
+
return null;
|
|
34
|
+
let value;
|
|
35
|
+
try {
|
|
36
|
+
value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new VersionReportError('state_corrupt', `Installation state file is not valid JSON: ${filePath} (${error instanceof Error ? error.message : String(error)})`, 'Run the official installer recovery, or re-run the official installer to repair the installation record.');
|
|
40
|
+
}
|
|
41
|
+
if (!isPlainObject(value)) {
|
|
42
|
+
throw new VersionReportError('state_corrupt', `Installation state file is not a JSON object: ${filePath}`, 'Run the official installer recovery, or re-run the official installer to repair the installation record.');
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
/** `pd --version` short stable text contract. */
|
|
47
|
+
export function formatShortVersion(report) {
|
|
48
|
+
return `Principles Disciple ${report.productVersion} (${report.releaseId.slice(0, 12)})`;
|
|
49
|
+
}
|
|
50
|
+
function buildLegacyOverlayReport(overlayDir, bootstrap) {
|
|
51
|
+
// The current official installer copies the plugin package directly into
|
|
52
|
+
// the OpenClaw extension root. Older overlay bundles kept it under plugin/;
|
|
53
|
+
// accept that layout only when the canonical root manifest is absent.
|
|
54
|
+
const rootManifest = readJsonIfPresent(path.join(overlayDir, 'package.json'));
|
|
55
|
+
const pluginManifest = rootManifest ?? readJsonIfPresent(path.join(overlayDir, 'plugin', 'package.json'));
|
|
56
|
+
const version = pluginManifest?.version;
|
|
57
|
+
if (typeof version !== 'string' || version.length === 0) {
|
|
58
|
+
throw new VersionReportError('legacy_overlay_manifest_invalid', `The legacy overlay at ${overlayDir} has no readable plugin version.`, 'Re-install PD with the official installer to migrate this installation into the supported layout.');
|
|
59
|
+
}
|
|
60
|
+
const bootstrapVersion = bootstrap?.bootstrapVersion;
|
|
61
|
+
return {
|
|
62
|
+
productVersion: version,
|
|
63
|
+
releaseId: '0'.repeat(64),
|
|
64
|
+
components: { plugin: version },
|
|
65
|
+
bootstrapVersion: typeof bootstrapVersion === 'string' ? bootstrapVersion : 'unknown',
|
|
66
|
+
channel: 'stable',
|
|
67
|
+
source: 'official-legacy-overlay',
|
|
68
|
+
generation: 0,
|
|
69
|
+
health: 'degraded',
|
|
70
|
+
lastTransaction: null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function readLastTransaction(historyPath) {
|
|
74
|
+
if (!fs.existsSync(historyPath))
|
|
75
|
+
return null;
|
|
76
|
+
const lines = fs.readFileSync(historyPath, 'utf8').split('\n').filter((line) => line.trim().length > 0);
|
|
77
|
+
const lastLine = lines[lines.length - 1];
|
|
78
|
+
if (lastLine === undefined)
|
|
79
|
+
return null;
|
|
80
|
+
let value;
|
|
81
|
+
try {
|
|
82
|
+
value = JSON.parse(lastLine);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new VersionReportError('state_corrupt', `The last transaction record is not valid JSON: ${historyPath} (${error instanceof Error ? error.message : String(error)})`, 'Run the official installer recovery to reconcile the transaction journal before trusting the installed version.');
|
|
86
|
+
}
|
|
87
|
+
if (!isPlainObject(value))
|
|
88
|
+
return null;
|
|
89
|
+
const { transactionId, kind, outcome } = value;
|
|
90
|
+
if (typeof transactionId !== 'string' || typeof kind !== 'string' || typeof outcome !== 'string') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return { id: transactionId, kind, outcome };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Builds the canonical version report from the installation state. Throws
|
|
97
|
+
* VersionReportError with an installer next-action when no installation
|
|
98
|
+
* exists at all.
|
|
99
|
+
*/
|
|
100
|
+
export function buildVersionReport(homeDir = os.homedir()) {
|
|
101
|
+
const pdHome = path.join(homeDir, '.pd');
|
|
102
|
+
const active = readJsonIfPresent(path.join(pdHome, 'active.json'));
|
|
103
|
+
const bootstrap = readJsonIfPresent(path.join(pdHome, 'bootstrap', 'bootstrap.json'));
|
|
104
|
+
const installConfig = readJsonIfPresent(path.join(pdHome, 'install.json'));
|
|
105
|
+
const overlayDir = path.join(homeDir, '.openclaw', 'extensions', 'principles-disciple');
|
|
106
|
+
if (active === null && fs.existsSync(overlayDir)) {
|
|
107
|
+
return buildLegacyOverlayReport(overlayDir, bootstrap);
|
|
108
|
+
}
|
|
109
|
+
if (active === null && !fs.existsSync(pdHome)) {
|
|
110
|
+
throw new VersionReportError('not_installed', 'No PD installation was found under ~/.pd or the legacy overlay location.', 'Install PD with the official installer (npx create-principles-disciple), then run pd version again.');
|
|
111
|
+
}
|
|
112
|
+
if (active === null) {
|
|
113
|
+
throw new VersionReportError('active_record_missing', 'The ~/.pd installation exists but has no active release record.', 'Run the official installer to complete the installation, or re-run it to repair the record.');
|
|
114
|
+
}
|
|
115
|
+
const { generation, releaseId, productVersion } = active;
|
|
116
|
+
if (typeof generation !== 'number' || !Number.isSafeInteger(generation) || generation < 1
|
|
117
|
+
|| typeof releaseId !== 'string' || releaseId.length === 0
|
|
118
|
+
|| typeof productVersion !== 'string' || productVersion.length === 0) {
|
|
119
|
+
throw new VersionReportError('active_record_corrupt', 'The active release record under ~/.pd is malformed.', 'Run the official installer recovery, or re-run the official installer to repair the installation record.');
|
|
120
|
+
}
|
|
121
|
+
const releaseDir = path.join(pdHome, 'releases', releaseId);
|
|
122
|
+
const releaseManifest = readJsonIfPresent(path.join(releaseDir, 'metadata.json'));
|
|
123
|
+
const releaseMetadataMatchesActive = releaseManifest !== null
|
|
124
|
+
&& releaseManifest.productVersion === productVersion
|
|
125
|
+
&& releaseManifest.releaseId === releaseId
|
|
126
|
+
&& releaseManifest.metadataDigest === active.releaseMetadataDigest;
|
|
127
|
+
const health = releaseManifest === null
|
|
128
|
+
? 'degraded'
|
|
129
|
+
: releaseMetadataMatchesActive ? 'healthy' : 'corrupt';
|
|
130
|
+
const components = {};
|
|
131
|
+
for (const component of ['plugin', 'console', 'core', 'pd-cli', 'host-runtime', 'install-layout']) {
|
|
132
|
+
const manifest = readJsonIfPresent(path.join(releaseDir, component, 'package.json'));
|
|
133
|
+
const version = manifest?.version;
|
|
134
|
+
if (typeof version === 'string') {
|
|
135
|
+
components[component] = version;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const bootstrapVersion = bootstrap?.bootstrapVersion;
|
|
139
|
+
const channelValue = installConfig?.channel;
|
|
140
|
+
return {
|
|
141
|
+
productVersion,
|
|
142
|
+
releaseId,
|
|
143
|
+
components,
|
|
144
|
+
bootstrapVersion: typeof bootstrapVersion === 'string' ? bootstrapVersion : 'unknown',
|
|
145
|
+
channel: channelValue === 'candidate' ? 'candidate' : 'stable',
|
|
146
|
+
source: 'official-installer',
|
|
147
|
+
generation,
|
|
148
|
+
health,
|
|
149
|
+
lastTransaction: readLastTransaction(path.join(pdHome, 'logs', 'history.jsonl')),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=version-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-report.js","sourceRoot":"","sources":["../../src/services/version-report.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/pd-cli",
|
|
3
|
-
"version": "1.142.
|
|
3
|
+
"version": "1.142.4",
|
|
4
4
|
"description": "PD CLI — Pain recording, sample management, and governance tasks for Principles Disciple",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@principles/core": "^1.74.1",
|
|
17
17
|
"@principles/codex-adapter": "^0.1.0",
|
|
18
18
|
"@principles/host-runtime": "^0.1.0",
|
|
19
|
+
"@principles/install-layout": "0.1.0",
|
|
19
20
|
"principles-disciple": "^1.74.1",
|
|
20
21
|
"better-sqlite3": "^13.0.3",
|
|
21
22
|
"commander": "^12.0.0",
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pd telemetry — parser-level flag wiring tests (PRI-597, CLI Operator Gate
|
|
3
|
+
* rule 7 / EP-04).
|
|
4
|
+
*
|
|
5
|
+
* Exercises the real Commander tree via registerTelemetryCommand; actions are
|
|
6
|
+
* captured so no handler logic runs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { registerTelemetryCommand } from '../telemetry.js';
|
|
12
|
+
|
|
13
|
+
function freshProgram(): Command {
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program.name('pd').exitOverride();
|
|
16
|
+
return program;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function requireCmd(cmd: Command | undefined, name: string): Command {
|
|
20
|
+
if (cmd === undefined) {
|
|
21
|
+
throw new Error(`Command '${name}' not found in tree`);
|
|
22
|
+
}
|
|
23
|
+
return cmd;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function telemetrySub(name: string): Command {
|
|
27
|
+
const program = freshProgram();
|
|
28
|
+
registerTelemetryCommand(program);
|
|
29
|
+
const telemetry = requireCmd(program.commands.find((c) => c.name() === 'telemetry'), 'telemetry');
|
|
30
|
+
return requireCmd(telemetry.commands.find((c) => c.name() === name), `telemetry ${name}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('pd telemetry — command registration', () => {
|
|
34
|
+
it('registers the telemetry group with all five subcommands', () => {
|
|
35
|
+
const program = freshProgram();
|
|
36
|
+
registerTelemetryCommand(program);
|
|
37
|
+
const telemetry = requireCmd(program.commands.find((c) => c.name() === 'telemetry'), 'telemetry');
|
|
38
|
+
const subNames = telemetry.commands.map((c) => c.name()).sort();
|
|
39
|
+
expect(subNames).toEqual(['disable', 'enable', 'preview', 'reset', 'status']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('status registers --workspace (-w) and --json', () => {
|
|
43
|
+
const status = telemetrySub('status');
|
|
44
|
+
expect(status.options.find((o) => o.long === '--workspace')?.short).toBe('-w');
|
|
45
|
+
expect(status.options.find((o) => o.long === '--json')).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('enable/disable/reset register the dry-run/confirm pair (cli-4) plus --workspace and --json', () => {
|
|
49
|
+
for (const name of ['enable', 'disable', 'reset']) {
|
|
50
|
+
const cmd = telemetrySub(name);
|
|
51
|
+
expect(cmd.options.find((o) => o.long === '--dry-run'), name).toBeDefined();
|
|
52
|
+
expect(cmd.options.find((o) => o.long === '--confirm'), name).toBeDefined();
|
|
53
|
+
expect(cmd.options.find((o) => o.long === '--workspace'), name).toBeDefined();
|
|
54
|
+
expect(cmd.options.find((o) => o.long === '--json'), name).toBeDefined();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('preview registers --workspace (-w) and --json', () => {
|
|
59
|
+
const preview = telemetrySub('preview');
|
|
60
|
+
expect(preview.options.find((o) => o.long === '--workspace')?.short).toBe('-w');
|
|
61
|
+
expect(preview.options.find((o) => o.long === '--json')).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('parser dispatches telemetry enable with --confirm as opts.confirm === true', async () => {
|
|
65
|
+
const program = freshProgram();
|
|
66
|
+
registerTelemetryCommand(program);
|
|
67
|
+
const holder: { opts: Record<string, unknown> | null } = { opts: null };
|
|
68
|
+
const telemetry = requireCmd(program.commands.find((c) => c.name() === 'telemetry'), 'telemetry');
|
|
69
|
+
const enable = requireCmd(telemetry.commands.find((c) => c.name() === 'enable'), 'enable');
|
|
70
|
+
enable.action((...args: unknown[]) => {
|
|
71
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
72
|
+
const arg = args[i];
|
|
73
|
+
if (arg !== null && typeof arg === 'object' && !(arg instanceof Command)) {
|
|
74
|
+
holder.opts = arg as Record<string, unknown>;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
await program.parseAsync(['node', 'pd', 'telemetry', 'enable', '--confirm', '--json']);
|
|
80
|
+
expect(holder.opts).not.toBeNull();
|
|
81
|
+
expect(holder.opts?.confirm).toBe(true);
|
|
82
|
+
expect(holder.opts?.json).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { buildVersionReport, formatShortVersion, VersionReportError } from '../../services/version-report.js';
|
|
6
|
+
|
|
7
|
+
const temporaryDirectories: string[] = [];
|
|
8
|
+
|
|
9
|
+
function tempHome(): string {
|
|
10
|
+
const root = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'pd-version-'));
|
|
11
|
+
temporaryDirectories.push(root);
|
|
12
|
+
return root;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeDualSlotHome(home: string): void {
|
|
16
|
+
const pdHome = path.join(home, '.pd');
|
|
17
|
+
fs.mkdirSync(path.join(pdHome, 'releases', 'b'.repeat(64), 'plugin'), { recursive: true });
|
|
18
|
+
fs.mkdirSync(path.join(pdHome, 'bootstrap'), { recursive: true });
|
|
19
|
+
fs.mkdirSync(path.join(pdHome, 'logs'), { recursive: true });
|
|
20
|
+
fs.writeFileSync(path.join(pdHome, 'active.json'), JSON.stringify({
|
|
21
|
+
schemaVersion: 1,
|
|
22
|
+
generation: 4,
|
|
23
|
+
releaseId: 'b'.repeat(64),
|
|
24
|
+
releaseMetadataDigest: '2'.repeat(64),
|
|
25
|
+
previousReleaseId: 'a'.repeat(64),
|
|
26
|
+
transactionId: 'txn-9',
|
|
27
|
+
productVersion: '1.223.0',
|
|
28
|
+
}));
|
|
29
|
+
fs.writeFileSync(path.join(pdHome, 'bootstrap', 'bootstrap.json'), JSON.stringify({
|
|
30
|
+
bootstrapVersion: '1.1.0',
|
|
31
|
+
installedAt: '2026-08-25T00:00:00Z',
|
|
32
|
+
}));
|
|
33
|
+
fs.writeFileSync(path.join(pdHome, 'install.json'), JSON.stringify({ channel: 'candidate', autoCheck: true }));
|
|
34
|
+
fs.writeFileSync(path.join(pdHome, 'releases', 'b'.repeat(64), 'metadata.json'), JSON.stringify({
|
|
35
|
+
productVersion: '1.223.0',
|
|
36
|
+
releaseId: 'b'.repeat(64),
|
|
37
|
+
metadataDigest: '2'.repeat(64),
|
|
38
|
+
}));
|
|
39
|
+
fs.writeFileSync(path.join(pdHome, 'releases', 'b'.repeat(64), 'plugin', 'package.json'), JSON.stringify({ version: '1.76.1' }));
|
|
40
|
+
fs.writeFileSync(path.join(pdHome, 'logs', 'history.jsonl'), [
|
|
41
|
+
JSON.stringify({ at: '2026-08-24T00:00:00Z', kind: 'update', outcome: 'succeeded', transactionId: 'txn-8' }),
|
|
42
|
+
JSON.stringify({ at: '2026-08-25T00:00:00Z', kind: 'recovery', outcome: 'recovered', transactionId: 'txn-9' }),
|
|
43
|
+
].join('\n') + '\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
while (temporaryDirectories.length > 0) {
|
|
48
|
+
const directory = temporaryDirectories.pop();
|
|
49
|
+
if (directory) fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('canonical version report (SPEC 12 / 18-1, 18-10)', () => {
|
|
54
|
+
it('reports the canonical product identity from ~/.pd, not package.json', () => {
|
|
55
|
+
const home = tempHome();
|
|
56
|
+
writeDualSlotHome(home);
|
|
57
|
+
const report = buildVersionReport(home);
|
|
58
|
+
expect(report).toMatchObject({
|
|
59
|
+
productVersion: '1.223.0',
|
|
60
|
+
releaseId: 'b'.repeat(64),
|
|
61
|
+
bootstrapVersion: '1.1.0',
|
|
62
|
+
channel: 'candidate',
|
|
63
|
+
source: 'official-installer',
|
|
64
|
+
generation: 4,
|
|
65
|
+
health: 'healthy',
|
|
66
|
+
});
|
|
67
|
+
expect(report.components.plugin).toBe('1.76.1');
|
|
68
|
+
expect(report.lastTransaction).toEqual({ id: 'txn-9', kind: 'recovery', outcome: 'recovered' });
|
|
69
|
+
expect(formatShortVersion(report)).toBe(`Principles Disciple 1.223.0 (${'b'.repeat(12)})`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('classifies a legacy overlay installation with an installer next action', () => {
|
|
73
|
+
const home = tempHome();
|
|
74
|
+
// The current official installer creates ~/.pd/bin before the transactional
|
|
75
|
+
// dual-slot layout is activated. That support directory must not make a
|
|
76
|
+
// valid legacy overlay look like a corrupt partial dual-slot install.
|
|
77
|
+
fs.mkdirSync(path.join(home, '.pd', 'bin'), { recursive: true });
|
|
78
|
+
const overlay = path.join(home, '.openclaw', 'extensions', 'principles-disciple');
|
|
79
|
+
fs.mkdirSync(overlay, { recursive: true });
|
|
80
|
+
fs.writeFileSync(path.join(overlay, 'package.json'), JSON.stringify({ version: '1.218.0' }));
|
|
81
|
+
const report = buildVersionReport(home);
|
|
82
|
+
expect(report).toMatchObject({
|
|
83
|
+
productVersion: '1.218.0',
|
|
84
|
+
source: 'official-legacy-overlay',
|
|
85
|
+
health: 'degraded',
|
|
86
|
+
generation: 0,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('retains compatibility with the older nested legacy overlay manifest', () => {
|
|
91
|
+
const home = tempHome();
|
|
92
|
+
const overlay = path.join(home, '.openclaw', 'extensions', 'principles-disciple', 'plugin');
|
|
93
|
+
fs.mkdirSync(overlay, { recursive: true });
|
|
94
|
+
fs.writeFileSync(path.join(overlay, 'package.json'), JSON.stringify({ version: '1.202.0' }));
|
|
95
|
+
expect(buildVersionReport(home)).toMatchObject({
|
|
96
|
+
productVersion: '1.202.0', source: 'official-legacy-overlay', health: 'degraded',
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('refuses with a structured reason and next action when nothing is installed', () => {
|
|
101
|
+
const home = tempHome();
|
|
102
|
+
try {
|
|
103
|
+
buildVersionReport(home);
|
|
104
|
+
throw new Error('expected VersionReportError');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
expect(error).toBeInstanceOf(VersionReportError);
|
|
107
|
+
const refusal = error as VersionReportError;
|
|
108
|
+
expect(refusal.reason).toBe('not_installed');
|
|
109
|
+
expect(refusal.nextAction).toMatch(/official installer/i);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('reports degraded health when the active release directory is incomplete', () => {
|
|
114
|
+
const home = tempHome();
|
|
115
|
+
writeDualSlotHome(home);
|
|
116
|
+
fs.rmSync(path.join(home, '.pd', 'releases', 'b'.repeat(64), 'metadata.json'), { force: true });
|
|
117
|
+
const report = buildVersionReport(home);
|
|
118
|
+
expect(report.health).toBe('degraded');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('reports corrupt health when release metadata exists but disagrees with active.json', () => {
|
|
122
|
+
const home = tempHome();
|
|
123
|
+
writeDualSlotHome(home);
|
|
124
|
+
fs.writeFileSync(path.join(home, '.pd', 'releases', 'b'.repeat(64), 'metadata.json'), '{}');
|
|
125
|
+
const report = buildVersionReport(home);
|
|
126
|
+
expect(report.health).toBe('corrupt');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('refuses a malformed active record loudly instead of guessing', () => {
|
|
130
|
+
const home = tempHome();
|
|
131
|
+
fs.mkdirSync(path.join(home, '.pd'), { recursive: true });
|
|
132
|
+
fs.writeFileSync(path.join(home, '.pd', 'active.json'), JSON.stringify({ generation: 'four' }));
|
|
133
|
+
try {
|
|
134
|
+
buildVersionReport(home);
|
|
135
|
+
throw new Error('expected VersionReportError');
|
|
136
|
+
} catch (error) {
|
|
137
|
+
expect(error).toBeInstanceOf(VersionReportError);
|
|
138
|
+
expect((error as VersionReportError).reason).toBe('active_record_corrupt');
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
package/src/commands/console.ts
CHANGED
|
@@ -2,6 +2,13 @@ import * as path from 'path';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import { spawn, type ChildProcess } from 'child_process';
|
|
4
4
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
5
|
+
import {
|
|
6
|
+
getConsoleServerEntry,
|
|
7
|
+
getConsoleWebIndex,
|
|
8
|
+
getInstallLayoutPaths,
|
|
9
|
+
resolveInstallLayout,
|
|
10
|
+
type InstallLayoutMode,
|
|
11
|
+
} from '@principles/install-layout';
|
|
5
12
|
import {
|
|
6
13
|
planConsoleLaunch,
|
|
7
14
|
openBrowser,
|
|
@@ -36,11 +43,25 @@ interface ConsoleOptions {
|
|
|
36
43
|
json?: boolean;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
|
-
function getConsoleDir(): string | null {
|
|
46
|
+
function getConsoleDir(): { dir: string; mode: InstallLayoutMode } | null {
|
|
40
47
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
41
48
|
if (!homeDir) return null;
|
|
42
|
-
const
|
|
43
|
-
|
|
49
|
+
const paths = getInstallLayoutPaths(homeDir);
|
|
50
|
+
let manifest: unknown;
|
|
51
|
+
try {
|
|
52
|
+
manifest = JSON.parse(fs.readFileSync(paths.manifest, 'utf8')) as unknown;
|
|
53
|
+
} catch {
|
|
54
|
+
manifest = undefined;
|
|
55
|
+
}
|
|
56
|
+
const resolved = resolveInstallLayout({
|
|
57
|
+
homeDir,
|
|
58
|
+
manifest,
|
|
59
|
+
canonicalRuntimeExists: fs.existsSync(paths.runtimeDir),
|
|
60
|
+
legacyExtensionExists: fs.existsSync(paths.openClawExtensionDir),
|
|
61
|
+
});
|
|
62
|
+
if (resolved.mode === 'missing') return null;
|
|
63
|
+
const dir = resolved.mode === 'canonical' ? paths.consoleDir : path.join(paths.openClawExtensionDir, 'console');
|
|
64
|
+
return { dir, mode: resolved.mode };
|
|
44
65
|
}
|
|
45
66
|
|
|
46
67
|
export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
|
|
@@ -48,8 +69,8 @@ export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
|
|
|
48
69
|
? path.resolve(opts.workspace)
|
|
49
70
|
: resolveWorkspaceDir();
|
|
50
71
|
|
|
51
|
-
const
|
|
52
|
-
if (!
|
|
72
|
+
const consoleLocation = getConsoleDir();
|
|
73
|
+
if (!consoleLocation) {
|
|
53
74
|
const msg = 'pd-console is not installed. Run: npx create-principles-disciple to install.';
|
|
54
75
|
if (opts.json) {
|
|
55
76
|
console.log(JSON.stringify({ success: false, reason: msg, nextAction: 'npx create-principles-disciple' }));
|
|
@@ -60,7 +81,8 @@ export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
|
|
|
60
81
|
return;
|
|
61
82
|
}
|
|
62
83
|
|
|
63
|
-
const
|
|
84
|
+
const paths = getInstallLayoutPaths(process.env.HOME || process.env.USERPROFILE || '.');
|
|
85
|
+
const serverEntry = getConsoleServerEntry(paths, consoleLocation.mode);
|
|
64
86
|
if (!fs.existsSync(serverEntry)) {
|
|
65
87
|
const msg = `Console server entry not found at ${serverEntry}. Re-run installer.`;
|
|
66
88
|
if (opts.json) {
|
|
@@ -74,7 +96,7 @@ export async function handleConsole(opts: ConsoleOptions = {}): Promise<void> {
|
|
|
74
96
|
|
|
75
97
|
// EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
|
|
76
98
|
// Without dist/web/index.html the server returns 404 "Run npm run build:ui first".
|
|
77
|
-
const webIndex =
|
|
99
|
+
const webIndex = getConsoleWebIndex(paths, consoleLocation.mode);
|
|
78
100
|
if (!fs.existsSync(webIndex)) {
|
|
79
101
|
const msg = `Console web UI not found at ${webIndex}. The console bundle is corrupted. Re-run installer.`;
|
|
80
102
|
if (opts.json) {
|
|
@@ -274,8 +296,8 @@ export async function handleConsoleOpen(opts: ConsoleOpenOptions = {}): Promise<
|
|
|
274
296
|
}
|
|
275
297
|
|
|
276
298
|
// 3) Check that the console runtime is installed (ERR-040: fail loud if missing)
|
|
277
|
-
const
|
|
278
|
-
if (!
|
|
299
|
+
const consoleLocation = getConsoleDir();
|
|
300
|
+
if (!consoleLocation) {
|
|
279
301
|
const result: ConsoleLaunchResult = {
|
|
280
302
|
status: 'failed',
|
|
281
303
|
url: '',
|
|
@@ -296,7 +318,8 @@ export async function handleConsoleOpen(opts: ConsoleOpenOptions = {}): Promise<
|
|
|
296
318
|
process.exit(1);
|
|
297
319
|
return;
|
|
298
320
|
}
|
|
299
|
-
const
|
|
321
|
+
const paths = getInstallLayoutPaths(process.env.HOME || process.env.USERPROFILE || '.');
|
|
322
|
+
const serverEntry = getConsoleServerEntry(paths, consoleLocation.mode);
|
|
300
323
|
if (!fs.existsSync(serverEntry)) {
|
|
301
324
|
const result: ConsoleLaunchResult = {
|
|
302
325
|
status: 'failed',
|
|
@@ -322,7 +345,7 @@ export async function handleConsoleOpen(opts: ConsoleOpenOptions = {}): Promise<
|
|
|
322
345
|
// EP-06 regression guard (PR #1169): verify web UI bundle exists before launch.
|
|
323
346
|
// Without dist/web/index.html the server returns 404 "Run npm run build:ui first"
|
|
324
347
|
// — a fatal first-impression bug for new users.
|
|
325
|
-
const webIndex =
|
|
348
|
+
const webIndex = getConsoleWebIndex(paths, consoleLocation.mode);
|
|
326
349
|
if (!fs.existsSync(webIndex)) {
|
|
327
350
|
const result: ConsoleLaunchResult = {
|
|
328
351
|
status: 'failed',
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
resolveRuntimeConfig,
|
|
13
13
|
isRuntimeConfigError,
|
|
14
14
|
isFeatureEnabled,
|
|
15
|
+
isBuiltinPiAiProvider,
|
|
15
16
|
} from '@principles/core/runtime-v2';
|
|
16
17
|
import { resolveWorkspaceDir } from '../resolve-workspace.js';
|
|
17
18
|
import { loadPdConfig, computeFlagsFromLoadResult } from '../services/pd-config-loader.js';
|
|
@@ -141,19 +142,11 @@ export async function handlePainRecord(opts: RecordOptions): Promise<void> {
|
|
|
141
142
|
if (!config.model) missing.push('model');
|
|
142
143
|
if (!config.apiKeyEnv) missing.push('apiKeyEnv');
|
|
143
144
|
if (config.provider) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// @ts-ignore
|
|
150
|
-
const { getProviders } = await import('@mariozechner/pi-ai');
|
|
151
|
-
const knownProviders = getProviders() as readonly string[];
|
|
152
|
-
if (!knownProviders.includes(config.provider) && !config.baseUrl) {
|
|
153
|
-
missing.push('baseUrl');
|
|
154
|
-
}
|
|
155
|
-
} catch {
|
|
156
|
-
// pi-ai may not be available
|
|
145
|
+
// PRI-621 PR2 review: the pi-ai catalog lookup is a core capability —
|
|
146
|
+
// the CLI queries it through @principles/core and must not depend on
|
|
147
|
+
// pi-ai directly (EP-06).
|
|
148
|
+
if (!isBuiltinPiAiProvider(config.provider) && !config.baseUrl) {
|
|
149
|
+
missing.push('baseUrl');
|
|
157
150
|
}
|
|
158
151
|
}
|
|
159
152
|
|