@newsails/veil-cli 1.0.1
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/.veil/agents/analyst/AGENT.md +21 -0
- package/.veil/agents/analyst/agent.json +23 -0
- package/.veil/agents/assistant/AGENT.md +15 -0
- package/.veil/agents/assistant/agent.json +19 -0
- package/.veil/agents/coder/AGENT.md +18 -0
- package/.veil/agents/coder/agent.json +19 -0
- package/.veil/agents/hello/AGENT.md +5 -0
- package/.veil/agents/hello/agent.json +13 -0
- package/.veil/agents/writer/AGENT.md +12 -0
- package/.veil/agents/writer/agent.json +17 -0
- package/.veil/memory/MEMORY.md +343 -0
- package/.veil/memory/agents/analyst/MEMORY.md +55 -0
- package/.veil/memory/agents/hello/MEMORY.md +12 -0
- package/.veil/runtime.pid +1 -0
- package/.veil/settings.json +10 -0
- package/.veil-studio/studio.db +0 -0
- package/.veil-studio/studio.db-shm +0 -0
- package/.veil-studio/studio.db-wal +0 -0
- package/PLAN/01-vision.md +26 -0
- package/PLAN/02-tech-stack.md +94 -0
- package/PLAN/03-agents.md +232 -0
- package/PLAN/04-runtime.md +171 -0
- package/PLAN/05-tools.md +211 -0
- package/PLAN/06-communication.md +243 -0
- package/PLAN/07-storage.md +218 -0
- package/PLAN/08-api-cli.md +153 -0
- package/PLAN/09-permissions.md +108 -0
- package/PLAN/10-ably.md +105 -0
- package/PLAN/11-file-formats.md +442 -0
- package/PLAN/12-folder-structure.md +205 -0
- package/PLAN/13-operations.md +212 -0
- package/PLAN/README.md +23 -0
- package/README.md +128 -0
- package/REPORT.md +174 -0
- package/TODO.md +45 -0
- package/ai-tests/FRONTEND_PROMPT.md +220 -0
- package/ai-tests/Research & Planning.md +814 -0
- package/ai-tests/prompt-001-basic-api.md +230 -0
- package/ai-tests/prompt-002-basic-flows.md +230 -0
- package/ai-tests/prompt-003-agent-behaviors.md +220 -0
- package/api/middleware.js +60 -0
- package/api/routes/agents.js +193 -0
- package/api/routes/chat.js +93 -0
- package/api/routes/completions.js +122 -0
- package/api/routes/daemons.js +80 -0
- package/api/routes/memory.js +169 -0
- package/api/routes/models.js +40 -0
- package/api/routes/remote-methods.js +74 -0
- package/api/routes/sessions.js +208 -0
- package/api/routes/settings.js +108 -0
- package/api/routes/system.js +50 -0
- package/api/routes/tasks.js +270 -0
- package/api/server.js +120 -0
- package/cli/formatter.js +70 -0
- package/cli/index.js +443 -0
- package/cli/parser.js +113 -0
- package/config/config.json +10 -0
- package/config/models.json +6826 -0
- package/core/agent.js +329 -0
- package/core/cancel.js +38 -0
- package/core/compaction.js +176 -0
- package/core/events.js +13 -0
- package/core/loop.js +564 -0
- package/core/memory.js +51 -0
- package/core/prompt.js +185 -0
- package/core/queue.js +96 -0
- package/core/registry.js +291 -0
- package/core/remote-methods.js +124 -0
- package/core/router.js +386 -0
- package/core/running-sessions.js +18 -0
- package/docs/api/01-system.md +84 -0
- package/docs/api/02-agents.md +374 -0
- package/docs/api/03-chat.md +269 -0
- package/docs/api/04-tasks.md +470 -0
- package/docs/api/05-sessions.md +444 -0
- package/docs/api/06-daemons.md +142 -0
- package/docs/api/07-memory.md +186 -0
- package/docs/api/08-settings.md +133 -0
- package/docs/api/09-models.md +119 -0
- package/docs/api/09-websocket.md +350 -0
- package/docs/api/10-completions.md +134 -0
- package/docs/api/README.md +116 -0
- package/docs/guide/01-quickstart.md +220 -0
- package/docs/guide/02-folder-structure.md +185 -0
- package/docs/guide/03-configuration.md +252 -0
- package/docs/guide/04-agents.md +267 -0
- package/docs/guide/05-cli.md +290 -0
- package/docs/guide/06-tools.md +643 -0
- package/docs/guide/07-permissions.md +236 -0
- package/docs/guide/08-memory.md +139 -0
- package/docs/guide/09-multi-agent.md +271 -0
- package/docs/guide/10-daemons.md +226 -0
- package/docs/guide/README.md +53 -0
- package/docs/index.html +623 -0
- package/examples/README.md +151 -0
- package/examples/agents/assistant/AGENT.md +31 -0
- package/examples/agents/assistant/SOUL.md +9 -0
- package/examples/agents/assistant/agent.json +74 -0
- package/examples/agents/hello/AGENT.md +15 -0
- package/examples/agents/hello/agent.json +14 -0
- package/examples/agents/monitor/AGENT.md +51 -0
- package/examples/agents/monitor/agent.json +33 -0
- package/examples/agents/monitor/heartbeats/monitor.md +24 -0
- package/examples/agents/orchestrator/AGENT.md +70 -0
- package/examples/agents/orchestrator/agent.json +30 -0
- package/examples/agents/researcher/AGENT.md +52 -0
- package/examples/agents/researcher/agent.json +49 -0
- package/examples/agents/researcher/skills/web-research.md +28 -0
- package/examples/skills/code-review.md +72 -0
- package/examples/skills/summarise.md +59 -0
- package/examples/skills/web-research.md +42 -0
- package/examples/tools/word-count/index.js +27 -0
- package/examples/tools/word-count/tool.json +18 -0
- package/infrastructure/database.js +563 -0
- package/infrastructure/scheduler.js +122 -0
- package/llm/client.js +206 -0
- package/migrations/001-initial.sql +121 -0
- package/migrations/002-debuggability.sql +13 -0
- package/migrations/003-drop-orphaned-columns.sql +72 -0
- package/migrations/004-session-message-token-fields.sql +78 -0
- package/migrations/005-session-thinking.sql +5 -0
- package/package.json +30 -0
- package/schemas/agent.json +143 -0
- package/schemas/settings.json +111 -0
- package/scripts/fetch-models.js +93 -0
- package/session-debug-scenario.md +248 -0
- package/settings/fields.js +52 -0
- package/system-prompts/base-core.md +7 -0
- package/system-prompts/environment.md +13 -0
- package/system-prompts/reminders/anti-drift.md +6 -0
- package/system-prompts/reminders/stall-recovery.md +10 -0
- package/system-prompts/safety-rules.md +25 -0
- package/system-prompts/task-heuristics.md +27 -0
- package/test/client.js +71 -0
- package/test/integration/01-health.test.js +25 -0
- package/test/integration/02-agents.test.js +80 -0
- package/test/integration/03-chat-hello.test.js +48 -0
- package/test/integration/04-chat-multiturn.test.js +61 -0
- package/test/integration/05-chat-writer.test.js +48 -0
- package/test/integration/06-task-basic.test.js +68 -0
- package/test/integration/07-task-tools.test.js +74 -0
- package/test/integration/08-task-code-analysis.test.js +69 -0
- package/test/integration/09-memory-analyst.test.js +63 -0
- package/test/integration/10-task-advanced.test.js +85 -0
- package/test/integration/11-sessions-advanced.test.js +84 -0
- package/test/integration/12-assistant-chat-tools.test.js +75 -0
- package/test/integration/13-edge-cases.test.js +99 -0
- package/test/integration/14-cancel.test.js +62 -0
- package/test/integration/15-debug.test.js +106 -0
- package/test/integration/16-memory-api.test.js +83 -0
- package/test/integration/17-settings-api.test.js +41 -0
- package/test/integration/18-tool-search-activation.test.js +119 -0
- package/test/results/.gitkeep +0 -0
- package/test/runner.js +206 -0
- package/test/smoke.js +216 -0
- package/tools/agent_message.js +85 -0
- package/tools/agent_send.js +80 -0
- package/tools/agent_spawn.js +44 -0
- package/tools/bash.js +49 -0
- package/tools/edit_file.js +41 -0
- package/tools/glob.js +64 -0
- package/tools/grep.js +82 -0
- package/tools/list_dir.js +63 -0
- package/tools/log_write.js +31 -0
- package/tools/memory_read.js +38 -0
- package/tools/memory_search.js +65 -0
- package/tools/memory_write.js +42 -0
- package/tools/read_file.js +48 -0
- package/tools/sleep.js +22 -0
- package/tools/task_create.js +41 -0
- package/tools/task_respond.js +37 -0
- package/tools/task_spawn.js +64 -0
- package/tools/task_status.js +39 -0
- package/tools/task_subscribe.js +37 -0
- package/tools/todo_read.js +26 -0
- package/tools/todo_write.js +38 -0
- package/tools/tool_activate.js +24 -0
- package/tools/tool_search.js +24 -0
- package/tools/web_fetch.js +50 -0
- package/tools/web_search.js +52 -0
- package/tools/write_file.js +28 -0
- package/ui/api.js +190 -0
- package/ui/app.js +281 -0
- package/ui/index.html +382 -0
- package/ui/views/agents.js +377 -0
- package/ui/views/chat.js +610 -0
- package/ui/views/connection.js +96 -0
- package/ui/views/daemons.js +129 -0
- package/ui/views/feed.js +194 -0
- package/ui/views/memory.js +263 -0
- package/ui/views/models.js +146 -0
- package/ui/views/sessions.js +314 -0
- package/ui/views/settings.js +142 -0
- package/ui/views/tasks.js +415 -0
- package/utils/context.js +49 -0
- package/utils/id.js +16 -0
- package/utils/models.js +88 -0
- package/utils/paths.js +213 -0
- package/utils/settings.js +172 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'task_create',
|
|
7
|
+
description: 'Create a new task for any agent. The task runs asynchronously. Use task_status to check progress.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
agent: { type: 'string', description: 'Name of the agent to run the task' },
|
|
12
|
+
input: { type: 'string', description: 'Task input / instructions' },
|
|
13
|
+
priority: { type: 'string', enum: ['high', 'normal', 'low'], description: 'Task priority (default: normal)' },
|
|
14
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags for filtering' },
|
|
15
|
+
maxIterations: { type: 'integer', description: 'Override max iterations for this task', minimum: 1 },
|
|
16
|
+
maxDurationSeconds: { type: 'integer', description: 'Override max duration in seconds', minimum: 1 },
|
|
17
|
+
},
|
|
18
|
+
required: ['agent', 'input'],
|
|
19
|
+
},
|
|
20
|
+
timeout: 5,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
async function execute({ agent, input, priority = 'normal', tags, maxIterations, maxDurationSeconds, _cwd, _taskId }) {
|
|
24
|
+
const taskId = db.createTask({
|
|
25
|
+
agentName: agent,
|
|
26
|
+
input,
|
|
27
|
+
priority,
|
|
28
|
+
tags,
|
|
29
|
+
maxIterations,
|
|
30
|
+
maxDurationSeconds,
|
|
31
|
+
parentTaskId: _taskId || null,
|
|
32
|
+
instanceFolder: _cwd,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Auto-subscribe the calling task to this new task's completion
|
|
36
|
+
// (done by the task runner when it picks up the task)
|
|
37
|
+
|
|
38
|
+
return JSON.stringify({ taskId, status: 'pending', agent, message: 'Task created. Use task_status to check progress.' });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'task_respond',
|
|
7
|
+
description: 'Respond to a task that is waiting for input (task mode only). Resumes the paused task with your message.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
taskId: { type: 'string', description: 'Task ID that is waiting' },
|
|
12
|
+
message: { type: 'string', description: 'Response message to send to the waiting task' },
|
|
13
|
+
},
|
|
14
|
+
required: ['taskId', 'message'],
|
|
15
|
+
},
|
|
16
|
+
timeout: 5,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function execute({ taskId, message }) {
|
|
20
|
+
const task = db.getTask(taskId);
|
|
21
|
+
if (!task) return `Task not found: ${taskId}`;
|
|
22
|
+
if (task.status !== 'waiting') return `Task ${taskId} is not waiting (status: ${task.status})`;
|
|
23
|
+
|
|
24
|
+
db.enqueueAgentMessage({
|
|
25
|
+
targetAgent: task.agent_name,
|
|
26
|
+
fromAgent: 'user',
|
|
27
|
+
content: message,
|
|
28
|
+
followup: false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
db.updateTask(taskId, { status: 'processing' });
|
|
32
|
+
db.addTaskEvent({ taskId, type: 'status.change', data: { from: 'waiting', to: 'processing', reason: 'task_respond' } });
|
|
33
|
+
|
|
34
|
+
return `Response sent to task ${taskId}. Task resumed.`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'task_spawn',
|
|
7
|
+
description: 'Spawn a sub-agent task with an isolated context. Sync (wait:true, default) or async (wait:false for parallel fan-out). Returns task result or taskId.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
agent: { type: 'string', description: 'Name of the agent to spawn a task for' },
|
|
12
|
+
instruction: { type: 'string', description: 'Instruction for the sub-agent. Use "/skill-name args" to trigger a skill.' },
|
|
13
|
+
returnMode: { type: 'string', enum: ['summary', 'raw', 'lastMessage'], description: 'How to return results (default: summary)' },
|
|
14
|
+
wait: { type: 'boolean', description: 'true=sync (wait for result), false=async (returns taskId immediately). Default: true' },
|
|
15
|
+
},
|
|
16
|
+
required: ['agent', 'instruction'],
|
|
17
|
+
},
|
|
18
|
+
timeout: 86400,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
async function execute({ agent, instruction, returnMode = 'summary', wait = true, _cwd, _taskId, _sessionId, _settings, _agent: callerAgent }) {
|
|
22
|
+
const taskId = db.createTask({
|
|
23
|
+
agentName: agent,
|
|
24
|
+
input: instruction,
|
|
25
|
+
priority: 'normal',
|
|
26
|
+
parentTaskId: _taskId || null,
|
|
27
|
+
instanceFolder: _cwd,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Auto-subscribe caller to this task's completion
|
|
31
|
+
if (_sessionId) {
|
|
32
|
+
db.createTaskSubscription({
|
|
33
|
+
taskId,
|
|
34
|
+
subscriberSessionId: _sessionId,
|
|
35
|
+
subscriberAgent: callerAgent?.name || 'unknown',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
db.addTaskEvent({ taskId, type: 'status.change', data: { from: 'pending', to: 'pending', reason: 'task_spawn' } });
|
|
40
|
+
|
|
41
|
+
if (!wait) {
|
|
42
|
+
return JSON.stringify({ taskId, status: 'pending', agent, message: 'Task spawned async. Use task_status to check.' });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sync: run the subagent inline using the router
|
|
46
|
+
try {
|
|
47
|
+
const { runSubagent } = require('../core/router');
|
|
48
|
+
const result = await runSubagent({ taskId, cwd: _cwd, settings: _settings });
|
|
49
|
+
const task = db.getTask(taskId);
|
|
50
|
+
|
|
51
|
+
if (returnMode === 'raw') {
|
|
52
|
+
return JSON.stringify({ taskId, status: task.status, output: task.output });
|
|
53
|
+
}
|
|
54
|
+
if (returnMode === 'lastMessage') {
|
|
55
|
+
return task.output || '(no output)';
|
|
56
|
+
}
|
|
57
|
+
// summary (default) — return output directly
|
|
58
|
+
return task.output || `Sub-agent ${agent} completed with no output`;
|
|
59
|
+
} catch (err) {
|
|
60
|
+
return `Task error: ${err.message}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'task_status',
|
|
7
|
+
description: 'Check the status and output of a task.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
taskId: { type: 'string', description: 'Task ID to check' },
|
|
12
|
+
},
|
|
13
|
+
required: ['taskId'],
|
|
14
|
+
},
|
|
15
|
+
timeout: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function execute({ taskId }) {
|
|
19
|
+
const task = db.getTask(taskId);
|
|
20
|
+
if (!task) return `Task not found: ${taskId}`;
|
|
21
|
+
|
|
22
|
+
const result = {
|
|
23
|
+
taskId: task.id,
|
|
24
|
+
status: task.status,
|
|
25
|
+
agent: task.agent_name,
|
|
26
|
+
iterations: task.iterations,
|
|
27
|
+
createdAt: task.created_at,
|
|
28
|
+
updatedAt: task.updated_at,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (task.status === 'finished') result.output = task.output;
|
|
32
|
+
if (task.status === 'failed') result.error = task.error;
|
|
33
|
+
if (task.started_at) result.startedAt = task.started_at;
|
|
34
|
+
if (task.finished_at) result.finishedAt = task.finished_at;
|
|
35
|
+
|
|
36
|
+
return JSON.stringify(result, null, 2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'task_subscribe',
|
|
7
|
+
description: 'Subscribe to a task\'s completion. For external observers — own spawned tasks auto-subscribe. Subscription is durable (survives session closure).',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
taskId: { type: 'string', description: 'Task ID to subscribe to' },
|
|
12
|
+
},
|
|
13
|
+
required: ['taskId'],
|
|
14
|
+
},
|
|
15
|
+
timeout: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function execute({ taskId, _sessionId, _agent: callerAgent }) {
|
|
19
|
+
const task = db.getTask(taskId);
|
|
20
|
+
if (!task) return `Task not found: ${taskId}`;
|
|
21
|
+
|
|
22
|
+
if (task.status === 'finished' || task.status === 'failed' || task.status === 'canceled') {
|
|
23
|
+
return JSON.stringify({ taskId, status: task.status, message: 'Task already completed — no subscription needed' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (_sessionId) {
|
|
27
|
+
db.createTaskSubscription({
|
|
28
|
+
taskId,
|
|
29
|
+
subscriberSessionId: _sessionId,
|
|
30
|
+
subscriberAgent: callerAgent?.name || 'unknown',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return JSON.stringify({ taskId, subscribed: true, message: `Subscribed to task ${taskId}. You will be notified on completion.` });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'todo_read',
|
|
7
|
+
description: 'Read the current TODO list for the active session.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {},
|
|
11
|
+
},
|
|
12
|
+
timeout: 5,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
async function execute({ _sessionId }) {
|
|
16
|
+
if (!_sessionId) return 'No active session';
|
|
17
|
+
const todos = db.getTodos(_sessionId);
|
|
18
|
+
if (todos.length === 0) return 'No TODOs yet. Use todo_write to create a plan.';
|
|
19
|
+
const lines = todos.map(t => {
|
|
20
|
+
const check = t.status === 'completed' ? 'x' : t.status === 'in_progress' ? '~' : ' ';
|
|
21
|
+
return `- [${check}] [${t.priority}] ${t.content}`;
|
|
22
|
+
});
|
|
23
|
+
return `Current TODOs (${todos.length}):\n${lines.join('\n')}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const db = require('../infrastructure/database');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'todo_write',
|
|
7
|
+
description: 'Create or update a structured TODO list that survives context compaction.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
todos: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
description: 'Full list of todo items (replaces existing list)',
|
|
14
|
+
items: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
required: ['id', 'content', 'status', 'priority'],
|
|
17
|
+
properties: {
|
|
18
|
+
id: { type: 'string' },
|
|
19
|
+
content: { type: 'string' },
|
|
20
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
21
|
+
priority: { type: 'string', enum: ['high', 'medium', 'low'] },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: ['todos'],
|
|
27
|
+
},
|
|
28
|
+
timeout: 5,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
async function execute({ todos, _sessionId }) {
|
|
32
|
+
if (!_sessionId) return 'No active session for todos';
|
|
33
|
+
db.saveTodos(_sessionId, todos);
|
|
34
|
+
const summary = todos.map(t => `- [${t.status === 'completed' ? 'x' : ' '}] ${t.content}`).join('\n');
|
|
35
|
+
return `TODOs saved (${todos.length} items):\n${summary}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const registry = require('../core/registry');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'tool_activate',
|
|
7
|
+
description: 'Make a custom tool callable by loading its full schema into the active tools list. Call this after tool_search once you have decided to use a specific custom tool.',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
name: { type: 'string', description: 'Exact tool name to activate (as returned by tool_search)' },
|
|
12
|
+
},
|
|
13
|
+
required: ['name'],
|
|
14
|
+
},
|
|
15
|
+
timeout: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function execute({ name, _agent, _cwd }) {
|
|
19
|
+
const tool = registry.resolveTool({ name, agentConfig: _agent, cwd: _cwd });
|
|
20
|
+
if (!tool) return `Tool "${name}" not found. Use tool_search to discover available tools.`;
|
|
21
|
+
return JSON.stringify({ activated: name });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const registry = require('../core/registry');
|
|
4
|
+
|
|
5
|
+
const schema = {
|
|
6
|
+
name: 'tool_search',
|
|
7
|
+
description: 'Search available tools by name or description. Returns full schemas for matching tools (Tier 3 loading).',
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
query: { type: 'string', description: 'Search query (tool name or description keyword)' },
|
|
12
|
+
},
|
|
13
|
+
required: ['query'],
|
|
14
|
+
},
|
|
15
|
+
timeout: 5,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
async function execute({ query, _agent, _cwd }) {
|
|
19
|
+
const results = registry.searchTools({ query, agentConfig: _agent, cwd: _cwd });
|
|
20
|
+
if (results.length === 0) return `No tools found matching: ${query}`;
|
|
21
|
+
return JSON.stringify(results, null, 2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const schema = {
|
|
4
|
+
name: 'web_fetch',
|
|
5
|
+
description: 'Fetch content from a URL. Returns text, JSON, or base64 for images.',
|
|
6
|
+
input_schema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
url: { type: 'string', description: 'URL to fetch' },
|
|
10
|
+
mode: { type: 'string', enum: ['text', 'json', 'base64'], description: 'Response format (default: text)' },
|
|
11
|
+
},
|
|
12
|
+
required: ['url'],
|
|
13
|
+
},
|
|
14
|
+
timeout: 30,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function execute({ url, mode = 'text' }) {
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
headers: { 'User-Agent': 'VeilCLI/1.0' },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
return `HTTP ${response.status}: ${response.statusText} for ${url}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (mode === 'json') {
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return JSON.stringify(data, null, 2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (mode === 'base64') {
|
|
32
|
+
const buffer = await response.arrayBuffer();
|
|
33
|
+
return Buffer.from(buffer).toString('base64');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// text mode — strip HTML tags for cleaner output
|
|
37
|
+
let text = await response.text();
|
|
38
|
+
const contentType = response.headers.get('content-type') || '';
|
|
39
|
+
if (contentType.includes('text/html')) {
|
|
40
|
+
text = text
|
|
41
|
+
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
|
42
|
+
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
|
|
43
|
+
.replace(/<[^>]+>/g, ' ')
|
|
44
|
+
.replace(/\s{2,}/g, ' ')
|
|
45
|
+
.trim();
|
|
46
|
+
}
|
|
47
|
+
return text.slice(0, 50000); // cap at 50KB
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const schema = {
|
|
4
|
+
name: 'web_search',
|
|
5
|
+
description: 'Search the web and return a list of results with titles, URLs, and snippets.',
|
|
6
|
+
input_schema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
query: { type: 'string', description: 'Search query' },
|
|
10
|
+
numResults: { type: 'integer', description: 'Number of results to return (default: 5, max: 10)', minimum: 1, maximum: 10 },
|
|
11
|
+
},
|
|
12
|
+
required: ['query'],
|
|
13
|
+
},
|
|
14
|
+
timeout: 15,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function execute({ query, numResults = 5 }) {
|
|
18
|
+
// Using DuckDuckGo HTML search as a no-auth fallback
|
|
19
|
+
const encoded = encodeURIComponent(query);
|
|
20
|
+
const url = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(url, {
|
|
24
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (compatible; VeilCLI/1.0)' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!response.ok) return `Search failed: HTTP ${response.status}`;
|
|
28
|
+
|
|
29
|
+
const html = await response.text();
|
|
30
|
+
|
|
31
|
+
// Parse results from DDG HTML
|
|
32
|
+
const resultRegex = /<a class="result__a" href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
33
|
+
const results = [];
|
|
34
|
+
let match;
|
|
35
|
+
|
|
36
|
+
while ((match = resultRegex.exec(html)) !== null && results.length < numResults) {
|
|
37
|
+
const url = match[1];
|
|
38
|
+
const title = match[2].replace(/<[^>]+>/g, '').trim();
|
|
39
|
+
const snippet = match[3].replace(/<[^>]+>/g, '').trim();
|
|
40
|
+
if (url && title) {
|
|
41
|
+
results.push(`${results.length + 1}. **${title}**\n URL: ${url}\n ${snippet}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (results.length === 0) return `No results found for: ${query}`;
|
|
46
|
+
return `Search results for "${query}":\n\n${results.join('\n\n')}`;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return `Search error: ${err.message}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { schema, execute };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const schema = {
|
|
7
|
+
name: 'write_file',
|
|
8
|
+
description: 'Write or overwrite an entire file. Creates parent directories if needed. Prefer edit_file for modifications to existing files.',
|
|
9
|
+
input_schema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
file: { type: 'string', description: 'Path to the file (relative to cwd or absolute)' },
|
|
13
|
+
content: { type: 'string', description: 'Full content to write to the file' },
|
|
14
|
+
},
|
|
15
|
+
required: ['file', 'content'],
|
|
16
|
+
},
|
|
17
|
+
timeout: 10,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function execute({ file, content, _cwd }) {
|
|
21
|
+
const filePath = path.isAbsolute(file) ? file : path.resolve(_cwd, file);
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
24
|
+
const lines = content.split('\n').length;
|
|
25
|
+
return `Written ${lines} lines to ${filePath}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { schema, execute };
|
package/ui/api.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
window.Veil = window.Veil || {};
|
|
4
|
+
|
|
5
|
+
window.Veil.API = class API {
|
|
6
|
+
constructor(baseUrl, secret) {
|
|
7
|
+
this.baseUrl = (baseUrl || '').replace(/\/+$/, '');
|
|
8
|
+
this.secret = secret || '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_headers(extra) {
|
|
12
|
+
const h = Object.assign({ 'Content-Type': 'application/json' }, extra);
|
|
13
|
+
if (this.secret) h['X-Veil-Secret'] = this.secret;
|
|
14
|
+
return h;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async _fetch(method, path, body) {
|
|
18
|
+
const opts = { method, headers: this._headers() };
|
|
19
|
+
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
20
|
+
let res;
|
|
21
|
+
try {
|
|
22
|
+
res = await fetch(this.baseUrl + path, opts);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
const e = new Error('Network error: ' + err.message);
|
|
25
|
+
e.status = 0; throw e;
|
|
26
|
+
}
|
|
27
|
+
const ct = res.headers.get('content-type') || '';
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
let errBody;
|
|
30
|
+
try { errBody = ct.includes('json') ? await res.json() : await res.text(); }
|
|
31
|
+
catch { errBody = 'HTTP ' + res.status; }
|
|
32
|
+
const msg = typeof errBody === 'object'
|
|
33
|
+
? (errBody.error || errBody.message || JSON.stringify(errBody))
|
|
34
|
+
: (errBody || 'HTTP ' + res.status);
|
|
35
|
+
const e = new Error(msg);
|
|
36
|
+
e.status = res.status; e.body = errBody; throw e;
|
|
37
|
+
}
|
|
38
|
+
if (ct.includes('json')) return res.json();
|
|
39
|
+
return res.text();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get(path) { return this._fetch('GET', path); }
|
|
43
|
+
post(path, body) { return this._fetch('POST', path, body); }
|
|
44
|
+
put(path, body) { return this._fetch('PUT', path, body); }
|
|
45
|
+
delete(path) { return this._fetch('DELETE', path); }
|
|
46
|
+
|
|
47
|
+
/* SSE helper: splits chunks on \n\n event boundaries */
|
|
48
|
+
async _readSSE(response, onEvent) {
|
|
49
|
+
const reader = response.body.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
let buf = '';
|
|
52
|
+
while (true) {
|
|
53
|
+
const { done, value } = await reader.read();
|
|
54
|
+
if (done) break;
|
|
55
|
+
buf += decoder.decode(value, { stream: true });
|
|
56
|
+
const parts = buf.split('\n\n');
|
|
57
|
+
buf = parts.pop();
|
|
58
|
+
for (const block of parts) {
|
|
59
|
+
if (!block.trim()) continue;
|
|
60
|
+
let type = null, data = null;
|
|
61
|
+
for (const line of block.split('\n')) {
|
|
62
|
+
if (line.startsWith('event: ')) type = line.slice(7).trim();
|
|
63
|
+
else if (line.startsWith('data: ')) data = line.slice(6).trim();
|
|
64
|
+
}
|
|
65
|
+
if (type && data) {
|
|
66
|
+
try { onEvent(type, JSON.parse(data)); } catch {}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Chat SSE stream */
|
|
73
|
+
async chatStream(agentName, body, cbs) {
|
|
74
|
+
const { onChunk, onToolName, onMessage, onDone, onError } = cbs;
|
|
75
|
+
let res;
|
|
76
|
+
try {
|
|
77
|
+
res = await fetch(`${this.baseUrl}/agents/${encodeURIComponent(agentName)}/chat`, {
|
|
78
|
+
method: 'POST', headers: this._headers(),
|
|
79
|
+
body: JSON.stringify(Object.assign({}, body, { sse: true })),
|
|
80
|
+
});
|
|
81
|
+
} catch (err) { onError && onError({ error: err.message }); return; }
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
let b; try { b = await res.json(); } catch { b = { error: 'HTTP ' + res.status }; }
|
|
84
|
+
onError && onError(b); return;
|
|
85
|
+
}
|
|
86
|
+
await this._readSSE(res, (type, data) => {
|
|
87
|
+
if (type === 'inference.chunk') onChunk && onChunk(data);
|
|
88
|
+
else if (type === 'inference.tool') onToolName && onToolName(data);
|
|
89
|
+
else if (type === 'message') onMessage && onMessage(data);
|
|
90
|
+
else if (type === 'done') onDone && onDone(data);
|
|
91
|
+
else if (type === 'error') onError && onError(data);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Task SSE stream — returns { close } */
|
|
96
|
+
taskStream(taskId, cbs) {
|
|
97
|
+
const { onStatus, onEvent, onError } = cbs;
|
|
98
|
+
const ctrl = new AbortController();
|
|
99
|
+
fetch(`${this.baseUrl}/tasks/${encodeURIComponent(taskId)}/stream`, {
|
|
100
|
+
headers: this._headers(), signal: ctrl.signal,
|
|
101
|
+
}).then(async res => {
|
|
102
|
+
if (!res.ok) { onError && onError('HTTP ' + res.status); return; }
|
|
103
|
+
await this._readSSE(res, (type, data) => {
|
|
104
|
+
if (type === 'status') onStatus && onStatus(data);
|
|
105
|
+
else if (type === 'task') onEvent && onEvent(data);
|
|
106
|
+
});
|
|
107
|
+
}).catch(err => { if (err.name !== 'AbortError') onError && onError(err.message); });
|
|
108
|
+
return { close: () => ctrl.abort() };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* WebSocket */
|
|
112
|
+
connectWS(onMessage, onOpen, onClose) {
|
|
113
|
+
const wsUrl = this.baseUrl.replace(/^http/, 'ws') + '/ws'
|
|
114
|
+
+ (this.secret ? '?secret=' + encodeURIComponent(this.secret) : '');
|
|
115
|
+
const ws = new WebSocket(wsUrl);
|
|
116
|
+
ws.onopen = () => onOpen && onOpen();
|
|
117
|
+
ws.onclose = () => onClose && onClose();
|
|
118
|
+
ws.onerror = () => ws.close();
|
|
119
|
+
ws.onmessage = e => { try { onMessage && onMessage(JSON.parse(e.data)); } catch {} };
|
|
120
|
+
return ws;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ── System ─────────────────────────────────────────────── */
|
|
124
|
+
health() { return this.get('/health'); }
|
|
125
|
+
status() { return this.get('/status'); }
|
|
126
|
+
|
|
127
|
+
/* ── Agents ─────────────────────────────────────────────── */
|
|
128
|
+
listAgents() { return this.get('/agents'); }
|
|
129
|
+
getAgent(name) { return this.get(`/agents/${encodeURIComponent(name)}`); }
|
|
130
|
+
createAgent(body) { return this.post('/agents', body); }
|
|
131
|
+
updateAgent(name, body) { return this.put(`/agents/${encodeURIComponent(name)}`, body); }
|
|
132
|
+
deleteAgent(name) { return this.delete(`/agents/${encodeURIComponent(name)}`); }
|
|
133
|
+
reloadAgent(name) { return this.post(`/agents/${encodeURIComponent(name)}/reload`, {}); }
|
|
134
|
+
getAgentSkills(name) { return this.get(`/agents/${encodeURIComponent(name)}/skills`); }
|
|
135
|
+
getAgentSessions(name, p) {
|
|
136
|
+
const q = p ? '?' + new URLSearchParams(p) : '';
|
|
137
|
+
return this.get(`/agents/${encodeURIComponent(name)}/sessions${q}`);
|
|
138
|
+
}
|
|
139
|
+
getAgentTasks(name, p) {
|
|
140
|
+
const q = p ? '?' + new URLSearchParams(p) : '';
|
|
141
|
+
return this.get(`/agents/${encodeURIComponent(name)}/tasks${q}`);
|
|
142
|
+
}
|
|
143
|
+
getAgentMemory(name) { return this.get(`/agents/${encodeURIComponent(name)}/memory`); }
|
|
144
|
+
getAgentMemoryFile(name, file) { return this.get(`/agents/${encodeURIComponent(name)}/memory/${encodeURIComponent(file)}`); }
|
|
145
|
+
putAgentMemoryFile(name, file, c) { return this.put(`/agents/${encodeURIComponent(name)}/memory/${encodeURIComponent(file)}`, { content: c }); }
|
|
146
|
+
deleteAgentMemoryFile(name, file) { return this.delete(`/agents/${encodeURIComponent(name)}/memory/${encodeURIComponent(file)}`); }
|
|
147
|
+
|
|
148
|
+
/* ── Chat ───────────────────────────────────────────────── */
|
|
149
|
+
chat(agentName, message, sessionId) {
|
|
150
|
+
const body = { message };
|
|
151
|
+
if (sessionId) body.sessionId = sessionId;
|
|
152
|
+
return this.post(`/agents/${encodeURIComponent(agentName)}/chat`, body);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ── Tasks ──────────────────────────────────────────────── */
|
|
156
|
+
createTask(agentName, body) { return this.post(`/agents/${encodeURIComponent(agentName)}/task`, body); }
|
|
157
|
+
listTasks(p) { const q = p ? '?' + new URLSearchParams(p) : ''; return this.get(`/tasks${q}`); }
|
|
158
|
+
getTask(id) { return this.get(`/tasks/${encodeURIComponent(id)}`); }
|
|
159
|
+
getTaskEvents(id, since) { return this.get(`/tasks/${encodeURIComponent(id)}/events?since=${since || 0}`); }
|
|
160
|
+
cancelTask(id) { return this.post(`/tasks/${encodeURIComponent(id)}/cancel`, {}); }
|
|
161
|
+
respondTask(id, message) { return this.post(`/tasks/${encodeURIComponent(id)}/respond`, { message }); }
|
|
162
|
+
deleteTask(id, force) { return this.delete(`/tasks/${encodeURIComponent(id)}${force ? '?force=true' : ''}`); }
|
|
163
|
+
|
|
164
|
+
/* ── Sessions ───────────────────────────────────────────── */
|
|
165
|
+
listSessions(p) { const q = p ? '?' + new URLSearchParams(p) : ''; return this.get(`/sessions${q}`); }
|
|
166
|
+
getSession(id) { return this.get(`/sessions/${encodeURIComponent(id)}`); }
|
|
167
|
+
getSessionMessages(id, p) { const q = p ? '?' + new URLSearchParams(p) : ''; return this.get(`/sessions/${encodeURIComponent(id)}/messages${q}`); }
|
|
168
|
+
resetSession(id) { return this.post(`/sessions/${encodeURIComponent(id)}/reset`, {}); }
|
|
169
|
+
deleteSession(id, hard) { return this.delete(`/sessions/${encodeURIComponent(id)}${hard ? '?hard=true' : ''}`); }
|
|
170
|
+
|
|
171
|
+
/* ── Daemons ────────────────────────────────────────────── */
|
|
172
|
+
listDaemons() { return this.get('/daemons'); }
|
|
173
|
+
startDaemon(name) { return this.post(`/agents/${encodeURIComponent(name)}/daemon/start`, {}); }
|
|
174
|
+
stopDaemon(name) { return this.post(`/agents/${encodeURIComponent(name)}/daemon/stop`, {}); }
|
|
175
|
+
triggerDaemon(name) { return this.post(`/agents/${encodeURIComponent(name)}/daemon/trigger`, {}); }
|
|
176
|
+
|
|
177
|
+
/* ── Memory (global) ────────────────────────────────────── */
|
|
178
|
+
listMemory() { return this.get('/memory'); }
|
|
179
|
+
getMemoryFile(file) { return this.get(`/memory/${encodeURIComponent(file)}`); }
|
|
180
|
+
putMemoryFile(file, c) { return this.put(`/memory/${encodeURIComponent(file)}`, { content: c }); }
|
|
181
|
+
deleteMemoryFile(file) { return this.delete(`/memory/${encodeURIComponent(file)}`); }
|
|
182
|
+
|
|
183
|
+
/* ── Settings ───────────────────────────────────────────── */
|
|
184
|
+
getSettings(level) { return this.get(`/settings?level=${level || 'merged'}`); }
|
|
185
|
+
putSettings(body, level){ return this.put(`/settings?level=${level || 'project'}`, body); }
|
|
186
|
+
|
|
187
|
+
/* ── Models ─────────────────────────────────────────────── */
|
|
188
|
+
listModels() { return this.get('/models'); }
|
|
189
|
+
refreshModels() { return this.post('/models/refresh', {}); }
|
|
190
|
+
};
|