bare-agent 0.11.0 → 0.12.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.
- package/bin/cli.d.ts +4 -0
- package/bin/cli.js +40 -10
- package/bin/test-provider.d.ts +2 -0
- package/bin/test-provider.js +5 -1
- package/index.d.ts +20 -0
- package/package.json +44 -10
- package/src/bareguard-adapter.d.ts +118 -0
- package/src/bareguard-adapter.js +75 -3
- package/src/checkpoint.d.ts +61 -0
- package/src/checkpoint.js +17 -8
- package/src/circuit-breaker.d.ts +70 -0
- package/src/circuit-breaker.js +20 -4
- package/src/errors.d.ts +106 -0
- package/src/errors.js +50 -1
- package/src/loop.d.ts +135 -0
- package/src/loop.js +73 -17
- package/src/mcp-bridge.d.ts +133 -0
- package/src/mcp-bridge.js +179 -27
- package/src/mcp.d.ts +4 -0
- package/src/memory.d.ts +50 -0
- package/src/memory.js +22 -2
- package/src/planner.d.ts +62 -0
- package/src/planner.js +26 -7
- package/src/provider-anthropic.d.ts +55 -0
- package/src/provider-anthropic.js +32 -11
- package/src/provider-clipipe.d.ts +86 -0
- package/src/provider-clipipe.js +28 -18
- package/src/provider-fallback.d.ts +44 -0
- package/src/provider-fallback.js +18 -8
- package/src/provider-ollama.d.ts +41 -0
- package/src/provider-ollama.js +27 -7
- package/src/provider-openai.d.ts +57 -0
- package/src/provider-openai.js +31 -16
- package/src/providers.d.ts +6 -0
- package/src/retry.d.ts +44 -0
- package/src/retry.js +15 -1
- package/src/run-plan.d.ts +126 -0
- package/src/run-plan.js +46 -13
- package/src/scheduler.d.ts +102 -0
- package/src/scheduler.js +32 -4
- package/src/state.d.ts +45 -0
- package/src/state.js +18 -2
- package/src/store-jsonfile.d.ts +85 -0
- package/src/store-jsonfile.js +33 -8
- package/src/store-sqlite.d.ts +90 -0
- package/src/store-sqlite.js +31 -7
- package/src/stores.d.ts +3 -0
- package/src/stream.d.ts +79 -0
- package/src/stream.js +32 -0
- package/src/tools.d.ts +8 -0
- package/src/transport-jsonl.d.ts +30 -0
- package/src/transport-jsonl.js +13 -0
- package/src/transports.d.ts +2 -0
- package/tools/browse.d.ts +10 -0
- package/tools/browse.js +2 -0
- package/tools/defer.d.ts +33 -0
- package/tools/defer.js +12 -3
- package/tools/mobile.d.ts +34 -0
- package/tools/mobile.js +28 -15
- package/tools/shell.d.ts +31 -0
- package/tools/shell.js +55 -6
- package/tools/spawn.d.ts +107 -0
- package/tools/spawn.js +24 -5
- package/types/index.d.ts +66 -0
- package/types/shims.d.ts +16 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type JsonlTransportOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* - Writable stream to write JSONL lines to. Defaults to process.stdout.
|
|
4
|
+
*/
|
|
5
|
+
output?: NodeJS.WritableStream | undefined;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* JSONL transport: one JSON object per line to a writable stream.
|
|
9
|
+
* Default: process.stdout. Pipe-friendly, parseable by any language.
|
|
10
|
+
*
|
|
11
|
+
* Debug output goes to stderr (never pollutes stdout).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {object} JsonlTransportOptions
|
|
15
|
+
* @property {NodeJS.WritableStream} [output] - Writable stream to write JSONL lines to. Defaults to process.stdout.
|
|
16
|
+
*/
|
|
17
|
+
export class JsonlTransport {
|
|
18
|
+
/**
|
|
19
|
+
* @param {JsonlTransportOptions} [options={}]
|
|
20
|
+
*/
|
|
21
|
+
constructor(options?: JsonlTransportOptions);
|
|
22
|
+
_output: NodeJS.WritableStream | (NodeJS.WriteStream & {
|
|
23
|
+
fd: 1;
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* @param {*} event - Event object to serialize as one JSON line.
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
29
|
+
write(event: any): void;
|
|
30
|
+
}
|
package/src/transport-jsonl.js
CHANGED
|
@@ -6,11 +6,24 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Debug output goes to stderr (never pollutes stdout).
|
|
8
8
|
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {object} JsonlTransportOptions
|
|
12
|
+
* @property {NodeJS.WritableStream} [output] - Writable stream to write JSONL lines to. Defaults to process.stdout.
|
|
13
|
+
*/
|
|
14
|
+
|
|
9
15
|
class JsonlTransport {
|
|
16
|
+
/**
|
|
17
|
+
* @param {JsonlTransportOptions} [options={}]
|
|
18
|
+
*/
|
|
10
19
|
constructor(options = {}) {
|
|
11
20
|
this._output = options.output || process.stdout;
|
|
12
21
|
}
|
|
13
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @param {*} event - Event object to serialize as one JSON line.
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*/
|
|
14
27
|
write(event) {
|
|
15
28
|
this._output.write(JSON.stringify(event) + '\n');
|
|
16
29
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates browsing tools via barebrowse (optional dep).
|
|
3
|
+
* Returns { tools, close } or null if barebrowse is not installed.
|
|
4
|
+
* @param {object} [opts] - Options passed to barebrowse createBrowseTools
|
|
5
|
+
* @returns {Promise<{tools: Array, close: Function}|null>}
|
|
6
|
+
*/
|
|
7
|
+
export function createBrowsingTools(opts?: object): Promise<{
|
|
8
|
+
tools: any[];
|
|
9
|
+
close: Function;
|
|
10
|
+
} | null>;
|
package/tools/browse.js
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
async function createBrowsingTools(opts = {}) {
|
|
10
10
|
try {
|
|
11
|
+
// barebrowse is an optional dep; the subpath is declared as an ambient `any`
|
|
12
|
+
// module in types/shims.d.ts and resolves to `any` at runtime.
|
|
11
13
|
const { createBrowseTools } = await import('barebrowse/bareagent');
|
|
12
14
|
return createBrowseTools(opts);
|
|
13
15
|
} catch {
|
package/tools/defer.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {object} [options]
|
|
3
|
+
* @param {string} [options.queuePath] - Override queue file path.
|
|
4
|
+
* @returns {{tool: import('../types').ToolDef, readQueue: () => Promise<Record<string, any>[]>, queuePath: string}}
|
|
5
|
+
*/
|
|
6
|
+
export function createDeferTool(options?: {
|
|
7
|
+
queuePath?: string | undefined;
|
|
8
|
+
}): {
|
|
9
|
+
tool: import("../types").ToolDef;
|
|
10
|
+
readQueue: () => Promise<Record<string, any>[]>;
|
|
11
|
+
queuePath: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Read the queue and reconstruct the live status of each id by folding
|
|
15
|
+
* append-only status lines (latest wins). Exposed for tests + library
|
|
16
|
+
* users; the wake script does its own jq-based fold.
|
|
17
|
+
*/
|
|
18
|
+
/** @param {string} [queuePath] */
|
|
19
|
+
export function readQueue(queuePath?: string): Promise<Record<string, any>[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a sortable, unique id. 9-char base36 timestamp + 20-char hex
|
|
22
|
+
* random. Lexicographically sortable by emit time; unique enough for any
|
|
23
|
+
* realistic defer rate. Same shape as the PRD's `def_01J...` sketch.
|
|
24
|
+
*/
|
|
25
|
+
export function generateId(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the active queue path. Precedence:
|
|
28
|
+
* 1. Caller-supplied option (createDeferTool({ queuePath: '...' }))
|
|
29
|
+
* 2. BAREAGENT_DEFER_QUEUE env var
|
|
30
|
+
* 3. ./bareagent-defers.jsonl
|
|
31
|
+
*/
|
|
32
|
+
/** @param {string} [option] */
|
|
33
|
+
export function resolveQueuePath(option?: string): string;
|
package/tools/defer.js
CHANGED
|
@@ -54,6 +54,7 @@ function generateId() {
|
|
|
54
54
|
* 2. BAREAGENT_DEFER_QUEUE env var
|
|
55
55
|
* 3. ./bareagent-defers.jsonl
|
|
56
56
|
*/
|
|
57
|
+
/** @param {string} [option] */
|
|
57
58
|
function resolveQueuePath(option) {
|
|
58
59
|
return option
|
|
59
60
|
|| process.env.BAREAGENT_DEFER_QUEUE
|
|
@@ -68,6 +69,7 @@ function resolveQueuePath(option) {
|
|
|
68
69
|
*
|
|
69
70
|
* Returns { ok: true, iso } on success, { ok: false, error } on failure.
|
|
70
71
|
*/
|
|
72
|
+
/** @param {*} when */
|
|
71
73
|
function validateWhen(when) {
|
|
72
74
|
if (typeof when !== 'string' || !when) {
|
|
73
75
|
return { ok: false, error: 'when must be an ISO 8601 timestamp string' };
|
|
@@ -88,6 +90,7 @@ function validateWhen(when) {
|
|
|
88
90
|
* Anything else is the LLM either confused or trying to defer something
|
|
89
91
|
* meaningless.
|
|
90
92
|
*/
|
|
93
|
+
/** @param {*} action */
|
|
91
94
|
function validateAction(action) {
|
|
92
95
|
if (!action || typeof action !== 'object' || Array.isArray(action)) {
|
|
93
96
|
return { ok: false, error: 'action must be an object' };
|
|
@@ -103,6 +106,10 @@ function validateAction(action) {
|
|
|
103
106
|
* atomic for writes < PIPE_BUF on POSIX (4KB on Linux); a JSON record
|
|
104
107
|
* with a small action is well under that.
|
|
105
108
|
*/
|
|
109
|
+
/**
|
|
110
|
+
* @param {string} queuePath
|
|
111
|
+
* @param {Record<string, any>} record
|
|
112
|
+
*/
|
|
106
113
|
async function appendRecord(queuePath, record) {
|
|
107
114
|
const dir = path.dirname(path.resolve(queuePath));
|
|
108
115
|
// Best-effort dir creation; ignore "already exists".
|
|
@@ -121,10 +128,12 @@ async function appendRecord(queuePath, record) {
|
|
|
121
128
|
* append-only status lines (latest wins). Exposed for tests + library
|
|
122
129
|
* users; the wake script does its own jq-based fold.
|
|
123
130
|
*/
|
|
131
|
+
/** @param {string} [queuePath] */
|
|
124
132
|
async function readQueue(queuePath) {
|
|
125
133
|
const path = resolveQueuePath(queuePath);
|
|
126
134
|
try {
|
|
127
135
|
const text = await fsp.readFile(path, 'utf8');
|
|
136
|
+
/** @type {Record<string, Record<string, any>>} */
|
|
128
137
|
const records = {};
|
|
129
138
|
for (const line of text.split('\n')) {
|
|
130
139
|
if (!line.trim()) continue;
|
|
@@ -134,7 +143,7 @@ async function readQueue(queuePath) {
|
|
|
134
143
|
records[r.id] = { ...records[r.id], ...r };
|
|
135
144
|
}
|
|
136
145
|
return Object.values(records);
|
|
137
|
-
} catch (err) {
|
|
146
|
+
} catch (/** @type {any} */ err) {
|
|
138
147
|
if (err.code === 'ENOENT') return [];
|
|
139
148
|
throw err;
|
|
140
149
|
}
|
|
@@ -143,7 +152,7 @@ async function readQueue(queuePath) {
|
|
|
143
152
|
/**
|
|
144
153
|
* @param {object} [options]
|
|
145
154
|
* @param {string} [options.queuePath] - Override queue file path.
|
|
146
|
-
* @returns {{tool:
|
|
155
|
+
* @returns {{tool: import('../types').ToolDef, readQueue: () => Promise<Record<string, any>[]>, queuePath: string}}
|
|
147
156
|
*/
|
|
148
157
|
function createDeferTool(options = {}) {
|
|
149
158
|
const queuePath = resolveQueuePath(options.queuePath);
|
|
@@ -166,7 +175,7 @@ function createDeferTool(options = {}) {
|
|
|
166
175
|
},
|
|
167
176
|
required: ['action', 'when'],
|
|
168
177
|
},
|
|
169
|
-
execute: async ({ action, when }) => {
|
|
178
|
+
execute: async (/** @type {{action: *, when: *}} */ { action, when }) => {
|
|
170
179
|
const a = validateAction(action);
|
|
171
180
|
if (!a.ok) throw new Error(`[defer] ${a.error}`);
|
|
172
181
|
const w = validateWhen(when);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type ToolDef = import("../types").ToolDef;
|
|
2
|
+
/**
|
|
3
|
+
* A connected baremobile page/device handle. baremobile ships no types, so the
|
|
4
|
+
* surface used here is described structurally; all calls resolve to `any`.
|
|
5
|
+
*/
|
|
6
|
+
export type MobilePage = any;
|
|
7
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
8
|
+
/**
|
|
9
|
+
* A connected baremobile page/device handle. baremobile ships no types, so the
|
|
10
|
+
* surface used here is described structurally; all calls resolve to `any`.
|
|
11
|
+
* @typedef {any} MobilePage
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Creates mobile control tools via baremobile (optional dep).
|
|
15
|
+
* Returns { tools, close } or null if baremobile is not installed.
|
|
16
|
+
*
|
|
17
|
+
* Tools follow the snapshot() → tap(ref) observe-act pattern.
|
|
18
|
+
* Action tools auto-return a fresh snapshot so the LLM sees the result.
|
|
19
|
+
* Supports dual platform: Android (default) and iOS via platform option.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} [opts] - Options passed to baremobile connect()
|
|
22
|
+
* @param {string} [opts.platform] - 'android' (default) or 'ios'
|
|
23
|
+
* @param {string} [opts.device] - Device serial or 'auto'
|
|
24
|
+
* @param {boolean} [opts.termux] - Use Termux ADB on-device mode
|
|
25
|
+
* @returns {Promise<{tools: ToolDef[], close: Function}|null>}
|
|
26
|
+
*/
|
|
27
|
+
export function createMobileTools(opts?: {
|
|
28
|
+
platform?: string | undefined;
|
|
29
|
+
device?: string | undefined;
|
|
30
|
+
termux?: boolean | undefined;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
tools: ToolDef[];
|
|
33
|
+
close: Function;
|
|
34
|
+
} | null>;
|
package/tools/mobile.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A connected baremobile page/device handle. baremobile ships no types, so the
|
|
7
|
+
* surface used here is described structurally; all calls resolve to `any`.
|
|
8
|
+
* @typedef {any} MobilePage
|
|
9
|
+
*/
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
12
|
* Creates mobile control tools via baremobile (optional dep).
|
|
5
13
|
* Returns { tools, close } or null if baremobile is not installed.
|
|
@@ -12,14 +20,16 @@
|
|
|
12
20
|
* @param {string} [opts.platform] - 'android' (default) or 'ios'
|
|
13
21
|
* @param {string} [opts.device] - Device serial or 'auto'
|
|
14
22
|
* @param {boolean} [opts.termux] - Use Termux ADB on-device mode
|
|
15
|
-
* @returns {Promise<{tools:
|
|
23
|
+
* @returns {Promise<{tools: ToolDef[], close: Function}|null>}
|
|
16
24
|
*/
|
|
17
25
|
async function createMobileTools(opts = {}) {
|
|
18
26
|
const platform = opts.platform || 'android';
|
|
19
27
|
|
|
28
|
+
/** @type {(opts: object) => Promise<MobilePage>} */
|
|
20
29
|
let connectFn;
|
|
21
30
|
try {
|
|
22
31
|
if (platform === 'ios') {
|
|
32
|
+
// baremobile/ios is declared as an ambient `any` module in types/shims.d.ts.
|
|
23
33
|
({ connect: connectFn } = await import('baremobile/ios'));
|
|
24
34
|
} else {
|
|
25
35
|
({ connect: connectFn } = await import('baremobile'));
|
|
@@ -31,6 +41,7 @@ async function createMobileTools(opts = {}) {
|
|
|
31
41
|
const SETTLE_MS = 1000;
|
|
32
42
|
const settle = () => new Promise((r) => setTimeout(r, SETTLE_MS));
|
|
33
43
|
|
|
44
|
+
/** @type {MobilePage|null} */
|
|
34
45
|
let _page = null;
|
|
35
46
|
|
|
36
47
|
async function getPage() {
|
|
@@ -38,6 +49,7 @@ async function createMobileTools(opts = {}) {
|
|
|
38
49
|
return _page;
|
|
39
50
|
}
|
|
40
51
|
|
|
52
|
+
/** @param {(page: MobilePage) => any} fn */
|
|
41
53
|
async function actionAndSnapshot(fn) {
|
|
42
54
|
const page = await getPage();
|
|
43
55
|
await fn(page);
|
|
@@ -45,6 +57,7 @@ async function createMobileTools(opts = {}) {
|
|
|
45
57
|
return await page.snapshot();
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
/** @type {ToolDef[]} */
|
|
48
61
|
const tools = [
|
|
49
62
|
{
|
|
50
63
|
name: 'mobile_snapshot',
|
|
@@ -65,7 +78,7 @@ async function createMobileTools(opts = {}) {
|
|
|
65
78
|
},
|
|
66
79
|
required: ['ref'],
|
|
67
80
|
},
|
|
68
|
-
execute: async ({ ref }) => actionAndSnapshot((page) => page.tap(ref)),
|
|
81
|
+
execute: async (/** @type {{ ref: number }} */ { ref }) => actionAndSnapshot((page) => page.tap(ref)),
|
|
69
82
|
},
|
|
70
83
|
{
|
|
71
84
|
name: 'mobile_type',
|
|
@@ -79,7 +92,7 @@ async function createMobileTools(opts = {}) {
|
|
|
79
92
|
},
|
|
80
93
|
required: ['ref', 'text'],
|
|
81
94
|
},
|
|
82
|
-
execute: async ({ ref, text, clear }) => actionAndSnapshot((page) => page.type(ref, text, { clear })),
|
|
95
|
+
execute: async (/** @type {{ ref: number, text: string, clear?: boolean }} */ { ref, text, clear }) => actionAndSnapshot((page) => page.type(ref, text, { clear })),
|
|
83
96
|
},
|
|
84
97
|
{
|
|
85
98
|
name: 'mobile_press',
|
|
@@ -91,7 +104,7 @@ async function createMobileTools(opts = {}) {
|
|
|
91
104
|
},
|
|
92
105
|
required: ['key'],
|
|
93
106
|
},
|
|
94
|
-
execute: async ({ key }) => actionAndSnapshot((page) => page.press(key)),
|
|
107
|
+
execute: async (/** @type {{ key: string }} */ { key }) => actionAndSnapshot((page) => page.press(key)),
|
|
95
108
|
},
|
|
96
109
|
{
|
|
97
110
|
name: 'mobile_scroll',
|
|
@@ -104,7 +117,7 @@ async function createMobileTools(opts = {}) {
|
|
|
104
117
|
},
|
|
105
118
|
required: ['ref', 'direction'],
|
|
106
119
|
},
|
|
107
|
-
execute: async ({ ref, direction }) => actionAndSnapshot((page) => page.scroll(ref, direction)),
|
|
120
|
+
execute: async (/** @type {{ ref: number, direction: string }} */ { ref, direction }) => actionAndSnapshot((page) => page.scroll(ref, direction)),
|
|
108
121
|
},
|
|
109
122
|
{
|
|
110
123
|
name: 'mobile_swipe',
|
|
@@ -120,7 +133,7 @@ async function createMobileTools(opts = {}) {
|
|
|
120
133
|
},
|
|
121
134
|
required: ['x1', 'y1', 'x2', 'y2'],
|
|
122
135
|
},
|
|
123
|
-
execute: async ({ x1, y1, x2, y2, duration }) => actionAndSnapshot((page) => page.swipe(x1, y1, x2, y2, duration)),
|
|
136
|
+
execute: async (/** @type {{ x1: number, y1: number, x2: number, y2: number, duration?: number }} */ { x1, y1, x2, y2, duration }) => actionAndSnapshot((page) => page.swipe(x1, y1, x2, y2, duration)),
|
|
124
137
|
},
|
|
125
138
|
{
|
|
126
139
|
name: 'mobile_long_press',
|
|
@@ -132,7 +145,7 @@ async function createMobileTools(opts = {}) {
|
|
|
132
145
|
},
|
|
133
146
|
required: ['ref'],
|
|
134
147
|
},
|
|
135
|
-
execute: async ({ ref }) => actionAndSnapshot((page) => page.longPress(ref)),
|
|
148
|
+
execute: async (/** @type {{ ref: number }} */ { ref }) => actionAndSnapshot((page) => page.longPress(ref)),
|
|
136
149
|
},
|
|
137
150
|
{
|
|
138
151
|
name: 'mobile_launch',
|
|
@@ -144,7 +157,7 @@ async function createMobileTools(opts = {}) {
|
|
|
144
157
|
},
|
|
145
158
|
required: ['pkg'],
|
|
146
159
|
},
|
|
147
|
-
execute: async ({ pkg }) => {
|
|
160
|
+
execute: async (/** @type {{ pkg: string }} */ { pkg }) => {
|
|
148
161
|
const page = await getPage();
|
|
149
162
|
await page.launch(pkg);
|
|
150
163
|
await new Promise((r) => setTimeout(r, 2000));
|
|
@@ -184,7 +197,7 @@ async function createMobileTools(opts = {}) {
|
|
|
184
197
|
},
|
|
185
198
|
required: ['x', 'y'],
|
|
186
199
|
},
|
|
187
|
-
execute: async ({ x, y }) => actionAndSnapshot((page) => page.tapXY(x, y)),
|
|
200
|
+
execute: async (/** @type {{ x: number, y: number }} */ { x, y }) => actionAndSnapshot((page) => page.tapXY(x, y)),
|
|
188
201
|
},
|
|
189
202
|
];
|
|
190
203
|
|
|
@@ -202,7 +215,7 @@ async function createMobileTools(opts = {}) {
|
|
|
202
215
|
},
|
|
203
216
|
required: ['action'],
|
|
204
217
|
},
|
|
205
|
-
execute: async ({ action, extras }) => actionAndSnapshot((page) => page.intent(action, extras || {})),
|
|
218
|
+
execute: async (/** @type {{ action: string, extras?: object }} */ { action, extras }) => actionAndSnapshot((page) => page.intent(action, extras || {})),
|
|
206
219
|
},
|
|
207
220
|
{
|
|
208
221
|
name: 'mobile_tap_grid',
|
|
@@ -214,7 +227,7 @@ async function createMobileTools(opts = {}) {
|
|
|
214
227
|
},
|
|
215
228
|
required: ['cell'],
|
|
216
229
|
},
|
|
217
|
-
execute: async ({ cell }) => actionAndSnapshot((page) => page.tapGrid(cell)),
|
|
230
|
+
execute: async (/** @type {{ cell: string }} */ { cell }) => actionAndSnapshot((page) => page.tapGrid(cell)),
|
|
218
231
|
},
|
|
219
232
|
{
|
|
220
233
|
name: 'mobile_grid',
|
|
@@ -241,7 +254,7 @@ async function createMobileTools(opts = {}) {
|
|
|
241
254
|
},
|
|
242
255
|
required: ['passcode'],
|
|
243
256
|
},
|
|
244
|
-
execute: async ({ passcode }) => actionAndSnapshot((page) => page.unlock(passcode)),
|
|
257
|
+
execute: async (/** @type {{ passcode: string }} */ { passcode }) => actionAndSnapshot((page) => page.unlock(passcode)),
|
|
245
258
|
});
|
|
246
259
|
}
|
|
247
260
|
|
|
@@ -256,7 +269,7 @@ async function createMobileTools(opts = {}) {
|
|
|
256
269
|
},
|
|
257
270
|
required: ['text'],
|
|
258
271
|
},
|
|
259
|
-
execute: async ({ text }) => {
|
|
272
|
+
execute: async (/** @type {{ text: string }} */ { text }) => {
|
|
260
273
|
const page = await getPage();
|
|
261
274
|
const ref = page.findByText(text);
|
|
262
275
|
return ref !== null && ref !== undefined ? ref : null;
|
|
@@ -276,7 +289,7 @@ async function createMobileTools(opts = {}) {
|
|
|
276
289
|
},
|
|
277
290
|
required: ['text'],
|
|
278
291
|
},
|
|
279
|
-
execute: async ({ text, timeout }) => {
|
|
292
|
+
execute: async (/** @type {{ text: string, timeout?: number }} */ { text, timeout }) => {
|
|
280
293
|
const page = await getPage();
|
|
281
294
|
return await page.waitForText(text, timeout || 10000);
|
|
282
295
|
},
|
|
@@ -293,7 +306,7 @@ async function createMobileTools(opts = {}) {
|
|
|
293
306
|
},
|
|
294
307
|
required: ['ref', 'state'],
|
|
295
308
|
},
|
|
296
|
-
execute: async ({ ref, state, timeout }) => {
|
|
309
|
+
execute: async (/** @type {{ ref: number, state: string, timeout?: number }} */ { ref, state, timeout }) => {
|
|
297
310
|
const page = await getPage();
|
|
298
311
|
return await page.waitForState(ref, state, timeout || 10000);
|
|
299
312
|
},
|
package/tools/shell.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type GrepArgs = {
|
|
2
|
+
pattern: string;
|
|
3
|
+
path: string;
|
|
4
|
+
recursive?: boolean | undefined;
|
|
5
|
+
maxMatches?: number | undefined;
|
|
6
|
+
flags?: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
export type RunArgvArgs = {
|
|
9
|
+
argv: string[];
|
|
10
|
+
cwd?: string | undefined;
|
|
11
|
+
timeout?: number | undefined;
|
|
12
|
+
maxBuffer?: number | undefined;
|
|
13
|
+
env?: Record<string, string> | undefined;
|
|
14
|
+
};
|
|
15
|
+
export type ExecCommandArgs = {
|
|
16
|
+
command: string;
|
|
17
|
+
cwd?: string | undefined;
|
|
18
|
+
timeout?: number | undefined;
|
|
19
|
+
maxBuffer?: number | undefined;
|
|
20
|
+
env?: Record<string, string> | undefined;
|
|
21
|
+
};
|
|
22
|
+
export type ToolDef = import("../types").ToolDef;
|
|
23
|
+
/**
|
|
24
|
+
* Create the three shell tools. No options — configuration is per-call via tool args,
|
|
25
|
+
* gating is the caller's responsibility via `new Loop({ policy })`.
|
|
26
|
+
*
|
|
27
|
+
* @returns {{tools: ToolDef[]}}
|
|
28
|
+
*/
|
|
29
|
+
export function createShellTools(): {
|
|
30
|
+
tools: ToolDef[];
|
|
31
|
+
};
|
package/tools/shell.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* Library ships zero baked-in allowlist — gating is the agent author's responsibility.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
16
|
+
|
|
15
17
|
const fs = require('node:fs/promises');
|
|
16
18
|
const path = require('node:path');
|
|
17
19
|
const { exec, execFile } = require('node:child_process');
|
|
@@ -21,6 +23,10 @@ const DEFAULT_GREP_MAX_MATCHES = 200;
|
|
|
21
23
|
const DEFAULT_EXEC_TIMEOUT_MS = 30_000;
|
|
22
24
|
const DEFAULT_EXEC_MAX_BUFFER = 1024 * 1024; // 1 MB
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} p
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
24
30
|
function expandHome(p) {
|
|
25
31
|
if (!p) return p;
|
|
26
32
|
if (p.startsWith('~/') || p === '~') {
|
|
@@ -30,6 +36,10 @@ function expandHome(p) {
|
|
|
30
36
|
return p;
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} rawPath
|
|
41
|
+
* @param {number} [maxBytes]
|
|
42
|
+
*/
|
|
33
43
|
async function readEntry(rawPath, maxBytes) {
|
|
34
44
|
const resolved = path.resolve(expandHome(rawPath));
|
|
35
45
|
const stat = await fs.stat(resolved);
|
|
@@ -56,6 +66,7 @@ async function readEntry(rawPath, maxBytes) {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
// Probe the first 1KB for NUL bytes to skip binary files in grep walks.
|
|
69
|
+
/** @param {string} filePath */
|
|
59
70
|
async function isProbablyText(filePath) {
|
|
60
71
|
try {
|
|
61
72
|
const fh = await fs.open(filePath, 'r');
|
|
@@ -74,6 +85,11 @@ async function isProbablyText(filePath) {
|
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} dir
|
|
90
|
+
* @param {boolean} recursive
|
|
91
|
+
* @returns {AsyncGenerator<string>}
|
|
92
|
+
*/
|
|
77
93
|
async function* walk(dir, recursive) {
|
|
78
94
|
let entries;
|
|
79
95
|
try {
|
|
@@ -99,6 +115,7 @@ async function* walk(dir, recursive) {
|
|
|
99
115
|
// rejection; the agent simply rephrases. (Single-level nesting only — does not
|
|
100
116
|
// detect deeply nested groups or overlapping alternation like (a|a)*.)
|
|
101
117
|
const UNBOUNDED_QUANT = /[*+]|\{\d+,\}/;
|
|
118
|
+
/** @param {string} pattern */
|
|
102
119
|
function looksCatastrophic(pattern) {
|
|
103
120
|
// A quantifier binds to the atom immediately before it — no whitespace between
|
|
104
121
|
// `)` and the quantifier in a real regex.
|
|
@@ -113,6 +130,16 @@ function looksCatastrophic(pattern) {
|
|
|
113
130
|
return false;
|
|
114
131
|
}
|
|
115
132
|
|
|
133
|
+
/**
|
|
134
|
+
* @typedef {object} GrepArgs
|
|
135
|
+
* @property {string} pattern
|
|
136
|
+
* @property {string} path
|
|
137
|
+
* @property {boolean} [recursive]
|
|
138
|
+
* @property {number} [maxMatches]
|
|
139
|
+
* @property {string} [flags]
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/** @param {GrepArgs} args */
|
|
116
143
|
async function grepPath({ pattern, path: rawPath, recursive = true, maxMatches, flags = 'i' }) {
|
|
117
144
|
const resolved = path.resolve(expandHome(rawPath));
|
|
118
145
|
const cap = maxMatches || DEFAULT_GREP_MAX_MATCHES;
|
|
@@ -125,10 +152,11 @@ async function grepPath({ pattern, path: rawPath, recursive = true, maxMatches,
|
|
|
125
152
|
let re;
|
|
126
153
|
try {
|
|
127
154
|
re = new RegExp(pattern, flags);
|
|
128
|
-
} catch (err) {
|
|
155
|
+
} catch (/** @type {any} */ err) {
|
|
129
156
|
throw new Error(`shell_grep: invalid regex — ${err.message}`);
|
|
130
157
|
}
|
|
131
158
|
|
|
159
|
+
/** @type {{file: string, line: number, text: string}[]} */
|
|
132
160
|
const hits = [];
|
|
133
161
|
const stat = await fs.stat(resolved).catch(() => null);
|
|
134
162
|
if (!stat) throw new Error(`shell_grep: path not found — ${rawPath}`);
|
|
@@ -162,6 +190,16 @@ async function grepPath({ pattern, path: rawPath, recursive = true, maxMatches,
|
|
|
162
190
|
return { hits, truncated, fileCount: files.length };
|
|
163
191
|
}
|
|
164
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @typedef {object} RunArgvArgs
|
|
195
|
+
* @property {string[]} argv
|
|
196
|
+
* @property {string} [cwd]
|
|
197
|
+
* @property {number} [timeout]
|
|
198
|
+
* @property {number} [maxBuffer]
|
|
199
|
+
* @property {Record<string, string>} [env]
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/** @param {RunArgvArgs} args */
|
|
165
203
|
function runArgv({ argv, cwd, timeout, maxBuffer, env }) {
|
|
166
204
|
if (!Array.isArray(argv) || argv.length === 0 || typeof argv[0] !== 'string') {
|
|
167
205
|
return Promise.reject(new Error('shell_run: argv must be a non-empty array of strings, starting with the command'));
|
|
@@ -203,6 +241,16 @@ function runArgv({ argv, cwd, timeout, maxBuffer, env }) {
|
|
|
203
241
|
});
|
|
204
242
|
}
|
|
205
243
|
|
|
244
|
+
/**
|
|
245
|
+
* @typedef {object} ExecCommandArgs
|
|
246
|
+
* @property {string} command
|
|
247
|
+
* @property {string} [cwd]
|
|
248
|
+
* @property {number} [timeout]
|
|
249
|
+
* @property {number} [maxBuffer]
|
|
250
|
+
* @property {Record<string, string>} [env]
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/** @param {ExecCommandArgs} args */
|
|
206
254
|
function execCommand({ command, cwd, timeout, maxBuffer, env }) {
|
|
207
255
|
return new Promise((resolve) => {
|
|
208
256
|
exec(
|
|
@@ -238,9 +286,10 @@ function execCommand({ command, cwd, timeout, maxBuffer, env }) {
|
|
|
238
286
|
* Create the three shell tools. No options — configuration is per-call via tool args,
|
|
239
287
|
* gating is the caller's responsibility via `new Loop({ policy })`.
|
|
240
288
|
*
|
|
241
|
-
* @returns {{tools:
|
|
289
|
+
* @returns {{tools: ToolDef[]}}
|
|
242
290
|
*/
|
|
243
291
|
function createShellTools() {
|
|
292
|
+
/** @type {ToolDef[]} */
|
|
244
293
|
const tools = [
|
|
245
294
|
{
|
|
246
295
|
name: 'shell_read',
|
|
@@ -253,7 +302,7 @@ function createShellTools() {
|
|
|
253
302
|
},
|
|
254
303
|
required: ['path'],
|
|
255
304
|
},
|
|
256
|
-
execute: async ({ path: p, maxBytes }) => readEntry(p, maxBytes),
|
|
305
|
+
execute: async (/** @type {{path: string, maxBytes?: number}} */ { path: p, maxBytes }) => readEntry(p, maxBytes),
|
|
257
306
|
},
|
|
258
307
|
{
|
|
259
308
|
name: 'shell_grep',
|
|
@@ -269,7 +318,7 @@ function createShellTools() {
|
|
|
269
318
|
},
|
|
270
319
|
required: ['pattern', 'path'],
|
|
271
320
|
},
|
|
272
|
-
execute: async (args) => grepPath(args),
|
|
321
|
+
execute: async (/** @type {GrepArgs} */ args) => grepPath(args),
|
|
273
322
|
},
|
|
274
323
|
{
|
|
275
324
|
name: 'shell_run',
|
|
@@ -289,7 +338,7 @@ function createShellTools() {
|
|
|
289
338
|
},
|
|
290
339
|
required: ['argv'],
|
|
291
340
|
},
|
|
292
|
-
execute: async (args) => runArgv(args),
|
|
341
|
+
execute: async (/** @type {RunArgvArgs} */ args) => runArgv(args),
|
|
293
342
|
},
|
|
294
343
|
{
|
|
295
344
|
name: 'shell_exec',
|
|
@@ -305,7 +354,7 @@ function createShellTools() {
|
|
|
305
354
|
},
|
|
306
355
|
required: ['command'],
|
|
307
356
|
},
|
|
308
|
-
execute: async (args) => execCommand(args),
|
|
357
|
+
execute: async (/** @type {ExecCommandArgs} */ args) => execCommand(args),
|
|
309
358
|
},
|
|
310
359
|
];
|
|
311
360
|
return { tools };
|