@j-o-r/hello-dave 0.0.2 → 0.0.4
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 +21 -0
- package/README.md +445 -160
- package/README.md.backup +269 -0
- package/README.md.bak +481 -0
- package/README.md.bak.1774780058 +338 -0
- package/README.md.bak2 +455 -0
- package/bin/dave.js +165 -0
- package/docs.bak.1774780058/agent-manager.md +167 -0
- package/docs.bak.1774780058/agent-manager.md.bak +137 -0
- package/docs.bak.1774780058/agent-manager.md.bak2 +157 -0
- package/docs.bak.1774780058/codeserver-pattern.md +191 -0
- package/docs.bak.1774780058/path-resolution-best-practices.md +104 -0
- package/docs.bak.1774780058/project-overview.md +67 -0
- package/docs.bak.1774780058/project-overview.md.bak +67 -0
- package/docs.bak.1774780058/prompt-class.md +141 -0
- package/docs.bak.1774780058/prompt-class.md.bak +142 -0
- package/docs.bak.1774780058/tools-syntax-validation.md +121 -0
- package/docs.bak.1774780058/tools-syntax-validation.md.bak2 +125 -0
- package/docs.bak.1774780058/tools-syntax-validation.md.bak3 +125 -0
- package/docs.bak.1774780058/tools-syntax-validation.md.bak4 +106 -0
- package/docs.bak.1774780058/tools-syntax-validation.md.bak_path +106 -0
- package/docs.bak.1774780058/toolset.md +164 -0
- package/docs.bak.1774780058/toolset.md.bak +94 -0
- package/docs.bak.1774780058/toolset.md.bak3 +161 -0
- package/docs.bak.1774780058/toolset.md.bak4 +161 -0
- package/docs.bak.1774780058/toolset.md.bak5 +161 -0
- package/docs.bak.1774780058/toolset.md.bak6 +163 -0
- package/docs.bak.1774780058/toolset.md.bak_path +163 -0
- package/docs.bak.1774780058/toolset.md.bak_syntax +161 -0
- package/docs.bak.1774780058/xai-responses.md +111 -0
- package/docs.bak.1774780058/xai-responses.md.bak +107 -0
- package/docs.bak.1774780058/xai-responses.md.bak2 +107 -0
- package/docs.bak.1774780058/xai_collections.md +106 -0
- package/examples/ask_agent.js +137 -0
- package/examples/code_agent.js +149 -0
- package/examples/coderev_agent.js +136 -0
- package/examples/codeserver.sh +47 -0
- package/examples/daisy_agent.js +170 -0
- package/examples/docs_agent.js +148 -0
- package/examples/gpt_agent.js +125 -0
- package/examples/grok_agent.js +132 -0
- package/examples/grok_agent.js.bak +98 -0
- package/examples/grok_agent.js.bak.2 +99 -0
- package/examples/grok_agent.js.bak.3 +1 -0
- package/examples/grok_agent.js.bak.4 +124 -0
- package/examples/grok_agent.js.bak.5 +1 -0
- package/examples/grok_agent.js.bak.6 +1 -0
- package/examples/memory_agent.js +152 -0
- package/examples/npm_agent.js +202 -0
- package/examples/npm_agent.js.bak.3 +2 -0
- package/examples/npm_agent.js.bak.4 +205 -0
- package/examples/npm_agent.js.bak.5 +1 -0
- package/examples/npm_agent.js.bak.6 +1 -0
- package/examples/prompt_agent.js +133 -0
- package/examples/readme_agent.js +148 -0
- package/examples/spawn_agent.js +293 -0
- package/examples/test_agent.js +187 -0
- package/examples/todo_agent.js +175 -0
- package/examples.bak.1774780058/ask_agent.js +114 -0
- package/examples.bak.1774780058/code_agent.js +149 -0
- package/examples.bak.1774780058/coderev_agent.js +72 -0
- package/examples.bak.1774780058/codeserver.sh +47 -0
- package/examples.bak.1774780058/daisy_agent.js +177 -0
- package/examples.bak.1774780058/docs_agent.js +119 -0
- package/{bin/hdAsk.js → examples.bak.1774780058/gpt_agent.js} +46 -40
- package/examples.bak.1774780058/grok_agent.js +98 -0
- package/examples.bak.1774780058/memory_agent.js +112 -0
- package/examples.bak.1774780058/npm_agent.js +175 -0
- package/examples.bak.1774780058/prompt_agent.js +112 -0
- package/examples.bak.1774780058/readme_agent.js +144 -0
- package/examples.bak.1774780058/spawn_agent.js +263 -0
- package/examples.bak.1774780058/test_agent.js +162 -0
- package/examples.bak.1774780058/todo_agent.js +138 -0
- package/lib/API/openai.com/reponses/text.js +12 -18
- package/lib/API/x.ai/collections.js +354 -0
- package/lib/API/x.ai/files.js +218 -0
- package/lib/API/x.ai/responses.js +492 -0
- package/lib/API/x.ai/text.js +1 -1
- package/lib/AgentClient.js +13 -6
- package/lib/AgentManager.js +80 -10
- package/lib/AgentServer.js +50 -22
- package/lib/Cli.js +7 -1
- package/lib/Prompt.js +4 -2
- package/lib/ToolSet.js +2 -1
- package/lib/genericToolset.js +258 -88
- package/lib/genericToolset.js.bak_syntax +402 -0
- package/lib/index.js +4 -2
- package/lib/wsCli.js +256 -0
- package/lib/wsIO.js +96 -0
- package/package.json +26 -21
- package/scenarios.bak.1774780058/data/eval_node_message.json +9 -0
- package/scenarios.bak.1774780058/data/hist_oa.json +66 -0
- package/scenarios.bak.1774780058/data/o3_response1.json +96 -0
- package/scenarios.bak.1774780058/data/oa_reasoning_parse.json +112 -0
- package/scenarios.bak.1774780058/data/tool_oa.json +96 -0
- package/scenarios.bak.1774780058/data/tool_xai.json +59 -0
- package/scenarios.bak.1774780058/data/tool_xai2.json +40 -0
- package/scenarios.bak.1774780058/data/xai-response-1.json +59 -0
- package/scenarios.bak.1774780058/data/xai-response-2.json +10 -0
- package/scenarios.bak.1774780058/data/xai_reasoning_tools_resp.json +59 -0
- package/scenarios.bak.1774780058/data/xai_search_response.json +58 -0
- package/scenarios.bak.1774780058/environment.js +10 -0
- package/scenarios.bak.1774780058/example.js +17 -0
- package/scenarios.bak.1774780058/genericToolset.test.js +182 -0
- package/scenarios.bak.1774780058/grok.js +113 -0
- package/scenarios.bak.1774780058/memory-tools.js +51 -0
- package/scenarios.bak.1774780058/openai-o3.js +137 -0
- package/scenarios.bak.1774780058/openai-prompt.js +155 -0
- package/scenarios.bak.1774780058/openai-session.js +148 -0
- package/scenarios.bak.1774780058/openai.js +102 -0
- package/scenarios.bak.1774780058/prompt.js +118 -0
- package/scenarios.bak.1774780058/promptFishbowl.js +76 -0
- package/scenarios.bak.1774780058/search.brave.com.js +25 -0
- package/scenarios.bak.1774780058/sh.js +15 -0
- package/scenarios.bak.1774780058/test-wsio.js +26 -0
- package/scenarios.bak.1774780058/testToolset.js +42 -0
- package/scenarios.bak.1774780058/toolset.js +16 -0
- package/scenarios.bak.1774780058/toolset.test.js +141 -0
- package/scenarios.bak.1774780058/write_file_syntax.test.js +145 -0
- package/scenarios.bak.1774780058/write_file_validation/README.md +30 -0
- package/scenarios.bak.1774780058/write_file_validation/bad.js +3 -0
- package/scenarios.bak.1774780058/write_file_validation/good.js +4 -0
- package/scenarios.bak.1774780058/write_file_validation/test.sh +43 -0
- package/scenarios.bak.1774780058/wsClient.js +69 -0
- package/scenarios.bak.1774780058/xai_responses.integration.test.js +57 -0
- package/scenarios.bak.1774780058/xai_responses.test.js +154 -0
- package/scenarios.bak.1774780058/xaicoll.js +50 -0
- package/scenarios.bak.1774780058/xaifiles.js +48 -0
- package/types/API/openai.com/reponses/text.d.ts +17 -3
- package/types/API/x.ai/collections.d.ts +167 -0
- package/types/API/x.ai/files.d.ts +84 -0
- package/types/API/x.ai/responses.d.ts +379 -0
- package/types/AgentClient.d.ts +5 -0
- package/types/AgentManager.d.ts +25 -31
- package/types/AgentServer.d.ts +5 -1
- package/types/Prompt.d.ts +4 -2
- package/types/ToolSet.d.ts +1 -0
- package/types/index.d.ts +4 -3
- package/types/wsCli.d.ts +3 -0
- package/types/wsIO.d.ts +26 -0
- package/utils/bars.js +40 -0
- package/utils/clear_sessions.sh +54 -0
- package/{bin/hdInspect.js → utils/format_log.js} +5 -0
- package/utils/list_sessions.sh +46 -0
- package/utils/search_sessions.sh +73 -0
- package/utils/syntax_check.sh +61 -0
- package/utils/test.sh +46 -0
- package/bin/hdClear.js +0 -13
- package/bin/hdCode.js +0 -115
- package/bin/hdConnect.js +0 -230
- package/bin/hdNpm.js +0 -114
- package/bin/hdPrompt.js +0 -108
- package/examples/claude-test.js +0 -89
- package/examples/claude.js +0 -143
- package/examples/gpt.js +0 -127
- package/examples/gpt_code.js +0 -125
- package/examples/gpt_note_keeping.js +0 -117
- package/examples/grok.js +0 -119
- package/examples/grok_code.js +0 -114
- package/examples/grok_note_keeping.js +0 -111
- package/module.md +0 -189
package/lib/genericToolset.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import { SH } from '@j-o-r/sh'
|
|
1
|
+
import { SH, bashEscape } from '@j-o-r/sh'
|
|
2
2
|
import { ToolSet, env } from './index.js'
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { promises as fs } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// STANDARDIZED UTILS PATHS (hoisted constants for performance/consistency)
|
|
11
|
+
const utilsDir = path.resolve(__dirname, '..', 'utils');
|
|
12
|
+
const searchSessionsSh = path.join(utilsDir, 'search_sessions.sh');
|
|
13
|
+
const listSessionsSh = path.join(utilsDir, 'list_sessions.sh');
|
|
14
|
+
const syntaxCheckSh = path.join(utilsDir, 'syntax_check.sh');
|
|
3
15
|
|
|
4
16
|
const user = await env();
|
|
5
17
|
const environment = `
|
|
@@ -33,27 +45,6 @@ const getJSError = (errorStr) => {
|
|
|
33
45
|
return result;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
/**
|
|
37
|
-
* bash escape a string suitable as an argument on the commandline
|
|
38
|
-
* for javascript code
|
|
39
|
-
* @param {string} x
|
|
40
|
-
* @retruns {string}
|
|
41
|
-
*/
|
|
42
|
-
const bashEscape = (x) => {
|
|
43
|
-
let str = String(x).trim();
|
|
44
|
-
// the trick is to double escape escape vars first
|
|
45
|
-
str = str.replace(/\\/g, '\\\\');
|
|
46
|
-
// then add escaping for oddities
|
|
47
|
-
// Replace literal escape sequences like \n, \t, \r in quoted strings
|
|
48
|
-
str = str.replace(/(['"])\\(.)\1/g, '$1\\\\$2$1');
|
|
49
|
-
// escape $ (is a BASH var)
|
|
50
|
-
str = str.replace(/\$/g, '\\$');
|
|
51
|
-
// Escape backticks
|
|
52
|
-
str = str.replace(/`/g, '\\`');
|
|
53
|
-
// Escape quotes
|
|
54
|
-
str = str.replace(/"/g, '\\"');
|
|
55
|
-
return str;
|
|
56
|
-
}
|
|
57
48
|
tools.add(
|
|
58
49
|
'javascript_interpreter',
|
|
59
50
|
`Execute ESM ES6 javascript on \`node\`.`,
|
|
@@ -67,37 +58,21 @@ tools.add(
|
|
|
67
58
|
},
|
|
68
59
|
required: ['script']
|
|
69
60
|
},
|
|
70
|
-
// @ts-ignore
|
|
71
61
|
async (params) => {
|
|
72
|
-
// @ts-ignore
|
|
73
|
-
const script = bashEscape(params.script);
|
|
74
62
|
let response = '';
|
|
75
63
|
try {
|
|
76
|
-
|
|
77
|
-
response = await SH`node -
|
|
64
|
+
const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
65
|
+
response = await SH`cat <<'${delim}' | node --input-type=module -
|
|
66
|
+
${params.script}
|
|
67
|
+
${delim}
|
|
68
|
+
`.run();
|
|
78
69
|
} catch (e) {
|
|
79
|
-
const errorStr = e.toString()
|
|
80
|
-
response = getJSError(errorStr)
|
|
70
|
+
const errorStr = e.toString();
|
|
71
|
+
response = getJSError(errorStr);
|
|
81
72
|
}
|
|
82
73
|
return response;
|
|
83
74
|
}
|
|
84
75
|
);
|
|
85
|
-
tools.add(
|
|
86
|
-
'bash_cmd',
|
|
87
|
-
`Execute a Bash command on ${user.system}.`,
|
|
88
|
-
{
|
|
89
|
-
type: 'object',
|
|
90
|
-
properties: {
|
|
91
|
-
command: {
|
|
92
|
-
type: 'string',
|
|
93
|
-
description: 'The bash command.',
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
required: ['command']
|
|
97
|
-
},
|
|
98
|
-
// @ts-ignore
|
|
99
|
-
async (params) => (await SH`${params.command}`.run())
|
|
100
|
-
);
|
|
101
76
|
tools.add(
|
|
102
77
|
'get_user_env', // name
|
|
103
78
|
'Get the user location, name and OS environment', // desciption
|
|
@@ -113,11 +88,11 @@ tools.add(
|
|
|
113
88
|
|
|
114
89
|
tools.add(
|
|
115
90
|
'execute_bash_script',
|
|
116
|
-
'Execute a bash script.',
|
|
91
|
+
'Execute a bash script or command. (char escaping not needed)',
|
|
117
92
|
{
|
|
118
93
|
type: 'object',
|
|
119
94
|
properties: {
|
|
120
|
-
bash_script: { type: 'string', description: `
|
|
95
|
+
bash_script: { type: 'string', description: `RAW bash script (LITERAL TEXT: NO ESCAPING—output $, |, <, >, &, ", ', \\, \` , newlines, $(()), [[ ]] verbatim. Heredoc delimiter handles safely. EX: echo "$((1+1)) | grep \'<&>\"hi$USER\"\' && ls -la\`. (${user.system})` }
|
|
121
96
|
},
|
|
122
97
|
required: ['bash_script']
|
|
123
98
|
},
|
|
@@ -129,27 +104,6 @@ ${delim}
|
|
|
129
104
|
`.run()
|
|
130
105
|
}
|
|
131
106
|
);
|
|
132
|
-
/*
|
|
133
|
-
### nu shell Assumptions and Prerequisites
|
|
134
|
-
- You can install it via your package manager (e.g., on Ubuntu: `sudo apt install nushell` or download from the [official Nushell releases](https://github.com/nushell/nushell/releases)).
|
|
135
|
-
- The tool will use a heredoc approach (similar to the Bash tool) to pass the script content to `nu` for execution. This ensures multi-line scripts work reliably.
|
|
136
|
-
- The output from `nu` will be returned as-is, preserving its structured format (e.g., tables or JSON), which is beneficial for LLM consumption.o
|
|
137
|
-
*/
|
|
138
|
-
tools.add(
|
|
139
|
-
'execute_nushell_script',
|
|
140
|
-
'Execute a Nushell script.',
|
|
141
|
-
{
|
|
142
|
-
type: 'object',
|
|
143
|
-
properties: {
|
|
144
|
-
nushell_script: { type: 'string', description: `The Nushell script to execute (${user.system})` }
|
|
145
|
-
},
|
|
146
|
-
required: ['nushell_script']
|
|
147
|
-
},
|
|
148
|
-
async (params) => {
|
|
149
|
-
const script = bashEscape(params.nushell_script);
|
|
150
|
-
return await SH`nu -c "${script}"`.run()
|
|
151
|
-
}
|
|
152
|
-
);
|
|
153
107
|
|
|
154
108
|
tools.add(
|
|
155
109
|
'send_email',
|
|
@@ -176,11 +130,11 @@ ${delim}
|
|
|
176
130
|
);
|
|
177
131
|
tools.add(
|
|
178
132
|
'open_link',
|
|
179
|
-
'Open an url in local user environment.',
|
|
133
|
+
'Open an url or file in the local user environment. (xdg-open)',
|
|
180
134
|
{
|
|
181
135
|
type: 'object',
|
|
182
136
|
properties: {
|
|
183
|
-
url: { type: 'string', description: '
|
|
137
|
+
url: { type: 'string', description: 'file | URL' }
|
|
184
138
|
},
|
|
185
139
|
required: ['url']
|
|
186
140
|
},
|
|
@@ -190,18 +144,17 @@ tools.add(
|
|
|
190
144
|
);
|
|
191
145
|
tools.add(
|
|
192
146
|
'execute_remote_script',
|
|
193
|
-
'Execute
|
|
147
|
+
'Execute bash script on a remote machine via SSH.',
|
|
194
148
|
{
|
|
195
149
|
type: 'object',
|
|
196
150
|
properties: {
|
|
197
|
-
type: { type: 'string', enum: ['bash','nu','python','javascript'], description: 'Type of script (default: bash)' },
|
|
198
151
|
url: { type: 'string', description: 'SSH URL, e.g., ssh://user@host or ssh://user@host:port' },
|
|
199
|
-
script: { type: 'string', description: '
|
|
152
|
+
script: { type: 'string', description: 'RAW script code to execute remotely (no escaping needed)' }
|
|
200
153
|
},
|
|
201
154
|
required: ['url', 'script']
|
|
202
155
|
},
|
|
203
156
|
async (params) => {
|
|
204
|
-
const { url,
|
|
157
|
+
const { url, script } = params;
|
|
205
158
|
if (!url.startsWith('ssh://')) throw new Error('Invalid SSH URL');
|
|
206
159
|
const withoutProto = url.slice(6);
|
|
207
160
|
const parts = withoutProto.split(':');
|
|
@@ -212,23 +165,240 @@ tools.add(
|
|
|
212
165
|
port = parseInt(parts[1]);
|
|
213
166
|
}
|
|
214
167
|
const [user, host] = userHost.split('@');
|
|
215
|
-
if (!user || !host) throw new Error('Invalid SSH URL format');
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
command = `nu -c "${escaped}"`;
|
|
220
|
-
} else {
|
|
221
|
-
const interpreters = { bash: 'bash', python: 'python3', javascript: 'node' };
|
|
222
|
-
const interpreter = interpreters[type];
|
|
223
|
-
if (!interpreter) throw new Error('Unsupported type');
|
|
224
|
-
const delim = `END_REMOTE_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
225
|
-
command = `${interpreter} <<'${delim}'
|
|
168
|
+
if (!user || !host) throw new Error('Invalid SSH URL format: use ssh://user@host[:port]');
|
|
169
|
+
|
|
170
|
+
const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
171
|
+
return await SH`ssh -p ${port} ${user}@${host} bash <<'${delim}'
|
|
226
172
|
${script}
|
|
227
173
|
${delim}
|
|
228
|
-
|
|
174
|
+
`.run();
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
tools.add(
|
|
179
|
+
'history_search',
|
|
180
|
+
`Search previous LLM chat sessions or list them hierarchically.
|
|
181
|
+
Example query: "(todo|task)" or "package.json".
|
|
182
|
+
Searches filenames & content in .cache/[app]/[prompt]/sessions/*.ndjson (case-insensitive regex, with context).
|
|
183
|
+
Omit query (or use empty string) to list sessions (see utils/list_sessions.sh).`,
|
|
184
|
+
{
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
query: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
description: `Search query or regex (quoted for multi-word/regex). Omit or empty for list mode.`
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
required: []
|
|
193
|
+
},
|
|
194
|
+
async (params) => {
|
|
195
|
+
if (typeof params.query === 'string' && params.query.trim() !== '') {
|
|
196
|
+
const escapedQuery = bashEscape(params.query);
|
|
197
|
+
return await SH`${searchSessionsSh} "${escapedQuery}"`.run();
|
|
198
|
+
} else {
|
|
199
|
+
return await SH`${listSessionsSh}`.run();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
);
|
|
203
|
+
tools.add(
|
|
204
|
+
'read_file',
|
|
205
|
+
'Read the raw content of a file strictly within the current working directory (CWD). Paths must be relative (no leading /, no ..).',
|
|
206
|
+
{
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
file: {
|
|
210
|
+
type: 'string',
|
|
211
|
+
description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed). cwd: ${user.cwd}`
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
required: ['file']
|
|
215
|
+
},
|
|
216
|
+
async (params) => {
|
|
217
|
+
const file = params.file?.trim();
|
|
218
|
+
if (typeof file !== 'string' || !file) {
|
|
219
|
+
throw new Error('Valid relative file path required.');
|
|
220
|
+
}
|
|
221
|
+
if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
222
|
+
throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
|
|
223
|
+
}
|
|
224
|
+
const resolvedPath = path.resolve(process.cwd(), file);
|
|
225
|
+
if (!resolvedPath.startsWith(process.cwd())) {
|
|
226
|
+
throw new Error(`Path '${file}' escapes CWD scope.`);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const content = await fs.readFile(resolvedPath, 'utf8');
|
|
230
|
+
return content;
|
|
231
|
+
} catch (e) {
|
|
232
|
+
throw new Error(`Failed to read '${file}': ${e.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
tools.add(
|
|
238
|
+
'write_file',
|
|
239
|
+
'Write raw content to a file strictly within the current working directory (CWD). Paths must be relative (no leading /, no ..). Content written as-is (no escaping). **AUTO-VALIDATES** JS/Python/Bash/JSON/etc. via `utils/syntax_check.sh`; chmod +x shebangs. Retries syntax errors force LLM fix.',
|
|
240
|
+
{
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
file: {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: `Relative path to the file within CWD, e.g., 'path/to/file.txt' (no escaping needed).`
|
|
246
|
+
},
|
|
247
|
+
content: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: `Raw content to write (as-is, no char escaping needed; supports newlines, $, |, <, >, &, ", ', \\, etc.).`
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
required: ['file', 'content']
|
|
253
|
+
},
|
|
254
|
+
async (params) => {
|
|
255
|
+
const file = params.file?.trim();
|
|
256
|
+
const content = params.content ?? '';
|
|
257
|
+
if (typeof file !== 'string' || !file) {
|
|
258
|
+
throw new Error('Valid relative file path required.');
|
|
259
|
+
}
|
|
260
|
+
if (file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
261
|
+
throw new Error('Path must be relative within CWD only (no `/`, `..`, or `\\\\`).');
|
|
262
|
+
}
|
|
263
|
+
const resolvedPath = path.resolve(process.cwd(), file);
|
|
264
|
+
if (!resolvedPath.startsWith(process.cwd())) {
|
|
265
|
+
throw new Error(`Path '${file}' escapes CWD scope.`);
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
await fs.writeFile(resolvedPath, content, 'utf8');
|
|
269
|
+
|
|
270
|
+
// AUTO-VALIDATE: Multi-lang syntax check via utils/syntax_check.sh + chmod shebang
|
|
271
|
+
const hasShebang = content.startsWith('#!');
|
|
272
|
+
|
|
273
|
+
let validationMsg = '';
|
|
274
|
+
try {
|
|
275
|
+
await SH`${syntaxCheckSh} ${[resolvedPath]}`.run();
|
|
276
|
+
validationMsg = ' ✓ Multi-lang syntax OK';
|
|
277
|
+
} catch (e) {
|
|
278
|
+
const errPreview = content.slice(0, 1000).split('\n').slice(0, 20).join('\n');
|
|
279
|
+
throw new Error(`❌ SYNTAX ERROR in '${file}' (syntax_check.sh failed):\n${e.message}\n\nPREVIEW:\n${errPreview}\n\n... Fix syntax (quotes/backticks) and retry write_file.`);
|
|
280
|
+
}
|
|
281
|
+
if (hasShebang) {
|
|
282
|
+
await SH`chmod +x ${[resolvedPath]}`.run();
|
|
283
|
+
validationMsg += ' ✓ chmod +x';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return `Successfully wrote to '${file}' (${Buffer.byteLength(content, 'utf8')} bytes).${validationMsg}`;
|
|
287
|
+
} catch (e) {
|
|
288
|
+
throw new Error(`Failed to write '${file}': ${e.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
tools.add(
|
|
294
|
+
'syntax_check',
|
|
295
|
+
'Standalone syntax validation for files (JS/Python/Bash/JSON/etc.) via utils/syntax_check.sh. Detects lang from ext/shebang.',
|
|
296
|
+
{
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
file: {
|
|
300
|
+
type: 'string',
|
|
301
|
+
description: `Relative path to the file within CWD.`
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
required: ['file']
|
|
305
|
+
},
|
|
306
|
+
async (params) => {
|
|
307
|
+
const file = params.file?.trim();
|
|
308
|
+
if (typeof file !== 'string' || !file) {
|
|
309
|
+
throw new Error('Valid relative file path required.');
|
|
310
|
+
}
|
|
311
|
+
const resolvedPath = path.resolve(process.cwd(), file);
|
|
312
|
+
if (!resolvedPath.startsWith(process.cwd())) {
|
|
313
|
+
throw new Error(`Path '${file}' escapes CWD scope.`);
|
|
314
|
+
}
|
|
315
|
+
return await SH`${syntaxCheckSh} ${[resolvedPath]}`.run();
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
tools.add(
|
|
320
|
+
'memory_write',
|
|
321
|
+
`Persist agent memory for tasks, errors, or user preferences to .cache/memory.ndjson in CWD (${user.cwd}). Use to store decisions, tasks, errors, or prefs for later recall. Agent should use this to avoid repeating work or token burn on loops.`,
|
|
322
|
+
{
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
category: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
enum: ['tasks', 'errors', 'prefs'],
|
|
328
|
+
description: 'Category: one of "tasks", "errors", "prefs"'
|
|
329
|
+
},
|
|
330
|
+
content: {
|
|
331
|
+
type: 'string',
|
|
332
|
+
description: 'Detailed content (e.g., "Pending task: implement runWithMemory optimization", "Error: loop burning tokens", "Pref: temperature=0.2 for decisions")'
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
required: ['category', 'content']
|
|
336
|
+
},
|
|
337
|
+
async ({ category, content }) => {
|
|
338
|
+
if (!['tasks', 'errors', 'prefs'].includes(category)) {
|
|
339
|
+
throw new Error(`Invalid category '${category}'. Must be 'tasks', 'errors', or 'prefs'.`);
|
|
340
|
+
}
|
|
341
|
+
const memoryPath = path.join(process.cwd(), '.cache', 'memory.ndjson');
|
|
342
|
+
const dir = path.dirname(memoryPath);
|
|
343
|
+
try {
|
|
344
|
+
await fs.mkdir(dir, { recursive: true });
|
|
345
|
+
} catch (e) {
|
|
346
|
+
// Dir likely exists
|
|
347
|
+
}
|
|
348
|
+
const entry = {
|
|
349
|
+
timestamp: new Date().toISOString(),
|
|
350
|
+
category,
|
|
351
|
+
content: content.trim()
|
|
352
|
+
};
|
|
353
|
+
await fs.appendFile(memoryPath, JSON.stringify(entry) + '\n', 'utf8');
|
|
354
|
+
return `✓ Memory stored: [${category}] ${content.length > 50 ? content.slice(0, 47) + '...' : content}`;
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
tools.add(
|
|
359
|
+
'memory_recall',
|
|
360
|
+
`Retrieve stored agent memories (tasks, errors, prefs) from .cache/memory.ndjson in CWD (${user.cwd}). Use before acting to check prior decisions/tasks/errors/prefs. Query by keyword or category; empty lists recent.`,
|
|
361
|
+
{
|
|
362
|
+
type: 'object',
|
|
363
|
+
properties: {
|
|
364
|
+
query: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Keyword, category (tasks/errors/prefs), or phrase to filter (case-insensitive). Empty/omit lists last 20.'
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
required: []
|
|
370
|
+
},
|
|
371
|
+
async (params = {}) => {
|
|
372
|
+
const memoryPath = path.join(process.cwd(), '.cache', 'memory.ndjson');
|
|
373
|
+
let content;
|
|
374
|
+
try {
|
|
375
|
+
content = await fs.readFile(memoryPath, 'utf8');
|
|
376
|
+
} catch (e) {
|
|
377
|
+
return 'No memories stored yet. Use memory_write first.';
|
|
378
|
+
}
|
|
379
|
+
const lines = content.trim().split('\n').filter(l => l.trim());
|
|
380
|
+
const memories = [];
|
|
381
|
+
for (const line of lines) {
|
|
382
|
+
try {
|
|
383
|
+
memories.push(JSON.parse(line));
|
|
384
|
+
} catch {
|
|
385
|
+
// Skip invalid lines
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!params.query || !params.query.toString().trim()) {
|
|
389
|
+
const recent = memories.slice(-20).reverse();
|
|
390
|
+
return recent.length
|
|
391
|
+
? 'Recent memories:\n' + recent.map(m => `• ${m.timestamp.slice(0, 19).replace('T', ' ')} [${m.category}] ${m.content}`).join('\n')
|
|
392
|
+
: 'No memories stored.';
|
|
229
393
|
}
|
|
230
|
-
|
|
394
|
+
const q = params.query.toString().toLowerCase();
|
|
395
|
+
const matches = memories.filter(m =>
|
|
396
|
+
m.category.toLowerCase().includes(q) || m.content.toLowerCase().includes(q)
|
|
397
|
+
);
|
|
398
|
+
return matches.length
|
|
399
|
+
? `Matches for "${params.query}":\n` + matches.slice(0, 20).map(m => `• ${m.timestamp.slice(0, 19).replace('T', ' ')} [${m.category}] ${m.content}`).join('\n')
|
|
400
|
+
: `No memories match "${params.query}".`;
|
|
231
401
|
}
|
|
232
402
|
);
|
|
233
403
|
|
|
234
|
-
export default tools
|
|
404
|
+
export default tools
|