@hyperdrive.bot/paseo-server 0.3.39 → 0.3.40
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/README.md +3 -3
- package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +33 -1
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +110 -5
- package/dist/server/server/agent/providers/claude/transport/pty.d.ts +32 -0
- package/dist/server/server/agent/providers/claude/transport/pty.js +69 -5
- package/dist/server/server/agent/providers/claude/transport/sdk.d.ts +2 -0
- package/dist/server/server/agent/providers/claude/transport/sdk.js +4 -0
- package/dist/server/server/agent/providers/claude/transport/types.d.ts +8 -0
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +13 -1
- package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +27 -3
- package/dist/server/server/session.js +6 -2
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js → index-73ebfe5c6b82437cad59d50a40d2c8ef.js} +4 -4
- package/dist/server/web-ui/_expo/static/js/web/index-73ebfe5c6b82437cad59d50a40d2c8ef.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js.gz → index-73ebfe5c6b82437cad59d50a40d2c8ef.js.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js.map.br → index-73ebfe5c6b82437cad59d50a40d2c8ef.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-46b675f5daddb88c514f0eee26695f71.js.map.gz → index-73ebfe5c6b82437cad59d50a40d2c8ef.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-46b675f5daddb88c514f0eee26695f71.js.br +0 -0
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ npm run dev
|
|
|
24
24
|
- **Vite Dev Server** (port 5173) - Hot-reload React UI in development
|
|
25
25
|
- **WebSocket** (`/ws`) - Real-time bidirectional communication
|
|
26
26
|
- **Agent** - STT → LLM → TTS pipeline with terminal control
|
|
27
|
-
- **Daemon** -
|
|
27
|
+
- **Daemon** - node-pty terminal management (in-process)
|
|
28
28
|
|
|
29
29
|
## Development
|
|
30
30
|
|
|
@@ -57,7 +57,7 @@ npm start
|
|
|
57
57
|
|
|
58
58
|
**⏳ In Progress** (Phase 3):
|
|
59
59
|
|
|
60
|
-
- Terminal control (
|
|
60
|
+
- Terminal control (node-pty integration)
|
|
61
61
|
|
|
62
62
|
**📋 Planned** (Phases 4-9):
|
|
63
63
|
|
|
@@ -92,7 +92,7 @@ PASEO_HOME=~/.paseo-blue PASEO_LISTEN=127.0.0.1:7777 npm run dev
|
|
|
92
92
|
|
|
93
93
|
- **Server**: Express, TypeScript, ws (WebSocket)
|
|
94
94
|
- **Client**: React 18, Vite, TypeScript
|
|
95
|
-
- **Terminal**:
|
|
95
|
+
- **Terminal**: node-pty (in-process PTY) + @xterm/headless (screen model)
|
|
96
96
|
- **AI**: OpenAI (LLM + TTS), Deepgram (STT)
|
|
97
97
|
|
|
98
98
|
## Testing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
2
|
import type { ModelInfo, SDKMessage, SDKSystemMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
3
|
-
import type
|
|
3
|
+
import { type PtyTransport } from "./pty.js";
|
|
4
4
|
/** An interactive TUI dialog the user has to answer for the turn to continue. */
|
|
5
5
|
export interface PtyInteractiveDialog {
|
|
6
6
|
toolUseId: string;
|
|
@@ -58,6 +58,10 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
58
58
|
private done;
|
|
59
59
|
private readonly readyPromise;
|
|
60
60
|
private offReadyData;
|
|
61
|
+
/** Unsubscribes the transport exit tap. Teardown MUST call this. */
|
|
62
|
+
private offExitTap;
|
|
63
|
+
/** Set once the agent process has exited; the two remaining "still running" claims read it. */
|
|
64
|
+
private transportExited;
|
|
61
65
|
/**
|
|
62
66
|
* Cancels the readiness gate and its timers.
|
|
63
67
|
*
|
|
@@ -133,6 +137,16 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
133
137
|
* agent running by the time the input loop sees it: silence here is a forever-spinner.
|
|
134
138
|
*/
|
|
135
139
|
private buildNothingDeliverableResult;
|
|
140
|
+
/**
|
|
141
|
+
* " The session itself is still running." -- but only when it is.
|
|
142
|
+
*
|
|
143
|
+
* The exit tap fixes the in-flight turn and the stall message. These two other results
|
|
144
|
+
* asserted the same thing unconditionally and are reachable after the process is gone, so
|
|
145
|
+
* they need the same correction. Wording matches buildExitedResult: no "aborted", and
|
|
146
|
+
* "status" rather than "code", so isAbortError() cannot swallow it and
|
|
147
|
+
* buildTurnFailedEvent() cannot scrape a phantom exit code out of it.
|
|
148
|
+
*/
|
|
149
|
+
private stillRunningClause;
|
|
136
150
|
/** Tell the provider the dialog state changed; never let a listener throw into the loop. */
|
|
137
151
|
private notifyDialogChange;
|
|
138
152
|
/** The dialog the TUI is waiting on, if any. */
|
|
@@ -344,6 +358,24 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
344
358
|
*/
|
|
345
359
|
private readTerminalTail;
|
|
346
360
|
private buildResult;
|
|
361
|
+
/**
|
|
362
|
+
* The agent process exited. Fail any in-flight turn immediately and end the query.
|
|
363
|
+
*
|
|
364
|
+
* Unlike a stall there is nothing to wait for: no retry, no recovery, no later transcript
|
|
365
|
+
* activity. Emitting here rather than letting the stall timer fire turns a ten-minute wait
|
|
366
|
+
* plus a false "still running" into an immediate, accurate failure, and finishing the query
|
|
367
|
+
* stops the layer above treating this transport as a live destination for prompts.
|
|
368
|
+
*/
|
|
369
|
+
private onTransportExit;
|
|
370
|
+
/**
|
|
371
|
+
* A non-success `result` for an agent that exited.
|
|
372
|
+
*
|
|
373
|
+
* Wording constraints match {@link buildStalledResult}: `isAbortError()` drops results
|
|
374
|
+
* matching /\baborted\b/i and `buildTurnFailedEvent()` scrapes /\bcode\s+(\d+)\b/i for an
|
|
375
|
+
* exit code, so this says "status", never "code", and never says "aborted". It also must
|
|
376
|
+
* NOT claim the session is still running, which is the exact lie this change deletes.
|
|
377
|
+
*/
|
|
378
|
+
private buildExitedResult;
|
|
347
379
|
private buildStalledResult;
|
|
348
380
|
interrupt(): Promise<void>;
|
|
349
381
|
setPermissionMode(): Promise<void>;
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { PtyExitedError } from "./pty.js";
|
|
5
6
|
import { TranscriptSdkReader } from "./transcript-sdk-reader.js";
|
|
6
7
|
/**
|
|
7
8
|
* PtyQuery — a `Query`-shaped adapter over an interactive `claude` PTY.
|
|
@@ -245,6 +246,10 @@ export class PtyQuery {
|
|
|
245
246
|
this.outResolvers = [];
|
|
246
247
|
this.done = false;
|
|
247
248
|
this.offReadyData = null;
|
|
249
|
+
/** Unsubscribes the transport exit tap. Teardown MUST call this. */
|
|
250
|
+
this.offExitTap = null;
|
|
251
|
+
/** Set once the agent process has exited; the two remaining "still running" claims read it. */
|
|
252
|
+
this.transportExited = null;
|
|
248
253
|
/**
|
|
249
254
|
* Cancels the readiness gate and its timers.
|
|
250
255
|
*
|
|
@@ -311,6 +316,12 @@ export class PtyQuery {
|
|
|
311
316
|
// session so a stall has something to explain itself with. Chunks arrive already redacted (PtyTransport.onData
|
|
312
317
|
// runs redactChunk), so nothing further is needed here.
|
|
313
318
|
this.offTerminalTap = this.transport.onData((chunk) => this.appendTerminalTail(chunk));
|
|
319
|
+
// Learn about the agent process dying the moment it happens. Without this tap the only
|
|
320
|
+
// thing that ever noticed was the stall backstop, ten minutes later, and it then told the
|
|
321
|
+
// user the session was "still running and may recover" - of a process that had already
|
|
322
|
+
// printed its farewell banner and exited. The stall timer is for a live agent that is
|
|
323
|
+
// slow; it is the wrong instrument for a dead one.
|
|
324
|
+
this.offExitTap = this.transport.onExit((code, signal) => this.onTransportExit(code, signal));
|
|
314
325
|
// The executor stays trivial and settlement lives outside it, so there is exactly one
|
|
315
326
|
// resolve() call in the file. Timers and handlers below all funnel through settle(),
|
|
316
327
|
// which is guarded by `settled`; expressing that inside the executor tripped
|
|
@@ -467,6 +478,8 @@ export class PtyQuery {
|
|
|
467
478
|
this.offReadyData?.();
|
|
468
479
|
this.offTerminalTap?.();
|
|
469
480
|
this.offTerminalTap = null;
|
|
481
|
+
this.offExitTap?.();
|
|
482
|
+
this.offExitTap = null;
|
|
470
483
|
this.stopHeartbeat();
|
|
471
484
|
this.recordLine("--- PtyQuery detached ---");
|
|
472
485
|
try {
|
|
@@ -533,6 +546,13 @@ export class PtyQuery {
|
|
|
533
546
|
this.emit(this.buildCommandRejectedResult(notice));
|
|
534
547
|
continue;
|
|
535
548
|
}
|
|
549
|
+
if (delivery === "exited") {
|
|
550
|
+
// Dead, not deaf. `continue`, never `break`: the exit tap is what ends this query,
|
|
551
|
+
// and breaking here would instead leave the input channel undrained, so a resend
|
|
552
|
+
// would sit unread and the sender would watch a spinner - the symptom being fixed.
|
|
553
|
+
this.emit(this.buildExitedResult(this.transportExited?.code ?? null, this.transportExited?.signal ?? null));
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
536
556
|
if (delivery === "undelivered") {
|
|
537
557
|
// No transcript receipt for the whole budget: the message never became a turn,
|
|
538
558
|
// nothing was queued, and only the sender can decide what to do next.
|
|
@@ -620,11 +640,27 @@ export class PtyQuery {
|
|
|
620
640
|
is_error: true,
|
|
621
641
|
errors: [
|
|
622
642
|
`Your message had nothing the terminal transport could deliver: no text, and no ` +
|
|
623
|
-
`image it could stage to disk. Nothing was sent, so please resend it with text
|
|
624
|
-
|
|
643
|
+
`image it could stage to disk. Nothing was sent, so please resend it with text.` +
|
|
644
|
+
`${this.stillRunningClause()}`,
|
|
625
645
|
],
|
|
626
646
|
};
|
|
627
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* " The session itself is still running." -- but only when it is.
|
|
650
|
+
*
|
|
651
|
+
* The exit tap fixes the in-flight turn and the stall message. These two other results
|
|
652
|
+
* asserted the same thing unconditionally and are reachable after the process is gone, so
|
|
653
|
+
* they need the same correction. Wording matches buildExitedResult: no "aborted", and
|
|
654
|
+
* "status" rather than "code", so isAbortError() cannot swallow it and
|
|
655
|
+
* buildTurnFailedEvent() cannot scrape a phantom exit code out of it.
|
|
656
|
+
*/
|
|
657
|
+
stillRunningClause() {
|
|
658
|
+
if (!this.transportExited)
|
|
659
|
+
return " The session itself is still running.";
|
|
660
|
+
return (` The agent process has exited, so the session is NOT running any more and will not ` +
|
|
661
|
+
`recover on its own; the conversation is still on disk, recover it with ` +
|
|
662
|
+
`"paseo import ${this.sessionId}".`);
|
|
663
|
+
}
|
|
628
664
|
/** Tell the provider the dialog state changed; never let a listener throw into the loop. */
|
|
629
665
|
notifyDialogChange() {
|
|
630
666
|
if (!this.onDialogChange)
|
|
@@ -727,8 +763,7 @@ export class PtyQuery {
|
|
|
727
763
|
is_error: true,
|
|
728
764
|
errors: [
|
|
729
765
|
`Your message did not reach the agent: the terminal would not accept it. ` +
|
|
730
|
-
`Nothing was sent, so please send it again.
|
|
731
|
-
`running.${tailLine}`,
|
|
766
|
+
`Nothing was sent, so please send it again.${this.stillRunningClause()}${tailLine}`,
|
|
732
767
|
],
|
|
733
768
|
};
|
|
734
769
|
}
|
|
@@ -897,6 +932,11 @@ export class PtyQuery {
|
|
|
897
932
|
let backoff = DEAF_RETRY_INITIAL_MS;
|
|
898
933
|
let attempt = 0;
|
|
899
934
|
while (!this.done && monotonicNowMs() < deadline) {
|
|
935
|
+
// Re-checked every attempt, not once up front: the child can die between attempts and
|
|
936
|
+
// this loop's budget is ten minutes. Retrying into a corpse is what produced the
|
|
937
|
+
// "no delivery receipt; retrying" storms that ended at the 20-retry cap.
|
|
938
|
+
if (this.transportExited)
|
|
939
|
+
return "exited";
|
|
900
940
|
attempt += 1;
|
|
901
941
|
const attemptStartedAt = monotonicNowMs();
|
|
902
942
|
// Empty the input box first: whatever an earlier attempt may have left there is
|
|
@@ -904,7 +944,17 @@ export class PtyQuery {
|
|
|
904
944
|
// became nine stacked paste blocks in production.
|
|
905
945
|
if (attempt > 1)
|
|
906
946
|
await this.clearComposer();
|
|
907
|
-
|
|
947
|
+
try {
|
|
948
|
+
await this.transport.write(text);
|
|
949
|
+
}
|
|
950
|
+
catch (err) {
|
|
951
|
+
// The transport now refuses a write into a dead pty. Without this catch the rejection
|
|
952
|
+
// escapes deliverPrompt into the input loop's handler, which only logs "input loop
|
|
953
|
+
// ended" -- so the sender would watch a spinner and never be told anything.
|
|
954
|
+
if (err instanceof PtyExitedError)
|
|
955
|
+
return "exited";
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
908
958
|
const submitted = await this.submitTurn(text, watermark, fingerprint);
|
|
909
959
|
if (submitted === "rejected")
|
|
910
960
|
return "rejected";
|
|
@@ -1426,7 +1476,62 @@ export class PtyQuery {
|
|
|
1426
1476
|
};
|
|
1427
1477
|
return result;
|
|
1428
1478
|
}
|
|
1479
|
+
/**
|
|
1480
|
+
* The agent process exited. Fail any in-flight turn immediately and end the query.
|
|
1481
|
+
*
|
|
1482
|
+
* Unlike a stall there is nothing to wait for: no retry, no recovery, no later transcript
|
|
1483
|
+
* activity. Emitting here rather than letting the stall timer fire turns a ten-minute wait
|
|
1484
|
+
* plus a false "still running" into an immediate, accurate failure, and finishing the query
|
|
1485
|
+
* stops the layer above treating this transport as a live destination for prompts.
|
|
1486
|
+
*/
|
|
1487
|
+
onTransportExit(code, signal) {
|
|
1488
|
+
// Recorded before the `done` bail: the wording helpers and the delivery guards must know
|
|
1489
|
+
// the process is gone even when the query had already been torn down.
|
|
1490
|
+
this.transportExited ?? (this.transportExited = { code, signal });
|
|
1491
|
+
if (this.done)
|
|
1492
|
+
return;
|
|
1493
|
+
const wasInFlight = this.turnInFlight;
|
|
1494
|
+
this.turnInFlight = false;
|
|
1495
|
+
this.clearStallTimer();
|
|
1496
|
+
this.logger.error({ sessionId: this.sessionId, exitCode: code, exitSignal: signal, wasInFlight }, "PtyQuery: the agent process exited");
|
|
1497
|
+
this.recordLine(`--- agent process exited (status ${code ?? "none"}, signal ${signal ?? "none"}) ---`);
|
|
1498
|
+
if (wasInFlight) {
|
|
1499
|
+
this.numTurns += 1;
|
|
1500
|
+
this.emit(this.buildExitedResult(code, signal));
|
|
1501
|
+
}
|
|
1502
|
+
this.finish();
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* A non-success `result` for an agent that exited.
|
|
1506
|
+
*
|
|
1507
|
+
* Wording constraints match {@link buildStalledResult}: `isAbortError()` drops results
|
|
1508
|
+
* matching /\baborted\b/i and `buildTurnFailedEvent()` scrapes /\bcode\s+(\d+)\b/i for an
|
|
1509
|
+
* exit code, so this says "status", never "code", and never says "aborted". It also must
|
|
1510
|
+
* NOT claim the session is still running, which is the exact lie this change deletes.
|
|
1511
|
+
*/
|
|
1512
|
+
buildExitedResult(code, signal) {
|
|
1513
|
+
const base = this.buildResult("process_exited");
|
|
1514
|
+
const how = signal ? `on signal ${signal}` : `with status ${code ?? "unknown"}`;
|
|
1515
|
+
const excerpt = this.terminalTail.slice(-TERMINAL_TAIL_EXCERPT_CHARS).trim();
|
|
1516
|
+
const tailLine = excerpt ? ` Last terminal output: ${JSON.stringify(excerpt)}.` : "";
|
|
1517
|
+
return {
|
|
1518
|
+
...base,
|
|
1519
|
+
subtype: "error_during_execution",
|
|
1520
|
+
is_error: true,
|
|
1521
|
+
errors: [
|
|
1522
|
+
`The agent process exited ${how} before finishing this turn, so the session is NOT ` +
|
|
1523
|
+
`running any more and will not recover on its own. The conversation is still on ` +
|
|
1524
|
+
`disk: recover it with "paseo import ${this.sessionId}", then restore the agent's ` +
|
|
1525
|
+
`model, thinking budget and permission mode, which import does not carry over.` +
|
|
1526
|
+
tailLine,
|
|
1527
|
+
],
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1429
1530
|
buildStalledResult(silentForMs, terminalTail) {
|
|
1531
|
+
// A dead process is not a stall, and must never be described as one that "may recover".
|
|
1532
|
+
if (this.transportExited) {
|
|
1533
|
+
return this.buildExitedResult(this.transportExited.code, this.transportExited.signal);
|
|
1534
|
+
}
|
|
1430
1535
|
const minutes = Math.round(silentForMs / 60000);
|
|
1431
1536
|
const base = this.buildResult("stall_timeout");
|
|
1432
1537
|
// The last thing the terminal showed is usually the whole diagnosis ("Compacting
|
|
@@ -28,6 +28,23 @@ export interface PtyInputEvent {
|
|
|
28
28
|
/** Install (or clear, with null) the input-provenance sink. */
|
|
29
29
|
export declare function setPtyInputSink(sink: ((event: PtyInputEvent) => void) | null): void;
|
|
30
30
|
export declare function __setNodePtyForTesting(stub: NodePtyModule | null): void;
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when something tries to write to a pty whose process has already exited.
|
|
33
|
+
*
|
|
34
|
+
* This has a dedicated type because the two failure modes read identically at the call site
|
|
35
|
+
* and must not: "never spawned" is a programming error, while "already exited" is a normal
|
|
36
|
+
* runtime event that the layer above has to react to by failing the turn. Before this class
|
|
37
|
+
* existed, `doWrite` happily called `_pty.write()` on a dead pty, node-pty swallowed it, and
|
|
38
|
+
* `waitForEcho()`'s fixed timer reported success - so paseo typed prompts into a corpse for
|
|
39
|
+
* fourteen retries and then told the user "the session itself is still running".
|
|
40
|
+
*/
|
|
41
|
+
export declare class PtyExitedError extends Error {
|
|
42
|
+
readonly code = "PTY_EXITED";
|
|
43
|
+
/** How the process died, so a caller can report it without a second lookup. */
|
|
44
|
+
readonly exitCode: number | null;
|
|
45
|
+
readonly exitSignal: NodeJS.Signals | null;
|
|
46
|
+
constructor(op: string, exitCode?: number | null, exitSignal?: NodeJS.Signals | null);
|
|
47
|
+
}
|
|
31
48
|
export declare class PtyTransport implements AgentTransport {
|
|
32
49
|
private _pty;
|
|
33
50
|
private _cwd;
|
|
@@ -35,11 +52,26 @@ export declare class PtyTransport implements AgentTransport {
|
|
|
35
52
|
private _rows;
|
|
36
53
|
private _killed;
|
|
37
54
|
private _exited;
|
|
55
|
+
private _exitInfo;
|
|
56
|
+
private exitHandlers;
|
|
38
57
|
private injectChain;
|
|
39
58
|
private hookHandlers;
|
|
40
59
|
private hookBuffer;
|
|
41
60
|
private bridgeClose;
|
|
42
61
|
private systemPromptFilePath;
|
|
62
|
+
/**
|
|
63
|
+
* Has the child process exited?
|
|
64
|
+
*
|
|
65
|
+
* Public because guarding the write paths fixes the symptom but leaves the question
|
|
66
|
+
* unanswerable: `_exited` was private, so no caller could ask a transport whether its
|
|
67
|
+
* child was alive without provoking an error. `AgentTransport` now declares it.
|
|
68
|
+
*/
|
|
69
|
+
get exited(): boolean;
|
|
70
|
+
/** Exit status once the child is gone, else null. */
|
|
71
|
+
get exitInfo(): {
|
|
72
|
+
code: number | null;
|
|
73
|
+
signal: NodeJS.Signals | null;
|
|
74
|
+
} | null;
|
|
43
75
|
/**
|
|
44
76
|
* Bridge-side / test entry point — push a hook event into this transport's
|
|
45
77
|
* stream. If no `onHookEvent` handler is registered yet, the event is
|
|
@@ -49,6 +49,25 @@ function getNodePty() {
|
|
|
49
49
|
export function __setNodePtyForTesting(stub) {
|
|
50
50
|
nodePty = stub;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Thrown when something tries to write to a pty whose process has already exited.
|
|
54
|
+
*
|
|
55
|
+
* This has a dedicated type because the two failure modes read identically at the call site
|
|
56
|
+
* and must not: "never spawned" is a programming error, while "already exited" is a normal
|
|
57
|
+
* runtime event that the layer above has to react to by failing the turn. Before this class
|
|
58
|
+
* existed, `doWrite` happily called `_pty.write()` on a dead pty, node-pty swallowed it, and
|
|
59
|
+
* `waitForEcho()`'s fixed timer reported success - so paseo typed prompts into a corpse for
|
|
60
|
+
* fourteen retries and then told the user "the session itself is still running".
|
|
61
|
+
*/
|
|
62
|
+
export class PtyExitedError extends Error {
|
|
63
|
+
constructor(op, exitCode = null, exitSignal = null) {
|
|
64
|
+
super(`PtyTransport.${op} called after the pty process exited`);
|
|
65
|
+
this.code = "PTY_EXITED";
|
|
66
|
+
this.name = "PtyExitedError";
|
|
67
|
+
this.exitCode = exitCode;
|
|
68
|
+
this.exitSignal = exitSignal;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
52
71
|
export class PtyTransport {
|
|
53
72
|
constructor() {
|
|
54
73
|
this._pty = null;
|
|
@@ -57,12 +76,28 @@ export class PtyTransport {
|
|
|
57
76
|
this._rows = DEFAULT_ROWS;
|
|
58
77
|
this._killed = false;
|
|
59
78
|
this._exited = false;
|
|
79
|
+
this._exitInfo = null;
|
|
80
|
+
this.exitHandlers = [];
|
|
60
81
|
this.injectChain = Promise.resolve();
|
|
61
82
|
this.hookHandlers = [];
|
|
62
83
|
this.hookBuffer = [];
|
|
63
84
|
this.bridgeClose = null;
|
|
64
85
|
this.systemPromptFilePath = null;
|
|
65
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Has the child process exited?
|
|
89
|
+
*
|
|
90
|
+
* Public because guarding the write paths fixes the symptom but leaves the question
|
|
91
|
+
* unanswerable: `_exited` was private, so no caller could ask a transport whether its
|
|
92
|
+
* child was alive without provoking an error. `AgentTransport` now declares it.
|
|
93
|
+
*/
|
|
94
|
+
get exited() {
|
|
95
|
+
return this._exited;
|
|
96
|
+
}
|
|
97
|
+
/** Exit status once the child is gone, else null. */
|
|
98
|
+
get exitInfo() {
|
|
99
|
+
return this._exitInfo ? { ...this._exitInfo } : null;
|
|
100
|
+
}
|
|
66
101
|
/**
|
|
67
102
|
* Bridge-side / test entry point — push a hook event into this transport's
|
|
68
103
|
* stream. If no `onHookEvent` handler is registered yet, the event is
|
|
@@ -114,8 +149,24 @@ export class PtyTransport {
|
|
|
114
149
|
cwd: opts.cwd,
|
|
115
150
|
env: opts.env,
|
|
116
151
|
});
|
|
117
|
-
|
|
152
|
+
// ONE internal subscription owns exit state and fans it out to every public onExit()
|
|
153
|
+
// handler. Registering handlers straight onto node-pty (the previous shape) meant a
|
|
154
|
+
// subscriber that attached AFTER the child had already died heard nothing at all -- and
|
|
155
|
+
// a PtyQuery attaches to a long-lived terminal, not only to a fresh boot, so that is a
|
|
156
|
+
// reachable case and not a theoretical one.
|
|
157
|
+
this._pty.onExit(({ exitCode, signal }) => {
|
|
158
|
+
const code = typeof exitCode === "number" ? exitCode : null;
|
|
159
|
+
const sig = signal ?? null;
|
|
118
160
|
this._exited = true;
|
|
161
|
+
this._exitInfo = { code, signal: sig };
|
|
162
|
+
for (const h of this.exitHandlers) {
|
|
163
|
+
try {
|
|
164
|
+
h(code, sig);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// one bad subscriber must not stop the others hearing about the death
|
|
168
|
+
}
|
|
169
|
+
}
|
|
119
170
|
});
|
|
120
171
|
}
|
|
121
172
|
write(text) {
|
|
@@ -138,6 +189,9 @@ export class PtyTransport {
|
|
|
138
189
|
if (!this._pty) {
|
|
139
190
|
throw new Error("PtyTransport.writeRaw called before spawn()");
|
|
140
191
|
}
|
|
192
|
+
if (this._exited) {
|
|
193
|
+
throw new PtyExitedError("writeRaw", this._exitInfo?.code ?? null, this._exitInfo?.signal ?? null);
|
|
194
|
+
}
|
|
141
195
|
this._pty.write(data);
|
|
142
196
|
await this.waitForEcho();
|
|
143
197
|
}
|
|
@@ -148,6 +202,9 @@ export class PtyTransport {
|
|
|
148
202
|
if (!this._pty) {
|
|
149
203
|
throw new Error("PtyTransport.write called before spawn()");
|
|
150
204
|
}
|
|
205
|
+
if (this._exited) {
|
|
206
|
+
throw new PtyExitedError("write", this._exitInfo?.code ?? null, this._exitInfo?.signal ?? null);
|
|
207
|
+
}
|
|
151
208
|
this._pty.write(this.bracketedPaste(text));
|
|
152
209
|
await this.waitForEcho();
|
|
153
210
|
}
|
|
@@ -216,10 +273,17 @@ export class PtyTransport {
|
|
|
216
273
|
if (!this._pty) {
|
|
217
274
|
throw new Error("PtyTransport.onExit called before spawn()");
|
|
218
275
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
276
|
+
this.exitHandlers.push(handler);
|
|
277
|
+
// Already dead: fire, rather than never. Deferred a microtask so a subscriber that
|
|
278
|
+
// registers from a constructor (PtyQuery does) cannot be re-entered before it has
|
|
279
|
+
// finished building itself.
|
|
280
|
+
if (this._exited) {
|
|
281
|
+
const info = this._exitInfo;
|
|
282
|
+
queueMicrotask(() => handler(info?.code ?? null, info?.signal ?? null));
|
|
283
|
+
}
|
|
284
|
+
return () => {
|
|
285
|
+
this.exitHandlers = this.exitHandlers.filter((h) => h !== handler);
|
|
286
|
+
};
|
|
223
287
|
}
|
|
224
288
|
context() {
|
|
225
289
|
if (!this._pty) {
|
|
@@ -53,6 +53,8 @@ export declare class SdkTransport implements AgentTransport {
|
|
|
53
53
|
createQuery(input: ClaudeQueryInput): Query;
|
|
54
54
|
spawn(opts: AgentTransportSpawnOptions): Promise<void>;
|
|
55
55
|
write(text: string): Promise<void>;
|
|
56
|
+
/** True once the SDK query has been closed/returned. Mirrors PtyTransport.exited. */
|
|
57
|
+
get exited(): boolean;
|
|
56
58
|
kill(timeoutMs?: number): Promise<void>;
|
|
57
59
|
onHookEvent(_handler: (event: HookEvent) => void): () => void;
|
|
58
60
|
onData(_handler: (chunk: string) => void): () => void;
|
|
@@ -135,6 +135,10 @@ export class SdkTransport {
|
|
|
135
135
|
const queryWithInput = this._query;
|
|
136
136
|
await queryWithInput.input?.send(message);
|
|
137
137
|
}
|
|
138
|
+
/** True once the SDK query has been closed/returned. Mirrors PtyTransport.exited. */
|
|
139
|
+
get exited() {
|
|
140
|
+
return this._exited;
|
|
141
|
+
}
|
|
138
142
|
async kill(timeoutMs = 5000) {
|
|
139
143
|
if (this._killed)
|
|
140
144
|
return;
|
|
@@ -29,6 +29,14 @@ export type AgentTransportContext = {
|
|
|
29
29
|
cwd: string;
|
|
30
30
|
};
|
|
31
31
|
export interface AgentTransport {
|
|
32
|
+
/**
|
|
33
|
+
* True once the underlying agent process has exited.
|
|
34
|
+
*
|
|
35
|
+
* Optional so a transport that genuinely cannot know stays honest by omitting it;
|
|
36
|
+
* `undefined` means "no liveness signal" and must not be read as "alive". Both shipped
|
|
37
|
+
* transports implement it.
|
|
38
|
+
*/
|
|
39
|
+
readonly exited?: boolean;
|
|
32
40
|
spawn(opts: AgentTransportSpawnOptions): Promise<void>;
|
|
33
41
|
write(text: string): Promise<void>;
|
|
34
42
|
/**
|
package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts
CHANGED
|
@@ -19,9 +19,21 @@ export interface ResolveOrCreateWorkspaceIdInput {
|
|
|
19
19
|
cwd: string;
|
|
20
20
|
initialTitle: string | null;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* `createdWorkspace` is true ONLY when this call minted a brand new workspace
|
|
24
|
+
* record. Callers use it to decide whether post-create side effects that are
|
|
25
|
+
* only correct on a fresh workspace may run — notably workspace auto-naming,
|
|
26
|
+
* which would otherwise rename a workspace the user already had. Deriving that
|
|
27
|
+
* from the REQUEST (`!msg.workspaceId`) is wrong now that an omitted id can
|
|
28
|
+
* resolve to an existing workspace.
|
|
29
|
+
*/
|
|
30
|
+
export interface ResolveOrCreateWorkspaceIdResult {
|
|
31
|
+
workspaceId: string;
|
|
32
|
+
createdWorkspace: boolean;
|
|
33
|
+
}
|
|
22
34
|
export interface WorkspaceProvisioningService {
|
|
23
35
|
findOrCreateWorkspaceForDirectory(cwd: string): Promise<PersistedWorkspaceRecord>;
|
|
24
|
-
resolveOrCreateWorkspaceIdForCreateAgent(input: ResolveOrCreateWorkspaceIdInput): Promise<
|
|
36
|
+
resolveOrCreateWorkspaceIdForCreateAgent(input: ResolveOrCreateWorkspaceIdInput): Promise<ResolveOrCreateWorkspaceIdResult>;
|
|
25
37
|
createWorkspaceForDirectory(cwd: string, title?: string | null): Promise<PersistedWorkspaceRecord>;
|
|
26
38
|
findOrCreateProjectForDirectory(cwd: string): Promise<PersistedProjectRecord>;
|
|
27
39
|
ensureWorkspaceRecordUnarchived(workspace: PersistedWorkspaceRecord): Promise<PersistedWorkspaceRecord>;
|
|
@@ -119,12 +119,36 @@ export function createWorkspaceProvisioningService(deps) {
|
|
|
119
119
|
}
|
|
120
120
|
async function resolveOrCreateWorkspaceIdForCreateAgent(input) {
|
|
121
121
|
if (input.createdWorktree) {
|
|
122
|
-
return
|
|
122
|
+
return {
|
|
123
|
+
workspaceId: input.createdWorktree.workspace.workspaceId,
|
|
124
|
+
createdWorkspace: false,
|
|
125
|
+
};
|
|
123
126
|
}
|
|
124
127
|
if (input.requestedWorkspaceId) {
|
|
125
|
-
return input.requestedWorkspaceId;
|
|
128
|
+
return { workspaceId: input.requestedWorkspaceId, createdWorkspace: false };
|
|
126
129
|
}
|
|
127
|
-
|
|
130
|
+
// Look before you mint. This branch used to call createWorkspaceForDirectory
|
|
131
|
+
// unconditionally, so every createAgent that omitted `workspaceId` produced a
|
|
132
|
+
// SECOND workspace for a directory that already had one — no lookup, not even
|
|
133
|
+
// against the identical cwd. On one daemon that turned 19 directories into 114
|
|
134
|
+
// workspace records, 48 of them on a single repo root, and the agent surfaced
|
|
135
|
+
// in the fresh workspace instead of the one on screen.
|
|
136
|
+
//
|
|
137
|
+
// 0.3.38 fixed the palette by making its caller pass the id. That closes one
|
|
138
|
+
// door and leaves the hole: any other caller (MCP create_agent, loops,
|
|
139
|
+
// schedules, whatever is written next) still mints. Fix it where ownership is
|
|
140
|
+
// actually decided.
|
|
141
|
+
const existing = await findExactWorkspaceByDirectory(input.cwd, { refreshGit: false });
|
|
142
|
+
if (existing) {
|
|
143
|
+
const reused = await reclassifyOrUnarchiveWorkspaceForDirectory({
|
|
144
|
+
workspace: existing,
|
|
145
|
+
project: await projectRegistry.get(existing.projectId),
|
|
146
|
+
cwd: await resolveWorkspaceDirectory(input.cwd, { refreshGit: false }),
|
|
147
|
+
});
|
|
148
|
+
return { workspaceId: reused.workspaceId, createdWorkspace: false };
|
|
149
|
+
}
|
|
150
|
+
const created = await createWorkspaceForDirectory(input.cwd, input.initialTitle);
|
|
151
|
+
return { workspaceId: created.workspaceId, createdWorkspace: true };
|
|
128
152
|
}
|
|
129
153
|
async function createWorkspaceForDirectory(cwd, title) {
|
|
130
154
|
const checkout = await workspaceGitService.getCheckout(cwd);
|
|
@@ -2693,13 +2693,17 @@ export class Session {
|
|
|
2693
2693
|
let createAgentConfig = createdWorktree
|
|
2694
2694
|
? { ...config, cwd: createdWorktree.worktree.worktreePath }
|
|
2695
2695
|
: config;
|
|
2696
|
-
const workspaceId = await this.workspaceProvisioning.resolveOrCreateWorkspaceIdForCreateAgent({
|
|
2696
|
+
const { workspaceId, createdWorkspace } = await this.workspaceProvisioning.resolveOrCreateWorkspaceIdForCreateAgent({
|
|
2697
2697
|
createdWorktree,
|
|
2698
2698
|
requestedWorkspaceId: msg.workspaceId,
|
|
2699
2699
|
cwd: createAgentConfig.cwd,
|
|
2700
2700
|
initialTitle: workspacePromptTitle,
|
|
2701
2701
|
});
|
|
2702
|
-
|
|
2702
|
+
// Gate auto-naming on what actually happened, NOT on what was requested.
|
|
2703
|
+
// An omitted workspaceId no longer implies a fresh workspace: provisioning
|
|
2704
|
+
// now reuses the one that already owns this cwd, and auto-naming a reused
|
|
2705
|
+
// workspace would rename one the user already had.
|
|
2706
|
+
const createdDirectoryWorkspaceForAgent = !createdWorktree && createdWorkspace;
|
|
2703
2707
|
// Resuming a past session: the recorded cwd may no longer exist on this
|
|
2704
2708
|
// machine (different $HOME, deleted folder). The transcript is read from
|
|
2705
2709
|
// ~/.claude/projects regardless, so fall back to an existing directory
|
|
@@ -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.40\",\"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]);
|
|
@@ -15048,7 +15048,7 @@ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{v
|
|
|
15048
15048
|
__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])},3403,[3383,3404]);
|
|
15049
15049
|
__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}},3404,[3282]);
|
|
15050
15050
|
__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}},3405,[1006,3406]);
|
|
15051
|
-
__d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.
|
|
15051
|
+
__d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.40",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"}}},3406,[]);
|
|
15052
15052
|
__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}}},3407,[3408,3410]);
|
|
15053
15053
|
__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()}},3408,[25,3409]);
|
|
15054
15054
|
__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}},3409,[]);
|
|
@@ -16579,5 +16579,5 @@ __d(function(g,r,_i,a,_m,_e,d){"use strict";var e,t=r(d[0]),n=this&&this.__creat
|
|
|
16579
16579
|
__r(975);
|
|
16580
16580
|
__r(341);
|
|
16581
16581
|
__r(0);
|
|
16582
|
-
//# sourceMappingURL=/_expo/static/js/web/index-
|
|
16583
|
-
//# debugId=
|
|
16582
|
+
//# sourceMappingURL=/_expo/static/js/web/index-73ebfe5c6b82437cad59d50a40d2c8ef.js.map
|
|
16583
|
+
//# debugId=5852f948-6dc7-4e11-9de2-d9969b3a568d
|
|
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-73ebfe5c6b82437cad59d50a40d2c8ef.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.40",
|
|
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.40",
|
|
69
|
+
"@hyperdrive.bot/paseo-extension-sdk": "0.3.40",
|
|
70
|
+
"@hyperdrive.bot/paseo-highlight": "0.3.40",
|
|
71
|
+
"@hyperdrive.bot/paseo-protocol": "0.3.40",
|
|
72
|
+
"@hyperdrive.bot/paseo-relay": "0.3.40",
|
|
73
73
|
"@isaacs/ttlcache": "^2.1.4",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
75
75
|
"@opencode-ai/sdk": "1.2.6",
|