Skip to content

feat(mod): report unanswered worker turns and name the conversation on state reports - #67

Merged
omartelo merged 5 commits into
mainfrom
feat/worker-unanswered-turn
Oct 9, 2026
Merged

omartelo merged 5 commits into
mainfrom
feat/worker-unanswered-turn

Conversation

@omartelo

@omartelo omartelo commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Implements the plugin side of two lich contracts merged on lich main: lichdotdev/lich#705 (unanswered turns, 19e808e4) and lichdotdev/lich#709 (provider_session_id, ce8c36a6). Fixtures are vendored byte-identical from those merge commits; both are in no lich release yet, so refresh-fixtures.sh pins them by commit until one ships. No version bump.

Unanswered turns (hooks/worker-answer.js)

  • A worker's final turn that ends blank, or with turn.complete reason refusal, and with background_tasks empty, posts {session_id, unanswered: "blank" | "refusal"} to /mod/answer, so the caller stops waiting.
  • An aborted turn posts nothing.
  • error stays pending: StopFailure carries no background_tasks (measured on Claude Code 2.1.296), so the mod cannot tell nothing is left running, and it does not guess.

provider_session_id on every state and tool report

  • Every busy/done/tool report names the conversation it comes from, so lich can drop reports from an agent CLI the session runs as a tool (claude -p, cursor-agent -p), which inherits the session's LICH_* variables.
  • Sent by the scripts, opencode, omp and the compacting mod; omitted when the harness did not hand it over.
  • report-state.sh now reads stdin for every state, bounded to one second so an unwritten pipe cannot hold the turn.

Shared hooks/conversation-id.sh

  • One sourced helper reads the conversation id for the three scripts that need it, instead of each parsing the payload.
  • If the file is missing, each script keeps its current behavior (reports without provider_session_id; session-start is not reported) and writes one line to stderr naming the file and what is lost. A test covers it.

Checks

  • node --test tests/*.test.mjs: 393 pass
  • claude plugin test .: 32 pass
  • claude plugin validate: passes
  • fixtures drift check: refresh-fixtures.sh leaves tests/fixtures unchanged

A lich subagent worker whose final turn ended blank or refused posted
nothing, so its caller waited on an errand that would never be answered.
The mod now posts {session_id, unanswered: blank|refusal} to /mod/answer
when the turn's own main-loop Stop lists no background task. An aborted
turn still posts nothing.

unanswered: error stays unsent: an API error fires StopFailure instead of
Stop, and in Claude Code 2.1.296 neither StopFailure's input nor
turn.complete's carries background_tasks, so the mod cannot tell the
failed turn left nothing running.

turn.complete is now matched once per reason, so every turn drops the
Stop it read. mod-answer.jsonl is read at lich's unreleased 040195f5.
An agent CLI run as a tool inside a lich session (claude -p,
cursor-agent -p) inherits LICH_* and its hooks reported as the host card:
a nested cursor-agent -p SessionEnd closed the host's relay errands. lich
now drops a state report whose provider_session_id is not the bound one.

Every /hook report now carries provider_session_id when the harness hands
one over: report-state.sh and report-tool.sh read .conversationId //
.session_id off stdin (sed fallback without jq), opencode the event's
sessionID, omp ctx.sessionManager.getSessionId(), and mod-compacting
$.session.id(). report-state.sh reads stdin for every state now, bounded
at one second so a pipe nobody writes to cannot hold the turn.

session-state.jsonl is read at lich's unreleased faa40def.
report-session-start.sh, report-state.sh and report-tool.sh each parsed
.conversationId // .session_id with the same sed fallback. They now source
hooks/conversation-id.sh and call conversation_id on the payload. A script
shipped without the helper still reports, without the conversation.
A file-shipped install without the helper ran on silently. Each script now
writes one line naming the missing file and what is lost, then reports as
before: state and tool without provider_session_id, session-start not at all.
@omartelo
omartelo merged commit e232f65 into main Oct 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant