@hyperdrive.bot/paseo-server 0.3.26 → 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.
@@ -187,14 +187,74 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
187
187
  * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
188
188
  * (zero echo), and every iteration first checks whether a user line landed anyway.
189
189
  */
190
- private typeWithEchoVerify;
190
+ /**
191
+ * Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
192
+ * pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
193
+ * keys just leaves the composer as it was.
194
+ */
195
+ private clearComposer;
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
+ */
191
233
  private submitTurn;
192
234
  /**
193
235
  * True once a real `user` line has been appended past `offset`. Reads only the bytes
194
236
  * added since then, so it stays cheap on a transcript that can reach tens of MB.
195
237
  * `isMeta` user lines are claude's own bookkeeping and do not mean a turn started.
196
238
  */
197
- 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;
198
258
  private transcriptSize;
199
259
  private onTranscriptMessage;
200
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,9 +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;
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;
99
105
  /** Backoff between retype attempts against a deaf terminal. */
100
- const DEAF_RETRY_INITIAL_MS = 5000;
106
+ const DEAF_RETRY_INITIAL_MS = 2000;
101
107
  const DEAF_RETRY_MAX_MS = 60000;
102
108
  const TERMINAL_STOP_REASONS = new Set(["end_turn", "stop_sequence", "max_tokens"]);
103
109
  /**
@@ -486,45 +492,28 @@ export class PtyQuery {
486
492
  // handled by relaying the CLI's own rejection -- including its "did you mean"
487
493
  // suggestion -- back to the sender, which corrects every rejection cause honestly
488
494
  // instead of hiding one narrow cause invisibly.
489
- const typed = await this.typeWithEchoVerify(text);
495
+ const delivery = await this.deliverPrompt(text);
490
496
  if (this.done)
491
497
  break;
492
- if (!typed) {
493
- // The terminal stayed deaf for the whole ceiling: keystrokes discarded, nothing
494
- // delivered, and only the sender can decide what to do next.
495
- this.recordLine("--- terminal deaf for the whole budget; message not delivered ---");
496
- this.emit(this.buildSubmitFailedResult());
497
- continue;
498
- }
499
- const started = await this.submitTurn(text);
500
- if (!started && this.done)
501
- break;
502
- if (!started && this.rejectedCommandNotice) {
498
+ if (delivery === "rejected") {
503
499
  // Not lost, refused: hand the CLI's own answer (which includes its "did you
504
500
  // mean" suggestion) to the sender instead of a phantom running turn.
505
- const notice = this.rejectedCommandNotice;
501
+ const notice = this.rejectedCommandNotice ?? "the command was not recognized.";
506
502
  this.rejectedCommandNotice = null;
507
503
  this.emit(this.buildCommandRejectedResult(notice));
508
504
  continue;
509
505
  }
510
- if (!started && !this.looksIdle()) {
511
- // NOT a lost message. The TUI accepts keystrokes into a queue while a turn is
512
- // running and only writes the user line when that turn ends, which can be far
513
- // longer than submitTurn() waits. Observed live: reported failed at 23:04:40,
514
- // delivered at 23:04:57. Telling the user it was lost is worse than saying
515
- // nothing, because they resend and the agent gets it twice.
516
- //
517
- // A busy terminal has accepted the input, so treat the turn as started and let
518
- // the stall backstop be the thing that notices if it never runs.
519
- this.recordLine("--- submit queued behind a running turn ---");
520
- }
521
- else if (!started) {
522
- // Idle terminal, five Enters, still no user line: the prompt genuinely did not
523
- // land and only the user can resend it.
524
- 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 ---");
525
510
  this.emit(this.buildSubmitFailedResult());
526
511
  continue;
527
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.
528
517
  // The turn is now claude's to answer; start the liveness clock. submitTurn() has
529
518
  // confirmed claude accepted it (a real user line landed), so from here on silence
530
519
  // means a stall, not a slow start.
@@ -794,48 +783,100 @@ export class PtyQuery {
794
783
  * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
795
784
  * (zero echo), and every iteration first checks whether a user line landed anyway.
796
785
  */
797
- async typeWithEchoVerify(text) {
798
- const transcriptBefore = this.transcriptSize();
786
+ /**
787
+ * Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
788
+ * pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
789
+ * keys just leaves the composer as it was.
790
+ */
791
+ async clearComposer() {
792
+ try {
793
+ await this.transport.writeRaw?.("\u0015");
794
+ await delay(120);
795
+ }
796
+ catch (err) {
797
+ this.logger.debug({ err }, "PtyQuery: could not clear the composer before retyping");
798
+ }
799
+ }
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();
799
814
  const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
800
815
  let backoff = DEAF_RETRY_INITIAL_MS;
801
816
  let attempt = 0;
802
817
  while (!this.done && monotonicNowMs() < deadline) {
803
818
  attempt += 1;
804
- // The needle is whitespace-stripped on both sides: the TUI wraps and re-spaces the
805
- // echoed text freely (recordings show "Replywithexactly:PROBE_OK"), so only the
806
- // character sequence survives rendering, never the spacing. Verified against a RAW
807
- // probe buffer, never the rolling tail: the tail's noise filter drops lines with
808
- // fewer than three letters, which swallowed the echo of a short message outright.
809
- const needle = text.replace(/\s+/g, "").slice(0, ECHO_TEXT_FRAGMENT_CHARS);
810
- this.echoProbe = { buf: "" };
811
- let echoed = false;
812
- try {
813
- await this.transport.write(text);
814
- const probe = this.echoProbe;
815
- echoed = await pollUntil(() => (needle ? probe.buf.includes(needle) : probe.buf.length > 0), ECHO_VERIFY_WINDOW_MS, 100);
816
- }
817
- finally {
818
- this.echoProbe = null;
819
- }
820
- 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") {
821
830
  if (attempt > 1) {
822
- this.recordLine(`--- terminal came back after ${attempt - 1} deaf attempt(s); prompt delivered ---`);
831
+ this.recordLine(`--- delivered on attempt ${attempt} ---`);
823
832
  }
824
- await delay(200);
825
- return true;
833
+ return "delivered";
826
834
  }
827
- // Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe.
828
- if (this.sawUserLineSince(transcriptBefore))
829
- return true;
830
- this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: terminal painted but is not accepting input yet; holding the prompt");
831
- 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 ---`);
832
848
  await delay(backoff);
833
849
  backoff = Math.min(backoff * 2, DEAF_RETRY_MAX_MS);
834
850
  }
835
- return false;
851
+ return "undelivered";
836
852
  }
837
- async submitTurn(text) {
838
- 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;
839
880
  // A slash command gets EXACTLY ONE Enter. Never retry it.
840
881
  //
841
882
  // Typing "/x" opens the TUI's own command menu, and in that state each Enter runs the
@@ -859,7 +900,7 @@ export class PtyQuery {
859
900
  if (text.trim().startsWith("/")) {
860
901
  const bytesBefore = this.ptyBytesTotal;
861
902
  await this.transport.writeRaw?.("\r");
862
- 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);
863
904
  // The terminal responding is not the same as the command running. An unknown command
864
905
  // is answered with `Unknown command: /X. Did you mean /y?` and NOTHING starts -- but
865
906
  // that reply previously counted as acceptance, so the app showed a running turn that
@@ -873,42 +914,74 @@ export class PtyQuery {
873
914
  if (m) {
874
915
  this.rejectedCommandNotice = m[0].trim();
875
916
  this.recordLine(`--- CLI rejected the command: ${this.rejectedCommandNotice} ---`);
876
- return false;
917
+ return "rejected";
877
918
  }
878
919
  if (!reacted) {
879
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");
880
921
  }
881
- 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";
882
927
  }
883
- for (let attempt = 0; attempt < 5 && !this.done; attempt++) {
884
- await this.transport.writeRaw?.("\r");
885
- if (await pollUntil(() => this.sawUserLineSince(before), 2500, 150))
886
- return true;
928
+ await this.transport.writeRaw?.("\r");
929
+ if (await pollUntil(() => this.receiptSince(before, fingerprint), RECEIPT_WINDOW_MS, 150)) {
930
+ return "delivered";
887
931
  }
888
- // Say WHICH it is. A busy terminal has queued the keystrokes and will run them when the
889
- // current turn ends, so warning identically for both trained every reader (and the
890
- // alerting built on this line) to treat a normal queue as a lost message.
891
- const queued = !this.looksIdle();
892
- this.logger[queued ? "info" : "warn"]({
893
- sessionId: this.sessionId,
894
- queuedBehindRunningTurn: queued,
895
- lastLine: this.lastMeaningfulLine.slice(-120),
896
- }, queued
897
- ? "PtyQuery: prompt queued behind a running turn, no user line yet"
898
- : "PtyQuery: turn did not start after submit retries");
899
- 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";
900
936
  }
901
937
  /**
902
938
  * True once a real `user` line has been appended past `offset`. Reads only the bytes
903
939
  * added since then, so it stays cheap on a transcript that can reach tens of MB.
904
940
  * `isMeta` user lines are claude's own bookkeeping and do not mean a turn started.
905
941
  */
906
- 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) {
907
979
  let fd = null;
980
+ const out = [];
908
981
  try {
909
982
  const size = this.transcriptSize();
910
983
  if (size <= offset)
911
- return false;
984
+ return out;
912
985
  fd = fs.openSync(this.transcriptPath, "r");
913
986
  const length = size - offset;
914
987
  const buffer = Buffer.alloc(length);
@@ -919,17 +992,18 @@ export class PtyQuery {
919
992
  try {
920
993
  // A trailing partial line simply fails to parse; the next poll sees it whole.
921
994
  const parsed = JSON.parse(line);
922
- if (parsed?.type === "user" && !parsed.isMeta)
923
- return true;
995
+ if (parsed?.type !== "user" || parsed.isMeta)
996
+ continue;
997
+ out.push(userRecordText(parsed.message?.content));
924
998
  }
925
999
  catch {
926
1000
  /* partial or non-JSON line */
927
1001
  }
928
1002
  }
929
- return false;
1003
+ return out;
930
1004
  }
931
1005
  catch {
932
- return false;
1006
+ return out;
933
1007
  }
934
1008
  finally {
935
1009
  if (fd !== null) {
@@ -1344,6 +1418,21 @@ function matchDialogOption(text, options) {
1344
1418
  const byLabel = options.findIndex((o) => o.trim().toLowerCase() === lowered);
1345
1419
  return byLabel >= 0 ? byLabel : null;
1346
1420
  }
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(" ");
1435
+ }
1347
1436
  function isRecord(value) {
1348
1437
  return !!value && typeof value === "object" && !Array.isArray(value);
1349
1438
  }
@@ -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.26\",\"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.26",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-045767cf5362f85583f2649254d4471a.js.map
16543
- //# debugId=810a9939-800e-468d-a63e-6ad656f9655c
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-045767cf5362f85583f2649254d4471a.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.26",
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.26",
69
- "@hyperdrive.bot/paseo-extension-sdk": "0.3.26",
70
- "@hyperdrive.bot/paseo-highlight": "0.3.26",
71
- "@hyperdrive.bot/paseo-protocol": "0.3.26",
72
- "@hyperdrive.bot/paseo-relay": "0.3.26",
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",