@mku1tra/que-pasa 0.2.1 → 0.2.3
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/README.md +1 -1
- package/dist/sandbox/backend.d.ts +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/shell.js +2 -0
- package/dist/sandbox/tools.d.ts +6 -1
- package/dist/sandbox/tools.js +17 -11
- package/dist/sandbox/view.js +11 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Install it from npm: `npm install @mku1tra/que-pasa zod`. zod is a peer dependen
|
|
|
51
51
|
|
|
52
52
|
## Release
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
A land is a version when its commits ask for one: crate reads their types (Conventional Commits) on the server, tags main `v<version>`, and copies the tag to GitHub ([crate](https://github.com/matta-kelly/crate#release)). While que-pasa is at 0.x, a breaking change bumps minor, and `feat`, `fix` and `perf` bump patch. `.github/workflows/publish.yml` runs the check set, sets `package.json`'s version from the tag, and publishes on npm by trusted publishing (no token, with provenance). `package.json`'s version in git is not kept: the tag is the version. A ruleset lets only crate's deploy key, or an admin of the repo, make a version tag. A version npm has is not published again. A published version cannot change: npm unpublishes only within 72 hours.
|
|
55
55
|
|
|
56
56
|
## Ship
|
|
57
57
|
|
|
@@ -66,7 +66,7 @@ export interface FileBackend {
|
|
|
66
66
|
edit(path: string, old: string, next: string, all: boolean): Promise<EditResult>;
|
|
67
67
|
/** The files under a folder whose path from that folder matches a glob. */
|
|
68
68
|
glob(pattern: string, path: string): Promise<GlobResult>;
|
|
69
|
-
/** The lines of the files under a path that hold a text, as is (not a pattern), in files matching `glob` if given. */
|
|
69
|
+
/** The lines of the files under a path that hold a text, as is (not a pattern), in files matching `glob` if given: by path, then line. */
|
|
70
70
|
grep(pattern: string, path: string, glob?: string): Promise<GrepResult>;
|
|
71
71
|
}
|
|
72
72
|
/** Files, and commands: a backend that can run a shell command. */
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { local } from "./local.ts";
|
|
|
3
3
|
export { podman, type Mount, type Podman, type PodmanOptions, } from "./podman.ts";
|
|
4
4
|
export { shellBackend, type Exec } from "./shell.ts";
|
|
5
5
|
export type { Ran, Script } from "./process.ts";
|
|
6
|
-
export { Sandbox, edit_file, execute, fileTools, glob, grep, ls, read_file, readTools, write_file, } from "./tools.ts";
|
|
6
|
+
export { Sandbox, commandAnswer, edit_file, execute, fileTools, glob, grep, ls, maxAnswer, read_file, readTools, write_file, } from "./tools.ts";
|
|
7
7
|
export { view, type ViewPaths } from "./view.ts";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -2,5 +2,5 @@ export { canExecute, } from "./backend.js";
|
|
|
2
2
|
export { local } from "./local.js";
|
|
3
3
|
export { podman, } from "./podman.js";
|
|
4
4
|
export { shellBackend } from "./shell.js";
|
|
5
|
-
export { Sandbox, edit_file, execute, fileTools, glob, grep, ls, read_file, readTools, write_file, } from "./tools.js";
|
|
5
|
+
export { Sandbox, commandAnswer, edit_file, execute, fileTools, glob, grep, ls, maxAnswer, read_file, readTools, write_file, } from "./tools.js";
|
|
6
6
|
export { view } from "./view.js";
|
package/dist/sandbox/shell.js
CHANGED
|
@@ -157,6 +157,8 @@ else exit ${missing}; fi`,
|
|
|
157
157
|
const files = glob ? await matching(at, glob) : await filesUnder(at);
|
|
158
158
|
if (!files)
|
|
159
159
|
return { error: "file_not_found" };
|
|
160
|
+
// By path, then line, as glob lists them: the same order on any file system.
|
|
161
|
+
files.sort();
|
|
160
162
|
// No line holds a newline, so a text with one is in no file.
|
|
161
163
|
if (pattern.includes("\n"))
|
|
162
164
|
return { matches: [] };
|
package/dist/sandbox/tools.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { type Json } from "../define/index.ts";
|
|
8
|
-
import { type FileBackend } from "./backend.ts";
|
|
8
|
+
import { type ExecuteResult, type FileBackend } from "./backend.ts";
|
|
9
9
|
/** The backend a run's file tools work in: the host provides it per run, such as a container, or a view over one. */
|
|
10
10
|
export declare const Sandbox: import("../define/resource.ts").Resource<FileBackend>;
|
|
11
11
|
/** The most of a tool's answer the model gets (Deep Agents: 20,000 tokens, at four characters each). */
|
|
@@ -40,6 +40,11 @@ export declare const grep: import("../define/tool.ts").Tool<z.ZodObject<{
|
|
|
40
40
|
export declare const execute: import("../define/tool.ts").Tool<z.ZodObject<{
|
|
41
41
|
command: z.ZodString;
|
|
42
42
|
}, z.core.$strip>, Json | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* What the model reads for a command that ran: the tail of its output, at most `maxAnswer` characters, where a failure
|
|
45
|
+
* is, then how it ended. `execute`'s answer, for a host tool that runs a command to answer the same way.
|
|
46
|
+
*/
|
|
47
|
+
export declare function commandAnswer(result: ExecuteResult): string;
|
|
43
48
|
/** The tools that only read. */
|
|
44
49
|
export declare const readTools: readonly [import("../define/tool.ts").Tool<z.ZodObject<{
|
|
45
50
|
path: z.ZodDefault<z.ZodString>;
|
package/dist/sandbox/tools.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { defineResource, defineTool, } from "../define/index.js";
|
|
8
|
-
import { canExecute } from "./backend.js";
|
|
8
|
+
import { canExecute, } from "./backend.js";
|
|
9
9
|
/** The backend a run's file tools work in: the host provides it per run, such as a container, or a view over one. */
|
|
10
10
|
export const Sandbox = defineResource("sandbox");
|
|
11
11
|
/** The most of a tool's answer the model gets (Deep Agents: 20,000 tokens, at four characters each). */
|
|
@@ -224,18 +224,24 @@ export const execute = defineTool({
|
|
|
224
224
|
if (!canExecute(backend)) {
|
|
225
225
|
return "Error: Execution not available. This agent's backend does not support command execution.";
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
const cutTail = result.output.length > maxAnswer;
|
|
229
|
-
const output = cutTail ? result.output.slice(-maxAnswer) : result.output;
|
|
230
|
-
const status = result.exitCode === null
|
|
231
|
-
? "[Command was killed]"
|
|
232
|
-
: `[Command ${result.exitCode === 0 ? "succeeded" : "failed"} with exit code ${result.exitCode}]`;
|
|
233
|
-
const truncated = result.truncated || cutTail
|
|
234
|
-
? "\n[Output was truncated due to size limits]"
|
|
235
|
-
: "";
|
|
236
|
-
return `${output}${output.endsWith("\n") || output === "" ? "" : "\n"}${status}${truncated}`;
|
|
227
|
+
return commandAnswer(await backend.execute(command));
|
|
237
228
|
},
|
|
238
229
|
});
|
|
230
|
+
/**
|
|
231
|
+
* What the model reads for a command that ran: the tail of its output, at most `maxAnswer` characters, where a failure
|
|
232
|
+
* is, then how it ended. `execute`'s answer, for a host tool that runs a command to answer the same way.
|
|
233
|
+
*/
|
|
234
|
+
export function commandAnswer(result) {
|
|
235
|
+
const cutTail = result.output.length > maxAnswer;
|
|
236
|
+
const output = cutTail ? result.output.slice(-maxAnswer) : result.output;
|
|
237
|
+
const status = result.exitCode === null
|
|
238
|
+
? "[Command was killed]"
|
|
239
|
+
: `[Command ${result.exitCode === 0 ? "succeeded" : "failed"} with exit code ${result.exitCode}]`;
|
|
240
|
+
const truncated = result.truncated || cutTail
|
|
241
|
+
? "\n[Output was truncated due to size limits]"
|
|
242
|
+
: "";
|
|
243
|
+
return `${output}${output.endsWith("\n") || output === "" ? "" : "\n"}${status}${truncated}`;
|
|
244
|
+
}
|
|
239
245
|
/** The tools that only read. */
|
|
240
246
|
export const readTools = [ls, read_file, glob, grep];
|
|
241
247
|
/** The tools that read and write files. `execute` is given on its own. */
|
package/dist/sandbox/view.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { posix } from "node:path";
|
|
2
1
|
import { fromRoot, isUnder } from "./paths.js";
|
|
3
2
|
/**
|
|
4
3
|
* A backend over a backend, limited to some files: to its tools, a path outside the view does not exist, and a write
|
|
@@ -18,32 +17,15 @@ export function view(backend, paths) {
|
|
|
18
17
|
});
|
|
19
18
|
const writes = new Set(checked(paths.write));
|
|
20
19
|
const files = [...new Set([...checked(paths.read), ...writes])];
|
|
20
|
+
const mine = new Set(files);
|
|
21
21
|
/** A path of the view: one of its files, or a folder on the way to one. */
|
|
22
22
|
const sees = (at) => files.some((f) => f === at || isUnder(f, at));
|
|
23
23
|
/** A path the view resolves, or undefined when it is outside the root. */
|
|
24
24
|
const at = (path) => fromRoot(root, path);
|
|
25
25
|
const missing = { error: "file_not_found" };
|
|
26
26
|
const invalid = { error: "invalid_path" };
|
|
27
|
-
/**
|
|
28
|
-
const
|
|
29
|
-
// A search leaves out .git, as a backend's does.
|
|
30
|
-
if (!isUnder(f, folder) || f.split("/").includes(".git"))
|
|
31
|
-
return false;
|
|
32
|
-
const from = f === folder
|
|
33
|
-
? posix.basename(f)
|
|
34
|
-
: folder
|
|
35
|
-
? f.slice(folder.length + 1)
|
|
36
|
-
: f;
|
|
37
|
-
return glob === undefined || posix.matchesGlob(from, glob);
|
|
38
|
-
});
|
|
39
|
-
/** Which of the files are there: a write file can be one not made yet. */
|
|
40
|
-
const present = async (list) => {
|
|
41
|
-
const there = await Promise.all(list.map(async (f) => {
|
|
42
|
-
const listed = await backend.ls(f);
|
|
43
|
-
return listed.files?.some((e) => e.path === f && !e.isDir) ?? false;
|
|
44
|
-
}));
|
|
45
|
-
return list.filter((_, i) => there[i]);
|
|
46
|
-
};
|
|
27
|
+
/** A path the backend gave back that is one of the view's files. */
|
|
28
|
+
const own = (path) => mine.has(path);
|
|
47
29
|
const refused = (path) => ({
|
|
48
30
|
error: "permission_denied",
|
|
49
31
|
message: `${path} is not a file this view may write: it may write ${[...writes].join(", ") || "none"}`,
|
|
@@ -79,13 +61,16 @@ export function view(backend, paths) {
|
|
|
79
61
|
return invalid;
|
|
80
62
|
return writes.has(p) ? backend.edit(path, old, next, all) : refused(path);
|
|
81
63
|
},
|
|
64
|
+
// A search runs once on the backend, and the view keeps its own files: Deep Agents filters its path rules' results
|
|
65
|
+
// the same way. The backend's limit counts files outside the view too, so a cut result says it is cut.
|
|
82
66
|
async glob(pattern, path) {
|
|
83
67
|
const p = at(path);
|
|
84
68
|
if (p === undefined)
|
|
85
69
|
return invalid;
|
|
86
70
|
if (!sees(p))
|
|
87
71
|
return missing;
|
|
88
|
-
|
|
72
|
+
const found = await backend.glob(pattern, path);
|
|
73
|
+
return found.files ? { ...found, files: found.files.filter(own) } : found;
|
|
89
74
|
},
|
|
90
75
|
async grep(pattern, path, glob) {
|
|
91
76
|
const p = at(path);
|
|
@@ -93,13 +78,10 @@ export function view(backend, paths) {
|
|
|
93
78
|
return invalid;
|
|
94
79
|
if (!sees(p))
|
|
95
80
|
return missing;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
matches.push(...(found.matches ?? []));
|
|
101
|
-
}
|
|
102
|
-
return { matches };
|
|
81
|
+
const found = await backend.grep(pattern, path, glob);
|
|
82
|
+
return found.matches
|
|
83
|
+
? { ...found, matches: found.matches.filter((m) => own(m.path)) }
|
|
84
|
+
: found;
|
|
103
85
|
},
|
|
104
86
|
};
|
|
105
87
|
}
|
package/package.json
CHANGED