skydive-cli 0.5.0-beta.30 → 0.5.0-beta.31
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/bin.mjs +17 -17
- package/dist/js/{boot-0-dpPM1_.mjs → boot-iaVSnd7h.mjs} +100 -365
- package/dist/js/{daemon-DuxaUuek.mjs → daemon-BTyg329O.mjs} +1 -1
- package/dist/js/{daemon-Ce8WLdGI.mjs → daemon-SPQgtsbW.mjs} +1 -1
- package/dist/js/{daemon-client-C4heMWTq.mjs → daemon-client--A_yMKq6.mjs} +1 -1
- package/dist/js/daemon-client-C7emKKlj.mjs +7 -0
- package/dist/js/{print-DYiQC5IW.mjs → print-C_TnQSPg.mjs} +1 -1
- package/dist/js/{print-CQkXZ02a.mjs → print-DACzRUwQ.mjs} +2 -2
- package/dist/js/{profiler-BoO7ldO0.mjs → profiler-BVbYs_Yg.mjs} +2 -2
- package/dist/js/{rest-xXfqT4SX.mjs → rest-CgfbKXst.mjs} +13 -0
- package/dist/js/{rest-DSpGS5XU.mjs → rest-VV5nc-Mn.mjs} +1 -1
- package/package.json +1 -1
- package/dist/js/daemon-client-LJdnM_nE.mjs +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
- `/search` (alias `/find`) in the chat TUI — opens the conversation picker with its search field focused, so "find a conversation" has a discoverable command instead of being a feature you had to know the bare picker already had. Pass a query inline (`/search billing deep dive`) and the picker opens already narrowed to the matches; bare `/search` opens it ready for you to type. The search runs server-side across conversation titles, message bodies, and participant names, the same search the picker exposes.
|
|
13
13
|
- First run in a workspace with no agents now sets you up automatically: instead of dropping you on an empty agent picker, `skydive` creates your first agent for you (with a suggested name and avatar, mirroring the web onboarding flow) and then offers to have it learn how you already work — reading the other coding agents set up on your machine (Claude Code, Cursor, Codex, and more), the same import that `skydive import` runs, with a plan shown first and credentials never copied. Decline and you go straight into the new agent's chat. You can replay this flow anytime with `skydive --onboarding` (or `SKYDIVE_FORCE_ONBOARDING=1`), even in a workspace that already has agents.
|
|
14
|
-
- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU
|
|
14
|
+
- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU, memory, and disk usage. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The numbers come from the Skydive API (the same live resource sample the web computer view reads), not by connecting to the sandbox — so the card shows instantly, never waking or hanging on a box that isn't running: when the agent has no live sandbox it simply reads "no sandbox running". The card keeps itself current quietly (a slow background refresh, no flicker or "updating" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom.
|
|
15
15
|
- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)
|
|
16
16
|
|
|
17
17
|
## [0.4.0] - 2026-08-07
|
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as takenAliasNames, B as name, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, I as getSessionIdentity, L as listWorkspaces, M as defaultInstallEnv, N as detectShell, O as installAgentAlias, P as ensureActiveOrganization, R as resolveWorkspaceId, S as themes, T as machineOsFromPlatform, V as version, a as installCrashHandler, j as SUPPORTED_SHELLS, k as slugifyAliasName, t as maybeStartProfiling, u as brandHelpArt, w as buildImportSeedPrompt, z as setActiveWorkspace } from "./profiler-
|
|
3
|
-
import { A as getShareMachineDefault, C as getConfigPath, D as getPromptHistoryPath, E as getPreference, F as resolveAppUrl, H as saveSession, I as resolveChatAuth, L as resolveConfig, M as getStoredApiKeyWorkspaceName, N as getUpdateCheckDisabled, R as resolveManagementAuth, S as deleteConfig, T as getLastSeenVersion, V as saveConfig, W as setLastSeenVersion, _ as API_KEY_FAMILY_PREFIX, g as API_KEYS_URL, i as resolveAgent$1, j as getStoredApiKeyId, v as API_KEY_PREFIX, w as getDefaultAgent, x as PREFERENCES, y as DEFAULT_API_URL, z as resolveSession } from "./print-
|
|
2
|
+
import { A as takenAliasNames, B as name, D as dedupeAliasName, E as aliasActivationHint, F as getActiveWorkspaceId, I as getSessionIdentity, L as listWorkspaces, M as defaultInstallEnv, N as detectShell, O as installAgentAlias, P as ensureActiveOrganization, R as resolveWorkspaceId, S as themes, T as machineOsFromPlatform, V as version, a as installCrashHandler, j as SUPPORTED_SHELLS, k as slugifyAliasName, t as maybeStartProfiling, u as brandHelpArt, w as buildImportSeedPrompt, z as setActiveWorkspace } from "./profiler-BVbYs_Yg.mjs";
|
|
3
|
+
import { A as getShareMachineDefault, C as getConfigPath, D as getPromptHistoryPath, E as getPreference, F as resolveAppUrl, H as saveSession, I as resolveChatAuth, L as resolveConfig, M as getStoredApiKeyWorkspaceName, N as getUpdateCheckDisabled, R as resolveManagementAuth, S as deleteConfig, T as getLastSeenVersion, V as saveConfig, W as setLastSeenVersion, _ as API_KEY_FAMILY_PREFIX, g as API_KEYS_URL, i as resolveAgent$1, j as getStoredApiKeyId, v as API_KEY_PREFIX, w as getDefaultAgent, x as PREFERENCES, y as DEFAULT_API_URL, z as resolveSession } from "./print-C_TnQSPg.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
6
|
-
import { t as createRestClient } from "./rest-
|
|
6
|
+
import { t as createRestClient } from "./rest-CgfbKXst.mjs";
|
|
7
7
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
8
8
|
import { r as resolveMachineIdentity } from "./client-DbqRBquD.mjs";
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-BTyg329O.mjs";
|
|
10
10
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DG5W6iwx.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
12
12
|
import { hideBin } from "yargs/helpers";
|
|
@@ -1584,7 +1584,7 @@ const importCommand = {
|
|
|
1584
1584
|
process.exit(1);
|
|
1585
1585
|
}
|
|
1586
1586
|
}
|
|
1587
|
-
const { runChat } = await import("./boot-
|
|
1587
|
+
const { runChat } = await import("./boot-iaVSnd7h.mjs");
|
|
1588
1588
|
await runChat({
|
|
1589
1589
|
appUrl,
|
|
1590
1590
|
sessionToken: session.value.sessionToken,
|
|
@@ -1622,7 +1622,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1622
1622
|
});
|
|
1623
1623
|
const extra = (argv.print ?? "").trim();
|
|
1624
1624
|
const prompt = buildImportSeedPrompt(process.cwd(), machineOsFromPlatform(process.platform)) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
|
|
1625
|
-
const { runPrint } = await import("./print-
|
|
1625
|
+
const { runPrint } = await import("./print-DACzRUwQ.mjs");
|
|
1626
1626
|
try {
|
|
1627
1627
|
const result = await runPrint({
|
|
1628
1628
|
appUrl,
|
|
@@ -2029,7 +2029,7 @@ const chatCommand = {
|
|
|
2029
2029
|
sessionToken: auth.value.token,
|
|
2030
2030
|
agentSelector: argv.agent ?? null
|
|
2031
2031
|
});
|
|
2032
|
-
const { runChat } = await import("./boot-
|
|
2032
|
+
const { runChat } = await import("./boot-iaVSnd7h.mjs");
|
|
2033
2033
|
await runChat({
|
|
2034
2034
|
appUrl: auth.value.appUrl,
|
|
2035
2035
|
sessionToken: auth.value.token,
|
|
@@ -2113,7 +2113,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
2113
2113
|
printError(`${auth.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
2114
2114
|
process.exit(1);
|
|
2115
2115
|
}
|
|
2116
|
-
const { runPrint, readStdin } = await import("./print-
|
|
2116
|
+
const { runPrint, readStdin } = await import("./print-DACzRUwQ.mjs");
|
|
2117
2117
|
const workspaceId = await resolveWorkspaceFlag({
|
|
2118
2118
|
appUrl,
|
|
2119
2119
|
sessionToken: auth.value.token,
|
|
@@ -2208,7 +2208,7 @@ const getCommand$1 = {
|
|
|
2208
2208
|
printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
2209
2209
|
process.exit(1);
|
|
2210
2210
|
}
|
|
2211
|
-
const { messageGet } = await import("./print-
|
|
2211
|
+
const { messageGet } = await import("./print-DACzRUwQ.mjs");
|
|
2212
2212
|
try {
|
|
2213
2213
|
const result = await messageGet({
|
|
2214
2214
|
appUrl: auth.value.appUrl,
|
|
@@ -2411,7 +2411,7 @@ const switchCommand = {
|
|
|
2411
2411
|
printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
|
|
2412
2412
|
process.exit(1);
|
|
2413
2413
|
}
|
|
2414
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2414
|
+
const { runWorkspacePicker } = await import("./boot-iaVSnd7h.mjs");
|
|
2415
2415
|
await runWorkspacePicker(session);
|
|
2416
2416
|
return;
|
|
2417
2417
|
}
|
|
@@ -2490,7 +2490,7 @@ const openCommand = {
|
|
|
2490
2490
|
const agent = argv.agent ? resolveAgent$1((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2491
2491
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2492
2492
|
const { machineName } = await resolveMachineIdentity(null);
|
|
2493
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2493
|
+
const { PortalDaemonClient } = await import("./daemon-client-C7emKKlj.mjs");
|
|
2494
2494
|
let lastLine = "";
|
|
2495
2495
|
let signalConnected;
|
|
2496
2496
|
const connected = new Promise((resolve) => {
|
|
@@ -2800,8 +2800,8 @@ const sandboxCommand = {
|
|
|
2800
2800
|
}).example("skydive sandbox --agent grace", "Live terminal (Ctrl-] detaches)").example("skydive sandbox --agent grace -- tail -n 50 /tmp/harness.log", "One-shot command (use `--` so its flags reach the sandbox)").example("skydive sandbox --agent grace -- sh -c 'ls /tmp | wc -l'", "Shell features go through an explicit `sh -c`"),
|
|
2801
2801
|
handler: async (argv) => {
|
|
2802
2802
|
const session = requireSession(argv);
|
|
2803
|
-
const { createRestClient } = await import("./rest-
|
|
2804
|
-
const { resolveAgent } = await import("./print-
|
|
2803
|
+
const { createRestClient } = await import("./rest-VV5nc-Mn.mjs");
|
|
2804
|
+
const { resolveAgent } = await import("./print-DACzRUwQ.mjs");
|
|
2805
2805
|
const client = createRestClient({
|
|
2806
2806
|
appUrl: session.appUrl,
|
|
2807
2807
|
sessionToken: session.sessionToken
|
|
@@ -2958,8 +2958,8 @@ const fsEditCommand = {
|
|
|
2958
2958
|
handler: async (argv) => {
|
|
2959
2959
|
const session = requireSession(argv);
|
|
2960
2960
|
const remotePath = argv.path;
|
|
2961
|
-
const { createRestClient } = await import("./rest-
|
|
2962
|
-
const { resolveAgent } = await import("./print-
|
|
2961
|
+
const { createRestClient } = await import("./rest-VV5nc-Mn.mjs");
|
|
2962
|
+
const { resolveAgent } = await import("./print-DACzRUwQ.mjs");
|
|
2963
2963
|
const client = createRestClient({
|
|
2964
2964
|
appUrl: session.appUrl,
|
|
2965
2965
|
sessionToken: session.sessionToken
|
|
@@ -4193,7 +4193,7 @@ function setupUpdateCheck(argv) {
|
|
|
4193
4193
|
|
|
4194
4194
|
//#endregion
|
|
4195
4195
|
//#region src/changelog.generated.ts
|
|
4196
|
-
const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- `/search` (alias `/find`) in the chat TUI — opens the conversation picker with its search field focused, so \"find a conversation\" has a discoverable command instead of being a feature you had to know the bare picker already had. Pass a query inline (`/search billing deep dive`) and the picker opens already narrowed to the matches; bare `/search` opens it ready for you to type. The search runs server-side across conversation titles, message bodies, and participant names, the same search the picker exposes.\n- First run in a workspace with no agents now sets you up automatically: instead of dropping you on an empty agent picker, `skydive` creates your first agent for you (with a suggested name and avatar, mirroring the web onboarding flow) and then offers to have it learn how you already work — reading the other coding agents set up on your machine (Claude Code, Cursor, Codex, and more), the same import that `skydive import` runs, with a plan shown first and credentials never copied. Decline and you go straight into the new agent's chat. You can replay this flow anytime with `skydive --onboarding` (or `SKYDIVE_FORCE_ONBOARDING=1`), even in a workspace that already has agents.\n- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU load (scaled to core count), memory, swap (when present), and disk usage, plus uptime and process count. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The card keeps itself current quietly (a slow background refresh, no flicker or \"updating\" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom. Stats are read over the same relay `skydive sandbox` uses, so it's gated on EDIT access to the agent like the other sandbox commands.\n- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)\n\n## [0.4.0] - 2026-08-07\n\n### Added\n\n- `skydive chat` works with API key auth: an API key alone now opens the chat TUI (and headless `chat -p`) without a browser login, using the key's pinned workspace.\n- `skydive config` command to view and set the safe, hand-editable CLI preferences (`shareMachineDefault`, `updateCheck`) without opening `config.json`. `config`/`config list` shows each key, its effective value, and whether it comes from the file or the built-in default; `config get`/`set`/`unset` read and change one key; `config path` prints the config file location. It only ever touches those preference keys, never credentials or flow-managed state.\n- Billing-blocked sends and runs now surface the server-authored recovery\n guidance from the typed billing outcome. The chat TUI renders it as a\n warning-toned system-notice row — live when a send is refused or a run is\n stopped at the billing boundary, and again when reopening a conversation\n that holds a persisted billing notice — instead of a generic error.\n Headless `chat -p` and `messages get` print the guidance (state, recovery\n step, and manage-billing URL) on stderr, keep any partial output already\n streamed on stdout, and exit with code `5` (distinct from the generic `1`)\n so scripted callers can tell a billing pause from a failure.\n- Syntax highlighting for 14 more languages in the chat transcript and file/review panes (python, bash, go, rust, c, cpp, java, ruby, css, html, json, yaml, and more) — previously only JavaScript/TypeScript rendered colored.\n\n### Changed\n\n- `skydive agent list` lists the full agent roster by default instead of a truncated page.\n\n### Fixed\n\n- Long multi-line bash tool-call commands (e.g. heredocs) are clamped with a `+N more · click to expand` tail instead of dumping their whole body into the scrollback.\n- Tool error output is clipped the same way as ok output, so a failing command no longer floods the transcript.\n- The TUI toast overlay has an opaque background instead of letting the transcript bleed through.\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
|
|
4196
|
+
const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- `/search` (alias `/find`) in the chat TUI — opens the conversation picker with its search field focused, so \"find a conversation\" has a discoverable command instead of being a feature you had to know the bare picker already had. Pass a query inline (`/search billing deep dive`) and the picker opens already narrowed to the matches; bare `/search` opens it ready for you to type. The search runs server-side across conversation titles, message bodies, and participant names, the same search the picker exposes.\n- First run in a workspace with no agents now sets you up automatically: instead of dropping you on an empty agent picker, `skydive` creates your first agent for you (with a suggested name and avatar, mirroring the web onboarding flow) and then offers to have it learn how you already work — reading the other coding agents set up on your machine (Claude Code, Cursor, Codex, and more), the same import that `skydive import` runs, with a plan shown first and credentials never copied. Decline and you go straight into the new agent's chat. You can replay this flow anytime with `skydive --onboarding` (or `SKYDIVE_FORCE_ONBOARDING=1`), even in a workspace that already has agents.\n- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU, memory, and disk usage. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The numbers come from the Skydive API (the same live resource sample the web computer view reads), not by connecting to the sandbox — so the card shows instantly, never waking or hanging on a box that isn't running: when the agent has no live sandbox it simply reads \"no sandbox running\". The card keeps itself current quietly (a slow background refresh, no flicker or \"updating\" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom.\n- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)\n\n## [0.4.0] - 2026-08-07\n\n### Added\n\n- `skydive chat` works with API key auth: an API key alone now opens the chat TUI (and headless `chat -p`) without a browser login, using the key's pinned workspace.\n- `skydive config` command to view and set the safe, hand-editable CLI preferences (`shareMachineDefault`, `updateCheck`) without opening `config.json`. `config`/`config list` shows each key, its effective value, and whether it comes from the file or the built-in default; `config get`/`set`/`unset` read and change one key; `config path` prints the config file location. It only ever touches those preference keys, never credentials or flow-managed state.\n- Billing-blocked sends and runs now surface the server-authored recovery\n guidance from the typed billing outcome. The chat TUI renders it as a\n warning-toned system-notice row — live when a send is refused or a run is\n stopped at the billing boundary, and again when reopening a conversation\n that holds a persisted billing notice — instead of a generic error.\n Headless `chat -p` and `messages get` print the guidance (state, recovery\n step, and manage-billing URL) on stderr, keep any partial output already\n streamed on stdout, and exit with code `5` (distinct from the generic `1`)\n so scripted callers can tell a billing pause from a failure.\n- Syntax highlighting for 14 more languages in the chat transcript and file/review panes (python, bash, go, rust, c, cpp, java, ruby, css, html, json, yaml, and more) — previously only JavaScript/TypeScript rendered colored.\n\n### Changed\n\n- `skydive agent list` lists the full agent roster by default instead of a truncated page.\n\n### Fixed\n\n- Long multi-line bash tool-call commands (e.g. heredocs) are clamped with a `+N more · click to expand` tail instead of dumping their whole body into the scrollback.\n- Tool error output is clipped the same way as ok output, so a failing command no longer floods the transcript.\n- The TUI toast overlay has an opaque background instead of letting the transcript bleed through.\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
|
|
4197
4197
|
|
|
4198
4198
|
//#endregion
|
|
4199
4199
|
//#region src/whats-new.ts
|
|
@@ -4336,7 +4336,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4336
4336
|
process.exit(0);
|
|
4337
4337
|
}
|
|
4338
4338
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4339
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4339
|
+
const { runPortalDaemon } = await import("./daemon-SPQgtsbW.mjs");
|
|
4340
4340
|
runPortalDaemon(process.argv);
|
|
4341
4341
|
} else runCli();
|
|
4342
4342
|
function runCli() {
|
|
@@ -1,14 +1,14 @@
|
|
|
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-
|
|
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-BVbYs_Yg.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-C_TnQSPg.mjs";
|
|
4
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-
|
|
5
|
+
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-CgfbKXst.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
7
|
import { t as PortalClient } from "./client-DbqRBquD.mjs";
|
|
8
|
-
import "./daemon-
|
|
8
|
+
import "./daemon-BTyg329O.mjs";
|
|
9
9
|
import "./api-DG5W6iwx.mjs";
|
|
10
10
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
-
import { t as PortalDaemonClient } from "./daemon-client
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client--A_yMKq6.mjs";
|
|
12
12
|
import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
|
|
13
13
|
import * as os$1 from "node:os";
|
|
14
14
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -4805,28 +4805,38 @@ function useConversationContext() {
|
|
|
4805
4805
|
/**
|
|
4806
4806
|
* `/computer`: a live snapshot of the agent's sandbox as a set of bar graphs.
|
|
4807
4807
|
*
|
|
4808
|
-
* The
|
|
4809
|
-
*
|
|
4810
|
-
*
|
|
4811
|
-
*
|
|
4812
|
-
*
|
|
4808
|
+
* The numbers come from the api, NOT from the CLI reaching into the sandbox.
|
|
4809
|
+
* The worker's fleet metrics sweep already samples every live box's CPU /
|
|
4810
|
+
* memory / disk from the provider and stores the latest per-sandbox snapshot;
|
|
4811
|
+
* the CLI reads that back over one plain REST call (`collectSandboxStats`). So
|
|
4812
|
+
* there is no exec into the box and no cold-start wake — when the agent has no
|
|
4813
|
+
* running sandbox the api answers "no snapshot" immediately instead of the card
|
|
4814
|
+
* hanging on a stream that never replies.
|
|
4813
4815
|
*
|
|
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.
|
|
4816
|
+
* Everything in this module is pure so the layout math is unit-testable without
|
|
4817
|
+
* an api or a renderer.
|
|
4818
4818
|
*/
|
|
4819
|
-
const SANDBOX_STATS_SENTINEL = "SKYDIVE_STATS_V1";
|
|
4820
4819
|
/**
|
|
4821
4820
|
* How often the live `/computer` card refreshes its snapshot, in ms.
|
|
4822
4821
|
* Deliberately slow: the card is a background health readout, not a `top`, so
|
|
4823
|
-
* it refreshes quietly every 30s rather than flickering every second.
|
|
4822
|
+
* it refreshes quietly every 30s rather than flickering every second. Also
|
|
4823
|
+
* comfortably slower than the worker's ~60s sweep cadence, so we never poll for
|
|
4824
|
+
* a sample that can't have changed.
|
|
4824
4825
|
*/
|
|
4825
4826
|
const COMPUTER_POLL_INTERVAL_MS = 3e4;
|
|
4826
4827
|
/**
|
|
4828
|
+
* A snapshot older than this is stale enough that we no longer present it as
|
|
4829
|
+
* "current". The api sample carries the provider's measurement time; the sweep
|
|
4830
|
+
* that writes it runs ~every 60s and the Redis key TTLs at 5 min, so a sample
|
|
4831
|
+
* this old means the box likely stopped reporting (paused / gone). The card
|
|
4832
|
+
* treats it as "no current snapshot" rather than drawing minutes-old bars as if
|
|
4833
|
+
* they were live.
|
|
4834
|
+
*/
|
|
4835
|
+
const COMPUTER_STATS_STALE_MS = 5 * 6e4;
|
|
4836
|
+
/**
|
|
4827
4837
|
* Whether a `/computer` card should refresh right now. Pure so the gating is
|
|
4828
4838
|
* unit-testable. A card refreshes only when it is the most recent `/computer`
|
|
4829
|
-
* in the transcript (`isLatest`), the session can reach the
|
|
4839
|
+
* in the transcript (`isLatest`), the session can reach the api
|
|
4830
4840
|
* (`canCollect`), and the transcript is at the bottom (`!scrolledUp`) — while
|
|
4831
4841
|
* you're scrolled up reading something else it quietly stops (no wasted polls),
|
|
4832
4842
|
* and resumes when you scroll back down. Older cards freeze regardless, so a
|
|
@@ -4836,180 +4846,6 @@ const COMPUTER_POLL_INTERVAL_MS = 3e4;
|
|
|
4836
4846
|
function shouldPollComputer(opts) {
|
|
4837
4847
|
return opts.isLatest && opts.canCollect && !opts.scrolledUp;
|
|
4838
4848
|
}
|
|
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
4849
|
function severityFor(fraction) {
|
|
5014
4850
|
if (fraction >= .9) return "crit";
|
|
5015
4851
|
if (fraction >= .75) return "warn";
|
|
@@ -5019,52 +4855,29 @@ const GiB = 1024 * 1024 * 1024;
|
|
|
5019
4855
|
function gb(bytes) {
|
|
5020
4856
|
return (bytes / GiB).toFixed(1);
|
|
5021
4857
|
}
|
|
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
4858
|
/**
|
|
5035
4859
|
* Reduce a snapshot to the ordered list of gauges the `/computer` card draws:
|
|
5036
|
-
* CPU
|
|
5037
|
-
*
|
|
5038
|
-
*
|
|
4860
|
+
* CPU utilization, memory, and disk — the three signals the api's live-stats
|
|
4861
|
+
* read carries. CPU is a provider-measured utilization percent (already
|
|
4862
|
+
* normalized across cores), so it maps straight to a 0..1 bar; the caption
|
|
4863
|
+
* shows the vCPU count for context.
|
|
5039
4864
|
*/
|
|
5040
4865
|
function toGauges(stats) {
|
|
5041
4866
|
const gauges = [];
|
|
5042
|
-
const
|
|
5043
|
-
const loadFraction = load1 / stats.nproc;
|
|
4867
|
+
const cpuFraction = stats.cpuUsedPct / 100;
|
|
5044
4868
|
gauges.push({
|
|
5045
|
-
label: "cpu
|
|
5046
|
-
fraction:
|
|
5047
|
-
detail: `${
|
|
5048
|
-
severity: severityFor(
|
|
4869
|
+
label: "cpu",
|
|
4870
|
+
fraction: cpuFraction,
|
|
4871
|
+
detail: `${stats.cpuUsedPct.toFixed(0)}% · ${stats.cpuCount} vCPU`,
|
|
4872
|
+
severity: severityFor(cpuFraction)
|
|
5049
4873
|
});
|
|
5050
|
-
const
|
|
5051
|
-
const memFraction = stats.memTotalBytes > 0 ? memUsed / stats.memTotalBytes : 0;
|
|
4874
|
+
const memFraction = stats.memTotalBytes > 0 ? stats.memUsedBytes / stats.memTotalBytes : 0;
|
|
5052
4875
|
gauges.push({
|
|
5053
4876
|
label: "memory",
|
|
5054
4877
|
fraction: memFraction,
|
|
5055
|
-
detail: `${gb(
|
|
4878
|
+
detail: `${gb(stats.memUsedBytes)} / ${gb(stats.memTotalBytes)} GB`,
|
|
5056
4879
|
severity: severityFor(memFraction)
|
|
5057
4880
|
});
|
|
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
4881
|
const diskFraction = stats.diskTotalBytes > 0 ? stats.diskUsedBytes / stats.diskTotalBytes : 0;
|
|
5069
4882
|
gauges.push({
|
|
5070
4883
|
label: "disk",
|
|
@@ -5100,29 +4913,18 @@ function renderBar(fraction, width) {
|
|
|
5100
4913
|
const filled = "█".repeat(Math.min(full, cells)) + partial;
|
|
5101
4914
|
return (filled + "░".repeat(Math.max(0, cells - filled.length))).slice(0, cells);
|
|
5102
4915
|
}
|
|
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
4916
|
|
|
5120
4917
|
//#endregion
|
|
5121
4918
|
//#region src/chat/tui/chunks/computer.tsx
|
|
5122
4919
|
/**
|
|
5123
4920
|
* Renders the `/computer` card: a snapshot of the agent's sandbox as a stack of
|
|
5124
|
-
* colored bar graphs (cpu
|
|
5125
|
-
*
|
|
4921
|
+
* colored bar graphs (cpu, memory, disk).
|
|
4922
|
+
*
|
|
4923
|
+
* The numbers come from the api, not from reaching into the box: the card polls
|
|
4924
|
+
* `collectSandboxStats` (injected via context), which reads the live resource
|
|
4925
|
+
* sample the worker's fleet sweep already stores. So there's no exec and no
|
|
4926
|
+
* cold-start wake — when the agent has no running sandbox the read returns
|
|
4927
|
+
* "no snapshot" at once and the card shows an idle line instead of hanging.
|
|
5126
4928
|
*
|
|
5127
4929
|
* The card keeps itself current in place rather than making the user re-run the
|
|
5128
4930
|
* command — but quietly: it refreshes on a slow interval with no "updating"
|
|
@@ -5130,9 +4932,8 @@ function toPortRows(ports) {
|
|
|
5130
4932
|
* recent `/computer` card refreshes (older ones freeze at their last snapshot),
|
|
5131
4933
|
* and the latest one silently stops while the transcript is scrolled up (no
|
|
5132
4934
|
* 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.
|
|
4935
|
+
* pause is never rendered. The parse/layout math lives in `sandbox-stats.ts` and
|
|
4936
|
+
* this component only draws.
|
|
5136
4937
|
*/
|
|
5137
4938
|
function ComputerItem({ item }) {
|
|
5138
4939
|
const { collectSandboxStats, latestComputerId, scrolledUp } = useConversationContext();
|
|
@@ -5143,7 +4944,8 @@ function ComputerItem({ item }) {
|
|
|
5143
4944
|
});
|
|
5144
4945
|
const [stats, setStats] = useState(item.stats);
|
|
5145
4946
|
const [error, setError] = useState(item.error);
|
|
5146
|
-
const
|
|
4947
|
+
const hasResolved = useRef(item.stats !== null || item.state !== "loading");
|
|
4948
|
+
const [idle, setIdle] = useState(item.state !== "loading" && item.stats === null && item.error === null);
|
|
5147
4949
|
useEffect(() => {
|
|
5148
4950
|
if (!live || !collectSandboxStats) return;
|
|
5149
4951
|
let cancelled = false;
|
|
@@ -5156,11 +4958,12 @@ function ComputerItem({ item }) {
|
|
|
5156
4958
|
inFlight = null;
|
|
5157
4959
|
if (cancelled) return;
|
|
5158
4960
|
if (result.ok) {
|
|
5159
|
-
|
|
4961
|
+
hasResolved.current = true;
|
|
5160
4962
|
setStats(result.stats);
|
|
5161
4963
|
setError(null);
|
|
4964
|
+
setIdle(result.stats === null);
|
|
5162
4965
|
} else if (!("aborted" in result)) {
|
|
5163
|
-
if (!
|
|
4966
|
+
if (!hasResolved.current) setError(result.error);
|
|
5164
4967
|
}
|
|
5165
4968
|
if (!cancelled) timer = setTimeout(tick, COMPUTER_POLL_INTERVAL_MS);
|
|
5166
4969
|
};
|
|
@@ -5171,43 +4974,26 @@ function ComputerItem({ item }) {
|
|
|
5171
4974
|
inFlight?.abort();
|
|
5172
4975
|
};
|
|
5173
4976
|
}, [live, collectSandboxStats]);
|
|
5174
|
-
if (stats === null && error === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
4977
|
+
if (stats === null && error === null && !idle) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
5175
4978
|
fg: theme.dim,
|
|
5176
4979
|
children: "reading sandbox stats…"
|
|
5177
4980
|
}) });
|
|
5178
|
-
if (stats === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsxs("text", {
|
|
4981
|
+
if (stats === null && error !== null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsxs("text", {
|
|
5179
4982
|
fg: theme.error,
|
|
5180
|
-
children: ["couldn't read sandbox stats", error
|
|
4983
|
+
children: ["couldn't read sandbox stats: ", error]
|
|
4984
|
+
}) });
|
|
4985
|
+
if (stats === null) return /* @__PURE__ */ jsx(Shell, { children: /* @__PURE__ */ jsx("text", {
|
|
4986
|
+
fg: theme.dim,
|
|
4987
|
+
children: "no sandbox running"
|
|
5181
4988
|
}) });
|
|
5182
4989
|
const gauges = toGauges(stats);
|
|
5183
|
-
const
|
|
5184
|
-
const labelWidth = Math.max(5, ...gauges.map((g) => g.label.length));
|
|
4990
|
+
const labelWidth = Math.max(...gauges.map((g) => g.label.length));
|
|
5185
4991
|
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
|
-
] });
|
|
4992
|
+
return /* @__PURE__ */ jsx(Shell, { children: gauges.map((g) => /* @__PURE__ */ jsx(GaugeRow, {
|
|
4993
|
+
gauge: g,
|
|
4994
|
+
labelWidth,
|
|
4995
|
+
barWidth
|
|
4996
|
+
}, g.label)) });
|
|
5211
4997
|
}
|
|
5212
4998
|
/** The card frame: a bold `computer` title over the graph rows. */
|
|
5213
4999
|
function Shell({ children }) {
|
|
@@ -5255,42 +5041,6 @@ function GaugeRow({ gauge, labelWidth, barWidth }) {
|
|
|
5255
5041
|
})
|
|
5256
5042
|
] });
|
|
5257
5043
|
}
|
|
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
5044
|
|
|
5295
5045
|
//#endregion
|
|
5296
5046
|
//#region src/chat/tui/chunks/index.tsx
|
|
@@ -7042,55 +6792,45 @@ function formatShare(share) {
|
|
|
7042
6792
|
|
|
7043
6793
|
//#endregion
|
|
7044
6794
|
//#region src/chat/sandbox/collect-stats.ts
|
|
6795
|
+
/**
|
|
6796
|
+
* Drop a snapshot the api hasn't refreshed recently: the sweep that writes it
|
|
6797
|
+
* runs ~every 60s, so a sample older than {@link COMPUTER_STATS_STALE_MS} means
|
|
6798
|
+
* the box stopped reporting (paused / gone) and its numbers are no longer live.
|
|
6799
|
+
* Presenting it as current would be a lie, so treat it as "no snapshot".
|
|
6800
|
+
*/
|
|
6801
|
+
function freshOrNull(stats, now) {
|
|
6802
|
+
if (!stats) return null;
|
|
6803
|
+
return now - stats.sampledAt <= COMPUTER_STATS_STALE_MS ? stats : null;
|
|
6804
|
+
}
|
|
7045
6805
|
function collectSandboxStats(cfg) {
|
|
7046
|
-
|
|
7047
|
-
const dec = new TextDecoder();
|
|
7048
|
-
let buffer = "";
|
|
7049
|
-
let settled = false;
|
|
7050
|
-
let stream = null;
|
|
6806
|
+
let aborted = false;
|
|
7051
6807
|
let resolveRef = null;
|
|
6808
|
+
let settled = false;
|
|
6809
|
+
const settle = (r) => {
|
|
6810
|
+
if (settled) return;
|
|
6811
|
+
settled = true;
|
|
6812
|
+
resolveRef?.(r);
|
|
6813
|
+
};
|
|
7052
6814
|
return {
|
|
7053
6815
|
result: new Promise((resolve) => {
|
|
7054
6816
|
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
|
-
}
|
|
6817
|
+
cfg.read().then((stats) => {
|
|
6818
|
+
if (aborted) return;
|
|
6819
|
+
settle({
|
|
6820
|
+
ok: true,
|
|
6821
|
+
stats: freshOrNull(stats, cfg.now())
|
|
6822
|
+
});
|
|
6823
|
+
}).catch((err) => {
|
|
6824
|
+
if (aborted) return;
|
|
6825
|
+
settle({
|
|
6826
|
+
ok: false,
|
|
6827
|
+
error: err instanceof Error ? err.message : String(err)
|
|
6828
|
+
});
|
|
7087
6829
|
});
|
|
7088
6830
|
}),
|
|
7089
6831
|
abort: () => {
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
stream?.close();
|
|
7093
|
-
resolveRef?.({
|
|
6832
|
+
aborted = true;
|
|
6833
|
+
settle({
|
|
7094
6834
|
ok: false,
|
|
7095
6835
|
aborted: true,
|
|
7096
6836
|
error: "aborted"
|
|
@@ -13123,17 +12863,12 @@ function ChatScreen({ agent, conversation }) {
|
|
|
13123
12863
|
return null;
|
|
13124
12864
|
}, [items]);
|
|
13125
12865
|
const boundCollectSandboxStats = useMemo(() => {
|
|
13126
|
-
if (!
|
|
12866
|
+
if (!rest) return null;
|
|
13127
12867
|
return () => collectSandboxStats({
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
agentId: agent.id
|
|
12868
|
+
read: () => rest.computeLiveStats(),
|
|
12869
|
+
now: () => Date.now()
|
|
13131
12870
|
});
|
|
13132
|
-
}, [
|
|
13133
|
-
appUrl,
|
|
13134
|
-
sessionToken,
|
|
13135
|
-
agent.id
|
|
13136
|
-
]);
|
|
12871
|
+
}, [rest]);
|
|
13137
12872
|
const conversationContext = useMemo(() => ({
|
|
13138
12873
|
rest,
|
|
13139
12874
|
conversationId,
|
|
@@ -20,7 +20,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
20
20
|
* the commit time of the built tree is one monotonic clock they share.
|
|
21
21
|
*/
|
|
22
22
|
function portalDaemonBuild() {
|
|
23
|
-
return "
|
|
23
|
+
return "1786761220";
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./client-DbqRBquD.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
3
|
+
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-BTyg329O.mjs";
|
|
4
4
|
import "./api-DG5W6iwx.mjs";
|
|
5
5
|
|
|
6
6
|
export { runPortalDaemon };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-BTyg329O.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
-
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
3
|
+
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-CgfbKXst.mjs";
|
|
4
4
|
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import Conf from "conf";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
3
|
-
import "./rest-
|
|
2
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-C_TnQSPg.mjs";
|
|
3
|
+
import "./rest-CgfbKXst.mjs";
|
|
4
4
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as getConfigPath } from "./print-
|
|
2
|
+
import { C as getConfigPath } from "./print-C_TnQSPg.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { err, ok } from "neverthrow";
|
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
|
|
9
9
|
//#region package.json
|
|
10
10
|
var name = "skydive-cli";
|
|
11
|
-
var version$1 = "0.5.0-beta.
|
|
11
|
+
var version$1 = "0.5.0-beta.31";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/auth/organization.ts
|
|
@@ -299,6 +299,10 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
299
299
|
const { result } = await get(`/api/v1/trpc/v2.subagentTasks.list?input=${encodeURIComponent(JSON.stringify({ conversationId }))}`, subagentTasksResponseSchema);
|
|
300
300
|
return new Map(result.data.subagentTasks.map((task) => [task.id, task]));
|
|
301
301
|
},
|
|
302
|
+
computeLiveStats: async () => {
|
|
303
|
+
const { stats } = await get("/api/v1/compute/live-stats", liveStatsResponseSchema);
|
|
304
|
+
return stats;
|
|
305
|
+
},
|
|
302
306
|
uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
|
|
303
307
|
const size = data.byteLength;
|
|
304
308
|
const presign = await post("/api/v1/attachments/presign", {
|
|
@@ -549,6 +553,15 @@ const subagentTaskSnapshotSchema = z.object({
|
|
|
549
553
|
completedAt: z.string().nullable()
|
|
550
554
|
});
|
|
551
555
|
const subagentTasksResponseSchema = z.object({ result: z.object({ data: z.object({ subagentTasks: z.array(subagentTaskSnapshotSchema) }) }) });
|
|
556
|
+
const liveStatsResponseSchema = z.object({ stats: z.object({
|
|
557
|
+
sampledAt: z.number(),
|
|
558
|
+
cpuUsedPct: z.number(),
|
|
559
|
+
cpuCount: z.number(),
|
|
560
|
+
memUsedBytes: z.number(),
|
|
561
|
+
memTotalBytes: z.number(),
|
|
562
|
+
diskUsedBytes: z.number(),
|
|
563
|
+
diskTotalBytes: z.number()
|
|
564
|
+
}).nullable() });
|
|
552
565
|
const listMessagesResponseSchema = z.object({ messages: z.array(uiMessageSchema) });
|
|
553
566
|
const workspaceFileSchema = z.object({
|
|
554
567
|
id: z.string(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
-
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
3
|
+
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-CgfbKXst.mjs";
|
|
4
4
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
|
|
6
6
|
export { createRestClient };
|
package/package.json
CHANGED