@yeaft/webchat-agent 0.1.441 → 0.1.443
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/claude.js +41 -1
- package/package.json +1 -1
- package/unify/tools/agent.js +89 -0
- package/unify/tools/apply-patch.js +176 -0
- package/unify/tools/ask-user.js +62 -0
- package/unify/tools/bash.js +189 -0
- package/unify/tools/close-agent.js +58 -0
- package/unify/tools/file-edit.js +120 -0
- package/unify/tools/file-read.js +125 -0
- package/unify/tools/file-write.js +73 -0
- package/unify/tools/glob.js +143 -0
- package/unify/tools/grep.js +268 -0
- package/unify/tools/history-search.js +69 -0
- package/unify/tools/image-generation.js +97 -0
- package/unify/tools/index.js +92 -0
- package/unify/tools/js-repl.js +122 -0
- package/unify/tools/list-agents.js +56 -0
- package/unify/tools/list-dir.js +106 -0
- package/unify/tools/memory-read.js +91 -0
- package/unify/tools/memory-search.js +101 -0
- package/unify/tools/memory-write.js +114 -0
- package/unify/tools/notebook-edit.js +132 -0
- package/unify/tools/request-permissions.js +60 -0
- package/unify/tools/send-message.js +62 -0
- package/unify/tools/task-tools.js +358 -0
- package/unify/tools/tool-search.js +97 -0
- package/unify/tools/view-image.js +117 -0
- package/unify/tools/wait-agent.js +84 -0
- package/unify/tools/web-fetch.js +131 -0
- package/unify/tools/web-search.js +80 -0
- package/unify/tools/write-stdin.js +54 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* send-message.js — Send a message to a sub-agent.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineTool } from './types.js';
|
|
6
|
+
import { getAgentRegistry } from './agent.js';
|
|
7
|
+
|
|
8
|
+
export default defineTool({
|
|
9
|
+
name: 'SendMessage',
|
|
10
|
+
description: `Send a message to a sub-agent.
|
|
11
|
+
|
|
12
|
+
Use this to give tasks, provide instructions, or relay information to a sub-agent.
|
|
13
|
+
The message is queued for the agent to process.`,
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
agent_id: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'The sub-agent ID (returned by Agent tool)',
|
|
20
|
+
},
|
|
21
|
+
message: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'The message to send to the agent',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['agent_id', 'message'],
|
|
27
|
+
},
|
|
28
|
+
modes: ['work'],
|
|
29
|
+
isConcurrencySafe: () => false,
|
|
30
|
+
isReadOnly: () => false,
|
|
31
|
+
async execute(input, ctx) {
|
|
32
|
+
const { agent_id, message } = input;
|
|
33
|
+
if (!agent_id) return JSON.stringify({ error: 'agent_id is required' });
|
|
34
|
+
if (!message) return JSON.stringify({ error: 'message is required' });
|
|
35
|
+
|
|
36
|
+
const agents = getAgentRegistry();
|
|
37
|
+
const agent = agents.get(agent_id);
|
|
38
|
+
|
|
39
|
+
if (!agent) {
|
|
40
|
+
return JSON.stringify({ error: `Agent not found: ${agent_id}` });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (agent.status === 'closed') {
|
|
44
|
+
return JSON.stringify({ error: `Agent "${agent.name}" is closed` });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
agent.messages.push({
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: message,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
});
|
|
52
|
+
agent.status = 'active';
|
|
53
|
+
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
success: true,
|
|
56
|
+
agentId: agent_id,
|
|
57
|
+
name: agent.name,
|
|
58
|
+
messageCount: agent.messages.length,
|
|
59
|
+
message: `Message sent to agent "${agent.name}"`,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task management tools — in-memory task tracking for work mode.
|
|
3
|
+
*
|
|
4
|
+
* Tasks are organized in a simple flat list with status tracking.
|
|
5
|
+
* Persisted only in memory for the session duration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTool } from './types.js';
|
|
9
|
+
import { randomUUID } from 'crypto';
|
|
10
|
+
|
|
11
|
+
/** In-memory task store. */
|
|
12
|
+
const tasks = new Map();
|
|
13
|
+
|
|
14
|
+
/** Plan text (free-form markdown). */
|
|
15
|
+
let currentPlan = '';
|
|
16
|
+
|
|
17
|
+
/** Get task store for other tools. */
|
|
18
|
+
export function getTaskStore() {
|
|
19
|
+
return tasks;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getPlan() {
|
|
23
|
+
return currentPlan;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ─── TaskCreate ─────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export const taskCreate = defineTool({
|
|
29
|
+
name: 'TaskCreate',
|
|
30
|
+
description: `Create a new task for tracking work progress.
|
|
31
|
+
|
|
32
|
+
Tasks have a title, description, priority, and status.
|
|
33
|
+
Use this to break down complex work into trackable items.`,
|
|
34
|
+
parameters: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
title: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Short task title',
|
|
40
|
+
},
|
|
41
|
+
description: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Detailed task description',
|
|
44
|
+
},
|
|
45
|
+
priority: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
enum: ['low', 'medium', 'high', 'critical'],
|
|
48
|
+
description: 'Task priority (default: "medium")',
|
|
49
|
+
},
|
|
50
|
+
parent_id: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Parent task ID for subtasks',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
required: ['title'],
|
|
56
|
+
},
|
|
57
|
+
modes: ['work'],
|
|
58
|
+
isConcurrencySafe: () => false,
|
|
59
|
+
isReadOnly: () => false,
|
|
60
|
+
async execute(input, ctx) {
|
|
61
|
+
const { title, description, priority = 'medium', parent_id } = input;
|
|
62
|
+
if (!title) return JSON.stringify({ error: 'title is required' });
|
|
63
|
+
|
|
64
|
+
const id = `task-${randomUUID().slice(0, 8)}`;
|
|
65
|
+
const task = {
|
|
66
|
+
id,
|
|
67
|
+
title,
|
|
68
|
+
description: description || '',
|
|
69
|
+
priority,
|
|
70
|
+
status: 'pending',
|
|
71
|
+
parentId: parent_id || null,
|
|
72
|
+
createdAt: Date.now(),
|
|
73
|
+
updatedAt: Date.now(),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
tasks.set(id, task);
|
|
77
|
+
|
|
78
|
+
return JSON.stringify({
|
|
79
|
+
success: true,
|
|
80
|
+
task: { id, title, priority, status: 'pending' },
|
|
81
|
+
message: `Task created: ${title} (${id})`,
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ─── TaskUpdate ─────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export const taskUpdate = defineTool({
|
|
89
|
+
name: 'TaskUpdate',
|
|
90
|
+
description: `Update a task's status, priority, or details.
|
|
91
|
+
|
|
92
|
+
Status values: pending, in_progress, completed, blocked, cancelled`,
|
|
93
|
+
parameters: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
task_id: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Task ID to update',
|
|
99
|
+
},
|
|
100
|
+
status: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
enum: ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'],
|
|
103
|
+
description: 'New task status',
|
|
104
|
+
},
|
|
105
|
+
priority: {
|
|
106
|
+
type: 'string',
|
|
107
|
+
enum: ['low', 'medium', 'high', 'critical'],
|
|
108
|
+
description: 'New priority',
|
|
109
|
+
},
|
|
110
|
+
title: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Updated title',
|
|
113
|
+
},
|
|
114
|
+
description: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Updated description',
|
|
117
|
+
},
|
|
118
|
+
result: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'Task result or completion notes',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
required: ['task_id'],
|
|
124
|
+
},
|
|
125
|
+
modes: ['work'],
|
|
126
|
+
isConcurrencySafe: () => false,
|
|
127
|
+
isReadOnly: () => false,
|
|
128
|
+
async execute(input, ctx) {
|
|
129
|
+
const { task_id, status, priority, title, description, result } = input;
|
|
130
|
+
if (!task_id) return JSON.stringify({ error: 'task_id is required' });
|
|
131
|
+
|
|
132
|
+
const task = tasks.get(task_id);
|
|
133
|
+
if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
|
|
134
|
+
|
|
135
|
+
if (status) task.status = status;
|
|
136
|
+
if (priority) task.priority = priority;
|
|
137
|
+
if (title) task.title = title;
|
|
138
|
+
if (description !== undefined) task.description = description;
|
|
139
|
+
if (result) task.result = result;
|
|
140
|
+
task.updatedAt = Date.now();
|
|
141
|
+
|
|
142
|
+
return JSON.stringify({
|
|
143
|
+
success: true,
|
|
144
|
+
task: { id: task.id, title: task.title, status: task.status, priority: task.priority },
|
|
145
|
+
message: `Task "${task.title}" updated`,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ─── TaskList ───────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
export const taskList = defineTool({
|
|
153
|
+
name: 'TaskList',
|
|
154
|
+
description: `List all tracked tasks with their status.
|
|
155
|
+
|
|
156
|
+
Shows task IDs, titles, status, and priority. Filter by status if needed.`,
|
|
157
|
+
parameters: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
status: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
enum: ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'],
|
|
163
|
+
description: 'Filter by status (optional)',
|
|
164
|
+
},
|
|
165
|
+
include_completed: {
|
|
166
|
+
type: 'boolean',
|
|
167
|
+
description: 'Include completed tasks (default: true)',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
modes: ['work'],
|
|
172
|
+
isConcurrencySafe: () => true,
|
|
173
|
+
isReadOnly: () => true,
|
|
174
|
+
async execute(input, ctx) {
|
|
175
|
+
const { status, include_completed = true } = input;
|
|
176
|
+
|
|
177
|
+
const taskList = [];
|
|
178
|
+
for (const [, task] of tasks) {
|
|
179
|
+
if (status && task.status !== status) continue;
|
|
180
|
+
if (!include_completed && task.status === 'completed') continue;
|
|
181
|
+
taskList.push({
|
|
182
|
+
id: task.id,
|
|
183
|
+
title: task.title,
|
|
184
|
+
status: task.status,
|
|
185
|
+
priority: task.priority,
|
|
186
|
+
parentId: task.parentId,
|
|
187
|
+
hasResult: !!task.result,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Sort: in_progress first, then pending, then others
|
|
192
|
+
const ORDER = { in_progress: 0, pending: 1, blocked: 2, completed: 3, cancelled: 4 };
|
|
193
|
+
taskList.sort((a, b) => (ORDER[a.status] ?? 5) - (ORDER[b.status] ?? 5));
|
|
194
|
+
|
|
195
|
+
return JSON.stringify({
|
|
196
|
+
tasks: taskList,
|
|
197
|
+
totalCount: taskList.length,
|
|
198
|
+
summary: {
|
|
199
|
+
pending: taskList.filter(t => t.status === 'pending').length,
|
|
200
|
+
in_progress: taskList.filter(t => t.status === 'in_progress').length,
|
|
201
|
+
completed: taskList.filter(t => t.status === 'completed').length,
|
|
202
|
+
blocked: taskList.filter(t => t.status === 'blocked').length,
|
|
203
|
+
},
|
|
204
|
+
}, null, 2);
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// ─── TaskGet ────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
export const taskGet = defineTool({
|
|
211
|
+
name: 'TaskGet',
|
|
212
|
+
description: `Get detailed information about a specific task.`,
|
|
213
|
+
parameters: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
task_id: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: 'Task ID to retrieve',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ['task_id'],
|
|
222
|
+
},
|
|
223
|
+
modes: ['work'],
|
|
224
|
+
isConcurrencySafe: () => true,
|
|
225
|
+
isReadOnly: () => true,
|
|
226
|
+
async execute(input, ctx) {
|
|
227
|
+
const { task_id } = input;
|
|
228
|
+
if (!task_id) return JSON.stringify({ error: 'task_id is required' });
|
|
229
|
+
|
|
230
|
+
const task = tasks.get(task_id);
|
|
231
|
+
if (!task) return JSON.stringify({ error: `Task not found: ${task_id}` });
|
|
232
|
+
|
|
233
|
+
// Find subtasks
|
|
234
|
+
const subtasks = [];
|
|
235
|
+
for (const [, t] of tasks) {
|
|
236
|
+
if (t.parentId === task_id) {
|
|
237
|
+
subtasks.push({ id: t.id, title: t.title, status: t.status });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return JSON.stringify({
|
|
242
|
+
...task,
|
|
243
|
+
subtasks,
|
|
244
|
+
}, null, 2);
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ─── FollowupTask ───────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
export const followupTask = defineTool({
|
|
251
|
+
name: 'FollowupTask',
|
|
252
|
+
description: `Create a follow-up task linked to an existing task.
|
|
253
|
+
|
|
254
|
+
Use when a completed task reveals additional work needed.
|
|
255
|
+
The new task is linked as a child of the original.`,
|
|
256
|
+
parameters: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
properties: {
|
|
259
|
+
parent_task_id: {
|
|
260
|
+
type: 'string',
|
|
261
|
+
description: 'ID of the original task',
|
|
262
|
+
},
|
|
263
|
+
title: {
|
|
264
|
+
type: 'string',
|
|
265
|
+
description: 'Follow-up task title',
|
|
266
|
+
},
|
|
267
|
+
description: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
description: 'Why this follow-up is needed',
|
|
270
|
+
},
|
|
271
|
+
priority: {
|
|
272
|
+
type: 'string',
|
|
273
|
+
enum: ['low', 'medium', 'high', 'critical'],
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
required: ['parent_task_id', 'title'],
|
|
277
|
+
},
|
|
278
|
+
modes: ['work'],
|
|
279
|
+
isConcurrencySafe: () => false,
|
|
280
|
+
isReadOnly: () => false,
|
|
281
|
+
async execute(input, ctx) {
|
|
282
|
+
const { parent_task_id, title, description, priority = 'medium' } = input;
|
|
283
|
+
if (!parent_task_id) return JSON.stringify({ error: 'parent_task_id is required' });
|
|
284
|
+
if (!title) return JSON.stringify({ error: 'title is required' });
|
|
285
|
+
|
|
286
|
+
const parent = tasks.get(parent_task_id);
|
|
287
|
+
if (!parent) return JSON.stringify({ error: `Parent task not found: ${parent_task_id}` });
|
|
288
|
+
|
|
289
|
+
const id = `task-${randomUUID().slice(0, 8)}`;
|
|
290
|
+
const task = {
|
|
291
|
+
id,
|
|
292
|
+
title,
|
|
293
|
+
description: description || `Follow-up to: ${parent.title}`,
|
|
294
|
+
priority,
|
|
295
|
+
status: 'pending',
|
|
296
|
+
parentId: parent_task_id,
|
|
297
|
+
createdAt: Date.now(),
|
|
298
|
+
updatedAt: Date.now(),
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
tasks.set(id, task);
|
|
302
|
+
|
|
303
|
+
return JSON.stringify({
|
|
304
|
+
success: true,
|
|
305
|
+
task: { id, title, priority, status: 'pending', parentId: parent_task_id },
|
|
306
|
+
message: `Follow-up task created: ${title} (linked to ${parent.title})`,
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// ─── UpdatePlan ─────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
export const updatePlan = defineTool({
|
|
314
|
+
name: 'UpdatePlan',
|
|
315
|
+
description: `Update or view the current execution plan.
|
|
316
|
+
|
|
317
|
+
The plan is a free-form markdown document that describes the overall
|
|
318
|
+
approach, steps, and status of the current work.`,
|
|
319
|
+
parameters: {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties: {
|
|
322
|
+
action: {
|
|
323
|
+
type: 'string',
|
|
324
|
+
enum: ['view', 'update', 'append'],
|
|
325
|
+
description: '"view" shows current plan, "update" replaces it, "append" adds to it',
|
|
326
|
+
},
|
|
327
|
+
content: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Plan content (for "update" and "append" actions)',
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
required: ['action'],
|
|
333
|
+
},
|
|
334
|
+
modes: ['work'],
|
|
335
|
+
isConcurrencySafe: () => false,
|
|
336
|
+
isReadOnly: (input) => input?.action === 'view',
|
|
337
|
+
async execute(input, ctx) {
|
|
338
|
+
const { action, content } = input;
|
|
339
|
+
|
|
340
|
+
switch (action) {
|
|
341
|
+
case 'view':
|
|
342
|
+
return currentPlan || '(No plan set yet)';
|
|
343
|
+
|
|
344
|
+
case 'update':
|
|
345
|
+
if (!content) return JSON.stringify({ error: 'content is required for "update"' });
|
|
346
|
+
currentPlan = content;
|
|
347
|
+
return JSON.stringify({ success: true, message: 'Plan updated', length: content.length });
|
|
348
|
+
|
|
349
|
+
case 'append':
|
|
350
|
+
if (!content) return JSON.stringify({ error: 'content is required for "append"' });
|
|
351
|
+
currentPlan = currentPlan ? `${currentPlan}\n\n${content}` : content;
|
|
352
|
+
return JSON.stringify({ success: true, message: 'Plan updated (appended)', length: currentPlan.length });
|
|
353
|
+
|
|
354
|
+
default:
|
|
355
|
+
return JSON.stringify({ error: `Unknown action: ${action}` });
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-search.js — Search available tools by name or description.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { defineTool } from './types.js';
|
|
6
|
+
|
|
7
|
+
export default defineTool({
|
|
8
|
+
name: 'ToolSearch',
|
|
9
|
+
description: `Search available tools by name or description keyword.
|
|
10
|
+
|
|
11
|
+
Use when you're unsure which tool to use for a task.
|
|
12
|
+
Returns matching tools with their descriptions and parameters.`,
|
|
13
|
+
parameters: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
query: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
description: 'Search keyword to match against tool names and descriptions',
|
|
19
|
+
},
|
|
20
|
+
mode: {
|
|
21
|
+
type: 'string',
|
|
22
|
+
enum: ['chat', 'work'],
|
|
23
|
+
description: 'Filter by mode (optional)',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['query'],
|
|
27
|
+
},
|
|
28
|
+
modes: ['chat', 'work'],
|
|
29
|
+
isConcurrencySafe: () => true,
|
|
30
|
+
isReadOnly: () => true,
|
|
31
|
+
async execute(input, ctx) {
|
|
32
|
+
const { query, mode } = input;
|
|
33
|
+
if (!query) return JSON.stringify({ error: 'query is required' });
|
|
34
|
+
|
|
35
|
+
// Access the tool registry through the engine context
|
|
36
|
+
// Since we don't have direct registry access, list what we know
|
|
37
|
+
const lowerQuery = query.toLowerCase();
|
|
38
|
+
|
|
39
|
+
// Get all tool definitions from the registry if available
|
|
40
|
+
// This is a self-referential tool — it describes the tools available to this engine
|
|
41
|
+
const toolList = [
|
|
42
|
+
{ name: 'AskUser', description: 'Ask the user a question', modes: ['chat', 'work'] },
|
|
43
|
+
{ name: 'MemoryRead', description: 'Read from memory system', modes: ['chat', 'work'] },
|
|
44
|
+
{ name: 'MemoryWrite', description: 'Write to memory system', modes: ['chat', 'work'] },
|
|
45
|
+
{ name: 'MemorySearch', description: 'Search memory entries', modes: ['chat', 'work'] },
|
|
46
|
+
{ name: 'WebSearch', description: 'Search the web', modes: ['chat', 'work'] },
|
|
47
|
+
{ name: 'WebFetch', description: 'Fetch web page content', modes: ['chat', 'work'] },
|
|
48
|
+
{ name: 'HistorySearch', description: 'Search conversation history', modes: ['chat', 'work'] },
|
|
49
|
+
{ name: 'Bash', description: 'Execute shell commands', modes: ['work'] },
|
|
50
|
+
{ name: 'FileRead', description: 'Read file with line numbers', modes: ['work'] },
|
|
51
|
+
{ name: 'FileWrite', description: 'Write/create files', modes: ['work'] },
|
|
52
|
+
{ name: 'FileEdit', description: 'Surgical string replacement in files', modes: ['work'] },
|
|
53
|
+
{ name: 'Glob', description: 'Find files by pattern', modes: ['work'] },
|
|
54
|
+
{ name: 'Grep', description: 'Search file contents', modes: ['work'] },
|
|
55
|
+
{ name: 'ListDir', description: 'List directory contents', modes: ['work'] },
|
|
56
|
+
{ name: 'ApplyPatch', description: 'Apply unified diff patches', modes: ['work'] },
|
|
57
|
+
{ name: 'Agent', description: 'Create sub-agents', modes: ['work'] },
|
|
58
|
+
{ name: 'SendMessage', description: 'Send message to sub-agent', modes: ['work'] },
|
|
59
|
+
{ name: 'WaitAgent', description: 'Wait for sub-agent result', modes: ['work'] },
|
|
60
|
+
{ name: 'CloseAgent', description: 'Close a sub-agent', modes: ['work'] },
|
|
61
|
+
{ name: 'ListAgents', description: 'List all sub-agents', modes: ['work'] },
|
|
62
|
+
{ name: 'TaskCreate', description: 'Create a task', modes: ['work'] },
|
|
63
|
+
{ name: 'TaskUpdate', description: 'Update task status', modes: ['work'] },
|
|
64
|
+
{ name: 'TaskList', description: 'List all tasks', modes: ['work'] },
|
|
65
|
+
{ name: 'TaskGet', description: 'Get task details', modes: ['work'] },
|
|
66
|
+
{ name: 'FollowupTask', description: 'Create follow-up task', modes: ['work'] },
|
|
67
|
+
{ name: 'UpdatePlan', description: 'View/update execution plan', modes: ['work'] },
|
|
68
|
+
{ name: 'JsRepl', description: 'JavaScript REPL evaluation', modes: ['chat', 'work'] },
|
|
69
|
+
{ name: 'JsReplReset', description: 'Reset REPL state', modes: ['chat', 'work'] },
|
|
70
|
+
{ name: 'NotebookEdit', description: 'Edit Jupyter notebooks', modes: ['work'] },
|
|
71
|
+
{ name: 'ImageGeneration', description: 'Generate images from text', modes: ['chat', 'work'] },
|
|
72
|
+
{ name: 'ViewImage', description: 'View image metadata', modes: ['chat', 'work'] },
|
|
73
|
+
{ name: 'RequestPermissions', description: 'Request dangerous operation permissions', modes: ['work'] },
|
|
74
|
+
{ name: 'WriteStdin', description: 'Write to running process stdin', modes: ['work'] },
|
|
75
|
+
{ name: 'Skill', description: 'Load skills from library', modes: ['chat', 'work'] },
|
|
76
|
+
{ name: 'EnterWorktree', description: 'Create git worktree', modes: ['work'] },
|
|
77
|
+
{ name: 'ExitWorktree', description: 'Exit git worktree', modes: ['work'] },
|
|
78
|
+
{ name: 'mcp_list_tools', description: 'List MCP server tools', modes: ['chat', 'work'] },
|
|
79
|
+
{ name: 'mcp_call_tool', description: 'Call MCP server tool', modes: ['chat', 'work'] },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
let results = toolList.filter(t =>
|
|
83
|
+
t.name.toLowerCase().includes(lowerQuery) ||
|
|
84
|
+
t.description.toLowerCase().includes(lowerQuery)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (mode) {
|
|
88
|
+
results = results.filter(t => t.modes.includes(mode));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return JSON.stringify({
|
|
92
|
+
results,
|
|
93
|
+
totalResults: results.length,
|
|
94
|
+
query,
|
|
95
|
+
}, null, 2);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* view-image.js — Read and describe an image file.
|
|
3
|
+
*
|
|
4
|
+
* Returns image metadata (dimensions, format, size).
|
|
5
|
+
* In a full multimodal integration, would pass the image to the LLM.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineTool } from './types.js';
|
|
9
|
+
import { stat, readFile } from 'fs/promises';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import { resolve, extname } from 'path';
|
|
12
|
+
|
|
13
|
+
/** Supported image formats. */
|
|
14
|
+
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.svg', '.ico']);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse basic image dimensions from headers.
|
|
18
|
+
*/
|
|
19
|
+
function parseImageDimensions(buffer, ext) {
|
|
20
|
+
try {
|
|
21
|
+
if (ext === '.png') {
|
|
22
|
+
// PNG: width at offset 16, height at 20 (big-endian 32-bit)
|
|
23
|
+
if (buffer.length >= 24) {
|
|
24
|
+
return { width: buffer.readUInt32BE(16), height: buffer.readUInt32BE(20) };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (ext === '.jpg' || ext === '.jpeg') {
|
|
28
|
+
// JPEG: scan for SOF0 marker (0xFF 0xC0)
|
|
29
|
+
for (let i = 0; i < buffer.length - 9; i++) {
|
|
30
|
+
if (buffer[i] === 0xFF && (buffer[i + 1] === 0xC0 || buffer[i + 1] === 0xC2)) {
|
|
31
|
+
return {
|
|
32
|
+
height: buffer.readUInt16BE(i + 5),
|
|
33
|
+
width: buffer.readUInt16BE(i + 7),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (ext === '.gif') {
|
|
39
|
+
// GIF: width at offset 6, height at 8 (little-endian 16-bit)
|
|
40
|
+
if (buffer.length >= 10) {
|
|
41
|
+
return { width: buffer.readUInt16LE(6), height: buffer.readUInt16LE(8) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Dimension parsing is best-effort
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default defineTool({
|
|
51
|
+
name: 'ViewImage',
|
|
52
|
+
description: `View an image file and get its metadata.
|
|
53
|
+
|
|
54
|
+
Returns image format, dimensions, and file size.
|
|
55
|
+
Supports PNG, JPEG, GIF, BMP, WebP, SVG, and ICO.`,
|
|
56
|
+
parameters: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
file_path: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Path to the image file',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
required: ['file_path'],
|
|
65
|
+
},
|
|
66
|
+
modes: ['chat', 'work'],
|
|
67
|
+
isConcurrencySafe: () => true,
|
|
68
|
+
isReadOnly: () => true,
|
|
69
|
+
async execute(input, ctx) {
|
|
70
|
+
const { file_path } = input;
|
|
71
|
+
if (!file_path) return JSON.stringify({ error: 'file_path is required' });
|
|
72
|
+
|
|
73
|
+
const cwd = ctx?.cwd || process.cwd();
|
|
74
|
+
const absPath = resolve(cwd, file_path);
|
|
75
|
+
|
|
76
|
+
if (!existsSync(absPath)) {
|
|
77
|
+
return JSON.stringify({ error: `Image not found: ${absPath}` });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const ext = extname(absPath).toLowerCase();
|
|
81
|
+
if (!IMAGE_EXTS.has(ext)) {
|
|
82
|
+
return JSON.stringify({ error: `Not a recognized image format: ${ext}` });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const fileStat = await stat(absPath);
|
|
87
|
+
const buffer = await readFile(absPath);
|
|
88
|
+
|
|
89
|
+
const dimensions = parseImageDimensions(buffer, ext);
|
|
90
|
+
|
|
91
|
+
const result = {
|
|
92
|
+
path: absPath,
|
|
93
|
+
format: ext.slice(1).toUpperCase(),
|
|
94
|
+
size: fileStat.size,
|
|
95
|
+
sizeFormatted: fileStat.size < 1024 ? `${fileStat.size}B`
|
|
96
|
+
: fileStat.size < 1024 * 1024 ? `${(fileStat.size / 1024).toFixed(1)}KB`
|
|
97
|
+
: `${(fileStat.size / 1024 / 1024).toFixed(1)}MB`,
|
|
98
|
+
modified: fileStat.mtime.toISOString(),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (dimensions) {
|
|
102
|
+
result.width = dimensions.width;
|
|
103
|
+
result.height = dimensions.height;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// For SVG, include a text preview
|
|
107
|
+
if (ext === '.svg') {
|
|
108
|
+
const svgText = buffer.toString('utf-8');
|
|
109
|
+
result.preview = svgText.slice(0, 500);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return JSON.stringify(result, null, 2);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
return JSON.stringify({ error: `Failed to read image: ${err.message}` });
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|