agents-relay 2.0.153 → 2.0.154

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.
@@ -34,6 +34,7 @@ ${dashboardStyles}
34
34
  <button id="jobs-tab" type="button" role="tab" aria-selected="true" aria-controls="jobs-view">Jobs</button>
35
35
  <button id="usage-tab" type="button" role="tab" aria-selected="false" aria-controls="usage-view">Usage</button>
36
36
  <button id="rules-tab" type="button" role="tab" aria-selected="false" aria-controls="rules-view">Router Rules</button>
37
+ <button id="qa-tab" type="button" role="tab" aria-selected="false" aria-controls="qa-view">Troubleshooter Q&A</button>
37
38
  <button id="shortcuts-tab" type="button" role="tab" aria-selected="false" aria-controls="shortcuts-view">Shortcuts</button>
38
39
  <button id="graphs-tab" type="button" role="tab" aria-selected="false" aria-controls="graphs-view">Workflow</button>
39
40
  </nav>
@@ -83,6 +84,14 @@ ${dashboardStyles}
83
84
  </section>
84
85
  <dialog id="graph-dialog" class="graph-dialog" aria-label="Workflow graph viewer"><div class="graph-dialog-header"><strong id="graph-dialog-title">Workflow graph</strong><div class="graph-navigation"><button type="button" id="graph-zoom-out" class="refresh-button" aria-label="Zoom out">−</button><span id="graph-zoom-level" aria-live="polite">100%</span><button type="button" id="graph-zoom-in" class="refresh-button" aria-label="Zoom in">+</button><button type="button" id="graph-zoom-reset" class="refresh-button">Fit</button><button type="button" id="graph-dialog-close" class="refresh-button">Close</button></div></div><div id="graph-dialog-viewport" class="graph-dialog-viewport"></div></dialog>
85
86
  <section id="rules-view" role="tabpanel" aria-labelledby="rules-tab" hidden><div class="panel"><div class="panel-head"><h2>Router Rules</h2><span id="rules-meta" class="subtle"></span></div><div class="panel-body"><div class="router-rules-workspace"><div class="router-rules-pane rules-editor"><h3>Rules Editor</h3><p class="rules-help">Execution Router rules with an explicit <code>[choices]</code> section (provider: model [reasoning] entries, additive to profile defaults) and a <code>[routing-instruction]</code> section forwarded verbatim to Laya. Relay deduplicates, applies hard gates, and sends one merged choice list; impossible routes remain excluded by code and Codex stays the deterministic fallback.</p><textarea id="rules-text" class="rules-textarea" spellcheck="true" aria-label="Execution router rules" placeholder="[choices]\nopenai: sol medium, luna high\nzai: glm-5.3-flash high\n\n[routing-instruction]\nPrefer zai until its quota resets; otherwise use the openai default."></textarea><div class="rules-footer"><span id="rules-status" class="subtle" aria-live="polite"></span><div><button id="rules-test" class="rules-save" type="button">Test Router</button> <button id="rules-save" class="rules-save" type="button">Save rules</button></div></div></div><div class="router-rules-pane"><div class="panel-head"><h3>Audit Log</h3><button id="audit-refresh" type="button">Refresh</button></div><div id="audit-log" class="audit-log" aria-live="polite"><p class="empty">Loading latest routing decisions…</p></div></div></div><div class="router-test-panel"><h3>Test Router Result</h3><div id="router-test-results" class="router-test-results" aria-live="polite"><p class="empty">Select a Job and Task, then run Test Router.</p></div></div></div></div></section>
87
+ <section id="qa-view" role="tabpanel" aria-labelledby="qa-tab" hidden>
88
+ <div class="panel"><div class="panel-head"><h2>Troubleshooter Q&A</h2><span class="subtle">skills/agents-relay/troubleshooter-qa.md</span></div>
89
+ <div class="panel-body"><div class="rules-editor">
90
+ <p class="rules-help">Edit Markdown directly. Troubleshooter reads this file as guidance, not workflow policy.</p>
91
+ <textarea id="qa-text" class="rules-textarea" spellcheck="true" aria-label="Troubleshooter Q&A Markdown"></textarea>
92
+ <div class="rules-footer"><span id="qa-status" class="subtle" aria-live="polite"></span><div><button id="qa-reload" class="rules-save" type="button">Reload</button> <button id="qa-save" class="rules-save" type="button">Save Q&A</button></div></div>
93
+ </div></div></div>
94
+ </section>
86
95
  <section id="shortcuts-view" role="tabpanel" aria-labelledby="shortcuts-tab" hidden>
87
96
  <div class="panel">
88
97
  <div class="panel-head"><h2>ChatGPT Shortcuts</h2><span class="subtle">One per line · %1 = current repo/pr</span></div>
@@ -117,6 +126,13 @@ const usageTab=document.querySelector('#usage-tab');
117
126
  const graphsTab=document.querySelector('#graphs-tab');
118
127
  const rulesTab=document.querySelector('#rules-tab');
119
128
  const shortcutsTab=document.querySelector('#shortcuts-tab');
129
+ const qaTab=document.querySelector('#qa-tab');
130
+ const qaView=document.querySelector('#qa-view');
131
+ const qaText=document.querySelector('#qa-text');
132
+ const qaStatus=document.querySelector('#qa-status');
133
+ const qaSave=document.querySelector('#qa-save');
134
+ let qaRevision='';
135
+
120
136
  const jobsView=document.querySelector('#jobs-view');
121
137
  const usageView=document.querySelector('#usage-view');
122
138
  const graphsView=document.querySelector('#graphs-view');
@@ -318,7 +334,9 @@ async function testRouter(){rulesTest.disabled=true;routerTestResults.replaceChi
318
334
  async function saveRules(){rulesSave.disabled=true;rulesStatus.textContent='Saving…';try{const response=await fetch('/api/execution-router-rules',{method:'PUT',headers:{'content-type':'text/plain; charset=utf-8'},body:rulesText.value});if(!response.ok)throw new Error(response.status+' '+await response.text());const rules=await response.json();rulesMeta.textContent='Saved · '+rules.hash.slice(0,12);rulesStatus.textContent='Saved. New routing decisions will load these rules.';}catch(error){rulesStatus.textContent='Save failed: '+error.message;}finally{rulesSave.disabled=false;}}
319
335
  async function drawShortcuts(){shortcutsStatus.textContent='';shortcutsText.value=chatShortcutText();}
320
336
  function saveShortcuts(){const value=shortcutsText.value.trim();if(value)localStorage.setItem(CHAT_SHORTCUTS_KEY,value);else localStorage.removeItem(CHAT_SHORTCUTS_KEY);shortcutsText.value=chatShortcutText();shortcutsStatus.textContent='Saved.';}
321
- function showView(view){const isJobs=view==='jobs';const isUsage=view==='usage';const isGraphs=view==='graphs';const isRules=view==='rules';const isShortcuts=view==='shortcuts';jobsView.hidden=!isJobs;jobsWorkspace.hidden=!isJobs;usageView.hidden=!isUsage;graphsView.hidden=!isGraphs;rulesView.hidden=!isRules;shortcutsView.hidden=!isShortcuts;jobsTab.setAttribute('aria-selected',String(isJobs));usageTab.setAttribute('aria-selected',String(isUsage));graphsTab.setAttribute('aria-selected',String(isGraphs));rulesTab.setAttribute('aria-selected',String(isRules));shortcutsTab.setAttribute('aria-selected',String(isShortcuts));if(isJobs)void refresh();else if(isUsage)void drawUsage();else if(isGraphs)void drawGraphs();else if(isRules)void drawRules();else void drawShortcuts();}
337
+ async function drawQa(){qaStatus.textContent='Loading…';try{const result=await loadJSON('/api/troubleshooter-qa');qaText.value=result.text;qaRevision=result.hash;qaStatus.textContent='Loaded';}catch(error){qaStatus.textContent='Load failed: '+error.message;}}
338
+ async function saveQa(){qaSave.disabled=true;qaStatus.textContent='Saving…';try{const response=await fetch('/api/troubleshooter-qa',{method:'PUT',headers:{'content-type':'application/json'},body:JSON.stringify({text:qaText.value,hash:qaRevision})});if(!response.ok)throw new Error(response.status+' '+await response.text());const saved=await response.json();qaRevision=saved.hash;qaStatus.textContent='Saved to Markdown file';}catch(error){qaStatus.textContent='Save failed: '+error.message;}finally{qaSave.disabled=false;}}
339
+ function showView(view){const isJobs=view==='jobs';const isUsage=view==='usage';const isGraphs=view==='graphs';const isRules=view==='rules';const isShortcuts=view==='shortcuts';const isQa=view==='qa';qaView.hidden=!isQa;qaTab.setAttribute('aria-selected',String(isQa));jobsView.hidden=!isJobs;jobsWorkspace.hidden=!isJobs;usageView.hidden=!isUsage;graphsView.hidden=!isGraphs;rulesView.hidden=!isRules;shortcutsView.hidden=!isShortcuts;jobsTab.setAttribute('aria-selected',String(isJobs));usageTab.setAttribute('aria-selected',String(isUsage));graphsTab.setAttribute('aria-selected',String(isGraphs));rulesTab.setAttribute('aria-selected',String(isRules));shortcutsTab.setAttribute('aria-selected',String(isShortcuts));if(isJobs)void refresh();else if(isUsage)void drawUsage();else if(isGraphs)void drawGraphs();else if(isRules)void drawRules();else if(isQa)void drawQa();else void drawShortcuts();}
322
340
  jobsTab.addEventListener('click',()=>{location.hash=selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs';showView('jobs');});
323
341
  jobsRefresh.addEventListener('click',()=>void refresh(true));
324
342
  eventsAllTasks.addEventListener('click',()=>{eventTaskId=null;clearEvents();renderEventScope();void loadEventHistory();});
@@ -337,15 +355,18 @@ document.querySelector('#graph-entry').addEventListener('click',()=>navigateGrap
337
355
  const graphDialog=document.querySelector('#graph-dialog');const graphViewport=document.querySelector('#graph-dialog-viewport');let graphZoom=1;const graphCanvasHome=graphCanvas.parentNode;function setGraphZoom(value){graphZoom=Math.min(4,Math.max(.35,value));const svg=graphCanvas.querySelector('svg');if(svg){svg.style.width=(Number(svg.getAttribute('width'))||Math.max(900,svg.viewBox.baseVal.width))*graphZoom+'px';svg.style.maxWidth='none';}document.querySelector('#graph-zoom-level').textContent=Math.round(graphZoom*100)+'%';}document.querySelector('#graph-maximize').addEventListener('click',()=>{graphDialog.showModal();graphViewport.append(graphCanvas);document.querySelector('#graph-dialog-title').textContent=graphTitle.textContent;setGraphZoom(1);});function closeGraphViewer(){graphCanvasHome.insertBefore(graphCanvas,graphCanvasHome.querySelector('.graph-source-details'));setGraphZoom(1);}graphDialog.addEventListener('close',closeGraphViewer);document.querySelector('#graph-dialog-close').addEventListener('click',()=>graphDialog.close());document.querySelector('#graph-zoom-in').addEventListener('click',()=>setGraphZoom(graphZoom*1.25));document.querySelector('#graph-zoom-out').addEventListener('click',()=>setGraphZoom(graphZoom/1.25));document.querySelector('#graph-zoom-reset').addEventListener('click',()=>setGraphZoom(1));graphViewport.addEventListener('wheel',event=>{if(!event.ctrlKey&&!event.metaKey)return;event.preventDefault();setGraphZoom(graphZoom*(event.deltaY<0?1.15:1/1.15));},{passive:false});let drag=null;graphViewport.addEventListener('pointerdown',event=>{if(event.target.closest('.drillable'))return;drag={x:event.clientX,y:event.clientY,left:graphViewport.scrollLeft,top:graphViewport.scrollTop};graphViewport.setPointerCapture(event.pointerId);});graphViewport.addEventListener('pointermove',event=>{if(!drag)return;graphViewport.scrollLeft=drag.left-(event.clientX-drag.x);graphViewport.scrollTop=drag.top-(event.clientY-drag.y);});graphViewport.addEventListener('pointerup',()=>drag=null);graphViewport.addEventListener('pointercancel',()=>drag=null);
338
356
  document.querySelector('#graph-back').addEventListener('click',()=>history.back());
339
357
  document.querySelector('#graph-forward').addEventListener('click',()=>history.forward());
340
- window.addEventListener('popstate',()=>{if(graphFromHash()){renderGraphNavigation();showView('graphs');}else if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else showView('jobs');});
341
- window.addEventListener('hashchange',()=>{if(graphFromHash()){renderGraphNavigation();showView('graphs');}else if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else showView('jobs');});
358
+ window.addEventListener('popstate',()=>{if(graphFromHash()){renderGraphNavigation();showView('graphs');}else if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else if(location.hash==='#/troubleshooter-qa')showView('qa');else showView('jobs');});
359
+ window.addEventListener('hashchange',()=>{if(graphFromHash()){renderGraphNavigation();showView('graphs');}else if(location.hash==='#/usage')showView('usage');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else if(location.hash==='#/troubleshooter-qa')showView('qa');else showView('jobs');});
342
360
  rulesTab.addEventListener('click',()=>{location.hash='#/execution-router-rules';showView('rules');});
361
+ qaTab.addEventListener('click',()=>{location.hash='#/troubleshooter-qa';showView('qa');});
362
+ qaSave.addEventListener('click',()=>void saveQa());
363
+ document.querySelector('#qa-reload').addEventListener('click',()=>{if(!qaText.value||confirm('Discard unsaved Q&A edits and reload?'))void drawQa();});
343
364
  shortcutsTab.addEventListener('click',()=>{location.hash='#/shortcuts';showView('shortcuts');});
344
365
  rulesSave.addEventListener('click',()=>void saveRules());
345
366
  rulesTest.addEventListener('click',()=>void testRouter());
346
367
  auditRefresh.addEventListener('click',()=>void drawAudit());
347
368
  shortcutsSave.addEventListener('click',saveShortcuts);
348
- window.addEventListener('keydown',event=>{if(event.key==='Tab'&&event.altKey){event.preventDefault();const views=['jobs','usage','graphs','rules','shortcuts'];const current=location.hash==='#/usage'?'usage':graphFromHash()?'graphs':location.hash==='#/execution-router-rules'?'rules':location.hash==='#/shortcuts'?'shortcuts':'jobs';const next=views[(views.indexOf(current)+1)%views.length];location.hash=next==='usage'?'#/usage':next==='graphs'?'#/graphs':next==='rules'?'#/execution-router-rules':next==='shortcuts'?'#/shortcuts':(selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs');showView(next);}});
369
+ window.addEventListener('keydown',event=>{if(event.key==='Tab'&&event.altKey){event.preventDefault();const views=['jobs','usage','graphs','rules','qa','shortcuts'];const current=location.hash==='#/usage'?'usage':graphFromHash()?'graphs':location.hash==='#/execution-router-rules'?'rules':location.hash==='#/shortcuts'?'shortcuts':location.hash==='#/troubleshooter-qa'?'qa':'jobs';const next=views[(views.indexOf(current)+1)%views.length];location.hash=next==='usage'?'#/usage':next==='graphs'?'#/graphs':next==='rules'?'#/execution-router-rules':next==='shortcuts'?'#/shortcuts':next==='qa'?'#/troubleshooter-qa':(selectedJob?'#/jobs/'+encodeURIComponent(selectedJob):'#/jobs');showView(next);}});
349
370
  function parseMessage(message){try{return JSON.parse(message.data);}catch{return null;}}
350
371
  function isFreshEnough(incoming,current){const a=Date.parse(incoming||''),b=Date.parse(current||'');return !Number.isFinite(b)||!Number.isFinite(a)||a>=b;}
351
372
  function applyStatePatch(patch){if(!patch?.jobId)return false;const job=jobs.find(candidate=>candidate.id===patch.jobId);if(!job)return false;if(patch.jobState&&isFreshEnough(patch.jobUpdatedAt,job.updatedAt)){job['state']=patch.jobState;if(patch.jobUpdatedAt)job.updatedAt=patch.jobUpdatedAt;}if(patch.taskId&&patch.taskState){const task=(job.tasks||[]).find(candidate=>candidate.id===patch.taskId);if(task&&isFreshEnough(patch.taskUpdatedAt,task.updatedAt)){task['state']=patch.taskState;if(patch.taskUpdatedAt)task.updatedAt=patch.taskUpdatedAt;}}return true;}
@@ -355,7 +376,7 @@ stream.onopen=()=>{connection.textContent='';connection.setAttribute('aria-label
355
376
  stream.onerror=()=>{connection.textContent='';connection.setAttribute('aria-label','Event stream reconnecting');connection.className='status-pill warn';};
356
377
  stream.onmessage=message=>{const payload=parseMessage(message);if(payload?.job){upsertFreshJob(payload.job);renderAll();void drawRuntimeSummary();}};
357
378
  stream.addEventListener('relay',message=>{const payload=parseMessage(message);if(payload?.job)upsertFreshJob(payload.job);else if(payload?.statePatch)applyStatePatch(payload.statePatch);if(payload?.event)addEvent(payload.event);renderAll();void drawRuntimeSummary();});
358
- if(location.hash==='#/usage')showView('usage');else if(graphFromHash())showView('graphs');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else showView('jobs');
379
+ if(location.hash==='#/usage')showView('usage');else if(graphFromHash())showView('graphs');else if(location.hash==='#/execution-router-rules')showView('rules');else if(location.hash==='#/shortcuts')showView('shortcuts');else if(location.hash==='#/troubleshooter-qa')showView('qa');else showView('jobs');
359
380
  setInterval(()=>{if(location.hash==='#/usage')void drawUsage();},30000);
360
381
  setInterval(()=>void refreshVersion(),30000);
361
382
  async function refreshSelected(){const job=selected();if(!job){await refresh();return;}selectedJobRefresh.disabled=true;selectedJobRefresh.textContent='Refreshing…';try{const params=new URLSearchParams({id:job.id,repo:job.repository,pr:String(job.prNumber)});const updated=await loadJSON('/api/job?'+params.toString());const index=jobs.findIndex(candidate=>jobSelectionKey(candidate)===jobSelectionKey(job));if(index>=0)jobs[index]=updated;else jobs.push(updated);selectedJob=jobSelectionKey(updated);rememberSelection();renderAll();connection.textContent='';connection.setAttribute('aria-label','Live durable state connected');connection.className='status-pill ok';}catch(error){connection.textContent='';connection.setAttribute('aria-label','Durable state unavailable');connection.className='status-pill bad';}finally{selectedJobRefresh.disabled=false;selectedJobRefresh.textContent='Refresh';}}
package/dist/dashboard.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { inspectWorkflowSymbol } from './dashboard-workflow-inspection.js';
2
2
  import { createServer } from 'node:http';
3
3
  import { subscribeLocalCommits } from './local-commit-events.js';
4
+ import { handleTroubleshooterQa } from './troubleshooter-qa-api.js';
4
5
  import { readFile, readdir } from 'node:fs/promises';
5
6
  import { join } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
@@ -192,6 +193,10 @@ export function serveDashboard(store, port = 8787, bus, refreshMs = 300000, jobI
192
193
  }
193
194
  return;
194
195
  }
196
+ if (req.url === '/api/troubleshooter-qa') {
197
+ await handleTroubleshooterQa(req, res);
198
+ return;
199
+ }
195
200
  if (req.url === '/api/graphs') {
196
201
  try {
197
202
  const graphs = await dashboardWorkflowGraphs();
@@ -0,0 +1,61 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { createHash } from 'node:crypto';
3
+ import { fileURLToPath } from 'node:url';
4
+ const FILE = fileURLToPath(new URL('../skills/agents-relay/troubleshooter-qa.md', import.meta.url));
5
+ const qaHash = (content) => createHash('sha256').update(content).digest('hex');
6
+ export async function handleTroubleshooterQa(req, res) {
7
+ try {
8
+ const current = await readFile(FILE, 'utf8');
9
+ if (req.method === 'GET') {
10
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
11
+ res.end(JSON.stringify({ text: current, hash: qaHash(current) }));
12
+ return;
13
+ }
14
+ if (req.method === 'PUT') {
15
+ if (req.headers['content-type']?.split(';')[0] !== 'application/json') {
16
+ res.writeHead(415);
17
+ res.end('Expected JSON');
18
+ return;
19
+ }
20
+ const chunks = [];
21
+ let size = 0;
22
+ for await (const chunk of req) {
23
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
24
+ size += buffer.length;
25
+ if (size > 128 * 1024) {
26
+ res.writeHead(413);
27
+ res.end('Q&A too large');
28
+ return;
29
+ }
30
+ chunks.push(buffer);
31
+ }
32
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
33
+ if (!body || typeof body !== 'object' || !('text' in body) || !('hash' in body) || typeof body.text !== 'string' || typeof body.hash !== 'string') {
34
+ res.writeHead(400);
35
+ res.end('Invalid Q&A payload');
36
+ return;
37
+ }
38
+ if (body.hash !== qaHash(current)) {
39
+ res.writeHead(409);
40
+ res.end('Q&A changed on disk; reload before saving');
41
+ return;
42
+ }
43
+ if (Buffer.byteLength(body.text) > 128 * 1024) {
44
+ res.writeHead(413);
45
+ res.end('Q&A too large');
46
+ return;
47
+ }
48
+ await writeFile(FILE, body.text, 'utf8');
49
+ res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
50
+ res.end(JSON.stringify({ hash: qaHash(body.text) }));
51
+ return;
52
+ }
53
+ res.writeHead(405, { allow: 'GET, PUT' });
54
+ res.end('Method not allowed');
55
+ }
56
+ catch (error) {
57
+ res.writeHead(500);
58
+ res.end(error instanceof Error ? error.message : String(error));
59
+ }
60
+ return;
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-relay",
3
- "version": "2.0.153",
3
+ "version": "2.0.154",
4
4
  "description": "Durable async agent jobs coordinated through GitHub pull requests",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,6 +15,8 @@ Your goal is not to explain the failure. Your goal is to make the original task
15
15
 
16
16
  Read and follow the shared Task / Job contract and orchestrator P0/self-job workflow before acting.
17
17
 
18
+ Read `skills/agents-relay/troubleshooter-qa.md` for user-editable best practices before diagnosis. Treat its answers as guidance only, below current evidence and authoritative contracts. After a verified recovery, add or refine a concise Q&A entry when it provides reusable knowledge; never include secrets or unverified claims.
19
+
18
20
  ## Recovery loop
19
21
 
20
22
  1. Retrieve the durable original Job and Task from the authoritative Relay store with `agents-relay task context`; GitHub is only an optional projection and is never lifecycle authority.
@@ -0,0 +1,25 @@
1
+ # Troubleshooter Q&A
2
+
3
+ A simple, user-editable reference for recurring failures and proven recovery practices.
4
+
5
+ Troubleshooter: read this file before diagnosis, but always inspect current task events and logs. Answers are guidance, not authority to retry, unblock, change routing, or change workflow state. The canonical workflow graphs and contracts remain authoritative.
6
+
7
+ Users can add or edit entries directly. After a recovery is verified, the Troubleshooter may add a short entry or improve an existing answer. Do not write secrets, tokens, or private log contents here. Keep unverified hypotheses clearly labeled.
8
+
9
+ ## Q: Browser Workspace validation says COMPOSER_NOT_READY. Does that mean the user is logged out?
10
+
11
+ **A:** No. This code reports UI readiness, not authentication. Use the platform's validated session and inspect worker authentication and delivery evidence separately. Do not infer login failure from a disabled Send button or DOM snapshot.
12
+
13
+ ## Q: A worker fails or times out. Should Troubleshooter immediately retry?
14
+
15
+ **A:** No. Read the original task's correlated lifecycle events, attempts, routing, and relevant logs. Diagnose the actual cause first. Only recover the original Task after verifying that the blocker is resolved, using the canonical recovery path.
16
+
17
+ ## Q: A shared runtime or tool defect blocks several tasks. What should Troubleshooter do?
18
+
19
+ **A:** Reuse an existing blocker Job when possible. Otherwise create the owning P0/self-job through Agents Relay. Verify the repair and any required release or deployment before recovering dependent tasks. For deferred repair, register the durable blocker relation instead of keeping a worker alive.
20
+
21
+ ## Q: When can a BLOCKED task resume?
22
+
23
+ **A:** Only after an authoritative unblock operation or verified blocker-Job resolution. A Q&A answer cannot unblock a task.
24
+
25
+ <!-- Add more entries below using: ## Q: ... / **A:** ... -->