skydive-cli 0.6.0-beta.3 → 0.6.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.
@@ -160,7 +160,7 @@ function errorMessage(err) {
160
160
  * the commit time of the built tree is one monotonic clock they share.
161
161
  */
162
162
  function portalDaemonBuild() {
163
- return "1787865403";
163
+ return "1790192806";
164
164
  }
165
165
  /**
166
166
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -186,6 +186,8 @@ function parseStamp(stamp) {
186
186
 
187
187
  //#endregion
188
188
  //#region ../portal-protocol/src/index.ts
189
+ const CLOSE_SUPERSEDED = 4001;
190
+ const CLOSE_FLAP_COOLDOWN = 4002;
189
191
  const MAX_WS_FRAME_BYTES = 16 * 1024 * 1024;
190
192
  const T_DATA = 1;
191
193
  const T_CTRL = 2;
@@ -584,7 +586,11 @@ const DEFAULT_HEARTBEAT = {
584
586
  wakeGapThresholdMs: 1e4,
585
587
  netWatchTickMs: 2e3,
586
588
  initialBackoffMs: 500,
587
- maxBackoffMs: 1e4
589
+ maxBackoffMs: 1e4,
590
+ supersededBackoffFloorMs: 5e3,
591
+ flapWindowMs: 6e4,
592
+ flapMaxStandDowns: 3,
593
+ flapBackoffMs: 6e4
588
594
  };
589
595
  /**
590
596
  * A cheap fingerprint of the machine's network identity: every non-internal
@@ -669,6 +675,7 @@ var PortalClient = class {
669
675
  netWatch = null;
670
676
  netFingerprint = networkFingerprint();
671
677
  backoffKick = null;
678
+ inStandDownSleep = false;
672
679
  dialReason = null;
673
680
  disconnectedAt = null;
674
681
  handingOff = false;
@@ -739,6 +746,7 @@ var PortalClient = class {
739
746
  this.ws.terminate();
740
747
  return;
741
748
  }
749
+ if (this.inStandDownSleep) return;
742
750
  this.kickBackoff();
743
751
  }
744
752
  kickBackoff() {
@@ -914,11 +922,23 @@ var PortalClient = class {
914
922
  this.opts.onMachineName(identity.machineName, identity.machineNameSource);
915
923
  }
916
924
  let backoff = this.heartbeatConfig.initialBackoffMs;
925
+ let standDowns = [];
917
926
  while (this.enabled && !this.disposed && !this.handingOff) {
918
927
  this.setStatus("connecting");
928
+ let sleepFloor = 0;
919
929
  try {
920
930
  const token = await this.wsToken();
921
- if (await this.runConnection(token)) backoff = this.heartbeatConfig.initialBackoffMs;
931
+ const { healthy, standDown } = await this.runConnection(token);
932
+ if (standDown) {
933
+ const now = Date.now();
934
+ standDowns.push(now);
935
+ standDowns = standDowns.filter((at) => now - at <= this.heartbeatConfig.flapWindowMs);
936
+ if (standDowns.length >= this.heartbeatConfig.flapMaxStandDowns) {
937
+ backoff = this.heartbeatConfig.flapBackoffMs;
938
+ this.setStatus("error", "another portal connection for this machine keeps taking over — if Skydive Desktop or `skydive portal open` is running twice, quit the duplicate; an unstable network can also cause this briefly");
939
+ } else backoff = Math.max(Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs), this.heartbeatConfig.supersededBackoffFloorMs);
940
+ sleepFloor = backoff / 2;
941
+ } else if (healthy) backoff = this.heartbeatConfig.initialBackoffMs;
922
942
  else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
923
943
  } catch (err) {
924
944
  if (!this.enabled || this.disposed || this.handingOff) break;
@@ -926,17 +946,23 @@ var PortalClient = class {
926
946
  backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
927
947
  }
928
948
  if (!this.enabled || this.disposed || this.handingOff) break;
929
- await this.backoffSleep(Math.random() * backoff);
949
+ this.inStandDownSleep = sleepFloor > 0;
950
+ await this.backoffSleep(sleepFloor + Math.random() * Math.max(backoff - sleepFloor, 0));
951
+ this.inStandDownSleep = false;
930
952
  }
931
953
  }
932
954
  runConnection(token) {
933
955
  return new Promise((resolve) => {
934
956
  let settled = false;
935
957
  let healthy = false;
958
+ let standDown = false;
936
959
  const settle = () => {
937
960
  if (settled) return;
938
961
  settled = true;
939
- resolve(healthy);
962
+ resolve({
963
+ healthy,
964
+ standDown
965
+ });
940
966
  };
941
967
  const ws = new WebSocket(portalWsUrl(this.opts.appUrl, this.machineName, this.friendlyName, {
942
968
  build: portalDaemonBuild(),
@@ -988,7 +1014,9 @@ var PortalClient = class {
988
1014
  ws.on("error", (err) => {
989
1015
  this.error = errorMessage(err);
990
1016
  });
991
- ws.on("close", (code) => {
1017
+ ws.on("close", (code, reason) => {
1018
+ standDown = code === CLOSE_SUPERSEDED || code === CLOSE_FLAP_COOLDOWN || code === 1e3 && reason.toString() === "superseded";
1019
+ if (standDown) this.dialReason ??= `superseded (code ${code})`;
992
1020
  this.dialReason ??= `connection closed (code ${code})`;
993
1021
  this.disconnectedAt ??= Date.now();
994
1022
  clearInterval(heartbeat);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { t as PortalClient } from "./client-B72n-W4g.mjs";
3
+ import "./api-BFQ4PQDA.mjs";
4
+
5
+ export { PortalClient };
@@ -223,8 +223,23 @@ var SandboxStream = class SandboxStream {
223
223
  }
224
224
  });
225
225
  let failure = null;
226
+ ws.on("unexpected-response", (_request, response) => {
227
+ const chunks = [];
228
+ let bytes = 0;
229
+ response.on("data", (chunk) => {
230
+ if (bytes >= MAX_HANDSHAKE_BODY_BYTES) return;
231
+ const remaining = MAX_HANDSHAKE_BODY_BYTES - bytes;
232
+ const kept = Buffer.from(chunk).subarray(0, remaining);
233
+ chunks.push(kept);
234
+ bytes += kept.length;
235
+ });
236
+ response.on("end", () => {
237
+ failure = formatHandshakeRejection(response.statusCode, response.statusMessage, Buffer.concat(chunks).toString("utf8"));
238
+ ws.terminate();
239
+ });
240
+ });
226
241
  ws.on("error", (err) => {
227
- failure = err.message;
242
+ failure ??= err.message;
228
243
  });
229
244
  ws.on("close", () => {
230
245
  this.closed = true;
@@ -265,6 +280,12 @@ var SandboxStream = class SandboxStream {
265
280
  return new SandboxStream(new WebSocket(url.toString(), { headers: { authorization: `Bearer ${opts.sessionToken}` } }), opts.onEvent);
266
281
  }
267
282
  };
283
+ const MAX_HANDSHAKE_BODY_BYTES = 512;
284
+ function formatHandshakeRejection(statusCode, statusMessage, body) {
285
+ const status = [statusCode, statusMessage].filter(Boolean).join(" ");
286
+ const reason = Array.from(body, (char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127 ? " " : char).join("").replace(/\s+/g, " ").trim();
287
+ return reason ? `${status}: ${reason}` : status || "WebSocket upgrade rejected";
288
+ }
268
289
  function toBuffer(data) {
269
290
  if (Buffer.isBuffer(data)) return data;
270
291
  if (Array.isArray(data)) return Buffer.concat(data);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-CErCpXl9.mjs";
2
+ import { i as isRecord, n as isNewerBuild, r as portalDaemonBuild, t as PortalClient } from "./client-B72n-W4g.mjs";
3
3
  import { t as defaultTlsCertSource } from "./tls-cert-Rua2oV7n.mjs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
@@ -73,17 +73,37 @@ function canonicalPortalAppUrl(appUrl) {
73
73
  * every alias of one deployment (see {@link canonicalPortalAppUrl}) shares
74
74
  * one. Kept under the OS temp dir because Unix socket paths are
75
75
  * length-limited (~104 bytes on macOS).
76
+ *
77
+ * Darwin `sockaddr_un.sun_path` is 104 bytes including NUL (103 usable).
78
+ * `os.tmpdir()` on macOS is `$TMPDIR` (`/var/folders/<id>/T`, often ~50
79
+ * chars). Adding `skydive-portal-<user>-<hash>/daemon.sock` overruns that
80
+ * cap: bind() fails, the sidecar handshake still reports the long path, and
81
+ * connect() returns ENOENT (or Go's "path must be shorter than SUN_LEN").
82
+ * Stay in `os.tmpdir()` (do not relocate to `/tmp`) and shorten the leaf
83
+ * when the composed path would blow the cap. Windows named pipes have no
84
+ * sun_path, so they keep the long leaf.
76
85
  */
86
+ const SUN_PATH_MAX = 103;
77
87
  function daemonPaths(appUrl) {
88
+ const parent = os.tmpdir();
78
89
  const user = safeSegment(os.userInfo().username || "user");
79
90
  const app = createHash("sha256").update(canonicalPortalAppUrl(appUrl)).digest("hex").slice(0, 12);
80
- const dir = path.join(os.tmpdir(), `skydive-portal-${user}-${app}`);
81
- return {
91
+ const dir = path.join(parent, `skydive-portal-${user}-${app}`);
92
+ const paths = {
82
93
  dir,
83
94
  socketPath: path.join(dir, "daemon.sock"),
84
95
  statePath: path.join(dir, "state.json"),
85
96
  logPath: path.join(dir, "daemon.log")
86
97
  };
98
+ if (process.platform === "win32" || Buffer.byteLength(paths.socketPath) <= SUN_PATH_MAX) return paths;
99
+ const short = createHash("sha256").update(`${os.userInfo().username || "user"}\0${canonicalPortalAppUrl(appUrl)}`).digest("hex").slice(0, 12);
100
+ const shortDir = path.join(parent, `skydive-p-${short}`);
101
+ return {
102
+ dir: shortDir,
103
+ socketPath: path.join(shortDir, "d.sock"),
104
+ statePath: path.join(shortDir, "state.json"),
105
+ logPath: path.join(shortDir, "daemon.log")
106
+ };
87
107
  }
88
108
  function safeSegment(value) {
89
109
  return value.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 24) || "user";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./client-CErCpXl9.mjs";
2
+ import "./client-B72n-W4g.mjs";
3
3
  import "./tls-cert-Rua2oV7n.mjs";
4
4
  import "./api-BFQ4PQDA.mjs";
5
- import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-Cuy4S6Rw.mjs";
5
+ import { a as runPortalDaemon, c as stopDaemonForHandoff, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-C4cZdILE.mjs";
6
6
 
7
7
  export { runPortalDaemon };
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import "./client-B72n-W4g.mjs";
3
+ import "./tls-cert-Rua2oV7n.mjs";
4
+ import "./api-BFQ4PQDA.mjs";
5
+ import "./daemon-C4cZdILE.mjs";
6
+ import { t as PortalDaemonClient } from "./daemon-client-P4qpHwFG.mjs";
7
+
8
+ export { PortalDaemonClient };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-Cuy4S6Rw.mjs";
2
+ import { d as daemonPaths, f as encodeLine, l as LOCAL_PROTOCOL_VERSION, m as parseDaemonMessage, n as ensureDaemonRunning, p as makeLineParser } from "./daemon-C4cZdILE.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
- import { c as version$1 } from "./rest-ChXt8YGA.mjs";
3
- import { w as getConfigPath } from "./print-B3yhPm0N.mjs";
2
+ import { c as version$1 } from "./rest-DU74TXCi.mjs";
3
+ import { E as getConfigPath } from "./print-D1CeaocN.mjs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  import { err, ok } from "neverthrow";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import "./rest-ChXt8YGA.mjs";
3
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-B3yhPm0N.mjs";
2
+ import "./rest-DU74TXCi.mjs";
3
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-D1CeaocN.mjs";
4
4
  import "./billing-blocked-D3l5kJlX.mjs";
5
5
 
6
6
  export { messageGet, readStdin, resolveAgent, runPrint };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-ChXt8YGA.mjs";
2
+ import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-DU74TXCi.mjs";
3
3
  import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
4
4
  import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-D3l5kJlX.mjs";
5
5
  import path from "node:path";
@@ -691,6 +691,66 @@ function reconcileMaskedInput(previousValue, displayed) {
691
691
  while (kept < displayed.length && kept < previousValue.length && displayed[kept] === MASK_CHAR) kept++;
692
692
  return previousValue.slice(0, kept) + displayed.slice(kept);
693
693
  }
694
+ /**
695
+ * Whether a card is one an agent RUN is actively blocked on until the user
696
+ * decides here: a portal-share consent (`platform portal request`), a compute
697
+ * upgrade decision (`platform compute request`), or a connect/credential card
698
+ * the run is waiting to be fulfilled. The lone non-blocking actionable card is
699
+ * `answer_question` — a `platform ask` form, whose answer is a normal user
700
+ * message (the run keeps going regardless), so it must never shadow a decision
701
+ * the agent is stalled on.
702
+ */
703
+ function isBlockingCardAction(action) {
704
+ return action.kind !== "answer_question";
705
+ }
706
+ /**
707
+ * Narrow a set of actionable cards to the ones ctrl+r (and the card picker)
708
+ * should target, prioritizing blocking cards over `platform ask` forms. When
709
+ * both a blocking card and a stale `answer_question` are waiting, ctrl+r must
710
+ * approve the thing the agent is stalled on, not re-answer an earlier ask that
711
+ * never stops being actionable (the CLI only settles it via the AskPicker, so
712
+ * an ignored or composer-answered ask lingers forever). Falls through to the
713
+ * ask forms only when they are the only actionable cards left. Preserves input
714
+ * order within a tier, so a single card and genuine multi-card cases behave
715
+ * exactly as before.
716
+ */
717
+ function prioritizeActionableCards(cards) {
718
+ const blocking = cards.filter((c) => c.card.button !== null && isBlockingCardAction(c.card.button.action));
719
+ return blocking.length > 0 ? blocking : [...cards];
720
+ }
721
+ /**
722
+ * The footer verb for a single card action. Derived from the action's own
723
+ * `kind`, not from `isBlockingCardAction` (which answers a different question —
724
+ * "is the run stalled on this" — and is right for prioritization but wrong as a
725
+ * label: it would read a portal-share or compute-approval card as "connect").
726
+ * Each blocking kind gets the verb that matches what the chord actually does:
727
+ * an OAuth/credential card connects, a portal-share card grants, a compute
728
+ * request is decided, a `platform ask` form is answered.
729
+ */
730
+ function verbForAction(action) {
731
+ switch (action.kind) {
732
+ case "open_oauth":
733
+ case "open_external_oauth":
734
+ case "open_github_app":
735
+ case "submit_credential": return "connect";
736
+ case "grant_portal": return "grant";
737
+ case "decide_compute": return "decide";
738
+ case "answer_question": return "answer";
739
+ }
740
+ }
741
+ /**
742
+ * The footer verb for the actionable cards ctrl+r would target. Reflects the
743
+ * first card's actual action so a stale ask never mislabels the footer as
744
+ * "connect", and a portal/compute card reads as "grant"/"decide" rather than
745
+ * being lumped under "connect". Pass the ALREADY-prioritized set (see
746
+ * prioritizeActionableCards) so the label matches what ctrl+r acts on; null
747
+ * when nothing is waiting.
748
+ */
749
+ function actionableCardVerb(cards) {
750
+ const [first] = cards;
751
+ if (!first || first.card.button === null) return null;
752
+ return verbForAction(first.card.button.action);
753
+ }
694
754
 
695
755
  //#endregion
696
756
  //#region src/chat/connect-cards.ts
@@ -1017,4 +1077,4 @@ async function readStdin() {
1017
1077
  }
1018
1078
 
1019
1079
  //#endregion
1020
- export { getSavedTheme as A, resolveSession as B, deleteConfig as C, getPreference as D, getLastSeenVersion as E, recordDefaultAgent as F, setLastSeenVersion as G, saveConfig as H, resolveAppUrl as I, resolveChatAuth as L, getStoredApiKeyId as M, getStoredApiKeyWorkspaceName as N, getPromptHistoryPath as O, getUpdateCheckDisabled as P, resolveConfig as R, PREFERENCES as S, getDefaultAgent as T, saveSession as U, resolveWebUrl as V, saveTheme as W, API_KEYS_URL as _, runPrint as a, DEFAULT_API_URL as b, cardActionErrorMessage as c, reconcileMaskedInput as d, resolveConnectUrl as f, specKeyFor as g, parseConnectCard as h, resolveAgent as i, getShareMachineDefault as j, getReviewStateDir as k, parseExternalOauthConnectParams as l, computeSettledLabel as m, messageGet as n, toPrintError as o, composeQuestionAnswer as p, readStdin as r, MASK_CHAR as s, collectRunText as t, parseOauthConnectParams as u, API_KEY_FAMILY_PREFIX as v, getConfigPath as w, DEFAULT_APP_URL as x, API_KEY_PREFIX as y, resolveManagementAuth as z };
1080
+ export { getPromptHistoryPath as A, resolveConfig as B, DEFAULT_APP_URL as C, getDefaultAgent as D, getConfigPath as E, getStoredApiKeyWorkspaceName as F, saveSession as G, resolveSession as H, getUpdateCheckDisabled as I, saveTheme as K, recordDefaultAgent as L, getSavedTheme as M, getShareMachineDefault as N, getLastSeenVersion as O, getStoredApiKeyId as P, resolveAppUrl as R, DEFAULT_API_URL as S, deleteConfig as T, resolveWebUrl as U, resolveManagementAuth as V, saveConfig as W, parseConnectCard as _, runPrint as a, API_KEY_FAMILY_PREFIX as b, actionableCardVerb as c, parseOauthConnectParams as d, prioritizeActionableCards as f, computeSettledLabel as g, composeQuestionAnswer as h, resolveAgent as i, getReviewStateDir as j, getPreference as k, cardActionErrorMessage as l, resolveConnectUrl as m, messageGet as n, toPrintError as o, reconcileMaskedInput as p, setLastSeenVersion as q, readStdin as r, MASK_CHAR as s, collectRunText as t, parseExternalOauthConnectParams as u, specKeyFor as v, PREFERENCES as w, API_KEY_PREFIX as x, API_KEYS_URL as y, resolveChatAuth as z };
@@ -14,7 +14,7 @@ import { n as printError } from "./output-C9mb3sUB.mjs";
14
14
  * the reply (and to --json).
15
15
  */
16
16
  async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
17
- const { PortalClient } = await import("./client-qY4nTj2Y.mjs");
17
+ const { PortalClient } = await import("./client-CIwTYHhL.mjs");
18
18
  const { defaultTlsCertSource } = await import("./tls-cert-BpCaD5AT.mjs");
19
19
  let signalConnected;
20
20
  const connected = new Promise((resolve) => {
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import "./client-l6orYfqq.mjs";
3
+ import { t as runRawPtyPassthrough } from "./raw-pty-dndn1Kxc.mjs";
4
+
5
+ export { runRawPtyPassthrough };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as SandboxStream } from "./client-Btq6bMzX.mjs";
2
+ import { t as SandboxStream } from "./client-l6orYfqq.mjs";
3
3
 
4
4
  //#region src/chat/sandbox/raw-pty.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-ChXt8YGA.mjs";
2
+ import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-DU74TXCi.mjs";
3
3
  import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
4
4
  import "./billing-blocked-D3l5kJlX.mjs";
5
5
 
@@ -6,7 +6,7 @@ import { createParser } from "eventsource-parser";
6
6
 
7
7
  //#region package.json
8
8
  var name = "skydive-cli";
9
- var version = "0.6.0-beta.3";
9
+ var version = "0.6.0-beta.31";
10
10
 
11
11
  //#endregion
12
12
  //#region src/chat/util.ts
@@ -279,6 +279,14 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
279
279
  for (const [modelId, config] of Object.entries(modelConfigOverrides ?? {})) if (config.thinkingLevel) efforts[modelId] = config.thinkingLevel;
280
280
  return efforts;
281
281
  },
282
+ getBillingSummary: async () => {
283
+ const { result } = await get("/api/v1/trpc/v2.billing.getBillingOrgSummary", billingSummaryResponseSchema);
284
+ return result.data;
285
+ },
286
+ getFreeFloorState: async () => {
287
+ const { result } = await get("/api/v1/trpc/v2.billing.freeFloorState", freeFloorStateResponseSchema);
288
+ return result.data;
289
+ },
282
290
  setModelEffort: async ({ modelId, thinkingLevel }) => {
283
291
  await post("/api/v1/models/config", {
284
292
  modelId,
@@ -295,6 +303,17 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
295
303
  thinkingLevel: agent.thinkingLevel ?? null
296
304
  };
297
305
  },
306
+ setConversationModel: async ({ conversationId, agentId, model, thinkingLevel }) => {
307
+ const settled = await post(`/api/v1/conversations/${conversationId}/model`, {
308
+ agentId,
309
+ model,
310
+ thinkingLevel
311
+ }, conversationModelResponseSchema);
312
+ return {
313
+ ...settled,
314
+ scope: settled.scope ?? null
315
+ };
316
+ },
298
317
  updateAgentEffort: async ({ agentId, thinkingLevel }) => {
299
318
  const { agent } = await patchAgent(agentId, { thinkingLevel });
300
319
  return { thinkingLevel: agent.thinkingLevel ?? null };
@@ -541,6 +560,21 @@ const platformModelSchema = z.object({
541
560
  }).passthrough();
542
561
  const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
543
562
  const modelEffortPreferencesResponseSchema = z.object({ modelConfigOverrides: z.record(z.string(), z.object({ thinkingLevel: z.string().optional() })).optional() });
563
+ const billingSummaryResponseSchema = z.object({ result: z.object({ data: z.object({
564
+ planKey: z.string().nullable(),
565
+ blockReason: z.string().nullable(),
566
+ billingEnforcementEnabled: z.boolean()
567
+ }) }) });
568
+ const freeFloorStateResponseSchema = z.object({ result: z.object({ data: z.object({
569
+ available: z.boolean(),
570
+ models: z.array(z.string()),
571
+ resetsAt: z.string().nullable()
572
+ }).nullable() }) });
573
+ const conversationModelResponseSchema = z.object({
574
+ model: z.string().nullable(),
575
+ thinkingLevel: z.string().nullable(),
576
+ scope: z.enum(["agent", "conversation"]).nullish()
577
+ });
544
578
  const updateAgentResponseSchema = z.object({ agent: z.object({
545
579
  model: z.string().nullable().optional(),
546
580
  thinkingLevel: z.string().nullable().optional()
@@ -573,6 +607,7 @@ const conversationSummarySchema = z.object({
573
607
  channel: z.string().nullable(),
574
608
  channelLabel: z.string().nullable(),
575
609
  viewerArchivedAt: z.string().nullable().optional(),
610
+ viewerParticipates: z.boolean().optional(),
576
611
  unread: z.boolean().optional(),
577
612
  agent: conversationAgentSchema,
578
613
  agents: z.array(conversationAgentSchema).optional()
@@ -709,7 +744,8 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [
709
744
  id: z.string(),
710
745
  model: z.string().nullable(),
711
746
  modelLocked: z.boolean(),
712
- thinkingLevel: z.string().nullish()
747
+ thinkingLevel: z.string().nullish(),
748
+ agentModel: z.string().nullish()
713
749
  }),
714
750
  z.object({
715
751
  kind: z.literal("run"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.6.0-beta.3",
3
+ "version": "0.6.0-beta.31",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -25,12 +25,13 @@
25
25
  "test:unit": "vitest run --passWithNoTests && yarn test:tui",
26
26
  "test:ci": "vitest run --coverage --coverage.reporter=lcovonly --reporter=default --reporter=github-actions --minWorkers=1 --maxWorkers=2 --passWithNoTests && yarn test:tui",
27
27
  "test:tui": "node scripts/run-tui-tests.mjs",
28
+ "test:blackbox": "bun test test/blackbox --timeout 120000",
28
29
  "render:frames": "bun scripts/render-frames.tsx",
29
30
  "render:send-errors": "bun scripts/render-send-errors.tsx",
30
31
  "typecheck": "tsgo --noEmit"
31
32
  },
32
33
  "dependencies": {
33
- "@opentui/react": "0.5.8",
34
+ "@opentui/react": "0.4.3",
34
35
  "conf": "^13.0.1",
35
36
  "diff": "9.0.0",
36
37
  "eventsource-parser": "^3.0.8",
@@ -38,7 +39,7 @@
38
39
  "fuzzysort": "^3.1.0",
39
40
  "neverthrow": "^8.2.0",
40
41
  "open": "^10.1.0",
41
- "react": "^19.2.0",
42
+ "react": "^19.0.0",
42
43
  "react-devtools-core": "^7.0.1",
43
44
  "safe-stable-stringify": "^2.3.1",
44
45
  "semver": "^7.7.4",
@@ -70,9 +71,9 @@
70
71
  },
71
72
  "//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
72
73
  "optionalDependencies": {
73
- "skydive-cli-darwin-arm64": "0.6.0-beta.3",
74
- "skydive-cli-darwin-x64": "0.6.0-beta.3",
75
- "skydive-cli-linux-x64": "0.6.0-beta.3",
76
- "skydive-cli-linux-arm64": "0.6.0-beta.3"
74
+ "skydive-cli-darwin-arm64": "0.6.0-beta.31",
75
+ "skydive-cli-darwin-x64": "0.6.0-beta.31",
76
+ "skydive-cli-linux-x64": "0.6.0-beta.31",
77
+ "skydive-cli-linux-arm64": "0.6.0-beta.31"
77
78
  }
78
79
  }
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as PortalClient } from "./client-CErCpXl9.mjs";
3
- import "./api-BFQ4PQDA.mjs";
4
-
5
- export { PortalClient };
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-CErCpXl9.mjs";
3
- import "./tls-cert-Rua2oV7n.mjs";
4
- import "./api-BFQ4PQDA.mjs";
5
- import "./daemon-Cuy4S6Rw.mjs";
6
- import { t as PortalDaemonClient } from "./daemon-client-b10uck-j.mjs";
7
-
8
- export { PortalDaemonClient };
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-Btq6bMzX.mjs";
3
- import { t as runRawPtyPassthrough } from "./raw-pty-D5PhKZSl.mjs";
4
-
5
- export { runRawPtyPassthrough };