@j-o-r/hello-dave 0.0.8 → 0.0.9
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/TODO.md +14 -3
- package/agents/code_agent.js +9 -9
- package/bin/dave.js +2 -2
- package/docs/agent-dave-websocket-protocol.md +180 -0
- package/lib/genericToolset.js +107 -21
- package/package.json +1 -1
package/TODO.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## TODO (high priority pending)
|
|
2
|
-
(
|
|
2
|
+
- [ ] Create a personal CDN on VPS using SSH + Nginx to serve static assets (images, HTML, JS) so that Grok / browse_page tools can directly access them without crawling or conversion issues
|
|
3
|
+
- [ ] Subtask: Set up VPS and SSH access
|
|
4
|
+
- [ ] Subtask: Install and configure Nginx for static file serving
|
|
5
|
+
- [ ] Subtask: Implement security measures (e.g., firewall, SSL/TLS, access controls)
|
|
6
|
+
- [ ] Subtask: Upload and organize static assets
|
|
7
|
+
- [ ] Subtask: Test with previous image test case and verify direct access via tools
|
|
8
|
+
- [ ] Create publish_agent.js in agents/ for secure deployment of documents to remote VPS (Nginx + ~/htdocs or /var/www/htdocs). Features: persistent config for multiple VPS (SSH host, port, user, htdocs path, http base URL), ask for missing endpoints on first use, generate long/obscure/randomized filenames for privacy (especially sensitive files), clean old/unused files in htdocs, rsync or scp with safety, support for static publishing of docs/markdown/html. Integrate with memory_agent for config persistence if possible. High privacy focus.
|
|
3
9
|
|
|
4
10
|
## In Progress
|
|
5
11
|
(none)
|
|
@@ -8,6 +14,11 @@
|
|
|
8
14
|
(none)
|
|
9
15
|
|
|
10
16
|
## Done
|
|
11
|
-
|
|
17
|
+
- [x] Document and review the "agent dave websocket protocol". Focus on communication between lib/AgentServer.js (central hub), lib/AgentClient.js (agent connections), and lib/wsIO.js (user interaction). Include description of the protocol, sequence diagrams if possible, and optimization suggestions.
|
|
18
|
+
- [x] Subtask: Review and analyze source files (AgentServer.js, AgentClient.js, wsIO.js)
|
|
19
|
+
- [x] Subtask: Summarize findings from review - Protocol uses JSON messages with 'action' from fixed ACTIONS list, bidirectional query/response with IDs for matching, introduction for registration, user_* for CLI/WS interaction, reset handling with epoch, pending response map with timeout. Suggestion: Add sequence diagram in Mermaid and document in docs/agent-dave-websocket-protocol.md.
|
|
20
|
+
- [x] Subtask: Document the protocol structure and message formats
|
|
21
|
+
- [x] Subtask: Create sequence diagrams for key interactions (Mermaid diagrams added to the new docs file)
|
|
22
|
+
- [x] Subtask: Identify and suggest optimizations for performance and reliability (detailed section added with 6 categories of improvements)
|
|
12
23
|
|
|
13
|
-
Older tasks archived on 2026-04-
|
|
24
|
+
Older tasks archived on 2026-04-20 to docs/todo-archive-v0.0.9.md. Previous archives: docs/todo-archive-v0.0.8.md (2026-04-15), docs/todo-archive.md (2026-04-13).
|
package/agents/code_agent.js
CHANGED
|
@@ -24,7 +24,7 @@ if (args['secret']) {
|
|
|
24
24
|
// Set properties only if provided via command line (except model which has default)
|
|
25
25
|
if (args['model'] || true) { // model gets default value
|
|
26
26
|
// @ts-ignore
|
|
27
|
-
options.model = args['model'] || 'grok-4-
|
|
27
|
+
options.model = args['model'] || 'grok-4.20-reasoning';
|
|
28
28
|
}
|
|
29
29
|
// if (args['temperature']) {
|
|
30
30
|
options.temperature = 0.2;
|
|
@@ -35,14 +35,14 @@ if (args['tokens']) {
|
|
|
35
35
|
if (args['top_p']) {
|
|
36
36
|
options.top_p = parseFloat(args['top_p']);
|
|
37
37
|
}
|
|
38
|
-
const reasoning = args['reasoning'] ? args['reasoning'] : 'medium';
|
|
39
|
-
if (reasoning) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
38
|
+
// const reasoning = args['reasoning'] ? args['reasoning'] : 'medium';
|
|
39
|
+
// if (reasoning) {
|
|
40
|
+
// options.reasoning = {
|
|
41
|
+
// // @ts-ignore
|
|
42
|
+
// effort:reasoning,
|
|
43
|
+
// summary: 'auto'
|
|
44
|
+
// }
|
|
45
|
+
// }
|
|
46
46
|
const toolsetMode = 'auto';
|
|
47
47
|
const contextWindow = args['context'] ? parseInt(args['context']) : 2565000;
|
|
48
48
|
|
package/bin/dave.js
CHANGED
|
@@ -118,9 +118,9 @@ if (args.clear) {
|
|
|
118
118
|
};
|
|
119
119
|
options.tools.push({ type: 'web_search' });
|
|
120
120
|
options.tools.push({ type: 'x_search' });
|
|
121
|
-
options.model = args.model || 'grok-4-
|
|
121
|
+
options.model = args.model || 'grok-4.20-reasoning';
|
|
122
122
|
options.temperature = 0.2;
|
|
123
|
-
options.reasoning = { effort: 'medium', summary: 'auto' };
|
|
123
|
+
// options.reasoning = { effort: 'medium', summary: 'auto' };
|
|
124
124
|
|
|
125
125
|
const prompt = `
|
|
126
126
|
Respond briefly and directly, using minimal words. Reason step-by-step first. Focus solely on core point; avoid elaboration or follow-ups. If unclear, ask clarifying questions before proceeding.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Agent Dave WebSocket Protocol
|
|
2
|
+
|
|
3
|
+
**Version**: 0.0.8 (as of April 2026)
|
|
4
|
+
**Repository**: https://codeberg.org/duin/hello-dave
|
|
5
|
+
**Core Files**:
|
|
6
|
+
- `lib/AgentServer.js` — Central hub, registers agents as dynamic tools, handles user interactions and sessions.
|
|
7
|
+
- `lib/AgentClient.js` — Agent-side wrapper (Prompt + ToolSet), queue-based message processor, auto-reconnect, epoch-based reset handling.
|
|
8
|
+
- `lib/wsIO.js` — One-shot WS client for CLI tools (`dave --connect`, `wsio()` helper).
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The protocol enables **multi-agent collaboration** over WebSocket.
|
|
13
|
+
|
|
14
|
+
- A central **AgentServer** listens on `ws://127.0.0.1:<port>/ws`.
|
|
15
|
+
- **AgentClient** instances connect, introduce themselves, and get registered as **dynamic tools** in the server's `ToolSet`.
|
|
16
|
+
- The server exposes these agents to the main Prompt, allowing the LLM to call them like any other tool (`agent_query` / `agent_response`).
|
|
17
|
+
- **User/CLI interaction** uses `user_*` actions (via `wsIO.js` or `dave` CLI).
|
|
18
|
+
- All messages are **JSON** objects with an `action` field validated against a fixed list.
|
|
19
|
+
- **Authentication**: `?wssrc_id=<secret>` query param (plain in AgentServer, base64 in wsIO.js).
|
|
20
|
+
- **Reset handling**: Broadcast `reset`, clients use an `#epoch` counter to invalidate in-flight work.
|
|
21
|
+
- **Response matching**: Uses `id` (timestamp-based) + pending map on server.
|
|
22
|
+
|
|
23
|
+
This design turns specialized agents (code, todo, memory, readme, etc.) into callable tools while supporting direct CLI, interactive REPL, and one-shot queries.
|
|
24
|
+
|
|
25
|
+
## Message Format
|
|
26
|
+
|
|
27
|
+
Every message is a JSON object:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"action": "one_of_the_actions_below",
|
|
32
|
+
"content": "string or object (varies)",
|
|
33
|
+
"id": "unique_timestamp_string_or_number",
|
|
34
|
+
"name"?: "agent_name_on_introduction"
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Supported Actions (ACTIONS array in AgentServer.js)
|
|
39
|
+
|
|
40
|
+
**Agent ↔ Server (Tool Calls)**
|
|
41
|
+
- `agent_introduction` — Agent registers itself (sends `name` + `description`).
|
|
42
|
+
- `agent_query` — Server calls an agent tool with natural language `content` (query).
|
|
43
|
+
- `agent_response` — Agent returns result.
|
|
44
|
+
- `agent_error` — Agent reports failure.
|
|
45
|
+
|
|
46
|
+
**User/CLI ↔ Server**
|
|
47
|
+
- `user_introduction` — CLI connects (sent by wsIO.js).
|
|
48
|
+
- `user_request` — User query to main prompt.
|
|
49
|
+
- `server_response` — Final answer from main prompt.
|
|
50
|
+
- `server_error` — Error from main prompt.
|
|
51
|
+
- `user_reset` — Reset current session.
|
|
52
|
+
- `user_sessionlist` — Request list of saved sessions.
|
|
53
|
+
- `user_loadsession` — Load a previous session.
|
|
54
|
+
- `user_info` — Get server/prompt/session info.
|
|
55
|
+
|
|
56
|
+
**Server → Clients (broadcasts/responses)**
|
|
57
|
+
- `reset` — Broadcast to all agents on reset.
|
|
58
|
+
- `server_reset`, `server_sessionlist`, `server_sessionloaded`, `server_info` — Responses to user actions.
|
|
59
|
+
|
|
60
|
+
Unknown actions cause immediate client disconnection.
|
|
61
|
+
|
|
62
|
+
## Key Interactions & Sequence Diagrams
|
|
63
|
+
|
|
64
|
+
### 1. Agent Registration & Tool Call
|
|
65
|
+
|
|
66
|
+
```mermaid
|
|
67
|
+
sequenceDiagram
|
|
68
|
+
participant AgentClient
|
|
69
|
+
participant AgentServer
|
|
70
|
+
participant Prompt/ToolSet
|
|
71
|
+
|
|
72
|
+
AgentClient->>AgentServer: WS connect + ?wssrc_id=secret
|
|
73
|
+
AgentClient->>AgentServer: {action: "agent_introduction", name: "code", content: "Code execution agent..."}
|
|
74
|
+
AgentServer->>ToolSet: add("code", description, schema, handler)
|
|
75
|
+
Note over ToolSet,AgentServer: Tool now available to LLM
|
|
76
|
+
|
|
77
|
+
Prompt->>ToolSet: LLM decides to call "code" with query
|
|
78
|
+
ToolSet->>AgentServer: handler(query) → Promise
|
|
79
|
+
AgentServer->>AgentClient: {action: "agent_query", id: "1745...", content: "Write a bash script..."}
|
|
80
|
+
AgentClient->>AgentClient: #queue.push(), #processOne()
|
|
81
|
+
AgentClient->>Prompt: prompt.call(query)
|
|
82
|
+
AgentClient->>AgentServer: {action: "agent_response", id: "1745...", content: "```bash ...```"}
|
|
83
|
+
AgentServer->>Prompt: resolve(promise)
|
|
84
|
+
Prompt->>LLM: Continue with tool result
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 2. User One-Shot Query (via wsIO.js or dave --connect)
|
|
88
|
+
|
|
89
|
+
```mermaid
|
|
90
|
+
sequenceDiagram
|
|
91
|
+
participant CLI (wsIO)
|
|
92
|
+
participant AgentServer
|
|
93
|
+
participant Prompt
|
|
94
|
+
|
|
95
|
+
CLI->>AgentServer: WS connect + wssrc_id
|
|
96
|
+
CLI->>AgentServer: {action: "user_introduction", id: X}
|
|
97
|
+
CLI->>AgentServer: {action: "user_request", id: Y, content: "Hello Dave"}
|
|
98
|
+
AgentServer->>Prompt: prompt.call("Hello Dave")
|
|
99
|
+
Prompt->>LLM: Full reasoning + tool calls (may call registered agents)
|
|
100
|
+
Prompt-->>AgentServer: Final content
|
|
101
|
+
AgentServer->>CLI: {action: "server_response", id: Y, content: "..."}
|
|
102
|
+
CLI->>CLI: resolve promise, close WS
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Reset Handling
|
|
106
|
+
|
|
107
|
+
```mermaid
|
|
108
|
+
sequenceDiagram
|
|
109
|
+
participant User
|
|
110
|
+
participant AgentServer
|
|
111
|
+
participant AgentClient
|
|
112
|
+
|
|
113
|
+
User->>AgentServer: {action: "user_reset"}
|
|
114
|
+
AgentServer->>Prompt: prompt.reset()
|
|
115
|
+
AgentServer-->>All Agents: broadcast {action: "reset"}
|
|
116
|
+
AgentClient->>AgentClient: #epoch++, #queue=[], prompt.reset()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Implementation Details
|
|
120
|
+
|
|
121
|
+
- **Server-side pending responses**: `pendingResponses` Map keyed by `${conn.id}:${msg.id}` with timeout via `setInterval` (30s intervals, max 20 → 10min).
|
|
122
|
+
- **Client-side queue**: Strict sequential processing (`#processing` guard, 2s polling interval by default). Prevents race conditions in tool calls.
|
|
123
|
+
- **Auto-reconnect**: AgentClient retries every 5s on close.
|
|
124
|
+
- **Session Management**: Integrated with `Session.js` for `user_sessionlist` / `user_loadsession`.
|
|
125
|
+
- **wsIO.js specifics**: Sends both `user_introduction` + action in one connection, matches response by `id`, then closes. Uses base64 secret.
|
|
126
|
+
|
|
127
|
+
## Optimizations & Suggestions
|
|
128
|
+
|
|
129
|
+
**Current Strengths**
|
|
130
|
+
- Simple JSON, no complex framing.
|
|
131
|
+
- Epoch prevents stale responses after reset.
|
|
132
|
+
- Dynamic tool registration — agents can be added at runtime.
|
|
133
|
+
|
|
134
|
+
**Potential Improvements**
|
|
135
|
+
1. **Performance**:
|
|
136
|
+
- Replace polling interval in AgentClient with event-driven queue (e.g. `setImmediate` or microtask after each message).
|
|
137
|
+
- Use a proper request-response correlation library or WebSocket subprotocol.
|
|
138
|
+
- Add backpressure handling for high-volume tool calls.
|
|
139
|
+
|
|
140
|
+
2. **Reliability**:
|
|
141
|
+
- Heartbeat/ping-pong to detect dead connections faster than timeout.
|
|
142
|
+
- Exponential backoff on reconnect.
|
|
143
|
+
- Persistent message IDs (UUID instead of timestamp).
|
|
144
|
+
- Schema validation for all incoming messages (e.g. Zod or JSON Schema).
|
|
145
|
+
|
|
146
|
+
3. **Observability**:
|
|
147
|
+
- Add structured logging (instead of console.log).
|
|
148
|
+
- Metrics: active clients, avg response time, error rate.
|
|
149
|
+
- OpenTelemetry or Prometheus integration.
|
|
150
|
+
|
|
151
|
+
4. **Security**:
|
|
152
|
+
- Current secret is weak (query param). Consider JWT or mTLS for production.
|
|
153
|
+
- Rate limiting per connection.
|
|
154
|
+
- Validate `content` size.
|
|
155
|
+
|
|
156
|
+
5. **Extensibility**:
|
|
157
|
+
- Support binary messages for large data (e.g. images, files).
|
|
158
|
+
- Add `agent_stream` for streaming responses from agents.
|
|
159
|
+
- Formalize as a subprotocol (`Sec-WebSocket-Protocol: agent-dave-v1`).
|
|
160
|
+
|
|
161
|
+
6. **Documentation**:
|
|
162
|
+
- Generate Mermaid diagrams automatically from code comments.
|
|
163
|
+
- Add protocol test suite in `scenarios/`.
|
|
164
|
+
|
|
165
|
+
**Next Steps** (from TODO):
|
|
166
|
+
- Complete sequence diagrams (expand the Mermaid examples above).
|
|
167
|
+
- Identify specific performance bottlenecks via profiling.
|
|
168
|
+
- Create formal spec with error codes and versioning.
|
|
169
|
+
|
|
170
|
+
## References
|
|
171
|
+
|
|
172
|
+
- `lib/AgentServer.js`
|
|
173
|
+
- `lib/AgentClient.js`
|
|
174
|
+
- `lib/wsIO.js`
|
|
175
|
+
- `agents/spawn_agent.js` (uses this protocol for hybrid/server/client modes)
|
|
176
|
+
- `docs/multi-agent-clusters.md`
|
|
177
|
+
- `docs/prompt/spawn_agent.md`
|
|
178
|
+
|
|
179
|
+
*Last updated: April 20, 2026*
|
|
180
|
+
*This document was generated as part of the TODO task "Document and review the 'agent dave websocket protocol'."*
|
package/lib/genericToolset.js
CHANGED
|
@@ -44,8 +44,82 @@ const getJSError = (errorStr) => {
|
|
|
44
44
|
}
|
|
45
45
|
return result;
|
|
46
46
|
};
|
|
47
|
+
// /**
|
|
48
|
+
// * Try to prevent double escaping by LLMS models
|
|
49
|
+
// * When a string stays a string it is probably double escaped
|
|
50
|
+
// * @parameter {string} s
|
|
51
|
+
// * @returns {string}
|
|
52
|
+
// */
|
|
53
|
+
// const guessOverEscaping = (s) => {
|
|
54
|
+
// let test;
|
|
55
|
+
// if (typeof s === 'string') {
|
|
56
|
+
// try {
|
|
57
|
+
// test = JSON.parse(s);
|
|
58
|
+
// } catch (_e) { }
|
|
59
|
+
// if (typeof test === 'string') {
|
|
60
|
+
// return test;
|
|
61
|
+
// }
|
|
62
|
+
// }
|
|
63
|
+
// return s;
|
|
64
|
+
// }
|
|
47
65
|
|
|
48
66
|
/**
|
|
67
|
+
* Try to prevent double escaping by LLMs.
|
|
68
|
+
* Handles common patterns like \"path\", \\\"path\\\", JSON strings,
|
|
69
|
+
* and multiple layers while being much safer on complex bash/JS scripts.
|
|
70
|
+
*/
|
|
71
|
+
const guessOverEscaping = (s) => {
|
|
72
|
+
if (typeof s !== 'string') return s;
|
|
73
|
+
|
|
74
|
+
let current = s.trim();
|
|
75
|
+
const seen = new Set();
|
|
76
|
+
let iterations = 0;
|
|
77
|
+
const MAX_ITER = 6; // safety limit
|
|
78
|
+
|
|
79
|
+
while (iterations < MAX_ITER && !seen.has(current)) {
|
|
80
|
+
seen.add(current);
|
|
81
|
+
iterations++;
|
|
82
|
+
|
|
83
|
+
// 1. Most reliable: JSON.parse (undoes proper JSON string escaping)
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(current);
|
|
86
|
+
if (typeof parsed === 'string') {
|
|
87
|
+
current = parsed;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
} catch (e) { }
|
|
91
|
+
|
|
92
|
+
// 2. Specifically strip the pattern you saw: \"...\" (including the backslashes)
|
|
93
|
+
if (current.startsWith('\\"') && current.endsWith('\\"')) {
|
|
94
|
+
current = current.slice(2, -2);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Strip normal outer quotes, but protect JSON objects/arrays
|
|
99
|
+
if (current.startsWith('"') && current.endsWith('"') && current.length > 2) {
|
|
100
|
+
const inner = current.slice(1, -1);
|
|
101
|
+
const trimmedInner = inner.trim();
|
|
102
|
+
if (!trimmedInner.startsWith('{') && !trimmedInner.startsWith('[')) {
|
|
103
|
+
current = inner;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 4. Conservative global unescape of \" → "
|
|
109
|
+
// Only applied to short strings without newlines (i.e. not full scripts)
|
|
110
|
+
if (!current.includes('\n') && (current.match(/\\"/g) || []).length >= 1) {
|
|
111
|
+
const lessEscaped = current.replace(/\\"/g, '"');
|
|
112
|
+
if (lessEscaped !== current) {
|
|
113
|
+
current = lessEscaped;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
break; // no more productive changes
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return current;
|
|
122
|
+
};/**
|
|
49
123
|
* @module lib/genericToolset
|
|
50
124
|
* Secure utility tools for code execution, file I/O, system ops. Pre-populated ToolSet.
|
|
51
125
|
*
|
|
@@ -71,9 +145,10 @@ tools.add(
|
|
|
71
145
|
async (params) => {
|
|
72
146
|
let response = '';
|
|
73
147
|
try {
|
|
148
|
+
const script = guessOverEscaping(params.script);
|
|
74
149
|
const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
75
150
|
response = await SH`cat <<'${delim}' | node --input-type=module -
|
|
76
|
-
${
|
|
151
|
+
${script}
|
|
77
152
|
${delim}
|
|
78
153
|
`.run();
|
|
79
154
|
} catch (e) {
|
|
@@ -110,14 +185,15 @@ tools.add(
|
|
|
110
185
|
},
|
|
111
186
|
async (params) => {
|
|
112
187
|
const timeoutSec = Number(params.timeout ?? 300);
|
|
188
|
+
const bash_script = guessOverEscaping(params.bash_script);
|
|
113
189
|
if (isNaN(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
114
190
|
const timeout = timeoutSec * 1000;
|
|
115
191
|
// return await SH`bash`.options({ timeout: timeoutMs }).run(params.bash_script);
|
|
116
|
-
|
|
192
|
+
const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
117
193
|
return await SH`bash <<'${delim}'
|
|
118
|
-
${
|
|
194
|
+
${bash_script}
|
|
119
195
|
${delim}
|
|
120
|
-
`.options({timeout}).run();
|
|
196
|
+
`.options({ timeout }).run();
|
|
121
197
|
|
|
122
198
|
}
|
|
123
199
|
);
|
|
@@ -135,12 +211,15 @@ tools.add(
|
|
|
135
211
|
required: ['to', 'subject', 'body']
|
|
136
212
|
},
|
|
137
213
|
async (params) => {
|
|
214
|
+
const to = guessOverEscaping(params.to);
|
|
215
|
+
const subject = guessOverEscaping(params.subject);
|
|
216
|
+
const body = guessOverEscaping(params.body);
|
|
138
217
|
const delim = `END_EMAIL_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
139
218
|
return await SH`msmtp ${params.to} <<'${delim}'
|
|
140
|
-
To: ${
|
|
141
|
-
Subject: ${
|
|
219
|
+
To: ${to}
|
|
220
|
+
Subject: ${subject}
|
|
142
221
|
|
|
143
|
-
${
|
|
222
|
+
${body}
|
|
144
223
|
${delim}
|
|
145
224
|
`.run();
|
|
146
225
|
}
|
|
@@ -156,7 +235,10 @@ tools.add(
|
|
|
156
235
|
},
|
|
157
236
|
required: ['url']
|
|
158
237
|
},
|
|
159
|
-
async (params) =>
|
|
238
|
+
async (params) => {
|
|
239
|
+
let url = guessOverEscaping(params.url?.trim());
|
|
240
|
+
return await SH`xdg-open ${[url]}`.run();
|
|
241
|
+
}
|
|
160
242
|
);
|
|
161
243
|
|
|
162
244
|
tools.add(
|
|
@@ -176,7 +258,10 @@ tools.add(
|
|
|
176
258
|
required: ['url', 'script']
|
|
177
259
|
},
|
|
178
260
|
async (params) => {
|
|
179
|
-
|
|
261
|
+
let { url, script } = params;
|
|
262
|
+
url = guessOverEscaping(url);
|
|
263
|
+
script = guessOverEscaping(script);
|
|
264
|
+
|
|
180
265
|
const timeoutSec = Number(params.timeout ?? 30);
|
|
181
266
|
if (isNaN(timeoutSec) || timeoutSec <= 0) throw new Error('Invalid timeout');
|
|
182
267
|
const timeoutMs = timeoutSec * 1000;
|
|
@@ -203,7 +288,8 @@ tools.add(
|
|
|
203
288
|
},
|
|
204
289
|
async (params) => {
|
|
205
290
|
if (typeof params.query === 'string' && params.query.trim()) {
|
|
206
|
-
|
|
291
|
+
const q = guessOverEscaping(params.query);
|
|
292
|
+
return await SH`${searchSessionsSh} "${bashEscape(q)}"`.run();
|
|
207
293
|
}
|
|
208
294
|
return await SH`${listSessionsSh}`.run();
|
|
209
295
|
}
|
|
@@ -220,7 +306,7 @@ tools.add(
|
|
|
220
306
|
required: ['file']
|
|
221
307
|
},
|
|
222
308
|
async (params) => {
|
|
223
|
-
|
|
309
|
+
let file = guessOverEscaping(params.file?.trim());
|
|
224
310
|
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
225
311
|
throw new Error('Relative CWD path only (no /, .., \\\\).');
|
|
226
312
|
}
|
|
@@ -242,8 +328,8 @@ tools.add(
|
|
|
242
328
|
required: ['file', 'content']
|
|
243
329
|
},
|
|
244
330
|
async (params) => {
|
|
245
|
-
|
|
246
|
-
|
|
331
|
+
let file = guessOverEscaping(params.file?.trim());
|
|
332
|
+
let content = guessOverEscaping(params.content ?? '');
|
|
247
333
|
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
|
|
248
334
|
throw new Error('Relative CWD path only.');
|
|
249
335
|
}
|
|
@@ -253,29 +339,29 @@ tools.add(
|
|
|
253
339
|
const ext = path.extname(file);
|
|
254
340
|
let finalContent = content, validationMsg = '';
|
|
255
341
|
|
|
256
|
-
const temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2,6)}${ext || '.tmp'}`);
|
|
342
|
+
const temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2, 6)}${ext || '.tmp'}`);
|
|
257
343
|
await fs.writeFile(temp1, content, 'utf8');
|
|
258
344
|
|
|
259
345
|
try {
|
|
260
346
|
await SH`${syntaxCheckSh} ${[temp1]}`.run();
|
|
261
347
|
validationMsg = ' ✓ Syntax OK';
|
|
262
348
|
} catch (e) {
|
|
263
|
-
if (!['.js','.mjs'].includes(ext)) {
|
|
264
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
349
|
+
if (!['.js', '.mjs'].includes(ext)) {
|
|
350
|
+
await fs.unlink(temp1).catch(() => { });
|
|
265
351
|
throw new Error(`Syntax error: ${e.message}`);
|
|
266
352
|
}
|
|
267
353
|
let fixed = content.replace(/\\\\`/g, '\\`').replace(/\\\`/g, '`').replace(/\\`/g, '`').replace(/\\\$/g, '$').replace(/\\\${/g, '${');
|
|
268
|
-
const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2,6)}.js`);
|
|
354
|
+
const temp2 = path.join(dir, `temp_fix_${Date.now()}_${Math.random().toString(36).slice(2, 6)}.js`);
|
|
269
355
|
await fs.writeFile(temp2, fixed, 'utf8');
|
|
270
356
|
try {
|
|
271
357
|
await SH`${syntaxCheckSh} ${[temp2]}`.run();
|
|
272
358
|
finalContent = fixed;
|
|
273
359
|
await fs.rename(temp2, resolved);
|
|
274
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
360
|
+
await fs.unlink(temp1).catch(() => { });
|
|
275
361
|
validationMsg = ' ✓ Fixed JS';
|
|
276
362
|
} catch {
|
|
277
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
278
|
-
await fs.unlink(temp2).catch(()=>{});
|
|
363
|
+
await fs.unlink(temp1).catch(() => { });
|
|
364
|
+
await fs.unlink(temp2).catch(() => { });
|
|
279
365
|
throw new Error(`Syntax error (fix failed): ${e.message}`);
|
|
280
366
|
}
|
|
281
367
|
}
|
|
@@ -302,7 +388,7 @@ tools.add(
|
|
|
302
388
|
required: ['file']
|
|
303
389
|
},
|
|
304
390
|
async (params) => {
|
|
305
|
-
const file = params.file?.trim();
|
|
391
|
+
const file = guessOverEscaping(params.file?.trim());
|
|
306
392
|
if (typeof file !== 'string' || !file) throw new Error('Relative path required.');
|
|
307
393
|
const resolved = path.resolve(process.cwd(), file);
|
|
308
394
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@j-o-r/hello-dave",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.9",
|
|
5
5
|
"description": "ESM toolkit for building AI agents with unified access to Grok (XAI), OpenAI, and Anthropic endpoints",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|