@sapienx/agentos 0.3.11 → 0.3.14

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 (201) hide show
  1. package/README.md +12 -2
  2. package/bin/agentos.js +777 -10
  3. package/bundle/.next/BUILD_ID +1 -1
  4. package/bundle/.next/app-path-routes-manifest.json +10 -8
  5. package/bundle/.next/build-manifest.json +3 -3
  6. package/bundle/.next/prerender-manifest.json +3 -3
  7. package/bundle/.next/react-loadable-manifest.json +3 -3
  8. package/bundle/.next/required-server-files.json +16 -0
  9. package/bundle/.next/routes-manifest.json +14 -0
  10. package/bundle/.next/server/app/_global-error/page.js +3 -3
  11. package/bundle/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/bundle/.next/server/app/_global-error.html +1 -1
  13. package/bundle/.next/server/app/_global-error.rsc +1 -1
  14. package/bundle/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/bundle/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/bundle/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/bundle/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/bundle/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/bundle/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/bundle/.next/server/app/_not-found/page.js +2 -2
  21. package/bundle/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/bundle/.next/server/app/_not-found.html +1 -1
  23. package/bundle/.next/server/app/_not-found.rsc +3 -3
  24. package/bundle/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  25. package/bundle/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  26. package/bundle/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  27. package/bundle/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  28. package/bundle/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  29. package/bundle/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/bundle/.next/server/app/api/agents/[agentId]/chat/route.js +8 -1
  31. package/bundle/.next/server/app/api/agents/[agentId]/chat/route.js.nft.json +1 -1
  32. package/bundle/.next/server/app/api/agents/route.js +1 -1
  33. package/bundle/.next/server/app/api/agents/route.js.nft.json +1 -1
  34. package/bundle/.next/server/app/api/diagnostics/route.js +1 -1
  35. package/bundle/.next/server/app/api/diagnostics/route.js.nft.json +1 -1
  36. package/bundle/.next/server/app/api/files/reveal/route.js +1 -1
  37. package/bundle/.next/server/app/api/gateway/control/route.js +1 -1
  38. package/bundle/.next/server/app/api/gateway/control/route.js.nft.json +1 -1
  39. package/bundle/.next/server/app/api/mission/route.js +1 -1
  40. package/bundle/.next/server/app/api/mission/route.js.nft.json +1 -1
  41. package/bundle/.next/server/app/api/models/catalog/route.js +1 -0
  42. package/bundle/.next/server/app/api/models/catalog/route.js.nft.json +1 -0
  43. package/bundle/.next/server/app/api/models/catalog/route_client-reference-manifest.js +1 -0
  44. package/bundle/.next/server/app/api/models/providers/route.js +2 -2
  45. package/bundle/.next/server/app/api/models/providers/route.js.nft.json +1 -1
  46. package/bundle/.next/server/app/api/onboarding/models/route.js +7 -7
  47. package/bundle/.next/server/app/api/onboarding/models/route.js.nft.json +1 -1
  48. package/bundle/.next/server/app/api/onboarding/route.js +8 -8
  49. package/bundle/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  50. package/bundle/.next/server/app/api/openclaw/capabilities/route.js +3 -3
  51. package/bundle/.next/server/app/api/planner/[planId]/deploy/route.js +2 -2
  52. package/bundle/.next/server/app/api/planner/[planId]/deploy/route.js.nft.json +1 -1
  53. package/bundle/.next/server/app/api/planner/[planId]/document-rewrite/route.js +1 -1
  54. package/bundle/.next/server/app/api/planner/[planId]/document-rewrite/route.js.nft.json +1 -1
  55. package/bundle/.next/server/app/api/planner/[planId]/route.js +1 -1
  56. package/bundle/.next/server/app/api/planner/[planId]/route.js.nft.json +1 -1
  57. package/bundle/.next/server/app/api/planner/[planId]/simulate/route.js +1 -1
  58. package/bundle/.next/server/app/api/planner/[planId]/simulate/route.js.nft.json +1 -1
  59. package/bundle/.next/server/app/api/planner/[planId]/turn/route.js +1 -1
  60. package/bundle/.next/server/app/api/planner/[planId]/turn/route.js.nft.json +1 -1
  61. package/bundle/.next/server/app/api/planner/route.js +1 -1
  62. package/bundle/.next/server/app/api/planner/route.js.nft.json +1 -1
  63. package/bundle/.next/server/app/api/reset/route.js +3 -3
  64. package/bundle/.next/server/app/api/reset/route.js.nft.json +1 -1
  65. package/bundle/.next/server/app/api/runtimes/[runtimeId]/route.js +1 -1
  66. package/bundle/.next/server/app/api/runtimes/[runtimeId]/route.js.nft.json +1 -1
  67. package/bundle/.next/server/app/api/settings/gateway/route.js +1 -1
  68. package/bundle/.next/server/app/api/settings/gateway/route.js.nft.json +1 -1
  69. package/bundle/.next/server/app/api/settings/workspace-root/route.js +1 -1
  70. package/bundle/.next/server/app/api/settings/workspace-root/route.js.nft.json +1 -1
  71. package/bundle/.next/server/app/api/snapshot/route.js +1 -1
  72. package/bundle/.next/server/app/api/snapshot/route.js.nft.json +1 -1
  73. package/bundle/.next/server/app/api/stream/route.js +2 -2
  74. package/bundle/.next/server/app/api/stream/route.js.nft.json +1 -1
  75. package/bundle/.next/server/app/api/system/open-terminal/route.js +1 -1
  76. package/bundle/.next/server/app/api/tasks/[taskId]/abort/route.js +1 -1
  77. package/bundle/.next/server/app/api/tasks/[taskId]/abort/route.js.nft.json +1 -1
  78. package/bundle/.next/server/app/api/tasks/[taskId]/stream/route.js +2 -2
  79. package/bundle/.next/server/app/api/tasks/[taskId]/stream/route.js.nft.json +1 -1
  80. package/bundle/.next/server/app/api/update/route.js +4 -4
  81. package/bundle/.next/server/app/api/update/route.js.nft.json +1 -1
  82. package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/discovered-groups/route.js +1 -1
  83. package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/discovered-groups/route.js.nft.json +1 -1
  84. package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/route.js +1 -1
  85. package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/route.js.nft.json +1 -1
  86. package/bundle/.next/server/app/api/workspaces/[workspaceId]/edit-draft/route.js +1 -1
  87. package/bundle/.next/server/app/api/workspaces/[workspaceId]/edit-draft/route.js.nft.json +1 -1
  88. package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route.js +1 -0
  89. package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route.js.nft.json +1 -0
  90. package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route_client-reference-manifest.js +1 -0
  91. package/bundle/.next/server/app/api/workspaces/route.js +2 -2
  92. package/bundle/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  93. package/bundle/.next/server/app/page.js +44 -41
  94. package/bundle/.next/server/app/page.js.nft.json +1 -1
  95. package/bundle/.next/server/app/page_client-reference-manifest.js +1 -1
  96. package/bundle/.next/server/app-paths-manifest.json +10 -8
  97. package/bundle/.next/server/chunks/199.js +412 -0
  98. package/bundle/.next/server/chunks/639.js +98 -0
  99. package/bundle/.next/server/chunks/661.js +1 -1
  100. package/bundle/.next/server/functions-config-manifest.json +3 -1
  101. package/bundle/.next/server/middleware-build-manifest.js +1 -1
  102. package/bundle/.next/server/middleware-react-loadable-manifest.js +1 -1
  103. package/bundle/.next/server/pages/404.html +1 -1
  104. package/bundle/.next/server/pages/500.html +1 -1
  105. package/bundle/.next/server/server-reference-manifest.json +1 -1
  106. package/bundle/.next/static/3RfU29upg-cPxACyzCFUb/_buildManifest.js +1 -0
  107. package/bundle/.next/static/chunks/{1364.5bd755c8584fcbc8.js → 2999.fef298fbe30cd4bd.js} +1 -1
  108. package/bundle/.next/static/chunks/5261.29ae1e1fe185a617.js +1 -0
  109. package/bundle/.next/static/chunks/7963-6bd42810c5733ae8.js +15 -0
  110. package/bundle/.next/static/chunks/app/_global-error/page-cde9c645dcfceb9f.js +1 -0
  111. package/bundle/.next/static/chunks/app/_not-found/page-cde9c645dcfceb9f.js +1 -0
  112. package/bundle/.next/static/chunks/app/api/agents/[agentId]/chat/route-cde9c645dcfceb9f.js +1 -0
  113. package/bundle/.next/static/chunks/app/api/agents/route-cde9c645dcfceb9f.js +1 -0
  114. package/bundle/.next/static/chunks/app/api/diagnostics/route-cde9c645dcfceb9f.js +1 -0
  115. package/bundle/.next/static/chunks/app/api/files/reveal/route-cde9c645dcfceb9f.js +1 -0
  116. package/bundle/.next/static/chunks/app/api/gateway/control/route-cde9c645dcfceb9f.js +1 -0
  117. package/bundle/.next/static/chunks/app/api/mission/route-cde9c645dcfceb9f.js +1 -0
  118. package/bundle/.next/static/chunks/app/api/models/catalog/route-cde9c645dcfceb9f.js +1 -0
  119. package/bundle/.next/static/chunks/app/api/models/providers/route-cde9c645dcfceb9f.js +1 -0
  120. package/bundle/.next/static/chunks/app/api/onboarding/models/route-cde9c645dcfceb9f.js +1 -0
  121. package/bundle/.next/static/chunks/app/api/onboarding/route-cde9c645dcfceb9f.js +1 -0
  122. package/bundle/.next/static/chunks/app/api/openclaw/capabilities/route-cde9c645dcfceb9f.js +1 -0
  123. package/bundle/.next/static/chunks/app/api/planner/[planId]/deploy/route-cde9c645dcfceb9f.js +1 -0
  124. package/bundle/.next/static/chunks/app/api/planner/[planId]/document-rewrite/route-cde9c645dcfceb9f.js +1 -0
  125. package/bundle/.next/static/chunks/app/api/planner/[planId]/route-cde9c645dcfceb9f.js +1 -0
  126. package/bundle/.next/static/chunks/app/api/planner/[planId]/simulate/route-cde9c645dcfceb9f.js +1 -0
  127. package/bundle/.next/static/chunks/app/api/planner/[planId]/turn/route-cde9c645dcfceb9f.js +1 -0
  128. package/bundle/.next/static/chunks/app/api/planner/route-cde9c645dcfceb9f.js +1 -0
  129. package/bundle/.next/static/chunks/app/api/reset/route-cde9c645dcfceb9f.js +1 -0
  130. package/bundle/.next/static/chunks/app/api/runtimes/[runtimeId]/route-cde9c645dcfceb9f.js +1 -0
  131. package/bundle/.next/static/chunks/app/api/settings/gateway/route-cde9c645dcfceb9f.js +1 -0
  132. package/bundle/.next/static/chunks/app/api/settings/workspace-root/route-cde9c645dcfceb9f.js +1 -0
  133. package/bundle/.next/static/chunks/app/api/snapshot/route-cde9c645dcfceb9f.js +1 -0
  134. package/bundle/.next/static/chunks/app/api/stream/route-cde9c645dcfceb9f.js +1 -0
  135. package/bundle/.next/static/chunks/app/api/system/open-terminal/route-cde9c645dcfceb9f.js +1 -0
  136. package/bundle/.next/static/chunks/app/api/tasks/[taskId]/abort/route-cde9c645dcfceb9f.js +1 -0
  137. package/bundle/.next/static/chunks/app/api/tasks/[taskId]/stream/route-cde9c645dcfceb9f.js +1 -0
  138. package/bundle/.next/static/chunks/app/api/update/route-cde9c645dcfceb9f.js +1 -0
  139. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/discovered-groups/route-cde9c645dcfceb9f.js +1 -0
  140. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/route-cde9c645dcfceb9f.js +1 -0
  141. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/edit-draft/route-cde9c645dcfceb9f.js +1 -0
  142. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/surfaces/discovery/route-cde9c645dcfceb9f.js +1 -0
  143. package/bundle/.next/static/chunks/app/api/workspaces/route-cde9c645dcfceb9f.js +1 -0
  144. package/bundle/.next/static/chunks/app/not-found-cde9c645dcfceb9f.js +1 -0
  145. package/bundle/.next/static/chunks/app/page-29f20effd872e616.js +179 -0
  146. package/bundle/.next/static/chunks/next/dist/client/components/builtin/app-error-cde9c645dcfceb9f.js +1 -0
  147. package/bundle/.next/static/chunks/next/dist/client/components/builtin/forbidden-cde9c645dcfceb9f.js +1 -0
  148. package/bundle/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cde9c645dcfceb9f.js +1 -0
  149. package/bundle/.next/static/chunks/{webpack-4fdc6767ef8ff7b1.js → webpack-e0accbaa78fb0b56.js} +1 -1
  150. package/bundle/.next/static/css/9f1b31fd4086df59.css +3 -0
  151. package/bundle/package.json +1 -1
  152. package/bundle/public/readme/banner.jpeg +0 -0
  153. package/bundle/public/readme/mission-control-hero.svg +2 -2
  154. package/bundle/public/site.webmanifest +1 -1
  155. package/bundle/scripts/openclaw-mission-dispatch-runner.mjs +180 -9
  156. package/bundle/server.js +1 -1
  157. package/package.json +2 -2
  158. package/bundle/.next/server/chunks/212.js +0 -98
  159. package/bundle/.next/server/chunks/49.js +0 -420
  160. package/bundle/.next/static/PMHP584Iqd3KMZJH68hEV/_buildManifest.js +0 -1
  161. package/bundle/.next/static/chunks/1506.ec91294c1cbc454f.js +0 -1
  162. package/bundle/.next/static/chunks/5954-fd2a9c905dcede3c.js +0 -15
  163. package/bundle/.next/static/chunks/app/_global-error/page-1a770949224cd2a0.js +0 -1
  164. package/bundle/.next/static/chunks/app/_not-found/page-1a770949224cd2a0.js +0 -1
  165. package/bundle/.next/static/chunks/app/api/agents/[agentId]/chat/route-1a770949224cd2a0.js +0 -1
  166. package/bundle/.next/static/chunks/app/api/agents/route-1a770949224cd2a0.js +0 -1
  167. package/bundle/.next/static/chunks/app/api/diagnostics/route-1a770949224cd2a0.js +0 -1
  168. package/bundle/.next/static/chunks/app/api/files/reveal/route-1a770949224cd2a0.js +0 -1
  169. package/bundle/.next/static/chunks/app/api/gateway/control/route-1a770949224cd2a0.js +0 -1
  170. package/bundle/.next/static/chunks/app/api/mission/route-1a770949224cd2a0.js +0 -1
  171. package/bundle/.next/static/chunks/app/api/models/providers/route-1a770949224cd2a0.js +0 -1
  172. package/bundle/.next/static/chunks/app/api/onboarding/models/route-1a770949224cd2a0.js +0 -1
  173. package/bundle/.next/static/chunks/app/api/onboarding/route-1a770949224cd2a0.js +0 -1
  174. package/bundle/.next/static/chunks/app/api/openclaw/capabilities/route-1a770949224cd2a0.js +0 -1
  175. package/bundle/.next/static/chunks/app/api/planner/[planId]/deploy/route-1a770949224cd2a0.js +0 -1
  176. package/bundle/.next/static/chunks/app/api/planner/[planId]/document-rewrite/route-1a770949224cd2a0.js +0 -1
  177. package/bundle/.next/static/chunks/app/api/planner/[planId]/route-1a770949224cd2a0.js +0 -1
  178. package/bundle/.next/static/chunks/app/api/planner/[planId]/simulate/route-1a770949224cd2a0.js +0 -1
  179. package/bundle/.next/static/chunks/app/api/planner/[planId]/turn/route-1a770949224cd2a0.js +0 -1
  180. package/bundle/.next/static/chunks/app/api/planner/route-1a770949224cd2a0.js +0 -1
  181. package/bundle/.next/static/chunks/app/api/reset/route-1a770949224cd2a0.js +0 -1
  182. package/bundle/.next/static/chunks/app/api/runtimes/[runtimeId]/route-1a770949224cd2a0.js +0 -1
  183. package/bundle/.next/static/chunks/app/api/settings/gateway/route-1a770949224cd2a0.js +0 -1
  184. package/bundle/.next/static/chunks/app/api/settings/workspace-root/route-1a770949224cd2a0.js +0 -1
  185. package/bundle/.next/static/chunks/app/api/snapshot/route-1a770949224cd2a0.js +0 -1
  186. package/bundle/.next/static/chunks/app/api/stream/route-1a770949224cd2a0.js +0 -1
  187. package/bundle/.next/static/chunks/app/api/system/open-terminal/route-1a770949224cd2a0.js +0 -1
  188. package/bundle/.next/static/chunks/app/api/tasks/[taskId]/abort/route-1a770949224cd2a0.js +0 -1
  189. package/bundle/.next/static/chunks/app/api/tasks/[taskId]/stream/route-1a770949224cd2a0.js +0 -1
  190. package/bundle/.next/static/chunks/app/api/update/route-1a770949224cd2a0.js +0 -1
  191. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/discovered-groups/route-1a770949224cd2a0.js +0 -1
  192. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/route-1a770949224cd2a0.js +0 -1
  193. package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/edit-draft/route-1a770949224cd2a0.js +0 -1
  194. package/bundle/.next/static/chunks/app/api/workspaces/route-1a770949224cd2a0.js +0 -1
  195. package/bundle/.next/static/chunks/app/not-found-1a770949224cd2a0.js +0 -1
  196. package/bundle/.next/static/chunks/app/page-e7ca3c6ffb09198b.js +0 -176
  197. package/bundle/.next/static/chunks/next/dist/client/components/builtin/app-error-1a770949224cd2a0.js +0 -1
  198. package/bundle/.next/static/chunks/next/dist/client/components/builtin/forbidden-1a770949224cd2a0.js +0 -1
  199. package/bundle/.next/static/chunks/next/dist/client/components/builtin/unauthorized-1a770949224cd2a0.js +0 -1
  200. package/bundle/.next/static/css/e20ab76ee4e77d93.css +0 -3
  201. /package/bundle/.next/static/{PMHP584Iqd3KMZJH68hEV → 3RfU29upg-cPxACyzCFUb}/_ssgManifest.js +0 -0
package/bin/agentos.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawn, spawnSync } from "node:child_process";
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
5
6
  import { createInterface } from "node:readline/promises";
6
7
  import os from "node:os";
7
8
  import path from "node:path";
@@ -19,8 +20,14 @@ const defaultInstallRoot = path.join(os.homedir(), ".agentos");
19
20
  const defaultBinDir = path.join(os.homedir(), ".local", "bin");
20
21
  const runtimeInstallRoot = resolveRuntimeInstallRoot();
21
22
  const runtimeStateDir = path.join(runtimeInstallRoot, "run");
23
+ const updateCacheDir = path.join(runtimeInstallRoot, "cache");
24
+ const updateCachePath = path.join(updateCacheDir, "update-check.json");
22
25
  const stopPollIntervalMs = 100;
23
26
  const stopTimeoutMs = 5_000;
27
+ const startupGracePeriodMs = 15_000;
28
+ const updateCacheTtlMs = 24 * 60 * 60 * 1000;
29
+ const updateWarningCooldownMs = 24 * 60 * 60 * 1000;
30
+ const updateRequestTimeoutMs = 5_000;
24
31
 
25
32
  main().catch((error) => {
26
33
  console.error(error instanceof Error ? error.message : String(error));
@@ -51,6 +58,16 @@ async function main() {
51
58
  return;
52
59
  }
53
60
 
61
+ if (firstArg === "update") {
62
+ if (args[1] === "--help" || args[1] === "-h" || args[1] === "help") {
63
+ printUpdateHelp();
64
+ return;
65
+ }
66
+
67
+ await runUpdate(args.slice(1));
68
+ return;
69
+ }
70
+
54
71
  if (firstArg === "stop") {
55
72
  if (args[1] === "--help" || args[1] === "-h" || args[1] === "help") {
56
73
  printStopHelp();
@@ -87,10 +104,26 @@ async function startServer(rawArgs) {
87
104
  const trackedState = readRuntimeState(runtimeStatePath);
88
105
  const openClawCheck = detectOpenClaw();
89
106
  const browserOpener = detectBrowserOpener();
107
+ const url = createAgentOsUrl(options.host, options.port);
108
+ const existingServer = await detectExistingServer(options, trackedState, runtimeStatePath);
109
+
110
+ if (existingServer) {
111
+ if (options.open) {
112
+ if (!browserOpener.available) {
113
+ console.warn(
114
+ `Browser auto-open is unavailable on this machine${browserOpener.detail ? ` (${browserOpener.detail})` : ""}.`
115
+ );
116
+ console.log(`AgentOS is already running on ${existingServer.url} (PID ${existingServer.pid}).`);
117
+ return;
118
+ }
119
+
120
+ console.log(`AgentOS is already running on ${existingServer.url} (PID ${existingServer.pid}). Opening it...`);
121
+ openBrowser(existingServer.url, browserOpener);
122
+ return;
123
+ }
90
124
 
91
- if (trackedState?.pid && isProcessRunning(trackedState.pid)) {
92
125
  throw new Error(
93
- `AgentOS is already running on port ${options.port} (PID ${trackedState.pid}). Use "agentos stop --port ${options.port}" first.`
126
+ `AgentOS is already running on port ${options.port} (PID ${existingServer.pid}). Use "agentos stop --port ${options.port}" first.`
94
127
  );
95
128
  }
96
129
 
@@ -98,7 +131,6 @@ async function startServer(rawArgs) {
98
131
  clearRuntimeState(runtimeStatePath);
99
132
  }
100
133
 
101
- const url = `http://${displayHost(options.host)}:${options.port}`;
102
134
  console.log(`Starting AgentOS on ${url}`);
103
135
 
104
136
  if (!openClawCheck.available) {
@@ -147,13 +179,22 @@ async function startServer(rawArgs) {
147
179
  child.stdout.on("data", relayStdout);
148
180
  child.stderr.on("data", relayStderr);
149
181
 
182
+ schedulePassiveUpdateNotice();
183
+
150
184
  let cleanedUp = false;
185
+ const shutdownState = {
186
+ forceTimer: null,
187
+ parentSignal: null
188
+ };
151
189
  const cleanup = () => {
152
190
  if (cleanedUp) {
153
191
  return;
154
192
  }
155
193
 
156
194
  cleanedUp = true;
195
+ if (shutdownState.forceTimer) {
196
+ clearTimeout(shutdownState.forceTimer);
197
+ }
157
198
  process.off("SIGINT", forwardSignal);
158
199
  process.off("SIGTERM", forwardSignal);
159
200
  process.off("SIGQUIT", forwardSignal);
@@ -161,9 +202,28 @@ async function startServer(rawArgs) {
161
202
  };
162
203
 
163
204
  const forwardSignal = (signal) => {
164
- if (!child.killed) {
165
- child.kill(signal);
205
+ if (shutdownState.parentSignal) {
206
+ if (sendSignalToChild(child, "SIGKILL")) {
207
+ console.warn("Force stopping AgentOS...");
208
+ }
209
+ return;
210
+ }
211
+
212
+ shutdownState.parentSignal = signal;
213
+
214
+ if (signal === "SIGINT") {
215
+ process.stdout.write("\nStopping AgentOS... Press Ctrl+C again to force quit.\n");
216
+ } else {
217
+ console.log(`Stopping AgentOS after ${signal}...`);
166
218
  }
219
+
220
+ sendSignalToChild(child, "SIGTERM");
221
+ shutdownState.forceTimer = setTimeout(() => {
222
+ if (sendSignalToChild(child, "SIGKILL")) {
223
+ console.warn("AgentOS did not stop in time. Sending SIGKILL...");
224
+ }
225
+ }, stopTimeoutMs);
226
+ shutdownState.forceTimer.unref?.();
167
227
  };
168
228
 
169
229
  process.on("SIGINT", forwardSignal);
@@ -179,6 +239,11 @@ async function startServer(rawArgs) {
179
239
  child.on("exit", (code, signal) => {
180
240
  cleanup();
181
241
 
242
+ if (shutdownState.parentSignal) {
243
+ process.kill(process.pid, shutdownState.parentSignal);
244
+ return;
245
+ }
246
+
182
247
  if (signal) {
183
248
  process.kill(process.pid, signal);
184
249
  return;
@@ -251,14 +316,89 @@ function runDoctor() {
251
316
  }
252
317
  }
253
318
 
319
+ async function runUpdate(rawArgs) {
320
+ const options = parseUpdateArgs(rawArgs);
321
+ const install = inspectInstallation();
322
+
323
+ if (install.kind === "package-manager") {
324
+ if (options.check) {
325
+ const status = await getUpdateStatus({
326
+ install,
327
+ forceRefresh: true,
328
+ timeoutMs: updateRequestTimeoutMs,
329
+ fallbackToCache: false
330
+ });
331
+
332
+ if (!status.ok) {
333
+ console.error(status.errorMessage);
334
+ process.exitCode = 1;
335
+ return;
336
+ }
337
+
338
+ printUpdateStatus(status, {
339
+ includeInstallInstructions: true
340
+ });
341
+ process.exitCode = status.updateAvailable ? 1 : 0;
342
+ return;
343
+ }
344
+
345
+ printPackageManagerUpdateGuidance();
346
+ return;
347
+ }
348
+
349
+ if (install.kind === "source") {
350
+ console.log("This AgentOS copy looks like a source checkout, not a release installation.");
351
+ console.log(`Update it with git pull from: ${findRepoRoot()}`);
352
+ return;
353
+ }
354
+
355
+ const status = await getUpdateStatus({
356
+ install,
357
+ forceRefresh: true,
358
+ timeoutMs: updateRequestTimeoutMs,
359
+ fallbackToCache: false
360
+ });
361
+
362
+ if (!status.ok) {
363
+ console.error(status.errorMessage);
364
+ process.exitCode = 1;
365
+ return;
366
+ }
367
+
368
+ if (options.check) {
369
+ printUpdateStatus(status, {
370
+ includeInstallInstructions: true
371
+ });
372
+ process.exitCode = status.updateAvailable ? 1 : 0;
373
+ return;
374
+ }
375
+
376
+ if (!status.updateAvailable) {
377
+ console.log(`AgentOS is already up to date (${status.currentVersion}).`);
378
+ return;
379
+ }
380
+
381
+ await installReleaseUpdate(status);
382
+ clearUpdateCache();
383
+ console.log(`Updated AgentOS to ${status.latestVersion}. Restart AgentOS to use the new version.`);
384
+ }
385
+
254
386
  async function runStop(rawArgs) {
255
387
  const options = parseStopArgs(rawArgs);
256
388
  const runtimeStatePath = resolveRuntimeStatePath(options.port);
257
389
  const trackedState = readRuntimeState(runtimeStatePath);
258
- const targetPid = trackedState?.pid ?? findListeningPidForPort(options.port);
390
+ const listeningPid = findListeningPidForPort(options.port);
391
+ const trackedPid =
392
+ trackedState?.pid && isProcessRunning(trackedState.pid) && getStartupWaitMs(trackedState) > 0 ? trackedState.pid : null;
393
+ const targetPid = listeningPid ?? trackedPid;
259
394
 
260
395
  if (!targetPid) {
261
- clearRuntimeState(runtimeStatePath);
396
+ if (trackedState) {
397
+ clearRuntimeState(runtimeStatePath);
398
+ console.log(`Cleared stale AgentOS runtime state for port ${options.port}. No process is listening on that port.`);
399
+ return;
400
+ }
401
+
262
402
  console.log(`No running AgentOS process was found on port ${options.port}.`);
263
403
  return;
264
404
  }
@@ -280,6 +420,12 @@ async function runStop(rawArgs) {
280
420
  const stopped = await waitForProcessExit(targetPid, options.force ? 1_000 : stopTimeoutMs);
281
421
 
282
422
  if (!stopped) {
423
+ if (!findListeningPidForPort(options.port)) {
424
+ clearRuntimeState(runtimeStatePath);
425
+ console.log(`Cleared stale AgentOS runtime state for port ${options.port}. No process is listening on that port.`);
426
+ return;
427
+ }
428
+
283
429
  console.error(
284
430
  options.force
285
431
  ? `AgentOS did not stop after SIGKILL on port ${options.port}.`
@@ -386,6 +532,23 @@ function parseStopArgs(rawArgs) {
386
532
  return options;
387
533
  }
388
534
 
535
+ function parseUpdateArgs(rawArgs) {
536
+ const options = {
537
+ check: false
538
+ };
539
+
540
+ for (const arg of rawArgs) {
541
+ if (arg === "--check") {
542
+ options.check = true;
543
+ continue;
544
+ }
545
+
546
+ throw new Error(`Unknown argument: ${arg}`);
547
+ }
548
+
549
+ return options;
550
+ }
551
+
389
552
  function parseUninstallArgs(rawArgs) {
390
553
  const options = {
391
554
  yes: false
@@ -480,6 +643,87 @@ function detectBrowserOpener() {
480
643
  };
481
644
  }
482
645
 
646
+ function schedulePassiveUpdateNotice() {
647
+ const install = inspectInstallation();
648
+
649
+ if (install.kind === "source") {
650
+ return;
651
+ }
652
+
653
+ const cachedStatus = readCachedUpdateStatus(install);
654
+ const normalizedCachedStatus = cachedStatus ? buildUpdateStatusFromCache(cachedStatus, install) : null;
655
+
656
+ if (normalizedCachedStatus?.updateAvailable && shouldNotifyCachedUpdate(normalizedCachedStatus)) {
657
+ printPassiveUpdateWarning(normalizedCachedStatus);
658
+ markUpdateNotified(normalizedCachedStatus);
659
+ return;
660
+ }
661
+
662
+ if (cachedStatus && isUpdateCacheFresh(cachedStatus)) {
663
+ return;
664
+ }
665
+
666
+ void getUpdateStatus({
667
+ install,
668
+ forceRefresh: true,
669
+ timeoutMs: 2_500,
670
+ fallbackToCache: true
671
+ })
672
+ .then((status) => {
673
+ if (!status.ok || !status.updateAvailable || !shouldNotifyCachedUpdate(status)) {
674
+ return;
675
+ }
676
+
677
+ printPassiveUpdateWarning(status);
678
+ markUpdateNotified(status);
679
+ })
680
+ .catch(() => {});
681
+ }
682
+
683
+ async function detectExistingServer(options, trackedState, runtimeStatePath) {
684
+ const listeningPid = findListeningPidForPort(options.port);
685
+
686
+ if (listeningPid) {
687
+ const host = trackedState?.host || options.host;
688
+ syncRuntimeState(runtimeStatePath, trackedState, {
689
+ pid: listeningPid,
690
+ port: options.port,
691
+ host
692
+ });
693
+
694
+ return {
695
+ pid: listeningPid,
696
+ url: createAgentOsUrl(host, options.port)
697
+ };
698
+ }
699
+
700
+ if (!trackedState?.pid || !isProcessRunning(trackedState.pid)) {
701
+ return null;
702
+ }
703
+
704
+ const waitMs = getStartupWaitMs(trackedState);
705
+
706
+ if (waitMs > 0) {
707
+ const readyPid = await waitForListeningPid(options.port, waitMs);
708
+
709
+ if (readyPid) {
710
+ const host = trackedState.host || options.host;
711
+ syncRuntimeState(runtimeStatePath, trackedState, {
712
+ pid: readyPid,
713
+ port: options.port,
714
+ host
715
+ });
716
+
717
+ return {
718
+ pid: readyPid,
719
+ url: createAgentOsUrl(host, options.port)
720
+ };
721
+ }
722
+ }
723
+
724
+ return null;
725
+ }
726
+
483
727
  function ensureBundleExists() {
484
728
  if (!existsSync(bundledServerPath)) {
485
729
  throw new Error(
@@ -488,16 +732,21 @@ function ensureBundleExists() {
488
732
  }
489
733
  }
490
734
 
735
+ function createAgentOsUrl(host, port) {
736
+ return `http://${displayHost(host)}:${port}`;
737
+ }
738
+
491
739
  function displayHost(host) {
492
740
  return host === "0.0.0.0" ? "127.0.0.1" : host;
493
741
  }
494
742
 
495
743
  function printHelp() {
496
- console.log(`AgentOS Mission Control
744
+ console.log(`AgentOS
497
745
 
498
746
  Usage:
499
747
  agentos
500
748
  agentos start --port 3000 --host 127.0.0.1 --open
749
+ agentos update [--check]
501
750
  agentos stop --port 3000 [--force]
502
751
  agentos doctor
503
752
  agentos uninstall [--yes]
@@ -506,7 +755,7 @@ Usage:
506
755
  Options:
507
756
  start: --port, -p Port to bind the local server (default: 3000)
508
757
  start: --host, -H Host to bind the local server (default: 127.0.0.1)
509
- start: --open, -o Open AgentOS in the default browser after startup
758
+ start: --open, -o Open AgentOS in the default browser after startup or reuse an existing instance
510
759
  start: --no-open Disable browser auto-open even if AGENTOS_OPEN is set
511
760
  stop: --port, -p Port to stop (default: 3000)
512
761
  stop: --force, -f Send SIGKILL if SIGTERM does not stop the server
@@ -555,6 +804,57 @@ function createRelay(target, options, url, browserOpener, browserState) {
555
804
  };
556
805
  }
557
806
 
807
+ function printUpdateStatus(status, options = {}) {
808
+ if (status.updateAvailable) {
809
+ console.log(`Update available: ${status.currentVersion} -> ${status.latestVersion}.`);
810
+
811
+ if (options.includeInstallInstructions) {
812
+ if (status.install.kind === "release") {
813
+ console.log('Run "agentos update" to install it.');
814
+ } else if (status.install.kind === "package-manager") {
815
+ printPackageManagerUpdateGuidance();
816
+ }
817
+ }
818
+
819
+ return;
820
+ }
821
+
822
+ console.log(`AgentOS is already up to date (${status.currentVersion}).`);
823
+ }
824
+
825
+ function printPassiveUpdateWarning(status) {
826
+ if (status.install.kind === "package-manager") {
827
+ console.warn(`Update available: ${status.currentVersion} -> ${status.latestVersion}.`);
828
+ printPackageManagerUpdateGuidance();
829
+ return;
830
+ }
831
+
832
+ console.warn(`Update available: ${status.currentVersion} -> ${status.latestVersion}. Run "agentos update".`);
833
+ }
834
+
835
+ function printPackageManagerUpdateGuidance() {
836
+ console.log("This AgentOS install appears to come from a package manager.");
837
+ console.log("Update it with one of:");
838
+ console.log(" pnpm add -g @sapienx/agentos@latest");
839
+ console.log(" npm install -g @sapienx/agentos@latest");
840
+ }
841
+
842
+ function printUpdateHelp() {
843
+ console.log(`Update AgentOS.
844
+
845
+ Usage:
846
+ agentos update
847
+ agentos update --check
848
+
849
+ Options:
850
+ --check Check for a newer version without installing it
851
+
852
+ Notes:
853
+ - Release installs can update themselves with this command.
854
+ - Package manager installs are redirected to pnpm or npm.
855
+ `);
856
+ }
857
+
558
858
  function openBrowser(url, browserOpener) {
559
859
  const browser = spawn(browserOpener.command, [...browserOpener.args, url], {
560
860
  detached: true,
@@ -568,6 +868,418 @@ function openBrowser(url, browserOpener) {
568
868
  browser.unref();
569
869
  }
570
870
 
871
+ function shouldNotifyCachedUpdate(status) {
872
+ if (status.latestVersion !== status.cachedNotifiedVersion) {
873
+ return true;
874
+ }
875
+
876
+ if (!status.cachedNotifiedAt) {
877
+ return true;
878
+ }
879
+
880
+ return Date.now() - Date.parse(status.cachedNotifiedAt) >= updateWarningCooldownMs;
881
+ }
882
+
883
+ function isUpdateCacheFresh(status) {
884
+ if (!status.cachedCheckedAt) {
885
+ return false;
886
+ }
887
+
888
+ const checkedAtMs = Date.parse(status.cachedCheckedAt);
889
+
890
+ if (!Number.isFinite(checkedAtMs)) {
891
+ return false;
892
+ }
893
+
894
+ return Date.now() - checkedAtMs < updateCacheTtlMs;
895
+ }
896
+
897
+ function markUpdateNotified(status) {
898
+ writeUpdateCache({
899
+ ...status,
900
+ cachedNotifiedVersion: status.latestVersion,
901
+ cachedNotifiedAt: new Date().toISOString()
902
+ });
903
+ }
904
+
905
+ function clearUpdateCache() {
906
+ rmSync(updateCachePath, {
907
+ force: true
908
+ });
909
+ }
910
+
911
+ function readCachedUpdateStatus(install) {
912
+ if (!existsSync(updateCachePath)) {
913
+ return null;
914
+ }
915
+
916
+ try {
917
+ const payload = JSON.parse(readFileSync(updateCachePath, "utf8"));
918
+
919
+ if (!payload || typeof payload !== "object") {
920
+ return null;
921
+ }
922
+
923
+ if (
924
+ payload.installKind !== install.kind ||
925
+ payload.currentVersion !== packageJson.version ||
926
+ payload.sourceId !== getUpdateSourceId(install)
927
+ ) {
928
+ return null;
929
+ }
930
+
931
+ return payload;
932
+ } catch {
933
+ return null;
934
+ }
935
+ }
936
+
937
+ function writeUpdateCache(payload) {
938
+ mkdirSync(updateCacheDir, {
939
+ recursive: true
940
+ });
941
+
942
+ writeFileSync(updateCachePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
943
+ }
944
+
945
+ async function getUpdateStatus({ install, forceRefresh, timeoutMs, fallbackToCache }) {
946
+ try {
947
+ const cache = readCachedUpdateStatus(install);
948
+
949
+ if (!forceRefresh && cache && isUpdateCacheFresh(cache)) {
950
+ return buildUpdateStatusFromCache(cache, install);
951
+ }
952
+
953
+ const latestVersionInfo = await fetchLatestVersionInfo(install, timeoutMs);
954
+ const updateAvailable = compareVersions(latestVersionInfo.latestVersion, packageJson.version) > 0;
955
+ const status = {
956
+ ok: true,
957
+ install,
958
+ installKind: install.kind,
959
+ currentVersion: packageJson.version,
960
+ latestVersion: latestVersionInfo.latestVersion,
961
+ downloadBaseUrl: latestVersionInfo.downloadBaseUrl || null,
962
+ updateAvailable,
963
+ sourceId: getUpdateSourceId(install),
964
+ cachedCheckedAt: new Date().toISOString(),
965
+ cachedNotifiedVersion: cache?.cachedNotifiedVersion || null,
966
+ cachedNotifiedAt: cache?.cachedNotifiedAt || null
967
+ };
968
+
969
+ writeUpdateCache(status);
970
+ return status;
971
+ } catch (error) {
972
+ const message = error instanceof Error ? error.message : String(error);
973
+ const cache = readCachedUpdateStatus(install);
974
+
975
+ if (fallbackToCache && cache) {
976
+ return {
977
+ ...buildUpdateStatusFromCache(cache, install),
978
+ ok: true
979
+ };
980
+ }
981
+
982
+ return {
983
+ ok: false,
984
+ errorMessage: `Unable to check for updates: ${message}`
985
+ };
986
+ }
987
+ }
988
+
989
+ function buildUpdateStatusFromCache(cache, install) {
990
+ const updateAvailable = compareVersions(cache.latestVersion, packageJson.version) > 0;
991
+
992
+ return {
993
+ ok: true,
994
+ install,
995
+ currentVersion: packageJson.version,
996
+ latestVersion: cache.latestVersion,
997
+ downloadBaseUrl: cache.downloadBaseUrl || null,
998
+ installKind: install.kind,
999
+ updateAvailable,
1000
+ sourceId: getUpdateSourceId(install),
1001
+ cachedCheckedAt: cache.cachedCheckedAt || null,
1002
+ cachedNotifiedVersion: cache.cachedNotifiedVersion || null,
1003
+ cachedNotifiedAt: cache.cachedNotifiedAt || null
1004
+ };
1005
+ }
1006
+
1007
+ function getUpdateSourceId(install) {
1008
+ if (install.kind === "release") {
1009
+ return `github:${process.env.AGENTOS_REPO || "SapienXai/AgentOS"}`;
1010
+ }
1011
+
1012
+ if (install.kind === "package-manager") {
1013
+ return `npm:${packageJson.name}`;
1014
+ }
1015
+
1016
+ return "source";
1017
+ }
1018
+
1019
+ async function fetchLatestVersionInfo(install, timeoutMs) {
1020
+ if (install.kind === "release") {
1021
+ return fetchGitHubLatestVersion(timeoutMs);
1022
+ }
1023
+
1024
+ if (install.kind === "package-manager") {
1025
+ return fetchNpmLatestVersion(timeoutMs);
1026
+ }
1027
+
1028
+ throw new Error("Update checks are not supported for source checkouts.");
1029
+ }
1030
+
1031
+ async function fetchGitHubLatestVersion(timeoutMs) {
1032
+ const repo = process.env.AGENTOS_REPO || "SapienXai/AgentOS";
1033
+ const response = await fetchJsonWithTimeout(`https://api.github.com/repos/${repo}/releases/latest`, timeoutMs, {
1034
+ headers: {
1035
+ Accept: "application/vnd.github+json",
1036
+ "User-Agent": "AgentOS"
1037
+ }
1038
+ });
1039
+
1040
+ const tagName = typeof response.tag_name === "string" ? response.tag_name : "";
1041
+ const latestVersion = normalizeVersion(tagName);
1042
+
1043
+ if (!latestVersion) {
1044
+ throw new Error("GitHub release metadata did not include a valid version.");
1045
+ }
1046
+
1047
+ return {
1048
+ latestVersion,
1049
+ downloadBaseUrl: `https://github.com/${repo}/releases/download/agentos-v${latestVersion}`
1050
+ };
1051
+ }
1052
+
1053
+ async function fetchNpmLatestVersion(timeoutMs) {
1054
+ const response = await fetchJsonWithTimeout(`https://registry.npmjs.org/${encodeURIComponent(packageJson.name)}/latest`, timeoutMs, {
1055
+ headers: {
1056
+ Accept: "application/json",
1057
+ "User-Agent": "AgentOS"
1058
+ }
1059
+ });
1060
+
1061
+ const latestVersion = normalizeVersion(response.version);
1062
+
1063
+ if (!latestVersion) {
1064
+ throw new Error("npm registry metadata did not include a valid version.");
1065
+ }
1066
+
1067
+ return {
1068
+ latestVersion,
1069
+ downloadBaseUrl: null
1070
+ };
1071
+ }
1072
+
1073
+ async function fetchJsonWithTimeout(url, timeoutMs, options = {}) {
1074
+ const controller = new AbortController();
1075
+ const timeout = setTimeout(() => {
1076
+ controller.abort();
1077
+ }, timeoutMs);
1078
+
1079
+ try {
1080
+ const response = await fetch(url, {
1081
+ ...options,
1082
+ signal: controller.signal
1083
+ });
1084
+
1085
+ if (!response.ok) {
1086
+ throw new Error(`Request failed with status ${response.status}.`);
1087
+ }
1088
+
1089
+ return await response.json();
1090
+ } finally {
1091
+ clearTimeout(timeout);
1092
+ }
1093
+ }
1094
+
1095
+ function normalizeVersion(value) {
1096
+ if (!value || typeof value !== "string") {
1097
+ return null;
1098
+ }
1099
+
1100
+ const match = value.trim().match(/(?:agentos-v|v)?(\d+)\.(\d+)\.(\d+)/i);
1101
+
1102
+ if (!match) {
1103
+ return null;
1104
+ }
1105
+
1106
+ return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
1107
+ }
1108
+
1109
+ function compareVersions(a, b) {
1110
+ const left = parseVersion(a);
1111
+ const right = parseVersion(b);
1112
+
1113
+ if (!left || !right) {
1114
+ return 0;
1115
+ }
1116
+
1117
+ if (left.major !== right.major) {
1118
+ return left.major - right.major;
1119
+ }
1120
+
1121
+ if (left.minor !== right.minor) {
1122
+ return left.minor - right.minor;
1123
+ }
1124
+
1125
+ return left.patch - right.patch;
1126
+ }
1127
+
1128
+ function parseVersion(value) {
1129
+ const normalized = normalizeVersion(value);
1130
+
1131
+ if (!normalized) {
1132
+ return null;
1133
+ }
1134
+
1135
+ const parts = normalized.split(".").map(Number);
1136
+
1137
+ return {
1138
+ major: parts[0],
1139
+ minor: parts[1],
1140
+ patch: parts[2]
1141
+ };
1142
+ }
1143
+
1144
+ async function installReleaseUpdate(status) {
1145
+ const artifactName = `agentos-${getAssetPlatform()}-${getAssetArch()}.tgz`;
1146
+ const checksumName = `${artifactName}.sha256`;
1147
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "agentos-update-"));
1148
+
1149
+ try {
1150
+ const artifactPath = path.join(tempDir, artifactName);
1151
+ const checksumPath = path.join(tempDir, checksumName);
1152
+ const stageDir = path.join(tempDir, "stage");
1153
+
1154
+ await downloadFileToPath(`${status.downloadBaseUrl}/${artifactName}`, artifactPath);
1155
+
1156
+ try {
1157
+ await downloadFileToPath(`${status.downloadBaseUrl}/${checksumName}`, checksumPath);
1158
+ verifyChecksumFile(checksumPath, artifactPath);
1159
+ } catch {
1160
+ console.warn("No checksum file found; skipping SHA-256 verification.");
1161
+ }
1162
+
1163
+ mkdirSync(stageDir, {
1164
+ recursive: true
1165
+ });
1166
+
1167
+ const extractResult = spawnSync("tar", ["-xzf", artifactPath, "-C", stageDir], {
1168
+ encoding: "utf8"
1169
+ });
1170
+
1171
+ if (extractResult.error || extractResult.status !== 0) {
1172
+ throw new Error(`Failed to extract update archive: ${extractResult.stderr || extractResult.error?.message || "tar failed"}`);
1173
+ }
1174
+
1175
+ const stagedPackagePath = path.join(stageDir, "package");
1176
+
1177
+ if (!existsSync(stagedPackagePath)) {
1178
+ throw new Error("Update archive did not contain a package directory.");
1179
+ }
1180
+
1181
+ const backupPackagePath = `${packageRoot}.previous-${Date.now()}`;
1182
+
1183
+ renameSync(packageRoot, backupPackagePath);
1184
+
1185
+ try {
1186
+ renameSync(stagedPackagePath, packageRoot);
1187
+ } catch (error) {
1188
+ renameSync(backupPackagePath, packageRoot);
1189
+ throw error;
1190
+ }
1191
+
1192
+ rmSync(backupPackagePath, {
1193
+ recursive: true,
1194
+ force: true
1195
+ });
1196
+ } finally {
1197
+ rmSync(tempDir, {
1198
+ recursive: true,
1199
+ force: true
1200
+ });
1201
+ }
1202
+ }
1203
+
1204
+ function verifyChecksumFile(checksumPath, artifactPath) {
1205
+ const checksumLine = readFileSync(checksumPath, "utf8").trim();
1206
+
1207
+ if (!checksumLine) {
1208
+ throw new Error(`Checksum file is empty: ${checksumPath}`);
1209
+ }
1210
+
1211
+ const parts = checksumLine.split(/\s+/);
1212
+ const expectedHash = parts[0];
1213
+ const expectedName = parts[parts.length - 1];
1214
+ const actualName = path.basename(artifactPath);
1215
+
1216
+ if (expectedName !== actualName) {
1217
+ throw new Error(`Checksum file does not match ${actualName}.`);
1218
+ }
1219
+
1220
+ const actualHash = createHash("sha256").update(readFileSync(artifactPath)).digest("hex");
1221
+
1222
+ if (actualHash.toLowerCase() !== expectedHash.toLowerCase()) {
1223
+ throw new Error(`SHA-256 verification failed for ${artifactPath}.`);
1224
+ }
1225
+ }
1226
+
1227
+ async function downloadFileToPath(url, targetPath) {
1228
+ const response = await fetch(url, {
1229
+ headers: {
1230
+ "User-Agent": "AgentOS"
1231
+ }
1232
+ });
1233
+
1234
+ if (!response.ok) {
1235
+ throw new Error(`Download failed with status ${response.status}.`);
1236
+ }
1237
+
1238
+ const arrayBuffer = await response.arrayBuffer();
1239
+ writeFileSync(targetPath, Buffer.from(arrayBuffer));
1240
+ }
1241
+
1242
+ function getAssetPlatform() {
1243
+ if (process.platform === "darwin") {
1244
+ return "darwin";
1245
+ }
1246
+
1247
+ if (process.platform === "win32") {
1248
+ return "win32";
1249
+ }
1250
+
1251
+ return "linux";
1252
+ }
1253
+
1254
+ function getAssetArch() {
1255
+ if (process.arch === "arm64") {
1256
+ return "arm64";
1257
+ }
1258
+
1259
+ return "x64";
1260
+ }
1261
+
1262
+ function sendSignalToChild(child, signal) {
1263
+ if (!isChildProcessActive(child)) {
1264
+ return false;
1265
+ }
1266
+
1267
+ try {
1268
+ child.kill(signal);
1269
+ return true;
1270
+ } catch (error) {
1271
+ if (error && typeof error === "object" && "code" in error && error.code === "ESRCH") {
1272
+ return false;
1273
+ }
1274
+
1275
+ throw error;
1276
+ }
1277
+ }
1278
+
1279
+ function isChildProcessActive(child) {
1280
+ return Boolean(child.pid) && child.exitCode === null && child.signalCode === null;
1281
+ }
1282
+
571
1283
  function resolveCommandPath(command) {
572
1284
  if (process.platform === "win32") {
573
1285
  const result = spawnSync("where", [command], {
@@ -785,6 +1497,25 @@ function writeRuntimeState(runtimeStatePath, payload) {
785
1497
  writeFileSync(runtimeStatePath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
786
1498
  }
787
1499
 
1500
+ function syncRuntimeState(runtimeStatePath, trackedState, payload) {
1501
+ if (
1502
+ trackedState?.pid === payload.pid &&
1503
+ trackedState.port === payload.port &&
1504
+ trackedState.host === payload.host
1505
+ ) {
1506
+ return;
1507
+ }
1508
+
1509
+ try {
1510
+ writeRuntimeState(runtimeStatePath, {
1511
+ ...payload,
1512
+ startedAt: trackedState?.startedAt || new Date().toISOString()
1513
+ });
1514
+ } catch {
1515
+ // Port discovery still lets stop/find logic work even if the state file cannot be refreshed.
1516
+ }
1517
+ }
1518
+
788
1519
  function clearRuntimeState(runtimeStatePath, expectedPid) {
789
1520
  if (existsSync(runtimeStatePath)) {
790
1521
  if (expectedPid) {
@@ -853,6 +1584,42 @@ function findListeningPidForPort(port) {
853
1584
  return Number(firstLine);
854
1585
  }
855
1586
 
1587
+ function getStartupWaitMs(trackedState) {
1588
+ if (!trackedState?.startedAt) {
1589
+ return 0;
1590
+ }
1591
+
1592
+ const startedAtMs = Date.parse(trackedState.startedAt);
1593
+
1594
+ if (!Number.isFinite(startedAtMs)) {
1595
+ return 0;
1596
+ }
1597
+
1598
+ return Math.max(0, startupGracePeriodMs - (Date.now() - startedAtMs));
1599
+ }
1600
+
1601
+ async function waitForListeningPid(port, timeoutMs) {
1602
+ if (timeoutMs <= 0) {
1603
+ return findListeningPidForPort(port);
1604
+ }
1605
+
1606
+ const startedAt = Date.now();
1607
+
1608
+ while (Date.now() - startedAt < timeoutMs) {
1609
+ const pid = findListeningPidForPort(port);
1610
+
1611
+ if (pid) {
1612
+ return pid;
1613
+ }
1614
+
1615
+ await new Promise((resolve) => {
1616
+ setTimeout(resolve, stopPollIntervalMs);
1617
+ });
1618
+ }
1619
+
1620
+ return findListeningPidForPort(port);
1621
+ }
1622
+
856
1623
  async function waitForProcessExit(pid, timeoutMs) {
857
1624
  const startedAt = Date.now();
858
1625