@songsid/agend 2.2.0-alpha.2 → 2.2.0-alpha.4

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 (257) hide show
  1. package/README.md +4 -4
  2. package/README.zh-TW.md +4 -4
  3. package/dist/agent-endpoint.d.ts +4 -1
  4. package/dist/agent-endpoint.js +14 -2
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/atomic-write.d.ts +21 -0
  7. package/dist/atomic-write.js +65 -0
  8. package/dist/atomic-write.js.map +1 -0
  9. package/dist/auth-api.js +2 -0
  10. package/dist/auth-api.js.map +1 -1
  11. package/dist/backend/codex.d.ts +7 -0
  12. package/dist/backend/codex.js +7 -0
  13. package/dist/backend/codex.js.map +1 -1
  14. package/dist/backend/muse.d.ts +7 -0
  15. package/dist/backend/muse.js +7 -0
  16. package/dist/backend/muse.js.map +1 -1
  17. package/dist/backend/types.d.ts +6 -0
  18. package/dist/backend/types.js.map +1 -1
  19. package/dist/binary-probe.d.ts +80 -0
  20. package/dist/binary-probe.js +182 -0
  21. package/dist/binary-probe.js.map +1 -0
  22. package/dist/cache-service.d.ts +17 -1
  23. package/dist/cache-service.js +39 -4
  24. package/dist/cache-service.js.map +1 -1
  25. package/dist/channel/adapters/discord.d.ts +5 -1
  26. package/dist/channel/adapters/discord.js +56 -11
  27. package/dist/channel/adapters/discord.js.map +1 -1
  28. package/dist/channel/adapters/telegram.d.ts +38 -1
  29. package/dist/channel/adapters/telegram.js +82 -0
  30. package/dist/channel/adapters/telegram.js.map +1 -1
  31. package/dist/channel/mcp-tools.js +1 -1
  32. package/dist/channel/mcp-tools.js.map +1 -1
  33. package/dist/channel/tool-router.d.ts +8 -1
  34. package/dist/channel/tool-router.js +4 -2
  35. package/dist/channel/tool-router.js.map +1 -1
  36. package/dist/channel/types.d.ts +21 -0
  37. package/dist/cli.js +133 -38
  38. package/dist/cli.js.map +1 -1
  39. package/dist/connection-secrets.d.ts +5 -0
  40. package/dist/connection-secrets.js.map +1 -1
  41. package/dist/cpu-profile.js +50 -0
  42. package/dist/cpu-profile.js.map +1 -1
  43. package/dist/daemon.d.ts +12 -0
  44. package/dist/daemon.js +166 -26
  45. package/dist/daemon.js.map +1 -1
  46. package/dist/delivery-outbox.d.ts +1 -0
  47. package/dist/delivery-outbox.js +12 -0
  48. package/dist/delivery-outbox.js.map +1 -1
  49. package/dist/discord-general.d.ts +15 -0
  50. package/dist/discord-general.js +24 -0
  51. package/dist/discord-general.js.map +1 -0
  52. package/dist/discord-permissions.d.ts +33 -0
  53. package/dist/discord-permissions.js +42 -0
  54. package/dist/discord-permissions.js.map +1 -0
  55. package/dist/env-file.d.ts +11 -0
  56. package/dist/env-file.js +27 -0
  57. package/dist/env-file.js.map +1 -0
  58. package/dist/fleet-context.d.ts +5 -0
  59. package/dist/fleet-context.js.map +1 -1
  60. package/dist/fleet-level-config.d.ts +8 -1
  61. package/dist/fleet-level-config.js +31 -0
  62. package/dist/fleet-level-config.js.map +1 -1
  63. package/dist/fleet-manager.d.ts +76 -7
  64. package/dist/fleet-manager.js +610 -142
  65. package/dist/fleet-manager.js.map +1 -1
  66. package/dist/fleet-stop-budget.d.ts +22 -0
  67. package/dist/fleet-stop-budget.js +95 -0
  68. package/dist/fleet-stop-budget.js.map +1 -0
  69. package/dist/kiro-transcript-lane.d.ts +8 -4
  70. package/dist/kiro-transcript-lane.js +66 -44
  71. package/dist/kiro-transcript-lane.js.map +1 -1
  72. package/dist/locale.js +88 -0
  73. package/dist/locale.js.map +1 -1
  74. package/dist/next-steps.d.ts +5 -0
  75. package/dist/next-steps.js +26 -0
  76. package/dist/next-steps.js.map +1 -0
  77. package/dist/outbound-handlers.js +8 -0
  78. package/dist/outbound-handlers.js.map +1 -1
  79. package/dist/outbound-schemas.d.ts +4 -0
  80. package/dist/outbound-schemas.js +6 -1
  81. package/dist/outbound-schemas.js.map +1 -1
  82. package/dist/outbox-open-error.d.ts +7 -0
  83. package/dist/outbox-open-error.js +15 -0
  84. package/dist/outbox-open-error.js.map +1 -0
  85. package/dist/pane-context-cache.d.ts +25 -0
  86. package/dist/pane-context-cache.js +73 -0
  87. package/dist/pane-context-cache.js.map +1 -0
  88. package/dist/paths.d.ts +0 -7
  89. package/dist/paths.js +8 -2
  90. package/dist/paths.js.map +1 -1
  91. package/dist/provider-probe.d.ts +15 -0
  92. package/dist/provider-probe.js +20 -0
  93. package/dist/provider-probe.js.map +1 -1
  94. package/dist/public-link-progress.d.ts +75 -0
  95. package/dist/public-link-progress.js +204 -0
  96. package/dist/public-link-progress.js.map +1 -0
  97. package/dist/public-web-gateway.js +4 -1
  98. package/dist/public-web-gateway.js.map +1 -1
  99. package/dist/public-web-link.d.ts +9 -2
  100. package/dist/public-web-link.js +89 -15
  101. package/dist/public-web-link.js.map +1 -1
  102. package/dist/quickstart-api.d.ts +93 -20
  103. package/dist/quickstart-api.js +198 -63
  104. package/dist/quickstart-api.js.map +1 -1
  105. package/dist/quickstart.d.ts +15 -0
  106. package/dist/quickstart.js +100 -29
  107. package/dist/quickstart.js.map +1 -1
  108. package/dist/reply-buttons-controller.d.ts +98 -0
  109. package/dist/reply-buttons-controller.js +174 -0
  110. package/dist/reply-buttons-controller.js.map +1 -0
  111. package/dist/reply-buttons.d.ts +113 -0
  112. package/dist/reply-buttons.js +208 -0
  113. package/dist/reply-buttons.js.map +1 -0
  114. package/dist/reply-dedup.js +3 -1
  115. package/dist/reply-dedup.js.map +1 -1
  116. package/dist/restart-guard.d.ts +1 -0
  117. package/dist/restart-guard.js +8 -3
  118. package/dist/restart-guard.js.map +1 -1
  119. package/dist/service-activation.d.ts +15 -2
  120. package/dist/service-activation.js +81 -4
  121. package/dist/service-activation.js.map +1 -1
  122. package/dist/service-installer.js +19 -1
  123. package/dist/service-installer.js.map +1 -1
  124. package/dist/settings-api.js +53 -12
  125. package/dist/settings-api.js.map +1 -1
  126. package/dist/settings-authority.d.ts +11 -0
  127. package/dist/settings-authority.js +128 -0
  128. package/dist/settings-authority.js.map +1 -0
  129. package/dist/settings-change.d.ts +5 -0
  130. package/dist/settings-change.js +4 -3
  131. package/dist/settings-change.js.map +1 -1
  132. package/dist/settings-confirmation.d.ts +3 -0
  133. package/dist/settings-confirmation.js +7 -1
  134. package/dist/settings-confirmation.js.map +1 -1
  135. package/dist/settings-effect.js +19 -7
  136. package/dist/settings-effect.js.map +1 -1
  137. package/dist/settings-http-confirmation.js +1 -1
  138. package/dist/settings-http-confirmation.js.map +1 -1
  139. package/dist/settings-redaction.d.ts +18 -0
  140. package/dist/settings-redaction.js +58 -0
  141. package/dist/settings-redaction.js.map +1 -0
  142. package/dist/setup-form.d.ts +9 -1
  143. package/dist/setup-form.js +25 -1
  144. package/dist/setup-form.js.map +1 -1
  145. package/dist/setup-host.js +13 -3
  146. package/dist/setup-host.js.map +1 -1
  147. package/dist/setup-wizard.js +26 -19
  148. package/dist/setup-wizard.js.map +1 -1
  149. package/dist/systemd-runtime.d.ts +15 -0
  150. package/dist/systemd-runtime.js +41 -0
  151. package/dist/systemd-runtime.js.map +1 -0
  152. package/dist/tmux-control.d.ts +22 -0
  153. package/dist/tmux-control.js +82 -17
  154. package/dist/tmux-control.js.map +1 -1
  155. package/dist/token-env-name.d.ts +37 -0
  156. package/dist/token-env-name.js +93 -0
  157. package/dist/token-env-name.js.map +1 -0
  158. package/dist/tool-permissions.js +5 -3
  159. package/dist/tool-permissions.js.map +1 -1
  160. package/dist/topic-commands.d.ts +7 -16
  161. package/dist/topic-commands.js +48 -84
  162. package/dist/topic-commands.js.map +1 -1
  163. package/dist/transcript-monitor.js +3 -0
  164. package/dist/transcript-monitor.js.map +1 -1
  165. package/dist/transcript-sources.d.ts +68 -0
  166. package/dist/transcript-sources.js +336 -29
  167. package/dist/transcript-sources.js.map +1 -1
  168. package/dist/transcript-turns.d.ts +39 -0
  169. package/dist/transcript-turns.js +58 -0
  170. package/dist/transcript-turns.js.map +1 -0
  171. package/dist/tunnel/cloudflared-install.d.ts +42 -0
  172. package/dist/tunnel/cloudflared-install.js +194 -9
  173. package/dist/tunnel/cloudflared-install.js.map +1 -1
  174. package/dist/tunnel/deb-extract.d.ts +9 -0
  175. package/dist/tunnel/deb-extract.js +94 -0
  176. package/dist/tunnel/deb-extract.js.map +1 -0
  177. package/dist/turn-reply-guard.d.ts +3 -0
  178. package/dist/turn-reply-guard.js.map +1 -1
  179. package/dist/ui/app.html +3 -0
  180. package/dist/ui/chat-render.js +32 -6
  181. package/dist/ui/chat-store.js +21 -1
  182. package/dist/ui/chat-strings.js +45 -0
  183. package/dist/ui/chat-thread.js +109 -52
  184. package/dist/ui/first-run.js +64 -0
  185. package/dist/ui/html-attachment.js +26 -0
  186. package/dist/ui/icons/apple-touch-icon.png +0 -0
  187. package/dist/ui/icons/favicon.ico +0 -0
  188. package/dist/ui/icons/favicon.svg +8 -0
  189. package/dist/ui/panel-chat.js +16 -7
  190. package/dist/ui/panel-fleet.js +5 -5
  191. package/dist/ui/panel-settings.js +18 -8
  192. package/dist/ui/preview-panel.js +47 -7
  193. package/dist/ui/preview.js +36 -13
  194. package/dist/ui/settings-apply.js +3 -1
  195. package/dist/ui/settings-dialogs.js +95 -25
  196. package/dist/ui/settings-discord.js +130 -0
  197. package/dist/ui/settings-model.js +21 -0
  198. package/dist/ui/settings-strings.js +46 -6
  199. package/dist/ui/settings-token.js +54 -0
  200. package/dist/ui/settings-wizard.js +63 -29
  201. package/dist/ui/shared/app-shell.js +19 -33
  202. package/dist/ui/shared/app-store.js +5 -3
  203. package/dist/ui/shared/app-stream.js +2 -1
  204. package/dist/ui/shared/app.css +34 -2
  205. package/dist/ui/shared/app.js +2 -1
  206. package/dist/ui/shared/instance-nav.js +310 -0
  207. package/dist/ui/shared/panel-view.js +12 -190
  208. package/dist/ui/shared/view-roster-store.js +5 -0
  209. package/dist/ui/shared/view-strings.js +12 -0
  210. package/dist/ui/signin.html +3 -0
  211. package/dist/update-dispatch.d.ts +1 -1
  212. package/dist/update-dispatch.js +1 -1
  213. package/dist/update-install.d.ts +15 -3
  214. package/dist/update-install.js +41 -13
  215. package/dist/update-install.js.map +1 -1
  216. package/dist/update-launch.d.ts +22 -0
  217. package/dist/update-launch.js +106 -0
  218. package/dist/update-launch.js.map +1 -0
  219. package/dist/update-marker.js +3 -8
  220. package/dist/update-marker.js.map +1 -1
  221. package/dist/usage/format-rich.js +7 -1
  222. package/dist/usage/format-rich.js.map +1 -1
  223. package/dist/usage/i18n-keys.d.ts +1 -1
  224. package/dist/usage/i18n-keys.js +1 -1
  225. package/dist/usage/i18n-keys.js.map +1 -1
  226. package/dist/usage/providers.d.ts +4 -0
  227. package/dist/usage/providers.js +2 -1
  228. package/dist/usage/providers.js.map +1 -1
  229. package/dist/usage/usage-api.js +48 -2
  230. package/dist/usage/usage-api.js.map +1 -1
  231. package/dist/view-api.d.ts +40 -0
  232. package/dist/view-api.js +28 -10
  233. package/dist/view-api.js.map +1 -1
  234. package/dist/web-api.d.ts +8 -0
  235. package/dist/web-api.js +53 -17
  236. package/dist/web-api.js.map +1 -1
  237. package/dist/web-auth.d.ts +8 -0
  238. package/dist/web-auth.js +34 -8
  239. package/dist/web-auth.js.map +1 -1
  240. package/dist/web-chat-history.d.ts +21 -0
  241. package/dist/web-chat-history.js +44 -0
  242. package/dist/web-chat-history.js.map +1 -1
  243. package/dist/web-host-guard.d.ts +18 -2
  244. package/dist/web-host-guard.js +30 -5
  245. package/dist/web-host-guard.js.map +1 -1
  246. package/dist/web-icons.d.ts +21 -0
  247. package/dist/web-icons.js +38 -0
  248. package/dist/web-icons.js.map +1 -0
  249. package/dist/web-preview.d.ts +9 -0
  250. package/dist/web-preview.js +15 -9
  251. package/dist/web-preview.js.map +1 -1
  252. package/dist/web-session.d.ts +7 -1
  253. package/dist/web-session.js +23 -2
  254. package/dist/web-session.js.map +1 -1
  255. package/launcher/postinstall.cjs +8 -4
  256. package/package.json +1 -1
  257. package/templates/systemd.service.ejs +3 -3
package/dist/cli.js CHANGED
@@ -26,6 +26,7 @@ import { COMPLETION_SHELLS, completionScript } from "./completion.js";
26
26
  import { getUpdateSelector, isExactVersion, isUnrequestedDowngrade, lookupTargetVersion, reportUpdateRestart, shouldSkipUpdate, runningFleetPredatesInstall, processStartMs, } from "./update-check.js";
27
27
  import { clearUpdateMarker, markUpdateInProgress, setUpdateProgressStage } from "./update-marker.js";
28
28
  import { describeSignalSource, forceAllowed, gateFleetControl, recordInstanceControl, withOrigin } from "./fleet-control-audit.js";
29
+ import { FLEET_STOP_TIMEOUT_MS, stopDetachedOwner, systemdStopTimeoutMs, detachedProcessStart, detachedProcessExited } from "./fleet-stop-budget.js";
29
30
  import { acquireFleetLock, isFleetStartCommandLine, readProcessCommandLine, releaseProcessFleetLock, setProcessFleetLock } from "./fleet-lock.js";
30
31
  import { limitFleetCoreDumps } from "./coredump-filter.js";
31
32
  import { SYSTEMD_RESTART_TIMEOUT_MS } from "./service-installer.js";
@@ -1479,6 +1480,8 @@ program
1479
1480
  }
1480
1481
  process.exitCode = 1;
1481
1482
  }
1483
+ if (restartOutcome === "pending")
1484
+ process.exitCode = 75;
1482
1485
  return restartOutcome;
1483
1486
  };
1484
1487
  const nvmSh = join(homedir(), ".nvm", "nvm.sh");
@@ -1506,10 +1509,11 @@ program
1506
1509
  // C6: a failure before the fleet runs the new install puts the previous package back too (taken before npm).
1507
1510
  const { restorePackagePreimage, prunePreimages } = await import("./package-preimage.js");
1508
1511
  const rollback = verified.rollback;
1509
- const restorePackage = rollback?.preimage ? () => {
1512
+ const restoreChecked = rollback?.preimage ? () => {
1510
1513
  const back = restorePackagePreimage(rollback.root, rollback.prefix, rollback.preimage);
1511
- return back.ok ? `The previous package (v${rollback.preimage.version}) is back in place` : `The previous package could NOT be put back: ${back.reason}`;
1514
+ return { ok: back.ok, message: back.ok ? `The previous package (v${rollback.preimage.version}) is back in place` : `The previous package could NOT be put back: ${back.reason}` };
1512
1515
  } : undefined;
1516
+ const restorePackage = restoreChecked ? () => restoreChecked().message : undefined;
1513
1517
  const capture = (command, args) => {
1514
1518
  const result = spawnSync(command, args, { encoding: "utf-8", timeout: 15_000, stdio: ["ignore", "pipe", "pipe"] });
1515
1519
  return { status: result.status, signal: result.signal, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
@@ -1557,18 +1561,24 @@ program
1557
1561
  }
1558
1562
  catch { /* cosmetic */ }
1559
1563
  restartResult = restartFleetForUpdate(newAgend.command, newAgend.args, verified.version);
1564
+ return restartResult;
1560
1565
  },
1561
1566
  log: message => console.log(message),
1562
1567
  restorePackage,
1568
+ systemdRecovery: restoreChecked ? { restorePackage: restoreChecked } : undefined,
1563
1569
  });
1564
1570
  // Settled: after a success only this transition's preimage is kept (for a repair); a failure consumed it.
1565
- if (outcome.ok && rollback)
1571
+ if (activationSettled(outcome, restartResult) && rollback)
1566
1572
  prunePreimages(rollback.prefix, rollback.preimage);
1567
1573
  // nvm transition: the old system copy is what the previous service ran. Only once the fleet POSITIVELY runs the
1568
1574
  // new install (activationSettled) is it removed; pending, failed or refused, it stays, so the old unit still starts.
1569
1575
  if (verified.retireSystemCopy) {
1570
1576
  if (activationSettled(outcome, restartResult)) {
1571
- retireSystemCopy({ run: (command, args) => capture(command, args), log: message => console.log(message) });
1577
+ const retireResult = retireSystemCopy({ run: (command, args) => capture(command, args), log: message => console.log(message) }, verified.npmPath);
1578
+ if (!retireResult.ok) {
1579
+ console.error(` ✗ Old system install was not removed: ${retireResult.reason}`);
1580
+ process.exitCode = 1;
1581
+ }
1572
1582
  }
1573
1583
  else {
1574
1584
  console.log(" Note: the old system install was kept, since the new one is not running yet.");
@@ -1586,6 +1596,11 @@ program
1586
1596
  return;
1587
1597
  }
1588
1598
  if (!outcome.ok) {
1599
+ if (outcome.pending) {
1600
+ console.log(outcome.message);
1601
+ process.exitCode = 75;
1602
+ return;
1603
+ }
1589
1604
  console.error(outcome.message);
1590
1605
  if (!setUpdateProgressStage(DATA_DIR, "failed", { error: outcome.message.trim() }))
1591
1606
  clearUpdateMarker(DATA_DIR);
@@ -2006,6 +2021,8 @@ program
2006
2021
  return false;
2007
2022
  }
2008
2023
  console.error(` ✗ Not restarting: ${judged.reason}. Nothing was stopped.`);
2024
+ if (judged.recovery)
2025
+ console.error(` To repair: ${judged.recovery}`);
2009
2026
  process.exitCode = 1;
2010
2027
  return true;
2011
2028
  };
@@ -2063,8 +2080,8 @@ program
2063
2080
  const unitPath = systemdTarget.user ? getServicePath() : getSystemServicePath();
2064
2081
  const reloadCmd = systemdTarget.user ? "systemctl --user daemon-reload" : "systemctl daemon-reload";
2065
2082
  // #1113 rides the same migration: crash-dump, start-limit and
2066
- // start-timeout directives are filled in alongside KillMode. Only
2067
- // KillMode gates the restart — the others change no stop behaviour.
2083
+ // start-timeout directives are filled in alongside KillMode. KillMode
2084
+ // and the loaded #1071 stop grace gate the restart; the others are informational.
2068
2085
  // The drop-ins systemd applies to this unit, by its own account
2069
2086
  // (DropInPaths), plus any on disk it has not loaded yet. Guessing the
2070
2087
  // directories alone misses type-wide service.d and other load paths.
@@ -2122,6 +2139,23 @@ program
2122
2139
  return;
2123
2140
  }
2124
2141
  }
2142
+ // Migration preserves operator overrides. Admission independently requires
2143
+ // the actual loaded budget for EVERY selected systemd target, even if its
2144
+ // unit file disappeared or no migration/classification was possible.
2145
+ if (hardening.TimeoutStopSec === "custom") {
2146
+ console.log(` ⚠ ${unitPath} sets its own TimeoutStopSec; left as is. A short grace can cut off busy CLI batches (#1071).`);
2147
+ }
2148
+ let loadedStopGrace = "";
2149
+ try {
2150
+ loadedStopGrace = execSync(`systemctl${scopeFlag} show -p TimeoutStopUSec --value ${systemdTarget.unit}`, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 }).trim();
2151
+ }
2152
+ catch { /* unknown refuses */ }
2153
+ const milliseconds = systemdStopTimeoutMs(loadedStopGrace);
2154
+ if (milliseconds === null || milliseconds < FLEET_STOP_TIMEOUT_MS) {
2155
+ console.error(` ✗ Not restarting: systemd has ${loadedStopGrace || "unknown"} stop grace loaded; this fleet needs ${FLEET_STOP_TIMEOUT_MS / 1000}s. Set TimeoutStopSec to at least ${FLEET_STOP_TIMEOUT_MS / 1000}s in ${unitPath ?? systemdTarget.unit}, run \`${reloadCmd}\`, and retry (#1071).`);
2156
+ process.exitCode = 1;
2157
+ return;
2158
+ }
2125
2159
  if (outcome === "added") {
2126
2160
  console.log(` ✓ ${unitPath}: added KillMode=mixed (the fleet stops its CLIs; systemd no longer signals them all at once)`);
2127
2161
  }
@@ -2129,7 +2163,7 @@ program
2129
2163
  .filter(([key, state]) => key !== "KillMode" && (state === "added" || state === "upgraded"))
2130
2164
  .map(([key]) => key);
2131
2165
  if (updated.length > 0)
2132
- console.log(` ✓ ${unitPath}: updated ${updated.join(", ")} (#1113: small crash dumps, bounded start, start limit)`);
2166
+ console.log(` ✓ ${unitPath}: updated ${updated.join(", ")} (#908/#1071/#1113: graceful stop, small crash dumps, bounded start, start limit)`);
2133
2167
  // Re-judged after the reload, against what systemd now applies: an
2134
2168
  // operator's own mask (anywhere — main file, unit or type drop-in, or a
2135
2169
  // trailing empty assignment that restores the inherited one) is warned
@@ -2220,7 +2254,7 @@ program
2220
2254
  return false;
2221
2255
  } },
2222
2256
  refresh: () => ({ status: 0, signal: null, stdout: "", stderr: "" }),
2223
- restart: () => { },
2257
+ restart: () => { throw new Error("planned launchd activation does not invoke restart"); },
2224
2258
  log: message => console.log(message),
2225
2259
  launchdPreimage: plan.preimage.plist,
2226
2260
  });
@@ -2253,50 +2287,111 @@ program
2253
2287
  }
2254
2288
  // 4. Detached process tracked by fleet.pid (no service manager)
2255
2289
  if (existsSync(pidPath)) {
2256
- const oldPid = parseInt(readFileSync(pidPath, "utf-8").trim(), 10);
2257
- // Only signal a process confirmed to BE this fleet (#1125 review): a
2258
- // stale fleet.pid may name a recycled pid — an unrelated process. Not
2259
- // confirmed (gone, or something else) is treated as a stale pid file.
2260
- const isOurFleet = () => {
2290
+ const originalPidFile = readFileSync(pidPath, "utf-8");
2291
+ const oldPid = Number(originalPidFile.trim());
2292
+ let claimedStart = null;
2293
+ const absence = () => {
2261
2294
  try {
2262
2295
  process.kill(oldPid, 0);
2263
2296
  }
2264
- catch {
2265
- return false;
2297
+ catch (error) {
2298
+ return error.code === "ESRCH" ? "gone" : "unknown";
2299
+ }
2300
+ return detachedProcessExited(oldPid) ? "gone" : null;
2301
+ };
2302
+ const inspect = () => {
2303
+ try {
2304
+ if (existsSync(pidPath) && readFileSync(pidPath, "utf-8") !== originalPidFile)
2305
+ return "unknown";
2266
2306
  }
2267
- return isFleetStartCommandLine(readProcessCommandLine(oldPid));
2307
+ catch {
2308
+ return "unknown";
2309
+ }
2310
+ const missing = absence();
2311
+ if (missing)
2312
+ return missing;
2313
+ const start = detachedProcessStart(oldPid);
2314
+ if (start === null)
2315
+ return absence() ?? "unknown";
2316
+ const command = readProcessCommandLine(oldPid);
2317
+ if (!command)
2318
+ return absence() ?? (claimedStart !== null && start === claimedStart ? "waiting" : "unknown");
2319
+ const fleet = isFleetStartCommandLine(command);
2320
+ if (claimedStart !== null && start !== claimedStart)
2321
+ return fleet ? "unknown" : "other";
2322
+ // The claimed process is still alive: rewritten argv is not exit proof.
2323
+ if (claimedStart !== null && !fleet)
2324
+ return "unknown";
2325
+ claimedStart ??= start;
2326
+ return fleet ? "fleet" : "other";
2268
2327
  };
2269
- if (oldPid && isOurFleet()) {
2270
- // The replacement is `<this Node> <entry> fleet start` (C5): this Node must be the selected one.
2328
+ if (!Number.isSafeInteger(oldPid) || oldPid <= 1) {
2329
+ console.error(" ✗ Invalid detached fleet PID; nothing was stopped or started.");
2330
+ process.exitCode = 1;
2331
+ return;
2332
+ }
2333
+ const state = inspect();
2334
+ if (state === "unknown") {
2335
+ console.error(" ✗ Cannot confirm the detached fleet owner; nothing was stopped or started.");
2336
+ process.exitCode = 1;
2337
+ return;
2338
+ }
2339
+ if (state === "fleet") {
2271
2340
  if (expectation.ok && refuses(guard.guardDetached(process.execPath, expectation.expected, guardDeps)))
2272
2341
  return;
2342
+ console.log(`Stopping detached fleet (up to ${FLEET_STOP_TIMEOUT_MS / 60_000} minutes for graceful shutdown)...`);
2273
2343
  try {
2274
- process.kill(oldPid, "SIGTERM");
2344
+ await stopDetachedOwner({
2345
+ inspect,
2346
+ signal: signal => {
2347
+ if (signal === "SIGKILL")
2348
+ console.log(" ⚠ Grace expired; forcing the confirmed fleet owner to exit.");
2349
+ // stdout can block. The helper's earlier proof cannot authorize a
2350
+ // signal after that write; prove the exact owner again now.
2351
+ const owner = inspect();
2352
+ if (owner === "gone" || owner === "other")
2353
+ return;
2354
+ if (owner !== "fleet")
2355
+ throw new Error("Detached fleet ownership changed before signalling; refusing the signal");
2356
+ try {
2357
+ process.kill(oldPid, signal);
2358
+ }
2359
+ catch (error) {
2360
+ if (error.code !== "ESRCH")
2361
+ throw error;
2362
+ }
2363
+ },
2364
+ now: () => performance.now(),
2365
+ sleep: ms => new Promise(resolve => setTimeout(resolve, ms)),
2366
+ });
2275
2367
  }
2276
- catch { /* already gone */ }
2277
- for (let i = 0; i < 20; i++) {
2278
- try {
2279
- process.kill(oldPid, 0);
2280
- }
2281
- catch {
2282
- break;
2283
- }
2284
- spawnSync("sleep", ["0.5"]);
2368
+ catch (error) {
2369
+ console.error(` ✗ ${error.message}`);
2370
+ process.exitCode = 1;
2371
+ return;
2285
2372
  }
2286
- // Re-confirmed right before the SIGKILL: the pid may have been reused
2287
- // during the wait.
2288
- if (isOurFleet()) {
2289
- try {
2290
- process.kill(oldPid, "SIGKILL");
2291
- }
2292
- catch { /* already gone */ }
2373
+ if (expectation.ok && refuses(guard.guardDetached(process.execPath, expectation.expected, guardDeps)))
2374
+ return;
2375
+ const start = selfCommand(["fleet", "start"]);
2376
+ // The runtime guard and command preparation can read files. Recheck
2377
+ // publication AFTER both, immediately before cleanup/spawn. Unreadable
2378
+ // publication cannot authorize deleting a newly published owner's file.
2379
+ let publicationCurrent = false;
2380
+ try {
2381
+ publicationCurrent = readFileSync(pidPath, "utf-8") === originalPidFile;
2382
+ }
2383
+ catch (error) {
2384
+ publicationCurrent = error?.code === "ENOENT";
2385
+ }
2386
+ if (!publicationCurrent) {
2387
+ console.error(" ✗ Fleet owner changed or its publication is unreadable; refusing a duplicate restart.");
2388
+ process.exitCode = 1;
2389
+ return;
2293
2390
  }
2294
2391
  try {
2295
2392
  unlinkSync(pidPath);
2296
2393
  }
2297
- catch { /* best effort */ }
2298
- // This Node on this CLI, not `sh -c "agend fleet start"` through PATH (#1450 C5).
2299
- const start = selfCommand(["fleet", "start"]);
2394
+ catch { /* old owner already removed it */ }
2300
2395
  const child = spawn(start.command, start.args, { detached: true, stdio: "ignore" });
2301
2396
  child.unref();
2302
2397
  console.log("Fleet restarting in background.");