Skip to content

Commit f5c9eab

Browse files
committed
fix: replace onclick with data-action event delegation (fixes CSP block), bump welcome key v2 (v0.2.5)
1 parent fa6e235 commit f5c9eab

5 files changed

Lines changed: 141 additions & 63 deletions

File tree

extension/media/panel.js

Lines changed: 131 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,38 @@
1010
configured: true,
1111
};
1212

13+
// ── Event delegation (fixes onclick CSP block) ───────────────────────────
14+
document.addEventListener('click', function (e) {
15+
const el = e.target.closest('[data-action]');
16+
if (!el) return;
17+
const action = el.dataset.action;
18+
switch (action) {
19+
case 'switchTab': switchTab(el.dataset.tab); break;
20+
case 'sendChat': sendChat(); break;
21+
case 'sendAsRun': sendAsRun(); break;
22+
case 'startRun': startRun(); break;
23+
case 'selectRun': selectRun(el.dataset.id); break;
24+
case 'cancelRun': cancelRun(el.dataset.id); break;
25+
case 'applyFiles': applyFiles(el.dataset.id); break;
26+
case 'openBrowser': openInBrowser(el.dataset.id); break;
27+
case 'configure': configure(); break;
28+
case 'copyCode': copyCode(parseInt(el.dataset.idx)); break;
29+
case 'insertCode': insertCode(parseInt(el.dataset.idx)); break;
30+
}
31+
});
32+
33+
// ── Keyboard events ───────────────────────────────────────────────────────
34+
document.addEventListener('keydown', function (e) {
35+
const target = e.target;
36+
if (target.id === 'chatInput') {
37+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); }
38+
}
39+
if (target.id === 'objective') {
40+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); startRun(); }
41+
}
42+
});
43+
44+
// ── Messages from extension host ─────────────────────────────────────────
1345
window.addEventListener('message', ({ data }) => {
1446
switch (data.type) {
1547
case 'init':
@@ -39,7 +71,10 @@
3971
break;
4072
case 'triggerNewRun':
4173
switchTab('runs');
42-
setTimeout(() => { const el = document.getElementById('objective'); if (el) { if (data.selection) el.value = data.selection; el.focus(); } }, 100);
74+
setTimeout(function () {
75+
const el = document.getElementById('objective');
76+
if (el) { if (data.selection) el.value = data.selection; el.focus(); }
77+
}, 100);
4378
break;
4479
case 'runStarting':
4580
state.streaming = [{ cls: 'dim', text: '\u27F3 Starting run...' }];
@@ -49,7 +84,7 @@
4984
case 'runStarted':
5085
state.activeRunId = data.runId;
5186
state.selectedRunId = data.runId;
52-
state.streaming = [{ cls: 'ok', text: '\u2713 Run started: ' + data.runId.slice(0,8) + '...' }];
87+
state.streaming = [{ cls: 'ok', text: '\u2713 Run started: ' + data.runId.slice(0, 8) + '...' }];
5388
renderRuns();
5489
break;
5590
case 'runError':
@@ -75,39 +110,39 @@
75110
}
76111
});
77112

113+
// ── Tab switching ─────────────────────────────────────────────────────────
78114
function switchTab(tab) {
79115
state.tab = tab;
80116
document.getElementById('chat-panel').classList.toggle('hidden', tab !== 'chat');
81117
document.getElementById('runs-panel').classList.toggle('hidden', tab !== 'runs');
82118
document.getElementById('tab-chat').classList.toggle('active', tab === 'chat');
83119
document.getElementById('tab-runs').classList.toggle('active', tab === 'runs');
84120
}
85-
window.switchTab = switchTab;
86121

122+
// ── Chat ─────────────────────────────────────────────────────────────────
87123
function sendChat() {
88124
const input = document.getElementById('chatInput');
125+
if (!input) return;
89126
const msg = input.value.trim();
90127
if (!msg || state.thinking) return;
91128
state.messages.push({ role: 'user', text: msg });
92129
renderMessages();
93130
input.value = '';
94131
vscode.postMessage({ type: 'chat', message: msg, context: state.chatContext || '' });
95-
state.chatContext = null;
96132
}
97-
window.sendChat = sendChat;
98133

99134
function sendAsRun() {
100135
const input = document.getElementById('chatInput');
136+
if (!input) return;
101137
const msg = input.value.trim();
102138
if (!msg) return;
103139
input.value = '';
104140
switchTab('runs');
105-
setTimeout(() => {
141+
setTimeout(function () {
106142
const obj = document.getElementById('objective');
107143
if (obj) { obj.value = msg; startRun(); }
108144
}, 100);
109145
}
110-
window.sendAsRun = sendAsRun;
111146

112147
function renderMessages() {
113148
const el = document.getElementById('messages');
@@ -116,14 +151,18 @@
116151
el.innerHTML = '<div class="empty">Ask anything about your code...<br><span style="font-size:11px;opacity:0.6">Enter to send \u2022 Shift+Enter for newline<br>\u26A1 "Run as Agent" to start a multi-agent task</span></div>';
117152
return;
118153
}
119-
let html = state.messages.map((m, i) => {
154+
let html = state.messages.map(function (m, i) {
120155
if (m.role === 'user') {
121156
return '<div class="msg user"><div class="bubble user-bubble">' + escHtml(m.text) + '</div></div>';
122157
}
123158
let h = '<div class="msg assistant"><div class="bubble asst-bubble">' + escHtml(m.text) + '</div>';
124159
if (m.hasCode && m.code) {
125-
h += '<div class="code-block"><div class="code-header"><span class="code-lang">' + escHtml(m.lang || 'code') + '</span>';
126-
h += '<div class="code-actions"><button class="cbtn" onclick="copyCode(' + i + ')">Copy</button><button class="cbtn" onclick="insertCode(' + i + ')">Insert</button></div></div>';
160+
h += '<div class="code-block">';
161+
h += '<div class="code-header"><span class="code-lang">' + escHtml(m.lang || 'code') + '</span>';
162+
h += '<div class="code-actions">';
163+
h += '<button class="cbtn" data-action="copyCode" data-idx="' + i + '">Copy</button>';
164+
h += '<button class="cbtn" data-action="insertCode" data-idx="' + i + '">Insert</button>';
165+
h += '</div></div>';
127166
h += '<code>' + escHtml(m.code) + '</code></div>';
128167
}
129168
h += '</div>';
@@ -140,85 +179,111 @@
140179
const m = state.messages[i];
141180
if (m && m.code) vscode.postMessage({ type: 'copyToClipboard', text: m.code });
142181
}
143-
window.copyCode = copyCode;
144182

145183
function insertCode(i) {
146184
const m = state.messages[i];
147185
if (m && m.code) vscode.postMessage({ type: 'insertCode', code: m.code });
148186
}
149-
window.insertCode = insertCode;
150-
151-
document.getElementById('chatInput').addEventListener('keydown', function(e) {
152-
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendChat(); }
153-
});
154187

188+
// ── Runs ──────────────────────────────────────────────────────────────────
155189
function renderRuns() {
156190
const el = document.getElementById('runs-content');
157191
if (!el) return;
158192
updatePlanBadge();
159193
if (!state.configured) {
160-
el.innerHTML = '<div class="setup"><p>Connect your CesaFlow account to start running AI agents.</p><button class="run-btn" onclick="configure()">\u2699 Configure API Key</button></div>';
194+
el.innerHTML = '<div class="setup"><p>Connect your CesaFlow account to start running AI agents.</p><button class="run-btn" data-action="configure">\u2699 Configure API Key</button></div>';
161195
return;
162196
}
163197
let html = '<div class="section-title">New Run</div><div class="run-form">';
164-
html += '<textarea id="objective" class="run-input" placeholder="Describe your coding task..." rows="3" onkeydown="handleRunKeydown(event)"></textarea>';
165-
html += '<button class="run-btn" onclick="startRun()">\u25B6 Start Run</button></div>';
198+
html += '<textarea id="objective" class="run-input" placeholder="Describe your coding task..." rows="3"></textarea>';
199+
html += '<button class="run-btn" data-action="startRun">\u25B6 Start Run</button></div>';
166200
if (state.streaming.length > 0) {
167-
html += '<hr class="divider"><div class="section-title">Output</div><div class="stream-box" id="streamBox">';
168-
state.streaming.slice(-80).forEach(function(l) { html += '<div class="stream-line ' + l.cls + '">' + escHtml(l.text) + '</div>'; });
201+
html += '<hr class="divider"><div class="section-title">Output</div>';
202+
html += '<div class="stream-box" id="streamBox">';
203+
state.streaming.slice(-80).forEach(function (l) {
204+
html += '<div class="stream-line ' + l.cls + '">' + escHtml(l.text) + '</div>';
205+
});
169206
html += '</div>';
170207
}
171-
if (state.selectedRunId && state.runDetail) { html += '<hr class="divider">' + renderDetailHtml(); }
208+
if (state.selectedRunId && state.runDetail) {
209+
html += '<hr class="divider">' + renderDetailHtml();
210+
}
172211
html += '<hr class="divider"><div class="section-title">Recent Runs</div>';
173-
if (state.runs.length === 0) { html += '<div class="empty">No runs yet</div>'; }
174-
else {
175-
state.runs.forEach(function(run) {
212+
if (state.runs.length === 0) {
213+
html += '<div class="empty">No runs yet</div>';
214+
} else {
215+
state.runs.forEach(function (run) {
176216
const obj = (run.task_objective || run.objective || 'Untitled').slice(0, 52);
177217
const status = run.status || 'pending';
178218
const sel = run.run_id === state.selectedRunId;
179-
html += '<div class="run-item' + (sel ? ' active' : '') + '" onclick="selectRun(\'' + run.run_id + '\')">';
180-
html += '<div class="run-dot ' + status + '"></div><span class="run-obj">' + escHtml(obj) + '</span><span class="run-status">' + status + '</span></div>';
219+
html += '<div class="run-item' + (sel ? ' active' : '') + '" data-action="selectRun" data-id="' + escAttr(run.run_id) + '">';
220+
html += '<div class="run-dot ' + status + '"></div>';
221+
html += '<span class="run-obj">' + escHtml(obj) + '</span>';
222+
html += '<span class="run-status">' + status + '</span>';
223+
html += '</div>';
181224
});
182225
}
183226
el.innerHTML = html;
184227
scrollStream();
185228
}
186229

187230
function renderDetailHtml() {
188-
const run = state.runDetail; if (!run) return '';
231+
const run = state.runDetail;
232+
if (!run) return '';
189233
const files = (state.runWorkspace || {}).files || [];
190234
const isRunning = run.status === 'running' || run.status === 'pending';
191235
let h = '<div class="section-title">Run Detail</div><div class="detail">';
192236
h += '<div style="font-size:13px;font-weight:600;margin-bottom:8px">' + escHtml((run.task_objective || '').slice(0, 60)) + '</div>';
193-
(run.nodes || []).forEach(function(n) {
237+
(run.nodes || []).forEach(function (n) {
194238
const c = n.status === 'completed' ? '#4ec994' : n.status === 'failed' ? '#f48771' : n.status === 'running' ? '#7c9ef8' : '#888';
195-
h += '<div class="agent-row"><div class="agent-dot" style="background:' + c + '"></div><span>' + escHtml(n.agent_name) + '</span><span style="margin-left:auto;font-size:10px;opacity:0.6">' + n.status + '</span></div>';
239+
h += '<div class="agent-row"><div class="agent-dot" style="background:' + c + '"></div>';
240+
h += '<span>' + escHtml(n.agent_name) + '</span>';
241+
h += '<span style="margin-left:auto;font-size:10px;opacity:0.6">' + n.status + '</span></div>';
196242
});
197243
if (files.length > 0) {
198244
h += '<div style="margin-top:6px">';
199-
files.slice(0, 8).forEach(function(f) { h += '<span class="file-chip">' + escHtml(f) + '</span>'; });
245+
files.slice(0, 8).forEach(function (f) { h += '<span class="file-chip">' + escHtml(f) + '</span>'; });
200246
if (files.length > 8) h += '<span class="file-chip">+' + (files.length - 8) + ' more</span>';
201247
h += '</div>';
202248
}
203249
h += '<div class="detail-actions">';
204-
if (isRunning) { h += '<button class="btn-sm danger" onclick="cancelRun(\'' + run.run_id + '\')">\u25A0 Cancel</button>'; }
205-
else {
206-
if (files.length > 0) h += '<button class="btn-sm primary" onclick="applyFiles(\'' + run.run_id + '\')">\u2B07 Apply Files</button>';
207-
h += '<button class="btn-sm" onclick="openInBrowser(\'' + run.run_id + '\')">\u2197 Open</button>';
250+
if (isRunning) {
251+
h += '<button class="btn-sm danger" data-action="cancelRun" data-id="' + escAttr(run.run_id) + '">\u25A0 Cancel</button>';
252+
} else {
253+
if (files.length > 0) {
254+
h += '<button class="btn-sm primary" data-action="applyFiles" data-id="' + escAttr(run.run_id) + '">\u2B07 Apply Files</button>';
255+
}
256+
h += '<button class="btn-sm" data-action="openBrowser" data-id="' + escAttr(run.run_id) + '">\u2197 Open</button>';
208257
}
209258
h += '</div></div>';
210259
return h;
211260
}
212261

262+
function renderStreaming() {
263+
const b = document.getElementById('streamBox');
264+
if (!b) { renderRuns(); return; }
265+
b.innerHTML = state.streaming.slice(-80).map(function (l) {
266+
return '<div class="stream-line ' + l.cls + '">' + escHtml(l.text) + '</div>';
267+
}).join('');
268+
scrollStream();
269+
}
270+
213271
function handleWsEvent(ev) {
214272
const t = ev.type;
215273
if (t === 'token_chunk') {
216274
const last = state.streaming[state.streaming.length - 1];
217275
if (last && last.streaming) { last.text += ev.chunk || ''; }
218-
else { state.streaming.push({ cls: 'agent', text: '[' + (ev.agent || '?') + '] ', streaming: true }); state.streaming[state.streaming.length-1].text += ev.chunk || ''; }
219-
renderStreaming(); return;
276+
else {
277+
state.streaming.push({ cls: 'agent', text: '[' + (ev.agent || '?') + '] ', streaming: true });
278+
state.streaming[state.streaming.length - 1].text += ev.chunk || '';
279+
}
280+
renderStreaming();
281+
return;
282+
}
283+
if (t === 'node_completed') {
284+
const l = state.streaming[state.streaming.length - 1];
285+
if (l && l.streaming) { l.streaming = false; state.streaming.push({ cls: 'dim', text: '' }); }
220286
}
221-
if (t === 'node_completed') { const l = state.streaming[state.streaming.length-1]; if (l && l.streaming) { l.streaming = false; state.streaming.push({ cls: 'dim', text: '' }); } }
222287
if (t === 'file_written') state.streaming.push({ cls: 'file', text: '\uD83D\uDCC4 ' + ev.path });
223288
else if (t === 'node_started') state.streaming.push({ cls: 'agent', text: '\u25B6 ' + (ev.agent || '') + ' agent starting...' });
224289
else if (t === 'run_completed') { state.streaming.push({ cls: 'ok', text: '\uD83C\uDF89 Run completed!' }); state.activeRunId = null; }
@@ -227,25 +292,38 @@
227292
renderStreaming();
228293
}
229294

230-
function renderStreaming() { const b = document.getElementById('streamBox'); if (!b) { renderRuns(); return; } b.innerHTML = state.streaming.slice(-80).map(function(l) { return '<div class="stream-line ' + l.cls + '">' + escHtml(l.text) + '</div>'; }).join(''); scrollStream(); }
231-
function updatePlanBadge() { const b = document.getElementById('planBadge'); if (!b) return; b.textContent = (state.me && state.me.organization) ? state.me.organization.plan : 'free'; }
232-
function startRun() { const ta = document.getElementById('objective'); if (!ta) return; const obj = ta.value.trim(); if (!obj) return; state.streaming = []; ta.value = ''; vscode.postMessage({ type: 'startRun', objective: obj }); }
233-
function handleRunKeydown(e) { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); startRun(); } }
234-
function selectRun(id) { state.selectedRunId = id; state.runDetail = null; state.runWorkspace = null; renderRuns(); vscode.postMessage({ type: 'selectRun', runId: id }); }
295+
// ── Actions ───────────────────────────────────────────────────────────────
296+
function updatePlanBadge() {
297+
const b = document.getElementById('planBadge');
298+
if (!b) return;
299+
b.textContent = (state.me && state.me.organization) ? state.me.organization.plan : 'free';
300+
}
301+
function startRun() {
302+
const ta = document.getElementById('objective');
303+
if (!ta) return;
304+
const obj = ta.value.trim();
305+
if (!obj) return;
306+
state.streaming = [];
307+
ta.value = '';
308+
vscode.postMessage({ type: 'startRun', objective: obj });
309+
}
310+
function selectRun(id) {
311+
state.selectedRunId = id;
312+
state.runDetail = null;
313+
state.runWorkspace = null;
314+
renderRuns();
315+
vscode.postMessage({ type: 'selectRun', runId: id });
316+
}
235317
function cancelRun(id) { vscode.postMessage({ type: 'cancelRun', runId: id }); }
236318
function applyFiles(id) { vscode.postMessage({ type: 'applyFiles', runId: id }); }
237-
function openInBrowser(id) { const base = state.serverUrl ? state.serverUrl.replace(':8001', ':3000') : 'http://localhost:3000'; vscode.postMessage({ type: 'openBrowser', url: base + '/dashboard/runs/' + id }); }
319+
function openInBrowser(id) {
320+
const base = state.serverUrl ? state.serverUrl.replace(':8001', ':3000') : 'http://localhost:3000';
321+
vscode.postMessage({ type: 'openBrowser', url: base + '/dashboard/runs/' + id });
322+
}
238323
function configure() { vscode.postMessage({ type: 'configure' }); }
239324
function scrollStream() { const b = document.getElementById('streamBox'); if (b) b.scrollTop = b.scrollHeight; }
240-
function escHtml(s) { return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
241-
242-
window.startRun = startRun;
243-
window.handleRunKeydown = handleRunKeydown;
244-
window.selectRun = selectRun;
245-
window.cancelRun = cancelRun;
246-
window.applyFiles = applyFiles;
247-
window.openInBrowser = openInBrowser;
248-
window.configure = configure;
325+
function escHtml(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
326+
function escAttr(s) { return String(s || '').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
249327

250328
vscode.postMessage({ type: 'ready' });
251329
}());

extension/out/panel.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extension/out/welcome.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)