@modelprofile.com/flexharness 10.3.0 → 10.4.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '10.3.0',
6
+ version: '10.4.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwrQkFBK0I7SUFDckMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLDBFQUEwRTtDQUN4RixDQUFBIn0=
@@ -0,0 +1,9 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IBrowserRuntimeFlexToolProviderOptions } from './interfaces.js';
3
+ export declare class BrowserRuntimeFlexToolProvider<TScope> implements plugins.IFlexToolProvider<TScope> {
4
+ private readonly client;
5
+ private readonly resolveCapability;
6
+ private state;
7
+ constructor(options: IBrowserRuntimeFlexToolProviderOptions<TScope>);
8
+ provideTools(context: plugins.IFlexToolProviderContext<TScope>): Promise<plugins.IFlexToolHandle>;
9
+ }
@@ -0,0 +1,135 @@
1
+ import * as plugins from './plugins.js';
2
+ import { createFramedToolBrowserContext } from './tool.context.js';
3
+ const browserActions = [
4
+ 'navigate',
5
+ 'snapshot',
6
+ 'screenshot',
7
+ 'click',
8
+ 'fill',
9
+ 'press',
10
+ ];
11
+ const boundedIdentifier = (minimum, maximum) => plugins.z.string().trim().min(minimum).max(maximum);
12
+ const runBindingSchema = plugins.z.object({
13
+ scopeId: boundedIdentifier(1, 128),
14
+ sessionId: boundedIdentifier(1, 128),
15
+ runId: boundedIdentifier(1, 256),
16
+ });
17
+ const capabilitySchema = plugins.z.strictObject({
18
+ capabilityToken: boundedIdentifier(16, 512),
19
+ });
20
+ export class BrowserRuntimeFlexToolProvider {
21
+ client;
22
+ resolveCapability;
23
+ state = 'unused';
24
+ constructor(options) {
25
+ if (!options || !options.client || typeof options.resolveCapability !== 'function') {
26
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
27
+ }
28
+ this.client = options.client;
29
+ this.resolveCapability = options.resolveCapability;
30
+ }
31
+ async provideTools(context) {
32
+ if (this.state !== 'unused')
33
+ throw new plugins.BrowserRuntimeError('BUSY');
34
+ this.state = 'provisioning';
35
+ try {
36
+ const binding = runBindingSchema.safeParse(context);
37
+ if (!binding.success)
38
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
39
+ if (this.client.scopeId !== binding.data.scopeId
40
+ || this.client.sessionId.harnessId !== 'flex'
41
+ || this.client.sessionId.nativeId !== binding.data.sessionId
42
+ || this.client.runId !== binding.data.runId)
43
+ throw new plugins.BrowserRuntimeError('CAPABILITY_INVALID');
44
+ context.signal.throwIfAborted();
45
+ const capability = capabilitySchema.safeParse(await this.resolveCapability(context));
46
+ if (!capability.success)
47
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
48
+ context.signal.throwIfAborted();
49
+ await this.client.acquire(capability.data.capabilityToken, context.signal);
50
+ context.signal.throwIfAborted();
51
+ }
52
+ catch (error) {
53
+ this.state = 'closed';
54
+ const cleanupErrors = [];
55
+ await this.client.release().catch((cleanupError) => cleanupErrors.push(cleanupError));
56
+ await this.client.close().catch((cleanupError) => cleanupErrors.push(cleanupError));
57
+ if (cleanupErrors.length > 0) {
58
+ throw new AggregateError([error, ...cleanupErrors], 'Browser tool provisioning cleanup failed.');
59
+ }
60
+ throw error;
61
+ }
62
+ this.state = 'active';
63
+ const tools = plugins.createBrowserTools({
64
+ abortSignal: context.signal,
65
+ browser: createFramedToolBrowserContext(this.client, context.signal),
66
+ requestPermission: async (request) => {
67
+ const action = plugins.z.enum(browserActions).safeParse(request.metadata?.action);
68
+ if (!action.success)
69
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
70
+ const toolCallId = request.toolCallId === undefined
71
+ ? undefined
72
+ : boundedIdentifier(1, 128).safeParse(request.toolCallId);
73
+ if (toolCallId !== undefined && !toolCallId.success) {
74
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
75
+ }
76
+ const signal = request.abortSignal
77
+ ? AbortSignal.any([context.signal, request.abortSignal])
78
+ : context.signal;
79
+ signal.throwIfAborted();
80
+ const permission = context.requestPermission({
81
+ kind: `browser.${action.data}`,
82
+ description: `Allow the browser to perform the ${action.data} action.`,
83
+ ...(toolCallId === undefined ? {} : { toolCallId: toolCallId.data }),
84
+ metadata: { action: action.data },
85
+ });
86
+ let onAbort;
87
+ const aborted = new Promise((_resolve, reject) => {
88
+ onAbort = () => reject(new plugins.BrowserRuntimeError('ABORTED'));
89
+ signal.addEventListener('abort', onAbort, { once: true });
90
+ if (signal.aborted)
91
+ onAbort();
92
+ });
93
+ try {
94
+ await Promise.race([permission, aborted]);
95
+ signal.throwIfAborted();
96
+ }
97
+ finally {
98
+ signal.removeEventListener('abort', onAbort);
99
+ }
100
+ },
101
+ }, {
102
+ allowedActions: browserActions,
103
+ maxLines: 2000,
104
+ maxBytes: 128 * 1024,
105
+ });
106
+ let closePromise;
107
+ const close = () => {
108
+ if (closePromise)
109
+ return closePromise;
110
+ closePromise = (async () => {
111
+ context.signal.removeEventListener('abort', onRunAbort);
112
+ const cleanupErrors = [];
113
+ await this.client.release().catch((error) => cleanupErrors.push(error));
114
+ await this.client.close().catch((error) => cleanupErrors.push(error));
115
+ this.state = 'closed';
116
+ if (cleanupErrors.length === 1)
117
+ throw cleanupErrors[0];
118
+ if (cleanupErrors.length > 1) {
119
+ throw new AggregateError(cleanupErrors, 'Browser tool cleanup failed.');
120
+ }
121
+ })();
122
+ return closePromise;
123
+ };
124
+ const onRunAbort = () => {
125
+ void close().catch(() => undefined);
126
+ };
127
+ context.signal.addEventListener('abort', onRunAbort, { once: true });
128
+ if (context.signal.aborted) {
129
+ await close();
130
+ throw new plugins.BrowserRuntimeError('ABORTED');
131
+ }
132
+ return { tools, close };
133
+ }
134
+ }
135
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5mbGV4cHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19icm93c2VyL2NsYXNzZXMuZmxleHByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBRW5FLE1BQU0sY0FBYyxHQUFHO0lBQ3JCLFVBQVU7SUFDVixVQUFVO0lBQ1YsWUFBWTtJQUNaLE9BQU87SUFDUCxNQUFNO0lBQ04sT0FBTztDQUNpRCxDQUFDO0FBRTNELE1BQU0saUJBQWlCLEdBQUcsQ0FBQyxPQUFlLEVBQUUsT0FBZSxFQUFFLEVBQUUsQ0FDN0QsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBRXRELE1BQU0sZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDeEMsT0FBTyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7SUFDbEMsU0FBUyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7SUFDcEMsS0FBSyxFQUFFLGlCQUFpQixDQUFDLENBQUMsRUFBRSxHQUFHLENBQUM7Q0FDakMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUM5QyxlQUFlLEVBQUUsaUJBQWlCLENBQUMsRUFBRSxFQUFFLEdBQUcsQ0FBQztDQUM1QyxDQUFDLENBQUM7QUFFSCxNQUFNLE9BQU8sOEJBQThCO0lBRXhCLE1BQU0sQ0FBcUM7SUFDM0MsaUJBQWlCLENBQXNFO0lBQ2hHLEtBQUssR0FBb0QsUUFBUSxDQUFDO0lBRTFFLFlBQVksT0FBdUQ7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
@@ -0,0 +1,3 @@
1
+ export { BrowserRuntimeFlexToolProvider } from './classes.flexprovider.js';
2
+ export { createFramedToolBrowserContext } from './tool.context.js';
3
+ export type { IBrowserRuntimeFlexToolProviderOptions, IResolvedBrowserFlexCapability, } from './interfaces.js';
@@ -0,0 +1,3 @@
1
+ export { BrowserRuntimeFlexToolProvider } from './classes.flexprovider.js';
2
+ export { createFramedToolBrowserContext } from './tool.context.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90c19icm93c2VyL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQzNFLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLG1CQUFtQixDQUFDIn0=
@@ -0,0 +1,8 @@
1
+ import type * as plugins from './plugins.js';
2
+ export interface IResolvedBrowserFlexCapability {
3
+ capabilityToken: string;
4
+ }
5
+ export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
6
+ client: plugins.BrowserRuntimeFramedClient;
7
+ resolveCapability(context: plugins.IFlexToolProviderContext<TScope>): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
8
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2Jyb3dzZXIvaW50ZXJmYWNlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -0,0 +1,7 @@
1
+ import { z } from 'zod';
2
+ import { BrowserRuntimeError, validateAgentAction } from '@push.rocks/smartbrowser/runtime';
3
+ import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
4
+ export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
5
+ export type { BrowserRuntimeFramedClient, TBrowserAgentAction, } from '@push.rocks/smartbrowser/runtime';
6
+ export type { IFlexToolHandle, IFlexToolProvider, IFlexToolProviderContext, } from '@modelprofile.com/flexharness';
7
+ export type { IBrowserToolInput, IToolBrowserContext, IToolBrowserRunOptions, TBrowserToolAction, } from '@modelprofile.com/flexharness/tools';
@@ -0,0 +1,5 @@
1
+ import { z } from 'zod';
2
+ import { BrowserRuntimeError, validateAgentAction, } from '@push.rocks/smartbrowser/runtime';
3
+ import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
4
+ export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX2Jyb3dzZXIvcGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQ3hCLE9BQU8sRUFDTCxtQkFBbUIsRUFDbkIsbUJBQW1CLEdBQ3BCLE1BQU0sa0NBQWtDLENBQUM7QUFDMUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFFekUsT0FBTyxFQUFFLENBQUMsRUFBRSxtQkFBbUIsRUFBRSxtQkFBbUIsRUFBRSxrQkFBa0IsRUFBRSxDQUFDIn0=
@@ -0,0 +1,2 @@
1
+ import * as plugins from './plugins.js';
2
+ export declare function createFramedToolBrowserContext(client: plugins.BrowserRuntimeFramedClient, runSignal: AbortSignal): plugins.IToolBrowserContext;
@@ -0,0 +1,61 @@
1
+ import * as plugins from './plugins.js';
2
+ const browserToolInputSchema = plugins.z.strictObject({
3
+ action: plugins.z.preprocess((value) => typeof value === 'string' ? value.trim() : value ?? 'snapshot', plugins.z.enum(['navigate', 'snapshot', 'screenshot', 'click', 'fill', 'press'])),
4
+ url: plugins.z.string().optional(),
5
+ selector: plugins.z.string().optional(),
6
+ text: plugins.z.string().optional(),
7
+ timeoutMs: plugins.z.number().int().min(100).max(120_000).optional(),
8
+ });
9
+ export function createFramedToolBrowserContext(client, runSignal) {
10
+ return {
11
+ execute: async (input, options) => {
12
+ const parsed = browserToolInputSchema.safeParse(input);
13
+ if (!parsed.success)
14
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
15
+ const { action, url, selector, text, timeoutMs } = parsed.data;
16
+ let runtimeAction;
17
+ switch (action) {
18
+ case 'navigate':
19
+ runtimeAction = plugins.validateAgentAction({
20
+ action,
21
+ url: url ?? '',
22
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
23
+ });
24
+ break;
25
+ case 'snapshot':
26
+ case 'screenshot':
27
+ runtimeAction = { action };
28
+ break;
29
+ case 'click':
30
+ runtimeAction = plugins.validateAgentAction({
31
+ action,
32
+ selector: selector ?? '',
33
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
34
+ });
35
+ break;
36
+ case 'fill':
37
+ runtimeAction = plugins.validateAgentAction({
38
+ action,
39
+ selector: selector ?? '',
40
+ text: text ?? '',
41
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
42
+ });
43
+ break;
44
+ case 'press':
45
+ runtimeAction = plugins.validateAgentAction({
46
+ action,
47
+ selector: selector ?? '',
48
+ key: text ?? '',
49
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
50
+ });
51
+ break;
52
+ }
53
+ const signal = options?.abortSignal
54
+ ? AbortSignal.any([runSignal, options.abortSignal])
55
+ : runSignal;
56
+ signal.throwIfAborted();
57
+ return client.executeAgentAction(runtimeAction, { signal });
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbC5jb250ZXh0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHNfYnJvd3Nlci90b29sLmNvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFFeEMsTUFBTSxzQkFBc0IsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUNwRCxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQzFCLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLFVBQVUsRUFDekUsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQ2pGO0lBQ0QsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQ2xDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUN2QyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxRQUFRLEVBQUU7SUFDbkMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxRQUFRLEVBQUU7Q0FDckUsQ0FBQyxDQUFDO0FBRUgsTUFBTSxVQUFVLDhCQUE4QixDQUM1QyxNQUEwQyxFQUMxQyxTQUFzQjtJQUV0QixPQUFPO1FBQ0wsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFnQyxFQUFFLE9BQXdD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
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "author": "Task Venture Capital GmbH",
3
3
  "license": "MIT",
4
4
  "name": "@modelprofile.com/flexharness",
5
- "version": "10.3.0",
5
+ "version": "10.4.0",
6
6
  "type": "module",
7
7
  "description": "Managed agent sessions with tools, MCP, chat, OCR, documents and browser control through focused subpath imports.",
8
8
  "main": "./dist_ts/index.js",
@@ -24,6 +24,10 @@
24
24
  "types": "./dist_ts_tools_node/index.d.ts",
25
25
  "import": "./dist_ts_tools_node/index.js"
26
26
  },
27
+ "./browser": {
28
+ "types": "./dist_ts_browser/index.d.ts",
29
+ "import": "./dist_ts_browser/index.js"
30
+ },
27
31
  "./compaction": {
28
32
  "types": "./dist_ts_compaction/index.d.ts",
29
33
  "import": "./dist_ts_compaction/index.js"
@@ -70,14 +74,15 @@
70
74
  }
71
75
  },
72
76
  "dependencies": {
73
- "@modelprofile.com/flexharness-agent": "10.3.0",
74
- "@modelprofile.com/flexharness-models": "10.3.0",
77
+ "@modelprofile.com/flexharness-agent": "10.4.0",
78
+ "@modelprofile.com/flexharness-models": "10.4.0",
75
79
  "@push.rocks/smartrequest": "^5.5.1",
76
80
  "ai": "^7.0.111",
77
81
  "zod": "^4.6.5"
78
82
  },
79
83
  "peerDependencies": {
80
84
  "@modelcontextprotocol/sdk": "^1.29.0",
85
+ "@push.rocks/smartbrowser": "^5.0.0",
81
86
  "@push.rocks/smartpdf": "^6.0.0",
82
87
  "sharp": "^0.35.3",
83
88
  "puppeteer": "^24.42.0",
@@ -93,6 +98,9 @@
93
98
  "@modelcontextprotocol/sdk": {
94
99
  "optional": true
95
100
  },
101
+ "@push.rocks/smartbrowser": {
102
+ "optional": true
103
+ },
96
104
  "@push.rocks/smartpdf": {
97
105
  "optional": true
98
106
  },
@@ -139,6 +147,8 @@
139
147
  "dist_ts_tools",
140
148
  "ts_tools_node",
141
149
  "dist_ts_tools_node",
150
+ "ts_browser",
151
+ "dist_ts_browser",
142
152
  "ts_compaction",
143
153
  "dist_ts_compaction",
144
154
  "ts_mcp",
package/readme.md CHANGED
@@ -55,6 +55,7 @@ or provider SDKs.
55
55
  | --- | --- | --- |
56
56
  | `/tools` | Host-supplied execution contexts, HTTP and JSON tools | None |
57
57
  | `/tools/node` | Local filesystem and process tools, file-backed job adapters | None |
58
+ | `/browser` | Run-scoped SmartBrowser framed-client adapter for Flex tool providers | `@push.rocks/smartbrowser` |
58
59
  | `/compaction` | Model-driven conversation compaction | None |
59
60
  | `/media` | Vision recipes using an injected model | None |
60
61
  | `/ocr` | `ImageOcr`, OCR contracts and Mistral transport | None |
@@ -75,6 +76,12 @@ only for the entrypoints your project imports, for example:
75
76
  pnpm add @modelprofile.com/flexharness lit
76
77
  ```
77
78
 
79
+ For a managed SmartBrowser resource, install its optional browser peer:
80
+
81
+ ```sh
82
+ pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
83
+ ```
84
+
78
85
  ```typescript
79
86
  import { ChatSession } from '@modelprofile.com/flexharness/chat';
80
87
  import '@modelprofile.com/flexharness/chat/web';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@modelprofile.com/flexharness',
6
- version: '10.3.0',
6
+ version: '10.4.0',
7
7
  description: 'The FlexHarness toolbox source repository and shared dependency catalog.'
8
8
  }
package/ts/readme.md CHANGED
@@ -55,6 +55,7 @@ or provider SDKs.
55
55
  | --- | --- | --- |
56
56
  | `/tools` | Host-supplied execution contexts, HTTP and JSON tools | None |
57
57
  | `/tools/node` | Local filesystem and process tools, file-backed job adapters | None |
58
+ | `/browser` | Run-scoped SmartBrowser framed-client adapter for Flex tool providers | `@push.rocks/smartbrowser` |
58
59
  | `/compaction` | Model-driven conversation compaction | None |
59
60
  | `/media` | Vision recipes using an injected model | None |
60
61
  | `/ocr` | `ImageOcr`, OCR contracts and Mistral transport | None |
@@ -75,6 +76,12 @@ only for the entrypoints your project imports, for example:
75
76
  pnpm add @modelprofile.com/flexharness lit
76
77
  ```
77
78
 
79
+ For a managed SmartBrowser resource, install its optional browser peer:
80
+
81
+ ```sh
82
+ pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
83
+ ```
84
+
78
85
  ```typescript
79
86
  import { ChatSession } from '@modelprofile.com/flexharness/chat';
80
87
  import '@modelprofile.com/flexharness/chat/web';
@@ -0,0 +1,140 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IBrowserRuntimeFlexToolProviderOptions } from './interfaces.js';
3
+ import { createFramedToolBrowserContext } from './tool.context.js';
4
+
5
+ const browserActions = [
6
+ 'navigate',
7
+ 'snapshot',
8
+ 'screenshot',
9
+ 'click',
10
+ 'fill',
11
+ 'press',
12
+ ] as const satisfies readonly plugins.TBrowserToolAction[];
13
+
14
+ const boundedIdentifier = (minimum: number, maximum: number) =>
15
+ plugins.z.string().trim().min(minimum).max(maximum);
16
+
17
+ const runBindingSchema = plugins.z.object({
18
+ scopeId: boundedIdentifier(1, 128),
19
+ sessionId: boundedIdentifier(1, 128),
20
+ runId: boundedIdentifier(1, 256),
21
+ });
22
+
23
+ const capabilitySchema = plugins.z.strictObject({
24
+ capabilityToken: boundedIdentifier(16, 512),
25
+ });
26
+
27
+ export class BrowserRuntimeFlexToolProvider<TScope>
28
+ implements plugins.IFlexToolProvider<TScope> {
29
+ private readonly client: plugins.BrowserRuntimeFramedClient;
30
+ private readonly resolveCapability: IBrowserRuntimeFlexToolProviderOptions<TScope>['resolveCapability'];
31
+ private state: 'unused' | 'provisioning' | 'active' | 'closed' = 'unused';
32
+
33
+ constructor(options: IBrowserRuntimeFlexToolProviderOptions<TScope>) {
34
+ if (!options || !options.client || typeof options.resolveCapability !== 'function') {
35
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
36
+ }
37
+ this.client = options.client;
38
+ this.resolveCapability = options.resolveCapability;
39
+ }
40
+
41
+ public async provideTools(
42
+ context: plugins.IFlexToolProviderContext<TScope>,
43
+ ): Promise<plugins.IFlexToolHandle> {
44
+ if (this.state !== 'unused') throw new plugins.BrowserRuntimeError('BUSY');
45
+ this.state = 'provisioning';
46
+ try {
47
+ const binding = runBindingSchema.safeParse(context);
48
+ if (!binding.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
49
+ if (
50
+ this.client.scopeId !== binding.data.scopeId
51
+ || this.client.sessionId.harnessId !== 'flex'
52
+ || this.client.sessionId.nativeId !== binding.data.sessionId
53
+ || this.client.runId !== binding.data.runId
54
+ ) throw new plugins.BrowserRuntimeError('CAPABILITY_INVALID');
55
+ context.signal.throwIfAborted();
56
+ const capability = capabilitySchema.safeParse(await this.resolveCapability(context));
57
+ if (!capability.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
58
+ context.signal.throwIfAborted();
59
+ await this.client.acquire(capability.data.capabilityToken, context.signal);
60
+ context.signal.throwIfAborted();
61
+ } catch (error) {
62
+ this.state = 'closed';
63
+ const cleanupErrors: unknown[] = [];
64
+ await this.client.release().catch((cleanupError: unknown) => cleanupErrors.push(cleanupError));
65
+ await this.client.close().catch((cleanupError: unknown) => cleanupErrors.push(cleanupError));
66
+ if (cleanupErrors.length > 0) {
67
+ throw new AggregateError([error, ...cleanupErrors], 'Browser tool provisioning cleanup failed.');
68
+ }
69
+ throw error;
70
+ }
71
+ this.state = 'active';
72
+
73
+ const tools = plugins.createBrowserTools({
74
+ abortSignal: context.signal,
75
+ browser: createFramedToolBrowserContext(this.client, context.signal),
76
+ requestPermission: async (request) => {
77
+ const action = plugins.z.enum(browserActions).safeParse(request.metadata?.action);
78
+ if (!action.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
79
+ const toolCallId = request.toolCallId === undefined
80
+ ? undefined
81
+ : boundedIdentifier(1, 128).safeParse(request.toolCallId);
82
+ if (toolCallId !== undefined && !toolCallId.success) {
83
+ throw new plugins.BrowserRuntimeError('INVALID_INPUT');
84
+ }
85
+ const signal = request.abortSignal
86
+ ? AbortSignal.any([context.signal, request.abortSignal])
87
+ : context.signal;
88
+ signal.throwIfAborted();
89
+ const permission = context.requestPermission({
90
+ kind: `browser.${action.data}`,
91
+ description: `Allow the browser to perform the ${action.data} action.`,
92
+ ...(toolCallId === undefined ? {} : { toolCallId: toolCallId.data }),
93
+ metadata: { action: action.data },
94
+ });
95
+ let onAbort!: () => void;
96
+ const aborted = new Promise<never>((_resolve, reject) => {
97
+ onAbort = () => reject(new plugins.BrowserRuntimeError('ABORTED'));
98
+ signal.addEventListener('abort', onAbort, { once: true });
99
+ if (signal.aborted) onAbort();
100
+ });
101
+ try {
102
+ await Promise.race([permission, aborted]);
103
+ signal.throwIfAborted();
104
+ } finally {
105
+ signal.removeEventListener('abort', onAbort);
106
+ }
107
+ },
108
+ }, {
109
+ allowedActions: browserActions,
110
+ maxLines: 2000,
111
+ maxBytes: 128 * 1024,
112
+ });
113
+
114
+ let closePromise: Promise<void> | undefined;
115
+ const close = (): Promise<void> => {
116
+ if (closePromise) return closePromise;
117
+ closePromise = (async () => {
118
+ context.signal.removeEventListener('abort', onRunAbort);
119
+ const cleanupErrors: unknown[] = [];
120
+ await this.client.release().catch((error: unknown) => cleanupErrors.push(error));
121
+ await this.client.close().catch((error: unknown) => cleanupErrors.push(error));
122
+ this.state = 'closed';
123
+ if (cleanupErrors.length === 1) throw cleanupErrors[0];
124
+ if (cleanupErrors.length > 1) {
125
+ throw new AggregateError(cleanupErrors, 'Browser tool cleanup failed.');
126
+ }
127
+ })();
128
+ return closePromise;
129
+ };
130
+ const onRunAbort = (): void => {
131
+ void close().catch(() => undefined);
132
+ };
133
+ context.signal.addEventListener('abort', onRunAbort, { once: true });
134
+ if (context.signal.aborted) {
135
+ await close();
136
+ throw new plugins.BrowserRuntimeError('ABORTED');
137
+ }
138
+ return { tools, close };
139
+ }
140
+ }
@@ -0,0 +1,6 @@
1
+ export { BrowserRuntimeFlexToolProvider } from './classes.flexprovider.js';
2
+ export { createFramedToolBrowserContext } from './tool.context.js';
3
+ export type {
4
+ IBrowserRuntimeFlexToolProviderOptions,
5
+ IResolvedBrowserFlexCapability,
6
+ } from './interfaces.js';
@@ -0,0 +1,12 @@
1
+ import type * as plugins from './plugins.js';
2
+
3
+ export interface IResolvedBrowserFlexCapability {
4
+ capabilityToken: string;
5
+ }
6
+
7
+ export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
8
+ client: plugins.BrowserRuntimeFramedClient;
9
+ resolveCapability(
10
+ context: plugins.IFlexToolProviderContext<TScope>,
11
+ ): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
12
+ }
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ BrowserRuntimeError,
4
+ validateAgentAction,
5
+ } from '@push.rocks/smartbrowser/runtime';
6
+ import { createBrowserTools } from '@modelprofile.com/flexharness/tools';
7
+
8
+ export { z, BrowserRuntimeError, validateAgentAction, createBrowserTools };
9
+ export type {
10
+ BrowserRuntimeFramedClient,
11
+ TBrowserAgentAction,
12
+ } from '@push.rocks/smartbrowser/runtime';
13
+ export type {
14
+ IFlexToolHandle,
15
+ IFlexToolProvider,
16
+ IFlexToolProviderContext,
17
+ } from '@modelprofile.com/flexharness';
18
+ export type {
19
+ IBrowserToolInput,
20
+ IToolBrowserContext,
21
+ IToolBrowserRunOptions,
22
+ TBrowserToolAction,
23
+ } from '@modelprofile.com/flexharness/tools';
@@ -0,0 +1,32 @@
1
+ # FlexHarness browser adapter
2
+
3
+ Install both packages before importing this optional entrypoint:
4
+
5
+ ```sh
6
+ pnpm add @modelprofile.com/flexharness @push.rocks/smartbrowser
7
+ ```
8
+
9
+ Import `BrowserRuntimeFlexToolProvider` from `@modelprofile.com/flexharness/browser` and `BrowserRuntimeFramedClient` from `@push.rocks/smartbrowser/runtime`. Create a fresh client and provider for each Flex run. The host supplies the trusted framed streams and the complete SmartBrowser binding when constructing the client; the provider resolves only the capability token.
10
+
11
+ ```typescript
12
+ import { BrowserRuntimeFlexToolProvider } from '@modelprofile.com/flexharness/browser';
13
+ import { BrowserRuntimeFramedClient } from '@push.rocks/smartbrowser/runtime';
14
+
15
+ const client = new BrowserRuntimeFramedClient({
16
+ ...trustedBinding,
17
+ readable,
18
+ writable,
19
+ });
20
+ const provider = new BrowserRuntimeFlexToolProvider({
21
+ client,
22
+ resolveCapability: () => ({ capabilityToken }),
23
+ });
24
+ ```
25
+
26
+ `trustedBinding`, `readable`, `writable`, and `capabilityToken` are host-provided values. The binding contains the exact project, browser resource, attachment authority and revision, actor, peer, Flex scope, session, run, and channel identities. The provider checks the Flex run's scope, session, and run against the client before resolving or acquiring a capability. SmartBrowser's framed server validates the token against the complete trusted binding.
27
+
28
+ The provider exposes `navigate`, `snapshot`, `screenshot`, `click`, `fill`, and `press` through the existing `/tools` browser tool. Navigation and input actions request Flex permission as `browser.<action>` with the tool call ID. Snapshot and screenshot are observations and do not request permission. Tool and run cancellation reach the framed client. The returned Flex tool handle releases the capability and closes the client; FlexHarness closes this handle after the run. A screenshot returns bounded artifact metadata, which the host can use for a separate authorized artifact read.
29
+
30
+ This entrypoint replaces `BrowserRuntimeFlexToolProvider` from `@modelprofile.com/browser-runtime`. Browser resource ownership, capabilities, framed transport, and artifact storage now come from `@push.rocks/smartbrowser/runtime`.
31
+
32
+ For hosts that used the old client's `client.asToolBrowserContext()`, import `createFramedToolBrowserContext` from this entrypoint and call `createFramedToolBrowserContext(client, runSignal)`. It returns the same `IToolBrowserContext` shape, maps the six supported actions to `executeAgentAction()`, and combines the run signal with each tool invocation's abort signal.
@@ -0,0 +1,67 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ const browserToolInputSchema = plugins.z.strictObject({
4
+ action: plugins.z.preprocess(
5
+ (value) => typeof value === 'string' ? value.trim() : value ?? 'snapshot',
6
+ plugins.z.enum(['navigate', 'snapshot', 'screenshot', 'click', 'fill', 'press']),
7
+ ),
8
+ url: plugins.z.string().optional(),
9
+ selector: plugins.z.string().optional(),
10
+ text: plugins.z.string().optional(),
11
+ timeoutMs: plugins.z.number().int().min(100).max(120_000).optional(),
12
+ });
13
+
14
+ export function createFramedToolBrowserContext(
15
+ client: plugins.BrowserRuntimeFramedClient,
16
+ runSignal: AbortSignal,
17
+ ): plugins.IToolBrowserContext {
18
+ return {
19
+ execute: async (input: plugins.IBrowserToolInput, options?: plugins.IToolBrowserRunOptions) => {
20
+ const parsed = browserToolInputSchema.safeParse(input);
21
+ if (!parsed.success) throw new plugins.BrowserRuntimeError('INVALID_INPUT');
22
+ const { action, url, selector, text, timeoutMs } = parsed.data;
23
+ let runtimeAction: plugins.TBrowserAgentAction;
24
+ switch (action) {
25
+ case 'navigate':
26
+ runtimeAction = plugins.validateAgentAction({
27
+ action,
28
+ url: url ?? '',
29
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
30
+ });
31
+ break;
32
+ case 'snapshot':
33
+ case 'screenshot':
34
+ runtimeAction = { action };
35
+ break;
36
+ case 'click':
37
+ runtimeAction = plugins.validateAgentAction({
38
+ action,
39
+ selector: selector ?? '',
40
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
41
+ });
42
+ break;
43
+ case 'fill':
44
+ runtimeAction = plugins.validateAgentAction({
45
+ action,
46
+ selector: selector ?? '',
47
+ text: text ?? '',
48
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
49
+ });
50
+ break;
51
+ case 'press':
52
+ runtimeAction = plugins.validateAgentAction({
53
+ action,
54
+ selector: selector ?? '',
55
+ key: text ?? '',
56
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
57
+ });
58
+ break;
59
+ }
60
+ const signal = options?.abortSignal
61
+ ? AbortSignal.any([runSignal, options.abortSignal])
62
+ : runSignal;
63
+ signal.throwIfAborted();
64
+ return client.executeAgentAction(runtimeAction, { signal });
65
+ },
66
+ };
67
+ }