skydive-cli 0.2.0-beta.595 → 0.2.0-beta.604
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/dist/js/{api-CQXTPT-p.mjs → api-Bx5VyWaC.mjs} +1 -1
- package/dist/js/bin.mjs +32 -18
- package/dist/js/{boot-BY_wmF2R.mjs → boot-Cmte0eob.mjs} +213 -24
- package/dist/js/client-BYYp0E6M.mjs +6 -0
- package/dist/js/{client-BwMzD7Iw.mjs → client-BsLCAWf1.mjs} +2 -2
- package/dist/js/{print-SAL2aiGh.mjs → print-DLGCpIM9.mjs} +13 -7
- package/dist/js/{print-D0SpmKCV.mjs → print-QPPaRx38.mjs} +2 -2
- package/dist/js/{print-share-CzS5Y6De.mjs → print-share-DPsQq2Cy.mjs} +1 -1
- package/dist/js/{raw-pty-GAvxm2ol.mjs → raw-pty-Dbi2kb9v.mjs} +1 -1
- package/dist/js/raw-pty-pspO57gT.mjs +5 -0
- package/dist/js/rest-nx0AmgJy.mjs +4 -0
- package/dist/js/{rest-BMh7CURU.mjs → rest-vu2-Lrsa.mjs} +77 -1
- package/package.json +2 -1
- package/dist/js/client-C9p4ta7J.mjs +0 -6
- package/dist/js/raw-pty-2_VA1kw_.mjs +0 -5
- package/dist/js/rest-DPta4XUe.mjs +0 -4
- package/dist/js/util-CeisaZVY.mjs +0 -15
- /package/dist/js/{client-CpEvH2Pq.mjs → client-BVOAwU8M.mjs} +0 -0
package/dist/js/bin.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { A as getPromptHistoryPath, B as resolveSession, C as API_KEY_PREFIX, D as deleteConfig, E as DEFAULT_WEB_URL, F as getStoredApiKeyWorkspaceName, G as setLastSeenVersion, H as saveConfig, I as getUpdateCheckDisabled, L as resolveAppUrl, N as getShareMachineDefault, O as getConfigPath, P as getStoredApiKeyId, R as resolveConfig, S as API_KEY_FAMILY_PREFIX, U as saveSession, _ as getActiveWorkspaceId, b as setActiveWorkspace, g as ensureActiveOrganization, k as getLastSeenVersion, m as themes, r as brandHelpArt, v as getSessionIdentity, w as DEFAULT_API_URL, x as API_KEYS_URL, y as listWorkspaces, z as resolveManagementAuth } from "./brand-BVALAEf1.mjs";
|
|
3
3
|
import { n as printError, r as printTable, t as output } from "./output-B4cW10Ph.mjs";
|
|
4
|
-
import { n as createRestClient } from "./rest-
|
|
5
|
-
import { i as resolveAgent } from "./print-
|
|
6
|
-
import { a as registerPortalDevice, c as machineIdentity, n as findThisDevice, o as revokePortalAccess, r as grantPortalAccess, t as fetchPortalDevices } from "./api-
|
|
7
|
-
import { t as SandboxStream } from "./client-
|
|
4
|
+
import { n as createRestClient } from "./rest-vu2-Lrsa.mjs";
|
|
5
|
+
import { i as resolveAgent } from "./print-DLGCpIM9.mjs";
|
|
6
|
+
import { a as registerPortalDevice, c as machineIdentity, n as findThisDevice, o as revokePortalAccess, r as grantPortalAccess, t as fetchPortalDevices } from "./api-Bx5VyWaC.mjs";
|
|
7
|
+
import { t as SandboxStream } from "./client-BVOAwU8M.mjs";
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
9
9
|
import yargs from "yargs";
|
|
10
10
|
import { hostname } from "node:os";
|
|
@@ -20,7 +20,7 @@ import semver from "semver";
|
|
|
20
20
|
|
|
21
21
|
//#region package.json
|
|
22
22
|
var name = "skydive-cli";
|
|
23
|
-
var version = "0.2.0-beta.
|
|
23
|
+
var version = "0.2.0-beta.604";
|
|
24
24
|
|
|
25
25
|
//#endregion
|
|
26
26
|
//#region src/types.ts
|
|
@@ -1200,7 +1200,7 @@ const importCommand = {
|
|
|
1200
1200
|
process.exit(1);
|
|
1201
1201
|
}
|
|
1202
1202
|
}
|
|
1203
|
-
const { runChat } = await import("./boot-
|
|
1203
|
+
const { runChat } = await import("./boot-Cmte0eob.mjs");
|
|
1204
1204
|
await runChat({
|
|
1205
1205
|
appUrl,
|
|
1206
1206
|
sessionToken: session.value.sessionToken,
|
|
@@ -1209,6 +1209,7 @@ const importCommand = {
|
|
|
1209
1209
|
notifications: true,
|
|
1210
1210
|
agentSelector: argv.agent ?? null,
|
|
1211
1211
|
conversationId: null,
|
|
1212
|
+
newConversation: false,
|
|
1212
1213
|
seedPrompt: buildImportSeedPrompt(process.cwd())
|
|
1213
1214
|
});
|
|
1214
1215
|
}
|
|
@@ -1226,7 +1227,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1226
1227
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1227
1228
|
process.exit(1);
|
|
1228
1229
|
}
|
|
1229
|
-
const { connectMachineShare } = await import("./print-share-
|
|
1230
|
+
const { connectMachineShare } = await import("./print-share-DPsQq2Cy.mjs");
|
|
1230
1231
|
const machineShare = await connectMachineShare({
|
|
1231
1232
|
appUrl,
|
|
1232
1233
|
sessionToken: session.value.sessionToken,
|
|
@@ -1234,7 +1235,7 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1234
1235
|
});
|
|
1235
1236
|
const extra = (argv.print ?? "").trim();
|
|
1236
1237
|
const prompt = buildImportSeedPrompt(process.cwd()) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
|
|
1237
|
-
const { runPrint } = await import("./print-
|
|
1238
|
+
const { runPrint } = await import("./print-QPPaRx38.mjs");
|
|
1238
1239
|
try {
|
|
1239
1240
|
const result = await runPrint({
|
|
1240
1241
|
appUrl,
|
|
@@ -1465,6 +1466,10 @@ const chatCommand = {
|
|
|
1465
1466
|
alias: "resume",
|
|
1466
1467
|
type: "string",
|
|
1467
1468
|
describe: "Continue an existing conversation by id. With -p, the one-shot lands in that conversation; without -p, the TUI opens directly into it (the agent is resolved from the conversation, no --agent needed)."
|
|
1469
|
+
}).option("new", {
|
|
1470
|
+
type: "boolean",
|
|
1471
|
+
default: false,
|
|
1472
|
+
describe: "Start a fresh conversation instead of opening the conversation list. With --agent, opens a new conversation with that agent directly. Without --agent, opens a new conversation once you pick an agent (auto-picked if the account has only one). Ignored with --resume, which always continues the named conversation."
|
|
1468
1473
|
}).option("share-machine", {
|
|
1469
1474
|
type: "boolean",
|
|
1470
1475
|
describe: "Share this machine with the agent over the portal so it can run commands here (default-deny; in the TUI you approve per agent, with -p the flag grants the target agent for the run). Defaults to `shareMachineDefault` in the CLI config file; --no-share-machine disables for this invocation."
|
|
@@ -1475,7 +1480,7 @@ const chatCommand = {
|
|
|
1475
1480
|
type: "boolean",
|
|
1476
1481
|
default: true,
|
|
1477
1482
|
describe: "Show a desktop notification when a run finishes or needs your input while this terminal is unfocused (use --no-notify to disable)"
|
|
1478
|
-
}).example("skydive chat -p \"summarize my open PRs\" --agent grace", "One-shot, non-interactive").example("echo \"what changed today?\" | skydive chat -p --agent grace", "Read the prompt from stdin").example("skydive chat --agent grace", "Open Grace's conversation list in the TUI (skips the agent picker)").example("skydive chat --resume 0197e0f3-…", "Reopen a previous conversation in the TUI (the id is printed when you quit a chat)"),
|
|
1483
|
+
}).example("skydive chat -p \"summarize my open PRs\" --agent grace", "One-shot, non-interactive").example("echo \"what changed today?\" | skydive chat -p --agent grace", "Read the prompt from stdin").example("skydive chat --agent grace", "Open Grace's conversation list in the TUI (skips the agent picker)").example("skydive chat --agent grace --new", "Start a fresh conversation with Grace directly (skips the conversation list)").example("skydive chat --resume 0197e0f3-…", "Reopen a previous conversation in the TUI (the id is printed when you quit a chat)"),
|
|
1479
1484
|
handler: async (argv) => {
|
|
1480
1485
|
const appUrl = resolveAppUrl({ appUrl: argv["api-url"] });
|
|
1481
1486
|
if (argv.print !== void 0) {
|
|
@@ -1511,7 +1516,7 @@ const chatCommand = {
|
|
|
1511
1516
|
printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
|
|
1512
1517
|
process.exit(1);
|
|
1513
1518
|
}
|
|
1514
|
-
const { runChat } = await import("./boot-
|
|
1519
|
+
const { runChat } = await import("./boot-Cmte0eob.mjs");
|
|
1515
1520
|
await runChat({
|
|
1516
1521
|
appUrl,
|
|
1517
1522
|
sessionToken: session.value.sessionToken,
|
|
@@ -1521,6 +1526,7 @@ const chatCommand = {
|
|
|
1521
1526
|
notifications: argv.notify,
|
|
1522
1527
|
agentSelector: argv.agent ?? null,
|
|
1523
1528
|
conversationId: argv.conversation ?? null,
|
|
1529
|
+
newConversation: resolveNewConversation(argv),
|
|
1524
1530
|
seedPrompt: null
|
|
1525
1531
|
});
|
|
1526
1532
|
}
|
|
@@ -1533,13 +1539,21 @@ const chatCommand = {
|
|
|
1533
1539
|
function resolveShareMachine(argv) {
|
|
1534
1540
|
return argv["share-machine"] ?? getShareMachineDefault();
|
|
1535
1541
|
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Whether this invocation should boot straight into a fresh conversation:
|
|
1544
|
+
* `--new` requests it, but `--resume <id>` always wins (it pins an existing
|
|
1545
|
+
* conversation, so "new" is meaningless alongside it).
|
|
1546
|
+
*/
|
|
1547
|
+
function resolveNewConversation(argv) {
|
|
1548
|
+
return argv.new === true && !argv.conversation;
|
|
1549
|
+
}
|
|
1536
1550
|
async function runPrintMode({ argv, appUrl }) {
|
|
1537
1551
|
const session = resolveSession({ appUrl });
|
|
1538
1552
|
if (session.isErr()) {
|
|
1539
1553
|
printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1540
1554
|
process.exit(1);
|
|
1541
1555
|
}
|
|
1542
|
-
const { runPrint, readStdin } = await import("./print-
|
|
1556
|
+
const { runPrint, readStdin } = await import("./print-QPPaRx38.mjs");
|
|
1543
1557
|
let prompt = (argv.print ?? "").trim();
|
|
1544
1558
|
if (!prompt) {
|
|
1545
1559
|
if (process.stdin.isTTY) {
|
|
@@ -1554,7 +1568,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
1554
1568
|
}
|
|
1555
1569
|
let machineShare = null;
|
|
1556
1570
|
if (resolveShareMachine(argv)) {
|
|
1557
|
-
const { connectMachineShare } = await import("./print-share-
|
|
1571
|
+
const { connectMachineShare } = await import("./print-share-DPsQq2Cy.mjs");
|
|
1558
1572
|
machineShare = await connectMachineShare({
|
|
1559
1573
|
appUrl,
|
|
1560
1574
|
sessionToken: session.value.sessionToken,
|
|
@@ -1613,7 +1627,7 @@ const getCommand = {
|
|
|
1613
1627
|
printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
|
|
1614
1628
|
process.exit(1);
|
|
1615
1629
|
}
|
|
1616
|
-
const { messageGet } = await import("./print-
|
|
1630
|
+
const { messageGet } = await import("./print-QPPaRx38.mjs");
|
|
1617
1631
|
try {
|
|
1618
1632
|
const result = await messageGet({
|
|
1619
1633
|
appUrl,
|
|
@@ -1810,7 +1824,7 @@ const switchCommand = {
|
|
|
1810
1824
|
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.");
|
|
1811
1825
|
process.exit(1);
|
|
1812
1826
|
}
|
|
1813
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
1827
|
+
const { runWorkspacePicker } = await import("./boot-Cmte0eob.mjs");
|
|
1814
1828
|
await runWorkspacePicker(session);
|
|
1815
1829
|
return;
|
|
1816
1830
|
}
|
|
@@ -1888,7 +1902,7 @@ const openCommand = {
|
|
|
1888
1902
|
const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
1889
1903
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
1890
1904
|
const { machineName } = machineIdentity();
|
|
1891
|
-
const { PortalClient } = await import("./client-
|
|
1905
|
+
const { PortalClient } = await import("./client-BYYp0E6M.mjs");
|
|
1892
1906
|
let lastLine = "";
|
|
1893
1907
|
let signalConnected;
|
|
1894
1908
|
const connected = new Promise((resolve) => {
|
|
@@ -2060,8 +2074,8 @@ const sandboxCommand = {
|
|
|
2060
2074
|
}).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`"),
|
|
2061
2075
|
handler: async (argv) => {
|
|
2062
2076
|
const session = requireSession(argv);
|
|
2063
|
-
const { createRestClient } = await import("./rest-
|
|
2064
|
-
const { resolveAgent } = await import("./print-
|
|
2077
|
+
const { createRestClient } = await import("./rest-nx0AmgJy.mjs");
|
|
2078
|
+
const { resolveAgent } = await import("./print-QPPaRx38.mjs");
|
|
2065
2079
|
const client = createRestClient({
|
|
2066
2080
|
appUrl: session.appUrl,
|
|
2067
2081
|
sessionToken: session.sessionToken
|
|
@@ -2130,7 +2144,7 @@ async function runPty({ session, agentId, agentName }) {
|
|
|
2130
2144
|
return 1;
|
|
2131
2145
|
}
|
|
2132
2146
|
console.error(`Connecting to ${agentName}'s sandbox… (Ctrl-] detaches)`);
|
|
2133
|
-
const { runRawPtyPassthrough } = await import("./raw-pty-
|
|
2147
|
+
const { runRawPtyPassthrough } = await import("./raw-pty-pspO57gT.mjs");
|
|
2134
2148
|
const result = await runRawPtyPassthrough({
|
|
2135
2149
|
stdin: process.stdin,
|
|
2136
2150
|
stdout: process.stdout,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { M as getSavedTheme, O as getConfigPath, T as DEFAULT_APP_URL, V as resolveWebUrl, W as saveTheme, _ as getActiveWorkspaceId, a as applyTheme, b as setActiveWorkspace, c as noColorRequested, d as themeMode, f as themeModeFromColorFgBg, h as themesForMode, i as DEFAULT_THEME_ID, j as getReviewStateDir, l as theme, n as WORDMARK, o as findTheme, p as themeVersion, s as monoTheme, t as MARK_CELLS, u as themeForMode, w as DEFAULT_API_URL, y as listWorkspaces } from "./brand-BVALAEf1.mjs";
|
|
3
|
-
import { n as createRestClient, r as errorDetail, t as HttpError } from "./rest-
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import "./
|
|
7
|
-
import { t as
|
|
8
|
-
import { t as
|
|
9
|
-
import { t as runRawPtyPassthrough } from "./raw-pty-GAvxm2ol.mjs";
|
|
3
|
+
import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, r as errorDetail, t as HttpError } from "./rest-vu2-Lrsa.mjs";
|
|
4
|
+
import { c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams } from "./print-DLGCpIM9.mjs";
|
|
5
|
+
import "./api-Bx5VyWaC.mjs";
|
|
6
|
+
import { t as SandboxStream } from "./client-BVOAwU8M.mjs";
|
|
7
|
+
import { t as PortalClient } from "./client-BsLCAWf1.mjs";
|
|
8
|
+
import { t as runRawPtyPassthrough } from "./raw-pty-Dbi2kb9v.mjs";
|
|
10
9
|
import * as os$1 from "node:os";
|
|
11
10
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
12
11
|
import path, { basename, extname, isAbsolute, join, win32 } from "node:path";
|
|
@@ -79,6 +78,7 @@ const useStore = create((set, get) => ({
|
|
|
79
78
|
toast: null,
|
|
80
79
|
seedPrompt: null,
|
|
81
80
|
autoGrantMachine: false,
|
|
81
|
+
newConversationIntent: false,
|
|
82
82
|
goTo: (screen) => set({
|
|
83
83
|
screen,
|
|
84
84
|
chatTitle: initialChatTitle(screen)
|
|
@@ -1325,7 +1325,8 @@ function FilterableAgentList({ agents, scope, onPick }) {
|
|
|
1325
1325
|
else if (key.name === "return") {
|
|
1326
1326
|
const agent = filtered[clamped];
|
|
1327
1327
|
if (!agent) return;
|
|
1328
|
-
|
|
1328
|
+
const store = useStore.getState();
|
|
1329
|
+
onPick(store.seedPrompt !== null || store.newConversationIntent ? {
|
|
1329
1330
|
kind: "chat",
|
|
1330
1331
|
agent,
|
|
1331
1332
|
conversation: {
|
|
@@ -3688,6 +3689,149 @@ function formatElapsed(totalSeconds) {
|
|
|
3688
3689
|
return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
|
|
3689
3690
|
}
|
|
3690
3691
|
|
|
3692
|
+
//#endregion
|
|
3693
|
+
//#region src/chat/tui/chat/clamp.ts
|
|
3694
|
+
/** Max bytes we keep for a single clamped string field (head + tail combined). */
|
|
3695
|
+
const MAX_FIELD_CHARS = 24e3;
|
|
3696
|
+
/** Below this a string is never worth clamping (avoids churn on small fields). */
|
|
3697
|
+
const MIN_CLAMP_CHARS = MAX_FIELD_CHARS + 2e3;
|
|
3698
|
+
/** Fraction of the budget kept from the head; the rest is kept from the tail. */
|
|
3699
|
+
const HEAD_FRACTION = .7;
|
|
3700
|
+
/**
|
|
3701
|
+
* Clamp one string to a head + tail with an elision marker in the middle.
|
|
3702
|
+
* Idempotent: a string already carrying our marker is returned unchanged, so
|
|
3703
|
+
* re-clamping a loaded-from-history item (or a re-render) never re-slices.
|
|
3704
|
+
*/
|
|
3705
|
+
function clampString(text) {
|
|
3706
|
+
if (text.length <= MIN_CLAMP_CHARS) return text;
|
|
3707
|
+
if (text.includes(ELISION_MARKER_TAG)) return text;
|
|
3708
|
+
const headLen = Math.floor(MAX_FIELD_CHARS * HEAD_FRACTION);
|
|
3709
|
+
const tailLen = MAX_FIELD_CHARS - headLen;
|
|
3710
|
+
const elided = text.length - MAX_FIELD_CHARS;
|
|
3711
|
+
const head = text.slice(0, headLen);
|
|
3712
|
+
const tail = text.slice(text.length - tailLen);
|
|
3713
|
+
return `${head}\n${elisionMarker(elided)}\n${tail}`;
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* Append to a growing output buffer while keeping it bounded, preferring the
|
|
3717
|
+
* TAIL — for live streaming shell output the newest bytes are what you want on
|
|
3718
|
+
* screen. Used by the `!` and `/sandbox` streams, whose output would otherwise
|
|
3719
|
+
* grow without limit for a long-running command.
|
|
3720
|
+
*/
|
|
3721
|
+
function clampedAppend(current, chunk) {
|
|
3722
|
+
const next = current + chunk;
|
|
3723
|
+
if (next.length <= MIN_CLAMP_CHARS) return next;
|
|
3724
|
+
const priorMarkerLen = leadingMarkerLength(current);
|
|
3725
|
+
const bodyLen = next.length - priorMarkerLen;
|
|
3726
|
+
const kept = stripLeadingMarker(next.slice(next.length - MAX_FIELD_CHARS));
|
|
3727
|
+
return `${elisionMarker(Math.max(0, bodyLen - kept.length))}\n${kept}`;
|
|
3728
|
+
}
|
|
3729
|
+
/** Length (incl. trailing newline) of a marker line at the very start of `s`. */
|
|
3730
|
+
function leadingMarkerLength(s) {
|
|
3731
|
+
if (!s.startsWith(ELISION_MARKER_TAG)) return 0;
|
|
3732
|
+
const nl = s.indexOf("\n");
|
|
3733
|
+
return nl === -1 ? s.length : nl + 1;
|
|
3734
|
+
}
|
|
3735
|
+
function stripLeadingMarker(s) {
|
|
3736
|
+
return s.slice(leadingMarkerLength(s));
|
|
3737
|
+
}
|
|
3738
|
+
const ELISION_MARKER_TAG = "⋯ elided";
|
|
3739
|
+
function elisionMarker(bytes) {
|
|
3740
|
+
return `${ELISION_MARKER_TAG} ${formatBytes$2(bytes)} to save memory ⋯`;
|
|
3741
|
+
}
|
|
3742
|
+
function formatBytes$2(n) {
|
|
3743
|
+
if (n < 1024) return `${n} chars`;
|
|
3744
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
3745
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3748
|
+
* Recursively clamp the large string fields of an arbitrary tool payload
|
|
3749
|
+
* (input or output `value`). Structure is preserved so the tool renderers,
|
|
3750
|
+
* which reach for known keys like `.stdout` / `.output` / `.content`, still
|
|
3751
|
+
* find them — only the *size* of long strings is bounded. Depth and breadth
|
|
3752
|
+
* are capped so a pathological payload can't make clamping itself expensive.
|
|
3753
|
+
*/
|
|
3754
|
+
function clampPayload(value, depth = 0) {
|
|
3755
|
+
if (typeof value === "string") return clampString(value);
|
|
3756
|
+
if (depth >= 6) return value;
|
|
3757
|
+
if (Array.isArray(value)) {
|
|
3758
|
+
const overLong = value.length > 5e3;
|
|
3759
|
+
const source = overLong ? value.slice(0, 5e3) : value;
|
|
3760
|
+
let changed = overLong;
|
|
3761
|
+
const mapped = source.map((v) => {
|
|
3762
|
+
const c = clampPayload(v, depth + 1);
|
|
3763
|
+
if (c !== v) changed = true;
|
|
3764
|
+
return c;
|
|
3765
|
+
});
|
|
3766
|
+
return changed ? mapped : value;
|
|
3767
|
+
}
|
|
3768
|
+
if (isRecord(value)) {
|
|
3769
|
+
let changed = false;
|
|
3770
|
+
const out = {};
|
|
3771
|
+
for (const [k, v] of Object.entries(value)) {
|
|
3772
|
+
const c = clampPayload(v, depth + 1);
|
|
3773
|
+
if (c !== v) changed = true;
|
|
3774
|
+
out[k] = c;
|
|
3775
|
+
}
|
|
3776
|
+
return changed ? out : value;
|
|
3777
|
+
}
|
|
3778
|
+
return value;
|
|
3779
|
+
}
|
|
3780
|
+
/**
|
|
3781
|
+
* Clamp a single MessageItem in place-safe (returns a new object only when it
|
|
3782
|
+
* actually changed). Applied by the reducer when a tool settles and by the
|
|
3783
|
+
* history loader for every loaded item. Streaming text bubbles are left alone
|
|
3784
|
+
* — they're bounded by the model's output and clamping mid-stream would fight
|
|
3785
|
+
* the append path; only large tool/shell payloads and finished long texts are
|
|
3786
|
+
* worth clamping.
|
|
3787
|
+
*/
|
|
3788
|
+
function clampItem(item) {
|
|
3789
|
+
switch (item.kind) {
|
|
3790
|
+
case "tool": {
|
|
3791
|
+
const input = item.input == null ? item.input : clampPayload(item.input);
|
|
3792
|
+
const inputText = item.inputText.length > MIN_CLAMP_CHARS ? clampString(item.inputText) : item.inputText;
|
|
3793
|
+
let output = item.output;
|
|
3794
|
+
if (output?.kind === "ok") {
|
|
3795
|
+
const value = clampPayload(output.value);
|
|
3796
|
+
if (value !== output.value) output = {
|
|
3797
|
+
kind: "ok",
|
|
3798
|
+
value
|
|
3799
|
+
};
|
|
3800
|
+
} else if (output?.kind === "error") {
|
|
3801
|
+
const errorText = clampString(output.errorText);
|
|
3802
|
+
if (errorText !== output.errorText) output = {
|
|
3803
|
+
kind: "error",
|
|
3804
|
+
errorText
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
if (input === item.input && inputText === item.inputText && output === item.output) return item;
|
|
3808
|
+
return {
|
|
3809
|
+
...item,
|
|
3810
|
+
input,
|
|
3811
|
+
inputText,
|
|
3812
|
+
output
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
case "shell": {
|
|
3816
|
+
const clamped = clampString(item.output);
|
|
3817
|
+
return clamped === item.output ? item : {
|
|
3818
|
+
...item,
|
|
3819
|
+
output: clamped
|
|
3820
|
+
};
|
|
3821
|
+
}
|
|
3822
|
+
case "assistant-text":
|
|
3823
|
+
case "reasoning": {
|
|
3824
|
+
if (!item.done) return item;
|
|
3825
|
+
const clamped = clampString(item.text);
|
|
3826
|
+
return clamped === item.text ? item : {
|
|
3827
|
+
...item,
|
|
3828
|
+
text: clamped
|
|
3829
|
+
};
|
|
3830
|
+
}
|
|
3831
|
+
default: return item;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3691
3835
|
//#endregion
|
|
3692
3836
|
//#region src/chat/tui/chat/reducer.ts
|
|
3693
3837
|
function str(chunk, key) {
|
|
@@ -3770,7 +3914,7 @@ function applyChunk(items, chunk, agentName = null) {
|
|
|
3770
3914
|
case "tool-input-available": {
|
|
3771
3915
|
const toolCallId = str(chunk, "toolCallId");
|
|
3772
3916
|
const toolName = str(chunk, "toolName");
|
|
3773
|
-
const input = chunk.input
|
|
3917
|
+
const input = chunk.input == null ? null : clampPayload(chunk.input);
|
|
3774
3918
|
if (!toolCallId) return [...items];
|
|
3775
3919
|
if (items.find((m) => m.kind === "tool" && m.id === toolCallId)) return items.map((m) => m.kind === "tool" && m.id === toolCallId ? {
|
|
3776
3920
|
...m,
|
|
@@ -3791,7 +3935,7 @@ function applyChunk(items, chunk, agentName = null) {
|
|
|
3791
3935
|
case "tool-output-available": {
|
|
3792
3936
|
const toolCallId = str(chunk, "toolCallId");
|
|
3793
3937
|
if (!toolCallId) return [...items];
|
|
3794
|
-
const output = chunk.output
|
|
3938
|
+
const output = chunk.output == null ? null : clampPayload(chunk.output);
|
|
3795
3939
|
return items.map((m) => m.kind === "tool" && m.id === toolCallId ? {
|
|
3796
3940
|
...m,
|
|
3797
3941
|
output: {
|
|
@@ -3804,7 +3948,7 @@ function applyChunk(items, chunk, agentName = null) {
|
|
|
3804
3948
|
case "tool-output-error": {
|
|
3805
3949
|
const toolCallId = str(chunk, "toolCallId");
|
|
3806
3950
|
if (!toolCallId) return [...items];
|
|
3807
|
-
const errorText = str(chunk, "errorText") ?? "tool error";
|
|
3951
|
+
const errorText = clampString(str(chunk, "errorText") ?? "tool error");
|
|
3808
3952
|
return items.map((m) => m.kind === "tool" && m.id === toolCallId ? {
|
|
3809
3953
|
...m,
|
|
3810
3954
|
output: {
|
|
@@ -3958,6 +4102,32 @@ function nextCardId(items, toolCallId) {
|
|
|
3958
4102
|
return `${toolCallId}#c${items.filter((m) => m.kind === "card" && m.toolCallId === toolCallId).length}`;
|
|
3959
4103
|
}
|
|
3960
4104
|
|
|
4105
|
+
//#endregion
|
|
4106
|
+
//#region src/chat/tui/chat/window.ts
|
|
4107
|
+
/** Max transcript rows mounted at once. */
|
|
4108
|
+
const MAX_MOUNTED_ROWS = 400;
|
|
4109
|
+
/**
|
|
4110
|
+
* Keep the most recent `max` rows mounted; report how many older ones were
|
|
4111
|
+
* dropped from the mount so the UI can show a "N earlier messages" marker.
|
|
4112
|
+
*/
|
|
4113
|
+
function windowItems(items, max = MAX_MOUNTED_ROWS) {
|
|
4114
|
+
if (items.length <= max) return {
|
|
4115
|
+
visible: items.map((item, index) => ({
|
|
4116
|
+
item,
|
|
4117
|
+
index
|
|
4118
|
+
})),
|
|
4119
|
+
hiddenCount: 0
|
|
4120
|
+
};
|
|
4121
|
+
const start = items.length - max;
|
|
4122
|
+
return {
|
|
4123
|
+
visible: items.slice(start).map((item, offset) => ({
|
|
4124
|
+
item,
|
|
4125
|
+
index: start + offset
|
|
4126
|
+
})),
|
|
4127
|
+
hiddenCount: start
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
|
|
3961
4131
|
//#endregion
|
|
3962
4132
|
//#region src/chat/tui/chat/attribution.ts
|
|
3963
4133
|
/**
|
|
@@ -4117,7 +4287,7 @@ function uiMessagesToItems(messages) {
|
|
|
4117
4287
|
});
|
|
4118
4288
|
}
|
|
4119
4289
|
}
|
|
4120
|
-
return out;
|
|
4290
|
+
return out.map(clampItem);
|
|
4121
4291
|
}
|
|
4122
4292
|
function mapToolState(raw) {
|
|
4123
4293
|
switch (raw) {
|
|
@@ -8798,6 +8968,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8798
8968
|
agentHost
|
|
8799
8969
|
]);
|
|
8800
8970
|
const multiAgent = useMemo(() => conversationAgentNames(items, agent.name), [items, agent.name]).size > 1;
|
|
8971
|
+
const transcriptWindow = useMemo(() => windowItems(items), [items]);
|
|
8801
8972
|
const themeGeneration = themeVersion();
|
|
8802
8973
|
useEffect(() => {
|
|
8803
8974
|
if (!rest || !conversationId) return;
|
|
@@ -8900,7 +9071,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8900
9071
|
setItems((prev) => [...prev.filter((m) => m.id !== optimisticId), {
|
|
8901
9072
|
kind: "error",
|
|
8902
9073
|
id: crypto.randomUUID(),
|
|
8903
|
-
text:
|
|
9074
|
+
text: sendErrorMessage(err)
|
|
8904
9075
|
}]);
|
|
8905
9076
|
if (echo) setInput((existing) => existing ? existing : trimmed);
|
|
8906
9077
|
const restore = staged.filter((a) => uploadsRef.current.has(a.tempId));
|
|
@@ -9154,10 +9325,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
9154
9325
|
}]);
|
|
9155
9326
|
let captured = "";
|
|
9156
9327
|
const appendOutput = (chunk) => {
|
|
9157
|
-
captured
|
|
9328
|
+
captured = clampedAppend(captured, chunk);
|
|
9158
9329
|
setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
9159
9330
|
...m,
|
|
9160
|
-
output: m.output
|
|
9331
|
+
output: clampedAppend(m.output, chunk)
|
|
9161
9332
|
} : m));
|
|
9162
9333
|
};
|
|
9163
9334
|
let exitCode;
|
|
@@ -9194,7 +9365,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
9194
9365
|
const dec = new TextDecoder();
|
|
9195
9366
|
const append = (chunk) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
9196
9367
|
...m,
|
|
9197
|
-
output: m.output
|
|
9368
|
+
output: clampedAppend(m.output, chunk)
|
|
9198
9369
|
} : m));
|
|
9199
9370
|
const stamp = (exit) => setItems((prev) => prev.map((m) => m.id === id && m.kind === "shell" ? {
|
|
9200
9371
|
...m,
|
|
@@ -10104,11 +10275,26 @@ function ChatScreen({ agent, conversation }) {
|
|
|
10104
10275
|
agent.name,
|
|
10105
10276
|
" below."
|
|
10106
10277
|
]
|
|
10107
|
-
}) :
|
|
10278
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [transcriptWindow.hiddenCount > 0 ? /* @__PURE__ */ jsx("box", {
|
|
10279
|
+
style: {
|
|
10280
|
+
paddingLeft: 1,
|
|
10281
|
+
paddingRight: 1
|
|
10282
|
+
},
|
|
10283
|
+
children: /* @__PURE__ */ jsxs("text", {
|
|
10284
|
+
fg: theme.dim,
|
|
10285
|
+
children: [
|
|
10286
|
+
"↑ ",
|
|
10287
|
+
transcriptWindow.hiddenCount,
|
|
10288
|
+
" earlier message",
|
|
10289
|
+
transcriptWindow.hiddenCount === 1 ? "" : "s",
|
|
10290
|
+
" · kept in this session, not shown"
|
|
10291
|
+
]
|
|
10292
|
+
})
|
|
10293
|
+
}) : null, transcriptWindow.visible.map(({ item, index }) => /* @__PURE__ */ jsx(TranscriptRow, {
|
|
10108
10294
|
item,
|
|
10109
|
-
label: multiAgent ? agentLabelFor(items,
|
|
10295
|
+
label: multiAgent ? agentLabelFor(items, index) : null,
|
|
10110
10296
|
themeVersion: themeGeneration
|
|
10111
|
-
}, item.id)), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, {
|
|
10297
|
+
}, item.id))] }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, {
|
|
10112
10298
|
label: run.kind === "sending" ? "sending" : "working",
|
|
10113
10299
|
startedAtMs: run.startedAtMs
|
|
10114
10300
|
}) : null]
|
|
@@ -10527,7 +10713,7 @@ function ToastView() {
|
|
|
10527
10713
|
|
|
10528
10714
|
//#endregion
|
|
10529
10715
|
//#region src/chat/tui/app.tsx
|
|
10530
|
-
function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId }) {
|
|
10716
|
+
function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation }) {
|
|
10531
10717
|
const renderer = useRenderer();
|
|
10532
10718
|
const screen = useStore((s) => s.screen);
|
|
10533
10719
|
const chatTitle = useStore((s) => s.chatTitle);
|
|
@@ -10609,7 +10795,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
|
|
|
10609
10795
|
rest,
|
|
10610
10796
|
agentSelector,
|
|
10611
10797
|
conversationId,
|
|
10612
|
-
newConversation: useStore.getState().seedPrompt !== null
|
|
10798
|
+
newConversation: newConversation || useStore.getState().seedPrompt !== null
|
|
10613
10799
|
});
|
|
10614
10800
|
if (cancelled) return;
|
|
10615
10801
|
goTo(screen);
|
|
@@ -10631,6 +10817,7 @@ function App({ appUrl, sessionToken, shareMachine, promptHistoryPath, notificati
|
|
|
10631
10817
|
promptHistoryPath,
|
|
10632
10818
|
agentSelector,
|
|
10633
10819
|
conversationId,
|
|
10820
|
+
newConversation,
|
|
10634
10821
|
goTo,
|
|
10635
10822
|
setClients,
|
|
10636
10823
|
fail
|
|
@@ -10934,12 +11121,13 @@ var LinkifiedMarkdownRenderable = class extends MarkdownRenderable {
|
|
|
10934
11121
|
* module — and the heavy OpenTUI dependency graph it pulls in — is
|
|
10935
11122
|
* dynamically imported and never loaded on the Node management path.
|
|
10936
11123
|
*/
|
|
10937
|
-
async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, seedPrompt }) {
|
|
11124
|
+
async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, seedPrompt }) {
|
|
10938
11125
|
const renderer = await createCliRenderer({ exitOnCtrlC: false });
|
|
10939
11126
|
extend({ markdown: LinkifiedMarkdownRenderable });
|
|
10940
11127
|
useStore.setState({
|
|
10941
11128
|
seedPrompt: seedPrompt ?? null,
|
|
10942
|
-
autoGrantMachine: Boolean(seedPrompt && shareMachine)
|
|
11129
|
+
autoGrantMachine: Boolean(seedPrompt && shareMachine),
|
|
11130
|
+
newConversationIntent: newConversation
|
|
10943
11131
|
});
|
|
10944
11132
|
const store = useStore.getState();
|
|
10945
11133
|
if (noColorRequested()) {
|
|
@@ -10963,7 +11151,8 @@ async function runChat({ appUrl, sessionToken, shareMachine, promptHistoryPath,
|
|
|
10963
11151
|
promptHistoryPath,
|
|
10964
11152
|
notifications,
|
|
10965
11153
|
agentSelector: agentSelector ?? null,
|
|
10966
|
-
conversationId: conversationId ?? null
|
|
11154
|
+
conversationId: conversationId ?? null,
|
|
11155
|
+
newConversation
|
|
10967
11156
|
}));
|
|
10968
11157
|
}
|
|
10969
11158
|
/** Open the same workspace picker used by the initial agent screen. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { c as machineIdentity, i as mintPortalDeviceToken, l as portalWsUrl, n as findThisDevice, r as grantPortalAccess, s as buildEnv, t as fetchPortalDevices } from "./api-
|
|
2
|
+
import { a as errorMessage } from "./rest-vu2-Lrsa.mjs";
|
|
3
|
+
import { c as machineIdentity, i as mintPortalDeviceToken, l as portalWsUrl, n as findThisDevice, r as grantPortalAccess, s as buildEnv, t as fetchPortalDevices } from "./api-Bx5VyWaC.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { WebSocket } from "ws";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as createRestClient, t as HttpError } from "./rest-
|
|
3
|
-
import { n as isRecord, t as errorMessage } from "./util-CeisaZVY.mjs";
|
|
2
|
+
import { a as errorMessage, i as sendErrorMessage, n as createRestClient, o as isRecord, t as HttpError } from "./rest-vu2-Lrsa.mjs";
|
|
4
3
|
import stableStringify from "safe-stable-stringify";
|
|
5
4
|
|
|
6
5
|
//#region src/chat/tui/chat/card.ts
|
|
@@ -355,17 +354,24 @@ async function runPrint({ appUrl, sessionToken, prompt, agentSelector, conversat
|
|
|
355
354
|
};
|
|
356
355
|
}
|
|
357
356
|
/**
|
|
358
|
-
* Turn a transport failure into an actionable CLI error.
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
357
|
+
* Turn a transport failure into an actionable CLI error. Two failure classes
|
|
358
|
+
* reach here, both of which otherwise leak a raw HTML/JSON error page to stdout:
|
|
359
|
+
*
|
|
360
|
+
* - A Cloudflare edge 5xx (502/504) on a long run. When a messageId is known
|
|
361
|
+
* and recoverable we point the user at `skydive messages get <messageId>`
|
|
362
|
+
* rather than a blind retry (a retry re-executes an agent that may have
|
|
363
|
+
* write access).
|
|
364
|
+
* - A 4xx from the send/stream endpoints (a validation 400 whose body is a raw
|
|
365
|
+
* Zod issues array, a billing/permission block, an expired session). These
|
|
366
|
+
* go through {@link sendErrorMessage} so the `-p` path prints the same plain,
|
|
367
|
+
* actionable copy the interactive TUI shows instead of an obtuse blob.
|
|
363
368
|
*/
|
|
364
369
|
function toPrintError(err, messageId) {
|
|
365
370
|
if (err instanceof HttpError && err.status >= 500) {
|
|
366
371
|
const recovery = messageId ? ` The run may still be completing server-side. Do NOT blindly retry (it would re-run the agent). Fetch the result with: skydive messages get ${messageId}` : "";
|
|
367
372
|
return /* @__PURE__ */ new Error(`The request to Skydive timed out at the edge (HTTP ${err.status}).${recovery}`);
|
|
368
373
|
}
|
|
374
|
+
if (err instanceof HttpError) return new Error(sendErrorMessage(err));
|
|
369
375
|
return err instanceof Error ? err : new Error(String(err));
|
|
370
376
|
}
|
|
371
377
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./rest-
|
|
3
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
2
|
+
import "./rest-vu2-Lrsa.mjs";
|
|
3
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-DLGCpIM9.mjs";
|
|
4
4
|
|
|
5
5
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -14,7 +14,7 @@ import { n as printError } from "./output-B4cW10Ph.mjs";
|
|
|
14
14
|
* the reply (and to --json).
|
|
15
15
|
*/
|
|
16
16
|
async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
|
|
17
|
-
const { PortalClient } = await import("./client-
|
|
17
|
+
const { PortalClient } = await import("./client-BYYp0E6M.mjs");
|
|
18
18
|
let signalConnected;
|
|
19
19
|
const connected = new Promise((resolve) => {
|
|
20
20
|
signalConnected = resolve;
|
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createParser } from "eventsource-parser";
|
|
4
4
|
|
|
5
|
+
//#region src/chat/util.ts
|
|
6
|
+
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
7
|
+
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
8
|
+
* sites can read properties without asserting. */
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
/** Best-effort message from an unknown thrown value. */
|
|
13
|
+
function errorMessage(err) {
|
|
14
|
+
return err instanceof Error ? err.message : String(err);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
5
18
|
//#region src/chat/api/rest.ts
|
|
6
19
|
var HttpError = class extends Error {
|
|
7
20
|
constructor(status, body) {
|
|
@@ -25,6 +38,69 @@ function errorDetail(err) {
|
|
|
25
38
|
}
|
|
26
39
|
return err instanceof Error ? err.message : String(err);
|
|
27
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The server's human-readable line out of an error response body, or null when
|
|
43
|
+
* the body carries none. Two shapes reach the CLI on a failed send:
|
|
44
|
+
*
|
|
45
|
+
* - `{ error: string }` — the chat route's own errors (a ChatClientError like
|
|
46
|
+
* "agent not found" / "attachment not found: <id>", the 429 "Too many
|
|
47
|
+
* requests", the generic "send failed"). Returned verbatim; these are
|
|
48
|
+
* written to be shown.
|
|
49
|
+
* - `{ success: false, error: { issues: [{ message }, …] } }` — a Zod request
|
|
50
|
+
* validation failure from `zValidator` (e.g. the message-too-long copy).
|
|
51
|
+
* Without this the CLI printed the whole issues array as a raw JSON blob,
|
|
52
|
+
* which is the "obtuse 400 with no recourse" a user sees. We lift the first
|
|
53
|
+
* issue's message, which the schema authors wrote for humans.
|
|
54
|
+
*
|
|
55
|
+
* Anything else (a Cloudflare/ALB HTML error page, an empty body) yields null
|
|
56
|
+
* so the caller can fall back to a status-based line.
|
|
57
|
+
*/
|
|
58
|
+
function serverErrorMessage(body) {
|
|
59
|
+
let parsed;
|
|
60
|
+
try {
|
|
61
|
+
parsed = JSON.parse(body);
|
|
62
|
+
} catch (_error) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!isRecord(parsed)) return null;
|
|
66
|
+
if (typeof parsed.error === "string" && parsed.error.length > 0) return parsed.error;
|
|
67
|
+
if (isRecord(parsed.error) && Array.isArray(parsed.error.issues)) {
|
|
68
|
+
const first = parsed.error.issues.find((issue) => isRecord(issue) && typeof issue.message === "string" && !!issue.message);
|
|
69
|
+
if (first) return first.message;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A short, actionable line for a failed chat send, for both the interactive
|
|
75
|
+
* TUI error row and the headless `-p` path. The raw transport error is an
|
|
76
|
+
* `HTTP 400: {"success":false,"error":{"issues":[…]}}` blob that a user can do
|
|
77
|
+
* nothing with; this maps every send failure the server can produce to plain
|
|
78
|
+
* language and, where possible, a next step.
|
|
79
|
+
*
|
|
80
|
+
* Precedence: a message the SERVER wrote for humans always wins (validation
|
|
81
|
+
* copy, "agent not found", a billing/permission reason) — we only synthesize a
|
|
82
|
+
* line from the HTTP status when the body carried nothing usable. This keeps
|
|
83
|
+
* new server-side error copy flowing through without a CLI change, while never
|
|
84
|
+
* leaving the user staring at a bare status code.
|
|
85
|
+
*/
|
|
86
|
+
function sendErrorMessage(err) {
|
|
87
|
+
if (!(err instanceof HttpError)) return `Couldn't reach Skydive (${err instanceof Error ? err.message : String(err)}). Check your connection and try again.`;
|
|
88
|
+
const fromServer = serverErrorMessage(err.body);
|
|
89
|
+
switch (err.status) {
|
|
90
|
+
case 401: return "Your session has expired. Run `skydive auth login --web`, then send again.";
|
|
91
|
+
case 403: return fromServer ?? "You don't have permission to send to this agent in the current workspace.";
|
|
92
|
+
case 404: return fromServer ?? "This conversation or agent no longer exists.";
|
|
93
|
+
case 413: return fromServer ?? "That message is too large to send. Attach it as a file instead of pasting it inline.";
|
|
94
|
+
case 429: return "You're sending messages too quickly. Wait a moment and try again.";
|
|
95
|
+
default: break;
|
|
96
|
+
}
|
|
97
|
+
if (err.status >= 500) return `Skydive had a problem sending that (HTTP ${err.status}). Wait a moment and try again.`;
|
|
98
|
+
if (err.status === 400) {
|
|
99
|
+
if (fromServer && fromServer !== "send failed") return fromServer;
|
|
100
|
+
return "Skydive couldn't send that message. Try again in a moment.";
|
|
101
|
+
}
|
|
102
|
+
return fromServer ?? `Send failed (HTTP ${err.status}).`;
|
|
103
|
+
}
|
|
28
104
|
const MAX_STREAM_RECONNECTS = 5;
|
|
29
105
|
function createRestClient({ appUrl, sessionToken }) {
|
|
30
106
|
const baseHeaders = {
|
|
@@ -487,4 +563,4 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [
|
|
|
487
563
|
]);
|
|
488
564
|
|
|
489
565
|
//#endregion
|
|
490
|
-
export { createRestClient as n, errorDetail as r, HttpError as t };
|
|
566
|
+
export { errorMessage as a, sendErrorMessage as i, createRestClient as n, isRecord as o, errorDetail as r, HttpError as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.604",
|
|
4
4
|
"description": "Skydive CLI — cloud agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --minWorkers=1 --maxWorkers=2 --passWithNoTests && yarn test:tui",
|
|
26
26
|
"test:tui": "bun test .tui.test",
|
|
27
27
|
"render:frames": "bun scripts/render-frames.tsx",
|
|
28
|
+
"render:send-errors": "bun scripts/render-send-errors.tsx",
|
|
28
29
|
"typecheck": "tsgo --noEmit"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
//#region src/chat/util.ts
|
|
3
|
-
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
4
|
-
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
5
|
-
* sites can read properties without asserting. */
|
|
6
|
-
function isRecord(value) {
|
|
7
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
-
}
|
|
9
|
-
/** Best-effort message from an unknown thrown value. */
|
|
10
|
-
function errorMessage(err) {
|
|
11
|
-
return err instanceof Error ? err.message : String(err);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
//#endregion
|
|
15
|
-
export { isRecord as n, errorMessage as t };
|
|
File without changes
|