@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,270 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const router = express.Router({ mergeParams: true });
|
|
5
|
+
const { runTask } = require('../../core/router');
|
|
6
|
+
const { loadAgent } = require('../../core/agent');
|
|
7
|
+
const { sendError } = require('../middleware');
|
|
8
|
+
const context = require('../../utils/context');
|
|
9
|
+
const db = require('../../infrastructure/database');
|
|
10
|
+
|
|
11
|
+
// POST /agents/:name/task — create a new task
|
|
12
|
+
router.post('/agents/:name/task', async (req, res, next) => {
|
|
13
|
+
try {
|
|
14
|
+
const { name } = req.params;
|
|
15
|
+
const { input, priority, tags, maxIterations, maxDurationSeconds, tokenBudget } = req.body;
|
|
16
|
+
const cwd = context.getCwd();
|
|
17
|
+
|
|
18
|
+
if (!input || typeof input !== 'string') {
|
|
19
|
+
return sendError(res, 400, 'VALIDATION_ERROR', 'input is required and must be a string');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let agent;
|
|
23
|
+
try {
|
|
24
|
+
agent = loadAgent({ cwd, name });
|
|
25
|
+
} catch {
|
|
26
|
+
return sendError(res, 404, 'AGENT_NOT_FOUND', `Agent "${name}" not found`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!agent.modes?.task?.enabled) {
|
|
30
|
+
return sendError(res, 400, 'MODE_NOT_SUPPORTED', `Agent "${name}" does not support task mode`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const taskId = db.createTask({
|
|
34
|
+
agentName: name,
|
|
35
|
+
input,
|
|
36
|
+
priority: priority || 'normal',
|
|
37
|
+
tags,
|
|
38
|
+
maxIterations,
|
|
39
|
+
maxDurationSeconds,
|
|
40
|
+
tokenBudget: tokenBudget || null,
|
|
41
|
+
instanceFolder: cwd,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Run task asynchronously (don't await — return taskId immediately)
|
|
45
|
+
const settings = req.app.locals.settings;
|
|
46
|
+
setImmediate(() => runTask({ taskId, cwd, settings }).catch(err => {
|
|
47
|
+
console.error(`Task ${taskId} failed:`, err.message);
|
|
48
|
+
db.updateTask(taskId, { status: 'failed', error: err.message });
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
res.status(202).json({ taskId, status: 'pending' });
|
|
52
|
+
} catch (err) {
|
|
53
|
+
next(err);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// GET /tasks — list tasks
|
|
58
|
+
router.get('/tasks', (req, res, next) => {
|
|
59
|
+
try {
|
|
60
|
+
const cwd = context.getCwd();
|
|
61
|
+
const { agentName, status, priority, limit, cursor } = req.query;
|
|
62
|
+
const tasks = db.listTasks({
|
|
63
|
+
instanceFolder: cwd,
|
|
64
|
+
agentName,
|
|
65
|
+
status,
|
|
66
|
+
priority,
|
|
67
|
+
limit: limit ? parseInt(limit, 10) : 20,
|
|
68
|
+
cursor,
|
|
69
|
+
});
|
|
70
|
+
res.json({ tasks });
|
|
71
|
+
} catch (err) {
|
|
72
|
+
next(err);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// GET /tasks/:id — get task details
|
|
77
|
+
router.get('/tasks/:id', (req, res, next) => {
|
|
78
|
+
try {
|
|
79
|
+
const task = db.getTask(req.params.id);
|
|
80
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
81
|
+
res.json({ task });
|
|
82
|
+
} catch (err) {
|
|
83
|
+
next(err);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// GET /tasks/:id/events — get task progress events
|
|
88
|
+
router.get('/tasks/:id/events', (req, res, next) => {
|
|
89
|
+
try {
|
|
90
|
+
const task = db.getTask(req.params.id);
|
|
91
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
92
|
+
const { since, limit } = req.query;
|
|
93
|
+
const events = db.getTaskEvents(req.params.id, {
|
|
94
|
+
since: since ? parseInt(since, 10) : 0,
|
|
95
|
+
limit: limit ? parseInt(limit, 10) : 500,
|
|
96
|
+
});
|
|
97
|
+
res.json({ taskId: req.params.id, events });
|
|
98
|
+
} catch (err) {
|
|
99
|
+
next(err);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// GET /tasks/:id/context — LLM context snapshot for debuggability
|
|
104
|
+
router.get('/tasks/:id/context', (req, res, next) => {
|
|
105
|
+
try {
|
|
106
|
+
const task = db.getTask(req.params.id);
|
|
107
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
108
|
+
const context = db.getTaskContext(req.params.id);
|
|
109
|
+
res.json({
|
|
110
|
+
taskId: req.params.id,
|
|
111
|
+
context,
|
|
112
|
+
note: context ? null : 'No context snapshot yet (task may still be pending or has not made an LLM call)',
|
|
113
|
+
});
|
|
114
|
+
} catch (err) {
|
|
115
|
+
next(err);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// POST /tasks/:id/respond — respond to a waiting task and resume it
|
|
120
|
+
router.post('/tasks/:id/respond', async (req, res, next) => {
|
|
121
|
+
try {
|
|
122
|
+
const task = db.getTask(req.params.id);
|
|
123
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
124
|
+
if (task.status !== 'waiting') return sendError(res, 400, 'TASK_NOT_WAITING', `Task is not in waiting status (current: ${task.status})`);
|
|
125
|
+
|
|
126
|
+
const { message } = req.body;
|
|
127
|
+
if (!message) return sendError(res, 400, 'VALIDATION_ERROR', 'message is required');
|
|
128
|
+
|
|
129
|
+
// Enqueue the response message so it's picked up by the loop
|
|
130
|
+
db.enqueueAgentMessage({
|
|
131
|
+
targetAgent: task.agent_name,
|
|
132
|
+
fromAgent: 'user',
|
|
133
|
+
content: message,
|
|
134
|
+
followup: false,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Respond immediately, then resume the task asynchronously
|
|
138
|
+
res.json({ taskId: req.params.id, status: 'processing' });
|
|
139
|
+
|
|
140
|
+
// Resume the task loop in the background
|
|
141
|
+
const { resumeTask } = require('../../core/router');
|
|
142
|
+
const cwd = req.app.locals.cwd;
|
|
143
|
+
const settings = req.app.locals.settings;
|
|
144
|
+
resumeTask({ taskId: req.params.id, cwd, settings }).catch(err => {
|
|
145
|
+
console.error(`[tasks] Failed to resume task ${req.params.id}:`, err.message);
|
|
146
|
+
});
|
|
147
|
+
} catch (err) {
|
|
148
|
+
next(err);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// DELETE /tasks/:id — hard-delete a task and all its associated data
|
|
153
|
+
router.delete('/tasks/:id', (req, res, next) => {
|
|
154
|
+
try {
|
|
155
|
+
const task = db.getTask(req.params.id);
|
|
156
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
157
|
+
|
|
158
|
+
const ACTIVE_STATUSES = ['pending', 'processing', 'waiting'];
|
|
159
|
+
if (ACTIVE_STATUSES.includes(task.status) && req.query.force !== 'true') {
|
|
160
|
+
return sendError(res, 409, 'TASK_ACTIVE', `Task is still active (status: ${task.status}). Use ?force=true to cancel and delete.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Cancel first if active and forced
|
|
164
|
+
if (ACTIVE_STATUSES.includes(task.status)) {
|
|
165
|
+
const cancelRegistry = require('../../core/cancel');
|
|
166
|
+
cancelRegistry.cancel(req.params.id);
|
|
167
|
+
db.updateTask(req.params.id, { status: 'canceled', finishedAt: new Date().toISOString() });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
db.deleteTask(req.params.id);
|
|
171
|
+
res.json({ deleted: true, taskId: req.params.id });
|
|
172
|
+
} catch (err) {
|
|
173
|
+
next(err);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// GET /tasks/:id/stream — SSE real-time event stream for a single task
|
|
178
|
+
router.get('/tasks/:id/stream', (req, res, next) => {
|
|
179
|
+
try {
|
|
180
|
+
const task = db.getTask(req.params.id);
|
|
181
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
182
|
+
|
|
183
|
+
const TERMINAL_STATUSES = new Set(['finished', 'failed', 'canceled']);
|
|
184
|
+
|
|
185
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
186
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
187
|
+
res.setHeader('Connection', 'keep-alive');
|
|
188
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
189
|
+
res.flushHeaders();
|
|
190
|
+
|
|
191
|
+
const taskId = req.params.id;
|
|
192
|
+
|
|
193
|
+
function send(eventType, data) {
|
|
194
|
+
try {
|
|
195
|
+
res.write(`event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
196
|
+
} catch { /* client disconnected */ }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 1. Replay historical DB events
|
|
200
|
+
const history = db.getTaskEvents(taskId, { since: 0, limit: 1000 });
|
|
201
|
+
for (const ev of history) {
|
|
202
|
+
send('task', { id: ev.id, type: ev.type, data: ev.data ? JSON.parse(ev.data) : null, created_at: ev.created_at });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 2. Send current task state
|
|
206
|
+
send('status', { taskId, status: task.status, output: task.output || null, error: task.error ? JSON.parse(task.error) : null });
|
|
207
|
+
|
|
208
|
+
// 3. If already terminal, close immediately
|
|
209
|
+
if (TERMINAL_STATUSES.has(task.status)) {
|
|
210
|
+
res.end();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 4. Subscribe to live events from the event bus
|
|
215
|
+
const eventBus = require('../../core/events');
|
|
216
|
+
|
|
217
|
+
function onEvent(msg) {
|
|
218
|
+
if (msg.taskId !== taskId) return;
|
|
219
|
+
|
|
220
|
+
send('task', { type: msg.type, event: msg.event, timestamp: msg.event?.timestamp });
|
|
221
|
+
|
|
222
|
+
// Auto-close when task reaches a terminal state
|
|
223
|
+
if (msg.type === 'task.status' && TERMINAL_STATUSES.has(msg.event?.status)) {
|
|
224
|
+
cleanup();
|
|
225
|
+
res.end();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
eventBus.on('event', onEvent);
|
|
230
|
+
|
|
231
|
+
// 5. Keepalive ping every 15s to prevent proxy timeouts
|
|
232
|
+
const keepalive = setInterval(() => {
|
|
233
|
+
try { res.write(': keepalive\n\n'); } catch { cleanup(); }
|
|
234
|
+
}, 15000);
|
|
235
|
+
|
|
236
|
+
function cleanup() {
|
|
237
|
+
clearInterval(keepalive);
|
|
238
|
+
eventBus.off('event', onEvent);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
req.on('close', cleanup);
|
|
242
|
+
req.on('error', cleanup);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
next(err);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// POST /tasks/:id/cancel — cancel a task (cooperative for processing tasks)
|
|
249
|
+
router.post('/tasks/:id/cancel', (req, res, next) => {
|
|
250
|
+
try {
|
|
251
|
+
const task = db.getTask(req.params.id);
|
|
252
|
+
if (!task) return sendError(res, 404, 'TASK_NOT_FOUND', `Task not found: ${req.params.id}`);
|
|
253
|
+
if (['finished', 'failed', 'canceled'].includes(task.status)) {
|
|
254
|
+
return sendError(res, 400, 'TASK_ALREADY_TERMINAL', `Task is already in terminal state: ${task.status}`);
|
|
255
|
+
}
|
|
256
|
+
// Signal the running loop to abort (cooperative cancellation)
|
|
257
|
+
const cancelRegistry = require('../../core/cancel');
|
|
258
|
+
cancelRegistry.cancel(req.params.id);
|
|
259
|
+
// Also update DB immediately for pending/waiting tasks that have no running loop
|
|
260
|
+
if (task.status !== 'processing') {
|
|
261
|
+
db.updateTask(req.params.id, { status: 'canceled', finishedAt: new Date().toISOString() });
|
|
262
|
+
db.addTaskEvent({ taskId: req.params.id, type: 'status.change', data: { from: task.status, to: 'canceled', reason: 'user_cancel' } });
|
|
263
|
+
}
|
|
264
|
+
res.json({ taskId: req.params.id, status: 'canceled' });
|
|
265
|
+
} catch (err) {
|
|
266
|
+
next(err);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
module.exports = router;
|
package/api/server.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
const { authMiddleware, errorHandler } = require('./middleware');
|
|
6
|
+
const agentsRouter = require('./routes/agents');
|
|
7
|
+
const chatRouter = require('./routes/chat');
|
|
8
|
+
const tasksRouter = require('./routes/tasks');
|
|
9
|
+
const sessionsRouter = require('./routes/sessions');
|
|
10
|
+
const daemonsRouter = require('./routes/daemons');
|
|
11
|
+
const systemRouter = require('./routes/system');
|
|
12
|
+
const memoryRouter = require('./routes/memory');
|
|
13
|
+
const settingsRouter = require('./routes/settings');
|
|
14
|
+
const modelsRouter = require('./routes/models');
|
|
15
|
+
const completionsRouter = require('./routes/completions');
|
|
16
|
+
const remoteMethodsRouter = require('./routes/remote-methods');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create and configure the Express app.
|
|
20
|
+
* @param {{ settings: Object, scheduler: Object }} opts
|
|
21
|
+
* @returns {import('express').Application}
|
|
22
|
+
*/
|
|
23
|
+
function createApp({ settings, scheduler }) {
|
|
24
|
+
const app = express();
|
|
25
|
+
|
|
26
|
+
// Store shared state in app.locals (no circular deps)
|
|
27
|
+
app.locals.settings = settings;
|
|
28
|
+
app.locals.scheduler = scheduler;
|
|
29
|
+
|
|
30
|
+
app.use(cors());
|
|
31
|
+
app.use(express.json({ limit: '10mb' }));
|
|
32
|
+
|
|
33
|
+
// Auth middleware on all routes
|
|
34
|
+
app.use(authMiddleware);
|
|
35
|
+
|
|
36
|
+
// ── Routes ────────────────────────────────────────────────────────────────
|
|
37
|
+
app.use('/agents', agentsRouter);
|
|
38
|
+
app.use('/agents/:name/chat', chatRouter);
|
|
39
|
+
|
|
40
|
+
// Task routes are mixed — some under /agents/:name/task, some under /tasks
|
|
41
|
+
app.use('/', tasksRouter);
|
|
42
|
+
|
|
43
|
+
app.use('/sessions', sessionsRouter);
|
|
44
|
+
|
|
45
|
+
// Daemon routes: GET /daemons + POST /agents/:name/daemon/*
|
|
46
|
+
app.use('/daemons', daemonsRouter);
|
|
47
|
+
app.use('/agents', daemonsRouter);
|
|
48
|
+
|
|
49
|
+
// Memory API: /agents/:name/memory/* (must come before generic agents router catches :name)
|
|
50
|
+
app.use('/agents', memoryRouter);
|
|
51
|
+
|
|
52
|
+
// Global memory API: /memory/*
|
|
53
|
+
app.use('/', memoryRouter);
|
|
54
|
+
|
|
55
|
+
// Settings API: GET/PUT /settings
|
|
56
|
+
app.use('/settings', settingsRouter);
|
|
57
|
+
|
|
58
|
+
// Models API: GET /models, GET /models/:provider/:name, POST /models/refresh
|
|
59
|
+
app.use('/models', modelsRouter);
|
|
60
|
+
|
|
61
|
+
// Standalone completions: POST /completions (raw LLM call, no agent/session)
|
|
62
|
+
app.use('/completions', completionsRouter);
|
|
63
|
+
|
|
64
|
+
// Remote Method Execution: GET /remote-methods (SSE) + POST /remote-methods/:id/result
|
|
65
|
+
app.use('/remote-methods', remoteMethodsRouter);
|
|
66
|
+
|
|
67
|
+
app.use('/', systemRouter);
|
|
68
|
+
|
|
69
|
+
// Global error handler (must be last)
|
|
70
|
+
app.use(errorHandler);
|
|
71
|
+
|
|
72
|
+
return app;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Start the HTTP server (with WebSocket support on /ws).
|
|
77
|
+
* @param {{ settings: Object, scheduler: Object, port?: number }} opts
|
|
78
|
+
* @returns {Promise<import('http').Server>}
|
|
79
|
+
*/
|
|
80
|
+
function startServer({ settings, scheduler, port }) {
|
|
81
|
+
const app = createApp({ settings, scheduler });
|
|
82
|
+
const listenPort = port || settings.port || 5050;
|
|
83
|
+
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
const server = app.listen(listenPort, (err) => {
|
|
86
|
+
if (err) return reject(err);
|
|
87
|
+
|
|
88
|
+
// Attach WebSocket server for real-time event streaming
|
|
89
|
+
try {
|
|
90
|
+
const { WebSocketServer } = require('ws');
|
|
91
|
+
const eventBus = require('../core/events');
|
|
92
|
+
const wss = new WebSocketServer({ server, path: '/ws' });
|
|
93
|
+
|
|
94
|
+
wss.on('connection', (ws) => {
|
|
95
|
+
const handler = (ev) => {
|
|
96
|
+
try {
|
|
97
|
+
if (ws.readyState === 1) ws.send(JSON.stringify(ev));
|
|
98
|
+
} catch { /* client may have disconnected */ }
|
|
99
|
+
};
|
|
100
|
+
eventBus.on('event', handler);
|
|
101
|
+
ws.on('close', () => eventBus.off('event', handler));
|
|
102
|
+
ws.on('error', () => eventBus.off('event', handler));
|
|
103
|
+
// Send a welcome ping so client knows connection is live
|
|
104
|
+
try { ws.send(JSON.stringify({ type: 'connected', timestamp: Date.now() })); } catch { /* ignore */ }
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
wss.on('error', (err) => {
|
|
108
|
+
console.error('[ws] WebSocket server error:', err.message);
|
|
109
|
+
});
|
|
110
|
+
} catch (wsErr) {
|
|
111
|
+
console.warn('[ws] WebSocket unavailable (install ws package):', wsErr.message);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
resolve(server);
|
|
115
|
+
});
|
|
116
|
+
server.on('error', reject);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { createApp, startServer };
|
package/cli/formatter.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI output formatting helpers.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function bold(text) { return `\x1b[1m${text}\x1b[0m`; }
|
|
8
|
+
function green(text) { return `\x1b[32m${text}\x1b[0m`; }
|
|
9
|
+
function yellow(text) { return `\x1b[33m${text}\x1b[0m`; }
|
|
10
|
+
function red(text) { return `\x1b[31m${text}\x1b[0m`; }
|
|
11
|
+
function dim(text) { return `\x1b[2m${text}\x1b[0m`; }
|
|
12
|
+
function cyan(text) { return `\x1b[36m${text}\x1b[0m`; }
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Print a success message.
|
|
16
|
+
* @param {string} msg
|
|
17
|
+
*/
|
|
18
|
+
function success(msg) { console.log(green('✓ ') + msg); }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Print a warning message.
|
|
22
|
+
* @param {string} msg
|
|
23
|
+
*/
|
|
24
|
+
function warn(msg) { console.log(yellow('⚠ ') + msg); }
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Print an error message.
|
|
28
|
+
* @param {string} msg
|
|
29
|
+
*/
|
|
30
|
+
function error(msg) { console.error(red('✗ ') + msg); }
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Print an info message.
|
|
34
|
+
* @param {string} msg
|
|
35
|
+
*/
|
|
36
|
+
function info(msg) { console.log(dim(' ') + msg); }
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Print a section header.
|
|
40
|
+
* @param {string} title
|
|
41
|
+
*/
|
|
42
|
+
function header(title) { console.log('\n' + bold(title)); }
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format a table from an array of objects.
|
|
46
|
+
* @param {Object[]} rows
|
|
47
|
+
* @param {string[]} columns
|
|
48
|
+
*/
|
|
49
|
+
function table(rows, columns) {
|
|
50
|
+
if (rows.length === 0) { console.log(dim(' (none)')); return; }
|
|
51
|
+
const widths = columns.map(col => Math.max(col.length, ...rows.map(r => String(r[col] || '').length)));
|
|
52
|
+
const header = columns.map((col, i) => bold(col.padEnd(widths[i]))).join(' ');
|
|
53
|
+
const separator = widths.map(w => '─'.repeat(w)).join(' ');
|
|
54
|
+
console.log(' ' + header);
|
|
55
|
+
console.log(' ' + dim(separator));
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
const line = columns.map((col, i) => String(row[col] || '').padEnd(widths[i])).join(' ');
|
|
58
|
+
console.log(' ' + line);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Print a progress event (for one-shot mode).
|
|
64
|
+
* @param {Object} event
|
|
65
|
+
*/
|
|
66
|
+
function progress(event) {
|
|
67
|
+
process.stdout.write(`VEIL_PROGRESS=${JSON.stringify(event)}\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { bold, green, yellow, red, dim, cyan, success, warn, error, info, header, table, progress };
|