diff --git a/static/messages.js b/static/messages.js
index 6ba2c80..82ac5f4 100644
--- a/static/messages.js
+++ b/static/messages.js
@@ -146,6 +146,7 @@ async function send(){
}
if(S.session&&S.session.session_id===activeSid){
S.session=d.session;S.messages=d.session.messages||[];
+ if(d.usage) S.lastUsage=d.usage;
if(d.session.tool_calls&&d.session.tool_calls.length){
S.toolCalls=d.session.tool_calls.map(tc=>({...tc,done:true}));
} else {
diff --git a/static/panels.js b/static/panels.js
index ad9ad2a..bd13b69 100644
--- a/static/panels.js
+++ b/static/panels.js
@@ -79,6 +79,9 @@ async function loadCrons() {
} catch(e) { box.innerHTML = `
Error: ${esc(e.message)}
`; }
}
+let _cronSelectedSkills=[];
+let _cronSkillsCache=null;
+
function toggleCronForm(){
const form=$('cronCreateForm');
if(!form)return;
@@ -90,10 +93,65 @@ function toggleCronForm(){
$('cronFormPrompt').value='';
$('cronFormDeliver').value='local';
$('cronFormError').style.display='none';
+ _cronSelectedSkills=[];
+ _renderCronSkillTags();
+ const search=$('cronFormSkillSearch');
+ if(search)search.value='';
+ // Pre-fetch skills for the picker
+ if(!_cronSkillsCache){
+ api('/api/skills').then(d=>{_cronSkillsCache=d.skills||[];}).catch(()=>{});
+ }
$('cronFormName').focus();
}
}
+function _renderCronSkillTags(){
+ const wrap=$('cronFormSkillTags');
+ if(!wrap)return;
+ wrap.innerHTML='';
+ for(const name of _cronSelectedSkills){
+ const tag=document.createElement('span');
+ tag.className='skill-tag';
+ tag.innerHTML=esc(name)+'
×';
+ wrap.appendChild(tag);
+ }
+}
+
+// Skill search input handler
+(function(){
+ const setup=()=>{
+ const search=$('cronFormSkillSearch');
+ const dropdown=$('cronFormSkillDropdown');
+ if(!search||!dropdown)return;
+ search.oninput=()=>{
+ const q=search.value.trim().toLowerCase();
+ if(!q||!_cronSkillsCache){dropdown.style.display='none';return;}
+ const matches=_cronSkillsCache.filter(s=>
+ !_cronSelectedSkills.includes(s.name)&&
+ (s.name.toLowerCase().includes(q)||(s.category||'').toLowerCase().includes(q))
+ ).slice(0,8);
+ if(!matches.length){dropdown.style.display='none';return;}
+ dropdown.innerHTML='';
+ for(const s of matches){
+ const opt=document.createElement('div');
+ opt.className='skill-opt';
+ opt.textContent=s.name+(s.category?' ('+s.category+')':'');
+ opt.onclick=()=>{
+ _cronSelectedSkills.push(s.name);
+ _renderCronSkillTags();
+ search.value='';
+ dropdown.style.display='none';
+ };
+ dropdown.appendChild(opt);
+ }
+ dropdown.style.display='';
+ };
+ search.onblur=()=>setTimeout(()=>{dropdown.style.display='none';},150);
+ };
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',setup);
+ else setTimeout(setup,0);
+})();
+
async function submitCronCreate(){
const name=$('cronFormName').value.trim();
const schedule=$('cronFormSchedule').value.trim();
@@ -104,7 +162,10 @@ async function submitCronCreate(){
if(!schedule){errEl.textContent='Schedule is required (e.g. "0 9 * * *" or "every 1h")';errEl.style.display='';return;}
if(!prompt){errEl.textContent='Prompt is required';errEl.style.display='';return;}
try{
- await api('/api/crons/create',{method:'POST',body:JSON.stringify({name:name||undefined,schedule,prompt,deliver})});
+ const body={schedule,prompt,deliver};
+ if(name)body.name=name;
+ if(_cronSelectedSkills.length)body.skills=_cronSelectedSkills;
+ await api('/api/crons/create',{method:'POST',body:JSON.stringify(body)});
toggleCronForm();
showToast('Job created ✓');
await loadCrons();
@@ -344,12 +405,45 @@ async function openSkill(name, el) {
$('previewBadge').textContent = 'skill';
$('previewBadge').className = 'preview-badge md';
showPreview('md');
- $('previewMd').innerHTML = renderMd(data.content || '(no content)');
+ let html = renderMd(data.content || '(no content)');
+ // Render linked files section if present
+ const lf = data.linked_files || {};
+ const categories = Object.entries(lf).filter(([,files]) => files && files.length > 0);
+ if (categories.length) {
+ html += '
Linked Files
';
+ for (const [cat, files] of categories) {
+ html += `
${esc(cat)}
`;
+ for (const f of files) {
+ html += `
${esc(f)}`;
+ }
+ html += '
';
+ }
+ html += '
';
+ }
+ $('previewMd').innerHTML = html;
$('previewArea').classList.add('visible');
$('fileTree').style.display = 'none';
} catch(e) { setStatus('Could not load skill: ' + e.message); }
}
+async function openSkillFile(skillName, filePath) {
+ try {
+ const data = await api(`/api/skills/content?name=${encodeURIComponent(skillName)}&file=${encodeURIComponent(filePath)}`);
+ $('previewPathText').textContent = skillName + ' / ' + filePath;
+ $('previewBadge').textContent = filePath.split('.').pop() || 'file';
+ $('previewBadge').className = 'preview-badge code';
+ const ext = filePath.split('.').pop() || '';
+ if (['md','markdown'].includes(ext)) {
+ showPreview('md');
+ $('previewMd').innerHTML = renderMd(data.content || '');
+ } else {
+ showPreview('code');
+ $('previewCode').textContent = data.content || '';
+ requestAnimationFrame(() => highlightCode());
+ }
+ } catch(e) { setStatus('Could not load file: ' + e.message); }
+}
+
// ── Skill create/edit form ──
let _editingSkillName = null;
diff --git a/static/style.css b/static/style.css
index e5c39ed..642347b 100644
--- a/static/style.css
+++ b/static/style.css
@@ -562,6 +562,26 @@ body.resizing{user-select:none;cursor:col-resize;}
/* Show more button inside tool card result */
.tool-card-more{background:none;border:none;color:var(--blue);font-size:10px;cursor:pointer;padding:3px 0 0;opacity:.7;display:block;}
.tool-card-more:hover{opacity:1;}
+/* Subagent cards: indented with accent border */
+.tool-card-subagent{border-left:2px solid rgba(124,185,255,.3);margin-left:8px;}
+/* Token usage badge below assistant messages */
+.msg-usage{font-size:11px;color:var(--muted);opacity:.6;margin-top:2px;padding-left:42px;}
+.msg-usage:hover{opacity:1;}
+/* Skill picker (cron create form) */
+.skill-picker-wrap{position:relative;}
+.skill-picker-dropdown{position:absolute;left:0;right:0;top:100%;background:var(--sidebar);border:1px solid var(--border2);border-radius:6px;z-index:10;max-height:180px;overflow-y:auto;box-shadow:0 4px 12px rgba(0,0,0,.3);}
+.skill-opt{padding:6px 10px;cursor:pointer;font-size:12px;color:var(--muted);transition:background .1s;}
+.skill-opt:hover{background:rgba(255,255,255,.08);color:var(--text);}
+.skill-picker-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;}
+.skill-tag{background:rgba(124,185,255,.12);border:1px solid rgba(124,185,255,.25);border-radius:12px;padding:2px 8px;font-size:11px;color:var(--blue);display:flex;align-items:center;gap:4px;}
+.remove-tag{cursor:pointer;opacity:.6;font-size:13px;line-height:1;}
+.remove-tag:hover{opacity:1;color:var(--accent);}
+/* Skill linked files section */
+.skill-linked-files{margin-top:16px;border-top:1px solid var(--border);padding-top:12px;}
+.skill-linked-section{margin-bottom:8px;}
+.skill-linked-section h4{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:4px;}
+.skill-linked-file{display:block;font-size:12px;padding:3px 6px;border-radius:4px;cursor:pointer;color:var(--blue);text-decoration:none;}
+.skill-linked-file:hover{background:rgba(255,255,255,.06);}
.tool-card-row{margin:0;padding:1px 0;}
.tool-card{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);border-radius:6px;margin:2px 0 2px 40px;overflow:hidden;transition:border-color .15s;}
.tool-card:hover{border-color:rgba(255,255,255,.12);}
diff --git a/static/ui.js b/static/ui.js
index 0650985..8124383 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -82,6 +82,8 @@ let _scrollPinned=true;
_scrollPinned=nearBottom;
});
})();
+function _fmtTokens(n){if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'k';return String(n);}
+
function scrollIfPinned(){
if(!_scrollPinned) return;
const el=$('messages');
@@ -486,6 +488,21 @@ function renderMessages(){
else inner.appendChild(frag);
}
}
+ // Render per-turn usage badge on the last assistant message (if usage data exists)
+ if(S.session&&(S.session.input_tokens||S.session.output_tokens)){
+ const lastAssist=inner.querySelector('.msg-row:last-child');
+ if(lastAssist&&!lastAssist.querySelector('.msg-usage')){
+ const usage=document.createElement('div');
+ usage.className='msg-usage';
+ const inTok=S.session.input_tokens||0;
+ const outTok=S.session.output_tokens||0;
+ const cost=S.session.estimated_cost;
+ let text=`${_fmtTokens(inTok)} in · ${_fmtTokens(outTok)} out`;
+ if(cost) text+=` · ~$${cost<0.01?cost.toFixed(4):cost.toFixed(2)}`;
+ usage.textContent=text;
+ lastAssist.appendChild(usage);
+ }
+ }
scrollToBottom();
// Apply syntax highlighting after DOM is built
requestAnimationFrame(()=>{highlightCode();addCopyButtons();renderMermaidBlocks();});
@@ -499,7 +516,8 @@ function toolIcon(name){
const icons={terminal:'⬛',read_file:'📄',write_file:'✏️',search_files:'🔍',
web_search:'🌐',web_extract:'🌐',execute_code:'⚙️',patch:'🔧',
memory:'🧠',skill_manage:'📚',todo:'✅',cronjob:'⏱️',delegate_task:'🤖',
- send_message:'💬',browser_navigate:'🌐',vision_analyze:'👁️'};
+ send_message:'💬',browser_navigate:'🌐',vision_analyze:'👁️',
+ subagent_progress:'🔀'};
return icons[name]||'🔧';
}
@@ -520,13 +538,22 @@ function buildToolCard(tc){
}
const hasMore=tc.snippet&&tc.snippet.length>displaySnippet.length;
const runIndicator=tc.done===false?'
':'';
+ const isSubagent=tc.name==='subagent_progress';
+ const isDelegation=tc.name==='delegate_task';
+ const cardClass='tool-card'+(tc.done===false?' tool-card-running':'')+(isSubagent?' tool-card-subagent':'');
+ // Clean up subagent preview: strip leading 🔀 emoji since the icon already shows it
+ let displayName=tc.name;
+ if(isSubagent) displayName='Subagent';
+ if(isDelegation) displayName='Delegate task';
+ let previewText=tc.preview||displaySnippet||'';
+ if(isSubagent) previewText=previewText.replace(/^🔀\s*/,'');
row.innerHTML=`
-