@mind-harness/mh-browser 0.1.2 → 0.1.4
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/extension/manifest.json +1 -1
- package/lib/index.js +2055 -1093
- package/lib/typert.host.js +27 -16
- package/lib/typert.remote-client.js +1 -1
- package/lib/types/render/envelope.d.ts +24 -5
- package/lib/types/representation/agent-view.d.ts +2 -0
- package/lib/types/representation/agent-view.js +2 -0
- package/lib/types/representation/read.d.ts +1 -0
- package/lib/types/service/actions.d.ts +9 -6
- package/lib/types/service/control.d.ts +15 -5
- package/lib/types/service/evaluate.d.ts +36 -0
- package/lib/types/service/find.d.ts +7 -0
- package/lib/types/service/invoke.d.ts +3 -4
- package/lib/types/service/service.d.ts +56 -9
- package/lib/types/service/windows.d.ts +8 -3
- package/lib/types/tools/delivery.d.ts +9 -11
- package/lib/types/tools/page-ops.d.ts +6 -0
- package/lib/types/tools/paths.d.ts +13 -0
- package/lib/types/tools/tab-log.d.ts +15 -1
- package/lib/types/types.d.ts +102 -36
- package/package.json +4 -2
package/lib/typert.host.js
CHANGED
|
@@ -44,7 +44,7 @@ const $remote = createTypertRemoteInfo({
|
|
|
44
44
|
create: _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema,
|
|
45
45
|
jsonSchema: {"type":"object","properties":{"status":{"type":"string","enum":["revealed","not-handing-over","no-tab"]}},"required":["status"],"additionalProperties":true},
|
|
46
46
|
},
|
|
47
|
-
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":
|
|
47
|
+
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":610,"column":9},
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
}, z)
|
|
@@ -65,10 +65,17 @@ export const TYPERT = {
|
|
|
65
65
|
"key": "mhBrowser",
|
|
66
66
|
"exportName": "MhBrowserService",
|
|
67
67
|
"members": [
|
|
68
|
+
{
|
|
69
|
+
"kind": "method",
|
|
70
|
+
"name": "declareAgentViewOrigin",
|
|
71
|
+
"signature": "declareAgentViewOrigin(origin: string): void",
|
|
72
|
+
"summary": "Remember an origin whose pages offer Agent Views.",
|
|
73
|
+
"jsDoc": "/** Remember an origin whose pages offer Agent Views. A missing page does not remove it. */"
|
|
74
|
+
},
|
|
68
75
|
{
|
|
69
76
|
"kind": "method",
|
|
70
77
|
"name": "open",
|
|
71
|
-
"signature": "async open( owner: string, items: { url: string; waitFor?: WaitFor }[], signal?: AbortSignal, ): Promise<(PageView | PageError)[]>"
|
|
78
|
+
"signature": "async open( owner: string, items: { url: string; waitFor?: WaitFor; snapshot?: boolean }[], signal?: AbortSignal, ): Promise<(PageView | PageError)[]>"
|
|
72
79
|
},
|
|
73
80
|
{
|
|
74
81
|
"kind": "method",
|
|
@@ -141,7 +148,7 @@ export const TYPERT = {
|
|
|
141
148
|
{
|
|
142
149
|
"kind": "method",
|
|
143
150
|
"name": "fetch",
|
|
144
|
-
"signature": "async fetch( url: string, opts?: { accept?: string; ifNoneMatch?: string }, ): Promise<FetchResponseBody | { kind: 'error'; reason: string }>"
|
|
151
|
+
"signature": "async fetch( url: string, opts?: { accept?: string; ifNoneMatch?: string; signal?: AbortSignal }, ): Promise<FetchResponseBody | { kind: 'error'; reason: string }>"
|
|
145
152
|
},
|
|
146
153
|
{
|
|
147
154
|
"kind": "method",
|
|
@@ -161,7 +168,7 @@ export const TYPERT = {
|
|
|
161
168
|
{
|
|
162
169
|
"kind": "method",
|
|
163
170
|
"name": "act",
|
|
164
|
-
"signature": "async act(
|
|
171
|
+
"signature": "async act(owner: string, tab: string, op: PageOp, signal?: AbortSignal): Promise<PageView>"
|
|
165
172
|
},
|
|
166
173
|
{
|
|
167
174
|
"kind": "method",
|
|
@@ -183,12 +190,12 @@ export const TYPERT = {
|
|
|
183
190
|
{
|
|
184
191
|
"kind": "method",
|
|
185
192
|
"name": "invoke",
|
|
186
|
-
"signature": "invoke( owner: string,
|
|
193
|
+
"signature": "invoke( owner: string, _page: string, call: InvokeCall, opts?: { signal?: AbortSignal; readFile?: (value: unknown) => Promise<string> }, ): Promise<InvokeSent>"
|
|
187
194
|
},
|
|
188
195
|
{
|
|
189
196
|
"kind": "method",
|
|
190
197
|
"name": "download",
|
|
191
|
-
"signature": "async download(
|
|
198
|
+
"signature": "async download( _owner: string, url: string, root: string, signal?: AbortSignal, ): Promise<Download>",
|
|
192
199
|
"summary": "Not queued: it opens no tab, and a long body must not hold up the Session's other calls.",
|
|
193
200
|
"jsDoc": "/**\n * Not queued: it opens no tab, and a long body must not hold up the\n * Session's other calls. `root` is the directory the session may write.\n */"
|
|
194
201
|
},
|
|
@@ -255,10 +262,6 @@ export const TYPERT = {
|
|
|
255
262
|
}
|
|
256
263
|
],
|
|
257
264
|
"types": [
|
|
258
|
-
{
|
|
259
|
-
"name": "ActAction",
|
|
260
|
-
"declaration": "export type ActAction = { kind: 'click'; ref: string; double?: boolean; button?: 'left' | 'right' | 'middle'; modifiers?: Array<'Alt' | 'Control' | 'Meta' | 'Shift'>; } | { kind: 'select'; ref: string; values: string[]; } | { kind: 'press'; key: string; ref?: string; } | { kind: 'fill'; fields: { ref: string; value: string; }[]; } | { kind: 'upload'; ref: string; files: string[]; } | { kind: 'dialog'; accept: boolean; promptText?: string; } | { kind: 'back'; } | { kind: 'wait'; text?: string; textGone?: string; time?: number; } | { kind: 'look'; };"
|
|
261
|
-
},
|
|
262
265
|
{
|
|
263
266
|
"name": "ActionResponse",
|
|
264
267
|
"declaration": "export interface ActionResponse {\n name: string;\n status?: number;\n location?: string;\n mediaType?: string;\n body?: string;\n bytes?: number;\n lost?: boolean;\n}"
|
|
@@ -285,19 +288,27 @@ export const TYPERT = {
|
|
|
285
288
|
},
|
|
286
289
|
{
|
|
287
290
|
"name": "InvokeCall",
|
|
288
|
-
"declaration": "export interface InvokeCall {\n name: string;\n item?: string;\n input?: Record<string, unknown>;\n
|
|
291
|
+
"declaration": "export interface InvokeCall {\n name: string;\n item?: string;\n input?: Record<string, unknown>;\n shown: { url: string; text: string; };\n}"
|
|
289
292
|
},
|
|
290
293
|
{
|
|
291
294
|
"name": "InvokeSent",
|
|
292
295
|
"declaration": "export interface InvokeSent {\n schemaRejected: boolean;\n response?: ActionResponse;\n page: PageView | PageError;\n}"
|
|
293
296
|
},
|
|
297
|
+
{
|
|
298
|
+
"name": "Modifier",
|
|
299
|
+
"declaration": "export type Modifier = 'Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift';"
|
|
300
|
+
},
|
|
294
301
|
{
|
|
295
302
|
"name": "PageError",
|
|
296
303
|
"declaration": "export interface PageError {\n tab?: TabId;\n url: string;\n reason: string;\n}"
|
|
297
304
|
},
|
|
305
|
+
{
|
|
306
|
+
"name": "PageOp",
|
|
307
|
+
"declaration": "export type PageOp = (Target & { kind: 'click'; doubleClick?: boolean; button?: 'left' | 'right' | 'middle'; modifiers?: Modifier[]; }) | (Target & { kind: 'hover'; }) | (Target & { kind: 'select_option'; values: string[]; }) | { kind: 'drag'; startTarget: string; startElement?: string; endTarget: string; endElement?: string; } | (Target & { kind: 'type'; text: string; submit?: boolean; slowly?: boolean; }) | { kind: 'fill_form'; fields: (Target & { name: string; type: 'textbox' | 'checkbox' | 'radio' | 'combobox' | 'slider'; value: string; })[]; } | { kind: 'press_key'; key: string; } | { kind: 'file_upload'; paths?: string[]; } | { kind: 'handle_dialog'; accept: boolean; promptText?: string; } | { kind: 'navigate_back'; } | { kind: 'wait_for'; text?: string; textGone?: string; time?: number; } | { kind: 'snapshot'; target?: string; depth?: number; boxes?: boolean; } | { kind: 'find'; text?: string; regex?: string; maxResults?: number; } | { kind: 'take_screenshot'; element?: string; target?: string; type?: 'png' | 'jpeg' | 'webp'; fullPage?: boolean; scale?: 'css' | 'device'; } | { kind: 'evaluate'; element?: string; target?: string; function: string; callId: string; };"
|
|
308
|
+
},
|
|
298
309
|
{
|
|
299
310
|
"name": "PageView",
|
|
300
|
-
"declaration": "export interface PageView {\n tab
|
|
311
|
+
"declaration": "export interface PageView {\n tab?: TabId;\n url: string;\n title: string;\n observedAt: string;\n representation: { kind: 'agent-view'; text: string; version?: string; href?: string; } | { kind: 'snapshot'; text: string; agentViewError?: string; agentView?: { version?: string; }; };\n fragment?: string;\n status?: number;\n mediaType?: string;\n body?: string;\n inPresence: boolean;\n headerOnly?: boolean;\n dialog?: Dialog;\n incomplete?: { load: boolean; pendingRequests: number; };\n waited?: { met: 'text' | 'textGone' | 'time' | false; seconds: number; };\n download?: Download;\n summary?: string;\n detail?: string;\n writes?: { method: string; origin: string; fromCall: boolean; count: number; }[];\n shot?: { mediaType: 'image/png' | 'image/jpeg' | 'image/webp'; bytes: Uint8Array; };\n}"
|
|
301
312
|
},
|
|
302
313
|
{
|
|
303
314
|
"name": "PendingOp",
|
|
@@ -323,10 +334,6 @@ export const TYPERT = {
|
|
|
323
334
|
"name": "PresenceSource",
|
|
324
335
|
"declaration": "export type PresenceSource = (typeof PRESENCE_SOURCES)[number];"
|
|
325
336
|
},
|
|
326
|
-
{
|
|
327
|
-
"name": "SeenView",
|
|
328
|
-
"declaration": "export interface SeenView {\n digest: string;\n version?: string;\n}"
|
|
329
|
-
},
|
|
330
337
|
{
|
|
331
338
|
"name": "TabId",
|
|
332
339
|
"declaration": "export type TabId = string;"
|
|
@@ -339,6 +346,10 @@ export const TYPERT = {
|
|
|
339
346
|
"name": "Taken",
|
|
340
347
|
"declaration": "export interface Taken {\n operations: number;\n tabs: string[];\n from: string;\n to: string;\n tab?: string;\n}"
|
|
341
348
|
},
|
|
349
|
+
{
|
|
350
|
+
"name": "Target",
|
|
351
|
+
"declaration": "export interface Target {\n target: string;\n element?: string;\n}"
|
|
352
|
+
},
|
|
342
353
|
{
|
|
343
354
|
"name": "TestHooks",
|
|
344
355
|
"declaration": "export interface TestHooks {\n crash(): Promise<void>;\n page(owner: string, tab: string): unknown;\n}"
|
|
@@ -44,7 +44,7 @@ export const TYPERT_REMOTE = createTypertRemoteInfo({
|
|
|
44
44
|
create: _mind_harness_mh_browser_mhBrowser_revealHandover_result$schema,
|
|
45
45
|
jsonSchema: {"type":"object","properties":{"status":{"type":"string","enum":["revealed","not-handing-over","no-tab"]}},"required":["status"],"additionalProperties":true},
|
|
46
46
|
},
|
|
47
|
-
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":
|
|
47
|
+
sourceLocation: {"file":"packages/mh-browser/src/service/service.ts","line":610,"column":9},
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
}, z)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { PageError, PageView, TabInfo } from '../types.ts';
|
|
2
|
+
/** Every element the service writes around site text. */
|
|
3
|
+
export declare const FRAMES: string[];
|
|
2
4
|
/**
|
|
3
|
-
* Escape a closer a site could use to end a frame the service opened
|
|
4
|
-
*
|
|
5
|
-
* and no site text is dropped.
|
|
5
|
+
* Escape a closer a site could use to end a frame the service opened, and an
|
|
6
|
+
* opener it could use to pass its text off as one. Only `</name` and `<name`
|
|
7
|
+
* are rewritten, so a tag cut off before `>` is caught and no site text is dropped.
|
|
6
8
|
*/
|
|
7
9
|
export declare function escapeBody(text: string): string;
|
|
8
10
|
/** Escape text the service writes into an attribute. */
|
|
@@ -59,11 +61,28 @@ export declare function renderAbsent(input: {
|
|
|
59
61
|
* `omitInPresence`: the page's body is the same one presence carries, so a
|
|
60
62
|
* page in presence gets only its attributes.
|
|
61
63
|
*/
|
|
62
|
-
export declare function renderOutcome(pages: Array<PageView | PageError>,
|
|
64
|
+
export declare function renderOutcome(pages: Array<PageView | PageError>, screenshot?: string, omitInPresence?: boolean): string;
|
|
65
|
+
/** One attached `<tabs>` list, the comparison key for the next result. */
|
|
66
|
+
export interface TabSnap {
|
|
67
|
+
tabs: {
|
|
68
|
+
id: string;
|
|
69
|
+
url: string;
|
|
70
|
+
presence: boolean;
|
|
71
|
+
changed: boolean;
|
|
72
|
+
inUse: boolean;
|
|
73
|
+
}[];
|
|
74
|
+
closed: string[];
|
|
75
|
+
}
|
|
76
|
+
export declare function tabSnap(tabs: TabInfo[], closed: string[]): TabSnap;
|
|
77
|
+
/**
|
|
78
|
+
* Attach the full list when this session has not attached one yet, when the
|
|
79
|
+
* tab key changed, or when this result closed something. Title is not a change.
|
|
80
|
+
*/
|
|
81
|
+
export declare function tabsChanged(previous: TabSnap | undefined, next: TabSnap): boolean;
|
|
63
82
|
export interface PageReading {
|
|
64
83
|
url: string;
|
|
65
84
|
tab?: string;
|
|
66
|
-
representation: 'agent-view' | 'snapshot';
|
|
85
|
+
representation: 'agent-view' | 'snapshot' | 'header';
|
|
67
86
|
version?: string;
|
|
68
87
|
digest?: string;
|
|
69
88
|
/** The `id` the Agent View declares; `browser_invoke` takes it as well as `url`. */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const AGENT_VIEW_VERSION = "0.1";
|
|
2
2
|
export declare const AGENT_VIEW_ACCEPT = "text/markdown; variant=agent-view";
|
|
3
|
+
/** Accept on the page address when its origin is known to offer Agent Views. */
|
|
4
|
+
export declare const PAGE_ACCEPT = "text/markdown; variant=agent-view, text/html;q=0.9, */*;q=0.8";
|
|
3
5
|
export interface AgentViewRequest {
|
|
4
6
|
method: string;
|
|
5
7
|
url: string;
|
|
@@ -2,6 +2,8 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { parse as parseYaml } from 'yaml';
|
|
3
3
|
export const AGENT_VIEW_VERSION = '0.1';
|
|
4
4
|
export const AGENT_VIEW_ACCEPT = 'text/markdown; variant=agent-view';
|
|
5
|
+
/** Accept on the page address when its origin is known to offer Agent Views. */
|
|
6
|
+
export const PAGE_ACCEPT = 'text/markdown; variant=agent-view, text/html;q=0.9, */*;q=0.8';
|
|
5
7
|
const FENCE_OPEN = /```agent-view\b/g;
|
|
6
8
|
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'DELETE']);
|
|
7
9
|
const PLACEHOLDER = /\{[^{}]+\}/;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { Page } from 'playwright';
|
|
2
|
-
import type {
|
|
3
|
-
/**
|
|
4
|
-
export declare function
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type { Locator, Page } from 'playwright';
|
|
2
|
+
import type { PageOp } from '../types.ts';
|
|
3
|
+
/** A page operation returns a snapshot after it. The rest return one sentence. */
|
|
4
|
+
export declare function showsSnapshot(op: PageOp): boolean;
|
|
5
|
+
/** The sentence for an operation whose result has no snapshot. */
|
|
6
|
+
export declare function summaryOf(op: PageOp): string | undefined;
|
|
7
|
+
/** Run one snapshot action. Dialog, wait, upload, find, and evaluate are handled by the service. */
|
|
8
|
+
export declare function runAction(page: Page, op: PageOp, snapshot: string, signal: AbortSignal | undefined): Promise<boolean>;
|
|
9
|
+
export declare function locate(page: Page, target: string, snapshot: string): Promise<Locator>;
|
|
7
10
|
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Who holds
|
|
3
|
-
*
|
|
4
|
-
* A
|
|
2
|
+
* Who holds each tab of one session. Person input outside a service dispatch
|
|
3
|
+
* moves that tab to the person and aborts the action still waiting on it.
|
|
4
|
+
* A handover that names no tab holds every tab until hand-back.
|
|
5
5
|
*/
|
|
6
6
|
export declare class Control {
|
|
7
7
|
private readonly who;
|
|
8
8
|
private readonly holding;
|
|
9
|
+
/** A handover that named no tab: every tab of the session is the person's. */
|
|
10
|
+
private readonly blanket;
|
|
9
11
|
private readonly inflight;
|
|
10
12
|
private readonly operations;
|
|
11
13
|
private readonly tabs;
|
|
12
14
|
private readonly since;
|
|
13
15
|
private readonly named;
|
|
16
|
+
/** Whether the person holds any tab, or a handover is waiting. */
|
|
14
17
|
of(owner: string): 'model' | 'person';
|
|
15
|
-
/**
|
|
16
|
-
|
|
18
|
+
/** The person holds this tab until they hand it back. */
|
|
19
|
+
ofTab(owner: string, tab: string): 'model' | 'person';
|
|
20
|
+
/**
|
|
21
|
+
* Abort this waiting action when the caller aborts, or when the person
|
|
22
|
+
* takes the tab it is running on. A call with no tab (the request channel)
|
|
23
|
+
* ends only when its caller aborts.
|
|
24
|
+
*/
|
|
25
|
+
link(owner: string, caller: AbortSignal | undefined, tab?: string): {
|
|
17
26
|
signal: AbortSignal;
|
|
18
27
|
release(): void;
|
|
19
28
|
};
|
|
@@ -37,6 +46,7 @@ export declare class Control {
|
|
|
37
46
|
takePerson(owner: string): Taken | undefined;
|
|
38
47
|
/** The Session is gone. Its in-flight calls are aborted. */
|
|
39
48
|
forget(owner: string): void;
|
|
49
|
+
private abort;
|
|
40
50
|
private clear;
|
|
41
51
|
}
|
|
42
52
|
export interface Taken {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Dialog, Locator, Page } from 'playwright';
|
|
2
|
+
export interface WriteNote {
|
|
3
|
+
method: string;
|
|
4
|
+
origin: string;
|
|
5
|
+
fromCall: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Write requests grouped by method, target site, and who sent them. */
|
|
8
|
+
export interface WriteCount extends WriteNote {
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
export type Evaluated = {
|
|
12
|
+
kind: 'value';
|
|
13
|
+
value: string;
|
|
14
|
+
writes: WriteCount[];
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'navigated';
|
|
17
|
+
writes: WriteCount[];
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'dialog';
|
|
20
|
+
dialog: Dialog;
|
|
21
|
+
writes: WriteCount[];
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'stopped';
|
|
24
|
+
writes: WriteCount[];
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Run the expression in the page and collect write requests.
|
|
28
|
+
* A request is from this call when its initiator stack, including parents,
|
|
29
|
+
* has a frame from the sourceURL marker. Anything else is contemporaneous.
|
|
30
|
+
* GET, HEAD, and OPTIONS are not writes. A dialog, a stop, or a navigation
|
|
31
|
+
* ends the wait; requests already sent are reported either way.
|
|
32
|
+
*/
|
|
33
|
+
export declare function evaluateMarked(page: Page, source: string, target: Locator | undefined, callId: string, signal: AbortSignal | undefined, dialog: Promise<Dialog>): Promise<Evaluated>;
|
|
34
|
+
/** One line per group: `POST https://site × 2 (this call)`. */
|
|
35
|
+
export declare function formatWrites(writes: readonly WriteCount[]): string;
|
|
36
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -20,12 +20,11 @@ export type SendResult = {
|
|
|
20
20
|
kind: 'lost';
|
|
21
21
|
};
|
|
22
22
|
export interface InvokeHost {
|
|
23
|
-
idleView(url: string): Promise<PageView | undefined>;
|
|
24
23
|
open(url: string): Promise<PageView | PageError>;
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/** Reload other idle tabs on this address; an occupied one is left, marked changed. */
|
|
25
|
+
refreshIdle(url: string, exceptTab: string | undefined): Promise<void>;
|
|
27
26
|
send(request: SendRequest): Promise<SendResult>;
|
|
28
27
|
readFile(value: unknown): Promise<string>;
|
|
29
28
|
}
|
|
30
|
-
export declare function runInvoke(host: InvokeHost,
|
|
29
|
+
export declare function runInvoke(host: InvokeHost, call: InvokeCall): Promise<InvokeSent>;
|
|
31
30
|
//# sourceMappingURL=invoke.d.ts.map
|
|
@@ -3,7 +3,7 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
|
3
3
|
import type { BrowserConfig } from '../config.ts';
|
|
4
4
|
import type { HeldPage, PresenceReceipt, PresenceRecord, PresenceSource } from '../presence/record.ts';
|
|
5
5
|
import type { PendingOp } from '../presence/fold.ts';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Download, FetchResponseBody, InvokeCall, InvokeSent, PageError, PageOp, PageView, TabInfo, WaitFor } from '../types.ts';
|
|
7
7
|
import { type Taken } from './control.ts';
|
|
8
8
|
import { testHooks, type TestHooks } from './test-hooks.ts';
|
|
9
9
|
declare module '@deepseek-ai/cordis' {
|
|
@@ -46,10 +46,17 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
46
46
|
private readonly workspaces;
|
|
47
47
|
private readonly control;
|
|
48
48
|
private readonly personTouch;
|
|
49
|
+
/** Origins that have served a readable Agent View. Memory only; a restart clears it. */
|
|
50
|
+
private readonly agentViewOrigins;
|
|
51
|
+
/** The last Agent View read for a page address, so a later read can send If-None-Match. */
|
|
52
|
+
private readonly documents;
|
|
49
53
|
constructor(ctx: Context, config: BrowserConfig);
|
|
54
|
+
/** Remember an origin whose pages offer Agent Views. A missing page does not remove it. */
|
|
55
|
+
declareAgentViewOrigin(origin: string): void;
|
|
50
56
|
open(owner: string, items: {
|
|
51
57
|
url: string;
|
|
52
58
|
waitFor?: WaitFor;
|
|
59
|
+
snapshot?: boolean;
|
|
53
60
|
}[], signal?: AbortSignal): Promise<(PageView | PageError)[]>;
|
|
54
61
|
view(owner: string, tab: string, signal?: AbortSignal): Promise<PageView | PageError>;
|
|
55
62
|
/** Pages whose current representation is already in the model's presence. */
|
|
@@ -91,6 +98,7 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
91
98
|
fetch(url: string, opts?: {
|
|
92
99
|
accept?: string;
|
|
93
100
|
ifNoneMatch?: string;
|
|
101
|
+
signal?: AbortSignal;
|
|
94
102
|
}): Promise<FetchResponseBody | {
|
|
95
103
|
kind: 'error';
|
|
96
104
|
reason: string;
|
|
@@ -98,12 +106,12 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
98
106
|
pushPath(owner: string, tab: string, path: string): Promise<void>;
|
|
99
107
|
started(): Promise<void>;
|
|
100
108
|
[testHooks](): TestHooks;
|
|
101
|
-
act(owner: string, tab: string,
|
|
109
|
+
act(owner: string, tab: string, op: PageOp, signal?: AbortSignal): Promise<PageView>;
|
|
102
110
|
holdHandover(owner: string, tab: string): void;
|
|
103
111
|
releaseHandover(owner: string): void;
|
|
104
112
|
/** `undefined` when no screenshot can be taken now: a dialog is open, or the page refused. */
|
|
105
113
|
capture(owner: string, tab: string, signal?: AbortSignal): Promise<Uint8Array | undefined>;
|
|
106
|
-
invoke(owner: string,
|
|
114
|
+
invoke(owner: string, _page: string, call: InvokeCall, opts?: {
|
|
107
115
|
signal?: AbortSignal;
|
|
108
116
|
readFile?: (value: unknown) => Promise<string>;
|
|
109
117
|
}): Promise<InvokeSent>;
|
|
@@ -111,7 +119,7 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
111
119
|
* Not queued: it opens no tab, and a long body must not hold up the
|
|
112
120
|
* Session's other calls. `root` is the directory the session may write.
|
|
113
121
|
*/
|
|
114
|
-
download(
|
|
122
|
+
download(_owner: string, url: string, root: string, signal?: AbortSignal): Promise<Download>;
|
|
115
123
|
controlOf(owner: string): 'model' | 'person';
|
|
116
124
|
/** Presence registers this. A hand-back calls it with the touched presence pages. */
|
|
117
125
|
bindPersonTouch(fn: (owner: string, urls: string[]) => void): () => void;
|
|
@@ -140,8 +148,44 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
140
148
|
* hand-back. A handover that is still waiting on its card is not.
|
|
141
149
|
*/
|
|
142
150
|
takePersonHandback(owner: string): HandBack | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Read one address. A known origin is tried on the request channel first.
|
|
153
|
+
* A readable Agent View found on a tab this call opened is returned without
|
|
154
|
+
* the tab, and that tab is not listed as closed.
|
|
155
|
+
*/
|
|
156
|
+
private openAddress;
|
|
157
|
+
/** A tab this call opened can be closed again unless the person took it or it holds input. */
|
|
158
|
+
private closable;
|
|
159
|
+
private knownOrigin;
|
|
160
|
+
private rememberOrigin;
|
|
161
|
+
/** A readable Agent View puts its origin on the list and keeps the document for the next read. */
|
|
162
|
+
private keepAgentView;
|
|
163
|
+
/**
|
|
164
|
+
* Read a known origin without a tab. `undefined` means this page has to be
|
|
165
|
+
* opened in a tab: nothing declared, the declaration could not be read, or
|
|
166
|
+
* the request failed. The origin stays on the list either way. A stop
|
|
167
|
+
* ends the read without falling back to a tab.
|
|
168
|
+
*/
|
|
169
|
+
private readKnown;
|
|
170
|
+
/** One page from a document, with no tab. */
|
|
171
|
+
private pageOfShown;
|
|
172
|
+
/**
|
|
173
|
+
* GET that follows same-origin redirects only. `undefined` when it cannot.
|
|
174
|
+
* A body that is neither text nor an Agent View is not read: the page then
|
|
175
|
+
* goes to a tab, which loads it anyway.
|
|
176
|
+
*/
|
|
177
|
+
private fetchSameOrigin;
|
|
143
178
|
private openOne;
|
|
144
179
|
private perform;
|
|
180
|
+
/**
|
|
181
|
+
* `browser_evaluate`. A dialog the function opens is held like any other; a
|
|
182
|
+
* stop or a takeover ends the wait and still reports the writes already sent.
|
|
183
|
+
*/
|
|
184
|
+
private evaluateOn;
|
|
185
|
+
private uploadHeld;
|
|
186
|
+
private shoot;
|
|
187
|
+
/** A snapshot limited to a target, a depth, or boxes. Refs still resolve against the full one. */
|
|
188
|
+
private scopedSnapshot;
|
|
145
189
|
/** A file chooser the action opened. The tab stays in use until it navigates. */
|
|
146
190
|
private chooserHeld;
|
|
147
191
|
private refreshSnapshot;
|
|
@@ -151,18 +195,21 @@ export declare class MhBrowserService extends TypertRemoteService {
|
|
|
151
195
|
private fetchForRead;
|
|
152
196
|
private aria;
|
|
153
197
|
private pageFromShown;
|
|
154
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* Read one presence page. A known origin is read on the request channel,
|
|
200
|
+
* with If-None-Match when the document was read before. Anything else uses
|
|
201
|
+
* a tab already on the address, or opens one and closes it again.
|
|
202
|
+
*/
|
|
155
203
|
private observePresence;
|
|
156
204
|
private tabOn;
|
|
157
205
|
private refreshView;
|
|
158
|
-
|
|
159
|
-
/** The most recently touched idle tab on this address. */
|
|
206
|
+
/** The most recently touched idle tab on this address. A tab the person holds is left alone. */
|
|
160
207
|
private idleTabOn;
|
|
161
208
|
/** Only Session windows are capped; the person's window is theirs to close. */
|
|
162
209
|
private enforceCap;
|
|
163
|
-
|
|
210
|
+
/** Other tabs on this address: idle ones reload, occupied or taken ones are marked. */
|
|
211
|
+
private refreshIdle;
|
|
164
212
|
private navigateTab;
|
|
165
|
-
private reloadSame;
|
|
166
213
|
private sendAction;
|
|
167
214
|
private saveResponse;
|
|
168
215
|
private attachDownload;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BrowserContext, Dialog, Download, Page } from 'playwright';
|
|
1
|
+
import type { BrowserContext, Dialog, Download, FileChooser, Page } from 'playwright';
|
|
2
2
|
import { type OwnerId, type TabId, type TabInfo } from '../types.ts';
|
|
3
3
|
export interface LiveTab {
|
|
4
4
|
id: TabId;
|
|
@@ -17,6 +17,8 @@ export interface LiveTab {
|
|
|
17
17
|
inputHeld: boolean;
|
|
18
18
|
/** An unaccepted dialog keeps the tab occupied across navigations. */
|
|
19
19
|
dialogHeld: boolean;
|
|
20
|
+
/** A file chooser opened by an action, until upload or navigation. */
|
|
21
|
+
fileChooser: FileChooser | undefined;
|
|
20
22
|
/** `browser_handover` named this tab and the person has not handed it back. */
|
|
21
23
|
handoverHeld: boolean;
|
|
22
24
|
inPresence: boolean;
|
|
@@ -127,8 +129,11 @@ export declare class Windows {
|
|
|
127
129
|
openBlank(owner: OwnerId): Promise<LiveTab>;
|
|
128
130
|
private createTab;
|
|
129
131
|
private keep;
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Close one tab. `note` is listed in the next `<tabs>` block. A tab the
|
|
134
|
+
* model was never shown (an Agent View discovered on a new tab) omits it.
|
|
135
|
+
*/
|
|
136
|
+
closeTab(tab: LiveTab, note?: string): Promise<void>;
|
|
132
137
|
closeOwner(owner: OwnerId): Promise<void>;
|
|
133
138
|
private closePage;
|
|
134
139
|
/** A tab leaves the registry. The window closes with its last tab. */
|
|
@@ -9,8 +9,8 @@ export interface Delivery {
|
|
|
9
9
|
/** The `id` its Agent View declares. A log written before it was recorded has none. */
|
|
10
10
|
id?: string;
|
|
11
11
|
callId?: string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/** The page body the model was shown. Absent when the result carried only a header. */
|
|
13
|
+
text?: string;
|
|
14
14
|
kind: 'tool' | 'presence';
|
|
15
15
|
}
|
|
16
16
|
/** What the model was shown in this session. Plain JSON: it is a session projection state. */
|
|
@@ -30,11 +30,9 @@ export declare const META_BY = "mh-browser";
|
|
|
30
30
|
export declare const NOT_DELIVERED_REASON = "this address was not shown to you in this session";
|
|
31
31
|
export declare function initDeliveries(): DeliveryState;
|
|
32
32
|
/**
|
|
33
|
-
* One event folded into the delivery record.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* of context, presence reminders included: the replacement baseline that
|
|
37
|
-
* follows the summary is the delivery that counts.
|
|
33
|
+
* One event folded into the delivery record. Compaction does not revoke a
|
|
34
|
+
* delivery. A later result with the same call id is the replacement a prune
|
|
35
|
+
* appends, so it does not replace the text the model was shown.
|
|
38
36
|
*/
|
|
39
37
|
export declare function applyDelivery(state: DeliveryState, event: LogEvent): DeliveryState;
|
|
40
38
|
export declare function foldDeliveries(events: readonly LogEvent[]): DeliveryState;
|
|
@@ -42,18 +40,18 @@ export declare function foldDeliveries(events: readonly LogEvent[]): DeliverySta
|
|
|
42
40
|
* The Agent View of this address the model saw last, and whether it is still
|
|
43
41
|
* in context. A page shown only as a snapshot has no Agent View digest.
|
|
44
42
|
*/
|
|
45
|
-
export declare function latestSeen(state: DeliveryState, url: string): SeenView |
|
|
43
|
+
export declare function latestSeen(state: DeliveryState, url: string): SeenView | undefined;
|
|
46
44
|
export type ResolvedPage = {
|
|
47
45
|
url: string;
|
|
48
|
-
|
|
46
|
+
text: string;
|
|
49
47
|
} | {
|
|
50
48
|
ambiguous: string[];
|
|
51
49
|
};
|
|
52
50
|
/**
|
|
53
51
|
* The document `page` names: the one shown at that address, else the one whose
|
|
54
52
|
* Agent View declares that `id`. One `id` can name several documents, such as
|
|
55
|
-
* the pages of a collection; when
|
|
56
|
-
*
|
|
53
|
+
* the pages of a collection; when they differ, the caller must pick an address.
|
|
54
|
+
* `text` is the body the model was shown for that document.
|
|
57
55
|
*/
|
|
58
56
|
export declare function resolvePage(state: DeliveryState, page: string): ResolvedPage | undefined;
|
|
59
57
|
/**
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
3
|
+
export declare const PAGE_OPERATION_NAMES: readonly ["browser_click", "browser_hover", "browser_select_option", "browser_drag", "browser_type", "browser_fill_form", "browser_press_key", "browser_file_upload", "browser_handle_dialog", "browser_navigate_back", "browser_wait_for", "browser_snapshot", "browser_find", "browser_take_screenshot", "browser_evaluate"];
|
|
4
|
+
export declare function pageOperationTools(ctx: Context): ToolDefinition[];
|
|
5
|
+
export declare function pageOperationTool(ctx: Context, name: string): ToolDefinition;
|
|
6
|
+
//# sourceMappingURL=page-ops.d.ts.map
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import type { WriteTarget } from '../service/service.ts';
|
|
1
2
|
/** Resolve upload paths and reject anything outside the session's cwd. */
|
|
2
3
|
export declare function filesInWorkspace(cwd: string | undefined, files: string[]): Promise<string[]>;
|
|
4
|
+
/** Refuse a filename that leaves the session's write root, without creating it. */
|
|
5
|
+
export declare function assertInside(target: WriteTarget, filename: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Write `filename` under the session's write root. A path that leaves the
|
|
8
|
+
* root is refused, through a symbolic link as well: the deepest existing
|
|
9
|
+
* directory is checked before any is created, and the file itself is not
|
|
10
|
+
* followed if it is a link.
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeInside(target: WriteTarget, filename: string, data: string | Uint8Array): Promise<{
|
|
13
|
+
path: string;
|
|
14
|
+
bytes: number;
|
|
15
|
+
}>;
|
|
3
16
|
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import type { Context } from '@deepseek-ai/cordis';
|
|
2
2
|
import type { LogReader } from '../projections.ts';
|
|
3
|
+
import { type TabSnap } from '../render/envelope.ts';
|
|
3
4
|
import type { ToolSession } from './session.ts';
|
|
4
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Tabs an mh-browser result named, the tab of the latest result, and the last
|
|
7
|
+
* `<tabs>` list attached. Plain JSON: a session projection state.
|
|
8
|
+
* A log written before `snap` existed has none, so the next result attaches the full list.
|
|
9
|
+
*/
|
|
5
10
|
export interface TabLogState {
|
|
6
11
|
open: string[];
|
|
12
|
+
lastTab?: string;
|
|
13
|
+
snap?: TabSnap;
|
|
7
14
|
}
|
|
8
15
|
export declare function initTabLog(): TabLogState;
|
|
9
16
|
export declare function applyTabLog(state: TabLogState, event: {
|
|
10
17
|
type: string;
|
|
11
18
|
data: unknown;
|
|
19
|
+
surfaceOp?: unknown;
|
|
12
20
|
}): TabLogState;
|
|
21
|
+
/** The `<tabs>` suffix for this result, and the snap to store when it is attached. */
|
|
22
|
+
export declare function tabBlock(ctx: Context, reader: LogReader, session: ToolSession): {
|
|
23
|
+
suffix: string;
|
|
24
|
+
closed: string[];
|
|
25
|
+
snap?: TabSnap;
|
|
26
|
+
};
|
|
13
27
|
/**
|
|
14
28
|
* The closed notes for this result: the service's own, then each tab an
|
|
15
29
|
* earlier result named that is gone without a note, as after a browser restart.
|