log-llm-config-staging 1.3.48 → 1.3.51
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.
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* When autofix returns restart_commands, this process does not spawn them — the shell hook pipes
|
|
7
7
|
* the same JSON line to execute_trusted_restarts (TS allowlist + spawn).
|
|
8
8
|
*/
|
|
9
|
-
import { applyAutofixViolations, pruneSatisfiedOneTimeRemediations, runLocalRemediationComplianceCheck, } from './log_config_files/runtime/compliance_check.js';
|
|
9
|
+
import { applyAutofixViolations, normalizeAgentToken, pruneSatisfiedOneTimeRemediations, runLocalRemediationComplianceCheck, } from './log_config_files/runtime/compliance_check.js';
|
|
10
10
|
import { existsSync, statSync } from 'node:fs';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { basename } from 'node:path';
|
|
@@ -23,6 +23,18 @@ function parseIde() {
|
|
|
23
23
|
return 'claude';
|
|
24
24
|
return 'cursor';
|
|
25
25
|
}
|
|
26
|
+
function defaultAgentFromIde(ide) {
|
|
27
|
+
return ide === 'claude' ? 'claude' : 'cursor';
|
|
28
|
+
}
|
|
29
|
+
function parseAgent(ide) {
|
|
30
|
+
const eq = process.argv.find((a) => a.startsWith('--agent='));
|
|
31
|
+
if (eq) {
|
|
32
|
+
const normalized = normalizeAgentToken(eq.slice('--agent='.length));
|
|
33
|
+
if (normalized)
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
return defaultAgentFromIde(ide);
|
|
37
|
+
}
|
|
26
38
|
function printAllow(ide) {
|
|
27
39
|
if (ide === 'claude')
|
|
28
40
|
console.log('{}');
|
|
@@ -94,8 +106,9 @@ export async function runCompliancePromptGateCli() {
|
|
|
94
106
|
/** Exported for tests; runs when `dist/compliance_prompt_gate.js` is the process entry (argv[1]). */
|
|
95
107
|
export async function runCompliancePromptGate() {
|
|
96
108
|
const ide = parseIde();
|
|
109
|
+
const agent = parseAgent(ide);
|
|
97
110
|
hookLogSessionBanner('compliance_prompt_gate (before submit)');
|
|
98
|
-
const status = runLocalRemediationComplianceCheck();
|
|
111
|
+
const status = runLocalRemediationComplianceCheck(agent);
|
|
99
112
|
if (status.status === 'fail' && status.violations.length > 0) {
|
|
100
113
|
const staleMs = getManifestStalenessMs();
|
|
101
114
|
if (staleMs !== null && staleMs > MANIFEST_STALE_MS) {
|
|
@@ -110,7 +123,7 @@ export async function runCompliancePromptGate() {
|
|
|
110
123
|
printAllowWithAdvisory(ide, advisory);
|
|
111
124
|
return;
|
|
112
125
|
}
|
|
113
|
-
const { fixed, appliedViolations = [], restartCommands, failedViolations, reportPromises, deferredSqlitePending, } = applyAutofixViolations(status.violations);
|
|
126
|
+
const { fixed, appliedViolations = [], restartCommands, failedViolations, reportPromises, deferredSqlitePending, } = applyAutofixViolations(status.violations, agent);
|
|
114
127
|
hookRunLog(`compliance_prompt_gate: autofix result ide=${ide} fixed=${fixed} applied=${appliedViolations.length} failed=${failedViolations.length} restart_commands=${restartCommands.length} deferred_sqlite_pending=${deferredSqlitePending}`);
|
|
115
128
|
if (fixed > 0) {
|
|
116
129
|
// Wait for all server reports before exiting so the POST lands.
|
|
@@ -129,7 +142,7 @@ export async function runCompliancePromptGate() {
|
|
|
129
142
|
console.log(blockPayload(ide, msg));
|
|
130
143
|
return;
|
|
131
144
|
}
|
|
132
|
-
const recheck = runLocalRemediationComplianceCheck();
|
|
145
|
+
const recheck = runLocalRemediationComplianceCheck(agent);
|
|
133
146
|
const recheckOk = recheck.status === 'ok' || recheck.violations.length === 0;
|
|
134
147
|
// Cursor: tolerate a failing recheck only when SQLite updates are deferred (apply after restart).
|
|
135
148
|
// Claude Code: JSON remediations are written immediately; merge/verify timing can still leave the
|
|
@@ -188,7 +201,7 @@ export async function runCompliancePromptGate() {
|
|
|
188
201
|
return;
|
|
189
202
|
}
|
|
190
203
|
// No violations: clean up satisfied one-time remediations so they don't linger locally forever.
|
|
191
|
-
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
204
|
+
const pruned = pruneSatisfiedOneTimeRemediations(agent);
|
|
192
205
|
if (pruned.removed > 0) {
|
|
193
206
|
await Promise.allSettled(pruned.reportPromises);
|
|
194
207
|
}
|
|
@@ -20,6 +20,57 @@ import { resolveHardwareUuid, tryResolveHardwareUuid } from './hardware_uuid.js'
|
|
|
20
20
|
import { buildDeferredCursorRestartCommand, enforceRemediation, fetchSync, isTrustedRestartCommandForAutofix, remediationFixSpec, reportAutofixApplied, syncRemediations, } from './remediation_sync.js';
|
|
21
21
|
import { sendConfigFile } from '../sender/batch_sender.js';
|
|
22
22
|
import { readStoredAuthKey } from '../auth/auth_key_store.js';
|
|
23
|
+
/** Normalize manifest/env/CLI agent tokens to a known Agent, or '' if unrecognized. */
|
|
24
|
+
export function normalizeAgentToken(raw) {
|
|
25
|
+
if (typeof raw !== 'string')
|
|
26
|
+
return '';
|
|
27
|
+
const s = raw.trim().toLowerCase();
|
|
28
|
+
if (!s)
|
|
29
|
+
return '';
|
|
30
|
+
if (s === 'claude-desktop')
|
|
31
|
+
return 'claude_desktop';
|
|
32
|
+
if (s === 'claude_desktop')
|
|
33
|
+
return 'claude_desktop';
|
|
34
|
+
if (s === 'claude')
|
|
35
|
+
return 'claude';
|
|
36
|
+
if (s === 'cursor')
|
|
37
|
+
return 'cursor';
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
function currentAgentFromEnv() {
|
|
41
|
+
// Allow explicit override for debugging / special launchers.
|
|
42
|
+
const override = normalizeAgentToken(process.env.OPTIMUS_AGENT);
|
|
43
|
+
if (override)
|
|
44
|
+
return override;
|
|
45
|
+
// Backwards-compatible: hook wrappers set OPTIMUS_HOOK_TYPE to cursor|claude.
|
|
46
|
+
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
47
|
+
return hookType === 'cursor' ? 'cursor' : 'claude';
|
|
48
|
+
}
|
|
49
|
+
function targetsCurrentAgent(entry, agent) {
|
|
50
|
+
// Prefer top-level manifest field; fall back to embedded fix payload for older local files.
|
|
51
|
+
const embedded = (entry.fix && typeof entry.fix === 'object'
|
|
52
|
+
? entry.fix.target_agent
|
|
53
|
+
: null) ??
|
|
54
|
+
(entry.compliance && typeof entry.compliance === 'object'
|
|
55
|
+
? entry.compliance.target_agent
|
|
56
|
+
: null);
|
|
57
|
+
const t = entry.target_agent ?? embedded;
|
|
58
|
+
// Backwards compat: missing/empty target_agent means "applies to all agents".
|
|
59
|
+
if (t === null || t === undefined)
|
|
60
|
+
return true;
|
|
61
|
+
if (typeof t !== 'string') {
|
|
62
|
+
complianceRunnerDiag(`Ignoring remediation with non-string target_agent: ${String(t)}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (!t.trim())
|
|
66
|
+
return true;
|
|
67
|
+
const normalized = normalizeAgentToken(t);
|
|
68
|
+
if (!normalized) {
|
|
69
|
+
complianceRunnerDiag(`Ignoring remediation with unknown target_agent: ${t}`);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return normalized === agent;
|
|
73
|
+
}
|
|
23
74
|
// ---------------------------------------------------------------------------
|
|
24
75
|
// Helpers
|
|
25
76
|
// ---------------------------------------------------------------------------
|
|
@@ -133,10 +184,10 @@ function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
|
|
|
133
184
|
* Evaluate current on-disk configs against remediation_instructions.json only (no server).
|
|
134
185
|
* Returns status for prompt gating / callers; does not persist compliance.json.
|
|
135
186
|
*/
|
|
136
|
-
export function runLocalRemediationComplianceCheck() {
|
|
187
|
+
export function runLocalRemediationComplianceCheck(agent = 'cursor') {
|
|
137
188
|
try {
|
|
138
189
|
const { remediations: rawEntries } = readRemediationInstructionsFile();
|
|
139
|
-
const entries = rawEntries;
|
|
190
|
+
const entries = rawEntries.filter((e) => targetsCurrentAgent(e, agent));
|
|
140
191
|
if (entries.length === 0) {
|
|
141
192
|
hookRunLog('compliance_check: no remediation instructions present');
|
|
142
193
|
return { status: 'ok', checked_at: new Date().toISOString(), manifest_uuids: [], violations: [] };
|
|
@@ -272,7 +323,7 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
272
323
|
return fallback;
|
|
273
324
|
}
|
|
274
325
|
}
|
|
275
|
-
export function applyAutofixViolations(violations) {
|
|
326
|
+
export function applyAutofixViolations(violations, agent = 'cursor') {
|
|
276
327
|
for (const v of violations) {
|
|
277
328
|
if (!v.autofix_allowed) {
|
|
278
329
|
logRemediationApplyFailure('autofix_skipped_not_allowed', {
|
|
@@ -294,7 +345,8 @@ export function applyAutofixViolations(violations) {
|
|
|
294
345
|
reportPromises: [],
|
|
295
346
|
};
|
|
296
347
|
const { remediations } = readRemediationInstructionsFile();
|
|
297
|
-
const
|
|
348
|
+
const scopedRemediations = remediations.filter((e) => targetsCurrentAgent(e, agent));
|
|
349
|
+
const byUuid = new Map(scopedRemediations.map((r) => [r.uuid, r]));
|
|
298
350
|
let fixed = 0;
|
|
299
351
|
const appliedViolations = [];
|
|
300
352
|
const seen = new Set();
|
|
@@ -405,11 +457,17 @@ export function applyAutofixViolations(violations) {
|
|
|
405
457
|
hookRunLog('autofix: deferred vscdb — restart command runs apply_deferred_vscdb.js then open -a Cursor');
|
|
406
458
|
}
|
|
407
459
|
if (oneTimeAppliedUuids.size > 0) {
|
|
408
|
-
|
|
460
|
+
// Only prune one-time remediations for this agent; keep other-agent remediations intact.
|
|
461
|
+
const remaining = remediations.filter((r) => {
|
|
462
|
+
if (!targetsCurrentAgent(r, agent))
|
|
463
|
+
return true;
|
|
464
|
+
return !oneTimeAppliedUuids.has(r.uuid);
|
|
465
|
+
});
|
|
409
466
|
writeRemediationInstructionsFile({ remediations: remaining });
|
|
410
467
|
hookRunLog(`autofix: removed ${oneTimeAppliedUuids.size} one-time remediation(s) from local store`);
|
|
411
468
|
// Send a post-autofix heartbeat so the server sees the updated (reduced) UUID set immediately,
|
|
412
469
|
// without waiting for the background runner (which may be locked out).
|
|
470
|
+
// Heartbeat should reflect the full local file state (including other-agent remediations that remain).
|
|
413
471
|
const remainingUuids = remaining.map((r) => r.uuid);
|
|
414
472
|
const hwHeartbeat = tryResolveHardwareUuid();
|
|
415
473
|
if (hwHeartbeat) {
|
|
@@ -434,7 +492,7 @@ export function applyAutofixViolations(violations) {
|
|
|
434
492
|
*
|
|
435
493
|
* Returns number removed and any async report promises (heartbeat).
|
|
436
494
|
*/
|
|
437
|
-
export function pruneSatisfiedOneTimeRemediations() {
|
|
495
|
+
export function pruneSatisfiedOneTimeRemediations(agent = 'cursor') {
|
|
438
496
|
const { remediations } = readRemediationInstructionsFile();
|
|
439
497
|
if (!Array.isArray(remediations) || remediations.length === 0)
|
|
440
498
|
return { removed: 0, reportPromises: [] };
|
|
@@ -442,6 +500,10 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
442
500
|
let removed = 0;
|
|
443
501
|
for (const raw of remediations) {
|
|
444
502
|
const inst = raw;
|
|
503
|
+
if (!targetsCurrentAgent(inst, agent)) {
|
|
504
|
+
remaining.push(raw);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
445
507
|
if (inst.is_enforced) {
|
|
446
508
|
remaining.push(raw);
|
|
447
509
|
continue;
|
|
@@ -502,15 +564,16 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
502
564
|
* a fresh manifest so the gate has up-to-date data when it runs.
|
|
503
565
|
*/
|
|
504
566
|
export async function runComplianceCheck() {
|
|
567
|
+
const agent = currentAgentFromEnv();
|
|
505
568
|
try {
|
|
506
569
|
await syncRemediations(loadEndpointBase(), resolveHardwareUuid());
|
|
507
570
|
}
|
|
508
571
|
catch (err) {
|
|
509
572
|
hookRunLog(`compliance_check: remediation_sync unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
510
573
|
}
|
|
511
|
-
const status = runLocalRemediationComplianceCheck();
|
|
574
|
+
const status = runLocalRemediationComplianceCheck(agent);
|
|
512
575
|
if (status.status === 'ok' || status.violations.length === 0) {
|
|
513
|
-
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
576
|
+
const pruned = pruneSatisfiedOneTimeRemediations(agent);
|
|
514
577
|
if (pruned.removed > 0) {
|
|
515
578
|
await Promise.allSettled(pruned.reportPromises);
|
|
516
579
|
}
|
|
@@ -368,6 +368,14 @@ const TRUSTED_CURSOR_SQLITE_DEFERRED_RESTART_COMMAND = 'REPO_ROOT=$(git rev-pars
|
|
|
368
368
|
"nohup bash -c 'exec >>\"\$OPTIMUS_DEFERRED_LOG\" 2>&1; echo deferred_restart:begin ts=\$(date -u +%Y-%m-%dT%H:%M:%SZ) REPO_ROOT=\"\$REPO_ROOT\" CURSOR_PROJECT=\"\$CURSOR_PROJECT\"; sleep 2; if [ -f \"\$REPO_ROOT/dev_npx_packages/log-llm-config/dist/apply_deferred_vscdb.js\" ]; then echo deferred_restart:apply_via_monorepo_node; node \"\$REPO_ROOT/dev_npx_packages/log-llm-config/dist/apply_deferred_vscdb.js\"; APPLY_EC=\$?; else echo deferred_restart:apply_via_npx; cd \"\$REPO_ROOT\" && npx --yes log-llm-config@latest apply-deferred-vscdb; APPLY_EC=\$?; fi; echo deferred_restart:apply_exit=\$APPLY_EC; if [ \$APPLY_EC -ne 0 ]; then echo deferred_restart:APPLY_FAILED_see_messages_above; fi; echo deferred_restart:open_cursor; open -a Cursor \"\$CURSOR_PROJECT\"; echo deferred_restart:open_exit=\$?; echo deferred_restart:end ts=\$(date -u +%Y-%m-%dT%H:%M:%SZ)' >/dev/null 2>&1 & killall -9 Cursor";
|
|
369
369
|
const TRUSTED_CURSOR_JSON_SETTINGS_RESTART_COMMAND = 'CURSOR_PROJECT=$(git rev-parse --show-toplevel 2>/dev/null || pwd) && export CURSOR_PROJECT && nohup bash -c \'sleep 2 && open -a Cursor "$CURSOR_PROJECT"\' >/dev/null 2>&1 & killall -9 Cursor';
|
|
370
370
|
const TRUSTED_CLAUDE_RESTART_COMMAND = "nohup bash -c 'sleep 2 && open -a Claude' >/dev/null 2>&1 & pkill -x 'Claude'";
|
|
371
|
+
export function isClaudeRestartCommand(cmd) {
|
|
372
|
+
return cmd.trim() === TRUSTED_CLAUDE_RESTART_COMMAND;
|
|
373
|
+
}
|
|
374
|
+
export function isCursorRestartCommand(cmd) {
|
|
375
|
+
const t = cmd.trim();
|
|
376
|
+
return (t === TRUSTED_CURSOR_SQLITE_DEFERRED_RESTART_COMMAND ||
|
|
377
|
+
t === TRUSTED_CURSOR_JSON_SETTINGS_RESTART_COMMAND);
|
|
378
|
+
}
|
|
371
379
|
/**
|
|
372
380
|
* Autofix restart_command allowlist: manifest strings are attacker-controlled if JSON is tampered.
|
|
373
381
|
* SQLite-deferred Cursor path always uses {@link buildDeferredCursorRestartCommand}; manifests may still
|
|
@@ -5,13 +5,26 @@
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { appendComplianceRunnerLine, hookRunLog } from './hook_logger.js';
|
|
7
7
|
import { getDeferredVscdbRestartLogPath } from './management_storage.js';
|
|
8
|
-
import {
|
|
8
|
+
import { normalizeAgentToken } from './compliance_check.js';
|
|
9
|
+
import { isClaudeRestartCommand, isCursorRestartCommand, isTrustedRestartCommandForAutofix } from './remediation_sync.js';
|
|
9
10
|
const FALLBACK_PATH = '/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin';
|
|
10
11
|
function isDeferredVscdbRestart(cmd) {
|
|
11
12
|
return cmd.includes('OPTIMUS_DEFERRED_LOG') || cmd.includes('apply_deferred_vscdb');
|
|
12
13
|
}
|
|
14
|
+
function currentAgentFromEnv() {
|
|
15
|
+
// Keep restart scoping consistent with remediation scoping:
|
|
16
|
+
// prefer OPTIMUS_AGENT override, then fall back to OPTIMUS_HOOK_TYPE.
|
|
17
|
+
const override = normalizeAgentToken(process.env.OPTIMUS_AGENT);
|
|
18
|
+
if (override === 'cursor')
|
|
19
|
+
return 'cursor';
|
|
20
|
+
if (override === 'claude' || override === 'claude_desktop')
|
|
21
|
+
return 'claude';
|
|
22
|
+
const hookType = normalizeAgentToken(process.env.OPTIMUS_HOOK_TYPE);
|
|
23
|
+
return hookType === 'cursor' ? 'cursor' : 'claude';
|
|
24
|
+
}
|
|
13
25
|
/** Spawn each trusted command detached (same pattern as former compliance_prompt_gate fireRestartCommands). */
|
|
14
26
|
export function executeTrustedRestartCommands(commands) {
|
|
27
|
+
const agent = currentAgentFromEnv();
|
|
15
28
|
for (const cmd of commands) {
|
|
16
29
|
const t = cmd.trim();
|
|
17
30
|
if (!t)
|
|
@@ -22,6 +35,19 @@ export function executeTrustedRestartCommands(commands) {
|
|
|
22
35
|
appendComplianceRunnerLine('RESTART', msg);
|
|
23
36
|
continue;
|
|
24
37
|
}
|
|
38
|
+
// Safety: never restart the wrong app for the current hook type.
|
|
39
|
+
if (agent === 'cursor' && isClaudeRestartCommand(t)) {
|
|
40
|
+
const msg = 'rejected_claude_restart_in_cursor_hook';
|
|
41
|
+
hookRunLog(`execute_trusted_restarts: ${msg}`);
|
|
42
|
+
appendComplianceRunnerLine('RESTART', msg);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (agent === 'claude' && isCursorRestartCommand(t)) {
|
|
46
|
+
const msg = 'rejected_cursor_restart_in_claude_hook';
|
|
47
|
+
hookRunLog(`execute_trusted_restarts: ${msg}`);
|
|
48
|
+
appendComplianceRunnerLine('RESTART', msg);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
25
51
|
const deferred = isDeferredVscdbRestart(t);
|
|
26
52
|
const detailPath = getDeferredVscdbRestartLogPath();
|
|
27
53
|
if (deferred) {
|