@ducci/jarvis 1.0.9 → 1.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.
@@ -62,7 +62,9 @@ async function callModelWithFallback(client, config, messages, tools) {
62
62
  * Runs a single agent loop up to maxIterations.
63
63
  * Returns { iteration, response, logSummary, status, runToolCalls, checkpoint }.
64
64
  */
65
- async function runAgentLoop(client, config, session, tools, toolDefs, prepareMessages) {
65
+ async function runAgentLoop(client, config, session, prepareMessages) {
66
+ let tools = loadTools();
67
+ let toolDefs = getToolDefinitions(tools);
66
68
  let iteration = 0;
67
69
  const runToolCalls = [];
68
70
  let done = false;
@@ -113,6 +115,7 @@ async function runAgentLoop(client, config, session, tools, toolDefs, prepareMes
113
115
  tool_calls: assistantMessage.tool_calls,
114
116
  });
115
117
 
118
+ let toolsModified = false;
116
119
  for (const toolCall of assistantMessage.tool_calls) {
117
120
  const toolName = toolCall.function.name;
118
121
  let toolArgs;
@@ -131,6 +134,10 @@ async function runAgentLoop(client, config, session, tools, toolDefs, prepareMes
131
134
  toolStatus = 'error';
132
135
  }
133
136
 
137
+ if (toolName === 'save_tool' && toolStatus === 'ok') {
138
+ toolsModified = true;
139
+ }
140
+
134
141
  const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
135
142
  runToolCalls.push({ name: toolName, args: toolArgs, status: toolStatus, result: resultStr });
136
143
 
@@ -141,6 +148,12 @@ async function runAgentLoop(client, config, session, tools, toolDefs, prepareMes
141
148
  });
142
149
  }
143
150
 
151
+ // Reload tools if any were created/updated this iteration
152
+ if (toolsModified) {
153
+ tools = loadTools();
154
+ toolDefs = getToolDefinitions(tools);
155
+ }
156
+
144
157
  continue;
145
158
  }
146
159
 
@@ -251,9 +264,6 @@ export async function handleChat(config, requestSessionId, userMessage) {
251
264
  session.messages.push({ role: 'user', content: userMessage });
252
265
  session.metadata.handoffCount = 0;
253
266
 
254
- const tools = loadTools();
255
- const toolDefs = getToolDefinitions(tools);
256
-
257
267
  // Resolves {{user_info}} in system prompt at runtime (never persisted)
258
268
  function prepareMessages(messages) {
259
269
  return messages.map((msg, i) => {
@@ -272,7 +282,7 @@ export async function handleChat(config, requestSessionId, userMessage) {
272
282
  // Handoff loop
273
283
  try {
274
284
  while (true) {
275
- const run = await runAgentLoop(client, config, session, tools, toolDefs, prepareMessages);
285
+ const run = await runAgentLoop(client, config, session, prepareMessages);
276
286
  allToolCalls.push(...run.runToolCalls);
277
287
 
278
288
  if (run.status !== 'checkpoint_reached') {
package/src/server/app.js CHANGED
@@ -6,6 +6,7 @@ import { realpathSync } from 'fs';
6
6
  import { loadConfig, ensureDirectories } from './config.js';
7
7
  import { seedTools } from './tools.js';
8
8
  import { handleChat } from './agent.js';
9
+ import { startTelegramChannel } from '../channels/telegram/index.js';
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
@@ -74,6 +75,7 @@ function startServer() {
74
75
  const PORT = config.port;
75
76
  app.listen(PORT, () => {
76
77
  console.log(`Jarvis server listening on port ${PORT}`);
78
+ startTelegramChannel(config);
77
79
  });
78
80
  }
79
81
 
@@ -49,6 +49,10 @@ export function loadConfig() {
49
49
  maxIterations: settings.maxIterations || 10,
50
50
  maxHandoffs: settings.maxHandoffs || 5,
51
51
  port: settings.port || 18008,
52
+ telegram: {
53
+ token: process.env.TELEGRAM_BOT_TOKEN || null,
54
+ allowedUserIds: settings.channels?.telegram?.allowedUserIds || [],
55
+ },
52
56
  };
53
57
  }
54
58
 
@@ -90,6 +90,53 @@ const SEED_TOOLS = {
90
90
  },
91
91
  code: `const filePath = path.join(process.env.HOME, '.jarvis/data/user-info.json'); const raw = await fs.promises.readFile(filePath, 'utf8').catch(() => '{"items":[]}'); const { items } = JSON.parse(raw); return { status: 'ok', items };`,
92
92
  },
93
+ save_tool: {
94
+ definition: {
95
+ type: 'function',
96
+ function: {
97
+ name: 'save_tool',
98
+ description: 'Create or update a custom tool and make it available immediately in this session. Use this to build reusable JS tools for tasks you repeat. The tool code runs in Node.js and has access to: args, fs, path, process, require.',
99
+ parameters: {
100
+ type: 'object',
101
+ properties: {
102
+ name: {
103
+ type: 'string',
104
+ description: 'Tool name in snake_case (e.g. "parse_json_file"). Must be unique.',
105
+ },
106
+ description: {
107
+ type: 'string',
108
+ description: 'What the tool does. Be specific — the LLM uses this to decide when to call it.',
109
+ },
110
+ parameters: {
111
+ type: 'object',
112
+ description: 'JSON Schema object for the tool parameters (with type, properties, required fields).',
113
+ },
114
+ code: {
115
+ type: 'string',
116
+ description: 'The body of an async function. Must end with a return statement — the returned value becomes the tool result. Available bindings: args (your tool parameters), fs (node:fs), path (node:path), process, require. Do NOT wrap in a function declaration. Example: const raw = await fs.promises.readFile(args.filePath, "utf8"); const data = JSON.parse(raw); return { count: data.length, first: data[0] };',
117
+ },
118
+ },
119
+ required: ['name', 'description', 'parameters', 'code'],
120
+ },
121
+ },
122
+ },
123
+ code: `const toolsFile = path.join(process.env.HOME, '.jarvis/data/tools/tools.json'); const raw = await fs.promises.readFile(toolsFile, 'utf8').catch(() => '{}'); const tools = JSON.parse(raw); tools[args.name] = { definition: { type: 'function', function: { name: args.name, description: args.description, parameters: args.parameters } }, code: args.code }; await fs.promises.writeFile(toolsFile, JSON.stringify(tools, null, 2), 'utf8'); return { status: 'ok', saved: args.name };`,
124
+ },
125
+ list_tools: {
126
+ definition: {
127
+ type: 'function',
128
+ function: {
129
+ name: 'list_tools',
130
+ description: 'List all available tools with their names and descriptions. Use this to see what tools exist before creating a new one.',
131
+ parameters: {
132
+ type: 'object',
133
+ properties: {},
134
+ required: [],
135
+ },
136
+ },
137
+ },
138
+ code: `const toolsFile = path.join(process.env.HOME, '.jarvis/data/tools/tools.json'); const raw = await fs.promises.readFile(toolsFile, 'utf8').catch(() => '{}'); const tools = JSON.parse(raw); const list = Object.entries(tools).map(([name, t]) => ({ name, description: t.definition.function.description })); return { status: 'ok', tools: list };`,
139
+ },
93
140
  get_recent_sessions: {
94
141
  definition: {
95
142
  type: 'function',