@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.
Files changed (199) hide show
  1. package/.veil/agents/analyst/AGENT.md +21 -0
  2. package/.veil/agents/analyst/agent.json +23 -0
  3. package/.veil/agents/assistant/AGENT.md +15 -0
  4. package/.veil/agents/assistant/agent.json +19 -0
  5. package/.veil/agents/coder/AGENT.md +18 -0
  6. package/.veil/agents/coder/agent.json +19 -0
  7. package/.veil/agents/hello/AGENT.md +5 -0
  8. package/.veil/agents/hello/agent.json +13 -0
  9. package/.veil/agents/writer/AGENT.md +12 -0
  10. package/.veil/agents/writer/agent.json +17 -0
  11. package/.veil/memory/MEMORY.md +343 -0
  12. package/.veil/memory/agents/analyst/MEMORY.md +55 -0
  13. package/.veil/memory/agents/hello/MEMORY.md +12 -0
  14. package/.veil/runtime.pid +1 -0
  15. package/.veil/settings.json +10 -0
  16. package/.veil-studio/studio.db +0 -0
  17. package/.veil-studio/studio.db-shm +0 -0
  18. package/.veil-studio/studio.db-wal +0 -0
  19. package/PLAN/01-vision.md +26 -0
  20. package/PLAN/02-tech-stack.md +94 -0
  21. package/PLAN/03-agents.md +232 -0
  22. package/PLAN/04-runtime.md +171 -0
  23. package/PLAN/05-tools.md +211 -0
  24. package/PLAN/06-communication.md +243 -0
  25. package/PLAN/07-storage.md +218 -0
  26. package/PLAN/08-api-cli.md +153 -0
  27. package/PLAN/09-permissions.md +108 -0
  28. package/PLAN/10-ably.md +105 -0
  29. package/PLAN/11-file-formats.md +442 -0
  30. package/PLAN/12-folder-structure.md +205 -0
  31. package/PLAN/13-operations.md +212 -0
  32. package/PLAN/README.md +23 -0
  33. package/README.md +128 -0
  34. package/REPORT.md +174 -0
  35. package/TODO.md +45 -0
  36. package/ai-tests/FRONTEND_PROMPT.md +220 -0
  37. package/ai-tests/Research & Planning.md +814 -0
  38. package/ai-tests/prompt-001-basic-api.md +230 -0
  39. package/ai-tests/prompt-002-basic-flows.md +230 -0
  40. package/ai-tests/prompt-003-agent-behaviors.md +220 -0
  41. package/api/middleware.js +60 -0
  42. package/api/routes/agents.js +193 -0
  43. package/api/routes/chat.js +93 -0
  44. package/api/routes/completions.js +122 -0
  45. package/api/routes/daemons.js +80 -0
  46. package/api/routes/memory.js +169 -0
  47. package/api/routes/models.js +40 -0
  48. package/api/routes/remote-methods.js +74 -0
  49. package/api/routes/sessions.js +208 -0
  50. package/api/routes/settings.js +108 -0
  51. package/api/routes/system.js +50 -0
  52. package/api/routes/tasks.js +270 -0
  53. package/api/server.js +120 -0
  54. package/cli/formatter.js +70 -0
  55. package/cli/index.js +443 -0
  56. package/cli/parser.js +113 -0
  57. package/config/config.json +10 -0
  58. package/config/models.json +6826 -0
  59. package/core/agent.js +329 -0
  60. package/core/cancel.js +38 -0
  61. package/core/compaction.js +176 -0
  62. package/core/events.js +13 -0
  63. package/core/loop.js +564 -0
  64. package/core/memory.js +51 -0
  65. package/core/prompt.js +185 -0
  66. package/core/queue.js +96 -0
  67. package/core/registry.js +291 -0
  68. package/core/remote-methods.js +124 -0
  69. package/core/router.js +386 -0
  70. package/core/running-sessions.js +18 -0
  71. package/docs/api/01-system.md +84 -0
  72. package/docs/api/02-agents.md +374 -0
  73. package/docs/api/03-chat.md +269 -0
  74. package/docs/api/04-tasks.md +470 -0
  75. package/docs/api/05-sessions.md +444 -0
  76. package/docs/api/06-daemons.md +142 -0
  77. package/docs/api/07-memory.md +186 -0
  78. package/docs/api/08-settings.md +133 -0
  79. package/docs/api/09-models.md +119 -0
  80. package/docs/api/09-websocket.md +350 -0
  81. package/docs/api/10-completions.md +134 -0
  82. package/docs/api/README.md +116 -0
  83. package/docs/guide/01-quickstart.md +220 -0
  84. package/docs/guide/02-folder-structure.md +185 -0
  85. package/docs/guide/03-configuration.md +252 -0
  86. package/docs/guide/04-agents.md +267 -0
  87. package/docs/guide/05-cli.md +290 -0
  88. package/docs/guide/06-tools.md +643 -0
  89. package/docs/guide/07-permissions.md +236 -0
  90. package/docs/guide/08-memory.md +139 -0
  91. package/docs/guide/09-multi-agent.md +271 -0
  92. package/docs/guide/10-daemons.md +226 -0
  93. package/docs/guide/README.md +53 -0
  94. package/docs/index.html +623 -0
  95. package/examples/README.md +151 -0
  96. package/examples/agents/assistant/AGENT.md +31 -0
  97. package/examples/agents/assistant/SOUL.md +9 -0
  98. package/examples/agents/assistant/agent.json +74 -0
  99. package/examples/agents/hello/AGENT.md +15 -0
  100. package/examples/agents/hello/agent.json +14 -0
  101. package/examples/agents/monitor/AGENT.md +51 -0
  102. package/examples/agents/monitor/agent.json +33 -0
  103. package/examples/agents/monitor/heartbeats/monitor.md +24 -0
  104. package/examples/agents/orchestrator/AGENT.md +70 -0
  105. package/examples/agents/orchestrator/agent.json +30 -0
  106. package/examples/agents/researcher/AGENT.md +52 -0
  107. package/examples/agents/researcher/agent.json +49 -0
  108. package/examples/agents/researcher/skills/web-research.md +28 -0
  109. package/examples/skills/code-review.md +72 -0
  110. package/examples/skills/summarise.md +59 -0
  111. package/examples/skills/web-research.md +42 -0
  112. package/examples/tools/word-count/index.js +27 -0
  113. package/examples/tools/word-count/tool.json +18 -0
  114. package/infrastructure/database.js +563 -0
  115. package/infrastructure/scheduler.js +122 -0
  116. package/llm/client.js +206 -0
  117. package/migrations/001-initial.sql +121 -0
  118. package/migrations/002-debuggability.sql +13 -0
  119. package/migrations/003-drop-orphaned-columns.sql +72 -0
  120. package/migrations/004-session-message-token-fields.sql +78 -0
  121. package/migrations/005-session-thinking.sql +5 -0
  122. package/package.json +30 -0
  123. package/schemas/agent.json +143 -0
  124. package/schemas/settings.json +111 -0
  125. package/scripts/fetch-models.js +93 -0
  126. package/session-debug-scenario.md +248 -0
  127. package/settings/fields.js +52 -0
  128. package/system-prompts/base-core.md +7 -0
  129. package/system-prompts/environment.md +13 -0
  130. package/system-prompts/reminders/anti-drift.md +6 -0
  131. package/system-prompts/reminders/stall-recovery.md +10 -0
  132. package/system-prompts/safety-rules.md +25 -0
  133. package/system-prompts/task-heuristics.md +27 -0
  134. package/test/client.js +71 -0
  135. package/test/integration/01-health.test.js +25 -0
  136. package/test/integration/02-agents.test.js +80 -0
  137. package/test/integration/03-chat-hello.test.js +48 -0
  138. package/test/integration/04-chat-multiturn.test.js +61 -0
  139. package/test/integration/05-chat-writer.test.js +48 -0
  140. package/test/integration/06-task-basic.test.js +68 -0
  141. package/test/integration/07-task-tools.test.js +74 -0
  142. package/test/integration/08-task-code-analysis.test.js +69 -0
  143. package/test/integration/09-memory-analyst.test.js +63 -0
  144. package/test/integration/10-task-advanced.test.js +85 -0
  145. package/test/integration/11-sessions-advanced.test.js +84 -0
  146. package/test/integration/12-assistant-chat-tools.test.js +75 -0
  147. package/test/integration/13-edge-cases.test.js +99 -0
  148. package/test/integration/14-cancel.test.js +62 -0
  149. package/test/integration/15-debug.test.js +106 -0
  150. package/test/integration/16-memory-api.test.js +83 -0
  151. package/test/integration/17-settings-api.test.js +41 -0
  152. package/test/integration/18-tool-search-activation.test.js +119 -0
  153. package/test/results/.gitkeep +0 -0
  154. package/test/runner.js +206 -0
  155. package/test/smoke.js +216 -0
  156. package/tools/agent_message.js +85 -0
  157. package/tools/agent_send.js +80 -0
  158. package/tools/agent_spawn.js +44 -0
  159. package/tools/bash.js +49 -0
  160. package/tools/edit_file.js +41 -0
  161. package/tools/glob.js +64 -0
  162. package/tools/grep.js +82 -0
  163. package/tools/list_dir.js +63 -0
  164. package/tools/log_write.js +31 -0
  165. package/tools/memory_read.js +38 -0
  166. package/tools/memory_search.js +65 -0
  167. package/tools/memory_write.js +42 -0
  168. package/tools/read_file.js +48 -0
  169. package/tools/sleep.js +22 -0
  170. package/tools/task_create.js +41 -0
  171. package/tools/task_respond.js +37 -0
  172. package/tools/task_spawn.js +64 -0
  173. package/tools/task_status.js +39 -0
  174. package/tools/task_subscribe.js +37 -0
  175. package/tools/todo_read.js +26 -0
  176. package/tools/todo_write.js +38 -0
  177. package/tools/tool_activate.js +24 -0
  178. package/tools/tool_search.js +24 -0
  179. package/tools/web_fetch.js +50 -0
  180. package/tools/web_search.js +52 -0
  181. package/tools/write_file.js +28 -0
  182. package/ui/api.js +190 -0
  183. package/ui/app.js +281 -0
  184. package/ui/index.html +382 -0
  185. package/ui/views/agents.js +377 -0
  186. package/ui/views/chat.js +610 -0
  187. package/ui/views/connection.js +96 -0
  188. package/ui/views/daemons.js +129 -0
  189. package/ui/views/feed.js +194 -0
  190. package/ui/views/memory.js +263 -0
  191. package/ui/views/models.js +146 -0
  192. package/ui/views/sessions.js +314 -0
  193. package/ui/views/settings.js +142 -0
  194. package/ui/views/tasks.js +415 -0
  195. package/utils/context.js +49 -0
  196. package/utils/id.js +16 -0
  197. package/utils/models.js +88 -0
  198. package/utils/paths.js +213 -0
  199. package/utils/settings.js +172 -0
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const context = require('../utils/context');
4
+
5
+ /**
6
+ * Auth middleware — validates X-Veil-Secret header if a secret is configured.
7
+ */
8
+ function authMiddleware(req, res, next) {
9
+ // /health is always unprotected (liveness probe for load balancers / orchestrators)
10
+ if (req.path === '/health') return next();
11
+
12
+ // Secret is read from settings at request time via context
13
+ const secret = req.app.locals.settings?.secret;
14
+ if (!secret) return next(); // No secret configured — open access
15
+
16
+ const provided = req.headers['x-veil-secret'];
17
+ if (!provided || provided !== secret) {
18
+ return res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Invalid or missing X-Veil-Secret header' } });
19
+ }
20
+ next();
21
+ }
22
+
23
+ /**
24
+ * Standard error response formatter.
25
+ * @param {Response} res
26
+ * @param {number} status
27
+ * @param {string} code
28
+ * @param {string} message
29
+ */
30
+ function sendError(res, status, code, message) {
31
+ return res.status(status).json({ error: { code, message } });
32
+ }
33
+
34
+ /**
35
+ * Global error handler middleware.
36
+ */
37
+ function errorHandler(err, req, res, next) {
38
+ const codeMap = {
39
+ 'AGENT_NOT_FOUND': 404,
40
+ 'MODE_NOT_SUPPORTED': 400,
41
+ 'TASK_NOT_FOUND': 404,
42
+ 'SESSION_NOT_FOUND': 404,
43
+ 'SESSION_CLOSED': 400,
44
+ 'TASK_NOT_WAITING': 400,
45
+ 'PERMISSION_DENIED': 403,
46
+ 'VALIDATION_ERROR': 400,
47
+ };
48
+
49
+ const code = err.code || 'INTERNAL_ERROR';
50
+ const status = codeMap[code] || 500;
51
+ const message = err.message || 'An unexpected error occurred';
52
+
53
+ if (status === 500) {
54
+ console.error('[ERROR]', err);
55
+ }
56
+
57
+ return sendError(res, status, code, message);
58
+ }
59
+
60
+ module.exports = { authMiddleware, sendError, errorHandler };
@@ -0,0 +1,193 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const router = express.Router();
7
+ const { listAgents, loadAgent, getEffectiveModeConfig, createAgent, updateAgent, deleteAgent } = require('../../core/agent');
8
+ const { listCustomToolSummaries } = require('../../core/registry');
9
+ const { sendError } = require('../middleware');
10
+ const context = require('../../utils/context');
11
+ const db = require('../../infrastructure/database');
12
+ const paths = require('../../utils/paths');
13
+
14
+ // POST /agents — create a new agent
15
+ router.post('/', (req, res, next) => {
16
+ try {
17
+ const cwd = context.getCwd();
18
+ const { name, level = 'project', config, agentMd } = req.body;
19
+
20
+ if (!name) return sendError(res, 400, 'VALIDATION_ERROR', '"name" is required');
21
+ if (!config || typeof config !== 'object') return sendError(res, 400, 'VALIDATION_ERROR', '"config" object is required');
22
+ if (level !== 'project' && level !== 'global') return sendError(res, 400, 'VALIDATION_ERROR', '"level" must be "project" or "global"');
23
+
24
+ const result = createAgent({ name, level, cwd, config, agentMd });
25
+ res.status(201).json({ agent: result });
26
+ } catch (err) {
27
+ if (err.code === 'AGENT_EXISTS') return sendError(res, 409, 'AGENT_EXISTS', err.message);
28
+ if (err.code === 'INVALID_NAME') return sendError(res, 400, 'INVALID_NAME', err.message);
29
+ if (err.code === 'INVALID_CONFIG') return sendError(res, 400, 'INVALID_CONFIG', err.message);
30
+ next(err);
31
+ }
32
+ });
33
+
34
+ // GET /agents
35
+ router.get('/', (req, res, next) => {
36
+ try {
37
+ const agents = listAgents({ cwd: context.getCwd() });
38
+ res.json({ agents });
39
+ } catch (err) {
40
+ next(err);
41
+ }
42
+ });
43
+
44
+ // GET /agents/:name
45
+ router.get('/:name', (req, res, next) => {
46
+ try {
47
+ const agent = loadAgent({ cwd: context.getCwd(), name: req.params.name });
48
+ res.json({ agent: {
49
+ name: agent.name,
50
+ description: agent.description || '',
51
+ model: agent.model,
52
+ temperature: agent.temperature,
53
+ reasoning: agent.reasoning,
54
+ modes: agent.modes,
55
+ skillDiscovery: agent.skillDiscovery,
56
+ memory: agent.memory,
57
+ agentFolder: agent.agentFolder,
58
+ source: agent.source,
59
+ agentMd: agent.agentMd || '',
60
+ }});
61
+ } catch (err) {
62
+ if (err.message && err.message.startsWith('Agent "')) {
63
+ err.code = 'AGENT_NOT_FOUND';
64
+ }
65
+ next(err);
66
+ }
67
+ });
68
+
69
+ // POST /agents/:name/reload — hot-reload agent config from disk
70
+ router.post('/:name/reload', (req, res, next) => {
71
+ try {
72
+ const cwd = context.getCwd();
73
+ const agent = loadAgent({ cwd, name: req.params.name });
74
+ res.json({ reloaded: true, agent });
75
+ } catch (err) {
76
+ if (err.message && err.message.includes('not found')) {
77
+ return sendError(res, 404, 'AGENT_NOT_FOUND', err.message);
78
+ }
79
+ next(err);
80
+ }
81
+ });
82
+
83
+ // PUT /agents/:name — update agent config and/or AGENT.md
84
+ router.put('/:name', (req, res, next) => {
85
+ try {
86
+ const cwd = context.getCwd();
87
+ const { config, agentMd } = req.body;
88
+
89
+ if (config === undefined && agentMd === undefined) {
90
+ return sendError(res, 400, 'VALIDATION_ERROR', 'At least one of "config" or "agentMd" is required');
91
+ }
92
+
93
+ const result = updateAgent({ name: req.params.name, cwd, config, agentMd });
94
+ res.json({ agent: result });
95
+ } catch (err) {
96
+ if (err.code === 'AGENT_NOT_FOUND') return sendError(res, 404, 'AGENT_NOT_FOUND', err.message);
97
+ if (err.code === 'AGENT_READ_ONLY') return sendError(res, 403, 'AGENT_READ_ONLY', err.message);
98
+ if (err.code === 'INVALID_CONFIG') return sendError(res, 400, 'INVALID_CONFIG', err.message);
99
+ next(err);
100
+ }
101
+ });
102
+
103
+ // DELETE /agents/:name — delete an agent folder
104
+ router.delete('/:name', (req, res, next) => {
105
+ try {
106
+ const cwd = context.getCwd();
107
+ const result = deleteAgent({ name: req.params.name, cwd });
108
+ res.json({ deleted: true, agent: result });
109
+ } catch (err) {
110
+ if (err.code === 'AGENT_NOT_FOUND') return sendError(res, 404, 'AGENT_NOT_FOUND', err.message);
111
+ if (err.code === 'AGENT_READ_ONLY') return sendError(res, 403, 'AGENT_READ_ONLY', err.message);
112
+ next(err);
113
+ }
114
+ });
115
+
116
+ // GET /agents/:name/sessions
117
+ router.get('/:name/sessions', (req, res, next) => {
118
+ try {
119
+ const cwd = context.getCwd();
120
+ const { limit, cursor, status } = req.query;
121
+ const sessions = db.listSessions({
122
+ instanceFolder: cwd,
123
+ agentName: req.params.name,
124
+ status,
125
+ limit: limit ? parseInt(limit, 10) : 20,
126
+ cursor,
127
+ });
128
+ res.json({ agentName: req.params.name, sessions });
129
+ } catch (err) {
130
+ next(err);
131
+ }
132
+ });
133
+
134
+ // GET /agents/:name/tasks
135
+ router.get('/:name/tasks', (req, res, next) => {
136
+ try {
137
+ const cwd = context.getCwd();
138
+ const { limit, cursor, status, priority } = req.query;
139
+ const tasks = db.listTasks({
140
+ instanceFolder: cwd,
141
+ agentName: req.params.name,
142
+ status,
143
+ priority,
144
+ limit: limit ? parseInt(limit, 10) : 20,
145
+ cursor,
146
+ });
147
+ res.json({ agentName: req.params.name, tasks });
148
+ } catch (err) {
149
+ next(err);
150
+ }
151
+ });
152
+
153
+ // GET /agents/:name/skills
154
+ router.get('/:name/skills', (req, res, next) => {
155
+ try {
156
+ const cwd = context.getCwd();
157
+ let agent;
158
+ try {
159
+ agent = loadAgent({ cwd, name: req.params.name });
160
+ } catch {
161
+ return sendError(res, 404, 'AGENT_NOT_FOUND', `Agent "${req.params.name}" not found`);
162
+ }
163
+
164
+ const modeConfig = agent.modes?.task || agent.modes?.subagent || {};
165
+ const skillNames = modeConfig.skills || [];
166
+ const projectSkillsDir = paths.getProjectSkillsDir(cwd);
167
+
168
+ const skills = skillNames.map(skillName => {
169
+ const projectSkillPath = path.join(projectSkillsDir, skillName + '.md');
170
+ const agentSkillPath = agent.agentFolder
171
+ ? path.join(agent.agentFolder, 'skills', skillName + '.md')
172
+ : null;
173
+ const agentLoaded = agentSkillPath && fs.existsSync(agentSkillPath);
174
+ const projectLoaded = fs.existsSync(projectSkillPath);
175
+ const loaded = agentLoaded || projectLoaded;
176
+ return {
177
+ name: skillName,
178
+ loaded,
179
+ source: agentLoaded ? 'agent' : projectLoaded ? 'project' : null,
180
+ path: agentLoaded ? agentSkillPath : projectLoaded ? projectSkillPath : null,
181
+ error: loaded ? null : 'File not found',
182
+ };
183
+ });
184
+
185
+ const customTools = listCustomToolSummaries({ agentConfig: agent, cwd });
186
+
187
+ res.json({ agentName: req.params.name, skills, customTools });
188
+ } catch (err) {
189
+ next(err);
190
+ }
191
+ });
192
+
193
+ module.exports = router;
@@ -0,0 +1,93 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const router = express.Router({ mergeParams: true });
5
+ const { runChat } = require('../../core/router');
6
+ const { loadAgent } = require('../../core/agent');
7
+ const { sendError } = require('../middleware');
8
+ const context = require('../../utils/context');
9
+ const eventBus = require('../../core/events');
10
+
11
+ // POST /agents/:name/chat
12
+ router.post('/', async (req, res, next) => {
13
+ try {
14
+ const { name } = req.params;
15
+ const { message, sessionId, sse = false } = req.body;
16
+ const cwd = context.getCwd();
17
+ const settings = req.app.locals.settings;
18
+
19
+ if (!message || typeof message !== 'string') {
20
+ return sendError(res, 400, 'VALIDATION_ERROR', 'message is required and must be a string');
21
+ }
22
+
23
+ // Verify agent exists and supports chat mode
24
+ let agent;
25
+ try {
26
+ agent = loadAgent({ cwd, name });
27
+ } catch (err) {
28
+ return sendError(res, 404, 'AGENT_NOT_FOUND', err.message);
29
+ }
30
+
31
+ if (!agent.modes?.chat?.enabled) {
32
+ return sendError(res, 400, 'MODE_NOT_SUPPORTED', `Agent "${name}" does not support chat mode`);
33
+ }
34
+
35
+ // ── SSE streaming mode ────────────────────────────────────────────────────
36
+ if (sse) {
37
+ res.setHeader('Content-Type', 'text/event-stream');
38
+ res.setHeader('Cache-Control', 'no-cache');
39
+ res.setHeader('Connection', 'keep-alive');
40
+ res.setHeader('X-Accel-Buffering', 'no');
41
+ res.flushHeaders();
42
+
43
+ let currentSessionId = sessionId || null;
44
+
45
+ function sendEvent(event, data) {
46
+ try { res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); } catch {}
47
+ eventBus.emit('event', { type: 'session.stream', sessionId: currentSessionId, agentName: name, eventType: event, data });
48
+ }
49
+
50
+ try {
51
+ const result = await runChat({
52
+ agentName: name, message, sessionId, cwd, settings,
53
+ onSessionStart: (sid) => { currentSessionId = sid; },
54
+ onStreamChunk: (text) => sendEvent('inference.chunk', { content: text }),
55
+ onInferenceToolStart: (toolName) => sendEvent('inference.tool', { name: toolName }),
56
+ onEvent: (event) => {
57
+ if (event.type === 'message')
58
+ sendEvent('message', {
59
+ ...event.message,
60
+ ...(event.finishReason !== undefined && { finishReason: event.finishReason }),
61
+ ...(event.iteration !== undefined && { iteration: event.iteration }),
62
+ ...(event.tokenUsage !== undefined && { tokenUsage: event.tokenUsage }),
63
+ });
64
+ },
65
+ });
66
+ sendEvent('done', {
67
+ session: result.session,
68
+ agentName: result.agentName, model: result.model,
69
+ iterations: result.iterations, durationMs: result.durationMs,
70
+ tokenUsage: result.tokenUsage, toolCalls: result.toolCalls || [],
71
+ });
72
+ } catch (err) {
73
+ sendEvent('error', { error: err.message, code: err.code || 'INTERNAL_ERROR' });
74
+ }
75
+ res.end();
76
+ return;
77
+ }
78
+
79
+ // ── Standard JSON mode ────────────────────────────────────────────────────
80
+ const result = await runChat({ agentName: name, message, sessionId, cwd, settings });
81
+
82
+ res.json({
83
+ sessionId: result.sessionId,
84
+ message: { role: 'assistant', content: result.content },
85
+ tokenUsage: result.tokenUsage,
86
+ toolCalls: result.toolCalls || [],
87
+ });
88
+ } catch (err) {
89
+ next(err);
90
+ }
91
+ });
92
+
93
+ module.exports = router;
@@ -0,0 +1,122 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const router = express.Router();
5
+ const { callLLM, extractMessage, extractUsage } = require('../../llm/client');
6
+ const { getModelConfig } = require('../../utils/settings');
7
+ const { calculateCost } = require('../../utils/models');
8
+ const { sendError } = require('../middleware');
9
+ const F = require('../../settings/fields');
10
+
11
+ /**
12
+ * POST /completions
13
+ *
14
+ * Standalone chat completion endpoint — no agent, no session, no tool loop.
15
+ * Useful for independent LLM calls from apps built on Veil.
16
+ *
17
+ * Body:
18
+ * model? {string} Override the default model from settings
19
+ * messages {array} OpenAI-compatible messages array (required)
20
+ * temperature? {number}
21
+ * max_tokens? {number}
22
+ * tools? {array} OpenAI-compatible tool definitions
23
+ * reasoning? {string} Reasoning effort (e.g. "high")
24
+ * thinking? {object} Extended thinking config (e.g. { type: "enabled", budget_tokens: 10000 })
25
+ * sse? {boolean} Stream response via Server-Sent Events
26
+ */
27
+ router.post('/', async (req, res, next) => {
28
+ try {
29
+ const settings = req.app.locals.settings;
30
+ const {
31
+ model,
32
+ messages,
33
+ temperature,
34
+ max_tokens,
35
+ tools,
36
+ reasoning,
37
+ thinking,
38
+ modalities,
39
+ audio,
40
+ sse = false,
41
+ } = req.body;
42
+
43
+ if (!Array.isArray(messages) || messages.length === 0) {
44
+ return sendError(res, 400, 'VALIDATION_ERROR', 'messages is required and must be a non-empty array');
45
+ }
46
+
47
+ const modelConfig = getModelConfig(settings, F.MODEL_MAIN);
48
+ const resolvedModel = model || modelConfig[F.MODEL_NAME];
49
+
50
+ if (!resolvedModel) {
51
+ return sendError(res, 400, 'VALIDATION_ERROR', 'model is required (or configure a default model in settings)');
52
+ }
53
+
54
+ const llmParams = {
55
+ baseUrl: modelConfig[F.MODEL_BASE_URL],
56
+ apiKey: modelConfig[F.MODEL_API_KEY],
57
+ model: resolvedModel,
58
+ messages,
59
+ tools: Array.isArray(tools) ? tools : [],
60
+ temperature: temperature !== undefined ? temperature : undefined,
61
+ maxTokens: max_tokens !== undefined ? max_tokens : undefined,
62
+ reasoning: reasoning !== undefined ? reasoning : undefined,
63
+ thinking: thinking !== undefined ? thinking : undefined,
64
+ modalities: modalities !== undefined ? modalities : undefined,
65
+ audio: audio !== undefined ? audio : undefined,
66
+ };
67
+
68
+ // ── SSE streaming mode ──────────────────────────────────────────────────
69
+ if (sse) {
70
+ res.setHeader('Content-Type', 'text/event-stream');
71
+ res.setHeader('Cache-Control', 'no-cache');
72
+ res.setHeader('Connection', 'keep-alive');
73
+ res.setHeader('X-Accel-Buffering', 'no');
74
+ res.flushHeaders();
75
+
76
+ function sendEvent(event, data) {
77
+ try { res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`); } catch {}
78
+ }
79
+
80
+ try {
81
+ const response = await callLLM({
82
+ ...llmParams,
83
+ onChunk: (text) => sendEvent('chunk', { content: text }),
84
+ });
85
+
86
+ const { content, audio: audioOut, toolCalls, finishReason } = extractMessage(response);
87
+ const usage = extractUsage(response);
88
+ const cost = usage.cost || calculateCost(resolvedModel, { input: usage.input, output: usage.output, cache: usage.cache });
89
+
90
+ sendEvent('done', {
91
+ message: { role: 'assistant', content, audio: audioOut, tool_calls: toolCalls || [] },
92
+ usage: { input: usage.input, output: usage.output, cache: usage.cache },
93
+ cost,
94
+ finish_reason: finishReason,
95
+ });
96
+ } catch (err) {
97
+ sendEvent('error', { error: err.message, code: err.code || 'INTERNAL_ERROR' });
98
+ }
99
+
100
+ res.end();
101
+ return;
102
+ }
103
+
104
+ // ── Standard JSON mode ──────────────────────────────────────────────────
105
+ const response = await callLLM(llmParams);
106
+
107
+ const { content, audio: audioOut, toolCalls, finishReason } = extractMessage(response);
108
+ const usage = extractUsage(response);
109
+ const cost = usage.cost || calculateCost(resolvedModel, { input: usage.input, output: usage.output, cache: usage.cache });
110
+
111
+ res.json({
112
+ message: { role: 'assistant', content, audio: audioOut, tool_calls: toolCalls || [] },
113
+ usage: { input: usage.input, output: usage.output, cache: usage.cache },
114
+ cost,
115
+ finish_reason: finishReason,
116
+ });
117
+ } catch (err) {
118
+ next(err);
119
+ }
120
+ });
121
+
122
+ module.exports = router;
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const router = express.Router();
5
+ const { loadAgent } = require('../../core/agent');
6
+ const { sendError } = require('../middleware');
7
+ const context = require('../../utils/context');
8
+
9
+ // GET /daemons
10
+ router.get('/', (req, res, next) => {
11
+ try {
12
+ const scheduler = req.app.locals.scheduler;
13
+ if (!scheduler) return res.json({ daemons: [] });
14
+ res.json({ daemons: scheduler.listDaemons() });
15
+ } catch (err) {
16
+ next(err);
17
+ }
18
+ });
19
+
20
+ // POST /agents/:name/daemon/start
21
+ router.post('/:name/daemon/start', (req, res, next) => {
22
+ try {
23
+ const { name } = req.params;
24
+ const cwd = context.getCwd();
25
+ let agent;
26
+ try {
27
+ agent = loadAgent({ cwd, name });
28
+ } catch {
29
+ return sendError(res, 404, 'AGENT_NOT_FOUND', `Agent "${name}" not found`);
30
+ }
31
+ if (!agent.modes?.daemon?.enabled) {
32
+ return sendError(res, 400, 'MODE_NOT_SUPPORTED', `Agent "${name}" does not support daemon mode`);
33
+ }
34
+ const scheduler = req.app.locals.scheduler;
35
+ scheduler.startDaemon({ agentName: name, agent, cwd, settings: req.app.locals.settings });
36
+ res.json({ agentName: name, status: 'started' });
37
+ } catch (err) {
38
+ next(err);
39
+ }
40
+ });
41
+
42
+ // POST /agents/:name/daemon/stop
43
+ router.post('/:name/daemon/stop', (req, res, next) => {
44
+ try {
45
+ const { name } = req.params;
46
+ const scheduler = req.app.locals.scheduler;
47
+ scheduler.stopDaemon(name);
48
+ res.json({ agentName: name, status: 'stopped' });
49
+ } catch (err) {
50
+ next(err);
51
+ }
52
+ });
53
+
54
+ // POST /agents/:name/daemon/trigger
55
+ router.post('/:name/daemon/trigger', async (req, res, next) => {
56
+ try {
57
+ const { name } = req.params;
58
+ const cwd = context.getCwd();
59
+ const settings = req.app.locals.settings;
60
+ let agent;
61
+ try {
62
+ agent = loadAgent({ cwd, name });
63
+ } catch {
64
+ return sendError(res, 404, 'AGENT_NOT_FOUND', `Agent "${name}" not found`);
65
+ }
66
+ if (!agent.modes?.daemon?.enabled) {
67
+ return sendError(res, 400, 'MODE_NOT_SUPPORTED', `Agent "${name}" does not support daemon mode`);
68
+ }
69
+ const { runDaemonTick } = require('../../core/router');
70
+ // Fire async
71
+ setImmediate(() => runDaemonTick({ agentName: name, cwd, settings }).catch(err => {
72
+ console.error(`Daemon tick error for ${name}:`, err.message);
73
+ }));
74
+ res.json({ agentName: name, status: 'triggered' });
75
+ } catch (err) {
76
+ next(err);
77
+ }
78
+ });
79
+
80
+ module.exports = router;