fullcourtdefense-cli 1.18.7 → 1.18.8

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.
@@ -465,6 +465,67 @@ async function runDaemon(args, config) {
465
465
  log(`Could not report remote action ${actionId} status ${status}; the server will expire it safely.`);
466
466
  }
467
467
  };
468
+ // ── Honest upgrade_cli completion ─────────────────────────────────────────
469
+ // An MSI/npm upgrade replaces THIS process: the daemon that triggered the
470
+ // update cannot observe the outcome. Persist the action id, let the freshly
471
+ // upgraded daemon (or a stale-marker check) report the REAL result — the
472
+ // dashboard should never show "succeeded" for a version that never arrived.
473
+ const pendingUpgradeFile = () => path.join(daemonDir(), 'pending-upgrade.json');
474
+ const PENDING_UPGRADE_STALE_MS = 15 * 60_000;
475
+ const writePendingUpgradeMarker = (actionId, target) => {
476
+ try {
477
+ fs.writeFileSync(pendingUpgradeFile(), JSON.stringify({
478
+ actionId,
479
+ target,
480
+ fromVersion: cliVersion(),
481
+ startedAt: new Date().toISOString(),
482
+ }), 'utf8');
483
+ }
484
+ catch { /* marker is best-effort; the server TTL expires the action safely */ }
485
+ };
486
+ const verifyPendingUpgrade = async () => {
487
+ let marker;
488
+ try {
489
+ marker = JSON.parse(fs.readFileSync(pendingUpgradeFile(), 'utf8'));
490
+ }
491
+ catch {
492
+ return; // no pending upgrade
493
+ }
494
+ if (!marker?.actionId || !marker.target) {
495
+ try {
496
+ fs.unlinkSync(pendingUpgradeFile());
497
+ }
498
+ catch { /* ignore */ }
499
+ return;
500
+ }
501
+ const current = cliVersion();
502
+ if (compareVersions(current, marker.target) >= 0) {
503
+ try {
504
+ fs.unlinkSync(pendingUpgradeFile());
505
+ }
506
+ catch { /* ignore */ }
507
+ await reportMachineAction(marker.actionId, 'succeeded', {
508
+ resultSummary: `CLI updated to ${current} and the daemon restarted on the new build.`,
509
+ });
510
+ log(`Upgrade verified: CLI ${marker.fromVersion || 'unknown'} -> ${current} is live (action ${marker.actionId}).`);
511
+ await uploadLogTail();
512
+ return;
513
+ }
514
+ const startedAt = marker.startedAt ? Date.parse(marker.startedAt) : NaN;
515
+ if (!Number.isFinite(startedAt) || Date.now() - startedAt > PENDING_UPGRADE_STALE_MS) {
516
+ try {
517
+ fs.unlinkSync(pendingUpgradeFile());
518
+ }
519
+ catch { /* ignore */ }
520
+ await reportMachineAction(marker.actionId, 'failed', {
521
+ error: `The updater was triggered but the CLI still reports ${current || 'unknown'} after ${Math.round(PENDING_UPGRADE_STALE_MS / 60_000)} minutes (target ${marker.target}). Check %ProgramData%\\FullCourtDefense\\updater.log on the machine.`,
522
+ });
523
+ log(`Upgrade verification failed: still on ${current || 'unknown'}, target was ${marker.target} (action ${marker.actionId}).`);
524
+ await uploadLogTail();
525
+ }
526
+ // Otherwise the install is still in flight — keep the marker and re-check
527
+ // on the next heartbeat; the post-install daemon relaunch settles it.
528
+ };
468
529
  const executeMachineAction = async (action) => {
469
530
  if (executingActionIds.has(action.id))
470
531
  return;
@@ -547,7 +608,14 @@ async function runDaemon(args, config) {
547
608
  resultSummary = `CLI ${cliVersion() || 'unknown'} is already at ${target}.`;
548
609
  }
549
610
  else if (outcome.started) {
550
- resultSummary = outcome.detail;
611
+ // The install replaces this process — DON'T report success yet.
612
+ // Leave the action "running" with a marker; the freshly upgraded
613
+ // daemon confirms the new version (or the check reports failure).
614
+ writePendingUpgradeMarker(action.id, target);
615
+ log(`Upgrade CLI: installer started — success will be confirmed once the new daemon is on ${target}.`);
616
+ executingActionIds.delete(action.id);
617
+ await uploadLogTail();
618
+ return;
551
619
  }
552
620
  else {
553
621
  throw new Error(outcome.detail);
@@ -660,10 +728,16 @@ async function runDaemon(args, config) {
660
728
  await uploadLogTail();
661
729
  }
662
730
  catch { /* spool stays on disk for the next tick */ }
731
+ // Settle any in-flight upgrade_cli action (success once the new build is
732
+ // live, failure when the target never arrived).
733
+ await verifyPendingUpgrade();
663
734
  };
664
735
  // --- boot ---------------------------------------------------------------
665
736
  const watched = refreshWatchTargets();
666
737
  log(`Watching ${watched} config file(s) across ${watchers.size} director${watchers.size === 1 ? 'y' : 'ies'}.`);
738
+ // First: if this boot IS the post-upgrade relaunch, confirm the pending
739
+ // upgrade_cli action before anything else touches the control plane.
740
+ await verifyPendingUpgrade();
667
741
  await pollBundle();
668
742
  // One protective pass at startup so a machine that drifted while the daemon
669
743
  // was down converges immediately. Runs BEFORE the first heartbeat: upgrades
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.18.7"
2
+ "version": "1.18.8"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.18.7",
3
+ "version": "1.18.8",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {