summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/api/taskops.go7
-rw-r--r--web/app.js9
-rw-r--r--web/test/stats.test.mjs16
3 files changed, 30 insertions, 2 deletions
diff --git a/internal/api/taskops.go b/internal/api/taskops.go
index 254d73c..e6b6239 100644
--- a/internal/api/taskops.go
+++ b/internal/api/taskops.go
@@ -110,7 +110,12 @@ func (s *Server) submitTask(ctx context.Context, spec submitTaskSpec) (*task.Tas
return nil, err
}
t.State = task.StateQueued
- if err := s.pool.Submit(ctx, t); err != nil {
+ // Use the server's long-lived lifecycle context, not the caller's request
+ // context — the request (REST or chatbot MCP tool call) returns long
+ // before the task finishes running, and its context is cancelled when it
+ // does. Submitting with it would cancel the task's execution moments
+ // after dispatch. Mirrors the pattern at server.go's other Submit call sites.
+ if err := s.pool.Submit(s.ctx, t); err != nil {
return nil, err
}
return t, nil
diff --git a/web/app.js b/web/app.js
index fc6864c..2f5aacf 100644
--- a/web/app.js
+++ b/web/app.js
@@ -665,7 +665,14 @@ export function computeExecutionStats(executions) {
for (const e of executions) {
const outcome = e.state || 'unknown';
byOutcome[outcome] = (byOutcome[outcome] || 0) + 1;
- if (outcome === 'completed') completed++;
+ // Executions carry the task state machine's uppercase values (see
+ // internal/executor.handleRunResult): a top-level task (no parent) that
+ // succeeds lands its execution at READY, not COMPLETED — COMPLETED is
+ // only set for subtask executions. Both represent a successful run from
+ // the executor's perspective; only the human/chatbot accept step (a
+ // separate, later transition) turns READY into COMPLETED.
+ const normalized = outcome.toUpperCase();
+ if (normalized === 'COMPLETED' || normalized === 'READY') completed++;
totalCost += e.cost_usd || 0;
if (e.duration_ms != null) {
durationSum += e.duration_ms;
diff --git a/web/test/stats.test.mjs b/web/test/stats.test.mjs
index a7fe657..19f2b1e 100644
--- a/web/test/stats.test.mjs
+++ b/web/test/stats.test.mjs
@@ -150,4 +150,20 @@ describe('computeExecutionStats', () => {
assert.equal(stats.byOutcome.failed, 1);
assert.equal(stats.byOutcome.cancelled, 1);
});
+
+ // READY is the execution state for a successful top-level (no parent) task
+ // run — see internal/executor.handleRunResult. COMPLETED is only reached
+ // after a separate human/chatbot accept step, so READY must count as
+ // success here or every top-level task's success rate reads as 0.
+ it('counts READY executions as successful', () => {
+ const execs = [makeExec('READY'), makeExec('READY'), makeExec('FAILED')];
+ const stats = computeExecutionStats(execs);
+ assert.equal(stats.successRate, 2 / 3);
+ });
+
+ it('counts real-world uppercase state values the same as lowercase fixtures', () => {
+ const execs = [makeExec('COMPLETED'), makeExec('READY'), makeExec('FAILED'), makeExec('CANCELLED')];
+ const stats = computeExecutionStats(execs);
+ assert.equal(stats.successRate, 0.5);
+ });
});