@robota-sdk/agent-tool-defaults 3.0.0-beta.96 → 3.0.0-beta.99
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/CHANGELOG.md +43 -0
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.cts +7 -1
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +7 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/create-default-tools.test.ts +78 -1
- package/src/create-default-tools.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @robota-sdk/agent-tool-defaults
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.99
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- @robota-sdk/agent-core@3.0.0-beta.99
|
|
7
|
+
- @robota-sdk/agent-tools@3.0.0-beta.99
|
|
8
|
+
|
|
9
|
+
## 3.0.0-beta.98
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- @robota-sdk/agent-core@3.0.0-beta.98
|
|
13
|
+
- @robota-sdk/agent-tools@3.0.0-beta.98
|
|
14
|
+
|
|
15
|
+
## 3.0.0-beta.97
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- e5a4254: **An editor that offers its file system carries the file tools' reads and writes.**
|
|
19
|
+
|
|
20
|
+
`Read`, `Write` and `Edit` take an optional `editorFiles` port (`IEditorFileAccess`), asked at each
|
|
21
|
+
call. On the host, after the tool's containment check (the permission gate has already decided),
|
|
22
|
+
`Read` returns the editor's text, unsaved changes included, and `Write` hands the content to the
|
|
23
|
+
editor; `Edit` reads where `Read` reads and writes where `Write` writes. The editor's text keeps
|
|
24
|
+
`Read`'s byte budget, binary refusal, formatting and cancellation, and an editor failure becomes the
|
|
25
|
+
tool's error result. `Glob` and `Grep` still search the disk and end their result with a note saying
|
|
26
|
+
so while an editor is attached. A sandbox with its own filesystem still carries every file tool, and
|
|
27
|
+
commands never use the editor. `createDefaultTools` and `createCodingPack` pass `editorFiles` through
|
|
28
|
+
to those tools only.
|
|
29
|
+
|
|
30
|
+
The ACP agent transport records the editor's `clientCapabilities.fs` and, once `session/new` binds the
|
|
31
|
+
session, hands the host an access through the new `onEditorFileAccess` option that sends
|
|
32
|
+
`fs/read_text_file` and `fs/write_text_file` for that session, for what the editor offered; stopping
|
|
33
|
+
the transport withdraws it. `robota acp` connects the two, so Read, Write and Edit go through the
|
|
34
|
+
editor in an editor that offers `fs`; `terminal/*` is not used.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [e5a4254]
|
|
39
|
+
- Updated dependencies [c207a31]
|
|
40
|
+
- Updated dependencies [9b4f63b]
|
|
41
|
+
- Updated dependencies [43df9c0]
|
|
42
|
+
- Updated dependencies [23a30cd]
|
|
43
|
+
- @robota-sdk/agent-tools@3.0.0-beta.97
|
|
44
|
+
- @robota-sdk/agent-core@3.0.0-beta.97
|
|
45
|
+
|
|
3
46
|
## 3.0.0-beta.96
|
|
4
47
|
### Patch Changes
|
|
5
48
|
|
package/dist/node/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@robota-sdk/agent-tools");function t(t){let n=(0,e.routesFilesThroughSandbox)(t.sandboxClient),{
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@robota-sdk/agent-tools");function t(t){let n=(0,e.routesFilesThroughSandbox)(t.sandboxClient),{editorFiles:r,...i}=t,{sandboxClient:a,...o}=t,s=n?i:o;return[(0,e.createShellTool)(i),(0,e.createBashTool)(i),(0,e.createReadTool)(s),(0,e.createWriteTool)(s),(0,e.createEditTool)(s),...n?[]:[(0,e.createGlobTool)(o),(0,e.createGrepTool)(o)],(0,e.createWebFetchTool)({userAgent:t.httpUserAgent}),(0,e.createWebSearchTool)(),(0,e.createAskUserQuestionTool)(),...t.retrievalAdapter?[(0,e.createRetrievalTool)({adapter:t.retrievalAdapter})]:[],...t.computerDriver?(0,e.createComputerTool)({driver:t.computerDriver}):[]]}exports.createDefaultTools=t;
|
package/dist/node/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FunctionTool } from "@robota-sdk/agent-core";
|
|
2
|
-
import { IComputerDriver, IRetrievalAdapter, ISandboxClient } from "@robota-sdk/agent-tools";
|
|
2
|
+
import { IComputerDriver, IEditorFileAccess, IRetrievalAdapter, ISandboxClient } from "@robota-sdk/agent-tools";
|
|
3
3
|
//#region src/create-default-tools.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Create the default set of CLI tools.
|
|
@@ -19,6 +19,12 @@ interface ICreateDefaultToolsOptions {
|
|
|
19
19
|
* the only way a containment root stays a contract rather than a convention.
|
|
20
20
|
*/
|
|
21
21
|
cwd: string;
|
|
22
|
+
/**
|
|
23
|
+
* An editor that has the workspace open, asked at each call. The host file tools read and write
|
|
24
|
+
* through what it offers and the searches say they read the disk; commands never use it, and a
|
|
25
|
+
* sandbox with its own filesystem keeps every file tool, since its files are not the editor's.
|
|
26
|
+
*/
|
|
27
|
+
editorFiles?: () => IEditorFileAccess | undefined;
|
|
22
28
|
/** SELFHOST-003: when present, adds the adapter-gated `CodebaseRetrieval` tool (absent otherwise). */
|
|
23
29
|
retrievalAdapter?: IRetrievalAdapter;
|
|
24
30
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/create-default-tools.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/create-default-tools.ts"],"mappings":";;;;;;;UA+CiB;EACf,gBAAgB;;EAEhB;EACA;;;;;;;;;EASA;;;;;;EAMA,oBAAoB;;EAEpB,mBAAmB;;;;;;EAMnB,iBAAiB;;;;;;;;;;;iBAYH,mBAAmB,SAAS,6BAA6B"}
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IComputerDriver, IRetrievalAdapter, ISandboxClient } from "@robota-sdk/agent-tools";
|
|
1
|
+
import { IComputerDriver, IEditorFileAccess, IRetrievalAdapter, ISandboxClient } from "@robota-sdk/agent-tools";
|
|
2
2
|
import { FunctionTool } from "@robota-sdk/agent-core";
|
|
3
3
|
//#region src/create-default-tools.d.ts
|
|
4
4
|
/**
|
|
@@ -19,6 +19,12 @@ interface ICreateDefaultToolsOptions {
|
|
|
19
19
|
* the only way a containment root stays a contract rather than a convention.
|
|
20
20
|
*/
|
|
21
21
|
cwd: string;
|
|
22
|
+
/**
|
|
23
|
+
* An editor that has the workspace open, asked at each call. The host file tools read and write
|
|
24
|
+
* through what it offers and the searches say they read the disk; commands never use it, and a
|
|
25
|
+
* sandbox with its own filesystem keeps every file tool, since its files are not the editor's.
|
|
26
|
+
*/
|
|
27
|
+
editorFiles?: () => IEditorFileAccess | undefined;
|
|
22
28
|
/** SELFHOST-003: when present, adds the adapter-gated `CodebaseRetrieval` tool (absent otherwise). */
|
|
23
29
|
retrievalAdapter?: IRetrievalAdapter;
|
|
24
30
|
/**
|
package/dist/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/create-default-tools.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/create-default-tools.ts"],"mappings":";;;;;;;UA+CiB;EACf,gBAAgB;;EAEhB;EACA;;;;;;;;;EASA;;;;;;EAMA,oBAAoB;;EAEpB,mBAAmB;;;;;;EAMnB,iBAAiB;;;;;;;;;;;iBAYH,mBAAmB,SAAS,6BAA6B"}
|
package/dist/node/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createAskUserQuestionTool as e,createBashTool as t,createComputerTool as n,createEditTool as r,createGlobTool as i,createGrepTool as a,createReadTool as o,createRetrievalTool as s,createShellTool as c,createWebFetchTool as l,createWebSearchTool as u,createWriteTool as d,routesFilesThroughSandbox as f}from"@robota-sdk/agent-tools";function p(p){let m=f(p.sandboxClient),{
|
|
1
|
+
import{createAskUserQuestionTool as e,createBashTool as t,createComputerTool as n,createEditTool as r,createGlobTool as i,createGrepTool as a,createReadTool as o,createRetrievalTool as s,createShellTool as c,createWebFetchTool as l,createWebSearchTool as u,createWriteTool as d,routesFilesThroughSandbox as f}from"@robota-sdk/agent-tools";function p(p){let m=f(p.sandboxClient),{editorFiles:h,...g}=p,{sandboxClient:_,...v}=p,y=m?g:v;return[c(g),t(g),o(y),d(y),r(y),...m?[]:[i(v),a(v)],l({userAgent:p.httpUserAgent}),u(),e(),...p.retrievalAdapter?[s({adapter:p.retrievalAdapter})]:[],...p.computerDriver?n({driver:p.computerDriver}):[]]}export{p as createDefaultTools};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/create-default-tools.ts"],"sourcesContent":["/**\n * The DEFAULT TOOL SET — a composition leaf.\n *\n * ARCH-035 moved this out of `@robota-sdk/agent-framework`. It is a defaults aggregator, and\n * `.agents/project-structure.md` classifies that family as a composition leaf \"imported only at\n * composition roots (entry-point-only)\" — not something a mid-layer assembly library owns, publishes\n * and self-consumes.\n *\n * The move is what carries the guarantee. `agent-subagent-runner` may depend on `agent-framework`\n * (it needs `createSubagentSession` and friends) but has no manifest edge to THIS package, so\n * `import { createDefaultTools } from '@robota-sdk/agent-tool-defaults'` does not resolve there at\n * all. A neutral runner composing the product's tool surface is now a compile error rather than a\n * scan finding — the same shape ARCH-021 achieved on the provider axis with\n * `@robota-sdk/agent-builtin-providers`.\n *\n * `DEFAULT_TOOL_DESCRIPTIONS` deliberately did NOT come along: its only consumer builds the system\n * prompt synchronously, and dragging it here would force either a static edge back into this leaf or\n * a second async hop through prompt building. It stays beside its consumer in `agent-framework`.\n */\nimport {\n createAskUserQuestionTool,\n createShellTool,\n createBashTool,\n createReadTool,\n createWriteTool,\n createEditTool,\n createRetrievalTool,\n createComputerTool,\n createGlobTool,\n createGrepTool,\n createWebFetchTool,\n createWebSearchTool,\n routesFilesThroughSandbox,\n} from '@robota-sdk/agent-tools';\n\nimport type { FunctionTool } from '@robota-sdk/agent-core';\nimport type {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/create-default-tools.ts"],"sourcesContent":["/**\n * The DEFAULT TOOL SET — a composition leaf.\n *\n * ARCH-035 moved this out of `@robota-sdk/agent-framework`. It is a defaults aggregator, and\n * `.agents/project-structure.md` classifies that family as a composition leaf \"imported only at\n * composition roots (entry-point-only)\" — not something a mid-layer assembly library owns, publishes\n * and self-consumes.\n *\n * The move is what carries the guarantee. `agent-subagent-runner` may depend on `agent-framework`\n * (it needs `createSubagentSession` and friends) but has no manifest edge to THIS package, so\n * `import { createDefaultTools } from '@robota-sdk/agent-tool-defaults'` does not resolve there at\n * all. A neutral runner composing the product's tool surface is now a compile error rather than a\n * scan finding — the same shape ARCH-021 achieved on the provider axis with\n * `@robota-sdk/agent-builtin-providers`.\n *\n * `DEFAULT_TOOL_DESCRIPTIONS` deliberately did NOT come along: its only consumer builds the system\n * prompt synchronously, and dragging it here would force either a static edge back into this leaf or\n * a second async hop through prompt building. It stays beside its consumer in `agent-framework`.\n */\nimport {\n createAskUserQuestionTool,\n createShellTool,\n createBashTool,\n createReadTool,\n createWriteTool,\n createEditTool,\n createRetrievalTool,\n createComputerTool,\n createGlobTool,\n createGrepTool,\n createWebFetchTool,\n createWebSearchTool,\n routesFilesThroughSandbox,\n} from '@robota-sdk/agent-tools';\n\nimport type { FunctionTool } from '@robota-sdk/agent-core';\nimport type {\n IComputerDriver,\n IEditorFileAccess,\n IRetrievalAdapter,\n ISandboxClient,\n} from '@robota-sdk/agent-tools';\n\n/**\n * Create the default set of CLI tools.\n * Returns the standard tools.\n */\nexport interface ICreateDefaultToolsOptions {\n sandboxClient?: ISandboxClient;\n /** Host-selected executable for Shell and Bash. */\n shellExecutable?: string;\n httpUserAgent?: string;\n /**\n * The execution root every file tool is contained by. REQUIRED — ARCH-010.\n *\n * It was optional, and `child-process-subagent-worker.ts` called this factory with no argument at\n * all: `cwd` was `undefined`, the path guard was fail-open, and the subagent's `Read` returned\n * `/etc/hostname`. Requiring it means a construction site that forgets does not compile, which is\n * the only way a containment root stays a contract rather than a convention.\n */\n cwd: string;\n /**\n * An editor that has the workspace open, asked at each call. The host file tools read and write\n * through what it offers and the searches say they read the disk; commands never use it, and a\n * sandbox with its own filesystem keeps every file tool, since its files are not the editor's.\n */\n editorFiles?: () => IEditorFileAccess | undefined;\n /** SELFHOST-003: when present, adds the adapter-gated `CodebaseRetrieval` tool (absent otherwise). */\n retrievalAdapter?: IRetrievalAdapter;\n /**\n * SELFHOST-010: when present, adds the adapter-gated `ComputerView` + `Computer` tools. Unlike the\n * shell tool's host `spawn` fallback, computer-use has NO host fallback — with no driver the tools are\n * simply ABSENT (there is no safe library-side \"local\" screen to fall back to).\n */\n computerDriver?: IComputerDriver;\n}\n\n/**\n * ARCH-035: returns `FunctionTool[]`, which is what every factory below actually returns.\n *\n * An earlier revision cast each one to `IToolWithEventService` because session assembly consumes that\n * interface. The cast was lossy in the direction that mattered: `ICapabilityPack.tools` is\n * `readonly FunctionTool[]`, so a pack CONSUMING this set could not assign it without a second cast\n * back — one erasure creating the need for another. `FunctionTool` satisfies `IToolWithEventService`,\n * so session assembly is unaffected and no cast is needed at either end.\n */\nexport function createDefaultTools(options: ICreateDefaultToolsOptions): FunctionTool[] {\n // Issue #3081: the sandbox's filesystem relation decides where file tools read and write. On a\n // SEPARATE filesystem every file tool goes through the sandbox — and `Glob`/`Grep`, which have no\n // sandbox path, are withheld, so a search can never read the host while an edit writes the\n // sandbox. On a SHARED filesystem (commands confined over the host's) file tools stay on the host\n // under the path guard, and only commands run through the sandbox.\n const separate = routesFilesThroughSandbox(options.sandboxClient);\n // An editor holds the host's files, so it reaches only the tools that work on them; commands keep\n // the agent's own sandbox.\n const { editorFiles: _hostFilesOnly, ...commandOptions } = options;\n const { sandboxClient: _commandsOnly, ...hostFileOptions } = options;\n const fileOptions = separate ? commandOptions : hostFileOptions;\n return [\n createShellTool(commandOptions),\n createBashTool(commandOptions),\n createReadTool(fileOptions),\n createWriteTool(fileOptions),\n createEditTool(fileOptions),\n // SEC-007: built per call, NOT the module-level singletons. A singleton is context-free by\n // construction, so registering one meant `Glob`/`Grep` could enumerate outside the session's\n // working directory while `Read`/`Write`/`Edit` were contained.\n ...(separate ? [] : [createGlobTool(hostFileOptions), createGrepTool(hostFileOptions)]),\n // MUST 4: also built per call, for the same reason — `webFetchTool`/`webSearchTool` are\n // module-level singletons in `agent-tools`, so two concurrently open sessions built from this\n // set (e.g. two `sessionParticipant`s) shared them and tripped `resource-reused` even though\n // nothing about a WebFetch/WebSearch tool actually needs to be shared across sessions.\n createWebFetchTool({ userAgent: options.httpUserAgent }),\n createWebSearchTool(),\n createAskUserQuestionTool(),\n // Retrieval is adapter-gated: absent when no adapter is supplied (there is no host fallback).\n ...(options.retrievalAdapter\n ? [createRetrievalTool({ adapter: options.retrievalAdapter })]\n : []),\n // Computer-use is adapter-gated on the driver: absent when no driver is supplied. There is NO host\n // fallback (unlike the shell tool's host spawn) — no library-side \"local\" screen exists.\n ...(options.computerDriver ? createComputerTool({ driver: options.computerDriver }) : []),\n ];\n}\n"],"mappings":"mVAsFA,SAAgB,EAAmB,EAAqD,CAMtF,IAAM,EAAW,EAA0B,EAAQ,aAAa,EAG1D,CAAE,YAAa,EAAgB,GAAG,GAAmB,EACrD,CAAE,cAAe,EAAe,GAAG,GAAoB,EACvD,EAAc,EAAW,EAAiB,EAChD,MAAO,CACL,EAAgB,CAAc,EAC9B,EAAe,CAAc,EAC7B,EAAe,CAAW,EAC1B,EAAgB,CAAW,EAC3B,EAAe,CAAW,EAI1B,GAAI,EAAW,CAAC,EAAI,CAAC,EAAe,CAAe,EAAG,EAAe,CAAe,CAAC,EAKrF,EAAmB,CAAE,UAAW,EAAQ,aAAc,CAAC,EACvD,EAAoB,EACpB,EAA0B,EAE1B,GAAI,EAAQ,iBACR,CAAC,EAAoB,CAAE,QAAS,EAAQ,gBAAiB,CAAC,CAAC,EAC3D,CAAC,EAGL,GAAI,EAAQ,eAAiB,EAAmB,CAAE,OAAQ,EAAQ,cAAe,CAAC,EAAI,CAAC,CACzF,CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-tool-defaults",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.99",
|
|
4
4
|
"description": "The SDK's built-in default tool set (createDefaultTools) as a composition leaf over agent-tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@robota-sdk/agent-core": "3.0.0-beta.
|
|
54
|
-
"@robota-sdk/agent-tools": "3.0.0-beta.
|
|
53
|
+
"@robota-sdk/agent-core": "3.0.0-beta.99",
|
|
54
|
+
"@robota-sdk/agent-tools": "3.0.0-beta.99"
|
|
55
55
|
},
|
|
56
56
|
"volta": {
|
|
57
57
|
"extends": "../../package.json"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
@@ -210,3 +210,80 @@ describe('file tools follow the sandbox filesystem relation (issue #3081)', () =
|
|
|
210
210
|
expect(JSON.stringify(result)).not.toContain('inside the sandbox');
|
|
211
211
|
});
|
|
212
212
|
});
|
|
213
|
+
|
|
214
|
+
/** Issue #3550 item 4 — an editor reaches the host file tools and the searches, never the commands. */
|
|
215
|
+
describe('an editor reaches the host file tools only', () => {
|
|
216
|
+
function recordingEditor() {
|
|
217
|
+
const asked: string[] = [];
|
|
218
|
+
const requests: string[] = [];
|
|
219
|
+
const access = {
|
|
220
|
+
readTextFile: async (path: string) => {
|
|
221
|
+
requests.push(`read ${path}`);
|
|
222
|
+
return 'EDITOR TEXT\n';
|
|
223
|
+
},
|
|
224
|
+
writeTextFile: async (path: string) => {
|
|
225
|
+
requests.push(`write ${path}`);
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
return {
|
|
229
|
+
requests,
|
|
230
|
+
asked,
|
|
231
|
+
editorFiles: () => {
|
|
232
|
+
asked.push('asked');
|
|
233
|
+
return access;
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const parse = (outcome: unknown) =>
|
|
238
|
+
JSON.parse(String((outcome as { data?: unknown }).data)) as { output: string };
|
|
239
|
+
|
|
240
|
+
it('Read and Write go through the editor and the searches say they read the disk', async () => {
|
|
241
|
+
const root = mkdtempSync(join(PRIVATE_BASE, 'editor-'));
|
|
242
|
+
writeFileSync(join(root, 'probe.txt'), 'DISK TEXT\n');
|
|
243
|
+
const editor = recordingEditor();
|
|
244
|
+
const tools = createDefaultTools({ cwd: root, editorFiles: editor.editorFiles });
|
|
245
|
+
const byName = (name: string) => tools.find((tool) => tool.getName() === name)!;
|
|
246
|
+
|
|
247
|
+
expect(parse(await byName('Read').execute({ filePath: 'probe.txt' })).output).toContain(
|
|
248
|
+
'EDITOR TEXT',
|
|
249
|
+
);
|
|
250
|
+
await byName('Write').execute({ filePath: 'out.txt', content: 'x' });
|
|
251
|
+
expect(editor.requests).toEqual([`read ${join(root, 'probe.txt')}`, `write ${join(root, 'out.txt')}`]);
|
|
252
|
+
expect(parse(await byName('Grep').execute({ pattern: 'DISK' })).output).toContain(
|
|
253
|
+
'[Searched the files on disk.',
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('Shell and Bash never ask for the editor', async () => {
|
|
258
|
+
const root = mkdtempSync(join(PRIVATE_BASE, 'editor-shell-'));
|
|
259
|
+
const editor = recordingEditor();
|
|
260
|
+
const tools = createDefaultTools({ cwd: root, editorFiles: editor.editorFiles });
|
|
261
|
+
for (const name of ['Shell', 'Bash']) {
|
|
262
|
+
const outcome = await tools
|
|
263
|
+
.find((tool) => tool.getName() === name)!
|
|
264
|
+
.execute({ command: 'echo shell-ok' });
|
|
265
|
+
expect(JSON.stringify(outcome)).toContain('shell-ok');
|
|
266
|
+
}
|
|
267
|
+
expect(editor.asked).toEqual([]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('a separate sandbox filesystem keeps every file tool, so the editor is not asked', async () => {
|
|
271
|
+
const sandboxClient = new InMemorySandboxClient();
|
|
272
|
+
await sandboxClient.writeFile('/sandbox/only.txt', 'inside the sandbox');
|
|
273
|
+
const editor = recordingEditor();
|
|
274
|
+
const tools = createDefaultTools({
|
|
275
|
+
cwd: '/sandbox',
|
|
276
|
+
sandboxClient,
|
|
277
|
+
editorFiles: editor.editorFiles,
|
|
278
|
+
});
|
|
279
|
+
const read = tools.find((tool) => tool.getName() === 'Read')!;
|
|
280
|
+
expect(JSON.stringify(await read.execute({ filePath: '/sandbox/only.txt' }))).toContain(
|
|
281
|
+
'inside the sandbox',
|
|
282
|
+
);
|
|
283
|
+
await tools.find((tool) => tool.getName() === 'Write')!.execute({
|
|
284
|
+
filePath: '/sandbox/out.txt',
|
|
285
|
+
content: 'x',
|
|
286
|
+
});
|
|
287
|
+
expect(editor.asked).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
@@ -34,7 +34,12 @@ import {
|
|
|
34
34
|
} from '@robota-sdk/agent-tools';
|
|
35
35
|
|
|
36
36
|
import type { FunctionTool } from '@robota-sdk/agent-core';
|
|
37
|
-
import type {
|
|
37
|
+
import type {
|
|
38
|
+
IComputerDriver,
|
|
39
|
+
IEditorFileAccess,
|
|
40
|
+
IRetrievalAdapter,
|
|
41
|
+
ISandboxClient,
|
|
42
|
+
} from '@robota-sdk/agent-tools';
|
|
38
43
|
|
|
39
44
|
/**
|
|
40
45
|
* Create the default set of CLI tools.
|
|
@@ -54,6 +59,12 @@ export interface ICreateDefaultToolsOptions {
|
|
|
54
59
|
* the only way a containment root stays a contract rather than a convention.
|
|
55
60
|
*/
|
|
56
61
|
cwd: string;
|
|
62
|
+
/**
|
|
63
|
+
* An editor that has the workspace open, asked at each call. The host file tools read and write
|
|
64
|
+
* through what it offers and the searches say they read the disk; commands never use it, and a
|
|
65
|
+
* sandbox with its own filesystem keeps every file tool, since its files are not the editor's.
|
|
66
|
+
*/
|
|
67
|
+
editorFiles?: () => IEditorFileAccess | undefined;
|
|
57
68
|
/** SELFHOST-003: when present, adds the adapter-gated `CodebaseRetrieval` tool (absent otherwise). */
|
|
58
69
|
retrievalAdapter?: IRetrievalAdapter;
|
|
59
70
|
/**
|
|
@@ -80,18 +91,21 @@ export function createDefaultTools(options: ICreateDefaultToolsOptions): Functio
|
|
|
80
91
|
// sandbox. On a SHARED filesystem (commands confined over the host's) file tools stay on the host
|
|
81
92
|
// under the path guard, and only commands run through the sandbox.
|
|
82
93
|
const separate = routesFilesThroughSandbox(options.sandboxClient);
|
|
94
|
+
// An editor holds the host's files, so it reaches only the tools that work on them; commands keep
|
|
95
|
+
// the agent's own sandbox.
|
|
96
|
+
const { editorFiles: _hostFilesOnly, ...commandOptions } = options;
|
|
83
97
|
const { sandboxClient: _commandsOnly, ...hostFileOptions } = options;
|
|
84
|
-
const fileOptions = separate ?
|
|
98
|
+
const fileOptions = separate ? commandOptions : hostFileOptions;
|
|
85
99
|
return [
|
|
86
|
-
createShellTool(
|
|
87
|
-
createBashTool(
|
|
100
|
+
createShellTool(commandOptions),
|
|
101
|
+
createBashTool(commandOptions),
|
|
88
102
|
createReadTool(fileOptions),
|
|
89
103
|
createWriteTool(fileOptions),
|
|
90
104
|
createEditTool(fileOptions),
|
|
91
105
|
// SEC-007: built per call, NOT the module-level singletons. A singleton is context-free by
|
|
92
106
|
// construction, so registering one meant `Glob`/`Grep` could enumerate outside the session's
|
|
93
107
|
// working directory while `Read`/`Write`/`Edit` were contained.
|
|
94
|
-
...(separate ? [] : [createGlobTool(
|
|
108
|
+
...(separate ? [] : [createGlobTool(hostFileOptions), createGrepTool(hostFileOptions)]),
|
|
95
109
|
// MUST 4: also built per call, for the same reason — `webFetchTool`/`webSearchTool` are
|
|
96
110
|
// module-level singletons in `agent-tools`, so two concurrently open sessions built from this
|
|
97
111
|
// set (e.g. two `sessionParticipant`s) shared them and tripped `resource-reused` even though
|