@hamp10/agentforge 0.2.15 → 0.2.16

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/OllamaAgent.js +52 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamp10/agentforge",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "description": "AgentForge worker — connect your machine to agentforge.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,112 +8,63 @@ import { fileURLToPath } from 'url';
8
8
  const execAsync = promisify(exec);
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
 
11
- // Tool definitions — used both for embedded system prompt (qwen3 format)
12
- // and kept as structured objects for _toolDesc lookups.
11
+ // Minimal tool definitions — one compact JSON per line, embedded in system prompt.
12
+ // Ollama's `tools` API param is broken for qwen3 (github.com/ollama/ollama/issues/14601).
13
+ // Descriptions kept short to fit within qwen3-vl:8b's 4096 token context.
13
14
  const TOOL_DEFS = [
14
15
  {
15
16
  type: 'function',
16
17
  function: {
17
18
  name: 'bash',
18
- description: 'Execute a shell command in the working directory. Returns stdout and stderr.',
19
- parameters: {
20
- type: 'object',
21
- properties: {
22
- command: { type: 'string', description: 'The shell command to run' }
23
- },
24
- required: ['command']
25
- }
19
+ description: 'Run a shell command. Returns stdout/stderr.',
20
+ parameters: { type: 'object', properties: { command: { type: 'string' } }, required: ['command'] }
26
21
  }
27
22
  },
28
23
  {
29
24
  type: 'function',
30
25
  function: {
31
26
  name: 'read_file',
32
- description: 'Read the full contents of a file.',
33
- parameters: {
34
- type: 'object',
35
- properties: {
36
- path: { type: 'string', description: 'Path to the file (absolute or relative to workdir)' }
37
- },
38
- required: ['path']
39
- }
27
+ description: 'Read a file.',
28
+ parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] }
40
29
  }
41
30
  },
42
31
  {
43
32
  type: 'function',
44
33
  function: {
45
34
  name: 'write_file',
46
- description: 'Write content to a file, creating it and any missing parent directories.',
47
- parameters: {
48
- type: 'object',
49
- properties: {
50
- path: { type: 'string', description: 'Path to write (absolute or relative to workdir)' },
51
- content: { type: 'string', description: 'File content to write' }
52
- },
53
- required: ['path', 'content']
54
- }
35
+ description: 'Write a file.',
36
+ parameters: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } }, required: ['path', 'content'] }
55
37
  }
56
38
  },
57
39
  {
58
40
  type: 'function',
59
41
  function: {
60
42
  name: 'list_directory',
61
- description: 'List files and subdirectories at a path.',
62
- parameters: {
63
- type: 'object',
64
- properties: {
65
- path: { type: 'string', description: 'Directory path (absolute or relative to workdir)' }
66
- },
67
- required: ['path']
68
- }
43
+ description: 'List files in a directory.',
44
+ parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] }
69
45
  }
70
46
  },
71
47
  {
72
48
  type: 'function',
73
49
  function: {
74
50
  name: 'web_fetch',
75
- description: 'Fetch the text content of a URL (first 4000 chars).',
76
- parameters: {
77
- type: 'object',
78
- properties: {
79
- url: { type: 'string', description: 'URL to fetch' }
80
- },
81
- required: ['url']
82
- }
51
+ description: 'Fetch text content from a URL.',
52
+ parameters: { type: 'object', properties: { url: { type: 'string' } }, required: ['url'] }
83
53
  }
84
54
  },
85
55
  {
86
56
  type: 'function',
87
57
  function: {
88
58
  name: 'take_screenshot',
89
- description: 'Take a screenshot of the current screen or the agent browser (port 9223). Returns base64 image data you can analyze visually. Use this to check what a webpage looks like, verify a build result, or monitor a running process. Set send_to_user=true ONLY when the user explicitly asked to see a screenshot.',
90
- parameters: {
91
- type: 'object',
92
- properties: {
93
- target: {
94
- type: 'string',
95
- enum: ['screen', 'browser'],
96
- description: 'screen = full screen capture. browser = screenshot of the agent browser (port 9223).'
97
- },
98
- url: {
99
- type: 'string',
100
- description: 'Optional: navigate the browser to this URL before taking the screenshot.'
101
- },
102
- send_to_user: {
103
- type: 'boolean',
104
- description: 'If true, send the screenshot to the user\'s chat. Only set this when the user explicitly asked to see a screenshot or visual output.'
105
- }
106
- },
107
- required: ['target']
108
- }
59
+ description: 'Screenshot the screen. Set send_to_user=true only if user asked to see it.',
60
+ parameters: { type: 'object', properties: { target: { type: 'string', enum: ['screen', 'browser'] }, send_to_user: { type: 'boolean' } }, required: ['target'] }
109
61
  }
110
62
  }
111
63
  ];
112
64
 
113
- // Build the <tools> XML block to embed in the system prompt.
114
- // Ollama's `tools` API parameter is broken for qwen3 models (malformed JSON in the prompt).
115
- // The reliable fix is to embed tool definitions directly in the system prompt as XML.
116
- const TOOLS_XML = `<tools>\n${TOOL_DEFS.map(t => JSON.stringify(t)).join('\n')}\n</tools>`;
65
+ // Minimal <tools> XML for system prompt one compact JSON per line, no outer array.
66
+ // Per qwen3 Hermes chat template (tokenizer_config.json).
67
+ const TOOLS_XML = `<tools>\n${TOOL_DEFS.map(t => JSON.stringify(t.function)).join('\n')}\n</tools>`;
117
68
 
118
69
  /**
119
70
  * Parse <tool_call>...</tool_call> blocks from streamed content.
@@ -281,24 +232,34 @@ export class OllamaAgent extends EventEmitter {
281
232
  // Load conversation history from disk (session persistence)
282
233
  const history = this._loadHistory(agentId, workDir, sessionId);
283
234
 
284
- // For qwen3 models: embed tool definitions in the system prompt.
285
- // Ollama's `tools` API param is broken for qwen3 (malformed JSON sent to model).
286
- // Embedding as XML matches the model's native Hermes-style chat template.
287
- const toolsBlock = isQwen3 ? `\n\n${TOOLS_XML}\n\nFor each tool call, output ONLY a <tool_call> block with no surrounding text:\n<tool_call>\n{"name": "<tool_name>", "arguments": {<args>}}\n</tool_call>` : '';
288
-
289
- const systemPrompt = [
290
- isQwen3 ? '/no_think' : null,
291
- `You are an AI agent running on AgentForge.ai.`,
292
- `Your working directory is: ${workDir}`,
293
- ``,
294
- `CRITICAL RULES:`,
295
- `1. Use tools to act. Do NOT describe steps or write code blocks — call the actual tool.`,
296
- `2. bash = run shell commands. write_file = write files. read_file = read files. take_screenshot = screenshot.`,
297
- `3. For conversational messages (greetings, casual chat) — respond with plain text. No tools needed.`,
298
- `4. Do not ask for clarification make your best judgment and act immediately.`,
299
- `5. After completing work, write a brief summary of what you did.`,
300
- toolsBlock,
301
- ].filter(Boolean).join('\n');
235
+ // System prompt uses the exact format from qwen3's Hermes chat template.
236
+ // Tools are embedded as <tools> XML never passed via the API `tools` param (broken in Ollama).
237
+ const systemPrompt = isQwen3
238
+ ? [
239
+ '/no_think',
240
+ `You are a helpful assistant. Working directory: ${workDir}`,
241
+ ``,
242
+ `# Tools`,
243
+ ``,
244
+ `You may call one or more functions to complete the task.`,
245
+ ``,
246
+ `You are provided with function signatures within <tools></tools> XML tags:`,
247
+ TOOLS_XML,
248
+ ``,
249
+ `For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:`,
250
+ `<tool_call>`,
251
+ `{"name": <function-name>, "arguments": <args-json-object>}`,
252
+ `</tool_call>`,
253
+ ``,
254
+ `Rules:`,
255
+ `- Call tools to take actions. Do NOT describe what you would do — just do it.`,
256
+ `- For simple conversation (greetings, questions) respond with plain text, no tools.`,
257
+ `- After finishing, write a brief summary.`,
258
+ ].join('\n')
259
+ : [
260
+ `You are a helpful AI agent. Working directory: ${workDir}`,
261
+ `Use the provided tools to complete tasks. Don't describe — act.`,
262
+ ].join('\n');
302
263
 
303
264
  const messages = [
304
265
  { role: 'system', content: systemPrompt },
@@ -329,10 +290,13 @@ export class OllamaAgent extends EventEmitter {
329
290
  model: effectiveModel,
330
291
  messages,
331
292
  stream: true,
332
- // qwen3: tools embedded in system prompt — do NOT pass tools param (broken in Ollama)
333
- // Other models: pass tools normally via API
293
+ // qwen3: tools embedded in system prompt — do NOT pass tools param (broken in Ollama for qwen3)
294
+ // Other models: pass tools normally
334
295
  ...(!isQwen3 ? { tools: TOOL_DEFS, tool_choice: 'auto' } : {}),
335
- ...(isQwen3 ? { options: { think: false } } : {}),
296
+ options: {
297
+ num_ctx: 8192, // explicit context — Ollama defaults to 2048 which is too small
298
+ ...(isQwen3 ? { think: false } : {}), // CRITICAL: thinking + tools corrupts template
299
+ },
336
300
  };
337
301
 
338
302
  response = await fetch(`${this.baseUrl}/v1/chat/completions`, {