@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,415 @@
1
+ 'use strict';
2
+ window.Veil = window.Veil || {};
3
+ window.Veil.views = window.Veil.views || {};
4
+
5
+ window.Veil.views.tasks = {
6
+ _selected: null,
7
+ _stream: null,
8
+ _pollTimer: null,
9
+
10
+ render() {
11
+ return `
12
+ <div style="display:flex;height:100%;">
13
+ <!-- List panel -->
14
+ <div style="width:280px;flex-shrink:0;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;background:var(--surface);">
15
+ <div style="padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;">
16
+ <span style="font-weight:600;font-size:13px;color:var(--text);flex:1;">Tasks</span>
17
+ <button id="btn-create-task" class="btn btn-primary btn-sm">+ New</button>
18
+ <button id="btn-refresh-tasks" class="btn btn-secondary btn-sm" title="Refresh">↻</button>
19
+ </div>
20
+ <!-- Filters -->
21
+ <div style="padding:8px 14px;border-bottom:1px solid var(--border);display:flex;gap:6px;flex-wrap:wrap;">
22
+ <select id="task-filter-status" class="input" style="flex:1;padding:4px 8px;font-size:12px;cursor:pointer;">
23
+ <option value="">All statuses</option>
24
+ <option value="pending">pending</option>
25
+ <option value="processing">processing</option>
26
+ <option value="finished">finished</option>
27
+ <option value="failed">failed</option>
28
+ <option value="waiting">waiting</option>
29
+ <option value="canceled">canceled</option>
30
+ </select>
31
+ <select id="task-filter-agent" class="input" style="flex:1;padding:4px 8px;font-size:12px;cursor:pointer;">
32
+ <option value="">All agents</option>
33
+ </select>
34
+ </div>
35
+ <div id="task-list" style="flex:1;overflow-y:auto;"></div>
36
+ </div>
37
+ <!-- Detail panel -->
38
+ <div id="task-detail" style="flex:1;overflow-y:auto;">
39
+ <div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">
40
+ Select a task
41
+ </div>
42
+ </div>
43
+ </div>`;
44
+ },
45
+
46
+ async mount(params) {
47
+ params = params || {};
48
+ await this._populateAgentFilter();
49
+ await this._loadList();
50
+
51
+ document.getElementById('btn-create-task').addEventListener('click', () => this._showCreateModal());
52
+ document.getElementById('btn-refresh-tasks').addEventListener('click', () => this._loadList());
53
+ document.getElementById('task-filter-status').addEventListener('change', () => this._loadList());
54
+ document.getElementById('task-filter-agent').addEventListener('change', () => this._loadList());
55
+
56
+ if (params.taskId) {
57
+ this._selected = params.taskId;
58
+ this._loadDetail(params.taskId);
59
+ }
60
+ },
61
+
62
+ unmount() {
63
+ if (this._stream) { this._stream.close(); this._stream = null; }
64
+ if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; }
65
+ this._selected = null;
66
+ },
67
+
68
+ async _populateAgentFilter() {
69
+ const sel = document.getElementById('task-filter-agent');
70
+ if (!sel) return;
71
+ try {
72
+ const data = await window.Veil.state.api.listAgents();
73
+ const agents = data.agents || [];
74
+ const preferred = window.Veil.state.currentAgent;
75
+ agents.forEach(a => {
76
+ const opt = document.createElement('option');
77
+ opt.value = a.name; opt.textContent = a.name;
78
+ if (a.name === preferred) opt.selected = true;
79
+ sel.appendChild(opt);
80
+ });
81
+ } catch {}
82
+ },
83
+
84
+ async _loadList() {
85
+ const el = document.getElementById('task-list');
86
+ if (!el) return;
87
+ el.innerHTML = `<div style="padding:16px;text-align:center;color:var(--text3);">${window.Veil.utils.spinner()}</div>`;
88
+ const params = {};
89
+ const status = document.getElementById('task-filter-status')?.value;
90
+ const agent = document.getElementById('task-filter-agent')?.value;
91
+ if (status) params.status = status;
92
+ if (agent) params.agentName = agent;
93
+ params.limit = 50;
94
+ try {
95
+ const data = await window.Veil.state.api.listTasks(params);
96
+ const tasks = data.tasks || [];
97
+ if (tasks.length === 0) {
98
+ el.innerHTML = `<div style="padding:20px;text-align:center;color:var(--text3);font-size:13px;">No tasks found</div>`;
99
+ return;
100
+ }
101
+ const U = window.Veil.utils;
102
+ el.innerHTML = tasks.map(t => `
103
+ <div class="list-item ${this._selected === t.id ? 'selected' : ''}" data-task-id="${U.esc(t.id)}">
104
+ <div style="display:flex;align-items:center;gap:6px;margin-bottom:3px;">
105
+ ${U.badge(t.status)}
106
+ <span style="font-size:12px;color:var(--text3);margin-left:auto;">${U.relTime(t.created_at)}</span>
107
+ </div>
108
+ <div style="font-size:12.5px;color:var(--text2);margin-bottom:2px;">${U.esc(t.agent_name)}</div>
109
+ <div style="font-size:13px;color:var(--text);">${U.esc(U.truncate(t.input, 60))}</div>
110
+ </div>`).join('');
111
+ el.querySelectorAll('[data-task-id]').forEach(row => {
112
+ row.addEventListener('click', () => {
113
+ const id = row.getAttribute('data-task-id');
114
+ el.querySelectorAll('.list-item').forEach(r => r.classList.remove('selected'));
115
+ row.classList.add('selected');
116
+ this._selected = id;
117
+ this._loadDetail(id);
118
+ });
119
+ });
120
+ if (this._selected) this._loadDetail(this._selected);
121
+ } catch (err) {
122
+ if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
123
+ }
124
+ },
125
+
126
+ async _loadDetail(id) {
127
+ const el = document.getElementById('task-detail');
128
+ if (!el) return;
129
+ if (this._stream) { this._stream.close(); this._stream = null; }
130
+ if (this._pollTimer) { clearInterval(this._pollTimer); this._pollTimer = null; }
131
+ el.innerHTML = `<div style="padding:20px;color:var(--text3);">${window.Veil.utils.spinner()} Loading…</div>`;
132
+ try {
133
+ const data = await window.Veil.state.api.getTask(id);
134
+ this._renderDetail(data.task);
135
+ await this._renderEvents(id);
136
+ } catch (err) {
137
+ if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
138
+ }
139
+ },
140
+
141
+ _renderDetail(task) {
142
+ const el = document.getElementById('task-detail');
143
+ if (!el) return;
144
+ const U = window.Veil.utils;
145
+ const isActive = ['pending','processing','waiting'].includes(task.status);
146
+ const isWaiting = task.status === 'waiting';
147
+ const isRunning = ['pending','processing'].includes(task.status);
148
+ const duration = task.started_at && task.finished_at
149
+ ? U.formatMs(new Date(task.finished_at) - new Date(task.started_at)) : '—';
150
+
151
+ const tagsHtml = (task.tags || []).map(t =>
152
+ `<span style="font-size:11px;background:var(--surface2);border:1px solid var(--border2);color:var(--text2);border-radius:4px;padding:1px 6px;">${U.esc(t)}</span>`
153
+ ).join(' ');
154
+
155
+ el.innerHTML = `
156
+ <div style="padding:20px;max-width:800px;">
157
+ <!-- Header -->
158
+ <div style="display:flex;align-items:flex-start;gap:10px;margin-bottom:14px;flex-wrap:wrap;">
159
+ <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1;min-width:0;">
160
+ <span id="task-status-badge">${U.badge(task.status)}</span>
161
+ ${task.priority && task.priority !== 'normal' ? `<span class="badge badge-${task.priority === 'high' ? 'failed' : 'closed'}">${U.esc(task.priority)}</span>` : ''}
162
+ <code style="font-size:12px;color:var(--text3);font-family:'JetBrains Mono',monospace;word-break:break-all;">${U.esc(task.id)}</code>
163
+ ${tagsHtml}
164
+ </div>
165
+ <span style="font-size:12px;color:var(--text3);white-space:nowrap;">${U.relTime(task.created_at)}</span>
166
+ </div>
167
+ ${task.parent_task_id ? `
168
+ <div style="font-size:12px;color:var(--text3);margin-bottom:10px;">Spawned by: <a href="#" onclick="event.preventDefault();window.Veil.navigate('tasks',{taskId:'${U.esc(task.parent_task_id)}'})" style="color:var(--accent);">${U.esc(task.parent_task_id)}</a></div>` : ''}
169
+
170
+ <!-- Stats grid -->
171
+ <div class="card" style="padding:14px;margin-bottom:14px;display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px;">
172
+ <div><div class="info-label">Agent</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${U.esc(task.agent_name)}</div></div>
173
+ <div><div class="info-label">Duration</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${duration}</div></div>
174
+ <div><div class="info-label">Iterations</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${task.iterations || 0}</div></div>
175
+ <div><div class="info-label">Events</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${task.eventCount || 0}</div></div>
176
+ <div><div class="info-label">Tokens in</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${(task.token_input || 0).toLocaleString()}</div></div>
177
+ <div><div class="info-label">Tokens out</div><div style="font-size:13px;color:var(--text);margin-top:2px;">${(task.token_output || 0).toLocaleString()}</div></div>
178
+ ${task.started_at ? `<div><div class="info-label">Started</div><div style="font-size:12px;color:var(--text2);margin-top:2px;">${U.relTime(task.started_at)}</div></div>` : ''}
179
+ ${task.finished_at ? `<div><div class="info-label">Finished</div><div style="font-size:12px;color:var(--text2);margin-top:2px;">${U.relTime(task.finished_at)}</div></div>` : ''}
180
+ </div>
181
+
182
+ <!-- Actions -->
183
+ <div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
184
+ ${isRunning ? `<button class="btn btn-danger btn-sm" id="btn-cancel-task">Cancel</button>` : ''}
185
+ ${isActive ? `<button class="btn btn-secondary btn-sm" id="btn-stream-task">▶ Stream</button>` : ''}
186
+ <button class="btn btn-secondary btn-sm" id="btn-refresh-detail">↻ Refresh</button>
187
+ ${task.session_id ? `<button class="btn btn-secondary btn-sm" id="btn-view-sess-task">→ Session</button>` : ''}
188
+ <button class="btn btn-danger btn-sm" id="btn-delete-task">${isActive ? 'Force Delete' : 'Delete'}</button>
189
+ </div>
190
+
191
+ <!-- Respond (if waiting) -->
192
+ ${isWaiting ? `
193
+ <div class="card" style="padding:14px;margin-bottom:14px;border-color:rgba(234,179,8,0.25);">
194
+ <div style="font-size:13px;color:var(--yellow);margin-bottom:8px;font-weight:600;">⏳ Task is waiting for a response</div>
195
+ <div style="display:flex;gap:8px;">
196
+ <input id="task-respond-input" class="input" placeholder="Enter your response…" />
197
+ <button class="btn btn-success btn-sm" id="btn-respond-task" style="flex-shrink:0;">Respond</button>
198
+ </div>
199
+ </div>` : ''}
200
+
201
+ <!-- Input -->
202
+ <div class="card" style="margin-bottom:12px;">
203
+ <div class="panel-header" style="display:flex;align-items:center;justify-content:space-between;">
204
+ <span>Input</span>
205
+ ${task.priority !== 'normal' ? `<span class="badge badge-${task.priority==='high'?'failed':'closed'}">${U.esc(task.priority)}</span>` : ''}
206
+ </div>
207
+ <div style="padding:12px;font-size:13.5px;white-space:pre-wrap;color:var(--text);line-height:1.5;">${U.esc(task.input || '')}</div>
208
+ </div>
209
+
210
+ <!-- Output -->
211
+ ${task.output != null ? `
212
+ <div class="card" style="margin-bottom:12px;">
213
+ <div class="panel-header">Output</div>
214
+ <div style="padding:12px;font-size:13.5px;white-space:pre-wrap;color:var(--text);line-height:1.5;">${U.esc(task.output)}</div>
215
+ </div>` : ''}
216
+
217
+ <!-- Error -->
218
+ ${task.error ? (() => {
219
+ const err = typeof task.error === 'object' ? task.error : { message: task.error };
220
+ const codeHtml = err.code ? `<span style="font-size:12px;background:rgba(239,68,68,0.12);border:1px solid rgba(239,68,68,0.25);color:var(--red);border-radius:4px;padding:1px 7px;font-family:'JetBrains Mono',monospace;">${U.esc(err.code)}</span>` : '';
221
+ const msgHtml = err.message ? `<div style="font-size:13px;color:var(--red);margin-top:6px;line-height:1.5;">${U.esc(err.message)}</div>` : '';
222
+ const extraKeys = Object.keys(err).filter(k => k !== 'code' && k !== 'message');
223
+ const extraHtml = extraKeys.length ? `<pre style="font-size:12px;color:var(--text2);margin-top:8px;background:var(--bg);border-radius:5px;padding:8px;overflow-x:auto;">${U.esc(JSON.stringify(Object.fromEntries(extraKeys.map(k=>[k,err[k]])),null,2))}</pre>` : '';
224
+ return `<div class="card" style="margin-bottom:12px;border-color:rgba(239,68,68,0.25);">
225
+ <div class="panel-header" style="color:var(--red);display:flex;align-items:center;gap:8px;">Error ${codeHtml}</div>
226
+ <div style="padding:10px 12px;">${msgHtml}${extraHtml}</div>
227
+ </div>`;
228
+ })() : ''}
229
+
230
+ <!-- Event Timeline placeholder -->
231
+ <div class="card" id="task-events-card">
232
+ <div class="panel-header">Event Timeline</div>
233
+ <div id="task-events-body" style="padding:12px;color:var(--text3);font-size:13px;">${U.spinner()} Loading events…</div>
234
+ </div>
235
+ </div>`;
236
+
237
+ document.getElementById('btn-refresh-detail')?.addEventListener('click', () => this._loadDetail(task.id));
238
+ document.getElementById('btn-cancel-task')?.addEventListener('click', async () => {
239
+ try {
240
+ await window.Veil.state.api.cancelTask(task.id);
241
+ window.Veil.utils.toast('Task canceled', 'success');
242
+ this._loadDetail(task.id);
243
+ this._loadList();
244
+ } catch (err) { window.Veil.utils.toast('Cancel failed: ' + err.message, 'error'); }
245
+ });
246
+ document.getElementById('btn-delete-task')?.addEventListener('click', async () => {
247
+ if (!confirm('Delete this task and all its data?')) return;
248
+ const force = isActive;
249
+ try {
250
+ await window.Veil.state.api.deleteTask(task.id, force);
251
+ window.Veil.utils.toast('Task deleted', 'success');
252
+ this._selected = null;
253
+ document.getElementById('task-detail').innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">Task deleted</div>`;
254
+ this._loadList();
255
+ } catch (err) { window.Veil.utils.toast('Delete failed: ' + err.message, 'error'); }
256
+ });
257
+ document.getElementById('btn-stream-task')?.addEventListener('click', () => this._startStream(task.id));
258
+ document.getElementById('btn-view-sess-task')?.addEventListener('click', () => {
259
+ window.Veil.navigate('sessions', { sessionId: task.session_id });
260
+ });
261
+ document.getElementById('btn-respond-task')?.addEventListener('click', async () => {
262
+ const input = document.getElementById('task-respond-input');
263
+ if (!input?.value.trim()) return;
264
+ try {
265
+ await window.Veil.state.api.respondTask(task.id, input.value.trim());
266
+ window.Veil.utils.toast('Response sent', 'success');
267
+ this._loadDetail(task.id);
268
+ } catch (err) { window.Veil.utils.toast('Respond failed: ' + err.message, 'error'); }
269
+ });
270
+ },
271
+
272
+ async _renderEvents(taskId) {
273
+ const el = document.getElementById('task-events-body');
274
+ if (!el) return;
275
+ try {
276
+ const data = await window.Veil.state.api.getTaskEvents(taskId, 0);
277
+ const events = data.events || [];
278
+ if (events.length === 0) {
279
+ el.innerHTML = `<span style="color:var(--text3);">No events recorded</span>`; return;
280
+ }
281
+ const U = window.Veil.utils;
282
+ el.innerHTML = events.map(ev => this._renderEventRow(ev, U)).join('');
283
+ } catch (err) {
284
+ if (el) el.innerHTML = `<span style="color:var(--red);">${window.Veil.utils.esc(err.message)}</span>`;
285
+ }
286
+ },
287
+
288
+ _renderEventRow(ev, U) {
289
+ const d = ev.data || {};
290
+ let icon = '◦', color = 'var(--text2)', content = '';
291
+ if (ev.type === 'status.change') {
292
+ icon = '⟳'; color = 'var(--accent)';
293
+ content = `${U.esc(d.from || '?')} → ${U.esc(d.to || '?')}${d.reason ? ` (${U.esc(d.reason)})` : ''}`;
294
+ } else if (ev.type === 'tool.start') {
295
+ icon = '⚙'; color = 'var(--yellow)';
296
+ content = `<b style="color:var(--yellow);">${U.esc(d.toolName)}</b>`;
297
+ if (d.toolInput) content += ` <span style="color:var(--text3);">${U.esc(JSON.stringify(d.toolInput)).slice(0, 120)}</span>`;
298
+ } else if (ev.type === 'tool.end') {
299
+ icon = '✓'; color = d.success ? 'var(--green)' : 'var(--red)';
300
+ content = `<b style="color:${color};">${U.esc(d.toolName)}</b> ${d.durationMs ? U.formatMs(d.durationMs) : ''}`;
301
+ if (d.outputPreview) content += `<div style="font-family:monospace;font-size:12px;color:var(--text3);margin-top:3px;padding-left:12px;">${U.esc(d.outputPreview.slice(0, 200))}</div>`;
302
+ }
303
+ return `<div style="display:flex;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);">
304
+ <div class="timeline-dot" style="background:${color};margin-top:6px;"></div>
305
+ <div style="flex:1;">
306
+ <div style="display:flex;gap:8px;align-items:center;">
307
+ <span style="font-size:13px;">${content || U.esc(ev.type)}</span>
308
+ <span style="font-size:12px;color:var(--text3);margin-left:auto;">${U.relTime(ev.created_at)}</span>
309
+ </div>
310
+ </div>
311
+ </div>`;
312
+ },
313
+
314
+ _startStream(taskId) {
315
+ if (this._stream) { this._stream.close(); this._stream = null; }
316
+ const evEl = document.getElementById('task-events-body');
317
+ if (evEl) evEl.innerHTML = `<span style="color:var(--accent);">📡 Streaming live events…</span><br>`;
318
+ const U = window.Veil.utils;
319
+ this._stream = window.Veil.state.api.taskStream(taskId, {
320
+ onStatus: (data) => {
321
+ const badge = document.getElementById('task-status-badge');
322
+ if (badge && data.status) badge.innerHTML = U.badge(data.status);
323
+ if (['finished','failed','canceled'].includes(data.status)) {
324
+ if (this._stream) { this._stream.close(); this._stream = null; }
325
+ this._loadDetail(taskId);
326
+ this._loadList();
327
+ }
328
+ },
329
+ onEvent: (ev) => {
330
+ const el = document.getElementById('task-events-body');
331
+ if (!el) return;
332
+ if (typeof ev === 'object' && ev.type) {
333
+ el.innerHTML += this._renderEventRow(ev, U);
334
+ el.scrollTop = el.scrollHeight;
335
+ }
336
+ },
337
+ onError: (msg) => { window.Veil.utils.toast('Stream error: ' + msg, 'error'); },
338
+ });
339
+ },
340
+
341
+ _showCreateModal() {
342
+ const U = window.Veil.utils;
343
+ window.Veil.modal.show(`
344
+ <div>
345
+ <h3 style="font-size:16px;font-weight:700;color:var(--text);margin-bottom:16px;">Create Task</h3>
346
+ <div id="ct-err" style="display:none;background:var(--red-dim);color:var(--red);border:1px solid rgba(239,68,68,0.3);border-radius:6px;padding:8px 12px;margin-bottom:12px;font-size:13px;"></div>
347
+ <div style="margin-bottom:12px;">
348
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;">Agent *</label>
349
+ <select id="ct-agent" class="input" style="cursor:pointer;"></select>
350
+ </div>
351
+ <div style="margin-bottom:12px;">
352
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;">Input *</label>
353
+ <textarea id="ct-input" class="input mono" rows="4" placeholder="Task instructions…" style="resize:vertical;"></textarea>
354
+ </div>
355
+ <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-bottom:20px;">
356
+ <div>
357
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;">Priority</label>
358
+ <select id="ct-priority" class="input" style="cursor:pointer;">
359
+ <option value="normal">normal</option>
360
+ <option value="high">high</option>
361
+ <option value="low">low</option>
362
+ </select>
363
+ </div>
364
+ <div>
365
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;">Max Iterations</label>
366
+ <input id="ct-maxiter" class="input" type="number" placeholder="default" />
367
+ </div>
368
+ <div>
369
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:6px;">Token Budget</label>
370
+ <input id="ct-budget" class="input" type="number" placeholder="optional" />
371
+ </div>
372
+ </div>
373
+ <div style="display:flex;gap:10px;justify-content:flex-end;">
374
+ <button class="btn btn-secondary" onclick="window.Veil.modal.hide()">Cancel</button>
375
+ <button class="btn btn-primary" id="ct-submit">Create Task</button>
376
+ </div>
377
+ </div>`);
378
+
379
+ window.Veil.state.api.listAgents().then(data => {
380
+ const sel = document.getElementById('ct-agent');
381
+ if (!sel) return;
382
+ const agents = (data.agents || []).filter(a => a.modes && a.modes.includes('task'));
383
+ const preferred = window.Veil.state.currentAgent;
384
+ sel.innerHTML = agents.map(a =>
385
+ `<option value="${U.esc(a.name)}" ${a.name === preferred ? 'selected' : ''}>${U.esc(a.name)}</option>`
386
+ ).join('') || '<option value="">No task-enabled agents</option>';
387
+ }).catch(() => {});
388
+
389
+ document.getElementById('ct-submit').addEventListener('click', async () => {
390
+ const agent = document.getElementById('ct-agent')?.value;
391
+ const input = document.getElementById('ct-input')?.value.trim();
392
+ const errEl = document.getElementById('ct-err');
393
+ if (!agent || !input) {
394
+ errEl.textContent = 'Agent and input are required'; errEl.style.display = 'block'; return;
395
+ }
396
+ const body = { input, priority: document.getElementById('ct-priority').value };
397
+ const maxIter = parseInt(document.getElementById('ct-maxiter').value, 10);
398
+ const budget = parseInt(document.getElementById('ct-budget').value, 10);
399
+ if (maxIter > 0) body.maxIterations = maxIter;
400
+ if (budget > 0) body.tokenBudget = budget;
401
+ const btn = document.getElementById('ct-submit');
402
+ btn.disabled = true; btn.innerHTML = U.spinner() + ' Creating…';
403
+ try {
404
+ const res = await window.Veil.state.api.createTask(agent, body);
405
+ window.Veil.modal.hide();
406
+ window.Veil.utils.toast('Task created: ' + res.taskId, 'success');
407
+ this._selected = res.taskId;
408
+ await this._loadList();
409
+ } catch (err) {
410
+ btn.disabled = false; btn.textContent = 'Create Task';
411
+ errEl.textContent = err.message; errEl.style.display = 'block';
412
+ }
413
+ });
414
+ },
415
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CWD + version singleton.
5
+ * Set ONCE at startup in cli/index.js. All other modules read from here.
6
+ * Never call process.cwd() outside cli/index.js.
7
+ */
8
+
9
+ let _cwd = null;
10
+ let _version = null;
11
+
12
+ module.exports = {
13
+ /**
14
+ * @param {string} cwd - Absolute path to the project working directory
15
+ */
16
+ setCwd(cwd) {
17
+ _cwd = cwd;
18
+ },
19
+
20
+ /**
21
+ * @returns {string} Absolute path to the project working directory
22
+ */
23
+ getCwd() {
24
+ if (!_cwd) throw new Error('Context not initialized — call setCwd() at startup before using getCwd()');
25
+ return _cwd;
26
+ },
27
+
28
+ /**
29
+ * @param {string} version - Package version string
30
+ */
31
+ setVersion(version) {
32
+ _version = version;
33
+ },
34
+
35
+ /**
36
+ * @returns {string} Package version string
37
+ */
38
+ getVersion() {
39
+ return _version || '0.0.0';
40
+ },
41
+
42
+ /**
43
+ * Reset for testing purposes only
44
+ */
45
+ _reset() {
46
+ _cwd = null;
47
+ _version = null;
48
+ }
49
+ };
package/utils/id.js ADDED
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ /**
6
+ * Generate a unique ID with an optional prefix.
7
+ * Uses crypto.randomBytes — no external dependency.
8
+ *
9
+ * @param {string} [prefix=''] - Prefix for the ID (e.g. 'sess_', 'task_')
10
+ * @returns {string} Unique ID string
11
+ */
12
+ function generateId(prefix = '') {
13
+ return `${prefix}${crypto.randomBytes(8).toString('hex')}`;
14
+ }
15
+
16
+ module.exports = { generateId };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const MODELS_PATH = path.join(__dirname, '..', 'config', 'models.json');
7
+
8
+ let _cache = null;
9
+
10
+ /**
11
+ * Load and cache the models index from config/models.json.
12
+ * @returns {{ updated_at: string, models: Object }}
13
+ */
14
+ function loadModels() {
15
+ if (_cache) return _cache;
16
+ try {
17
+ _cache = JSON.parse(fs.readFileSync(MODELS_PATH, 'utf8'));
18
+ } catch {
19
+ _cache = { updated_at: null, models: {} };
20
+ }
21
+ return _cache;
22
+ }
23
+
24
+ /**
25
+ * Invalidate the in-memory cache (e.g. after re-fetching models).
26
+ */
27
+ function invalidateCache() {
28
+ _cache = null;
29
+ }
30
+
31
+ /**
32
+ * Get the full data entry for a model by its ID.
33
+ * Returns null if not found.
34
+ *
35
+ * @param {string} modelId - e.g. "anthropic/claude-sonnet-4.6"
36
+ * @returns {Object|null}
37
+ */
38
+ function getModel(modelId) {
39
+ if (!modelId) return null;
40
+ const { models } = loadModels();
41
+ return models[modelId] || null;
42
+ }
43
+
44
+ /**
45
+ * Get the context window size (in tokens) for a model.
46
+ * Returns null if the model is unknown.
47
+ *
48
+ * @param {string} modelId
49
+ * @returns {number|null}
50
+ */
51
+ function getContextLimit(modelId) {
52
+ const m = getModel(modelId);
53
+ return m ? (m.context_length || null) : null;
54
+ }
55
+
56
+ /**
57
+ * Calculate the estimated cost in USD for a set of token counts.
58
+ * Falls back to 0 if the model is unknown or pricing is missing.
59
+ *
60
+ * @param {string} modelId
61
+ * @param {{ input: number, output: number, cache: number }} tokens
62
+ * @returns {number} cost in USD
63
+ */
64
+ function calculateCost(modelId, { input = 0, output = 0, cache = 0 } = {}) {
65
+ const m = getModel(modelId);
66
+ if (!m) return 0;
67
+
68
+ const { prompt = 0, completion = 0, cache_read = 0 } = m.pricing || {};
69
+
70
+ // cache tokens are a subset of input tokens that were read from cache
71
+ // billed at cache_read rate instead of prompt rate
72
+ const billableInput = Math.max(0, input - cache);
73
+ return (billableInput * prompt) + (cache * cache_read) + (output * completion);
74
+ }
75
+
76
+ /**
77
+ * Return the full flat list of models (array form) for API responses.
78
+ * @returns {Object[]}
79
+ */
80
+ function listModels() {
81
+ const { updated_at, models } = loadModels();
82
+ return {
83
+ updated_at,
84
+ models: Object.entries(models).map(([id, data]) => ({ id, ...data })),
85
+ };
86
+ }
87
+
88
+ module.exports = { loadModels, getModel, getContextLimit, calculateCost, listModels, invalidateCache };