synabun 2026.4.21 → 2026.4.25-beta.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/CHANGELOG.md +161 -881
- package/README.md +1 -1
- package/hooks/claude-code/prompt-submit.mjs +39 -18
- package/hooks/claude-code/session-start.mjs +10 -3
- package/hooks/claude-code/shared.mjs +39 -2
- package/hooks/claude-code/stop.mjs +33 -6
- package/mcp-server/src/index.ts +9 -1
- package/mcp-server/src/services/neural-interface.ts +42 -14
- package/mcp-server/src/services/profiles.ts +8 -7
- package/mcp-server/src/tools/browser-advanced.ts +12 -10
- package/mcp-server/src/tools/browser-interact.ts +39 -38
- package/mcp-server/src/tools/browser-navigate.ts +12 -4
- package/mcp-server/src/tools/loop.ts +7 -3
- package/mcp-server/src/tools/profile.ts +19 -1
- package/neural-interface/lib/loop-logger.js +103 -0
- package/neural-interface/lib/mcp-installer.js +109 -0
- package/neural-interface/lib/plugin-installer.js +264 -0
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/shared/api.js +66 -17
- package/neural-interface/public/shared/cdx/cdx-icons.js +5 -4
- package/neural-interface/public/shared/cdx/cdx-panel.js +44 -14
- package/neural-interface/public/shared/cdx/cdx-render.js +170 -43
- package/neural-interface/public/shared/cdx/cdx-styles.js +23 -10
- package/neural-interface/public/shared/cdx/cdx-tabs.js +384 -70
- package/neural-interface/public/shared/constants.js +3 -2
- package/neural-interface/public/shared/html-shell.js +32 -11
- package/neural-interface/public/shared/ocp/ocp-panel.js +45 -28
- package/neural-interface/public/shared/ocp/ocp-render.js +50 -43
- package/neural-interface/public/shared/ocp/ocp-tabs.js +693 -82
- package/neural-interface/public/shared/styles.css +635 -0
- package/neural-interface/public/shared/ui-automation-studio.js +38 -12
- package/neural-interface/public/shared/ui-claude-panel.js +216 -21
- package/neural-interface/public/shared/ui-file-explorer.js +38 -18
- package/neural-interface/public/shared/ui-menubar.js +34 -10
- package/neural-interface/public/shared/ui-navbar.js +8 -6
- package/neural-interface/public/shared/ui-schedules-studio.js +1464 -0
- package/neural-interface/public/shared/ui-settings.js +274 -50
- package/neural-interface/public/shared/ui-sidepanel-layout.js +18 -7
- package/neural-interface/public/shared/ui-sidepanel-tray.js +287 -0
- package/neural-interface/public/shared/ui-sync.js +9 -5
- package/neural-interface/public/shared/ui-terminal.js +76 -27
- package/neural-interface/public/shared/ui-tool-updates.js +49 -15
- package/neural-interface/public/shared/ui-tutorial-steps.js +4 -2
- package/neural-interface/public/shared/ui-tutorial.js +24 -18
- package/neural-interface/public/shared/ui-whiteboard.js +79 -31
- package/neural-interface/public/shared/vterm-buffer.js +9 -8
- package/neural-interface/public/variant/2d/main.js +9 -7
- package/neural-interface/public/variant/3d/main.js +10 -8
- package/neural-interface/server.js +1281 -252
- package/package.json +1 -1
- package/skills/leonardo/SKILL.md +5 -1
- package/skills/leonardo/modules/image-prompter.md +5 -1
- package/skills/leonardo/modules/video-prompter.md +5 -1
- package/skills/synabun/SKILL.md +44 -26
- package/skills/synabun/modules/audit.md +40 -36
- package/skills/synabun/modules/changelog.md +22 -18
- package/skills/synabun/modules/memorize.md +19 -15
- package/skills/synabun/modules/schedule.md +285 -0
- package/CLAUDE.md +0 -217
|
@@ -67,8 +67,9 @@ export const KEYS = {
|
|
|
67
67
|
ACTIVE_WORKSPACE: 'neural-active-workspace', // sessionStorage
|
|
68
68
|
GRID_SNAP: 'neural-grid-snap',
|
|
69
69
|
CATEGORIES_VISIBLE: 'neural-categories-visible',
|
|
70
|
-
WHITEBOARD: 'neural-whiteboard',
|
|
71
|
-
|
|
70
|
+
WHITEBOARD: 'neural-whiteboard',
|
|
71
|
+
WHITEBOARD_RULESET_ALERT_DISMISSED: 'neural-whiteboard-ruleset-alert-dismissed',
|
|
72
|
+
TUTORIAL_COMPLETED: 'neural-tutorial-completed',
|
|
72
73
|
TUTORIAL_SKIPPED: 'neural-tutorial-skipped',
|
|
73
74
|
TUTORIAL_STEP: 'neural-tutorial-step',
|
|
74
75
|
TUTORIAL_STARTED: 'neural-tutorial-started',
|
|
@@ -294,11 +294,23 @@ export function getSharedHTML() {
|
|
|
294
294
|
<span class="menu-shortcut">A</span>
|
|
295
295
|
</div>
|
|
296
296
|
<div class="menu-sep"></div>
|
|
297
|
-
<div class="menu-item" id="menu-automations-schedules">
|
|
298
|
-
<span class="menu-check"></span>
|
|
299
|
-
<span class="menu-text"><span class="menu-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></span> Schedules</span>
|
|
300
|
-
</div>
|
|
301
|
-
<div class="menu-
|
|
297
|
+
<div class="menu-item" id="menu-automations-schedules">
|
|
298
|
+
<span class="menu-check"></span>
|
|
299
|
+
<span class="menu-text"><span class="menu-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg></span> Schedules</span>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="menu-item" id="menu-automations-schedule-new">
|
|
302
|
+
<span class="menu-check"></span>
|
|
303
|
+
<span class="menu-text">New Cron Schedule</span>
|
|
304
|
+
</div>
|
|
305
|
+
<div class="menu-item" id="menu-automations-quick-timer">
|
|
306
|
+
<span class="menu-check"></span>
|
|
307
|
+
<span class="menu-text">Quick Timer</span>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="menu-item" id="menu-automations-schedule-groups">
|
|
310
|
+
<span class="menu-check"></span>
|
|
311
|
+
<span class="menu-text">Schedule Groups</span>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="menu-sep"></div>
|
|
302
314
|
<div class="menu-item" id="menu-automations-new">
|
|
303
315
|
<span class="menu-check"></span>
|
|
304
316
|
<span class="menu-text">${t('menu.automations.newAutomation')}</span>
|
|
@@ -703,12 +715,21 @@ export function getSharedHTML() {
|
|
|
703
715
|
</div>
|
|
704
716
|
|
|
705
717
|
<!-- Static background (shown in Focus Mode) -->
|
|
706
|
-
<div id="static-bg">
|
|
707
|
-
<div class="focus-breathe"></div>
|
|
708
|
-
<img src="synabun.png?v=2" alt="SynaBun" class="static-bg-logo">
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
718
|
+
<div id="static-bg">
|
|
719
|
+
<div class="focus-breathe"></div>
|
|
720
|
+
<img src="synabun.png?v=2" alt="SynaBun" class="static-bg-logo">
|
|
721
|
+
<div id="wb-ruleset-alert" class="wb-ruleset-alert" aria-live="polite">
|
|
722
|
+
<div class="wb-ruleset-alert-text">
|
|
723
|
+
Add <code>AGENTS.md</code> and <code>CLAUDE.md</code> rulesets so memory usage stays consistent.
|
|
724
|
+
</div>
|
|
725
|
+
<div class="wb-ruleset-alert-actions">
|
|
726
|
+
<button type="button" id="wb-ruleset-settings" class="wb-ruleset-alert-btn">Setup</button>
|
|
727
|
+
<button type="button" id="wb-ruleset-dismiss" class="wb-ruleset-alert-btn wb-ruleset-alert-dismiss">Don't Show This Again</button>
|
|
728
|
+
</div>
|
|
729
|
+
</div>
|
|
730
|
+
|
|
731
|
+
<!-- Whiteboard (Focus Mode canvas) -->
|
|
732
|
+
<div id="wb-root">
|
|
712
733
|
<div id="wb-toolbar" class="glass">
|
|
713
734
|
<button class="wb-tool active" data-tool="select" data-tooltip="Select">
|
|
714
735
|
<svg viewBox="0 0 24 24"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/></svg>
|
|
@@ -21,11 +21,18 @@ import {
|
|
|
21
21
|
setPanelEl, setOnUpdate, setPanelVisible, setOnShow, setOnHide, setOnEditPlan,
|
|
22
22
|
createTab, switchTab, closeTab, renderPills, saveTabs, restoreTabs,
|
|
23
23
|
loadSessions, createSession, renameSession, loadProviders, loadAgents, populateModelDropdown,
|
|
24
|
-
sendMessage, abortMessage,
|
|
24
|
+
sendMessage, abortMessage, handleSSEEvent,
|
|
25
25
|
revertSession, compactSession, shareSession, executeCommand, renderTabMessages,
|
|
26
26
|
getActiveTabMode, setTabMode, resolveContextInputTokens, setActivityVisible,
|
|
27
|
-
toggleActivityExpanded, abortAgent,
|
|
27
|
+
toggleActivityExpanded, abortAgent, ensurePlanFile, showPostPlanUI, applySavedPlan,
|
|
28
|
+
flushAllSessionSnapshots,
|
|
28
29
|
} from './ocp-tabs.js';
|
|
30
|
+
|
|
31
|
+
window.addEventListener('beforeunload', () => { try { flushAllSessionSnapshots(); } catch {} });
|
|
32
|
+
window.addEventListener('pagehide', () => { try { flushAllSessionSnapshots(); } catch {} });
|
|
33
|
+
document.addEventListener('visibilitychange', () => {
|
|
34
|
+
if (document.hidden) { try { flushAllSessionSnapshots(); } catch {} }
|
|
35
|
+
});
|
|
29
36
|
import { isClaudePanelOpen, toggleClaudePanel } from '../ui-claude-panel.js';
|
|
30
37
|
import { isCodexPanelOpen, toggleCodexPanel } from '../ui-codex-panel.js';
|
|
31
38
|
|
|
@@ -60,6 +67,10 @@ let _projectsLoaded = false;
|
|
|
60
67
|
|
|
61
68
|
function panelEl(sel) { return _panel?.querySelector(sel) || null; }
|
|
62
69
|
|
|
70
|
+
function activeTabRunning() {
|
|
71
|
+
return !!activeTab()?.running;
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
function sessionTitleFor(sessionOrTab) {
|
|
64
75
|
const sid = sessionOrTab?.id || sessionOrTab?.sessionID || sessionOrTab?.sessionId || '';
|
|
65
76
|
const candidates = [sessionOrTab?.sessionTitle, sessionOrTab?.title, sessionOrTab?.description, sessionOrTab?.slug];
|
|
@@ -496,10 +507,10 @@ function wireEvents() {
|
|
|
496
507
|
}
|
|
497
508
|
if (e.key === 'Escape') {
|
|
498
509
|
hideSlashHints();
|
|
499
|
-
if (
|
|
510
|
+
if (activeTabRunning()) {
|
|
500
511
|
e.preventDefault();
|
|
501
512
|
e.stopPropagation();
|
|
502
|
-
|
|
513
|
+
abortMessage();
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
// Navigate slash hints with arrow keys
|
|
@@ -543,17 +554,17 @@ function wireEvents() {
|
|
|
543
554
|
|| _panel.querySelector('#ocp-slash-hints:not([hidden])')
|
|
544
555
|
|| _panel.querySelector('.ocp-rename-input')
|
|
545
556
|
|| _panel.querySelector('#ocp-session-menu.open');
|
|
546
|
-
if (!ownsEsc &&
|
|
557
|
+
if (!ownsEsc && activeTabRunning()) {
|
|
547
558
|
e.preventDefault();
|
|
548
559
|
e.stopPropagation();
|
|
549
|
-
|
|
560
|
+
abortMessage();
|
|
550
561
|
}
|
|
551
562
|
}
|
|
552
563
|
});
|
|
553
564
|
if (sendBtn) {
|
|
554
565
|
sendBtn.addEventListener('click', () => {
|
|
555
|
-
if (
|
|
556
|
-
|
|
566
|
+
if (activeTabRunning()) {
|
|
567
|
+
abortMessage();
|
|
557
568
|
} else {
|
|
558
569
|
handleSend();
|
|
559
570
|
}
|
|
@@ -899,6 +910,10 @@ async function handleSend() {
|
|
|
899
910
|
let text = input.value.trim();
|
|
900
911
|
const images = Array.isArray(tab.attachedImages) ? [...tab.attachedImages] : [];
|
|
901
912
|
if (!text && !images.length) return;
|
|
913
|
+
if (tab.showPostPlanActions) {
|
|
914
|
+
setStatus('working', 'Choose Continue, Compact, or Edit plan first.');
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
902
917
|
|
|
903
918
|
// Parse and execute slash commands. If executeSlashCommand returns false
|
|
904
919
|
// (e.g. a SynaBun skill that the server will expand), keep `text` in flight
|
|
@@ -991,14 +1006,16 @@ function syncSendButton() {
|
|
|
991
1006
|
const tab = activeTab();
|
|
992
1007
|
if (!sendBtn) return;
|
|
993
1008
|
|
|
994
|
-
|
|
995
|
-
// abort the whole panel regardless of which tab is focused.
|
|
996
|
-
const panelRunning = anyTabRunning();
|
|
997
|
-
if (panelRunning) {
|
|
1009
|
+
if (tab?.running) {
|
|
998
1010
|
sendBtn.disabled = false;
|
|
999
1011
|
sendBtn.classList.add('running');
|
|
1000
1012
|
sendBtn.setAttribute('data-tooltip', 'Stop turn (Esc)');
|
|
1001
1013
|
sendBtn.setAttribute('aria-label', 'Stop turn');
|
|
1014
|
+
} else if (tab?.showPostPlanActions) {
|
|
1015
|
+
sendBtn.classList.remove('running');
|
|
1016
|
+
sendBtn.disabled = true;
|
|
1017
|
+
sendBtn.setAttribute('data-tooltip', 'Choose a plan action first');
|
|
1018
|
+
sendBtn.setAttribute('aria-label', 'Choose a plan action first');
|
|
1002
1019
|
} else {
|
|
1003
1020
|
sendBtn.classList.remove('running');
|
|
1004
1021
|
sendBtn.disabled = (!input?.value.trim() && !(tab?.attachedImages?.length)) || !_serverReady;
|
|
@@ -1478,12 +1495,12 @@ function syncReservedWidth() {
|
|
|
1478
1495
|
function _ocpDocEscHandler(event) {
|
|
1479
1496
|
if (event.key !== 'Escape') return;
|
|
1480
1497
|
if (!_visible) return;
|
|
1481
|
-
if (!
|
|
1498
|
+
if (!activeTabRunning()) return;
|
|
1482
1499
|
const hintsEl = panelEl('#ocp-slash-hints');
|
|
1483
1500
|
if (hintsEl && hintsEl.classList.contains('open')) return; // let input handler dismiss hints first
|
|
1484
1501
|
event.preventDefault();
|
|
1485
1502
|
event.stopImmediatePropagation();
|
|
1486
|
-
|
|
1503
|
+
abortMessage();
|
|
1487
1504
|
}
|
|
1488
1505
|
|
|
1489
1506
|
function setVisible(nextVisible) {
|
|
@@ -1599,32 +1616,31 @@ function ensurePanel() {
|
|
|
1599
1616
|
if (tab.planFilePath) { openPlan(tab.planFilePath); return; }
|
|
1600
1617
|
if (!tab.planContent) return;
|
|
1601
1618
|
try {
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1604
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1605
|
-
body: JSON.stringify({ content: tab.planContent }),
|
|
1606
|
-
});
|
|
1607
|
-
const result = await res.json();
|
|
1608
|
-
if (result.ok && result.path) {
|
|
1609
|
-
tab.planFilePath = result.path;
|
|
1610
|
-
saveTabs();
|
|
1611
|
-
openPlan(result.path);
|
|
1612
|
-
}
|
|
1619
|
+
const planPath = await ensurePlanFile(tab);
|
|
1620
|
+
if (planPath) openPlan(planPath);
|
|
1613
1621
|
} catch (e) {
|
|
1614
1622
|
console.error('[ocp-panel] Edit plan failed:', e);
|
|
1623
|
+
showPostPlanUI(tab);
|
|
1615
1624
|
}
|
|
1616
1625
|
});
|
|
1617
1626
|
|
|
1618
1627
|
// Listen for edited plan content from file explorer
|
|
1619
|
-
on('plan-saved', ({ content, source, tabId }) => {
|
|
1628
|
+
on('plan-saved', ({ filePath, content, source, tabId }) => {
|
|
1620
1629
|
if (source !== 'opencode') return;
|
|
1621
1630
|
const tab = getTabs().find(t => t.id === tabId) || activeTab();
|
|
1622
1631
|
if (tab && content) {
|
|
1623
|
-
tab
|
|
1624
|
-
saveTabs();
|
|
1632
|
+
applySavedPlan(tab, content, filePath);
|
|
1625
1633
|
}
|
|
1626
1634
|
});
|
|
1627
1635
|
|
|
1636
|
+
on('plan-edit-cancelled', ({ source, tabId } = {}) => {
|
|
1637
|
+
if (source !== 'opencode') return;
|
|
1638
|
+
const tab = getTabs().find(t => t.id === tabId) || activeTab();
|
|
1639
|
+
if (!tab) return;
|
|
1640
|
+
tab.showPostPlanActions = true;
|
|
1641
|
+
showPostPlanUI(tab, tab.postPlanHeader || 'PLAN COMPLETE');
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1628
1644
|
wireEvents();
|
|
1629
1645
|
autosizeInput();
|
|
1630
1646
|
|
|
@@ -1632,6 +1648,7 @@ function ensurePanel() {
|
|
|
1632
1648
|
if (!restoreTabs()) {
|
|
1633
1649
|
createTab({ project: storage.getItem(STOR.project) || '' });
|
|
1634
1650
|
}
|
|
1651
|
+
window.dispatchEvent(new CustomEvent('sidepanel-tray:provider-loaded', { detail: { provider: 'opencode' } }));
|
|
1635
1652
|
|
|
1636
1653
|
syncSendButton();
|
|
1637
1654
|
|
|
@@ -229,27 +229,6 @@ function linkifyFilePaths(container) {
|
|
|
229
229
|
attachFileLinkHandlers(container);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
// ── Throttle helper for streaming ──
|
|
233
|
-
function throttle(fn, ms) {
|
|
234
|
-
let last = 0;
|
|
235
|
-
let timer = null;
|
|
236
|
-
return function (...args) {
|
|
237
|
-
const now = Date.now();
|
|
238
|
-
const remaining = ms - (now - last);
|
|
239
|
-
if (remaining <= 0) {
|
|
240
|
-
if (timer) { clearTimeout(timer); timer = null; }
|
|
241
|
-
last = now;
|
|
242
|
-
fn.apply(this, args);
|
|
243
|
-
} else if (!timer) {
|
|
244
|
-
timer = setTimeout(() => {
|
|
245
|
-
last = Date.now();
|
|
246
|
-
timer = null;
|
|
247
|
-
fn.apply(this, args);
|
|
248
|
-
}, remaining);
|
|
249
|
-
}
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
232
|
function thinkBlockHtml(content, partial) {
|
|
254
233
|
const label = partial ? 'Thinking…' : 'Thought';
|
|
255
234
|
return `<details class="ocp-think-block"${partial ? ' open' : ''}><summary><span class="ocp-think-icon">${THINK_ICON_SVG}</span><span class="ocp-think-label">${label}</span><span class="ocp-think-chevron">›</span></summary><div class="ocp-think-content">${esc(content)}</div></details>`;
|
|
@@ -842,19 +821,46 @@ export function getOrCreateStreamingEl(container) {
|
|
|
842
821
|
return el;
|
|
843
822
|
}
|
|
844
823
|
|
|
824
|
+
function renderStreamingElement(container, el, { partialThinking = true } = {}) {
|
|
825
|
+
if (!el) return;
|
|
826
|
+
let html = '';
|
|
827
|
+
let hasContent = false;
|
|
828
|
+
if (el.dataset.thinkText) {
|
|
829
|
+
html += thinkBlockHtml(el.dataset.thinkText, partialThinking);
|
|
830
|
+
hasContent = true;
|
|
831
|
+
}
|
|
832
|
+
if (el.dataset.rawText) {
|
|
833
|
+
const rendered = renderAssistantMarkdown(el.dataset.rawText);
|
|
834
|
+
if (rendered.hasContent) {
|
|
835
|
+
html += rendered.html;
|
|
836
|
+
hasContent = true;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
el.innerHTML = html;
|
|
840
|
+
clearThinkingTimer(el);
|
|
841
|
+
el.classList.toggle('ocp-msg-empty', !hasContent);
|
|
842
|
+
postProcessRenderedHtml(el);
|
|
843
|
+
scrollToBottom(container);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
function scheduleStreamingRender(container, el) {
|
|
847
|
+
if (el._ocpStreamRaf) return;
|
|
848
|
+
const schedule = typeof requestAnimationFrame === 'function'
|
|
849
|
+
? requestAnimationFrame
|
|
850
|
+
: (fn) => setTimeout(fn, 16);
|
|
851
|
+
el._ocpStreamRaf = schedule(() => {
|
|
852
|
+
el._ocpStreamRaf = null;
|
|
853
|
+
renderStreamingElement(container, el, { partialThinking: true });
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
845
857
|
/** Append a text chunk to the streaming element. */
|
|
846
858
|
export function appendStreamChunk(container, text) {
|
|
847
859
|
const chunk = normalizeTextContent(text, { stringifyObjects: false });
|
|
848
860
|
if (!chunk) return getOrCreateStreamingEl(container);
|
|
849
861
|
const el = getOrCreateStreamingEl(container);
|
|
850
862
|
el.dataset.rawText = (el.dataset.rawText || '') + chunk;
|
|
851
|
-
|
|
852
|
-
if (el.dataset.rawText != null) {
|
|
853
|
-
setAssistantHtml(el, el.dataset.rawText);
|
|
854
|
-
scrollToBottom(container);
|
|
855
|
-
}
|
|
856
|
-
}, 32);
|
|
857
|
-
doRender();
|
|
863
|
+
scheduleStreamingRender(container, el);
|
|
858
864
|
return el;
|
|
859
865
|
}
|
|
860
866
|
|
|
@@ -864,12 +870,7 @@ export function appendThinkChunk(container, text) {
|
|
|
864
870
|
if (!chunk) return getOrCreateStreamingEl(container);
|
|
865
871
|
const el = getOrCreateStreamingEl(container);
|
|
866
872
|
el.dataset.thinkText = (el.dataset.thinkText || '') + chunk;
|
|
867
|
-
|
|
868
|
-
const thinkHtml = thinkBlockHtml(el.dataset.thinkText, true);
|
|
869
|
-
const responseHtml = el.dataset.rawText ? renderAssistantMarkdown(el.dataset.rawText).html : '';
|
|
870
|
-
el.innerHTML = thinkHtml + responseHtml;
|
|
871
|
-
clearThinkingTimer(el);
|
|
872
|
-
scrollToBottom(container);
|
|
873
|
+
scheduleStreamingRender(container, el);
|
|
873
874
|
return el;
|
|
874
875
|
}
|
|
875
876
|
|
|
@@ -878,10 +879,9 @@ export function finalizeStreamingMessage(container) {
|
|
|
878
879
|
const el = container.querySelector('.ocp-msg-assistant.streaming');
|
|
879
880
|
if (el) {
|
|
880
881
|
el.classList.remove('streaming');
|
|
881
|
-
|
|
882
|
-
setAssistantHtml(el, el.dataset.rawText);
|
|
883
|
-
}
|
|
882
|
+
renderStreamingElement(container, el, { partialThinking: false });
|
|
884
883
|
delete el.dataset.rawText;
|
|
884
|
+
delete el.dataset.thinkText;
|
|
885
885
|
}
|
|
886
886
|
const pending = container.querySelector('.ocp-msg-assistant.pending');
|
|
887
887
|
if (pending) {
|
|
@@ -1137,6 +1137,7 @@ function extractAllQuestions(input) {
|
|
|
1137
1137
|
: (input.question || input.text || input.options) ? [input]
|
|
1138
1138
|
: [input];
|
|
1139
1139
|
return questions.map(q => ({
|
|
1140
|
+
id: q.id || q.key || q.name || '',
|
|
1140
1141
|
question: q.question || q.text || q.message || '',
|
|
1141
1142
|
header: q.header || '',
|
|
1142
1143
|
options: normalizeOptions(q.options || q.choices),
|
|
@@ -1192,7 +1193,7 @@ export function renderQuestionCard(container, toolName, toolInput, toolId, { onA
|
|
|
1192
1193
|
// Render each question block
|
|
1193
1194
|
for (let qi = 0; qi < questions.length; qi++) {
|
|
1194
1195
|
const q = questions[qi];
|
|
1195
|
-
const questionKey = q.question || q.header || `question_${qi}`;
|
|
1196
|
+
const questionKey = q.id || q.question || q.header || `question_${qi}`;
|
|
1196
1197
|
const block = document.createElement('div');
|
|
1197
1198
|
block.className = 'ocp-question-block';
|
|
1198
1199
|
|
|
@@ -1255,6 +1256,7 @@ export function renderQuestionCard(container, toolName, toolInput, toolId, { onA
|
|
|
1255
1256
|
const isOther = opt.value === '__other__' || /^other$/i.test(opt.label);
|
|
1256
1257
|
const btn = document.createElement('button');
|
|
1257
1258
|
btn.className = `ocp-question-option${q.multiSelect ? ' multi' : ''}`;
|
|
1259
|
+
btn.dataset.value = opt.value;
|
|
1258
1260
|
btn.innerHTML = `
|
|
1259
1261
|
<span class="ocp-question-option-radio"></span>
|
|
1260
1262
|
<span>
|
|
@@ -1268,9 +1270,9 @@ export function renderQuestionCard(container, toolName, toolInput, toolId, { onA
|
|
|
1268
1270
|
const selected = [];
|
|
1269
1271
|
optsEl.querySelectorAll('.ocp-question-option.selected').forEach(b => {
|
|
1270
1272
|
const lbl = b.querySelector('.ocp-question-option-label');
|
|
1271
|
-
if (lbl && !/^other$/i.test(lbl.textContent)) selected.push(lbl.textContent);
|
|
1273
|
+
if (lbl && !/^other$/i.test(lbl.textContent)) selected.push(b.dataset.value || lbl.textContent);
|
|
1272
1274
|
});
|
|
1273
|
-
if (selected.length > 0) pendingAnswers[questionKey] = selected
|
|
1275
|
+
if (selected.length > 0) pendingAnswers[questionKey] = selected;
|
|
1274
1276
|
else delete pendingAnswers[questionKey];
|
|
1275
1277
|
} else {
|
|
1276
1278
|
optsEl.querySelectorAll('.ocp-question-option').forEach(b => b.classList.remove('selected'));
|
|
@@ -1283,7 +1285,7 @@ export function renderQuestionCard(container, toolName, toolInput, toolId, { onA
|
|
|
1283
1285
|
queueMicrotask(() => otherInput.focus());
|
|
1284
1286
|
} else {
|
|
1285
1287
|
otherWrap.hidden = true;
|
|
1286
|
-
pendingAnswers[questionKey] = opt.
|
|
1288
|
+
pendingAnswers[questionKey] = opt.value;
|
|
1287
1289
|
}
|
|
1288
1290
|
}
|
|
1289
1291
|
updateSubmitState();
|
|
@@ -1339,7 +1341,7 @@ export function lockQuestionCard(container, toolId) {
|
|
|
1339
1341
|
|
|
1340
1342
|
// ── Post-plan action card ──
|
|
1341
1343
|
|
|
1342
|
-
export function renderPostPlanCard(container, { onContinue, onCompact, onEditPlan } = {}) {
|
|
1344
|
+
export function renderPostPlanCard(container, { headerText = 'PLAN COMPLETE', onContinue, onCompact, onEditPlan, onContinuePlanning } = {}) {
|
|
1343
1345
|
const card = document.createElement('div');
|
|
1344
1346
|
card.className = 'ocp-post-plan-card';
|
|
1345
1347
|
card.innerHTML = `
|
|
@@ -1347,11 +1349,12 @@ export function renderPostPlanCard(container, { onContinue, onCompact, onEditPla
|
|
|
1347
1349
|
<svg class="ocp-post-plan-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1348
1350
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
1349
1351
|
</svg>
|
|
1350
|
-
PLAN COMPLETE
|
|
1352
|
+
${esc(headerText || 'PLAN COMPLETE')}
|
|
1351
1353
|
</div>
|
|
1352
1354
|
<div class="ocp-post-plan-note">Review the plan above, then choose how to proceed.</div>
|
|
1353
1355
|
<div class="ocp-post-plan-actions">
|
|
1354
1356
|
<button class="ocp-post-plan-btn primary" data-action="continue">Continue with implementation</button>
|
|
1357
|
+
<button class="ocp-post-plan-btn" data-action="continue-planning">Continue planning</button>
|
|
1355
1358
|
<button class="ocp-post-plan-btn" data-action="compact">Compact context</button>
|
|
1356
1359
|
<button class="ocp-post-plan-btn" data-action="edit">Edit plan</button>
|
|
1357
1360
|
</div>
|
|
@@ -1367,6 +1370,10 @@ export function renderPostPlanCard(container, { onContinue, onCompact, onEditPla
|
|
|
1367
1370
|
card.querySelector('[data-action="edit"]').addEventListener('click', () => {
|
|
1368
1371
|
if (typeof onEditPlan === 'function') onEditPlan();
|
|
1369
1372
|
});
|
|
1373
|
+
card.querySelector('[data-action="continue-planning"]').addEventListener('click', () => {
|
|
1374
|
+
card.remove();
|
|
1375
|
+
if (typeof onContinuePlanning === 'function') onContinuePlanning();
|
|
1376
|
+
});
|
|
1370
1377
|
|
|
1371
1378
|
container.appendChild(card);
|
|
1372
1379
|
scrollToBottom(container);
|