@getmarrow/install 0.1.55 → 0.1.56
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/README.md +13 -7
- package/package.json +1 -1
- package/src/installer.js +186 -33
package/README.md
CHANGED
|
@@ -75,17 +75,19 @@ Tool visibility is not authorization. Every visible call still reaches Marrow's
|
|
|
75
75
|
Marrow's hosted API, website, and dashboard update automatically; local SDK dependencies, generated runtime files, MCP hooks/configuration, and pinned package versions do not silently rewrite themselves. Keeping them current delivers new client-side features, compatibility improvements, and any published security fixes. Supported clients report their package version during authenticated status/runtime activity, and Marrow returns a `client_update` notice with the exact action when the version is behind or unknown.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
|
-
|
|
79
|
-
npx -y @getmarrow/install@latest
|
|
78
|
+
# When doctor reports stale or mixed MCP configuration
|
|
79
|
+
npx -y @getmarrow/install@latest update
|
|
80
|
+
|
|
81
|
+
# Restart the detected owning harnesses once, then verify
|
|
82
|
+
npx -y @getmarrow/install@latest doctor --self-test
|
|
80
83
|
|
|
81
84
|
# Measured API read health and local backlog
|
|
82
85
|
npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
|
|
83
|
-
|
|
84
|
-
# Use only when doctor reports drift
|
|
85
|
-
npx -y @getmarrow/install@latest --repair
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
`
|
|
88
|
+
`update` resolves official npm metadata once, selects one exact verified MCP target, and synchronizes every Marrow-managed surface in the detected owning workspace while retaining unrelated user hooks and configuration. Restart the detected owning harnesses once after it completes; running processes do not change before that restart. Run the doctor verification once after restart. Do not run separate `marrow-mcp setup` and restart cycles for the same detected workspace.
|
|
89
|
+
|
|
90
|
+
`activate` remains available for initial activation. After explicit activation, the local controller can restore drifted Marrow-managed hooks and configuration. Package upgrades, owner policy, credentials, explicitly disabled hooks, and unrelated local files remain explicit and subject to the operator's normal change policy.
|
|
89
91
|
|
|
90
92
|
## Automatic Controller
|
|
91
93
|
|
|
@@ -99,7 +101,11 @@ npx @getmarrow/install controller stop
|
|
|
99
101
|
|
|
100
102
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still writes supported configuration and verifies one server-side install self-test without certifying that hooks continuously ran; run `npx @getmarrow/install sidecar` under an owner-managed service and pass `--no-controller`. The controller does not silently upgrade packages, change governance policy, rotate credentials, or modify unrelated project configuration.
|
|
101
103
|
|
|
102
|
-
## What's New in v0.1.
|
|
104
|
+
## What's New in v0.1.56
|
|
105
|
+
|
|
106
|
+
v0.1.56 pins the officially verified MCP `3.9.80` release from source `ee1eda3f201965a6530256accbdf175660dd8ad6` with registry integrity `sha512-uou3X18pESV39EMmddDrYN7yd6MrZzosrnQ1eRtvK5j3yuBLAlupj5kQVrrKEWAL6xwuLebXq2isivK2O/2WrA==`; SDK remains `3.7.62`. Doctor now directs stale, mixed, or version-unknown MCP installations through one `npx -y @getmarrow/install@latest update`, followed by one owning-harness restart and one `doctor --self-test` verification. The update resolves one official target and applies it consistently across detected Marrow-managed instructions, MCP launch configuration, and supported native hooks without claiming that already-running processes changed.
|
|
107
|
+
|
|
108
|
+
## Previous: v0.1.55
|
|
103
109
|
|
|
104
110
|
v0.1.55 pins sealed MCP candidate `3.9.79` from source `11f00049043d0aba90704ecbf69f32d2278a4573` for ordinary setup, generated launch configuration, and native hooks. Doctor, update, and repair retain exact-version resolution and never propagate a newer local version unless official registry metadata verifies it; offline operation preserves unverified-ahead owner surfaces and uses the sealed candidate for new managed targets.
|
|
105
111
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -10,9 +10,9 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
10
10
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
11
11
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
12
12
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
13
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
14
|
-
const MCP_ADAPTER_SOURCE_SHA = '
|
|
15
|
-
const MCP_ADAPTER_INTEGRITY = 'sha512-
|
|
13
|
+
const MCP_ADAPTER_VERSION = '3.9.80';
|
|
14
|
+
const MCP_ADAPTER_SOURCE_SHA = 'ee1eda3f201965a6530256accbdf175660dd8ad6';
|
|
15
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-uou3X18pESV39EMmddDrYN7yd6MrZzosrnQ1eRtvK5j3yuBLAlupj5kQVrrKEWAL6xwuLebXq2isivK2O/2WrA==';
|
|
16
16
|
const SDK_ADAPTER_VERSION = '3.7.62';
|
|
17
17
|
const SDK_ADAPTER_INTEGRITY = 'sha512-n1i6Be09TpAQ9BPNRKY7aCvA2iSUPpJfw8djw2MELwpNbBCtKiZ29Jji77BK/6EFLUpSIcTW/Gmdf/ccf0JRYQ==';
|
|
18
18
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
@@ -33,6 +33,9 @@ const ADAPTER_PROVENANCE = Object.freeze({
|
|
|
33
33
|
});
|
|
34
34
|
const MCP_REGISTRY_LATEST_URL = 'https://registry.npmjs.org/%40getmarrow%2Fmcp/latest';
|
|
35
35
|
const MCP_REGISTRY_VERIFICATION_COMMAND = 'npm view @getmarrow/mcp@latest name version dist.integrity dist.tarball --json --registry=https://registry.npmjs.org';
|
|
36
|
+
const INSTALLER_UPDATE_COMMAND = 'npx -y @getmarrow/install@latest update';
|
|
37
|
+
const INSTALLER_DOCTOR_COMMAND = 'npx -y @getmarrow/install@latest doctor --self-test';
|
|
38
|
+
const INSTALLER_RESTART_INSTRUCTION = 'After the update completes, restart the detected owning harnesses once. Running Marrow MCP processes remain unchanged until that restart.';
|
|
36
39
|
const MCP_STABLE_VERSION_RE = /^(\d{1,6})\.(\d{1,6})\.(\d{1,9})$/;
|
|
37
40
|
const SHA512_INTEGRITY_RE = /^sha512-[A-Za-z0-9+/]+={0,2}$/;
|
|
38
41
|
const VERIFIED_MCP_EXECUTABLE_TARGET = Symbol('verified_mcp_executable_target');
|
|
@@ -79,10 +82,19 @@ function mcpVersionsInText(value) {
|
|
|
79
82
|
.map((match) => match[1]);
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
function marrowManagedBlockInText(value) {
|
|
86
|
+
const content = String(value || '');
|
|
87
|
+
const start = content.indexOf(MARROW_BLOCK_START);
|
|
88
|
+
if (start < 0) return '';
|
|
89
|
+
const end = content.indexOf(MARROW_BLOCK_END, start + MARROW_BLOCK_START.length);
|
|
90
|
+
if (end < 0) return '';
|
|
91
|
+
return content.slice(start, end + MARROW_BLOCK_END.length);
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
function unverifiedAheadMcpVersions(versions, targetVersion = MCP_ADAPTER_VERSION) {
|
|
83
|
-
if (!
|
|
95
|
+
if (!parsedStableMcpVersion(targetVersion)) return [];
|
|
84
96
|
return [...new Set((Array.isArray(versions) ? versions : [])
|
|
85
|
-
.filter((version) =>
|
|
97
|
+
.filter((version) => parsedStableMcpVersion(version)
|
|
86
98
|
&& compareMcpVersions(version, targetVersion) > 0))].sort((left, right) => compareMcpVersions(left, right));
|
|
87
99
|
}
|
|
88
100
|
|
|
@@ -554,7 +566,6 @@ function inspectMcpProcesses(options = {}) {
|
|
|
554
566
|
const aheadUnverified = aheadUnverifiedVersions.length > 0;
|
|
555
567
|
const needsRepair = stale || mixedVersions || unknownVersionProcesses > 0 || aheadUnverified;
|
|
556
568
|
const automaticRepairSuppressed = aheadUnverified;
|
|
557
|
-
const repairCommand = `npx -y --package=@getmarrow/mcp@${expectedVersion} marrow-mcp setup`;
|
|
558
569
|
return {
|
|
559
570
|
available: process.platform === 'linux' || Array.isArray(options.commands),
|
|
560
571
|
expected_version: expectedVersion,
|
|
@@ -570,21 +581,30 @@ function inspectMcpProcesses(options = {}) {
|
|
|
570
581
|
registry_verification_required: aheadUnverified,
|
|
571
582
|
exact_fix: automaticRepairSuppressed
|
|
572
583
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
573
|
-
: needsRepair ?
|
|
584
|
+
: needsRepair ? INSTALLER_UPDATE_COMMAND : null,
|
|
574
585
|
restart_required: needsRepair && !automaticRepairSuppressed,
|
|
575
586
|
restart_instruction: needsRepair && !automaticRepairSuppressed
|
|
576
|
-
?
|
|
587
|
+
? INSTALLER_RESTART_INSTRUCTION
|
|
577
588
|
: null,
|
|
578
|
-
verification_command: needsRepair
|
|
589
|
+
verification_command: needsRepair && !automaticRepairSuppressed ? INSTALLER_DOCTOR_COMMAND : null,
|
|
579
590
|
};
|
|
580
591
|
}
|
|
581
592
|
|
|
582
593
|
function inspectMcpConfigurations(detection, options = {}) {
|
|
583
594
|
const home = options.home || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
584
595
|
const configuredPaths = Array.isArray(options.paths) ? options.paths : [
|
|
596
|
+
detection?.paths?.agentsMd,
|
|
597
|
+
detection?.paths?.mcpJson,
|
|
585
598
|
detection?.paths?.claudeSettings,
|
|
599
|
+
detection?.paths?.codexHooks,
|
|
600
|
+
detection?.paths?.cursorHooks,
|
|
586
601
|
detection?.paths?.cursorMcp,
|
|
587
|
-
detection?.paths?.
|
|
602
|
+
detection?.paths?.clinePreToolUseHook,
|
|
603
|
+
detection?.paths?.clinePostToolUseHook,
|
|
604
|
+
detection?.paths?.clineTaskCancelHook,
|
|
605
|
+
detection?.paths?.windsurfHooks,
|
|
606
|
+
detection?.paths?.geminiSettings,
|
|
607
|
+
detection?.paths?.grokHooks,
|
|
588
608
|
path.join(home, '.claude', 'settings.json'),
|
|
589
609
|
path.join(home, '.claude.json'),
|
|
590
610
|
path.join(home, '.cursor', 'mcp.json'),
|
|
@@ -601,9 +621,11 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
601
621
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 2 * 1024 * 1024) continue;
|
|
602
622
|
filesChecked += 1;
|
|
603
623
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
624
|
+
const inspected = path.basename(filePath) === 'AGENTS.md'
|
|
625
|
+
? marrowManagedBlockInText(raw)
|
|
626
|
+
: raw;
|
|
627
|
+
const specs = mcpVersionsInText(inspected);
|
|
628
|
+
if (/@getmarrow\/mcp(?:@|["'\s])/.test(inspected)) {
|
|
607
629
|
configurationsFound += 1;
|
|
608
630
|
if (specs.length === 0) unknownVersionConfigurations += 1;
|
|
609
631
|
}
|
|
@@ -639,8 +661,10 @@ function inspectMcpConfigurations(detection, options = {}) {
|
|
|
639
661
|
? null
|
|
640
662
|
: aheadUnverified
|
|
641
663
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
642
|
-
:
|
|
643
|
-
|
|
664
|
+
: INSTALLER_UPDATE_COMMAND,
|
|
665
|
+
restart_required: !healthy && !aheadUnverified,
|
|
666
|
+
restart_instruction: !healthy && !aheadUnverified ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
667
|
+
verification_command: !healthy && !aheadUnverified ? INSTALLER_DOCTOR_COMMAND : null,
|
|
644
668
|
};
|
|
645
669
|
}
|
|
646
670
|
|
|
@@ -1691,6 +1715,75 @@ function grokNativeHookFingerprint(settings) {
|
|
|
1691
1715
|
})).digest('hex');
|
|
1692
1716
|
}
|
|
1693
1717
|
|
|
1718
|
+
function grokMarrowHookSubcommand(command) {
|
|
1719
|
+
const direct = marrowHookSubcommand(command);
|
|
1720
|
+
if (direct) return direct;
|
|
1721
|
+
if (typeof command !== 'string') return null;
|
|
1722
|
+
const guarded = command.match(
|
|
1723
|
+
/@getmarrow\/mcp@[^\s"',]+["'],["']marrow-mcp["'],["']grok-(pre-action-hook)["']/,
|
|
1724
|
+
);
|
|
1725
|
+
return guarded?.[1] || null;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function reconcileGrokHook(settings, eventName, subcommand, command, matcher, timeout) {
|
|
1729
|
+
const original = Array.isArray(settings?.hooks?.[eventName]) ? settings.hooks[eventName] : [];
|
|
1730
|
+
const retained = [];
|
|
1731
|
+
let preferredHandler = null;
|
|
1732
|
+
for (const entry of original) {
|
|
1733
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry) || !Array.isArray(entry.hooks)) {
|
|
1734
|
+
retained.push(entry);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
const remaining = [];
|
|
1738
|
+
for (const handler of entry.hooks) {
|
|
1739
|
+
const detected = handler && typeof handler === 'object' && !Array.isArray(handler)
|
|
1740
|
+
&& handler.type === 'command' ? grokMarrowHookSubcommand(handler.command) : null;
|
|
1741
|
+
if (detected) {
|
|
1742
|
+
if (detected === subcommand && !preferredHandler) preferredHandler = handler;
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
remaining.push(handler);
|
|
1746
|
+
}
|
|
1747
|
+
if (remaining.length > 0) retained.push({ ...entry, hooks: remaining });
|
|
1748
|
+
}
|
|
1749
|
+
if (!command) return retained;
|
|
1750
|
+
const canonical = {
|
|
1751
|
+
...(matcher === undefined ? {} : { matcher }),
|
|
1752
|
+
hooks: [{ ...preferredHandler, type: 'command', command, timeout }],
|
|
1753
|
+
};
|
|
1754
|
+
return [...retained, canonical];
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function upsertGrokHooks(settingsPath) {
|
|
1758
|
+
const settings = parseJsonObject(settingsPath);
|
|
1759
|
+
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
1760
|
+
? settings.hooks
|
|
1761
|
+
: {};
|
|
1762
|
+
settings.hooks = {
|
|
1763
|
+
...hooks,
|
|
1764
|
+
UserPromptSubmit: reconcileGrokHook(settings, 'UserPromptSubmit', 'context-hook', GROK_CONTEXT_HOOK_COMMAND, undefined, 5),
|
|
1765
|
+
PreToolUse: reconcileGrokHook(settings, 'PreToolUse', 'pre-action-hook', GROK_PRE_ACTION_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 7),
|
|
1766
|
+
PostToolUse: reconcileGrokHook(settings, 'PostToolUse', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1767
|
+
PostToolUseFailure: reconcileGrokHook(settings, 'PostToolUseFailure', 'hook', GROK_ACTION_RESULT_HOOK_COMMAND, GROK_NATIVE_HOOK_MATCHER, 5),
|
|
1768
|
+
Stop: reconcileGrokHook(settings, 'Stop', 'session-hook', GROK_SESSION_END_HOOK_COMMAND, undefined, 3),
|
|
1769
|
+
SessionEnd: reconcileGrokHook(settings, 'SessionEnd', 'session-hook', null, undefined, 3),
|
|
1770
|
+
};
|
|
1771
|
+
return JSON.stringify(settings, null, 2) + '\n';
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
function managedGrokHooksFile(filePath) {
|
|
1775
|
+
if (!exists(filePath)) return false;
|
|
1776
|
+
try {
|
|
1777
|
+
const stat = fs.lstatSync(filePath);
|
|
1778
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return true;
|
|
1779
|
+
if (stat.size > 2 * 1024 * 1024) return true;
|
|
1780
|
+
return /@getmarrow\/mcp@[^\s"']+[\s\S]{0,4096}marrow-mcp[\s\S]{0,512}grok-(?:context-hook|pre-action-hook|hook|session-hook)/
|
|
1781
|
+
.test(fs.readFileSync(filePath, 'utf8'));
|
|
1782
|
+
} catch {
|
|
1783
|
+
return true;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1694
1787
|
function upsertGeminiHooks(settingsPath) {
|
|
1695
1788
|
const settings = parseJsonObject(settingsPath);
|
|
1696
1789
|
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
@@ -2303,6 +2396,15 @@ function buildPlan(detection, options) {
|
|
|
2303
2396
|
transform: (filePath) => retarget(upsertGeminiHooks(filePath)),
|
|
2304
2397
|
});
|
|
2305
2398
|
}
|
|
2399
|
+
if (options.update && detection.grok && managedGrokHooksFile(detection.paths.grokHooks)) {
|
|
2400
|
+
writes.push({
|
|
2401
|
+
type: 'json-transform',
|
|
2402
|
+
path: detection.paths.grokHooks,
|
|
2403
|
+
root: path.resolve(detection.paths.grokHooks, '..', '..', '..'),
|
|
2404
|
+
label: 'Grok native hooks',
|
|
2405
|
+
transform: (filePath) => retarget(upsertGrokHooks(filePath)),
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2306
2408
|
writes.push({
|
|
2307
2409
|
type: 'json-transform',
|
|
2308
2410
|
path: detection.paths.mcpJson,
|
|
@@ -2405,12 +2507,17 @@ function atomicWriteManagedFile(root, targetPath, contents) {
|
|
|
2405
2507
|
function applyPlan(plan, options) {
|
|
2406
2508
|
if (!Array.isArray(plan?.writes) || plan.writes.length === 0) return [];
|
|
2407
2509
|
const root = path.resolve(plan.root || path.dirname(plan.writes[0].path));
|
|
2408
|
-
for (const write of plan.writes)
|
|
2510
|
+
for (const write of plan.writes) {
|
|
2511
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2512
|
+
}
|
|
2409
2513
|
const prepared = plan.writes.map((write) => {
|
|
2410
2514
|
const fileExists = exists(write.path);
|
|
2411
2515
|
const before = safeRead(write.path);
|
|
2516
|
+
const inspectedBefore = write.type === 'md-block'
|
|
2517
|
+
? marrowManagedBlockInText(before)
|
|
2518
|
+
: before;
|
|
2412
2519
|
const aheadUnverifiedVersions = unverifiedAheadMcpVersions(
|
|
2413
|
-
mcpVersionsInText(
|
|
2520
|
+
mcpVersionsInText(inspectedBefore),
|
|
2414
2521
|
plan.mcp_target_version,
|
|
2415
2522
|
);
|
|
2416
2523
|
const automaticRepairSuppressed = aheadUnverifiedVersions.length > 0;
|
|
@@ -2481,10 +2588,10 @@ function applyPlan(plan, options) {
|
|
|
2481
2588
|
...(hookConflict ? { exact_fix: write.conflict_fix } : {}),
|
|
2482
2589
|
});
|
|
2483
2590
|
if (contentChanged && writeApplied) {
|
|
2484
|
-
atomicWriteManagedFile(root, write.path, after);
|
|
2591
|
+
atomicWriteManagedFile(path.resolve(write.root || root), write.path, after);
|
|
2485
2592
|
}
|
|
2486
2593
|
if (modeChanged && writeApplied) {
|
|
2487
|
-
assertContainedManagedTarget(root, write.path);
|
|
2594
|
+
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2488
2595
|
fs.chmodSync(write.path, write.mode);
|
|
2489
2596
|
}
|
|
2490
2597
|
}
|
|
@@ -2997,12 +3104,12 @@ function printReport(report) {
|
|
|
2997
3104
|
}
|
|
2998
3105
|
}
|
|
2999
3106
|
|
|
3000
|
-
if (report.harnessReload) {
|
|
3107
|
+
if (report.harnessReload && report.writeMode !== 'doctor') {
|
|
3001
3108
|
process.stdout.write('\nHarness reload:\n');
|
|
3002
3109
|
process.stdout.write(`- required: ${report.harnessReload.required ? 'yes' : 'no'}\n`);
|
|
3003
3110
|
process.stdout.write(`- live in this process: ${report.harnessReload.live_in_this_process ? 'yes' : 'no'}\n`);
|
|
3004
|
-
if (report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
3005
|
-
if (report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
3111
|
+
if (report.harnessReload.required && report.harnessReload.instruction) process.stdout.write(`- restart: ${report.harnessReload.instruction}\n`);
|
|
3112
|
+
if (report.harnessReload.required && report.harnessReload.prove_command) process.stdout.write(`- prove after restart: ${report.harnessReload.prove_command}\n`);
|
|
3006
3113
|
}
|
|
3007
3114
|
if (report.firstCapture) {
|
|
3008
3115
|
process.stdout.write('\nFirst capture:\n');
|
|
@@ -3049,7 +3156,9 @@ function printReport(report) {
|
|
|
3049
3156
|
if (report.controller?.exact_fix) process.stdout.write(`- exact fix: ${report.controller.exact_fix}\n`);
|
|
3050
3157
|
|
|
3051
3158
|
if (report.writeMode === 'doctor') {
|
|
3052
|
-
const liveHere = !report.harnessReload?.required
|
|
3159
|
+
const liveHere = !report.harnessReload?.required
|
|
3160
|
+
&& report.doctor.mcpProcesses?.healthy !== false
|
|
3161
|
+
&& report.doctor.mcpConfigurations?.healthy !== false;
|
|
3053
3162
|
const activeLabel = report.doctor.active && liveHere
|
|
3054
3163
|
? 'yes'
|
|
3055
3164
|
: report.doctor.active
|
|
@@ -3066,8 +3175,6 @@ function printReport(report) {
|
|
|
3066
3175
|
process.stdout.write(`- stale/mixed/version-unknown MCP clients: ${processes.healthy ? 'no' : 'yes'}\n`);
|
|
3067
3176
|
if (processes.ahead_unverified_versions.length) process.stdout.write(`- unverified-ahead MCP clients: ${processes.ahead_unverified_versions.join(', ')}\n`);
|
|
3068
3177
|
if (processes.automatic_repair_suppressed) process.stdout.write('- automatic MCP process repair: suppressed pending official registry verification\n');
|
|
3069
|
-
if (processes.restart_instruction) process.stdout.write(`- restart required: ${processes.restart_instruction}\n`);
|
|
3070
|
-
if (processes.verification_command) process.stdout.write(`- verify repair: ${processes.verification_command}\n`);
|
|
3071
3178
|
}
|
|
3072
3179
|
if (report.doctor.mcpConfigurations) {
|
|
3073
3180
|
const configurations = report.doctor.mcpConfigurations;
|
|
@@ -3077,6 +3184,10 @@ function printReport(report) {
|
|
|
3077
3184
|
if (configurations.automatic_repair_suppressed) process.stdout.write('- automatic MCP configuration repair: suppressed pending official registry verification\n');
|
|
3078
3185
|
}
|
|
3079
3186
|
if (report.doctor.recommendedFix) process.stdout.write(`- recommended fix: ${report.doctor.recommendedFix}\n`);
|
|
3187
|
+
if (report.doctor.mcp_update_required) {
|
|
3188
|
+
process.stdout.write(`- restart once after update: ${report.doctor.restart_instruction}\n`);
|
|
3189
|
+
process.stdout.write(`- verify after restart: ${report.doctor.verification_command}\n`);
|
|
3190
|
+
}
|
|
3080
3191
|
process.stdout.write(`- live health: ${report.doctor.healthCommand}\n`);
|
|
3081
3192
|
}
|
|
3082
3193
|
|
|
@@ -3176,6 +3287,8 @@ async function install(options) {
|
|
|
3176
3287
|
const registryVerificationAction = automaticMcpRepairSuppressed
|
|
3177
3288
|
? mcpRegistryVerificationAction(aheadUnverifiedVersions)
|
|
3178
3289
|
: null;
|
|
3290
|
+
const mcpUpdateRequired = !automaticMcpRepairSuppressed
|
|
3291
|
+
&& (!mcpConfigurations.healthy || (!options.update && !mcpProcesses.healthy));
|
|
3179
3292
|
if (automaticMcpRepairSuppressed) {
|
|
3180
3293
|
profile = {
|
|
3181
3294
|
...profile,
|
|
@@ -3208,7 +3321,40 @@ async function install(options) {
|
|
|
3208
3321
|
if (options.activate && !selfTest.activation_verified) {
|
|
3209
3322
|
throw new Error('Marrow activation failed: server confirmation was not returned');
|
|
3210
3323
|
}
|
|
3211
|
-
const
|
|
3324
|
+
const plannedHarnessReload = harnessReloadPlan(detection, options.doctor ? [] : changes);
|
|
3325
|
+
const updateNeedsRestart = Boolean(options.update
|
|
3326
|
+
&& !automaticMcpRepairSuppressed
|
|
3327
|
+
&& (plannedHarnessReload.required || (mcpConfigurations.healthy && !mcpProcesses.healthy)));
|
|
3328
|
+
const harnessReload = options.doctor && mcpUpdateRequired
|
|
3329
|
+
? {
|
|
3330
|
+
...plannedHarnessReload,
|
|
3331
|
+
required: true,
|
|
3332
|
+
live_in_this_process: false,
|
|
3333
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3334
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3335
|
+
}
|
|
3336
|
+
: updateNeedsRestart
|
|
3337
|
+
? {
|
|
3338
|
+
...plannedHarnessReload,
|
|
3339
|
+
required: true,
|
|
3340
|
+
live_in_this_process: false,
|
|
3341
|
+
instruction: INSTALLER_RESTART_INSTRUCTION,
|
|
3342
|
+
prove_command: INSTALLER_DOCTOR_COMMAND,
|
|
3343
|
+
}
|
|
3344
|
+
: plannedHarnessReload;
|
|
3345
|
+
const updateAwaitingRestart = Boolean(options.update
|
|
3346
|
+
&& updateNeedsRestart
|
|
3347
|
+
&& mcpConfigurations.healthy
|
|
3348
|
+
&& !mcpProcesses.healthy
|
|
3349
|
+
&& !automaticMcpRepairSuppressed);
|
|
3350
|
+
const reportedMcpProcesses = updateAwaitingRestart
|
|
3351
|
+
? {
|
|
3352
|
+
...mcpProcesses,
|
|
3353
|
+
update_completed: true,
|
|
3354
|
+
awaiting_restart: true,
|
|
3355
|
+
exact_fix: null,
|
|
3356
|
+
}
|
|
3357
|
+
: mcpProcesses;
|
|
3212
3358
|
if (harnessReload.required && selfTest.mcp_tool_profile) {
|
|
3213
3359
|
selfTest.mcp_tool_profile = {
|
|
3214
3360
|
...selfTest.mcp_tool_profile,
|
|
@@ -3268,6 +3414,8 @@ async function install(options) {
|
|
|
3268
3414
|
automaticMcpRepairSuppressed,
|
|
3269
3415
|
message: automaticMcpRepairSuppressed
|
|
3270
3416
|
? registryVerificationAction
|
|
3417
|
+
: options.update && harnessReload.required
|
|
3418
|
+
? 'Managed Marrow configuration is synchronized. Running MCP processes are unchanged; complete the Harness reload steps below before treating the update as live.'
|
|
3271
3419
|
: selfTestPassed
|
|
3272
3420
|
? selfTest.health === 'healthy'
|
|
3273
3421
|
? 'I fixed Marrow passive config, one-call runtime is active, and self-test passed.'
|
|
@@ -3277,7 +3425,6 @@ async function install(options) {
|
|
|
3277
3425
|
: `Config repair ran, but self-test failed: ${selfTest.error || 'unknown error'}.`,
|
|
3278
3426
|
}
|
|
3279
3427
|
: null;
|
|
3280
|
-
|
|
3281
3428
|
return {
|
|
3282
3429
|
root: detection.root,
|
|
3283
3430
|
adapterProvenance: adapterProvenanceForMcpTarget(mcpTarget),
|
|
@@ -3314,12 +3461,18 @@ async function install(options) {
|
|
|
3314
3461
|
missingEnv: options.apiKey ? [] : ['MARROW_API_KEY'],
|
|
3315
3462
|
envHints,
|
|
3316
3463
|
missingHooks: changes.filter((change) => change.changed).map((change) => change.label),
|
|
3317
|
-
mcpProcesses,
|
|
3464
|
+
mcpProcesses: reportedMcpProcesses,
|
|
3318
3465
|
mcpConfigurations,
|
|
3319
3466
|
ahead_unverified_versions: aheadUnverifiedVersions,
|
|
3320
3467
|
automatic_mcp_repair_suppressed: automaticMcpRepairSuppressed,
|
|
3321
3468
|
registry_verification_action: registryVerificationAction,
|
|
3322
|
-
|
|
3469
|
+
mcp_update_required: mcpUpdateRequired,
|
|
3470
|
+
restart_required: mcpUpdateRequired || updateAwaitingRestart,
|
|
3471
|
+
restart_instruction: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
3472
|
+
verification_command: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_DOCTOR_COMMAND : null,
|
|
3473
|
+
recommendedFix: registryVerificationAction
|
|
3474
|
+
|| (updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null)
|
|
3475
|
+
|| mcpProcesses.exact_fix || mcpConfigurations.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
3323
3476
|
? envHints.length
|
|
3324
3477
|
? `MARROW_API_KEY was found in a likely env file at ${envHints[0]}. Load that key from trusted secret storage, export only MARROW_API_KEY, then run npx @getmarrow/install --repair.`
|
|
3325
3478
|
: 'Set MARROW_API_KEY, then run npx @getmarrow/install --repair.'
|
|
@@ -3339,13 +3492,13 @@ async function install(options) {
|
|
|
3339
3492
|
: []),
|
|
3340
3493
|
...(mcpProcesses.ahead_unverified
|
|
3341
3494
|
? ['Registry-unverified ahead Marrow MCP clients are active. Automatic repair is suppressed; run the exact registry verification action before owner-directed replacement.']
|
|
3342
|
-
: !mcpProcesses.healthy
|
|
3343
|
-
? [
|
|
3495
|
+
: !mcpProcesses.healthy && !options.update && !options.doctor
|
|
3496
|
+
? [`Stale, mixed, or version-unknown Marrow MCP clients are active. Run ${INSTALLER_UPDATE_COMMAND}; the installer will report the one required restart and later doctor verification.`]
|
|
3344
3497
|
: []),
|
|
3345
3498
|
...(mcpConfigurations.ahead_unverified || changes.some((change) => change.automatic_repair_suppressed)
|
|
3346
3499
|
? ['Registry-unverified ahead Marrow MCP configuration was preserved. It was not copied into other managed targets, and automatic repair is suppressed for that surface.']
|
|
3347
|
-
: !mcpConfigurations.healthy
|
|
3348
|
-
? [
|
|
3500
|
+
: !mcpConfigurations.healthy && !options.update && !options.doctor
|
|
3501
|
+
? [`Stale, mixed, or version-unknown Marrow MCP versions remain in the detected workspace. Run ${INSTALLER_UPDATE_COMMAND} once to synchronize its managed surfaces.`]
|
|
3349
3502
|
: []),
|
|
3350
3503
|
],
|
|
3351
3504
|
};
|