skydive-cli 0.5.0-beta.41 → 0.5.0-beta.46

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,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- 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-BkMcGVYS.mjs";
3
- import { B as resolveWebUrl, C as getConfigPath, O as getReviewStateDir, P as recordDefaultAgent, U as saveTheme, b as DEFAULT_APP_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, h as specKeyFor, i as resolveAgent, k as getSavedTheme, l as parseExternalOauthConnectParams, m as parseConnectCard, p as computeSettledLabel, s as MASK_CHAR, u as parseOauthConnectParams, y as DEFAULT_API_URL } from "./print-CwbdwCeQ.mjs";
4
- import { t as HttpError } from "./http-error-UHH3mVBF.mjs";
5
- import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-Bbe-RhMy.mjs";
2
+ import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-BtHVI0y6.mjs";
3
+ import { A as getSavedTheme, F as recordDefaultAgent, V as resolveWebUrl, W as saveTheme, b as DEFAULT_API_URL, c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, g as specKeyFor, h as parseConnectCard, i as resolveAgent, k as getReviewStateDir, l as parseExternalOauthConnectParams, m as computeSettledLabel, p as composeQuestionAnswer, s as MASK_CHAR, u as parseOauthConnectParams, w as getConfigPath, x as DEFAULT_APP_URL } from "./print-Bg8rzq9t.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-DNJ6k578.mjs";
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-DMYwKALl.mjs";
8
- import "./daemon-D8WoX21I.mjs";
7
+ import { t as PortalClient } from "./client-C2lPer4b.mjs";
8
+ import "./daemon-MZN78SYL.mjs";
9
9
  import { t as defaultTlsCertSource } from "./tls-cert-CV-pwxVN.mjs";
10
- import "./api-DQCaztBg.mjs";
10
+ import "./api-TwLD7ibI.mjs";
11
11
  import { t as SandboxStream } from "./client-Btq6bMzX.mjs";
12
- import { t as PortalDaemonClient } from "./daemon-client-Bp5DMeIi.mjs";
12
+ import { t as PortalDaemonClient } from "./daemon-client-qNlw9inL.mjs";
13
13
  import { t as runRawPtyPassthrough } from "./raw-pty-D5PhKZSl.mjs";
14
14
  import * as os$1 from "node:os";
15
15
  import { homedir, platform, release, tmpdir } from "node:os";
@@ -2794,26 +2794,80 @@ function CollapseHint() {
2794
2794
  /** Collapsed line cap for tool error output. */
2795
2795
  const maxErrorLines = 8;
2796
2796
  /**
2797
+ * Character budget for a collapsed error block, sized so the block still
2798
+ * costs about `maxErrorLines` rows on a conventional 80-column terminal.
2799
+ * Error lines wrap instead of clipping, so without this a single
2800
+ * multi-kilobyte line (a raw JSON body, a minified stack) would wrap into a
2801
+ * wall of scrollback.
2802
+ */
2803
+ const maxErrorChars = maxErrorLines * 80;
2804
+ /**
2805
+ * Squeeze the vertical dead space out of a block: leading and trailing blank
2806
+ * lines dropped, runs of blank lines collapsed to one. A failing command's
2807
+ * error text arrives as stderr (newline-terminated) plus a blank-line-
2808
+ * separated `Command exited with code N`, which otherwise renders as a
2809
+ * two-row hole in the middle of the block.
2810
+ */
2811
+ function tightenBlanks(text) {
2812
+ return text.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").replace(/\s+$/, "");
2813
+ }
2814
+ /**
2815
+ * Collapsed form of an error block: whole lines, up to the line cap and the
2816
+ * character budget, left for the renderer to word-wrap. Unlike ok output,
2817
+ * errors are not clipped at a fixed column — the reason a command failed is
2818
+ * usually in the tail of the message, so an ellipsis at column 100 hides the
2819
+ * one thing the block exists to say.
2820
+ */
2821
+ function collapseError(text) {
2822
+ const source = expandLines(text);
2823
+ const lines = [];
2824
+ let room = maxErrorChars;
2825
+ let cut = false;
2826
+ for (const line of source) {
2827
+ if (lines.length === maxErrorLines || room <= 0) break;
2828
+ if (line.length > room) {
2829
+ lines.push(truncate(line, room));
2830
+ cut = true;
2831
+ break;
2832
+ }
2833
+ lines.push(line);
2834
+ room -= line.length;
2835
+ }
2836
+ const hidden = source.length - lines.length;
2837
+ return {
2838
+ lines,
2839
+ hidden,
2840
+ clipped: cut || hidden > 0
2841
+ };
2842
+ }
2843
+ /** Whether a collapsed error block hides any of its text. */
2844
+ function isErrorClipped(text) {
2845
+ return collapseError(tightenBlanks(text)).clipped;
2846
+ }
2847
+ /**
2797
2848
  * Tool error output, clamped like ok output: collapsed it clips to
2798
2849
  * `maxErrorLines` with the "+N more · click to expand" tail; expanded it
2799
- * renders in full, word-wrapped. Same click-to-toggle box as the rest of
2800
- * the block — a failing command's output shouldn't flood the scrollback
2801
- * just because it arrived on the error channel.
2850
+ * renders in full. Same click-to-toggle box as the rest of the block — a
2851
+ * failing command's output shouldn't flood the scrollback just because it
2852
+ * arrived on the error channel.
2802
2853
  */
2803
2854
  function ErrorLines({ text, expanded }) {
2855
+ const tidy = tightenBlanks(text);
2804
2856
  if (expanded) return /* @__PURE__ */ jsx(ExpandedLines, {
2805
- text,
2857
+ text: tidy,
2806
2858
  fg: theme.error
2807
2859
  });
2808
- return /* @__PURE__ */ jsx("box", {
2860
+ const { lines, hidden, clipped } = collapseError(tidy);
2861
+ return /* @__PURE__ */ jsxs("box", {
2809
2862
  style: { flexDirection: "column" },
2810
- children: clipLines(text, {
2811
- maxLines: maxErrorLines,
2812
- maxLineLen: 100
2813
- }).map((line, idx) => /* @__PURE__ */ jsx("text", {
2863
+ children: [lines.map((line, idx) => /* @__PURE__ */ jsx("text", {
2814
2864
  fg: theme.error,
2865
+ style: { wrapMode: "word" },
2815
2866
  children: line
2816
- }, idx))
2867
+ }, idx)), clipped && /* @__PURE__ */ jsx("text", {
2868
+ fg: theme.error,
2869
+ children: hidden > 0 ? `… +${hidden} more lines · click to expand` : "… click to expand"
2870
+ })]
2817
2871
  });
2818
2872
  }
2819
2873
  /** Indents tool output two columns under its header — no rule, just space. */
@@ -3790,12 +3844,38 @@ function CardItem({ item }) {
3790
3844
  children: [
3791
3845
  /* @__PURE__ */ jsxs("text", {
3792
3846
  fg: theme.fg,
3793
- children: ["⚿ ", card.title]
3847
+ children: [card.questions.length > 0 ? "? " : "⚿ ", card.title]
3794
3848
  }),
3795
3849
  card.subtitle ? /* @__PURE__ */ jsx("text", {
3796
3850
  fg: theme.muted,
3797
3851
  children: card.subtitle
3798
3852
  }) : null,
3853
+ card.questions.map((q, qi) => /* @__PURE__ */ jsxs("box", {
3854
+ style: { flexDirection: "column" },
3855
+ children: [
3856
+ card.questions.length > 1 ? /* @__PURE__ */ jsx("text", {
3857
+ fg: theme.fg,
3858
+ children: q.question
3859
+ }) : null,
3860
+ q.options.map((opt, i) => /* @__PURE__ */ jsxs("text", {
3861
+ fg: theme.muted,
3862
+ children: [
3863
+ " ",
3864
+ String.fromCharCode(65 + i),
3865
+ ". ",
3866
+ opt.label
3867
+ ]
3868
+ }, opt.label)),
3869
+ /* @__PURE__ */ jsxs("text", {
3870
+ fg: theme.dim,
3871
+ children: [
3872
+ " ",
3873
+ String.fromCharCode(65 + q.options.length),
3874
+ ". Other…"
3875
+ ]
3876
+ })
3877
+ ]
3878
+ }, qi)),
3799
3879
  card.description ? /* @__PURE__ */ jsx("markdown", {
3800
3880
  content: card.description,
3801
3881
  syntaxStyle: syntaxStyle(),
@@ -4412,7 +4492,7 @@ function ToolEdit({ item }) {
4412
4492
  item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
4413
4493
  text: item.output.errorText,
4414
4494
  expanded
4415
- }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] }),
4495
+ }), expanded && isErrorClipped(item.output.errorText) && /* @__PURE__ */ jsx(CollapseHint, {})] }),
4416
4496
  item.output?.kind === "ok" && pairs === null && inputReady && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx("text", {
4417
4497
  fg: theme.muted,
4418
4498
  children: summarize(item.output.value, 60)
@@ -4595,7 +4675,7 @@ function ToolBash({ item }) {
4595
4675
  text: item.output.errorText,
4596
4676
  expanded
4597
4677
  }) }),
4598
- expanded && (showCommandBlock && isClipped(commandBlockText, maxCommandLines) || output !== null && isClipped(output, maxLines$1) || item.output?.kind === "error" && isClipped(item.output.errorText, maxErrorLines)) && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx(CollapseHint, {}) })
4678
+ expanded && (showCommandBlock && isClipped(commandBlockText, maxCommandLines) || output !== null && isClipped(output, maxLines$1) || item.output?.kind === "error" && isErrorClipped(item.output.errorText)) && /* @__PURE__ */ jsx(Indented, { children: /* @__PURE__ */ jsx(CollapseHint, {}) })
4599
4679
  ]
4600
4680
  });
4601
4681
  }
@@ -4622,7 +4702,11 @@ const maxLines = 6;
4622
4702
  function ToolRead({ item }) {
4623
4703
  const glyph = toolGlyph(item.state);
4624
4704
  const input = isRecord(item.input) ? item.input : {};
4625
- const filePath = typeof input.filePath === "string" ? input.filePath : null;
4705
+ const filePath = pickString(input, [
4706
+ "path",
4707
+ "filePath",
4708
+ "file"
4709
+ ]);
4626
4710
  const offset = typeof input.offset === "number" ? input.offset : null;
4627
4711
  const limit = typeof input.limit === "number" ? input.limit : null;
4628
4712
  const header = filePath ? offset !== null ? `${filePath}:L${offset}${limit ? `-L${offset + limit - 1}` : ""}` : filePath : "(unknown path)";
@@ -4666,7 +4750,7 @@ function ToolRead({ item }) {
4666
4750
  item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
4667
4751
  text: item.output.errorText,
4668
4752
  expanded
4669
- }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] })
4753
+ }), expanded && isErrorClipped(item.output.errorText) && /* @__PURE__ */ jsx(CollapseHint, {})] })
4670
4754
  ]
4671
4755
  });
4672
4756
  }
@@ -4750,7 +4834,7 @@ function ToolGrep({ item }) {
4750
4834
  item.output?.kind === "error" && /* @__PURE__ */ jsxs(Indented, { children: [/* @__PURE__ */ jsx(ErrorLines, {
4751
4835
  text: item.output.errorText,
4752
4836
  expanded
4753
- }), expanded && isClipped(item.output.errorText, maxErrorLines) && /* @__PURE__ */ jsx(CollapseHint, {})] })
4837
+ }), expanded && isErrorClipped(item.output.errorText) && /* @__PURE__ */ jsx(CollapseHint, {})] })
4754
4838
  ]
4755
4839
  });
4756
4840
  }
@@ -7681,7 +7765,7 @@ const keybindGroups = [
7681
7765
  },
7682
7766
  {
7683
7767
  keys: "ctrl+r",
7684
- action: "act on a waiting connect card (auth cards)"
7768
+ action: "act on a waiting connect / ask card"
7685
7769
  },
7686
7770
  {
7687
7771
  keys: "pgup / pgdn",
@@ -8433,6 +8517,248 @@ function printResumeHint(write = (text) => process.stdout.write(text)) {
8433
8517
  lastConversationId = null;
8434
8518
  }
8435
8519
 
8520
+ //#endregion
8521
+ //#region src/chat/tui/screens/ask-picker.tsx
8522
+ /**
8523
+ * In-TUI picker for a `platform ask` form. Modeled on Claude Code's
8524
+ * AskUserQuestion overlay: one question at a time, a highlighted option
8525
+ * list (Other is the last row), enter selects, letters jump, esc cancels.
8526
+ * Multi-question forms advance after each pick and submit once every
8527
+ * question is answered. The composed answer is a normal user message.
8528
+ */
8529
+ function AskPicker({ questions, onSubmit, onCancel }) {
8530
+ const [active, setActive] = useState(0);
8531
+ const [highlight, setHighlight] = useState(0);
8532
+ const [selected, setSelected] = useState({});
8533
+ const [otherOpen, setOtherOpen] = useState(false);
8534
+ const [otherText, setOtherText] = useState("");
8535
+ const q = questions[active] ?? questions[0];
8536
+ const otherIndex = q ? q.options.length : 0;
8537
+ const doneIndex = q?.multiSelect ? otherIndex + 1 : -1;
8538
+ const rowCount = q?.multiSelect ? otherIndex + 2 : otherIndex + 1;
8539
+ const clamped = Math.min(highlight, Math.max(0, rowCount - 1));
8540
+ const picked = selected[active] ?? [];
8541
+ function answersFor(nextSelected, nextOther) {
8542
+ return questions.map((_, qi) => {
8543
+ const labels = nextSelected[qi] ?? [];
8544
+ if (qi === active && nextOther.trim()) return [...labels, nextOther.trim()];
8545
+ return labels;
8546
+ });
8547
+ }
8548
+ function finish(nextSelected, nextOther) {
8549
+ const answers = answersFor(nextSelected, nextOther);
8550
+ if (answers.some((a) => a.length === 0)) return;
8551
+ onSubmit(composeQuestionAnswer(questions, answers));
8552
+ }
8553
+ function advanceOrSubmit(nextSelected) {
8554
+ const nextUnanswered = questions.findIndex((_, i) => i > active && !(nextSelected[i] ?? []).length);
8555
+ if (nextUnanswered === -1) {
8556
+ finish(nextSelected, "");
8557
+ return;
8558
+ }
8559
+ setSelected(nextSelected);
8560
+ setActive(nextUnanswered);
8561
+ setHighlight(0);
8562
+ setOtherOpen(false);
8563
+ setOtherText("");
8564
+ }
8565
+ function pickOption(idx) {
8566
+ if (!q) return;
8567
+ const opt = q.options[idx];
8568
+ if (!opt) return;
8569
+ if (q.multiSelect) {
8570
+ const cur = selected[active] ?? [];
8571
+ const next = cur.includes(opt.label) ? cur.filter((l) => l !== opt.label) : [...cur, opt.label];
8572
+ setSelected({
8573
+ ...selected,
8574
+ [active]: next
8575
+ });
8576
+ return;
8577
+ }
8578
+ advanceOrSubmit({
8579
+ ...selected,
8580
+ [active]: [opt.label]
8581
+ });
8582
+ }
8583
+ function goToQuestion(index) {
8584
+ if (index < 0 || index >= questions.length || index === active) return;
8585
+ setActive(index);
8586
+ setHighlight(0);
8587
+ setOtherOpen(false);
8588
+ setOtherText("");
8589
+ }
8590
+ useKeyboard((key) => {
8591
+ if (!q) return;
8592
+ if (key.name === "escape") {
8593
+ if (otherOpen) {
8594
+ setOtherOpen(false);
8595
+ setOtherText("");
8596
+ return;
8597
+ }
8598
+ onCancel();
8599
+ return;
8600
+ }
8601
+ if (otherOpen) {
8602
+ if (key.name === "return" || key.name === "kpenter") {
8603
+ key.preventDefault();
8604
+ const custom = otherText.trim();
8605
+ if (!custom) return;
8606
+ const nextSelected = {
8607
+ ...selected,
8608
+ [active]: q.multiSelect ? [...selected[active] ?? [], custom] : [custom]
8609
+ };
8610
+ if (q.multiSelect) {
8611
+ setSelected(nextSelected);
8612
+ setOtherOpen(false);
8613
+ setOtherText("");
8614
+ return;
8615
+ }
8616
+ advanceOrSubmit(nextSelected);
8617
+ return;
8618
+ }
8619
+ if (key.name === "backspace") {
8620
+ setOtherText((t) => t.slice(0, -1));
8621
+ return;
8622
+ }
8623
+ const ch = key.sequence ?? key.name;
8624
+ if (ch && ch.length === 1 && !key.ctrl && !key.meta) setOtherText((t) => t + ch);
8625
+ return;
8626
+ }
8627
+ if (key.name === "up") {
8628
+ setHighlight(Math.max(0, clamped - 1));
8629
+ return;
8630
+ }
8631
+ if (key.name === "down") {
8632
+ setHighlight(Math.min(rowCount - 1, clamped + 1));
8633
+ return;
8634
+ }
8635
+ if (key.name === "left" || (key.name === "tab" || key.sequence === "\x1B[Z") && key.shift) {
8636
+ key.preventDefault();
8637
+ goToQuestion(active - 1);
8638
+ return;
8639
+ }
8640
+ if (key.name === "right" || key.name === "tab" && !key.shift) {
8641
+ key.preventDefault();
8642
+ goToQuestion(active + 1);
8643
+ return;
8644
+ }
8645
+ if (key.name === "space" || key.sequence === " ") {
8646
+ key.preventDefault();
8647
+ if (!q.multiSelect) return;
8648
+ if (clamped === otherIndex) {
8649
+ setOtherOpen(true);
8650
+ return;
8651
+ }
8652
+ if (clamped === doneIndex) {
8653
+ if (picked.length === 0) return;
8654
+ advanceOrSubmit({
8655
+ ...selected,
8656
+ [active]: picked
8657
+ });
8658
+ return;
8659
+ }
8660
+ pickOption(clamped);
8661
+ return;
8662
+ }
8663
+ if (key.name === "return" || key.name === "kpenter") {
8664
+ key.preventDefault();
8665
+ if (clamped === otherIndex) {
8666
+ setOtherOpen(true);
8667
+ return;
8668
+ }
8669
+ if (clamped === doneIndex) {
8670
+ if (picked.length === 0) return;
8671
+ advanceOrSubmit({
8672
+ ...selected,
8673
+ [active]: picked
8674
+ });
8675
+ return;
8676
+ }
8677
+ pickOption(clamped);
8678
+ return;
8679
+ }
8680
+ const letter = (key.sequence ?? key.name ?? "").toLowerCase();
8681
+ if (letter.length === 1 && /[a-z]/.test(letter)) {
8682
+ const idx = letter.charCodeAt(0) - 97;
8683
+ if (idx === otherIndex || letter === "o") {
8684
+ setHighlight(otherIndex);
8685
+ setOtherOpen(true);
8686
+ return;
8687
+ }
8688
+ if (q.options[idx]) {
8689
+ setHighlight(idx);
8690
+ pickOption(idx);
8691
+ }
8692
+ }
8693
+ });
8694
+ if (!q) return null;
8695
+ const nav = questions.length > 1 ? `${active + 1}/${questions.length}` : null;
8696
+ return /* @__PURE__ */ jsxs("box", {
8697
+ style: {
8698
+ flexDirection: "column",
8699
+ flexGrow: 1
8700
+ },
8701
+ children: [
8702
+ nav ? /* @__PURE__ */ jsx("text", {
8703
+ fg: theme.dim,
8704
+ children: nav
8705
+ }) : null,
8706
+ /* @__PURE__ */ jsx("text", {
8707
+ fg: theme.fg,
8708
+ children: q.question
8709
+ }),
8710
+ /* @__PURE__ */ jsxs("box", {
8711
+ style: {
8712
+ flexDirection: "column",
8713
+ marginTop: 1
8714
+ },
8715
+ children: [
8716
+ q.options.map((opt, i) => {
8717
+ const isHi = !otherOpen && i === clamped;
8718
+ const isPicked = picked.includes(opt.label);
8719
+ return /* @__PURE__ */ jsxs("text", {
8720
+ fg: isHi ? theme.accent : theme.fg,
8721
+ children: [
8722
+ isHi ? "› " : " ",
8723
+ q.multiSelect ? isPicked ? "☑ " : "☐ " : "",
8724
+ String.fromCharCode(65 + i),
8725
+ ". ",
8726
+ opt.label
8727
+ ]
8728
+ }, opt.label);
8729
+ }),
8730
+ otherOpen ? /* @__PURE__ */ jsxs("text", {
8731
+ fg: theme.accent,
8732
+ children: [
8733
+ "› Other: ",
8734
+ otherText,
8735
+ /* @__PURE__ */ jsx("span", {
8736
+ fg: theme.dim,
8737
+ children: "_"
8738
+ })
8739
+ ]
8740
+ }) : /* @__PURE__ */ jsxs("text", {
8741
+ fg: clamped === otherIndex ? theme.accent : theme.muted,
8742
+ children: [
8743
+ clamped === otherIndex ? "› " : " ",
8744
+ String.fromCharCode(65 + otherIndex),
8745
+ ". Other"
8746
+ ]
8747
+ }),
8748
+ q.multiSelect && !otherOpen ? /* @__PURE__ */ jsxs("text", {
8749
+ fg: clamped === doneIndex ? theme.accent : theme.muted,
8750
+ children: [clamped === doneIndex ? "› " : " ", "Done"]
8751
+ }) : null
8752
+ ]
8753
+ }),
8754
+ /* @__PURE__ */ jsx("text", {
8755
+ fg: theme.dim,
8756
+ children: otherOpen ? "↵ submit · esc back" : q.multiSelect ? questions.length > 1 ? "space toggle · ← back · Done to continue · esc cancel" : "space toggle · Done to continue · esc cancel" : questions.length > 1 ? "↵ select · ← back · ↑/↓ move · esc cancel" : "↵ select · ↑/↓ move · esc cancel"
8757
+ })
8758
+ ]
8759
+ });
8760
+ }
8761
+
8436
8762
  //#endregion
8437
8763
  //#region src/chat/tui/screens/card-picker.tsx
8438
8764
  /**
@@ -11969,10 +12295,12 @@ function ChatScreen({ agent, conversation, attachRunId }) {
11969
12295
  const credPromptOpen = credPrompt !== null;
11970
12296
  const [computePrompt, setComputePrompt] = useState(null);
11971
12297
  const computePromptOpen = computePrompt !== null;
12298
+ const [askPrompt, setAskPrompt] = useState(null);
12299
+ const askPromptOpen = askPrompt !== null;
11972
12300
  const [restartConfirm, setRestartConfirm] = useState(false);
11973
12301
  const [menuDismissed, setMenuDismissed] = useState(false);
11974
12302
  const [menuHighlight, setMenuHighlight] = useState(0);
11975
- const commandQuery = grantPrompt || credPrompt || computePrompt || restartConfirm ? null : detectCommandTrigger(input);
12303
+ const commandQuery = grantPrompt || credPrompt || computePrompt || askPrompt || restartConfirm ? null : detectCommandTrigger(input);
11976
12304
  const menuCommands = commandQuery === null ? [] : filterSlashCommands(commandQuery);
11977
12305
  const menuOpen = commandQuery !== null && !menuDismissed;
11978
12306
  const menuHighlightClamped = Math.min(menuHighlight, Math.max(0, menuCommands.length - 1));
@@ -11990,7 +12318,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
11990
12318
  credPromptRef.current = credPrompt;
11991
12319
  const seenRunsRef = useRef(/* @__PURE__ */ new Set());
11992
12320
  const shellSessionRef = useRef(null);
11993
- const hostState = grantPrompt || credPrompt || computePrompt || restartConfirm ? "blocked" : run.kind === "idle" ? "idle" : "working";
12321
+ const hostState = grantPrompt || credPrompt || computePrompt || askPrompt || restartConfirm ? "blocked" : run.kind === "idle" ? "idle" : "working";
11994
12322
  useEffect(() => {
11995
12323
  agentHost.transition({ state: hostState });
11996
12324
  }, [agentHost, hostState]);
@@ -12046,7 +12374,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
12046
12374
  const reviewBelowBoxRef = useRef(null);
12047
12375
  const paneDragRef = useRef(null);
12048
12376
  const composerBoxHeight = composerRows + 2;
12049
- const pendingVisible = !grantPrompt && !credPrompt && !computePrompt && !nonWebChannel && attachments.length > 0;
12377
+ const pendingVisible = !grantPrompt && !credPrompt && !computePrompt && !askPrompt && !nonWebChannel && attachments.length > 0;
12050
12378
  const pendingRows = pendingVisible ? attachments.length + 2 : 0;
12051
12379
  const credPromptHeight = 4 + (credPrompt && (credPrompt.error || credPrompt.submitting) ? 1 : 0);
12052
12380
  const nonWebNoticeHeight = 5;
@@ -12112,7 +12440,8 @@ function ChatScreen({ agent, conversation, attachRunId }) {
12112
12440
  reviewFocused,
12113
12441
  reviewPlacement,
12114
12442
  credPromptOpen,
12115
- computePromptOpen
12443
+ computePromptOpen,
12444
+ askPromptOpen
12116
12445
  ]);
12117
12446
  useEffect(() => {
12118
12447
  if (!rest || !initialConversationId) return;
@@ -12514,6 +12843,13 @@ function ChatScreen({ agent, conversation, attachRunId }) {
12514
12843
  });
12515
12844
  return;
12516
12845
  }
12846
+ if (action.kind === "answer_question") {
12847
+ setAskPrompt({
12848
+ cardId: item.id,
12849
+ questions: item.card.questions
12850
+ });
12851
+ return;
12852
+ }
12517
12853
  updateCard(item.id, {
12518
12854
  status: "busy",
12519
12855
  detail: null
@@ -12628,6 +12964,11 @@ function ChatScreen({ agent, conversation, attachRunId }) {
12628
12964
  const target = items.find((m) => m.kind === "card" && m.id === computePrompt.cardId);
12629
12965
  if (!target || target.card.settled !== null || target.status === "done") setComputePrompt(null);
12630
12966
  }, [computePrompt, items]);
12967
+ useEffect(() => {
12968
+ if (!askPrompt) return;
12969
+ const target = items.find((m) => m.kind === "card" && m.id === askPrompt.cardId);
12970
+ if (!target || target.card.settled !== null || target.status === "done") setAskPrompt(null);
12971
+ }, [askPrompt, items]);
12631
12972
  const promptForCards = useCallback(() => {
12632
12973
  const targets = itemsRef.current.filter(isActionableCard);
12633
12974
  const [only] = targets;
@@ -12913,7 +13254,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
12913
13254
  stageAttachment(image);
12914
13255
  }, [stageAttachment]);
12915
13256
  usePaste((event) => {
12916
- if (cardPickerOpen || modelPickerOpen || themePickerOpen || prPickerOpen || helpOpen || bgTasksOpen || credPromptOpen || grantPrompt || computePromptOpen || restartConfirm || nonWebChannel) return;
13257
+ if (cardPickerOpen || modelPickerOpen || themePickerOpen || prPickerOpen || helpOpen || bgTasksOpen || credPromptOpen || grantPrompt || computePromptOpen || askPromptOpen || restartConfirm || nonWebChannel) return;
12917
13258
  const text = event.metadata?.kind === "binary" ? "" : decodePasteBytes(event.bytes);
12918
13259
  const route = routePaste({
12919
13260
  kind: event.metadata?.kind,
@@ -13052,7 +13393,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
13052
13393
  goTo
13053
13394
  ]);
13054
13395
  const archiveConversation = useCallback(() => {
13055
- if (grantPrompt || credPrompt || computePrompt || restartConfirm) return;
13396
+ if (grantPrompt || credPrompt || computePrompt || askPrompt || restartConfirm) return;
13056
13397
  if (agentRunConversation) {
13057
13398
  useStore.getState().showToast({ message: "agent runs can't be archived — ctrl+d in the picker deletes" });
13058
13399
  return;
@@ -13087,6 +13428,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
13087
13428
  grantPrompt,
13088
13429
  credPrompt,
13089
13430
  computePrompt,
13431
+ askPrompt,
13090
13432
  restartConfirm,
13091
13433
  agentRunConversation,
13092
13434
  conversationId,
@@ -13537,7 +13879,7 @@ function ChatScreen({ agent, conversation, attachRunId }) {
13537
13879
  }
13538
13880
  if (reviewFocused) return;
13539
13881
  }
13540
- if (cardPickerOpen || modelPickerOpen || themePickerOpen || prPickerOpen || helpOpen || bgTasksOpen) return;
13882
+ if (cardPickerOpen || askPromptOpen || modelPickerOpen || themePickerOpen || prPickerOpen || helpOpen || bgTasksOpen) return;
13541
13883
  if (grantPrompt) {
13542
13884
  if (key.name === "y") {
13543
13885
  confirmGrant();
@@ -13789,6 +14131,19 @@ function ChatScreen({ agent, conversation, attachRunId }) {
13789
14131
  nonWebChannel
13790
14132
  ]);
13791
14133
  const modelHint = formatModelHint(model, effort);
14134
+ if (askPrompt) return /* @__PURE__ */ jsx(AskPicker, {
14135
+ questions: askPrompt.questions,
14136
+ onSubmit: (text) => {
14137
+ const cardId = askPrompt.cardId;
14138
+ setAskPrompt(null);
14139
+ updateCard(cardId, {
14140
+ status: "done",
14141
+ detail: text
14142
+ });
14143
+ sendContent(text, []);
14144
+ },
14145
+ onCancel: () => setAskPrompt(null)
14146
+ });
13792
14147
  if (cardPickerOpen) return /* @__PURE__ */ jsx(CardPicker, {
13793
14148
  cards: actionableCards,
13794
14149
  onSelect: (card) => {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as mintPortalDeviceToken, c as unifyLegacyCliGrants, i as grantPortalAccess, l as startTlsForward, n as fetchPortalDevices, r as findThisDevice } from "./api-DQCaztBg.mjs";
2
+ import { a as mintPortalDeviceToken, c as unifyLegacyCliGrants, i as grantPortalAccess, l as startTlsForward, n as fetchPortalDevices, r as findThisDevice } from "./api-TwLD7ibI.mjs";
3
3
  import os from "node:os";
4
4
  import { z } from "zod";
5
5
  import * as childProcess from "node:child_process";
@@ -568,6 +568,8 @@ var PortalClient = class {
568
568
  legacyGrantsChecked = false;
569
569
  wakeWatch = null;
570
570
  lastTick = Date.now();
571
+ handingOff = false;
572
+ handoffResolve = null;
571
573
  heartbeatConfig;
572
574
  constructor(opts) {
573
575
  this.opts = opts;
@@ -641,6 +643,48 @@ var PortalClient = class {
641
643
  this.jobs = null;
642
644
  this.ws?.close();
643
645
  this.ws = null;
646
+ this.finishHandoff();
647
+ }
648
+ /**
649
+ * Begin a make-before-break handoff to a successor daemon. Keep the current
650
+ * portal socket (and thus this machine's presence claim) alive so there is no
651
+ * `registered but not connected` gap, but stop redialing once it closes — the
652
+ * successor's `claim()` supersedes us and the server closes our socket, which
653
+ * is our cue to step down. Resolves when the socket has closed (clean
654
+ * handoff) OR after `timeoutMs` (the successor never came up, so we release
655
+ * the slot rather than hold it forever). Idempotent.
656
+ */
657
+ beginHandoff(timeoutMs) {
658
+ if (this.disposed) return Promise.resolve();
659
+ if (this.handingOff) return new Promise((resolve) => {
660
+ const prev = this.handoffResolve;
661
+ this.handoffResolve = () => {
662
+ prev?.();
663
+ resolve();
664
+ };
665
+ });
666
+ this.handingOff = true;
667
+ if (!this.ws) return Promise.resolve();
668
+ return new Promise((resolve) => {
669
+ let done = false;
670
+ const finish = () => {
671
+ if (done) return;
672
+ done = true;
673
+ clearTimeout(timer);
674
+ resolve();
675
+ };
676
+ this.handoffResolve = finish;
677
+ const timer = setTimeout(() => {
678
+ this.disable();
679
+ this.finishHandoff();
680
+ }, timeoutMs);
681
+ if (typeof timer.unref === "function") timer.unref();
682
+ });
683
+ }
684
+ finishHandoff() {
685
+ const resolve = this.handoffResolve;
686
+ this.handoffResolve = null;
687
+ resolve?.();
644
688
  }
645
689
  /**
646
690
  * Grant one agent access to this machine (default-deny; user-initiated).
@@ -702,18 +746,18 @@ var PortalClient = class {
702
746
  this.opts.onMachineName(identity.machineName, identity.machineNameSource);
703
747
  }
704
748
  let backoff = this.heartbeatConfig.initialBackoffMs;
705
- while (this.enabled && !this.disposed) {
749
+ while (this.enabled && !this.disposed && !this.handingOff) {
706
750
  this.setStatus("connecting");
707
751
  try {
708
752
  const token = await this.wsToken();
709
753
  if (await this.runConnection(token)) backoff = this.heartbeatConfig.initialBackoffMs;
710
754
  else backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
711
755
  } catch (err) {
712
- if (!this.enabled || this.disposed) break;
756
+ if (!this.enabled || this.disposed || this.handingOff) break;
713
757
  this.setStatus("error", errorMessage(err));
714
758
  backoff = Math.min(backoff * 2, this.heartbeatConfig.maxBackoffMs);
715
759
  }
716
- if (!this.enabled || this.disposed) break;
760
+ if (!this.enabled || this.disposed || this.handingOff) break;
717
761
  await sleep(Math.random() * backoff);
718
762
  }
719
763
  }
@@ -772,6 +816,7 @@ var PortalClient = class {
772
816
  jobs.killAll();
773
817
  if (this.jobs === jobs) this.jobs = null;
774
818
  if (this.ws === ws) this.ws = null;
819
+ if (this.handingOff) this.finishHandoff();
775
820
  settle();
776
821
  });
777
822
  });
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { t as PortalClient } from "./client-C2lPer4b.mjs";
3
+ import "./api-TwLD7ibI.mjs";
4
+
5
+ export { PortalClient };