@hyperdrive.bot/paseo-server 0.3.37 → 0.3.39
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/agent.d.ts +10 -0
- package/dist/server/server/agent/providers/claude/agent.js +17 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +2 -0
- package/dist/server/server/agent/providers/claude/pty-session-launcher.js +1 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +26 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +108 -6
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-ca0b24dda014cbbe8a2642bd9af67472.js → desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-772a640bc24e7edf384fb56d198ec1b5.js → desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-57ed87071e3df45480e3c20e6e4c926d.js → index-46b675f5daddb88c514f0eee26695f71.js} +1323 -1322
- package/dist/server/web-ui/_expo/static/js/web/index-46b675f5daddb88c514f0eee26695f71.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-46b675f5daddb88c514f0eee26695f71.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-46b675f5daddb88c514f0eee26695f71.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-57ed87071e3df45480e3c20e6e4c926d.js.map.gz → index-46b675f5daddb88c514f0eee26695f71.js.map.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-926f0030be75a7025e895c75f4b85a85.js → indexeddb-attachment-store-dd5ff961ec8c17df2652edf5ad3a445d.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-dd5ff961ec8c17df2652edf5ad3a445d.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-dd5ff961ec8c17df2652edf5ad3a445d.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-dd5ff961ec8c17df2652edf5ad3a445d.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-dd5ff961ec8c17df2652edf5ad3a445d.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-2533e8c49bacf836af66efee39221288.js → native-file-attachment-store-3c9685afc8b167936544f93e1997acb6.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-3c9685afc8b167936544f93e1997acb6.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-3c9685afc8b167936544f93e1997acb6.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-3c9685afc8b167936544f93e1997acb6.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-3c9685afc8b167936544f93e1997acb6.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{profiler-68be8bb2b97a9fed728062ccc384203e.js → profiler-a9a6c61297c2e9e84bdf23826f88f8fb.js} +11 -11
- package/dist/server/web-ui/_expo/static/js/web/profiler-a9a6c61297c2e9e84bdf23826f88f8fb.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-a9a6c61297c2e9e84bdf23826f88f8fb.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-a9a6c61297c2e9e84bdf23826f88f8fb.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-a9a6c61297c2e9e84bdf23826f88f8fb.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-5856a1607a3cafa2e7768e6f36918cce.js → startRecording-172521d053daae12167e1ba74a7f1ee4.js} +60 -60
- package/dist/server/web-ui/_expo/static/js/web/startRecording-172521d053daae12167e1ba74a7f1ee4.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-172521d053daae12167e1ba74a7f1ee4.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-172521d053daae12167e1ba74a7f1ee4.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-5856a1607a3cafa2e7768e6f36918cce.js.map.gz → startRecording-172521d053daae12167e1ba74a7f1ee4.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/desktop-attachment-bridge-ca0b24dda014cbbe8a2642bd9af67472.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-ca0b24dda014cbbe8a2642bd9af67472.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-ca0b24dda014cbbe8a2642bd9af67472.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-ca0b24dda014cbbe8a2642bd9af67472.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-772a640bc24e7edf384fb56d198ec1b5.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-772a640bc24e7edf384fb56d198ec1b5.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-772a640bc24e7edf384fb56d198ec1b5.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-772a640bc24e7edf384fb56d198ec1b5.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-57ed87071e3df45480e3c20e6e4c926d.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-57ed87071e3df45480e3c20e6e4c926d.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-57ed87071e3df45480e3c20e6e4c926d.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-926f0030be75a7025e895c75f4b85a85.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-926f0030be75a7025e895c75f4b85a85.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-926f0030be75a7025e895c75f4b85a85.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-926f0030be75a7025e895c75f4b85a85.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-2533e8c49bacf836af66efee39221288.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-2533e8c49bacf836af66efee39221288.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-2533e8c49bacf836af66efee39221288.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-2533e8c49bacf836af66efee39221288.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-68be8bb2b97a9fed728062ccc384203e.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-68be8bb2b97a9fed728062ccc384203e.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-68be8bb2b97a9fed728062ccc384203e.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-68be8bb2b97a9fed728062ccc384203e.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-5856a1607a3cafa2e7768e6f36918cce.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-5856a1607a3cafa2e7768e6f36918cce.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-5856a1607a3cafa2e7768e6f36918cce.js.map.br +0 -0
|
@@ -296,6 +296,16 @@ export declare class ClaudeAgentSession implements AgentSession {
|
|
|
296
296
|
* Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
|
|
297
297
|
* dependent, so guessing an index there could approve an implementation nobody chose.
|
|
298
298
|
*/
|
|
299
|
+
/**
|
|
300
|
+
* Surface a delivery-path notice to whoever sent the message.
|
|
301
|
+
*
|
|
302
|
+
* Uses the same synthetic-timeline-message channel executeRewindTurn() already uses, so
|
|
303
|
+
* this needs no protocol change and renders in every client that renders a turn today.
|
|
304
|
+
* The alternative, PtyQuery.emit(), is the SDK message stream, where the only shapes
|
|
305
|
+
* available are `result` messages -- and a result ENDS the turn, which is the opposite
|
|
306
|
+
* of what a "still queued" notice means.
|
|
307
|
+
*/
|
|
308
|
+
private emitPtyNotice;
|
|
299
309
|
private handlePtyDialogChange;
|
|
300
310
|
/** Map the app's chosen answer back to the dialog's option index and press the keys. */
|
|
301
311
|
private answerPtyDialogFromPermission;
|
|
@@ -2568,6 +2568,7 @@ export class ClaudeAgentSession {
|
|
|
2568
2568
|
: undefined,
|
|
2569
2569
|
logger: this.logger,
|
|
2570
2570
|
onDialogChange: (dialog) => this.handlePtyDialogChange(dialog),
|
|
2571
|
+
onNotice: (text) => this.emitPtyNotice(text),
|
|
2571
2572
|
});
|
|
2572
2573
|
this.activeTransport = ptySession.transport;
|
|
2573
2574
|
this.query = ptySession.query;
|
|
@@ -3660,6 +3661,22 @@ export class ClaudeAgentSession {
|
|
|
3660
3661
|
* Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
|
|
3661
3662
|
* dependent, so guessing an index there could approve an implementation nobody chose.
|
|
3662
3663
|
*/
|
|
3664
|
+
/**
|
|
3665
|
+
* Surface a delivery-path notice to whoever sent the message.
|
|
3666
|
+
*
|
|
3667
|
+
* Uses the same synthetic-timeline-message channel executeRewindTurn() already uses, so
|
|
3668
|
+
* this needs no protocol change and renders in every client that renders a turn today.
|
|
3669
|
+
* The alternative, PtyQuery.emit(), is the SDK message stream, where the only shapes
|
|
3670
|
+
* available are `result` messages -- and a result ENDS the turn, which is the opposite
|
|
3671
|
+
* of what a "still queued" notice means.
|
|
3672
|
+
*/
|
|
3673
|
+
emitPtyNotice(text) {
|
|
3674
|
+
this.notifySubscribers({
|
|
3675
|
+
type: "timeline",
|
|
3676
|
+
provider: "claude",
|
|
3677
|
+
item: { type: "assistant_message", text },
|
|
3678
|
+
});
|
|
3679
|
+
}
|
|
3663
3680
|
handlePtyDialogChange(dialog) {
|
|
3664
3681
|
if (!dialog) {
|
|
3665
3682
|
const pendingId = this.ptyDialogRequestId;
|
|
@@ -25,6 +25,8 @@ export interface CreatePtySessionOptions {
|
|
|
25
25
|
};
|
|
26
26
|
/** Surfaces TUI question/plan dialogs so the provider can raise a permission request. */
|
|
27
27
|
onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
|
|
28
|
+
/** Sender-facing notice from the delivery path (see PtyQueryOptions.onNotice). */
|
|
29
|
+
onNotice?: (text: string) => void;
|
|
28
30
|
}
|
|
29
31
|
export interface PtySession {
|
|
30
32
|
transport: PtyTransport;
|
|
@@ -56,6 +56,7 @@ export async function createPtySession(opts) {
|
|
|
56
56
|
sessionId: opts.sessionId,
|
|
57
57
|
logger: opts.logger,
|
|
58
58
|
...(opts.onDialogChange ? { onDialogChange: opts.onDialogChange } : {}),
|
|
59
|
+
...(opts.onNotice ? { onNotice: opts.onNotice } : {}),
|
|
59
60
|
});
|
|
60
61
|
opts.logger.debug({ pid: transport.context().pid, sessionId: opts.sessionId, transcriptPath }, "PTY claude session spawned");
|
|
61
62
|
return { transport, query, transcriptPath, systemPromptFilePath };
|
|
@@ -31,6 +31,16 @@ export interface PtyQueryOptions {
|
|
|
31
31
|
* the real options instead of an inert tool chip nobody can answer.
|
|
32
32
|
*/
|
|
33
33
|
onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Called with a short, sender-facing sentence when paseo knows something the person who
|
|
36
|
+
* sent the message cannot see. Today that is exactly one thing: the message was accepted
|
|
37
|
+
* but QUEUED behind a running turn, so the spinner they are looking at means "waiting in
|
|
38
|
+
* line", not "working on it".
|
|
39
|
+
*
|
|
40
|
+
* Everything PtyQuery already knew about this went to recordLine(), which appends to
|
|
41
|
+
* ~/.paseo/pty-recordings/<id>.log and reaches nobody.
|
|
42
|
+
*/
|
|
43
|
+
onNotice?: (text: string) => void;
|
|
34
44
|
}
|
|
35
45
|
export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
36
46
|
private readonly transport;
|
|
@@ -40,6 +50,9 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
40
50
|
private readonly logger;
|
|
41
51
|
private readonly reader;
|
|
42
52
|
private readonly onDialogChange;
|
|
53
|
+
private readonly onNotice;
|
|
54
|
+
/** One queued notice per message, not one per retry. Reset by deliverPrompt(). */
|
|
55
|
+
private queuedNoticeSent;
|
|
43
56
|
private readonly outQueue;
|
|
44
57
|
private readonly outResolvers;
|
|
45
58
|
private done;
|
|
@@ -192,6 +205,19 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
192
205
|
* pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
|
|
193
206
|
* keys just leaves the composer as it was.
|
|
194
207
|
*/
|
|
208
|
+
/**
|
|
209
|
+
* Is the TUI currently showing its "queued messages" footer?
|
|
210
|
+
*
|
|
211
|
+
* This reads a CLI STATUS LINE, not a rendering of our own text. The distinction matters
|
|
212
|
+
* because this file has (rightly) burned four incidents on trusting renderings as proof
|
|
213
|
+
* of DELIVERY -- bytes, then echoed text, then a raw probe, then a paste chip. Delivery
|
|
214
|
+
* is still proved by one thing only, a matching `user` record. The footer answers a
|
|
215
|
+
* different and much cheaper question: is a turn running right now. The CLI is the
|
|
216
|
+
* authority on that, and it prints the answer.
|
|
217
|
+
*/
|
|
218
|
+
private queuedFooterVisible;
|
|
219
|
+
/** Tell the sender their message is in line, once per message. */
|
|
220
|
+
private notifyQueued;
|
|
195
221
|
private clearComposer;
|
|
196
222
|
/**
|
|
197
223
|
* Deliver a prompt and prove it, or say it failed. See docs/pty-delivery.md.
|
|
@@ -102,6 +102,33 @@ const RECEIPT_EXACT_MATCH_BELOW_CHARS = 12;
|
|
|
102
102
|
const RECEIPT_WINDOW_MS = 4000;
|
|
103
103
|
/** A queued message can only land when the running turn ends, so wait far longer for it. */
|
|
104
104
|
const QUEUED_RECEIPT_WINDOW_MS = 10 * 60000;
|
|
105
|
+
/**
|
|
106
|
+
* The TUI's own words for "your message is in the queue".
|
|
107
|
+
*
|
|
108
|
+
* A message typed while a turn is running is QUEUED by claude, and a queued message
|
|
109
|
+
* appends NO `user` record until the running turn ends -- so the one delivery oracle
|
|
110
|
+
* (receiptSince) cannot fire, by construction, for minutes at a time. The old test for
|
|
111
|
+
* "is a turn running" was `lastTranscriptAt > attemptStartedAt`, i.e. "did the transcript
|
|
112
|
+
* grow during my 4s window". That is a proxy, and it reads FALSE whenever the running
|
|
113
|
+
* turn is simply quiet for four seconds (thinking, or one slow tool call). When it reads
|
|
114
|
+
* false the loop concludes the terminal is deaf and retypes.
|
|
115
|
+
*
|
|
116
|
+
* Retyping a QUEUED message is not idempotent: the composer is empty (the text already
|
|
117
|
+
* left it), so Ctrl+U clears nothing and the retype either stacks a second queued copy or
|
|
118
|
+
* concatenates onto text the clear failed to kill. Observed live 2026-08-20 on session
|
|
119
|
+
* 0b1ec622, four retries logged at 18:28:13 / 18:29:59 / 18:30:06 / 18:30:14 / 18:30:27,
|
|
120
|
+
* producing these transcript records:
|
|
121
|
+
*
|
|
122
|
+
* mint another xpmint another xp
|
|
123
|
+
* a new one with another cpf: 76797678153, caixaa new one with another cpf: ...
|
|
124
|
+
*
|
|
125
|
+
* The terminal was stating the answer the whole time. Paseo's own turn heartbeat captured
|
|
126
|
+
* it at 18:30:15: `... Press up to edit queued messages - esc to interrupt`. This reads
|
|
127
|
+
* that footer instead of inferring it from file growth.
|
|
128
|
+
*/
|
|
129
|
+
const QUEUED_FOOTER_RE = /Press up to edit queued messages/i;
|
|
130
|
+
/** How much of the tail to scan for the footer. It is repainted on every frame. */
|
|
131
|
+
const QUEUED_FOOTER_SCAN_CHARS = 2000;
|
|
105
132
|
/** Backoff between retype attempts against a deaf terminal. */
|
|
106
133
|
const DEAF_RETRY_INITIAL_MS = 2000;
|
|
107
134
|
const DEAF_RETRY_MAX_MS = 60000;
|
|
@@ -211,6 +238,8 @@ function monotonicNowMs() {
|
|
|
211
238
|
}
|
|
212
239
|
export class PtyQuery {
|
|
213
240
|
constructor(opts) {
|
|
241
|
+
/** One queued notice per message, not one per retry. Reset by deliverPrompt(). */
|
|
242
|
+
this.queuedNoticeSent = false;
|
|
214
243
|
// output queue → async iterator
|
|
215
244
|
this.outQueue = [];
|
|
216
245
|
this.outResolvers = [];
|
|
@@ -270,6 +299,7 @@ export class PtyQuery {
|
|
|
270
299
|
this.logger = opts.logger;
|
|
271
300
|
this.stallTimeoutMs = opts.stallTimeoutMs ?? TURN_STALL_TIMEOUT_MS;
|
|
272
301
|
this.onDialogChange = opts.onDialogChange ?? null;
|
|
302
|
+
this.onNotice = opts.onNotice ?? null;
|
|
273
303
|
this.reader = new TranscriptSdkReader({
|
|
274
304
|
transcriptPath: opts.transcriptPath,
|
|
275
305
|
onMessage: (m) => this.onTranscriptMessage(m),
|
|
@@ -788,9 +818,60 @@ export class PtyQuery {
|
|
|
788
818
|
* pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
|
|
789
819
|
* keys just leaves the composer as it was.
|
|
790
820
|
*/
|
|
821
|
+
/**
|
|
822
|
+
* Is the TUI currently showing its "queued messages" footer?
|
|
823
|
+
*
|
|
824
|
+
* This reads a CLI STATUS LINE, not a rendering of our own text. The distinction matters
|
|
825
|
+
* because this file has (rightly) burned four incidents on trusting renderings as proof
|
|
826
|
+
* of DELIVERY -- bytes, then echoed text, then a raw probe, then a paste chip. Delivery
|
|
827
|
+
* is still proved by one thing only, a matching `user` record. The footer answers a
|
|
828
|
+
* different and much cheaper question: is a turn running right now. The CLI is the
|
|
829
|
+
* authority on that, and it prints the answer.
|
|
830
|
+
*/
|
|
831
|
+
queuedFooterVisible() {
|
|
832
|
+
return QUEUED_FOOTER_RE.test(this.terminalTail.slice(-QUEUED_FOOTER_SCAN_CHARS));
|
|
833
|
+
}
|
|
834
|
+
/** Tell the sender their message is in line, once per message. */
|
|
835
|
+
notifyQueued() {
|
|
836
|
+
if (this.queuedNoticeSent)
|
|
837
|
+
return;
|
|
838
|
+
this.queuedNoticeSent = true;
|
|
839
|
+
try {
|
|
840
|
+
this.onNotice?.("Your message was accepted and is queued behind the turn this session is already " +
|
|
841
|
+
"running. It will start as soon as that turn finishes.");
|
|
842
|
+
}
|
|
843
|
+
catch (err) {
|
|
844
|
+
this.logger.debug({ err }, "PtyQuery: queued notice callback threw");
|
|
845
|
+
}
|
|
846
|
+
}
|
|
791
847
|
async clearComposer() {
|
|
792
848
|
try {
|
|
793
|
-
|
|
849
|
+
// Ctrl+E (end of line), Ctrl+U (kill to start), Ctrl+K (kill to end).
|
|
850
|
+
//
|
|
851
|
+
// Ctrl+U alone was not enough. In a readline-style buffer it kills from the CURSOR
|
|
852
|
+
// to the start of the line: it empties the composer only when the cursor already
|
|
853
|
+
// sits at the end, and it never touches what is after the cursor. Live 2026-08-20 a
|
|
854
|
+
// retype landed ON TOP of text a Ctrl+U was supposed to have killed, which is how
|
|
855
|
+
// session 0b1ec622 got these into its transcript:
|
|
856
|
+
//
|
|
857
|
+
// mint another xpmint another xp
|
|
858
|
+
// a new one with another cpf: 76797678153, caixaa new one with another cpf: ...
|
|
859
|
+
//
|
|
860
|
+
// This sequence is cursor-position independent and idempotent: all three keys are
|
|
861
|
+
// no-ops on an already empty composer, so it cannot damage the common case. Each
|
|
862
|
+
// gets its own beat, because the TUI only processes keys as fast as its input loop
|
|
863
|
+
// drains them.
|
|
864
|
+
//
|
|
865
|
+
// It makes the clear far more likely to succeed. It does NOT prove the composer is
|
|
866
|
+
// empty, and this file deliberately has no way to prove that: every signal for it is
|
|
867
|
+
// a rendering, and renderings are what four earlier incidents taught it not to
|
|
868
|
+
// trust. The real protection against duplication is not retyping at all once the
|
|
869
|
+
// text has demonstrably left the composer -- the queued-footer guard in
|
|
870
|
+
// deliverPrompt(), which removes the case behind every duplicate observed so far.
|
|
871
|
+
for (const key of ["\u0005", "\u0015", "\u000b"]) {
|
|
872
|
+
await this.transport.writeRaw?.(key);
|
|
873
|
+
await delay(60);
|
|
874
|
+
}
|
|
794
875
|
await delay(120);
|
|
795
876
|
}
|
|
796
877
|
catch (err) {
|
|
@@ -809,6 +890,7 @@ export class PtyQuery {
|
|
|
809
890
|
* running turn is never retyped - that is the one way to send it twice.
|
|
810
891
|
*/
|
|
811
892
|
async deliverPrompt(text) {
|
|
893
|
+
this.queuedNoticeSent = false;
|
|
812
894
|
const fingerprint = PtyQuery.fingerprint(text);
|
|
813
895
|
const watermark = this.transcriptSize();
|
|
814
896
|
const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
|
|
@@ -833,15 +915,35 @@ export class PtyQuery {
|
|
|
833
915
|
return "delivered";
|
|
834
916
|
}
|
|
835
917
|
// No receipt. Is a turn actually RUNNING (so our text is queued behind it), or was
|
|
836
|
-
// the Enter simply swallowed?
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
918
|
+
// the Enter simply swallowed? Two independent signals, ORed on purpose:
|
|
919
|
+
//
|
|
920
|
+
// - the TUI's own queued footer, which is the CLI stating the fact outright, and
|
|
921
|
+
// - real SDK messages arriving during this attempt, the original heuristic.
|
|
922
|
+
//
|
|
923
|
+
// File growth alone cannot tell queued from swallowed: claude writes `attachment`
|
|
924
|
+
// and other bookkeeping records on its own schedule, and treating that as "queued"
|
|
925
|
+
// would sit here for ten minutes while the prompt was never submitted (the
|
|
926
|
+
// 2026-08-01 bug, in reverse). But it also misses the common case, a running turn
|
|
927
|
+
// that is quiet for the four-second receipt window, and the cost of missing it is a
|
|
928
|
+
// retype that corrupts the queued prompt. The footer settles that case directly.
|
|
929
|
+
if (this.queuedFooterVisible() || this.lastTranscriptAt > attemptStartedAt) {
|
|
841
930
|
this.recordLine("--- no receipt yet; queued behind a running turn ---");
|
|
931
|
+
// Say so where the sender can see it. Once per message, not once per poll.
|
|
932
|
+
this.notifyQueued();
|
|
842
933
|
const late = await pollUntil(() => this.receiptSince(watermark, fingerprint), QUEUED_RECEIPT_WINDOW_MS, 500);
|
|
843
934
|
if (late)
|
|
844
935
|
return "delivered";
|
|
936
|
+
// Queued, and still no receipt after the full window. Do NOT fall through to the
|
|
937
|
+
// retype: the text is demonstrably out of the composer, so retyping cannot undo
|
|
938
|
+
// anything and can only stack a duplicate.
|
|
939
|
+
//
|
|
940
|
+
// "unconfirmed", never "undelivered". They are not interchangeable here:
|
|
941
|
+
// `undelivered` makes the caller emit buildSubmitFailedResult(), whose text is
|
|
942
|
+
// "Nothing was sent, so please send it again" -- a lie about a message sitting in
|
|
943
|
+
// claude's queue, and an instruction that produces the exact duplicate this branch
|
|
944
|
+
// exists to prevent. `unconfirmed` means "accepted, start not observed", which is
|
|
945
|
+
// true, and the stall backstop is already the right net for it.
|
|
946
|
+
return "unconfirmed";
|
|
845
947
|
}
|
|
846
948
|
this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: no delivery receipt for the prompt; retrying");
|
|
847
949
|
this.recordLine(`--- no delivery receipt (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},
|
|
2
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},
|
|
3
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},
|
|
4
|
-
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-
|
|
5
|
-
//# debugId=
|
|
1
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4821,[4826,4827]);
|
|
2
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},4826,[3410]);
|
|
3
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},4827,[4498,3410,52]);
|
|
4
|
+
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-a50033674b896f739a450ea6663b581b.js.map
|
|
5
|
+
//# debugId=9e1e80f7-e500-4016-8ec2-26ccbeaae816
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},
|
|
2
|
-
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-
|
|
3
|
-
//# debugId=
|
|
1
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4820,[4498]);
|
|
2
|
+
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-fd5764cba5bb72838c62c5a3c701bea1.js.map
|
|
3
|
+
//# debugId=0161bfe7-deeb-46d5-9490-d37d91b621c4
|
|
Binary file
|
|
Binary file
|
|
Binary file
|