@j-o-r/hello-dave 0.1.1 → 0.1.5
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 +42 -25
- package/README.md +81 -221
- package/TODO.md +173 -35
- package/agents/agent_creator.js +105 -0
- package/agents/agent_creator.prompt.md +371 -0
- package/agents/ask_agent.js +64 -127
- package/agents/claude_agent.js +68 -0
- package/agents/code_agent.js +55 -135
- package/agents/code_agent.prompt.md +50 -0
- package/agents/echo_agent.js +76 -0
- package/agents/financial_expert.js +75 -0
- package/agents/gpt_agent.js +52 -103
- package/agents/gpt_code.js +81 -0
- package/agents/grok_agent.js +58 -114
- package/agents/minimax_agent.js +92 -0
- package/agents/mureka_agent.js +77 -0
- package/agents/planner_agent.js +172 -0
- package/agents/stability_agent.js +87 -0
- package/agents/test_agent.js +75 -157
- package/agents/weather_agent.js +73 -0
- package/agents/workflow_agent.js +189 -0
- package/bin/dave.js +436 -184
- package/docs/bin-dave.md +85 -35
- package/docs/cdn-ssh.md +100 -0
- package/docs/creating-agents.md +301 -0
- package/docs/creating-toolsets.md +336 -0
- package/docs/docs-organization.md +48 -0
- package/docs/project-overview.md +86 -51
- package/lib/API/elevenlabs.io/music.compose.md +441 -0
- package/lib/API/elevenlabs.io/music.create-composition-plan.md +370 -0
- package/lib/API/elevenlabs.io/music.stream.md +425 -0
- package/lib/API/lalal.ai/lalal.js +445 -0
- package/lib/API/lalal.ai/openapi.json +2614 -0
- package/lib/API/minimax/ImageToolset.js +82 -37
- package/lib/API/minimax/MusicToolset.js +125 -79
- package/lib/API/minimax/VideoToolset.js +170 -167
- package/lib/API/minimax/image.js +5 -1
- package/lib/API/minimax/music.js +210 -23
- package/lib/API/minimax/video.js +242 -53
- package/lib/API/mureka/MusicToolset.js +646 -0
- package/lib/API/mureka/README.md +41 -0
- package/lib/API/mureka/index.js +7 -0
- package/lib/API/mureka/music.js +658 -0
- package/lib/API/openai.com/index.js +7 -0
- package/lib/API/openai.com/{reponses/text.js → responses.js} +64 -18
- package/lib/API/openai.com/video.create.character.md +40 -0
- package/lib/API/openai.com/video.create.md +219 -0
- package/lib/API/openai.com/video.delete.md +44 -0
- package/lib/API/openai.com/video.download.md +31 -0
- package/lib/API/openai.com/video.edit.md +155 -0
- package/lib/API/openai.com/video.extend.md +166 -0
- package/lib/API/openai.com/video.fetch.character.md +43 -0
- package/lib/API/openai.com/video.js +784 -0
- package/lib/API/openai.com/video.list.md +201 -0
- package/lib/API/openai.com/video.remix.md +175 -0
- package/lib/API/openai.com/video.retrieve.md +139 -0
- package/lib/API/openai.com/videoToolset.js +616 -0
- package/lib/API/stability.ai/ImageToolset.js +131 -40
- package/lib/API/stability.ai/MusicToolset.js +79 -47
- package/lib/API/stability.ai/audio.js +63 -131
- package/lib/API/x.ai/chat.responses.md +1040 -0
- package/lib/API/x.ai/image.js +229 -59
- package/lib/API/x.ai/imageToolset.js +376 -0
- package/lib/API/x.ai/index.js +1 -1
- package/lib/API/x.ai/responses.js +9 -18
- package/lib/Agent.js +271 -0
- package/lib/Agent.js.old +284 -0
- package/lib/AgentLauncher.js +593 -0
- package/lib/Cli.js +87 -13
- package/lib/Prompt.js +23 -1
- package/lib/Session.js +5 -4
- package/lib/ToolSet.js +102 -6
- package/lib/agentLoader.js +369 -0
- package/lib/cdn.js +67 -231
- package/lib/{CdnToolset.js → cdnToolset.js} +47 -64
- package/lib/defaultToolsets.js +43 -0
- package/lib/fafs.js +1 -1
- package/lib/genericToolset.js +442 -119
- package/lib/handOffToolset.js +179 -0
- package/lib/index.js +34 -27
- package/lib/toolsetLoader.js +248 -0
- package/package.json +10 -4
- package/types/API/lalal.ai/lalal.d.ts +116 -0
- package/types/API/minimax/image.d.ts +2 -1
- package/types/API/minimax/music.d.ts +189 -26
- package/types/API/minimax/video.d.ts +100 -31
- package/types/API/mureka/index.d.ts +7 -0
- package/types/API/mureka/music.d.ts +472 -0
- package/types/API/openai.com/index.d.ts +7 -0
- package/types/API/openai.com/{reponses/text.d.ts → responses.d.ts} +11 -11
- package/types/API/openai.com/video.d.ts +409 -0
- package/types/API/openai.com/videoToolset.d.ts +24 -0
- package/types/API/stability.ai/audio.d.ts +14 -103
- package/types/API/stability.ai/image.d.ts +2 -2
- package/types/API/x.ai/image.d.ts +138 -26
- package/types/API/x.ai/imageToolset.d.ts +3 -0
- package/types/API/x.ai/index.d.ts +1 -1
- package/types/API/x.ai/responses.d.ts +4 -4
- package/types/Agent.d.ts +123 -0
- package/types/AgentLauncher.d.ts +250 -0
- package/types/Cli.d.ts +28 -8
- package/types/Prompt.d.ts +23 -5
- package/types/Session.d.ts +1 -1
- package/types/ToolSet.d.ts +10 -0
- package/types/agentLoader.d.ts +78 -0
- package/types/cdn.d.ts +15 -90
- package/types/defaultToolsets.d.ts +9 -0
- package/types/fafs.d.ts +1 -1
- package/types/genericToolset.d.ts +1 -1
- package/types/handOffToolset.d.ts +28 -0
- package/types/index.d.ts +19 -17
- package/types/toolsetLoader.d.ts +114 -0
- package/utils/format_log.js +101 -23
- package/utils/launch_agent.js +18 -0
- package/utils/list_sessions.sh +13 -5
- package/utils/search_sessions.sh +65 -29
- package/utils/toolsets.js +33 -0
- package/README.md.bak.1779452127 +0 -240
- package/agents/codeserver.sh +0 -47
- package/agents/daisy_agent.js +0 -173
- package/agents/docs_agent.js +0 -148
- package/agents/memory_agent.js +0 -263
- package/agents/minimax.js +0 -173
- package/agents/npm_agent.js +0 -202
- package/agents/prompt_agent.js +0 -133
- package/agents/readme_agent.js +0 -148
- package/agents/spawn_agent.js +0 -160
- package/agents/stability.js +0 -173
- package/agents/todo_agent.js +0 -175
- package/bin/codeDave +0 -58
- package/docs/agent-dave-websocket-protocol.md +0 -180
- package/docs/agent-manager.md +0 -244
- package/docs/codeserver-pattern.md +0 -191
- package/docs/generic-toolset.md +0 -326
- package/docs/howtos/agent-networking.md +0 -253
- package/docs/howtos/spawn-agents.md.bak +0 -200
- package/docs/howtos/spawn-agents.md.bak_new +0 -200
- package/docs/multi-agent-clusters.md +0 -265
- package/docs/music-toolsets.md +0 -137
- package/docs/path-resolution-best-practices.md +0 -104
- package/docs/plans/minimax-music-generation.md +0 -80
- package/docs/plans/unified-agent-architecture.md +0 -146
- package/docs/plans/websocket-streaming-plan.md.bak +0 -317
- package/docs/prompt/spawn_agent.md +0 -175
- package/docs/prompt/spawn_agent.md.bak +0 -201
- package/docs/prompt/task_clarification_and_documentation.md +0 -35
- package/docs/prompt-class.md +0 -141
- package/docs/todo-archive-infra-2026-04-21.md +0 -15
- package/docs/todo-archive-v0.0.8.md +0 -1
- package/docs/todo-archive-v0.1.0.md +0 -32
- package/docs/todo-archive.md +0 -44
- package/docs/tools-syntax-validation.md +0 -121
- package/docs/toolset.md +0 -164
- package/docs/xai-responses.md +0 -111
- package/docs/xai_collections.md +0 -106
- package/lib/API/x.ai/ImageToolset.js +0 -165
- package/lib/API/x.ai/text.js +0 -415
- package/lib/AgentClient.js +0 -248
- package/lib/AgentManager.js +0 -245
- package/lib/AgentServer.js +0 -404
- package/lib/wsCli.js +0 -287
- package/lib/wsIO.js +0 -90
- package/types/API/x.ai/text.d.ts +0 -286
- package/types/AgentClient.d.ts +0 -109
- package/types/AgentManager.d.ts +0 -100
- package/types/AgentServer.d.ts +0 -89
- package/types/wsCli.d.ts +0 -17
- package/types/wsIO.d.ts +0 -30
- package/utils/test.sh +0 -46
- /package/docs/{suggestions.md → _notes/token-counts.md} +0 -0
- /package/lib/API/openai.com/{reponses/MESSAGES.md → MESSAGES.md} +0 -0
- /package/types/API/{x.ai/ImageToolset.d.ts → mureka/MusicToolset.d.ts} +0 -0
- /package/types/{CdnToolset.d.ts → cdnToolset.d.ts} +0 -0
package/lib/genericToolset.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { SH
|
|
2
|
-
import
|
|
1
|
+
import { SH } from '@j-o-r/sh'
|
|
2
|
+
import ToolSet from './ToolSet.js';
|
|
3
|
+
import { env } from './fafs.js'
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { promises as fs } from 'node:fs';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
@@ -16,7 +17,22 @@ const syntaxCheckSh = path.join(utilsDir, 'syntax_check.sh');
|
|
|
16
17
|
// Do we have a SSH access point?
|
|
17
18
|
const SSH_EP = process.env.SSH_EP || '';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
let user;
|
|
21
|
+
try {
|
|
22
|
+
user = await env();
|
|
23
|
+
} catch (e) {
|
|
24
|
+
user = {
|
|
25
|
+
name: process.env.USER || '',
|
|
26
|
+
system: process.platform,
|
|
27
|
+
city: '',
|
|
28
|
+
region: '',
|
|
29
|
+
country: '',
|
|
30
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || '',
|
|
31
|
+
external_ip: '',
|
|
32
|
+
cwd: process.cwd()
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
const environment = `
|
|
21
37
|
Name: ${user.name}
|
|
22
38
|
System: ${user.system}
|
|
@@ -29,45 +45,50 @@ ExternalIp: ${user.external_ip}
|
|
|
29
45
|
const tools = new ToolSet('auto');
|
|
30
46
|
|
|
31
47
|
/**
|
|
32
|
-
*
|
|
48
|
+
* Preserve an arbitrary text argument exactly as a string.
|
|
49
|
+
*
|
|
50
|
+
* Important: do not JSON.parse arbitrary text arguments. A valid user payload
|
|
51
|
+
* can itself be a JavaScript/Bash/string literal such as `"hello"`; parsing it
|
|
52
|
+
* would remove the quotes and corrupt the code or content.
|
|
53
|
+
*
|
|
54
|
+
* @param {*} value - Candidate text value.
|
|
55
|
+
* @returns {string} Exact string value, or an empty string for non-strings.
|
|
56
|
+
*/
|
|
57
|
+
const exactText = (value) => typeof value === 'string' ? value : '';
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Normalize scalar selector arguments such as paths, URLs, email addresses, and
|
|
61
|
+
* subjects where accidental extra wrapping quotes are almost always unintended.
|
|
62
|
+
*
|
|
63
|
+
* This helper is intentionally not used for executable scripts or file content.
|
|
64
|
+
*
|
|
65
|
+
* @param {*} value - Candidate scalar value.
|
|
66
|
+
* @returns {*} Normalized scalar, or the original non-string value.
|
|
33
67
|
*/
|
|
34
|
-
const
|
|
35
|
-
if (typeof
|
|
68
|
+
const cleanScalar = (value) => {
|
|
69
|
+
if (typeof value !== 'string') return value;
|
|
36
70
|
|
|
37
|
-
let current =
|
|
71
|
+
let current = value.trim();
|
|
38
72
|
const seen = new Set();
|
|
39
|
-
|
|
40
|
-
const MAX_ITER = 6;
|
|
73
|
+
const MAX_ITER = 3;
|
|
41
74
|
|
|
42
|
-
|
|
75
|
+
for (let i = 0; i < MAX_ITER && !seen.has(current); i++) {
|
|
43
76
|
seen.add(current);
|
|
44
|
-
iterations++;
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const parsed = JSON.parse(current);
|
|
48
|
-
if (typeof parsed === 'string') {
|
|
49
|
-
current = parsed;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
} catch (e) { }
|
|
53
77
|
|
|
54
|
-
if (current.startsWith('\\"') && current.endsWith('\\"')) {
|
|
78
|
+
if (current.startsWith('\\"') && current.endsWith('\\"') && current.length > 4) {
|
|
55
79
|
current = current.slice(2, -2);
|
|
56
80
|
continue;
|
|
57
81
|
}
|
|
58
82
|
|
|
59
|
-
if (current.startsWith('"') && current.endsWith('"') && current.length >
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const lessEscaped = current.replace(/\\"/g, '"');
|
|
69
|
-
if (lessEscaped !== current) {
|
|
70
|
-
current = lessEscaped;
|
|
83
|
+
if (current.startsWith('"') && current.endsWith('"') && current.length > 1) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(current);
|
|
86
|
+
if (typeof parsed === 'string') {
|
|
87
|
+
current = parsed;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
} catch (_) {
|
|
91
|
+
current = current.slice(1, -1);
|
|
71
92
|
continue;
|
|
72
93
|
}
|
|
73
94
|
}
|
|
@@ -78,187 +99,489 @@ const guessOverEscaping = (s) => {
|
|
|
78
99
|
return current;
|
|
79
100
|
};
|
|
80
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Extract concise JavaScript error text from Node stdin-module output.
|
|
104
|
+
* Keeps the user-facing error message, source location, and first code-frame
|
|
105
|
+
* lines while dropping Node internals and version banners.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} errorStr - Raw error text.
|
|
108
|
+
* @returns {string} Condensed error text.
|
|
109
|
+
*/
|
|
81
110
|
const getJSError = (errorStr) => {
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
const raw = String(errorStr || '').trim();
|
|
112
|
+
const withoutCommandPrefix = raw.replace(/^(?:Error: )?Command failed with code \d+:\s*/s, '');
|
|
113
|
+
const cleaned = withoutCommandPrefix
|
|
114
|
+
.split('\n')
|
|
115
|
+
.filter((line) => !/^\s*at (?:node:internal|ModuleJob\.|ModuleLoader\.|compileSourceTextModule|process\.|asyncRunEntryPoint|Object\.|evalModuleEntryPoint|Socket\.)/.test(line))
|
|
116
|
+
.filter((line) => !/^Node\.js v/.test(line))
|
|
117
|
+
.join('\n')
|
|
118
|
+
.trim();
|
|
119
|
+
|
|
120
|
+
const lines = cleaned.split('\n');
|
|
121
|
+
const location = lines.find((line) => /^file:\/\//.test(line));
|
|
122
|
+
const errorLine = lines.find((line) => /^(?:[A-Za-z]+Error|Error): /.test(line));
|
|
123
|
+
const frame = [];
|
|
124
|
+
|
|
125
|
+
if (location) frame.push(location);
|
|
126
|
+
for (const line of lines) {
|
|
127
|
+
if (line === location || line === errorLine) continue;
|
|
128
|
+
if (frame.length >= 3) break;
|
|
129
|
+
if (line.trim() !== '') frame.push(line);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const message = errorLine || lines.find((line) => line.trim() !== '') || 'JavaScript execution failed';
|
|
133
|
+
const prefixedMessage = message.startsWith('Error:') ? message : `Error: ${message}`;
|
|
134
|
+
return [prefixedMessage, ...frame].join('\n').trim();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Stringify a structured tool response with stable formatting.
|
|
139
|
+
* Structured responses preserve exact paths, URLs, commands, and other handles
|
|
140
|
+
* so the assistant can repeat essential references in normal conversation before
|
|
141
|
+
* raw function-call history is pruned.
|
|
142
|
+
*
|
|
143
|
+
* @param {Record<string, *>} payload - JSON-serializable tool response payload.
|
|
144
|
+
* @returns {string} Formatted JSON function response.
|
|
145
|
+
*/
|
|
146
|
+
const json = (payload) => JSON.stringify(payload, null, 2);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Convert an unknown thrown value into a message.
|
|
150
|
+
*
|
|
151
|
+
* @param {*} error - Unknown error value.
|
|
152
|
+
* @returns {string} Error message.
|
|
153
|
+
*/
|
|
154
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Return true when child is cwd or a path inside cwd.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} cwd - Real absolute CWD path.
|
|
160
|
+
* @param {string} child - Absolute candidate path.
|
|
161
|
+
* @returns {boolean} Whether child is inside cwd.
|
|
162
|
+
*/
|
|
163
|
+
const isInsideCwd = (cwd, child) => {
|
|
164
|
+
const relative = path.relative(cwd, child);
|
|
165
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Resolve a user-supplied path to a CWD-contained absolute path.
|
|
170
|
+
* For writes, parent directories are created safely and final symlinks are rejected
|
|
171
|
+
* if they resolve outside CWD.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} file - User-supplied relative path.
|
|
174
|
+
* @param {{ forWrite?: boolean }} [options] - Resolution options.
|
|
175
|
+
* @returns {Promise<{ file: string, resolved: string }>} Clean relative and absolute paths.
|
|
176
|
+
*/
|
|
177
|
+
const resolveCwdPath = async (file, options = {}) => {
|
|
178
|
+
const cleaned = cleanScalar(file);
|
|
179
|
+
if (!cleaned || typeof cleaned !== 'string' || path.isAbsolute(cleaned)) {
|
|
180
|
+
throw new Error('Relative CWD path only');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const cwd = await fs.realpath(process.cwd());
|
|
184
|
+
const resolved = path.resolve(cwd, cleaned);
|
|
185
|
+
if (!isInsideCwd(cwd, resolved)) {
|
|
186
|
+
throw new Error('Path escapes CWD');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (options.forWrite) {
|
|
190
|
+
await fs.mkdir(path.dirname(resolved), { recursive: true });
|
|
191
|
+
const parentReal = await fs.realpath(path.dirname(resolved));
|
|
192
|
+
if (!isInsideCwd(cwd, parentReal)) {
|
|
193
|
+
throw new Error('Path escapes CWD');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const existingReal = await fs.realpath(resolved);
|
|
198
|
+
if (!isInsideCwd(cwd, existingReal)) {
|
|
199
|
+
throw new Error('Path escapes CWD');
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
if (e?.code !== 'ENOENT') throw e;
|
|
203
|
+
}
|
|
89
204
|
} else {
|
|
90
|
-
|
|
205
|
+
const real = await fs.realpath(resolved);
|
|
206
|
+
if (!isInsideCwd(cwd, real)) {
|
|
207
|
+
throw new Error('Path escapes CWD');
|
|
208
|
+
}
|
|
91
209
|
}
|
|
92
|
-
|
|
210
|
+
|
|
211
|
+
return { file: path.relative(cwd, resolved), resolved };
|
|
93
212
|
};
|
|
94
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Return a structured failure response for a tool.
|
|
216
|
+
*
|
|
217
|
+
* @param {string} tool - Tool name.
|
|
218
|
+
* @param {*} error - Unknown error value.
|
|
219
|
+
* @param {Record<string, *>} [extra] - Additional response fields.
|
|
220
|
+
* @returns {string} JSON error payload.
|
|
221
|
+
*/
|
|
222
|
+
const toolError = (tool, error, extra = {}) => json({
|
|
223
|
+
tool,
|
|
224
|
+
success: false,
|
|
225
|
+
...extra,
|
|
226
|
+
error: errorMessage(error)
|
|
227
|
+
});
|
|
228
|
+
|
|
95
229
|
/**
|
|
96
230
|
* @module lib/genericToolset
|
|
97
|
-
* Secure utility tools.
|
|
231
|
+
* Secure utility tools.
|
|
98
232
|
*/
|
|
99
233
|
|
|
100
234
|
tools.add(
|
|
101
235
|
'javascript_interpreter',
|
|
102
|
-
|
|
236
|
+
`Execute ESM ES6 JavaScript on node. Tool-specific calling rules: pass script as raw ESM JavaScript exactly as intended; preserve quotes, backticks, dollar braces, and newlines; do not JSON.parse, strip quotes, escape, shell-wrap, or otherwise transform the code. Use console.log for output. Returns structured JSON with success, output/error, and a note. If output contains exact paths, URLs, ids, or values needed later, include them verbatim in your assistant response before old tool calls are pruned.`,
|
|
103
237
|
{
|
|
104
238
|
type: 'object',
|
|
105
239
|
properties: {
|
|
106
240
|
script: {
|
|
107
241
|
type: 'string',
|
|
108
|
-
description: `ES6 ESM code. Use console.log for output. cwd: ${user.cwd}`
|
|
242
|
+
description: `Raw ES6 ESM code to execute with Node from stdin. Preserve the code exactly, including JavaScript string literal quotes such as "hello", template literals, dollar braces, and newlines. Use console.log for output. cwd: ${user.cwd}`
|
|
109
243
|
}
|
|
110
244
|
},
|
|
111
245
|
required: ['script']
|
|
112
246
|
},
|
|
113
247
|
async (params) => {
|
|
114
|
-
|
|
248
|
+
const tool = 'javascript_interpreter';
|
|
115
249
|
try {
|
|
116
|
-
const script =
|
|
250
|
+
const script = exactText(params.script);
|
|
117
251
|
const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
118
|
-
|
|
252
|
+
const output = await SH`cat <<'${delim}' | node --input-type=module -
|
|
119
253
|
${script}
|
|
120
254
|
${delim}
|
|
121
255
|
`.run();
|
|
256
|
+
return json({
|
|
257
|
+
tool,
|
|
258
|
+
success: true,
|
|
259
|
+
output,
|
|
260
|
+
note: 'Assistant: preserve exact paths, URLs, ids, and values from output in your normal response if needed for follow-up.'
|
|
261
|
+
});
|
|
122
262
|
} catch (e) {
|
|
123
|
-
|
|
263
|
+
const error = getJSError(errorMessage(e));
|
|
264
|
+
return json({
|
|
265
|
+
tool,
|
|
266
|
+
success: false,
|
|
267
|
+
output: error,
|
|
268
|
+
error,
|
|
269
|
+
note: 'Assistant: report JavaScript errors compactly: message, relevant line/location, no Node internals unless asked.'
|
|
270
|
+
});
|
|
124
271
|
}
|
|
125
|
-
return response;
|
|
126
272
|
}
|
|
127
273
|
);
|
|
128
274
|
|
|
129
275
|
tools.add(
|
|
130
276
|
'get_user_env',
|
|
131
|
-
'Get user environment info.',
|
|
277
|
+
'Get user environment info. Tool-specific behavior: call with an empty object; do not invent missing fields. Returns name/location/system/cwd context as text; mention only user-relevant facts in your assistant response.',
|
|
132
278
|
{ type: 'object', properties: {} },
|
|
133
|
-
async () =>
|
|
279
|
+
async () => json({
|
|
280
|
+
tool: 'get_user_env',
|
|
281
|
+
environment,
|
|
282
|
+
note: 'Assistant: use these environment facts only when relevant.'
|
|
283
|
+
})
|
|
134
284
|
);
|
|
135
285
|
|
|
136
286
|
tools.add(
|
|
137
287
|
'execute_bash_script',
|
|
138
|
-
'Execute raw bash script or command
|
|
288
|
+
'Execute raw bash script or command. Tool-specific calling rules: pass bash_script as raw Bash exactly as intended; preserve quotes, dollar signs, backticks, heredocs, and newlines; do not JSON.parse, escape, re-quote, or wrap the script. Use shellcheck=false for exploratory scripts unless the user explicitly requests linting; if shellcheck=true fails, report the ShellCheck error and do not imply the script ran. For timeout tests, report both timeout and timeoutSec from the tool result. Returns structured JSON with success, timeout, timeoutSec, and output. If output includes important paths, generated files, URLs, ids, or command results needed later, include those exact references in your assistant response before old tool calls are pruned.',
|
|
139
289
|
{
|
|
140
290
|
type: 'object',
|
|
141
291
|
properties: {
|
|
142
292
|
bash_script: {
|
|
143
293
|
type: 'string',
|
|
144
|
-
description:
|
|
294
|
+
description: 'Raw Bash to execute. Preserve exactly as intended, including quotes, variables, command substitutions, heredocs, and newlines. Do not add extra wrapping quotes or escaping.'
|
|
145
295
|
},
|
|
146
|
-
timeout: {
|
|
147
|
-
|
|
296
|
+
timeout: {
|
|
297
|
+
type: 'number',
|
|
298
|
+
default: 360,
|
|
299
|
+
description: 'Timeout in seconds. For timeout tests, report both timeout and timeoutSec from the result.'
|
|
300
|
+
},
|
|
301
|
+
shellcheck: {
|
|
302
|
+
type: 'boolean',
|
|
303
|
+
default: false,
|
|
304
|
+
description: 'Run shellcheck before executing the script. Defaults to false; keep false for exploratory scripts unless the user explicitly asks for linting. If shellcheck fails, the script does not run.'
|
|
305
|
+
}
|
|
148
306
|
},
|
|
149
307
|
required: ['bash_script']
|
|
150
308
|
},
|
|
151
309
|
async (params) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
310
|
+
const tool = 'execute_bash_script';
|
|
311
|
+
let timeoutSec = Number(params.timeout ?? 360);
|
|
312
|
+
try {
|
|
313
|
+
let bashScript = params.bash_script;
|
|
314
|
+
if (typeof bashScript !== 'string') bashScript = '';
|
|
315
|
+
if (!Number.isFinite(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
316
|
+
const timeout = timeoutSec * 1000;
|
|
317
|
+
const shouldRunShellcheck = Boolean(params.shellcheck ?? false);
|
|
318
|
+
if (shouldRunShellcheck) {
|
|
319
|
+
const valid = SH`shellcheck -s bash -`.runSync(bashScript).stdout.toString().trim();
|
|
320
|
+
if (valid !== '') throw new Error(valid);
|
|
321
|
+
}
|
|
322
|
+
const output = await SH`bash`.options({ timeout }).run(bashScript);
|
|
323
|
+
return json({
|
|
324
|
+
tool,
|
|
325
|
+
success: true,
|
|
326
|
+
timeout: false,
|
|
327
|
+
timeoutSec,
|
|
328
|
+
output,
|
|
329
|
+
note: 'Assistant: preserve exact paths, URLs, ids, filenames, and command results from output in your normal response if needed for follow-up.'
|
|
330
|
+
});
|
|
331
|
+
} catch (e) {
|
|
332
|
+
const message = errorMessage(e);
|
|
333
|
+
return toolError(tool, e, {
|
|
334
|
+
timeout: /timeout|timed out|killed/i.test(message),
|
|
335
|
+
timeoutSec
|
|
336
|
+
});
|
|
159
337
|
}
|
|
160
|
-
const timeoutSec = Number(params.timeout ?? 360);
|
|
161
|
-
const prams = params.strict ? '' : '-S error';
|
|
162
|
-
if (isNaN(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
163
|
-
const timeout = timeoutSec * 1000;
|
|
164
|
-
const valid = SH`shellcheck ${prams} -`.runSync(bash_script).stdout.toString().trim();
|
|
165
|
-
if (valid !== '') throw new Error(valid);
|
|
166
|
-
return await SH`bash`.options({ timeout }).run(bash_script);
|
|
167
338
|
}
|
|
168
339
|
);
|
|
169
340
|
|
|
170
341
|
tools.add(
|
|
171
|
-
'send_email', 'Send email via msmtp.
|
|
342
|
+
'send_email', 'Send email via msmtp. Tool-specific calling rules: to and subject are scalar fields, so do not add extra wrapping quotes; body is exact text and must preserve newlines, quotes, and formatting. Returns structured JSON with recipient and subject. After success, mention the recipient and subject exactly in your assistant response.', {
|
|
343
|
+
type: 'object',
|
|
344
|
+
properties: {
|
|
345
|
+
to: { type: 'string', description: 'Scalar recipient email address. Do not add extra wrapping quotes or newlines.' },
|
|
346
|
+
subject: { type: 'string', description: 'Scalar email subject. Do not add extra wrapping quotes or newlines.' },
|
|
347
|
+
body: { type: 'string', description: 'Exact email body text. Preserve newlines, quotes, and formatting exactly.' }
|
|
348
|
+
},
|
|
349
|
+
required: ['to', 'subject', 'body']
|
|
350
|
+
},
|
|
172
351
|
async (params) => {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
352
|
+
const tool = 'send_email';
|
|
353
|
+
try {
|
|
354
|
+
const to = cleanScalar(params.to);
|
|
355
|
+
const subject = cleanScalar(params.subject);
|
|
356
|
+
const body = exactText(params.body);
|
|
357
|
+
if (!to || /[\r\n]/.test(to) || /[\r\n]/.test(subject)) {
|
|
358
|
+
throw new Error('Email recipient and subject must be non-empty and must not contain newlines');
|
|
359
|
+
}
|
|
360
|
+
const delim = `END_EMAIL_${Date.now().toString(36)}`;
|
|
361
|
+
const output = await SH`msmtp ${to} <<'${delim}'
|
|
178
362
|
To: ${to}
|
|
179
363
|
Subject: ${subject}
|
|
180
364
|
|
|
181
365
|
${body}
|
|
182
366
|
${delim}
|
|
183
367
|
`.run();
|
|
368
|
+
return json({
|
|
369
|
+
tool,
|
|
370
|
+
success: true,
|
|
371
|
+
to,
|
|
372
|
+
subject,
|
|
373
|
+
output,
|
|
374
|
+
note: 'Assistant: tell the user the email was sent and include the recipient and subject exactly.'
|
|
375
|
+
});
|
|
376
|
+
} catch (e) {
|
|
377
|
+
return toolError(tool, e);
|
|
378
|
+
}
|
|
184
379
|
}
|
|
185
380
|
);
|
|
186
381
|
|
|
187
382
|
tools.add(
|
|
188
|
-
'open_link', 'Open URL/file with xdg-open.
|
|
189
|
-
|
|
383
|
+
'open_link', 'Open URL/file with xdg-open. Tool-specific calling rules: url is a scalar URL/file value; pass it without extra wrapping quotes such as "https://...". Returns structured JSON with the opened URL/file. After success, mention the exact URL/file if it is useful for follow-up.', {
|
|
384
|
+
type: 'object',
|
|
385
|
+
properties: {
|
|
386
|
+
url: { type: 'string', description: 'Scalar URL or file path to open. Do not add extra wrapping quotes.' }
|
|
387
|
+
},
|
|
388
|
+
required: ['url']
|
|
389
|
+
},
|
|
390
|
+
async (params) => {
|
|
391
|
+
const tool = 'open_link';
|
|
392
|
+
try {
|
|
393
|
+
const url = cleanScalar(params.url);
|
|
394
|
+
const output = await SH`xdg-open ${[url]}`.run();
|
|
395
|
+
return json({
|
|
396
|
+
tool,
|
|
397
|
+
success: true,
|
|
398
|
+
url,
|
|
399
|
+
output,
|
|
400
|
+
note: 'Assistant: mention the opened URL/file exactly if relevant for follow-up.'
|
|
401
|
+
});
|
|
402
|
+
} catch (e) {
|
|
403
|
+
return toolError(tool, e);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
190
406
|
);
|
|
191
407
|
|
|
192
408
|
tools.add(
|
|
193
|
-
'execute_remote_script', `Run bash on remote via SSH. ${SSH_EP}
|
|
409
|
+
'execute_remote_script', `Run bash on remote via SSH. Tool-specific calling rules: script is raw Bash and must preserve quotes, dollar signs, backticks, heredocs, and newlines exactly; url is a scalar ssh:// URL and should not have extra wrapping quotes. ${SSH_EP} Returns structured JSON with target, timeout, and output. If output contains important paths, URLs, ids, or files needed later, include them verbatim in your assistant response.`,
|
|
194
410
|
{
|
|
195
411
|
type: 'object',
|
|
196
412
|
properties: {
|
|
197
|
-
url: { type: 'string', description: `ssh://user@host[:port]
|
|
198
|
-
script: { type: 'string' },
|
|
199
|
-
timeout: {
|
|
413
|
+
url: { type: 'string', description: `Scalar SSH URL ssh://user@host[:port]. Do not add extra wrapping quotes. Default: ${SSH_EP}` },
|
|
414
|
+
script: { type: 'string', description: 'Raw Bash to execute remotely. Preserve exactly, including quotes, variables, heredocs, and newlines.' },
|
|
415
|
+
timeout: {
|
|
416
|
+
type: 'number',
|
|
417
|
+
default: 30,
|
|
418
|
+
description: 'Remote execution timeout in seconds.'
|
|
419
|
+
}
|
|
200
420
|
},
|
|
201
|
-
required: ['
|
|
421
|
+
required: ['script']
|
|
202
422
|
},
|
|
203
423
|
async (params) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
424
|
+
const tool = 'execute_remote_script';
|
|
425
|
+
try {
|
|
426
|
+
let { url, script } = params;
|
|
427
|
+
if (!url || url === '') url = SSH_EP;
|
|
428
|
+
url = cleanScalar(url);
|
|
429
|
+
script = exactText(script);
|
|
430
|
+
const timeoutSec = Number(params.timeout ?? 30);
|
|
431
|
+
if (!Number.isFinite(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
432
|
+
const parsed = new URL(url);
|
|
433
|
+
if (parsed.protocol !== 'ssh:') throw new Error('Expected ssh://user@host[:port]');
|
|
434
|
+
const remoteUser = decodeURIComponent(parsed.username);
|
|
435
|
+
const host = parsed.hostname;
|
|
436
|
+
const port = parsed.port ? Number(parsed.port) : 22;
|
|
437
|
+
if (!remoteUser || !host || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
438
|
+
throw new Error('Invalid SSH URL');
|
|
439
|
+
}
|
|
440
|
+
const output = await SH`ssh -p ${port} ${remoteUser}@${host} bash`.options({ timeout: timeoutSec * 1000 }).run(script);
|
|
441
|
+
return json({
|
|
442
|
+
tool,
|
|
443
|
+
success: true,
|
|
444
|
+
url,
|
|
445
|
+
host,
|
|
446
|
+
user: remoteUser,
|
|
447
|
+
port,
|
|
448
|
+
timeoutSec,
|
|
449
|
+
output,
|
|
450
|
+
note: 'Assistant: preserve exact remote paths, URLs, ids, filenames, and command results from output in your normal response if needed for follow-up.'
|
|
451
|
+
});
|
|
452
|
+
} catch (e) {
|
|
453
|
+
return toolError(tool, e);
|
|
454
|
+
}
|
|
216
455
|
}
|
|
217
456
|
);
|
|
218
457
|
|
|
219
458
|
tools.add(
|
|
220
|
-
'history_search', 'Search/list chat sessions in .cache/.',
|
|
221
|
-
{
|
|
459
|
+
'history_search', 'Search/list chat sessions in .cache/. Tool-specific calling rules: query is a scalar search string; pass it without extra wrapping quotes. Omit query or pass an empty string to list sessions. Returns structured JSON with query and results. If a session id/path is useful for follow-up, include it exactly in your assistant response.',
|
|
460
|
+
{
|
|
461
|
+
type: 'object',
|
|
462
|
+
properties: {
|
|
463
|
+
query: { type: 'string', description: 'Scalar search query. Do not add extra wrapping quotes. Omit or use empty string to list sessions.' }
|
|
464
|
+
}
|
|
465
|
+
},
|
|
222
466
|
async (params) => {
|
|
223
|
-
|
|
224
|
-
|
|
467
|
+
const tool = 'history_search';
|
|
468
|
+
try {
|
|
469
|
+
const query = typeof params.query === 'string' ? cleanScalar(params.query) : '';
|
|
470
|
+
const output = query.trim()
|
|
471
|
+
? await SH`${searchSessionsSh} ${[query]}`.run()
|
|
472
|
+
: await SH`${listSessionsSh}`.run();
|
|
473
|
+
return json({
|
|
474
|
+
tool,
|
|
475
|
+
success: true,
|
|
476
|
+
query,
|
|
477
|
+
output,
|
|
478
|
+
note: 'Assistant: if a session id or path is useful for follow-up, mention it exactly in your normal response.'
|
|
479
|
+
});
|
|
480
|
+
} catch (e) {
|
|
481
|
+
return toolError(tool, e);
|
|
225
482
|
}
|
|
226
|
-
return await SH`${listSessionsSh}`.run();
|
|
227
483
|
}
|
|
228
484
|
);
|
|
229
485
|
|
|
230
486
|
tools.add(
|
|
231
|
-
'read_file', 'Read file from CWD (relative path only).',
|
|
232
|
-
{
|
|
487
|
+
'read_file', 'Read file from CWD (relative path only). Tool-specific calling rules: file is a scalar relative path inside CWD; do not use absolute paths, parent-directory escapes, or extra wrapping quotes such as "src/file.js". Returns structured JSON with file path, byte count, and content. If this file is relevant for later edits, mention the exact relative path in your assistant response.',
|
|
488
|
+
{
|
|
489
|
+
type: 'object',
|
|
490
|
+
properties: {
|
|
491
|
+
file: { type: 'string', description: 'Scalar relative path inside CWD. Do not use absolute paths, parent-directory escapes, or extra wrapping quotes.' }
|
|
492
|
+
},
|
|
493
|
+
required: ['file']
|
|
494
|
+
},
|
|
233
495
|
async (params) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
496
|
+
const tool = 'read_file';
|
|
497
|
+
try {
|
|
498
|
+
const { file, resolved } = await resolveCwdPath(params.file);
|
|
499
|
+
const content = await fs.readFile(resolved, 'utf8');
|
|
500
|
+
return json({
|
|
501
|
+
tool,
|
|
502
|
+
success: true,
|
|
503
|
+
file,
|
|
504
|
+
absolutePath: resolved,
|
|
505
|
+
bytes: Buffer.byteLength(content, 'utf8'),
|
|
506
|
+
content,
|
|
507
|
+
note: 'Assistant: if this file matters for follow-up work, mention the exact relative file path in your normal response.'
|
|
508
|
+
});
|
|
509
|
+
} catch (e) {
|
|
510
|
+
return toolError(tool, e);
|
|
511
|
+
}
|
|
237
512
|
}
|
|
238
513
|
);
|
|
239
514
|
|
|
240
515
|
tools.add(
|
|
241
|
-
'write_file', 'Write/validate file in CWD.',
|
|
242
|
-
{
|
|
516
|
+
'write_file', 'Write/validate file in CWD. Tool-specific calling rules: file is a scalar relative path inside CWD; do not use absolute paths, parent-directory escapes, or extra wrapping quotes. content is exact file content; preserve quotes, backticks, dollar braces, and newlines; do not JSON.parse, strip quotes, or reformat unless asked. Returns structured JSON with exact relative path and byte count. After success, mention the written file path exactly in your assistant response before old tool calls are pruned.',
|
|
517
|
+
{
|
|
518
|
+
type: 'object',
|
|
519
|
+
properties: {
|
|
520
|
+
file: { type: 'string', description: 'Scalar relative path inside CWD. Do not use absolute paths, parent-directory escapes, or extra wrapping quotes.' },
|
|
521
|
+
content: { type: 'string', description: 'Exact file content. Preserve quotes, backticks, dollar braces, and newlines exactly; do not JSON.parse, strip quotes, or reformat unless asked.' }
|
|
522
|
+
},
|
|
523
|
+
required: ['file', 'content']
|
|
524
|
+
},
|
|
243
525
|
async (params) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
526
|
+
const tool = 'write_file';
|
|
527
|
+
try {
|
|
528
|
+
const { file, resolved } = await resolveCwdPath(params.file, { forWrite: true });
|
|
529
|
+
const content = exactText(params.content);
|
|
530
|
+
await fs.writeFile(resolved, content, 'utf8');
|
|
531
|
+
const bytes = Buffer.byteLength(content, 'utf8');
|
|
532
|
+
return json({
|
|
533
|
+
tool,
|
|
534
|
+
success: true,
|
|
535
|
+
file,
|
|
536
|
+
absolutePath: resolved,
|
|
537
|
+
bytes,
|
|
538
|
+
message: `Wrote ${file} (${bytes} bytes).`,
|
|
539
|
+
note: 'Assistant: tell the user the exact written file path and byte count in your normal response.'
|
|
540
|
+
});
|
|
541
|
+
} catch (e) {
|
|
542
|
+
return toolError(tool, e);
|
|
543
|
+
}
|
|
251
544
|
}
|
|
252
545
|
);
|
|
253
546
|
|
|
254
547
|
tools.add(
|
|
255
|
-
'syntax_check', 'Syntax validate file via utils/syntax_check.sh.',
|
|
256
|
-
{
|
|
548
|
+
'syntax_check', 'Syntax validate file via utils/syntax_check.sh. Tool-specific calling rules: file is a scalar relative path inside CWD; do not use absolute paths, parent-directory escapes, or extra wrapping quotes. Returns structured JSON with exact file path, status, and output. After success/failure, mention the file path and validation status exactly.',
|
|
549
|
+
{
|
|
550
|
+
type: 'object',
|
|
551
|
+
properties: {
|
|
552
|
+
file: { type: 'string', description: 'Scalar relative path inside CWD. Do not use absolute paths, parent-directory escapes, or extra wrapping quotes.' }
|
|
553
|
+
},
|
|
554
|
+
required: ['file']
|
|
555
|
+
},
|
|
257
556
|
async (params) => {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
557
|
+
const tool = 'syntax_check';
|
|
558
|
+
let file = '';
|
|
559
|
+
let resolved = '';
|
|
560
|
+
try {
|
|
561
|
+
({ file, resolved } = await resolveCwdPath(params.file));
|
|
562
|
+
const output = await SH`${syntaxCheckSh} ${resolved}`.run();
|
|
563
|
+
return json({
|
|
564
|
+
tool,
|
|
565
|
+
success: true,
|
|
566
|
+
file,
|
|
567
|
+
absolutePath: resolved,
|
|
568
|
+
status: 'ok',
|
|
569
|
+
output: output || 'Syntax OK',
|
|
570
|
+
note: 'Assistant: mention the exact file path and validation status in your normal response.'
|
|
571
|
+
});
|
|
572
|
+
} catch (e) {
|
|
573
|
+
return json({
|
|
574
|
+
tool,
|
|
575
|
+
success: false,
|
|
576
|
+
file: file || undefined,
|
|
577
|
+
absolutePath: resolved || undefined,
|
|
578
|
+
status: 'failed',
|
|
579
|
+
output: errorMessage(e),
|
|
580
|
+
error: errorMessage(e),
|
|
581
|
+
note: 'Assistant: mention the exact file path and validation status in your normal response.'
|
|
582
|
+
});
|
|
583
|
+
}
|
|
261
584
|
}
|
|
262
585
|
);
|
|
263
586
|
|
|
264
|
-
export default tools;
|
|
587
|
+
export default tools;
|