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.
Files changed (36) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/js/api-DQCaztBg.mjs +315 -0
  3. package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
  4. package/dist/js/bin.mjs +35 -30
  5. package/dist/js/{boot-0-dpPM1_.mjs → boot-yyXrIeVy.mjs} +110 -371
  6. package/dist/js/chunk-BbwQpWto.mjs +33 -0
  7. package/dist/js/{client-DbqRBquD.mjs → client-d6K9qm6B.mjs} +70 -2
  8. package/dist/js/client-yz3zrpAS.mjs +5 -0
  9. package/dist/js/daemon-Btd2436i.mjs +7 -0
  10. package/dist/js/{daemon-DuxaUuek.mjs → daemon-CxFxH36O.mjs} +4 -2
  11. package/dist/js/daemon-client-1Qfc_Y3J.mjs +8 -0
  12. package/dist/js/{daemon-client-C4heMWTq.mjs → daemon-client-Br1NlkRp.mjs} +1 -1
  13. package/dist/js/dist-CRtjM7ba.mjs +1750 -0
  14. package/dist/js/{forward-DL6DYqyc.mjs → forward-zwB55Bls.mjs} +25 -4
  15. package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
  16. package/dist/js/{print-CQkXZ02a.mjs → print-B9djx8GU.mjs} +3 -3
  17. package/dist/js/{print-DYiQC5IW.mjs → print-Clgq46GU.mjs} +3 -3
  18. package/dist/js/{print-share-BH9gvCls.mjs → print-share-BBy3OTno.mjs} +6 -2
  19. package/dist/js/{profiler-BoO7ldO0.mjs → profiler-CJMnC90G.mjs} +2 -2
  20. package/dist/js/{raw-pty-DY4KelZW.mjs → raw-pty-B9Bue8gg.mjs} +1 -1
  21. package/dist/js/raw-pty-g3kATVQe.mjs +5 -0
  22. package/dist/js/{rest-xXfqT4SX.mjs → rest-C6E4mS2A.mjs} +15 -2
  23. package/dist/js/rest-CaWUSrnH.mjs +6 -0
  24. package/dist/js/tls-cert-CLgSQALB.mjs +4 -0
  25. package/dist/js/tls-cert-CV-pwxVN.mjs +67 -0
  26. package/package.json +1 -1
  27. package/dist/js/api-DG5W6iwx.mjs +0 -131
  28. package/dist/js/client-hH2PJL8y.mjs +0 -5
  29. package/dist/js/daemon-Ce8WLdGI.mjs +0 -6
  30. package/dist/js/daemon-client-LJdnM_nE.mjs +0 -7
  31. package/dist/js/raw-pty-DmdUf4_w.mjs +0 -5
  32. package/dist/js/rest-DSpGS5XU.mjs +0 -6
  33. /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
  34. /package/dist/js/{client-c4c5MmgN.mjs → client-BTQ1fwzM.mjs} +0 -0
  35. /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-UHH3mVBF.mjs} +0 -0
  36. /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-BoO7ldO0.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-DYiQC5IW.mjs";
4
- import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
5
- import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-xXfqT4SX.mjs";
6
- import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
7
- import { t as PortalClient } from "./client-DbqRBquD.mjs";
8
- import "./daemon-DuxaUuek.mjs";
9
- import "./api-DG5W6iwx.mjs";
10
- import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
11
- import { t as PortalDaemonClient } from "./daemon-client-C4heMWTq.mjs";
12
- import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
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 CLI runs on the USER's machine, so it can't read the sandbox's `/proc`
4809
- * directly it runs one small, portable shell command over the sandbox-exec
4810
- * stream (`SandboxStream`, mode `exec`) and parses the output here. Everything
4811
- * in this module is pure so the parse + layout are unit-testable without a
4812
- * sandbox or a renderer.
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
- * The command reads `/proc` and `df` only (no tools that might be missing from
4815
- * a minimal image), emitting a tagged, tab-separated block. The leading
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 sandbox
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 load (normalized to core count), memory, swap (only when the box has
5037
- * any), and disk. Load can exceed 1.0 (oversubscribed CPU) — the bar clamps
5038
- * but the caption shows the true number so that's still legible.
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 load1 = stats.loadavg[0];
5043
- const loadFraction = load1 / stats.nproc;
4871
+ const cpuFraction = stats.cpuUsedPct / 100;
5044
4872
  gauges.push({
5045
- label: "cpu load",
5046
- fraction: loadFraction,
5047
- detail: `${load1.toFixed(2)} / ${stats.nproc} cores`,
5048
- severity: severityFor(loadFraction)
4873
+ label: "cpu",
4874
+ fraction: cpuFraction,
4875
+ detail: `${stats.cpuUsedPct.toFixed(0)}% · ${stats.cpuCount} vCPU`,
4876
+ severity: severityFor(cpuFraction)
5049
4877
  });
5050
- const memUsed = Math.max(0, stats.memTotalBytes - stats.memAvailBytes);
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(memUsed)} / ${gb(stats.memTotalBytes)} 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 load, memory, swap, disk) plus a footer of scalar
5125
- * facts (uptime, process count).
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 refresh uses the same sandbox-exec relay
5134
- * `/sandbox` uses, injected via context; the parse/layout math lives in
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 hasStats = useRef(item.stats !== null);
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
- hasStats.current = true;
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 (!hasStats.current) setError(result.error);
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 ? `: ${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 portRows = toPortRows(stats.ports);
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__ */ jsxs(Shell, { children: [
5187
- gauges.map((g) => /* @__PURE__ */ jsx(GaugeRow, {
5188
- gauge: g,
5189
- labelWidth,
5190
- barWidth
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
- const open = cfg.open ?? SandboxStream.open;
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
- const settle = (r) => {
7056
- if (settled) return;
7057
- settled = true;
7058
- resolve(r);
7059
- };
7060
- stream = open({
7061
- mode: "exec",
7062
- appUrl: cfg.appUrl,
7063
- sessionToken: cfg.sessionToken,
7064
- agentId: cfg.agentId,
7065
- command: SANDBOX_STATS_COMMAND,
7066
- onEvent: (e) => {
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
- if (settled) return;
7091
- settled = true;
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 (!appUrl || !sessionToken) return null;
12870
+ if (!rest) return null;
13127
12871
  return () => collectSandboxStats({
13128
- appUrl,
13129
- sessionToken,
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 };