@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.
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +62 -2
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +175 -86
- package/dist/server/web-ui/_expo/static/js/web/{index-045767cf5362f85583f2649254d4471a.js → index-f8bb839b399b809d73376d962613fce2.js} +4 -4
- package/dist/server/web-ui/_expo/static/js/web/index-f8bb839b399b809d73376d962613fce2.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-045767cf5362f85583f2649254d4471a.js.gz → index-f8bb839b399b809d73376d962613fce2.js.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-045767cf5362f85583f2649254d4471a.js.map.br → index-f8bb839b399b809d73376d962613fce2.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-045767cf5362f85583f2649254d4471a.js.map.gz → index-f8bb839b399b809d73376d962613fce2.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-045767cf5362f85583f2649254d4471a.js.br +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
495
|
+
const delivery = await this.deliverPrompt(text);
|
|
490
496
|
if (this.done)
|
|
491
497
|
break;
|
|
492
|
-
if (
|
|
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 (
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
|
|
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
|
-
|
|
798
|
-
|
|
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
|
-
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
this.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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(`---
|
|
831
|
+
this.recordLine(`--- delivered on attempt ${attempt} ---`);
|
|
823
832
|
}
|
|
824
|
-
|
|
825
|
-
return true;
|
|
833
|
+
return "delivered";
|
|
826
834
|
}
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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
|
|
851
|
+
return "undelivered";
|
|
836
852
|
}
|
|
837
|
-
|
|
838
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
-
//
|
|
889
|
-
//
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
923
|
-
|
|
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
|
|
1003
|
+
return out;
|
|
930
1004
|
}
|
|
931
1005
|
catch {
|
|
932
|
-
return
|
|
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.
|
|
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.
|
|
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-
|
|
16543
|
-
//# debugId=
|
|
16542
|
+
//# sourceMappingURL=/_expo/static/js/web/index-f8bb839b399b809d73376d962613fce2.js.map
|
|
16543
|
+
//# debugId=2fd27551-57e7-48c2-a288-88c7228210fa
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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-
|
|
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.
|
|
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.
|
|
69
|
-
"@hyperdrive.bot/paseo-extension-sdk": "0.3.
|
|
70
|
-
"@hyperdrive.bot/paseo-highlight": "0.3.
|
|
71
|
-
"@hyperdrive.bot/paseo-protocol": "0.3.
|
|
72
|
-
"@hyperdrive.bot/paseo-relay": "0.3.
|
|
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",
|