mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-09 05:19:33 -10:00
* fix(hooks): dispatch specialistStart/Stop and allow matchers These events were documented and accepted by hooks add, but never fired. Wire advisory dispatch from specialist accounting and allow specialist name matchers so EXTENDING.md matches runtime behavior. * fix(hooks): unlock before specialistStop hooks and fire once Release accountingMu before running lifecycle hooks, and dispatch specialistStop only when the stop event is newly recorded so concurrent finishers cannot re-run arbitrary hook commands. * fix(hooks): fire specialistStop without relying on session append Decouple stop-hook dispatch from parent-session event append so swarm members (empty parent) and TaskOutput still get exactly one hook via a shared LifecycleHooks claim, and wire that bridge into TaskOutput. * fix(hooks): claim specialistStop durably and gate hooks on real launches Address the review findings on specialist lifecycle dispatch: - specialistStop is now claimed in the parent session's event log via a record-once specialist_stop_hook event, sharing the accounting run-ID equivalence (empty runId is a catch-all). A later reaper in another process (TaskOutput poll, recovered TaskStop, restart) no longer re-runs the hook. The in-process claim remains only as fallback for swarm members with no parent session and for session-store errors. - Recovered TaskStop records the terminal transition after Kill, so an explicitly stopped recovered task emits specialistStop exactly once. - specialistStart fires only after the child process actually started (onStarted after Start()); a launch that fails before exec records attempt accounting but no public start/stop hooks. Reapers derive the same eligibility from the durable stop event's childStarted flag. Adds a CLI startup wire-up test asserting both real entry points (interactive TUI, headless exec) attach the lifecycle bridge to the same LifecycleHooks the TaskOutput tool holds, fixes the stale import path on the rebased branch, and documents the once-per-run semantics. * fix(hooks): gate background exit accounting behind the start decision A background child that exits immediately can run its launcher exit callback before Executor.LaunchBackground returns, so the terminal accounting could claim and dispatch specialistStop before runBackground dispatched specialistStart, and the recorded stop missed the pid that SetPID had not stored yet. The exit callback now waits on a settled channel closed when runBackground returns (after the start hook and SetPID), from its own goroutine so a synchronous injected launcher cannot deadlock waiting for a signal only the launch return can close. Regression test: TestRunBackgroundDefersExitUntilStartDecisionCompletes drives a synchronous fast-exit launcher and asserts start-before-stop hook order, the recorded stop pid, and no deadlock. It fails on the previous code with hooks "specialistStop,specialistStart". * fix(hooks): record specialistStop only after the orphan kill succeeds When SetPID fails after a background child has started, runBackground kills the unrecorded child and records the stop. If TerminateProcess fails, the child may still be running, but the branch still marked the task errored, recorded the stop, took the durable specialist_stop_hook claim and fired specialistStop. The hook could then run while the sub-agent was alive, and the one-shot claim suppressed the hook on the child's real exit. Marking the task non-running also let a TaskOutput or TaskStop reap claim the stop before the launcher's Wait observed the exit. On a failed kill, surface the joined error and return without the status update or the stop. The task stays running, so reapers remain no-ops, and the launcher's exit callback marks it exited and records the single stop when the child ends. A successful kill keeps the immediate stop as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(hooks): keep the prompt on a failed orphan kill and share one stop claim When SetPID fails and terminating the unrecorded child also fails, the child may still be running and the task stays running. That branch still deleted the --file prompt, so a child that had not yet read it failed at startup. The launcher's exit callback already removes the prompt once the child really exits, and Runtime.Close removes tracked prompts at shutdown, so the failed-kill branch no longer removes it. dispatchSpecialistStopHook made two independent dispatch decisions: the durable specialist_stop_hook claim, and the in-process claim used when the session store errors. A won durable claim never recorded the in-process claim, so on one bridge a transient store error before or after a durable claim dispatched the same stop twice, breaking the documented at-most-once per process. A won durable claim now also takes the in-process claim (including for an ineligible stop), and the hook runs only if both are free. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Ayush7614 <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>