dsh-browser-application 0.37.0

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.
Files changed (44) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +27 -0
  3. package/cordis.patch.yml +29 -0
  4. package/lib/index.js +3377 -0
  5. package/lib/invariant.js +26 -0
  6. package/lib/types/bridge-url.d.ts +27 -0
  7. package/lib/types/browser-context.d.ts +38 -0
  8. package/lib/types/dsh-gateway.d.ts +42 -0
  9. package/lib/types/event-generation.d.ts +56 -0
  10. package/lib/types/extension-sessions.d.ts +26 -0
  11. package/lib/types/host-api.d.ts +47 -0
  12. package/lib/types/image-relay.d.ts +43 -0
  13. package/lib/types/index.d.ts +158 -0
  14. package/lib/types/invariant.d.ts +16 -0
  15. package/lib/types/remote-host-api.d.ts +12 -0
  16. package/lib/types/server.d.ts +166 -0
  17. package/lib/types/session-deferral.d.ts +33 -0
  18. package/lib/types/session-history.d.ts +30 -0
  19. package/lib/types/session-purge.d.ts +55 -0
  20. package/lib/types/session-workspace.d.ts +37 -0
  21. package/lib/types/token.d.ts +57 -0
  22. package/lib/types/tools.d.ts +42 -0
  23. package/lib/types/vision-selfcheck.d.ts +18 -0
  24. package/lib/types/vision.d.ts +57 -0
  25. package/package.json +95 -0
  26. package/src/bridge-url.ts +57 -0
  27. package/src/browser-context.ts +102 -0
  28. package/src/dsh-gateway.ts +66 -0
  29. package/src/event-generation.ts +385 -0
  30. package/src/extension-sessions.ts +40 -0
  31. package/src/host-api.ts +64 -0
  32. package/src/image-relay.ts +118 -0
  33. package/src/index.ts +575 -0
  34. package/src/invariant.ts +33 -0
  35. package/src/remote-host-api.ts +397 -0
  36. package/src/server.ts +658 -0
  37. package/src/session-deferral.ts +296 -0
  38. package/src/session-history.ts +220 -0
  39. package/src/session-purge.ts +154 -0
  40. package/src/session-workspace.ts +147 -0
  41. package/src/token.ts +100 -0
  42. package/src/tools.ts +301 -0
  43. package/src/vision-selfcheck.ts +35 -0
  44. package/src/vision.ts +135 -0
@@ -0,0 +1,26 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@yuxianglin/dsh-bridge-browser`.
4
+ * @module @yuxianglin/dsh-bridge-browser/invariant
5
+ */
6
+ const PACKAGE_NAME = "@yuxianglin/dsh-bridge-browser";
7
+ /** Cordis companion plugin name. */
8
+ const name = "bridge-browser-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: the bridge's connection registry and pending tool map
13
+ * are instance-private (no published event stream to assert against), and the
14
+ * wire contract is pinned by protocol.ts and covered by its unit tests. The
15
+ * tools are plain ctx.tools registrations observed by dsh-tools' own
16
+ * invariant.
17
+ */
18
+ const install = () => {};
19
+ /**
20
+ * Register this package's invariant companion.
21
+ * @param ctx - Cordis context carrying the invariant service.
22
+ * @returns the installed registration's disposer after setup succeeds.
23
+ */
24
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
25
+ //#endregion
26
+ export { apply, inject, name };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Resolve the browser-extension bridge WebSocket URL from the current page
3
+ * location or a discovery response. Shared by the settings row and tests.
4
+ * @module dsh-browser-application/src/bridge-url
5
+ */
6
+ /** Minimal Location fields needed to rebuild a loopback-friendly ws URL. */
7
+ export interface BridgeLocationLike {
8
+ protocol: string;
9
+ hostname: string;
10
+ port: string;
11
+ host: string;
12
+ }
13
+ /**
14
+ * Build `ws(s)://…/ext/bridge` from the page that hosts the dsh web UI.
15
+ * Loopback hostnames are normalized to `127.0.0.1` so the address pastes cleanly
16
+ * into the Chrome extension settings.
17
+ */
18
+ export declare function bridgeWsUrlFromLocation(location: BridgeLocationLike, bridgePath?: string): string;
19
+ /**
20
+ * Prefer the discovery endpoint; fall back to reconstructing from `location`.
21
+ * @param fetchImpl - injectable fetch (defaults to global fetch).
22
+ * @param location - page location used for fallback and relative discovery URL.
23
+ */
24
+ export declare function resolveBridgeWsUrl(location: BridgeLocationLike & {
25
+ origin?: string;
26
+ }, fetchImpl?: typeof fetch, bridgeConfigPath?: string): Promise<string>;
27
+ //# sourceMappingURL=bridge-url.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Model-facing browser page context injected after an explicit tab handoff.
3
+ *
4
+ * The extension captures the page immediately after the user chooses to
5
+ * follow it. A live Agent receives that snapshot at once; a deferred session
6
+ * keeps only its newest snapshot until `agent/created` publishes the
7
+ * Agent. Live inboxes also keep only the newest unclaimed browser snapshot.
8
+ * Injection deliberately does not wake an idle Agent — the snapshot is
9
+ * claimed together with the user's next message.
10
+ *
11
+ * @module
12
+ */
13
+ import type { Agent, AgentRegistry } from '@deepseek-ai/dsh-agent';
14
+ import { type ContextFormed, type UserMessage } from '@deepseek-ai/dsh-llm';
15
+ declare module '@deepseek-ai/dsh-llm' {
16
+ interface MessageSourceMap {
17
+ /** Followed-tab browser page snapshot owned by bridge-browser. */
18
+ 'browser-context': {
19
+ kind: 'browser-context';
20
+ } & ContextFormed;
21
+ }
22
+ }
23
+ /** MessageSource.kind for snapshot supersession and transcript presentation. */
24
+ export declare const BROWSER_CONTEXT_KIND: "browser-context";
25
+ /** Build one immutable context message from a captured browser snapshot. */
26
+ export declare function createBrowserSnapshotMessage(snapshot: string): UserMessage;
27
+ /** Deliver followed-page snapshots to live or not-yet-materialized Agents. */
28
+ export declare class BrowserContextInjector {
29
+ private readonly agents;
30
+ private readonly maxPending;
31
+ private readonly pending;
32
+ constructor(agents: Pick<AgentRegistry, 'get'>, maxPending?: number);
33
+ /** Inject now when possible; otherwise retain the newest snapshot per session. */
34
+ inject(sessionId: string, snapshot: string): 'injected' | 'queued';
35
+ /** Flush one provisional session at the supported Agent startup boundary. */
36
+ activate(agent: Agent): boolean;
37
+ }
38
+ //# sourceMappingURL=browser-context.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Structural subset of the dsh Host services the bridge adapts to: the
3
+ * TypertGateway wire seam, the Connection fetch handler, and the version-aware
4
+ * wire-stream opener.
5
+ *
6
+ * @module dsh-browser-application/src/dsh-gateway
7
+ */
8
+ import type { HostRpcFailure } from './host-api.ts';
9
+ /** Structural subset of dsh 0.2's Host TypertGateway service. */
10
+ export interface TypertGatewayLike {
11
+ readonly wireStream: {
12
+ /**
13
+ * dsh 0.2: `(endpoint, payload, uplink, peer, signal)`.
14
+ * Legacy stubs/tests: `(endpoint, payload, signal)`.
15
+ */
16
+ open(endpoint: string, payload: unknown, uplinkOrSignal: AsyncIterable<unknown> | AbortSignal, peer?: unknown, signal?: AbortSignal): Promise<AsyncIterable<unknown>>;
17
+ failure(error: unknown): HostRpcFailure;
18
+ };
19
+ invoke(request: {
20
+ readonly namespace: string;
21
+ readonly method: string;
22
+ readonly args: Readonly<Record<string, unknown>>;
23
+ readonly signal?: AbortSignal;
24
+ }): Promise<unknown>;
25
+ }
26
+ /**
27
+ * Open a Host wire stream against either dsh 0.2 or the legacy three-arg form.
28
+ *
29
+ * - arity 3: composition/unit stubs still use `(endpoint, payload, signal)`.
30
+ * - arity 5: real dsh 0.2 TypertGatewayWireStream.
31
+ * - arity 0: Cordis/service wrappers — must use the five-arg call. Treating
32
+ * these as three-arg maps AbortSignal onto uplink and leaves signal
33
+ * undefined (hello.ok → stream-failed → WS 1011).
34
+ */
35
+ export declare function openWireStream(gateway: TypertGatewayLike, endpoint: string, payload: unknown, signal: AbortSignal): Promise<AsyncIterable<unknown>>;
36
+ /** Structural subset of dsh 0.2's Host Connection service. */
37
+ export interface HostConnectionLike {
38
+ createSharedFetchHandler(channel: '/api'): {
39
+ fetch(request: Request): Promise<Response>;
40
+ };
41
+ }
42
+ //# sourceMappingURL=dsh-gateway.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * One authenticated extension connection's event streams, its active Session
3
+ * follower, and the forwarded Host waterfalls it answers.
4
+ *
5
+ * @module dsh-browser-application/src/event-generation
6
+ */
7
+ import type { RespondResult } from '@dsh-browser/protocol';
8
+ import { type TypertGatewayLike } from './dsh-gateway.ts';
9
+ import { type SessionSnapshot } from './session-history.ts';
10
+ import type { HostEventFrame } from './host-api.ts';
11
+ import { ExtensionSessionRegistry } from './extension-sessions.ts';
12
+ export type SendRemoteEventResult = (clientId: string, eventId: string, outcome: RemoteEventOutcome, signal: AbortSignal) => Promise<void>;
13
+ export type RemoteEventOutcome = {
14
+ readonly kind: 'next';
15
+ } | {
16
+ readonly kind: 'result';
17
+ readonly value?: unknown;
18
+ } | {
19
+ readonly kind: 'rejected';
20
+ readonly error: {
21
+ readonly name: string;
22
+ readonly message: string;
23
+ readonly code?: string;
24
+ readonly details?: unknown;
25
+ };
26
+ };
27
+ /** One authenticated extension connection's event streams and active Session follower. */
28
+ export declare class EventGeneration {
29
+ private readonly gateway;
30
+ private readonly sendResult;
31
+ private readonly extensionSessions;
32
+ private readonly onHistoryCursor;
33
+ private readonly lifetime;
34
+ private readonly signal;
35
+ private readonly queue;
36
+ private readonly tasks;
37
+ private readonly pendingQuestions;
38
+ private clientId;
39
+ private followAbort;
40
+ private followedSessionId;
41
+ private followRevision;
42
+ private disposed;
43
+ constructor(gateway: TypertGatewayLike, sendResult: SendRemoteEventResult, extensionSessions: ExtensionSessionRegistry, onHistoryCursor: (sessionId: string, cursor: number) => void, outerSignal: AbortSignal);
44
+ start(): void;
45
+ events(): AsyncIterable<HostEventFrame>;
46
+ openSessionHistory(sessionId: string, callSignal: AbortSignal, maxMessages?: number): Promise<SessionSnapshot>;
47
+ ensureSessionFollow(sessionId: string, callSignal: AbortSignal): Promise<void>;
48
+ respond(rpcId: string, result: RespondResult, signal: AbortSignal): Promise<unknown>;
49
+ dispose(): Promise<void>;
50
+ private openSessionFollow;
51
+ private pumpSessionEvents;
52
+ private pumpRemoteEvents;
53
+ private handleRemoteEvent;
54
+ private track;
55
+ }
56
+ //# sourceMappingURL=event-generation.d.ts.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Track session ids that the browser extension has driven through the bridge.
3
+ * Desktop-native sessions must keep the host userQuestions waterfall so the
4
+ * Desktop UI can render ask_user_question cards.
5
+ * @module dsh-browser-application/src/extension-sessions
6
+ */
7
+ /** Mutable registry of extension-owned session ids. */
8
+ export declare class ExtensionSessionRegistry {
9
+ private readonly ids;
10
+ /** Remember a session the extension successfully created or prompted. */
11
+ note(sessionId: string | undefined): void;
12
+ /** Whether the extension has touched this session over the bridge. */
13
+ has(sessionId: string | undefined): boolean;
14
+ /** Test helper: drop all tracked ids. */
15
+ clear(): void;
16
+ }
17
+ /**
18
+ * Decide whether the bridge should own ask_user_question for this request.
19
+ * Desktop sessions must fall through to the native answerer waterfall.
20
+ */
21
+ export declare function shouldBridgeOwnQuestion(input: {
22
+ hasExtensionConnection: boolean;
23
+ sessionId: string | undefined;
24
+ extensionSessions: Pick<ExtensionSessionRegistry, 'has'>;
25
+ }): boolean;
26
+ //# sourceMappingURL=extension-sessions.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Bridge-owned Host API consumed by the WebSocket carrier.
3
+ *
4
+ * This boundary keeps release-specific Host topology out of the browser wire
5
+ * server. dsh 0.1.5 implements it with Typert Remotes and Connection.
6
+ *
7
+ * @module
8
+ */
9
+ import type { RespondResult } from '@dsh-browser/protocol';
10
+ /** Stable failure envelope understood by the extension panel. */
11
+ export interface HostRpcFailure {
12
+ readonly code: string;
13
+ readonly message: string;
14
+ readonly details: object;
15
+ }
16
+ /** Business result returned by the active dsh Host adapter. */
17
+ export type HostRpcResult<T = unknown> = {
18
+ readonly ok: true;
19
+ readonly value: T;
20
+ } | {
21
+ readonly ok: false;
22
+ readonly error: HostRpcFailure;
23
+ };
24
+ /** One extension-originated unary call after WebSocket decoding. */
25
+ export interface HostRpcCall {
26
+ readonly rpcId: string;
27
+ readonly method: string;
28
+ readonly payload: unknown;
29
+ readonly signal: AbortSignal;
30
+ }
31
+ /** Event retained as the private bridge-to-extension protocol. */
32
+ export interface HostEventFrame {
33
+ readonly rpcId: string;
34
+ readonly method: string;
35
+ readonly payload: unknown;
36
+ }
37
+ /** API surface the WebSocket carrier needs from a dsh Host. */
38
+ export interface BrowserHostApi {
39
+ call(call: HostRpcCall): Promise<HostRpcResult>;
40
+ events(signal: AbortSignal): AsyncIterable<HostEventFrame>;
41
+ respond(rpcId: string, result: RespondResult, signal: AbortSignal): Promise<unknown>;
42
+ }
43
+ /** Convert an arbitrary Host rejection to the open wire failure vocabulary. */
44
+ export declare function hostFailure(error: unknown): HostRpcFailure;
45
+ /** Narrow unknown JSON-like data without accepting arrays. */
46
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
47
+ //# sourceMappingURL=host-api.d.ts.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Serving the extension's image-recognition requests on the desktop side.
3
+ *
4
+ * The extension is asked for bytes first because only it carries the user's login
5
+ * state; when it could not fetch at all — its content-security policy, a host
6
+ * permission, or an enterprise rule — it sends the URL and the desktop tries with
7
+ * its own network stack instead. The two paths are complementary rather than
8
+ * redundant, which is why the frame carries a source rather than always one kind.
9
+ *
10
+ * The desktop has no image codec, so bytes it fetches itself are passed through
11
+ * unchanged once they are known to be an image and to be a sane size. Anything
12
+ * the extension already normalized arrives pre-scaled.
13
+ *
14
+ * @module
15
+ */
16
+ import type { ImageRecognitionRequest, ImageSource } from '@dsh-browser/protocol';
17
+ import type { VisionClient } from './vision.ts';
18
+ export type ImageRelayResult = {
19
+ ok: true;
20
+ desc: string;
21
+ } | {
22
+ ok: false;
23
+ code: string;
24
+ message: string;
25
+ };
26
+ export declare class ImageRelay {
27
+ private readonly vision;
28
+ private readonly fetchImpl;
29
+ constructor(vision: VisionClient | undefined, fetchImpl?: typeof fetch);
30
+ /** Whether a vision model is configured, which is what `hello.ok` advertises. */
31
+ get available(): boolean;
32
+ /**
33
+ * Recognize one image for the extension.
34
+ *
35
+ * @param request - the page context the extension gathered.
36
+ * @param source - normalized bytes, or a URL for the desktop to fetch.
37
+ * @returns a one-line description, or a classified failure.
38
+ */
39
+ recognize(request: ImageRecognitionRequest, source: ImageSource): Promise<ImageRelayResult>;
40
+ /** The desktop's own attempt, for when the extension's fetch did not work. */
41
+ private fetchByUrl;
42
+ }
43
+ //# sourceMappingURL=image-relay.d.ts.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * `dsh-browser-application`: token-authenticated WebSocket bridge for
3
+ * the browser extension plus the text-only `browser_*` tool set.
4
+ *
5
+ * The bridge mounts its own upgrade route (`/ext/bridge`) on the host
6
+ * webserver, OUTSIDE the /api trust fence — so it brings its own bearer-token
7
+ * authentication (first frame `hello` within HELLO_TIMEOUT_MS). Extension
8
+ * calls, Session streams, and Host waterfalls use dsh's Typert Gateway
9
+ * and Connection services.
10
+ * Tools execute by dispatching
11
+ * `tool.call` frames to the connected extension, which performs the action in
12
+ * the tab explicitly controlled by the user.
13
+ *
14
+ * Opt-in by design: nothing is registered unless this plugin appears in the
15
+ * composition. No dsh core code is touched.
16
+ *
17
+ * @module dsh-browser-application
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ import z from '@deepseek-ai/schemastery';
21
+ import { VisionClient } from './vision.ts';
22
+ /**
23
+ * The plugin's display title, shown wherever the desktop lists it.
24
+ *
25
+ * It reads as a settings page because that is what the entry is: the desktop's
26
+ * Plugins page renders this plugin's Config as an editable form, and this is the
27
+ * heading on it.
28
+ */
29
+ export declare const name = "dsh \u6D4F\u89C8\u5668\u8BBE\u7F6E";
30
+ /** Services required by this plugin. */
31
+ export declare const inject: string[];
32
+ /** Plugin config: deployment-varying tunables only; the wire contract stays fixed. */
33
+ export interface Config {
34
+ /** Fixed bearer token. When absent, a token is generated on first boot and persisted under the dsh home (0600). */
35
+ token?: string;
36
+ /** Per-tool-call timeout in ms. Defaults to 90000. */
37
+ toolTimeoutMs?: number;
38
+ /** Upper bound on one snapshot's rendered characters. Defaults to 32000; minimum 500. */
39
+ snapshotMaxChars?: number;
40
+ /** Upper bound on interactive inventory items per snapshot. Defaults to 60. */
41
+ maxInteractiveItems?: number;
42
+ /** Dedicated workspace path for extension-created sessions. Empty disables grouping. */
43
+ sessionWorkspacePath?: string;
44
+ /**
45
+ * Display name for that workspace. Defaults to {@link DEFAULT_SESSION_WORKSPACE_TITLE}.
46
+ *
47
+ * Without it the group is named after its directory, so it reads as
48
+ * "browser-sessions" — which looks like an internal detail rather than the
49
+ * user's own browser conversations, and nothing in the interface renames it. An
50
+ * empty string accepts whatever name the desktop derives.
51
+ */
52
+ sessionWorkspaceTitle?: string;
53
+ /** Defer real session creation until the first prompt. Defaults to true. */
54
+ deferSessionCreate?: boolean;
55
+ /**
56
+ * Allow the model to open pages in the user's browser on its own initiative.
57
+ *
58
+ * This is the authoritative switch, and it gates three things together so it
59
+ * cannot be a half-measure: the prompt guidance that tells the model to open
60
+ * pages, the extension's `@open` command, and the extension's automatic panel
61
+ * opening. Turning it off means the model may still read and operate a page
62
+ * the user already has open; it just stops driving the browser to new places
63
+ * unprompted. Defaults to true.
64
+ */
65
+ openPagesForUser?: boolean;
66
+ /**
67
+ * API key for desktop-side image recognition. Empty disables it, and the
68
+ * extension then keeps its own network path instead of relaying.
69
+ *
70
+ * Relaying exists so this key never enters a browser profile, and so image
71
+ * fetches that the extension's content-security policy or an enterprise rule
72
+ * blocks can still succeed through the desktop's own network stack.
73
+ */
74
+ visionApiKey?: string;
75
+ /** Chat-completions base URL. Defaults to the DeepSeek endpoint. */
76
+ visionBaseUrl?: string;
77
+ /**
78
+ * Model id that reads the images.
79
+ *
80
+ * Configurable because {@link visionBaseUrl} is: pointing this plugin at another
81
+ * provider while the model id stays fixed would send a name that provider has
82
+ * never heard of. Defaults to {@link VISION_MODEL}, which is the only DeepSeek id
83
+ * that reports an image input modality — and note that it is the *id*, not the
84
+ * display name `DeepSeek-V4.1-Flash`, which the API rejects with 400.
85
+ */
86
+ visionModel?: string;
87
+ /**
88
+ * `off` disables thinking blocks. On a pure perception task they cost more than
89
+ * the image does, and the model cannot verify that the setting took effect, so
90
+ * the desktop reads `usage` back to confirm no reasoning tokens were billed.
91
+ */
92
+ visionThinking?: string;
93
+ /** Per-image timeout in ms. Defaults to 20000. */
94
+ visionTimeoutMs?: number;
95
+ }
96
+ export declare const Config: z<Config>;
97
+ /** The shape after schemastery applies its defaults to every field. */
98
+ type ResolvedConfig = Required<Omit<Config, 'token'>> & Pick<Config, 'token'>;
99
+ /** Configured budgets must be positive integers. Exported for validation tests. */
100
+ export declare function assertPositiveInteger(name: string, value: number): void;
101
+ /**
102
+ * Apply defaults and direct-call validation at the plugin boundary.
103
+ * @param config - Loader-resolved or directly supplied plugin configuration.
104
+ * @returns a complete configuration ready for runtime use.
105
+ */
106
+ export declare function resolveConfig(config: Config): ResolvedConfig;
107
+ /**
108
+ * Build the desktop's vision client, or nothing when no key is configured.
109
+ *
110
+ * Absence is meaningful rather than an error: `hello.ok` then reports
111
+ * `imageRecognition: false`, and the extension keeps its own network path instead
112
+ * of sending frames nobody would answer.
113
+ *
114
+ * @param config - the resolved plugin configuration.
115
+ * @returns a client, or `undefined` when vision is not configured.
116
+ */
117
+ export declare function buildVisionClient(config: ResolvedConfig): VisionClient | undefined;
118
+ /** The host's credential service, narrowed to the one call this file makes. */
119
+ export interface CredentialSource {
120
+ resolve(ref: string): Promise<{
121
+ value: string;
122
+ source: string;
123
+ } | undefined>;
124
+ }
125
+ /** The host, narrowed to the one call this file makes on it. */
126
+ export interface VisionHost {
127
+ get(name: string): unknown;
128
+ }
129
+ /**
130
+ * Which vision client to use: an explicitly configured key first, the credential the
131
+ * desktop already holds for this provider second.
132
+ *
133
+ * The fallback is deliberately the *credential* service and not the *account* one.
134
+ * `deepseekAccount.resolveToken()` was tried first and is wrong: it answers with the
135
+ * desktop's platform token, which the public chat-completions API rejects with 401,
136
+ * turning a clear "not configured" into an authentication failure about a key nobody
137
+ * ever wrote. `credentials.resolve('DEEPSEEK_API_KEY')` is the key the desktop itself
138
+ * calls this provider with.
139
+ *
140
+ * Holding the key does not send anything: the bridge relays only when the extension
141
+ * asks, and the extension asks only for a tier the user turned on. The opt-in that
142
+ * matters is the tier, not the presence of a key.
143
+ *
144
+ * @param host - the Cordis context, narrowed to `get`.
145
+ * @param config - plugin config (schema defaults applied).
146
+ * @returns the client, or undefined when neither source supplies a key.
147
+ */
148
+ export declare function resolveVisionClient(host: VisionHost, config: ResolvedConfig): Promise<VisionClient | undefined>;
149
+ /**
150
+ * Mount the bridge: resolve the token, register the upgrade route, the tool
151
+ * set, and an optional system-prompt section, all effect-scoped for HMR.
152
+ *
153
+ * @param ctx - Cordis context.
154
+ * @param config - plugin config (schema defaults applied).
155
+ */
156
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
157
+ export {};
158
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `dsh-browser-application`.
3
+ * @module dsh-browser-application/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "bridge-browser-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * dsh 0.2 Host adapter: unary calls through TypertGateway, plus the Session
3
+ * and forwarded-event plumbing assembled from the gateway, history, and
4
+ * event-generation modules.
5
+ *
6
+ * @module dsh-browser-application/src/remote-host-api
7
+ */
8
+ import type { BrowserHostApi } from './host-api.ts';
9
+ import { type TypertGatewayLike, type HostConnectionLike } from './dsh-gateway.ts';
10
+ /** Build the dsh 0.2 Host implementation. */
11
+ export declare function createRemoteHostApi(gateway: TypertGatewayLike, connection: HostConnectionLike): BrowserHostApi;
12
+ //# sourceMappingURL=remote-host-api.d.ts.map
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Bridge WebSocket carrier: token-authenticated connection registry, gateway
3
+ * RPC dispatch, per-connection event pump, and tool-call dispatch to the
4
+ * connected browser extension.
5
+ *
6
+ * The route this server mounts (`/ext/bridge`) lives OUTSIDE the /api trust
7
+ * fence (which only guards the client-connection routes), so the bridge brings
8
+ * its own authentication: a bearer token presented in the `hello` frame within
9
+ * HELLO_TIMEOUT_MS. Host calls terminate at the bridge-owned Host adapter.
10
+ * Methods the /api carrier pins to loopback (`PRIVILEGED_METHODS`)
11
+ * stay loopback-only here regardless of the token, defense in depth for
12
+ * `--host 0.0.0.0` deployments.
13
+ *
14
+ * One active connection at a time: a new authenticated socket replaces the
15
+ * previous one (the old socket is closed and its in-flight tool calls settle
16
+ * as `bridge-closed`).
17
+ *
18
+ * @module
19
+ */
20
+ import type { IncomingMessage } from 'node:http';
21
+ import type { Duplex } from 'node:stream';
22
+ import type { BrowserHostApi } from './host-api.ts';
23
+ import { type BridgeCaps, type BridgePolicy, type ToolErrorCode } from '@dsh-browser/protocol';
24
+ import type { ImageRelay } from './image-relay.ts';
25
+ /** Loopback IPv4/IPv6 literals (IPv4-mapped included). Exported for tests and reuse. */
26
+ export declare function isLoopbackAddress(address: string | undefined): boolean;
27
+ /** Error thrown by requestTool; the tool registry turns it into an isError result. */
28
+ export declare class BridgeToolError extends Error {
29
+ readonly code: ToolErrorCode;
30
+ constructor(code: ToolErrorCode, message: string);
31
+ }
32
+ /** Dependencies the bridge needs from the host. */
33
+ export interface BridgeServerDeps {
34
+ /** Bearer token the extension must present in `hello`. */
35
+ token: string;
36
+ /** Active dsh Host adapter used for unary calls, events, and waterfalls. */
37
+ api: BrowserHostApi;
38
+ /** Default per-tool-call timeout in ms. */
39
+ toolTimeoutMs: number;
40
+ /** Capabilities to echo in `hello.ok` (negotiated snapshot budgets). */
41
+ caps: BridgeCaps;
42
+ /** What the desktop app allows, sent alongside the caps in `hello.ok`. */
43
+ policy: BridgePolicy;
44
+ /**
45
+ * Image recognition on the extension's behalf. Absent when no vision model is
46
+ * configured, which the extension learns from `hello.ok`'s policy.
47
+ */
48
+ imageRelay?: ImageRelay;
49
+ /**
50
+ * Why no relay is available, in words the user can act on.
51
+ *
52
+ * Sent in `hello.ok` so the extension can put it in front of whoever asked for an
53
+ * image, instead of reporting only that recognition is unavailable — which leaves
54
+ * the reader with a dead end and no next step.
55
+ */
56
+ visionUnavailableReason?: string;
57
+ /** Seed a followed-page snapshot into a live or deferred Agent session. */
58
+ injectBrowserSnapshot: (sessionId: string, snapshot: string) => void | Promise<void>;
59
+ /**
60
+ * Permanently delete one session's durable storage. Callers archive the
61
+ * session through the gateway first; this only removes files.
62
+ */
63
+ purgeSession: (sessionId: string) => Promise<void>;
64
+ /**
65
+ * Test seam: force the remote address seen by the privilege gate. The
66
+ * sandbox cannot bind arbitrary loopback literals, so the non-loopback
67
+ * branch is exercised through this override; production never sets it.
68
+ */
69
+ remoteAddressOverride?: string;
70
+ /** Seconds a fresh socket may present `hello`; defaults to HELLO_TIMEOUT_MS. */
71
+ helloTimeoutMs?: number;
72
+ /** Server ping cadence; defaults to PING_INTERVAL_MS. */
73
+ pingIntervalMs?: number;
74
+ }
75
+ /**
76
+ * Decode one ws message payload to text. Exported so all three delivery
77
+ * shapes (fragmented buffer list, Buffer, ArrayBuffer) are unit-testable
78
+ * directly — node ws only ever delivers Buffers in practice.
79
+ * @param data - ws message payload.
80
+ * @returns the decoded UTF-8 text.
81
+ */
82
+ export declare function messageToText(data: Buffer | ArrayBuffer | Buffer[]): string;
83
+ /**
84
+ * Token-authenticated bridge server. Construct once per plugin instance;
85
+ * dispose with {@link close}.
86
+ */
87
+ export declare class BridgeServer {
88
+ private readonly deps;
89
+ private readonly wss;
90
+ private current;
91
+ private readonly pendingTools;
92
+ private readonly orderedSessionRpcs;
93
+ private closed;
94
+ constructor(deps: BridgeServerDeps);
95
+ /**
96
+ * Handle one HTTP upgrade for the bridge path.
97
+ * @param req - upgrade request (carries the client's remote address).
98
+ * @param socket - raw socket transferred by the HTTP server.
99
+ * @param head - bytes already read after the upgrade headers.
100
+ */
101
+ handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void;
102
+ /**
103
+ * Request one browser action from the connected extension.
104
+ * @param name - tool name (also the wire action name).
105
+ * @param args - validated tool arguments.
106
+ * @param signal - caller cancellation (abort settles the call as cancelled).
107
+ * @param timeoutMs - per-call budget; defaults to the plugin config value.
108
+ * @param sessionId - optional owning Agent session for approval continuity.
109
+ * @returns the extension's action result.
110
+ * @throws BridgeToolError when no extension is connected, the call times
111
+ * out, is cancelled, or the extension reports a failure.
112
+ */
113
+ requestTool(name: string, args: Record<string, unknown>, signal: AbortSignal, timeoutMs?: number, sessionId?: string): Promise<unknown>;
114
+ /**
115
+ * Terminate the server: close the acceptor, drop all sockets, reject all
116
+ * in-flight tool calls.
117
+ * @returns a promise resolving after the acceptor and all pumps stop.
118
+ */
119
+ close(): Promise<void>;
120
+ /** @returns whether an authenticated extension is currently connected. */
121
+ hasConnection(): boolean;
122
+ private attach;
123
+ /** Promote an authenticated socket to the single active slot. */
124
+ private promote;
125
+ private handleReadyFrame;
126
+ /** Recognition requests in flight; the extension bounds its side as well. */
127
+ private imageCallsInFlight;
128
+ private readonly maxImageCalls;
129
+ /**
130
+ * Serve one image-recognition request from the extension.
131
+ *
132
+ * A failure is answered with a frame rather than a throw, because the extension
133
+ * records it in the manifest: the model must be told "there is an image here
134
+ * and it could not be read", not shown nothing at all.
135
+ */
136
+ private handleImageCall;
137
+ /**
138
+ * Preserve prompt/cancel arrival order per session. In particular, the
139
+ * first prompt may still be materializing a provisional session; its cancel
140
+ * must not reach the gateway until that admission has completed.
141
+ */
142
+ private routeRpc;
143
+ private handleRpc;
144
+ /** Relay a pending Host waterfall response through the active adapter. */
145
+ private handleRespond;
146
+ private settleTool;
147
+ /** Close the current connection (if any) and settle its in-flight calls. */
148
+ private replaceConnection;
149
+ }
150
+ /**
151
+ * Tool error payload → stable code. The wire parser enforces string fields,
152
+ * so the fallback branches are parser-gated; exported so the fallback
153
+ * contract is unit-testable directly.
154
+ * @param payload - extension-reported error payload.
155
+ * @returns the stable error code.
156
+ */
157
+ export declare function payloadCode(payload: unknown): ToolErrorCode;
158
+ /**
159
+ * Tool error payload → message. The wire parser enforces string fields, so
160
+ * the fallback branches are parser-gated; exported so the fallback contract
161
+ * is unit-testable directly.
162
+ * @param payload - extension-reported error payload.
163
+ * @returns the human-readable message.
164
+ */
165
+ export declare function payloadMessage(payload: unknown): string;
166
+ //# sourceMappingURL=server.d.ts.map