@hyperdrive.bot/paseo-server 0.3.27 → 0.3.28

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.
@@ -193,14 +193,68 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
193
193
  * keys just leaves the composer as it was.
194
194
  */
195
195
  private clearComposer;
196
- private typeWithEchoVerify;
196
+ /**
197
+ * Deliver a prompt and prove it, or say it failed. See docs/pty-delivery.md.
198
+ *
199
+ * One authority: a transcript user record matching the text. Nothing the terminal paints
200
+ * gates this - echoed characters, paste chips, boot noise and footer hints are renderings,
201
+ * and renderings changed with every CLI version while this code kept "discovering" new
202
+ * ones (bytes -> text -> raw probe -> paste chip, four incidents, same defect each time).
203
+ *
204
+ * Retries are safe because the composer is cleared first, and a message QUEUED behind a
205
+ * running turn is never retyped - that is the one way to send it twice.
206
+ */
207
+ private deliverPrompt;
208
+ /**
209
+ * Press Enter to submit the composed prompt, and CONFIRM the turn actually started by
210
+ * watching for the USER LINE claude appends to <sid>.jsonl the instant it accepts a turn.
211
+ * During boot the TUI intermittently swallows the first Enter (a "what's new" notice /
212
+ * MCP-auth warning steals focus), so retry until it takes.
213
+ *
214
+ * The confirmation used to be `transcriptSize() > before`, which is any growth at all.
215
+ * That is not specific to the turn starting: claude writes `attachment`, `last-prompt`
216
+ * and other bookkeeping lines on its own schedule, so a swallowed Enter that happened to
217
+ * coincide with unrelated writes read as success. Observed in production 2026-08-01: a
218
+ * session logged 157 consecutive `attachment` lines and NOT ONE `user` line, yet
219
+ * submitTurn returned on the first attempt. The prompt stayed unsent in the TUI composer,
220
+ * paseo set turnInFlight and waited on a turn that had never begun, and the agent showed
221
+ * a spinner until the liveness backstop fired ten minutes later. The retry loop below was
222
+ * always right; the thing it was checking was not.
223
+ */
224
+ /**
225
+ * Type the prompt and confirm the terminal actually received it, retrying against a
226
+ * deaf terminal with backoff.
227
+ *
228
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
229
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
230
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
231
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
232
+ */
197
233
  private submitTurn;
198
234
  /**
199
235
  * True once a real `user` line has been appended past `offset`. Reads only the bytes
200
236
  * added since then, so it stays cheap on a transcript that can reach tens of MB.
201
237
  * `isMeta` user lines are claude's own bookkeeping and do not mean a turn started.
202
238
  */
203
- private sawUserLineSince;
239
+ /**
240
+ * Fingerprint used to match a transcript record against what we typed. Whitespace is
241
+ * stripped on both sides because the terminal is free to re-wrap and re-space anything
242
+ * it renders, while the transcript keeps the text verbatim - so the character sequence
243
+ * is the part that survives every rendering.
244
+ */
245
+ private static fingerprint;
246
+ /**
247
+ * THE delivery authority: did claude append a user record matching what we sent?
248
+ *
249
+ * Content-matched on purpose. The old check accepted ANY user record past the watermark,
250
+ * which cannot tell "my message became a turn" apart from an unrelated record (a resume
251
+ * dialog answering itself, a queued earlier message). Matching the text answers the only
252
+ * question that matters, and answers it identically whether the terminal echoed the
253
+ * characters, collapsed them into a paste chip, or drew nothing at all.
254
+ */
255
+ private receiptSince;
256
+ /** Text of every non-meta user record written past `offset`. */
257
+ private readUserRecordsSince;
204
258
  private transcriptSize;
205
259
  private onTranscriptMessage;
206
260
  /**
@@ -86,7 +86,6 @@ const SLASH_COMMAND_ECHO_WAIT_MS = 3000;
86
86
  * "idle-looking but deaf", and its absence also proves the keystrokes were discarded,
87
87
  * which is exactly what makes retyping safe rather than a duplication risk.
88
88
  */
89
- const ECHO_VERIFY_WINDOW_MS = 1500;
90
89
  /**
91
90
  * How much of the typed text must be found in the terminal output for it to count as an
92
91
  * echo. Byte-counting was not enough: a freshly spawned claude under load keeps painting
@@ -95,18 +94,16 @@ const ECHO_VERIFY_WINDOW_MS = 1500;
95
94
  * spawns failed that way in five minutes on a loaded box, 2026-08-03. Boot output never
96
95
  * contains the user's message; a real input-box echo always does.
97
96
  */
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"];
97
+ /** How much of the message identifies it in a transcript record. */
98
+ const RECEIPT_FINGERPRINT_CHARS = 64;
99
+ /** Below this length a fingerprint must match a record WHOLE, never as a substring. */
100
+ const RECEIPT_EXACT_MATCH_BELOW_CHARS = 12;
101
+ /** How long one delivery attempt waits for its transcript receipt before retrying. */
102
+ const RECEIPT_WINDOW_MS = 4000;
103
+ /** A queued message can only land when the running turn ends, so wait far longer for it. */
104
+ const QUEUED_RECEIPT_WINDOW_MS = 10 * 60000;
108
105
  /** Backoff between retype attempts against a deaf terminal. */
109
- const DEAF_RETRY_INITIAL_MS = 5000;
106
+ const DEAF_RETRY_INITIAL_MS = 2000;
110
107
  const DEAF_RETRY_MAX_MS = 60000;
111
108
  const TERMINAL_STOP_REASONS = new Set(["end_turn", "stop_sequence", "max_tokens"]);
112
109
  /**
@@ -495,45 +492,28 @@ export class PtyQuery {
495
492
  // handled by relaying the CLI's own rejection -- including its "did you mean"
496
493
  // suggestion -- back to the sender, which corrects every rejection cause honestly
497
494
  // instead of hiding one narrow cause invisibly.
498
- const typed = await this.typeWithEchoVerify(text);
495
+ const delivery = await this.deliverPrompt(text);
499
496
  if (this.done)
500
497
  break;
501
- if (!typed) {
502
- // The terminal stayed deaf for the whole ceiling: keystrokes discarded, nothing
503
- // delivered, and only the sender can decide what to do next.
504
- this.recordLine("--- terminal deaf for the whole budget; message not delivered ---");
505
- this.emit(this.buildSubmitFailedResult());
506
- continue;
507
- }
508
- const started = await this.submitTurn(text);
509
- if (!started && this.done)
510
- break;
511
- if (!started && this.rejectedCommandNotice) {
498
+ if (delivery === "rejected") {
512
499
  // Not lost, refused: hand the CLI's own answer (which includes its "did you
513
500
  // mean" suggestion) to the sender instead of a phantom running turn.
514
- const notice = this.rejectedCommandNotice;
501
+ const notice = this.rejectedCommandNotice ?? "the command was not recognized.";
515
502
  this.rejectedCommandNotice = null;
516
503
  this.emit(this.buildCommandRejectedResult(notice));
517
504
  continue;
518
505
  }
519
- if (!started && !this.looksIdle()) {
520
- // NOT a lost message. The TUI accepts keystrokes into a queue while a turn is
521
- // running and only writes the user line when that turn ends, which can be far
522
- // longer than submitTurn() waits. Observed live: reported failed at 23:04:40,
523
- // delivered at 23:04:57. Telling the user it was lost is worse than saying
524
- // nothing, because they resend and the agent gets it twice.
525
- //
526
- // A busy terminal has accepted the input, so treat the turn as started and let
527
- // the stall backstop be the thing that notices if it never runs.
528
- this.recordLine("--- submit queued behind a running turn ---");
529
- }
530
- else if (!started) {
531
- // Idle terminal, five Enters, still no user line: the prompt genuinely did not
532
- // land and only the user can resend it.
533
- this.recordLine("--- submit failed: prompt never became a turn ---");
506
+ if (delivery === "undelivered") {
507
+ // No transcript receipt for the whole budget: the message never became a turn,
508
+ // nothing was queued, and only the sender can decide what to do next.
509
+ this.recordLine("--- no delivery receipt within the budget; message not delivered ---");
534
510
  this.emit(this.buildSubmitFailedResult());
535
511
  continue;
536
512
  }
513
+ // "unconfirmed" is the slash-command case: exactly one Enter was sent and the CLI
514
+ // reacted, but a slash command may append no user record to confirm against.
515
+ // Treat it as started and let the stall backstop notice if it never runs -- never
516
+ // press Enter again, which would run the command twice.
537
517
  // The turn is now claude's to answer; start the liveness clock. submitTurn() has
538
518
  // confirmed claude accepted it (a real user line landed), so from here on silence
539
519
  // means a stall, not a slow start.
@@ -817,51 +797,86 @@ export class PtyQuery {
817
797
  this.logger.debug({ err }, "PtyQuery: could not clear the composer before retyping");
818
798
  }
819
799
  }
820
- async typeWithEchoVerify(text) {
821
- const transcriptBefore = this.transcriptSize();
800
+ /**
801
+ * Deliver a prompt and prove it, or say it failed. See docs/pty-delivery.md.
802
+ *
803
+ * One authority: a transcript user record matching the text. Nothing the terminal paints
804
+ * gates this - echoed characters, paste chips, boot noise and footer hints are renderings,
805
+ * and renderings changed with every CLI version while this code kept "discovering" new
806
+ * ones (bytes -> text -> raw probe -> paste chip, four incidents, same defect each time).
807
+ *
808
+ * Retries are safe because the composer is cleared first, and a message QUEUED behind a
809
+ * running turn is never retyped - that is the one way to send it twice.
810
+ */
811
+ async deliverPrompt(text) {
812
+ const fingerprint = PtyQuery.fingerprint(text);
813
+ const watermark = this.transcriptSize();
822
814
  const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
823
815
  let backoff = DEAF_RETRY_INITIAL_MS;
824
816
  let attempt = 0;
825
817
  while (!this.done && monotonicNowMs() < deadline) {
826
818
  attempt += 1;
827
- // The needle is whitespace-stripped on both sides: the TUI wraps and re-spaces the
828
- // echoed text freely (recordings show "Replywithexactly:PROBE_OK"), so only the
829
- // character sequence survives rendering, never the spacing. Verified against a RAW
830
- // probe buffer, never the rolling tail: the tail's noise filter drops lines with
831
- // fewer than three letters, which swallowed the echo of a short message outright.
832
- const needle = text.replace(/\s+/g, "").slice(0, ECHO_TEXT_FRAGMENT_CHARS);
833
- this.echoProbe = { buf: "" };
834
- let echoed = false;
835
- try {
836
- await this.transport.write(text);
837
- const probe = this.echoProbe;
838
- echoed = await pollUntil(() => sawEcho(probe.buf, needle), ECHO_VERIFY_WINDOW_MS, 100);
839
- }
840
- finally {
841
- this.echoProbe = null;
842
- }
843
- if (echoed) {
819
+ const attemptStartedAt = monotonicNowMs();
820
+ // Empty the input box first: whatever an earlier attempt may have left there is
821
+ // undelivered by definition (no receipt), and typing on top of it is how one message
822
+ // became nine stacked paste blocks in production.
823
+ if (attempt > 1)
824
+ await this.clearComposer();
825
+ await this.transport.write(text);
826
+ const submitted = await this.submitTurn(text, watermark, fingerprint);
827
+ if (submitted === "rejected")
828
+ return "rejected";
829
+ if (submitted === "delivered") {
844
830
  if (attempt > 1) {
845
- this.recordLine(`--- terminal came back after ${attempt - 1} deaf attempt(s); prompt delivered ---`);
831
+ this.recordLine(`--- delivered on attempt ${attempt} ---`);
846
832
  }
847
- await delay(200);
848
- return true;
833
+ return "delivered";
849
834
  }
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.
853
- if (this.sawUserLineSince(transcriptBefore))
854
- return true;
855
- await this.clearComposer();
856
- this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: terminal painted but is not accepting input yet; holding the prompt");
857
- this.recordLine(`--- terminal deaf (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
835
+ // No receipt. Is a turn actually RUNNING (so our text is queued behind it), or was
836
+ // the Enter simply swallowed? File growth cannot tell those apart: claude writes
837
+ // `attachment` and other bookkeeping records on its own schedule, and treating that
838
+ // as "queued" would sit here for ten minutes while the prompt was never submitted
839
+ // (the 2026-08-01 bug, in reverse). Real SDK messages arriving IS a running turn.
840
+ if (this.lastTranscriptAt > attemptStartedAt) {
841
+ this.recordLine("--- no receipt yet; queued behind a running turn ---");
842
+ const late = await pollUntil(() => this.receiptSince(watermark, fingerprint), QUEUED_RECEIPT_WINDOW_MS, 500);
843
+ if (late)
844
+ return "delivered";
845
+ }
846
+ this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: no delivery receipt for the prompt; retrying");
847
+ this.recordLine(`--- no delivery receipt (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
858
848
  await delay(backoff);
859
849
  backoff = Math.min(backoff * 2, DEAF_RETRY_MAX_MS);
860
850
  }
861
- return false;
851
+ return "undelivered";
862
852
  }
863
- async submitTurn(text) {
864
- const before = this.transcriptSize();
853
+ /**
854
+ * Press Enter to submit the composed prompt, and CONFIRM the turn actually started by
855
+ * watching for the USER LINE claude appends to <sid>.jsonl the instant it accepts a turn.
856
+ * During boot the TUI intermittently swallows the first Enter (a "what's new" notice /
857
+ * MCP-auth warning steals focus), so retry until it takes.
858
+ *
859
+ * The confirmation used to be `transcriptSize() > before`, which is any growth at all.
860
+ * That is not specific to the turn starting: claude writes `attachment`, `last-prompt`
861
+ * and other bookkeeping lines on its own schedule, so a swallowed Enter that happened to
862
+ * coincide with unrelated writes read as success. Observed in production 2026-08-01: a
863
+ * session logged 157 consecutive `attachment` lines and NOT ONE `user` line, yet
864
+ * submitTurn returned on the first attempt. The prompt stayed unsent in the TUI composer,
865
+ * paseo set turnInFlight and waited on a turn that had never begun, and the agent showed
866
+ * a spinner until the liveness backstop fired ten minutes later. The retry loop below was
867
+ * always right; the thing it was checking was not.
868
+ */
869
+ /**
870
+ * Type the prompt and confirm the terminal actually received it, retrying against a
871
+ * deaf terminal with backoff.
872
+ *
873
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
874
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
875
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
876
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
877
+ */
878
+ async submitTurn(text, watermark, fingerprint) {
879
+ const before = watermark;
865
880
  // A slash command gets EXACTLY ONE Enter. Never retry it.
866
881
  //
867
882
  // Typing "/x" opens the TUI's own command menu, and in that state each Enter runs the
@@ -885,7 +900,7 @@ export class PtyQuery {
885
900
  if (text.trim().startsWith("/")) {
886
901
  const bytesBefore = this.ptyBytesTotal;
887
902
  await this.transport.writeRaw?.("\r");
888
- const reacted = await pollUntil(() => this.sawUserLineSince(before) || this.ptyBytesTotal > bytesBefore, SLASH_COMMAND_ECHO_WAIT_MS, 150);
903
+ const reacted = await pollUntil(() => this.receiptSince(before, fingerprint) || this.ptyBytesTotal > bytesBefore, SLASH_COMMAND_ECHO_WAIT_MS, 150);
889
904
  // The terminal responding is not the same as the command running. An unknown command
890
905
  // is answered with `Unknown command: /X. Did you mean /y?` and NOTHING starts -- but
891
906
  // that reply previously counted as acceptance, so the app showed a running turn that
@@ -899,42 +914,74 @@ export class PtyQuery {
899
914
  if (m) {
900
915
  this.rejectedCommandNotice = m[0].trim();
901
916
  this.recordLine(`--- CLI rejected the command: ${this.rejectedCommandNotice} ---`);
902
- return false;
917
+ return "rejected";
903
918
  }
904
919
  if (!reacted) {
905
920
  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");
906
921
  }
907
- return reacted;
922
+ // A slash command may not append a user record at all (version-dependent), so the
923
+ // CLI's own reaction stands in for the receipt here. This reads a CLI RESPONSE, not
924
+ // a rendering, and it is never retried: a lost slash command is recoverable by
925
+ // resending, a duplicated one is not.
926
+ return reacted ? "delivered" : "unconfirmed";
908
927
  }
909
- for (let attempt = 0; attempt < 5 && !this.done; attempt++) {
910
- await this.transport.writeRaw?.("\r");
911
- if (await pollUntil(() => this.sawUserLineSince(before), 2500, 150))
912
- return true;
928
+ await this.transport.writeRaw?.("\r");
929
+ if (await pollUntil(() => this.receiptSince(before, fingerprint), RECEIPT_WINDOW_MS, 150)) {
930
+ return "delivered";
913
931
  }
914
- // Say WHICH it is. A busy terminal has queued the keystrokes and will run them when the
915
- // current turn ends, so warning identically for both trained every reader (and the
916
- // alerting built on this line) to treat a normal queue as a lost message.
917
- const queued = !this.looksIdle();
918
- this.logger[queued ? "info" : "warn"]({
919
- sessionId: this.sessionId,
920
- queuedBehindRunningTurn: queued,
921
- lastLine: this.lastMeaningfulLine.slice(-120),
922
- }, queued
923
- ? "PtyQuery: prompt queued behind a running turn, no user line yet"
924
- : "PtyQuery: turn did not start after submit retries");
925
- return false;
932
+ // No receipt from this attempt. The delivery loop decides what that means: it can see
933
+ // whether the transcript is moving (queued behind a running turn) and owns the retry.
934
+ this.logger.debug({ sessionId: this.sessionId, lastLine: this.lastMeaningfulLine.slice(-120) }, "PtyQuery: no receipt for this submit attempt");
935
+ return "unconfirmed";
926
936
  }
927
937
  /**
928
938
  * True once a real `user` line has been appended past `offset`. Reads only the bytes
929
939
  * added since then, so it stays cheap on a transcript that can reach tens of MB.
930
940
  * `isMeta` user lines are claude's own bookkeeping and do not mean a turn started.
931
941
  */
932
- sawUserLineSince(offset) {
942
+ /**
943
+ * Fingerprint used to match a transcript record against what we typed. Whitespace is
944
+ * stripped on both sides because the terminal is free to re-wrap and re-space anything
945
+ * it renders, while the transcript keeps the text verbatim - so the character sequence
946
+ * is the part that survives every rendering.
947
+ */
948
+ static fingerprint(text) {
949
+ return text.replace(/\s+/g, "").slice(0, RECEIPT_FINGERPRINT_CHARS);
950
+ }
951
+ /**
952
+ * THE delivery authority: did claude append a user record matching what we sent?
953
+ *
954
+ * Content-matched on purpose. The old check accepted ANY user record past the watermark,
955
+ * which cannot tell "my message became a turn" apart from an unrelated record (a resume
956
+ * dialog answering itself, a queued earlier message). Matching the text answers the only
957
+ * question that matters, and answers it identically whether the terminal echoed the
958
+ * characters, collapsed them into a paste chip, or drew nothing at all.
959
+ */
960
+ receiptSince(offset, fingerprint) {
961
+ for (const record of this.readUserRecordsSince(offset)) {
962
+ const body = PtyQuery.fingerprint(record);
963
+ if (!fingerprint)
964
+ return true;
965
+ // Short messages must match whole, or a two-letter fingerprint like "ok" would
966
+ // happily match an unrelated record that merely contains those letters.
967
+ if (fingerprint.length < RECEIPT_EXACT_MATCH_BELOW_CHARS) {
968
+ if (body === fingerprint)
969
+ return true;
970
+ continue;
971
+ }
972
+ if (body.includes(fingerprint))
973
+ return true;
974
+ }
975
+ return false;
976
+ }
977
+ /** Text of every non-meta user record written past `offset`. */
978
+ readUserRecordsSince(offset) {
933
979
  let fd = null;
980
+ const out = [];
934
981
  try {
935
982
  const size = this.transcriptSize();
936
983
  if (size <= offset)
937
- return false;
984
+ return out;
938
985
  fd = fs.openSync(this.transcriptPath, "r");
939
986
  const length = size - offset;
940
987
  const buffer = Buffer.alloc(length);
@@ -945,17 +992,18 @@ export class PtyQuery {
945
992
  try {
946
993
  // A trailing partial line simply fails to parse; the next poll sees it whole.
947
994
  const parsed = JSON.parse(line);
948
- if (parsed?.type === "user" && !parsed.isMeta)
949
- return true;
995
+ if (parsed?.type !== "user" || parsed.isMeta)
996
+ continue;
997
+ out.push(userRecordText(parsed.message?.content));
950
998
  }
951
999
  catch {
952
1000
  /* partial or non-JSON line */
953
1001
  }
954
1002
  }
955
- return false;
1003
+ return out;
956
1004
  }
957
1005
  catch {
958
- return false;
1006
+ return out;
959
1007
  }
960
1008
  finally {
961
1009
  if (fd !== null) {
@@ -1370,17 +1418,20 @@ function matchDialogOption(text, options) {
1370
1418
  const byLabel = options.findIndex((o) => o.trim().toLowerCase() === lowered);
1371
1419
  return byLabel >= 0 ? byLabel : null;
1372
1420
  }
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;
1421
+ /** Flatten a transcript user record's content to plain text for fingerprint matching. */
1422
+ function userRecordText(content) {
1423
+ if (typeof content === "string")
1424
+ return content;
1425
+ if (!Array.isArray(content))
1426
+ return "";
1427
+ const parts = [];
1428
+ for (const block of content) {
1429
+ if (typeof block === "string")
1430
+ parts.push(block);
1431
+ else if (isRecord(block) && typeof block.text === "string")
1432
+ parts.push(block.text);
1433
+ }
1434
+ return parts.join(" ");
1384
1435
  }
1385
1436
  function isRecord(value) {
1386
1437
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -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.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]);
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.28\",\"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.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,[]);
15047
+ __d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.28",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-11f21e9e7b941cc98daf2ca7e3a78592.js.map
16543
- //# debugId=a319d450-a568-46a4-9cc4-7d60b5c27f2b
16542
+ //# sourceMappingURL=/_expo/static/js/web/index-f8bb839b399b809d73376d962613fce2.js.map
16543
+ //# debugId=2fd27551-57e7-48c2-a288-88c7228210fa
@@ -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-11f21e9e7b941cc98daf2ca7e3a78592.js" defer></script>
88
+ <script src="/_expo/static/js/web/index-f8bb839b399b809d73376d962613fce2.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.27",
3
+ "version": "0.3.28",
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.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",
68
+ "@hyperdrive.bot/paseo-client": "0.3.28",
69
+ "@hyperdrive.bot/paseo-extension-sdk": "0.3.28",
70
+ "@hyperdrive.bot/paseo-highlight": "0.3.28",
71
+ "@hyperdrive.bot/paseo-protocol": "0.3.28",
72
+ "@hyperdrive.bot/paseo-relay": "0.3.28",
73
73
  "@isaacs/ttlcache": "^2.1.4",
74
74
  "@modelcontextprotocol/sdk": "^1.20.1",
75
75
  "@opencode-ai/sdk": "1.2.6",