ask_operator tool: non-blocking; operator answer arrives as helper event
new mcp tool on the manager surface that queues a question on the
dashboard and returns the question id immediately. operator submits an
answer via /answer-question/<id>; the dashboard fires
HelperEvent::OperatorAnswered { id, question, answer } into the manager
inbox so the next turn picks it up.
also: fix async-form button stuck on spinner after successful submit
(refreshState skipped re-rendering, so the button was never re-enabled).
This commit is contained in:
parent
abfd2cce4b
commit
2770630f33
17 changed files with 426 additions and 79 deletions
|
|
@ -58,6 +58,12 @@
|
|||
if (btn) { btn.disabled = false; btn.innerHTML = original; }
|
||||
return;
|
||||
}
|
||||
// Re-enable the button — refreshState() rebuilds most lists but
|
||||
// skips forms that didn't change (e.g. the spawn form), so without
|
||||
// this the spinner sticks and the button can't be clicked again.
|
||||
if (btn) { btn.disabled = false; btn.innerHTML = original; }
|
||||
// Clear text inputs whose value was just submitted.
|
||||
f.querySelectorAll('input[type="text"], input:not([type]), textarea').forEach((i) => { i.value = ''; });
|
||||
refreshState();
|
||||
} catch (err) {
|
||||
alert('action failed: ' + err);
|
||||
|
|
@ -170,6 +176,49 @@
|
|||
root.append(ul);
|
||||
}
|
||||
|
||||
function renderQuestions(s) {
|
||||
const root = $('questions-section');
|
||||
root.innerHTML = '';
|
||||
if (!s.questions || !s.questions.length) {
|
||||
root.append(el('p', { class: 'empty' }, '▓ no pending questions ▓'));
|
||||
return;
|
||||
}
|
||||
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
|
||||
const ul = el('ul', { class: 'questions' });
|
||||
for (const q of s.questions) {
|
||||
const li = el('li', { class: 'question' });
|
||||
li.append(
|
||||
el('div', { class: 'q-head' },
|
||||
el('span', { class: 'msg-ts' }, fmt(q.asked_at)), ' ',
|
||||
el('span', { class: 'msg-from' }, q.asker), ' ',
|
||||
el('span', { class: 'msg-sep' }, 'asks:'),
|
||||
),
|
||||
el('div', { class: 'q-body' }, q.question),
|
||||
);
|
||||
const f = el('form', {
|
||||
method: 'POST', action: '/answer-question/' + q.id,
|
||||
class: 'qform', 'data-async': '',
|
||||
});
|
||||
let input;
|
||||
if (q.options && q.options.length) {
|
||||
input = el('select', { name: 'answer', required: '' });
|
||||
input.append(el('option', { value: '', disabled: '', selected: '' }, 'choose…'));
|
||||
for (const opt of q.options) {
|
||||
input.append(el('option', { value: opt }, opt));
|
||||
}
|
||||
} else {
|
||||
input = el('input', {
|
||||
name: 'answer', type: 'text', required: '',
|
||||
placeholder: 'your answer', autocomplete: 'off',
|
||||
});
|
||||
}
|
||||
f.append(input, el('button', { type: 'submit', class: 'btn btn-approve' }, '▸ ANSW3R'));
|
||||
li.append(f);
|
||||
ul.append(li);
|
||||
}
|
||||
root.append(ul);
|
||||
}
|
||||
|
||||
function renderInbox(s) {
|
||||
const root = $('inbox-section');
|
||||
root.innerHTML = '';
|
||||
|
|
@ -250,6 +299,7 @@
|
|||
if (!resp.ok) throw new Error('http ' + resp.status);
|
||||
const s = await resp.json();
|
||||
renderContainers(s);
|
||||
renderQuestions(s);
|
||||
renderInbox(s);
|
||||
renderApprovals(s);
|
||||
// Auto-refresh while a spawn is in flight; otherwise back off.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue