@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 CHANGED
@@ -8,6 +8,6 @@
8
8
  (none)
9
9
 
10
10
  ## Done
11
- (none - archived to docs/todo-archive-v0.0.8.md on 2026-04-15)
11
+ (none - all archived to docs/todo-archive-v0.1.0.md on 2026-04-24)
12
12
 
13
- Older tasks archived on 2026-04-13 to docs/todo-archive.md.
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).
@@ -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-1-fast-reasoning';
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
- options.reasoning = {
41
- // @ts-ignore
42
- effort:reasoning,
43
- summary: 'auto'
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
 
@@ -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'; // Added for local fallback
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
- Live prompt: https://codeberg.org/duin/hello-dave/raw/branch/main/docs/prompt/spawn_agent.md`.trim();
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
- // Priority 1: Remote
101
- const promptUrl = `${REPO_URL}/raw/branch/main/docs/prompt/spawn_agent.md`;
102
- const response = await fetch(promptUrl);
103
- if (!response.ok) {
104
- throw new Error(`HTTP ${response.status}`);
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
- return await response.text();
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-1-fast-reasoning';
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'."*
@@ -0,0 +1,7 @@
1
+
2
+
3
+ ## A linux based os with
4
+
5
+ `node`
6
+ `spellcheck`
7
+ `msmtp`
@@ -1,59 +1,61 @@
1
- You are AgentCreator, expert @j-o-r/hello-dave. Create portable CLI/WS agents (./agents/&lt;name&gt;.js). **Tools use exec CWD** (/tmp OK).
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&gt;/dev/null || echo 'MISSING: npm i @j-o-r/hello-dave @j-o-r/sh --save-exact'
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&gt;/dev/null | wc -l 2&gt;/dev/null || echo 0)
16
- PM2_CHECK=$(pm2 list 2&gt;/dev/null | grep hello-dave || echo Standalone)
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 &gt;0): Full auto-deploy/validate/test using tools.
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: ..."). Reject if invalid.
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/&lt;name&gt;.md or hardcoded), cliIntro.
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 "&lt;generated_code&gt;"
43
- - execute_bash_script "node --check temp_agent.js &amp;&amp; echo 'SYNTAX:OK' || echo 'SYNTAX:FAIL'"
44
- - execute_bash_script "grep -q 'agent.start(' temp_agent.js &amp;&amp; grep -q 'new AgentManager' temp_agent.js &amp;&amp; echo 'BLUEPRINT:OK' || echo 'BLUEPRINT:FAIL'"
45
- - If both OK: execute_bash_script "mv temp_agent.js agents/${name}.js &amp;&amp; chmod +x agents/${name}.js &amp;&amp; echo 'DEPLOYED'"
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; echo '&lt;code&gt;' &gt; 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 &amp;&amp; typeof args._[0] === 'string' &amp;&amp; args._[0].trim() !== '') {
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(\`'\${name} --help' You are looking at it.
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 = \`${desc.trim()}
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\`.trim();
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 = \`\${REPO_URL}/raw/branch/main/docs/prompt/${name}.md\`;
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(\`HTTP \${response.status}\`);
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 \`${prompt_fallback || 'Default agent prompt.'}\`;
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 = \`🤖 \${name} (\${options.model}) ready! (context: \${contextWindow})
161
+ const cliIntro = `🤖 ${name} (${options.model}) ready! (context: ${contextWindow})
160
162
  ${cli_intro || desc.substring(0,100) + '...'}
161
- ${launch_mode_note || ''}\`.trim();
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 13, 2026. 🚀 Enforce blueprint strictly. See docs/multi-agent-clusters.md.
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: 20,
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
+ }
@@ -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 (hoisted constants for performance/consistency)
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
- * Reduces verbose JavaScript evaluation error output to essential info (line number + core message).
30
- * @param {string} errorStr - Full Node.js error string.
31
- * @returns {string} Simplified error.
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(/\" \"\[eval\]:\d+/s);
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 for code execution, file I/O, system ops. Pre-populated ToolSet.
51
- *
52
- * @example
53
- * import tools from './lib/genericToolset.js';
54
- * await tools.call('get_user_env', {});
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
- ${params.script}
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 to prevent hangs.',
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: `Raw bash verbatim. Supports all syntax safely via stdin. System: ${user.system}`
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 seconds, 0 is no timeout). Uses @j-o-r/sh timeout (ms) with SIGTERM on expiry to prevent hangs from interactive prompts or slow commands.'
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
- // return await SH`bash`.options({ timeout: timeoutMs }).run(params.bash_script);
116
- const delim = `END_SCRIPT_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
117
- return await SH`bash <<'${delim}'
118
- ${params.bash_script}
119
- ${delim}
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: ${params.to}
141
- Subject: ${params.subject}
213
+ To: ${to}
214
+ Subject: ${subject}
142
215
 
143
- ${params.body}
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) => await SH`xdg-open ${[params.url]}`.run()
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. Supports timeout to prevent hangs.',
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). Uses @j-o-r/sh timeout (ms) with SIGTERM on SSH process expiry.'
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
- const { url, script } = params;
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
- return await SH`${searchSessionsSh} "${bashEscape(params.query)}"`.run();
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
- const file = params.file?.trim();
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. Auto-syntax check + JS fix + chmod.',
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
- const file = params.file?.trim();
246
- const content = params.content ?? '';
247
- if (typeof file !== 'string' || !file || file.startsWith('/') || file.includes('..') || file.includes('\\\\')) {
248
- throw new Error('Relative CWD path only.');
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(), file);
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 temp1 = path.join(dir, `temp_${Date.now()}_${Math.random().toString(36).slice(2,6)}${ext || '.tmp'}`);
257
- await fs.writeFile(temp1, content, 'utf8');
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} ${[temp1]}`.run();
261
- validationMsg = ' ✓ Syntax OK';
262
- } catch (e) {
263
- if (!['.js','.mjs'].includes(ext)) {
264
- await fs.unlink(temp1).catch(()=>{});
265
- throw new Error(`Syntax error: ${e.message}`);
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 (validationMsg === ' ✓ Syntax OK') await fs.rename(temp1, resolved);
284
-
285
- if (finalContent.startsWith('#!')) {
286
- await SH`chmod +x ${[resolved]}`.run();
287
- validationMsg += ' ✓ +x';
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 ${file} (${Buffer.byteLength(finalContent, 'utf8')} bytes).${validationMsg}`;
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
- return await SH`${syntaxCheckSh} ${[resolved]}`.run();
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.8",
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",
@@ -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> [--fix|--verbose]
4
- # Detects lang from ext/shebang, runs checker. Returns 0=OK, 1=error.
5
- # Integrates with write_file tool retries.
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
- VERBOSE="${2:-}"
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" && echo "JS OK" || { echo "❌ JS syntax error"; exit 1; }
65
+ run_check "node --check \"$FILE\"" "JS" || exit 1
41
66
  ;;
42
67
  py)
43
- python3 -m py_compile "$FILE" && echo "Python OK" || { echo "❌ Python syntax error"; exit 1; }
68
+ run_check "python3 -m py_compile \"$FILE\"" "Python" || exit 1
44
69
  ;;
45
70
  bash|sh)
46
- bash -n "$FILE" && echo "Bash OK" || { echo "❌ Bash syntax error"; exit 1; }
47
- # Optional: shellcheck if installed
48
- if command -v shellcheck >/dev/null; then
49
- shellcheck "$FILE" || echo "⚠️ Shellcheck warnings"
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 "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$FILE" && echo "✅ JSON OK" || { echo "❌ JSON invalid"; exit 1; }
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 lang for $EXT / shebang: $SHEBANG"
57
- exit 0 # Non-fatal
99
+ echo "⚠️ Unknown language for extension .$EXT / shebang: $SHEBANG"
100
+ echo "✅ Skipping syntax check"
101
+ exit 0
58
102
  ;;
59
103
  esac
60
104