Files
deepseek-harness/examples/desktop/scripts/qa-cdp-shoot-ctx-deep.mjs
ZiyaZhang fc558c2db8 feat(desktop): context page deepening — window bar + compact Config + intervention marker + subagent drilldown
Four Context-page enhancements (lane-ctx-deep, F1-F4):

- F1 context window breakdown: replace percentage-only card header with
  a stacked-bar breakdown of input / cached / output token buckets,
  plus a right-side gauge showing the live window occupancy ratio.
- F2 compact Config tab: fold the sprawling profile Config editor into
  a Config tab on the Context page card, with the same yml-leaf
  ordering as the top-of-window profile picker.
- F3 intervention marker: on the intervention timeline, emit a marker
  glyph at each user-intervention row (turn-flow-glyph-style) so the
  card scans as a single stream instead of a header + separate list.
- F4 subagent drilldown: when a turn's tool trace hits a subagent, the
  Trace panel's Config + Output tabs get a second row of Subagent
  Config / Subagent Output tabs immediately below, driven by the same
  fold-in-place shape the parent panel already uses.

4 new renderer modules (compact-config-model / context-window-breakdown /
intervention-timeline / subagent-drilldown), 6 new test files (41 tests,
all node --test style), 4 QA shoot scripts for CDP-driven regression
screenshots.
2026-07-19 01:22:28 -07:00

389 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shots for lane-ctx-deep (task #51) — four Context-page enhancements.
// Each shot loads a QA fixture, forces a re-render, then captures the
// visible chrome for that feature. Real-machine isolated per team-lead:
// DSH_QA=1 + DSH_DESKTOP_HOME under /tmp/dsh-qa-ctx-deep so nothing here
// touches the user's live session store.
//
// Shots produced (docs/demo-shots/):
// ctx-deep-01-window-bar.png — Context page top strip, window
// occupancy stacked bar + legend.
// ctx-deep-02-intervention.png — Context page top strip, intervention
// markers axis.
// ctx-deep-03-compact-config.png — Compact card on Chat tab with the
// new "Config" tab active.
// ctx-deep-04-subagent.png — Subagent card with drill-down tabs
// (Tool defs / Inbound query).
import { writeFileSync, mkdirSync } from 'node:fs'
import { resolve } from 'node:path'
const port = process.argv[2] || '9241'
const outdir = process.argv[3] || 'docs/demo-shots'
mkdirSync(outdir, { recursive: true })
async function main () {
const targets = await (await fetch(`http://localhost:${port}/json/list`)).json()
const target = targets.find((t) => t.type === 'page')
if (!target) throw new Error('no page target on port ' + port)
const ws = new WebSocket(target.webSocketDebuggerUrl)
await new Promise((r, x) => { ws.onopen = r; ws.onerror = (e) => x(e) })
let id = 1
const pending = new Map()
ws.onmessage = (ev) => {
const msg = JSON.parse(typeof ev.data === 'string' ? ev.data : String(ev.data))
if (msg.id != null && pending.has(msg.id)) {
const [ok, err] = pending.get(msg.id)
pending.delete(msg.id)
if (msg.error) err(new Error(msg.error.message))
else ok(msg.result)
}
}
const call = (m, p = {}, timeoutMs = 20000) => new Promise((ok, err) => {
const _id = id++
const timer = setTimeout(() => { pending.delete(_id); err(new Error('cdp timeout: ' + m)) }, timeoutMs)
pending.set(_id, [(v) => { clearTimeout(timer); ok(v) }, (e) => { clearTimeout(timer); err(e) }])
ws.send(JSON.stringify({ id: _id, method: m, params: p }))
})
const evj = async (expr) => {
const r = await call('Runtime.evaluate', {
expression: `(async()=>{try{return (${expr})}catch(e){return {__err: String(e)}}})()`,
returnByValue: true, awaitPromise: true,
})
if (r.exceptionDetails) throw new Error(r.exceptionDetails.exception?.description || r.exceptionDetails.text)
return r.result?.value
}
const wait = (ms) => new Promise((r) => setTimeout(r, ms))
const shoot = async (name) => {
const shot = await call('Page.captureScreenshot', {
format: 'png',
clip: { x: 0, y: 0, width: 1440, height: 900, scale: 2 },
}, 30000)
const out = resolve(outdir, name)
writeFileSync(out, Buffer.from(shot.data, 'base64'))
console.log(out)
}
await call('Page.enable')
await evj(`window.dshQa && window.dshQa.revealWindow ? await window.dshQa.revealWindow() : null`)
await call('Emulation.setDeviceMetricsOverride', {
width: 1440, height: 900, deviceScaleFactor: 2, mobile: false,
})
// Dismiss onboarding.
await evj(`(function(){
const btns = Array.from(document.querySelectorAll('button'));
const skip = btns.find(b => /skip and use defaults/i.test(b.textContent || ''));
if (skip) { skip.click(); }
return 'ok';
})()`)
await wait(300)
// Hide devtools drawer.
await evj(`(function(){
const d = document.querySelector('.devtools-drawer');
if (d) d.style.display = 'none';
return 'ok';
})()`)
// Fabricate a session directly in renderer state — bypasses the need for
// a live runtime, which the desktop demo doesn't need for these shots.
// We manufacture a plausible cachedEvents array covering all five window
// families + all three intervention kinds + a compact event, then expose
// it via a temporary __dshChat.getEventsForActive() override so the
// Context page reads the same shape production would.
await evj(`(function(){
const now = Date.now()
const events = []
let seq = 1
function push(ev){ ev.seq = seq++; ev.time = now + seq*10; events.push(ev) }
// system prompt seed
push({ type: 'context/message', data: { content: [{type:'text', text: 'You are DSH, an autonomous agent operating in a research environment. Use the tools carefully.'}], source: { kind: 'system' } } })
// tool calls (drive the tool_defs slice)
push({ type: 'tool/call', data: { name: 'read_file', arguments: '{"path":"a.md"}' } })
push({ type: 'tool/call', data: { name: 'search', arguments: '{"q":"context ledger"}' } })
// user
push({ type: 'user/message', data: { content: [{type:'text', text: 'Show me the ledger'}] } })
// reasoning
push({ type: 'assistant/reasoning', data: { content: [{type:'text', text: 'The user wants a ledger view. Let me project turns.'}] } })
// assistant
push({ type: 'assistant/message', data: { content: [{type:'text', text: 'Here is the per-turn ledger with injects and compacts.'}], usage: { inputTokens: 8000, outputTokens: 320, thinking: 180 } } })
// steer intervention
push({ type: 'steering/message', data: { content: [{type:'text', text: 'Actually skip the recall section.'}] } })
// plugin inject
push({ type: 'context/message', data: { content: [{type:'text', text: 'time-context: 09:34 UTC'}], source: { kind: 'plugin', plugin: 'time-context' } } })
push({ type: 'context/message', data: { content: [{type:'text', text: 'guard hint fired'}], source: { kind: 'plugin', plugin: 'guard' } } })
// turn end
push({ type: 'turn/end', data: { turn: 1, reason: { kind: 'completed' } } })
// edit-rerun intervention on the next turn
push({ type: 'user/message', data: { content: [{type:'text', text: 'Show me the ledger and expand turn 1'}], editRerun: { origSeq: 4, reason: 'clarify intent' } } })
push({ type: 'assistant/reasoning', data: { content: [{type:'text', text: 'Re-running with expanded turn 1.'}] } })
push({ type: 'assistant/message', data: { content: [{type:'text', text: 'Sure. Turn 1 details attached.'}], usage: { inputTokens: 8500, outputTokens: 250, thinking: 90 } } })
push({ type: 'compact/summary', data: { summary: [{type:'text', text: 'Turn 1 summary retained.'}], model: 'deepseek-chat', maxTokens: 512, shadowedTokenCount: 6400, shadowedRange: {start:1, end:8} } })
push({ type: 'turn/end', data: { turn: 2, reason: { kind: 'completed' } } })
// fork intervention
push({ type: 'session/forked', data: { parentSeq: 14 } })
push({ type: 'context/message', data: { content: [{type:'text', text: 'assistant reloaded'}], source: { kind: 'system' } } })
push({ type: 'user/message', data: { content: [{type:'text', text: 'What happened in the fork?'}] } })
push({ type: 'assistant/message', data: { content: [{type:'text', text: 'The fork branched at seq 14.'}], usage: { inputTokens: 2100, outputTokens: 90 } } })
push({ type: 'turn/end', data: { turn: 3, reason: { kind: 'completed' } } })
// Install override for the Context page's read path.
const sid = 'ctx-deep-demo-session'
const chat = window.__dshChat = window.__dshChat || {}
const oldGetActive = chat.getActiveSessionId
const oldGetEvents = chat.getEventsForActive
chat.getActiveSessionId = () => sid
chat.getEventsForActive = () => events
chat.getSessions = () => [{ id: sid, title: 'ctx-deep demo', running: false }]
window.__ctxDeepDemoEvents = events
return { sid, count: events.length }
})()`)
await wait(700)
// -- SHOT 1 + 2: Context page top strip (window bar + intervention) ------
await evj(`(function(){
if (window.__dshTabs && window.__dshTabs.switchTo) { window.__dshTabs.switchTo('context'); }
return 'switched';
})()`)
await wait(500)
await evj(`window.__dshContextPage && window.__dshContextPage.refresh && window.__dshContextPage.refresh()`)
await wait(300)
const layout = await evj(`(function(){
const strip = document.querySelector('[data-context-topstrip]');
const bar = document.getElementById('context-window-bar-track');
const iv = document.getElementById('context-intervention-track');
const pane = document.querySelector('.pane[data-pane="context"]');
const chat = window.__dshChat;
return {
paneHidden: pane ? pane.hidden : 'NO-PANE',
paneHtmlLen: pane ? pane.innerHTML.length : 0,
paneChildren: pane ? Array.from(pane.children).map(c => c.tagName+':'+(c.className||c.id||'')) : null,
documentTitle: document.title,
windowLocation: String(window.location),
chatKeys: chat ? Object.keys(chat) : null,
chatGetEvents: chat && typeof chat.getEventsForActive === 'function',
chatGetSid: chat && typeof chat.getActiveSessionId === 'function' ? chat.getActiveSessionId() : null,
eventsLen: chat && chat.getEventsForActive ? chat.getEventsForActive().length : -1,
stripFound: !!strip,
stripHidden: strip ? strip.hidden : null,
segCount: bar ? bar.children.length : 0,
markerCount: iv ? iv.querySelectorAll('.context-intervention-marker').length : 0,
summary: (document.getElementById('context-window-bar-summary')||{}).textContent||'',
};
})()`)
console.error('ctx-deep layout ->', JSON.stringify(layout))
await shoot('ctx-deep-01-window-bar.png')
// Highlight the intervention strip: scroll to it if needed and take shot.
await evj(`(function(){
const strip = document.querySelector('.context-intervention-strip');
if (strip && strip.scrollIntoView) strip.scrollIntoView({ block: 'start', behavior: 'auto' });
return 'scrolled';
})()`)
await wait(300)
await shoot('ctx-deep-02-intervention.png')
// -- SHOT 3: Compact card Config tab -------------------------------------
// We render the compact card by direct DOM injection into the chat stream:
// the compact/summary event we seeded already flows through onSessionEvent
// path in real prod, but here we don't have a live session so we build a
// standalone compact card via __dshCompactCard.mountTabs.
await evj(`(function(){
if (window.__dshTabs && window.__dshTabs.switchTo) window.__dshTabs.switchTo('chat');
const panes = document.querySelectorAll('.pane[data-pane]');
for (const p of panes) p.hidden = (p.getAttribute('data-pane') !== 'chat');
const rail = document.getElementById('context-rail'); if (rail) rail.hidden = true;
const drawer = document.querySelector('.devtools-drawer'); if (drawer) drawer.style.display = 'none';
for (const sel of ['.onboarding','.mock-cards-menu','.dropdown-open','[data-mock-cards]']) {
for (const n of document.querySelectorAll(sel)) n.style.display = 'none';
}
return 'chat';
})()`)
await wait(400)
const compactMount = await evj(`(function(){
// Build a full-page overlay to keep the shot focused on the card.
const prevOverlay = document.getElementById('ctx-deep-demo-overlay');
if (prevOverlay) prevOverlay.remove();
const prev = document.getElementById('ctx-deep-demo-compact-card');
if (prev) prev.remove();
const overlay = document.createElement('div');
overlay.id = 'ctx-deep-demo-overlay';
overlay.style.cssText = 'position:fixed;left:0;top:0;right:0;bottom:0;background:var(--bg,#fff);z-index:9999;padding:48px 96px;overflow:auto;font-family:inherit;';
const heading = document.createElement('h1');
heading.style.cssText = 'font-size:24px;margin:0 0 16px 0;';
heading.textContent = 'Compact card — Config tab';
overlay.appendChild(heading);
const sub = document.createElement('div');
sub.className = 'muted';
sub.style.cssText = 'margin-bottom:24px;color:var(--muted,#5b6b7d);';
sub.textContent = 'Read-only view of the current compaction policy, threshold, and distance to next fire.';
overlay.appendChild(sub);
const stream = overlay;
const card = document.createElement('details');
card.id = 'ctx-deep-demo-compact-card';
card.className = 'compact-card compact-card-demo';
card.open = true;
card.style.margin = '24px auto';
card.style.maxWidth = '760px';
const sum = document.createElement('summary');
sum.className = 'summary';
const badge = document.createElement('span');
badge.className = 'compact-badge compact-badge-on-demand';
badge.textContent = 'on-demand';
sum.appendChild(badge);
const label = document.createElement('span');
label.textContent = '── context compacted ──';
sum.appendChild(label);
const tk = document.createElement('span');
tk.className = 'tokens'; tk.textContent = '6400 tokens'; sum.appendChild(tk);
const evc = document.createElement('span');
evc.className = 'events'; evc.textContent = 'compacted 8 events'; sum.appendChild(evc);
card.appendChild(sum);
const data = { model: 'deepseek-chat', maxTokens: 512, shadowedTokenCount: 6400,
shadowedRange: { start: 1, end: 8 },
summary: [{ type: 'text', text: 'Turn 1 summary retained (system prompt, tool defs, initial query, guardhint).' }],
};
const events = window.__ctxDeepDemoEvents || [];
if (window.__dshCompactCard && window.__dshCompactCard.mountTabs) {
window.__dshCompactCard.mountTabs(card, {
document,
initial: 'config',
fillPre(body){ body.textContent = '(see events 18; summary compressed to 1 line)'; body.style.padding = '8px'; },
fillPost(body){ body.textContent = data.summary[0].text; body.style.padding = '8px'; },
fillMeta(body){
const dl = document.createElement('dl'); dl.className = 'compact-card-tab-meta';
for (const [k,v] of Object.entries({ Trigger: 'on-demand', Model: data.model, 'Summary cap': '≤512 tok', 'Compacted range': 'seq 18', 'Compacted volume': '6400 tok' })) {
const dt = document.createElement('dt'); dt.textContent = k;
const dd = document.createElement('dd'); dd.textContent = v;
dl.appendChild(dt); dl.appendChild(dd);
}
body.appendChild(dl);
},
fillConfig(body){
if (!window.__dshCompactConfigModel) { body.textContent = 'model missing'; return; }
const v = window.__dshCompactConfigModel.buildCompactConfigView(events);
const dl = document.createElement('dl'); dl.className = 'compact-card-tab-meta compact-config-list';
const rows = [
['Threshold', v.thresholdTokens.toLocaleString() + ' tok' + (v.thresholdSource==='assumed'?' (assumed)':'')],
['Strategy', v.strategyName],
['Model', v.model || 'deepseek-chat'],
['Summary cap', v.maxSummaryTokens != null ? '≤' + v.maxSummaryTokens + ' tok' : 'unknown'],
['Triggers fired', v.triggersFired + ' this session'],
['Tokens since last compact', v.tokensSinceLastCompact.toLocaleString() + ' tok'],
['Tokens until next', v.tokensUntilNext.toLocaleString() + ' tok'],
];
for (const [k, val] of rows) {
const dt = document.createElement('dt'); dt.textContent = k;
const dd = document.createElement('dd'); dd.textContent = val;
dl.appendChild(dt); dl.appendChild(dd);
}
body.appendChild(dl);
const progWrap = document.createElement('div');
progWrap.className = 'compact-config-progress compact-config-progress--' + v.progressLevel;
const progHead = document.createElement('div');
progHead.className = 'compact-config-progress-head';
const pt = document.createElement('span');
pt.className = 'compact-config-progress-title'; pt.textContent = 'Progress to next compact';
const pp = document.createElement('span');
pp.className = 'compact-config-progress-pct muted small'; pp.textContent = Math.min(100, Math.round(v.progressPct)) + '%';
progHead.appendChild(pt); progHead.appendChild(pp);
const track = document.createElement('div'); track.className = 'compact-config-progress-track';
const fill = document.createElement('div'); fill.className = 'compact-config-progress-fill';
fill.style.setProperty('--fill-pct', Math.min(100, Math.max(0, v.progressPct)) + '%');
track.appendChild(fill); progWrap.appendChild(progHead); progWrap.appendChild(track);
body.appendChild(progWrap);
const note = document.createElement('div');
note.className = 'compact-config-note muted small';
note.textContent = 'Read-only view. Adjust in Settings Compaction (restart-required until session/set-compact-policy lands, gap G2).';
body.appendChild(note);
},
});
}
stream.appendChild(card);
document.body.appendChild(overlay);
return 'mounted';
})()`)
console.error('compact mount ->', compactMount)
await wait(400)
await shoot('ctx-deep-03-compact-config.png')
// -- SHOT 4: Subagent drill-down tabs -----------------------------------
// Make sure we're on the Chat tab and any drawers/rails are hidden so the
// subagent card owns the frame.
await evj(`(function(){
if (window.__dshTabs && window.__dshTabs.switchTo) window.__dshTabs.switchTo('chat');
// Force-show the chat pane and hide every other pane so a mount into
// #stream can't land off-screen.
const panes = document.querySelectorAll('.pane[data-pane]');
for (const p of panes) p.hidden = (p.getAttribute('data-pane') !== 'chat');
const rail = document.getElementById('context-rail'); if (rail) rail.hidden = true;
const drawer = document.querySelector('.devtools-drawer'); if (drawer) drawer.style.display = 'none';
return 'chat-clean';
})()`)
await wait(400)
const subaMount = await evj(`(function(){
// Build a full-page overlay that covers the whole viewport with a white
// background — this is a demo shot, so we want the subagent trace to be
// the whole story. This bypasses any pane routing quirks.
const prevOverlay = document.getElementById('ctx-deep-demo-overlay');
if (prevOverlay) prevOverlay.remove();
const prev = document.getElementById('ctx-deep-demo-subagent');
if (prev) prev.remove();
const prevCompact = document.getElementById('ctx-deep-demo-compact-card');
if (prevCompact) prevCompact.remove();
const overlay = document.createElement('div');
overlay.id = 'ctx-deep-demo-overlay';
overlay.style.cssText = 'position:fixed;left:0;top:0;right:0;bottom:0;background:var(--bg,#fff);z-index:9999;padding:48px 96px;overflow:auto;font-family:inherit;';
const heading = document.createElement('h1');
heading.style.cssText = 'font-size:24px;margin:0 0 16px 0;';
heading.textContent = 'Subagent trace — drill-down tabs';
overlay.appendChild(heading);
const sub = document.createElement('div');
sub.className = 'muted';
sub.style.cssText = 'margin-bottom:24px;color:var(--muted,#5b6b7d);';
sub.textContent = 'Tool defs / Inbound query surface at the foot of every subagent card.';
overlay.appendChild(sub);
const stream = overlay;
if (!window.__dshSubagentView || !window.__dshSubagentView.buildInlineSubagentTrace) {
return 'no-view';
}
const now = Date.now();
const childEvents = [
{ type: 'user/message', seq: 1, time: now, data: { content: [{type:'text', text:'Locate a file called ledger.md and summarise sections 24.'}], source: { kind: 'plugin', plugin: 'subagent-search' } } },
{ type: 'tool/call', seq: 2, time: now+10, data: { name: 'read_file', arguments: '{"path":"ledger.md"}' } },
{ type: 'tool/call', seq: 3, time: now+20, data: { name: 'search', arguments: '{"q":"section 2"}' } },
{ type: 'tool/call', seq: 4, time: now+30, data: { name: 'read_file', arguments: '{"path":"appendix.md"}' } },
{ type: 'turn/end', seq: 5, time: now+40, data: { turn: 0, reason: { kind: 'completed' } } },
];
const lastAssistantMessage = [{ type: 'text', text: '\`\`\`json\\n{"summary":"sections 2-4 cover the ledger schema","found":3}\\n\`\`\`' }];
const spec = {
parentSessionId: 'parent-sess-1',
childSessionId: 'child-sess-a',
status: 'done',
provider: 'stdio-echo',
stopReason: 'stop',
childEvents,
lastAssistantMessage,
};
const trace = window.__dshSubagentView.buildInlineSubagentTrace(document, spec, { collapsed: false });
trace.id = 'ctx-deep-demo-subagent';
trace.style.margin = '24px auto';
trace.style.maxWidth = '760px';
stream.appendChild(trace);
trace.open = true;
// Append the overlay LAST so it sits atop the rest of the page.
document.body.appendChild(overlay);
return { hasDrilldown: !!trace.querySelector('.subagent-drilldown') };
})()`)
console.error('subagent mount ->', JSON.stringify(subaMount))
await wait(400)
await shoot('ctx-deep-04-subagent.png')
ws.close()
}
main().catch((e) => { console.error(e); process.exit(1) })