@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.
- package/docs/cli.md +2 -1
- package/docs/setup.md +45 -2
- package/docs/telegram.md +235 -0
- package/package.json +3 -1
- package/src/channels/telegram/index.js +70 -0
- package/src/channels/telegram/sessions.js +18 -0
- package/src/index.js +43 -2
- package/src/scripts/onboarding.js +90 -20
- package/src/server/agent.js +15 -5
- package/src/server/app.js +2 -0
- package/src/server/config.js +4 -0
- package/src/server/tools.js +47 -0
- package/ui/dist/assets/{index-BFT9aOnN.js → index-DLrFBZmf.js} +3 -3
- package/ui/dist/index.html +1 -1
package/src/server/agent.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
package/src/server/config.js
CHANGED
|
@@ -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
|
|
package/src/server/tools.js
CHANGED
|
@@ -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',
|