failproofai 1.0.9-beta.2 → 1.0.9

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.
Files changed (118) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  30. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/index.html +1 -1
  33. package/.next/standalone/.next/server/app/index.rsc +14 -14
  34. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  35. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  36. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  37. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  38. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  41. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  42. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  45. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  48. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  55. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  58. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  59. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  62. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ox4w55._.js → [root-of-the-server]__1ec94jt._.js} +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{_0n6zrjv._.js → _0xq-p5o._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/{_1pa3k5k._.js → _1g7s7di._.js} +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  84. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  85. package/.next/standalone/.next/server/pages/404.html +1 -1
  86. package/.next/standalone/.next/server/pages/500.html +1 -1
  87. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  89. package/.next/standalone/.next/static/chunks/{42-l25bbfo0ec.js → 03madt33xbsx2.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/0w8_n30wmnorq.js +1 -0
  91. package/.next/standalone/.next/static/chunks/{2b59i-n9l-4of.js → 0zlixskj11-yh.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/{0h--izcpk__m9.js → 1-n1o5wh4wfy6.js} +1 -1
  93. package/.next/standalone/.next/static/chunks/{30z-240_lrczt.js → 1t6tmwe_1myeo.js} +1 -1
  94. package/.next/standalone/.next/static/chunks/28z6u4u7hdrnd.js +1 -0
  95. package/.next/standalone/.next/static/chunks/{28cmd5molgwa3.js → 2dj6xq9mueqb6.js} +1 -1
  96. package/.next/standalone/.next/static/chunks/{1srecm9k-z_i1.js → 2lczelmvvc2yg.js} +1 -1
  97. package/.next/standalone/.next/static/chunks/3fbsuyflx-2qi.js +1 -0
  98. package/.next/standalone/.next/static/chunks/3jffngwvgxm5q.js +6 -0
  99. package/.next/standalone/package.json +9 -9
  100. package/.next/standalone/sdk/typescript/CHANGELOG.md +39 -0
  101. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +3 -3
  102. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
  103. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +16 -113
  104. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +3 -1
  105. package/.next/standalone/server.js +1 -1
  106. package/dist/cli.mjs +230 -115
  107. package/dist/worker.mjs +1 -1
  108. package/package.json +9 -9
  109. package/src/hooks/daemon-service.ts +14 -1
  110. package/src/hooks/integrations.ts +108 -26
  111. package/src/hooks/safe-config-write.ts +80 -7
  112. package/.next/standalone/.next/static/chunks/2k5vhij_w1g82.js +0 -1
  113. package/.next/standalone/.next/static/chunks/2o1fy-waqn1he.js +0 -6
  114. package/.next/standalone/.next/static/chunks/38gpf5tb_rd0m.js +0 -1
  115. package/.next/standalone/.next/static/chunks/3nyf50dub1vuk.js +0 -1
  116. /package/.next/standalone/.next/static/{D1BjwOkHNPetPo47DhV4_ → Q2PyL8ZdGGdvVnn-3FrL2}/_buildManifest.js +0 -0
  117. /package/.next/standalone/.next/static/{D1BjwOkHNPetPo47DhV4_ → Q2PyL8ZdGGdvVnn-3FrL2}/_clientMiddlewareManifest.js +0 -0
  118. /package/.next/standalone/.next/static/{D1BjwOkHNPetPo47DhV4_ → Q2PyL8ZdGGdvVnn-3FrL2}/_ssgManifest.js +0 -0
package/dist/cli.mjs CHANGED
@@ -29,7 +29,7 @@ var __esm = (fn, res, err) => () => {
29
29
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
31
  // package.json
32
- var version2 = "1.0.9-beta.2", package_default;
32
+ var version2 = "1.0.9", package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "failproofai",
@@ -155,16 +155,16 @@ var init_package = __esm(() => {
155
155
  browserslist: "4.28.8"
156
156
  },
157
157
  optionalDependencies: {
158
- "@failproofai/failproofaid-linux-x64": "1.0.9-beta.2",
159
- "@failproofai/failproofaid-linux-arm64": "1.0.9-beta.2",
160
- "@failproofai/failproofaid-darwin-x64": "1.0.9-beta.2",
161
- "@failproofai/failproofaid-darwin-arm64": "1.0.9-beta.2"
158
+ "@failproofai/failproofaid-linux-x64": "1.0.9",
159
+ "@failproofai/failproofaid-linux-arm64": "1.0.9",
160
+ "@failproofai/failproofaid-darwin-x64": "1.0.9",
161
+ "@failproofai/failproofaid-darwin-arm64": "1.0.9"
162
162
  },
163
163
  failproofaidBinaries: {
164
- "linux-x64": "cd0a7124ae94ae78753cda6ffad985ba30be80443d8bc93d617ce507ba04e211",
165
- "linux-arm64": "925446186bbfa827275356a9dc80b7d5351a6dcce9d926731419bbd91c1f98ba",
166
- "darwin-x64": "cd35a6549f402b6c04848772074edb110a54a242fe7488792e59f8e8a00af475",
167
- "darwin-arm64": "587b045accada4947a556e1bc1d748780a846c2ef95cf8dafb973a23fdbcb173"
164
+ "linux-x64": "c406199fbd5682ee84eb3bdd679fe48d1f78a26e97710f05bcfe42771e33ca4a",
165
+ "linux-arm64": "8ec339035013aaf069bd882660b89eefde137a5fb3c66bed9c65bfbfa1afaa19",
166
+ "darwin-x64": "fdf77e2432131ff6081769555c0a87d709d3380c78bc33de92743694dd20a83b",
167
+ "darwin-arm64": "a85e7e2af98fbcc09b6dfdd7fba8289d309757eab6482770ca64d3eeb81407d5"
168
168
  }
169
169
  };
170
170
  });
@@ -12496,12 +12496,14 @@ var init_dist = __esm(() => {
12496
12496
  import {
12497
12497
  chmodSync as chmodSync2,
12498
12498
  closeSync as closeSync2,
12499
+ constants as fsConstants2,
12499
12500
  copyFileSync,
12500
12501
  existsSync as existsSync7,
12501
12502
  fsyncSync,
12502
12503
  lstatSync,
12503
12504
  mkdirSync as mkdirSync4,
12504
12505
  openSync as openSync2,
12506
+ readlinkSync,
12505
12507
  realpathSync as realpathSync2,
12506
12508
  renameSync as renameSync2,
12507
12509
  rmSync as rmSync2,
@@ -12511,19 +12513,50 @@ import {
12511
12513
  import { randomBytes } from "node:crypto";
12512
12514
  import { basename, dirname as dirname3, join as join3 } from "node:path";
12513
12515
  function resolveWriteTarget(path) {
12516
+ let isLink = false;
12514
12517
  try {
12515
- if (lstatSync(path).isSymbolicLink())
12516
- return realpathSync2(path);
12517
- } catch {}
12518
- return path;
12518
+ isLink = lstatSync(path).isSymbolicLink();
12519
+ } catch {
12520
+ return path;
12521
+ }
12522
+ if (!isLink)
12523
+ return path;
12524
+ try {
12525
+ return realpathSync2(path);
12526
+ } catch {
12527
+ let target = "(unreadable link)";
12528
+ try {
12529
+ target = readlinkSync(path);
12530
+ } catch {}
12531
+ throw new DanglingConfigSymlinkError(path, target);
12532
+ }
12533
+ }
12534
+ function isSymlink(path) {
12535
+ try {
12536
+ return lstatSync(path).isSymbolicLink();
12537
+ } catch {
12538
+ return false;
12539
+ }
12540
+ }
12541
+ function writeBackup(target, dir) {
12542
+ const backup = `${target}.failproofai-backup`;
12543
+ const temporary = join3(dir, `.${basename(target)}.failproofai-backup-${process.pid}-${randomBytes(4).toString("hex")}.tmp`);
12544
+ try {
12545
+ copyFileSync(target, temporary, fsConstants2.COPYFILE_EXCL);
12546
+ renameSync2(temporary, backup);
12547
+ } catch (err) {
12548
+ rmSync2(temporary, { force: true });
12549
+ throw err;
12550
+ }
12519
12551
  }
12520
12552
  function writeConfigFileAtomic(path, content, deps = {}) {
12521
- const target = resolveWriteTarget(path);
12553
+ const replacingLink = deps.symlinks === "replace" && isSymlink(path);
12554
+ const target = deps.symlinks === "replace" ? path : resolveWriteTarget(path);
12522
12555
  const dir = dirname3(target);
12523
12556
  mkdirSync4(dir, { recursive: true });
12524
12557
  let mode;
12525
12558
  try {
12526
- mode = statSync5(target).mode & 511;
12559
+ mode = replacingLink ? undefined : statSync5(target).mode & 511;
12527
12560
  } catch {
12528
12561
  mode = undefined;
12529
12562
  }
@@ -12540,8 +12573,8 @@ function writeConfigFileAtomic(path, content, deps = {}) {
12540
12573
  fd = undefined;
12541
12574
  if (mode !== undefined)
12542
12575
  chmodSync2(temporary, mode);
12543
- if (existsSync7(target))
12544
- copyFileSync(target, `${target}.failproofai-backup`);
12576
+ if (deps.backup !== false && !replacingLink && existsSync7(target))
12577
+ writeBackup(target, dir);
12545
12578
  (deps.rename ?? renameSync2)(temporary, target);
12546
12579
  } catch (err) {
12547
12580
  if (fd !== undefined) {
@@ -12561,7 +12594,19 @@ function writeConfigFileAtomic(path, content, deps = {}) {
12561
12594
  }
12562
12595
  } catch {}
12563
12596
  }
12564
- var init_safe_config_write = () => {};
12597
+ var DanglingConfigSymlinkError;
12598
+ var init_safe_config_write = __esm(() => {
12599
+ DanglingConfigSymlinkError = class DanglingConfigSymlinkError extends Error {
12600
+ path;
12601
+ target;
12602
+ constructor(path, target) {
12603
+ super(`Refusing to write ${path}: it is a symlink to ${target}, which does not exist. ` + `Fix or remove the link, then retry — failproofai will not replace it with a regular file.`);
12604
+ this.path = path;
12605
+ this.target = target;
12606
+ this.name = "DanglingConfigSymlinkError";
12607
+ }
12608
+ };
12609
+ });
12565
12610
 
12566
12611
  // lib/hermes-profiles.ts
12567
12612
  import { existsSync as existsSync8, readdirSync } from "node:fs";
@@ -12704,7 +12749,7 @@ import {
12704
12749
  existsSync as existsSync10,
12705
12750
  lstatSync as lstatSync2,
12706
12751
  mkdirSync as mkdirSync5,
12707
- readlinkSync,
12752
+ readlinkSync as readlinkSync2,
12708
12753
  realpathSync as realpathSync3,
12709
12754
  renameSync as renameSync3,
12710
12755
  rmSync as rmSync3,
@@ -13076,14 +13121,38 @@ function pathPresent(path) {
13076
13121
  function hasAllHermesPluginFiles(dir) {
13077
13122
  return HERMES_PLUGIN_FILES.every((file) => existsSync10(resolve8(dir, file)));
13078
13123
  }
13079
- function isFailproofaiHermesPluginSource(dir) {
13080
- if (basename4(dir) !== HERMES_PLUGIN_SOURCE_DIR)
13081
- return false;
13082
- if (!existsSync10(dir))
13124
+ function hermesLinkRecordPath(pluginPath) {
13125
+ return resolve8(dirname6(pluginPath), HERMES_LINK_RECORD);
13126
+ }
13127
+ function readHermesLinkRecord(pluginPath) {
13128
+ try {
13129
+ const recorded = readFileSync5(hermesLinkRecordPath(pluginPath), "utf8").trim();
13130
+ return recorded === "" ? null : resolve8(recorded);
13131
+ } catch {
13132
+ return null;
13133
+ }
13134
+ }
13135
+ function writeHermesLinkRecord(pluginPath, target) {
13136
+ writeConfigFileAtomic(hermesLinkRecordPath(pluginPath), resolve8(target) + `
13137
+ `, { backup: false, symlinks: "replace" });
13138
+ }
13139
+ function removeHermesLinkRecord(pluginPath) {
13140
+ rmSync3(hermesLinkRecordPath(pluginPath), { force: true });
13141
+ }
13142
+ function isOwnedHermesPluginLink(pluginPath, target) {
13143
+ const recorded = readHermesLinkRecord(pluginPath);
13144
+ if (recorded && resolve8(target) === recorded)
13083
13145
  return true;
13146
+ if (samePath(target, getHermesPluginSourcePath()))
13147
+ return true;
13148
+ const pkgRoot = dirname6(resolve8(target));
13149
+ if (basename4(resolve8(target)) !== HERMES_PLUGIN_SOURCE_DIR)
13150
+ return false;
13151
+ if (basename4(pkgRoot) !== "failproofai" || basename4(dirname6(pkgRoot)) !== "node_modules")
13152
+ return false;
13084
13153
  try {
13085
- const manifest = readFileSync5(resolve8(dir, "plugin.yaml"), "utf8");
13086
- return /^name:\s*["']?failproofai["']?\s*$/m.test(manifest);
13154
+ const pkg = JSON.parse(readFileSync5(resolve8(pkgRoot, "package.json"), "utf8"));
13155
+ return pkg.name === "failproofai";
13087
13156
  } catch {
13088
13157
  return false;
13089
13158
  }
@@ -13111,11 +13180,11 @@ function hermesPluginState(settingsPath) {
13111
13180
  if (isLink) {
13112
13181
  let target;
13113
13182
  try {
13114
- target = resolve8(dirname6(pluginPath), readlinkSync(pluginPath));
13183
+ target = resolve8(dirname6(pluginPath), readlinkSync2(pluginPath));
13115
13184
  } catch {
13116
13185
  return { kind: "foreign" };
13117
13186
  }
13118
- if (!isFailproofaiHermesPluginSource(target))
13187
+ if (!isOwnedHermesPluginLink(pluginPath, target))
13119
13188
  return { kind: "foreign" };
13120
13189
  return {
13121
13190
  kind: "link",
@@ -13148,8 +13217,14 @@ function installHermesPlugin(settingsPath, deps = {}) {
13148
13217
  if (existing.kind === "foreign") {
13149
13218
  throw new Error(`Refusing to overwrite an unmanaged Hermes plugin at ${destination}. ` + `Move it aside or install FailproofAI under a different Hermes profile.`);
13150
13219
  }
13151
- if (existing.kind === "link" && existing.current)
13220
+ if (existing.kind === "link" && existing.current) {
13221
+ if (readHermesLinkRecord(destination) !== resolve8(existing.target)) {
13222
+ try {
13223
+ writeHermesLinkRecord(destination, existing.target);
13224
+ } catch {}
13225
+ }
13152
13226
  return "unchanged";
13227
+ }
13153
13228
  mkdirSync5(dirname6(destination), { recursive: true });
13154
13229
  const suffix = `${process.pid}-${Date.now()}`;
13155
13230
  const temporary = `${destination}.install-${suffix}`;
@@ -13175,6 +13250,7 @@ function installHermesPlugin(settingsPath, deps = {}) {
13175
13250
  if (linked && (existing.kind === "absent" || existing.kind === "link")) {
13176
13251
  try {
13177
13252
  renameSync3(temporary, destination);
13253
+ writeHermesLinkRecord(destination, source);
13178
13254
  return "linked";
13179
13255
  } catch (err) {
13180
13256
  if (existing.kind !== "link") {
@@ -13193,6 +13269,10 @@ function installHermesPlugin(settingsPath, deps = {}) {
13193
13269
  renameSync3(temporary, destination);
13194
13270
  if (movedExisting)
13195
13271
  rmSync3(backup, { recursive: true, force: true });
13272
+ if (linked)
13273
+ writeHermesLinkRecord(destination, source);
13274
+ else
13275
+ removeHermesLinkRecord(destination);
13196
13276
  } catch (err) {
13197
13277
  rmSync3(temporary, { recursive: true, force: true });
13198
13278
  if (movedExisting && !pathPresent(destination) && pathPresent(backup)) {
@@ -13207,6 +13287,7 @@ function removeManagedHermesPlugin(settingsPath) {
13207
13287
  const state = hermesPluginState(settingsPath);
13208
13288
  if (state.kind === "link") {
13209
13289
  unlinkSync(pluginPath);
13290
+ removeHermesLinkRecord(pluginPath);
13210
13291
  return true;
13211
13292
  }
13212
13293
  if (state.kind !== "copy")
@@ -13281,85 +13362,90 @@ async function migrateHermesProfiles(opts) {
13281
13362
  const results = [];
13282
13363
  let daemonOk;
13283
13364
  for (const profile of listHermesProfiles()) {
13284
- if (!existsSync10(profile.home))
13285
- continue;
13286
- const settingsPath = resolve8(profile.home, "config.yaml");
13287
- const pluginPath = hermesPluginPathForSettings(settingsPath);
13288
- const config = hermesConfigState(settingsPath);
13289
- const plugin = hermesPluginState(settingsPath);
13290
- const ours = plugin.kind === "link" || plugin.kind === "copy";
13291
- const base = { name: profile.name, home: profile.home };
13292
- const legacy = config.legacyShellHookPresent;
13293
- if (config.unreadable) {
13294
- let raw = "";
13295
- try {
13296
- raw = readFileSync5(settingsPath, "utf8");
13297
- } catch {
13298
- raw = "";
13365
+ try {
13366
+ if (!existsSync10(profile.home))
13367
+ continue;
13368
+ const settingsPath = resolve8(profile.home, "config.yaml");
13369
+ const pluginPath = hermesPluginPathForSettings(settingsPath);
13370
+ const config = hermesConfigState(settingsPath);
13371
+ const plugin = hermesPluginState(settingsPath);
13372
+ const ours = plugin.kind === "link" || plugin.kind === "copy";
13373
+ const base = { name: profile.name, home: profile.home };
13374
+ const legacy = config.legacyShellHookPresent;
13375
+ if (config.unreadable) {
13376
+ let raw = "";
13377
+ try {
13378
+ raw = readFileSync5(settingsPath, "utf8");
13379
+ } catch {
13380
+ raw = "";
13381
+ }
13382
+ const mentionsUs = ours || raw.includes(FAILPROOFAI_HOOK_MARKER) || /failproofai/.test(raw);
13383
+ results.push(mentionsUs ? {
13384
+ ...base,
13385
+ status: "failed",
13386
+ detail: `${settingsPath} does not parse as YAML; left untouched`,
13387
+ legacyShellHooksRemain: /--hook\s+\S+\s+--cli\s+hermes|__failproofai_hook__/.test(raw)
13388
+ } : { ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
13389
+ continue;
13299
13390
  }
13300
- const mentionsUs = ours || raw.includes(FAILPROOFAI_HOOK_MARKER) || /failproofai/.test(raw);
13301
- results.push(mentionsUs ? {
13302
- ...base,
13303
- status: "failed",
13304
- detail: `${settingsPath} does not parse as YAML; left untouched`,
13305
- legacyShellHooksRemain: /--hook\s+\S+\s+--cli\s+hermes|__failproofai_hook__/.test(raw)
13306
- } : { ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
13307
- continue;
13308
- }
13309
- if (!legacy && !ours && !config.pluginListed && !config.pluginDisabled) {
13310
- results.push({ ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
13311
- continue;
13312
- }
13313
- if (plugin.kind === "link" && plugin.current && plugin.complete && config.pluginEnabled && !legacy) {
13314
- results.push({ ...base, status: "current", detail: "already linked", legacyShellHooksRemain: false });
13315
- continue;
13316
- }
13317
- if (plugin.kind === "foreign") {
13318
- results.push({
13319
- ...base,
13320
- status: "failed",
13321
- detail: `an unmanaged plugin occupies ${pluginPath}` + (legacy ? "; shell hooks left in place (cron jobs are not checked)" : ""),
13322
- legacyShellHooksRemain: legacy
13323
- });
13324
- continue;
13325
- }
13326
- if (existsSync10(settingsPath)) {
13327
- const errors = $parseDocument(readFileSync5(settingsPath, "utf8")).errors;
13328
- if (errors.length > 0) {
13391
+ if (!legacy && !ours && !config.pluginListed && !config.pluginDisabled) {
13392
+ results.push({ ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
13393
+ continue;
13394
+ }
13395
+ if (plugin.kind === "link" && plugin.current && plugin.complete && config.pluginEnabled && !legacy) {
13396
+ if (readHermesLinkRecord(pluginPath) !== resolve8(plugin.target)) {
13397
+ try {
13398
+ writeHermesLinkRecord(pluginPath, plugin.target);
13399
+ } catch {}
13400
+ }
13401
+ results.push({ ...base, status: "current", detail: "already linked", legacyShellHooksRemain: false });
13402
+ continue;
13403
+ }
13404
+ if (plugin.kind === "foreign") {
13329
13405
  results.push({
13330
13406
  ...base,
13331
13407
  status: "failed",
13332
- detail: `${settingsPath} does not parse as YAML; left untouched`,
13408
+ detail: `an unmanaged plugin occupies ${pluginPath}` + (legacy ? "; shell hooks left in place (cron jobs are not checked)" : ""),
13333
13409
  legacyShellHooksRemain: legacy
13334
13410
  });
13335
13411
  continue;
13336
13412
  }
13337
- }
13338
- if (daemonOk === undefined)
13339
- daemonOk = await opts.daemonSupportsPolicyEvaluation();
13340
- if (!daemonOk) {
13341
- results.push({
13342
- ...base,
13343
- status: "blocked",
13344
- detail: legacy ? "daemon lacks native policy evaluation; shell hooks left in place (cron jobs are not checked)" : "daemon lacks native policy evaluation; the plugin here cannot get verdicts",
13345
- legacyShellHooksRemain: legacy
13346
- });
13347
- continue;
13348
- }
13349
- try {
13350
- const mode = installHermesPlugin(settingsPath, opts.installDeps);
13351
- const doc = readYamlDoc(settingsPath);
13352
- enableHermesPluginInDoc(doc);
13353
- writeYamlDoc(settingsPath, doc);
13354
- const from = legacy ? "shell hooks" : plugin.kind === "copy" ? "copied plugin" : plugin.kind === "link" ? "old plugin link" : "enabled but missing plugin";
13355
- const to = mode === "copied" ? "plugin copy (symlink not possible here)" : "linked plugin";
13356
- results.push({ ...base, status: "migrated", detail: `${from} → ${to}`, legacyShellHooksRemain: false });
13413
+ if (daemonOk === undefined)
13414
+ daemonOk = await opts.daemonSupportsPolicyEvaluation();
13415
+ if (!daemonOk) {
13416
+ results.push({
13417
+ ...base,
13418
+ status: "blocked",
13419
+ detail: legacy ? "daemon lacks native policy evaluation; shell hooks left in place (cron jobs are not checked)" : "daemon lacks native policy evaluation; the plugin here cannot get verdicts",
13420
+ legacyShellHooksRemain: legacy
13421
+ });
13422
+ continue;
13423
+ }
13424
+ try {
13425
+ const mode = installHermesPlugin(settingsPath, opts.installDeps);
13426
+ const doc = readYamlDoc(settingsPath);
13427
+ enableHermesPluginInDoc(doc);
13428
+ writeYamlDoc(settingsPath, doc);
13429
+ const reenabledOnly = !legacy && plugin.kind === "link" && plugin.current && mode === "unchanged";
13430
+ const from = legacy ? "shell hooks" : plugin.kind === "copy" ? "copied plugin" : plugin.kind === "link" ? "old plugin link" : "enabled but missing plugin";
13431
+ const to = mode === "copied" ? "plugin copy (symlink not possible here)" : "linked plugin";
13432
+ const detail = reenabledOnly ? config.pluginDisabled ? "plugin re-enabled (was listed in plugins.disabled)" : "plugin enabled in config.yaml" : `${from} → ${to}`;
13433
+ results.push({ ...base, status: "migrated", detail, legacyShellHooksRemain: false });
13434
+ } catch (err) {
13435
+ results.push({
13436
+ ...base,
13437
+ status: "failed",
13438
+ detail: err instanceof Error ? err.message : String(err),
13439
+ legacyShellHooksRemain: hermesConfigState(settingsPath).legacyShellHookPresent
13440
+ });
13441
+ }
13357
13442
  } catch (err) {
13358
13443
  results.push({
13359
- ...base,
13444
+ name: profile.name,
13445
+ home: profile.home,
13360
13446
  status: "failed",
13361
- detail: err instanceof Error ? err.message : String(err),
13362
- legacyShellHooksRemain: hermesConfigState(settingsPath).legacyShellHookPresent
13447
+ detail: `could not inspect this profile (${err instanceof Error ? err.message : String(err)}); left untouched`,
13448
+ legacyShellHooksRemain: false
13363
13449
  });
13364
13450
  }
13365
13451
  }
@@ -13402,7 +13488,7 @@ function listInstallableIds() {
13402
13488
  function detectInstalledClis() {
13403
13489
  return INTEGRATION_TYPES.filter((id) => INTEGRATIONS[id]?.detectInstalled());
13404
13490
  }
13405
- var UnreadableAgentConfigError, claudeCode, codex, copilot, cursor, OPENCODE_PLUGIN_REL_PATH = "./plugins/failproofai.mjs", opencode, pi, HERMES_PLUGIN_ID = "failproofai", HERMES_PLUGIN_MARKER = ".failproofai-managed", HERMES_PLUGIN_FILES, HERMES_PLUGIN_SOURCE_DIR = "hermes-plugin", hermes, OPENCLAW_PLUGIN_ID = "failproofai", openclaw, factory, devin, ANTIGRAVITY_HOOK_NAME = "failproofai", ANTIGRAVITY_TOOL_EVENTS, antigravity, goose, INTEGRATIONS;
13491
+ var UnreadableAgentConfigError, claudeCode, codex, copilot, cursor, OPENCODE_PLUGIN_REL_PATH = "./plugins/failproofai.mjs", opencode, pi, HERMES_PLUGIN_ID = "failproofai", HERMES_PLUGIN_MARKER = ".failproofai-managed", HERMES_PLUGIN_FILES, HERMES_PLUGIN_SOURCE_DIR = "hermes-plugin", HERMES_LINK_RECORD = ".failproofai-link", hermes, OPENCLAW_PLUGIN_ID = "failproofai", openclaw, factory, devin, ANTIGRAVITY_HOOK_NAME = "failproofai", ANTIGRAVITY_TOOL_EVENTS, antigravity, goose, INTEGRATIONS;
13406
13492
  var init_integrations = __esm(() => {
13407
13493
  init_dist();
13408
13494
  init_safe_config_write();
@@ -13920,7 +14006,7 @@ var init_integrations = __esm(() => {
13920
14006
  const settingsPath = effectiveScope === "user" ? resolve8(homedir6(), ".config", "opencode", "opencode.json") : resolve8(process.cwd(), ".opencode", "opencode.json");
13921
14007
  const pluginPath = opencodePluginFilePath(settingsPath);
13922
14008
  mkdirSync5(dirname6(pluginPath), { recursive: true });
13923
- writeConfigFileAtomic(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope));
14009
+ writeConfigFileAtomic(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope), { symlinks: "replace" });
13924
14010
  if (!Array.isArray(s.plugin))
13925
14011
  s.plugin = [];
13926
14012
  const desired = effectiveScope === "user" ? `file://${pluginPath}` : OPENCODE_PLUGIN_REL_PATH;
@@ -27062,7 +27148,7 @@ var init_fp_reset = __esm(() => {
27062
27148
  });
27063
27149
 
27064
27150
  // src/hooks/semantic/jev-config.ts
27065
- import { closeSync as closeSync3, constants as fsConstants2, fstatSync as fstatSync2, openSync as openSync3, readSync as readSync2, statSync as statSync19 } from "node:fs";
27151
+ import { closeSync as closeSync3, constants as fsConstants3, fstatSync as fstatSync2, openSync as openSync3, readSync as readSync2, statSync as statSync19 } from "node:fs";
27066
27152
  import { dirname as dirname13 } from "node:path";
27067
27153
  function parseJevMode(raw) {
27068
27154
  return raw === "off" || raw === "observe" || raw === "enforce" ? raw : null;
@@ -27537,7 +27623,7 @@ var init_jev_config = __esm(() => {
27537
27623
  JEV_PROVIDER_KINDS = ["typesafe", "openrouter", "vercel", "cloudflare", "custom", "failproofai"];
27538
27624
  JEV_CALIBRATED_FAMILY = { major: 1, minor: 13 };
27539
27625
  MAX_CONFIG_BYTES = 64 * 1024;
27540
- OPEN_FLAGS = fsConstants2.O_RDONLY | (fsConstants2.O_NONBLOCK ?? 0);
27626
+ OPEN_FLAGS = fsConstants3.O_RDONLY | (fsConstants3.O_NONBLOCK ?? 0);
27541
27627
  VERSIONED_MODEL_RE = /^(?:(?:typesafe|typesafe-ai)\/)?jev-(\d{1,4})\.(\d{1,4})(?:\.(\d{1,6}))?(?:-(\d{8}))?$/;
27542
27628
  CLOUDFLARE_ACCOUNT_ID_RE = /^[0-9a-f]{32}$/;
27543
27629
  API_KEY_RE = /^[\x21-\x7e]{1,4096}$/;
@@ -32786,7 +32872,7 @@ var INTROSPECT_PATH = "/v1/auth/introspect", PERMISSION_EVENTS = "events:add", P
32786
32872
  import {
32787
32873
  chmodSync as chmodSync7,
32788
32874
  closeSync as closeSync5,
32789
- constants as fsConstants3,
32875
+ constants as fsConstants4,
32790
32876
  existsSync as existsSync34,
32791
32877
  fstatSync as fstatSync3,
32792
32878
  linkSync,
@@ -32979,7 +33065,7 @@ function fieldsOf(file) {
32979
33065
  const none = { provider: null, off: false };
32980
33066
  let fd;
32981
33067
  try {
32982
- fd = openSync5(file, fsConstants3.O_RDONLY | (fsConstants3.O_NONBLOCK ?? 0) | (fsConstants3.O_NOFOLLOW ?? 0));
33068
+ fd = openSync5(file, fsConstants4.O_RDONLY | (fsConstants4.O_NONBLOCK ?? 0) | (fsConstants4.O_NOFOLLOW ?? 0));
32983
33069
  } catch {
32984
33070
  return none;
32985
33071
  }
@@ -38952,7 +39038,7 @@ var init_jev_cli = __esm(() => {
38952
39038
  import {
38953
39039
  chmodSync as chmodSync10,
38954
39040
  closeSync as closeSync8,
38955
- constants as fsConstants4,
39041
+ constants as fsConstants5,
38956
39042
  existsSync as existsSync46,
38957
39043
  fstatSync as fstatSync4,
38958
39044
  mkdirSync as mkdirSync29,
@@ -39736,9 +39822,14 @@ async function refreshDaemonToCliVersion(deps = {}) {
39736
39822
  const install = deps.install ?? installDaemonService2;
39737
39823
  const prime = deps.prime ?? primeElevation2;
39738
39824
  const interactive = deps.interactive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY));
39739
- if (status() === "not-installed") {
39825
+ const current = status();
39826
+ if (current === "not-installed") {
39740
39827
  return { ok: true, lines: ["No failproofaid service on this machine; nothing to update."] };
39741
39828
  }
39829
+ const recorded = readVersionFile()?.daemon;
39830
+ if (current === "running" && recorded === version2 && existsSync48(installedBinaryPath(version2))) {
39831
+ return { ok: true, lines: [`failproofaid ${version2} is already installed and running; nothing to update.`] };
39832
+ }
39742
39833
  if (interactive())
39743
39834
  prime();
39744
39835
  const result = await install();
@@ -44219,7 +44310,7 @@ import {
44219
44310
  existsSync as existsSync54,
44220
44311
  lstatSync as lstatSync4,
44221
44312
  mkdirSync as mkdirSync33,
44222
- readlinkSync as readlinkSync2,
44313
+ readlinkSync as readlinkSync3,
44223
44314
  realpathSync as realpathSync8,
44224
44315
  renameSync as renameSync14,
44225
44316
  rmSync as rmSync21,
@@ -44286,14 +44377,31 @@ function hermesPluginPathForSettings2(settingsPath) {
44286
44377
  function hasAllHermesPluginFiles2(dir) {
44287
44378
  return HERMES_PLUGIN_FILES2.every((file) => existsSync54(resolve39(dir, file)));
44288
44379
  }
44289
- function isFailproofaiHermesPluginSource2(dir) {
44290
- if (basename13(dir) !== HERMES_PLUGIN_SOURCE_DIR2)
44291
- return false;
44292
- if (!existsSync54(dir))
44380
+ function hermesLinkRecordPath2(pluginPath) {
44381
+ return resolve39(dirname30(pluginPath), HERMES_LINK_RECORD2);
44382
+ }
44383
+ function readHermesLinkRecord2(pluginPath) {
44384
+ try {
44385
+ const recorded = readFileSync41(hermesLinkRecordPath2(pluginPath), "utf8").trim();
44386
+ return recorded === "" ? null : resolve39(recorded);
44387
+ } catch {
44388
+ return null;
44389
+ }
44390
+ }
44391
+ function isOwnedHermesPluginLink2(pluginPath, target) {
44392
+ const recorded = readHermesLinkRecord2(pluginPath);
44393
+ if (recorded && resolve39(target) === recorded)
44293
44394
  return true;
44395
+ if (samePath2(target, getHermesPluginSourcePath2()))
44396
+ return true;
44397
+ const pkgRoot = dirname30(resolve39(target));
44398
+ if (basename13(resolve39(target)) !== HERMES_PLUGIN_SOURCE_DIR2)
44399
+ return false;
44400
+ if (basename13(pkgRoot) !== "failproofai" || basename13(dirname30(pkgRoot)) !== "node_modules")
44401
+ return false;
44294
44402
  try {
44295
- const manifest = readFileSync41(resolve39(dir, "plugin.yaml"), "utf8");
44296
- return /^name:\s*["']?failproofai["']?\s*$/m.test(manifest);
44403
+ const pkg = JSON.parse(readFileSync41(resolve39(pkgRoot, "package.json"), "utf8"));
44404
+ return pkg.name === "failproofai";
44297
44405
  } catch {
44298
44406
  return false;
44299
44407
  }
@@ -44321,11 +44429,11 @@ function hermesPluginState2(settingsPath) {
44321
44429
  if (isLink) {
44322
44430
  let target;
44323
44431
  try {
44324
- target = resolve39(dirname30(pluginPath), readlinkSync2(pluginPath));
44432
+ target = resolve39(dirname30(pluginPath), readlinkSync3(pluginPath));
44325
44433
  } catch {
44326
44434
  return { kind: "foreign" };
44327
44435
  }
44328
- if (!isFailproofaiHermesPluginSource2(target))
44436
+ if (!isOwnedHermesPluginLink2(pluginPath, target))
44329
44437
  return { kind: "foreign" };
44330
44438
  return {
44331
44439
  kind: "link",
@@ -44378,6 +44486,7 @@ function hermesProfileHealth() {
44378
44486
  legacyShellHookPresent: config.legacyShellHookPresent,
44379
44487
  cronUnchecked: config.legacyShellHookPresent && !pluginWorks,
44380
44488
  configUnreadable: config.unreadable === true,
44489
+ pluginForeign: plugin.kind === "foreign",
44381
44490
  healthy: pluginWorks && !config.legacyShellHookPresent && config.unreadable !== true
44382
44491
  };
44383
44492
  });
@@ -44385,6 +44494,12 @@ function hermesProfileHealth() {
44385
44494
  function hermesProfileStatusRows() {
44386
44495
  return hermesProfileHealth().filter((profile) => existsSync54(profile.home)).map((profile) => {
44387
44496
  const label = "hermes/" + profile.name;
44497
+ if (profile.pluginForeign) {
44498
+ return [
44499
+ label,
44500
+ `UNHEALTHY — another plugin occupies ${resolve39(profile.home, "plugins", "failproofai")}; failproofai will not replace it. Move it aside, then run update`
44501
+ ];
44502
+ }
44388
44503
  if (profile.configUnreadable) {
44389
44504
  return [label, `UNHEALTHY — ${profile.settingsPath} does not parse; failproofai leaves it untouched until it is fixed`];
44390
44505
  }
@@ -44411,7 +44526,7 @@ function hermesProfileStatusRows() {
44411
44526
  ];
44412
44527
  });
44413
44528
  }
44414
- var UnreadableAgentConfigError2, HERMES_PLUGIN_ID2 = "failproofai", HERMES_PLUGIN_MARKER2 = ".failproofai-managed", HERMES_PLUGIN_FILES2, HERMES_PLUGIN_SOURCE_DIR2 = "hermes-plugin", ANTIGRAVITY_TOOL_EVENTS2;
44529
+ var UnreadableAgentConfigError2, HERMES_PLUGIN_ID2 = "failproofai", HERMES_PLUGIN_MARKER2 = ".failproofai-managed", HERMES_PLUGIN_FILES2, HERMES_PLUGIN_SOURCE_DIR2 = "hermes-plugin", HERMES_LINK_RECORD2 = ".failproofai-link", ANTIGRAVITY_TOOL_EVENTS2;
44415
44530
  var init_integrations2 = __esm(() => {
44416
44531
  init_dist();
44417
44532
  init_safe_config_write();
@@ -44559,7 +44674,7 @@ import { realpathSync as realpathSync10 } from "fs";
44559
44674
  import { dirname as dirname32, resolve as resolve41 } from "path";
44560
44675
  import { fileURLToPath as fileURLToPath5 } from "url";
44561
44676
  // package.json
44562
- var version = "1.0.9-beta.2";
44677
+ var version = "1.0.9";
44563
44678
 
44564
44679
  // bin/failproofai.mjs
44565
44680
  if (!process.env.FAILPROOFAI_PACKAGE_ROOT) {
package/dist/worker.mjs CHANGED
@@ -1162,7 +1162,7 @@ function instruct(reason) {
1162
1162
  }
1163
1163
 
1164
1164
  // package.json
1165
- var version = "1.0.9-beta.2";
1165
+ var version = "1.0.9";
1166
1166
  var init_package = () => {};
1167
1167
 
1168
1168
  // src/hooks/policy-types.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.9-beta.2",
3
+ "version": "1.0.9",
4
4
  "description": "Observability and enforcement for AI agent harnesses. 40 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -122,15 +122,15 @@
122
122
  "browserslist": "4.28.8"
123
123
  },
124
124
  "optionalDependencies": {
125
- "@failproofai/failproofaid-linux-x64": "1.0.9-beta.2",
126
- "@failproofai/failproofaid-linux-arm64": "1.0.9-beta.2",
127
- "@failproofai/failproofaid-darwin-x64": "1.0.9-beta.2",
128
- "@failproofai/failproofaid-darwin-arm64": "1.0.9-beta.2"
125
+ "@failproofai/failproofaid-linux-x64": "1.0.9",
126
+ "@failproofai/failproofaid-linux-arm64": "1.0.9",
127
+ "@failproofai/failproofaid-darwin-x64": "1.0.9",
128
+ "@failproofai/failproofaid-darwin-arm64": "1.0.9"
129
129
  },
130
130
  "failproofaidBinaries": {
131
- "linux-x64": "cd0a7124ae94ae78753cda6ffad985ba30be80443d8bc93d617ce507ba04e211",
132
- "linux-arm64": "925446186bbfa827275356a9dc80b7d5351a6dcce9d926731419bbd91c1f98ba",
133
- "darwin-x64": "cd35a6549f402b6c04848772074edb110a54a242fe7488792e59f8e8a00af475",
134
- "darwin-arm64": "587b045accada4947a556e1bc1d748780a846c2ef95cf8dafb973a23fdbcb173"
131
+ "linux-x64": "c406199fbd5682ee84eb3bdd679fe48d1f78a26e97710f05bcfe42771e33ca4a",
132
+ "linux-arm64": "8ec339035013aaf069bd882660b89eefde137a5fb3c66bed9c65bfbfa1afaa19",
133
+ "darwin-x64": "fdf77e2432131ff6081769555c0a87d709d3380c78bc33de92743694dd20a83b",
134
+ "darwin-arm64": "a85e7e2af98fbcc09b6dfdd7fba8289d309757eab6482770ca64d3eeb81407d5"
135
135
  }
136
136
  }
@@ -1556,10 +1556,23 @@ export async function refreshDaemonToCliVersion(
1556
1556
  const prime = deps.prime ?? primeElevation;
1557
1557
  const interactive =
1558
1558
  deps.interactive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY));
1559
- if (status() === "not-installed") {
1559
+ const current = status();
1560
+ if (current === "not-installed") {
1560
1561
  return { ok: true, lines: ["No failproofaid service on this machine; nothing to update."] };
1561
1562
  }
1562
1563
 
1564
+ // Already this version: nothing to install, so nothing to ask root for. The
1565
+ // reinstall below is idempotent, but it needs sudo, so without this every
1566
+ // `update` after the daemon was brought current either prompted for a password
1567
+ // for no reason or — with no TTY to prompt on (a fleet box, CI) — failed with
1568
+ // "root privileges are required" and exited 1 on a machine that was fine.
1569
+ // `VERSION.daemon` is written only after an install succeeds, and the binary
1570
+ // it names must still be on disk.
1571
+ const recorded = readVersionFile()?.daemon;
1572
+ if (current === "running" && recorded === version && existsSync(installedBinaryPath(version))) {
1573
+ return { ok: true, lines: [`failproofaid ${version} is already installed and running; nothing to update.`] };
1574
+ }
1575
+
1563
1576
  // `installDaemonService()`, not a binary fetch plus a restart.
1564
1577
  //
1565
1578
  // The first version of this did the latter, and it did not work: it called