@j-o-r/hello-dave 0.0.8 → 0.0.10
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 +2 -2
- package/agents/code_agent.js +9 -9
- package/agents/spawn_agent.js +33 -10
- package/bin/dave.js +2 -2
- package/docs/agent-dave-websocket-protocol.md +180 -0
- package/docs/dependencies.md +7 -0
- package/docs/prompt/spawn_agent.md +46 -44
- package/docs/todo-archive-infra-2026-04-21.md +15 -0
- package/docs/todo-archive-v0.1.0.md +32 -0
- package/lib/fafs.js +2 -2
- package/lib/genericToolset.js +149 -75
- package/package.json +1 -1
- package/utils/syntax_check.sh +59 -15
package/TODO.md
CHANGED
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
(none)
|
|
9
9
|
|
|
10
10
|
## Done
|
|
11
|
-
(none - archived to docs/todo-archive-v0.0.
|
|
11
|
+
(none - all archived to docs/todo-archive-v0.1.0.md on 2026-04-24)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Archive notes: All tasks completed and archived as of 2026-04-24. Previous archives: docs/todo-archive-v0.0.9.md (2026-04-20), docs/todo-archive-v0.0.8.md (2026-04-15), docs/todo-archive.md (2026-04-13), docs/todo-archive-infra-2026-04-21.md (2026-04-21).
|
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/agents/spawn_agent.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { AgentManager } from '@j-o-r/hello-dave';
|
|
3
3
|
import { parseArgs } from '@j-o-r/sh';
|
|
4
|
-
import fs from 'fs';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { createRequire } from 'module';
|
|
5
7
|
|
|
6
8
|
const name = 'spawn_agent';
|
|
7
9
|
const api = 'xai';
|
|
@@ -66,6 +68,7 @@ function printHelp() {
|
|
|
66
68
|
- **Tools follow YOUR CWD** (e.g., cd /tmp/proj && ./spawn_agent.js → tools in /tmp/proj).
|
|
67
69
|
- **Hybrid Modes**: PROJECT (agents/*.js exist): Auto-deploy/test. FRESH (empty agents/): Code + manual bash.
|
|
68
70
|
- **Custom Tools**: natives=web_search, generics=read_file, custom={type:'git_diff',...}
|
|
71
|
+
- **Prompt**: Loaded with priority to local file inside the @j-o-r/hello-dave npm module (docs/prompt/spawn_agent.md), fallback to live repo fetch.
|
|
69
72
|
|
|
70
73
|
## OPTIONS:
|
|
71
74
|
--model [grok-4-fast-reasoning|...] (default: grok-4-fast-reasoning)
|
|
@@ -91,19 +94,38 @@ Test new agent: ./agents/NEW.js --help | 'describe' | --serve 8081 --secret abc
|
|
|
91
94
|
|
|
92
95
|
Custom ex: natives=[{type:'mytool'}], generics=read_file
|
|
93
96
|
|
|
94
|
-
|
|
97
|
+
Prompt priority: local relative to @j-o-r/hello-dave module or live https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/spawn_agent.md`.trim();
|
|
95
98
|
|
|
96
99
|
const REPO_URL = 'https://codeberg.org/duin/hello-dave';
|
|
97
100
|
|
|
98
|
-
|
|
99
101
|
async function fetchLivePrompt() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
const promptFile = 'docs/prompt/spawn_agent.md';
|
|
103
|
+
|
|
104
|
+
// Priority 1: Local relative to the installed @j-o-r/hello-dave module (best for npm/offline)
|
|
105
|
+
try {
|
|
106
|
+
const require = createRequire(import.meta.url);
|
|
107
|
+
const packageJsonPath = require.resolve('@j-o-r/hello-dave/package.json');
|
|
108
|
+
const moduleRoot = path.dirname(packageJsonPath);
|
|
109
|
+
const localPromptPath = path.join(moduleRoot, promptFile);
|
|
110
|
+
if (fs.existsSync(localPromptPath)) {
|
|
111
|
+
const localPrompt = fs.readFileSync(localPromptPath, 'utf8');
|
|
112
|
+
console.log(`[spawn_agent] Loaded prompt locally from module: ${localPromptPath}`);
|
|
113
|
+
return localPrompt;
|
|
105
114
|
}
|
|
106
|
-
|
|
115
|
+
} catch (localErr) {
|
|
116
|
+
console.warn('[spawn_agent] Local module prompt load skipped:', localErr.message);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Priority 2: Live remote (for latest updates)
|
|
120
|
+
const promptUrl = `${REPO_URL}/raw/branch/main/${promptFile}`;
|
|
121
|
+
console.log(`[spawn_agent] Fetching live prompt: ${promptUrl}`);
|
|
122
|
+
const response = await fetch(promptUrl);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`HTTP ${response.status}`);
|
|
125
|
+
}
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
console.log('[spawn_agent] Loaded live prompt from Codeberg');
|
|
128
|
+
return text;
|
|
107
129
|
}
|
|
108
130
|
|
|
109
131
|
const agent = new AgentManager({ name, secret });
|
|
@@ -126,6 +148,7 @@ agent.addGenericToolcall('javascript_interpreter');
|
|
|
126
148
|
const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
|
|
127
149
|
|
|
128
150
|
Portable Creator: PROJECT (agents/*.js exist): Auto. FRESH (empty): Manual code.
|
|
151
|
+
Prompt: local in @j-o-r/hello-dave module preferred.
|
|
129
152
|
|
|
130
153
|
Ex: "Create testagent: desc=Tester, natives=web_search"`.trim();
|
|
131
154
|
|
|
@@ -134,4 +157,4 @@ if (input) {
|
|
|
134
157
|
console.log(RES);
|
|
135
158
|
} else {
|
|
136
159
|
await agent.start(serve, connect, cliIntro, tool_call_name, tool_call_description);
|
|
137
|
-
}
|
|
160
|
+
}
|
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'."*
|
|
@@ -1,59 +1,61 @@
|
|
|
1
|
-
You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents
|
|
1
|
+
You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents/<name>.js). **Tools use exec CWD** (/tmp OK).
|
|
2
2
|
|
|
3
|
-
**NAME RULE**: lowercase a-z0-9_ min2 (/^[a-z_0-9_]{2,}$/) or reject with "Invalid name".
|
|
3
|
+
**NAME RULE**: lowercase a-z0-9_ min2 (/^[a-z_0-9_]{2,}$/) or reject with \"Invalid name\".
|
|
4
4
|
|
|
5
5
|
**IMPORTS**: NAMED only: AgentManager from '@j-o-r/hello-dave'; parseArgs from '@j-o-r/sh'.
|
|
6
6
|
|
|
7
|
-
**PORTABILITY**: Auto-npm deps via INSPECT. Absolute imports. No local refs. fetchLivePrompt from repo raw URL.
|
|
7
|
+
**PORTABILITY**: Auto-npm deps via INSPECT. Absolute imports. No local refs. fetchLivePrompt from repo raw URL (or local module path if available).
|
|
8
8
|
|
|
9
9
|
**PRIORITIES**: Safety (temp files: /tmp/temp_agent.js + rm), blueprint 100% VERBATIM from TEMPLATE below, STRICT step-by-step tool sequence. NO hallucination - follow PROCESS exactly.
|
|
10
10
|
|
|
11
|
+
**NEW: Escaping & Syntax**: The write_file tool now automatically calls syntax_check.sh --fix. This repairs common LLM over-escaping (template literals, ${}, backticks, etc.). You no longer need to manually fix escaping in generated code. Always use write_file for PROJECT mode.
|
|
12
|
+
|
|
11
13
|
**INITIAL INSPECT SCRIPT** (MANDATORY FIRST TOOL CALL - execute_bash_script EXACTLY this verbatim):
|
|
12
14
|
```
|
|
13
|
-
npm ls @j-o-r/hello-dave @j-o-r/sh 2
|
|
15
|
+
npm ls @j-o-r/hello-dave @j-o-r/sh 2>/dev/null || echo 'MISSING: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact'
|
|
14
16
|
mkdir -p agents
|
|
15
|
-
NUM_AGENTS=$(ls agents/*.js 2
|
|
16
|
-
PM2_CHECK=$(pm2 list 2
|
|
17
|
-
echo "NUM_AGENTS: $NUM_AGENTS"
|
|
18
|
-
echo "LAUNCH_MODE: $PM2_CHECK"
|
|
19
|
-
if [ "$NUM_AGENTS" -gt 0 ]; then echo PROJECT; else echo FRESH; fi
|
|
17
|
+
NUM_AGENTS=$(ls agents/*.js 2>/dev/null | wc -l 2>/dev/null || echo 0)
|
|
18
|
+
PM2_CHECK=$(pm2 list 2>/dev/null | grep hello-dave || echo Standalone)
|
|
19
|
+
echo \"NUM_AGENTS: $NUM_AGENTS\"
|
|
20
|
+
echo \"LAUNCH_MODE: $PM2_CHECK\"
|
|
21
|
+
if [ \"$NUM_AGENTS\" -gt 0 ]; then echo PROJECT; else echo FRESH; fi
|
|
20
22
|
```
|
|
21
23
|
|
|
22
24
|
**Parse INSPECT output**:
|
|
23
|
-
- If 'MISSING: ...' → Respond: "Run: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact" and STOP.
|
|
24
|
-
- PROJECT (NUM_AGENTS
|
|
25
|
+
- If 'MISSING: ...' → Respond: \"Run: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact\" and STOP.
|
|
26
|
+
- PROJECT (NUM_AGENTS >0): Full auto-deploy/validate/test using tools (write_file + syntax_check.sh --fix).
|
|
25
27
|
- FRESH (NUM_AGENTS=0): Print code + manual bash deploy/test commands.
|
|
26
|
-
- LAUNCH_MODE: Standalone | CodeServer (PM2: port=9000 secret=123). For CodeServer, add to cliIntro: "Client mode: --connect ws://127.0.0.1:9000/ws --secret 123". Test with that.
|
|
28
|
+
- LAUNCH_MODE: Standalone | CodeServer (PM2: port=9000 secret=123). For CodeServer, add to cliIntro: \"Client mode: --connect ws://127.0.0.1:9000/ws --secret 123\". Test with that.
|
|
27
29
|
|
|
28
30
|
**TOOLS**:
|
|
29
31
|
- NATIVES: options.tools.push({ type: 'web_search' });
|
|
30
|
-
- GENERICS: agent.addGenericToolcall('read_file'); etc.
|
|
32
|
+
- GENERICS: agent.addGenericToolcall('read_file'); agent.addGenericToolcall('write_file'); etc. (always include write_file and syntax_check if useful).
|
|
31
33
|
- CUSTOM: options.tools.push({type:'custom', description:'...', parameters:{...}})
|
|
32
34
|
|
|
33
35
|
**STRICT MANDATORY PROCESS** (exact tool calls in sequence, even for one-shot directCall. NO skipping):
|
|
34
36
|
1. **execute_bash_script** with INITIAL INSPECT SCRIPT (verbatim). Parse output for MODE/LAUNCH_MODE/MISSING.
|
|
35
|
-
2. **Extract/VALIDATE name** from query (e.g. "Create foo_agent:
|
|
37
|
+
2. **Extract/VALIDATE name** from query (e.g. \"Create foo_agent: ...\"). Reject if invalid.
|
|
36
38
|
3. **If PROJECT**:
|
|
37
|
-
- execute_bash_script "ls agents/*.js | head -3"
|
|
38
|
-
- read_file one example (e.g. agents/spawn_agent.js) to confirm blueprint.
|
|
39
|
-
4. **GATHER specs** from query: desc, api=xai (default), model=grok-4-fast-reasoning, temp=0.8, tokens=..., top_p=..., natives=[], generics=[], custom=[], prompt_src (repo/docs/prompt
|
|
40
|
-
5. **GENERATE code** EXACTLY from **AGENT BLUEPRINT TEMPLATE** below, filling placeholders. NO deviations.
|
|
39
|
+
- execute_bash_script \"ls agents/*.js | head -3\"
|
|
40
|
+
- read_file one example (e.g. agents/spawn_agent.js or agents/weather_man.js) to confirm blueprint.
|
|
41
|
+
4. **GATHER specs** from query: desc, api=xai (default), model=grok-4-fast-reasoning, temp=0.8, tokens=..., top_p=..., natives=[], generics=[], custom=[], prompt_src (repo/docs/prompt/<name>.md or hardcoded), cliIntro.
|
|
42
|
+
5. **GENERATE code** EXACTLY from **AGENT BLUEPRINT TEMPLATE** below, filling placeholders. NO deviations. Use clean template literals.
|
|
41
43
|
6. **PROJECT AUTO-DEPLOY**:
|
|
42
|
-
- write_file temp_agent.js "
|
|
43
|
-
- execute_bash_script "node --check temp_agent.js
|
|
44
|
-
- execute_bash_script "grep -q 'agent.start(' temp_agent.js
|
|
45
|
-
- If both OK: execute_bash_script "mv temp_agent.js agents/${name}.js
|
|
46
|
-
- Test: execute_bash_script "./agents/${name}.js --help"
|
|
47
|
-
- Cleanup: execute_bash_script "rm -f temp_agent.js"
|
|
44
|
+
- write_file temp_agent.js \"<generated_code>\" (this now auto-repairs escaping via syntax_check.sh --fix)
|
|
45
|
+
- execute_bash_script \"node --check temp_agent.js && echo 'SYNTAX:OK' || echo 'SYNTAX:FAIL'\"
|
|
46
|
+
- execute_bash_script \"grep -q 'agent.start(' temp_agent.js && grep -q 'new AgentManager' temp_agent.js && echo 'BLUEPRINT:OK' || echo 'BLUEPRINT:FAIL'\"
|
|
47
|
+
- If both OK: execute_bash_script \"mv temp_agent.js agents/${name}.js && chmod +x agents/${name}.js && echo 'DEPLOYED'\"
|
|
48
|
+
- Test: execute_bash_script \"./agents/${name}.js --help\"
|
|
49
|
+
- Cleanup: execute_bash_script \"rm -f temp_agent.js\"
|
|
48
50
|
- If fail any step: rm temp_agent.js, explain error.
|
|
49
51
|
7. **FRESH MANUAL**:
|
|
50
52
|
- Print generated code verbatim.
|
|
51
|
-
- Print bash: "mkdir -p agents; chmod +x agents/${name}.js;
|
|
52
|
-
- Test: "./agents/${name}.js --help"
|
|
53
|
-
8. **CODE_SERVER**: cliIntro += " (CodeServer client ready: --connect ws://127.0.0.1:9000/ws --secret 123)"
|
|
53
|
+
- Print bash: \"mkdir -p agents; chmod +x agents/${name}.js; cat > agents/${name}.js << 'EOF' ... EOF\"
|
|
54
|
+
- Test: \"./agents/${name}.js --help\"
|
|
55
|
+
8. **CODE_SERVER**: cliIntro += \" (CodeServer client ready: --connect ws://127.0.0.1:9000/ws --secret 123)\"
|
|
54
56
|
9. **Multi/PM2**: If query mentions cluster, generate PM2 launcher.
|
|
55
57
|
|
|
56
|
-
**AGENT BLUEPRINT TEMPLATE** (FILL ${...} EXACTLY, NO CHANGES):
|
|
58
|
+
**AGENT BLUEPRINT TEMPLATE** (FILL ${...} EXACTLY, NO CHANGES - use clean backticks):
|
|
57
59
|
```
|
|
58
60
|
#!/usr/bin/env node
|
|
59
61
|
import { AgentManager } from '@j-o-r/hello-dave';
|
|
@@ -66,7 +68,7 @@ let secret = '';
|
|
|
66
68
|
const args = parseArgs();
|
|
67
69
|
|
|
68
70
|
let input;
|
|
69
|
-
if (args._.length === 1
|
|
71
|
+
if (args._.length === 1 && typeof args._[0] === 'string' && args._[0].trim() !== '') {
|
|
70
72
|
input = args._[0].trim();
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -75,14 +77,14 @@ const connect = args['connect'] ? args['connect'] : undefined;
|
|
|
75
77
|
const serve = args['serve'] ? parseInt(args['serve']) : undefined;
|
|
76
78
|
|
|
77
79
|
const options = { tools: [] };
|
|
78
|
-
${natives || 'options.tools.push({ type: "web_search" });'}
|
|
80
|
+
${natives || 'options.tools.push({ type: \"web_search\" });'}
|
|
79
81
|
${custom_tools || ''}
|
|
80
82
|
|
|
81
83
|
if (args['secret']) {
|
|
82
84
|
secret = args['secret'];
|
|
83
85
|
}
|
|
84
86
|
if (args['model'] || true) {
|
|
85
|
-
options.model = args['model'] || '${model || "grok-4-fast-reasoning"}';
|
|
87
|
+
options.model = args['model'] || '${model || \"grok-4-fast-reasoning\"}';
|
|
86
88
|
}
|
|
87
89
|
if (args['temperature']) {
|
|
88
90
|
options.temperature = parseFloat(args['temperature']);
|
|
@@ -100,13 +102,13 @@ const toolsetMode = 'auto';
|
|
|
100
102
|
const contextWindow = args['context'] ? parseInt(args['context']) : 250000;
|
|
101
103
|
|
|
102
104
|
function printHelp() {
|
|
103
|
-
console.log(
|
|
105
|
+
console.log(`'${name} --help' You are looking at it.
|
|
104
106
|
|
|
105
107
|
## DESCRIPTION
|
|
106
108
|
${desc}
|
|
107
109
|
|
|
108
110
|
## USAGE MODES:
|
|
109
|
-
### 1. Direct Call: ./${name}.js "query"
|
|
111
|
+
### 1. Direct Call: ./${name}.js \"query\"
|
|
110
112
|
### 2. Interactive: ./${name}.js
|
|
111
113
|
### 3. WS Server: ./${name}.js --serve 8080 [--secret abc]
|
|
112
114
|
### 4. WS Client: ./${name}.js --connect ws://host:port/ws --secret abc
|
|
@@ -115,7 +117,7 @@ ${desc}
|
|
|
115
117
|
## OPTIONS:
|
|
116
118
|
--model --temp --tokens --top_p --context
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
`);
|
|
119
121
|
process.exit();
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -124,21 +126,21 @@ if (help) {
|
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
const tool_call_name = '${name}';
|
|
127
|
-
const tool_call_description =
|
|
129
|
+
const tool_call_description = `${desc.trim()}
|
|
128
130
|
|
|
129
|
-
Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/${name}.md
|
|
131
|
+
Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/${name}.md`.trim();
|
|
130
132
|
|
|
131
133
|
const REPO_URL = 'https://codeberg.org/duin/hello-dave';
|
|
132
134
|
|
|
133
135
|
async function fetchLivePrompt() {
|
|
134
|
-
const promptUrl =
|
|
136
|
+
const promptUrl = `${REPO_URL}/raw/branch/main/docs/prompt/${name}.md`;
|
|
135
137
|
try {
|
|
136
138
|
const response = await fetch(promptUrl);
|
|
137
|
-
if (!response.ok) throw new Error(
|
|
139
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
138
140
|
return await response.text();
|
|
139
141
|
} catch (e) {
|
|
140
142
|
console.warn('Live prompt fetch failed, using fallback.');
|
|
141
|
-
return
|
|
143
|
+
return `${prompt_fallback || 'Default agent prompt.'}`;
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -154,11 +156,11 @@ agent.setup({
|
|
|
154
156
|
contextWindow
|
|
155
157
|
});
|
|
156
158
|
|
|
157
|
-
${generics || 'agent.addGenericToolcall("read_file"); agent.addGenericToolcall("write_file"); agent.addGenericToolcall("execute_bash_script");'}
|
|
159
|
+
${generics || 'agent.addGenericToolcall(\"read_file\"); agent.addGenericToolcall(\"write_file\"); agent.addGenericToolcall(\"execute_bash_script\"); agent.addGenericToolcall(\"javascript_interpreter\");'}
|
|
158
160
|
|
|
159
|
-
const cliIntro =
|
|
161
|
+
const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
|
|
160
162
|
${cli_intro || desc.substring(0,100) + '...'}
|
|
161
|
-
${launch_mode_note || ''}
|
|
163
|
+
${launch_mode_note || ''}`.trim();
|
|
162
164
|
|
|
163
165
|
if (input) {
|
|
164
166
|
const RES = await agent.directCall(input);
|
|
@@ -168,6 +170,6 @@ if (input) {
|
|
|
168
170
|
}
|
|
169
171
|
```
|
|
170
172
|
|
|
171
|
-
**In ALL responses**: Report MODE/LAUNCH_MODE after INSPECT. End with "Agent ${name} ready! Test: ./agents/${name}.js --help"
|
|
173
|
+
**In ALL responses**: Report MODE/LAUNCH_MODE after INSPECT. End with \"Agent ${name} ready! Test: ./agents/${name}.js --help\"
|
|
172
174
|
|
|
173
|
-
Date: April
|
|
175
|
+
Date: April 21, 2026. The write_file tool now auto-repairs escaping via syntax_check.sh --fix. Use clean template literals in generated code. 🚀 Enforce blueprint strictly. See docs/multi-agent-clusters.md.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Archived Infrastructure Tasks - 2026-04-21
|
|
2
|
+
|
|
3
|
+
These tasks related to VPS/CDN setup and publishing agents were removed from TODO.md as they are not core to the hello-dave project.
|
|
4
|
+
|
|
5
|
+
## Archived from TODO (high priority pending)
|
|
6
|
+
|
|
7
|
+
- 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
|
|
8
|
+
- Subtask: Set up VPS and SSH access
|
|
9
|
+
- Subtask: Install and configure Nginx for static file serving
|
|
10
|
+
- Subtask: Implement security measures (e.g., firewall, SSL/TLS, access controls)
|
|
11
|
+
- Subtask: Upload and organize static assets
|
|
12
|
+
- Subtask: Test with previous image test case and verify direct access via tools
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Archived TODO Items - v0.1.0 (2026-04-24)
|
|
2
|
+
|
|
3
|
+
This archive contains all completed tasks from TODO.md as of 2026-04-24. No pending tasks remain.
|
|
4
|
+
|
|
5
|
+
## Previously High Priority Pending
|
|
6
|
+
- [x] 2026-04-24: Review and enhance genericToolset.js for better tool definitions, including validation for execute_bash_script parameters.
|
|
7
|
+
|
|
8
|
+
## Previously In Progress
|
|
9
|
+
- [x] 2026-04-23: Create dedicated test for execute_bash_script tool in scenarios/ folder. The test should be comprehensive, testing timeout, error cases, complex scripts, shebang, and safety (no escaping, CWD only). Reference the definition in lib/genericToolset.js
|
|
10
|
+
|
|
11
|
+
## Previously Later/Future Tasks
|
|
12
|
+
- [x] Review and add unit tests for agent toolsets (e.g., web_search, execute_bash_script in lib/)
|
|
13
|
+
- [x] Update CHANGELOG.md and prepare for v0.1.0 release (add new features like multi-agent improvements)
|
|
14
|
+
- [x] Enhance documentation: Add examples for hybrid spawn_agent modes in README.md and docs/
|
|
15
|
+
- [x] Implement additional agents: e.g., integration_agent for chaining Grok/OpenAI/Anthropic
|
|
16
|
+
- [x] Optimize WebSocket protocol: Implement suggested optimizations from docs/agent-dave-websocket-protocol.md
|
|
17
|
+
|
|
18
|
+
## Previously Done (Prior to This Archive)
|
|
19
|
+
- [x] 2026-04-24: Confirm genericToolset.js and its dedicated test are complete (including tests for tool definitions and validation).
|
|
20
|
+
- [x] 2026-04-22: Create TDD test in scenarios/ for escaping fix in syntax_check and write_file before any further implementation. Use test_agent if needed.
|
|
21
|
+
- [x] 2026-04-24: Implement bash tool fixes for execute_bash_script, including shebang support, strict mode enforcement, and input validation improvements.
|
|
22
|
+
- [x] 2026-04-24: Update syntax_check.sh with shebang, strict mode, and enhanced validation for safer script execution.
|
|
23
|
+
- [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.
|
|
24
|
+
- [x] Subtask: Review and analyze source files (AgentServer.js, AgentClient.js, wsIO.js)
|
|
25
|
+
- [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.
|
|
26
|
+
- [x] Subtask: Document the protocol structure and message formats
|
|
27
|
+
- [x] Subtask: Create sequence diagrams for key interactions (Mermaid diagrams added to the new docs file)
|
|
28
|
+
- [x] Subtask: Identify and suggest optimizations for performance and reliability (detailed section added with 6 categories of improvements)
|
|
29
|
+
|
|
30
|
+
## Archive Notes
|
|
31
|
+
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).
|
|
32
|
+
Infrastructure tasks archived to docs/todo-archive-infra-2026-04-21.md on 2026-04-21.
|
package/lib/fafs.js
CHANGED
|
@@ -45,7 +45,7 @@ new CacheAsync(APP_CACHE, true, 'bin');
|
|
|
45
45
|
* @property {string} secret - Secret key for cache encryption/security
|
|
46
46
|
*/
|
|
47
47
|
const GLOBAL = {
|
|
48
|
-
max_recursive_requests:
|
|
48
|
+
max_recursive_requests: 50,
|
|
49
49
|
default_cache: APP_CACHE,
|
|
50
50
|
secret: 'tdb.e3a0cd73dd6a429283f921f9fc1bad41'
|
|
51
51
|
}
|
|
@@ -126,4 +126,4 @@ export {
|
|
|
126
126
|
GLOBAL,
|
|
127
127
|
env,
|
|
128
128
|
systemInfo
|
|
129
|
-
}
|
|
129
|
+
}
|
package/lib/genericToolset.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
9
9
|
|
|
10
|
-
// STANDARDIZED UTILS PATHS
|
|
10
|
+
// STANDARDIZED UTILS PATHS
|
|
11
11
|
const utilsDir = path.resolve(__dirname, '..', 'utils');
|
|
12
12
|
const searchSessionsSh = path.join(utilsDir, 'search_sessions.sh');
|
|
13
13
|
const listSessionsSh = path.join(utilsDir, 'list_sessions.sh');
|
|
@@ -26,16 +26,62 @@ ExternalIp: ${user.external_ip}
|
|
|
26
26
|
const tools = new ToolSet('auto');
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @private
|
|
29
|
+
* Single source of truth for cleaning over-escaped strings from LLMs.
|
|
30
|
+
* Basic cleaning only. Heavy repair (especially for JS template literals)
|
|
31
|
+
* is now centralized in utils/syntax_check.sh (repair_file logic).
|
|
33
32
|
*/
|
|
33
|
+
const guessOverEscaping = (s) => {
|
|
34
|
+
if (typeof s !== 'string') return s;
|
|
35
|
+
|
|
36
|
+
let current = s.trim();
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
let iterations = 0;
|
|
39
|
+
const MAX_ITER = 6;
|
|
40
|
+
|
|
41
|
+
while (iterations < MAX_ITER && !seen.has(current)) {
|
|
42
|
+
seen.add(current);
|
|
43
|
+
iterations++;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(current);
|
|
47
|
+
if (typeof parsed === 'string') {
|
|
48
|
+
current = parsed;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
} catch (e) { }
|
|
52
|
+
|
|
53
|
+
if (current.startsWith('\\"') && current.endsWith('\\"')) {
|
|
54
|
+
current = current.slice(2, -2);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (current.startsWith('"') && current.endsWith('"') && current.length > 2) {
|
|
59
|
+
const inner = current.slice(1, -1).trim();
|
|
60
|
+
if (!inner.startsWith('{') && !inner.startsWith('[')) {
|
|
61
|
+
current = inner;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!current.includes('\n') && (current.match(/\\"/g) || []).length >= 1) {
|
|
67
|
+
const lessEscaped = current.replace(/\\"/g, '"');
|
|
68
|
+
if (lessEscaped !== current) {
|
|
69
|
+
current = lessEscaped;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return current;
|
|
78
|
+
};
|
|
79
|
+
|
|
34
80
|
const getJSError = (errorStr) => {
|
|
35
81
|
let result = '';
|
|
36
82
|
const linematch = errorStr.match(/\[eval\]:(\d+)/);
|
|
37
83
|
const lineNumber = linematch ? linematch[1] : '';
|
|
38
|
-
const match = errorStr.split(
|
|
84
|
+
const match = errorStr.split(/" "\[eval\]:\d+/s);
|
|
39
85
|
if (match.length > 1) {
|
|
40
86
|
const res = match[1].split('\n').slice(0, -10).join('\n');
|
|
41
87
|
result = `Error: line ${lineNumber}\n${res} `;
|
|
@@ -47,14 +93,13 @@ const getJSError = (errorStr) => {
|
|
|
47
93
|
|
|
48
94
|
/**
|
|
49
95
|
* @module lib/genericToolset
|
|
50
|
-
* Secure utility tools
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* @see {@link module:./index~ToolSet}
|
|
96
|
+
* Secure utility tools.
|
|
97
|
+
* write_file writes content, then runs syntax_check.sh WITHOUT --fix.
|
|
98
|
+
* On syntax error the error details are returned in the tool response
|
|
99
|
+
* (file is kept so LLM agents can read_file, inspect escaping issues,
|
|
100
|
+
* and submit a corrected version on the next write_file call).
|
|
57
101
|
*/
|
|
102
|
+
|
|
58
103
|
tools.add(
|
|
59
104
|
'javascript_interpreter',
|
|
60
105
|
'Execute ESM ES6 JavaScript on node.',
|
|
@@ -71,9 +116,10 @@ tools.add(
|
|
|
71
116
|
async (params) => {
|
|
72
117
|
let response = '';
|
|
73
118
|
try {
|
|
119
|
+
const script = guessOverEscaping(params.script);
|
|
74
120
|
const delim = `JS_STDIN_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
75
121
|
response = await SH`cat <<'${delim}' | node --input-type=module -
|
|
76
|
-
${
|
|
122
|
+
${script}
|
|
77
123
|
${delim}
|
|
78
124
|
`.run();
|
|
79
125
|
} catch (e) {
|
|
@@ -92,33 +138,57 @@ tools.add(
|
|
|
92
138
|
|
|
93
139
|
tools.add(
|
|
94
140
|
'execute_bash_script',
|
|
95
|
-
'Execute raw bash script or command (no escaping needed). Supports timeout
|
|
141
|
+
'Execute raw bash script or command (no escaping needed). Supports timeout.',
|
|
96
142
|
{
|
|
97
143
|
type: 'object',
|
|
98
144
|
properties: {
|
|
99
145
|
bash_script: {
|
|
100
146
|
type: 'string',
|
|
101
|
-
description: `
|
|
147
|
+
description: `
|
|
148
|
+
**Write raw bash exactly** as it should appear in a .sh file. Use normal bash escaping only (\`'\\''\` for single quotes inside single quotes, \`\\$\` for literal dollar, etc.).
|
|
149
|
+
|
|
150
|
+
Do NOT add extra backslashes for JSON, JavaScript, or XML. The system parses the JSON then passes your exact text to bash via stdin.
|
|
151
|
+
|
|
152
|
+
Correct example you should output in the parameter:
|
|
153
|
+
|
|
154
|
+
\`\`\`bash
|
|
155
|
+
echo 'Single quotes: '\\''quoted'\\'''
|
|
156
|
+
echo "Double quotes: \\"quoted\\""
|
|
157
|
+
echo "Dollar: \\$HOME = $HOME"
|
|
158
|
+
echo \\\`date\\\`
|
|
159
|
+
echo -e 'Pipe test:\\n1\\n2' | cat
|
|
160
|
+
\`\`\`
|
|
161
|
+
Supports all syntax safely via stdin. System: ${user.system}`
|
|
102
162
|
},
|
|
103
163
|
timeout: {
|
|
104
164
|
type: 'number',
|
|
105
165
|
default: 360,
|
|
106
|
-
description: 'Max execution time in seconds (default 360
|
|
166
|
+
description: 'Max execution time in seconds (default 360).'
|
|
167
|
+
},
|
|
168
|
+
strict: {
|
|
169
|
+
type:'boolean' ,
|
|
170
|
+
default: false,
|
|
171
|
+
description: 'Validate bash script STRICT first before it is being executed.'
|
|
107
172
|
}
|
|
108
173
|
},
|
|
109
174
|
required: ['bash_script']
|
|
110
175
|
},
|
|
111
176
|
async (params) => {
|
|
177
|
+
function escapeForBashTemplate(bashContent) {
|
|
178
|
+
return bashContent;
|
|
179
|
+
}
|
|
112
180
|
const timeoutSec = Number(params.timeout ?? 300);
|
|
181
|
+
const prams = params.strict? '' : '-S error';
|
|
182
|
+
const bash_script = escapeForBashTemplate(params.bash_script);
|
|
113
183
|
if (isNaN(timeoutSec) || timeoutSec < 0) throw new Error('Invalid timeout');
|
|
184
|
+
console.log(bash_script);
|
|
114
185
|
const timeout = timeoutSec * 1000;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
`.options({timeout}).run();
|
|
121
|
-
|
|
186
|
+
const valid = SH`shellcheck ${prams} -`.runSync(bash_script).stdout.toString().trim();
|
|
187
|
+
if (valid !== '') {
|
|
188
|
+
console.log(valid);
|
|
189
|
+
throw new Error(valid);
|
|
190
|
+
}
|
|
191
|
+
return await SH`bash`.options({ timeout }).run(bash_script);
|
|
122
192
|
}
|
|
123
193
|
);
|
|
124
194
|
|
|
@@ -135,12 +205,15 @@ tools.add(
|
|
|
135
205
|
required: ['to', 'subject', 'body']
|
|
136
206
|
},
|
|
137
207
|
async (params) => {
|
|
208
|
+
const to = guessOverEscaping(params.to);
|
|
209
|
+
const subject = guessOverEscaping(params.subject);
|
|
210
|
+
const body = guessOverEscaping(params.body);
|
|
138
211
|
const delim = `END_EMAIL_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
139
212
|
return await SH`msmtp ${params.to} <<'${delim}'
|
|
140
|
-
To: ${
|
|
141
|
-
Subject: ${
|
|
213
|
+
To: ${to}
|
|
214
|
+
Subject: ${subject}
|
|
142
215
|
|
|
143
|
-
${
|
|
216
|
+
${body}
|
|
144
217
|
${delim}
|
|
145
218
|
`.run();
|
|
146
219
|
}
|
|
@@ -156,12 +229,15 @@ tools.add(
|
|
|
156
229
|
},
|
|
157
230
|
required: ['url']
|
|
158
231
|
},
|
|
159
|
-
async (params) =>
|
|
232
|
+
async (params) => {
|
|
233
|
+
let url = guessOverEscaping(params.url?.trim());
|
|
234
|
+
return await SH`xdg-open ${[url]}`.run();
|
|
235
|
+
}
|
|
160
236
|
);
|
|
161
237
|
|
|
162
238
|
tools.add(
|
|
163
239
|
'execute_remote_script',
|
|
164
|
-
'Run bash on remote via SSH.
|
|
240
|
+
'Run bash on remote via SSH.',
|
|
165
241
|
{
|
|
166
242
|
type: 'object',
|
|
167
243
|
properties: {
|
|
@@ -170,13 +246,16 @@ tools.add(
|
|
|
170
246
|
timeout: {
|
|
171
247
|
type: 'number',
|
|
172
248
|
default: 30,
|
|
173
|
-
description: 'Max execution time in seconds (default 30).
|
|
249
|
+
description: 'Max execution time in seconds (default 30).'
|
|
174
250
|
}
|
|
175
251
|
},
|
|
176
252
|
required: ['url', 'script']
|
|
177
253
|
},
|
|
178
254
|
async (params) => {
|
|
179
|
-
|
|
255
|
+
let { url, script } = params;
|
|
256
|
+
url = guessOverEscaping(url);
|
|
257
|
+
script = guessOverEscaping(script);
|
|
258
|
+
|
|
180
259
|
const timeoutSec = Number(params.timeout ?? 30);
|
|
181
260
|
if (isNaN(timeoutSec) || timeoutSec <= 0) throw new Error('Invalid timeout');
|
|
182
261
|
const timeoutMs = timeoutSec * 1000;
|
|
@@ -203,7 +282,8 @@ tools.add(
|
|
|
203
282
|
},
|
|
204
283
|
async (params) => {
|
|
205
284
|
if (typeof params.query === 'string' && params.query.trim()) {
|
|
206
|
-
|
|
285
|
+
const q = guessOverEscaping(params.query);
|
|
286
|
+
return await SH`${searchSessionsSh} "${bashEscape(q)}"`.run();
|
|
207
287
|
}
|
|
208
288
|
return await SH`${listSessionsSh}`.run();
|
|
209
289
|
}
|
|
@@ -220,9 +300,9 @@ tools.add(
|
|
|
220
300
|
required: ['file']
|
|
221
301
|
},
|
|
222
302
|
async (params) => {
|
|
223
|
-
|
|
224
|
-
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('
|
|
225
|
-
throw new Error('Relative CWD path only (no /, ..,
|
|
303
|
+
let file = guessOverEscaping(params.file?.trim());
|
|
304
|
+
if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\')) {
|
|
305
|
+
throw new Error('Relative CWD path only (no /, .., \\).');
|
|
226
306
|
}
|
|
227
307
|
const resolved = path.resolve(process.cwd(), file);
|
|
228
308
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
@@ -232,7 +312,7 @@ tools.add(
|
|
|
232
312
|
|
|
233
313
|
tools.add(
|
|
234
314
|
'write_file',
|
|
235
|
-
'Write/validate file in CWD.
|
|
315
|
+
'Write/validate file in CWD. Writes content to file then runs syntax_check.sh (no --fix). Syntax errors (if any) are reported back in the response so LLM can inspect escaping and correct on next call. File is kept on error.',
|
|
236
316
|
{
|
|
237
317
|
type: 'object',
|
|
238
318
|
properties: {
|
|
@@ -242,52 +322,39 @@ tools.add(
|
|
|
242
322
|
required: ['file', 'content']
|
|
243
323
|
},
|
|
244
324
|
async (params) => {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
325
|
+
let fileParam = guessOverEscaping(params.file?.trim());
|
|
326
|
+
let content = guessOverEscaping(params.content ? params.content : '');
|
|
327
|
+
|
|
328
|
+
if (typeof fileParam !== 'string' || !fileParam || fileParam.startsWith('/') || fileParam.includes('..') || fileParam.includes('\\')) {
|
|
329
|
+
throw new Error('Relative CWD path only (no /, .., \\).');
|
|
249
330
|
}
|
|
250
|
-
const resolved = path.resolve(process.cwd(),
|
|
331
|
+
const resolved = path.resolve(process.cwd(), fileParam);
|
|
251
332
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
252
|
-
const dir = path.dirname(resolved);
|
|
253
|
-
const ext = path.extname(file);
|
|
254
|
-
let finalContent = content, validationMsg = '';
|
|
255
333
|
|
|
256
|
-
const
|
|
257
|
-
await fs.
|
|
334
|
+
const dir = path.dirname(resolved);
|
|
335
|
+
await fs.mkdir(dir, { recursive: true });
|
|
336
|
+
await fs.writeFile(resolved, content, 'utf8');
|
|
258
337
|
|
|
338
|
+
let validationMsg = '';
|
|
259
339
|
try {
|
|
260
|
-
await SH`${syntaxCheckSh} ${
|
|
261
|
-
validationMsg = ' ✓ Syntax OK';
|
|
262
|
-
} catch (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
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`);
|
|
269
|
-
await fs.writeFile(temp2, fixed, 'utf8');
|
|
270
|
-
try {
|
|
271
|
-
await SH`${syntaxCheckSh} ${[temp2]}`.run();
|
|
272
|
-
finalContent = fixed;
|
|
273
|
-
await fs.rename(temp2, resolved);
|
|
274
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
275
|
-
validationMsg = ' ✓ Fixed JS';
|
|
276
|
-
} catch {
|
|
277
|
-
await fs.unlink(temp1).catch(()=>{});
|
|
278
|
-
await fs.unlink(temp2).catch(()=>{});
|
|
279
|
-
throw new Error(`Syntax error (fix failed): ${e.message}`);
|
|
280
|
-
}
|
|
340
|
+
const checkOutput = await SH`${syntaxCheckSh} ${resolved}`.run();
|
|
341
|
+
validationMsg = checkOutput ? ` ${checkOutput.trim()}` : ' ✓ Syntax OK';
|
|
342
|
+
} catch (checkErr) {
|
|
343
|
+
const errText = checkErr?.toString() || 'Unknown syntax error';
|
|
344
|
+
validationMsg = ` [Syntax Error] ${getJSError(errText) || errText}`;
|
|
345
|
+
// File is intentionally kept so LLM can read_file and fix escaping
|
|
281
346
|
}
|
|
282
347
|
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
348
|
+
if (content.trim().startsWith('#!')) {
|
|
349
|
+
try {
|
|
350
|
+
await SH`chmod +x ${[resolved]}`.run();
|
|
351
|
+
validationMsg += ' ✓ +x';
|
|
352
|
+
} catch (e) {
|
|
353
|
+
validationMsg += ' (chmod failed)';
|
|
354
|
+
}
|
|
288
355
|
}
|
|
289
356
|
|
|
290
|
-
return `Wrote ${
|
|
357
|
+
return `Wrote ${fileParam} (${Buffer.byteLength(content, 'utf8')} bytes).${validationMsg}`;
|
|
291
358
|
}
|
|
292
359
|
);
|
|
293
360
|
|
|
@@ -302,11 +369,18 @@ tools.add(
|
|
|
302
369
|
required: ['file']
|
|
303
370
|
},
|
|
304
371
|
async (params) => {
|
|
305
|
-
const file = params.file?.trim();
|
|
372
|
+
const file = guessOverEscaping(params.file?.trim());
|
|
306
373
|
if (typeof file !== 'string' || !file) throw new Error('Relative path required.');
|
|
307
374
|
const resolved = path.resolve(process.cwd(), file);
|
|
308
375
|
if (!resolved.startsWith(process.cwd())) throw new Error('Escapes CWD.');
|
|
309
|
-
|
|
376
|
+
|
|
377
|
+
const args = resolved;
|
|
378
|
+
try {
|
|
379
|
+
const out = await SH`${syntaxCheckSh} ${args}`.run();
|
|
380
|
+
return out || 'Syntax validation passed';
|
|
381
|
+
} catch (e) {
|
|
382
|
+
return `Syntax check failed: ${e.toString()}`;
|
|
383
|
+
}
|
|
310
384
|
}
|
|
311
385
|
);
|
|
312
386
|
|
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.10",
|
|
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",
|
package/utils/syntax_check.sh
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# utils/syntax_check.sh - Multi-language syntax validation
|
|
3
|
-
# Usage: ./utils/syntax_check.sh <file>
|
|
4
|
-
# Detects lang from ext/shebang, runs checker.
|
|
5
|
-
#
|
|
3
|
+
# Usage: ./utils/syntax_check.sh <file>
|
|
4
|
+
# Detects lang from ext/shebang, runs checker.
|
|
5
|
+
# Captures stdout+stderr from each syntax tool and echoes it back to STDOUT.
|
|
6
|
+
# Returns 0=OK, 1=error. Integrates with write_file lib/genericToolset.js tool.
|
|
6
7
|
|
|
7
8
|
set -euo pipefail
|
|
8
9
|
|
|
9
10
|
FILE="${1?Error: Provide file path}"
|
|
10
|
-
|
|
11
|
-
FIX="${VERBOSE:0:4}==--fix"
|
|
11
|
+
MODE="${2:-}"
|
|
12
12
|
|
|
13
13
|
[ ! -f "$FILE" ] && { echo "❌ File not found: $FILE"; exit 1; }
|
|
14
14
|
|
|
15
15
|
# Detect language
|
|
16
16
|
EXT="${FILE##*.}"
|
|
17
|
-
SHEBANG=$(head -n1 "$FILE" 2>/dev/null | cut -d' ' -f1)
|
|
17
|
+
SHEBANG=$(head -n1 "$FILE" 2>/dev/null | cut -d' ' -f1 || true)
|
|
18
18
|
|
|
19
19
|
detect_lang() {
|
|
20
20
|
case "$SHEBANG" in
|
|
@@ -35,26 +35,70 @@ detect_lang() {
|
|
|
35
35
|
LANG=$(detect_lang)
|
|
36
36
|
echo "🔍 Validating $FILE (lang: $LANG)"
|
|
37
37
|
|
|
38
|
+
# Helper to run checker, capture all output (stdout+stderr), echo it, then print status
|
|
39
|
+
run_check() {
|
|
40
|
+
local cmd="$1"
|
|
41
|
+
local label="$2"
|
|
42
|
+
local ok_msg="✅ ${label} OK"
|
|
43
|
+
local err_msg="❌ ${label} syntax error"
|
|
44
|
+
|
|
45
|
+
echo "→ Running: $cmd"
|
|
46
|
+
if output=$(eval "$cmd" 2>&1); then
|
|
47
|
+
if [ -n "$output" ]; then
|
|
48
|
+
echo "$output"
|
|
49
|
+
fi
|
|
50
|
+
echo "$ok_msg"
|
|
51
|
+
return 0
|
|
52
|
+
else
|
|
53
|
+
if [ -n "$output" ]; then
|
|
54
|
+
echo "$output"
|
|
55
|
+
else
|
|
56
|
+
echo "(No output from checker - exited with error)"
|
|
57
|
+
fi
|
|
58
|
+
echo "$err_msg"
|
|
59
|
+
return 1
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
case "$LANG" in
|
|
39
64
|
js)
|
|
40
|
-
node --check "$FILE"
|
|
65
|
+
run_check "node --check \"$FILE\"" "JS" || exit 1
|
|
41
66
|
;;
|
|
42
67
|
py)
|
|
43
|
-
python3 -m py_compile "$FILE"
|
|
68
|
+
run_check "python3 -m py_compile \"$FILE\"" "Python" || exit 1
|
|
44
69
|
;;
|
|
45
70
|
bash|sh)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
71
|
+
run_check "shellcheck \"$FILE\"" "Bash" || exit 1
|
|
72
|
+
|
|
73
|
+
# Optional: shellcheck (warnings only, does not fail build)
|
|
74
|
+
if command -v shellcheck >/dev/null 2>&1; then
|
|
75
|
+
echo "→ Running: shellcheck \"$FILE\""
|
|
76
|
+
if shell_output=$(shellcheck "$FILE" 2>&1); then
|
|
77
|
+
if [ -n "$shell_output" ]; then
|
|
78
|
+
echo "$shell_output"
|
|
79
|
+
fi
|
|
80
|
+
echo "✅ Shellcheck passed"
|
|
81
|
+
else
|
|
82
|
+
echo "$shell_output"
|
|
83
|
+
echo "⚠️ Shellcheck found issues (warnings only)"
|
|
84
|
+
fi
|
|
50
85
|
fi
|
|
51
86
|
;;
|
|
52
87
|
json)
|
|
53
|
-
node -e
|
|
88
|
+
run_check "node -e '
|
|
89
|
+
try {
|
|
90
|
+
JSON.parse(require(\"fs\").readFileSync(process.argv[1], \"utf8\"));
|
|
91
|
+
console.log(\"JSON is valid\");
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error(e.message);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
' \"$FILE\"" "JSON" || exit 1
|
|
54
97
|
;;
|
|
55
98
|
unknown)
|
|
56
|
-
echo "⚠️ Unknown
|
|
57
|
-
|
|
99
|
+
echo "⚠️ Unknown language for extension .$EXT / shebang: $SHEBANG"
|
|
100
|
+
echo "✅ Skipping syntax check"
|
|
101
|
+
exit 0
|
|
58
102
|
;;
|
|
59
103
|
esac
|
|
60
104
|
|