@hyperdrive.bot/paseo-server 0.3.23 → 0.3.27
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/provider-registry.d.ts +30 -0
- package/dist/server/server/agent/provider-registry.js +36 -0
- package/dist/server/server/agent/providers/claude/agent.d.ts +18 -0
- package/dist/server/server/agent/providers/claude/agent.js +104 -2
- package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +3 -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 +38 -0
- package/dist/server/server/agent/providers/claude/transport/pty-query.js +87 -6
- package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js → index-11f21e9e7b941cc98daf2ca7e3a78592.js} +4 -4
- package/dist/server/web-ui/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js.map.br → index-11f21e9e7b941cc98daf2ca7e3a78592.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-47b07b144bbc651587bf14a2bf8bc9e4.js.map.gz → index-11f21e9e7b941cc98daf2ca7e3a78592.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-47b07b144bbc651587bf14a2bf8bc9e4.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-47b07b144bbc651587bf14a2bf8bc9e4.js.gz +0 -0
|
@@ -8,6 +8,36 @@ export declare function resolveClaudeTransport(setting: "sdk" | "pty" | "auto" |
|
|
|
8
8
|
requireNodePty?: () => unknown;
|
|
9
9
|
probeClaudeBinary?: () => void;
|
|
10
10
|
}): "sdk" | "pty";
|
|
11
|
+
/**
|
|
12
|
+
* Internal agents (session-digest and auto-title generation) are headless,
|
|
13
|
+
* one-shot, `persistSession: false` runs. They have no TUI to drive: they are
|
|
14
|
+
* handed a prompt and asked for one structured answer.
|
|
15
|
+
*
|
|
16
|
+
* `resolveClaudeTransport` returns "pty" whenever node-pty and the `claude`
|
|
17
|
+
* binary are both present, which on a developer box is always, so these runs
|
|
18
|
+
* were being given a full interactive terminal and their prompt written through
|
|
19
|
+
* the PTY's bracketed-paste path. That path can refuse the write when the TUI
|
|
20
|
+
* is not in an input-accepting state, and the caller sees:
|
|
21
|
+
*
|
|
22
|
+
* "Your message did not reach the agent: the terminal would not accept it."
|
|
23
|
+
*
|
|
24
|
+
* Measured on 2026-08-04: 187 of 191 structured-generation failures (98%) were
|
|
25
|
+
* exactly that error, across claude/pool/pool-tu and across haiku, opus-5 and
|
|
26
|
+
* opus-4-8, so it is neither provider- nor model-specific. Each failure burned
|
|
27
|
+
* a provider attempt until the digest's 120s budget expired, losing ~46% of all
|
|
28
|
+
* digests (116 DigestGenerationTimeoutError against 113 successful digests that
|
|
29
|
+
* day). Every lost digest also silently costs a Jarvis judgement, because the
|
|
30
|
+
* judge only wakes on the `onSettled` hook that a completed digest fires.
|
|
31
|
+
*
|
|
32
|
+
* Two cheaper explanations were tested and rejected before this one: prompt
|
|
33
|
+
* size (2 KB / 8 KB / 32 KB pastes all delivered fine) and a spawn/readiness
|
|
34
|
+
* race (three spawn-then-immediately-send probes all delivered fine).
|
|
35
|
+
*
|
|
36
|
+
* So internal agents default to the SDK transport, which needs no terminal. An
|
|
37
|
+
* EXPLICIT `transport` setting still wins: this only redirects the "auto" case,
|
|
38
|
+
* so anyone deliberately pinning "pty" keeps it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveInternalAgentTransportSetting(internal: boolean | undefined, setting: "sdk" | "pty" | "auto" | undefined): "sdk" | "pty" | "auto" | undefined;
|
|
11
41
|
export declare const NODE_PTY_UNAVAILABLE_REASON = "node-pty native module not built. Run 'npm rebuild node-pty' in packages/server.";
|
|
12
42
|
export declare function isNodePtyAvailable(deps?: {
|
|
13
43
|
requireNodePty?: () => unknown;
|
|
@@ -38,6 +38,42 @@ export function resolveClaudeTransport(setting, deps) {
|
|
|
38
38
|
return "sdk";
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Internal agents (session-digest and auto-title generation) are headless,
|
|
43
|
+
* one-shot, `persistSession: false` runs. They have no TUI to drive: they are
|
|
44
|
+
* handed a prompt and asked for one structured answer.
|
|
45
|
+
*
|
|
46
|
+
* `resolveClaudeTransport` returns "pty" whenever node-pty and the `claude`
|
|
47
|
+
* binary are both present, which on a developer box is always, so these runs
|
|
48
|
+
* were being given a full interactive terminal and their prompt written through
|
|
49
|
+
* the PTY's bracketed-paste path. That path can refuse the write when the TUI
|
|
50
|
+
* is not in an input-accepting state, and the caller sees:
|
|
51
|
+
*
|
|
52
|
+
* "Your message did not reach the agent: the terminal would not accept it."
|
|
53
|
+
*
|
|
54
|
+
* Measured on 2026-08-04: 187 of 191 structured-generation failures (98%) were
|
|
55
|
+
* exactly that error, across claude/pool/pool-tu and across haiku, opus-5 and
|
|
56
|
+
* opus-4-8, so it is neither provider- nor model-specific. Each failure burned
|
|
57
|
+
* a provider attempt until the digest's 120s budget expired, losing ~46% of all
|
|
58
|
+
* digests (116 DigestGenerationTimeoutError against 113 successful digests that
|
|
59
|
+
* day). Every lost digest also silently costs a Jarvis judgement, because the
|
|
60
|
+
* judge only wakes on the `onSettled` hook that a completed digest fires.
|
|
61
|
+
*
|
|
62
|
+
* Two cheaper explanations were tested and rejected before this one: prompt
|
|
63
|
+
* size (2 KB / 8 KB / 32 KB pastes all delivered fine) and a spawn/readiness
|
|
64
|
+
* race (three spawn-then-immediately-send probes all delivered fine).
|
|
65
|
+
*
|
|
66
|
+
* So internal agents default to the SDK transport, which needs no terminal. An
|
|
67
|
+
* EXPLICIT `transport` setting still wins: this only redirects the "auto" case,
|
|
68
|
+
* so anyone deliberately pinning "pty" keeps it.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveInternalAgentTransportSetting(internal, setting) {
|
|
71
|
+
if (!internal)
|
|
72
|
+
return setting;
|
|
73
|
+
if (setting === "sdk" || setting === "pty")
|
|
74
|
+
return setting;
|
|
75
|
+
return "sdk";
|
|
76
|
+
}
|
|
41
77
|
export const NODE_PTY_UNAVAILABLE_REASON = "node-pty native module not built. Run 'npm rebuild node-pty' in packages/server.";
|
|
42
78
|
export function isNodePtyAvailable(deps) {
|
|
43
79
|
const tryRequire = deps?.requireNodePty ?? (() => requireFromHere("node-pty"));
|
|
@@ -101,6 +101,8 @@ export declare class ClaudeAgentSession implements AgentSession {
|
|
|
101
101
|
private toolUseIndexToId;
|
|
102
102
|
private toolUseInputBuffers;
|
|
103
103
|
private pendingPermissions;
|
|
104
|
+
/** Permission id currently standing in for a PTY TUI dialog, if any. */
|
|
105
|
+
private ptyDialogRequestId;
|
|
104
106
|
private activeForegroundTurnId;
|
|
105
107
|
private autonomousTurn;
|
|
106
108
|
private readonly subscribers;
|
|
@@ -280,6 +282,22 @@ export declare class ClaudeAgentSession implements AgentSession {
|
|
|
280
282
|
private handleSystemMessage;
|
|
281
283
|
private readMissingResumedConversationError;
|
|
282
284
|
private convertUsage;
|
|
285
|
+
/**
|
|
286
|
+
* Raise (and retract) a permission request for a dialog the PTY's TUI is showing.
|
|
287
|
+
*
|
|
288
|
+
* The SDK path gets these through `canUseTool`, which is how the app renders a real
|
|
289
|
+
* question card with its options. The PTY path has no such hook - the TUI owns the
|
|
290
|
+
* dialog - so before this the app only saw an inert `AskUserQuestion` tool chip and the
|
|
291
|
+
* session sat waiting with nothing to tap (observed live: 11h51m). Surfacing the same
|
|
292
|
+
* request shape reuses the app's existing question UI, and the answer is played back
|
|
293
|
+
* into the terminal as the dialog's own keystrokes.
|
|
294
|
+
*
|
|
295
|
+
* Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
|
|
296
|
+
* dependent, so guessing an index there could approve an implementation nobody chose.
|
|
297
|
+
*/
|
|
298
|
+
private handlePtyDialogChange;
|
|
299
|
+
/** Map the app's chosen answer back to the dialog's option index and press the keys. */
|
|
300
|
+
private answerPtyDialogFromPermission;
|
|
283
301
|
private handlePermissionRequest;
|
|
284
302
|
private enqueueTimeline;
|
|
285
303
|
private flushPendingToolCalls;
|
|
@@ -30,7 +30,7 @@ import { getAgentStreamEventTurnId, } from "../../agent-sdk-types.js";
|
|
|
30
30
|
import { importSessionFromPersistence } from "../../provider-session-import.js";
|
|
31
31
|
import { SETTING_APPLIES_NEXT_TURN_NOTICE } from "../../provider-notices.js";
|
|
32
32
|
import { checkProviderLaunchAvailable, createProviderEnv, createProviderEnvSpec, resolveProviderLaunch, } from "../../provider-launch-config.js";
|
|
33
|
-
import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, } from "../../provider-registry.js";
|
|
33
|
+
import { isNodePtyAvailable, NODE_PTY_UNAVAILABLE_REASON, resolveClaudeTransport, resolveInternalAgentTransportSetting, } from "../../provider-registry.js";
|
|
34
34
|
import { isCommandAvailable } from "../../../../executable-resolution/executable-resolution.js";
|
|
35
35
|
import { withTimeout } from "../../../../utils/promise-timeout.js";
|
|
36
36
|
import { terminateWithTreeKill } from "../../../../utils/tree-kill.js";
|
|
@@ -714,6 +714,33 @@ function isPermissionUpdate(value) {
|
|
|
714
714
|
const destination = value.destination;
|
|
715
715
|
return Array.isArray(rules) && typeof behavior === "string" && typeof destination === "string";
|
|
716
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* The app answers a question by echoing the tool input back with the chosen label(s). The
|
|
719
|
+
* exact key varies by question text, so collect every string leaf rather than guessing one
|
|
720
|
+
* path; the caller matches these against the dialog's on-screen options.
|
|
721
|
+
*/
|
|
722
|
+
function collectAnswerLabels(input) {
|
|
723
|
+
const labels = [];
|
|
724
|
+
const visit = (value, depth) => {
|
|
725
|
+
if (depth > 4)
|
|
726
|
+
return;
|
|
727
|
+
if (typeof value === "string") {
|
|
728
|
+
labels.push(value);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (Array.isArray(value)) {
|
|
732
|
+
for (const item of value)
|
|
733
|
+
visit(item, depth + 1);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (isMetadata(value)) {
|
|
737
|
+
for (const item of Object.values(value))
|
|
738
|
+
visit(item, depth + 1);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
visit(input, 0);
|
|
742
|
+
return labels;
|
|
743
|
+
}
|
|
717
744
|
function resolvePermissionKind(toolName, input) {
|
|
718
745
|
if (toolName === "ExitPlanMode")
|
|
719
746
|
return "plan";
|
|
@@ -1469,6 +1496,8 @@ export class ClaudeAgentSession {
|
|
|
1469
1496
|
this.toolUseIndexToId = new Map();
|
|
1470
1497
|
this.toolUseInputBuffers = new Map();
|
|
1471
1498
|
this.pendingPermissions = new Map();
|
|
1499
|
+
/** Permission id currently standing in for a PTY TUI dialog, if any. */
|
|
1500
|
+
this.ptyDialogRequestId = null;
|
|
1472
1501
|
this.activeForegroundTurnId = null;
|
|
1473
1502
|
this.autonomousTurn = null;
|
|
1474
1503
|
this.subscribers = new Set();
|
|
@@ -2314,7 +2343,7 @@ export class ClaudeAgentSession {
|
|
|
2314
2343
|
ensureTransport() {
|
|
2315
2344
|
if (this.resolvedTransport)
|
|
2316
2345
|
return this.resolvedTransport;
|
|
2317
|
-
this.resolvedTransport = resolveClaudeTransport(this.runtimeSettings?.transport);
|
|
2346
|
+
this.resolvedTransport = resolveClaudeTransport(resolveInternalAgentTransportSetting(this.config.internal, this.runtimeSettings?.transport));
|
|
2318
2347
|
this.fireTransportSpawnedTelemetry(this.resolvedTransport);
|
|
2319
2348
|
return this.resolvedTransport;
|
|
2320
2349
|
}
|
|
@@ -2501,6 +2530,7 @@ export class ClaudeAgentSession {
|
|
|
2501
2530
|
? this.normalizeMcpServers(this.config.mcpServers)
|
|
2502
2531
|
: undefined,
|
|
2503
2532
|
logger: this.logger,
|
|
2533
|
+
onDialogChange: (dialog) => this.handlePtyDialogChange(dialog),
|
|
2504
2534
|
});
|
|
2505
2535
|
this.activeTransport = ptySession.transport;
|
|
2506
2536
|
this.query = ptySession.query;
|
|
@@ -3580,6 +3610,78 @@ export class ClaudeAgentSession {
|
|
|
3580
3610
|
convertUsage(message, modelUsage) {
|
|
3581
3611
|
return this.contextUsage.buildResultUsage(message, modelUsage);
|
|
3582
3612
|
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Raise (and retract) a permission request for a dialog the PTY's TUI is showing.
|
|
3615
|
+
*
|
|
3616
|
+
* The SDK path gets these through `canUseTool`, which is how the app renders a real
|
|
3617
|
+
* question card with its options. The PTY path has no such hook - the TUI owns the
|
|
3618
|
+
* dialog - so before this the app only saw an inert `AskUserQuestion` tool chip and the
|
|
3619
|
+
* session sat waiting with nothing to tap (observed live: 11h51m). Surfacing the same
|
|
3620
|
+
* request shape reuses the app's existing question UI, and the answer is played back
|
|
3621
|
+
* into the terminal as the dialog's own keystrokes.
|
|
3622
|
+
*
|
|
3623
|
+
* Scoped to questions on purpose: a plan dialog's options are the CLI's and version-
|
|
3624
|
+
* dependent, so guessing an index there could approve an implementation nobody chose.
|
|
3625
|
+
*/
|
|
3626
|
+
handlePtyDialogChange(dialog) {
|
|
3627
|
+
if (!dialog) {
|
|
3628
|
+
const pendingId = this.ptyDialogRequestId;
|
|
3629
|
+
this.ptyDialogRequestId = null;
|
|
3630
|
+
if (!pendingId)
|
|
3631
|
+
return;
|
|
3632
|
+
const pending = this.pendingPermissions.get(pendingId);
|
|
3633
|
+
if (!pending)
|
|
3634
|
+
return;
|
|
3635
|
+
// Answered in the terminal (or by keystrokes): retract the card, don't leave it up.
|
|
3636
|
+
this.pendingPermissions.delete(pendingId);
|
|
3637
|
+
pending.cleanup?.();
|
|
3638
|
+
this.pushEvent({
|
|
3639
|
+
type: "permission_resolved",
|
|
3640
|
+
provider: "claude",
|
|
3641
|
+
requestId: pendingId,
|
|
3642
|
+
resolution: { behavior: "allow" },
|
|
3643
|
+
});
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
if (dialog.name !== "AskUserQuestion" || this.ptyDialogRequestId)
|
|
3647
|
+
return;
|
|
3648
|
+
const requestId = `permission-${randomUUID()}`;
|
|
3649
|
+
this.ptyDialogRequestId = requestId;
|
|
3650
|
+
const request = {
|
|
3651
|
+
id: requestId,
|
|
3652
|
+
provider: "claude",
|
|
3653
|
+
name: dialog.name,
|
|
3654
|
+
kind: "question",
|
|
3655
|
+
input: normalizeClaudeAskUserQuestionRequestInput(dialog.name, dialog.input),
|
|
3656
|
+
metadata: { toolUseId: dialog.toolUseId },
|
|
3657
|
+
};
|
|
3658
|
+
const options = dialog.options;
|
|
3659
|
+
this.pendingPermissions.set(requestId, {
|
|
3660
|
+
request,
|
|
3661
|
+
// There is no SDK promise to settle here: "resolving" means playing the answer into
|
|
3662
|
+
// the terminal. An answer that names no known option is left to the user to type.
|
|
3663
|
+
resolve: (result) => {
|
|
3664
|
+
void this.answerPtyDialogFromPermission(result, options);
|
|
3665
|
+
},
|
|
3666
|
+
reject: () => { },
|
|
3667
|
+
});
|
|
3668
|
+
this.pushEvent({ type: "permission_requested", provider: "claude", request });
|
|
3669
|
+
}
|
|
3670
|
+
/** Map the app's chosen answer back to the dialog's option index and press the keys. */
|
|
3671
|
+
async answerPtyDialogFromPermission(result, options) {
|
|
3672
|
+
if (result.behavior !== "allow")
|
|
3673
|
+
return;
|
|
3674
|
+
const query = this.query;
|
|
3675
|
+
if (typeof query?.answerDialogByIndex !== "function")
|
|
3676
|
+
return;
|
|
3677
|
+
const chosen = collectAnswerLabels(result.updatedInput);
|
|
3678
|
+
const index = options.findIndex((option) => chosen.some((label) => label.trim().toLowerCase() === option.trim().toLowerCase()));
|
|
3679
|
+
if (index < 0) {
|
|
3680
|
+
this.logger.warn({ chosen, options }, "PTY dialog answer matched no on-screen option; leaving the dialog up");
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
await query.answerDialogByIndex(index);
|
|
3684
|
+
}
|
|
3583
3685
|
enqueueTimeline(item) {
|
|
3584
3686
|
this.pushEvent({ type: "timeline", item, provider: "claude" });
|
|
3585
3687
|
}
|
|
@@ -2,6 +2,7 @@ import type { Logger } from "pino";
|
|
|
2
2
|
import type { Query, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
3
3
|
import { type ProviderRuntimeSettings } from "../../provider-launch-config.js";
|
|
4
4
|
import { PtyTransport } from "./transport/pty.js";
|
|
5
|
+
import { type PtyInteractiveDialog } from "./transport/pty-query.js";
|
|
5
6
|
export interface CreatePtySessionOptions {
|
|
6
7
|
binary: string;
|
|
7
8
|
cwd: string;
|
|
@@ -22,6 +23,8 @@ export interface CreatePtySessionOptions {
|
|
|
22
23
|
cols: number;
|
|
23
24
|
rows: number;
|
|
24
25
|
};
|
|
26
|
+
/** Surfaces TUI question/plan dialogs so the provider can raise a permission request. */
|
|
27
|
+
onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
|
|
25
28
|
}
|
|
26
29
|
export interface PtySession {
|
|
27
30
|
transport: PtyTransport;
|
|
@@ -55,6 +55,7 @@ export async function createPtySession(opts) {
|
|
|
55
55
|
transcriptPath,
|
|
56
56
|
sessionId: opts.sessionId,
|
|
57
57
|
logger: opts.logger,
|
|
58
|
+
...(opts.onDialogChange ? { onDialogChange: opts.onDialogChange } : {}),
|
|
58
59
|
});
|
|
59
60
|
opts.logger.debug({ pid: transport.context().pid, sessionId: opts.sessionId, transcriptPath }, "PTY claude session spawned");
|
|
60
61
|
return { transport, query, transcriptPath, systemPromptFilePath };
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
2
|
import type { ModelInfo, SDKMessage, SDKSystemMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
3
3
|
import type { PtyTransport } from "./pty.js";
|
|
4
|
+
/** An interactive TUI dialog the user has to answer for the turn to continue. */
|
|
5
|
+
export interface PtyInteractiveDialog {
|
|
6
|
+
toolUseId: string;
|
|
7
|
+
/** "AskUserQuestion" | "ExitPlanMode" */
|
|
8
|
+
name: string;
|
|
9
|
+
/** The tool_use input verbatim, so the app renders the real questions and options. */
|
|
10
|
+
input: Record<string, unknown>;
|
|
11
|
+
/** Option labels of the FIRST question, in on-screen order; empty when unknown. */
|
|
12
|
+
options: string[];
|
|
13
|
+
}
|
|
4
14
|
export interface PtyQueryOptions {
|
|
5
15
|
transport: PtyTransport;
|
|
6
16
|
/** The shared input channel (createAsyncMessageInput().iterable) fed by startTurn(). */
|
|
@@ -15,6 +25,12 @@ export interface PtyQueryOptions {
|
|
|
15
25
|
* {@link TURN_STALL_TIMEOUT_MS}. Pass `0` to disable the liveness backstop.
|
|
16
26
|
*/
|
|
17
27
|
stallTimeoutMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Called when the TUI starts (dialog) or finishes (null) waiting on an interactive
|
|
30
|
+
* question. The provider turns this into a permission request so the app can render
|
|
31
|
+
* the real options instead of an inert tool chip nobody can answer.
|
|
32
|
+
*/
|
|
33
|
+
onDialogChange?: (dialog: PtyInteractiveDialog | null) => void;
|
|
18
34
|
}
|
|
19
35
|
export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
20
36
|
private readonly transport;
|
|
@@ -23,6 +39,7 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
23
39
|
private readonly transcriptPath;
|
|
24
40
|
private readonly logger;
|
|
25
41
|
private readonly reader;
|
|
42
|
+
private readonly onDialogChange;
|
|
26
43
|
private readonly outQueue;
|
|
27
44
|
private readonly outResolvers;
|
|
28
45
|
private done;
|
|
@@ -103,6 +120,15 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
103
120
|
* agent running by the time the input loop sees it: silence here is a forever-spinner.
|
|
104
121
|
*/
|
|
105
122
|
private buildNothingDeliverableResult;
|
|
123
|
+
/** Tell the provider the dialog state changed; never let a listener throw into the loop. */
|
|
124
|
+
private notifyDialogChange;
|
|
125
|
+
/** The dialog the TUI is waiting on, if any. */
|
|
126
|
+
interactiveDialog(): PtyInteractiveDialog | null;
|
|
127
|
+
/**
|
|
128
|
+
* Answer the pending dialog by option index (0-based), the way the app's question card
|
|
129
|
+
* does. Returns false when there is no dialog or the keystrokes never registered.
|
|
130
|
+
*/
|
|
131
|
+
answerDialogByIndex(index: number): Promise<boolean>;
|
|
106
132
|
/**
|
|
107
133
|
* Try to answer the pending TUI dialog with the user's reply. A reply that names an
|
|
108
134
|
* option (its 1-based number or its label) becomes Down-arrow presses + Enter, the same
|
|
@@ -111,6 +137,12 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
111
137
|
* into a silent decline.
|
|
112
138
|
*/
|
|
113
139
|
private answerPendingDialog;
|
|
140
|
+
/**
|
|
141
|
+
* Drive the dialog's own keys: Down to the option, Enter to confirm. Confirmation is the
|
|
142
|
+
* tool_result landing in the transcript (which clears pendingDialog) - an optimistic flag
|
|
143
|
+
* here would record intent as fact.
|
|
144
|
+
*/
|
|
145
|
+
private sendDialogAnswer;
|
|
114
146
|
/** The agent is waiting on a dialog; tell the sender what it asks and how to answer. */
|
|
115
147
|
private buildDialogPendingResult;
|
|
116
148
|
private buildSubmitFailedResult;
|
|
@@ -155,6 +187,12 @@ export declare class PtyQuery implements AsyncGenerator<SDKMessage, void> {
|
|
|
155
187
|
* ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
|
|
156
188
|
* (zero echo), and every iteration first checks whether a user line landed anyway.
|
|
157
189
|
*/
|
|
190
|
+
/**
|
|
191
|
+
* Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
|
|
192
|
+
* pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
|
|
193
|
+
* keys just leaves the composer as it was.
|
|
194
|
+
*/
|
|
195
|
+
private clearComposer;
|
|
158
196
|
private typeWithEchoVerify;
|
|
159
197
|
private submitTurn;
|
|
160
198
|
/**
|
|
@@ -96,6 +96,15 @@ const ECHO_VERIFY_WINDOW_MS = 1500;
|
|
|
96
96
|
* contains the user's message; a real input-box echo always does.
|
|
97
97
|
*/
|
|
98
98
|
const ECHO_TEXT_FRAGMENT_CHARS = 24;
|
|
99
|
+
/**
|
|
100
|
+
* A long or multi-line prompt is written in one chunk, so the TUI takes it as a PASTE and
|
|
101
|
+
* renders a collapsed chip (`[Pasted text #9 +30 lines] paste again to expand`) instead of
|
|
102
|
+
* echoing the characters. Text-only echo verification therefore read a perfectly delivered
|
|
103
|
+
* message as a deaf terminal, retyped it, and every retry stacked ANOTHER chip in the
|
|
104
|
+
* composer: observed live at chip #9 for one message, none of them ever submitted. The
|
|
105
|
+
* chip is the echo for pasted input, so it counts as delivery.
|
|
106
|
+
*/
|
|
107
|
+
const PASTE_CHIP_MARKERS = ["pastedtext#", "pasteagaintoexpand"];
|
|
99
108
|
/** Backoff between retype attempts against a deaf terminal. */
|
|
100
109
|
const DEAF_RETRY_INITIAL_MS = 5000;
|
|
101
110
|
const DEAF_RETRY_MAX_MS = 60000;
|
|
@@ -263,6 +272,7 @@ export class PtyQuery {
|
|
|
263
272
|
this.transcriptPath = opts.transcriptPath;
|
|
264
273
|
this.logger = opts.logger;
|
|
265
274
|
this.stallTimeoutMs = opts.stallTimeoutMs ?? TURN_STALL_TIMEOUT_MS;
|
|
275
|
+
this.onDialogChange = opts.onDialogChange ?? null;
|
|
266
276
|
this.reader = new TranscriptSdkReader({
|
|
267
277
|
transcriptPath: opts.transcriptPath,
|
|
268
278
|
onMessage: (m) => this.onTranscriptMessage(m),
|
|
@@ -417,7 +427,10 @@ export class PtyQuery {
|
|
|
417
427
|
if (this.done)
|
|
418
428
|
return;
|
|
419
429
|
this.done = true;
|
|
430
|
+
const hadDialog = this.pendingDialog !== null;
|
|
420
431
|
this.pendingDialog = null;
|
|
432
|
+
if (hadDialog)
|
|
433
|
+
this.notifyDialogChange();
|
|
421
434
|
this.cancelReadiness?.();
|
|
422
435
|
if (this.turnEndTimer)
|
|
423
436
|
clearTimeout(this.turnEndTimer);
|
|
@@ -602,6 +615,30 @@ export class PtyQuery {
|
|
|
602
615
|
],
|
|
603
616
|
};
|
|
604
617
|
}
|
|
618
|
+
/** Tell the provider the dialog state changed; never let a listener throw into the loop. */
|
|
619
|
+
notifyDialogChange() {
|
|
620
|
+
if (!this.onDialogChange)
|
|
621
|
+
return;
|
|
622
|
+
try {
|
|
623
|
+
this.onDialogChange(this.pendingDialog ? { ...this.pendingDialog } : null);
|
|
624
|
+
}
|
|
625
|
+
catch (err) {
|
|
626
|
+
this.logger.warn({ err }, "PtyQuery: dialog listener threw");
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/** The dialog the TUI is waiting on, if any. */
|
|
630
|
+
interactiveDialog() {
|
|
631
|
+
return this.pendingDialog ? { ...this.pendingDialog } : null;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Answer the pending dialog by option index (0-based), the way the app's question card
|
|
635
|
+
* does. Returns false when there is no dialog or the keystrokes never registered.
|
|
636
|
+
*/
|
|
637
|
+
async answerDialogByIndex(index) {
|
|
638
|
+
if (!this.pendingDialog || index < 0)
|
|
639
|
+
return false;
|
|
640
|
+
return await this.sendDialogAnswer(this.pendingDialog, index);
|
|
641
|
+
}
|
|
605
642
|
/**
|
|
606
643
|
* Try to answer the pending TUI dialog with the user's reply. A reply that names an
|
|
607
644
|
* option (its 1-based number or its label) becomes Down-arrow presses + Enter, the same
|
|
@@ -619,28 +656,37 @@ export class PtyQuery {
|
|
|
619
656
|
this.emit(this.buildDialogPendingResult(dialog));
|
|
620
657
|
return;
|
|
621
658
|
}
|
|
659
|
+
const answered = await this.sendDialogAnswer(dialog, index);
|
|
660
|
+
if (!answered) {
|
|
661
|
+
this.emit(this.buildDialogPendingResult(dialog, true));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Drive the dialog's own keys: Down to the option, Enter to confirm. Confirmation is the
|
|
666
|
+
* tool_result landing in the transcript (which clears pendingDialog) - an optimistic flag
|
|
667
|
+
* here would record intent as fact.
|
|
668
|
+
*/
|
|
669
|
+
async sendDialogAnswer(dialog, index) {
|
|
622
670
|
this.recordLine(`--- answering ${dialog.name} with option ${index + 1} ---`);
|
|
623
671
|
for (let i = 0; i < index && !this.done; i++) {
|
|
624
672
|
await this.transport.writeRaw?.("\u001b[B");
|
|
625
673
|
await delay(DIALOG_KEY_DELAY_MS);
|
|
626
674
|
}
|
|
627
675
|
await this.transport.writeRaw?.("\r");
|
|
628
|
-
// Confirmation is the tool_result landing in the transcript (trackInteractiveDialog
|
|
629
|
-
// clears pendingDialog). Optimistic flags would record intent as fact.
|
|
630
676
|
const deadline = monotonicNowMs() + DIALOG_ANSWER_CONFIRM_MS;
|
|
631
677
|
while (this.pendingDialog && !this.done && monotonicNowMs() < deadline) {
|
|
632
678
|
await delay(250);
|
|
633
679
|
}
|
|
634
680
|
if (this.pendingDialog) {
|
|
635
681
|
this.recordLine(`--- ${dialog.name} answer did not register ---`);
|
|
636
|
-
|
|
637
|
-
return;
|
|
682
|
+
return false;
|
|
638
683
|
}
|
|
639
684
|
// Answer accepted: the turn is live again, restart the liveness clock.
|
|
640
685
|
this.turnStartedAt = monotonicNowMs();
|
|
641
686
|
this.turnInFlight = true;
|
|
642
687
|
this.armStallTimer();
|
|
643
688
|
this.startHeartbeat();
|
|
689
|
+
return true;
|
|
644
690
|
}
|
|
645
691
|
/** The agent is waiting on a dialog; tell the sender what it asks and how to answer. */
|
|
646
692
|
buildDialogPendingResult(dialog, answerFailed = false) {
|
|
@@ -757,6 +803,20 @@ export class PtyQuery {
|
|
|
757
803
|
* ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
|
|
758
804
|
* (zero echo), and every iteration first checks whether a user line landed anyway.
|
|
759
805
|
*/
|
|
806
|
+
/**
|
|
807
|
+
* Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
|
|
808
|
+
* pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
|
|
809
|
+
* keys just leaves the composer as it was.
|
|
810
|
+
*/
|
|
811
|
+
async clearComposer() {
|
|
812
|
+
try {
|
|
813
|
+
await this.transport.writeRaw?.("\u0015");
|
|
814
|
+
await delay(120);
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
this.logger.debug({ err }, "PtyQuery: could not clear the composer before retyping");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
760
820
|
async typeWithEchoVerify(text) {
|
|
761
821
|
const transcriptBefore = this.transcriptSize();
|
|
762
822
|
const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
|
|
@@ -775,7 +835,7 @@ export class PtyQuery {
|
|
|
775
835
|
try {
|
|
776
836
|
await this.transport.write(text);
|
|
777
837
|
const probe = this.echoProbe;
|
|
778
|
-
echoed = await pollUntil(() => (
|
|
838
|
+
echoed = await pollUntil(() => sawEcho(probe.buf, needle), ECHO_VERIFY_WINDOW_MS, 100);
|
|
779
839
|
}
|
|
780
840
|
finally {
|
|
781
841
|
this.echoProbe = null;
|
|
@@ -787,9 +847,12 @@ export class PtyQuery {
|
|
|
787
847
|
await delay(200);
|
|
788
848
|
return true;
|
|
789
849
|
}
|
|
790
|
-
// Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe
|
|
850
|
+
// Deaf. The keystrokes were discarded (no echo), so waiting and retyping is safe -
|
|
851
|
+
// but only after clearing the composer: if this attempt DID land as something the
|
|
852
|
+
// echo check cannot see, retyping on top of it sends the message twice.
|
|
791
853
|
if (this.sawUserLineSince(transcriptBefore))
|
|
792
854
|
return true;
|
|
855
|
+
await this.clearComposer();
|
|
793
856
|
this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: terminal painted but is not accepting input yet; holding the prompt");
|
|
794
857
|
this.recordLine(`--- terminal deaf (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
|
|
795
858
|
await delay(backoff);
|
|
@@ -952,14 +1015,17 @@ export class PtyQuery {
|
|
|
952
1015
|
this.pendingDialog = {
|
|
953
1016
|
toolUseId: b.id,
|
|
954
1017
|
name: b.name,
|
|
1018
|
+
input: isRecord(b.input) ? b.input : {},
|
|
955
1019
|
options: extractDialogOptions(b.name, b.input),
|
|
956
1020
|
};
|
|
957
1021
|
this.recordLine(`--- interactive dialog pending: ${b.name} ---`);
|
|
1022
|
+
this.notifyDialogChange();
|
|
958
1023
|
}
|
|
959
1024
|
const pending = this.pendingDialog;
|
|
960
1025
|
if (pending && b.type === "tool_result" && b.tool_use_id === pending.toolUseId) {
|
|
961
1026
|
this.recordLine(`--- interactive dialog answered: ${pending.name} ---`);
|
|
962
1027
|
this.pendingDialog = null;
|
|
1028
|
+
this.notifyDialogChange();
|
|
963
1029
|
}
|
|
964
1030
|
}
|
|
965
1031
|
}
|
|
@@ -1304,6 +1370,21 @@ function matchDialogOption(text, options) {
|
|
|
1304
1370
|
const byLabel = options.findIndex((o) => o.trim().toLowerCase() === lowered);
|
|
1305
1371
|
return byLabel >= 0 ? byLabel : null;
|
|
1306
1372
|
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Did the terminal acknowledge what we typed? Either it echoed the characters, or it
|
|
1375
|
+
* collapsed them into a paste chip - both mean the composer holds the message.
|
|
1376
|
+
*/
|
|
1377
|
+
function sawEcho(buf, needle) {
|
|
1378
|
+
if (!buf)
|
|
1379
|
+
return false;
|
|
1380
|
+
const lowered = buf.toLowerCase();
|
|
1381
|
+
if (PASTE_CHIP_MARKERS.some((marker) => lowered.includes(marker)))
|
|
1382
|
+
return true;
|
|
1383
|
+
return needle ? buf.includes(needle) : buf.length > 0;
|
|
1384
|
+
}
|
|
1385
|
+
function isRecord(value) {
|
|
1386
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1387
|
+
}
|
|
1307
1388
|
/** Extensions for the media types the app can send; anything unknown stages as .png. */
|
|
1308
1389
|
const IMAGE_MEDIA_TYPE_EXT = {
|
|
1309
1390
|
"image/jpeg": "jpg",
|
|
@@ -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.27\",\"orientation\":\"portrait\",\"icon\":\"./assets/images/icon.png\",\"scheme\":\"paseo\",\"userInterfaceStyle\":\"automatic\",\"newArchEnabled\":true,\"web\":{\"output\":\"single\",\"favicon\":\"./assets/images/favicon.png\",\"shortName\":\"Paseo\",\"orientation\":\"portrait\",\"name\":\"Paseo\"},\"autolinking\":{\"searchPaths\":[\"../../node_modules\",\"./node_modules\"]},\"experiments\":{\"typedRoutes\":true,\"reactCompiler\":true,\"autolinkingModuleResolution\":true},\"extra\":{\"router\":{},\"eas\":{\"build\":{\"experimental\":{\"ios\":{\"appExtensions\":[{\"bundleIdentifier\":\"bot.hyperdrive.paseo.AgentActivity\",\"targetName\":\"AgentActivity\"}]}}}}},\"sdkVersion\":\"54.0.0\",\"platforms\":[\"ios\",\"android\",\"web\"]}"},get manifest2(){return null},get experienceUrl(){return'undefined'!=typeof location?location.origin:''},get debugMode(){return!1},getWebViewUserAgentAsync:async()=>'undefined'!=typeof navigator?navigator.userAgent:null}},1009,[1008]);
|
|
1107
1107
|
__d(function(g,r,i,a,m,_e,d){"use strict";Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return u}});var e,t=r(d[0]),n=r(d[1]),o=(e=n)&&e.__esModule?e:{default:e};async function u(){if(!o.default.unregisterForNotificationsAsync)throw new t.UnavailabilityError('ExpoNotifications','unregisterForNotificationsAsync');return o.default.unregisterForNotificationsAsync()}},1010,[4,1011]);
|
|
1108
1108
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return n}}),r(d[0]);let t=!1;var n={addListener:()=>(t||(console.warn("[expo-notifications] Listening to push token changes is not yet fully supported on web. Adding a listener will have no effect."),t=!0),{remove:()=>{}}),removeListener:()=>{},removeAllListeners:()=>{},emit:()=>{},listenerCount:()=>0}},1011,[4]);
|
|
1109
1109
|
__d(function(g,r,i,a,m,_e,_d){"use strict";function e(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(_e,'__esModule',{value:!0}),Object.defineProperty(_e,"default",{enumerable:!0,get:function(){return u}});var t=(function(e){if(e&&e.__esModule)return e;var t={};return e&&Object.keys(e).forEach(function(o){var n=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(t,o,n.get?n:{enumerable:!0,get:function(){return e[o]}})}),t.default=e,t})(r(_d[0])),o=e(r(_d[1])),n=r(_d[2]),c=r(_d[3]),s=e(r(_d[4])),d=e(r(_d[5]));const p='https://exp.host/--/api/v2/';async function u(e={}){const s=e.devicePushToken||await(0,d.default)(),u=e.deviceId||await h(),R=e.projectId||o.default.easConfig?.projectId||o.default.expoConfig?.extra?.eas?.projectId;if(!R)throw new n.CodedError('ERR_NOTIFICATIONS_NO_EXPERIENCE_ID',"No \"projectId\" found. If \"projectId\" can't be inferred from the manifest (for instance, in bare workflow), you have to pass it in yourself.");const w=e.applicationId||t.applicationId;if(!w)throw new n.CodedError('ERR_NOTIFICATIONS_NO_APPLICATION_ID',"No \"applicationId\" found. If it can't be inferred from native configuration by expo-application, you have to pass it in yourself.");const O=e.type||y(s),_=e.development||await I(),v=e.baseUrl??p,N=e.url??`${v}push/getExpoPushToken`,x={type:O,deviceId:u.toLowerCase(),development:_,appId:w,deviceToken:E(s),projectId:R},T=await fetch(N,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(x)}).catch(e=>{throw new n.CodedError('ERR_NOTIFICATIONS_NETWORK_ERROR',`Error encountered while fetching Expo token: ${e}.`)});if(!T.ok){const e=T.statusText||T.status;let t;try{t=await T.text()}catch{}throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Error encountered while fetching Expo token, expected an OK response, received: ${e} (body: "${t}").`)}const b=l(await f(T));try{e.url||e.baseUrl?console.debug("[expo-notifications] Since the URL endpoint to register in has been customized in the options, expo-notifications won't try to auto-update the device push token on the server."):await(0,c.setAutoServerRegistrationEnabledAsync)(!0)}catch(e){console.warn('[expo-notifications] Could not enable automatically registering new device tokens with the Expo notification service',e)}return{type:'expo',data:b}}async function f(e){try{return await e.json()}catch{try{throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Expected a JSON response from server when fetching Expo token, received body: ${JSON.stringify(await e.text())}.`)}catch{throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Expected a JSON response from server when fetching Expo token, received response: ${JSON.stringify(e)}.`)}}}function l(e){if(!e||'object'!=typeof e||!e.data||'object'!=typeof e.data||!e.data.expoPushToken||'string'!=typeof e.data.expoPushToken)throw new n.CodedError('ERR_NOTIFICATIONS_SERVER_ERROR',`Malformed response from server, expected "{ data: { expoPushToken: string } }", received: ${JSON.stringify(e,null,2)}.`);return e.data.expoPushToken}async function h(){try{if(!s.default.getInstallationIdAsync)throw new n.UnavailabilityError('ExpoServerRegistrationModule','getInstallationIdAsync');return await s.default.getInstallationIdAsync()}catch(e){throw new n.CodedError('ERR_NOTIF_DEVICE_ID',`Could not have fetched installation ID of the application: ${e}.`)}}function E(e){return'string'==typeof e.data?e.data:JSON.stringify(e.data)}async function I(){return!1}function y(e){switch(e.type){case'ios':return'apns';case'android':return'fcm';default:return e.type}}},1012,[1013,1006,4,1016,1020,1005]);
|
|
@@ -15044,7 +15044,7 @@ __d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{v
|
|
|
15044
15044
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"decodeOfferFragmentPayload",{enumerable:!0,get:function(){return n.decodeOfferFragmentPayload}}),Object.defineProperty(e,"buildDaemonWebSocketUrl",{enumerable:!0,get:function(){return t.buildDaemonWebSocketUrl}}),Object.defineProperty(e,"deriveLabelFromEndpoint",{enumerable:!0,get:function(){return t.deriveLabelFromEndpoint}}),Object.defineProperty(e,"extractHostPortFromWebSocketUrl",{enumerable:!0,get:function(){return t.extractHostPortFromWebSocketUrl}}),Object.defineProperty(e,"normalizeHostPort",{enumerable:!0,get:function(){return t.normalizeHostPort}}),Object.defineProperty(e,"parseConnectionUri",{enumerable:!0,get:function(){return t.parseConnectionUri}}),Object.defineProperty(e,"parseHostPort",{enumerable:!0,get:function(){return t.parseHostPort}}),Object.defineProperty(e,"serializeConnectionUri",{enumerable:!0,get:function(){return t.serializeConnectionUri}}),Object.defineProperty(e,"serializeConnectionUriForStorage",{enumerable:!0,get:function(){return t.serializeConnectionUriForStorage}}),Object.defineProperty(e,"shouldUseTlsForDefaultHostedRelay",{enumerable:!0,get:function(){return t.shouldUseTlsForDefaultHostedRelay}}),e.buildRelayWebSocketUrl=function(n){return(0,t.buildRelayWebSocketUrl)(Object.assign({},n,{role:"client"}))};var t=r(d[0]),n=r(d[1])},3399,[3379,3400]);
|
|
15045
15045
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),Object.defineProperty(e,"ConnectionOfferV2Schema",{enumerable:!0,get:function(){return t}}),Object.defineProperty(e,"ConnectionOfferSchema",{enumerable:!0,get:function(){return o}}),e.decodeOfferFragmentPayload=l,e.parseConnectionOfferFromUrl=function(n){const t=f(n);if(!t)return null;const c=l(t);return o.parse(c)};var n=r(d[0]);const t=n.z.object({v:n.z.literal(2),serverId:n.z.string().min(1),daemonPublicKeyB64:n.z.string().min(1),relay:n.z.object({endpoint:n.z.string().min(1),useTls:n.z.boolean().optional()})}),o=t;function c(n){const t=n.replace(/-/g,"+").replace(/_/g,"/"),o=t.padEnd(t.length+(4-t.length%4)%4,"="),c=globalThis.atob(o),l=Uint8Array.from(c,n=>n.charCodeAt(0));return new TextDecoder("utf-8",{fatal:!0}).decode(l)}function l(n){const t=c(n);return JSON.parse(t)}const u="#offer=";function f(n){const t=n.trim();if(!t)return null;const o=t.indexOf(u);if(-1===o)return null;const c=t.slice(o+u.length).trim();return c.length>0?c:null}},3400,[3282]);
|
|
15046
15046
|
__d(function(g,r,i,a,m,_e,d){"use strict";function e(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(_e,'__esModule',{value:!0}),_e.resolveAppVersion=function(){const e=u(t.default?.version);if(e)return e;const o=u(n.default.expoConfig?.version);if(o)return o;const f=u(n.default.manifest?.version);if(f)return f;return null};var n=e(r(d[0])),t=e(r(d[1]));function u(e){if("string"!=typeof e)return null;const n=e.trim();return 0===n.length?null:n}},3401,[1006,3402]);
|
|
15047
|
-
__d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.
|
|
15047
|
+
__d(function(e,t,r,a,o,i,n){o.exports={name:"@hyperdrive.bot/paseo-app",version:"0.3.27",private:!0,main:"index.ts",scripts:{start:"npm run start:expo","start:expo":"cross-env APP_VARIANT=development expo start","reset-project":"node ./scripts/reset-project.js","build:terminal-webview":"node ./scripts/build-terminal-webview-html.mjs","eas-build-post-install":"npm --prefix ../.. run build:app-deps && npm run build:terminal-webview",android:"npm run android:development","android:development":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=development expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=development expo run:android --variant=debug","android:production":"npm --prefix ../.. run build:client && cross-env APP_VARIANT=production expo prebuild --platform android --non-interactive && cross-env APP_VARIANT=production expo run:android --variant=release","android:release":"npm run android:production","android:clear":"node -e \"require('node:fs').rmSync('android', { recursive: true, force: true })\"",ios:"npm --prefix ../.. run build:client && expo run:ios","ios:release":"npm --prefix ../.. run build:client && expo run:ios --configuration Release",web:"npm --prefix ../.. run build:client && concurrently --kill-others --names protocol,client,expo --prefix-colors yellow,blue,magenta \"npm --prefix ../.. run watch:protocol\" \"npm --prefix ../.. run watch:client\" \"npm run web:expo\"","web:expo":"expo start --web",lint:"expo lint",typecheck:"tsgo --noEmit",test:"vitest run","test:browser":"vitest run --project browser","test:e2e":"playwright test --project='Desktop Chrome'","test:e2e:real":"cross-env E2E_FORK_PASEO_HOME_FROM=../../.dev/paseo-home playwright test --project=real-provider","test:e2e:ui":"playwright test --ui","test:coverage":"vitest run --project unit --coverage",build:"npm run build:web","build:web":"npm --prefix ../.. run build:app-deps && expo export --platform web --source-maps","profile:workspace-tabs":"node ./scripts/profile-workspace-tabs.mjs","deploy:web":"npm run build:web && wrangler pages deploy dist --project-name paseo-app --branch main"},dependencies:{"@bacons/apple-targets":"4.0.6","@datadog/browser-rum":"^6.23.0","@datadog/browser-rum-react":"^6.23.0","@datadog/mobile-react-native":"^2.7.0","@datadog/mobile-react-native-session-replay":"^2.14.8","@dnd-kit/core":"^6.3.1","@dnd-kit/sortable":"^10.0.0","@dnd-kit/utilities":"^3.2.2","@expo/image-utils":"0.8.8","@expo/plist":"0.4.9","@expo/prebuild-config":"54.0.8","@floating-ui/react-native":"^0.10.7","@gorhom/bottom-sheet":"^5.2.14","@gorhom/portal":"^1.0.14","@hyperdrive.bot/paseo-client":"*","@hyperdrive.bot/paseo-expo-two-way-audio":"*","@hyperdrive.bot/paseo-extension-sdk":"*","@hyperdrive.bot/paseo-highlight":"*","@react-native-async-storage/async-storage":"2.2.0","@react-native-masked-view/masked-view":"^0.3.2","@react-native/normalize-colors":"^0.81.5","@react-navigation/native":"^7.1.8","@sentry/electron":"^6.11.0","@sentry/react-native":"^6.20.0","@tanstack/react-query":"^5.90.11","@tanstack/react-virtual":"^3.13.21","@xterm/addon-clipboard":"^0.3.0-beta.213","@xterm/addon-fit":"^0.12.0-beta.213","@xterm/addon-image":"^0.10.0-beta.213","@xterm/addon-ligatures":"0.11.0-beta.213","@xterm/addon-search":"^0.17.0-beta.213","@xterm/addon-unicode11":"^0.10.0-beta.213","@xterm/addon-web-links":"^0.13.0-beta.213","@xterm/addon-webgl":"^0.20.0-beta.212","@xterm/xterm":"^6.1.0-beta.213",buffer:"^6.0.3",expo:"^54.0.18","expo-asset":"~12.0.12","expo-audio":"~1.0.13","expo-background-fetch":"~14.0.9","expo-build-properties":"^1.0.9","expo-camera":"~17.0.10","expo-clipboard":"~8.0.7","expo-constants":"~18.0.9","expo-crypto":"^15.0.8","expo-dev-client":"^6.0.15","expo-document-picker":"~14.0.8","expo-file-system":"~19.0.17","expo-haptics":"~15.0.7","expo-image":"~3.0.10","expo-image-manipulator":"~14.0.8","expo-image-picker":"^17.0.8","expo-keep-awake":"^15.0.7","expo-linking":"~8.0.8","expo-localization":"~17.0.9","expo-notifications":"^0.32.16","expo-router":"~6.0.13","expo-sharing":"^14.0.8","expo-speech":"~14.0.8","expo-speech-recognition":"^56.0.1","expo-splash-screen":"~31.0.10","expo-system-ui":"~6.0.7","expo-task-manager":"~14.0.9","fast-deep-equal":"^3.1.3",htmlparser2:"^12.0.0",i18next:"^26.3.0","lucide-react-native":"^0.546.0","markdown-it":"^10.0.0","mnemonic-id":"^3.2.7",qrcode:"^1.5.4",react:"19.1.0","react-dom":"19.1.0","react-i18next":"^17.0.8","react-native":"0.81.5","react-native-draggable-flatlist":"^4.0.3","react-native-edge-to-edge":"^1.7.0","react-native-gesture-handler":"~2.28.0","react-native-keyboard-controller":"^1.21.7","react-native-markdown-display":"^7.0.2","react-native-nitro-modules":"0.35.5","react-native-reanimated":"~4.3.1","react-native-safe-area-context":"~5.6.0","react-native-screens":"~4.16.0","react-native-svg":"^15.14.0","react-native-uitextview":"^2.2.0","react-native-unistyles":"^3.2.4","react-native-web":"~0.21.0","react-native-webview":"^13.16.0","react-native-worklets":"~0.8.3","tiny-invariant":"^1.3.3","use-sync-external-store":"^1.6.0",zod:"^4.4.3",zustand:"^5.0.9"},devDependencies:{"@playwright/test":"^1.56.1","@testing-library/dom":"^10.4.1","@testing-library/react":"^16.3.2","@types/chai":"^5.2.2","@types/markdown-it":"^14.1.2","@types/qrcode":"^1.5.6","@types/react":"~19.2.0","@types/ws":"^8.18.1","@vitest/browser":"^4.1.7","@vitest/browser-playwright":"^4.1.7","@vitest/coverage-v8":"^4.1.7","@xterm/headless":"^6.1.0-beta.213",dotenv:"^17.2.3",eslint:"^9.25.0","eslint-config-expo":"~10.0.0",jsdom:"^20.0.3","material-icon-theme":"^5.32.0",playwright:"^1.56.1","serve-sim":"^0.1.40",typescript:"~5.9.2",vitest:"^4.1.6",wrangler:"^4.105.0",ws:"^8.20.0"}}},3402,[]);
|
|
15048
15048
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.shouldUseDesktopDaemon=function(){return(0,n.isElectronRuntime)()},e.getDesktopDaemonStatus=async function(){return c(await(0,t.invokeDesktopCommand)("desktop_daemon_status"))},e.startDesktopDaemon=async function(){return c(await(0,t.invokeDesktopCommand)("start_desktop_daemon"))},e.stopDesktopDaemon=async function(n="manual_ipc"){return c(await(0,t.invokeDesktopCommand)("stop_desktop_daemon",{reason:n}))},e.restartDesktopDaemon=async function(){return c(await(0,t.invokeDesktopCommand)("restart_desktop_daemon"))},e.getDesktopDaemonLogs=async function(){return p(await(0,t.invokeDesktopCommand)("desktop_daemon_logs"))},e.getDesktopDaemonPairing=async function(){return k(await(0,t.invokeDesktopCommand)("desktop_daemon_pairing"))},e.getCliDaemonStatus=async function(){const n=await(0,t.invokeDesktopCommand)("cli_daemon_status");if("string"!=typeof n)throw new Error("Unexpected CLI daemon status response.");return n},e.listenToLocalTransportEvents=async function(t){const u=(0,n.getDesktopHost)()?.events?.on;if("function"!=typeof u)throw new Error("Desktop events API is unavailable.");const c=await u("local-daemon-transport-event",n=>{o(n)&&t({sessionId:s(n.sessionId)??"",kind:s(n.kind)??"error",text:s(n.text),binaryBase64:s(n.binaryBase64),code:l(n.code),reason:s(n.reason),error:s(n.error)})});return"function"==typeof c?c:()=>{}},e.openLocalTransportSession=async function(n){const o=await(0,t.invokeDesktopCommand)("open_local_daemon_transport",n);if("string"!=typeof o||0===o.trim().length)throw new Error("Unexpected local transport session response.");return o},e.sendLocalTransportMessage=async function(n){await(0,t.invokeDesktopCommand)("send_local_daemon_transport_message",Object.assign({sessionId:n.sessionId},n.text?{text:n.text}:{},n.binaryBase64?{binaryBase64:n.binaryBase64}:{}))},e.closeLocalTransportSession=async function(n){await(0,t.invokeDesktopCommand)("close_local_daemon_transport",{sessionId:n})},e.getCliInstallStatus=async function(){return f(await(0,t.invokeDesktopCommand)("get_cli_install_status"))},e.installCli=async function(){return f(await(0,t.invokeDesktopCommand)("install_cli"))},e.getSkillsStatus=async function(){return y(await(0,t.invokeDesktopCommand)("get_skills_status"))},e.installSkills=async function(){return y(await(0,t.invokeDesktopCommand)("install_skills"))},e.updateSkills=async function(){return y(await(0,t.invokeDesktopCommand)("update_skills"))},e.uninstallSkills=async function(){return y(await(0,t.invokeDesktopCommand)("uninstall_skills"))};var n=r(d[0]),t=r(d[1]);function o(n){return"object"==typeof n&&null!==n}function s(n){return"string"==typeof n&&n.trim().length>0?n:null}function l(n){return"number"==typeof n&&Number.isFinite(n)?n:null}function u(n){const t=s(n)?.toLowerCase();switch(t){case"starting":return"starting";case"running":return"running";case"errored":case"error":return"errored";default:return"stopped"}}function c(n){if(!o(n))throw new Error("Unexpected desktop daemon status response.");return{serverId:s(n.serverId)??"",status:u(n.status),listen:s(n.listen),hostname:s(n.hostname),pid:l(n.pid),home:s(n.home)??"",version:s(n.version),desktopManaged:!0===n.desktopManaged,error:s(n.error)}}function p(n){if(!o(n))throw new Error("Unexpected desktop daemon logs response.");return{logPath:s(n.logPath)??"",contents:"string"==typeof n.contents?n.contents:""}}function k(n){if(!o(n))throw new Error("Unexpected desktop daemon pairing response.");return{relayEnabled:!0===n.relayEnabled,url:s(n.url),qr:s(n.qr)}}function f(n){if(!o(n))throw new Error("Unexpected install status response.");return{installed:!0===n.installed}}function w(n){switch(n){case"not-installed":case"up-to-date":case"drift":return n;default:throw new Error(`Unexpected skills status state: ${String(n)}`)}}function _(n){if(!o(n))throw new Error("Unexpected skill op response.");const t=s(n.name);if(!t)throw new Error("Skill op missing name.");switch(n.kind){case"add":return{kind:"add",name:t};case"update":return{kind:"update",name:t};case"delete":return{kind:"delete",name:t};default:throw new Error(`Unexpected skill op kind: ${String(n.kind)}`)}}function y(n){if(!o(n))throw new Error("Unexpected skills status response.");const t=Array.isArray(n.ops)?n.ops.map(_):[];return{state:w(n.state),ops:t}}},3403,[3404,3406]);
|
|
15049
15049
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.getDesktopHost=n,e.isElectronRuntime=o,e.isElectronRuntimeMac=function(){if(!o())return!1;if("undefined"==typeof navigator)return!1;const t=n()?.platform?.toLowerCase();if("darwin"===t||"mac"===t||"macos"===t)return!0;const u=navigator.userAgent;return u.includes("Mac OS")||u.includes("Macintosh")},r(d[0]);var t=r(d[1]);function n(){return(0,t.getElectronHost)()}function o(){return null!==n()}},3404,[25,3405]);
|
|
15050
15050
|
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.getElectronHost=function(){if("undefined"==typeof window)return null;const t=window.paseoDesktop;if(!t||"object"!=typeof t)return null;return t}},3405,[]);
|
|
@@ -16539,5 +16539,5 @@ __d(function(g,r,_i,a,_m,_e,d){"use strict";var e,t=r(d[0]),n=this&&this.__creat
|
|
|
16539
16539
|
__r(975);
|
|
16540
16540
|
__r(341);
|
|
16541
16541
|
__r(0);
|
|
16542
|
-
//# sourceMappingURL=/_expo/static/js/web/index-
|
|
16543
|
-
//# debugId=
|
|
16542
|
+
//# sourceMappingURL=/_expo/static/js/web/index-11f21e9e7b941cc98daf2ca7e3a78592.js.map
|
|
16543
|
+
//# debugId=a319d450-a568-46a4-9cc4-7d60b5c27f2b
|
|
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-11f21e9e7b941cc98daf2ca7e3a78592.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.27",
|
|
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.27",
|
|
69
|
+
"@hyperdrive.bot/paseo-extension-sdk": "0.3.27",
|
|
70
|
+
"@hyperdrive.bot/paseo-highlight": "0.3.27",
|
|
71
|
+
"@hyperdrive.bot/paseo-protocol": "0.3.27",
|
|
72
|
+
"@hyperdrive.bot/paseo-relay": "0.3.27",
|
|
73
73
|
"@isaacs/ttlcache": "^2.1.4",
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
75
75
|
"@opencode-ai/sdk": "1.2.6",
|
|
Binary file
|