@mind-harness/mh-browser 0.0.0-stage → 0.1.1
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/LICENSE +57 -0
- package/README.md +4 -2
- package/extension/manifest.json +7 -0
- package/extension/sw.js +1 -0
- package/icon.svg +5 -0
- package/lib/index.js +6194 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +360 -0
- package/lib/typert.remote-client.d.ts +24 -0
- package/lib/typert.remote-client.js +52 -0
- package/lib/types/config.d.ts +20 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/presence/book.d.ts +36 -0
- package/lib/types/presence/fold.d.ts +49 -0
- package/lib/types/presence/notice.d.ts +8 -0
- package/lib/types/presence/plan.d.ts +47 -0
- package/lib/types/presence/record.d.ts +47 -0
- package/lib/types/presence/register.d.ts +21 -0
- package/lib/types/presence/source.d.ts +18 -0
- package/lib/types/projections.d.ts +25 -0
- package/lib/types/render/envelope.d.ts +80 -0
- package/lib/types/representation/agent-view.d.ts +61 -0
- package/lib/types/representation/agent-view.js +279 -0
- package/lib/types/representation/discovery.d.ts +23 -0
- package/lib/types/representation/read.d.ts +45 -0
- package/lib/types/service/actions.d.ts +7 -0
- package/lib/types/service/browser.d.ts +16 -0
- package/lib/types/service/control.d.ts +49 -0
- package/lib/types/service/extension.d.ts +30 -0
- package/lib/types/service/files.d.ts +17 -0
- package/lib/types/service/invoke.d.ts +31 -0
- package/lib/types/service/service.d.ts +183 -0
- package/lib/types/service/test-hooks.d.ts +7 -0
- package/lib/types/service/waits.d.ts +40 -0
- package/lib/types/service/windows.d.ts +174 -0
- package/lib/types/tools/act.d.ts +4 -0
- package/lib/types/tools/attachments.d.ts +23 -0
- package/lib/types/tools/delivery.d.ts +64 -0
- package/lib/types/tools/download.d.ts +4 -0
- package/lib/types/tools/handover.d.ts +4 -0
- package/lib/types/tools/invoke.d.ts +4 -0
- package/lib/types/tools/open.d.ts +4 -0
- package/lib/types/tools/paths.d.ts +3 -0
- package/lib/types/tools/presence.d.ts +4 -0
- package/lib/types/tools/register.d.ts +3 -0
- package/lib/types/tools/session.d.ts +32 -0
- package/lib/types/tools/tab-log.d.ts +18 -0
- package/lib/types/types.d.ts +172 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +76 -3
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { BrowserContext, CDPSession } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Directory of the unpacked extension shipped with this package.
|
|
4
|
+
* Walks up from this module: sources live in `src/`, the bundle in `lib/`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extensionDir(): string;
|
|
7
|
+
/**
|
|
8
|
+
* The extension that opens tabs and groups them. Loaded once per browser.
|
|
9
|
+
* Calls run in its service worker, which Chrome may restart.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Extension {
|
|
12
|
+
private readonly context;
|
|
13
|
+
private readonly id;
|
|
14
|
+
private workerPage;
|
|
15
|
+
private constructor();
|
|
16
|
+
static load(context: BrowserContext, browserSession: CDPSession): Promise<Extension>;
|
|
17
|
+
/** `chrome.tabs.create` in the named window. Returns the extension's tab id. */
|
|
18
|
+
createTab(windowId: number, url: string): Promise<number>;
|
|
19
|
+
tabIdsIn(windowId: number): Promise<number[]>;
|
|
20
|
+
activate(tabId: number): Promise<void>;
|
|
21
|
+
group(tabId: number, target: {
|
|
22
|
+
groupId: number;
|
|
23
|
+
} | {
|
|
24
|
+
windowId: number;
|
|
25
|
+
}): Promise<number>;
|
|
26
|
+
styleGroup(groupId: number, title: string, color: 'blue' | 'red'): Promise<void>;
|
|
27
|
+
ungroup(tabId: number): Promise<void>;
|
|
28
|
+
private worker;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=extension.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A download name with no directory segments. */
|
|
2
|
+
export declare function safeFilename(name: string): string;
|
|
3
|
+
/** Content-Disposition filename, otherwise the last segment of the final URL. */
|
|
4
|
+
export declare function filenameFrom(disposition: string | undefined, url: string): string;
|
|
5
|
+
export interface Written {
|
|
6
|
+
/** Relative to the session cwd. */
|
|
7
|
+
path: string;
|
|
8
|
+
bytes: number;
|
|
9
|
+
sha256: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Stream a body into a new file under `<cwd>/downloads`. The directory must be
|
|
13
|
+
* a real directory inside the cwd, and the file is created exclusively without
|
|
14
|
+
* following links, so nothing outside the cwd and no existing file is written.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeDownload(cwd: string, name: string, body: AsyncIterable<Uint8Array>): Promise<Written>;
|
|
17
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { InvokeCall, InvokeSent, PageError, PageView } from '../types.ts';
|
|
2
|
+
export interface SendRequest {
|
|
3
|
+
method: string;
|
|
4
|
+
url: string;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
data?: string;
|
|
7
|
+
multipart?: Record<string, string | {
|
|
8
|
+
name: string;
|
|
9
|
+
mimeType: string;
|
|
10
|
+
buffer: Buffer;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export type SendResult = {
|
|
14
|
+
kind: 'response';
|
|
15
|
+
status: number;
|
|
16
|
+
location?: string;
|
|
17
|
+
mediaType?: string;
|
|
18
|
+
body: Uint8Array;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'lost';
|
|
21
|
+
};
|
|
22
|
+
export interface InvokeHost {
|
|
23
|
+
idleView(url: string): Promise<PageView | undefined>;
|
|
24
|
+
open(url: string): Promise<PageView | PageError>;
|
|
25
|
+
navigate(tab: string, url: string): Promise<PageView | PageError>;
|
|
26
|
+
reload(url: string, tab: string): Promise<PageView | PageError>;
|
|
27
|
+
send(request: SendRequest): Promise<SendResult>;
|
|
28
|
+
readFile(value: unknown): Promise<string>;
|
|
29
|
+
}
|
|
30
|
+
export declare function runInvoke(host: InvokeHost, pageUrl: string, call: InvokeCall): Promise<InvokeSent>;
|
|
31
|
+
//# sourceMappingURL=invoke.d.ts.map
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
import type { BrowserConfig } from '../config.ts';
|
|
4
|
+
import type { HeldPage, PresenceReceipt, PresenceRecord, PresenceSource } from '../presence/record.ts';
|
|
5
|
+
import type { PendingOp } from '../presence/fold.ts';
|
|
6
|
+
import type { ActAction, Download, FetchResponseBody, InvokeCall, InvokeSent, PageError, PageView, TabInfo, WaitFor } from '../types.ts';
|
|
7
|
+
import { type Taken } from './control.ts';
|
|
8
|
+
import { testHooks, type TestHooks } from './test-hooks.ts';
|
|
9
|
+
declare module '@deepseek-ai/cordis' {
|
|
10
|
+
interface Context {
|
|
11
|
+
mhBrowser: MhBrowserService;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export type WriteTarget = {
|
|
15
|
+
root: string;
|
|
16
|
+
} | {
|
|
17
|
+
denied: string;
|
|
18
|
+
code: 'SANDBOX_DENIED' | 'NO_WORKSPACE';
|
|
19
|
+
};
|
|
20
|
+
export interface HandBack extends Taken {
|
|
21
|
+
urls: string[];
|
|
22
|
+
note?: string;
|
|
23
|
+
page?: PageView;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Browser service. Launch, windows, tabs, waits, the request channel, and
|
|
27
|
+
* the current representation of each tab. Declared actions and downloads go
|
|
28
|
+
* through the request channel. Person input outside a service dispatch takes
|
|
29
|
+
* the session's browser. `act` returns a snapshot, including on a page that
|
|
30
|
+
* also has an Agent View.
|
|
31
|
+
*/
|
|
32
|
+
export declare class MhBrowserService extends TypertRemoteService {
|
|
33
|
+
static Config: import("@deepseek-ai/schemastery").default<BrowserConfig>;
|
|
34
|
+
private readonly windows;
|
|
35
|
+
private readonly launchOptions;
|
|
36
|
+
private context;
|
|
37
|
+
private ready;
|
|
38
|
+
private closing;
|
|
39
|
+
private relaunching;
|
|
40
|
+
private readonly crashes;
|
|
41
|
+
/** One queue per owner: a slow wait in one Session does not hold up another. */
|
|
42
|
+
private readonly queues;
|
|
43
|
+
private readonly shown;
|
|
44
|
+
private readonly book;
|
|
45
|
+
private readonly maxBytes;
|
|
46
|
+
private readonly workspaces;
|
|
47
|
+
private readonly control;
|
|
48
|
+
private readonly personTouch;
|
|
49
|
+
constructor(ctx: Context, config: BrowserConfig);
|
|
50
|
+
open(owner: string, items: {
|
|
51
|
+
url: string;
|
|
52
|
+
waitFor?: WaitFor;
|
|
53
|
+
}[], signal?: AbortSignal): Promise<(PageView | PageError)[]>;
|
|
54
|
+
view(owner: string, tab: string, signal?: AbortSignal): Promise<PageView | PageError>;
|
|
55
|
+
/** Pages whose current representation is already in the model's presence. */
|
|
56
|
+
rememberPresence(owner: string, urls: string[]): void;
|
|
57
|
+
presenceList(owner: string): HeldPage[];
|
|
58
|
+
presencePut(owner: string, urls: string[], source: PresenceSource): PresenceReceipt[];
|
|
59
|
+
presenceRemove(owner: string, urls: string[]): PresenceReceipt[];
|
|
60
|
+
/** The set the session log folds to; taken once per process. */
|
|
61
|
+
presenceLoad(owner: string, folded: {
|
|
62
|
+
pages: readonly HeldPage[];
|
|
63
|
+
removals: readonly PendingOp[];
|
|
64
|
+
}): void;
|
|
65
|
+
presenceTakeResume(owner: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Re-read presence pages and build the next reminder.
|
|
68
|
+
* A tab already on the address is read in place. A check that has to open
|
|
69
|
+
* one closes it before returning. Removals are included, and they cause
|
|
70
|
+
* every remaining page to be read so a page kept out by the byte budget
|
|
71
|
+
* can come back. An empty set has nothing to read and does not wait for the
|
|
72
|
+
* browser; a browser that is down makes every page absent.
|
|
73
|
+
*/
|
|
74
|
+
presenceCheck(owner: string, which: readonly string[] | 'all', opts?: {
|
|
75
|
+
baseline?: boolean;
|
|
76
|
+
replaces?: boolean;
|
|
77
|
+
force?: boolean;
|
|
78
|
+
signal?: AbortSignal;
|
|
79
|
+
}): Promise<{
|
|
80
|
+
text: string;
|
|
81
|
+
record: PresenceRecord;
|
|
82
|
+
} | undefined>;
|
|
83
|
+
/**
|
|
84
|
+
* Where a download the page starts may be written: the session's writable
|
|
85
|
+
* root, or why it may not write at all.
|
|
86
|
+
*/
|
|
87
|
+
noteWorkspace(owner: string, target: WriteTarget): void;
|
|
88
|
+
tabs(owner: string): TabInfo[];
|
|
89
|
+
takeClosed(owner: string): string[];
|
|
90
|
+
windowId(owner: string, tab: string): Promise<number | undefined>;
|
|
91
|
+
fetch(url: string, opts?: {
|
|
92
|
+
accept?: string;
|
|
93
|
+
ifNoneMatch?: string;
|
|
94
|
+
}): Promise<FetchResponseBody | {
|
|
95
|
+
kind: 'error';
|
|
96
|
+
reason: string;
|
|
97
|
+
}>;
|
|
98
|
+
pushPath(owner: string, tab: string, path: string): Promise<void>;
|
|
99
|
+
started(): Promise<void>;
|
|
100
|
+
[testHooks](): TestHooks;
|
|
101
|
+
act(owner: string, tab: string, action: ActAction, signal?: AbortSignal): Promise<PageView>;
|
|
102
|
+
holdHandover(owner: string, tab: string): void;
|
|
103
|
+
releaseHandover(owner: string): void;
|
|
104
|
+
/** `undefined` when no screenshot can be taken now: a dialog is open, or the page refused. */
|
|
105
|
+
capture(owner: string, tab: string, signal?: AbortSignal): Promise<Uint8Array | undefined>;
|
|
106
|
+
invoke(owner: string, page: string, call: InvokeCall, opts?: {
|
|
107
|
+
signal?: AbortSignal;
|
|
108
|
+
readFile?: (value: unknown) => Promise<string>;
|
|
109
|
+
}): Promise<InvokeSent>;
|
|
110
|
+
/**
|
|
111
|
+
* Not queued: it opens no tab, and a long body must not hold up the
|
|
112
|
+
* Session's other calls. `root` is the directory the session may write.
|
|
113
|
+
*/
|
|
114
|
+
download(owner: string, url: string, root: string, signal?: AbortSignal): Promise<Download>;
|
|
115
|
+
controlOf(owner: string): 'model' | 'person';
|
|
116
|
+
/** Presence registers this. A hand-back calls it with the touched presence pages. */
|
|
117
|
+
bindPersonTouch(fn: (owner: string, urls: string[]) => void): () => void;
|
|
118
|
+
/**
|
|
119
|
+
* Give the browser to the person. The named tab stays in use until hand-back.
|
|
120
|
+
* This does not wait; the tool waits on the question card.
|
|
121
|
+
*/
|
|
122
|
+
handover(owner: string, req: {
|
|
123
|
+
tab?: string;
|
|
124
|
+
reason: string;
|
|
125
|
+
}, signal?: AbortSignal): Promise<void>;
|
|
126
|
+
/** Remember the Session title for its tab group. Empty when the host has none. */
|
|
127
|
+
noteSessionTitle(owner: string, title: string): void;
|
|
128
|
+
/** Raise the window of this owner's latest tab. False when it has no tab. */
|
|
129
|
+
bringToFront(owner: string): Promise<boolean>;
|
|
130
|
+
/** The person asked to see the page this Session handed over. */
|
|
131
|
+
revealHandover(sessionId: string, signal?: AbortSignal): Promise<{
|
|
132
|
+
status: 'revealed' | 'not-handing-over' | 'no-tab';
|
|
133
|
+
}>;
|
|
134
|
+
/** The person answered. Returns the note, the operation count, and the named tab. */
|
|
135
|
+
handBack(owner: string, note?: string): Promise<HandBack>;
|
|
136
|
+
/** A stopped handover gives control back. A finished hand-back is left alone. */
|
|
137
|
+
releaseIfHeld(owner: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* The person took over between turns. The next message they send is the
|
|
140
|
+
* hand-back. A handover that is still waiting on its card is not.
|
|
141
|
+
*/
|
|
142
|
+
takePersonHandback(owner: string): HandBack | undefined;
|
|
143
|
+
private openOne;
|
|
144
|
+
private perform;
|
|
145
|
+
/** A file chooser the action opened. The tab stays in use until it navigates. */
|
|
146
|
+
private chooserHeld;
|
|
147
|
+
private refreshSnapshot;
|
|
148
|
+
private blockedView;
|
|
149
|
+
private goto;
|
|
150
|
+
private discover;
|
|
151
|
+
private fetchForRead;
|
|
152
|
+
private aria;
|
|
153
|
+
private pageFromShown;
|
|
154
|
+
/** Read one presence page. A tab opened only for this read is closed again. */
|
|
155
|
+
private observePresence;
|
|
156
|
+
private tabOn;
|
|
157
|
+
private refreshView;
|
|
158
|
+
private idleView;
|
|
159
|
+
/** The most recently touched idle tab on this address. */
|
|
160
|
+
private idleTabOn;
|
|
161
|
+
/** Only Session windows are capped; the person's window is theirs to close. */
|
|
162
|
+
private enforceCap;
|
|
163
|
+
private navigateOwned;
|
|
164
|
+
private navigateTab;
|
|
165
|
+
private reloadSame;
|
|
166
|
+
private sendAction;
|
|
167
|
+
private saveResponse;
|
|
168
|
+
private attachDownload;
|
|
169
|
+
private takePendingDownload;
|
|
170
|
+
private savePlaywrightDownload;
|
|
171
|
+
private applyDocumentType;
|
|
172
|
+
/** A failed launch is logged here; calls that need the browser see it as `BROWSER_UNAVAILABLE`. */
|
|
173
|
+
private launch;
|
|
174
|
+
private start;
|
|
175
|
+
private relaunch;
|
|
176
|
+
private stop;
|
|
177
|
+
private refuseIfPerson;
|
|
178
|
+
private takenOver;
|
|
179
|
+
private presenceUrls;
|
|
180
|
+
private snapshotNamed;
|
|
181
|
+
private exclusive;
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Key of the hooks tests use to crash the browser and reach a live page. Not exported by the package. */
|
|
2
|
+
export declare const testHooks: unique symbol;
|
|
3
|
+
export interface TestHooks {
|
|
4
|
+
crash(): Promise<void>;
|
|
5
|
+
page(owner: string, tab: string): unknown;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=test-hooks.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { WaitFor } from '../types.ts';
|
|
3
|
+
export interface LoadWait {
|
|
4
|
+
load: boolean;
|
|
5
|
+
pendingRequests: number;
|
|
6
|
+
dialog?: {
|
|
7
|
+
type: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface TextWait {
|
|
12
|
+
met: 'text' | 'textGone' | 'time' | false;
|
|
13
|
+
seconds: number;
|
|
14
|
+
dialog?: {
|
|
15
|
+
type: string;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function assertWaitFor(waitFor: WaitFor | undefined): void;
|
|
20
|
+
export interface ActionRequests {
|
|
21
|
+
/** A main-frame navigation request was among them. */
|
|
22
|
+
navigation(): boolean;
|
|
23
|
+
started(): number;
|
|
24
|
+
pending(): number;
|
|
25
|
+
/** Resolves when nothing is pending. */
|
|
26
|
+
drained(): Promise<void>;
|
|
27
|
+
stop(): void;
|
|
28
|
+
}
|
|
29
|
+
/** Requests the page starts from now on. Start before the action is dispatched. */
|
|
30
|
+
export declare function trackActionRequests(page: Page): ActionRequests;
|
|
31
|
+
/**
|
|
32
|
+
* Pause, then wait for what the action started: a navigation's load, or the
|
|
33
|
+
* requests it sent. Pause again when there were requests. A dialog ends the wait.
|
|
34
|
+
*/
|
|
35
|
+
export declare function settleAction(page: Page, navigated: boolean, requests: ActionRequests | undefined, signal: AbortSignal | undefined): Promise<LoadWait>;
|
|
36
|
+
/** `load` is given at most five seconds unless a caller passes another limit. */
|
|
37
|
+
export declare function waitForLoad(page: Page, signal: AbortSignal | undefined, timeout?: number): Promise<LoadWait>;
|
|
38
|
+
/** `text` / `textGone` race; `time` alone sleeps. The cap is 30 seconds. */
|
|
39
|
+
export declare function waitForText(page: Page, waitFor: WaitFor, signal: AbortSignal | undefined): Promise<TextWait>;
|
|
40
|
+
//# sourceMappingURL=waits.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import type { BrowserContext, Dialog, Download, Page } from 'playwright';
|
|
2
|
+
import { type OwnerId, type TabId, type TabInfo } from '../types.ts';
|
|
3
|
+
export interface LiveTab {
|
|
4
|
+
id: TabId;
|
|
5
|
+
owner: OwnerId;
|
|
6
|
+
page: Page;
|
|
7
|
+
targetId: string;
|
|
8
|
+
windowId: number;
|
|
9
|
+
/** Extension tab id. Absent without a window, and on a page the content opened itself. */
|
|
10
|
+
chromeTabId: number | undefined;
|
|
11
|
+
url: string;
|
|
12
|
+
title: string;
|
|
13
|
+
touchedAt: number;
|
|
14
|
+
/** True when input, a dialog, or a handover is still on this tab. */
|
|
15
|
+
occupied: boolean;
|
|
16
|
+
/** Filled, selected, or uploaded since the last navigation. */
|
|
17
|
+
inputHeld: boolean;
|
|
18
|
+
/** An unaccepted dialog keeps the tab occupied across navigations. */
|
|
19
|
+
dialogHeld: boolean;
|
|
20
|
+
/** `browser_handover` named this tab and the person has not handed it back. */
|
|
21
|
+
handoverHeld: boolean;
|
|
22
|
+
inPresence: boolean;
|
|
23
|
+
mayHaveChanged: boolean;
|
|
24
|
+
pendingDialog: Dialog | undefined;
|
|
25
|
+
dialogWaiters: Array<(dialog: Dialog) => void>;
|
|
26
|
+
pendingDownload: Download | undefined;
|
|
27
|
+
/** Content-Type of the latest main-frame document response. */
|
|
28
|
+
documentType: string | undefined;
|
|
29
|
+
/** Status of that same response. A download-start navigation has no main document. */
|
|
30
|
+
documentStatus: number | undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Session windows and tab ids. Ids come from one counter stored in the
|
|
34
|
+
* profile, so a restarted browser does not hand an old id to a new page.
|
|
35
|
+
* `context.newPage()` is not used: it lands in whichever window was active.
|
|
36
|
+
* With a window, tabs open through the extension and stay in one group per
|
|
37
|
+
* Session. Without a window, they open with `window.open` from a blank page.
|
|
38
|
+
*/
|
|
39
|
+
export declare class Windows {
|
|
40
|
+
private context;
|
|
41
|
+
private browserSession;
|
|
42
|
+
private readonly tabs;
|
|
43
|
+
private readonly byPage;
|
|
44
|
+
private nextId;
|
|
45
|
+
private readonly arrived;
|
|
46
|
+
private readonly waiters;
|
|
47
|
+
private readonly closedNotes;
|
|
48
|
+
/** One about:blank page per owner. New tabs open from it, because a modal dialog freezes its own page. */
|
|
49
|
+
private readonly openers;
|
|
50
|
+
private profileDir;
|
|
51
|
+
/** Set while a new tab is in flight. Only the page at `url` answers it. */
|
|
52
|
+
private slot;
|
|
53
|
+
/** Pages the service is sending input to. Events on them are not the person's. */
|
|
54
|
+
private readonly marks;
|
|
55
|
+
/** Ignore events briefly after a dispatch returns; the binding call arrives late. */
|
|
56
|
+
private readonly quietUntil;
|
|
57
|
+
/** Closes the service started. A person's close is not in here. */
|
|
58
|
+
private readonly serviceClose;
|
|
59
|
+
private readonly armed;
|
|
60
|
+
private readonly sessions;
|
|
61
|
+
/** Address of a tab when it closed, so a hand-back can still name it. */
|
|
62
|
+
private readonly lastUrls;
|
|
63
|
+
/** Tab creation claims the next `page` event through one slot. */
|
|
64
|
+
private creating;
|
|
65
|
+
private readonly pendingCreates;
|
|
66
|
+
private personInput;
|
|
67
|
+
private tabClosed;
|
|
68
|
+
/** Browser shutdown closes every page. That is not the person. */
|
|
69
|
+
private suppressed;
|
|
70
|
+
/** The browser has a window. New windows then pass `focus: false`. */
|
|
71
|
+
private headed;
|
|
72
|
+
/** Set when the browser has a window. Opening a tab and grouping go through it. */
|
|
73
|
+
private extension;
|
|
74
|
+
/** One tab group per Session window. */
|
|
75
|
+
private readonly groups;
|
|
76
|
+
private readonly titles;
|
|
77
|
+
/** Sessions whose handover is waiting. Their group title says so. */
|
|
78
|
+
private readonly waiting;
|
|
79
|
+
/** Pages a content tab opened itself. Their first navigation and close are not the person. */
|
|
80
|
+
private readonly popups;
|
|
81
|
+
attach(context: BrowserContext, profileDir: string, options: {
|
|
82
|
+
headed: boolean;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
setPersonInput(fn: (owner: OwnerId, tab: TabId | undefined) => void): void;
|
|
85
|
+
onTabClosed(fn: (tab: LiveTab) => void): void;
|
|
86
|
+
/** The tab's address, or the one it had when it closed. */
|
|
87
|
+
urlOf(owner: OwnerId, id: TabId): string | undefined;
|
|
88
|
+
suppressPersonInput(): void;
|
|
89
|
+
resumePersonInput(): void;
|
|
90
|
+
beginDispatch(page: Page): void;
|
|
91
|
+
endDispatch(page: Page): void;
|
|
92
|
+
dropPages(): void;
|
|
93
|
+
list(owner: OwnerId): TabInfo[];
|
|
94
|
+
takeClosed(owner: OwnerId): string[];
|
|
95
|
+
get(owner: OwnerId, id: TabId): LiveTab | undefined;
|
|
96
|
+
touch(tab: LiveTab): void;
|
|
97
|
+
noteUrl(tab: LiveTab, url: string): void;
|
|
98
|
+
noteTitle(tab: LiveTab, title: string): void;
|
|
99
|
+
/** Navigation drops filled-input state. A dialog or handover stays occupied. */
|
|
100
|
+
onNavigated(tab: LiveTab): void;
|
|
101
|
+
markMayHaveChanged(tab: LiveTab): void;
|
|
102
|
+
holdDialog(tab: LiveTab, dialog?: Dialog): void;
|
|
103
|
+
clearDialog(tab: LiveTab): void;
|
|
104
|
+
holdInput(tab: LiveTab): void;
|
|
105
|
+
holdHandover(tab: LiveTab): void;
|
|
106
|
+
releaseHandover(owner: OwnerId): void;
|
|
107
|
+
/** The dialog already open, or the next one. `cancel` drops the waiter. */
|
|
108
|
+
nextDialog(tab: LiveTab): {
|
|
109
|
+
dialog: Promise<Dialog>;
|
|
110
|
+
cancel(): void;
|
|
111
|
+
};
|
|
112
|
+
/** The tab touched most recently, for raising the window the person asked to see. */
|
|
113
|
+
latest(owner: OwnerId): LiveTab | undefined;
|
|
114
|
+
/** Make this tab the current one in its window. Does not raise the browser. */
|
|
115
|
+
activate(tab: LiveTab): Promise<void>;
|
|
116
|
+
/** Group title for the person. Empty until a title is known. */
|
|
117
|
+
setTitle(owner: OwnerId, title: string): void;
|
|
118
|
+
/** A handover in progress prefixes the group title and recolors it. */
|
|
119
|
+
markWaiting(owner: OwnerId, on: boolean): void;
|
|
120
|
+
windowId(owner: OwnerId, id: TabId): Promise<number | undefined>;
|
|
121
|
+
/**
|
|
122
|
+
* A blank page in this owner's window. With a window, the first page uses
|
|
123
|
+
* `Target.createTarget({ newWindow: true, focus: false })` and later pages
|
|
124
|
+
* use the extension. Without a window, later pages use `window.open` from
|
|
125
|
+
* an isolated world so they stay in that window.
|
|
126
|
+
*/
|
|
127
|
+
openBlank(owner: OwnerId): Promise<LiveTab>;
|
|
128
|
+
private createTab;
|
|
129
|
+
private keep;
|
|
130
|
+
/** Close one tab the service opened for a presence check. */
|
|
131
|
+
closeTab(tab: LiveTab, note: string): Promise<void>;
|
|
132
|
+
closeOwner(owner: OwnerId): Promise<void>;
|
|
133
|
+
private closePage;
|
|
134
|
+
/** A tab leaves the registry. The window closes with its last tab. */
|
|
135
|
+
private forget;
|
|
136
|
+
private closeOpener;
|
|
137
|
+
/** Close least-recently touched idle tabs until the owner is within the cap. */
|
|
138
|
+
enforceCap(owner: OwnerId): Promise<void>;
|
|
139
|
+
private owned;
|
|
140
|
+
private remember;
|
|
141
|
+
private allocate;
|
|
142
|
+
private openerFor;
|
|
143
|
+
private openWindow;
|
|
144
|
+
/** Headed windows pass `focus: false`. A browser that rejects it is opened without the flag. */
|
|
145
|
+
private createWindowTarget;
|
|
146
|
+
private openTabIn;
|
|
147
|
+
/**
|
|
148
|
+
* Wait for the page that arrives at a one-off blank address. Only that page
|
|
149
|
+
* answers the claim: a page the content opens meanwhile goes to its opener.
|
|
150
|
+
* The address goes back to `about:blank` without navigating.
|
|
151
|
+
*/
|
|
152
|
+
private claimBlank;
|
|
153
|
+
/** The extension's id for the only tab in a window just opened. */
|
|
154
|
+
private extensionTabIn;
|
|
155
|
+
/** Ask the extension to open a blank tab in `windowId`, and claim it. */
|
|
156
|
+
private openTabViaExtension;
|
|
157
|
+
/** Put a Session's tab in its group. The person's window is not a Session and stays ungrouped. */
|
|
158
|
+
private groupTab;
|
|
159
|
+
private applyGroupStyle;
|
|
160
|
+
private ingest;
|
|
161
|
+
private adopt;
|
|
162
|
+
/**
|
|
163
|
+
* Listen for the person's pointer and keys in an isolated world.
|
|
164
|
+
* The page's own scripts cannot see the binding. A main-frame navigation
|
|
165
|
+
* or a close the service did not start counts as well. A page the content
|
|
166
|
+
* tab opened itself is not counted for its first navigation or its close.
|
|
167
|
+
*/
|
|
168
|
+
private arm;
|
|
169
|
+
private report;
|
|
170
|
+
private waitForTarget;
|
|
171
|
+
private targetIdOf;
|
|
172
|
+
private windowIdOf;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=windows.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
interface SeenRef {
|
|
3
|
+
kind: 'file' | 'image';
|
|
4
|
+
ref: Record<string, unknown>;
|
|
5
|
+
}
|
|
6
|
+
/** Attachment references that appeared in this session, by id. Plain JSON: a session projection state. */
|
|
7
|
+
export interface AttachmentState {
|
|
8
|
+
refs: Record<string, SeenRef>;
|
|
9
|
+
}
|
|
10
|
+
export declare function initAttachments(): AttachmentState;
|
|
11
|
+
/** Attachments reach the model in user messages and tool results. */
|
|
12
|
+
export declare function applyAttachments(state: AttachmentState, event: {
|
|
13
|
+
type: string;
|
|
14
|
+
data: unknown;
|
|
15
|
+
}): AttachmentState;
|
|
16
|
+
/**
|
|
17
|
+
* Host paths for upload values. A value naming an attachment that appeared in
|
|
18
|
+
* this session resolves through the attachment provider; anything else is a
|
|
19
|
+
* path that must resolve inside the session cwd.
|
|
20
|
+
*/
|
|
21
|
+
export declare function uploadPaths(ctx: Context, seen: AttachmentState, cwd: string | undefined, values: readonly unknown[]): Promise<string[]>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { SeenView } from '../types.ts';
|
|
2
|
+
/** A page the session has shown the model, folded from the session log. */
|
|
3
|
+
export interface Delivery {
|
|
4
|
+
url: string;
|
|
5
|
+
seq: number;
|
|
6
|
+
digest?: string;
|
|
7
|
+
/** The `version` its Agent View declares, when it declares one. */
|
|
8
|
+
version?: string;
|
|
9
|
+
/** The `id` its Agent View declares. A log written before it was recorded has none. */
|
|
10
|
+
id?: string;
|
|
11
|
+
callId?: string;
|
|
12
|
+
/** Cleared once a prune shadows it, the surface replaces it, or a summary compacts it. */
|
|
13
|
+
inContext: boolean;
|
|
14
|
+
kind: 'tool' | 'presence';
|
|
15
|
+
}
|
|
16
|
+
/** What the model was shown in this session. Plain JSON: it is a session projection state. */
|
|
17
|
+
export interface DeliveryState {
|
|
18
|
+
deliveries: Delivery[];
|
|
19
|
+
/** Whole URLs written in delivered text, as written, trimmed, and with `&` decoded. */
|
|
20
|
+
tokens: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface LogEvent {
|
|
23
|
+
seq: number;
|
|
24
|
+
type: string;
|
|
25
|
+
data: unknown;
|
|
26
|
+
surfaceOp?: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Every mh-browser tool result's `meta.by`; a result without it is not a delivery. */
|
|
29
|
+
export declare const META_BY = "mh-browser";
|
|
30
|
+
export declare const NOT_DELIVERED_REASON = "this address was not shown to you in this session";
|
|
31
|
+
export declare function initDeliveries(): DeliveryState;
|
|
32
|
+
/**
|
|
33
|
+
* One event folded into the delivery record. A tool result shadowed by
|
|
34
|
+
* `compaction/prune` or replaced on the surface is out of context, and its
|
|
35
|
+
* replacement is not a new delivery; a summary puts everything before it out
|
|
36
|
+
* of context, presence reminders included: the replacement baseline that
|
|
37
|
+
* follows the summary is the delivery that counts.
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyDelivery(state: DeliveryState, event: LogEvent): DeliveryState;
|
|
40
|
+
export declare function foldDeliveries(events: readonly LogEvent[]): DeliveryState;
|
|
41
|
+
/**
|
|
42
|
+
* The Agent View of this address the model saw last, and whether it is still
|
|
43
|
+
* in context. A page shown only as a snapshot has no Agent View digest.
|
|
44
|
+
*/
|
|
45
|
+
export declare function latestSeen(state: DeliveryState, url: string): SeenView | 'not-in-context' | undefined;
|
|
46
|
+
export type ResolvedPage = {
|
|
47
|
+
url: string;
|
|
48
|
+
seen: SeenView | 'not-in-context';
|
|
49
|
+
} | {
|
|
50
|
+
ambiguous: string[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The document `page` names: the one shown at that address, else the one whose
|
|
54
|
+
* Agent View declares that `id`. One `id` can name several documents, such as
|
|
55
|
+
* the pages of a collection; when more than one is in context and they
|
|
56
|
+
* differ, the caller must pick an address.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolvePage(state: DeliveryState, page: string): ResolvedPage | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* True when this http(s) address was shown, or appeared as a whole URL in
|
|
61
|
+
* something that was shown. A prefix of a shown URL is not that URL.
|
|
62
|
+
*/
|
|
63
|
+
export declare function wasDelivered(state: DeliveryState, url: string): boolean;
|
|
64
|
+
//# sourceMappingURL=delivery.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import type { Session } from '@deepseek-ai/dsh-session';
|
|
4
|
+
import type { ToolRunContext } from '@deepseek-ai/dsh-tools';
|
|
5
|
+
import type { WriteTarget } from '../service/service.ts';
|
|
6
|
+
/** dsh shows the model only the message, so the message starts with the code. */
|
|
7
|
+
export declare class BrowserToolError extends HarnessError {
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
constructor(code: string, reason: string);
|
|
10
|
+
}
|
|
11
|
+
export declare const NO_WORKSPACE_REASON = "this session has no working directory";
|
|
12
|
+
export interface ToolSession {
|
|
13
|
+
id: string;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
session: Session;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The session of this tool call. Also records its title for the tab group.
|
|
19
|
+
* No title service, or no title yet, records an empty one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sessionFor(ctx: Context, exec: ToolRunContext): ToolSession;
|
|
22
|
+
/** A title written after the last tool call still reaches the tab group. */
|
|
23
|
+
export declare function followSessionTitles(ctx: Context): void;
|
|
24
|
+
export declare function sessionOf(exec: ToolRunContext): ToolSession;
|
|
25
|
+
/**
|
|
26
|
+
* Where this session may write a download. With dsh's sandbox policy loaded,
|
|
27
|
+
* its mode decides: `read-only` writes nothing, otherwise the policy's
|
|
28
|
+
* workspace root. Without it, the session cwd.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeTarget(ctx: Context, session: ToolSession): WriteTarget;
|
|
31
|
+
export declare function rethrow(error: unknown, detail?: string): never;
|
|
32
|
+
//# sourceMappingURL=session.d.ts.map
|