@getmarrow/install 0.1.57 → 0.1.58
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 +5 -1
- package/package.json +2 -1
- package/src/installer.js +162 -11
package/README.md
CHANGED
|
@@ -101,7 +101,11 @@ npx @getmarrow/install controller stop
|
|
|
101
101
|
|
|
102
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.
|
|
103
103
|
|
|
104
|
-
## What's New in v0.1.
|
|
104
|
+
## What's New in v0.1.58
|
|
105
|
+
|
|
106
|
+
v0.1.58 pins MCP `3.9.89` from source `ff229e17419f65aeebd7fa7754dd61cbda61900d` with packed integrity `sha512-KC/P4dStzOOfZKxSwigQE4TWCt1TBgYINQzlXrnA5jpYHRrT2Jo73vaV4FiucVPOT+xyU1uCci+bmJoC35aZ0g==`; SDK remains `3.7.63`. Supported native hooks now include their host read, search, and status surfaces so MCP's private local session loop guard can stop unchanged successful checks, polls, and failed retries without adding routine backend writes. Install and update run `marrow-mcp loop-guard-self-test` against isolated temporary state and report configuration, isolated proof, and live host observation separately. Missing local control state stays enabled by default; an explicit owner disable is preserved byte-for-byte and prevents the controller from starting. Grok's trusted global hook file is created only at its direct owner-safe path, managed files are reconciled, and unmanaged files are preserved for owner review. Restart and complete each host's hook trust or review flow before relying on live enforcement.
|
|
107
|
+
|
|
108
|
+
### Previous release: v0.1.57
|
|
105
109
|
|
|
106
110
|
v0.1.57 pins the npm-verified MCP `3.9.88` and SDK `3.7.63` packages. Doctor now recognizes an installed and lockfile-verified SDK `3.7.63` instead of suggesting `3.7.62`. If a workspace has a newer stable SDK version, doctor preserves it and asks for official registry verification before replacement. The generated MCP setup, hooks, and update instructions target `3.9.88`; restart the owning harness and run `npx @getmarrow/install@latest doctor --self-test` after activation.
|
|
107
111
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmarrow/install",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"description": "Universal installer and governed runner for Marrow agent fleets.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"marrow-install": "bin/marrow-install.js"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"mcp",
|
|
17
17
|
"installer",
|
|
18
18
|
"passive-runtime",
|
|
19
|
+
"loop-guard",
|
|
19
20
|
"governance",
|
|
20
21
|
"proof-packs",
|
|
21
22
|
"agent-control-plane"
|
package/src/installer.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
const os = require('node:os');
|
|
4
4
|
const crypto = require('node:crypto');
|
|
5
|
+
const { spawnSync } = require('node:child_process');
|
|
5
6
|
const { version: INSTALLER_ADAPTER_VERSION } = require('../package.json');
|
|
6
7
|
const { controllerStatus, controllerSupportedPlatform, ensureGovernanceController } = require('./controller-manager');
|
|
7
8
|
const { firstCapturePath, harnessReloadPlan } = require('./first-hour');
|
|
@@ -10,9 +11,9 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
10
11
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
11
12
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
12
13
|
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-
|
|
14
|
+
const MCP_ADAPTER_VERSION = '3.9.89';
|
|
15
|
+
const MCP_ADAPTER_SOURCE_SHA = 'ff229e17419f65aeebd7fa7754dd61cbda61900d';
|
|
16
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-KC/P4dStzOOfZKxSwigQE4TWCt1TBgYINQzlXrnA5jpYHRrT2Jo73vaV4FiucVPOT+xyU1uCci+bmJoC35aZ0g==';
|
|
16
17
|
const SDK_ADAPTER_VERSION = '3.7.63';
|
|
17
18
|
const SDK_ADAPTER_INTEGRITY = 'sha512-5BiV1P0J1NMVdgjqxqULfM+zGrTjHBuWm1amKZWba4yW6C3t10VlhIlj/A+4YKbKqvyZe8gXgUwW+VrYDbVQrQ==';
|
|
18
19
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
@@ -278,10 +279,10 @@ const GROK_PRE_ACTION_GUARD_SOURCE = [
|
|
|
278
279
|
'child.stdin.end(Buffer.concat(input));}catch{fail();}});',
|
|
279
280
|
].join('');
|
|
280
281
|
const GROK_PRE_ACTION_HOOK_COMMAND = `node -e '${GROK_PRE_ACTION_GUARD_SOURCE}'`;
|
|
281
|
-
const NATIVE_HOOK_MATCHER = 'Bash|Edit|Write|MultiEdit|mcp__(?!
|
|
282
|
+
const NATIVE_HOOK_MATCHER = 'Bash|Edit|Write|MultiEdit|Read|Glob|Grep|Search|WebSearch|Task|functions\\.(?!mcp__marrow__marrow_).*|mcp__(?!marrow__marrow_).*';
|
|
282
283
|
const CODEX_NATIVE_HOOK_MATCHER = 'Bash|apply_patch|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*|functions\\.(?!marrow_).*';
|
|
283
|
-
const CURSOR_NATIVE_HOOK_MATCHER = 'Shell|Write|Delete|Task|MCP:(?!marrow(?:_.*|:marrow_.*)$).*';
|
|
284
|
-
const GEMINI_NATIVE_HOOK_MATCHER = '^(?:run_shell_command|write_file|replace|edit_file|delete_file|mcp_(?!marrow_marrow_)[A-Za-z0-9_]{1,192})$';
|
|
284
|
+
const CURSOR_NATIVE_HOOK_MATCHER = 'Shell|Write|Delete|Task|Read|Glob|Grep|Search|WebSearch|List|MCP:(?!marrow(?:_.*|:marrow_.*)$).*';
|
|
285
|
+
const GEMINI_NATIVE_HOOK_MATCHER = '^(?:run_shell_command|write_file|replace|edit_file|delete_file|read_file|read_many_files|glob|grep_search|list_directory|get_file_info|web_search|google_web_search|mcp_(?!marrow_marrow_)[A-Za-z0-9_]{1,192})$';
|
|
285
286
|
const GROK_NATIVE_HOOK_MATCHER = 'run_terminal_command|search_replace|write|spawn_subagent|use_tool|workflow|image_gen|image_edit|image_to_video|reference_to_video';
|
|
286
287
|
const CODEX_HOOK_TIMEOUT_SECONDS = 5;
|
|
287
288
|
const CODEX_SESSION_TIMEOUT_SECONDS = 3;
|
|
@@ -710,6 +711,7 @@ function sourceClient() {
|
|
|
710
711
|
function parseArgs(argv, env = process.env) {
|
|
711
712
|
const options = {
|
|
712
713
|
cwd: process.cwd(),
|
|
714
|
+
home: env.HOME || env.USERPROFILE || os.homedir(),
|
|
713
715
|
yes: false,
|
|
714
716
|
dryRun: false,
|
|
715
717
|
doctor: false,
|
|
@@ -720,6 +722,7 @@ function parseArgs(argv, env = process.env) {
|
|
|
720
722
|
agentId: env.MARROW_FLEET_AGENT_ID || env.MARROW_AGENT_ID || '',
|
|
721
723
|
toolProfile: resolveToolProfile(env.MARROW_TOOL_PROFILE),
|
|
722
724
|
selfTest: true,
|
|
725
|
+
loopGuardSelfTest: true,
|
|
723
726
|
selfTestExplicitlyDisabled: false,
|
|
724
727
|
json: false,
|
|
725
728
|
activate: false,
|
|
@@ -873,6 +876,32 @@ function safeRead(filePath) {
|
|
|
873
876
|
return exists(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
874
877
|
}
|
|
875
878
|
|
|
879
|
+
function assertSafeGrokHookTarget(homePath, targetPath) {
|
|
880
|
+
const home = path.resolve(homePath);
|
|
881
|
+
const target = path.resolve(targetPath);
|
|
882
|
+
const expected = path.join(home, '.grok', 'hooks', 'marrow.json');
|
|
883
|
+
if (target !== expected) throw new Error('Refusing Grok hook write outside the direct owner hook path');
|
|
884
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
885
|
+
const checkDirectory = (directory) => {
|
|
886
|
+
if (!exists(directory)) return;
|
|
887
|
+
const stat = fs.lstatSync(directory);
|
|
888
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()
|
|
889
|
+
|| (uid !== null && stat.uid !== uid) || (stat.mode & 0o022) !== 0) {
|
|
890
|
+
throw new Error(`Refusing Grok hook write through unsafe owner path: ${directory}`);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
checkDirectory(home);
|
|
894
|
+
checkDirectory(path.join(home, '.grok'));
|
|
895
|
+
checkDirectory(path.join(home, '.grok', 'hooks'));
|
|
896
|
+
if (exists(target)) {
|
|
897
|
+
const stat = fs.lstatSync(target);
|
|
898
|
+
if (!stat.isFile() || stat.isSymbolicLink()
|
|
899
|
+
|| (uid !== null && stat.uid !== uid) || (stat.mode & 0o022) !== 0) {
|
|
900
|
+
throw new Error('Refusing Grok hook write to unsafe owner file');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
876
905
|
function findUp(startDir, names, maxDepth = 8) {
|
|
877
906
|
let dir = path.resolve(startDir);
|
|
878
907
|
for (let depth = 0; depth <= maxDepth; depth += 1) {
|
|
@@ -923,6 +952,7 @@ function detectEnvironment(cwd = process.cwd(), env = process.env) {
|
|
|
923
952
|
|
|
924
953
|
return {
|
|
925
954
|
root,
|
|
955
|
+
home,
|
|
926
956
|
paths,
|
|
927
957
|
node: exists(paths.packageJson),
|
|
928
958
|
python: exists(paths.pyproject) || exists(paths.requirements) || exists(paths.setupPy),
|
|
@@ -2061,6 +2091,9 @@ function activationProfile(detection, plan, changes, client) {
|
|
|
2061
2091
|
passive_live: false,
|
|
2062
2092
|
configuration_complete: complete,
|
|
2063
2093
|
complete,
|
|
2094
|
+
loop_guard_configured: capabilityLevel === 'native_hooks' && complete,
|
|
2095
|
+
loop_guard_self_tested: false,
|
|
2096
|
+
loop_guard_observed: false,
|
|
2064
2097
|
exact_fix: exactFix,
|
|
2065
2098
|
...(client === 'cline' ? {
|
|
2066
2099
|
hook_conflicts: clineConflicts,
|
|
@@ -2421,12 +2454,14 @@ function buildPlan(detection, options) {
|
|
|
2421
2454
|
transform: (filePath) => retarget(upsertGeminiHooks(filePath)),
|
|
2422
2455
|
});
|
|
2423
2456
|
}
|
|
2424
|
-
if (
|
|
2457
|
+
if (detection.grok) {
|
|
2425
2458
|
writes.push({
|
|
2426
|
-
type: 'json-transform',
|
|
2459
|
+
type: 'managed-json-transform',
|
|
2427
2460
|
path: detection.paths.grokHooks,
|
|
2428
|
-
root:
|
|
2461
|
+
root: detection.home,
|
|
2429
2462
|
label: 'Grok native hooks',
|
|
2463
|
+
isManaged: managedGrokHooksFile,
|
|
2464
|
+
conflict_fix: 'Preserve or move the unmanaged owner Grok hook file after owner review, then rerun npx @getmarrow/install update.',
|
|
2430
2465
|
transform: (filePath) => retarget(upsertGrokHooks(filePath)),
|
|
2431
2466
|
});
|
|
2432
2467
|
}
|
|
@@ -2533,6 +2568,9 @@ function applyPlan(plan, options) {
|
|
|
2533
2568
|
if (!Array.isArray(plan?.writes) || plan.writes.length === 0) return [];
|
|
2534
2569
|
const root = path.resolve(plan.root || path.dirname(plan.writes[0].path));
|
|
2535
2570
|
for (const write of plan.writes) {
|
|
2571
|
+
if (write.label === 'Grok native hooks') {
|
|
2572
|
+
assertSafeGrokHookTarget(path.resolve(write.root), write.path);
|
|
2573
|
+
}
|
|
2536
2574
|
assertContainedManagedTarget(path.resolve(write.root || root), write.path);
|
|
2537
2575
|
}
|
|
2538
2576
|
const prepared = plan.writes.map((write) => {
|
|
@@ -2560,6 +2598,13 @@ function applyPlan(plan, options) {
|
|
|
2560
2598
|
after = upsertBlock(before, write.block);
|
|
2561
2599
|
} else if (write.type === 'json-transform') {
|
|
2562
2600
|
after = write.transform(write.path);
|
|
2601
|
+
} else if (write.type === 'managed-json-transform') {
|
|
2602
|
+
if (fileExists && !write.isManaged(write.path)) {
|
|
2603
|
+
after = before;
|
|
2604
|
+
hookConflict = true;
|
|
2605
|
+
} else {
|
|
2606
|
+
after = write.transform(write.path);
|
|
2607
|
+
}
|
|
2563
2608
|
} else if (write.type === 'owned-executable') {
|
|
2564
2609
|
if (fileExists && before !== write.content) {
|
|
2565
2610
|
after = before;
|
|
@@ -2639,6 +2684,63 @@ async function requestJson(url, options) {
|
|
|
2639
2684
|
return json.data || json;
|
|
2640
2685
|
}
|
|
2641
2686
|
|
|
2687
|
+
function runLoopGuardSelfTest(options = {}) {
|
|
2688
|
+
const target = executableMcpTarget(options);
|
|
2689
|
+
const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), 'marrow-install-loop-guard-'));
|
|
2690
|
+
const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
2691
|
+
const args = ['-y', `--package=@getmarrow/mcp@${target.version}`, 'marrow-mcp', 'loop-guard-self-test'];
|
|
2692
|
+
try {
|
|
2693
|
+
const invocation = {
|
|
2694
|
+
command,
|
|
2695
|
+
args,
|
|
2696
|
+
cwd: isolatedHome,
|
|
2697
|
+
env: {
|
|
2698
|
+
...process.env,
|
|
2699
|
+
HOME: isolatedHome,
|
|
2700
|
+
USERPROFILE: isolatedHome,
|
|
2701
|
+
MARROW_API_KEY: '',
|
|
2702
|
+
MARROW_KEY: '',
|
|
2703
|
+
},
|
|
2704
|
+
encoding: 'utf8',
|
|
2705
|
+
timeout: 15_000,
|
|
2706
|
+
maxBuffer: 16 * 1024,
|
|
2707
|
+
};
|
|
2708
|
+
const result = typeof options.loopGuardSelfTestRunner === 'function'
|
|
2709
|
+
? options.loopGuardSelfTestRunner(invocation)
|
|
2710
|
+
: spawnSync(command, args, invocation);
|
|
2711
|
+
if (!result || result.error || result.status !== 0) {
|
|
2712
|
+
const reason = result?.error?.message || String(result?.stderr || '').trim() || `exit ${result?.status ?? 'unknown'}`;
|
|
2713
|
+
throw new Error(`MCP ${target.version} isolated loop-guard self-test failed: ${reason.slice(0, 300)}`);
|
|
2714
|
+
}
|
|
2715
|
+
let proof;
|
|
2716
|
+
try {
|
|
2717
|
+
proof = JSON.parse(String(result.stdout || ''));
|
|
2718
|
+
} catch {
|
|
2719
|
+
throw new Error(`MCP ${target.version} isolated loop-guard self-test returned invalid JSON`);
|
|
2720
|
+
}
|
|
2721
|
+
const valid = proof && !Array.isArray(proof)
|
|
2722
|
+
&& proof.pass === true
|
|
2723
|
+
&& proof.isolated === true
|
|
2724
|
+
&& proof.live_hook_observed === false
|
|
2725
|
+
&& proof.repeat_denied === true
|
|
2726
|
+
&& proof.mutation_reset === true
|
|
2727
|
+
&& proof.owner_disabled_bypass === true;
|
|
2728
|
+
if (!valid) throw new Error(`MCP ${target.version} isolated loop-guard self-test returned incomplete proof`);
|
|
2729
|
+
return {
|
|
2730
|
+
attempted: true,
|
|
2731
|
+
passed: true,
|
|
2732
|
+
isolated: true,
|
|
2733
|
+
mcp_version: target.version,
|
|
2734
|
+
repeat_denied: true,
|
|
2735
|
+
mutation_reset: true,
|
|
2736
|
+
owner_disabled_bypass: true,
|
|
2737
|
+
live_hook_observed: false,
|
|
2738
|
+
};
|
|
2739
|
+
} finally {
|
|
2740
|
+
fs.rmSync(isolatedHome, { recursive: true, force: true });
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2642
2744
|
function isCanonicalTimestamp(value) {
|
|
2643
2745
|
if (typeof value !== 'string' || value.length === 0) return false;
|
|
2644
2746
|
const parsed = Date.parse(value);
|
|
@@ -3042,11 +3144,21 @@ function printReport(report) {
|
|
|
3042
3144
|
for (const change of report.changes) {
|
|
3043
3145
|
const marker = change.automatic_repair_suppressed
|
|
3044
3146
|
? 'preserved unverified-ahead surface; repair suppressed'
|
|
3147
|
+
: change.hook_conflict ? 'preserved unmanaged owner file; review required'
|
|
3045
3148
|
: change.applied ? 'wrote' : change.changed ? 'would write' : 'unchanged';
|
|
3046
3149
|
process.stdout.write(`- ${marker}: ${change.label} (${change.path})\n`);
|
|
3047
3150
|
if (change.automatic_repair_suppressed && change.exact_fix) {
|
|
3048
3151
|
process.stdout.write(` exact verification: ${change.exact_fix}\n`);
|
|
3049
3152
|
}
|
|
3153
|
+
if (change.hook_conflict && change.exact_fix) process.stdout.write(` exact fix: ${change.exact_fix}\n`);
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
process.stdout.write('\nLocal session loop guard:\n');
|
|
3157
|
+
process.stdout.write(`- configured: ${report.loop_guard_configured ? 'yes' : 'no'}\n`);
|
|
3158
|
+
process.stdout.write(`- isolated self-test passed: ${report.loop_guard_self_tested ? 'yes' : 'no'}\n`);
|
|
3159
|
+
process.stdout.write(`- observed in a reloaded host: ${report.loop_guard_observed ? 'yes' : 'no'}\n`);
|
|
3160
|
+
if (!report.loop_guard_observed) {
|
|
3161
|
+
process.stdout.write('- next: restart the host and complete its hook trust/review step before relying on live enforcement\n');
|
|
3050
3162
|
}
|
|
3051
3163
|
|
|
3052
3164
|
process.stdout.write('\nSelf-test:\n');
|
|
@@ -3194,6 +3306,9 @@ function printReport(report) {
|
|
|
3194
3306
|
process.stdout.write(`- missing env: ${report.doctor.missingEnv.length ? report.doctor.missingEnv.join(', ') : 'none'}\n`);
|
|
3195
3307
|
if (report.doctor.envHints.length) process.stdout.write(`- possible env files: ${report.doctor.envHints.join(', ')}\n`);
|
|
3196
3308
|
process.stdout.write(`- missing hooks/config: ${report.doctor.missingHooks.length ? report.doctor.missingHooks.join('; ') : 'none'}\n`);
|
|
3309
|
+
process.stdout.write(`- loop guard configured: ${report.doctor.loop_guard_configured ? 'yes' : 'no'}\n`);
|
|
3310
|
+
process.stdout.write(`- loop guard isolated self-test: ${report.doctor.loop_guard_self_tested ? 'passed' : 'not passed'}\n`);
|
|
3311
|
+
process.stdout.write(`- loop guard observed after reload/trust: ${report.doctor.loop_guard_observed ? 'yes' : 'no'}\n`);
|
|
3197
3312
|
if (report.doctor.mcpProcesses?.available) {
|
|
3198
3313
|
const processes = report.doctor.mcpProcesses;
|
|
3199
3314
|
process.stdout.write(`- MCP process versions: ${processes.active_versions.length ? processes.active_versions.join(', ') : processes.active_processes ? 'unknown' : 'none'}\n`);
|
|
@@ -3244,7 +3359,11 @@ async function install(options) {
|
|
|
3244
3359
|
options.toolProfile = resolveToolProfile(options.toolProfile === undefined
|
|
3245
3360
|
? process.env.MARROW_TOOL_PROFILE
|
|
3246
3361
|
: options.toolProfile);
|
|
3247
|
-
const detection = detectEnvironment(options.cwd
|
|
3362
|
+
const detection = detectEnvironment(options.cwd, {
|
|
3363
|
+
...process.env,
|
|
3364
|
+
HOME: options.home || options.cwd,
|
|
3365
|
+
USERPROFILE: options.home || options.cwd,
|
|
3366
|
+
});
|
|
3248
3367
|
const client = detectedClient(detection);
|
|
3249
3368
|
options.client = client;
|
|
3250
3369
|
options.agentId = String(options.agentId || '').trim() || stableAgentId(detection.root, client);
|
|
@@ -3265,6 +3384,28 @@ async function install(options) {
|
|
|
3265
3384
|
const writeMode = options.doctor ? 'doctor' : options.dryRun ? 'dry-run' : options.repair ? 'repair' : options.yes ? 'write' : 'dry-run';
|
|
3266
3385
|
const changes = applyPlan(plan, options);
|
|
3267
3386
|
let profile = activationProfile(detection, plan, changes, client);
|
|
3387
|
+
const localControl = localControlEvidence({
|
|
3388
|
+
apiKey: options.apiKey,
|
|
3389
|
+
home: options.controlHome || options.home || detection.home,
|
|
3390
|
+
});
|
|
3391
|
+
const loopGuardConfigured = profile.capability_level === 'native_hooks'
|
|
3392
|
+
&& profile.configuration_complete === true;
|
|
3393
|
+
const loopGuardSelfTest = options.loopGuardSelfTest === true && !options.dryRun
|
|
3394
|
+
? runLoopGuardSelfTest(options)
|
|
3395
|
+
: {
|
|
3396
|
+
attempted: false,
|
|
3397
|
+
passed: false,
|
|
3398
|
+
isolated: true,
|
|
3399
|
+
mcp_version: mcpTarget.version,
|
|
3400
|
+
live_hook_observed: false,
|
|
3401
|
+
};
|
|
3402
|
+
profile = {
|
|
3403
|
+
...profile,
|
|
3404
|
+
loop_guard_configured: loopGuardConfigured,
|
|
3405
|
+
loop_guard_self_tested: loopGuardSelfTest.passed === true,
|
|
3406
|
+
loop_guard_observed: false,
|
|
3407
|
+
loop_guard_enabled: localControl.enabled === true,
|
|
3408
|
+
};
|
|
3268
3409
|
options.activation = options.activate ? {
|
|
3269
3410
|
harness: client,
|
|
3270
3411
|
agent_id: options.agentId,
|
|
@@ -3393,7 +3534,6 @@ async function install(options) {
|
|
|
3393
3534
|
const changedConfig = changes.some((change) => change.applied) || configRepairs.some((repair) => repair.changed);
|
|
3394
3535
|
const selfTestPassed = Boolean(!selfTest.skipped && selfTest.active && !selfTest.error);
|
|
3395
3536
|
const controllerPlatform = options.controllerPlatform || process.platform;
|
|
3396
|
-
const localControl = localControlEvidence({ apiKey: options.apiKey, home: options.controlHome });
|
|
3397
3537
|
let controller = await controllerStatus({
|
|
3398
3538
|
root: detection.root,
|
|
3399
3539
|
agentId: options.agentId,
|
|
@@ -3478,6 +3618,10 @@ async function install(options) {
|
|
|
3478
3618
|
receipt: selfTest.activation_receipt || null,
|
|
3479
3619
|
profile,
|
|
3480
3620
|
},
|
|
3621
|
+
loop_guard_configured: loopGuardConfigured,
|
|
3622
|
+
loop_guard_self_tested: loopGuardSelfTest.passed === true,
|
|
3623
|
+
loop_guard_observed: false,
|
|
3624
|
+
loop_guard: loopGuardSelfTest,
|
|
3481
3625
|
harnessReload,
|
|
3482
3626
|
firstCapture: firstCapturePath(detection, options.agentId),
|
|
3483
3627
|
changes,
|
|
@@ -3495,6 +3639,9 @@ async function install(options) {
|
|
|
3495
3639
|
restart_required: mcpUpdateRequired || updateAwaitingRestart,
|
|
3496
3640
|
restart_instruction: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null,
|
|
3497
3641
|
verification_command: mcpUpdateRequired || updateAwaitingRestart ? INSTALLER_DOCTOR_COMMAND : null,
|
|
3642
|
+
loop_guard_configured: loopGuardConfigured,
|
|
3643
|
+
loop_guard_self_tested: loopGuardSelfTest.passed === true,
|
|
3644
|
+
loop_guard_observed: false,
|
|
3498
3645
|
recommendedFix: registryVerificationAction
|
|
3499
3646
|
|| (updateAwaitingRestart ? INSTALLER_RESTART_INSTRUCTION : null)
|
|
3500
3647
|
|| mcpProcesses.exact_fix || mcpConfigurations.exact_fix || configDiagnostics.npm_token.recommended_fix || (!options.apiKey
|
|
@@ -3550,6 +3697,7 @@ module.exports = {
|
|
|
3550
3697
|
applyPlan,
|
|
3551
3698
|
install,
|
|
3552
3699
|
runSelfTest,
|
|
3700
|
+
runLoopGuardSelfTest,
|
|
3553
3701
|
runCli,
|
|
3554
3702
|
passiveRuntimeSource,
|
|
3555
3703
|
inspectNpmTokenConfig,
|
|
@@ -3572,6 +3720,9 @@ module.exports = {
|
|
|
3572
3720
|
GROK_ACTION_RESULT_HOOK_COMMAND,
|
|
3573
3721
|
GROK_SESSION_END_HOOK_COMMAND,
|
|
3574
3722
|
GROK_NATIVE_HOOK_MATCHER,
|
|
3723
|
+
NATIVE_HOOK_MATCHER,
|
|
3724
|
+
CURSOR_NATIVE_HOOK_MATCHER,
|
|
3725
|
+
GEMINI_NATIVE_HOOK_MATCHER,
|
|
3575
3726
|
printReport,
|
|
3576
3727
|
buildMcpToolProfileReport,
|
|
3577
3728
|
resolveMcpTargetVersion,
|