@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.
- package/README.md +12 -2
- package/bin/agentos.js +777 -10
- package/bundle/.next/BUILD_ID +1 -1
- package/bundle/.next/app-path-routes-manifest.json +10 -8
- package/bundle/.next/build-manifest.json +3 -3
- package/bundle/.next/prerender-manifest.json +3 -3
- package/bundle/.next/react-loadable-manifest.json +3 -3
- package/bundle/.next/required-server-files.json +16 -0
- package/bundle/.next/routes-manifest.json +14 -0
- package/bundle/.next/server/app/_global-error/page.js +3 -3
- package/bundle/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/bundle/.next/server/app/_global-error.html +1 -1
- package/bundle/.next/server/app/_global-error.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/bundle/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/bundle/.next/server/app/_not-found/page.js +2 -2
- package/bundle/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/bundle/.next/server/app/_not-found.html +1 -1
- package/bundle/.next/server/app/_not-found.rsc +3 -3
- package/bundle/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/bundle/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/bundle/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/bundle/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/bundle/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/bundle/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/bundle/.next/server/app/api/agents/[agentId]/chat/route.js +8 -1
- package/bundle/.next/server/app/api/agents/[agentId]/chat/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/agents/route.js +1 -1
- package/bundle/.next/server/app/api/agents/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/diagnostics/route.js +1 -1
- package/bundle/.next/server/app/api/diagnostics/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/files/reveal/route.js +1 -1
- package/bundle/.next/server/app/api/gateway/control/route.js +1 -1
- package/bundle/.next/server/app/api/gateway/control/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/mission/route.js +1 -1
- package/bundle/.next/server/app/api/mission/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/models/catalog/route.js +1 -0
- package/bundle/.next/server/app/api/models/catalog/route.js.nft.json +1 -0
- package/bundle/.next/server/app/api/models/catalog/route_client-reference-manifest.js +1 -0
- package/bundle/.next/server/app/api/models/providers/route.js +2 -2
- package/bundle/.next/server/app/api/models/providers/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/onboarding/models/route.js +7 -7
- package/bundle/.next/server/app/api/onboarding/models/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/onboarding/route.js +8 -8
- package/bundle/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/openclaw/capabilities/route.js +3 -3
- package/bundle/.next/server/app/api/planner/[planId]/deploy/route.js +2 -2
- package/bundle/.next/server/app/api/planner/[planId]/deploy/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/document-rewrite/route.js +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/document-rewrite/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/route.js +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/simulate/route.js +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/simulate/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/turn/route.js +1 -1
- package/bundle/.next/server/app/api/planner/[planId]/turn/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/planner/route.js +1 -1
- package/bundle/.next/server/app/api/planner/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/reset/route.js +3 -3
- package/bundle/.next/server/app/api/reset/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/runtimes/[runtimeId]/route.js +1 -1
- package/bundle/.next/server/app/api/runtimes/[runtimeId]/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/settings/gateway/route.js +1 -1
- package/bundle/.next/server/app/api/settings/gateway/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/settings/workspace-root/route.js +1 -1
- package/bundle/.next/server/app/api/settings/workspace-root/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/snapshot/route.js +1 -1
- package/bundle/.next/server/app/api/snapshot/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/stream/route.js +2 -2
- package/bundle/.next/server/app/api/stream/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/system/open-terminal/route.js +1 -1
- package/bundle/.next/server/app/api/tasks/[taskId]/abort/route.js +1 -1
- package/bundle/.next/server/app/api/tasks/[taskId]/abort/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/tasks/[taskId]/stream/route.js +2 -2
- package/bundle/.next/server/app/api/tasks/[taskId]/stream/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/update/route.js +4 -4
- package/bundle/.next/server/app/api/update/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/discovered-groups/route.js +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/discovered-groups/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/route.js +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/channels/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/edit-draft/route.js +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/edit-draft/route.js.nft.json +1 -1
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route.js +1 -0
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route.js.nft.json +1 -0
- package/bundle/.next/server/app/api/workspaces/[workspaceId]/surfaces/discovery/route_client-reference-manifest.js +1 -0
- package/bundle/.next/server/app/api/workspaces/route.js +2 -2
- package/bundle/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/bundle/.next/server/app/page.js +44 -41
- package/bundle/.next/server/app/page.js.nft.json +1 -1
- package/bundle/.next/server/app/page_client-reference-manifest.js +1 -1
- package/bundle/.next/server/app-paths-manifest.json +10 -8
- package/bundle/.next/server/chunks/199.js +412 -0
- package/bundle/.next/server/chunks/639.js +98 -0
- package/bundle/.next/server/chunks/661.js +1 -1
- package/bundle/.next/server/functions-config-manifest.json +3 -1
- package/bundle/.next/server/middleware-build-manifest.js +1 -1
- package/bundle/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/bundle/.next/server/pages/404.html +1 -1
- package/bundle/.next/server/pages/500.html +1 -1
- package/bundle/.next/server/server-reference-manifest.json +1 -1
- package/bundle/.next/static/3RfU29upg-cPxACyzCFUb/_buildManifest.js +1 -0
- package/bundle/.next/static/chunks/{1364.5bd755c8584fcbc8.js → 2999.fef298fbe30cd4bd.js} +1 -1
- package/bundle/.next/static/chunks/5261.29ae1e1fe185a617.js +1 -0
- package/bundle/.next/static/chunks/7963-6bd42810c5733ae8.js +15 -0
- package/bundle/.next/static/chunks/app/_global-error/page-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/_not-found/page-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/agents/[agentId]/chat/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/agents/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/diagnostics/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/files/reveal/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/gateway/control/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/mission/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/models/catalog/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/models/providers/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/onboarding/models/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/onboarding/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/openclaw/capabilities/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/[planId]/deploy/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/[planId]/document-rewrite/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/[planId]/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/[planId]/simulate/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/[planId]/turn/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/planner/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/reset/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/runtimes/[runtimeId]/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/settings/gateway/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/settings/workspace-root/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/snapshot/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/stream/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/system/open-terminal/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/tasks/[taskId]/abort/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/tasks/[taskId]/stream/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/update/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/discovered-groups/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/edit-draft/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/surfaces/discovery/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/api/workspaces/route-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/not-found-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/app/page-29f20effd872e616.js +179 -0
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/app-error-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/forbidden-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cde9c645dcfceb9f.js +1 -0
- package/bundle/.next/static/chunks/{webpack-4fdc6767ef8ff7b1.js → webpack-e0accbaa78fb0b56.js} +1 -1
- package/bundle/.next/static/css/9f1b31fd4086df59.css +3 -0
- package/bundle/package.json +1 -1
- package/bundle/public/readme/banner.jpeg +0 -0
- package/bundle/public/readme/mission-control-hero.svg +2 -2
- package/bundle/public/site.webmanifest +1 -1
- package/bundle/scripts/openclaw-mission-dispatch-runner.mjs +180 -9
- package/bundle/server.js +1 -1
- package/package.json +2 -2
- package/bundle/.next/server/chunks/212.js +0 -98
- package/bundle/.next/server/chunks/49.js +0 -420
- package/bundle/.next/static/PMHP584Iqd3KMZJH68hEV/_buildManifest.js +0 -1
- package/bundle/.next/static/chunks/1506.ec91294c1cbc454f.js +0 -1
- package/bundle/.next/static/chunks/5954-fd2a9c905dcede3c.js +0 -15
- package/bundle/.next/static/chunks/app/_global-error/page-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/_not-found/page-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/agents/[agentId]/chat/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/agents/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/diagnostics/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/files/reveal/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/gateway/control/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/mission/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/models/providers/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/onboarding/models/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/onboarding/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/openclaw/capabilities/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/[planId]/deploy/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/[planId]/document-rewrite/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/[planId]/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/[planId]/simulate/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/[planId]/turn/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/planner/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/reset/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/runtimes/[runtimeId]/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/settings/gateway/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/settings/workspace-root/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/snapshot/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/stream/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/system/open-terminal/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/tasks/[taskId]/abort/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/tasks/[taskId]/stream/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/update/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/discovered-groups/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/channels/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/workspaces/[workspaceId]/edit-draft/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/api/workspaces/route-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/not-found-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/app/page-e7ca3c6ffb09198b.js +0 -176
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/app-error-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/forbidden-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/chunks/next/dist/client/components/builtin/unauthorized-1a770949224cd2a0.js +0 -1
- package/bundle/.next/static/css/e20ab76ee4e77d93.css +0 -3
- /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 {
|
|
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 ${
|
|
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 (
|
|
165
|
-
child
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|