diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d44b5b..cf9e89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **A lich worker that pauses on a background command is no longer reported as + finished.** A worker whose turn ended before it reported, usually waiting on a + command it left running, now comes back as a backgrounded agent, and its + report still arrives as a [lich] note. + ## [0.15.0] - 2026-10-05 ### Added diff --git a/docs/agent-cards.md b/docs/agent-cards.md index f3cce3b..3d92dfd 100644 --- a/docs/agent-cards.md +++ b/docs/agent-cards.md @@ -56,7 +56,7 @@ Nothing runs after that: the delivery `lich open` printed decides the answer. |-------------------------------|---------------------------------------------------------------------| | `pending` | `async_launched` at once, plus a note on how the report comes back; the worker is counted in the status line | | `answered` | `completed`: the report, then the checkout (and branch) it is on | -| `unanswered` | `completed`: the worker ended its turn without reporting, and where to look | +| `unanswered` | `async_launched`, as for `pending`: a turn that ended unanswered usually left a background command running, and the report still arrives as a [lich] note | | `unread`, `undelivered` | denied: the task never reached the card | | a status it does not know | denied, naming the card and its branch | | the open failed | the native agent runs, with a toast saying why | diff --git a/hooks/agent-cards.js b/hooks/agent-cards.js index a31e761..7a1dffc 100644 --- a/hooks/agent-cards.js +++ b/hooks/agent-cards.js @@ -248,7 +248,11 @@ function completed(e, opened, branch, text, durationMs) { */ function answerFor(e, opened, branch, report, durationMs) { switch (report.status) { + // A worker whose turn ended unanswered is usually still at it: it left a + // command running in the background and resumes when it finishes, and its + // report still arrives as a [lich] note. case "pending": + case "unanswered": return backgrounded(e, opened, branch) case "answered": { const where = @@ -258,12 +262,6 @@ function answerFor(e, opened, branch, report, durationMs) { `Reach that session with send_to_session or lich send, not SendMessage.` return { result: completed(e, opened, branch, `${report.answer}\n\n${where}`, durationMs) } } - case "unanswered": { - const text = - `"${opened.label}" ended its turn without reporting back through lich. What it did is on its card and ` + - `${branch ? `on branch ${branch}` : "in this checkout"}; a report it sends later arrives here as a [lich] note.` - return { result: completed(e, opened, branch, text, durationMs) } - } case "unread": case "undelivered": return { deny: `the task never reached "${opened.label}" (${report.status}): open its card.` } diff --git a/tests/agent-cards.test.mjs b/tests/agent-cards.test.mjs index 5c7d750..3a5003b 100644 --- a/tests/agent-cards.test.mjs +++ b/tests/agent-cards.test.mjs @@ -368,24 +368,16 @@ test('an isolated task still pending at the open says which branch the worker is assert.deepEqual(answer, backgrounded()) }) -test('a worker that ended its turn without reporting is a completed subagent saying so', async () => { - const { answer } = await delegate([exits(3, openedHere('unanswered'))]) - assert.deepEqual( - answer, - completed( - `"${SHARED.label}" ended its turn without reporting back through lich. What it did is on its card and in ` + - `this checkout; a report it sends later arrives here as a [lich] note.`, - { worker: SHARED }, - ), - ) +// A worker whose turn ended before it reported is usually still working: it +// left a command running in the background and resumes when it finishes. Its +// report still arrives as a [lich] note, so the call is a backgrounded agent, +// never a finished one. +test('a worker that ended its turn without reporting comes back as a backgrounded agent', async () => { + const { answer, passed } = await delegate([exits(3, openedHere('unanswered'))]) + assert.deepEqual(answer, backgrounded({ worker: SHARED })) + assert.deepEqual(passed, []) const isolated = await delegate([onBranch('main'), exits(3, opened('unanswered'))], ISOLATED) - assert.deepEqual( - isolated.answer, - completed( - `"${BRANCH}" ended its turn without reporting back through lich. What it did is on its card and on branch ` + - `${BRANCH}; a report it sends later arrives here as a [lich] note.`, - ), - ) + assert.deepEqual(isolated.answer, backgrounded()) }) test('a task that never reached the worker is denied, never run natively', async () => {