skydive-cli 0.5.0-beta.30 → 0.5.0-beta.32
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/CHANGELOG.md +1 -1
- package/dist/js/api-DQCaztBg.mjs +315 -0
- package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
- package/dist/js/bin.mjs +35 -30
- package/dist/js/{boot-0-dpPM1_.mjs → boot-yyXrIeVy.mjs} +110 -371
- package/dist/js/chunk-BbwQpWto.mjs +33 -0
- package/dist/js/{client-DbqRBquD.mjs → client-d6K9qm6B.mjs} +70 -2
- package/dist/js/client-yz3zrpAS.mjs +5 -0
- package/dist/js/daemon-Btd2436i.mjs +7 -0
- package/dist/js/{daemon-DuxaUuek.mjs → daemon-CxFxH36O.mjs} +4 -2
- package/dist/js/daemon-client-1Qfc_Y3J.mjs +8 -0
- package/dist/js/{daemon-client-C4heMWTq.mjs → daemon-client-Br1NlkRp.mjs} +1 -1
- package/dist/js/dist-CRtjM7ba.mjs +1750 -0
- package/dist/js/{forward-DL6DYqyc.mjs → forward-zwB55Bls.mjs} +25 -4
- package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
- package/dist/js/{print-CQkXZ02a.mjs → print-B9djx8GU.mjs} +3 -3
- package/dist/js/{print-DYiQC5IW.mjs → print-Clgq46GU.mjs} +3 -3
- package/dist/js/{print-share-BH9gvCls.mjs → print-share-BBy3OTno.mjs} +6 -2
- package/dist/js/{profiler-BoO7ldO0.mjs → profiler-CJMnC90G.mjs} +2 -2
- package/dist/js/{raw-pty-DY4KelZW.mjs → raw-pty-B9Bue8gg.mjs} +1 -1
- package/dist/js/raw-pty-g3kATVQe.mjs +5 -0
- package/dist/js/{rest-xXfqT4SX.mjs → rest-C6E4mS2A.mjs} +15 -2
- package/dist/js/rest-CaWUSrnH.mjs +6 -0
- package/dist/js/tls-cert-CLgSQALB.mjs +4 -0
- package/dist/js/tls-cert-CV-pwxVN.mjs +67 -0
- package/package.json +1 -1
- package/dist/js/api-DG5W6iwx.mjs +0 -131
- package/dist/js/client-hH2PJL8y.mjs +0 -5
- package/dist/js/daemon-Ce8WLdGI.mjs +0 -6
- package/dist/js/daemon-client-LJdnM_nE.mjs +0 -7
- package/dist/js/raw-pty-DmdUf4_w.mjs +0 -5
- package/dist/js/rest-DSpGS5XU.mjs +0 -6
- /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
- /package/dist/js/{client-c4c5MmgN.mjs → client-BTQ1fwzM.mjs} +0 -0
- /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-UHH3mVBF.mjs} +0 -0
- /package/dist/js/{output-DYzzdXYV.mjs → output-wY0VQDea.mjs} +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as takenAliasNames, C as themesForMode, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, L as listWorkspaces, N as detectShell, O as installAgentAlias, T as machineOsFromPlatform, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, k as slugifyAliasName, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode, z as setActiveWorkspace } from "./profiler-
|
|
3
|
-
import { B as resolveWebUrl, C as getConfigPath, O as getReviewStateDir, P as recordDefaultAgent, U as saveTheme, b as DEFAULT_APP_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, h as specKeyFor, i as resolveAgent, k as getSavedTheme, l as parseExternalOauthConnectParams, m as parseConnectCard, p as computeSettledLabel, s as MASK_CHAR, u as parseOauthConnectParams, y as DEFAULT_API_URL } from "./print-
|
|
4
|
-
import { t as HttpError } from "./http-error-
|
|
5
|
-
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
6
|
-
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-
|
|
7
|
-
import { t as PortalClient } from "./client-
|
|
8
|
-
import "./daemon-
|
|
9
|
-
import "./
|
|
10
|
-
import
|
|
11
|
-
import { t as
|
|
12
|
-
import { t as
|
|
2
|
+
import { A as takenAliasNames, C as themesForMode, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, L as listWorkspaces, N as detectShell, O as installAgentAlias, T as machineOsFromPlatform, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, k as slugifyAliasName, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode, z as setActiveWorkspace } from "./profiler-CJMnC90G.mjs";
|
|
3
|
+
import { B as resolveWebUrl, C as getConfigPath, O as getReviewStateDir, P as recordDefaultAgent, U as saveTheme, b as DEFAULT_APP_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, h as specKeyFor, i as resolveAgent, k as getSavedTheme, l as parseExternalOauthConnectParams, m as parseConnectCard, p as computeSettledLabel, s as MASK_CHAR, u as parseOauthConnectParams, y as DEFAULT_API_URL } from "./print-Clgq46GU.mjs";
|
|
4
|
+
import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
|
|
5
|
+
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-C6E4mS2A.mjs";
|
|
6
|
+
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-D3l5kJlX.mjs";
|
|
7
|
+
import { t as PortalClient } from "./client-d6K9qm6B.mjs";
|
|
8
|
+
import "./daemon-CxFxH36O.mjs";
|
|
9
|
+
import { t as defaultTlsCertSource } from "./tls-cert-CV-pwxVN.mjs";
|
|
10
|
+
import "./api-DQCaztBg.mjs";
|
|
11
|
+
import { t as SandboxStream } from "./client-BTQ1fwzM.mjs";
|
|
12
|
+
import { t as PortalDaemonClient } from "./daemon-client-Br1NlkRp.mjs";
|
|
13
|
+
import { t as runRawPtyPassthrough } from "./raw-pty-B9Bue8gg.mjs";
|
|
13
14
|
import * as os$1 from "node:os";
|
|
14
15
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
15
16
|
import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
|
|
@@ -396,6 +397,9 @@ function createPortalClient(opts) {
|
|
|
396
397
|
deviceToken: null
|
|
397
398
|
}),
|
|
398
399
|
resolveCwd: () => process.cwd(),
|
|
400
|
+
tlsCertSource: defaultTlsCertSource(process.env, (msg) => {
|
|
401
|
+
console.error(msg);
|
|
402
|
+
}),
|
|
399
403
|
persistedMachineName: null,
|
|
400
404
|
onMachineName: () => {},
|
|
401
405
|
onState: (state) => opts.onState({
|
|
@@ -4805,28 +4809,38 @@ function useConversationContext() {
|
|
|
4805
4809
|
/**
|
|
4806
4810
|
* `/computer`: a live snapshot of the agent's sandbox as a set of bar graphs.
|
|
4807
4811
|
*
|
|
4808
|
-
* The
|
|
4809
|
-
*
|
|
4810
|
-
*
|
|
4811
|
-
*
|
|
4812
|
-
*
|
|
4812
|
+
* The numbers come from the api, NOT from the CLI reaching into the sandbox.
|
|
4813
|
+
* The worker's fleet metrics sweep already samples every live box's CPU /
|
|
4814
|
+
* memory / disk from the provider and stores the latest per-sandbox snapshot;
|
|
4815
|
+
* the CLI reads that back over one plain REST call (`collectSandboxStats`). So
|
|
4816
|
+
* there is no exec into the box and no cold-start wake — when the agent has no
|
|
4817
|
+
* running sandbox the api answers "no snapshot" immediately instead of the card
|
|
4818
|
+
* hanging on a stream that never replies.
|
|
4813
4819
|
*
|
|
4814
|
-
*
|
|
4815
|
-
*
|
|
4816
|
-
* `SKYDIVE_STATS_V1` sentinel lets the parser find the block even when a shell
|
|
4817
|
-
* rc file prints a banner first, and versions the wire shape.
|
|
4820
|
+
* Everything in this module is pure so the layout math is unit-testable without
|
|
4821
|
+
* an api or a renderer.
|
|
4818
4822
|
*/
|
|
4819
|
-
const SANDBOX_STATS_SENTINEL = "SKYDIVE_STATS_V1";
|
|
4820
4823
|
/**
|
|
4821
4824
|
* How often the live `/computer` card refreshes its snapshot, in ms.
|
|
4822
4825
|
* Deliberately slow: the card is a background health readout, not a `top`, so
|
|
4823
|
-
* it refreshes quietly every 30s rather than flickering every second.
|
|
4826
|
+
* it refreshes quietly every 30s rather than flickering every second. Also
|
|
4827
|
+
* comfortably slower than the worker's ~60s sweep cadence, so we never poll for
|
|
4828
|
+
* a sample that can't have changed.
|
|
4824
4829
|
*/
|
|
4825
4830
|
const COMPUTER_POLL_INTERVAL_MS = 3e4;
|
|
4826
4831
|
/**
|
|
4832
|
+
* A snapshot older than this is stale enough that we no longer present it as
|
|
4833
|
+
* "current". The api sample carries the provider's measurement time; the sweep
|
|
4834
|
+
* that writes it runs ~every 60s and the Redis key TTLs at 5 min, so a sample
|
|
4835
|
+
* this old means the box likely stopped reporting (paused / gone). The card
|
|
4836
|
+
* treats it as "no current snapshot" rather than drawing minutes-old bars as if
|
|
4837
|
+
* they were live.
|
|
4838
|
+
*/
|
|
4839
|
+
const COMPUTER_STATS_STALE_MS = 5 * 6e4;
|
|
4840
|
+
/**
|
|
4827
4841
|
* Whether a `/computer` card should refresh right now. Pure so the gating is
|
|
4828
4842
|
* unit-testable. A card refreshes only when it is the most recent `/computer`
|
|
4829
|
-
* in the transcript (`isLatest`), the session can reach the
|
|
4843
|
+
* in the transcript (`isLatest`), the session can reach the api
|
|
4830
4844
|
* (`canCollect`), and the transcript is at the bottom (`!scrolledUp`) — while
|
|
4831
4845
|
* you're scrolled up reading something else it quietly stops (no wasted polls),
|
|
4832
4846
|
* and resumes when you scroll back down. Older cards freeze regardless, so a
|
|
@@ -4836,180 +4850,6 @@ const COMPUTER_POLL_INTERVAL_MS = 3e4;
|
|
|
4836
4850
|
function shouldPollComputer(opts) {
|
|
4837
4851
|
return opts.isLatest && opts.canCollect && !opts.scrolledUp;
|
|
4838
4852
|
}
|
|
4839
|
-
/**
|
|
4840
|
-
* The command run in the sandbox to collect the snapshot. `sh`-portable, reads
|
|
4841
|
-
* only `/proc` and `df`. Each metric is one `key<TAB>value` line so the parser
|
|
4842
|
-
* never has to positionally align anything.
|
|
4843
|
-
*
|
|
4844
|
-
* The `port` lines enumerate LISTEN sockets the sandbox user's own processes
|
|
4845
|
-
* hold: one awk pass maps socket inodes from /proc/net/tcp{,6} to pids via
|
|
4846
|
-
* /proc/<pid>/fd, then a shell loop attaches each pid's cmdline. Platform
|
|
4847
|
-
* daemons run as root, so their /proc/<pid>/fd is unreadable here and they
|
|
4848
|
-
* never emit a line — the "user's servers only" filter falls out of
|
|
4849
|
-
* permissions rather than a curated list. Hex address:port is left for the
|
|
4850
|
-
* parser to decode (strtonum is gawk-only).
|
|
4851
|
-
*/
|
|
4852
|
-
const SANDBOX_STATS_COMMAND = [
|
|
4853
|
-
`printf '${SANDBOX_STATS_SENTINEL}\\n'`,
|
|
4854
|
-
`printf 'nproc\\t%s\\n' "$(nproc 2>/dev/null || echo 1)"`,
|
|
4855
|
-
`printf 'loadavg\\t%s\\n' "$(cut -d' ' -f1-3 /proc/loadavg)"`,
|
|
4856
|
-
`printf 'uptime\\t%s\\n' "$(cut -d' ' -f1 /proc/uptime)"`,
|
|
4857
|
-
`awk '/^MemTotal:/{t=$2}/^MemAvailable:/{a=$2}END{printf "mem_total_kb\\t%d\\nmem_avail_kb\\t%d\\n",t,a}' /proc/meminfo`,
|
|
4858
|
-
`awk '/^SwapTotal:/{t=$2}/^SwapFree:/{f=$2}END{printf "swap_total_kb\\t%d\\nswap_free_kb\\t%d\\n",t,f}' /proc/meminfo`,
|
|
4859
|
-
`df -Pk / | awk 'NR==2{printf "disk_total_kb\\t%d\\ndisk_used_kb\\t%d\\n",$2,$3}'`,
|
|
4860
|
-
`printf 'procs\\t%s\\n' "$(ls -1 /proc 2>/dev/null | grep -c '^[0-9]\\+$')"`,
|
|
4861
|
-
`ls -l /proc/[0-9]*/fd 2>/dev/null | awk 'BEGIN { while ((getline l < "/proc/net/tcp") > 0) { split(l,f," "); if (f[4]=="0A") want[f[10]]=f[2] } close("/proc/net/tcp"); while ((getline l < "/proc/net/tcp6") > 0) { split(l,f," "); if (f[4]=="0A") want[f[10]]=f[2] } close("/proc/net/tcp6") } $0 ~ "^/proc/[0-9]+/fd:$" { pid=substr($0,7,length($0)-10) } match($0, /socket:\\[[0-9]+\\]/) { ino=substr($0,RSTART+8,RLENGTH-9); if (ino in want && !(ino in done)) { done[ino]=1; print want[ino] "\\t" pid } }' | while read addr pid; do cl=$(head -c 200 "/proc/$pid/cmdline" 2>/dev/null | tr "\\0" " "); printf 'port\\t%s\\t%s\\t%s\\n' "$addr" "$pid" "$cl"; done`
|
|
4862
|
-
].join(" ; ");
|
|
4863
|
-
const KB = 1024;
|
|
4864
|
-
const HIDDEN_PORTS = new Set([
|
|
4865
|
-
38994,
|
|
4866
|
-
49983,
|
|
4867
|
-
38996
|
|
4868
|
-
]);
|
|
4869
|
-
const V6_LOOPBACK_HEX = "00000000000000000000000001000000";
|
|
4870
|
-
/**
|
|
4871
|
-
* Whether a /proc/net/tcp{,6} local-address hex field is a loopback bind.
|
|
4872
|
-
* v4 words are little-endian, so 127.0.0.1 is `0100007F` — the first octet
|
|
4873
|
-
* (127) is the LAST byte pair. Covers v4-mapped v6 (`::ffff:127.x.y.z`) via
|
|
4874
|
-
* the same tail check.
|
|
4875
|
-
*/
|
|
4876
|
-
function isLoopbackHex(addrHex) {
|
|
4877
|
-
const hex = addrHex.toUpperCase();
|
|
4878
|
-
if (hex.length === 8) return hex.slice(6) === "7F";
|
|
4879
|
-
if (hex.length === 32) {
|
|
4880
|
-
if (hex === V6_LOOPBACK_HEX) return true;
|
|
4881
|
-
return hex.startsWith("0000000000000000FFFF0000") && hex.slice(30) === "7F";
|
|
4882
|
-
}
|
|
4883
|
-
return false;
|
|
4884
|
-
}
|
|
4885
|
-
const RUNTIME_NAMES = new Set([
|
|
4886
|
-
"node",
|
|
4887
|
-
"bun",
|
|
4888
|
-
"deno",
|
|
4889
|
-
"python",
|
|
4890
|
-
"python3",
|
|
4891
|
-
"sh",
|
|
4892
|
-
"bash"
|
|
4893
|
-
]);
|
|
4894
|
-
const FLAGS_WITH_VALUE = new Set([
|
|
4895
|
-
"--require",
|
|
4896
|
-
"-r",
|
|
4897
|
-
"--import",
|
|
4898
|
-
"--loader",
|
|
4899
|
-
"--experimental-loader"
|
|
4900
|
-
]);
|
|
4901
|
-
/**
|
|
4902
|
-
* A short display name from a raw cmdline: basename of argv[0], plus — when
|
|
4903
|
-
* argv[0] is a bare runtime — the basename of the first non-flag argument with
|
|
4904
|
-
* its extension dropped (`node …/vite/bin/vite.js` → `node vite`).
|
|
4905
|
-
*/
|
|
4906
|
-
function processDisplayName(cmdline) {
|
|
4907
|
-
const argv = cmdline.trim().split(/\s+/).filter(Boolean);
|
|
4908
|
-
const first = argv[0];
|
|
4909
|
-
if (first === void 0) return null;
|
|
4910
|
-
const base = (p) => p.split("/").pop() ?? p;
|
|
4911
|
-
const name = base(first);
|
|
4912
|
-
if (!RUNTIME_NAMES.has(name)) return name;
|
|
4913
|
-
let script;
|
|
4914
|
-
for (let i = 1; i < argv.length; i += 1) {
|
|
4915
|
-
const arg = argv[i];
|
|
4916
|
-
if (arg === void 0) break;
|
|
4917
|
-
if (FLAGS_WITH_VALUE.has(arg)) {
|
|
4918
|
-
i += 1;
|
|
4919
|
-
continue;
|
|
4920
|
-
}
|
|
4921
|
-
if (arg.startsWith("-")) continue;
|
|
4922
|
-
script = arg;
|
|
4923
|
-
break;
|
|
4924
|
-
}
|
|
4925
|
-
if (!script) return name;
|
|
4926
|
-
return `${name} ${base(script).replace(/\.(?:mjs|cjs|js|ts|tsx|py)$/, "")}`;
|
|
4927
|
-
}
|
|
4928
|
-
/**
|
|
4929
|
-
* Fold the collector's `port` lines (one per listen socket) into one entry
|
|
4930
|
-
* per port: v4/v6 binds of the same server dedupe, `loopbackOnly` only when
|
|
4931
|
-
* every bind is loopback, and the hidden platform ports drop out.
|
|
4932
|
-
*/
|
|
4933
|
-
function parsePortLines(lines) {
|
|
4934
|
-
const byPort = /* @__PURE__ */ new Map();
|
|
4935
|
-
for (const line of lines) {
|
|
4936
|
-
const port = Number.parseInt(line.portHex, 16);
|
|
4937
|
-
if (!Number.isFinite(port) || port <= 0 || HIDDEN_PORTS.has(port)) continue;
|
|
4938
|
-
const entry = byPort.get(port);
|
|
4939
|
-
if (entry) entry.loopback.push(isLoopbackHex(line.addrHex));
|
|
4940
|
-
else byPort.set(port, {
|
|
4941
|
-
loopback: [isLoopbackHex(line.addrHex)],
|
|
4942
|
-
cmdline: line.cmdline
|
|
4943
|
-
});
|
|
4944
|
-
}
|
|
4945
|
-
return [...byPort.entries()].map(([port, entry]) => ({
|
|
4946
|
-
port,
|
|
4947
|
-
loopbackOnly: entry.loopback.every(Boolean),
|
|
4948
|
-
process: entry.cmdline ? processDisplayName(entry.cmdline) : null
|
|
4949
|
-
})).sort((a, b) => a.port - b.port);
|
|
4950
|
-
}
|
|
4951
|
-
/**
|
|
4952
|
-
* Parse the collector's output into `SandboxStats`, or return null when the
|
|
4953
|
-
* block is absent or malformed (so the caller can show the raw output as an
|
|
4954
|
-
* error instead of a broken graph). Tolerant of extra lines before the
|
|
4955
|
-
* sentinel (shell banners) and of a missing swap section (swap totals default
|
|
4956
|
-
* to 0, which the renderer treats as "no swap").
|
|
4957
|
-
*/
|
|
4958
|
-
function parseSandboxStats(raw) {
|
|
4959
|
-
const start = raw.indexOf(SANDBOX_STATS_SENTINEL);
|
|
4960
|
-
if (start === -1) return null;
|
|
4961
|
-
const fields = /* @__PURE__ */ new Map();
|
|
4962
|
-
const portLines = [];
|
|
4963
|
-
for (const line of raw.slice(start).split("\n")) {
|
|
4964
|
-
const tab = line.indexOf(" ");
|
|
4965
|
-
if (tab === -1) continue;
|
|
4966
|
-
const key = line.slice(0, tab).trim();
|
|
4967
|
-
if (key === "port") {
|
|
4968
|
-
const parts = line.split(" ");
|
|
4969
|
-
const [addrHex = "", portHex = ""] = (parts[1] ?? "").split(":");
|
|
4970
|
-
if (addrHex && portHex) portLines.push({
|
|
4971
|
-
addrHex,
|
|
4972
|
-
portHex,
|
|
4973
|
-
cmdline: parts.slice(3).join(" ").trim()
|
|
4974
|
-
});
|
|
4975
|
-
continue;
|
|
4976
|
-
}
|
|
4977
|
-
fields.set(key, line.slice(tab + 1).trim());
|
|
4978
|
-
}
|
|
4979
|
-
const num = (key) => {
|
|
4980
|
-
const v = fields.get(key);
|
|
4981
|
-
if (v === void 0) return null;
|
|
4982
|
-
const n = Number(v);
|
|
4983
|
-
return Number.isFinite(n) ? n : null;
|
|
4984
|
-
};
|
|
4985
|
-
const load = (fields.get("loadavg") ?? "").split(/\s+/).map(Number).filter((n) => Number.isFinite(n));
|
|
4986
|
-
const nproc = num("nproc");
|
|
4987
|
-
const uptime = num("uptime");
|
|
4988
|
-
const memTotal = num("mem_total_kb");
|
|
4989
|
-
const memAvail = num("mem_avail_kb");
|
|
4990
|
-
const diskTotal = num("disk_total_kb");
|
|
4991
|
-
const diskUsed = num("disk_used_kb");
|
|
4992
|
-
const procs = num("procs");
|
|
4993
|
-
const [load1, load5, load15] = load;
|
|
4994
|
-
if (nproc === null || uptime === null || memTotal === null || memAvail === null || diskTotal === null || diskUsed === null || procs === null || load1 === void 0 || load5 === void 0 || load15 === void 0) return null;
|
|
4995
|
-
return {
|
|
4996
|
-
nproc: Math.max(1, Math.round(nproc)),
|
|
4997
|
-
loadavg: [
|
|
4998
|
-
load1,
|
|
4999
|
-
load5,
|
|
5000
|
-
load15
|
|
5001
|
-
],
|
|
5002
|
-
uptimeSeconds: uptime,
|
|
5003
|
-
memTotalBytes: memTotal * KB,
|
|
5004
|
-
memAvailBytes: memAvail * KB,
|
|
5005
|
-
swapTotalBytes: (num("swap_total_kb") ?? 0) * KB,
|
|
5006
|
-
swapFreeBytes: (num("swap_free_kb") ?? 0) * KB,
|
|
5007
|
-
diskTotalBytes: diskTotal * KB,
|
|
5008
|
-
diskUsedBytes: diskUsed * KB,
|
|
5009
|
-
procs: Math.round(procs),
|
|
5010
|
-
ports: parsePortLines(portLines)
|
|
5011
|
-
};
|
|
5012
|
-
}
|
|
5013
4853
|
function severityFor(fraction) {
|
|
5014
4854
|
if (fraction >= .9) return "crit";
|
|
5015
4855
|
if (fraction >= .75) return "warn";
|
|
@@ -5019,52 +4859,29 @@ const GiB = 1024 * 1024 * 1024;
|
|
|
5019
4859
|
function gb(bytes) {
|
|
5020
4860
|
return (bytes / GiB).toFixed(1);
|
|
5021
4861
|
}
|
|
5022
|
-
/** `120050` seconds -> `1d 9h 20m`. Always at least `0m`. */
|
|
5023
|
-
function formatUptime(seconds) {
|
|
5024
|
-
const total = Math.max(0, Math.floor(seconds));
|
|
5025
|
-
const d = Math.floor(total / 86400);
|
|
5026
|
-
const h = Math.floor(total % 86400 / 3600);
|
|
5027
|
-
const m = Math.floor(total % 3600 / 60);
|
|
5028
|
-
const parts = [];
|
|
5029
|
-
if (d > 0) parts.push(`${d}d`);
|
|
5030
|
-
if (h > 0 || d > 0) parts.push(`${h}h`);
|
|
5031
|
-
parts.push(`${m}m`);
|
|
5032
|
-
return parts.join(" ");
|
|
5033
|
-
}
|
|
5034
4862
|
/**
|
|
5035
4863
|
* Reduce a snapshot to the ordered list of gauges the `/computer` card draws:
|
|
5036
|
-
* CPU
|
|
5037
|
-
*
|
|
5038
|
-
*
|
|
4864
|
+
* CPU utilization, memory, and disk — the three signals the api's live-stats
|
|
4865
|
+
* read carries. CPU is a provider-measured utilization percent (already
|
|
4866
|
+
* normalized across cores), so it maps straight to a 0..1 bar; the caption
|
|
4867
|
+
* shows the vCPU count for context.
|
|
5039
4868
|
*/
|
|
5040
4869
|
function toGauges(stats) {
|
|
5041
4870
|
const gauges = [];
|
|
5042
|
-
const
|
|
5043
|
-
const loadFraction = load1 / stats.nproc;
|
|
4871
|
+
const cpuFraction = stats.cpuUsedPct / 100;
|
|
5044
4872
|
gauges.push({
|
|
5045
|
-
label: "cpu
|
|
5046
|
-
fraction:
|
|
5047
|
-
detail: `${
|
|
5048
|
-
severity: severityFor(
|
|
4873
|
+
label: "cpu",
|
|
4874
|
+
fraction: cpuFraction,
|
|
4875
|
+
detail: `${stats.cpuUsedPct.toFixed(0)}% · ${stats.cpuCount} vCPU`,
|
|
4876
|
+
severity: severityFor(cpuFraction)
|
|
5049
4877
|
});
|
|
5050
|
-
const
|
|
5051
|
-
const memFraction = stats.memTotalBytes > 0 ? memUsed / stats.memTotalBytes : 0;
|
|
4878
|
+
const memFraction = stats.memTotalBytes > 0 ? stats.memUsedBytes / stats.memTotalBytes : 0;
|
|
5052
4879
|
gauges.push({
|
|
5053
4880
|
label: "memory",
|
|
5054
4881
|
fraction: memFraction,
|
|
5055
|
-
detail: `${gb(
|
|
4882
|
+
detail: `${gb(stats.memUsedBytes)} / ${gb(stats.memTotalBytes)} GB`,
|
|
5056
4883
|
severity: severityFor(memFraction)
|
|
5057
4884
|
});
|
|
5058
|
-
if (stats.swapTotalBytes > 0) {
|
|
5059
|
-
const swapUsed = Math.max(0, stats.swapTotalBytes - stats.swapFreeBytes);
|
|
5060
|
-
const swapFraction = swapUsed / stats.swapTotalBytes;
|
|
5061
|
-
gauges.push({
|
|
5062
|
-
label: "swap",
|
|
5063
|
-
fraction: swapFraction,
|
|
5064
|
-
detail: `${gb(swapUsed)} / ${gb(stats.swapTotalBytes)} GB`,
|
|
5065
|
-
severity: severityFor(swapFraction)
|
|
5066
|
-
});
|
|
5067
|
-
}
|
|
5068
4885
|
const diskFraction = stats.diskTotalBytes > 0 ? stats.diskUsedBytes / stats.diskTotalBytes : 0;
|
|
5069
4886
|
gauges.push({
|
|
5070
4887
|
label: "disk",
|
|
@@ -5100,29 +4917,18 @@ function renderBar(fraction, width) {
|
|
|
5100
4917
|
const filled = "█".repeat(Math.min(full, cells)) + partial;
|
|
5101
4918
|
return (filled + "░".repeat(Math.max(0, cells - filled.length))).slice(0, cells);
|
|
5102
4919
|
}
|
|
5103
|
-
/** How many port rows the card draws before folding the rest into `+N more`. */
|
|
5104
|
-
const MAX_PORT_ROWS = 8;
|
|
5105
|
-
const WELL_KNOWN_PORTS = new Map([[3e3, "agent harness"], [3001, "agent webserver"]]);
|
|
5106
|
-
/**
|
|
5107
|
-
* Reduce the snapshot's ports to drawable rows. The well-known note wins over
|
|
5108
|
-
* `local only` — a loopback-bound 3001 being unreachable is a distinct problem
|
|
5109
|
-
* the gauges' error path already surfaces, and one annotation per row keeps
|
|
5110
|
-
* the table scannable.
|
|
5111
|
-
*/
|
|
5112
|
-
function toPortRows(ports) {
|
|
5113
|
-
return ports.map((p) => ({
|
|
5114
|
-
port: p.port,
|
|
5115
|
-
process: p.process ?? "?",
|
|
5116
|
-
note: WELL_KNOWN_PORTS.get(p.port) ?? (p.loopbackOnly ? "local only" : null)
|
|
5117
|
-
}));
|
|
5118
|
-
}
|
|
5119
4920
|
|
|
5120
4921
|
//#endregion
|
|
5121
4922
|
//#region src/chat/tui/chunks/computer.tsx
|
|
5122
4923
|
/**
|
|
5123
4924
|
* Renders the `/computer` card: a snapshot of the agent's sandbox as a stack of
|
|
5124
|
-
* colored bar graphs (cpu
|
|
5125
|
-
*
|
|
4925
|
+
* colored bar graphs (cpu, memory, disk).
|
|
4926
|
+
*
|
|
4927
|
+
* The numbers come from the api, not from reaching into the box: the card polls
|
|
4928
|
+
* `collectSandboxStats` (injected via context), which reads the live resource
|
|
4929
|
+
* sample the worker's fleet sweep already stores. So there's no exec and no
|
|
4930
|
+
* cold-start wake — when the agent has no running sandbox the read returns
|
|
4931
|
+
* "no snapshot" at once and the card shows an idle line instead of hanging.
|
|
5126
4932
|
*
|
|
5127
4933
|
* The card keeps itself current in place rather than making the user re-run the
|
|
5128
4934
|
* command — but quietly: it refreshes on a slow interval with no "updating"
|
|
@@ -5130,9 +4936,8 @@ function toPortRows(ports) {
|
|
|
5130
4936
|
* recent `/computer` card refreshes (older ones freeze at their last snapshot),
|
|
5131
4937
|
* and the latest one silently stops while the transcript is scrolled up (no
|
|
5132
4938
|
* wasted polls on a card you're not looking at) and resumes at the bottom — the
|
|
5133
|
-
* pause is never rendered. The
|
|
5134
|
-
*
|
|
5135
|
-
* `sandbox-stats.ts` and this component only draws.
|
|
4939
|
+
* pause is never rendered. The parse/layout math lives in `sandbox-stats.ts` and
|
|
4940
|
+
* this component only draws.
|
|
5136
4941
|
*/
|
|
5137
4942
|
function ComputerItem({ item }) {
|
|
5138
4943
|
const { collectSandboxStats, latestComputerId, scrolledUp } = useConversationContext();
|
|
@@ -5143,7 +4948,8 @@ function ComputerItem({ item }) {
|
|
|
5143
4948
|
});
|
|
5144
4949
|
const [stats, setStats] = useState(item.stats);
|
|
5145
4950
|
const [error, setError] = useState(item.error);
|
|
5146
|
-
const
|
|
4951
|
+
const hasResolved = useRef(item.stats !== null || item.state !== "loading");
|
|
4952
|
+
const [idle, setIdle] = useState(item.state !== "loading" && item.stats === null && item.error === null);
|
|
5147
4953
|
useEffect(() => {
|
|
5148
4954
|
if (!live || !collectSandboxStats) return;
|
|
5149
4955
|
let cancelled = false;
|
|
@@ -5156,11 +4962,12 @@ function ComputerItem({ item }) {
|
|
|
5156
4962
|
inFlight = null;
|
|
5157
4963
|
if (cancelled) return;
|
|
5158
4964
|
if (result.ok) {
|
|
5159
|
-
|
|
4965
|
+
hasResolved.current = true;
|
|
5160
4966
|
setStats(result.stats);
|
|
5161
4967
|
setError(null);
|
|
4968
|
+
setIdle(result.stats === null);
|
|
5162
4969
|
} else if (!("aborted" in result)) {
|
|
5163
|
-
if (!
|
|
4970
|
+
if (!hasResolved.current) setError(result.error);
|
|
5164
4971
|
}
|
|
5165
4972
|
if (!cancelled) timer = setTimeout(tick, COMPUTER_POLL_INTERVAL_MS);
|
|
5166
4973
|
};
|
|
@@ -5171,43 +4978,26 @@ function ComputerItem({ item }) {
|
|
|
5171
4978
|
inFlight?.abort();
|
|
5172
4979
|
};
|
|
5173
4980
|
}, [live, collectSandboxStats]);
|
|
5174
|
-
if (stats === null && error === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
4981
|
+
if (stats === null && error === null && !idle) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
5175
4982
|
fg: theme.dim,
|
|
5176
4983
|
children: "reading sandbox stats…"
|
|
5177
4984
|
}) });
|
|
5178
|
-
if (stats === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsxs("text", {
|
|
4985
|
+
if (stats === null && error !== null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsxs("text", {
|
|
5179
4986
|
fg: theme.error,
|
|
5180
|
-
children: ["couldn't read sandbox stats", error
|
|
4987
|
+
children: ["couldn't read sandbox stats: ", error]
|
|
4988
|
+
}) });
|
|
4989
|
+
if (stats === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
4990
|
+
fg: theme.dim,
|
|
4991
|
+
children: "no sandbox running"
|
|
5181
4992
|
}) });
|
|
5182
4993
|
const gauges = toGauges(stats);
|
|
5183
|
-
const
|
|
5184
|
-
const labelWidth = Math.max(5, ...gauges.map((g) => g.label.length));
|
|
4994
|
+
const labelWidth = Math.max(...gauges.map((g) => g.label.length));
|
|
5185
4995
|
const barWidth = 24;
|
|
5186
|
-
return /* @__PURE__ */
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
}, g.label)),
|
|
5192
|
-
/* @__PURE__ */ jsx(PortsSection, {
|
|
5193
|
-
rows: portRows,
|
|
5194
|
-
labelWidth
|
|
5195
|
-
}),
|
|
5196
|
-
/* @__PURE__ */ jsxs("text", {
|
|
5197
|
-
fg: theme.dim,
|
|
5198
|
-
children: [
|
|
5199
|
-
`${"uptime".padEnd(labelWidth)} `,
|
|
5200
|
-
/* @__PURE__ */ jsx("span", {
|
|
5201
|
-
fg: theme.muted,
|
|
5202
|
-
children: formatUptime(stats.uptimeSeconds)
|
|
5203
|
-
}),
|
|
5204
|
-
/* @__PURE__ */ jsx("span", {
|
|
5205
|
-
fg: theme.dim,
|
|
5206
|
-
children: ` · ${stats.procs} processes`
|
|
5207
|
-
})
|
|
5208
|
-
]
|
|
5209
|
-
})
|
|
5210
|
-
] });
|
|
4996
|
+
return /* @__PURE__ */ jsx(Shell, { children: gauges.map((g) => /* @__PURE__ */ jsx(GaugeRow, {
|
|
4997
|
+
gauge: g,
|
|
4998
|
+
labelWidth,
|
|
4999
|
+
barWidth
|
|
5000
|
+
}, g.label)) });
|
|
5211
5001
|
}
|
|
5212
5002
|
/** The card frame: a bold `computer` title over the graph rows. */
|
|
5213
5003
|
function Shell({ children }) {
|
|
@@ -5255,42 +5045,6 @@ function GaugeRow({ gauge, labelWidth, barWidth }) {
|
|
|
5255
5045
|
})
|
|
5256
5046
|
] });
|
|
5257
5047
|
}
|
|
5258
|
-
/**
|
|
5259
|
-
* The listening-ports table: one row per port the sandbox user's processes
|
|
5260
|
-
* hold, first row carrying the `ports` gutter label to line up with the
|
|
5261
|
-
* gauges. Folds past MAX_PORT_ROWS into a `+N more` line so a busy box can't
|
|
5262
|
-
* flood the transcript.
|
|
5263
|
-
*/
|
|
5264
|
-
function PortsSection({ rows, labelWidth }) {
|
|
5265
|
-
if (rows.length === 0) return null;
|
|
5266
|
-
const shown = rows.slice(0, MAX_PORT_ROWS);
|
|
5267
|
-
const folded = rows.length - shown.length;
|
|
5268
|
-
const gutter = " ".repeat(labelWidth);
|
|
5269
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [shown.map((row, i) => /* @__PURE__ */ jsxs("text", { children: [
|
|
5270
|
-
/* @__PURE__ */ jsx("span", {
|
|
5271
|
-
fg: theme.muted,
|
|
5272
|
-
children: `${i === 0 ? "ports".padEnd(labelWidth) : gutter} `
|
|
5273
|
-
}),
|
|
5274
|
-
/* @__PURE__ */ jsx("span", {
|
|
5275
|
-
fg: theme.fg,
|
|
5276
|
-
children: String(row.port).padStart(5)
|
|
5277
|
-
}),
|
|
5278
|
-
/* @__PURE__ */ jsx("span", {
|
|
5279
|
-
fg: theme.muted,
|
|
5280
|
-
children: ` ${row.process}`
|
|
5281
|
-
}),
|
|
5282
|
-
row.note ? /* @__PURE__ */ jsx("span", {
|
|
5283
|
-
fg: theme.dim,
|
|
5284
|
-
children: ` · ${row.note}`
|
|
5285
|
-
}) : null
|
|
5286
|
-
] }, row.port)), folded > 0 ? /* @__PURE__ */ jsxs("text", { children: [/* @__PURE__ */ jsx("span", {
|
|
5287
|
-
fg: theme.muted,
|
|
5288
|
-
children: `${gutter} `
|
|
5289
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
5290
|
-
fg: theme.dim,
|
|
5291
|
-
children: `+${folded} more`
|
|
5292
|
-
})] }) : null] });
|
|
5293
|
-
}
|
|
5294
5048
|
|
|
5295
5049
|
//#endregion
|
|
5296
5050
|
//#region src/chat/tui/chunks/index.tsx
|
|
@@ -7042,55 +6796,45 @@ function formatShare(share) {
|
|
|
7042
6796
|
|
|
7043
6797
|
//#endregion
|
|
7044
6798
|
//#region src/chat/sandbox/collect-stats.ts
|
|
6799
|
+
/**
|
|
6800
|
+
* Drop a snapshot the api hasn't refreshed recently: the sweep that writes it
|
|
6801
|
+
* runs ~every 60s, so a sample older than {@link COMPUTER_STATS_STALE_MS} means
|
|
6802
|
+
* the box stopped reporting (paused / gone) and its numbers are no longer live.
|
|
6803
|
+
* Presenting it as current would be a lie, so treat it as "no snapshot".
|
|
6804
|
+
*/
|
|
6805
|
+
function freshOrNull(stats, now) {
|
|
6806
|
+
if (!stats) return null;
|
|
6807
|
+
return now - stats.sampledAt <= COMPUTER_STATS_STALE_MS ? stats : null;
|
|
6808
|
+
}
|
|
7045
6809
|
function collectSandboxStats(cfg) {
|
|
7046
|
-
|
|
7047
|
-
const dec = new TextDecoder();
|
|
7048
|
-
let buffer = "";
|
|
7049
|
-
let settled = false;
|
|
7050
|
-
let stream = null;
|
|
6810
|
+
let aborted = false;
|
|
7051
6811
|
let resolveRef = null;
|
|
6812
|
+
let settled = false;
|
|
6813
|
+
const settle = (r) => {
|
|
6814
|
+
if (settled) return;
|
|
6815
|
+
settled = true;
|
|
6816
|
+
resolveRef?.(r);
|
|
6817
|
+
};
|
|
7052
6818
|
return {
|
|
7053
6819
|
result: new Promise((resolve) => {
|
|
7054
6820
|
resolveRef = resolve;
|
|
7055
|
-
|
|
7056
|
-
if (
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
if (e.type === "data") buffer += dec.decode(e.bytes);
|
|
7068
|
-
else if (e.type === "error") settle({
|
|
7069
|
-
ok: false,
|
|
7070
|
-
error: e.message
|
|
7071
|
-
});
|
|
7072
|
-
else if (e.type === "exit") {
|
|
7073
|
-
const stats = parseSandboxStats(buffer);
|
|
7074
|
-
if (stats) settle({
|
|
7075
|
-
ok: true,
|
|
7076
|
-
stats
|
|
7077
|
-
});
|
|
7078
|
-
else settle({
|
|
7079
|
-
ok: false,
|
|
7080
|
-
error: e.code === 0 ? "unexpected output from the sandbox" : `sandbox command exited ${e.code}`
|
|
7081
|
-
});
|
|
7082
|
-
} else if (e.type === "close") settle({
|
|
7083
|
-
ok: false,
|
|
7084
|
-
error: e.failure ?? "connection closed before the snapshot finished"
|
|
7085
|
-
});
|
|
7086
|
-
}
|
|
6821
|
+
cfg.read().then((stats) => {
|
|
6822
|
+
if (aborted) return;
|
|
6823
|
+
settle({
|
|
6824
|
+
ok: true,
|
|
6825
|
+
stats: freshOrNull(stats, cfg.now())
|
|
6826
|
+
});
|
|
6827
|
+
}).catch((err) => {
|
|
6828
|
+
if (aborted) return;
|
|
6829
|
+
settle({
|
|
6830
|
+
ok: false,
|
|
6831
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6832
|
+
});
|
|
7087
6833
|
});
|
|
7088
6834
|
}),
|
|
7089
6835
|
abort: () => {
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
stream?.close();
|
|
7093
|
-
resolveRef?.({
|
|
6836
|
+
aborted = true;
|
|
6837
|
+
settle({
|
|
7094
6838
|
ok: false,
|
|
7095
6839
|
aborted: true,
|
|
7096
6840
|
error: "aborted"
|
|
@@ -13123,17 +12867,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
13123
12867
|
return null;
|
|
13124
12868
|
}, [items]);
|
|
13125
12869
|
const boundCollectSandboxStats = useMemo(() => {
|
|
13126
|
-
if (!
|
|
12870
|
+
if (!rest) return null;
|
|
13127
12871
|
return () => collectSandboxStats({
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
agentId: agent.id
|
|
12872
|
+
read: () => rest.computeLiveStats(),
|
|
12873
|
+
now: () => Date.now()
|
|
13131
12874
|
});
|
|
13132
|
-
}, [
|
|
13133
|
-
appUrl,
|
|
13134
|
-
sessionToken,
|
|
13135
|
-
agent.id
|
|
13136
|
-
]);
|
|
12875
|
+
}, [rest]);
|
|
13137
12876
|
const conversationContext = useMemo(() => ({
|
|
13138
12877
|
rest,
|
|
13139
12878
|
conversationId,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
|
|
4
|
+
//#region \0rolldown/runtime.js
|
|
5
|
+
var __create = Object.create;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
10
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
11
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
17
|
+
__defProp(to, key, {
|
|
18
|
+
get: ((k) => from[k]).bind(null, key),
|
|
19
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
27
|
+
value: mod,
|
|
28
|
+
enumerable: true
|
|
29
|
+
}) : target, mod));
|
|
30
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { __require as n, __toESM as r, __commonJSMin as t };
|