mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
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.
This commit is contained in:
89
examples/desktop/scripts/qa-cdp-run-ctx-deep.mjs
Normal file
89
examples/desktop/scripts/qa-cdp-run-ctx-deep.mjs
Normal file
@@ -0,0 +1,89 @@
|
||||
// Real-machine isolated runner for the lane-ctx-deep shots (task #51).
|
||||
// Boots Electron on a private CDP port with a private USER_DATA and
|
||||
// DSH_DESKTOP_HOME under $TMPDIR (2026-07-18 postmortem — nothing here
|
||||
// touches the user's real ~/.dsh-desktop), then invokes the shot script.
|
||||
//
|
||||
// Usage: node scripts/qa-cdp-run-ctx-deep.mjs
|
||||
|
||||
import { spawn } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
||||
import { resolve, join } from 'node:path'
|
||||
import { setTimeout as sleep } from 'node:timers/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
|
||||
const WORKTREE = resolve(process.env.DSH_WORKTREE || process.cwd())
|
||||
const PARENT = resolve(process.env.DSH_REPO || '/Users/ziya/harness/dsh-desktop-demo')
|
||||
const ELECTRON = join(PARENT, 'node_modules/.bin/electron')
|
||||
const CDP_PORT = Number(process.env.DSH_CTX_DEEP_PORT || 9285)
|
||||
const USER_DATA = join(tmpdir(), 'dsh-ctx-deep-userdata')
|
||||
const DSH_HOME = join(tmpdir(), 'dsh-ctx-deep-home')
|
||||
const OUTDIR = join(WORKTREE, 'docs/demo-shots')
|
||||
const SHOOT_SCRIPT = join(WORKTREE, 'scripts/qa-cdp-shoot-ctx-deep.mjs')
|
||||
|
||||
if (!existsSync(ELECTRON)) {
|
||||
console.error(`electron binary not found at ${ELECTRON}`)
|
||||
process.exit(2)
|
||||
}
|
||||
mkdirSync(OUTDIR, { recursive: true })
|
||||
for (const dir of [USER_DATA, DSH_HOME]) {
|
||||
try { rmSync(dir, { recursive: true, force: true }) } catch {}
|
||||
mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
// Seed a minimal config so onboarding modal stays out of the way. Use
|
||||
// echo-jsonrpc (jsonrpc-demo bin) — daemon-demo has been renamed to
|
||||
// jsonrpc-demo upstream and the daemon-* config still points at the old
|
||||
// path, so echo-jsonrpc is the correct keyless profile today.
|
||||
writeFileSync(join(DSH_HOME, 'user-overlay.cordis.yml'), [
|
||||
'# ctx-deep QA seed overlay',
|
||||
'plugins:',
|
||||
` - "@cordisjs/plugin-include":`,
|
||||
` path: ${join(WORKTREE, 'config/echo-jsonrpc.yml')}`,
|
||||
'',
|
||||
].join('\n'))
|
||||
writeFileSync(join(DSH_HOME, 'config.json'), JSON.stringify({ role: 'coding', approvalMode: 'never' }))
|
||||
writeFileSync(join(DSH_HOME, '.onboarded'), new Date().toISOString())
|
||||
|
||||
const child = spawn(ELECTRON, [
|
||||
`--remote-debugging-port=${CDP_PORT}`,
|
||||
`--user-data-dir=${USER_DATA}`,
|
||||
'--disable-gpu',
|
||||
'--no-sandbox',
|
||||
'.',
|
||||
], {
|
||||
cwd: WORKTREE,
|
||||
env: {
|
||||
...process.env,
|
||||
DSH_DESKTOP_HOME: DSH_HOME,
|
||||
DSH_DEV_ROOT: process.env.DSH_DEV_ROOT || '/Users/ziya/harness/deepseek-harness-dev',
|
||||
DSH_MAXIMIZE: '1',
|
||||
DSH_QA: '1',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
})
|
||||
const logs = []
|
||||
child.stdout.on('data', d => logs.push(String(d)))
|
||||
child.stderr.on('data', d => logs.push(String(d)))
|
||||
|
||||
let up = false
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await sleep(500)
|
||||
try {
|
||||
const r = await fetch(`http://localhost:${CDP_PORT}/json/list`)
|
||||
if (r.ok) { up = true; break }
|
||||
} catch {}
|
||||
}
|
||||
if (!up) {
|
||||
child.kill('SIGKILL')
|
||||
console.error('electron did not come up on CDP :' + CDP_PORT)
|
||||
console.error(logs.join(''))
|
||||
process.exit(3)
|
||||
}
|
||||
|
||||
// Run the shot script against our port.
|
||||
const shooter = spawn(process.execPath, [SHOOT_SCRIPT, String(CDP_PORT), OUTDIR], {
|
||||
cwd: WORKTREE,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
const rc = await new Promise((r) => shooter.on('exit', (code) => r(code)))
|
||||
child.kill('SIGKILL')
|
||||
process.exit(rc || 0)
|
||||
388
examples/desktop/scripts/qa-cdp-shoot-ctx-deep.mjs
Normal file
388
examples/desktop/scripts/qa-cdp-shoot-ctx-deep.mjs
Normal file
@@ -0,0 +1,388 @@
|
||||
// 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 1–8; 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 1–8', '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 2–4.'}], 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) })
|
||||
Reference in New Issue
Block a user