@newsails/veil-cli 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.veil/agents/analyst/AGENT.md +21 -0
- package/.veil/agents/analyst/agent.json +23 -0
- package/.veil/agents/assistant/AGENT.md +15 -0
- package/.veil/agents/assistant/agent.json +19 -0
- package/.veil/agents/coder/AGENT.md +18 -0
- package/.veil/agents/coder/agent.json +19 -0
- package/.veil/agents/hello/AGENT.md +5 -0
- package/.veil/agents/hello/agent.json +13 -0
- package/.veil/agents/writer/AGENT.md +12 -0
- package/.veil/agents/writer/agent.json +17 -0
- package/.veil/memory/MEMORY.md +343 -0
- package/.veil/memory/agents/analyst/MEMORY.md +55 -0
- package/.veil/memory/agents/hello/MEMORY.md +12 -0
- package/.veil/runtime.pid +1 -0
- package/.veil/settings.json +10 -0
- package/.veil-studio/studio.db +0 -0
- package/.veil-studio/studio.db-shm +0 -0
- package/.veil-studio/studio.db-wal +0 -0
- package/PLAN/01-vision.md +26 -0
- package/PLAN/02-tech-stack.md +94 -0
- package/PLAN/03-agents.md +232 -0
- package/PLAN/04-runtime.md +171 -0
- package/PLAN/05-tools.md +211 -0
- package/PLAN/06-communication.md +243 -0
- package/PLAN/07-storage.md +218 -0
- package/PLAN/08-api-cli.md +153 -0
- package/PLAN/09-permissions.md +108 -0
- package/PLAN/10-ably.md +105 -0
- package/PLAN/11-file-formats.md +442 -0
- package/PLAN/12-folder-structure.md +205 -0
- package/PLAN/13-operations.md +212 -0
- package/PLAN/README.md +23 -0
- package/README.md +128 -0
- package/REPORT.md +174 -0
- package/TODO.md +45 -0
- package/ai-tests/FRONTEND_PROMPT.md +220 -0
- package/ai-tests/Research & Planning.md +814 -0
- package/ai-tests/prompt-001-basic-api.md +230 -0
- package/ai-tests/prompt-002-basic-flows.md +230 -0
- package/ai-tests/prompt-003-agent-behaviors.md +220 -0
- package/api/middleware.js +60 -0
- package/api/routes/agents.js +193 -0
- package/api/routes/chat.js +93 -0
- package/api/routes/completions.js +122 -0
- package/api/routes/daemons.js +80 -0
- package/api/routes/memory.js +169 -0
- package/api/routes/models.js +40 -0
- package/api/routes/remote-methods.js +74 -0
- package/api/routes/sessions.js +208 -0
- package/api/routes/settings.js +108 -0
- package/api/routes/system.js +50 -0
- package/api/routes/tasks.js +270 -0
- package/api/server.js +120 -0
- package/cli/formatter.js +70 -0
- package/cli/index.js +443 -0
- package/cli/parser.js +113 -0
- package/config/config.json +10 -0
- package/config/models.json +6826 -0
- package/core/agent.js +329 -0
- package/core/cancel.js +38 -0
- package/core/compaction.js +176 -0
- package/core/events.js +13 -0
- package/core/loop.js +564 -0
- package/core/memory.js +51 -0
- package/core/prompt.js +185 -0
- package/core/queue.js +96 -0
- package/core/registry.js +291 -0
- package/core/remote-methods.js +124 -0
- package/core/router.js +386 -0
- package/core/running-sessions.js +18 -0
- package/docs/api/01-system.md +84 -0
- package/docs/api/02-agents.md +374 -0
- package/docs/api/03-chat.md +269 -0
- package/docs/api/04-tasks.md +470 -0
- package/docs/api/05-sessions.md +444 -0
- package/docs/api/06-daemons.md +142 -0
- package/docs/api/07-memory.md +186 -0
- package/docs/api/08-settings.md +133 -0
- package/docs/api/09-models.md +119 -0
- package/docs/api/09-websocket.md +350 -0
- package/docs/api/10-completions.md +134 -0
- package/docs/api/README.md +116 -0
- package/docs/guide/01-quickstart.md +220 -0
- package/docs/guide/02-folder-structure.md +185 -0
- package/docs/guide/03-configuration.md +252 -0
- package/docs/guide/04-agents.md +267 -0
- package/docs/guide/05-cli.md +290 -0
- package/docs/guide/06-tools.md +643 -0
- package/docs/guide/07-permissions.md +236 -0
- package/docs/guide/08-memory.md +139 -0
- package/docs/guide/09-multi-agent.md +271 -0
- package/docs/guide/10-daemons.md +226 -0
- package/docs/guide/README.md +53 -0
- package/docs/index.html +623 -0
- package/examples/README.md +151 -0
- package/examples/agents/assistant/AGENT.md +31 -0
- package/examples/agents/assistant/SOUL.md +9 -0
- package/examples/agents/assistant/agent.json +74 -0
- package/examples/agents/hello/AGENT.md +15 -0
- package/examples/agents/hello/agent.json +14 -0
- package/examples/agents/monitor/AGENT.md +51 -0
- package/examples/agents/monitor/agent.json +33 -0
- package/examples/agents/monitor/heartbeats/monitor.md +24 -0
- package/examples/agents/orchestrator/AGENT.md +70 -0
- package/examples/agents/orchestrator/agent.json +30 -0
- package/examples/agents/researcher/AGENT.md +52 -0
- package/examples/agents/researcher/agent.json +49 -0
- package/examples/agents/researcher/skills/web-research.md +28 -0
- package/examples/skills/code-review.md +72 -0
- package/examples/skills/summarise.md +59 -0
- package/examples/skills/web-research.md +42 -0
- package/examples/tools/word-count/index.js +27 -0
- package/examples/tools/word-count/tool.json +18 -0
- package/infrastructure/database.js +563 -0
- package/infrastructure/scheduler.js +122 -0
- package/llm/client.js +206 -0
- package/migrations/001-initial.sql +121 -0
- package/migrations/002-debuggability.sql +13 -0
- package/migrations/003-drop-orphaned-columns.sql +72 -0
- package/migrations/004-session-message-token-fields.sql +78 -0
- package/migrations/005-session-thinking.sql +5 -0
- package/package.json +30 -0
- package/schemas/agent.json +143 -0
- package/schemas/settings.json +111 -0
- package/scripts/fetch-models.js +93 -0
- package/session-debug-scenario.md +248 -0
- package/settings/fields.js +52 -0
- package/system-prompts/base-core.md +7 -0
- package/system-prompts/environment.md +13 -0
- package/system-prompts/reminders/anti-drift.md +6 -0
- package/system-prompts/reminders/stall-recovery.md +10 -0
- package/system-prompts/safety-rules.md +25 -0
- package/system-prompts/task-heuristics.md +27 -0
- package/test/client.js +71 -0
- package/test/integration/01-health.test.js +25 -0
- package/test/integration/02-agents.test.js +80 -0
- package/test/integration/03-chat-hello.test.js +48 -0
- package/test/integration/04-chat-multiturn.test.js +61 -0
- package/test/integration/05-chat-writer.test.js +48 -0
- package/test/integration/06-task-basic.test.js +68 -0
- package/test/integration/07-task-tools.test.js +74 -0
- package/test/integration/08-task-code-analysis.test.js +69 -0
- package/test/integration/09-memory-analyst.test.js +63 -0
- package/test/integration/10-task-advanced.test.js +85 -0
- package/test/integration/11-sessions-advanced.test.js +84 -0
- package/test/integration/12-assistant-chat-tools.test.js +75 -0
- package/test/integration/13-edge-cases.test.js +99 -0
- package/test/integration/14-cancel.test.js +62 -0
- package/test/integration/15-debug.test.js +106 -0
- package/test/integration/16-memory-api.test.js +83 -0
- package/test/integration/17-settings-api.test.js +41 -0
- package/test/integration/18-tool-search-activation.test.js +119 -0
- package/test/results/.gitkeep +0 -0
- package/test/runner.js +206 -0
- package/test/smoke.js +216 -0
- package/tools/agent_message.js +85 -0
- package/tools/agent_send.js +80 -0
- package/tools/agent_spawn.js +44 -0
- package/tools/bash.js +49 -0
- package/tools/edit_file.js +41 -0
- package/tools/glob.js +64 -0
- package/tools/grep.js +82 -0
- package/tools/list_dir.js +63 -0
- package/tools/log_write.js +31 -0
- package/tools/memory_read.js +38 -0
- package/tools/memory_search.js +65 -0
- package/tools/memory_write.js +42 -0
- package/tools/read_file.js +48 -0
- package/tools/sleep.js +22 -0
- package/tools/task_create.js +41 -0
- package/tools/task_respond.js +37 -0
- package/tools/task_spawn.js +64 -0
- package/tools/task_status.js +39 -0
- package/tools/task_subscribe.js +37 -0
- package/tools/todo_read.js +26 -0
- package/tools/todo_write.js +38 -0
- package/tools/tool_activate.js +24 -0
- package/tools/tool_search.js +24 -0
- package/tools/web_fetch.js +50 -0
- package/tools/web_search.js +52 -0
- package/tools/write_file.js +28 -0
- package/ui/api.js +190 -0
- package/ui/app.js +281 -0
- package/ui/index.html +382 -0
- package/ui/views/agents.js +377 -0
- package/ui/views/chat.js +610 -0
- package/ui/views/connection.js +96 -0
- package/ui/views/daemons.js +129 -0
- package/ui/views/feed.js +194 -0
- package/ui/views/memory.js +263 -0
- package/ui/views/models.js +146 -0
- package/ui/views/sessions.js +314 -0
- package/ui/views/settings.js +142 -0
- package/ui/views/tasks.js +415 -0
- package/utils/context.js +49 -0
- package/utils/id.js +16 -0
- package/utils/models.js +88 -0
- package/utils/paths.js +213 -0
- package/utils/settings.js +172 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
window.Veil = window.Veil || {};
|
|
3
|
+
window.Veil.views = window.Veil.views || {};
|
|
4
|
+
|
|
5
|
+
window.Veil.views.sessions = {
|
|
6
|
+
_selected: null,
|
|
7
|
+
_msgOffset: 0,
|
|
8
|
+
_msgLimit: 50,
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
return `
|
|
12
|
+
<div style="display:flex;height:100%;">
|
|
13
|
+
<!-- List -->
|
|
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;">Sessions</span>
|
|
17
|
+
<button id="btn-refresh-sessions" class="btn btn-secondary btn-sm" title="Refresh">↻</button>
|
|
18
|
+
</div>
|
|
19
|
+
<div style="padding:8px 14px;border-bottom:1px solid var(--border);display:flex;gap:6px;">
|
|
20
|
+
<select id="sess-filter-status" class="input" style="flex:1;padding:4px 8px;font-size:12px;cursor:pointer;">
|
|
21
|
+
<option value="">All</option>
|
|
22
|
+
<option value="active">active</option>
|
|
23
|
+
<option value="closed">closed</option>
|
|
24
|
+
</select>
|
|
25
|
+
<select id="sess-filter-agent" class="input" style="flex:1;padding:4px 8px;font-size:12px;cursor:pointer;">
|
|
26
|
+
<option value="">All agents</option>
|
|
27
|
+
</select>
|
|
28
|
+
</div>
|
|
29
|
+
<div id="sess-list" style="flex:1;overflow-y:auto;"></div>
|
|
30
|
+
</div>
|
|
31
|
+
<!-- Detail -->
|
|
32
|
+
<div id="sess-detail" style="flex:1;overflow-y:auto;">
|
|
33
|
+
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">
|
|
34
|
+
Select a session to view its messages
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>`;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
async mount(params) {
|
|
41
|
+
params = params || {};
|
|
42
|
+
await this._populateAgentFilter();
|
|
43
|
+
await this._loadList();
|
|
44
|
+
document.getElementById('btn-refresh-sessions').addEventListener('click', () => this._loadList());
|
|
45
|
+
document.getElementById('sess-filter-status').addEventListener('change', () => this._loadList());
|
|
46
|
+
document.getElementById('sess-filter-agent').addEventListener('change', () => this._loadList());
|
|
47
|
+
/* Navigate here directly with a session pre-selected (e.g. from Tasks) */
|
|
48
|
+
if (params.sessionId) {
|
|
49
|
+
this._selected = params.sessionId;
|
|
50
|
+
this._msgOffset = 0;
|
|
51
|
+
this._loadDetail(params.sessionId);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
unmount() { this._selected = null; },
|
|
56
|
+
|
|
57
|
+
async _populateAgentFilter() {
|
|
58
|
+
const sel = document.getElementById('sess-filter-agent');
|
|
59
|
+
if (!sel) return;
|
|
60
|
+
try {
|
|
61
|
+
const data = await window.Veil.state.api.listAgents();
|
|
62
|
+
const preferred = window.Veil.state.currentAgent;
|
|
63
|
+
(data.agents || []).forEach(a => {
|
|
64
|
+
const opt = document.createElement('option');
|
|
65
|
+
opt.value = a.name; opt.textContent = a.name;
|
|
66
|
+
if (a.name === preferred) opt.selected = true;
|
|
67
|
+
sel.appendChild(opt);
|
|
68
|
+
});
|
|
69
|
+
} catch {}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async _loadList() {
|
|
73
|
+
const el = document.getElementById('sess-list');
|
|
74
|
+
if (!el) return;
|
|
75
|
+
el.innerHTML = `<div style="padding:16px;text-align:center;color:var(--text3);">${window.Veil.utils.spinner()}</div>`;
|
|
76
|
+
const params = { limit: 50 };
|
|
77
|
+
const status = document.getElementById('sess-filter-status')?.value;
|
|
78
|
+
const agent = document.getElementById('sess-filter-agent')?.value;
|
|
79
|
+
if (status) params.status = status;
|
|
80
|
+
if (agent) params.agentName = agent;
|
|
81
|
+
try {
|
|
82
|
+
const data = await window.Veil.state.api.listSessions(params);
|
|
83
|
+
const sessions = data.sessions || [];
|
|
84
|
+
if (sessions.length === 0) {
|
|
85
|
+
el.innerHTML = `<div style="padding:20px;text-align:center;color:var(--text3);font-size:13px;">No sessions found</div>`;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const U = window.Veil.utils;
|
|
89
|
+
el.innerHTML = sessions.map(s => `
|
|
90
|
+
<div class="list-item ${this._selected === s.id ? 'selected' : ''}" data-sess-id="${U.esc(s.id)}">
|
|
91
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px;">
|
|
92
|
+
${U.badge(s.status)}
|
|
93
|
+
<span class="badge badge-closed">${U.esc(s.mode || 'chat')}</span>
|
|
94
|
+
<span style="font-size:12px;color:var(--text3);margin-left:auto;">${U.relTime(s.created_at)}</span>
|
|
95
|
+
</div>
|
|
96
|
+
<div style="font-size:12.5px;color:var(--text2);margin-bottom:2px;">${U.esc(s.agent_name)}</div>
|
|
97
|
+
<div style="font-size:12px;color:var(--text3);">${s.message_count || 0} messages · ${U.esc(s.model || '').split('/').pop()}</div>
|
|
98
|
+
</div>`).join('');
|
|
99
|
+
el.querySelectorAll('[data-sess-id]').forEach(row => {
|
|
100
|
+
row.addEventListener('click', () => {
|
|
101
|
+
const id = row.getAttribute('data-sess-id');
|
|
102
|
+
el.querySelectorAll('.list-item').forEach(r => r.classList.remove('selected'));
|
|
103
|
+
row.classList.add('selected');
|
|
104
|
+
this._selected = id;
|
|
105
|
+
this._msgOffset = 0;
|
|
106
|
+
this._loadDetail(id);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async _loadDetail(id) {
|
|
115
|
+
const el = document.getElementById('sess-detail');
|
|
116
|
+
if (!el) return;
|
|
117
|
+
el.innerHTML = `<div style="padding:20px;color:var(--text3);">${window.Veil.utils.spinner()} Loading…</div>`;
|
|
118
|
+
try {
|
|
119
|
+
const [sessData, msgData] = await Promise.all([
|
|
120
|
+
window.Veil.state.api.getSession(id),
|
|
121
|
+
window.Veil.state.api.getSessionMessages(id, { limit: this._msgLimit, offset: this._msgOffset }),
|
|
122
|
+
]);
|
|
123
|
+
const sess = sessData.session;
|
|
124
|
+
const msgs = msgData.messages || [];
|
|
125
|
+
const U = window.Veil.utils;
|
|
126
|
+
|
|
127
|
+
const ctxRingHtml = U.contextRing(sess.context_size, sess.context_size_limit, [
|
|
128
|
+
{ label: 'Context now', value: (sess.context_size || 0).toLocaleString() + (sess.context_size_limit ? ' / ' + sess.context_size_limit.toLocaleString() : '') + ' tokens' },
|
|
129
|
+
{ label: 'Total in', value: (sess.total_input_tokens || 0).toLocaleString() + ' tokens' },
|
|
130
|
+
{ label: 'Total out', value: (sess.total_output_tokens || 0).toLocaleString() + ' tokens' },
|
|
131
|
+
{ label: 'Cached', value: (sess.total_cache_tokens || 0).toLocaleString() + ' tokens' },
|
|
132
|
+
{ label: 'Total cost', value: U.formatCost(sess.cost) || '$0' },
|
|
133
|
+
]);
|
|
134
|
+
el.innerHTML = `
|
|
135
|
+
<div style="padding:14px 20px;border-bottom:1px solid var(--border);">
|
|
136
|
+
<!-- Row 1: badges + actions + ring -->
|
|
137
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap;">
|
|
138
|
+
${U.badge(sess.status)}
|
|
139
|
+
<span class="badge badge-closed">${U.esc(sess.mode)}</span>
|
|
140
|
+
${sess.title ? `<span style="font-size:13px;font-weight:600;color:var(--text);">${U.esc(sess.title)}</span>` : ''}
|
|
141
|
+
<code style="font-size:11.5px;color:var(--text3);font-family:'JetBrains Mono',monospace;">${U.esc(sess.id)}</code>
|
|
142
|
+
<div style="margin-left:auto;display:flex;align-items:center;gap:8px;">
|
|
143
|
+
${ctxRingHtml}
|
|
144
|
+
<button class="btn btn-primary btn-sm" id="btn-resume-chat-sess">→ Chat</button>
|
|
145
|
+
${sess.status === 'active' ? `<button class="btn btn-secondary btn-sm" id="btn-reset-sess">↺ Reset</button>` : ''}
|
|
146
|
+
<button class="btn btn-secondary btn-sm" id="btn-close-sess">${sess.status === 'active' ? 'Close' : 'Delete'}</button>
|
|
147
|
+
${sess.status !== 'active' ? `<button class="btn btn-danger btn-sm" id="btn-hard-del-sess">Hard Delete</button>` : ''}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
<!-- Row 2: token stats grid -->
|
|
151
|
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:8px 14px;">
|
|
152
|
+
<div><div class="info-label">Agent</div><div style="font-size:13px;color:var(--text);">${U.esc(sess.agent_name)}</div></div>
|
|
153
|
+
<div><div class="info-label">Model</div><div style="font-size:12.5px;color:var(--text2);word-break:break-all;">${U.esc(sess.model || '—')}</div></div>
|
|
154
|
+
<div><div class="info-label">Messages</div><div style="font-size:13px;color:var(--text);">${sess.message_count || 0}</div></div>
|
|
155
|
+
<div><div class="info-label">Total cost</div><div style="font-size:13px;color:var(--text);">${U.formatCost(sess.cost) || '—'}</div></div>
|
|
156
|
+
<div><div class="info-label">Tokens in</div><div style="font-size:13px;color:var(--text);">${(sess.total_input_tokens || 0).toLocaleString()}</div></div>
|
|
157
|
+
<div><div class="info-label">Tokens out</div><div style="font-size:13px;color:var(--text);">${(sess.total_output_tokens || 0).toLocaleString()}</div></div>
|
|
158
|
+
<div><div class="info-label">Cached</div><div style="font-size:13px;color:var(--text);">${(sess.total_cache_tokens || 0).toLocaleString()}</div></div>
|
|
159
|
+
<div><div class="info-label">Created</div><div style="font-size:12px;color:var(--text2);">${U.relTime(sess.created_at)}</div></div>
|
|
160
|
+
<div><div class="info-label">Updated</div><div style="font-size:12px;color:var(--text2);">${U.relTime(sess.updated_at)}</div></div>
|
|
161
|
+
${sess.model_thinking ? `<div><div class="info-label">Thinking</div><div style="font-size:12px;color:var(--accent);">enabled</div></div>` : ''}
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
<div id="sess-messages" style="padding:16px;display:flex;flex-direction:column;gap:10px;"></div>
|
|
165
|
+
${msgs.length >= this._msgLimit ? `
|
|
166
|
+
<div style="padding:12px 20px;text-align:center;border-top:1px solid var(--border);">
|
|
167
|
+
<button class="btn btn-secondary btn-sm" id="btn-load-more-msgs">Load more messages</button>
|
|
168
|
+
</div>` : ''}`;
|
|
169
|
+
|
|
170
|
+
const msgsEl = document.getElementById('sess-messages');
|
|
171
|
+
if (msgsEl) msgsEl.innerHTML = msgs.map(m => this._renderMessage(m, U)).join('');
|
|
172
|
+
|
|
173
|
+
document.getElementById('btn-resume-chat-sess')?.addEventListener('click', () => {
|
|
174
|
+
window.Veil.navigate('chat', { sessionId: sess.id, agentName: sess.agent_name });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
document.getElementById('btn-reset-sess')?.addEventListener('click', async () => {
|
|
178
|
+
if (!confirm('Reset this session? All messages will be cleared.')) return;
|
|
179
|
+
try {
|
|
180
|
+
await window.Veil.state.api.resetSession(id);
|
|
181
|
+
window.Veil.utils.toast('Session reset', 'success');
|
|
182
|
+
this._loadDetail(id);
|
|
183
|
+
} catch (err) { window.Veil.utils.toast('Reset failed: ' + err.message, 'error'); }
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
document.getElementById('btn-close-sess')?.addEventListener('click', async () => {
|
|
187
|
+
try {
|
|
188
|
+
await window.Veil.state.api.deleteSession(id, false);
|
|
189
|
+
window.Veil.utils.toast('Session closed', 'success');
|
|
190
|
+
this._selected = null;
|
|
191
|
+
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">Session closed</div>`;
|
|
192
|
+
this._loadList();
|
|
193
|
+
} catch (err) { window.Veil.utils.toast('Failed: ' + err.message, 'error'); }
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
document.getElementById('btn-hard-del-sess')?.addEventListener('click', async () => {
|
|
197
|
+
if (!confirm('Hard delete this session and all its messages? This is permanent.')) return;
|
|
198
|
+
try {
|
|
199
|
+
await window.Veil.state.api.deleteSession(id, true);
|
|
200
|
+
window.Veil.utils.toast('Session deleted', 'success');
|
|
201
|
+
this._selected = null;
|
|
202
|
+
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--text3);font-size:13.5px;">Session deleted</div>`;
|
|
203
|
+
this._loadList();
|
|
204
|
+
} catch (err) { window.Veil.utils.toast('Delete failed: ' + err.message, 'error'); }
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
document.getElementById('btn-load-more-msgs')?.addEventListener('click', async () => {
|
|
208
|
+
this._msgOffset += this._msgLimit;
|
|
209
|
+
const more = await window.Veil.state.api.getSessionMessages(id, { limit: this._msgLimit, offset: this._msgOffset });
|
|
210
|
+
const newMsgs = more.messages || [];
|
|
211
|
+
if (msgsEl) msgsEl.innerHTML += newMsgs.map(m => this._renderMessage(m, U)).join('');
|
|
212
|
+
if (newMsgs.length < this._msgLimit) {
|
|
213
|
+
document.getElementById('btn-load-more-msgs')?.remove();
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
if (el) el.innerHTML = `<div style="padding:14px;color:var(--red);font-size:13px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
_renderMessage(msg, U) {
|
|
222
|
+
const role = msg.role;
|
|
223
|
+
let cls = '', label = '', labelColor = 'var(--text2)';
|
|
224
|
+
if (role === 'system') { cls = 'msg-system'; label = 'system'; labelColor = 'var(--text3)'; }
|
|
225
|
+
else if (role === 'user') { cls = 'msg-user'; label = 'you'; labelColor = 'var(--accent)'; }
|
|
226
|
+
else if (role === 'assistant') { cls = 'msg-assistant'; label = 'assistant'; labelColor = 'var(--text2)'; }
|
|
227
|
+
else if (role === 'tool') { cls = 'msg-tool'; label = 'tool'; labelColor = 'var(--yellow)'; }
|
|
228
|
+
|
|
229
|
+
/* Collapsible tool_calls blocks — matching chat style */
|
|
230
|
+
let toolCallsHtml = '';
|
|
231
|
+
if (msg.tool_calls) {
|
|
232
|
+
try {
|
|
233
|
+
const calls = typeof msg.tool_calls === 'string' ? JSON.parse(msg.tool_calls) : msg.tool_calls;
|
|
234
|
+
toolCallsHtml = `<div style="margin-top:6px;">${calls.map(c => {
|
|
235
|
+
const name = c.function?.name || c.name || '?';
|
|
236
|
+
let argsStr = '';
|
|
237
|
+
try {
|
|
238
|
+
const a = c.function?.arguments ? JSON.parse(c.function.arguments) : null;
|
|
239
|
+
argsStr = a ? JSON.stringify(a, null, 2) : (c.function?.arguments || '');
|
|
240
|
+
} catch { argsStr = c.function?.arguments || ''; }
|
|
241
|
+
const cid = 'tc-' + (c.id || Math.random().toString(36).slice(2));
|
|
242
|
+
return `<div class="tool-block" style="padding:6px 10px;margin-top:4px;">
|
|
243
|
+
<div style="display:flex;align-items:center;gap:8px;cursor:pointer;" onclick="(function(){
|
|
244
|
+
var b=document.getElementById('${cid}-body');
|
|
245
|
+
var t=document.getElementById('${cid}-tog');
|
|
246
|
+
var open=b.style.display==='none';
|
|
247
|
+
b.style.display=open?'block':'none';
|
|
248
|
+
t.textContent=open?'▾ hide':'▸ show';
|
|
249
|
+
})()">
|
|
250
|
+
<span style="color:var(--yellow);font-weight:600;font-size:13px;">⚙ ${U.esc(name)}</span>
|
|
251
|
+
<span id="${cid}-tog" style="color:var(--text3);font-size:11.5px;margin-left:auto;">▸ show</span>
|
|
252
|
+
</div>
|
|
253
|
+
<div id="${cid}-body" style="display:none;margin-top:5px;">
|
|
254
|
+
${argsStr ? `<pre style="color:var(--text2);font-size:12px;padding:5px 8px;background:var(--bg);border-radius:5px;overflow-x:auto;">${U.esc(argsStr.slice(0, 800))}</pre>` : ''}
|
|
255
|
+
</div>
|
|
256
|
+
</div>`;
|
|
257
|
+
}).join('')}</div>`;
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Token info including cache + model key */
|
|
262
|
+
const tokenParts = [];
|
|
263
|
+
if (msg.input_tokens) tokenParts.push(msg.input_tokens.toLocaleString() + ' in');
|
|
264
|
+
if (msg.output_tokens) tokenParts.push(msg.output_tokens.toLocaleString() + ' out');
|
|
265
|
+
if (msg.cache_tokens) tokenParts.push(msg.cache_tokens.toLocaleString() + ' cached');
|
|
266
|
+
if (msg.cost) tokenParts.push(U.formatCost(msg.cost));
|
|
267
|
+
if (msg.model_key) tokenParts.push(msg.model_key.split('/').pop());
|
|
268
|
+
const tokenInfo = tokenParts.length
|
|
269
|
+
? `<span style="font-size:11px;color:var(--text3);margin-left:8px;">${U.esc(tokenParts.join(' / '))}</span>`
|
|
270
|
+
: '';
|
|
271
|
+
|
|
272
|
+
/* Collapse long messages — system always, others when > 400 chars */
|
|
273
|
+
const THRESHOLD = 400;
|
|
274
|
+
const content = msg.content || '';
|
|
275
|
+
const shouldCollapse = role === 'system' || content.length > THRESHOLD;
|
|
276
|
+
const msgId = 'msg-' + (msg.id || Math.random().toString(36).slice(2));
|
|
277
|
+
|
|
278
|
+
let contentHtml = '';
|
|
279
|
+
if (content) {
|
|
280
|
+
if (shouldCollapse) {
|
|
281
|
+
const preview = U.esc(content.slice(0, 200)) + (content.length > 200 ? '…' : '');
|
|
282
|
+
contentHtml = `
|
|
283
|
+
<div class="sess-msg-body" id="${msgId}-body" style="font-size:13.5px;white-space:pre-wrap;line-height:1.5;word-break:break-word;display:none;">${U.esc(content)}</div>
|
|
284
|
+
<div class="sess-msg-preview" id="${msgId}-preview" style="font-size:13.5px;white-space:pre-wrap;line-height:1.5;word-break:break-word;color:var(--text2);">${preview}</div>
|
|
285
|
+
<button onclick="(function(){
|
|
286
|
+
var b=document.getElementById('${msgId}-body');
|
|
287
|
+
var p=document.getElementById('${msgId}-preview');
|
|
288
|
+
var t=document.getElementById('${msgId}-tog');
|
|
289
|
+
var open=b.style.display==='none';
|
|
290
|
+
b.style.display=open?'block':'none';
|
|
291
|
+
p.style.display=open?'none':'block';
|
|
292
|
+
t.textContent=open?'▾ Show less':'▸ Show more';
|
|
293
|
+
})()" id="${msgId}-tog"
|
|
294
|
+
style="margin-top:6px;background:none;border:none;color:var(--accent);font-size:12px;cursor:pointer;padding:0;font-family:inherit;">▸ Show more</button>`;
|
|
295
|
+
} else {
|
|
296
|
+
contentHtml = `<div style="font-size:13.5px;white-space:pre-wrap;line-height:1.5;word-break:break-word;">${U.esc(content)}</div>`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const isRight = role === 'user';
|
|
301
|
+
return `
|
|
302
|
+
<div style="${isRight ? 'display:flex;justify-content:flex-end;' : ''}">
|
|
303
|
+
<div class="${cls}" style="max-width:80%;padding:10px 14px;">
|
|
304
|
+
<div style="font-size:12px;font-weight:600;color:${labelColor};margin-bottom:5px;display:flex;align-items:center;gap:6px;">
|
|
305
|
+
${U.esc(label)}${tokenInfo}
|
|
306
|
+
<span style="font-size:12px;color:var(--border2);margin-left:auto;">${U.relTime(msg.created_at)}</span>
|
|
307
|
+
</div>
|
|
308
|
+
${contentHtml}
|
|
309
|
+
${toolCallsHtml}
|
|
310
|
+
${role === 'tool' && msg.tool_call_id ? `<div style="font-size:11px;color:var(--text3);margin-top:4px;">call_id: ${U.esc(msg.tool_call_id)}</div>` : ''}
|
|
311
|
+
</div>
|
|
312
|
+
</div>`;
|
|
313
|
+
},
|
|
314
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
window.Veil = window.Veil || {};
|
|
3
|
+
window.Veil.views = window.Veil.views || {};
|
|
4
|
+
|
|
5
|
+
window.Veil.views.settings = {
|
|
6
|
+
_level: 'merged',
|
|
7
|
+
_settings: null,
|
|
8
|
+
_editing: false,
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
return `
|
|
12
|
+
<div style="padding:20px;">
|
|
13
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:10px;">
|
|
14
|
+
<h1 style="font-size:16px;font-weight:700;color:var(--text);">Settings</h1>
|
|
15
|
+
<div style="display:flex;gap:6px;flex-wrap:wrap;">
|
|
16
|
+
${['merged','project','global','local'].map(l => `
|
|
17
|
+
<button class="btn btn-${this._level===l?'primary':'secondary'} btn-sm" data-settings-level="${l}">${l}</button>
|
|
18
|
+
`).join('')}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div id="settings-content">
|
|
22
|
+
<div style="text-align:center;padding:30px;color:var(--text3);">${window.Veil.utils.spinner()}</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>`;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
async mount() {
|
|
28
|
+
document.querySelectorAll('[data-settings-level]').forEach(btn => {
|
|
29
|
+
btn.addEventListener('click', async () => {
|
|
30
|
+
this._level = btn.getAttribute('data-settings-level');
|
|
31
|
+
this._editing = false;
|
|
32
|
+
document.querySelectorAll('[data-settings-level]').forEach(b => {
|
|
33
|
+
b.className = `btn btn-${b.getAttribute('data-settings-level') === this._level ? 'primary' : 'secondary'} btn-sm`;
|
|
34
|
+
});
|
|
35
|
+
await this._load();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
await this._load();
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
unmount() { this._editing = false; },
|
|
42
|
+
|
|
43
|
+
async _load() {
|
|
44
|
+
const el = document.getElementById('settings-content');
|
|
45
|
+
if (!el) return;
|
|
46
|
+
el.innerHTML = `<div style="text-align:center;padding:30px;color:var(--text3);">${window.Veil.utils.spinner()}</div>`;
|
|
47
|
+
try {
|
|
48
|
+
const data = await window.Veil.state.api.getSettings(this._level);
|
|
49
|
+
this._settings = data.settings;
|
|
50
|
+
this._render(data);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (el) el.innerHTML = `<div style="color:var(--red);font-size:13px;padding:12px;">${window.Veil.utils.esc(err.message)}</div>`;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
_render(data) {
|
|
57
|
+
const el = document.getElementById('settings-content');
|
|
58
|
+
if (!el) return;
|
|
59
|
+
const U = window.Veil.utils;
|
|
60
|
+
const isEditable = this._level !== 'merged';
|
|
61
|
+
const settingsJson = JSON.stringify(this._redact(this._settings), null, 2);
|
|
62
|
+
|
|
63
|
+
el.innerHTML = `
|
|
64
|
+
<div class="card" style="margin-bottom:14px;">
|
|
65
|
+
<div class="panel-header" style="display:flex;align-items:center;justify-content:space-between;">
|
|
66
|
+
<span>
|
|
67
|
+
${U.esc(this._level)} settings
|
|
68
|
+
${data.path ? `<code style="font-size:11px;color:var(--text3);margin-left:8px;">${U.esc(data.path)}</code>` : ''}
|
|
69
|
+
</span>
|
|
70
|
+
${isEditable && !this._editing
|
|
71
|
+
? `<button class="btn btn-secondary btn-sm" id="btn-edit-settings">Edit</button>`
|
|
72
|
+
: ''}
|
|
73
|
+
${this._editing
|
|
74
|
+
? `<div style="display:flex;gap:8px;">
|
|
75
|
+
<button class="btn btn-success btn-sm" id="btn-save-settings">Save</button>
|
|
76
|
+
<button class="btn btn-secondary btn-sm" id="btn-cancel-settings">Cancel</button>
|
|
77
|
+
</div>`
|
|
78
|
+
: ''}
|
|
79
|
+
</div>
|
|
80
|
+
<div style="padding:4px;">
|
|
81
|
+
${this._editing
|
|
82
|
+
? `<textarea id="settings-editor" class="input mono" rows="28" style="border:none;border-radius:0;width:100%;resize:vertical;background:var(--bg);padding:14px 16px;line-height:1.6;">${U.esc(settingsJson)}</textarea>`
|
|
83
|
+
: `<pre style="padding:14px 16px;font-family:monospace;font-size:13px;line-height:1.6;color:var(--text);overflow-x:auto;">${U.esc(settingsJson)}</pre>`}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div id="settings-err" style="display:none;background:var(--red-dim);color:var(--red);border:1px solid var(--border);border-radius:6px;padding:10px 14px;font-size:13px;margin-bottom:12px;"></div>
|
|
87
|
+
${this._level === 'merged'
|
|
88
|
+
? `<div style="font-size:12.5px;color:var(--text3);padding:4px;">Read-only merged view. Switch to project/global/local to edit.</div>`
|
|
89
|
+
: ''}
|
|
90
|
+
${!isEditable
|
|
91
|
+
? ''
|
|
92
|
+
: `<div style="font-size:12px;color:var(--text3);padding:4px;">API keys shown redacted. Edit to see/change the actual file at <code>${U.esc(data.path || '?')}</code></div>`}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
document.getElementById('btn-edit-settings')?.addEventListener('click', () => {
|
|
96
|
+
this._editing = true;
|
|
97
|
+
this._render(data);
|
|
98
|
+
});
|
|
99
|
+
document.getElementById('btn-cancel-settings')?.addEventListener('click', () => {
|
|
100
|
+
this._editing = false;
|
|
101
|
+
this._render(data);
|
|
102
|
+
});
|
|
103
|
+
document.getElementById('btn-save-settings')?.addEventListener('click', async () => {
|
|
104
|
+
const textarea = document.getElementById('settings-editor');
|
|
105
|
+
const errEl = document.getElementById('settings-err');
|
|
106
|
+
const btn = document.getElementById('btn-save-settings');
|
|
107
|
+
if (!textarea) return;
|
|
108
|
+
let parsed;
|
|
109
|
+
try { parsed = JSON.parse(textarea.value); }
|
|
110
|
+
catch { errEl.textContent = 'Invalid JSON'; errEl.style.display = 'block'; return; }
|
|
111
|
+
btn.disabled = true; btn.innerHTML = window.Veil.utils.spinner() + ' Saving…';
|
|
112
|
+
errEl.style.display = 'none';
|
|
113
|
+
try {
|
|
114
|
+
const res = await window.Veil.state.api.putSettings(parsed, this._level);
|
|
115
|
+
this._settings = res.settings;
|
|
116
|
+
this._editing = false;
|
|
117
|
+
window.Veil.utils.toast('Settings saved', 'success');
|
|
118
|
+
await this._load();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
btn.disabled = false; btn.textContent = 'Save';
|
|
121
|
+
errEl.textContent = err.message; errEl.style.display = 'block';
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
_redact(obj, path) {
|
|
127
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
128
|
+
path = path || '';
|
|
129
|
+
const out = Array.isArray(obj) ? [] : {};
|
|
130
|
+
for (const key of Object.keys(obj)) {
|
|
131
|
+
const childPath = path ? path + '.' + key : key;
|
|
132
|
+
if (typeof obj[key] === 'string' && childPath.toLowerCase().includes('api_key') && obj[key].length > 4) {
|
|
133
|
+
out[key] = obj[key].slice(0, 3) + '…' + obj[key].slice(-4);
|
|
134
|
+
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
135
|
+
out[key] = this._redact(obj[key], childPath);
|
|
136
|
+
} else {
|
|
137
|
+
out[key] = obj[key];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
},
|
|
142
|
+
};
|