@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,96 @@
1
+ /* eslint-disable */
2
+ 'use strict';
3
+ window.Veil = window.Veil || {};
4
+ window.Veil.views = window.Veil.views || {};
5
+
6
+ window.Veil.views.connection = {
7
+ render() {
8
+ const url = localStorage.getItem('veil_url') || 'http://localhost:5050';
9
+ const secret = localStorage.getItem('veil_secret') || '';
10
+ const U = window.Veil.utils;
11
+ return `
12
+ <div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:24px;background:var(--bg);">
13
+ <div style="width:100%;max-width:400px;">
14
+
15
+ <!-- Logo -->
16
+ <div style="text-align:center;margin-bottom:40px;">
17
+ <div style="display:inline-flex;width:56px;height:56px;background:linear-gradient(135deg,var(--accent),#5B8AF0);border-radius:16px;align-items:center;justify-content:center;font-size:26px;box-shadow:0 4px 24px var(--accent-glow);margin-bottom:16px;">⚡</div>
18
+ <h1 style="font-size:22px;font-weight:700;letter-spacing:-0.03em;color:var(--text);margin-bottom:6px;">VeilCLI</h1>
19
+ <p style="font-size:13px;color:var(--text3);">Connect to your agent runtime</p>
20
+ </div>
21
+
22
+ <!-- Card -->
23
+ <div style="background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:28px;">
24
+
25
+ <!-- Error -->
26
+ <div id="conn-error" style="display:none;background:var(--red-dim);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:10px 14px;margin-bottom:20px;font-size:13px;color:var(--red);"></div>
27
+
28
+ <div style="margin-bottom:16px;">
29
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:7px;">Server URL</label>
30
+ <input id="conn-url" class="input" type="text" value="${U.esc(url)}"
31
+ placeholder="http://localhost:5050" autocomplete="off" spellcheck="false"
32
+ style="font-family:monospace;font-size:13px;" />
33
+ </div>
34
+
35
+ <div style="margin-bottom:24px;">
36
+ <label style="display:block;font-size:11.5px;font-weight:600;color:var(--text3);text-transform:uppercase;letter-spacing:0.06em;margin-bottom:7px;">
37
+ Secret Token <span style="color:var(--text3);font-weight:400;text-transform:none;letter-spacing:0;">(optional)</span>
38
+ </label>
39
+ <input id="conn-secret" class="input" type="password" value="${U.esc(secret)}"
40
+ placeholder="Leave empty if auth is disabled" autocomplete="off" />
41
+ </div>
42
+
43
+ <button id="conn-btn" class="btn btn-primary" style="width:100%;justify-content:center;padding:10px 14px;font-size:14px;border-radius:9px;">
44
+ Connect
45
+ </button>
46
+ </div>
47
+
48
+ <p style="font-size:12px;color:var(--text3);text-align:center;margin-top:20px;">
49
+ Credentials saved in <code style="color:var(--text2);">localStorage</code> · auto-reconnects on reload
50
+ </p>
51
+ </div>
52
+ </div>`;
53
+ },
54
+
55
+ mount() {
56
+ const btn = document.getElementById('conn-btn');
57
+ const urlEl = document.getElementById('conn-url');
58
+ const secEl = document.getElementById('conn-secret');
59
+ const errEl = document.getElementById('conn-error');
60
+
61
+ const connect = async () => {
62
+ const url = urlEl.value.trim();
63
+ const secret = secEl.value.trim();
64
+ if (!url) { showErr('Server URL is required.'); return; }
65
+ btn.disabled = true;
66
+ btn.innerHTML = `${window.Veil.utils.spinner()} Connecting…`;
67
+ errEl.style.display = 'none';
68
+ const api = new window.Veil.API(url, secret);
69
+ try {
70
+ const data = await api.health();
71
+ window.Veil.saveConnection(url, secret);
72
+ window.Veil.state.api = api;
73
+ window.Veil.state.connected = true;
74
+ window.Veil._updateIndicator();
75
+ document.getElementById('sidebar').style.display = 'flex';
76
+ window.Veil.utils.toast(`Connected to Veil v${data.version || '?'}`, 'success');
77
+ window.Veil.navigate('agents');
78
+ } catch (err) {
79
+ btn.disabled = false;
80
+ btn.textContent = 'Connect';
81
+ showErr(err.message || 'Connection failed');
82
+ }
83
+ };
84
+
85
+ const showErr = (msg) => {
86
+ errEl.textContent = msg;
87
+ errEl.style.display = 'block';
88
+ };
89
+
90
+ btn.addEventListener('click', connect);
91
+ urlEl.addEventListener('keydown', e => { if (e.key === 'Enter') connect(); });
92
+ secEl.addEventListener('keydown', e => { if (e.key === 'Enter') connect(); });
93
+ },
94
+
95
+ unmount() {},
96
+ };
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+ window.Veil = window.Veil || {};
3
+ window.Veil.views = window.Veil.views || {};
4
+
5
+ window.Veil.views.daemons = {
6
+ _refreshTimer: null,
7
+
8
+ render() {
9
+ return `
10
+ <div style="padding:20px;">
11
+ <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
12
+ <h1 style="font-size:16px;font-weight:700;color:var(--text);">Daemon Schedulers</h1>
13
+ <button id="btn-refresh-daemons" class="btn btn-secondary btn-sm">↻ Refresh</button>
14
+ </div>
15
+ <div id="daemon-list">
16
+ <div style="text-align:center;padding:30px;color:var(--text3);">${window.Veil.utils.spinner()}</div>
17
+ </div>
18
+ </div>`;
19
+ },
20
+
21
+ async mount() {
22
+ await this._load();
23
+ document.getElementById('btn-refresh-daemons')?.addEventListener('click', () => this._load());
24
+ this._refreshTimer = setInterval(() => this._load(), 15000);
25
+ },
26
+
27
+ unmount() {
28
+ if (this._refreshTimer) { clearInterval(this._refreshTimer); this._refreshTimer = null; }
29
+ },
30
+
31
+ async _load() {
32
+ const el = document.getElementById('daemon-list');
33
+ if (!el) return;
34
+ try {
35
+ const [daemonData, agentData] = await Promise.all([
36
+ window.Veil.state.api.listDaemons(),
37
+ window.Veil.state.api.listAgents(),
38
+ ]);
39
+ const daemons = daemonData.daemons || [];
40
+ const allAgents = (agentData.agents || []).filter(a => a.modes && a.modes.includes('daemon'));
41
+ const activeNames = new Set(daemons.map(d => d.agentName));
42
+ const U = window.Veil.utils;
43
+
44
+ if (allAgents.length === 0 && daemons.length === 0) {
45
+ el.innerHTML = `<div class="card" style="padding:24px;text-align:center;color:var(--text3);font-size:13.5px;">No daemon-enabled agents found.<br><br>Add <code>modes.daemon.enabled: true</code> to an agent's config.</div>`;
46
+ return;
47
+ }
48
+
49
+ const rows = allAgents.map(a => {
50
+ const d = daemons.find(x => x.agentName === a.name);
51
+ const isActive = !!d;
52
+ return `
53
+ <div class="card" style="padding:16px;margin-bottom:12px;" id="daemon-row-${U.esc(a.name)}">
54
+ <div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
55
+ <div style="flex:1;">
56
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;">
57
+ <span style="font-weight:600;font-size:14px;color:var(--text);">${U.esc(a.name)}</span>
58
+ ${isActive
59
+ ? `<span class="badge badge-active">active</span>`
60
+ : `<span class="badge badge-closed">stopped</span>`}
61
+ </div>
62
+ <div style="font-size:12.5px;color:var(--text2);display:flex;gap:16px;flex-wrap:wrap;">
63
+ ${d?.schedule ? `<span>⏱ ${U.esc(d.schedule)}</span>` : ''}
64
+ ${d?.lastTick ? `<span>Last: ${U.relTime(d.lastTick)}</span>` : ''}
65
+ ${d?.nextTick ? `<span>Next: ${U.relTime(d.nextTick)}</span>` : ''}
66
+ </div>
67
+ </div>
68
+ <div style="display:flex;gap:8px;flex-shrink:0;">
69
+ ${!isActive
70
+ ? `<button class="btn btn-success btn-sm" data-daemon-start="${U.esc(a.name)}">▶ Start</button>`
71
+ : `<button class="btn btn-danger btn-sm" data-daemon-stop="${U.esc(a.name)}">■ Stop</button>`}
72
+ <button class="btn btn-secondary btn-sm" data-daemon-trigger="${U.esc(a.name)}">⚡ Trigger</button>
73
+ </div>
74
+ </div>
75
+ <div id="daemon-result-${U.esc(a.name)}" style="margin-top:10px;display:none;font-size:13px;"></div>
76
+ </div>`;
77
+ });
78
+ el.innerHTML = rows.join('');
79
+
80
+ el.querySelectorAll('[data-daemon-start]').forEach(btn => {
81
+ btn.addEventListener('click', () => this._startDaemon(btn.getAttribute('data-daemon-start')));
82
+ });
83
+ el.querySelectorAll('[data-daemon-stop]').forEach(btn => {
84
+ btn.addEventListener('click', () => this._stopDaemon(btn.getAttribute('data-daemon-stop')));
85
+ });
86
+ el.querySelectorAll('[data-daemon-trigger]').forEach(btn => {
87
+ btn.addEventListener('click', () => this._triggerDaemon(btn.getAttribute('data-daemon-trigger')));
88
+ });
89
+ } catch (err) {
90
+ if (el) el.innerHTML = `<div class="card" style="padding:16px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
91
+ }
92
+ },
93
+
94
+ async _startDaemon(name) {
95
+ try {
96
+ await window.Veil.state.api.startDaemon(name);
97
+ window.Veil.utils.toast(`Daemon "${name}" started`, 'success');
98
+ this._load();
99
+ } catch (err) { window.Veil.utils.toast('Start failed: ' + err.message, 'error'); }
100
+ },
101
+
102
+ async _stopDaemon(name) {
103
+ try {
104
+ await window.Veil.state.api.stopDaemon(name);
105
+ window.Veil.utils.toast(`Daemon "${name}" stopped`, 'success');
106
+ this._load();
107
+ } catch (err) { window.Veil.utils.toast('Stop failed: ' + err.message, 'error'); }
108
+ },
109
+
110
+ async _triggerDaemon(name) {
111
+ const resultEl = document.getElementById('daemon-result-' + name);
112
+ try {
113
+ const data = await window.Veil.state.api.triggerDaemon(name);
114
+ window.Veil.utils.toast(`Daemon "${name}" triggered`, 'success');
115
+ if (resultEl) {
116
+ resultEl.style.display = 'block';
117
+ resultEl.style.color = 'var(--green)';
118
+ resultEl.textContent = 'Triggered — status: ' + (data.status || 'triggered');
119
+ }
120
+ } catch (err) {
121
+ window.Veil.utils.toast('Trigger failed: ' + err.message, 'error');
122
+ if (resultEl) {
123
+ resultEl.style.display = 'block';
124
+ resultEl.style.color = 'var(--red)';
125
+ resultEl.textContent = 'Error: ' + err.message;
126
+ }
127
+ }
128
+ },
129
+ };
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+ window.Veil = window.Veil || {};
3
+ window.Veil.views = window.Veil.views || {};
4
+
5
+ window.Veil.views.feed = {
6
+ _ws: null,
7
+ _paused: false,
8
+ _autoScroll: true,
9
+ _events: [],
10
+ _filters: { task: true, chat: true, daemon: true, system: true },
11
+
12
+ render() {
13
+ return `
14
+ <div style="display:flex;flex-direction:column;height:100%;">
15
+ <!-- Toolbar -->
16
+ <div style="flex-shrink:0;padding:10px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
17
+ <div style="display:flex;align-items:center;gap:8px;">
18
+ <span id="feed-status-dot" style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--border2);"></span>
19
+ <span id="feed-status-text" style="font-size:12.5px;color:var(--text3);">Disconnected</span>
20
+ </div>
21
+ <button id="btn-feed-connect" class="btn btn-primary btn-sm">Connect</button>
22
+ <button id="btn-feed-pause" class="btn btn-secondary btn-sm">⏸ Pause</button>
23
+ <button id="btn-feed-clear" class="btn btn-secondary btn-sm">Clear</button>
24
+ <div style="width:1px;height:20px;background:var(--border);margin:0 4px;"></div>
25
+ <div style="display:flex;align-items:center;gap:10px;font-size:12.5px;">
26
+ <label style="display:flex;align-items:center;gap:4px;cursor:pointer;"><input type="checkbox" id="filter-task" ${this._filters.task ?'checked':''}> task</label>
27
+ <label style="display:flex;align-items:center;gap:4px;cursor:pointer;"><input type="checkbox" id="filter-chat" ${this._filters.chat ?'checked':''}> chat</label>
28
+ <label style="display:flex;align-items:center;gap:4px;cursor:pointer;"><input type="checkbox" id="filter-daemon" ${this._filters.daemon ?'checked':''}> daemon</label>
29
+ <label style="display:flex;align-items:center;gap:4px;cursor:pointer;"><input type="checkbox" id="filter-system" ${this._filters.system ?'checked':''}> system</label>
30
+ </div>
31
+ <div style="margin-left:auto;display:flex;align-items:center;gap:8px;">
32
+ <label style="display:flex;align-items:center;gap:4px;font-size:12.5px;cursor:pointer;">
33
+ <input type="checkbox" id="feed-autoscroll" ${this._autoScroll ? 'checked' : ''}> Auto-scroll
34
+ </label>
35
+ <span id="feed-event-count" style="font-size:12px;color:var(--text3);">0 events</span>
36
+ </div>
37
+ </div>
38
+ <!-- Event log -->
39
+ <div id="feed-log" style="flex:1;overflow-y:auto;font-family:monospace;font-size:12.5px;padding:8px 0;background:var(--bg-alt);"></div>
40
+ </div>`;
41
+ },
42
+
43
+ mount() {
44
+ document.getElementById('btn-feed-connect').addEventListener('click', () => this._toggle());
45
+ document.getElementById('btn-feed-pause').addEventListener('click', () => this._togglePause());
46
+ document.getElementById('btn-feed-clear').addEventListener('click', () => this._clear());
47
+ document.getElementById('feed-autoscroll').addEventListener('change', (e) => {
48
+ this._autoScroll = e.target.checked;
49
+ });
50
+
51
+ ['task','chat','daemon','system'].forEach(type => {
52
+ document.getElementById('filter-' + type)?.addEventListener('change', (e) => {
53
+ this._filters[type] = e.target.checked;
54
+ this._reRender();
55
+ });
56
+ });
57
+
58
+ document.getElementById('feed-log')?.addEventListener('scroll', (e) => {
59
+ const el = e.target;
60
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
61
+ if (!atBottom && this._autoScroll) {
62
+ const cb = document.getElementById('feed-autoscroll');
63
+ if (cb) { cb.checked = false; this._autoScroll = false; }
64
+ }
65
+ });
66
+
67
+ this._connect();
68
+ },
69
+
70
+ unmount() {
71
+ if (this._ws) {
72
+ try { this._ws.close(); } catch {}
73
+ this._ws = null;
74
+ }
75
+ },
76
+
77
+ _connect() {
78
+ if (this._ws) {
79
+ try { this._ws.close(); } catch {}
80
+ this._ws = null;
81
+ }
82
+ this._setStatus('connecting', 'Connecting…', 'var(--yellow)');
83
+ this._ws = window.Veil.state.api.connectWS(
84
+ (msg) => this._onMessage(msg),
85
+ () => this._setStatus('connected', 'Connected', 'var(--green)'),
86
+ () => {
87
+ this._setStatus('disconnected', 'Disconnected', 'var(--red)');
88
+ const btn = document.getElementById('btn-feed-connect');
89
+ if (btn) btn.textContent = 'Reconnect';
90
+ },
91
+ );
92
+ const btn = document.getElementById('btn-feed-connect');
93
+ if (btn) btn.textContent = 'Disconnect';
94
+ },
95
+
96
+ _toggle() {
97
+ if (this._ws && this._ws.readyState <= 1) {
98
+ this._ws.close();
99
+ this._ws = null;
100
+ this._setStatus('disconnected', 'Disconnected', 'var(--border2)');
101
+ const btn = document.getElementById('btn-feed-connect');
102
+ if (btn) btn.textContent = 'Connect';
103
+ } else {
104
+ this._connect();
105
+ }
106
+ },
107
+
108
+ _togglePause() {
109
+ this._paused = !this._paused;
110
+ const btn = document.getElementById('btn-feed-pause');
111
+ if (btn) btn.textContent = this._paused ? '▶ Resume' : '⏸ Pause';
112
+ if (!this._paused) this._reRender();
113
+ },
114
+
115
+ _clear() {
116
+ this._events = [];
117
+ const el = document.getElementById('feed-log');
118
+ if (el) el.innerHTML = '';
119
+ this._updateCount();
120
+ },
121
+
122
+ _setStatus(state, text, color) {
123
+ const dot = document.getElementById('feed-status-dot');
124
+ const txt = document.getElementById('feed-status-text');
125
+ if (dot) dot.style.background = color;
126
+ if (txt) { txt.textContent = text; txt.style.color = color; }
127
+ },
128
+
129
+ _onMessage(msg) {
130
+ if (this._paused) { this._events.push(msg); return; }
131
+ this._events.push(msg);
132
+ const type = (msg.type || '').split('.')[0];
133
+ if (!this._filters[type] && !(type === 'connected' && this._filters.system)) return;
134
+ this._appendEvent(msg);
135
+ this._updateCount();
136
+ },
137
+
138
+ _reRender() {
139
+ const el = document.getElementById('feed-log');
140
+ if (!el) return;
141
+ el.innerHTML = '';
142
+ this._events.forEach(ev => {
143
+ const type = (ev.type || '').split('.')[0];
144
+ const show = this._filters[type] || (ev.type === 'connected' && this._filters.system);
145
+ if (show) this._appendEvent(ev);
146
+ });
147
+ this._updateCount();
148
+ },
149
+
150
+ _appendEvent(ev) {
151
+ const el = document.getElementById('feed-log');
152
+ if (!el) return;
153
+ const U = window.Veil.utils;
154
+ const row = document.createElement('div');
155
+ const now = new Date().toISOString().slice(11, 23);
156
+
157
+ const { typeColor, typeBg } = this._typeStyle(ev.type);
158
+ const agentPart = ev.agentName ? `<span style="color:var(--accent);">${U.esc(ev.agentName)}</span> ` : '';
159
+ const taskPart = ev.taskId ? `<span style="color:var(--text3);font-size:0.72rem;">${U.esc(U.shortId(ev.taskId))}</span> ` : '';
160
+ const sessionPart = ev.sessionId ? `<span style="color:var(--text3);font-size:0.72rem;">${U.esc(U.shortId(ev.sessionId))}</span> ` : '';
161
+
162
+ let dataPart = '';
163
+ if (ev.event && typeof ev.event === 'object') {
164
+ const e = ev.event;
165
+ if (e.status) dataPart = `<span style="color:var(--text);">status: ${U.esc(e.status)}</span>`;
166
+ else if (e.type === 'tool.start') dataPart = `<span style="color:var(--yellow);">⚙ ${U.esc(e.toolName)}</span>`;
167
+ else if (e.type === 'tool.end') dataPart = `<span style="color:var(--green);">✓ ${U.esc(e.toolName)} ${U.formatMs(e.durationMs)}</span>`;
168
+ else if (e.content) dataPart = `<span style="color:var(--text2);">${U.esc(U.truncate(e.content, 80))}</span>`;
169
+ else dataPart = `<span style="color:var(--text3);">${U.esc(JSON.stringify(e).slice(0, 100))}</span>`;
170
+ }
171
+
172
+ row.style.cssText = 'padding:4px 16px;border-bottom:1px solid var(--bg);display:flex;align-items:baseline;gap:8px;';
173
+ row.innerHTML = `
174
+ <span style="color:var(--text3);flex-shrink:0;">${now}</span>
175
+ <span style="background:${typeBg};color:${typeColor};padding:1px 6px;border-radius:3px;font-size:0.7rem;font-weight:600;flex-shrink:0;">${U.esc(ev.type || 'unknown')}</span>
176
+ <span style="flex:1;">${agentPart}${taskPart}${sessionPart}${dataPart}</span>`;
177
+
178
+ el.appendChild(row);
179
+ if (this._autoScroll) el.scrollTop = el.scrollHeight;
180
+ },
181
+
182
+ _typeStyle(type) {
183
+ const t = (type || '').split('.')[0];
184
+ if (t === 'task') return { typeColor: 'var(--accent)', typeBg: 'var(--bg-task)' };
185
+ if (t === 'chat') return { typeColor: 'var(--green)', typeBg: 'var(--bg-chat)' };
186
+ if (t === 'daemon') return { typeColor: 'var(--yellow)', typeBg: 'var(--bg-daemon)' };
187
+ return { typeColor: 'var(--text2)', typeBg: 'var(--border)' };
188
+ },
189
+
190
+ _updateCount() {
191
+ const el = document.getElementById('feed-event-count');
192
+ if (el) el.textContent = this._events.length + ' events';
193
+ },
194
+ };