@hyperdrive.bot/paseo-server 0.3.23 → 0.3.27

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.
Files changed (19) hide show
  1. package/dist/server/server/agent/provider-registry.d.ts +30 -0
  2. package/dist/server/server/agent/provider-registry.js +36 -0
  3. package/dist/server/server/agent/providers/claude/agent.d.ts +18 -0
  4. package/dist/server/server/agent/providers/claude/agent.js +104 -2
  5. package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +3 -0
  6. package/dist/server/server/agent/providers/claude/pty-session-launcher.js +1 -0
  7. package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +38 -0
  8. package/dist/server/server/agent/providers/claude/transport/pty-query.js +87 -6
  9. package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js → index-11f21e9e7b941cc98daf2ca7e3a78592.js} +4 -4
  10. package/dist/server/web-ui/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.br +0 -0
  11. package/dist/server/web-ui/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.gz +0 -0
  12. package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js.map.br → index-11f21e9e7b941cc98daf2ca7e3a78592.js.map.br} +0 -0
  13. package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js.map.gz → index-11f21e9e7b941cc98daf2ca7e3a78592.js.map.gz} +0 -0
  14. package/dist/server/web-ui/index.html +1 -1
  15. package/dist/server/web-ui/index.html.br +0 -0
  16. package/dist/server/web-ui/index.html.gz +0 -0
  17. package/package.json +6 -6
  18. package/dist/server/web-ui/_expo/static/js/web/index-47b07b144bbc651587bf14a2bf8bc9e4.js.br +0 -0
  19. package/dist/server/web-ui/_expo/static/js/web/index-47b07b144bbc651587bf14a2bf8bc9e4.js.gz +0 -0
@@ -8,6 +8,36 @@ export declare function resolveClaudeTransport(setting: "sdk" | "pty" | "auto" |
8
8
  requireNodePty?: () => unknown;
9
9
  probeClaudeBinary?: () => void;
10
10
  }): "sdk" | "pty";
11
+ /**
12
+ * Internal agents (session-digest and auto-title generation) are headless,
13
+ * one-shot, `persistSession: false` runs. They have no TUI to drive: they are
14
+ * handed a prompt and asked for one structured answer.
15
+ *
16
+ * `resolveClaudeTransport` returns "pty" whenever node-pty and the `claude`
17
+ * binary are both present, which on a developer box is always, so these runs
18
+ * were being given a full interactive terminal and their prompt written through
19
+ * the PTY's bracketed-paste path. That path can refuse the write when the TUI
20
+ * is not in an input-accepting state, and the caller sees:
21
+ *
22
+ * "Your message did not reach the agent: the terminal would not accept it."
23
+ *
24
+ * Measured on 2026-08-04: 187 of 191 structured-generation failures (98%) were
25
+ * exactly that error, across claude/pool/pool-tu and across haiku, opus-5 and
26
+ * opus-4-8, so it is neither provider- nor model-specific. Each failure burned
27
+ * a provider attempt until the digest's 120s budget expired, losing ~46% of all
28
+ * digests (116 DigestGenerationTimeoutError against 113 successful digests that
29
+ * day). Every lost digest also silently costs a Jarvis judgement, because the
30
+ * judge only wakes on the `onSettled` hook that a completed digest fires.
31
+ *
32
+ * Two cheaper explanations were tested and rejected before this one: prompt
33
+ * size (2 KB / 8 KB / 32 KB pastes all delivered fine) and a spawn/readiness
34
+ * race (three spawn-then-immediately-send probes all delivered fine).
35
+ *
36
+ * So internal agents default to the SDK transport, which needs no terminal. An
37
+ * EXPLICIT `transport` setting still wins: this only redirects the "auto" case,
38
+ * so anyone deliberately pinning "pty" keeps it.
39
+ */
40
+ export declare function resolveInternalAgentTransportSetting(internal: boolean | undefined, setting: "sdk" | "pty" | "auto" | undefined): "sdk" | "pty" | "auto" | undefined;
11
41
  export declare const NODE_PTY_UNAVAILABLE_REASON = "node-pty native module not built. Run 'npm rebuild node-pty' in packages/server.";
12
42
  export declare function isNodePtyAvailable(deps?: {
13
43
  requireNodePty?: () => unknown;
@@ -38,6 +38,42 @@ export function resolveClaudeTransport(setting, deps) {
38
38
  return "sdk";
39
39
  }
40
40
  }
41
+ /**
42
+ * Internal agents (session-digest and auto-title generation) are headless,
43
+ * one-shot, `persistSession: false` runs. They have no TUI to drive: they are
44
+ * handed a prompt and asked for one structured answer.
45
+ *
46
+ * `resolveClaudeTransport` returns "pty" whenever node-pty and the `claude`
47
+ * binary are both present, which on a developer box is always, so these runs
48
+ * were being given a full interactive terminal and their prompt written through
49
+ * the PTY's bracketed-paste path. That path can refuse the write when the TUI
50
+ * is not in an input-accepting state, and the caller sees:
51
+ *
52
+ * "Your message did not reach the agent: the terminal would not accept it."
53
+ *
54
+ * Measured on 2026-08-04: 187 of 191 structured-generation failures (98%) were
55
+ * exactly that error, across claude/pool/pool-tu and across haiku, opus-5 and
56
+ * opus-4-8, so it is neither provider- nor model-specific. Each failure burned
57
+ * a provider attempt until the digest's 120s budget expired, losing ~46% of all
58
+ * digests (116 DigestGenerationTimeoutError against 113 successful digests that
59
+ * day). Every lost digest also silently costs a Jarvis judgement, because the
60
+ * judge only wakes on the `onSettled` hook that a completed digest fires.
61
+ *
62
+ * Two cheaper explanations were tested and rejected before this one: prompt
63
+ * size (2 KB / 8 KB / 32 KB pastes all delivered fine) and a spawn/readiness
64
+ * race (three spawn-then-immediately-send probes all delivered fine).
65
+ *
66
+ * So internal agents default to the SDK transport, which needs no terminal. An
67
+ * EXPLICIT `transport` setting still wins: this only redirects the "auto" case,
68
+ * so anyone deliberately pinning "pty" keeps it.
69
+ */
70
+ export function resolveInternalAgentTransportSetting(internal, setting) {
71
+ if (!internal)
72
+ return setting;
73
+ if (setting === "sdk" || setting === "pty")
74
+ return setting;
75
+ return "sdk";
76
+ }
41
77
  export const NODE_PTY_UNAVAILABLE_REASON = "node-pty native module not built. Run 'npm rebuild node-pty' in packages/server.";
42
78
  export function isNodePtyAvailable(deps) {
43
79
  const tryRequire = deps?.requireNodePty ?? (() => requireFromHere("node-pty"));
@@ -101,6 +101,8 @@ export declare class ClaudeAgentSession implements AgentSession {
101
101
  private toolUseIndexToId;
102
102
  private toolUseInputBuffers;
103
103
  private pendingPermissions;
104
+ /** Permission id currently standing in for a PTY TUI dialog, if any. */
105
+ private ptyDialogRequestId;
104
106
  private activeForegroundTurnId;
105
107
  private autonomousTurn;
106
108
  private readonly subscribers;
@@ -280,6 +282,22 @@ export declare class ClaudeAgentSession implements AgentSession {
280
282
  private handleSystemMessage;
281
283
  private readMissingResumedConversationError;
282
284
  private convertUsage;
285
+ /**
286
+ * Raise (and retract) a permission request for a dialog the PTY's TUI is showing.
287
+ *
288
+ * The SDK path gets these through `canUseTool`, which is how the app renders a real
289
+ * question card with its options. The PTY path has no such hook - the TUI owns the
290
+ * dialog - so before this the app only saw an inert `AskUserQuestion` tool chip and the
291
+ * session sat waiting with nothing to tap (observed live: 11h51m). Surfacing the same
292
+ * request shape reuses the app's existing question UI, and the answer is played back
293
+ * into the terminal as the dialog's own keystrokes.
294
+ *
295
+ * Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
296
+ * dependent, so guessing an index there could approve an implementation nobody chose.
297
+ */
298
+ private handlePtyDialogChange;
299
+ /** Map the app's chosen answer back to the dialog's option index and press the keys. */
300
+ private answerPtyDialogFromPermission;
283
301
  private handlePermissionRequest;
284
302
  private enqueueTimeline;
285
303
  private flushPendingToolCalls;
@@ -30,7 +30,7 @@ import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
30
30
  import { importSessionFromPersistence } from "../../provider-session-import.js";
31
31
  import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
32
32
  import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
33
- import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, } from "../../provider-registry.js";
33
+ import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, resolveInternalAgentTransportSetting, } from "../../provider-registry.js";
34
34
  import { isCommandAvailable } from "../../../../executable-resolution/executable-resolution.js";
35
35
  import { withTimeout } from "../../../../utils/promise-timeout.js";
36
36
  import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
@@ -714,6 +714,33 @@ function isPermissionUpdate(value) {
714
714
  const destination = value.destination;
715
715
  return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
716
716
  }
717
+ /**
718
+ * The app answers a question by echoing the tool input back with the chosen label(s). The
719
+ * exact key varies by question text, so collect every string leaf rather than guessing one
720
+ * path; the caller matches these against the dialog's on-screen options.
721
+ */
722
+ function collectAnswerLabels(input) {
723
+ const labels = [];
724
+ const visit = (value, depth) => {
725
+ if (depth > 4)
726
+ return;
727
+ if (typeof value === "string") {
728
+ labels.push(value);
729
+ return;
730
+ }
731
+ if (Array.isArray(value)) {
732
+ for (const item of value)
733
+ visit(item, depth + 1);
734
+ return;
735
+ }
736
+ if (isMetadata(value)) {
737
+ for (const item of Object.values(value))
738
+ visit(item, depth + 1);
739
+ }
740
+ };
741
+ visit(input, 0);
742
+ return labels;
743
+ }
717
744
  function resolvePermissionKind(toolName, input) {
718
745
  if (toolName === "ExitPlanMode")
719
746
  return "plan";
@@ -1469,6 +1496,8 @@ export class ClaudeAgentSession {
1469
1496
  this.toolUseIndexToId = new Map();
1470
1497
  this.toolUseInputBuffers = new Map();
1471
1498
  this.pendingPermissions = new Map();
1499
+ /** Permission id currently standing in for a PTY TUI dialog, if any. */
1500
+ this.ptyDialogRequestId = null;
1472
1501
  this.activeForegroundTurnId = null;
1473
1502
  this.autonomousTurn = null;
1474
1503
  this.subscribers = new Set();
@@ -2314,7 +2343,7 @@ export class ClaudeAgentSession {
2314
2343
  ensureTransport() {
2315
2344
  if (this.resolvedTransport)
2316
2345
  return this.resolvedTransport;
2317
- this.resolvedTransport = resolveClaudeTransport(this.runtimeSettings?.transport);
2346
+ this.resolvedTransport = resolveClaudeTransport(resolveInternalAgentTransportSetting(this.config.internal, this.runtimeSettings?.transport));
2318
2347
  this.fireTransportSpawnedTelemetry(this.resolvedTransport);
2319
2348
  return this.resolvedTransport;
2320
2349
  }
@@ -2501,6 +2530,7 @@ export class ClaudeAgentSession {
2501
2530
  ? this.normalizeMcpServers(this.config.mcpServers)
2502
2531
  : undefined,
2503
2532
  logger: this.logger,
2533
+ onDialogChange: (dialog) => this.handlePtyDialogChange(dialog),
2504
2534
  });
2505
2535
  this.activeTransport = ptySession.transport;
2506
2536
  this.query = ptySession.query;
@@ -3580,6 +3610,78 @@ export class ClaudeAgentSession {
3580
3610
  convertUsage(message, modelUsage) {
3581
3611
  return this.contextUsage.buildResultUsage(message, modelUsage);
3582
3612
  }
3613
+ /**
3614
+ * Raise (and retract) a permission request for a dialog the PTY's TUI is showing.
3615
+ *
3616
+ * The SDK path gets these through `canUseTool`, which is how the app renders a real
3617
+ * question card with its options. The PTY path has no such hook - the TUI owns the
3618
+ * dialog - so before this the app only saw an inert `AskUserQuestion` tool chip and the
3619
+ * session sat waiting with nothing to tap (observed live: 11h51m). Surfacing the same
3620
+ * request shape reuses the app's existing question UI, and the answer is played back
3621
+ * into the terminal as the dialog's own keystrokes.
3622
+ *
3623
+ * Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
3624
+ * dependent, so guessing an index there could approve an implementation nobody chose.
3625
+ */
3626
+ handlePtyDialogChange(dialog) {
3627
+ if (!dialog) {
3628
+ const pendingId = this.ptyDialogRequestId;
3629
+ this.ptyDialogRequestId = null;
3630
+ if (!pendingId)
3631
+ return;
3632
+ const pending = this.pendingPermissions.get(pendingId);
3633
+ if (!pending)
3634
+ return;
3635
+ // Answered in the terminal (or by keystrokes): retract the card, don't leave it up.
3636
+ this.pendingPermissions.delete(pendingId);
3637
+ pending.cleanup?.();
3638
+ this.pushEvent({
3639
+ type: "permission_resolved",
3640
+ provider: "claude",
3641
+ requestId: pendingId,
3642
+ resolution: { behavior: "allow" },
3643
+ });
3644
+ return;
3645
+ }
3646
+ if (dialog.name !== "AskUserQuestion" || this.ptyDialogRequestId)
3647
+ return;
3648
+ const requestId = `permission-${randomUUID()}`;
3649
+ this.ptyDialogRequestId = requestId;
3650
+ const request = {
3651
+ id: requestId,
3652
+ provider: "claude",
3653
+ name: dialog.name,
3654
+ kind: "question",
3655
+ input: normalizeClaudeAskUserQuestionRequestInput(dialog.name, dialog.input),
3656
+ metadata: { toolUseId: dialog.toolUseId },
3657
+ };
3658
+ const options = dialog.options;
3659
+ this.pendingPermissions.set(requestId, {
3660
+ request,
3661
+ // There is no SDK promise to settle here: "resolving" means playing the answer into
3662
+ // the terminal. An answer that names no known option is left to the user to type.
3663
+ resolve: (result) => {
3664
+ void this.answerPtyDialogFromPermission(result, options);
3665
+ },
3666
+ reject: () => { },
3667
+ });
3668
+ this.pushEvent({ type: "permission_requested", provider: "claude", request });
3669
+ }
3670
+ /** Map the app's chosen answer back to the dialog's option index and press the keys. */
3671
+ async answerPtyDialogFromPermission(result, options) {
3672
+ if (result.behavior !== "allow")
3673
+ return;
3674
+ const query = this.query;
3675
+ if (typeof query?.answerDialogByIndex !== "function")
3676
+ return;
3677
+ const chosen = collectAnswerLabels(result.updatedInput);
3678
+ const index = options.findIndex((option) => chosen.some((label) => label.trim().toLowerCase() === option.trim().toLowerCase()));
3679
+ if (index < 0) {
3680
+ this.logger.warn({ chosen, options }, "PTY dialog answer matched no on-screen option; leaving the dialog up");
3681
+ return;
3682
+ }
3683
+ await query.answerDialogByIndex(index);
3684
+ }
3583
3685
  enqueueTimeline(item) {
3584
3686
  this.pushEvent({ type: "timeline", item, provider: "claude" });
3585
3687
  }
@@ -2,6 +2,7 @@ import type { Logger } from "pino";
2
2
  import type { Query, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
3
3
  import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
4
4
  import { PtyTransport } from "./transport/pty.js";
5
+ import { type PtyInteractiveDialog } from "./transport/pty-query.js";
5
6
  export interface CreatePtySessionOptions {
6
7
  binary: string;
7
8
  cwd: string;
@@ -22,6 +23,8 @@ export interface CreatePtySessionOptions {
22
23
  cols: number;
23
24
  rows: number;
24
25
  };
26
+ /** Surfaces TUI question/plan dialogs so the provider can raise a permission request. */
27
+ onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
25
28
  }
26
29
  export interface PtySession {
27
30
  transport: PtyTransport;
@@ -55,6 +55,7 @@ export async function createPtySession(opts) {
55
55
  transcriptPath,
56
56
  sessionId: opts.sessionId,
57
57
  logger: opts.logger,
58
+ ...(opts.onDialogChange ? { onDialogChange: opts.onDialogChange } : {}),
58
59
  });
59
60
  opts.logger.debug({ pid: transport.context().pid, sessionId: opts.sessionId, transcriptPath }, "PTY claude session spawned");
60
61
  return { transport, query, transcriptPath, systemPromptFilePath };
@@ -1,6 +1,16 @@
1
1
  import type { Logger } from "pino";
2
2
  import type { ModelInfo, SDKMessage, SDKSystemMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
3
3
  import type { PtyTransport } from "./pty.js";
4
+ /** An interactive TUI dialog the user has to answer for the turn to continue. */
5
+ export interface PtyInteractiveDialog {
6
+ toolUseId: string;
7
+ /** "AskUserQuestion" | "ExitPlanMode" */
8
+ name: string;
9
+ /** The tool_use input verbatim, so the app renders the real questions and options. */
10
+ input: Record<string, unknown>;
11
+ /** Option labels of the FIRST question, in on-screen order; empty when unknown. */
12
+ options: string[];
13
+ }
4
14
  export interface PtyQueryOptions {
5
15
  transport: PtyTransport;
6
16
  /** The shared input channel (createAsyncMessageInput().iterable) fed by startTurn(). */
@@ -15,6 +25,12 @@ export interface PtyQueryOptions {
15
25
  * {@link TURN_STALL_TIMEOUT_MS}. Pass `0` to disable the liveness backstop.
16
26
  */
17
27
  stallTimeoutMs?: number;
28
+ /**
29
+ * Called when the TUI starts (dialog) or finishes (null) waiting on an interactive
30
+ * question. The provider turns this into a permission request so the app can render
31
+ * the real options instead of an inert tool chip nobody can answer.
32
+ */
33
+ onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
18
34
  }
19
35
  export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
20
36
  private readonly transport;
@@ -23,6 +39,7 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
23
39
  private readonly transcriptPath;
24
40
  private readonly logger;
25
41
  private readonly reader;
42
+ private readonly onDialogChange;
26
43
  private readonly outQueue;
27
44
  private readonly outResolvers;
28
45
  private done;
@@ -103,6 +120,15 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
103
120
  * agent running by the time the input loop sees it: silence here is a forever-spinner.
104
121
  */
105
122
  private buildNothingDeliverableResult;
123
+ /** Tell the provider the dialog state changed; never let a listener throw into the loop. */
124
+ private notifyDialogChange;
125
+ /** The dialog the TUI is waiting on, if any. */
126
+ interactiveDialog(): PtyInteractiveDialog | null;
127
+ /**
128
+ * Answer the pending dialog by option index (0-based), the way the app's question card
129
+ * does. Returns false when there is no dialog or the keystrokes never registered.
130
+ */
131
+ answerDialogByIndex(index: number): Promise<boolean>;
106
132
  /**
107
133
  * Try to answer the pending TUI dialog with the user's reply. A reply that names an
108
134
  * option (its 1-based number or its label) becomes Down-arrow presses + Enter, the same
@@ -111,6 +137,12 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
111
137
  * into a silent decline.
112
138
  */
113
139
  private answerPendingDialog;
140
+ /**
141
+ * Drive the dialog's own keys: Down to the option, Enter to confirm. Confirmation is the
142
+ * tool_result landing in the transcript (which clears pendingDialog) - an optimistic flag
143
+ * here would record intent as fact.
144
+ */
145
+ private sendDialogAnswer;
114
146
  /** The agent is waiting on a dialog; tell the sender what it asks and how to answer. */
115
147
  private buildDialogPendingResult;
116
148
  private buildSubmitFailedResult;
@@ -155,6 +187,12 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
155
187
  * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
156
188
  * (zero echo), and every iteration first checks whether a user line landed anyway.
157
189
  */
190
+ /**
191
+ * Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
192
+ * pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
193
+ * keys just leaves the composer as it was.
194
+ */
195
+ private clearComposer;
158
196
  private typeWithEchoVerify;
159
197
  private submitTurn;
160
198
  /**
@@ -96,6 +96,15 @@ const ECHO_VERIFY_WINDOW_MS = 1500;
96
96
  * contains the user's message; a real input-box echo always does.
97
97
  */
98
98
  const ECHO_TEXT_FRAGMENT_CHARS = 24;
99
+ /**
100
+ * A long or multi-line prompt is written in one chunk, so the TUI takes it as a PASTE and
101
+ * renders a collapsed chip (`[Pasted text #9 +30 lines] paste again to expand`) instead of
102
+ * echoing the characters. Text-only echo verification therefore read a perfectly delivered
103
+ * message as a deaf terminal, retyped it, and every retry stacked ANOTHER chip in the
104
+ * composer: observed live at chip #9 for one message, none of them ever submitted. The
105
+ * chip is the echo for pasted input, so it counts as delivery.
106
+ */
107
+ const PASTE_CHIP_MARKERS = ["pastedtext#", "pasteagaintoexpand"];
99
108
  /** Backoff between retype attempts against a deaf terminal. */
100
109
  const DEAF_RETRY_INITIAL_MS = 5000;
101
110
  const DEAF_RETRY_MAX_MS = 60000;
@@ -263,6 +272,7 @@ export class PtyQuery {
263
272
  this.transcriptPath = opts.transcriptPath;
264
273
  this.logger = opts.logger;
265
274
  this.stallTimeoutMs = opts.stallTimeoutMs ?? TURN_STALL_TIMEOUT_MS;
275
+ this.onDialogChange = opts.onDialogChange ?? null;
266
276
  this.reader = new TranscriptSdkReader({
267
277
  transcriptPath: opts.transcriptPath,
268
278
  onMessage: (m) => this.onTranscriptMessage(m),
@@ -417,7 +427,10 @@ export class PtyQuery {
417
427
  if (this.done)
418
428
  return;
419
429
  this.done = true;
430
+ const hadDialog = this.pendingDialog !== null;
420
431
  this.pendingDialog = null;
432
+ if (hadDialog)
433
+ this.notifyDialogChange();
421
434
  this.cancelReadiness?.();
422
435
  if (this.turnEndTimer)
423
436
  clearTimeout(this.turnEndTimer);
@@ -602,6 +615,30 @@ export class PtyQuery {
602
615
  ],
603
616
  };
604
617
  }
618
+ /** Tell the provider the dialog state changed; never let a listener throw into the loop. */
619
+ notifyDialogChange() {
620
+ if (!this.onDialogChange)
621
+ return;
622
+ try {
623
+ this.onDialogChange(this.pendingDialog ? { ...this.pendingDialog } : null);
624
+ }
625
+ catch (err) {
626
+ this.logger.warn({ err }, "PtyQuery: dialog listener threw");
627
+ }
628
+ }
629
+ /** The dialog the TUI is waiting on, if any. */
630
+ interactiveDialog() {
631
+ return this.pendingDialog ? { ...this.pendingDialog } : null;
632
+ }
633
+ /**
634
+ * Answer the pending dialog by option index (0-based), the way the app's question card
635
+ * does. Returns false when there is no dialog or the keystrokes never registered.
636
+ */
637
+ async answerDialogByIndex(index) {
638
+ if (!this.pendingDialog || index < 0)
639
+ return false;
640
+ return await this.sendDialogAnswer(this.pendingDialog, index);
641
+ }
605
642
  /**
606
643
  * Try to answer the pending TUI dialog with the user's reply. A reply that names an
607
644
  * option (its 1-based number or its label) becomes Down-arrow presses + Enter, the same
@@ -619,28 +656,37 @@ export class PtyQuery {
619
656
  this.emit(this.buildDialogPendingResult(dialog));
620
657
  return;
621
658
  }
659
+ const answered = await this.sendDialogAnswer(dialog, index);
660
+ if (!answered) {
661
+ this.emit(this.buildDialogPendingResult(dialog, true));
662
+ }
663
+ }
664
+ /**
665
+ * Drive the dialog's own keys: Down to the option, Enter to confirm. Confirmation is the
666
+ * tool_result landing in the transcript (which clears pendingDialog) - an optimistic flag
667
+ * here would record intent as fact.
668
+ */
669
+ async sendDialogAnswer(dialog, index) {
622
670
  this.recordLine(`--- answering ${dialog.name} with option ${index + 1} ---`);
623
671
  for (let i = 0; i < index && !this.done; i++) {
624
672
  await this.transport.writeRaw?.("\u001b[B");
625
673
  await delay(DIALOG_KEY_DELAY_MS);
626
674
  }
627
675
  await this.transport.writeRaw?.("\r");
628
- // Confirmation is the tool_result landing in the transcript (trackInteractiveDialog
629
- // clears pendingDialog). Optimistic flags would record intent as fact.
630
676
  const deadline = monotonicNowMs() + DIALOG_ANSWER_CONFIRM_MS;
631
677
  while (this.pendingDialog && !this.done && monotonicNowMs() < deadline) {
632
678
  await delay(250);
633
679
  }
634
680
  if (this.pendingDialog) {
635
681
  this.recordLine(`--- ${dialog.name} answer did not register ---`);
636
- this.emit(this.buildDialogPendingResult(dialog, true));
637
- return;
682
+ return false;
638
683
  }
639
684
  // Answer accepted: the turn is live again, restart the liveness clock.
640
685
  this.turnStartedAt = monotonicNowMs();
641
686
  this.turnInFlight = true;
642
687
  this.armStallTimer();
643
688
  this.startHeartbeat();
689
+ return true;
644
690
  }
645
691
  /** The agent is waiting on a dialog; tell the sender what it asks and how to answer. */
646
692
  buildDialogPendingResult(dialog, answerFailed = false) {
@@ -757,6 +803,20 @@ export class PtyQuery {
757
803
  * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
758
804
  * (zero echo), and every iteration first checks whether a user line landed anyway.
759
805
  */
806
+ /**
807
+ * Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
808
+ * pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
809
+ * keys just leaves the composer as it was.
810
+ */
811
+ async clearComposer() {
812
+ try {
813
+ await this.transport.writeRaw?.("\u0015");
814
+ await delay(120);
815
+ }
816
+ catch (err) {
817
+ this.logger.debug({ err }, "PtyQuery: could not clear the composer before retyping");
818
+ }
819
+ }
760
820
  async typeWithEchoVerify(text) {
761
821
  const transcriptBefore = this.transcriptSize();
762
822
  const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
@@ -775,7 +835,7 @@ export class PtyQuery {
775
835
  try {
776
836
  await this.transport.write(text);
777
837
  const probe = this.echoProbe;
778
- echoed = await pollUntil(() => (needle ? probe.buf.includes(needle) : probe.buf.length > 0), ECHO_VERIFY_WINDOW_MS, 100);
838
+ echoed = await pollUntil(() => sawEcho(probe.buf, needle), ECHO_VERIFY_WINDOW_MS, 100);
779
839
  }
780
840
  finally {
781
841
  this.echoProbe = null;
@@ -787,9 +847,12 @@ export class PtyQuery {
787
847
  await delay(200);
788
848
  return true;
789
849
  }
790
- // Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe.
850
+ // Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe -
851
+ // but only after clearing the composer: if this attempt DID land as something the
852
+ // echo check cannot see, retyping on top of it sends the message twice.
791
853
  if (this.sawUserLineSince(transcriptBefore))
792
854
  return true;
855
+ await this.clearComposer();
793
856
  this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: terminal painted but is not accepting input yet; holding the prompt");
794
857
  this.recordLine(`--- terminal deaf (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
795
858
  await delay(backoff);
@@ -952,14 +1015,17 @@ export class PtyQuery {
952
1015
  this.pendingDialog = {
953
1016
  toolUseId: b.id,
954
1017
  name: b.name,
1018
+ input: isRecord(b.input) ? b.input : {},
955
1019
  options: extractDialogOptions(b.name, b.input),
956
1020
  };
957
1021
  this.recordLine(`--- interactive dialog pending: ${b.name} ---`);
1022
+ this.notifyDialogChange();
958
1023
  }
959
1024
  const pending = this.pendingDialog;
960
1025
  if (pending && b.type === "tool_result" && b.tool_use_id === pending.toolUseId) {
961
1026
  this.recordLine(`--- interactive dialog answered: ${pending.name} ---`);
962
1027
  this.pendingDialog = null;
1028
+ this.notifyDialogChange();
963
1029
  }
964
1030
  }
965
1031
  }
@@ -1304,6 +1370,21 @@ function matchDialogOption(text, options) {
1304
1370
  const byLabel = options.findIndex((o) => o.trim().toLowerCase() === lowered);
1305
1371
  return byLabel >= 0 ? byLabel : null;
1306
1372
  }
1373
+ /**
1374
+ * Did the terminal acknowledge what we typed? Either it echoed the characters, or it
1375
+ * collapsed them into a paste chip - both mean the composer holds the message.
1376
+ */
1377
+ function sawEcho(buf, needle) {
1378
+ if (!buf)
1379
+ return false;
1380
+ const lowered = buf.toLowerCase();
1381
+ if (PASTE_CHIP_MARKERS.some((marker) => lowered.includes(marker)))
1382
+ return true;
1383
+ return needle ? buf.includes(needle) : buf.length > 0;
1384
+ }
1385
+ function isRecord(value) {
1386
+ return !!value && typeof value === "object" && !Array.isArray(value);
1387
+ }
1307
1388
  /** Extensions for the media types the app can send; anything unknown stages as .png. */
1308
1389
  const IMAGE_MEDIA_TYPE_EXT = {
1309
1390
  "image/jpeg": "jpg",
@@ -1103,7 +1103,7 @@ __d(function(g,r,_i,a,m,_e,d){"use strict";function e(e){return e&&e.__esModule?
1103
1103
  __d(function(g,r,i,a,m,_e,d){"use strict";function e(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return N}}),Object.defineProperty(_e,"AppOwnership",{enumerable:!0,get:function(){return l.AppOwnership}}),Object.defineProperty(_e,"ExecutionEnvironment",{enumerable:!0,get:function(){return l.ExecutionEnvironment}}),Object.defineProperty(_e,"UserInterfaceIdiom",{enumerable:!0,get:function(){return l.UserInterfaceIdiom}});var n=e(r(d[0])),t=r(d[1]);r(d[2]);var u=e(r(d[3])),l=r(d[4]),o=e(r(d[5]));o.default||console.warn("No native ExponentConstants module found, are you sure the expo-constants's module is linked properly?");const s=(0,t.requireOptionalNativeModule)('ExpoUpdates');let f=null;if(s){let e;s.manifest?e=s.manifest:s.manifestString&&(e=JSON.parse(s.manifestString)),e&&Object.keys(e).length>0&&(f=e)}let c=null;if(u.default.EXDevLauncher){let e;u.default.EXDevLauncher.manifestString&&(e=JSON.parse(u.default.EXDevLauncher.manifestString)),e&&Object.keys(e).length>0&&(c=e)}let p=null;if(o.default&&o.default.manifest){const e=o.default.manifest;p='string'==typeof e?JSON.parse(e):e}let b=f??c??p;const E=o.default||{},{appOwnership:O}=E,x=(0,n.default)(E,["name","appOwnership"]),v=Object.assign({},x,{appOwnership:O??null});function _(e){return!h(e)}function h(e){return'metadata'in e}function S(e=!1){if(!b){const e=null===b?'null':'undefined';if(x.executionEnvironment,l.ExecutionEnvironment.Bare,x.executionEnvironment===l.ExecutionEnvironment.StoreClient||x.executionEnvironment===l.ExecutionEnvironment.Standalone)throw new t.CodedError('ERR_CONSTANTS_MANIFEST_UNAVAILABLE',`Constants.manifest is ${e}, must be an object.`)}return b}Object.defineProperties(v,{__unsafeNoWarnManifest:{get(){const e=S(!0);return e&&_(e)?e:null},enumerable:!1},__unsafeNoWarnManifest2:{get(){const e=S(!0);return e&&h(e)?e:null},enumerable:!1},manifest:{get(){const e=S();return e&&_(e)?e:null},enumerable:!0},manifest2:{get(){const e=S();return e&&h(e)?e:null},enumerable:!0},expoConfig:{get(){const e=S(!0);return e?s&&s.isEmbeddedLaunch?p:h(e)?e.extra?.expoClient??null:_(e)?e:null:null},enumerable:!0},expoGoConfig:{get(){const e=S(!0);return e?h(e)?e.extra?.expoGo??null:_(e)?e:null:null},enumerable:!0},easConfig:{get(){const e=S(!0);return e?h(e)?e.extra?.eas??null:_(e)?e:null:null},enumerable:!0},__rawManifest_TEST:{get:()=>b,set(e){b=e},enumerable:!1}});var N=v},1006,[35,4,25,1007,1008,1009]);
1104
1104
  __d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return u}});var e,t=r(d[0]),u={UIManager:((e=t)&&e.__esModule?e:{default:e}).default}},1007,[159]);
1105
1105
  __d(function(g,r,i,a,m,e,d){"use strict";var t,n,o;Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"AppOwnership",{enumerable:!0,get:function(){return t}}),Object.defineProperty(e,"ExecutionEnvironment",{enumerable:!0,get:function(){return n}}),Object.defineProperty(e,"UserInterfaceIdiom",{enumerable:!0,get:function(){return o}}),(function(t){t.Expo="expo"})(t||(t={})),(function(t){t.Bare="bare",t.Standalone="standalone",t.StoreClient="storeClient"})(n||(n={})),(function(t){t.Handset="handset",t.Tablet="tablet",t.Desktop="desktop",t.TV="tv",t.Unsupported="unsupported"})(o||(o={}))},1008,[]);
1106
- __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return s}});var t=r(d[0]);const n=(Date.now()+'-'+Math.floor(1e9*Math.random())).toString();function o(){if('undefined'!=typeof navigator&&'string'==typeof navigator.userAgent){const t=navigator.userAgent.toLowerCase();if(t.includes('edge'))return'Edge';if(t.includes('edg'))return'Chromium Edge';if(t.includes('opr')&&'opr'in window&&window.opr)return'Opera';if(t.includes('chrome')&&'chrome'in window&&window.chrome)return'Chrome';if(t.includes('trident'))return'IE';if(t.includes('firefox'))return'Firefox';if(t.includes('safari'))return'Safari'}}var s={get appOwnership(){return null},get executionEnvironment(){return t.ExecutionEnvironment.Bare},get sessionId(){return n},get isHeadless(){return'undefined'==typeof navigator||/\bHeadlessChrome\//.test(navigator.userAgent)},get expoVersion(){return this.manifest.sdkVersion||null},get linkingUri(){return'undefined'!=typeof location?location.origin:''},get expoRuntimeVersion(){return this.expoVersion},get deviceName(){return o()},get systemFonts(){return[]},get statusBarHeight(){return 0},get deviceYearClass(){return null},get manifest(){return"{\"name\":\"Paseo\",\"slug\":\"paseo-hyperdrive\",\"version\":\"0.3.23\",\"orientation\":\"portrait\",\"icon\":\"./assets/images/icon.png\",\"scheme\":\"paseo\",\"userInterfaceStyle\":\"automatic\",\"newArchEnabled\":true,\"web\":{\"output\":\"single\",\"favicon\":\"./assets/images/favicon.png\",\"shortName\":\"Paseo\",\"orientation\":\"portrait\",\"name\":\"Paseo\"},\"autolinking\":{\"searchPaths\":[\"../../node_modules\",\"./node_modules\"]},\"experiments\":{\"typedRoutes\":true,\"reactCompiler\":true,\"autolinkingModuleResolution\":true},\"extra\":{\"router\":{},\"eas\":{\"build\":{\"experimental\":{\"ios\":{\"appExtensions\":[{\"bundleIdentifier\":\"bot.hyperdrive.paseo.AgentActivity\",\"targetName\":\"AgentActivity\"}]}}}}},\"sdkVersion\":\"54.0.0\",\"platforms\":[\"ios\",\"android\",\"web\"]}"},get manifest2(){return null},get experienceUrl(){return'undefined'!=typeof location?location.origin:''},get debugMode(){return!1},getWebViewUserAgentAsync:async()=>'undefined'!=typeof navigator?navigator.userAgent:null}},1009,[1008]);
1106
+ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return s}});var t=r(d[0]);const n=(Date.now()+'-'+Math.floor(1e9*Math.random())).toString();function o(){if('undefined'!=typeof navigator&&'string'==typeof navigator.userAgent){const t=navigator.userAgent.toLowerCase();if(t.includes('edge'))return'Edge';if(t.includes('edg'))return'Chromium Edge';if(t.includes('opr')&&'opr'in window&&window.opr)return'Opera';if(t.includes('chrome')&&'chrome'in window&&window.chrome)return'Chrome';if(t.includes('trident'))return'IE';if(t.includes('firefox'))return'Firefox';if(t.includes('safari'))return'Safari'}}var s={get appOwnership(){return null},get executionEnvironment(){return t.ExecutionEnvironment.Bare},get sessionId(){return n},get isHeadless(){return'undefined'==typeof navigator||/\bHeadlessChrome\//.test(navigator.userAgent)},get expoVersion(){return this.manifest.sdkVersion||null},get linkingUri(){return'undefined'!=typeof location?location.origin:''},get expoRuntimeVersion(){return this.expoVersion},get deviceName(){return o()},get systemFonts(){return[]},get statusBarHeight(){return 0},get deviceYearClass(){return null},get manifest(){return"{\"name\":\"Paseo\",\"slug\":\"paseo-hyperdrive\",\"version\":\"0.3.27\",\"orientation\":\"portrait\",\"icon\":\"./assets/images/icon.png\",\"scheme\":\"paseo\",\"userInterfaceStyle\":\"automatic\",\"newArchEnabled\":true,\"web\":{\"output\":\"single\",\"favicon\":\"./assets/images/favicon.png\",\"shortName\":\"Paseo\",\"orientation\":\"portrait\",\"name\":\"Paseo\"},\"autolinking\":{\"searchPaths\":[\"../../node_modules\",\"./node_modules\"]},\"experiments\":{\"typedRoutes\":true,\"reactCompiler\":true,\"autolinkingModuleResolution\":true},\"extra\":{\"router\":{},\"eas\":{\"build\":{\"experimental\":{\"ios\":{\"appExtensions\":[{\"bundleIdentifier\":\"bot.hyperdrive.paseo.AgentActivity\",\"targetName\":\"AgentActivity\"}]}}}}},\"sdkVersion\":\"54.0.0\",\"platforms\":[\"ios\",\"android\",\"web\"]}"},get manifest2(){return null},get experienceUrl(){return'undefined'!=typeof location?location.origin:''},get debugMode(){return!1},getWebViewUserAgentAsync:async()=>'undefined'!=typeof navigator?navigator.userAgent:null}},1009,[1008]);
1107
1107
  __d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return u}});var e,t=r(d[0]),n=r(d[1]),o=(e=n)&&e.__esModule?e:{default:e};async function u(){if(!o.default.unregisterForNotificationsAsync)throw new t.UnavailabilityError('ExpoNotifications','unregisterForNotificationsAsync');return o.default.unregisterForNotificationsAsync()}},1010,[4,1011]);
1108
1108
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return n}}),r(d[0]);let t=!1;var n={addListener:()=>(t||(console.warn("[expo-notifications] Listening to push token changes is not yet fully supported on web. Adding a listener will have no effect."),t=!0),{remove:()=>{}}),removeListener:()=>{},removeAllListeners:()=>{},emit:()=>{},listenerCount:()=>0}},1011,[4]);
1109
1109
  __d(function(g,r,i,a,m,_e,_d){"use strict";function e(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return u}});var t=(function(e){if(e&&e.__esModule)return e;var t={};return e&&Object.keys(e).forEach(function(o){var n=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(t,o,n.get?n:{enumerable:!0,get:function(){return e[o]}})}),t.default=e,t})(r(_d[0])),o=e(r(_d[1])),n=r(_d[2]),c=r(_d[3]),s=e(r(_d[4])),d=e(r(_d[5]));const p='https://exp.host/--/api/v2/';async function u(e={}){const s=e.devicePushToken||await(0,d.default)(),u=e.deviceId||await h(),R=e.projectId||o.default.easConfig?.projectId||o.default.expoConfig?.extra?.eas?.projectId;if(!R)throw new n.CodedError('ERR_NOTIFICATIONS_NO_EXPERIENCE_ID',"No \"projectId\" found. If \"projectId\" can't be inferred from the manifest (for instance, in bare workflow), you have to pass it in yourself.");const w=e.applicationId||t.applicationId;if(!w)throw new n.CodedError('ERR_NOTIFICATIONS_NO_APPLICATION_ID',"No \"applicationId\" found. If it can't be inferred from native configuration by expo-application, you have to pass it in yourself.");const O=e.type||y(s),_=e.development||await I(),v=e.baseUrl??p,N=e.url??`${v}push/getExpoPushToken`,x={type:O,deviceId:u.toLowerCase(),development:_,appId:w,deviceToken:E(s),projectId:R},T=await fetch(N,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(x)}).catch(e=>{throw new n.CodedError('ERR_NOTIFICATIONS_NETWORK_ERROR',`Error encountered while fetching Expo token: ${e}.`)});if(!T.ok){const e=T.statusText||T.status;let t;try{t=await T.text()}catch{}throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Error encountered while fetching Expo token, expected an OK response, received: ${e} (body: "${t}").`)}const b=l(await f(T));try{e.url||e.baseUrl?console.debug("[expo-notifications] Since the URL endpoint to register in has been customized in the options, expo-notifications won't try to auto-update the device push token on the server."):await(0,c.setAutoServerRegistrationEnabledAsync)(!0)}catch(e){console.warn('[expo-notifications] Could not enable automatically registering new device tokens with the Expo notification service',e)}return{type:'expo',data:b}}async function f(e){try{return await e.json()}catch{try{throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Expected a JSON response from server when fetching Expo token, received body: ${JSON.stringify(await e.text())}.`)}catch{throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Expected a JSON response from server when fetching Expo token, received response: ${JSON.stringify(e)}.`)}}}function l(e){if(!e||'object'!=typeof e||!e.data||'object'!=typeof e.data||!e.data.expoPushToken||'string'!=typeof e.data.expoPushToken)throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Malformed response from server, expected "{ data: { expoPushToken: string } }", received: ${JSON.stringify(e,null,2)}.`);return e.data.expoPushToken}async function h(){try{if(!s.default.getInstallationIdAsync)throw new n.UnavailabilityError('ExpoServerRegistrationModule','getInstallationIdAsync');return await s.default.getInstallationIdAsync()}catch(e){throw new n.CodedError('ERR_NOTIF_DEVICE_ID',`Could not have fetched installation ID of the application: ${e}.`)}}function E(e){return'string'==typeof e.data?e.data:JSON.stringify(e.data)}async function I(){return!1}function y(e){switch(e.type){case'ios':return'apns';case'android':return'fcm';default:return e.type}}},1012,[1013,1006,4,1016,1020,1005]);
@@ -15044,7 +15044,7 @@ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{v
15044
15044
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"decodeOfferFragmentPayload",{enumerable:!0,get:function(){return n.decodeOfferFragmentPayload}}),Object.defineProperty(e,"buildDaemonWebSocketUrl",{enumerable:!0,get:function(){return t.buildDaemonWebSocketUrl}}),Object.defineProperty(e,"deriveLabelFromEndpoint",{enumerable:!0,get:function(){return t.deriveLabelFromEndpoint}}),Object.defineProperty(e,"extractHostPortFromWebSocketUrl",{enumerable:!0,get:function(){return t.extractHostPortFromWebSocketUrl}}),Object.defineProperty(e,"normalizeHostPort",{enumerable:!0,get:function(){return t.normalizeHostPort}}),Object.defineProperty(e,"parseConnectionUri",{enumerable:!0,get:function(){return t.parseConnectionUri}}),Object.defineProperty(e,"parseHostPort",{enumerable:!0,get:function(){return t.parseHostPort}}),Object.defineProperty(e,"serializeConnectionUri",{enumerable:!0,get:function(){return t.serializeConnectionUri}}),Object.defineProperty(e,"serializeConnectionUriForStorage",{enumerable:!0,get:function(){return t.serializeConnectionUriForStorage}}),Object.defineProperty(e,"shouldUseTlsForDefaultHostedRelay",{enumerable:!0,get:function(){return t.shouldUseTlsForDefaultHostedRelay}}),e.buildRelayWebSocketUrl=function(n){return(0,t.buildRelayWebSocketUrl)(Object.assign({},n,{role:"client"}))};var t=r(d[0]),n=r(d[1])},3399,[3379,3400]);
15045
15045
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"ConnectionOfferV2Schema",{enumerable:!0,get:function(){return t}}),Object.defineProperty(e,"ConnectionOfferSchema",{enumerable:!0,get:function(){return o}}),e.decodeOfferFragmentPayload=l,e.parseConnectionOfferFromUrl=function(n){const t=f(n);if(!t)return null;const c=l(t);return o.parse(c)};var n=r(d[0]);const t=n.z.object({v:n.z.literal(2),serverId:n.z.string().min(1),daemonPublicKeyB64:n.z.string().min(1),relay:n.z.object({endpoint:n.z.string().min(1),useTls:n.z.boolean().optional()})}),o=t;function c(n){const t=n.replace(/-/g,"+").replace(/_/g,"/"),o=t.padEnd(t.length+(4-t.length%4)%4,"="),c=globalThis.atob(o),l=Uint8Array.from(c,n=>n.charCodeAt(0));return new TextDecoder("utf-8",{fatal:!0}).decode(l)}function l(n){const t=c(n);return JSON.parse(t)}const u="#offer=";function f(n){const t=n.trim();if(!t)return null;const o=t.indexOf(u);if(-1===o)return null;const c=t.slice(o+u.length).trim();return c.length>0?c:null}},3400,[3282]);
15046
15046
  __d(function(g,r,i,a,m,_e,d){"use strict";function e(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(_e,'__esModule',{value:!0}),_e.resolveAppVersion=function(){const e=u(t.default?.version);if(e)return e;const o=u(n.default.expoConfig?.version);if(o)return o;const f=u(n.default.manifest?.version);if(f)return f;return null};var n=e(r(d[0])),t=e(r(d[1]));function u(e){if("string"!=typeof e)return null;const n=e.trim();return 0===n.length?null:n}},3401,[1006,3402]);
15047
- __d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.23",private:!0,main:"index.ts",scripts:{start:"npm run start:expo","start:expo":"cross-env APP_VARIANT=development expo start","reset-project":"node ./scripts/reset-project.js","build:terminal-webview":"node ./scripts/build-terminal-webview-html.mjs","eas-build-post-install":"npm --prefix ../.. run build:app-deps && npm run build:terminal-webview",android:"npm run android:development","android:development":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=development expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=development expo run:android --variant=debug","android:production":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=production expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=production expo run:android --variant=release","android:release":"npm run android:production","android:clear":"node -e \"require('node:fs').rmSync('android', { recursive: true, force: true })\"",ios:"npm --prefix ../.. run build:client && expo run:ios","ios:release":"npm --prefix ../.. run build:client && expo run:ios --configuration Release",web:"npm --prefix ../.. run build:client && concurrently --kill-others --names protocol,client,expo --prefix-colors yellow,blue,magenta \"npm --prefix ../.. run watch:protocol\" \"npm --prefix ../.. run watch:client\" \"npm run web:expo\"","web:expo":"expo start --web",lint:"expo lint",typecheck:"tsgo --noEmit",test:"vitest run","test:browser":"vitest run --project browser","test:e2e":"playwright test --project='Desktop Chrome'","test:e2e:real":"cross-env E2E_FORK_PASEO_HOME_FROM=../../.dev/paseo-home playwright test --project=real-provider","test:e2e:ui":"playwright test --ui","test:coverage":"vitest run --project unit --coverage",build:"npm run build:web","build:web":"npm --prefix ../.. run build:app-deps && expo export --platform web --source-maps","profile:workspace-tabs":"node ./scripts/profile-workspace-tabs.mjs","deploy:web":"npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main"},dependencies:{"@bacons/apple-targets":"4.0.6","@datadog/browser-rum":"^6.23.0","@datadog/browser-rum-react":"^6.23.0","@datadog/mobile-react-native":"^2.7.0","@datadog/mobile-react-native-session-replay":"^2.14.8","@dnd-kit/core":"^6.3.1","@dnd-kit/sortable":"^10.0.0","@dnd-kit/utilities":"^3.2.2","@expo/image-utils":"0.8.8","@expo/plist":"0.4.9","@expo/prebuild-config":"54.0.8","@floating-ui/react-native":"^0.10.7","@gorhom/bottom-sheet":"^5.2.14","@gorhom/portal":"^1.0.14","@hyperdrive.bot/paseo-client":"*","@hyperdrive.bot/paseo-expo-two-way-audio":"*","@hyperdrive.bot/paseo-extension-sdk":"*","@hyperdrive.bot/paseo-highlight":"*","@react-native-async-storage/async-storage":"2.2.0","@react-native-masked-view/masked-view":"^0.3.2","@react-native/normalize-colors":"^0.81.5","@react-navigation/native":"^7.1.8","@sentry/electron":"^6.11.0","@sentry/react-native":"^6.20.0","@tanstack/react-query":"^5.90.11","@tanstack/react-virtual":"^3.13.21","@xterm/addon-clipboard":"^0.3.0-beta.213","@xterm/addon-fit":"^0.12.0-beta.213","@xterm/addon-image":"^0.10.0-beta.213","@xterm/addon-ligatures":"0.11.0-beta.213","@xterm/addon-search":"^0.17.0-beta.213","@xterm/addon-unicode11":"^0.10.0-beta.213","@xterm/addon-web-links":"^0.13.0-beta.213","@xterm/addon-webgl":"^0.20.0-beta.212","@xterm/xterm":"^6.1.0-beta.213",buffer:"^6.0.3",expo:"^54.0.18","expo-asset":"~12.0.12","expo-audio":"~1.0.13","expo-background-fetch":"~14.0.9","expo-build-properties":"^1.0.9","expo-camera":"~17.0.10","expo-clipboard":"~8.0.7","expo-constants":"~18.0.9","expo-crypto":"^15.0.8","expo-dev-client":"^6.0.15","expo-document-picker":"~14.0.8","expo-file-system":"~19.0.17","expo-haptics":"~15.0.7","expo-image":"~3.0.10","expo-image-manipulator":"~14.0.8","expo-image-picker":"^17.0.8","expo-keep-awake":"^15.0.7","expo-linking":"~8.0.8","expo-localization":"~17.0.9","expo-notifications":"^0.32.16","expo-router":"~6.0.13","expo-sharing":"^14.0.8","expo-speech":"~14.0.8","expo-speech-recognition":"^56.0.1","expo-splash-screen":"~31.0.10","expo-system-ui":"~6.0.7","expo-task-manager":"~14.0.9","fast-deep-equal":"^3.1.3",htmlparser2:"^12.0.0",i18next:"^26.3.0","lucide-react-native":"^0.546.0","markdown-it":"^10.0.0","mnemonic-id":"^3.2.7",qrcode:"^1.5.4",react:"19.1.0","react-dom":"19.1.0","react-i18next":"^17.0.8","react-native":"0.81.5","react-native-draggable-flatlist":"^4.0.3","react-native-edge-to-edge":"^1.7.0","react-native-gesture-handler":"~2.28.0","react-native-keyboard-controller":"^1.21.7","react-native-markdown-display":"^7.0.2","react-native-nitro-modules":"0.35.5","react-native-reanimated":"~4.3.1","react-native-safe-area-context":"~5.6.0","react-native-screens":"~4.16.0","react-native-svg":"^15.14.0","react-native-uitextview":"^2.2.0","react-native-unistyles":"^3.2.4","react-native-web":"~0.21.0","react-native-webview":"^13.16.0","react-native-worklets":"~0.8.3","tiny-invariant":"^1.3.3","use-sync-external-store":"^1.6.0",zod:"^4.4.3",zustand:"^5.0.9"},devDependencies:{"@playwright/test":"^1.56.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@types/chai":"^5.2.2","@types/markdown-it":"^14.1.2","@types/qrcode":"^1.5.6","@types/react":"~19.2.0","@types/ws":"^8.18.1","@vitest/browser":"^4.1.7","@vitest/browser-playwright":"^4.1.7","@vitest/coverage-v8":"^4.1.7","@xterm/headless":"^6.1.0-beta.213",dotenv:"^17.2.3",eslint:"^9.25.0","eslint-config-expo":"~10.0.0",jsdom:"^20.0.3","material-icon-theme":"^5.32.0",playwright:"^1.56.1","serve-sim":"^0.1.40",typescript:"~5.9.2",vitest:"^4.1.6",wrangler:"^4.105.0",ws:"^8.20.0"}}},3402,[]);
15047
+ __d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.27",private:!0,main:"index.ts",scripts:{start:"npm run start:expo","start:expo":"cross-env APP_VARIANT=development expo start","reset-project":"node ./scripts/reset-project.js","build:terminal-webview":"node ./scripts/build-terminal-webview-html.mjs","eas-build-post-install":"npm --prefix ../.. run build:app-deps && npm run build:terminal-webview",android:"npm run android:development","android:development":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=development expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=development expo run:android --variant=debug","android:production":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=production expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=production expo run:android --variant=release","android:release":"npm run android:production","android:clear":"node -e \"require('node:fs').rmSync('android', { recursive: true, force: true })\"",ios:"npm --prefix ../.. run build:client && expo run:ios","ios:release":"npm --prefix ../.. run build:client && expo run:ios --configuration Release",web:"npm --prefix ../.. run build:client && concurrently --kill-others --names protocol,client,expo --prefix-colors yellow,blue,magenta \"npm --prefix ../.. run watch:protocol\" \"npm --prefix ../.. run watch:client\" \"npm run web:expo\"","web:expo":"expo start --web",lint:"expo lint",typecheck:"tsgo --noEmit",test:"vitest run","test:browser":"vitest run --project browser","test:e2e":"playwright test --project='Desktop Chrome'","test:e2e:real":"cross-env E2E_FORK_PASEO_HOME_FROM=../../.dev/paseo-home playwright test --project=real-provider","test:e2e:ui":"playwright test --ui","test:coverage":"vitest run --project unit --coverage",build:"npm run build:web","build:web":"npm --prefix ../.. run build:app-deps && expo export --platform web --source-maps","profile:workspace-tabs":"node ./scripts/profile-workspace-tabs.mjs","deploy:web":"npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main"},dependencies:{"@bacons/apple-targets":"4.0.6","@datadog/browser-rum":"^6.23.0","@datadog/browser-rum-react":"^6.23.0","@datadog/mobile-react-native":"^2.7.0","@datadog/mobile-react-native-session-replay":"^2.14.8","@dnd-kit/core":"^6.3.1","@dnd-kit/sortable":"^10.0.0","@dnd-kit/utilities":"^3.2.2","@expo/image-utils":"0.8.8","@expo/plist":"0.4.9","@expo/prebuild-config":"54.0.8","@floating-ui/react-native":"^0.10.7","@gorhom/bottom-sheet":"^5.2.14","@gorhom/portal":"^1.0.14","@hyperdrive.bot/paseo-client":"*","@hyperdrive.bot/paseo-expo-two-way-audio":"*","@hyperdrive.bot/paseo-extension-sdk":"*","@hyperdrive.bot/paseo-highlight":"*","@react-native-async-storage/async-storage":"2.2.0","@react-native-masked-view/masked-view":"^0.3.2","@react-native/normalize-colors":"^0.81.5","@react-navigation/native":"^7.1.8","@sentry/electron":"^6.11.0","@sentry/react-native":"^6.20.0","@tanstack/react-query":"^5.90.11","@tanstack/react-virtual":"^3.13.21","@xterm/addon-clipboard":"^0.3.0-beta.213","@xterm/addon-fit":"^0.12.0-beta.213","@xterm/addon-image":"^0.10.0-beta.213","@xterm/addon-ligatures":"0.11.0-beta.213","@xterm/addon-search":"^0.17.0-beta.213","@xterm/addon-unicode11":"^0.10.0-beta.213","@xterm/addon-web-links":"^0.13.0-beta.213","@xterm/addon-webgl":"^0.20.0-beta.212","@xterm/xterm":"^6.1.0-beta.213",buffer:"^6.0.3",expo:"^54.0.18","expo-asset":"~12.0.12","expo-audio":"~1.0.13","expo-background-fetch":"~14.0.9","expo-build-properties":"^1.0.9","expo-camera":"~17.0.10","expo-clipboard":"~8.0.7","expo-constants":"~18.0.9","expo-crypto":"^15.0.8","expo-dev-client":"^6.0.15","expo-document-picker":"~14.0.8","expo-file-system":"~19.0.17","expo-haptics":"~15.0.7","expo-image":"~3.0.10","expo-image-manipulator":"~14.0.8","expo-image-picker":"^17.0.8","expo-keep-awake":"^15.0.7","expo-linking":"~8.0.8","expo-localization":"~17.0.9","expo-notifications":"^0.32.16","expo-router":"~6.0.13","expo-sharing":"^14.0.8","expo-speech":"~14.0.8","expo-speech-recognition":"^56.0.1","expo-splash-screen":"~31.0.10","expo-system-ui":"~6.0.7","expo-task-manager":"~14.0.9","fast-deep-equal":"^3.1.3",htmlparser2:"^12.0.0",i18next:"^26.3.0","lucide-react-native":"^0.546.0","markdown-it":"^10.0.0","mnemonic-id":"^3.2.7",qrcode:"^1.5.4",react:"19.1.0","react-dom":"19.1.0","react-i18next":"^17.0.8","react-native":"0.81.5","react-native-draggable-flatlist":"^4.0.3","react-native-edge-to-edge":"^1.7.0","react-native-gesture-handler":"~2.28.0","react-native-keyboard-controller":"^1.21.7","react-native-markdown-display":"^7.0.2","react-native-nitro-modules":"0.35.5","react-native-reanimated":"~4.3.1","react-native-safe-area-context":"~5.6.0","react-native-screens":"~4.16.0","react-native-svg":"^15.14.0","react-native-uitextview":"^2.2.0","react-native-unistyles":"^3.2.4","react-native-web":"~0.21.0","react-native-webview":"^13.16.0","react-native-worklets":"~0.8.3","tiny-invariant":"^1.3.3","use-sync-external-store":"^1.6.0",zod:"^4.4.3",zustand:"^5.0.9"},devDependencies:{"@playwright/test":"^1.56.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@types/chai":"^5.2.2","@types/markdown-it":"^14.1.2","@types/qrcode":"^1.5.6","@types/react":"~19.2.0","@types/ws":"^8.18.1","@vitest/browser":"^4.1.7","@vitest/browser-playwright":"^4.1.7","@vitest/coverage-v8":"^4.1.7","@xterm/headless":"^6.1.0-beta.213",dotenv:"^17.2.3",eslint:"^9.25.0","eslint-config-expo":"~10.0.0",jsdom:"^20.0.3","material-icon-theme":"^5.32.0",playwright:"^1.56.1","serve-sim":"^0.1.40",typescript:"~5.9.2",vitest:"^4.1.6",wrangler:"^4.105.0",ws:"^8.20.0"}}},3402,[]);
15048
15048
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.shouldUseDesktopDaemon=function(){return(0,n.isElectronRuntime)()},e.getDesktopDaemonStatus=async function(){return c(await(0,t.invokeDesktopCommand)("desktop_daemon_status"))},e.startDesktopDaemon=async function(){return c(await(0,t.invokeDesktopCommand)("start_desktop_daemon"))},e.stopDesktopDaemon=async function(n="manual_ipc"){return c(await(0,t.invokeDesktopCommand)("stop_desktop_daemon",{reason:n}))},e.restartDesktopDaemon=async function(){return c(await(0,t.invokeDesktopCommand)("restart_desktop_daemon"))},e.getDesktopDaemonLogs=async function(){return p(await(0,t.invokeDesktopCommand)("desktop_daemon_logs"))},e.getDesktopDaemonPairing=async function(){return k(await(0,t.invokeDesktopCommand)("desktop_daemon_pairing"))},e.getCliDaemonStatus=async function(){const n=await(0,t.invokeDesktopCommand)("cli_daemon_status");if("string"!=typeof n)throw new Error("Unexpected CLI daemon status response.");return n},e.listenToLocalTransportEvents=async function(t){const u=(0,n.getDesktopHost)()?.events?.on;if("function"!=typeof u)throw new Error("Desktop events API is unavailable.");const c=await u("local-daemon-transport-event",n=>{o(n)&&t({sessionId:s(n.sessionId)??"",kind:s(n.kind)??"error",text:s(n.text),binaryBase64:s(n.binaryBase64),code:l(n.code),reason:s(n.reason),error:s(n.error)})});return"function"==typeof c?c:()=>{}},e.openLocalTransportSession=async function(n){const o=await(0,t.invokeDesktopCommand)("open_local_daemon_transport",n);if("string"!=typeof o||0===o.trim().length)throw new Error("Unexpected local transport session response.");return o},e.sendLocalTransportMessage=async function(n){await(0,t.invokeDesktopCommand)("send_local_daemon_transport_message",Object.assign({sessionId:n.sessionId},n.text?{text:n.text}:{},n.binaryBase64?{binaryBase64:n.binaryBase64}:{}))},e.closeLocalTransportSession=async function(n){await(0,t.invokeDesktopCommand)("close_local_daemon_transport",{sessionId:n})},e.getCliInstallStatus=async function(){return f(await(0,t.invokeDesktopCommand)("get_cli_install_status"))},e.installCli=async function(){return f(await(0,t.invokeDesktopCommand)("install_cli"))},e.getSkillsStatus=async function(){return y(await(0,t.invokeDesktopCommand)("get_skills_status"))},e.installSkills=async function(){return y(await(0,t.invokeDesktopCommand)("install_skills"))},e.updateSkills=async function(){return y(await(0,t.invokeDesktopCommand)("update_skills"))},e.uninstallSkills=async function(){return y(await(0,t.invokeDesktopCommand)("uninstall_skills"))};var n=r(d[0]),t=r(d[1]);function o(n){return"object"==typeof n&&null!==n}function s(n){return"string"==typeof n&&n.trim().length>0?n:null}function l(n){return"number"==typeof n&&Number.isFinite(n)?n:null}function u(n){const t=s(n)?.toLowerCase();switch(t){case"starting":return"starting";case"running":return"running";case"errored":case"error":return"errored";default:return"stopped"}}function c(n){if(!o(n))throw new Error("Unexpected desktop daemon status response.");return{serverId:s(n.serverId)??"",status:u(n.status),listen:s(n.listen),hostname:s(n.hostname),pid:l(n.pid),home:s(n.home)??"",version:s(n.version),desktopManaged:!0===n.desktopManaged,error:s(n.error)}}function p(n){if(!o(n))throw new Error("Unexpected desktop daemon logs response.");return{logPath:s(n.logPath)??"",contents:"string"==typeof n.contents?n.contents:""}}function k(n){if(!o(n))throw new Error("Unexpected desktop daemon pairing response.");return{relayEnabled:!0===n.relayEnabled,url:s(n.url),qr:s(n.qr)}}function f(n){if(!o(n))throw new Error("Unexpected install status response.");return{installed:!0===n.installed}}function w(n){switch(n){case"not-installed":case"up-to-date":case"drift":return n;default:throw new Error(`Unexpected skills status state: ${String(n)}`)}}function _(n){if(!o(n))throw new Error("Unexpected skill op response.");const t=s(n.name);if(!t)throw new Error("Skill op missing name.");switch(n.kind){case"add":return{kind:"add",name:t};case"update":return{kind:"update",name:t};case"delete":return{kind:"delete",name:t};default:throw new Error(`Unexpected skill op kind: ${String(n.kind)}`)}}function y(n){if(!o(n))throw new Error("Unexpected skills status response.");const t=Array.isArray(n.ops)?n.ops.map(_):[];return{state:w(n.state),ops:t}}},3403,[3404,3406]);
15049
15049
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.getDesktopHost=n,e.isElectronRuntime=o,e.isElectronRuntimeMac=function(){if(!o())return!1;if("undefined"==typeof navigator)return!1;const t=n()?.platform?.toLowerCase();if("darwin"===t||"mac"===t||"macos"===t)return!0;const u=navigator.userAgent;return u.includes("Mac OS")||u.includes("Macintosh")},r(d[0]);var t=r(d[1]);function n(){return(0,t.getElectronHost)()}function o(){return null!==n()}},3404,[25,3405]);
15050
15050
  __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.getElectronHost=function(){if("undefined"==typeof window)return null;const t=window.paseoDesktop;if(!t||"object"!=typeof t)return null;return t}},3405,[]);
@@ -16539,5 +16539,5 @@ __d(function(g,r,_i,a,_m,_e,d){"use strict";var e,t=r(d[0]),n=this&&this.__creat
16539
16539
  __r(975);
16540
16540
  __r(341);
16541
16541
  __r(0);
16542
- //# sourceMappingURL=/_expo/static/js/web/index-47b07b144bbc651587bf14a2bf8bc9e4.js.map
16543
- //# debugId=7cd4c1b0-8077-4285-991c-19c1481d11c5
16542
+ //# sourceMappingURL=/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.map
16543
+ //# debugId=a319d450-a568-46a4-9cc4-7d60b5c27f2b
@@ -85,6 +85,6 @@
85
85
  <body>
86
86
  <noscript>You need to enable JavaScript to run this app.</noscript>
87
87
  <div id="root"></div>
88
- <script src="/_expo/static/js/web/index-47b07b144bbc651587bf14a2bf8bc9e4.js" defer></script>
88
+ <script src="/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js" defer></script>
89
89
  </body>
90
90
  </html>
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/paseo-server",
3
- "version": "0.3.23",
3
+ "version": "0.3.27",
4
4
  "description": "Paseo backend server",
5
5
  "files": [
6
6
  "dist/server",
@@ -65,11 +65,11 @@
65
65
  "@agentclientprotocol/sdk": "^0.17.1",
66
66
  "@anthropic-ai/claude-agent-sdk": "^0.3.195",
67
67
  "@anthropic-ai/sdk": "^0.104.2",
68
- "@hyperdrive.bot/paseo-client": "0.3.23",
69
- "@hyperdrive.bot/paseo-extension-sdk": "0.3.23",
70
- "@hyperdrive.bot/paseo-highlight": "0.3.23",
71
- "@hyperdrive.bot/paseo-protocol": "0.3.23",
72
- "@hyperdrive.bot/paseo-relay": "0.3.23",
68
+ "@hyperdrive.bot/paseo-client": "0.3.27",
69
+ "@hyperdrive.bot/paseo-extension-sdk": "0.3.27",
70
+ "@hyperdrive.bot/paseo-highlight": "0.3.27",
71
+ "@hyperdrive.bot/paseo-protocol": "0.3.27",
72
+ "@hyperdrive.bot/paseo-relay": "0.3.27",
73
73
  "@isaacs/ttlcache": "^2.1.4",
74
74
  "@modelcontextprotocol/sdk": "^1.20.1",
75
75
  "@opencode-ai/sdk": "1.2.6",