log-llm-config 1.3.51 → 1.3.53
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,21 +184,22 @@ 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
190
|
const entries = rawEntries;
|
|
140
|
-
|
|
191
|
+
const scoped = entries.filter((e) => targetsCurrentAgent(e, agent));
|
|
192
|
+
if (scoped.length === 0) {
|
|
141
193
|
hookRunLog('compliance_check: no remediation instructions present');
|
|
142
194
|
return { status: 'ok', checked_at: new Date().toISOString(), manifest_uuids: [], violations: [] };
|
|
143
195
|
}
|
|
144
|
-
const uuids =
|
|
196
|
+
const uuids = scoped.map((e) => e.uuid);
|
|
145
197
|
const violations = [];
|
|
146
198
|
let skippedNoCompliance = 0;
|
|
147
199
|
let skippedNonJson = 0;
|
|
148
200
|
let skippedNoChecks = 0;
|
|
149
201
|
let skippedUnreadable = 0;
|
|
150
|
-
for (const entry of
|
|
202
|
+
for (const entry of scoped) {
|
|
151
203
|
const compliance = entry.fix ?? entry.compliance;
|
|
152
204
|
if (!compliance) {
|
|
153
205
|
skippedNoCompliance++;
|
|
@@ -272,7 +324,7 @@ export function runLocalRemediationComplianceCheck() {
|
|
|
272
324
|
return fallback;
|
|
273
325
|
}
|
|
274
326
|
}
|
|
275
|
-
export function applyAutofixViolations(violations) {
|
|
327
|
+
export function applyAutofixViolations(violations, agent = 'cursor') {
|
|
276
328
|
for (const v of violations) {
|
|
277
329
|
if (!v.autofix_allowed) {
|
|
278
330
|
logRemediationApplyFailure('autofix_skipped_not_allowed', {
|
|
@@ -294,7 +346,8 @@ export function applyAutofixViolations(violations) {
|
|
|
294
346
|
reportPromises: [],
|
|
295
347
|
};
|
|
296
348
|
const { remediations } = readRemediationInstructionsFile();
|
|
297
|
-
const
|
|
349
|
+
const scopedRemediations = remediations.filter((e) => targetsCurrentAgent(e, agent));
|
|
350
|
+
const byUuid = new Map(scopedRemediations.map((r) => [r.uuid, r]));
|
|
298
351
|
let fixed = 0;
|
|
299
352
|
const appliedViolations = [];
|
|
300
353
|
const seen = new Set();
|
|
@@ -405,11 +458,17 @@ export function applyAutofixViolations(violations) {
|
|
|
405
458
|
hookRunLog('autofix: deferred vscdb — restart command runs apply_deferred_vscdb.js then open -a Cursor');
|
|
406
459
|
}
|
|
407
460
|
if (oneTimeAppliedUuids.size > 0) {
|
|
408
|
-
|
|
461
|
+
// Only prune one-time remediations for this agent; keep other-agent remediations intact.
|
|
462
|
+
const remaining = remediations.filter((r) => {
|
|
463
|
+
if (!targetsCurrentAgent(r, agent))
|
|
464
|
+
return true;
|
|
465
|
+
return !oneTimeAppliedUuids.has(r.uuid);
|
|
466
|
+
});
|
|
409
467
|
writeRemediationInstructionsFile({ remediations: remaining });
|
|
410
468
|
hookRunLog(`autofix: removed ${oneTimeAppliedUuids.size} one-time remediation(s) from local store`);
|
|
411
469
|
// Send a post-autofix heartbeat so the server sees the updated (reduced) UUID set immediately,
|
|
412
470
|
// without waiting for the background runner (which may be locked out).
|
|
471
|
+
// Heartbeat should reflect the full local file state (including other-agent remediations that remain).
|
|
413
472
|
const remainingUuids = remaining.map((r) => r.uuid);
|
|
414
473
|
const hwHeartbeat = tryResolveHardwareUuid();
|
|
415
474
|
if (hwHeartbeat) {
|
|
@@ -434,7 +493,7 @@ export function applyAutofixViolations(violations) {
|
|
|
434
493
|
*
|
|
435
494
|
* Returns number removed and any async report promises (heartbeat).
|
|
436
495
|
*/
|
|
437
|
-
export function pruneSatisfiedOneTimeRemediations() {
|
|
496
|
+
export function pruneSatisfiedOneTimeRemediations(agent = 'cursor') {
|
|
438
497
|
const { remediations } = readRemediationInstructionsFile();
|
|
439
498
|
if (!Array.isArray(remediations) || remediations.length === 0)
|
|
440
499
|
return { removed: 0, reportPromises: [] };
|
|
@@ -442,6 +501,10 @@ export function pruneSatisfiedOneTimeRemediations() {
|
|
|
442
501
|
let removed = 0;
|
|
443
502
|
for (const raw of remediations) {
|
|
444
503
|
const inst = raw;
|
|
504
|
+
if (!targetsCurrentAgent(inst, agent)) {
|
|
505
|
+
remaining.push(raw);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
445
508
|
if (inst.is_enforced) {
|
|
446
509
|
remaining.push(raw);
|
|
447
510
|
continue;
|
|
@@ -508,9 +571,10 @@ export async function runComplianceCheck() {
|
|
|
508
571
|
catch (err) {
|
|
509
572
|
hookRunLog(`compliance_check: remediation_sync unexpected error: ${err instanceof Error ? err.message : String(err)}`);
|
|
510
573
|
}
|
|
511
|
-
const
|
|
574
|
+
const agent = currentAgentFromEnv();
|
|
575
|
+
const status = runLocalRemediationComplianceCheck(agent);
|
|
512
576
|
if (status.status === 'ok' || status.violations.length === 0) {
|
|
513
|
-
const pruned = pruneSatisfiedOneTimeRemediations();
|
|
577
|
+
const pruned = pruneSatisfiedOneTimeRemediations(agent);
|
|
514
578
|
if (pruned.removed > 0) {
|
|
515
579
|
await Promise.allSettled(pruned.reportPromises);
|
|
516
580
|
}
|
|
@@ -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) {
|