@hyperdrive.bot/paseo-server 0.3.14 → 0.3.16

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.
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { createProviderEnv } from "../../provider-launch-config.js";
5
- import { PtyTransport } from "./transport/pty.js";
5
+ import { PtyTransport, setPtyInputSink } from "./transport/pty.js";
6
6
  import { PtyQuery } from "./transport/pty-query.js";
7
7
  import { deriveTranscriptDir } from "./transport/transcript-tailer.js";
8
8
  import { buildSpawnArgs } from "./spawn-config-writer.js";
@@ -34,6 +34,12 @@ export async function createPtySession(opts) {
34
34
  });
35
35
  const env = buildPtyEnv(opts);
36
36
  const transcriptPath = path.join(deriveTranscriptDir(realCwd), `${opts.sessionId}.jsonl`);
37
+ // Input provenance: name whatever writes a slash command into a claude terminal.
38
+ //
39
+ // Installed here, in the claude provider layer, rather than in bootstrap: bootstrap is a
40
+ // generic integration point and a test rightly rejects provider names leaking into it.
41
+ // Module-level and idempotent, so constructing many transports installs it once.
42
+ installPtyInputProvenance(opts.logger);
37
43
  const transport = new PtyTransport();
38
44
  await transport.spawn({
39
45
  binary: opts.binary,
@@ -128,4 +134,26 @@ function pretrustCwd(cwd, logger) {
128
134
  logger.warn({ err, cwd }, "PTY pre-trust failed; folder-trust prompt may block the first turn");
129
135
  }
130
136
  }
137
+ let ptyProvenanceInstalled = false;
138
+ /**
139
+ * Log the origin of every slash command written to a claude terminal. Only the command
140
+ * name and the call site are recorded, never message text. Diagnostic; remove once the
141
+ * outstanding injected-command reports are closed.
142
+ */
143
+ function installPtyInputProvenance(logger) {
144
+ if (ptyProvenanceInstalled)
145
+ return;
146
+ ptyProvenanceInstalled = true;
147
+ setPtyInputSink((event) => {
148
+ if (!event.command)
149
+ return;
150
+ logger.info({
151
+ module: "pty-input",
152
+ kind: event.kind,
153
+ command: event.command,
154
+ length: event.length,
155
+ from: event.stack,
156
+ }, "pty input: slash command written to a terminal");
157
+ });
158
+ }
131
159
  //# sourceMappingURL=pty-session-launcher.js.map
@@ -58,6 +58,10 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
58
58
  private readySettledBy;
59
59
  /** Last time the TUI footer said a turn was running. 0 = never seen. */
60
60
  private lastBusyAt;
61
+ /** Last sighting of the large-session resume dialog. 0 = never seen. */
62
+ private resumeDialogAt;
63
+ /** Set when the CLI rejected the just-submitted command; carries the CLI's own reply. */
64
+ private rejectedCommandNotice;
61
65
  private interruptPromptAt;
62
66
  constructor(opts: PtyQueryOptions);
63
67
  private emit;
@@ -75,6 +79,8 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
75
79
  * resend. Shaped as a normal result so it renders in the timeline rather than being
76
80
  * buried in the daemon log.
77
81
  */
82
+ /** The CLI refused the command; relay its own reply so the sender can correct and resend. */
83
+ private buildCommandRejectedResult;
78
84
  private buildSubmitFailedResult;
79
85
  /**
80
86
  * Press Enter to submit the composed prompt, and CONFIRM the turn actually started by
@@ -98,6 +104,26 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
98
104
  * real work, so a stale marker must never trigger it.
99
105
  */
100
106
  private clearInterruptPrompt;
107
+ /**
108
+ * Answer the large-session resume dialog with "Resume full session as-is".
109
+ *
110
+ * Must run BEFORE anything is typed: text typed while the menu is up is eaten by it, and
111
+ * an Enter confirms the pre-selected "Resume from summary", compacting the session the
112
+ * user was trying to talk to. Down moves the selection off option 1; Enter confirms
113
+ * option 2. Only acts on a recent sighting, because on any other screen a stray
114
+ * Down+Enter is at best a wasted keystroke and at worst an answer to a different menu.
115
+ */
116
+ private answerResumeDialog;
117
+ /**
118
+ * Type the prompt and confirm the terminal actually received it, retrying against a
119
+ * deaf terminal with backoff.
120
+ *
121
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
122
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
123
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
124
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
125
+ */
126
+ private typeWithEchoVerify;
101
127
  private submitTurn;
102
128
  /**
103
129
  * True once a real `user` line has been appended past `offset`. Reads only the bytes
@@ -67,6 +67,30 @@ const IDLE_PROMPT_MARKERS = [
67
67
  "for commands",
68
68
  ];
69
69
  const TURN_END_IDLE_MS = 700;
70
+ /**
71
+ * How long to wait for the TUI to react to a slash command before giving up on it. Short:
72
+ * a command either renders immediately or was swallowed, and pressing Enter again would
73
+ * run it twice.
74
+ */
75
+ const SLASH_COMMAND_ECHO_WAIT_MS = 3000;
76
+ /**
77
+ * Echo-verified delivery.
78
+ *
79
+ * A claude TUI that is still parsing a large resumed transcript PAINTS its idle footer
80
+ * before its input loop is live, and in that state it DISCARDS keystrokes: typing produces
81
+ * no echo at all. Observed 2026-08-03 on a 215MB session: attach at 13:03:00, idle footer
82
+ * by 13:03:04, prompt typed plus five Enters, zero echo in the recording, no user line,
83
+ * message lost -- while every successful delivery in the same recordings shows the typed
84
+ * text echoed back. Echo is therefore the one signal that distinguishes "idle" from
85
+ * "idle-looking but deaf", and its absence also proves the keystrokes were discarded,
86
+ * which is exactly what makes retyping safe rather than a duplication risk.
87
+ */
88
+ const ECHO_VERIFY_WINDOW_MS = 1500;
89
+ /** Minimum PTY bytes that count as an echo; a real input-box repaint is far larger. */
90
+ const ECHO_MIN_BYTES = 8;
91
+ /** Backoff between retype attempts against a deaf terminal. */
92
+ const DEAF_RETRY_INITIAL_MS = 5000;
93
+ const DEAF_RETRY_MAX_MS = 60000;
70
94
  const TERMINAL_STOP_REASONS = new Set(["end_turn", "stop_sequence", "max_tokens"]);
71
95
  /**
72
96
  * Liveness backstop: how long an in-flight turn may append NOTHING to the transcript
@@ -121,6 +145,28 @@ const LIVENESS_HEARTBEAT_MS = 30000;
121
145
  * sighting is recent, because sending ESC blindly would interrupt a healthy running turn.
122
146
  */
123
147
  const INTERRUPT_PROMPT_MARKER = "what should claude do instead";
148
+ /**
149
+ * The CLI's large-session resume dialog. Resuming a session past a usage threshold shows
150
+ *
151
+ * This session is Nd old and NNNk tokens. ...We recommend resuming from a summary.
152
+ * > 1. Resume from summary (recommended) <- PRE-SELECTED
153
+ * 2. Resume full session as-is
154
+ * 3. Don't ask me again
155
+ *
156
+ * "Resume from summary" runs /compact, and confirming it is recorded in the transcript as
157
+ * a USER-TYPED /compact (entrypoint cli, userType external) -- indistinguishable from real
158
+ * input. Left unhandled, paseo's flow walks straight into it: the dialog is quiet, so the
159
+ * readiness gate settles; the user's message is typed into the menu and eaten; and the
160
+ * submit Enter confirms the pre-selected compaction. Proven by isolated replication
161
+ * 2026-08-03: two sessions compacted from a single Enter with paseo not involved, a third
162
+ * resumed intact when option 2 was selected instead.
163
+ *
164
+ * The answer is option 2: the user asked to talk to this session, so resume it whole.
165
+ * Selecting it takes one Down (to move off option 1) and one Enter.
166
+ */
167
+ const RESUME_DIALOG_MARKER = "resume from summary";
168
+ /** How long a sighting stays actionable; mirrors INTERRUPT_PROMPT_TTL_MS reasoning. */
169
+ const RESUME_DIALOG_TTL_MS = 5 * 60000;
124
170
  /** How long a sighting stays actionable. Beyond this, assume the modal is long gone. */
125
171
  const INTERRUPT_PROMPT_TTL_MS = 5 * 60000;
126
172
  /**
@@ -188,6 +234,10 @@ export class PtyQuery {
188
234
  this.readySettledBy = null;
189
235
  /** Last time the TUI footer said a turn was running. 0 = never seen. */
190
236
  this.lastBusyAt = 0;
237
+ /** Last sighting of the large-session resume dialog. 0 = never seen. */
238
+ this.resumeDialogAt = 0;
239
+ /** Set when the CLI rejected the just-submitted command; carries the CLI's own reply. */
240
+ this.rejectedCommandNotice = null;
191
241
  this.interruptPromptAt = 0;
192
242
  this.transport = opts.transport;
193
243
  this.input = opts.input;
@@ -383,14 +433,36 @@ export class PtyQuery {
383
433
  if (!text)
384
434
  continue;
385
435
  this.turnStartedAt = monotonicNowMs();
386
- // Clear a modal BEFORE typing, not after: text typed into the interrupt prompt is
387
- // consumed by the dialog, so recovering at submit time is already too late.
436
+ // Clear modals BEFORE typing, not after: text typed into either dialog is
437
+ // consumed by it, so recovering at submit time is already too late.
438
+ await this.answerResumeDialog();
388
439
  await this.clearInterruptPrompt();
389
- await this.transport.write(text);
390
- await delay(200);
391
- const started = await this.submitTurn();
440
+ // Deliberately NOT normalized (e.g. lowercasing an auto-capitalized command
441
+ // token): paseo must never silently rewrite what the user typed. A wrong command is
442
+ // handled by relaying the CLI's own rejection -- including its "did you mean"
443
+ // suggestion -- back to the sender, which corrects every rejection cause honestly
444
+ // instead of hiding one narrow cause invisibly.
445
+ const typed = await this.typeWithEchoVerify(text);
446
+ if (this.done)
447
+ break;
448
+ if (!typed) {
449
+ // The terminal stayed deaf for the whole ceiling: keystrokes discarded, nothing
450
+ // delivered, and only the sender can decide what to do next.
451
+ this.recordLine("--- terminal deaf for the whole budget; message not delivered ---");
452
+ this.emit(this.buildSubmitFailedResult());
453
+ continue;
454
+ }
455
+ const started = await this.submitTurn(text);
392
456
  if (!started && this.done)
393
457
  break;
458
+ if (!started && this.rejectedCommandNotice) {
459
+ // Not lost, refused: hand the CLI's own answer (which includes its "did you
460
+ // mean" suggestion) to the sender instead of a phantom running turn.
461
+ const notice = this.rejectedCommandNotice;
462
+ this.rejectedCommandNotice = null;
463
+ this.emit(this.buildCommandRejectedResult(notice));
464
+ continue;
465
+ }
394
466
  if (!started && !this.looksIdle()) {
395
467
  // NOT a lost message. The TUI accepts keystrokes into a queue while a turn is
396
468
  // running and only writes the user line when that turn ends, which can be far
@@ -429,6 +501,16 @@ export class PtyQuery {
429
501
  * resend. Shaped as a normal result so it renders in the timeline rather than being
430
502
  * buried in the daemon log.
431
503
  */
504
+ /** The CLI refused the command; relay its own reply so the sender can correct and resend. */
505
+ buildCommandRejectedResult(notice) {
506
+ const base = this.buildResult("submit_failed");
507
+ return {
508
+ ...base,
509
+ subtype: "error_during_execution",
510
+ is_error: true,
511
+ errors: [`The agent's CLI rejected the command: ${notice} Nothing was run.`],
512
+ };
513
+ }
432
514
  buildSubmitFailedResult() {
433
515
  const base = this.buildResult("submit_failed");
434
516
  const excerpt = this.terminalTail.slice(-TERMINAL_TAIL_EXCERPT_CHARS).trim();
@@ -486,8 +568,112 @@ export class PtyQuery {
486
568
  }
487
569
  this.interruptPromptAt = 0;
488
570
  }
489
- async submitTurn() {
571
+ /**
572
+ * Answer the large-session resume dialog with "Resume full session as-is".
573
+ *
574
+ * Must run BEFORE anything is typed: text typed while the menu is up is eaten by it, and
575
+ * an Enter confirms the pre-selected "Resume from summary", compacting the session the
576
+ * user was trying to talk to. Down moves the selection off option 1; Enter confirms
577
+ * option 2. Only acts on a recent sighting, because on any other screen a stray
578
+ * Down+Enter is at best a wasted keystroke and at worst an answer to a different menu.
579
+ */
580
+ async answerResumeDialog() {
581
+ if (!this.resumeDialogAt)
582
+ return;
583
+ if (monotonicNowMs() - this.resumeDialogAt > RESUME_DIALOG_TTL_MS) {
584
+ this.resumeDialogAt = 0;
585
+ return;
586
+ }
587
+ this.logger.warn({ sessionId: this.sessionId, lastLine: this.lastMeaningfulLine.slice(-120) }, "PtyQuery: large-session resume dialog is showing; selecting 'Resume full session as-is'");
588
+ this.recordLine("--- resume dialog: selecting 'Resume full session as-is' ---");
589
+ await this.transport.writeRaw?.("\u001b[B"); // Down: off the pre-selected compact option
590
+ await delay(300);
591
+ await this.transport.writeRaw?.("\r");
592
+ await delay(500);
593
+ this.resumeDialogAt = 0;
594
+ }
595
+ /**
596
+ * Type the prompt and confirm the terminal actually received it, retrying against a
597
+ * deaf terminal with backoff.
598
+ *
599
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
600
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
601
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
602
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
603
+ */
604
+ async typeWithEchoVerify(text) {
605
+ const transcriptBefore = this.transcriptSize();
606
+ const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
607
+ let backoff = DEAF_RETRY_INITIAL_MS;
608
+ let attempt = 0;
609
+ while (!this.done && monotonicNowMs() < deadline) {
610
+ attempt += 1;
611
+ const bytesBefore = this.ptyBytesTotal;
612
+ await this.transport.write(text);
613
+ const echoed = await pollUntil(() => this.ptyBytesTotal - bytesBefore >= ECHO_MIN_BYTES, ECHO_VERIFY_WINDOW_MS, 100);
614
+ if (echoed) {
615
+ if (attempt > 1) {
616
+ this.recordLine(`--- terminal came back after ${attempt - 1} deaf attempt(s); prompt delivered ---`);
617
+ }
618
+ await delay(200);
619
+ return true;
620
+ }
621
+ // Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe.
622
+ if (this.sawUserLineSince(transcriptBefore))
623
+ return true;
624
+ this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: terminal painted but is not accepting input yet; holding the prompt");
625
+ this.recordLine(`--- terminal deaf (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
626
+ await delay(backoff);
627
+ backoff = Math.min(backoff * 2, DEAF_RETRY_MAX_MS);
628
+ }
629
+ return false;
630
+ }
631
+ async submitTurn(text) {
490
632
  const before = this.transcriptSize();
633
+ // A slash command gets EXACTLY ONE Enter. Never retry it.
634
+ //
635
+ // Typing "/x" opens the TUI's own command menu, and in that state each Enter runs the
636
+ // highlighted entry. The retry loop below was added to beat a swallowed first Enter on
637
+ // a booting terminal, and for ordinary prompts it works; for a slash command it
638
+ // re-executes the command once per attempt. Captured on a recording:
639
+ //
640
+ // /compact <- written once by paseo
641
+ // /compact Free up context by summarizing… <- the TUI's menu opens
642
+ // > /compact > /compact Not enough messages to compact.
643
+ //
644
+ // Two executions from one message. That is why sessions compacted twice, and why a
645
+ // second /compact could land on an already-compacted session at a few thousand tokens.
646
+ // Confirmation cannot come from the transcript here either: a slash command does not
647
+ // append the user line that sawUserLineSince() waits for, so the loop never sees
648
+ // success and always burns all five attempts.
649
+ //
650
+ // Instead, confirm from the terminal: the command was accepted if the TUI produced
651
+ // output in response. If it produced nothing, report it rather than pressing again --
652
+ // a lost slash command is recoverable by resending, a duplicated one is not.
653
+ if (text.trim().startsWith("/")) {
654
+ const bytesBefore = this.ptyBytesTotal;
655
+ await this.transport.writeRaw?.("\r");
656
+ const reacted = await pollUntil(() => this.sawUserLineSince(before) || this.ptyBytesTotal > bytesBefore, SLASH_COMMAND_ECHO_WAIT_MS, 150);
657
+ // The terminal responding is not the same as the command running. An unknown command
658
+ // is answered with `Unknown command: /X. Did you mean /y?` and NOTHING starts -- but
659
+ // that reply previously counted as acceptance, so the app showed a running turn that
660
+ // did not exist and the sender saw dead silence. Observed live: a phone-keyboard
661
+ // auto-capitalized "/WhatsApp-read" rejected twice while the UI said "working".
662
+ // Checked regardless of the byte-poll outcome: the rejection can repaint before the
663
+ // poll's baseline is captured, in which case `reacted` is false but the refusal is
664
+ // already sitting in the tail.
665
+ await delay(400);
666
+ const m = this.terminalTail.slice(-500).match(/Unknown command:[^\n]{0,160}/i);
667
+ if (m) {
668
+ this.rejectedCommandNotice = m[0].trim();
669
+ this.recordLine(`--- CLI rejected the command: ${this.rejectedCommandNotice} ---`);
670
+ return false;
671
+ }
672
+ if (!reacted) {
673
+ this.logger.warn({ sessionId: this.sessionId, command: text.trim().split(/\s/, 1)[0] }, "PtyQuery: slash command produced no terminal response; not retrying to avoid double execution");
674
+ }
675
+ return reacted;
676
+ }
491
677
  for (let attempt = 0; attempt < 5 && !this.done; attempt++) {
492
678
  await this.transport.writeRaw?.("\r");
493
679
  if (await pollUntil(() => this.sawUserLineSince(before), 2500, 150))
@@ -689,6 +875,9 @@ export class PtyQuery {
689
875
  if (line.toLowerCase().includes(INTERRUPT_PROMPT_MARKER)) {
690
876
  this.interruptPromptAt = monotonicNowMs();
691
877
  }
878
+ if (line.toLowerCase().includes(RESUME_DIALOG_MARKER)) {
879
+ this.resumeDialogAt = monotonicNowMs();
880
+ }
692
881
  this.recordLine(line);
693
882
  }
694
883
  if (this.terminalTail.length > TERMINAL_TAIL_MAX_CHARS) {
@@ -1,6 +1,32 @@
1
1
  import type { HookEvent } from "@hyperdrive.bot/paseo-protocol/messages";
2
2
  import type { AgentTransport, AgentTransportContext, AgentTransportSpawnOptions } from "./types.js";
3
3
  type NodePtyModule = typeof import("node-pty");
4
+ /**
5
+ * Input provenance sink.
6
+ *
7
+ * Every byte paseo puts into a claude terminal passes through write()/writeRaw(), so this
8
+ * is the ONE seam no code path can bypass. That matters: an earlier trace was placed at
9
+ * sendPromptToAgent, which looked like the seam every prompt crosses, and a `/compact`
10
+ * reached a session at 22:01:44 without ever appearing in it. Slash commands are written
11
+ * directly to the transport from at least two other places (`/model`, `/effort` in
12
+ * agent.ts), so a prompt-level trace was always going to have holes.
13
+ *
14
+ * Wired as a module-level sink rather than a constructor arg so it can be installed
15
+ * without touching every construction site, and so it is a no-op (one null check) when
16
+ * nobody is listening.
17
+ */
18
+ export interface PtyInputEvent {
19
+ /** "write" = bracketed paste (a prompt); "writeRaw" = keystrokes (Enter, control chords). */
20
+ kind: "write" | "writeRaw";
21
+ /** First token only when the payload is a slash command, else null. */
22
+ command: string | null;
23
+ /** Payload length; the body itself is never captured. */
24
+ length: number;
25
+ /** Call site, so the responsible code path names itself. */
26
+ stack: string;
27
+ }
28
+ /** Install (or clear, with null) the input-provenance sink. */
29
+ export declare function setPtyInputSink(sink: ((event: PtyInputEvent) => void) | null): void;
4
30
  export declare function __setNodePtyForTesting(stub: NodePtyModule | null): void;
5
31
  export declare class PtyTransport implements AgentTransport {
6
32
  private _pty;
@@ -8,6 +8,29 @@ const DEFAULT_KILL_TIMEOUT_MS = 5000;
8
8
  const ECHO_WAIT_MS = 50;
9
9
  const BRACKETED_PASTE_START = "\x1b[200~";
10
10
  const BRACKETED_PASTE_END = "\x1b[201~";
11
+ let ptyInputSink = null;
12
+ /** Install (or clear, with null) the input-provenance sink. */
13
+ export function setPtyInputSink(sink) {
14
+ ptyInputSink = sink;
15
+ }
16
+ function reportInput(kind, payload) {
17
+ if (!ptyInputSink)
18
+ return;
19
+ const trimmed = payload.trim();
20
+ // Only slash commands are named. Ordinary prompt text is never captured, only its length.
21
+ const command = trimmed.startsWith("/") ? (trimmed.split(/\s/, 1)[0] ?? null) : null;
22
+ const stack = (new Error().stack ?? "")
23
+ .split("\n")
24
+ .slice(2, 8)
25
+ .map((l) => l.trim())
26
+ .join(" | ");
27
+ try {
28
+ ptyInputSink({ kind, command, length: payload.length, stack });
29
+ }
30
+ catch {
31
+ // Telemetry must never break input delivery.
32
+ }
33
+ }
11
34
  let nodePty = null;
12
35
  function getNodePty() {
13
36
  if (nodePty)
@@ -96,6 +119,7 @@ export class PtyTransport {
96
119
  });
97
120
  }
98
121
  write(text) {
122
+ reportInput("write", text);
99
123
  this.injectChain = this.injectChain.then(() => this.doWrite(text));
100
124
  return this.injectChain;
101
125
  }
@@ -106,6 +130,7 @@ export class PtyTransport {
106
130
  * inject chain as write() so it can't interleave mid-paste.
107
131
  */
108
132
  writeRaw(data) {
133
+ reportInput("writeRaw", data);
109
134
  this.injectChain = this.injectChain.then(() => this.doWriteRaw(data));
110
135
  return this.injectChain;
111
136
  }
@@ -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.14\",\"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.16\",\"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.14",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.16",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,[]);
@@ -16538,5 +16538,5 @@ __d(function(g,r,_i,a,_m,_e,d){"use strict";var e,t=r(d[0]),n=this&&this.__creat
16538
16538
  __r(975);
16539
16539
  __r(341);
16540
16540
  __r(0);
16541
- //# sourceMappingURL=/_expo/static/js/web/index-77c17325c5c43a39b1485d39e15ee152.js.map
16542
- //# debugId=2454f8f3-d756-4b3e-b9db-de016a8f9ccc
16541
+ //# sourceMappingURL=/_expo/static/js/web/index-bb2e4887026c2e6d1254dee6e102847d.js.map
16542
+ //# debugId=be672a5b-d536-40fb-aef8-47922046962c
@@ -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-77c17325c5c43a39b1485d39e15ee152.js" defer></script>
88
+ <script src="/_expo/static/js/web/index-bb2e4887026c2e6d1254dee6e102847d.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.14",
3
+ "version": "0.3.16",
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.14",
69
- "@hyperdrive.bot/paseo-extension-sdk": "0.3.14",
70
- "@hyperdrive.bot/paseo-highlight": "0.3.14",
71
- "@hyperdrive.bot/paseo-protocol": "0.3.14",
72
- "@hyperdrive.bot/paseo-relay": "0.3.14",
68
+ "@hyperdrive.bot/paseo-client": "0.3.16",
69
+ "@hyperdrive.bot/paseo-extension-sdk": "0.3.16",
70
+ "@hyperdrive.bot/paseo-highlight": "0.3.16",
71
+ "@hyperdrive.bot/paseo-protocol": "0.3.16",
72
+ "@hyperdrive.bot/paseo-relay": "0.3.16",
73
73
  "@isaacs/ttlcache": "^2.1.4",
74
74
  "@modelcontextprotocol/sdk": "^1.20.1",
75
75
  "@opencode-ai/sdk": "1.2.6",