@agentic-kit/pi-host 0.2.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/LICENSE +23 -0
- package/README.md +129 -0
- package/esm/events.d.ts +47 -0
- package/esm/events.js +161 -0
- package/esm/gate.d.ts +35 -0
- package/esm/gate.js +88 -0
- package/esm/host.d.ts +18 -0
- package/esm/host.js +26 -0
- package/esm/index.d.ts +16 -0
- package/esm/index.js +12 -0
- package/esm/model.d.ts +39 -0
- package/esm/model.js +211 -0
- package/esm/persona.d.ts +85 -0
- package/esm/persona.js +122 -0
- package/esm/project-context.d.ts +22 -0
- package/esm/project-context.js +50 -0
- package/esm/tools.d.ts +23 -0
- package/esm/tools.js +32 -0
- package/esm/workspace-tools.d.ts +22 -0
- package/esm/workspace-tools.js +199 -0
- package/events.d.ts +47 -0
- package/events.js +168 -0
- package/gate.d.ts +35 -0
- package/gate.js +92 -0
- package/host.d.ts +18 -0
- package/host.js +29 -0
- package/index.d.ts +16 -0
- package/index.js +42 -0
- package/model.d.ts +39 -0
- package/model.js +216 -0
- package/package.json +49 -0
- package/persona.d.ts +85 -0
- package/persona.js +131 -0
- package/project-context.d.ts +22 -0
- package/project-context.js +59 -0
- package/tools.d.ts +23 -0
- package/tools.js +36 -0
- package/workspace-tools.d.ts +22 -0
- package/workspace-tools.js +207 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentTool } from '@agentic-kit/agent';
|
|
2
|
+
export declare const DEFAULT_COMMAND_TIMEOUT_MS = 120000;
|
|
3
|
+
export declare const MAX_READ_BYTES = 200000;
|
|
4
|
+
export interface WorkspaceToolsOptions {
|
|
5
|
+
/** The clone the run edits. Every tool is relative to it. */
|
|
6
|
+
cwd: string;
|
|
7
|
+
/** Wall-clock cap for `run_command`. */
|
|
8
|
+
commandTimeoutMs?: number;
|
|
9
|
+
/** Environment `run_command` runs with. Defaults to the Job's, minus nothing. */
|
|
10
|
+
env?: NodeJS.ProcessEnv;
|
|
11
|
+
}
|
|
12
|
+
export declare class OutsideWorkspaceError extends Error {
|
|
13
|
+
readonly requested: string;
|
|
14
|
+
readonly cwd: string;
|
|
15
|
+
constructor(requested: string, cwd: string);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Read, write, edit, list, run. Named the way pi names them so a persona's
|
|
19
|
+
* tool allowlist reads the same in either host.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createWorkspaceTools(options: WorkspaceToolsOptions): AgentTool[];
|
|
22
|
+
export declare const WORKSPACE_TOOL_NAMES: readonly ["read_file", "write_file", "edit_file", "list_files", "run_command"];
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The coding lane's tools: a workspace, as tools.
|
|
3
|
+
//
|
|
4
|
+
// The pi control-plane tools (`@agentic-kit/pi`) are the *schema* lane — they
|
|
5
|
+
// resolve a Constructive project from the cwd and edit a tenant's database. A
|
|
6
|
+
// coding run's cwd is a git clone, so those tools have no project to act on;
|
|
7
|
+
// what it needs instead is the file surface of the checkout. pi's own
|
|
8
|
+
// read/write/edit/bash tools live inside the pi coding agent's TUI runtime
|
|
9
|
+
// rather than as `AgentTool`s an embedder can hand to `Agent`, so the four here
|
|
10
|
+
// are that surface and nothing more.
|
|
11
|
+
//
|
|
12
|
+
// Every path is resolved inside the workspace: a model that asks for
|
|
13
|
+
// `../../etc/passwd` gets an error result rather than the file, because the
|
|
14
|
+
// clone is the boundary the Job was given.
|
|
15
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
16
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.WORKSPACE_TOOL_NAMES = exports.OutsideWorkspaceError = exports.MAX_READ_BYTES = exports.DEFAULT_COMMAND_TIMEOUT_MS = void 0;
|
|
20
|
+
exports.createWorkspaceTools = createWorkspaceTools;
|
|
21
|
+
const node_child_process_1 = require("node:child_process");
|
|
22
|
+
const promises_1 = require("node:fs/promises");
|
|
23
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
24
|
+
const project_context_1 = require("./project-context");
|
|
25
|
+
exports.DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
26
|
+
exports.MAX_READ_BYTES = 200_000;
|
|
27
|
+
class OutsideWorkspaceError extends Error {
|
|
28
|
+
requested;
|
|
29
|
+
cwd;
|
|
30
|
+
constructor(requested, cwd) {
|
|
31
|
+
super(`'${requested}' is outside the workspace (${cwd})`);
|
|
32
|
+
this.requested = requested;
|
|
33
|
+
this.cwd = cwd;
|
|
34
|
+
this.name = 'OutsideWorkspaceError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.OutsideWorkspaceError = OutsideWorkspaceError;
|
|
38
|
+
function resolveInside(cwd, requested) {
|
|
39
|
+
const resolved = node_path_1.default.resolve(cwd, requested);
|
|
40
|
+
if (!(0, project_context_1.isInside)(cwd, resolved))
|
|
41
|
+
throw new OutsideWorkspaceError(requested, cwd);
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
function text(value) {
|
|
45
|
+
return { content: [{ type: 'text', text: value }] };
|
|
46
|
+
}
|
|
47
|
+
function failure(error) {
|
|
48
|
+
return text(`error: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
}
|
|
50
|
+
async function attempt(run) {
|
|
51
|
+
try {
|
|
52
|
+
return await run();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// A tool failure is the model's to recover from — a bad path or a failing
|
|
56
|
+
// command is an ordinary result it should read and retry, not a crashed
|
|
57
|
+
// run. Anything that must end the run (a gateway that cannot be reached,
|
|
58
|
+
// a thread that is gone) is raised outside a tool.
|
|
59
|
+
return failure(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function runCommand(command, options) {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
(0, node_child_process_1.execFile)('/bin/sh', ['-c', command], {
|
|
65
|
+
cwd: options.cwd,
|
|
66
|
+
timeout: options.timeoutMs,
|
|
67
|
+
maxBuffer: exports.MAX_READ_BYTES,
|
|
68
|
+
...(options.env ? { env: options.env } : {})
|
|
69
|
+
}, (error, stdout, stderr) => {
|
|
70
|
+
const code = error && typeof error.code === 'number'
|
|
71
|
+
? error.code
|
|
72
|
+
: error
|
|
73
|
+
? 1
|
|
74
|
+
: 0;
|
|
75
|
+
resolve({ stdout, stderr: stderr + (error && !stderr ? String(error.message) : ''), code });
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read, write, edit, list, run. Named the way pi names them so a persona's
|
|
81
|
+
* tool allowlist reads the same in either host.
|
|
82
|
+
*/
|
|
83
|
+
function createWorkspaceTools(options) {
|
|
84
|
+
const cwd = node_path_1.default.resolve(options.cwd);
|
|
85
|
+
const timeoutMs = options.commandTimeoutMs ?? exports.DEFAULT_COMMAND_TIMEOUT_MS;
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
name: 'read_file',
|
|
89
|
+
label: 'Read a file',
|
|
90
|
+
description: 'Read a UTF-8 file from the workspace, relative to its root.',
|
|
91
|
+
parameters: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: { path: { type: 'string', description: 'Path relative to the workspace root' } },
|
|
94
|
+
required: ['path'],
|
|
95
|
+
additionalProperties: false
|
|
96
|
+
},
|
|
97
|
+
execute: (_id, params) => attempt(async () => {
|
|
98
|
+
const file = resolveInside(cwd, String(params.path));
|
|
99
|
+
const info = await (0, promises_1.stat)(file);
|
|
100
|
+
if (info.size > exports.MAX_READ_BYTES) {
|
|
101
|
+
return text(`error: ${params.path} is ${info.size} bytes — too large to read whole`);
|
|
102
|
+
}
|
|
103
|
+
return text(await (0, promises_1.readFile)(file, 'utf-8'));
|
|
104
|
+
})
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'write_file',
|
|
108
|
+
label: 'Write a file',
|
|
109
|
+
description: 'Write a UTF-8 file in the workspace, creating parent directories. Replaces the whole file.',
|
|
110
|
+
parameters: {
|
|
111
|
+
type: 'object',
|
|
112
|
+
properties: {
|
|
113
|
+
path: { type: 'string', description: 'Path relative to the workspace root' },
|
|
114
|
+
content: { type: 'string', description: 'The file\'s new contents' }
|
|
115
|
+
},
|
|
116
|
+
required: ['path', 'content'],
|
|
117
|
+
additionalProperties: false
|
|
118
|
+
},
|
|
119
|
+
execute: (_id, params) => attempt(async () => {
|
|
120
|
+
const file = resolveInside(cwd, String(params.path));
|
|
121
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(file), { recursive: true });
|
|
122
|
+
await (0, promises_1.writeFile)(file, String(params.content), 'utf-8');
|
|
123
|
+
return text(`wrote ${params.path}`);
|
|
124
|
+
})
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'edit_file',
|
|
128
|
+
label: 'Edit a file',
|
|
129
|
+
description: 'Replace an exact string in a file. The string must occur exactly once, so an ambiguous edit fails rather than guessing.',
|
|
130
|
+
parameters: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
path: { type: 'string', description: 'Path relative to the workspace root' },
|
|
134
|
+
old_string: { type: 'string', description: 'The text to replace' },
|
|
135
|
+
new_string: { type: 'string', description: 'What to replace it with' }
|
|
136
|
+
},
|
|
137
|
+
required: ['path', 'old_string', 'new_string'],
|
|
138
|
+
additionalProperties: false
|
|
139
|
+
},
|
|
140
|
+
execute: (_id, params) => attempt(async () => {
|
|
141
|
+
const file = resolveInside(cwd, String(params.path));
|
|
142
|
+
const before = await (0, promises_1.readFile)(file, 'utf-8');
|
|
143
|
+
const target = String(params.old_string);
|
|
144
|
+
const occurrences = before.split(target).length - 1;
|
|
145
|
+
if (occurrences === 0)
|
|
146
|
+
return text(`error: ${params.path} does not contain that text`);
|
|
147
|
+
if (occurrences > 1) {
|
|
148
|
+
return text(`error: that text occurs ${occurrences} times in ${params.path} — include enough context to make it unique`);
|
|
149
|
+
}
|
|
150
|
+
await (0, promises_1.writeFile)(file, before.replace(target, String(params.new_string)), 'utf-8');
|
|
151
|
+
return text(`edited ${params.path}`);
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'list_files',
|
|
156
|
+
label: 'List files',
|
|
157
|
+
description: 'List the entries of a directory in the workspace.',
|
|
158
|
+
parameters: {
|
|
159
|
+
type: 'object',
|
|
160
|
+
properties: {
|
|
161
|
+
path: { type: 'string', description: 'Directory relative to the workspace root' }
|
|
162
|
+
},
|
|
163
|
+
additionalProperties: false
|
|
164
|
+
},
|
|
165
|
+
execute: (_id, params) => attempt(async () => {
|
|
166
|
+
const dir = resolveInside(cwd, params.path === undefined ? '.' : String(params.path));
|
|
167
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
168
|
+
const listed = entries
|
|
169
|
+
.filter((entry) => entry.name !== '.git')
|
|
170
|
+
.map((entry) => (entry.isDirectory() ? `${entry.name}/` : entry.name))
|
|
171
|
+
.sort();
|
|
172
|
+
return text(listed.join('\n') || '(empty)');
|
|
173
|
+
})
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: 'run_command',
|
|
177
|
+
label: 'Run a command',
|
|
178
|
+
description: 'Run a shell command in the workspace root and answer with its output and exit status.',
|
|
179
|
+
parameters: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
properties: { command: { type: 'string', description: 'The command line to run' } },
|
|
182
|
+
required: ['command'],
|
|
183
|
+
additionalProperties: false
|
|
184
|
+
},
|
|
185
|
+
execute: (_id, params) => attempt(async () => {
|
|
186
|
+
const result = await runCommand(String(params.command), {
|
|
187
|
+
cwd,
|
|
188
|
+
timeoutMs,
|
|
189
|
+
...(options.env ? { env: options.env } : {})
|
|
190
|
+
});
|
|
191
|
+
const parts = [
|
|
192
|
+
result.stdout.trim() ? `stdout:\n${result.stdout.trim()}` : '',
|
|
193
|
+
result.stderr.trim() ? `stderr:\n${result.stderr.trim()}` : '',
|
|
194
|
+
`exit: ${result.code}`
|
|
195
|
+
].filter(Boolean);
|
|
196
|
+
return text(parts.join('\n\n'));
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
exports.WORKSPACE_TOOL_NAMES = [
|
|
202
|
+
'read_file',
|
|
203
|
+
'write_file',
|
|
204
|
+
'edit_file',
|
|
205
|
+
'list_files',
|
|
206
|
+
'run_command'
|
|
207
|
+
];
|