log-llm-config 1.3.82 → 1.3.84

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.
@@ -13,9 +13,11 @@
13
13
  import { existsSync, readFileSync } from 'node:fs';
14
14
  import { homedir } from 'node:os';
15
15
  import { join } from 'node:path';
16
- import { readVscdbItemTableJson } from '../readers/vscdb_reader.js';
17
- import { readFileCollectionVscdbContract, readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
16
+ import { mergeComposerShadowKeysFromReactiveBlob, readVscdbItemTableJson, } from '../readers/vscdb_reader.js';
17
+ import { readRemediationInstructionsFile, writeRemediationInstructionsFile, } from './management_storage.js';
18
18
  import { resolveRemediationConfigPath } from './remediation_config_path.js';
19
+ import { resolveOpsTargetPath } from './ops_target_path.js';
20
+ import { isRemediationQuarantined, markRemediationApplyPendingVerification, processPendingPostRestartVerifications, readRemediationApplyTrackingFile, writeRemediationApplyTrackingFile, } from './remediation_apply_tracking.js';
19
21
  import { complianceRunnerDiag, hookRunLog, logRemediationApplyFailure } from './hook_logger.js';
20
22
  import { loadEndpointBase } from '../sender/endpoint_config.js';
21
23
  import { resolveHardwareUuid, tryResolveHardwareUuid } from './hardware_uuid.js';
@@ -84,6 +86,28 @@ export function itemTableKeyFromSettingPath(settingPath) {
84
86
  const i = settingPath.indexOf('.');
85
87
  return i === -1 ? settingPath : settingPath.slice(0, i);
86
88
  }
89
+ /**
90
+ * Compliance reads the live config at `config_file_path` (#itemKey), but sqlite apply may target
91
+ * a different ItemTable row. Verify against the manifest path's item key, not the apply target.
92
+ */
93
+ export function canonicalComplianceSettingPath(configFilePath, check) {
94
+ const settingPath = check.setting_path;
95
+ const applyKey = check.sqlite_op?.target_key?.trim();
96
+ if (!applyKey || applyKey === settingPath)
97
+ return settingPath;
98
+ const hashIdx = configFilePath.indexOf('#');
99
+ if (hashIdx < 0)
100
+ return settingPath;
101
+ const verifyKey = configFilePath.slice(hashIdx + 1).trim();
102
+ if (!verifyKey || applyKey === verifyKey)
103
+ return settingPath;
104
+ const prefix = `${applyKey}.`;
105
+ if (settingPath === applyKey)
106
+ return verifyKey;
107
+ if (settingPath.startsWith(prefix))
108
+ return `${verifyKey}${settingPath.slice(applyKey.length)}`;
109
+ return settingPath;
110
+ }
87
111
  /** Traverse a JSON object using dot-notation path. Returns undefined if any segment is missing. */
88
112
  export function getByPath(obj, path) {
89
113
  const parts = path.split('.');
@@ -143,13 +167,8 @@ function verifyOpsApplied(configJson, settingPath, ops) {
143
167
  const add = ops.add ?? {};
144
168
  const remove = ops.remove ?? {};
145
169
  const keys = new Set([...Object.keys(set), ...Object.keys(add), ...Object.keys(remove)]);
146
- // If ops targets a single leaf key, check at settingPath; otherwise treat keys as subkeys under parent.
147
170
  for (const k of keys) {
148
- const targetPath = k === leafKey || (keys.size === 1 && (k === leafKey || k === ''))
149
- ? settingPath
150
- : parentPath
151
- ? `${parentPath}.${k}`
152
- : k;
171
+ const targetPath = resolveOpsTargetPath(settingPath, k);
153
172
  if (Object.prototype.hasOwnProperty.call(set, k)) {
154
173
  const cur = getByPath(configJson, targetPath);
155
174
  const expected = set[k];
@@ -179,42 +198,8 @@ function shouldMergeComposerShadowKeys(itemKeyFromPath, checkSettingPaths) {
179
198
  return true;
180
199
  return checkSettingPaths.some((p) => p === 'composerState' || p.startsWith('composerState.'));
181
200
  }
182
- /**
183
- * Cursor stores web-tool toggles on the reactive blob root and nested `composerState`.
184
- * Policy scan merges root shadow keys into composerState; compliance must match or autofix
185
- * never runs when nested values are stale (e.g. lastBrowserConnectionMode none vs root editor).
186
- */
187
- export function mergeComposerShadowKeysFromReactiveBlob(dbPath, merged) {
188
- const contract = readFileCollectionVscdbContract();
189
- const reactiveKey = contract?.reactive_storage_item_key?.trim();
190
- const shadowKeys = contract?.composer_shadow_keys ?? [];
191
- if (!reactiveKey || shadowKeys.length === 0)
192
- return;
193
- const reactiveWrapped = readVscdbItemTableJson(dbPath, reactiveKey);
194
- if (reactiveWrapped === null)
195
- return;
196
- const blob = reactiveWrapped[reactiveKey];
197
- if (!blob || typeof blob !== 'object' || Array.isArray(blob))
198
- return;
199
- const root = blob;
200
- let cs = merged.composerState;
201
- if (!cs || typeof cs !== 'object' || Array.isArray(cs)) {
202
- const nested = root.composerState;
203
- if (nested && typeof nested === 'object' && !Array.isArray(nested)) {
204
- cs = { ...nested };
205
- }
206
- else {
207
- cs = {};
208
- }
209
- merged.composerState = cs;
210
- }
211
- const composerState = cs;
212
- for (const key of shadowKeys) {
213
- if (Object.prototype.hasOwnProperty.call(root, key) && root[key] !== undefined) {
214
- composerState[key] = root[key];
215
- }
216
- }
217
- }
201
+ /** @deprecated Import from vscdb_reader — re-export for existing tests. */
202
+ export { mergeComposerShadowKeysFromReactiveBlob } from '../readers/vscdb_reader.js';
218
203
  /** Plain JSON file or virtual `…/state.vscdb#itemKey` path for ItemTable-backed settings. */
219
204
  function loadRemediationConfigJson(configFilePath, checkSettingPaths = []) {
220
205
  const resolvedPath = resolveRemediationConfigPath(configFilePath);
@@ -270,6 +255,101 @@ function evaluateSecondaryGroup(group) {
270
255
  // ---------------------------------------------------------------------------
271
256
  // Check runner — Section 6: real per-check evaluation
272
257
  // ---------------------------------------------------------------------------
258
+ function violationFromCheck(entry, compliance, check, expected) {
259
+ return {
260
+ uuid: entry.uuid,
261
+ finding_formatted_id: compliance.finding_formatted_id,
262
+ setting_path: check.setting_path,
263
+ description: check.description,
264
+ finding_title: entry.finding_title,
265
+ finding_description: entry.finding_description,
266
+ policy_name: entry.policy_name,
267
+ severity: compliance.severity,
268
+ autofix_allowed: compliance.autofix_allowed,
269
+ config_file_path: entry.config_file_path,
270
+ expected_value: expected,
271
+ message: `[${compliance.finding_formatted_id}] ${entry.finding_title ?? compliance.description}\nDescription: ${entry.finding_description ?? compliance.description}\nHow to fix: Apply remediation ops for ${check.setting_path} in ${entry.config_file_path}`,
272
+ };
273
+ }
274
+ /** Evaluate one manifest row against on-disk config (used by gate + post-restart verify). */
275
+ export function evaluateManifestEntryCompliance(entry) {
276
+ const compliance = entry.fix ?? entry.compliance;
277
+ if (!compliance || compliance.file_format !== 'json')
278
+ return { violations: [] };
279
+ const checks = compliance.checks ?? [];
280
+ if (checks.length === 0)
281
+ return { violations: [] };
282
+ const loaded = loadRemediationConfigJson(entry.config_file_path, checks.map((c) => c.setting_path));
283
+ if (!loaded.ok)
284
+ return { violations: [] };
285
+ const configJson = loaded.json;
286
+ const entryViolations = [];
287
+ for (const check of checks) {
288
+ const effectivePath = canonicalComplianceSettingPath(entry.config_file_path, check);
289
+ if (check.sqlite_op?.apply_to_all_workspaces && check.ops) {
290
+ const segments = check.sqlite_op.workspace_storage_path_segments ?? [
291
+ 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage',
292
+ ];
293
+ const wsPath = join(homedir(), ...segments);
294
+ const vscdbPaths = discoverAllWorkspaceVscdbs(wsPath);
295
+ if (vscdbPaths.length === 0)
296
+ continue;
297
+ let violated = false;
298
+ let expectedForViolation = null;
299
+ for (const dbPath of vscdbPaths) {
300
+ const wrapped = readVscdbItemTableJson(dbPath, check.sqlite_op.target_key ?? '');
301
+ if (wrapped === null) {
302
+ violated = true;
303
+ expectedForViolation = { op: 'read_failed', db: dbPath };
304
+ break;
305
+ }
306
+ const { ok, expected } = verifyOpsApplied(wrapped, check.setting_path, check.ops);
307
+ if (!ok) {
308
+ violated = true;
309
+ expectedForViolation = expected;
310
+ break;
311
+ }
312
+ }
313
+ if (violated) {
314
+ entryViolations.push(violationFromCheck(entry, compliance, check, expectedForViolation));
315
+ }
316
+ continue;
317
+ }
318
+ if (check.ops) {
319
+ const { ok, expected } = verifyOpsApplied(configJson, effectivePath, check.ops);
320
+ if (!ok) {
321
+ entryViolations.push(violationFromCheck(entry, compliance, { ...check, setting_path: effectivePath }, expected));
322
+ }
323
+ continue;
324
+ }
325
+ const currentValue = getByPath(configJson, effectivePath);
326
+ const leafKey = effectivePath.split('.').pop();
327
+ const expectedValue = check.after?.[leafKey];
328
+ if (expectedValue === undefined)
329
+ continue;
330
+ if (!deepEqual(currentValue, expectedValue)) {
331
+ entryViolations.push(violationFromCheck(entry, compliance, { ...check, setting_path: effectivePath }, expectedValue));
332
+ }
333
+ }
334
+ if (entryViolations.length === 0)
335
+ return { violations: [] };
336
+ const secondaryGroups = compliance.secondary_checks ?? [];
337
+ const passingGroup = secondaryGroups.length > 0 ? secondaryGroups.find((g) => evaluateSecondaryGroup(g)) : undefined;
338
+ if (passingGroup) {
339
+ return {
340
+ violations: [],
341
+ secondarySatisfied: {
342
+ uuid: entry.uuid,
343
+ config_file_path: passingGroup.config_file_path,
344
+ file_type: passingGroup.file_type,
345
+ },
346
+ };
347
+ }
348
+ return { violations: entryViolations };
349
+ }
350
+ export function collectManifestEntryViolations(entry) {
351
+ return evaluateManifestEntryCompliance(entry).violations;
352
+ }
273
353
  /**
274
354
  * Evaluate current on-disk configs against remediation_instructions.json only (no server).
275
355
  * Returns status for prompt gating / callers; does not persist compliance.json.
@@ -300,12 +380,11 @@ export function runLocalRemediationComplianceCheck(agent = 'cursor') {
300
380
  hookRunLog(`compliance_check: skipping non-json entry uuid=${entry.uuid}`);
301
381
  continue;
302
382
  }
303
- const checks = compliance.checks ?? [];
304
- if (checks.length === 0) {
383
+ if ((compliance.checks ?? []).length === 0) {
305
384
  skippedNoChecks++;
306
385
  continue;
307
386
  }
308
- const loaded = loadRemediationConfigJson(entry.config_file_path, checks.map((c) => c.setting_path));
387
+ const loaded = loadRemediationConfigJson(entry.config_file_path, (compliance.checks ?? []).map((c) => c.setting_path));
309
388
  if (!loaded.ok) {
310
389
  skippedUnreadable++;
311
390
  const msg = loaded.reason === 'file_not_found'
@@ -327,121 +406,16 @@ export function runLocalRemediationComplianceCheck(agent = 'cursor') {
327
406
  });
328
407
  continue;
329
408
  }
330
- const configJson = loaded.json;
331
- const entryViolations = [];
332
- for (const check of checks) {
333
- // When the check carries a sqlite_op with apply_to_all_workspaces, the authoritative data
334
- // lives in workspace state.vscdb files — not in config_file_path (which may be mcp.json in
335
- // the fallback case where no workspace vscdb has been collected yet). Verify directly against
336
- // every discovered workspace vscdb; fail as a violation if any is missing the required entry.
337
- if (check.sqlite_op?.apply_to_all_workspaces && check.ops) {
338
- const segments = check.sqlite_op.workspace_storage_path_segments ?? [
339
- 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage',
340
- ];
341
- const wsPath = join(homedir(), ...segments);
342
- const vscdbPaths = discoverAllWorkspaceVscdbs(wsPath);
343
- if (vscdbPaths.length === 0) {
344
- hookRunLog(`compliance_check: apply_to_all_workspaces — no workspace vscdbs found at ${wsPath}, skipping uuid=${entry.uuid}`);
345
- }
346
- else {
347
- let violated = false;
348
- let expectedForViolation = null;
349
- for (const dbPath of vscdbPaths) {
350
- const wrapped = readVscdbItemTableJson(dbPath, check.sqlite_op.target_key ?? '');
351
- if (wrapped === null) {
352
- violated = true;
353
- expectedForViolation = { op: 'read_failed', db: dbPath };
354
- break;
355
- }
356
- const { ok, expected } = verifyOpsApplied(wrapped, check.setting_path, check.ops);
357
- if (!ok) {
358
- violated = true;
359
- expectedForViolation = expected;
360
- break;
361
- }
362
- }
363
- if (violated) {
364
- hookRunLog(`compliance_check: VIOLATION (vscdb) uuid=${entry.uuid} path=${check.setting_path} expected=${JSON.stringify(expectedForViolation)}`);
365
- entryViolations.push({
366
- uuid: entry.uuid,
367
- finding_formatted_id: compliance.finding_formatted_id,
368
- setting_path: check.setting_path,
369
- description: check.description,
370
- finding_title: entry.finding_title,
371
- finding_description: entry.finding_description,
372
- policy_name: entry.policy_name,
373
- severity: compliance.severity,
374
- autofix_allowed: compliance.autofix_allowed,
375
- config_file_path: entry.config_file_path,
376
- expected_value: expectedForViolation,
377
- message: `[${compliance.finding_formatted_id}] ${entry.finding_title ?? compliance.description}\nDescription: ${entry.finding_description ?? compliance.description}\nHow to fix: Apply remediation ops for ${check.setting_path} in workspace state.vscdb files`,
378
- });
379
- }
380
- }
381
- continue;
382
- }
383
- // Prefer ops-based verification (matches delta apply semantics; doesn't require full after snapshot).
384
- if (check.ops) {
385
- const { ok, expected } = verifyOpsApplied(configJson, check.setting_path, check.ops);
386
- if (!ok) {
387
- hookRunLog(`compliance_check: VIOLATION uuid=${entry.uuid} path=${check.setting_path} expected=${JSON.stringify(expected)}`);
388
- entryViolations.push({
389
- uuid: entry.uuid,
390
- finding_formatted_id: compliance.finding_formatted_id,
391
- setting_path: check.setting_path,
392
- description: check.description,
393
- finding_title: entry.finding_title,
394
- finding_description: entry.finding_description,
395
- policy_name: entry.policy_name,
396
- severity: compliance.severity,
397
- autofix_allowed: compliance.autofix_allowed,
398
- config_file_path: entry.config_file_path,
399
- expected_value: expected,
400
- message: `[${compliance.finding_formatted_id}] ${entry.finding_title ?? compliance.description}\nDescription: ${entry.finding_description ?? compliance.description}\nHow to fix: Apply remediation ops for ${check.setting_path} in ${entry.config_file_path}`,
401
- });
402
- }
403
- continue;
404
- }
405
- // Backwards compat: old local files may still carry after snapshots.
406
- const currentValue = getByPath(configJson, check.setting_path);
407
- const leafKey = check.setting_path.split('.').pop();
408
- const expectedValue = check.after?.[leafKey];
409
- if (expectedValue === undefined)
410
- continue;
411
- if (!deepEqual(currentValue, expectedValue)) {
412
- hookRunLog(`compliance_check: VIOLATION uuid=${entry.uuid} path=${check.setting_path} current=${JSON.stringify(currentValue)} expected=${JSON.stringify(expectedValue)}`);
413
- entryViolations.push({
414
- uuid: entry.uuid,
415
- finding_formatted_id: compliance.finding_formatted_id,
416
- setting_path: check.setting_path,
417
- description: check.description,
418
- finding_title: entry.finding_title,
419
- finding_description: entry.finding_description,
420
- policy_name: entry.policy_name,
421
- severity: compliance.severity,
422
- autofix_allowed: compliance.autofix_allowed,
423
- config_file_path: entry.config_file_path,
424
- expected_value: expectedValue,
425
- message: `[${compliance.finding_formatted_id}] ${entry.finding_title ?? compliance.description}\nDescription: ${entry.finding_description ?? compliance.description}\nHow to fix: Set ${check.setting_path} to ${JSON.stringify(expectedValue)} in ${entry.config_file_path}`,
426
- });
427
- }
409
+ const { violations: entryViolations, secondarySatisfied: entrySecondary } = evaluateManifestEntryCompliance(entry);
410
+ for (const v of entryViolations) {
411
+ hookRunLog(`compliance_check: VIOLATION uuid=${entry.uuid} path=${v.setting_path} expected=${JSON.stringify(v.expected_value)}`);
428
412
  }
429
- if (entryViolations.length > 0) {
430
- const secondaryGroups = compliance.secondary_checks ?? [];
431
- const passingGroup = secondaryGroups.length > 0
432
- ? secondaryGroups.find((g) => evaluateSecondaryGroup(g))
433
- : undefined;
434
- if (passingGroup) {
435
- hookRunLog(`compliance_check: secondary check satisfied uuid=${entry.uuid} — skipping ${entryViolations.length} primary violation(s)`);
436
- secondarySatisfied.push({
437
- uuid: entry.uuid,
438
- config_file_path: passingGroup.config_file_path,
439
- file_type: passingGroup.file_type,
440
- });
441
- }
442
- else {
443
- violations.push(...entryViolations);
444
- }
413
+ if (entrySecondary) {
414
+ hookRunLog(`compliance_check: secondary check satisfied uuid=${entry.uuid} skipping primary violation(s)`);
415
+ secondarySatisfied.push(entrySecondary);
416
+ }
417
+ else if (entryViolations.length > 0) {
418
+ violations.push(...entryViolations);
445
419
  }
446
420
  }
447
421
  const status = {
@@ -485,6 +459,34 @@ export function runLocalRemediationComplianceCheck(agent = 'cursor') {
485
459
  return fallback;
486
460
  }
487
461
  }
462
+ /**
463
+ * After restart, verify pending remediations and report outcomes to the server.
464
+ */
465
+ export function reportPostRestartVerificationOutcomes(violations) {
466
+ const { remediations } = readRemediationInstructionsFile();
467
+ const entriesByUuid = new Map(remediations.map((entry) => [entry.uuid, entry]));
468
+ const outcomes = processPendingPostRestartVerifications((uuid) => {
469
+ const entry = entriesByUuid.get(uuid);
470
+ if (entry && collectManifestEntryViolations(entry).length > 0)
471
+ return true;
472
+ return violations.some((v) => v.uuid === uuid);
473
+ });
474
+ const reportPromises = outcomes.map((o) => {
475
+ if (o.status === 'quarantined' || o.status === 'verification_failed') {
476
+ logRemediationApplyFailure('post_restart_verification_failed', {
477
+ uuid: o.uuid,
478
+ reason: o.reason ?? 'Setting unchanged after apply',
479
+ status: o.status,
480
+ consecutive_failures: o.consecutive_failures,
481
+ });
482
+ }
483
+ return reportAutofixApplied(o.uuid, o.status, {
484
+ failure_reason: o.reason,
485
+ consecutive_failures: o.consecutive_failures,
486
+ });
487
+ });
488
+ return { outcomes, reportPromises };
489
+ }
488
490
  export function applyAutofixViolations(violations, agent = 'cursor') {
489
491
  for (const v of violations) {
490
492
  if (!v.autofix_allowed) {
@@ -520,6 +522,18 @@ export function applyAutofixViolations(violations, agent = 'cursor') {
520
522
  for (const violation of autofixable) {
521
523
  if (seen.has(violation.uuid))
522
524
  continue;
525
+ if (isRemediationQuarantined(violation.uuid)) {
526
+ hookRunLog(`autofix: skipped quarantined uuid=${violation.uuid}`);
527
+ logRemediationApplyFailure('autofix_skipped_quarantined', {
528
+ uuid: violation.uuid,
529
+ finding_formatted_id: violation.finding_formatted_id,
530
+ config_file_path: violation.config_file_path,
531
+ setting_path: violation.setting_path,
532
+ reason: 'Auto-fix paused after repeated post-restart verification failures — agent config may have changed',
533
+ });
534
+ failedViolations.push(violation);
535
+ continue;
536
+ }
523
537
  const instruction = byUuid.get(violation.uuid);
524
538
  if (!instruction) {
525
539
  hookRunLog(`autofix: no instruction found for uuid=${violation.uuid}, skipping`);
@@ -548,6 +562,10 @@ export function applyAutofixViolations(violations, agent = 'cursor') {
548
562
  appliedViolations.push(violation);
549
563
  hookRunLog(`autofix: applied uuid=${inst.uuid} path=${configPathForDisk}`);
550
564
  reportPromises.push(reportAutofixApplied(inst.uuid, 'success'));
565
+ // Every successful autofix (Cursor + Claude, restart or immediate JSON) awaits verification on
566
+ // the next compliance check so we can quarantine stuck applies and stop restart/retry loops.
567
+ markRemediationApplyPendingVerification(inst.uuid);
568
+ const spec = remediationFixSpec(inst);
551
569
  if (er.deferredSqlite && configPathForDisk.includes('#')) {
552
570
  hookRunLog(`autofix: skip immediate vscdb upload (deferred until after restart) uuid=${inst.uuid}`);
553
571
  }
@@ -597,7 +615,6 @@ export function applyAutofixViolations(violations, agent = 'cursor') {
597
615
  }
598
616
  }
599
617
  }
600
- const spec = remediationFixSpec(inst);
601
618
  if (spec?.restart_required && spec.restart_command) {
602
619
  if (!er.deferredSqlite) {
603
620
  if (isTrustedRestartCommandForAutofix(spec.restart_command)) {
@@ -749,6 +766,68 @@ export function pruneSatisfiedOneTimeRemediations(agent = 'cursor') {
749
766
  }
750
767
  return { removed, reportPromises };
751
768
  }
769
+ /** Throttle satisfied-manifest uploads (disk already matches ops; autofix did not run). */
770
+ const SATISFIED_MANIFEST_UPLOAD_COOLDOWN_MS = 5 * 60 * 1000;
771
+ /**
772
+ * When local compliance already passes, autofix is skipped — so settings never reach the server.
773
+ * Upload satisfied JSON remediations (throttled) so scan can resolve findings.
774
+ */
775
+ export function uploadSatisfiedManifestConfigs(agent = 'cursor') {
776
+ const { remediations } = readRemediationInstructionsFile();
777
+ const entries = remediations.filter((e) => targetsCurrentAgent(e, agent));
778
+ const promises = [];
779
+ for (const entry of entries) {
780
+ const { violations } = evaluateManifestEntryCompliance(entry);
781
+ if (violations.length > 0)
782
+ continue;
783
+ const inst = entry;
784
+ const fileType = (inst.file_type ?? '').trim();
785
+ if (!fileType)
786
+ continue;
787
+ const diskPath = resolveRemediationConfigPath(entry.config_file_path);
788
+ if (diskPath.includes('#'))
789
+ continue;
790
+ const tracking = readRemediationApplyTrackingFile();
791
+ const prev = tracking.entries[entry.uuid];
792
+ const lastUpload = prev?.last_satisfied_upload_at;
793
+ if (lastUpload) {
794
+ const elapsed = Date.now() - Date.parse(lastUpload);
795
+ if (!Number.isNaN(elapsed) && elapsed < SATISFIED_MANIFEST_UPLOAD_COOLDOWN_MS) {
796
+ continue;
797
+ }
798
+ }
799
+ let rawContent;
800
+ try {
801
+ rawContent = JSON.parse(readFileSync(diskPath, 'utf8'));
802
+ }
803
+ catch {
804
+ hookRunLog(`satisfied_upload: could not read path=${diskPath} uuid=${entry.uuid}`);
805
+ continue;
806
+ }
807
+ const hw = tryResolveHardwareUuid();
808
+ if (!hw) {
809
+ hookRunLog(`satisfied_upload: skip uuid=${entry.uuid} (hardware UUID unavailable)`);
810
+ continue;
811
+ }
812
+ const uploadPath = entry.config_file_path.trim() || diskPath;
813
+ promises.push(ensureAuthentication(hw)
814
+ .then((authKey) => sendConfigFile({ file_type: fileType, file_path: uploadPath, raw_content: rawContent }, hw, authKey))
815
+ .then((sentOk) => {
816
+ hookRunLog(`satisfied_upload: uuid=${entry.uuid} path=${uploadPath} ok=${sentOk}`);
817
+ if (sentOk) {
818
+ const file = readRemediationApplyTrackingFile();
819
+ const ent = file.entries[entry.uuid] ?? { consecutive_verify_failures: 0, quarantined: false };
820
+ ent.last_satisfied_upload_at = new Date().toISOString();
821
+ file.entries[entry.uuid] = ent;
822
+ writeRemediationApplyTrackingFile(file);
823
+ }
824
+ })
825
+ .catch((err) => {
826
+ hookRunLog(`satisfied_upload: failed uuid=${entry.uuid} err=${err instanceof Error ? err.message : String(err)}`);
827
+ }));
828
+ }
829
+ return promises;
830
+ }
752
831
  /**
753
832
  * Background refresh: server sync for latest instructions, then the same local evaluation as the hook.
754
833
  * Apply (autofix) is intentionally deferred to the gate on the next prompt — this pass only downloads
@@ -768,5 +847,6 @@ export async function runComplianceCheck() {
768
847
  if (pruned.removed > 0) {
769
848
  await Promise.allSettled(pruned.reportPromises);
770
849
  }
850
+ await Promise.allSettled(uploadSatisfiedManifestConfigs(agent));
771
851
  }
772
852
  }
@@ -0,0 +1,31 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { atomicWriteJson, getDialogPreferencesPath, } from './management_storage.js';
3
+ export function readDialogPreferences() {
4
+ const path = getDialogPreferencesPath();
5
+ if (!existsSync(path)) {
6
+ return { version: 1, suppress_non_restart_autofix_dialogs: false };
7
+ }
8
+ try {
9
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
10
+ if (parsed?.version !== 1)
11
+ return { version: 1, suppress_non_restart_autofix_dialogs: false };
12
+ return {
13
+ version: 1,
14
+ suppress_non_restart_autofix_dialogs: Boolean(parsed.suppress_non_restart_autofix_dialogs),
15
+ };
16
+ }
17
+ catch {
18
+ return { version: 1, suppress_non_restart_autofix_dialogs: false };
19
+ }
20
+ }
21
+ export function isNonRestartAutofixDialogSuppressed() {
22
+ return readDialogPreferences().suppress_non_restart_autofix_dialogs === true;
23
+ }
24
+ export function setSuppressNonRestartAutofixDialogs(suppress) {
25
+ const next = {
26
+ version: 1,
27
+ suppress_non_restart_autofix_dialogs: suppress,
28
+ updated_at: new Date().toISOString(),
29
+ };
30
+ atomicWriteJson(getDialogPreferencesPath(), next);
31
+ }
@@ -17,6 +17,8 @@ export const DEFERRED_VSCDB_RESTART_LOG_BASENAME = 'deferred_vscdb_restart.log';
17
17
  * of hardcoded Cursor paths.
18
18
  */
19
19
  export const FILE_COLLECTION_VSCDB_CONTRACT_BASENAME = 'file_collection_vscdb_contract.json';
20
+ /** User preferences for Optimus macOS dialogs (hook-driven). */
21
+ export const DIALOG_PREFERENCES_BASENAME = 'optimus_dialog_preferences.json';
20
22
  export function sanitizeReactiveStorageItemKey(raw) {
21
23
  if (typeof raw !== 'string')
22
24
  return undefined;
@@ -59,6 +61,9 @@ export function getDeferredVscdbRestartLogPath() {
59
61
  export function getFileCollectionVscdbContractPath() {
60
62
  return join(getManagementDir(), FILE_COLLECTION_VSCDB_CONTRACT_BASENAME);
61
63
  }
64
+ export function getDialogPreferencesPath() {
65
+ return join(getManagementDir(), DIALOG_PREFERENCES_BASENAME);
66
+ }
62
67
  export function readFileCollectionVscdbContract() {
63
68
  const path = getFileCollectionVscdbContractPath();
64
69
  if (!existsSync(path))
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Map ops.add/set/remove keys to JSON dot-paths for apply + verify.
3
+ *
4
+ * Server may use either the leaf key (e.g. `deny`) or the full setting_path
5
+ * (e.g. `cursor.general.globalCursorIgnoreList`) as the op map key.
6
+ */
7
+ export function resolveOpsTargetPath(settingPath, opKey) {
8
+ const parts = settingPath.split('.');
9
+ const leafKey = parts[parts.length - 1] ?? '';
10
+ const parentPath = parts.slice(0, -1).join('.');
11
+ if (opKey === settingPath || opKey === leafKey) {
12
+ return settingPath;
13
+ }
14
+ if (opKey.includes('.')) {
15
+ return opKey;
16
+ }
17
+ return parentPath ? `${parentPath}.${opKey}` : opKey;
18
+ }