skydive-cli 0.6.0-beta.39 → 0.6.0-beta.41

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.
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import { a as errorMessage, i as sendErrorMessage, n as errorDetail, o as isRecord, t as createRestClient } from "./rest-HMJ6HYnl.mjs";
3
- import { C as DEFAULT_APP_URL, E as getConfigPath, K as saveTheme, L as recordDefaultAgent, M as getSavedTheme, S as DEFAULT_API_URL, U as resolveWebUrl, _ as parseConnectCard, c as actionableCardVerb, d as parseOauthConnectParams, f as prioritizeActionableCards, g as computeSettledLabel, h as composeQuestionAnswer, i as resolveAgent, j as getReviewStateDir, l as cardActionErrorMessage, m as resolveConnectUrl, p as reconcileMaskedInput, s as MASK_CHAR, u as parseExternalOauthConnectParams, v as specKeyFor } from "./print-BPQO_UaI.mjs";
4
- import { A as installAgentAlias, C as themeVersion, D as machineOsFromPlatform, F as detectShell, L as getActiveWorkspaceId, M as takenAliasNames, O as aliasActivationHint, S as themeModeFromColorFgBg, T as themesForMode, V as setActiveWorkspace, _ as monoTheme, a as WORDMARK, b as themeForMode, d as profilingEnabled, f as record, g as findTheme, h as applyTheme, i as MARK_CELLS, j as slugifyAliasName, k as dedupeAliasName, m as DEFAULT_THEME_ID, n as buildCrashReport, p as writeArtifact, r as writeCrashReport, s as splashFitsWidth, t as installCrashHandler, v as noColorRequested, x as themeMode, y as theme, z as listWorkspaces } from "./install-03ApVdVv.mjs";
2
+ import { a as errorMessage, i as sendErrorMessage, n as errorDetail, o as isRecord, t as createRestClient } from "./rest-CXCsoY4Q.mjs";
3
+ import { C as DEFAULT_API_URL, D as getConfigPath, M as getReviewStateDir, N as getSavedTheme, R as recordDefaultAgent, W as resolveWebUrl, _ as computeSettledLabel, c as actionableCardVerb, d as parseOauthConnectParams, f as prioritizeActionableCards, g as composeQuestionAnswerEntries, h as composeQuestionAnswer, i as resolveAgent, l as cardActionErrorMessage, m as resolveConnectUrl, p as reconcileMaskedInput, q as saveTheme, s as MASK_CHAR, u as parseExternalOauthConnectParams, v as parseConnectCard, w as DEFAULT_APP_URL, y as specKeyFor } from "./print-DTaoROjq.mjs";
4
+ import { A as installAgentAlias, C as themeVersion, D as machineOsFromPlatform, F as detectShell, L as getActiveWorkspaceId, M as takenAliasNames, O as aliasActivationHint, S as themeModeFromColorFgBg, T as themesForMode, V as setActiveWorkspace, _ as monoTheme, a as WORDMARK, b as themeForMode, d as profilingEnabled, f as record, g as findTheme, h as applyTheme, i as MARK_CELLS, j as slugifyAliasName, k as dedupeAliasName, m as DEFAULT_THEME_ID, n as buildCrashReport, p as writeArtifact, r as writeCrashReport, s as splashFitsWidth, t as installCrashHandler, v as noColorRequested, x as themeMode, y as theme, z as listWorkspaces } from "./install-DxZwvQnt.mjs";
5
5
  import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
6
6
  import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-D3l5kJlX.mjs";
7
- import { t as PortalClient } from "./client-C1r0rRmd.mjs";
7
+ import { n as PortalClient } from "./client-hxuyfRBc.mjs";
8
8
  import { t as defaultTlsCertSource } from "./tls-cert-Rua2oV7n.mjs";
9
9
  import "./api-BFQ4PQDA.mjs";
10
- import "./daemon-CSAbOyRz.mjs";
11
- import { t as SandboxStream } from "./client-l6orYfqq.mjs";
12
- import { t as PortalDaemonClient } from "./daemon-client-D4WAalkV.mjs";
13
- import { t as runRawPtyPassthrough } from "./raw-pty-dndn1Kxc.mjs";
10
+ import "./daemon-BHEJmgBq.mjs";
11
+ import { t as SandboxStream } from "./client-Dr81zuTm.mjs";
12
+ import { t as PortalDaemonClient } from "./daemon-client-B0-KqkuJ.mjs";
13
+ import { t as runRawPtyPassthrough } from "./raw-pty-C323I8xG.mjs";
14
14
  import * as os$1 from "node:os";
15
15
  import { homedir, platform, release, tmpdir } from "node:os";
16
16
  import path, { basename, extname, isAbsolute, join, posix, win32 } from "node:path";
@@ -3873,6 +3873,7 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
3873
3873
  ]);
3874
3874
  const [removedIds, setRemovedIds] = useState(/* @__PURE__ */ new Set());
3875
3875
  const [archiveOverrides, setArchiveOverrides] = useState(/* @__PURE__ */ new Map());
3876
+ const [pinOverrides, setPinOverrides] = useState(/* @__PURE__ */ new Map());
3876
3877
  const [confirmId, setConfirmId] = useState(null);
3877
3878
  const [error, setError] = useState(null);
3878
3879
  const [sending, setSending] = useState(false);
@@ -3887,7 +3888,16 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
3887
3888
  onError: setError
3888
3889
  });
3889
3890
  const isArchived = (c) => archiveOverrides.get(c.id) ?? c.viewerArchivedAt != null;
3890
- const list = ((isSearching && search?.query === debouncedQuery && search.state.kind === "ready" ? search.state.conversations : null) ?? conversations).filter((c) => !removedIds.has(c.id) && (showArchived || !isArchived(c)));
3891
+ const pinnedAt = (c) => pinOverrides.has(c.id) ? pinOverrides.get(c.id) ?? null : c.viewerPinnedAt ?? null;
3892
+ const isPinned = (c) => pinnedAt(c) !== null;
3893
+ const list = ((isSearching && search?.query === debouncedQuery && search.state.kind === "ready" ? search.state.conversations : null) ?? conversations).filter((c) => !removedIds.has(c.id) && (showArchived || !isArchived(c))).sort((a, b) => {
3894
+ const aPin = pinnedAt(a);
3895
+ const bPin = pinnedAt(b);
3896
+ if (aPin && bPin) return bPin.localeCompare(aPin);
3897
+ if (aPin) return -1;
3898
+ if (bPin) return 1;
3899
+ return 0;
3900
+ });
3891
3901
  useEffect(() => {
3892
3902
  setHighlight(0);
3893
3903
  }, [showAutomated, showArchived]);
@@ -3904,6 +3914,7 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
3904
3914
  const clamped = Math.min(highlight, Math.max(0, rows.length - 1));
3905
3915
  const highlightRow = rows[clamped];
3906
3916
  const highlightArchived = highlightRow?.kind === "conv" && isArchived(highlightRow.hit.item);
3917
+ const highlightPinned = highlightRow?.kind === "conv" && isPinned(highlightRow.hit.item);
3907
3918
  const highlightAgentRun = highlightRow?.kind === "conv" && isAgentRunConversation(highlightRow.hit.item);
3908
3919
  const highlightOutsider = highlightRow?.kind === "conv" && !viewerIsParticipant(highlightRow.hit.item);
3909
3920
  const confirmConv = confirmId !== null ? list.find((c) => c.id === confirmId) : void 0;
@@ -3932,7 +3943,8 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
3932
3943
  `tab ${showAutomated ? "hide" : "show"} agent runs`,
3933
3944
  `shift+tab ${showArchived ? "hide" : "show"} archived`,
3934
3945
  "↑/↓ move",
3935
- "esc back"
3946
+ "esc back",
3947
+ ...highlightAgentRun || highlightOutsider ? [] : [`ctrl+p ${highlightPinned ? "unpin" : "pin"}`]
3936
3948
  ].filter((segment) => segment !== ""), innerWidth)
3937
3949
  };
3938
3950
  const identityRows = height >= 24 ? 3 : 0;
@@ -4022,6 +4034,25 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
4022
4034
  * reachable with archived rows shown) it restores instead — which is the
4023
4035
  * only in-TUI way back.
4024
4036
  */
4037
+ const togglePinned = (conversation) => {
4038
+ if (!rest) return;
4039
+ const id = conversation.id;
4040
+ const pinned = !isPinned(conversation);
4041
+ const revert = pinOverrides.has(id) ? pinOverrides.get(id) : conversation.viewerPinnedAt;
4042
+ setPinOverrides((prev) => new Map(prev).set(id, pinned ? (/* @__PURE__ */ new Date()).toISOString() : null));
4043
+ rest.setConversationPinned({
4044
+ conversationId: id,
4045
+ pinned
4046
+ }).catch((err) => {
4047
+ setPinOverrides((prev) => {
4048
+ const next = new Map(prev);
4049
+ if (revert === void 0) next.delete(id);
4050
+ else next.set(id, revert ?? null);
4051
+ return next;
4052
+ });
4053
+ setError(`${pinned ? "pin" : "unpin"} failed: ${err instanceof Error ? err.message : String(err)}`);
4054
+ });
4055
+ };
4025
4056
  const toggleArchived = (conversation) => {
4026
4057
  if (!rest) return;
4027
4058
  const id = conversation.id;
@@ -4075,6 +4106,21 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
4075
4106
  setConfirmId(row.hit.item.id);
4076
4107
  }
4077
4108
  key.preventDefault();
4109
+ } else if (key.name === "p" && key.ctrl) {
4110
+ const row = rows[clamped];
4111
+ if (row?.kind === "conv") {
4112
+ if (isAgentRunConversation(row.hit.item)) {
4113
+ setError("agent runs can't be pinned");
4114
+ return;
4115
+ }
4116
+ if (!viewerIsParticipant(row.hit.item)) {
4117
+ setError("you're not in this thread, so there's nothing to pin");
4118
+ return;
4119
+ }
4120
+ setError(null);
4121
+ togglePinned(row.hit.item);
4122
+ }
4123
+ key.preventDefault();
4078
4124
  } else if (key.name === "a" && key.ctrl) {
4079
4125
  const row = rows[clamped];
4080
4126
  if (row?.kind === "conv") {
@@ -4200,7 +4246,7 @@ function ConversationHome({ agent, initialQuery, conversations, showAutomated, o
4200
4246
  const fg = selected ? theme.accent : theme.fg;
4201
4247
  const marker = selected ? "› " : " ";
4202
4248
  const archived = isArchived(row.hit.item);
4203
- const { title, time } = layoutRow(`${archived ? "archived · " : ""}${singleLine(row.hit.item.title ?? "(untitled)")}`, relativeTime(row.hit.item.updatedAt), coAgentsTag(row.hit.item, agent.id), rowWidth);
4249
+ const { title, time } = layoutRow(`${isPinned(row.hit.item) ? "pinned · " : ""}${archived ? "archived · " : ""}${singleLine(row.hit.item.title ?? "(untitled)")}`, relativeTime(row.hit.item.updatedAt), coAgentsTag(row.hit.item, agent.id), rowWidth);
4204
4250
  const glyph = channelGlyph(row.hit.item.channel);
4205
4251
  const tag = coAgentsTag(row.hit.item, agent.id);
4206
4252
  const unread = row.hit.item.unread === true;
@@ -4722,7 +4768,7 @@ function ErrorItem({ item }) {
4722
4768
 
4723
4769
  //#endregion
4724
4770
  //#region src/chat/tui/chunks/tool-call-summary.ts
4725
- const TOOL_CALL_SUMMARY_KEY = "__skydive_summary__";
4771
+ const TOOL_CALL_SUMMARY_KEYS = ["skydive_summary", "__skydive_summary__"];
4726
4772
  /**
4727
4773
  * The model's one-line summary for a tool call, read from its arguments.
4728
4774
  * Returns null when absent or blank; whitespace is collapsed so a stray
@@ -4730,8 +4776,8 @@ const TOOL_CALL_SUMMARY_KEY = "__skydive_summary__";
4730
4776
  */
4731
4777
  function getToolCallSummary(input) {
4732
4778
  if (!input || typeof input !== "object") return null;
4733
- for (const [key, value] of Object.entries(input)) {
4734
- if (key !== TOOL_CALL_SUMMARY_KEY) continue;
4779
+ for (const key of TOOL_CALL_SUMMARY_KEYS) for (const [entryKey, value] of Object.entries(input)) {
4780
+ if (entryKey !== key) continue;
4735
4781
  if (typeof value !== "string") return null;
4736
4782
  return value.replace(/\s+/g, " ").trim() || null;
4737
4783
  }
@@ -4744,8 +4790,8 @@ function getToolCallSummary(input) {
4744
4790
  */
4745
4791
  function withoutToolCallSummary(input) {
4746
4792
  if (!input || typeof input !== "object" || Array.isArray(input)) return input;
4747
- if (!(TOOL_CALL_SUMMARY_KEY in input)) return input;
4748
- return Object.fromEntries(Object.entries(input).filter(([key]) => key !== TOOL_CALL_SUMMARY_KEY));
4793
+ if (!TOOL_CALL_SUMMARY_KEYS.some((key) => key in input)) return input;
4794
+ return Object.fromEntries(Object.entries(input).filter(([key]) => !TOOL_CALL_SUMMARY_KEYS.includes(key)));
4749
4795
  }
4750
4796
 
4751
4797
  //#endregion
@@ -8355,6 +8401,10 @@ const keybindGroups = [
8355
8401
  keys: "ctrl+x",
8356
8402
  action: "drop the last staged image"
8357
8403
  },
8404
+ {
8405
+ keys: "ctrl+p",
8406
+ action: "pin / unpin this conversation for you (again to unpin)"
8407
+ },
8358
8408
  {
8359
8409
  keys: "ctrl+a",
8360
8410
  action: "archive conversation (hides it for you only; again to restore)"
@@ -9149,7 +9199,8 @@ function printResumeHint(write = (text) => process.stdout.write(text)) {
9149
9199
  * AskUserQuestion overlay: one question at a time, a highlighted option
9150
9200
  * list (Other is the last row), enter selects, letters jump, esc cancels.
9151
9201
  * Multi-question forms advance after each pick and submit once every
9152
- * question is answered. The composed answer is a normal user message.
9202
+ * question is answered. Submission carries both the normal user-message text
9203
+ * and a typed answer list so the transcript can settle this exact card.
9153
9204
  */
9154
9205
  function AskPicker({ questions, onSubmit, onCancel }) {
9155
9206
  const [active, setActive] = useState(0);
@@ -9173,7 +9224,7 @@ function AskPicker({ questions, onSubmit, onCancel }) {
9173
9224
  function finish(nextSelected, nextOther) {
9174
9225
  const answers = answersFor(nextSelected, nextOther);
9175
9226
  if (answers.some((a) => a.length === 0)) return;
9176
- onSubmit(composeQuestionAnswer(questions, answers));
9227
+ onSubmit(composeQuestionAnswer(questions, answers), composeQuestionAnswerEntries(questions, answers));
9177
9228
  }
9178
9229
  function advanceOrSubmit(nextSelected) {
9179
9230
  const nextUnanswered = questions.findIndex((_, i) => i > active && !(nextSelected[i] ?? []).length);
@@ -9629,7 +9680,7 @@ function ModelPicker({ agentId, conversationId, currentModel, currentEffort, mod
9629
9680
  let cancelled = false;
9630
9681
  (async () => {
9631
9682
  try {
9632
- const [models, savedEfforts, floor] = await Promise.all([
9683
+ const [catalog, savedEfforts, floor] = await Promise.all([
9633
9684
  rest.listModels(),
9634
9685
  rest.listModelEfforts().catch(() => ({})),
9635
9686
  loadFreeFloor(rest)
@@ -9637,9 +9688,10 @@ function ModelPicker({ agentId, conversationId, currentModel, currentEffort, mod
9637
9688
  if (cancelled) return;
9638
9689
  setState({
9639
9690
  kind: "ready",
9640
- models,
9691
+ models: catalog.models,
9641
9692
  savedEfforts,
9642
- floor
9693
+ floor,
9694
+ trainingOptedOut: catalog.trainingOptedOut
9643
9695
  });
9644
9696
  } catch (err) {
9645
9697
  if (cancelled) return;
@@ -9671,6 +9723,7 @@ function ModelPicker({ agentId, conversationId, currentModel, currentEffort, mod
9671
9723
  models: state.models,
9672
9724
  initialSavedEfforts: state.savedEfforts,
9673
9725
  floor: state.floor,
9726
+ trainingOptedOut: state.trainingOptedOut,
9674
9727
  currentModel,
9675
9728
  currentEffort,
9676
9729
  modelLocked,
@@ -9708,7 +9761,7 @@ function ModelPickerFrame({ hint, children }) {
9708
9761
  ]
9709
9762
  });
9710
9763
  }
9711
- function ModelSwitcher({ models, initialSavedEfforts, floor, currentModel, currentEffort, modelLocked, initialView, agentId, conversationId, onModelSelected, onEffortSelected, onCancel }) {
9764
+ function ModelSwitcher({ models, initialSavedEfforts, floor, trainingOptedOut, currentModel, currentEffort, modelLocked, initialView, agentId, conversationId, onModelSelected, onEffortSelected, onCancel }) {
9712
9765
  const rest = useStore((s) => s.rest);
9713
9766
  const [savedEfforts, setSavedEfforts] = useState(initialSavedEfforts);
9714
9767
  const [view, setView] = useState(() => {
@@ -9736,6 +9789,10 @@ function ModelSwitcher({ models, initialSavedEfforts, floor, currentModel, curre
9736
9789
  }
9737
9790
  async function selectModel(model) {
9738
9791
  if (!rest || saving) return;
9792
+ if (trainingOptedOut && !model.noTraining) {
9793
+ setSaveError("this workspace limits model selection to verified no-training routes");
9794
+ return;
9795
+ }
9739
9796
  const effort = effortFor(model);
9740
9797
  if (model.id === currentModel && effort === currentEffort) {
9741
9798
  onCancel();
@@ -9841,6 +9898,7 @@ function ModelSwitcher({ models, initialSavedEfforts, floor, currentModel, curre
9841
9898
  floor,
9842
9899
  currentModel,
9843
9900
  modelLocked,
9901
+ trainingOptedOut,
9844
9902
  saving,
9845
9903
  saveError,
9846
9904
  effortFor,
@@ -9855,7 +9913,7 @@ function ModelSwitcher({ models, initialSavedEfforts, floor, currentModel, curre
9855
9913
  onCancel
9856
9914
  });
9857
9915
  }
9858
- function ModelList({ models, floor, currentModel, modelLocked, saving, saveError, effortFor, onSelect, onEditEffort, onCancel }) {
9916
+ function ModelList({ models, floor, currentModel, modelLocked, trainingOptedOut, saving, saveError, effortFor, onSelect, onEditEffort, onCancel }) {
9859
9917
  const { width, height } = useTerminalDimensions();
9860
9918
  const [query, setQuery] = useState("");
9861
9919
  const { lock } = floor;
@@ -9887,8 +9945,8 @@ function ModelList({ models, floor, currentModel, modelLocked, saving, saveError
9887
9945
  onCancel();
9888
9946
  return;
9889
9947
  }
9890
- if (key.name === "up") setHighlight(Math.max(0, clamped - 1));
9891
- else if (key.name === "down") setHighlight(Math.min(Math.max(0, filtered.length - 1), clamped + 1));
9948
+ if (key.name === "up") setHighlight((prev) => Math.max(0, Math.min(prev, filtered.length - 1) - 1));
9949
+ else if (key.name === "down") setHighlight((prev) => Math.min(Math.max(0, filtered.length - 1), Math.max(0, prev) + 1));
9892
9950
  else if (key.name === "tab") {
9893
9951
  const model = filtered[clamped];
9894
9952
  if (model && !isLocked(model) && effortFor(model)) onEditEffort(model);
@@ -9911,6 +9969,10 @@ function ModelList({ models, floor, currentModel, modelLocked, saving, saveError
9911
9969
  fg: theme.accent,
9912
9970
  children: "switch model"
9913
9971
  }),
9972
+ trainingOptedOut ? /* @__PURE__ */ jsx("text", {
9973
+ fg: theme.warning,
9974
+ children: "this workspace limits model selection to verified no-training routes."
9975
+ }) : null,
9914
9976
  modelLocked ? /* @__PURE__ */ jsx("text", {
9915
9977
  fg: theme.warning,
9916
9978
  children: "model choice is limited while this agent uses a Google integration. models without a zero-data-retention route will be rejected."
@@ -12986,6 +13048,7 @@ function useCardActions({ rest, appUrl, portalClient, portalStatus, setPortalPro
12986
13048
  if (action.kind === "answer_question") {
12987
13049
  setAskPrompt({
12988
13050
  cardId: item.id,
13051
+ durableCardId: action.cardId,
12989
13052
  questions: item.card.questions
12990
13053
  });
12991
13054
  return;
@@ -14378,11 +14441,11 @@ function useChatStream({ rest, conversationId, initialConversationId, attachRunI
14378
14441
  return {
14379
14442
  markRead,
14380
14443
  attachToRun,
14381
- sendContent: useCallback(async (content, staged, opts) => {
14444
+ sendContent: useCallback(async (content, staged, params) => {
14382
14445
  if (!rest) return;
14383
14446
  const trimmed = content.trim();
14384
14447
  if (!trimmed && staged.length === 0) return;
14385
- const echo = opts?.echo !== false;
14448
+ const echo = params?.echo !== false;
14386
14449
  const optimisticId = crypto.randomUUID();
14387
14450
  const stagedNames = staged.map((a) => a.fileName);
14388
14451
  if (echo) setItems((prev) => [...prev, {
@@ -14414,6 +14477,7 @@ function useChatStream({ rest, conversationId, initialConversationId, attachRunI
14414
14477
  conversationId,
14415
14478
  content: trimmed,
14416
14479
  attachmentIds,
14480
+ questionAnswer: params?.questionAnswer ?? null,
14417
14481
  clientSurface: "tui"
14418
14482
  });
14419
14483
  releaseUploaded(rows);
@@ -15193,7 +15257,7 @@ function useExecuteLine({ agent, rest, conversationId, appUrl, goTo, history, se
15193
15257
  composerRef.current?.clear();
15194
15258
  setComposerRows(1);
15195
15259
  clearAttachments();
15196
- sendContent(content, staged);
15260
+ sendContent(content, staged, null);
15197
15261
  }, [
15198
15262
  history,
15199
15263
  runLocalShell,
@@ -15831,7 +15895,10 @@ function useLocalCommands({ setItems, shellSessionRef, bindPortalCwd, sendConten
15831
15895
  exit: 1
15832
15896
  } : m));
15833
15897
  }
15834
- sendContent(formatShellContext(command, captured, exitCode), [], { echo: false });
15898
+ sendContent(formatShellContext(command, captured, exitCode), [], {
15899
+ echo: false,
15900
+ questionAnswer: null
15901
+ });
15835
15902
  }, [
15836
15903
  setItems,
15837
15904
  shellSessionRef,
@@ -16471,7 +16538,7 @@ function ChatScreen({ agent, conversation, attachRunId, parentConversationId })
16471
16538
  variant: "warning"
16472
16539
  });
16473
16540
  }
16474
- await sendContent(seedPrompt ?? "", adoptUploaded(seedAttachments));
16541
+ await sendContent(seedPrompt ?? "", adoptUploaded(seedAttachments), null);
16475
16542
  })();
16476
16543
  }, [
16477
16544
  conversation,
@@ -16832,14 +16899,20 @@ function ChatScreen({ agent, conversation, attachRunId, parentConversationId })
16832
16899
  });
16833
16900
  if (askPrompt) return /* @__PURE__ */ jsx(AskPicker, {
16834
16901
  questions: askPrompt.questions,
16835
- onSubmit: (text) => {
16902
+ onSubmit: (text, answers) => {
16836
16903
  const cardId = askPrompt.cardId;
16837
16904
  setAskPrompt(null);
16838
16905
  updateCard(cardId, {
16839
16906
  status: "done",
16840
16907
  detail: text
16841
16908
  });
16842
- sendContent(text, []);
16909
+ sendContent(text, [], {
16910
+ echo: true,
16911
+ questionAnswer: {
16912
+ cardId: askPrompt.durableCardId,
16913
+ answers
16914
+ }
16915
+ });
16843
16916
  },
16844
16917
  onCancel: () => setAskPrompt(null)
16845
16918
  });
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { n as PortalClient, r as RATE_LIMIT_STAND_DOWN_MS, t as AUTH_STAND_DOWN_MS } from "./client-hxuyfRBc.mjs";
3
+ import "./api-BFQ4PQDA.mjs";
4
+
5
+ export { PortalClient };
@@ -113,6 +113,8 @@ const FRAME = {
113
113
  RESIZE: 17
114
114
  };
115
115
  const MAX_INPUT_BYTES = 1 * 1024 * 1024;
116
+ /** Upper bound for an exec's `timeoutMs`; larger asks are clamped to this. */
117
+ const MAX_EXEC_TIMEOUT_MS = 1440 * 60 * 1e3;
116
118
  /** Query params for the upgrade URL, from a spec. Inverse of {@link parseStreamSpec}. */
117
119
  function streamSpecToQuery(spec) {
118
120
  if (spec.mode === "pty") return {
@@ -124,7 +126,8 @@ function streamSpecToQuery(spec) {
124
126
  if (spec.mode === "exec") return {
125
127
  agentId: spec.agentId,
126
128
  mode: "exec",
127
- command: spec.command
129
+ command: spec.command,
130
+ ...spec.timeoutMs !== void 0 ? { timeoutMs: String(spec.timeoutMs) } : {}
128
131
  };
129
132
  return {
130
133
  agentId: spec.agentId,
@@ -273,7 +276,8 @@ var SandboxStream = class SandboxStream {
273
276
  } : {
274
277
  mode: "exec",
275
278
  agentId: opts.agentId,
276
- command: opts.command
279
+ command: opts.command,
280
+ ...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
277
281
  };
278
282
  const url = new URL(`${wsBase(opts.appUrl)}${SANDBOX_STREAM_PATH}`);
279
283
  for (const [key, value] of Object.entries(streamSpecToQuery(spec))) url.searchParams.set(key, value);
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
2
3
  import { a as mintPortalDeviceToken, c as unifyLegacyCliGrants, i as grantPortalAccess, l as startTlsForward, n as fetchPortalDevices, r as findThisDevice } from "./api-BFQ4PQDA.mjs";
3
4
  import os from "node:os";
4
5
  import { z } from "zod";
@@ -160,7 +161,7 @@ function errorMessage(err) {
160
161
  * the commit time of the built tree is one monotonic clock they share.
161
162
  */
162
163
  function portalDaemonBuild() {
163
- return "1790383310";
164
+ return "1790619216";
164
165
  }
165
166
  /**
166
167
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -579,18 +580,41 @@ var JobManager = class {
579
580
 
580
581
  //#endregion
581
582
  //#region ../portal-daemon/src/client.ts
583
+ const INITIAL_BACKOFF_MS = 500;
584
+ const MAX_BACKOFF_MS = 1e4;
585
+ const AUTH_STAND_DOWN_MS = 5 * 6e4;
586
+ const AUTH_STAND_DOWN_MESSAGE = "portal sign-in expired — reopen Skydive Desktop (or run `skydive portal open`) to reconnect";
587
+ /**
588
+ * True when the error means the server could not authenticate us: a token-mint
589
+ * failure (HttpError 401) or a WS upgrade rejected at the auth layer. `ws`
590
+ * reports a rejected upgrade as `Unexpected server response: <status>` with no
591
+ * structured status property, so the status is read from the message. A 429 is
592
+ * deliberately NOT an auth failure — see isRateLimited.
593
+ */
594
+ function isAuthFailure(err) {
595
+ if (err instanceof HttpError) return err.status === 401;
596
+ return /Unexpected server response: 401\b/.test(errorMessage(err));
597
+ }
598
+ const RATE_LIMIT_STAND_DOWN_MS = 12e4;
599
+ const RATE_LIMIT_STAND_DOWN_MESSAGE = "portal connection rate limited — retrying in 2 minutes";
600
+ function isRateLimited(err) {
601
+ if (err instanceof HttpError) return err.status === 429;
602
+ return /Unexpected server response: 429\b/.test(errorMessage(err));
603
+ }
582
604
  const DEFAULT_HEARTBEAT = {
583
605
  intervalMs: 15e3,
584
606
  maxMissed: 2,
585
607
  wakeTickMs: 2e3,
586
608
  wakeGapThresholdMs: 1e4,
587
609
  netWatchTickMs: 2e3,
588
- initialBackoffMs: 500,
589
- maxBackoffMs: 1e4,
610
+ initialBackoffMs: INITIAL_BACKOFF_MS,
611
+ maxBackoffMs: MAX_BACKOFF_MS,
590
612
  supersededBackoffFloorMs: 5e3,
591
613
  flapWindowMs: 6e4,
592
614
  flapMaxStandDowns: 3,
593
- flapBackoffMs: 6e4
615
+ flapBackoffMs: 6e4,
616
+ authStandDownMs: AUTH_STAND_DOWN_MS,
617
+ rateLimitStandDownMs: RATE_LIMIT_STAND_DOWN_MS
594
618
  };
595
619
  /**
596
620
  * A cheap fingerprint of the machine's network identity: every non-internal
@@ -670,6 +694,8 @@ var PortalClient = class {
670
694
  machineName = "";
671
695
  friendlyName = "";
672
696
  legacyGrantsChecked = false;
697
+ authRejected = false;
698
+ rateLimited = false;
673
699
  wakeWatch = null;
674
700
  lastTick = Date.now();
675
701
  netWatch = null;
@@ -785,6 +811,8 @@ var PortalClient = class {
785
811
  if (this.enabled || this.disposed) return;
786
812
  this.enabled = true;
787
813
  this.error = null;
814
+ this.authRejected = false;
815
+ this.rateLimited = false;
788
816
  this.connectLoop();
789
817
  }
790
818
  disable() {
@@ -939,11 +967,31 @@ var PortalClient = class {
939
967
  } else backoff = Math.max(Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs), this.heartbeatConfig.supersededBackoffFloorMs);
940
968
  sleepFloor = backoff / 2;
941
969
  } else if (healthy) backoff = this.heartbeatConfig.initialBackoffMs;
942
- else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
970
+ else if (this.authRejected) {
971
+ this.setStatus("error", AUTH_STAND_DOWN_MESSAGE);
972
+ this.authRejected = false;
973
+ backoff = this.heartbeatConfig.authStandDownMs;
974
+ sleepFloor = backoff;
975
+ } else if (this.rateLimited) {
976
+ this.setStatus("error", RATE_LIMIT_STAND_DOWN_MESSAGE);
977
+ this.rateLimited = false;
978
+ backoff = this.heartbeatConfig.rateLimitStandDownMs;
979
+ sleepFloor = backoff;
980
+ } else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
943
981
  } catch (err) {
944
982
  if (!this.enabled || this.disposed || this.handingOff) break;
945
- this.setStatus("error", errorMessage(err));
946
- backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
983
+ if (isAuthFailure(err)) {
984
+ this.setStatus("error", AUTH_STAND_DOWN_MESSAGE);
985
+ backoff = this.heartbeatConfig.authStandDownMs;
986
+ sleepFloor = backoff;
987
+ } else if (isRateLimited(err)) {
988
+ this.setStatus("error", RATE_LIMIT_STAND_DOWN_MESSAGE);
989
+ backoff = this.heartbeatConfig.rateLimitStandDownMs;
990
+ sleepFloor = backoff;
991
+ } else {
992
+ this.setStatus("error", errorMessage(err));
993
+ backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
994
+ }
947
995
  }
948
996
  if (!this.enabled || this.disposed || this.handingOff) break;
949
997
  this.inStandDownSleep = sleepFloor > 0;
@@ -1000,6 +1048,8 @@ var PortalClient = class {
1000
1048
  };
1001
1049
  ws.on("open", () => {
1002
1050
  missed = 0;
1051
+ this.authRejected = false;
1052
+ this.rateLimited = false;
1003
1053
  this.dialReason = null;
1004
1054
  this.disconnectedAt = null;
1005
1055
  this.setStatus("connected");
@@ -1013,6 +1063,14 @@ var PortalClient = class {
1013
1063
  });
1014
1064
  ws.on("error", (err) => {
1015
1065
  this.error = errorMessage(err);
1066
+ if (isAuthFailure(err)) {
1067
+ this.authRejected = true;
1068
+ this.dialReason ??= "portal credentials rejected; standing down";
1069
+ }
1070
+ if (isRateLimited(err)) {
1071
+ this.rateLimited = true;
1072
+ this.dialReason ??= "portal connection rate limited; standing down";
1073
+ }
1016
1074
  });
1017
1075
  ws.on("close", (code, reason) => {
1018
1076
  standDown = code === CLOSE_SUPERSEDED || code === CLOSE_FLAP_COOLDOWN || code === 1e3 && reason.toString() === "superseded";
@@ -1076,4 +1134,4 @@ function sleep(ms) {
1076
1134
  }
1077
1135
 
1078
1136
  //#endregion
1079
- export { resolveMachineIdentity as a, isRecord as i, isNewerBuild as n, portalDaemonBuild as r, PortalClient as t };
1137
+ export { portalDaemonBuild as a, isNewerBuild as i, PortalClient as n, isRecord as o, RATE_LIMIT_STAND_DOWN_MS as r, resolveMachineIdentity as s, AUTH_STAND_DOWN_MS as t };
@@ -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-C1r0rRmd.mjs";
2
+ import { a as portalDaemonBuild, i as isNewerBuild, n as PortalClient, o as isRecord } from "./client-hxuyfRBc.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";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "./client-C1r0rRmd.mjs";
2
+ import "./client-hxuyfRBc.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-CSAbOyRz.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-BHEJmgBq.mjs";
6
6
 
7
7
  export { runPortalDaemon };
@@ -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-CSAbOyRz.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-BHEJmgBq.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { connect } from "node:net";
5
5
 
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import "./client-hxuyfRBc.mjs";
3
+ import "./tls-cert-Rua2oV7n.mjs";
4
+ import "./api-BFQ4PQDA.mjs";
5
+ import "./daemon-BHEJmgBq.mjs";
6
+ import { t as PortalDaemonClient } from "./daemon-client-B0-KqkuJ.mjs";
7
+
8
+ export { PortalDaemonClient };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as version$1 } from "./rest-HMJ6HYnl.mjs";
3
- import { E as getConfigPath } from "./print-BPQO_UaI.mjs";
2
+ import { c as version$1 } from "./rest-CXCsoY4Q.mjs";
3
+ import { D as getConfigPath } from "./print-DTaoROjq.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-HMJ6HYnl.mjs";
3
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-BPQO_UaI.mjs";
2
+ import "./rest-CXCsoY4Q.mjs";
3
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-DTaoROjq.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-HMJ6HYnl.mjs";
2
+ import { a as errorMessage, i as sendErrorMessage, o as isRecord, t as createRestClient } from "./rest-CXCsoY4Q.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";
@@ -512,18 +512,18 @@ function readQuestionEntries(raw) {
512
512
  }];
513
513
  });
514
514
  }
515
- /**
516
- * The `platform ask` form. The web renders it as an interactive picker; here
517
- * it maps onto an answer_question button so ctrl+r (and letter shortcuts)
518
- * open an in-TUI picker. The composed answer is sent as a normal user
519
- * message — same non-blocking model as the web card. Without this the spec
520
- * is not a Card, parses to null, and the question is silently dropped.
521
- */
522
515
  /** Compose the user-message body the web card also sends. */
523
516
  function composeQuestionAnswer(questions, answers) {
524
517
  if (questions.length === 1) return (answers[0] ?? []).join(", ");
525
518
  return questions.map((q, i) => `${q.question} → ${(answers[i] ?? []).join(", ")}`).join("\n");
526
519
  }
520
+ /** Build the typed durable payload that settles this exact question card. */
521
+ function composeQuestionAnswerEntries(questions, answers) {
522
+ return questions.map((question, index) => ({
523
+ question: question.question,
524
+ answer: (answers[index] ?? []).join(", ")
525
+ }));
526
+ }
527
527
  function parseQuestionCard(rootEl) {
528
528
  const props = isRecord(rootEl.props) ? rootEl.props : {};
529
529
  const cardId = optionalString(props.cardId);
@@ -708,8 +708,8 @@ function isBlockingCardAction(action) {
708
708
  * should target, prioritizing blocking cards over `platform ask` forms. When
709
709
  * both a blocking card and a stale `answer_question` are waiting, ctrl+r must
710
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
711
+ * can remain actionable when ignored or answered as plain composer text.
712
+ * Falls through to the
713
713
  * ask forms only when they are the only actionable cards left. Preserves input
714
714
  * order within a tier, so a single card and genuine multi-card cases behave
715
715
  * exactly as before.
@@ -900,6 +900,7 @@ async function runPrint({ appUrl, sessionToken, workspaceId, prompt, agentSelect
900
900
  conversationId,
901
901
  content: prompt,
902
902
  attachmentIds: [],
903
+ questionAnswer: null,
903
904
  clientSurface: "cli"
904
905
  });
905
906
  } catch (err) {
@@ -1077,4 +1078,4 @@ async function readStdin() {
1077
1078
  }
1078
1079
 
1079
1080
  //#endregion
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 };
1081
+ export { getPreference as A, resolveChatAuth as B, DEFAULT_API_URL as C, getConfigPath as D, deleteConfig as E, getStoredApiKeyId as F, saveConfig as G, resolveManagementAuth as H, getStoredApiKeyWorkspaceName as I, setLastSeenVersion as J, saveSession as K, getUpdateCheckDisabled as L, getReviewStateDir as M, getSavedTheme as N, getDefaultAgent as O, getShareMachineDefault as P, recordDefaultAgent as R, API_KEY_PREFIX as S, PREFERENCES as T, resolveSession as U, resolveConfig as V, resolveWebUrl as W, computeSettledLabel as _, runPrint as a, API_KEYS_URL as b, actionableCardVerb as c, parseOauthConnectParams as d, prioritizeActionableCards as f, composeQuestionAnswerEntries as g, composeQuestionAnswer as h, resolveAgent as i, getPromptHistoryPath as j, getLastSeenVersion as k, cardActionErrorMessage as l, resolveConnectUrl as m, messageGet as n, toPrintError as o, reconcileMaskedInput as p, saveTheme as q, readStdin as r, MASK_CHAR as s, collectRunText as t, parseExternalOauthConnectParams as u, parseConnectCard as v, DEFAULT_APP_URL as w, API_KEY_FAMILY_PREFIX as x, specKeyFor as y, resolveAppUrl 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-DSfF8nrb.mjs");
17
+ const { PortalClient } = await import("./client-BQPhWfh2.mjs");
18
18
  const { defaultTlsCertSource } = await import("./tls-cert-BpCaD5AT.mjs");
19
19
  let signalConnected;
20
20
  const connected = new Promise((resolve) => {