@agentprojectcontext/apx 1.10.4 → 1.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/package.json +5 -1
- package/src/cli/commands/search.js +62 -0
- package/src/cli/commands/sys.js +6 -1
- package/src/cli/index.js +21 -0
- package/src/core/agent-system.js +15 -0
- package/src/daemon/api.js +229 -0
- package/src/daemon/engines/anthropic.js +19 -1
- package/src/daemon/engines/index.js +2 -1
- package/src/daemon/engines/openai.js +22 -2
- package/src/daemon/plugins/telegram.js +248 -2
- package/src/daemon/super-agent-tools/index.js +19 -1
- package/src/daemon/super-agent-tools/registry-bridge.js +122 -0
- package/src/daemon/super-agent.js +43 -1
- package/src/daemon/tools/browser.js +424 -0
- package/src/daemon/tools/fetch.js +138 -0
- package/src/daemon/tools/glob.js +165 -0
- package/src/daemon/tools/grep.js +218 -0
- package/src/daemon/tools/registry.js +729 -0
- package/src/daemon/tools/search.js +290 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
// daemon/tools/registry.js
|
|
2
|
+
// Tool Registry on-demand for APX.
|
|
3
|
+
//
|
|
4
|
+
// Endpoints registered by api.js:
|
|
5
|
+
// GET /tools → lightweight list [{name, description, category, schema_url}]
|
|
6
|
+
// GET /tools/:name → full schema + examples
|
|
7
|
+
// POST /tools/:name/call → execute the tool (proxy to internal handler)
|
|
8
|
+
//
|
|
9
|
+
// Tools that already exist as HTTP endpoints are listed here with their
|
|
10
|
+
// endpoint targets — no code duplication.
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Tool definitions
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
const TOOL_DEFINITIONS = [
|
|
17
|
+
// ── file ──────────────────────────────────────────────────────────────────
|
|
18
|
+
{
|
|
19
|
+
name: "read_file",
|
|
20
|
+
category: "file",
|
|
21
|
+
description: "Read the contents of a file inside the project.",
|
|
22
|
+
endpoint: { method: "GET", path: "/files", query: ["path", "project"] },
|
|
23
|
+
parameters: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
path: { type: "string", description: "Relative path inside the project" },
|
|
27
|
+
project: { type: "string", description: "Project ID or path (optional)" },
|
|
28
|
+
},
|
|
29
|
+
required: ["path"],
|
|
30
|
+
},
|
|
31
|
+
examples: [{ path: "src/index.js" }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "write_file",
|
|
35
|
+
category: "file",
|
|
36
|
+
description: "Write or overwrite a file inside the project.",
|
|
37
|
+
endpoint: { method: "POST", path: "/files" },
|
|
38
|
+
parameters: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
path: { type: "string" },
|
|
42
|
+
content: { type: "string" },
|
|
43
|
+
project: { type: "string" },
|
|
44
|
+
},
|
|
45
|
+
required: ["path", "content"],
|
|
46
|
+
},
|
|
47
|
+
examples: [{ path: "notes.md", content: "# Hello" }],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "list_files",
|
|
51
|
+
category: "file",
|
|
52
|
+
description: "List files and directories inside a project path.",
|
|
53
|
+
endpoint: { method: "GET", path: "/files" },
|
|
54
|
+
parameters: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
path: { type: "string", description: "Sub-path to list (optional)" },
|
|
58
|
+
project: { type: "string" },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
examples: [{ path: "src" }],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "search_files",
|
|
65
|
+
category: "file",
|
|
66
|
+
description: "Search for files by name glob or content pattern in the project.",
|
|
67
|
+
endpoint: { method: "GET", path: "/files/search" },
|
|
68
|
+
parameters: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
q: { type: "string", description: "Search query (filename or content)" },
|
|
72
|
+
project: { type: "string" },
|
|
73
|
+
},
|
|
74
|
+
required: ["q"],
|
|
75
|
+
},
|
|
76
|
+
examples: [{ q: "*.config.js" }],
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
// ── shell ─────────────────────────────────────────────────────────────────
|
|
80
|
+
{
|
|
81
|
+
name: "run_command",
|
|
82
|
+
category: "shell",
|
|
83
|
+
description: "Execute a shell command in the project directory. Returns stdout, stderr, exit_code.",
|
|
84
|
+
endpoint: { method: "POST", path: "/run" },
|
|
85
|
+
parameters: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
cmd: { type: "string", description: "Shell command to run" },
|
|
89
|
+
cwd: { type: "string", description: "Working directory override" },
|
|
90
|
+
project: { type: "string" },
|
|
91
|
+
timeout_ms: { type: "integer", default: 30000 },
|
|
92
|
+
},
|
|
93
|
+
required: ["cmd"],
|
|
94
|
+
},
|
|
95
|
+
examples: [{ cmd: "ls -la" }, { cmd: "git log --oneline -5" }],
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// ── memory ────────────────────────────────────────────────────────────────
|
|
99
|
+
{
|
|
100
|
+
name: "memory_get",
|
|
101
|
+
category: "memory",
|
|
102
|
+
description: "Read the memory.md of the default agent in a project.",
|
|
103
|
+
endpoint: { method: "GET", path: "/memory" },
|
|
104
|
+
parameters: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
project: { type: "string" },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
examples: [{}],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "memory_set",
|
|
114
|
+
category: "memory",
|
|
115
|
+
description: "Overwrite the memory.md of the default agent in a project.",
|
|
116
|
+
endpoint: { method: "POST", path: "/memory" },
|
|
117
|
+
parameters: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
body: { type: "string", description: "Full content to write" },
|
|
121
|
+
project: { type: "string" },
|
|
122
|
+
},
|
|
123
|
+
required: ["body"],
|
|
124
|
+
},
|
|
125
|
+
examples: [{ body: "# Agent Memory\n\n- Remember to greet the user." }],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "memory_append",
|
|
129
|
+
category: "memory",
|
|
130
|
+
description: "Append text to the agent memory.md (read-modify-write).",
|
|
131
|
+
endpoint: null, // implemented inline in the call handler
|
|
132
|
+
parameters: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
text: { type: "string" },
|
|
136
|
+
project: { type: "string" },
|
|
137
|
+
},
|
|
138
|
+
required: ["text"],
|
|
139
|
+
},
|
|
140
|
+
examples: [{ text: "\n- New fact to remember." }],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "memory_list",
|
|
144
|
+
category: "memory",
|
|
145
|
+
description: "List all agents that have memory files in a project.",
|
|
146
|
+
endpoint: null,
|
|
147
|
+
parameters: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: { project: { type: "string" } },
|
|
150
|
+
},
|
|
151
|
+
examples: [{}],
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// ── session ───────────────────────────────────────────────────────────────
|
|
155
|
+
{
|
|
156
|
+
name: "session_list",
|
|
157
|
+
category: "session",
|
|
158
|
+
description: "List sessions for an agent in a project.",
|
|
159
|
+
endpoint: { method: "GET", path: "/projects/:pid/agents/:slug/sessions" },
|
|
160
|
+
parameters: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
project: { type: "string", description: "Project ID" },
|
|
164
|
+
agent: { type: "string", description: "Agent slug" },
|
|
165
|
+
},
|
|
166
|
+
required: ["project", "agent"],
|
|
167
|
+
},
|
|
168
|
+
examples: [{ project: "1", agent: "sofia" }],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "session_get",
|
|
172
|
+
category: "session",
|
|
173
|
+
description: "Get a session by filename.",
|
|
174
|
+
endpoint: { method: "GET", path: "/projects/:pid/sessions/:sid" },
|
|
175
|
+
parameters: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
project: { type: "string" },
|
|
179
|
+
session_id: { type: "string" },
|
|
180
|
+
},
|
|
181
|
+
required: ["project", "session_id"],
|
|
182
|
+
},
|
|
183
|
+
examples: [{ project: "1", session_id: "2026-05-01-planning.md" }],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "session_search",
|
|
187
|
+
category: "session",
|
|
188
|
+
description: "Search session content by text query across all agents in a project.",
|
|
189
|
+
endpoint: { method: "GET", path: "/sessions/search" },
|
|
190
|
+
parameters: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
q: { type: "string", description: "Search query" },
|
|
194
|
+
project: { type: "string" },
|
|
195
|
+
limit: { type: "integer", default: 20 },
|
|
196
|
+
},
|
|
197
|
+
required: ["q"],
|
|
198
|
+
},
|
|
199
|
+
examples: [{ q: "authentication bug" }],
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: "session_compact",
|
|
203
|
+
category: "session",
|
|
204
|
+
description: "Compact (summarise and compress) a session conversation.",
|
|
205
|
+
endpoint: { method: "POST", path: "/sessions/:id/compact" },
|
|
206
|
+
parameters: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
session_id: { type: "string" },
|
|
210
|
+
project: { type: "string" },
|
|
211
|
+
agent: { type: "string" },
|
|
212
|
+
model: { type: "string" },
|
|
213
|
+
},
|
|
214
|
+
required: ["project", "agent", "session_id"],
|
|
215
|
+
},
|
|
216
|
+
examples: [{ project: "1", agent: "sofia", session_id: "2026-05-01-planning.md" }],
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// ── mcp ───────────────────────────────────────────────────────────────────
|
|
220
|
+
{
|
|
221
|
+
name: "mcp_list",
|
|
222
|
+
category: "mcp",
|
|
223
|
+
description: "List all MCP servers registered in a project.",
|
|
224
|
+
endpoint: { method: "GET", path: "/mcp" },
|
|
225
|
+
parameters: {
|
|
226
|
+
type: "object",
|
|
227
|
+
properties: { project: { type: "string" } },
|
|
228
|
+
},
|
|
229
|
+
examples: [{}],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "mcp_run",
|
|
233
|
+
category: "mcp",
|
|
234
|
+
description: "Call a tool on an MCP server.",
|
|
235
|
+
endpoint: { method: "POST", path: "/mcp/run" },
|
|
236
|
+
parameters: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
name: { type: "string", description: "MCP server name" },
|
|
240
|
+
tool: { type: "string", description: "Tool name on that server" },
|
|
241
|
+
params: { type: "object" },
|
|
242
|
+
project: { type: "string" },
|
|
243
|
+
},
|
|
244
|
+
required: ["name", "tool"],
|
|
245
|
+
},
|
|
246
|
+
examples: [{ name: "filesystem", tool: "list_directory", params: { path: "/tmp" } }],
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// ── glob / grep ───────────────────────────────────────────────────────────
|
|
250
|
+
{
|
|
251
|
+
name: "glob",
|
|
252
|
+
category: "file",
|
|
253
|
+
description: "List files matching a glob pattern (e.g. **/*.js). Uses native Node.js glob.",
|
|
254
|
+
endpoint: { method: "POST", path: "/tools/glob" },
|
|
255
|
+
parameters: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
pattern: { type: "string", description: "Glob pattern, e.g. src/**/*.ts" },
|
|
259
|
+
cwd: { type: "string", description: "Base directory (absolute path)" },
|
|
260
|
+
dot: { type: "boolean", default: false, description: "Include dotfiles" },
|
|
261
|
+
absolute: { type: "boolean", default: false },
|
|
262
|
+
limit: { type: "integer", default: 500 },
|
|
263
|
+
},
|
|
264
|
+
required: ["pattern"],
|
|
265
|
+
},
|
|
266
|
+
examples: [
|
|
267
|
+
{ pattern: "**/*.js", cwd: "/my/project" },
|
|
268
|
+
{ pattern: "src/**/*.ts", cwd: "/my/project", limit: 100 },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "grep",
|
|
273
|
+
category: "file",
|
|
274
|
+
description: "Search file contents by regex pattern. Uses ripgrep when available, pure Node.js fallback.",
|
|
275
|
+
endpoint: { method: "POST", path: "/tools/grep" },
|
|
276
|
+
parameters: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
pattern: { type: "string", description: "Regex to search for" },
|
|
280
|
+
path: { type: "string", description: "Directory or file to search in" },
|
|
281
|
+
glob: { type: "string", description: "Glob filter for files, e.g. *.ts" },
|
|
282
|
+
case_sensitive: { type: "boolean", default: false },
|
|
283
|
+
context: { type: "integer", default: 0, description: "Lines of context around matches" },
|
|
284
|
+
limit: { type: "integer", default: 100 },
|
|
285
|
+
},
|
|
286
|
+
required: ["pattern"],
|
|
287
|
+
},
|
|
288
|
+
examples: [
|
|
289
|
+
{ pattern: "export default", path: "/my/project/src", glob: "*.js" },
|
|
290
|
+
{ pattern: "TODO|FIXME", path: "/my/project", context: 2 },
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// ── fetch (native HTTP, no browser) ───────────────────────────────────────
|
|
295
|
+
{
|
|
296
|
+
name: "http_get",
|
|
297
|
+
category: "fetch",
|
|
298
|
+
description: "Native HTTP GET — fast, no headless browser. Use for REST APIs, raw HTML, JSON endpoints.",
|
|
299
|
+
endpoint: { method: "POST", path: "/tools/fetch/get" },
|
|
300
|
+
parameters: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
url: { type: "string" },
|
|
304
|
+
headers: { type: "object" },
|
|
305
|
+
timeout_ms: { type: "number", default: 30000 },
|
|
306
|
+
},
|
|
307
|
+
required: ["url"],
|
|
308
|
+
},
|
|
309
|
+
examples: [{ url: "https://api.github.com/repos/anthropics/anthropic-sdk-typescript" }],
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "http_post",
|
|
313
|
+
category: "fetch",
|
|
314
|
+
description: "Native HTTP POST — sends body as JSON when body is an object. Use for REST APIs.",
|
|
315
|
+
endpoint: { method: "POST", path: "/tools/fetch/post" },
|
|
316
|
+
parameters: {
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
url: { type: "string" },
|
|
320
|
+
body: { description: "Object → JSON-stringified. String → sent as-is." },
|
|
321
|
+
headers: { type: "object" },
|
|
322
|
+
timeout_ms: { type: "number", default: 30000 },
|
|
323
|
+
json: { type: "boolean", description: "Force JSON parsing of response body." },
|
|
324
|
+
},
|
|
325
|
+
required: ["url"],
|
|
326
|
+
},
|
|
327
|
+
examples: [{ url: "https://api.example.com/items", body: { name: "foo" } }],
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "http_request",
|
|
331
|
+
category: "fetch",
|
|
332
|
+
description: "Generic HTTP request with full control over method, headers, body, timeout.",
|
|
333
|
+
endpoint: { method: "POST", path: "/tools/fetch/request" },
|
|
334
|
+
parameters: {
|
|
335
|
+
type: "object",
|
|
336
|
+
properties: {
|
|
337
|
+
url: { type: "string" },
|
|
338
|
+
method: { type: "string", default: "GET" },
|
|
339
|
+
headers: { type: "object" },
|
|
340
|
+
body: {},
|
|
341
|
+
timeout_ms: { type: "number", default: 30000 },
|
|
342
|
+
json: { type: "boolean" },
|
|
343
|
+
},
|
|
344
|
+
required: ["url"],
|
|
345
|
+
},
|
|
346
|
+
examples: [{ url: "https://api.example.com/x", method: "DELETE" }],
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// ── browser (Puppeteer-backed — heavier, launches Chromium lazily) ────────
|
|
350
|
+
{
|
|
351
|
+
name: "browser_navigate",
|
|
352
|
+
category: "browser",
|
|
353
|
+
description: "Navigate the headless browser to a URL. Launches Chromium lazily on first call.",
|
|
354
|
+
endpoint: { method: "POST", path: "/tools/browser/navigate" },
|
|
355
|
+
parameters: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
url: { type: "string" },
|
|
359
|
+
launch_options: { type: "object", description: "Puppeteer launch overrides (headless, args, defaultViewport, etc.)." },
|
|
360
|
+
allow_dangerous: { type: "boolean", description: "Allow dangerous launch args (--no-sandbox, --single-process, etc.)." },
|
|
361
|
+
},
|
|
362
|
+
required: ["url"],
|
|
363
|
+
},
|
|
364
|
+
examples: [{ url: "https://example.com" }],
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "browser_screenshot",
|
|
368
|
+
category: "browser",
|
|
369
|
+
description: "Take a screenshot of the current browser page (or a single element via selector). Returns base64 PNG.",
|
|
370
|
+
endpoint: { method: "POST", path: "/tools/browser/screenshot" },
|
|
371
|
+
parameters: {
|
|
372
|
+
type: "object",
|
|
373
|
+
properties: {
|
|
374
|
+
selector: { type: "string", description: "CSS selector of element to capture. Omit to capture full viewport/page." },
|
|
375
|
+
full_page: { type: "boolean", default: false },
|
|
376
|
+
width: { type: "number", description: "Viewport width (capped at 1920)." },
|
|
377
|
+
height: { type: "number", description: "Viewport height (capped at 1080)." },
|
|
378
|
+
encoded: { type: "boolean", description: "Also return a data: URI." },
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
examples: [{}, { selector: "#hero" }],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: "browser_click",
|
|
385
|
+
category: "browser",
|
|
386
|
+
description: "Click a CSS selector on the current browser page.",
|
|
387
|
+
endpoint: { method: "POST", path: "/tools/browser/click" },
|
|
388
|
+
parameters: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: { selector: { type: "string" } },
|
|
391
|
+
required: ["selector"],
|
|
392
|
+
},
|
|
393
|
+
examples: [{ selector: "button#submit" }],
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "browser_type",
|
|
397
|
+
category: "browser",
|
|
398
|
+
description: "Type text into a CSS selector. Uses focus + Ctrl+A + Backspace to clear, then types with realistic delay.",
|
|
399
|
+
endpoint: { method: "POST", path: "/tools/browser/type" },
|
|
400
|
+
parameters: {
|
|
401
|
+
type: "object",
|
|
402
|
+
properties: {
|
|
403
|
+
selector: { type: "string" },
|
|
404
|
+
text: { type: "string" },
|
|
405
|
+
clear: { type: "boolean", default: true },
|
|
406
|
+
},
|
|
407
|
+
required: ["selector", "text"],
|
|
408
|
+
},
|
|
409
|
+
examples: [{ selector: "input#search", text: "hello world" }],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: "browser_select",
|
|
413
|
+
category: "browser",
|
|
414
|
+
description: "Choose an option in a <select> element by its value.",
|
|
415
|
+
endpoint: { method: "POST", path: "/tools/browser/select" },
|
|
416
|
+
parameters: {
|
|
417
|
+
type: "object",
|
|
418
|
+
properties: {
|
|
419
|
+
selector: { type: "string" },
|
|
420
|
+
value: { type: "string" },
|
|
421
|
+
},
|
|
422
|
+
required: ["selector", "value"],
|
|
423
|
+
},
|
|
424
|
+
examples: [{ selector: "select#country", value: "AR" }],
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "browser_hover",
|
|
428
|
+
category: "browser",
|
|
429
|
+
description: "Hover the cursor over an element (triggers tooltips, dropdowns, hover states).",
|
|
430
|
+
endpoint: { method: "POST", path: "/tools/browser/hover" },
|
|
431
|
+
parameters: {
|
|
432
|
+
type: "object",
|
|
433
|
+
properties: { selector: { type: "string" } },
|
|
434
|
+
required: ["selector"],
|
|
435
|
+
},
|
|
436
|
+
examples: [{ selector: "nav .menu-item" }],
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "browser_evaluate",
|
|
440
|
+
category: "browser",
|
|
441
|
+
description: "Execute JavaScript in the page context. Captures the script's console.log/info/warn/error output and returns it alongside the result.",
|
|
442
|
+
endpoint: { method: "POST", path: "/tools/browser/evaluate" },
|
|
443
|
+
parameters: {
|
|
444
|
+
type: "object",
|
|
445
|
+
properties: { code: { type: "string", description: "JS code to eval (function body)." } },
|
|
446
|
+
required: ["code"],
|
|
447
|
+
},
|
|
448
|
+
examples: [{ code: "return document.title;" }],
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "browser_get_text",
|
|
452
|
+
category: "browser",
|
|
453
|
+
description: "Extract readable text from the current page (or a single element). Strips script/style/nav/header/footer.",
|
|
454
|
+
endpoint: { method: "POST", path: "/tools/browser/get_text" },
|
|
455
|
+
parameters: {
|
|
456
|
+
type: "object",
|
|
457
|
+
properties: { selector: { type: "string", description: "Optional CSS selector." } },
|
|
458
|
+
},
|
|
459
|
+
examples: [{}, { selector: "article" }],
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: "browser_get_content",
|
|
463
|
+
category: "browser",
|
|
464
|
+
description: "Return raw innerHTML of the page or a single element (truncated at 1MB).",
|
|
465
|
+
endpoint: { method: "POST", path: "/tools/browser/get_content" },
|
|
466
|
+
parameters: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: { selector: { type: "string" } },
|
|
469
|
+
},
|
|
470
|
+
examples: [{}, { selector: "main" }],
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "browser_wait_for_selector",
|
|
474
|
+
category: "browser",
|
|
475
|
+
description: "Wait until a CSS selector appears on the page.",
|
|
476
|
+
endpoint: { method: "POST", path: "/tools/browser/wait_for_selector" },
|
|
477
|
+
parameters: {
|
|
478
|
+
type: "object",
|
|
479
|
+
properties: {
|
|
480
|
+
selector: { type: "string" },
|
|
481
|
+
timeout: { type: "number", default: 30000 },
|
|
482
|
+
},
|
|
483
|
+
required: ["selector"],
|
|
484
|
+
},
|
|
485
|
+
examples: [{ selector: ".results-loaded" }],
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "browser_close",
|
|
489
|
+
category: "browser",
|
|
490
|
+
description: "Close the headless browser and free resources.",
|
|
491
|
+
endpoint: { method: "POST", path: "/tools/browser/close" },
|
|
492
|
+
parameters: { type: "object", properties: {} },
|
|
493
|
+
examples: [{}],
|
|
494
|
+
},
|
|
495
|
+
|
|
496
|
+
// ── search ────────────────────────────────────────────────────────────────
|
|
497
|
+
{
|
|
498
|
+
name: "web_search",
|
|
499
|
+
category: "search",
|
|
500
|
+
description: "Search the web. Modes: auto (tries DDG → Brave → Browser), ddg, brave, browser.",
|
|
501
|
+
endpoint: { method: "POST", path: "/tools/search" },
|
|
502
|
+
parameters: {
|
|
503
|
+
type: "object",
|
|
504
|
+
properties: {
|
|
505
|
+
query: { type: "string" },
|
|
506
|
+
mode: { type: "string", enum: ["auto", "ddg", "brave", "browser"], default: "auto" },
|
|
507
|
+
limit: { type: "integer", default: 5 },
|
|
508
|
+
},
|
|
509
|
+
required: ["query"],
|
|
510
|
+
},
|
|
511
|
+
examples: [
|
|
512
|
+
{ query: "APC agent project context standard" },
|
|
513
|
+
{ query: "site:github.com puppeteer examples", mode: "ddg" },
|
|
514
|
+
],
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
// ── agents ────────────────────────────────────────────────────────────────
|
|
518
|
+
{
|
|
519
|
+
name: "agent_list",
|
|
520
|
+
category: "agents",
|
|
521
|
+
description: "List all agents in a project.",
|
|
522
|
+
endpoint: { method: "GET", path: "/projects/:pid/agents" },
|
|
523
|
+
parameters: {
|
|
524
|
+
type: "object",
|
|
525
|
+
properties: { project: { type: "string" } },
|
|
526
|
+
required: ["project"],
|
|
527
|
+
},
|
|
528
|
+
examples: [{ project: "1" }],
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
name: "agent_get",
|
|
532
|
+
category: "agents",
|
|
533
|
+
description: "Get details + memory for a specific agent.",
|
|
534
|
+
endpoint: { method: "GET", path: "/projects/:pid/agents/:slug" },
|
|
535
|
+
parameters: {
|
|
536
|
+
type: "object",
|
|
537
|
+
properties: {
|
|
538
|
+
project: { type: "string" },
|
|
539
|
+
agent: { type: "string" },
|
|
540
|
+
},
|
|
541
|
+
required: ["project", "agent"],
|
|
542
|
+
},
|
|
543
|
+
examples: [{ project: "1", agent: "sofia" }],
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// ── project ───────────────────────────────────────────────────────────────
|
|
547
|
+
{
|
|
548
|
+
name: "project_info",
|
|
549
|
+
category: "project",
|
|
550
|
+
description: "List all registered projects and their metadata.",
|
|
551
|
+
endpoint: { method: "GET", path: "/projects" },
|
|
552
|
+
parameters: { type: "object", properties: {} },
|
|
553
|
+
examples: [{}],
|
|
554
|
+
},
|
|
555
|
+
];
|
|
556
|
+
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
// Index for fast lookup
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
|
|
561
|
+
const TOOL_MAP = new Map(TOOL_DEFINITIONS.map((t) => [t.name, t]));
|
|
562
|
+
|
|
563
|
+
function listTools() {
|
|
564
|
+
return TOOL_DEFINITIONS.map(({ name, description, category, endpoint }) => ({
|
|
565
|
+
name,
|
|
566
|
+
description,
|
|
567
|
+
category,
|
|
568
|
+
schema_url: `/tools/${name}`,
|
|
569
|
+
endpoint_method: endpoint?.method || "inline",
|
|
570
|
+
endpoint_path: endpoint?.path || null,
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function getTool(name) {
|
|
575
|
+
const t = TOOL_MAP.get(name);
|
|
576
|
+
if (!t) return null;
|
|
577
|
+
return {
|
|
578
|
+
name: t.name,
|
|
579
|
+
description: t.description,
|
|
580
|
+
category: t.category,
|
|
581
|
+
parameters: t.parameters,
|
|
582
|
+
examples: t.examples || [],
|
|
583
|
+
endpoint: t.endpoint || null,
|
|
584
|
+
schema_url: `/tools/${name}`,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ---------------------------------------------------------------------------
|
|
589
|
+
// Inline call handlers for tools without a dedicated HTTP endpoint
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
|
|
592
|
+
function makeInlineHandlers({ projects, registries }) {
|
|
593
|
+
return {
|
|
594
|
+
memory_append: async (body) => {
|
|
595
|
+
const { default: fetch } = await import("node-fetch");
|
|
596
|
+
const base = `http://localhost:${process.env.APX_PORT || 7430}`;
|
|
597
|
+
// GET current
|
|
598
|
+
const getRes = await fetch(`${base}/memory${body.project ? `?project=${body.project}` : ""}`);
|
|
599
|
+
if (!getRes.ok) throw new Error(`memory_get failed: ${getRes.status}`);
|
|
600
|
+
const { body: current } = await getRes.json();
|
|
601
|
+
// POST updated
|
|
602
|
+
const text = body.text || "";
|
|
603
|
+
const postRes = await fetch(`${base}/memory${body.project ? `?project=${body.project}` : ""}`, {
|
|
604
|
+
method: "POST",
|
|
605
|
+
headers: { "content-type": "application/json" },
|
|
606
|
+
body: JSON.stringify({ body: current + text }),
|
|
607
|
+
});
|
|
608
|
+
if (!postRes.ok) throw new Error(`memory_set failed: ${postRes.status}`);
|
|
609
|
+
return { ok: true, appended_chars: text.length };
|
|
610
|
+
},
|
|
611
|
+
|
|
612
|
+
memory_list: async (body) => {
|
|
613
|
+
const { default: fs } = await import("node:fs");
|
|
614
|
+
const { default: path } = await import("node:path");
|
|
615
|
+
// Find the project
|
|
616
|
+
const all = projects.list();
|
|
617
|
+
let p = null;
|
|
618
|
+
if (body.project) {
|
|
619
|
+
const ref = String(body.project);
|
|
620
|
+
const found = all.find((x) => String(x.id) === ref || x.path === ref);
|
|
621
|
+
p = found ? projects.get(found.id) : null;
|
|
622
|
+
}
|
|
623
|
+
if (!p) p = projects.get(all.filter((x) => x.id !== 0)[0]?.id) || projects.get(0);
|
|
624
|
+
if (!p) throw new Error("no project registered");
|
|
625
|
+
const agentsDir = path.join(p.path, ".apc", "agents");
|
|
626
|
+
if (!fs.existsSync(agentsDir)) return { agents_with_memory: [] };
|
|
627
|
+
const result = fs.readdirSync(agentsDir).filter((slug) => {
|
|
628
|
+
return fs.existsSync(path.join(agentsDir, slug, "memory.md"));
|
|
629
|
+
}).map((slug) => {
|
|
630
|
+
const memPath = path.join(agentsDir, slug, "memory.md");
|
|
631
|
+
const stat = fs.statSync(memPath);
|
|
632
|
+
return { agent: slug, path: memPath, size: stat.size, mtime: stat.mtime };
|
|
633
|
+
});
|
|
634
|
+
return { project: p.path, agents_with_memory: result };
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ---------------------------------------------------------------------------
|
|
640
|
+
// Express router factory
|
|
641
|
+
// ---------------------------------------------------------------------------
|
|
642
|
+
|
|
643
|
+
export function buildRegistryRouter(express, ctx) {
|
|
644
|
+
const { projects, registries } = ctx;
|
|
645
|
+
const router = express.Router();
|
|
646
|
+
const inlineHandlers = makeInlineHandlers({ projects, registries });
|
|
647
|
+
|
|
648
|
+
// GET /tools — lightweight list
|
|
649
|
+
router.get("/", (_req, res) => {
|
|
650
|
+
res.json(listTools());
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// GET /tools/:name — full schema
|
|
654
|
+
router.get("/:name", (req, res) => {
|
|
655
|
+
const tool = getTool(req.params.name);
|
|
656
|
+
if (!tool) return res.status(404).json({ error: `tool "${req.params.name}" not found` });
|
|
657
|
+
res.json(tool);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// POST /tools/:name/call — execute tool
|
|
661
|
+
router.post("/:name/call", async (req, res) => {
|
|
662
|
+
const { name } = req.params;
|
|
663
|
+
const toolDef = TOOL_MAP.get(name);
|
|
664
|
+
if (!toolDef) return res.status(404).json({ error: `tool "${name}" not found` });
|
|
665
|
+
|
|
666
|
+
const body = req.body || {};
|
|
667
|
+
|
|
668
|
+
// If there's an inline handler, use it
|
|
669
|
+
if (inlineHandlers[name]) {
|
|
670
|
+
try {
|
|
671
|
+
const result = await inlineHandlers[name](body);
|
|
672
|
+
return res.json({ tool: name, result });
|
|
673
|
+
} catch (e) {
|
|
674
|
+
return res.status(500).json({ error: e.message });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Otherwise proxy to the HTTP endpoint
|
|
679
|
+
if (!toolDef.endpoint) {
|
|
680
|
+
return res.status(501).json({ error: `tool "${name}" has no endpoint and no inline handler` });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
try {
|
|
684
|
+
const { default: fetch } = await import("node-fetch");
|
|
685
|
+
const port = process.env.APX_PORT || 7430;
|
|
686
|
+
const base = `http://localhost:${port}`;
|
|
687
|
+
|
|
688
|
+
let urlPath = toolDef.endpoint.path;
|
|
689
|
+
// Replace :pid / :slug / :name params from body if present
|
|
690
|
+
urlPath = urlPath
|
|
691
|
+
.replace(":pid", body.project || "0")
|
|
692
|
+
.replace(":slug", body.agent || body.slug || "")
|
|
693
|
+
.replace(":sid", body.session_id || "")
|
|
694
|
+
.replace(":id", body.session_id || "")
|
|
695
|
+
.replace(":name", body.name || "");
|
|
696
|
+
|
|
697
|
+
const method = toolDef.endpoint.method || "GET";
|
|
698
|
+
let fetchUrl = `${base}${urlPath}`;
|
|
699
|
+
|
|
700
|
+
let fetchOpts = { method, headers: { "content-type": "application/json" } };
|
|
701
|
+
|
|
702
|
+
if (method === "GET") {
|
|
703
|
+
// Append body fields as query params
|
|
704
|
+
const qs = new URLSearchParams();
|
|
705
|
+
for (const [k, v] of Object.entries(body)) {
|
|
706
|
+
if (v !== undefined && v !== null) qs.set(k, String(v));
|
|
707
|
+
}
|
|
708
|
+
const qstr = qs.toString();
|
|
709
|
+
if (qstr) fetchUrl += `?${qstr}`;
|
|
710
|
+
} else {
|
|
711
|
+
fetchOpts.body = JSON.stringify(body);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const r = await fetch(fetchUrl, fetchOpts);
|
|
715
|
+
const text = await r.text();
|
|
716
|
+
let data;
|
|
717
|
+
try { data = JSON.parse(text); } catch { data = { raw: text }; }
|
|
718
|
+
|
|
719
|
+
if (!r.ok) return res.status(r.status).json({ error: data?.error || text });
|
|
720
|
+
res.json({ tool: name, result: data });
|
|
721
|
+
} catch (e) {
|
|
722
|
+
res.status(500).json({ error: e.message });
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
return router;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export { listTools, getTool, TOOL_DEFINITIONS };
|