ask_operator: ttl_seconds auto-cancel + remaining-time chip
manager can pass ttl_seconds to ask_operator. on submit, host
stores deadline_at = now + ttl in operator_questions (new column,
migrated via existing pragma_table_info pattern), spawns a tokio
task that sleeps until the deadline then resolves the question with
answer '[expired]' and fires the same OperatorAnswered helper event.
already-resolved races no-op silently.
dashboard renders a '⏳ MM:SS' chip on the question row when
deadline_at is set. format collapses seconds → s, < 1h → m s, ≥ 1h
→ h m. heartbeat refresh (5s) keeps the chip current; the operator
sees it tick down.
manager prompt + mcp tool description updated. journald viewer per
container queued in todo (separate task).
This commit is contained in:
parent
2146e47770
commit
754db7830e
8 changed files with 133 additions and 36 deletions
|
|
@ -237,14 +237,23 @@
|
|||
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 head = 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:'),
|
||||
);
|
||||
if (q.deadline_at) {
|
||||
const remaining = q.deadline_at - Math.floor(Date.now() / 1000);
|
||||
let txt;
|
||||
if (remaining <= 0) txt = 'expiring…';
|
||||
else if (remaining < 60) txt = '⏳ ' + remaining + 's';
|
||||
else if (remaining < 3600) txt = '⏳ ' + Math.floor(remaining / 60) + 'm '
|
||||
+ (remaining % 60) + 's';
|
||||
else txt = '⏳ ' + Math.floor(remaining / 3600) + 'h '
|
||||
+ Math.floor((remaining % 3600) / 60) + 'm';
|
||||
head.append(' ', el('span', { class: 'q-ttl' }, txt));
|
||||
}
|
||||
li.append(head, el('div', { class: 'q-body' }, q.question));
|
||||
const f = el('form', {
|
||||
method: 'POST', action: '/answer-question/' + q.id,
|
||||
class: 'qform', 'data-async': '',
|
||||
|
|
|
|||
|
|
@ -296,6 +296,12 @@ summary:hover { color: var(--purple); }
|
|||
}
|
||||
.questions li.question:last-child { border-bottom: 0; }
|
||||
.questions .q-head { font-size: 0.9em; }
|
||||
.questions .q-ttl {
|
||||
color: var(--amber);
|
||||
margin-left: 0.4em;
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.questions .q-body {
|
||||
color: var(--fg);
|
||||
margin: 0.3em 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue