create-principles-disciple 1.147.6 → 1.147.8
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/_release/product-identity.json +1 -1
- package/core/dist/host/host-installer.d.ts +15 -0
- package/core/dist/host/host-installer.d.ts.map +1 -1
- package/core/dist/host/host-installer.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js +2 -2
- package/core/dist/runtime-v2/feature-flags/feature-flag-lifecycle.js.map +1 -1
- package/core/dist/runtime-v2/internalization/artificer-run-correlation.d.ts +1 -1
- package/core/dist/runtime-v2/internalization/artificer-run-correlation.js +1 -1
- package/core/dist/runtime-v2/internalization/consumer-evidence-preflight.d.ts +1 -1
- package/core/dist/runtime-v2/internalization/evidence-readiness.js +1 -1
- package/core/dist/runtime-v2/internalization/owner-evidence-recovery.d.ts +1 -1
- package/core/dist/runtime-v2/pain-recall-capability.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-recall-capability.js +53 -2
- package/core/dist/runtime-v2/pain-recall-capability.js.map +1 -1
- package/core/dist/runtime-v2/pain-recall-read-model.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-recall-read-model.js +60 -5
- package/core/dist/runtime-v2/pain-recall-read-model.js.map +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.d.ts +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-domain.js +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.d.ts +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-proposal.js +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.d.ts +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-retrieval.js +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.d.ts.map +1 -1
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js +35 -0
- package/core/dist/runtime-v2/signal-collector/keyword-stage.js.map +1 -1
- package/core/dist/runtime-v2/trajectory-schema.d.ts +1 -1
- package/core/package.json +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -29
- package/dist/index.js.map +1 -1
- package/dist/install-summary.d.ts +38 -0
- package/dist/install-summary.d.ts.map +1 -0
- package/dist/install-summary.js +115 -0
- package/dist/install-summary.js.map +1 -0
- package/dist/installer.d.ts +36 -7
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +232 -54
- package/dist/installer.js.map +1 -1
- package/dist/installers/workbuddy-host-installer.d.ts +51 -17
- package/dist/installers/workbuddy-host-installer.d.ts.map +1 -1
- package/dist/installers/workbuddy-host-installer.js +510 -186
- package/dist/installers/workbuddy-host-installer.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/host-runtime/dist/governance-signal-admission.d.ts +2 -2
- package/host-runtime/dist/governance-signal-admission.js +2 -6
- package/host-runtime/package.json +1 -1
- package/package.json +3 -3
- package/pd-cli/dist/commands/health-mcp.d.ts +19 -0
- package/pd-cli/dist/commands/health-mcp.d.ts.map +1 -1
- package/pd-cli/dist/commands/health-mcp.js +226 -0
- package/pd-cli/dist/commands/health-mcp.js.map +1 -1
- package/pd-cli/dist/commands/health.d.ts.map +1 -1
- package/pd-cli/dist/commands/health.js +18 -1
- package/pd-cli/dist/commands/health.js.map +1 -1
- package/pd-cli/dist/commands/mcp-logs-export.d.ts +7 -0
- package/pd-cli/dist/commands/mcp-logs-export.d.ts.map +1 -0
- package/pd-cli/dist/commands/mcp-logs-export.js +267 -0
- package/pd-cli/dist/commands/mcp-logs-export.js.map +1 -0
- package/pd-cli/dist/commands/workbuddy-worker.d.ts.map +1 -1
- package/pd-cli/dist/commands/workbuddy-worker.js +2 -1
- package/pd-cli/dist/commands/workbuddy-worker.js.map +1 -1
- package/pd-cli/dist/index.js +35 -0
- package/pd-cli/dist/index.js.map +1 -1
- package/pd-cli/dist/services/zip-writer.d.ts +6 -0
- package/pd-cli/dist/services/zip-writer.d.ts.map +1 -0
- package/pd-cli/dist/services/zip-writer.js +97 -0
- package/pd-cli/dist/services/zip-writer.js.map +1 -0
- package/pd-cli/package.json +1 -1
- package/pd-mcp-server/dist/index.d.ts +1 -0
- package/pd-mcp-server/dist/index.js +167 -10
- package/pd-mcp-server/dist/log-file.d.ts +26 -0
- package/pd-mcp-server/dist/log-file.js +312 -0
- package/pd-mcp-server/dist/logging.d.ts +2 -1
- package/pd-mcp-server/dist/logging.js +25 -14
- package/pd-mcp-server/dist/server.js +78 -24
- package/pd-mcp-server/dist/version.d.ts +1 -1
- package/pd-mcp-server/dist/version.js +1 -1
- package/pd-mcp-server/dist/workspace.d.ts +3 -0
- package/pd-mcp-server/dist/workspace.js +5 -0
- package/pd-mcp-server/package.json +1 -1
- package/plugin/dist/bundle.js +274 -273
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/mcp/launcher.cjs +561 -0
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/dist/installer.js +232 -54
- package/release-manager/dist/installers/workbuddy-host-installer.js +510 -186
- package/release-manager/package.json +1 -1
- package/workbuddy-adapter/dist/pd-hook.js +3 -2
- package/workbuddy-adapter/package.json +1 -1
- package/workbuddy-adapter/plugin/.codebuddy-plugin/plugin.json +1 -1
- package/workbuddy-adapter/plugin/mcp/launcher.cjs +215 -1
- package/workbuddy-adapter/plugin/skills/pd-pain-signal/SKILL.md +4 -0
|
@@ -1553,6 +1553,11 @@ export async function checkBuiltPlugin(pluginDir) {
|
|
|
1553
1553
|
}
|
|
1554
1554
|
}
|
|
1555
1555
|
}
|
|
1556
|
+
function filesystemErrorCode(error) {
|
|
1557
|
+
if (typeof error !== 'object' || error === null || !Object.hasOwn(error, 'code'))
|
|
1558
|
+
return undefined;
|
|
1559
|
+
return error.code;
|
|
1560
|
+
}
|
|
1556
1561
|
/**
|
|
1557
1562
|
* PRI-1102: a target that exists as a DANGLING link (an interrupted install
|
|
1558
1563
|
* left a reparse point whose target is gone) is not a directory the installer
|
|
@@ -1574,14 +1579,23 @@ export async function preparePluginInstallTarget(targetDir) {
|
|
|
1574
1579
|
throw new Error(`Cannot inspect plugin install target ${targetDir}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1575
1580
|
}
|
|
1576
1581
|
if (stats !== undefined && stats.isSymbolicLink()) {
|
|
1577
|
-
let
|
|
1582
|
+
let targetAbsent = false;
|
|
1578
1583
|
try {
|
|
1579
1584
|
realpathSync(targetDir);
|
|
1580
1585
|
}
|
|
1581
|
-
catch {
|
|
1582
|
-
|
|
1586
|
+
catch (error) {
|
|
1587
|
+
const code = filesystemErrorCode(error);
|
|
1588
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
1589
|
+
// EACCES/EPERM/... means the link target may still be a live install
|
|
1590
|
+
// this process cannot read. Removing the link would destroy it, so
|
|
1591
|
+
// fail closed and let the caller report the refusal.
|
|
1592
|
+
throw new Error(`Cannot resolve plugin install target ${targetDir}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1593
|
+
}
|
|
1594
|
+
// Confirmed absent target (ENOENT/ENOTDIR) — the link itself is the
|
|
1595
|
+
// residue of an interrupted install.
|
|
1596
|
+
targetAbsent = true;
|
|
1583
1597
|
}
|
|
1584
|
-
if (
|
|
1598
|
+
if (targetAbsent) {
|
|
1585
1599
|
// fs-extra's remove unlinks the reparse point instead of traversing it.
|
|
1586
1600
|
await fse.remove(targetDir);
|
|
1587
1601
|
logger.warn(`Removed dangling link at ${targetDir} (target missing) — rebuilding the plugin install directory.`);
|
|
@@ -1589,35 +1603,105 @@ export async function preparePluginInstallTarget(targetDir) {
|
|
|
1589
1603
|
}
|
|
1590
1604
|
await fse.ensureDir(targetDir);
|
|
1591
1605
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1606
|
+
function restoredTreeRequirements() {
|
|
1607
|
+
return [
|
|
1608
|
+
// The runtime root itself (type + link resolution only).
|
|
1609
|
+
{ dir: getPdRuntimeDir(), entries: [], openClawOnly: false },
|
|
1610
|
+
{
|
|
1611
|
+
dir: getInstalledConsoleDir(),
|
|
1612
|
+
entries: [path.join('dist', 'server.js'), path.join('dist', 'web', 'index.html')],
|
|
1613
|
+
openClawOnly: false,
|
|
1614
|
+
},
|
|
1615
|
+
{ dir: getInstalledPdCliDir(), entries: [path.join('dist', 'index.js')], openClawOnly: false },
|
|
1616
|
+
{ dir: getInstalledPluginDir(), entries: [path.join('dist', 'bundle.js'), 'openclaw.plugin.json'], openClawOnly: false },
|
|
1617
|
+
{
|
|
1618
|
+
dir: getPluginExtDir(),
|
|
1619
|
+
entries: [path.join('dist', 'bundle.js'), 'openclaw.plugin.json', 'package.json'],
|
|
1620
|
+
openClawOnly: true,
|
|
1621
|
+
},
|
|
1622
|
+
];
|
|
1623
|
+
}
|
|
1624
|
+
function describeRestoredDirGap(dir, gaps) {
|
|
1625
|
+
let stats;
|
|
1626
|
+
try {
|
|
1627
|
+
stats = lstatSync(dir, { throwIfNoEntry: false });
|
|
1628
|
+
}
|
|
1629
|
+
catch (error) {
|
|
1630
|
+
gaps.push(`${dir} (unreadable: ${error instanceof Error ? error.message : String(error)})`);
|
|
1631
|
+
return false;
|
|
1632
|
+
}
|
|
1633
|
+
if (stats === undefined) {
|
|
1634
|
+
gaps.push(`${dir} (missing)`);
|
|
1635
|
+
return false;
|
|
1636
|
+
}
|
|
1637
|
+
if (stats.isSymbolicLink()) {
|
|
1603
1638
|
try {
|
|
1604
|
-
|
|
1639
|
+
realpathSync(dir);
|
|
1605
1640
|
}
|
|
1606
1641
|
catch (error) {
|
|
1607
|
-
|
|
1608
|
-
|
|
1642
|
+
const code = filesystemErrorCode(error);
|
|
1643
|
+
gaps.push(`${dir} (${code === 'ENOENT' || code === 'ENOTDIR' ? 'dangling link' : 'unresolvable link'})`);
|
|
1644
|
+
return false;
|
|
1609
1645
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1646
|
+
}
|
|
1647
|
+
else if (!stats.isDirectory()) {
|
|
1648
|
+
gaps.push(`${dir} (not a directory)`);
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
return true;
|
|
1652
|
+
}
|
|
1653
|
+
function describeRestoredEntryGap(entry, gaps) {
|
|
1654
|
+
let stats;
|
|
1655
|
+
try {
|
|
1656
|
+
stats = lstatSync(entry, { throwIfNoEntry: false });
|
|
1657
|
+
}
|
|
1658
|
+
catch (error) {
|
|
1659
|
+
gaps.push(`${entry} (unreadable: ${error instanceof Error ? error.message : String(error)})`);
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (stats === undefined) {
|
|
1663
|
+
gaps.push(`${entry} (missing)`);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (stats.isSymbolicLink()) {
|
|
1667
|
+
try {
|
|
1668
|
+
realpathSync(entry);
|
|
1613
1669
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1670
|
+
catch (error) {
|
|
1671
|
+
const code = filesystemErrorCode(error);
|
|
1672
|
+
gaps.push(`${entry} (${code === 'ENOENT' || code === 'ENOTDIR' ? 'dangling link' : 'unresolvable link'})`);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
// realpath only proves the target EXISTS. The entry is consumed as a file
|
|
1676
|
+
// at runtime, so a link to a directory is just as unusable as a missing
|
|
1677
|
+
// file path. stat follows the link and reports the target type.
|
|
1678
|
+
let targetStats;
|
|
1679
|
+
try {
|
|
1680
|
+
targetStats = statSync(entry);
|
|
1681
|
+
}
|
|
1682
|
+
catch (error) {
|
|
1683
|
+
const code = filesystemErrorCode(error);
|
|
1684
|
+
gaps.push(`${entry} (${code === 'ENOENT' || code === 'ENOTDIR' ? 'dangling link' : 'unresolvable link'})`);
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
if (!targetStats.isFile()) {
|
|
1688
|
+
gaps.push(`${entry} (not a file)`);
|
|
1689
|
+
}
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (!stats.isFile()) {
|
|
1693
|
+
gaps.push(`${entry} (not a file)`);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
export function describeRestoredStateGaps() {
|
|
1697
|
+
const gaps = [];
|
|
1698
|
+
for (const requirement of restoredTreeRequirements()) {
|
|
1699
|
+
if (requirement.openClawOnly && !installsOpenClaw(activeHostTarget))
|
|
1700
|
+
continue;
|
|
1701
|
+
if (!describeRestoredDirGap(requirement.dir, gaps))
|
|
1702
|
+
continue;
|
|
1703
|
+
for (const entry of requirement.entries) {
|
|
1704
|
+
describeRestoredEntryGap(path.join(requirement.dir, entry), gaps);
|
|
1621
1705
|
}
|
|
1622
1706
|
}
|
|
1623
1707
|
return gaps;
|
|
@@ -3360,40 +3444,124 @@ function assertUsableWorkspaceDir(workspaceDir) {
|
|
|
3360
3444
|
'Re-run with the path quoted, so the shell cannot strip its separators:\n' +
|
|
3361
3445
|
' --workspace "D:\\.openclaw\\workspace"');
|
|
3362
3446
|
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3447
|
+
/**
|
|
3448
|
+
* Where the OpenClaw plugin (and therefore the MCP launcher) reads the
|
|
3449
|
+
* Owner-declared governance workspace. The gateway process, hook processes and
|
|
3450
|
+
* MCP children all run with different environments, and the MCP child has its
|
|
3451
|
+
* environment sanitized by the host (PD_WORKSPACE_DIR / HOME are stripped), so
|
|
3452
|
+
* this FILE is the only signal every one of them can see.
|
|
3453
|
+
*/
|
|
3454
|
+
const OPENCLAW_PD_CONFIG_FILENAME = 'principles-disciple.json';
|
|
3455
|
+
function describeError(error) {
|
|
3456
|
+
return error instanceof Error ? error.message : String(error);
|
|
3457
|
+
}
|
|
3458
|
+
/** Same-directory identity for a workspace pointer (Windows is case-insensitive). */
|
|
3459
|
+
function sameWorkspaceTarget(left, right) {
|
|
3460
|
+
const normalize = (value) => {
|
|
3461
|
+
const resolved = path.resolve(value);
|
|
3462
|
+
const trimmed = resolved.length > 1 ? resolved.replace(/[\\/]+$/, '') : resolved;
|
|
3463
|
+
return process.platform === 'win32' ? trimmed.toLowerCase() : trimmed;
|
|
3464
|
+
};
|
|
3465
|
+
return normalize(left) === normalize(right);
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Read the existing declaration as UNTRUSTED input (it is a plain file on disk)
|
|
3469
|
+
* and classify it. Never overwrites and never silently repairs anything.
|
|
3470
|
+
*/
|
|
3471
|
+
export function inspectOpenClawWorkspaceDeclaration(workspaceDir) {
|
|
3472
|
+
const configPath = path.join(getOpenClawDir(), OPENCLAW_PD_CONFIG_FILENAME);
|
|
3473
|
+
if (!existsSync(configPath))
|
|
3474
|
+
return { configPath, state: 'absent' };
|
|
3475
|
+
let raw;
|
|
3476
|
+
try {
|
|
3477
|
+
raw = readFileSync(configPath, 'utf-8');
|
|
3478
|
+
}
|
|
3479
|
+
catch (e) {
|
|
3480
|
+
return { configPath, state: 'unreadable', reason: describeError(e) };
|
|
3481
|
+
}
|
|
3482
|
+
let parsed;
|
|
3483
|
+
try {
|
|
3484
|
+
parsed = JSON.parse(raw);
|
|
3485
|
+
}
|
|
3486
|
+
catch (e) {
|
|
3487
|
+
return { configPath, state: 'invalid', reason: `it is not valid JSON (${describeError(e)})` };
|
|
3488
|
+
}
|
|
3489
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
3490
|
+
return { configPath, state: 'invalid', reason: 'the root value is not a JSON object' };
|
|
3491
|
+
}
|
|
3492
|
+
const declaredRaw = Object.hasOwn(parsed, 'workspace') ? Reflect.get(parsed, 'workspace') : undefined;
|
|
3493
|
+
if (typeof declaredRaw !== 'string' || declaredRaw.trim().length === 0) {
|
|
3494
|
+
return { configPath, state: 'invalid', reason: 'it has no non-empty string "workspace" field' };
|
|
3495
|
+
}
|
|
3496
|
+
const declared = declaredRaw.trim();
|
|
3497
|
+
if (!isUsableWorkspacePointer(declared)) {
|
|
3498
|
+
return {
|
|
3499
|
+
configPath,
|
|
3500
|
+
state: 'invalid',
|
|
3501
|
+
declaredWorkspace: declared,
|
|
3502
|
+
reason: 'the "workspace" value does not name one fixed absolute directory',
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
if (!sameWorkspaceTarget(declared, workspaceDir)) {
|
|
3506
|
+
return { configPath, state: 'conflict', declaredWorkspace: declared };
|
|
3507
|
+
}
|
|
3508
|
+
return { configPath, state: 'compatible', declaredWorkspace: declared };
|
|
3509
|
+
}
|
|
3510
|
+
function throwOnIncompatibleDeclaration(inspection, workspaceDir) {
|
|
3511
|
+
if (inspection.state === 'absent' || inspection.state === 'compatible')
|
|
3512
|
+
return;
|
|
3513
|
+
if (inspection.state === 'conflict') {
|
|
3514
|
+
throw new Error('An existing OpenClaw PD config declares a DIFFERENT workspace; it was left unchanged.\n' +
|
|
3515
|
+
` Config: ${inspection.configPath}\n` +
|
|
3516
|
+
` Declared: ${JSON.stringify(inspection.declaredWorkspace)}\n` +
|
|
3517
|
+
` Requested: ${JSON.stringify(path.resolve(workspaceDir))}\n` +
|
|
3518
|
+
'The pd-recall MCP server can only serve the workspace declared in this file, so the installer\n' +
|
|
3519
|
+
'never overwrites it. Otherwise the host would report a successful install while recall served\n' +
|
|
3520
|
+
'another workspace.\n' +
|
|
3521
|
+
` To keep the existing workspace, re-run with --workspace ${JSON.stringify(inspection.declaredWorkspace)}.\n` +
|
|
3522
|
+
` To switch deliberately, back up ${inspection.configPath}, remove it, then re-run for the new workspace.`);
|
|
3523
|
+
}
|
|
3524
|
+
throw new Error(`The existing OpenClaw PD config cannot be verified and was left unchanged (${inspection.reason}).\n` +
|
|
3525
|
+
` Config: ${inspection.configPath}\n` +
|
|
3526
|
+
'Expected a JSON object with an absolute "workspace" string.\n' +
|
|
3527
|
+
' Fix it manually, or back it up and remove it, then re-run the installer.');
|
|
3528
|
+
}
|
|
3529
|
+
/**
|
|
3530
|
+
* Read-only preflight: refuse BEFORE any runtime mutation when the existing
|
|
3531
|
+
* declaration points somewhere else (or cannot be trusted). This is the check
|
|
3532
|
+
* that keeps an install from "succeeding" while pd-recall serves a different
|
|
3533
|
+
* workspace.
|
|
3534
|
+
*/
|
|
3535
|
+
export function assertOpenClawWorkspaceDeclarationCompatible(workspaceDir) {
|
|
3536
|
+
throwOnIncompatibleDeclaration(inspectOpenClawWorkspaceDeclaration(workspaceDir), workspaceDir);
|
|
3537
|
+
}
|
|
3538
|
+
/**
|
|
3539
|
+
* Ensure the OpenClaw plugin can locate the governance workspace through the
|
|
3540
|
+
* file declaration. Idempotent: a compatible existing declaration is left
|
|
3541
|
+
* byte-identical (no rewrite, no installedAt churn). A conflicting/invalid one
|
|
3542
|
+
* fails loud and is never overwritten.
|
|
3543
|
+
*/
|
|
3544
|
+
export async function ensureOpenClawWorkspaceDeclaration(workspaceDir, channels) {
|
|
3366
3545
|
// Defense in depth: the same refusal the install entry makes, kept here
|
|
3367
3546
|
// because this is the only writer of the pointer the plugin reads on every
|
|
3368
3547
|
// hook, and other callers reach it without passing through install().
|
|
3369
3548
|
assertUsableWorkspaceDir(workspaceDir);
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
if (
|
|
3373
|
-
|
|
3374
|
-
const existing = JSON.parse(existingRaw);
|
|
3375
|
-
if (existing !== null && typeof existing === 'object' && !Array.isArray(existing)) {
|
|
3376
|
-
const existingObj = existing;
|
|
3377
|
-
if (Object.hasOwn(existingObj, 'channels') && Array.isArray(existingObj.channels)) {
|
|
3378
|
-
existingChannels = existingObj.channels.filter((c) => typeof c === 'string');
|
|
3379
|
-
}
|
|
3380
|
-
if (Object.hasOwn(existingObj, 'features') && Array.isArray(existingObj.features)) {
|
|
3381
|
-
existingFeatures = existingObj.features.filter((f) => typeof f === 'string');
|
|
3382
|
-
}
|
|
3383
|
-
}
|
|
3549
|
+
const inspection = inspectOpenClawWorkspaceDeclaration(workspaceDir);
|
|
3550
|
+
throwOnIncompatibleDeclaration(inspection, workspaceDir);
|
|
3551
|
+
if (inspection.state === 'compatible') {
|
|
3552
|
+
return { configPath: inspection.configPath, action: 'unchanged' };
|
|
3384
3553
|
}
|
|
3554
|
+
const resolvedWorkspace = path.resolve(workspaceDir);
|
|
3385
3555
|
const config = {
|
|
3386
|
-
workspace:
|
|
3387
|
-
state: path.join(
|
|
3388
|
-
channels
|
|
3556
|
+
workspace: resolvedWorkspace,
|
|
3557
|
+
state: path.join(resolvedWorkspace, '.state'),
|
|
3558
|
+
channels,
|
|
3389
3559
|
installedAt: new Date().toISOString(),
|
|
3390
3560
|
mvpFirst: true,
|
|
3391
3561
|
};
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
}
|
|
3395
|
-
await fse.ensureDir(configDir);
|
|
3396
|
-
await fse.writeJson(configPath, config, { spaces: 2 });
|
|
3562
|
+
await fse.ensureDir(path.dirname(inspection.configPath));
|
|
3563
|
+
await fse.writeJson(inspection.configPath, config, { spaces: 2 });
|
|
3564
|
+
return { configPath: inspection.configPath, action: 'created' };
|
|
3397
3565
|
}
|
|
3398
3566
|
/**
|
|
3399
3567
|
* ADR-0020 §2.3: Run host installers for the selected HostTarget.
|
|
@@ -3788,6 +3956,14 @@ transaction) {
|
|
|
3788
3956
|
// (permissions / invalid path) flows into the unified failure result
|
|
3789
3957
|
// below instead of rejecting the install() promise (P1 review finding).
|
|
3790
3958
|
await fse.ensureDir(path.resolve(options.workspaceDir));
|
|
3959
|
+
// OpenClaw only: the MCP launcher re-derives the governance workspace from
|
|
3960
|
+
// this PD-owned file, because the host strips PD_WORKSPACE_DIR / HOME from
|
|
3961
|
+
// MCP children. An existing declaration for a DIFFERENT workspace must
|
|
3962
|
+
// refuse the install BEFORE any runtime mutation — otherwise the install
|
|
3963
|
+
// would report success while pd-recall served another workspace.
|
|
3964
|
+
if (installsOpenClaw(options.host)) {
|
|
3965
|
+
assertOpenClawWorkspaceDeclarationCompatible(options.workspaceDir);
|
|
3966
|
+
}
|
|
3791
3967
|
if (spinner)
|
|
3792
3968
|
updateProgress(spinner, stepIndex, 'Checking built plugin...');
|
|
3793
3969
|
await checkBuiltPlugin(pluginDir);
|
|
@@ -4032,7 +4208,9 @@ transaction) {
|
|
|
4032
4208
|
stepIndex++;
|
|
4033
4209
|
if (spinner)
|
|
4034
4210
|
updateProgress(spinner, stepIndex, 'Creating config...');
|
|
4035
|
-
|
|
4211
|
+
if (installsOpenClaw(options.host)) {
|
|
4212
|
+
await ensureOpenClawWorkspaceDeclaration(options.workspaceDir, options.channels);
|
|
4213
|
+
}
|
|
4036
4214
|
stepIndex++;
|
|
4037
4215
|
// PRI-686 Fix C: after pinning PD canonical, check OpenClaw's resolved
|
|
4038
4216
|
// main-agent workspace against it. Read-only; a divergence means PD
|