fix: publish and replace Claude goals reliably (#967)

## Summary

- publish optimistic provider-neutral goal snapshots for submitted and
steered goal commands
- advertise and implement `set`, using steering for an active turn and
an ordinary prompt while idle
- suppress stale old-goal runtime updates while replacement/clear is
pending and roll back failed commands
- keep cancellation turn-scoped and orphan-account pending steers so
stale results cannot settle the next prompt
- reject unsupported pause/resume controls without host-side fallbacks

## Validation

- `npm test -- --run` (702 passed, 20 skipped)
- `npm run check`
- `npm run build`
- regression coverage for idle/active set and clear, stale runtime
updates, command failure rollback, cancellation, and stale steer
isolation
This commit is contained in:
nikita-ashihmin
2026-08-07 17:01:18 +04:00
committed by GitHub
parent 133337ffe5
commit f8fd3ab822
4 changed files with 671 additions and 24 deletions

View File

@@ -52,4 +52,6 @@ This separation prevents a persistent goal from monopolizing the session's promp
Claude's session-scoped `/goal` command installs a Stop hook. The runtime emits internal `active_goal` messages when that hook updates or clears its state; the adapter maps them into the neutral snapshot before the exhaustive SDK message switch. `condition`, `iterations`, `set_at`, and `last_reason` become `objective`, `iterations`, `createdAt`, and `lastReason`. Provider-only bookkeeping such as `tokens_at_start` is not exposed.
Only `clear` is advertised for out-of-band control. The adapter submits `/goal clear` through the existing session prompt queue, preserving the same ordering and lifecycle rules as other Claude commands. Setting a goal remains available through `/goal <condition>` and is not advertised as an extension action because it starts Claude's long-running Stop-hook workflow.
Some Claude runtime versions persist the goal and begin the Stop-hook workflow without emitting the initial `active_goal` message on the SDK stream. To avoid leaving clients without visible state for the whole long-running prompt, the adapter publishes a minimal active snapshot as soon as it submits `/goal <condition>`, and publishes `goal: null` for `/goal clear`. While that command is pending, late updates for the previous goal are ignored. A matching runtime `active_goal` remains authoritative and enriches the optimistic snapshot with iteration, reason, and timestamp fields; a failed command restores the previously published state.
`set` and `clear` are advertised for out-of-band control; pause and resume are not. When a goal turn is running, the adapter injects the corresponding `/goal` command at steering priority `now` so it cannot remain queued behind the Stop-hook workflow; on an idle session it uses the ordinary prompt lifecycle. Cancelling a turn does not clear the session-scoped goal: clients use the goal control method when they intend to replace or remove it.