summaryrefslogtreecommitdiff
path: root/web/app.js
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-03-12 01:48:15 +0000
committerClaudomator Agent <agent@claudomator>2026-03-12 01:48:15 +0000
commitf28c22352aa1a8ede7552ee0277f7d60552d9094 (patch)
tree02e9085809462e09b7d2a68ebb754e247eaecd22 /web/app.js
parent4f83d35fa47bc71b31e0f92a0927bea8910c01b6 (diff)
feat: add Resume support for CANCELLED, FAILED, and BUDGET_EXCEEDED tasks
Interrupted tasks (CANCELLED, FAILED, BUDGET_EXCEEDED) now support session resume in addition to restart. Both buttons are shown on the task card. - executor: extend resumablePoolStates to include CANCELLED, FAILED, BUDGET_EXCEEDED - api: extend handleResumeTimedOutTask to accept all resumable states with state-specific resume messages; replace hard-coded TIMED_OUT check with a resumableStates map - web: add RESUME_STATES set; render Resume + Restart buttons for interrupted states; TIMED_OUT keeps Resume only - tests: 5 new Go tests (TestResumeInterrupted_*); updated task-actions.test.mjs with 17 tests covering dual-button behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web/app.js')
-rw-r--r--web/app.js84
1 files changed, 66 insertions, 18 deletions
diff --git a/web/app.js b/web/app.js
index 4652707..dddaeab 100644
--- a/web/app.js
+++ b/web/app.js
@@ -119,8 +119,11 @@ function createTaskCard(task) {
}
// Footer: action buttons based on state
- const RESTART_STATES = new Set(['FAILED', 'CANCELLED', 'BUDGET_EXCEEDED']);
- if (task.state === 'PENDING' || task.state === 'RUNNING' || task.state === 'READY' || task.state === 'BLOCKED' || task.state === 'TIMED_OUT' || RESTART_STATES.has(task.state)) {
+ // Interrupted states (CANCELLED, FAILED, BUDGET_EXCEEDED) show both Resume and Restart.
+ // TIMED_OUT shows Resume only. Others show a single action.
+ const RESUME_STATES = new Set(['TIMED_OUT', 'CANCELLED', 'FAILED', 'BUDGET_EXCEEDED']);
+ const RESTART_STATES = new Set(['CANCELLED', 'FAILED', 'BUDGET_EXCEEDED']);
+ if (task.state === 'PENDING' || task.state === 'RUNNING' || task.state === 'READY' || task.state === 'BLOCKED' || RESUME_STATES.has(task.state)) {
const footer = document.createElement('div');
footer.className = 'task-card-footer';
@@ -161,24 +164,25 @@ function createTaskCard(task) {
footer.appendChild(rejectBtn);
} else if (task.state === 'BLOCKED') {
renderQuestionFooter(task, footer);
- } else if (task.state === 'TIMED_OUT') {
- const btn = document.createElement('button');
- btn.className = 'btn-resume';
- btn.textContent = 'Resume';
- btn.addEventListener('click', (e) => {
+ } else if (RESUME_STATES.has(task.state)) {
+ const resumeBtn = document.createElement('button');
+ resumeBtn.className = 'btn-resume';
+ resumeBtn.textContent = 'Resume';
+ resumeBtn.addEventListener('click', (e) => {
e.stopPropagation();
- handleResume(task.id, btn, footer);
+ handleResume(task.id, resumeBtn, footer);
});
- footer.appendChild(btn);
- } else if (RESTART_STATES.has(task.state)) {
- const btn = document.createElement('button');
- btn.className = 'btn-restart';
- btn.textContent = 'Restart';
- btn.addEventListener('click', (e) => {
- e.stopPropagation();
- handleRestart(task.id, btn, footer);
- });
- footer.appendChild(btn);
+ footer.appendChild(resumeBtn);
+ if (RESTART_STATES.has(task.state)) {
+ const restartBtn = document.createElement('button');
+ restartBtn.className = 'btn-restart';
+ restartBtn.textContent = 'Restart';
+ restartBtn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ handleRestart(task.id, restartBtn, footer);
+ });
+ footer.appendChild(restartBtn);
+ }
}
card.appendChild(footer);
@@ -1292,6 +1296,50 @@ function renderTaskPanel(task, executions) {
const content = document.getElementById('task-panel-content');
content.innerHTML = '';
+ // ── Summary ──
+ if (task.summary) {
+ const summarySection = makeSection('Summary');
+ const summaryEl = document.createElement('p');
+ summaryEl.className = 'task-summary';
+ summaryEl.textContent = task.summary;
+ summarySection.appendChild(summaryEl);
+ content.appendChild(summarySection);
+ }
+
+ // ── Q&A History ──
+ if (task.interactions && task.interactions.length > 0) {
+ const qaSection = makeSection('Q&A History');
+ const qaList = document.createElement('div');
+ qaList.className = 'qa-list';
+ for (const interaction of task.interactions) {
+ const qaItem = document.createElement('div');
+ qaItem.className = 'qa-item';
+
+ const qEl = document.createElement('div');
+ qEl.className = 'qa-question';
+ qEl.textContent = interaction.question_text || '(question)';
+ qaItem.appendChild(qEl);
+
+ if (interaction.options && interaction.options.length > 0) {
+ const opts = document.createElement('div');
+ opts.className = 'qa-options';
+ opts.textContent = 'Options: ' + interaction.options.join(', ');
+ qaItem.appendChild(opts);
+ }
+
+ if (interaction.answer) {
+ const aEl = document.createElement('div');
+ aEl.className = 'qa-answer';
+ aEl.textContent = interaction.answer;
+ qaItem.appendChild(aEl);
+ }
+
+ qaList.appendChild(qaItem);
+ }
+ qaSection.appendChild(qaList);
+ content.appendChild(qaSection);
+ }
+
// ── Overview ──
const overview = makeSection('Overview');
const overviewGrid = document.createElement('div');