Dewey's round 3 candidate, manifest
agents/dewey/work/queue-40/candidate-manifest-r3.sha256 (d0aa0ded,
27 files, checked OK in the canonical tree).
- WebUI inbox, tasks, agents and trail views, read-only over /api/bus.
The README says the bus proof ends at the Console process.
- CHAT-03 seal: the engine command is fixed, the engine environment is
explicit, SEAL_FLAGS has --no-approve, escalating is cleared on throw.
- Terminal input typed after Ctrl-T or Ctrl-O is held. Only the run whose
own parse set held drains it (T1), and #run catches errors per action.
- DEFERRED keeps N2 and moves F2 to done, citing T1.
Reviews: Filbert approve (comment 27011, rev 260), Darkwing approve
(27013, rev 264). Landing gate on 8cad7722 plus the candidate: webui 22,
conversation 161, control-board 124, every scripts/test-*.sh green,
test-task 98/0. Mutant Mr survives; its flows test is the first
follow-up row.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
consttimer=setTimeout(()=>{child.kill('SIGKILL');done(reject,newBusReadError('outcome-unknown','the bus transport did not finish in time'));},timeoutMs);
'human.input':()=>b.kind==='answer'?(b.op==='resolved'?`The human chose “${b.choice}” from the CLI`:`The human ${b.op==='seen'?'marked it seen':b.op} from the CLI`)
:b.kind==='instruction'?'Request from the human, recorded from the CLI':b.kind==='admin'?'Admin command from the human, CLI':'Input from the human',
'task.created':()=>`Created for ${b.requirement}`,
'task.assigned':()=>`Assigned to ${b.role}`,
'task.state':()=>`Moved ${b.previous?`from ${b.previous}`:''}to ${b.state}${b.percent_done!==undefined?`, ${b.percent_done}% done`:''}${b.comment?', with a comment in Vikunja':''}`,
'task.closed':()=>`Closed with verdict ${b.verdict}`,
if(b.previous===null)return'Read from Vikunja for the first time: the stack did not create it';
returnchanged.length?`Changed in Vikunja: ${changed.join(', ')}${comments?`, and ${comments}`:''}`:comments?`${comments[0].toUpperCase()}${comments.slice(1)} in Vikunja`:'Read from Vikunja';
},
'task.conflict':()=>`Write refused (${b.verb}): Vikunja changed after the stack read it (expected ${short(b.expected)}, found ${short(b.actual)})`,
'task.missing':()=>`No longer readable in Vikunja (${b.reason}${b.project?`, now in project ${b.project}`:''})`,
'action.allowed':()=>b.operation==='decision.raise'?`Raised with this context: choosing “${b.approvalChoice}” approves ${b.action}${b.target?` on ${b.target}`:''}`:`Allowed ${b.action}${b.target?` on ${b.target}`:''}`,
'launch.revoked':()=>'Launching revoked from the CLI',
'launch.restored':()=>'Launching restored from the CLI',
'credential.expiring':()=>`${b.service} credential for ${b.instance} expires ${b.expires??'soon'}`,
'credential.expired':()=>`${b.service} credential for ${b.instance} expired`,
'credential.changed':()=>`${b.service} credential for ${b.instance} changed`,
out.text=`Message ${r.op}${r.holder_run?` to ${r.holder_run}`:''}${r.transport?` by ${r.transport}`:''}${r.transport==='discord-dm'?' (DM to the human)':''}`;
out.text=r.op==='resolved'?`${r.by} chose “${r.choice}” via ${r.via}`:r.op==='seen'?`${r.by} saw it${r.via?` via ${r.via}`:''}`:`${r.op[0].toUpperCase()}${r.op.slice(1)} by ${r.by}${r.note?`: ${r.note}`:''}`;
out.text=r.source==='self'?`Written: ${bucketText(r.fields)}`:`Read from Vikunja (${r.via} read sent ${String(r.read_at).slice(11,19)}): ${bucketText(r.fields)}`;
if(ctx.current!=null&&r.seq>ctx.current)out.note='stale read, not current (task_current skips it)';
out.text=r.op==='claim'?`${r.role} claimed by ${r.holder_run} (${r.harness}${r.address?`, ${r.address}`:''})`:`${r.role}${r.op==='release'?'released':'revoked'} by ${r.by}${r.reason?`: ${r.reason}`:''}`;
}else{out.kind=String(r.table);out.text='A row this page does not know how to show';}
constreadNote=()=>`Read from the bus ${when(readAt)}.`;
// Lead decision 63: what no Q1 read returns carries this label where it would show.
constgap=what=>`<span class="feat">${esc(what)}: not in the Q1 module</span>`;
// Which choice authorizes which action on which target, as the broker recorded it at
// decision.raise. Never inferred from the recommendation or an option's position.
constapproves=a=>a?`Choosing “${txt(a.approvalChoice)}” approves ${txt(a.action)}${a.target?` on ${txt(a.target)}`:''}.`:'No approval context was recorded for this decision, so no option is marked as approving.';
constitems=[['/','Control board',''],['/inbox','Inbox',inboxCount?`<span class="count" aria-label="${inboxCount.open} open for you">${inboxCount.open}</span>${inboxCount.blocking?`${badge(`${inboxCount.blocking} blocking`,'attn')}`:''}`:''],['/tasks','Tasks',''],['/agents','Agents','']];
constbody=rows.length?`<ul class="waiting s1-decs">${rows.map(decisionCard).join('')}</ul>`:'<p class="state">Nothing is waiting on you.</p>';
return`${head('Inbox',`${readNote()} Decisions are answered in a terminal with <code>mosaic decide</code>; Console only shows them.`)}
<h2 id="b-for-you">For you <span class="count">${rows.length}</span></h2>${body}
<p class="small muted">Not shown here yet: ${gap('Decisions routed to roles')}${gap('Closed decisions')}${gap('Seen and DM state per row')} Open a decision for its seen, DM and closing history.</p>`;
if(!d)returnnotFound(`No decision with id ${txt(id)}.`);
constc=d.closed;
conststatus=c?(c.op==='resolved'?`Resolved: ${txt(c.by)} chose “${txt(c.choice)}”${c.via?` via ${txt(c.via)}`:''}${when(c.at)}.`:`${esc(c.op[0].toUpperCase()+c.op.slice(1))} by ${txt(c.by)}${when(c.at)}${c.note?`: ${txt(c.note)}`:''}.`):`Open since ${when(d.at)}.`;
// A command is offered only for an open decision routed to the human, and only when
// the id and key pass the broker's identifier rule, so the copied text is safe to paste.
consthistory=[...d.seen.map(s=>`<li>Seen by ${txt(s.by)}${s.via?` via ${txt(s.via)}`:''}${when(s.at)}</li>`),...(c?[`<li>${esc(c.op)} by ${txt(c.by)}${c.via?` via ${txt(c.via)}`:''}${when(c.at)}</li>`]:[])];
consthelp=answerable?'<p class="small muted">To answer, run one of these in a terminal. Copy only puts the command on your clipboard. Console sends nothing.</p>'
:!c?`<p class="small muted">This decision is routed to ${txt(d.routeTo)}, not to you, so Console offers no command for it.</p>`:'';
<section aria-labelledby="b-c"><h2 id="b-c">Earlier versions</h2>${earlier.length?`<ul class="s1-plain">${earlier.map(x=>`<li><a href="#/inbox/${enc(x.id)}">${txt(short(x.id))}</a> ${badge(x.closed?x.closed.op:'open')}${txt(first(x.question))}${x.closed?.note?`<span class="source">${txt(x.closed.note)}</span>`:''}</li>`).join('')}</ul>`:'<p class="muted">None.</p>'}<p class="small muted">${gap('A later version that replaces this one')}</p></section>
<section aria-labelledby="b-h"><h2 id="b-h">History</h2>${history.length?`<ul class="s1-plain">${history.join('')}</ul>`:'<p class="muted">Not seen yet.</p>'}<p><a href="#/trail/decision/${enc(d.id)}">Full trail for this decision</a></p></section>`;
}
// Tasks
functionmarkers(t){
constout=[];
// The list has no trail, so it can only say the current state came from a Vikunja read;
// the task page tells a change to a task the stack wrote from one it never wrote.
if(t.external)out.push(t.external.kind===null?badge(`last read from Vikunja (${t.external.via})`,'attn'):t.external.kind==='foreign'?badge('not created by the stack','attn'):badge(`changed in Vikunja${t.external.changed.length?`: ${t.external.changed.join(', ')}`:''}`,'attn'));
return`Newest Vikunja read among these tasks: ${reads.length?when(reads.at(-1)):'none'}. <span class="feat">Poller status: from the S3 status read, not built yet</span>`;
if(!ts.length)return`${head('Tasks',freshness(rows))}<p class="state">No tasks in this business yet. Tasks appear after the PM creates them or the poller reads them from Vikunja.</p>`;
return`${head('Tasks',freshness(rows))}<p class="small muted">Edit tasks in Vikunja; Console only reads them. ${gap('Bucket names, bot names and the Vikunja address')}</p>${groups||'<p class="state">No open tasks.</p>'}${doneHtml}${missingHtml}`;
if(!row)returnnotFound(`No task ${txt(ref)} in this business.`);
consttr=awaitget('trail',ref),t=task(row,tr);
constext=t.external?`<dt>Vikunja</dt><dd>${t.external.kind==='foreign'?'Not created by the stack; first read':'Changed in Vikunja after the stack wrote it'}${t.external.changed.length?`: ${txt(t.external.changed.join(', '))}`:''}${t.external.comments?`, ${esc(plural(t.external.comments,'new comment'))}`:''} <span class="source">${txt(t.external.via)} read sent ${clock(t.external.readAt)}</span></dd>`:'';
constconflicts=t.conflicts.length?`<dt>Refused writes</dt><dd>${t.conflicts.map(c=>`${txt(c.verb)}: Vikunja changed after the stack read it (expected ${txt(short(c.expected))}, found ${txt(short(c.actual))}) ${when(c.at)}`).join('<br>')}</dd>`:'';
constsub=t.missing?`No longer readable in Vikunja (${txt(t.missing.reason)}${t.missing.project?`, now in project ${txt(t.missing.project)}`:''}) since ${when(t.missing.at)}.`:`${t.done?'Done':`In bucket ${esc(t.bucket)}`}, last changed ${when(t.changedAt)}. ${readNote()}`;
<details class="s1-snaps" id="b-snaps"><summary>Snapshots <span class="count">${t.snapshots.length}</span></summary><ol class="s1-plain">${t.snapshots.map(s=>`<li>${clock(s.at)}${esc(bucketText(s.fields))} <span class="source">${s.source==='self'?`written by ${txt(s.role)}`:`${txt(s.via)} read sent ${esc(String(s.readAt).slice(11,19))}`}${s.stale?'; stale read, not shown':''}</span></li>`).join('')}</ol></details></section>
<section aria-labelledby="b-trail"><h2 id="b-trail">Trail</h2>${trailList(lines(tr,row.seq),'all')}<p><a href="#/trail/task/${enc(ref)}">Trail with filters</a></p></section>`;
<section aria-labelledby="b-claims"><h2 id="b-claims">Held roles <span class="count">${claims.length}</span></h2><p class="small muted">The current holder of each claimed role. A role nobody holds does not appear.</p>
${claims.length?`<div class="table-wrap" tabindex="0" role="region" aria-label="Held roles, scroll for all columns"><table class="s1-table s1-agents"><thead><tr>${['Role','Holder','Harness','Held since'].map(h=>`<th scope="col">${h}</th>`).join('')}</tr></thead><tbody>${rows}</tbody></table></div>`:'<p class="state">No role is held right now.</p>'}</section>
<p class="small muted">Not shown here yet: ${gap('Role instances, launch limits and model families')}${gap('Launch state')}${gap('Live and ended sessions')}${gap('Credential notices')}</p>
<p class="small muted">Board seats stay on the <a href="#/">control board</a>. This view is about roles.</p>`;
// Why a read failed, in words, with the bus code as given. Never a path or a guess.
functionwhy(err){
constcode=`<code>${txt(err.code)}</code>`;
constsay={
'human-required':'The bus answers the Console only when the human started it from their own shell. This Console was started inside an agent session, so the bus refused.',
unauthenticated:'The bus did not accept this Console as the human.',
'unknown-business':'The bus does not know this business.',
'read-only':'The bus refused the read.',
'not-configured':'This Console has no bus configured: the system config could not be read when it started. The control board still works.',
'no-bus-host':'No bus host is running, and the Console was started without <code>--business</code>. Start one with <code>mosaic bus start <business></code>.',
'outcome-unknown':'The bus did not answer in time.',
'invalid-request':'The bus refused this address as a request.',
unreachable:'The Console server did not answer.',
}[err.code]||'The bus answered with something Console cannot use.';
return`${say} (${code})`;
}
constfailTitle=err=>err.status===403?'Bus refused the read':['not-configured','no-bus-host'].includes(err.code)?'No bus to read':'The read failed';
constfailedView=err=>`${head(failTitle(err))}<div class="state state-error" role="alert"><p><b>${why(err)}</b> Console shows nothing rather than a guess. Nothing was written.</p>${retry}</div>`;
conststaleBanner=err=>`<div class="state state-error s1-stale" role="alert"><p><b>The last read failed.</b> ${why(err)} This is what was read before, ${when(readAt)}. Nothing was changed.</p>${retry}</div>`;
<navclass="tree"aria-label="Projects"><h2class="tree-title">Projects</h2><divid="projects"><pclass="muted">Loading projects…</p></div><pclass="small muted">Select a project to filter sessions. Waiting on you always shows all projects.</p></nav>
<navclass="tree"aria-label="Console"><h2class="tree-title">Console</h2><ulclass="s1-sections"id="sections"></ul><divid="board-tree"><h2class="tree-title">Projects</h2><divid="projects"><pclass="muted">Loading projects…</p></div><pclass="small muted">Select a project to filter sessions. Waiting on you always shows all projects.</p></div></nav>
<sectionid="conversation"class="conversation"aria-labelledby="conv-title"hidden><divclass="page-head"><div><h2id="conv-title"tabindex="-1">Conversation</h2><pid="conv-meta"class="small muted"></p></div><divclass="conv-actions"><label>Session<selectid="conv-pick"></select></label><buttonclass="btn"id="conv-back"type="button">Back to board</button></div></div>
<divid="board-view"><sectionclass="console-waiting"aria-labelledby="waiting-title"><h2id="waiting-title">Waiting on you <spanid="waiting-count"class="count">0</span></h2><divid="waiting"><pclass="muted">Loading sessions…</p></div></section>
<sectionaria-labelledby="sessions-title"><divclass="page-head"><h2id="sessions-title">All sessions <spanid="session-count"class="count">0</span></h2><divclass="filters"><label><inputid="hide-offline"type="checkbox"checked>Hide offline</label><label><inputid="hide-seen"type="checkbox"checked>Hide seen</label></div></div><pclass="small muted">Select an agent to inspect. Arrow keys move between agents; Enter opens the inspector. Scroll the table for all columns.</p><divid="sessions"></div></section></div>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.