skyloom 1.16.2 → 1.18.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/README.md +15 -3
- package/dist/cli/loom_chat.d.ts.map +1 -1
- package/dist/cli/loom_chat.js +17 -0
- package/dist/cli/loom_chat.js.map +1 -1
- package/dist/cli/main.js +37 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/core/agent.d.ts +2 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +21 -5
- package/dist/core/agent.js.map +1 -1
- package/dist/core/bgproc.d.ts +59 -0
- package/dist/core/bgproc.d.ts.map +1 -0
- package/dist/core/bgproc.js +135 -0
- package/dist/core/bgproc.js.map +1 -0
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +20 -0
- package/dist/core/commands.js.map +1 -1
- package/dist/core/diagnostics.d.ts +39 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +206 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/diff.d.ts +31 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +82 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/envcontext.d.ts +25 -0
- package/dist/core/envcontext.d.ts.map +1 -0
- package/dist/core/envcontext.js +112 -0
- package/dist/core/envcontext.js.map +1 -0
- package/dist/core/factory.d.ts +2 -0
- package/dist/core/factory.d.ts.map +1 -1
- package/dist/core/factory.js +35 -2
- package/dist/core/factory.js.map +1 -1
- package/dist/core/patch.d.ts +59 -0
- package/dist/core/patch.d.ts.map +1 -0
- package/dist/core/patch.js +220 -0
- package/dist/core/patch.js.map +1 -0
- package/dist/core/protocol.d.ts +11 -0
- package/dist/core/protocol.d.ts.map +1 -0
- package/dist/core/protocol.js +39 -0
- package/dist/core/protocol.js.map +1 -0
- package/dist/core/sandbox.d.ts +1 -0
- package/dist/core/sandbox.d.ts.map +1 -1
- package/dist/core/sandbox.js +1 -0
- package/dist/core/sandbox.js.map +1 -1
- package/dist/core/search.d.ts +41 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +156 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/security.d.ts +22 -2
- package/dist/core/security.d.ts.map +1 -1
- package/dist/core/security.js +55 -24
- package/dist/core/security.js.map +1 -1
- package/dist/core/skill.d.ts +4 -0
- package/dist/core/skill.d.ts.map +1 -1
- package/dist/core/skill.js +1 -0
- package/dist/core/skill.js.map +1 -1
- package/dist/core/subagent.d.ts +75 -0
- package/dist/core/subagent.d.ts.map +1 -0
- package/dist/core/subagent.js +287 -0
- package/dist/core/subagent.js.map +1 -0
- package/dist/core/tool.d.ts +23 -1
- package/dist/core/tool.d.ts.map +1 -1
- package/dist/core/tool.js +95 -30
- package/dist/core/tool.js.map +1 -1
- package/dist/plugins/loader.d.ts +49 -8
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +129 -16
- package/dist/plugins/loader.js.map +1 -1
- package/dist/tools/builtin.d.ts.map +1 -1
- package/dist/tools/builtin.js +183 -17
- package/dist/tools/builtin.js.map +1 -1
- package/dist/tools/spawn.d.ts +23 -0
- package/dist/tools/spawn.d.ts.map +1 -0
- package/dist/tools/spawn.js +77 -0
- package/dist/tools/spawn.js.map +1 -0
- package/docs/OPTIMIZATION_PLAN.md +21 -4
- package/package.json +1 -1
- package/src/cli/loom_chat.ts +11 -0
- package/src/cli/main.ts +31 -1
- package/src/core/agent.ts +20 -5
- package/src/core/bgproc.ts +153 -0
- package/src/core/commands.ts +20 -0
- package/src/core/diagnostics.ts +178 -0
- package/src/core/diff.ts +98 -0
- package/src/core/envcontext.ts +79 -0
- package/src/core/factory.ts +31 -2
- package/src/core/patch.ts +176 -0
- package/src/core/protocol.ts +36 -0
- package/src/core/sandbox.ts +1 -1
- package/src/core/search.ts +138 -0
- package/src/core/security.ts +63 -21
- package/src/core/skill.ts +1 -1
- package/src/core/subagent.ts +272 -0
- package/src/core/tool.ts +101 -31
- package/src/plugins/loader.ts +145 -18
- package/src/tools/builtin.ts +167 -17
- package/src/tools/spawn.ts +92 -0
- package/tests/bgproc.test.ts +65 -0
- package/tests/diagnostics.test.ts +86 -0
- package/tests/edit_diff.test.ts +102 -0
- package/tests/envcontext.test.ts +67 -0
- package/tests/patch.test.ts +128 -0
- package/tests/plugins.test.ts +84 -0
- package/tests/protocol.test.ts +27 -0
- package/tests/search.test.ts +87 -0
- package/tests/security.test.ts +87 -0
- package/tests/subagent.test.ts +211 -0
- package/tests/tool.test.ts +120 -0
package/dist/plugins/loader.d.ts
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plugin loader —
|
|
2
|
+
* Plugin loader — discovers external plugins and runs them through an ordered
|
|
3
|
+
* hook lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* A plugin is a directory with an `index.js` exporting either:
|
|
6
|
+
* - activate(ctx): the lifecycle form. `ctx` scopes every registration to the
|
|
7
|
+
* plugin (registerTool / on(hook, fn)), so unload(name) cleanly removes
|
|
8
|
+
* exactly what the plugin added.
|
|
9
|
+
* - register(registry): the legacy form. Still supported — tools it adds are
|
|
10
|
+
* diffed against the registry so they're tracked for unload too.
|
|
11
|
+
*
|
|
12
|
+
* Hooks fire in registration order. Core hooks: `init` (after all plugins
|
|
13
|
+
* load), `tool.register` (a tool was added), `provider.update` (model/provider
|
|
14
|
+
* config changed). Plugins may define and emit their own hook names.
|
|
3
15
|
*/
|
|
4
|
-
import { ToolRegistry } from '../core/tool';
|
|
16
|
+
import { ToolRegistry, type ToolDefinition } from '../core/tool';
|
|
17
|
+
import { getLogger } from '../core/logger';
|
|
18
|
+
export type PluginHook = 'init' | 'tool.register' | 'provider.update' | string;
|
|
19
|
+
export type HookHandler = (payload?: any) => void | Promise<void>;
|
|
20
|
+
/** Scoped API handed to a plugin's activate(); every call is tracked for unload. */
|
|
21
|
+
export interface PluginContext {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly config: any;
|
|
24
|
+
readonly log: ReturnType<typeof getLogger>;
|
|
25
|
+
registerTool(def: ToolDefinition): void;
|
|
26
|
+
on(hook: PluginHook, handler: HookHandler): void;
|
|
27
|
+
}
|
|
28
|
+
export interface Plugin {
|
|
29
|
+
name?: string;
|
|
30
|
+
activate?(ctx: PluginContext): void | Promise<void>;
|
|
31
|
+
register?(registry: ToolRegistry): void;
|
|
32
|
+
deactivate?(): void | Promise<void>;
|
|
33
|
+
}
|
|
5
34
|
/**
|
|
6
35
|
* A plugin path is safe to `require` only if neither it nor (on POSIX) its
|
|
7
36
|
* permissions allow group/world write — otherwise a less-privileged user could
|
|
@@ -11,14 +40,26 @@ import { ToolRegistry } from '../core/tool';
|
|
|
11
40
|
export declare function isSafePluginPath(target: string): boolean;
|
|
12
41
|
export declare class PluginLoader {
|
|
13
42
|
private toolRegistry;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
43
|
+
private config;
|
|
44
|
+
private plugins;
|
|
45
|
+
/** hook name -> handlers in registration order, each tagged with its plugin. */
|
|
46
|
+
private hookHandlers;
|
|
47
|
+
constructor(toolRegistry: ToolRegistry, config?: any);
|
|
48
|
+
/** Load plugins from specified directories. Returns the number activated. */
|
|
18
49
|
loadFromDirectories(directories: string[]): number;
|
|
50
|
+
private loadDirectory;
|
|
19
51
|
/**
|
|
20
|
-
*
|
|
52
|
+
* Activate a plugin module under a name. Reactivating an already-loaded name
|
|
53
|
+
* unloads the previous instance first. Returns true if anything registered.
|
|
21
54
|
*/
|
|
22
|
-
|
|
55
|
+
activatePlugin(name: string, mod: Plugin): boolean;
|
|
56
|
+
/** Fire a hook; handlers run in registration order. Errors are isolated. */
|
|
57
|
+
emit(hook: PluginHook, payload?: any): Promise<void>;
|
|
58
|
+
/** Unload a plugin: remove its tools and hook handlers, call deactivate. */
|
|
59
|
+
unload(name: string): boolean;
|
|
60
|
+
/** Names of currently loaded plugins. */
|
|
61
|
+
list(): string[];
|
|
62
|
+
/** Number of handlers registered for a hook (for diagnostics/tests). */
|
|
63
|
+
hookCount(hook: PluginHook): number;
|
|
23
64
|
}
|
|
24
65
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAI3C,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,eAAe,GAAG,iBAAiB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAElE,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;IAC3C,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAAC;IACxC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC,UAAU,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC;AASD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CASxD;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAmC;IAClD,gFAAgF;IAChF,OAAO,CAAC,YAAY,CAAiE;gBAEzE,YAAY,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,GAAG;IAKpD,6EAA6E;IAC7E,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM;IAQlD,OAAO,CAAC,aAAa;IAqCrB;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAqDlD,4EAA4E;IACtE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAY1D,4EAA4E;IAC5E,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAiB7B,yCAAyC;IACzC,IAAI,IAAI,MAAM,EAAE;IAEhB,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM;CACpC"}
|
package/dist/plugins/loader.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Plugin loader —
|
|
3
|
+
* Plugin loader — discovers external plugins and runs them through an ordered
|
|
4
|
+
* hook lifecycle.
|
|
5
|
+
*
|
|
6
|
+
* A plugin is a directory with an `index.js` exporting either:
|
|
7
|
+
* - activate(ctx): the lifecycle form. `ctx` scopes every registration to the
|
|
8
|
+
* plugin (registerTool / on(hook, fn)), so unload(name) cleanly removes
|
|
9
|
+
* exactly what the plugin added.
|
|
10
|
+
* - register(registry): the legacy form. Still supported — tools it adds are
|
|
11
|
+
* diffed against the registry so they're tracked for unload too.
|
|
12
|
+
*
|
|
13
|
+
* Hooks fire in registration order. Core hooks: `init` (after all plugins
|
|
14
|
+
* load), `tool.register` (a tool was added), `provider.update` (model/provider
|
|
15
|
+
* config changed). Plugins may define and emit their own hook names.
|
|
4
16
|
*/
|
|
5
17
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
18
|
if (k2 === undefined) k2 = k;
|
|
@@ -62,12 +74,14 @@ function isSafePluginPath(target) {
|
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
class PluginLoader {
|
|
65
|
-
constructor(toolRegistry) {
|
|
77
|
+
constructor(toolRegistry, config) {
|
|
78
|
+
this.plugins = new Map();
|
|
79
|
+
/** hook name -> handlers in registration order, each tagged with its plugin. */
|
|
80
|
+
this.hookHandlers = new Map();
|
|
66
81
|
this.toolRegistry = toolRegistry;
|
|
82
|
+
this.config = config ?? {};
|
|
67
83
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Load plugins from specified directories.
|
|
70
|
-
*/
|
|
84
|
+
/** Load plugins from specified directories. Returns the number activated. */
|
|
71
85
|
loadFromDirectories(directories) {
|
|
72
86
|
let total = 0;
|
|
73
87
|
for (const dir of directories) {
|
|
@@ -75,9 +89,6 @@ class PluginLoader {
|
|
|
75
89
|
}
|
|
76
90
|
return total;
|
|
77
91
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Load a single plugin directory.
|
|
80
|
-
*/
|
|
81
92
|
loadDirectory(dir) {
|
|
82
93
|
if (!fs.existsSync(dir)) {
|
|
83
94
|
log.debug('plugin_dir_not_found', { dir });
|
|
@@ -85,11 +96,15 @@ class PluginLoader {
|
|
|
85
96
|
}
|
|
86
97
|
let count = 0;
|
|
87
98
|
try {
|
|
88
|
-
const
|
|
89
|
-
for (const entry of entries) {
|
|
99
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
90
100
|
const pluginPath = path.join(dir, entry);
|
|
91
|
-
|
|
101
|
+
try {
|
|
102
|
+
if (!fs.statSync(pluginPath).isDirectory())
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
92
106
|
continue;
|
|
107
|
+
}
|
|
93
108
|
const pluginFile = path.join(pluginPath, 'index.js');
|
|
94
109
|
if (!fs.existsSync(pluginFile))
|
|
95
110
|
continue;
|
|
@@ -101,12 +116,9 @@ class PluginLoader {
|
|
|
101
116
|
continue;
|
|
102
117
|
}
|
|
103
118
|
try {
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
plugin.register(this.toolRegistry);
|
|
119
|
+
const mod = require(pluginFile);
|
|
120
|
+
if (this.activatePlugin(entry, mod))
|
|
107
121
|
count++;
|
|
108
|
-
log.info('plugin_loaded', { name: entry });
|
|
109
|
-
}
|
|
110
122
|
}
|
|
111
123
|
catch (e) {
|
|
112
124
|
log.warn('plugin_load_failed', { name: entry, error: String(e) });
|
|
@@ -118,6 +130,107 @@ class PluginLoader {
|
|
|
118
130
|
}
|
|
119
131
|
return count;
|
|
120
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Activate a plugin module under a name. Reactivating an already-loaded name
|
|
135
|
+
* unloads the previous instance first. Returns true if anything registered.
|
|
136
|
+
*/
|
|
137
|
+
activatePlugin(name, mod) {
|
|
138
|
+
const pluginName = mod.name || name;
|
|
139
|
+
if (this.plugins.has(pluginName))
|
|
140
|
+
this.unload(pluginName);
|
|
141
|
+
const record = { name: pluginName, module: mod, tools: [], hooks: [] };
|
|
142
|
+
const self = this;
|
|
143
|
+
if (typeof mod.activate === 'function') {
|
|
144
|
+
const ctx = {
|
|
145
|
+
name: pluginName,
|
|
146
|
+
config: this.config,
|
|
147
|
+
log: (0, logger_1.getLogger)(`plugin:${pluginName}`),
|
|
148
|
+
registerTool(def) {
|
|
149
|
+
self.toolRegistry.register(def);
|
|
150
|
+
record.tools.push(def.name);
|
|
151
|
+
void self.emit('tool.register', { plugin: pluginName, tool: def.name });
|
|
152
|
+
},
|
|
153
|
+
on(hook, handler) {
|
|
154
|
+
record.hooks.push({ hook, fn: handler });
|
|
155
|
+
const arr = self.hookHandlers.get(hook) || [];
|
|
156
|
+
arr.push({ plugin: pluginName, fn: handler });
|
|
157
|
+
self.hookHandlers.set(hook, arr);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
try {
|
|
161
|
+
void mod.activate(ctx);
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
log.warn('plugin_activate_failed', { name: pluginName, error: String(e) });
|
|
165
|
+
this.unload(pluginName);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (typeof mod.register === 'function') {
|
|
170
|
+
// Legacy: diff the registry to learn which tools the plugin added.
|
|
171
|
+
const before = new Set(this.toolRegistry.listNames());
|
|
172
|
+
try {
|
|
173
|
+
mod.register(this.toolRegistry);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
log.warn('plugin_register_failed', { name: pluginName, error: String(e) });
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
for (const n of this.toolRegistry.listNames()) {
|
|
180
|
+
if (!before.has(n))
|
|
181
|
+
record.tools.push(n);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
log.warn('plugin_no_entrypoint', { name: pluginName });
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
this.plugins.set(pluginName, record);
|
|
189
|
+
log.info('plugin_loaded', { name: pluginName, tools: record.tools.length, hooks: record.hooks.length });
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
/** Fire a hook; handlers run in registration order. Errors are isolated. */
|
|
193
|
+
async emit(hook, payload) {
|
|
194
|
+
const handlers = this.hookHandlers.get(hook);
|
|
195
|
+
if (!handlers || handlers.length === 0)
|
|
196
|
+
return;
|
|
197
|
+
for (const { plugin, fn } of [...handlers]) {
|
|
198
|
+
try {
|
|
199
|
+
await fn(payload);
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
log.warn('plugin_hook_failed', { hook, plugin, error: String(e) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Unload a plugin: remove its tools and hook handlers, call deactivate. */
|
|
207
|
+
unload(name) {
|
|
208
|
+
const record = this.plugins.get(name);
|
|
209
|
+
if (!record)
|
|
210
|
+
return false;
|
|
211
|
+
for (const tool of record.tools)
|
|
212
|
+
this.toolRegistry.unregister(tool);
|
|
213
|
+
for (const [hook, arr] of this.hookHandlers) {
|
|
214
|
+
const filtered = arr.filter((h) => h.plugin !== name);
|
|
215
|
+
if (filtered.length)
|
|
216
|
+
this.hookHandlers.set(hook, filtered);
|
|
217
|
+
else
|
|
218
|
+
this.hookHandlers.delete(hook);
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
record.module.deactivate?.();
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
log.warn('plugin_deactivate_failed', { name, error: String(e) });
|
|
225
|
+
}
|
|
226
|
+
this.plugins.delete(name);
|
|
227
|
+
log.info('plugin_unloaded', { name });
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
/** Names of currently loaded plugins. */
|
|
231
|
+
list() { return [...this.plugins.keys()]; }
|
|
232
|
+
/** Number of handlers registered for a hook (for diagnostics/tests). */
|
|
233
|
+
hookCount(hook) { return this.hookHandlers.get(hook)?.length ?? 0; }
|
|
121
234
|
}
|
|
122
235
|
exports.PluginLoader = PluginLoader;
|
|
123
236
|
//# sourceMappingURL=loader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/tools/builtin.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"builtin.d.ts","sourceRoot":"","sources":["../../src/tools/builtin.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAQlF,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;AAIlE;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAkjBjE;AAID,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAKrD"}
|
package/dist/tools/builtin.js
CHANGED
|
@@ -50,6 +50,10 @@ Object.defineProperty(exports, "assertFetchAllowed", { enumerable: true, get: fu
|
|
|
50
50
|
Object.defineProperty(exports, "fenceRoot", { enumerable: true, get: function () { return guards_1.fenceRoot; } });
|
|
51
51
|
Object.defineProperty(exports, "fenceCheck", { enumerable: true, get: function () { return guards_1.fenceCheck; } });
|
|
52
52
|
const websearch_1 = require("./websearch");
|
|
53
|
+
const diff_1 = require("../core/diff");
|
|
54
|
+
const diagnostics_1 = require("../core/diagnostics");
|
|
55
|
+
const search_1 = require("../core/search");
|
|
56
|
+
const patch_1 = require("../core/patch");
|
|
53
57
|
const log = (0, logger_1.getLogger)('builtin-tools');
|
|
54
58
|
/**
|
|
55
59
|
* Register all built-in tools into the given registry.
|
|
@@ -116,11 +120,12 @@ function registerBuiltinTools(registry) {
|
|
|
116
120
|
});
|
|
117
121
|
registry.register({
|
|
118
122
|
name: 'edit_file',
|
|
119
|
-
description: 'Edit a file by replacing old_text with new_text.
|
|
123
|
+
description: 'Edit a file by replacing an exact occurrence of old_text with new_text. old_text must match the file exactly (including whitespace and indentation) and must be UNIQUE — include enough surrounding context to disambiguate, or set replace_all to change every occurrence. Returns a unified diff of the change.',
|
|
120
124
|
parameters: [
|
|
121
125
|
{ name: 'path', type: 'string', description: 'Path to the file to edit', required: true },
|
|
122
|
-
{ name: 'old_text', type: 'string', description: '
|
|
123
|
-
{ name: 'new_text', type: 'string', description: '
|
|
126
|
+
{ name: 'old_text', type: 'string', description: 'Exact text to replace (must match the file verbatim, and be unique unless replace_all is set)', required: true },
|
|
127
|
+
{ name: 'new_text', type: 'string', description: 'Replacement text (must differ from old_text)', required: true },
|
|
128
|
+
{ name: 'replace_all', type: 'boolean', description: 'Replace every occurrence instead of requiring a unique match (default false)', required: false },
|
|
124
129
|
],
|
|
125
130
|
handler: async (params) => {
|
|
126
131
|
const filePath = path.resolve(params.path);
|
|
@@ -129,22 +134,94 @@ function registerBuiltinTools(registry) {
|
|
|
129
134
|
return fenced;
|
|
130
135
|
if (!fs.existsSync(filePath))
|
|
131
136
|
return `Error: File not found: ${filePath}`;
|
|
137
|
+
const oldText = params.old_text;
|
|
138
|
+
const newText = params.new_text;
|
|
139
|
+
const replaceAll = params.replace_all === true || params.replace_all === 'true';
|
|
140
|
+
if (oldText === newText)
|
|
141
|
+
return 'Error: old_text and new_text are identical — nothing to change.';
|
|
132
142
|
try {
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return `Error: old_text not found in file. Searched for: ${oldText.slice(0, 50)}...`;
|
|
143
|
+
const orig = fs.readFileSync(filePath, 'utf-8');
|
|
144
|
+
const n = (0, diff_1.countOccurrences)(orig, oldText);
|
|
145
|
+
if (n === 0) {
|
|
146
|
+
return `Error: old_text not found in ${filePath}. It must match exactly (including whitespace). Searched for: ${JSON.stringify(oldText.slice(0, 80))}`;
|
|
138
147
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
if (n > 1 && !replaceAll) {
|
|
149
|
+
return `Error: old_text appears ${n} times in ${filePath} — the edit is ambiguous. Add more surrounding context to make it unique, or set replace_all=true to change all ${n} occurrences.`;
|
|
150
|
+
}
|
|
151
|
+
// Literal replacement: split/join (replace_all) and a function replacer
|
|
152
|
+
// (single) both avoid String.replace interpreting `$&`/`$1` in new_text.
|
|
153
|
+
const updated = replaceAll
|
|
154
|
+
? orig.split(oldText).join(newText)
|
|
155
|
+
: orig.replace(oldText, () => newText);
|
|
156
|
+
fs.writeFileSync(filePath, updated, 'utf-8');
|
|
157
|
+
const rel = path.relative(process.cwd(), filePath) || filePath;
|
|
158
|
+
const diff = (0, diff_1.unifiedDiff)(orig, updated, { path: rel, context: 3 });
|
|
159
|
+
const occ = replaceAll ? ` (${n} occurrence${n > 1 ? 's' : ''})` : '';
|
|
160
|
+
const body = diff.text ? `\n${diff.text}` : '';
|
|
161
|
+
return `Successfully edited ${filePath}${occ} · +${diff.stat.added} -${diff.stat.removed}${body}`;
|
|
142
162
|
}
|
|
143
163
|
catch (e) {
|
|
144
164
|
return `Error editing file: ${e}`;
|
|
145
165
|
}
|
|
146
166
|
},
|
|
147
167
|
});
|
|
168
|
+
registry.register({
|
|
169
|
+
name: 'get_diagnostics',
|
|
170
|
+
idempotent: true,
|
|
171
|
+
description: 'Get LSP-style diagnostics (type errors, lint issues with line:col) for a source file. TS/JS work out of the box via the workspace TypeScript; other languages use a configured checker (config.yaml diagnostics map). Call this after editing code to confirm it is error-free, or to locate the root cause of a type error.',
|
|
172
|
+
parameters: [
|
|
173
|
+
{ name: 'path', type: 'string', description: 'Path to the source file to check', required: true },
|
|
174
|
+
],
|
|
175
|
+
handler: async (params) => {
|
|
176
|
+
const filePath = path.resolve(params.path);
|
|
177
|
+
const fenced = (0, guards_1.fenceCheck)(filePath);
|
|
178
|
+
if (fenced)
|
|
179
|
+
return fenced;
|
|
180
|
+
let config = {};
|
|
181
|
+
try {
|
|
182
|
+
const { loadConfig } = require('../core/config');
|
|
183
|
+
config = loadConfig();
|
|
184
|
+
}
|
|
185
|
+
catch { /* defaults */ }
|
|
186
|
+
try {
|
|
187
|
+
const result = (0, diagnostics_1.getDiagnostics)(filePath, config);
|
|
188
|
+
if (!Array.isArray(result))
|
|
189
|
+
return `[diagnostics unavailable] ${result.unavailable}`;
|
|
190
|
+
return (0, diagnostics_1.formatDiagnostics)(path.relative(process.cwd(), filePath) || filePath, result);
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
return `Error getting diagnostics: ${e}`;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
registry.register({
|
|
198
|
+
name: 'apply_patch',
|
|
199
|
+
description: 'Apply an atomic, multi-file edit in one call — ideal for larger refactors touching several places/files. The whole patch is validated before anything is written, so a bad block aborts cleanly with no half-applied changes. Each SEARCH must match the file exactly and uniquely. Format:\n*** Update File: path\n<<<<<<< SEARCH\nold exact text\n=======\nnew text\n>>>>>>> REPLACE\n(repeat blocks; also *** Add File: path / full content, and *** Delete File: path)',
|
|
200
|
+
parameters: [
|
|
201
|
+
{ name: 'patch', type: 'string', description: 'The patch text in the *** Update/Add/Delete File + SEARCH/REPLACE format', required: true },
|
|
202
|
+
],
|
|
203
|
+
handler: async (params) => {
|
|
204
|
+
let snapshot;
|
|
205
|
+
try {
|
|
206
|
+
const { getFileCheckpoints } = require('../core/file_checkpoint');
|
|
207
|
+
const cp = getFileCheckpoints();
|
|
208
|
+
snapshot = (abs) => { try {
|
|
209
|
+
cp.snapshot(abs);
|
|
210
|
+
}
|
|
211
|
+
catch { /* best-effort */ } };
|
|
212
|
+
}
|
|
213
|
+
catch { /* checkpointing optional */ }
|
|
214
|
+
try {
|
|
215
|
+
return (0, patch_1.applyPatch)(String(params.patch || ''), {
|
|
216
|
+
fenceCheck: (abs) => (0, guards_1.fenceCheck)(abs),
|
|
217
|
+
snapshot,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
return `Error applying patch: ${e}`;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
148
225
|
registry.register({
|
|
149
226
|
name: 'delete_file',
|
|
150
227
|
description: 'Delete a file at the given path.',
|
|
@@ -222,13 +299,27 @@ function registerBuiltinTools(registry) {
|
|
|
222
299
|
// ── Shell Tool ──
|
|
223
300
|
registry.register({
|
|
224
301
|
name: 'run_bash',
|
|
225
|
-
description: 'Execute a shell command and return its output.',
|
|
302
|
+
description: 'Execute a shell command and return its output. Set background=true for long-running processes (dev servers, watchers, builds): it returns a job id immediately — read its output later with bash_output, stop it with kill_bash.',
|
|
226
303
|
parameters: [
|
|
227
304
|
{ name: 'command', type: 'string', description: 'Command to execute', required: true },
|
|
228
|
-
{ name: 'timeout', type: 'number', description: 'Timeout in milliseconds (default: 30000)', required: false },
|
|
305
|
+
{ name: 'timeout', type: 'number', description: 'Timeout in milliseconds (default: 30000). Ignored when background=true.', required: false },
|
|
306
|
+
{ name: 'background', type: 'boolean', description: 'Run detached in the background and return a job id instead of blocking (default false)', required: false },
|
|
229
307
|
],
|
|
230
308
|
handler: async (params) => {
|
|
231
309
|
const cmd = params.command;
|
|
310
|
+
const background = params.background === true || params.background === 'true';
|
|
311
|
+
if (background) {
|
|
312
|
+
try {
|
|
313
|
+
const { getBackgroundManager } = require('../core/bgproc');
|
|
314
|
+
const { id, error } = getBackgroundManager().start(cmd);
|
|
315
|
+
if (error)
|
|
316
|
+
return error;
|
|
317
|
+
return `[background job ${id} started] pid running. Use bash_output("${id}") to read output, kill_bash("${id}") to stop, list_bash to see all jobs.`;
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return `Error: ${e.message || e}`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
232
323
|
const timeout = params.timeout || 30000;
|
|
233
324
|
try {
|
|
234
325
|
const { runInSandbox, formatSandboxResult } = require('../core/sandbox');
|
|
@@ -241,6 +332,50 @@ function registerBuiltinTools(registry) {
|
|
|
241
332
|
},
|
|
242
333
|
dangerous: true,
|
|
243
334
|
});
|
|
335
|
+
registry.register({
|
|
336
|
+
name: 'bash_output',
|
|
337
|
+
description: 'Read new output from a background shell job (started by run_bash with background=true) since the last read, plus its current status.',
|
|
338
|
+
parameters: [
|
|
339
|
+
{ name: 'id', type: 'string', description: 'The background job id', required: true },
|
|
340
|
+
],
|
|
341
|
+
handler: async (params) => {
|
|
342
|
+
const { getBackgroundManager } = require('../core/bgproc');
|
|
343
|
+
const r = getBackgroundManager().read(String(params.id || ''));
|
|
344
|
+
if (!r.ok)
|
|
345
|
+
return r.error || 'Error reading background job.';
|
|
346
|
+
const statusLine = `[job ${params.id} · ${r.status}${r.exitCode != null ? ` · exit ${r.exitCode}` : ''}]`;
|
|
347
|
+
const out = r.text && r.text.length ? r.text : '(no new output)';
|
|
348
|
+
return `${statusLine}\n${out}`;
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
registry.register({
|
|
352
|
+
name: 'list_bash',
|
|
353
|
+
description: 'List background shell jobs with their status, pid, and runtime.',
|
|
354
|
+
parameters: [],
|
|
355
|
+
handler: async () => {
|
|
356
|
+
const { getBackgroundManager } = require('../core/bgproc');
|
|
357
|
+
const jobs = getBackgroundManager().list();
|
|
358
|
+
if (!jobs.length)
|
|
359
|
+
return 'No background jobs.';
|
|
360
|
+
return jobs.map((j) => {
|
|
361
|
+
const dur = ((j.endedAt || Date.now()) - j.startedAt) / 1000;
|
|
362
|
+
const ex = j.exitCode != null ? ` exit ${j.exitCode}` : '';
|
|
363
|
+
return `${j.id} · ${j.status}${ex} · pid ${j.pid ?? '?'} · ${dur.toFixed(1)}s · ${j.command.slice(0, 60)}`;
|
|
364
|
+
}).join('\n');
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
registry.register({
|
|
368
|
+
name: 'kill_bash',
|
|
369
|
+
description: 'Terminate a running background shell job.',
|
|
370
|
+
parameters: [
|
|
371
|
+
{ name: 'id', type: 'string', description: 'The background job id to kill', required: true },
|
|
372
|
+
],
|
|
373
|
+
handler: async (params) => {
|
|
374
|
+
const { getBackgroundManager } = require('../core/bgproc');
|
|
375
|
+
const r = getBackgroundManager().kill(String(params.id || ''));
|
|
376
|
+
return r.ok ? `[job ${params.id} killed]` : (r.error || 'Error killing background job.');
|
|
377
|
+
},
|
|
378
|
+
});
|
|
244
379
|
// ── HTTP Tools ──
|
|
245
380
|
registry.register({
|
|
246
381
|
name: 'http_get',
|
|
@@ -419,10 +554,40 @@ function registerBuiltinTools(registry) {
|
|
|
419
554
|
dangerous: true,
|
|
420
555
|
});
|
|
421
556
|
// ── Utility Tools ──
|
|
557
|
+
registry.register({
|
|
558
|
+
name: 'code_search',
|
|
559
|
+
idempotent: true,
|
|
560
|
+
description: 'Search source code for a regex pattern across files. Returns file:line matches with optional surrounding context. Use this to find where a symbol/string is defined or used. Restrict scope with glob (e.g. "**/*.ts") and add context lines to read around hits.',
|
|
561
|
+
parameters: [
|
|
562
|
+
{ name: 'pattern', type: 'string', description: 'Regex (or literal if regex=false) to search for', required: true },
|
|
563
|
+
{ name: 'path', type: 'string', description: 'Root directory to search (default: cwd)', required: false },
|
|
564
|
+
{ name: 'glob', type: 'string', description: 'Restrict to files matching this glob, e.g. "**/*.ts"', required: false },
|
|
565
|
+
{ name: 'context', type: 'number', description: 'Lines of context around each match (default 0)', required: false },
|
|
566
|
+
{ name: 'ignore_case', type: 'boolean', description: 'Case-insensitive match (default false)', required: false },
|
|
567
|
+
{ name: 'regex', type: 'boolean', description: 'Treat pattern as regex (default true; false = literal substring)', required: false },
|
|
568
|
+
{ name: 'max_results', type: 'number', description: 'Max matches to return (default 200)', required: false },
|
|
569
|
+
],
|
|
570
|
+
handler: async (params) => {
|
|
571
|
+
const root = params.path ? path.resolve(params.path) : process.cwd();
|
|
572
|
+
const fenced = (0, guards_1.fenceCheck)(root);
|
|
573
|
+
if (fenced)
|
|
574
|
+
return fenced;
|
|
575
|
+
const res = (0, search_1.searchCode)({
|
|
576
|
+
pattern: String(params.pattern || ''),
|
|
577
|
+
root,
|
|
578
|
+
glob: params.glob ? String(params.glob) : undefined,
|
|
579
|
+
context: params.context != null ? Number(params.context) : 0,
|
|
580
|
+
ignoreCase: params.ignore_case === true,
|
|
581
|
+
regex: params.regex !== false,
|
|
582
|
+
maxResults: params.max_results != null ? Number(params.max_results) : 200,
|
|
583
|
+
});
|
|
584
|
+
return (0, search_1.formatSearchResult)(res);
|
|
585
|
+
},
|
|
586
|
+
});
|
|
422
587
|
registry.register({
|
|
423
588
|
name: 'grep',
|
|
424
589
|
idempotent: true,
|
|
425
|
-
description: 'Search for a pattern in files using ripgrep
|
|
590
|
+
description: 'Search for a regex pattern in files using ripgrep/grep, with a built-in fallback when neither is installed. For richer control (glob, context, ignore-case) prefer code_search.',
|
|
426
591
|
parameters: [
|
|
427
592
|
{ name: 'pattern', type: 'string', description: 'Regex pattern to search for', required: true },
|
|
428
593
|
{ name: 'path', type: 'string', description: 'Directory to search in', required: false },
|
|
@@ -448,13 +613,14 @@ function registerBuiltinTools(registry) {
|
|
|
448
613
|
return out || 'No matches found.';
|
|
449
614
|
}
|
|
450
615
|
catch (e) {
|
|
451
|
-
// exit status 1 = ran successfully, zero matches
|
|
452
|
-
// (e.g. binary not installed) falls through to the next variant
|
|
616
|
+
// exit status 1 = ran successfully, zero matches. Any other failure
|
|
617
|
+
// (e.g. binary not installed) falls through to the next variant, then
|
|
618
|
+
// to the pure-JS engine so search works even with no rg/grep.
|
|
453
619
|
if (e?.status === 1)
|
|
454
620
|
return 'No matches found.';
|
|
455
621
|
}
|
|
456
622
|
}
|
|
457
|
-
return
|
|
623
|
+
return (0, search_1.formatSearchResult)((0, search_1.searchCode)({ pattern: pat, root: searchDir, maxResults: 200 }));
|
|
458
624
|
},
|
|
459
625
|
});
|
|
460
626
|
registry.register({
|