Repository navigation #
Releases · enes-alatas/spool
Release list #
v0.6.0 #
What's Changed #
- feat(users): the hub has users who sign in with a username and password by [@enes-alatas](https://github.com/enes-alatas) in[#676](https://github.com/enes-alatas/spool/pull/676)
- feat(web): users sign in with a username and password and change a one-time password by [@enes-alatas](https://github.com/enes-alatas) in[#678](https://github.com/enes-alatas/spool/pull/678)
- feat(httpapi): roles bound what a user may call, and members read no owner DM by [@enes-alatas](https://github.com/enes-alatas) in[#681](https://github.com/enes-alatas/spool/pull/681)
- docs(readme): the README names what v0.5.1 ships, and every release gets a README pass by [@enes-alatas](https://github.com/enes-alatas) in[#687](https://github.com/enes-alatas/spool/pull/687)
- feat(web): a member sees only the actions their role allows by [@enes-alatas](https://github.com/enes-alatas) in[#688](https://github.com/enes-alatas/spool/pull/688)
- feat(loop): the prompt no longer calls control_room private by [@enes-alatas](https://github.com/enes-alatas) in[#683](https://github.com/enes-alatas/spool/pull/683)
- fix(httpapi): a member reads no pending sender's pairing code by [@enes-alatas](https://github.com/enes-alatas) in[#692](https://github.com/enes-alatas/spool/pull/692)
- feat(httpapi): owners manage the hub's users on the API by [@enes-alatas](https://github.com/enes-alatas) in[#693](https://github.com/enes-alatas/spool/pull/693)
- feat(httpapi): the hub records who changed a loop by [@enes-alatas](https://github.com/enes-alatas) in[#697](https://github.com/enes-alatas/spool/pull/697)
- feat(hook): spool-hook refuses a gh or git call that carries a credential by [@enes-alatas](https://github.com/enes-alatas) in[#699](https://github.com/enes-alatas/spool/pull/699)
- feat(hook): spool-hook refuses kills that reach other loops' processes by [@enes-alatas](https://github.com/enes-alatas) in[#701](https://github.com/enes-alatas/spool/pull/701)
- docs(readme): the demo video shows v0.5.1's room by [@enes-alatas](https://github.com/enes-alatas) in[#694](https://github.com/enes-alatas/spool/pull/694)
- feat(route): every allowed sender holds a private conversation on dm: by [@enes-alatas](https://github.com/enes-alatas) in[#705](https://github.com/enes-alatas/spool/pull/705)
- feat(web): an owner manages the hub's users from Settings by [@enes-alatas](https://github.com/enes-alatas) in[#706](https://github.com/enes-alatas/spool/pull/706)
- feat(web): the room shows who created and changed a loop by [@enes-alatas](https://github.com/enes-alatas) in[#717](https://github.com/enes-alatas/spool/pull/717)
- feat(httpapi): a linked user reads the private conversations that are theirs by [@enes-alatas](https://github.com/enes-alatas) in[#709](https://github.com/enes-alatas/spool/pull/709)
- feat(scripts): a backup and restore of the data directory and workstation volumes by [@enes-alatas](https://github.com/enes-alatas) in[#713](https://github.com/enes-alatas/spool/pull/713)
- ci: every action is pinned by commit SHA by [@enes-alatas](https://github.com/enes-alatas) in[#716](https://github.com/enes-alatas/spool/pull/716)
- fix(scripts): the secret guards refuse an awk without regex intervals by [@enes-alatas](https://github.com/enes-alatas) in[#724](https://github.com/enes-alatas/spool/pull/724)
- feat(web): a user links their Telegram or Slack account from Settings by [@enes-alatas](https://github.com/enes-alatas) in[#727](https://github.com/enes-alatas/spool/pull/727)
- docs: an upgrade runbook with its rollback by [@enes-alatas](https://github.com/enes-alatas) in[#725](https://github.com/enes-alatas/spool/pull/725)
- ci: Bump the actions group across 1 directory with 7 updates by [@dependabot](https://github.com/dependabot) [bot] in[#726](https://github.com/enes-alatas/spool/pull/726)
- feat(httpapi): serve a loop's private conversations per person by [@enes-alatas](https://github.com/enes-alatas) in[#734](https://github.com/enes-alatas/spool/pull/734)
- feat(web): a loop's page shows the private conversations the reader may read by [@enes-alatas](https://github.com/enes-alatas) in[#735](https://github.com/enes-alatas/spool/pull/735)
- docs: run the hub as a systemd service behind a TLS proxy by [@enes-alatas](https://github.com/enes-alatas) in[#736](https://github.com/enes-alatas/spool/pull/736)
- ci: golangci-lint runs a pinned version, not latest by [@enes-alatas](https://github.com/enes-alatas) in[#739](https://github.com/enes-alatas/spool/pull/739)
- ci: a daily advisory scan files each new advisory as a chore issue by [@enes-alatas](https://github.com/enes-alatas) in[#740](https://github.com/enes-alatas/spool/pull/740)
- ci: a PR touching security-sensitive paths is labelled for review by [@enes-alatas](https://github.com/enes-alatas) in[#743](https://github.com/enes-alatas/spool/pull/743)
- feat(docker): a rebuilt image reaches existing workstations and the proxy by [@enes-alatas](https://github.com/enes-alatas) in[#741](https://github.com/enes-alatas/spool/pull/741)
- feat(route): a link code is the one way a person becomes an allowed sender by [@enes-alatas](https://github.com/enes-alatas) in[#746](https://github.com/enes-alatas/spool/pull/746)
- docs(adr): a release ships built artifacts, and the operator verifies them by [@enes-alatas](https://github.com/enes-alatas) in[#749](https://github.com/enes-alatas/spool/pull/749)
- feat(web): link a chat account through a dialog that waits for YES by [@enes-alatas](https://github.com/enes-alatas) in[#755](https://github.com/enes-alatas/spool/pull/755)
- feat(release): a tag builds and attaches the spool binaries, with checksums and attestations by [@enes-alatas](https://github.com/enes-alatas) in[#757](https://github.com/enes-alatas/spool/pull/757)
- feat(web): web addresses in messages and the timeline are links by [@enes-alatas](https://github.com/enes-alatas) in[#760](https://github.com/enes-alatas/spool/pull/760)
- test(itest): the rebuilt-proxy row waits only for turns its messages began by [@enes-alatas](https://github.com/enes-alatas) in[#762](https://github.com/enes-alatas/spool/pull/762)
- feat: a release publishes the images by digest, and the hub never pulls by [@enes-alatas](https://github.com/enes-alatas) in[#765](https://github.com/enes-alatas/spool/pull/765)
- chore(docker): pin the workstation base by digest, and let Dependabot move it by [@enes-alatas](https://github.com/enes-alatas) in[#768](https://github.com/enes-alatas/spool/pull/768)
- feat(web): a Fleet row shows its loop's mission on one line by [@enes-alatas](https://github.com/enes-alatas) in[#769](https://github.com/enes-alatas/spool/pull/769)
- feat(httpapi): a loop's owner is chosen as a user, by their link on its surface by [@enes-alatas](https://github.com/enes-alatas) in[#771](https://github.com/enes-alatas/spool/pull/771)
- ci(release): a release is smoke-tested on a fresh runner along the README's path by [@enes-alatas](https://github.com/enes-alatas) in[#772](https://github.com/enes-alatas/spool/pull/772)
- feat(web): a loop's owner is picked as a user, by their link on its surface by [@enes-alatas](https://github.com/enes-alatas) in[#774](https://github.com/enes-alatas/spool/pull/774)
- fix(route): a refused cross-conversation reply names no other conversation by [@enes-alatas](https://github.com/enes-alatas) in[#776](https://github.com/enes-alatas/spool/pull/776)
- docs(security): SECURITY.md describes the hub as it is: users, sessions, link codes, verifying a release by @enes-alatas in#779
- docs(security): a threat model of the hub, and a gate that keeps it current by [@enes-alatas](https://github.com/enes-alatas) in[#781](https://github.com/enes-alatas/spool/pull/781)
- fix(httpapi): open streams read access outside the publish, and follow role changes by [@enes-alatas](https://github.com/enes-alatas) in[#785](https://github.com/enes-alatas/spool/pull/785)
- docs(adr): ADR-0051 counts strangers' wrong codes per surface, and shows a by [@enes-alatas](https://github.com/enes-alatas) in[#791](https://github.com/enes-alatas/spool/pull/791)
- feat(web): the first-run Chat surface step links the operator's account by [@enes-alatas](https://github.com/enes-alatas) in[#796](https://github.com/enes-alatas/spool/pull/796)
- docs(readme): a five-step quick start, and link codes where pairing codes were by [@enes-alatas](https://github.com/enes-alatas) in[#802](https://github.com/enes-alatas/spool/pull/802)
- docs(conventions): a removed or renamed flow takes the copy describing it along by [@enes-alatas](https://github.com/enes-alatas) in[#794](https://github.com/enes-alatas/spool/pull/794)
- feat(httpapi): the hub serves Settings → Access: users with their links, block, remove a link, ignored strangers by @enes-alatas in#803
- docs(threat-model): only an allowed sender binds a loop's fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#806](https://github.com/enes-alatas/spool/pull/806)
- fix(httpapi): a sign-in's cookie lasts as long as an unused session by [@enes-alatas](https://github.com/enes-alatas) in[#807](https://github.com/enes-alatas/spool/pull/807)
- feat(httpapi): the first-run page can be finished without a chat surface by [@enes-alatas](https://github.com/enes-alatas) in[#808](https://github.com/enes-alatas/spool/pull/808)
- feat(web): the first-run page can be finished without a chat surface by [@enes-alatas](https://github.com/enes-alatas) in[#805](https://github.com/enes-alatas/spool/pull/805)
- fix(telegram): wait longer for a send's answer, and log a retry that may have posted twice by [@enes-alatas](https://github.com/enes-alatas) in[#809](https://github.com/enes-alatas/spool/pull/809)
- docs(readme): a short README, with the reference detail moved into docs/ by [@enes-alatas](https://github.com/enes-alatas) in[#812](https://github.com/enes-alatas/spool/pull/812)
- fix(web): the first-run cards outline their second buttons in white by [@enes-alatas](https://github.com/enes-alatas) in[#813](https://github.com/enes-alatas/spool/pull/813)
- fix(route): strangers' wrong codes linking on one surface, and an admin lifts it by [@enes-alatas](https://github.com/enes-alatas) in[#814](https://github.com/enes-alatas/spool/pull/814)
- feat(loop): a loop says what it is working on with a [doing: …] trailer by [@enes-alatas](https://github.com/enes-alatas) in[#816](https://github.com/enes-alatas/spool/pull/816)
- chore(web): the demo shows the first-run link flow by [@enes-alatas](https://github.com/enes-alatas) in[#815](https://github.com/enes-alatas/spool/pull/815)
- fix(operations): the runbook says which lines need root, and a root restore leaves the keys to the hub by @enes-alatas in#784
- feat(web): the Fleet row and the loop page show what a loop is doing by @enes-alatas in#821
- feat(loop): the hub tells a loop's owner once when it keeps failing or its workstation stays down by @enes-alatas in#820
- docs(telegram): a loop's reaction to a teammate shows in no group by [@enes-alatas](https://github.com/enes-alatas) in[#823](https://github.com/enes-alatas/spool/pull/823)
- chore(ci): a merge queue in CI, in place of "branches up to date" by [@enes-alatas](https://github.com/enes-alatas) in[#822](https://github.com/enes-alatas/spool/pull/822)
- fix(surface): a sender who isn't allowed costs the hub one log line an hour by [@enes-alatas](https://github.com/enes-alatas) in[#825](https://github.com/enes-alatas/spool/pull/825)
- feat(web): the control room shows a loop's open problems by [@enes-alatas](https://github.com/enes-alatas) in[#826](https://github.com/enes-alatas/spool/pull/826)
- test: pin the surface seam's guarantees that no test covered by [@enes-alatas](https://github.com/enes-alatas) in[#827](https://github.com/enes-alatas/spool/pull/827)
- fix(telegram): a loop's reaction to a teammate's post stays on the hub in a supergroup too by [@enes-alatas](https://github.com/enes-alatas) in[#828](https://github.com/enes-alatas/spool/pull/828)
- feat(web): Settings → Access lists users with their links and blocks them by [@enes-alatas](https://github.com/enes-alatas) in[#829](https://github.com/enes-alatas/spool/pull/829)
- feat: an edited message reaches its loops as a correction that names the ref it edits by [@enes-alatas](https://github.com/enes-alatas) in[#832](https://github.com/enes-alatas/spool/pull/832)
- chore(ci): the merge queue's order on a pinned noticeboard by [@enes-alatas](https://github.com/enes-alatas) in[https://gi](https://gi) ...
[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.6.0)
v0.5.1 #
What's Changed #
- feat: the hub reads the Claude plan's usage from its loops' streams by [@enes-alatas](https://github.com/enes-alatas) in[#654](https://github.com/enes-alatas/spool/pull/654)
- feat(web): each Fleet row shows its share of today's fleet spend by [@enes-alatas](https://github.com/enes-alatas) in[#656](https://github.com/enes-alatas/spool/pull/656)
- fix(docker): every docker wake catches the proxy up with the fleet's hosts by [@enes-alatas](https://github.com/enes-alatas) in[#658](https://github.com/enes-alatas/spool/pull/658)
- feat(web): the Fleet page shows the plan's 5-hour and 7-day usage by [@enes-alatas](https://github.com/enes-alatas) in[#659](https://github.com/enes-alatas/spool/pull/659)
- test(itest): the live egress row counts only turns begun after each reach by [@enes-alatas](https://github.com/enes-alatas) in[#660](https://github.com/enes-alatas/spool/pull/660)
- feat(web): a first-run step the hub is working on lists its phases by [@enes-alatas](https://github.com/enes-alatas) in[#663](https://github.com/enes-alatas/spool/pull/663)
- fix: the toolchain moves to go1.26.9 for its standard-library fixes by [@enes-alatas](https://github.com/enes-alatas) in[#666](https://github.com/enes-alatas/spool/pull/666)
- chore(web): bump source-map-js to 1.2.2 past its advisory by [@enes-alatas](https://github.com/enes-alatas) in[#669](https://github.com/enes-alatas/spool/pull/669)
- ci: a feat PR that changes one half of the product names the other by [@enes-alatas](https://github.com/enes-alatas) in[#670](https://github.com/enes-alatas/spool/pull/670)
- feat(httpapi): the onboarding read says what each waiting pillar is doing by [@enes-alatas](https://github.com/enes-alatas) in[#664](https://github.com/enes-alatas/spool/pull/664)
- feat(loop): above the plan cap every loop sleeps until the window resets by [@enes-alatas](https://github.com/enes-alatas) in[#672](https://github.com/enes-alatas/spool/pull/672)
- feat(web): the room sets the plan cap and shows a capped fleet by [@enes-alatas](https://github.com/enes-alatas) in[#671](https://github.com/enes-alatas/spool/pull/671)
- fix(itest): the docker MCP-broker row skips on a host without docker by [@enes-alatas](https://github.com/enes-alatas) in[#673](https://github.com/enes-alatas/spool/pull/673)
Full Changelog: v0.5.0...v0.5.1
v0.5.0 #
What's Changed #
- fix(loop): a rehomed loop is not_provisioned until its workstation is built by [@enes-alatas](https://github.com/enes-alatas) in[#641](https://github.com/enes-alatas/spool/pull/641)
- feat(store): secrets sealed at rest under a hub key by [@enes-alatas](https://github.com/enes-alatas) in[#642](https://github.com/enes-alatas/spool/pull/642)
- feat(loop): an env-var change reaches an awake loop from its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#644](https://github.com/enes-alatas/spool/pull/644)
- feat(redact): the operator token and the hub key are redacted by [@enes-alatas](https://github.com/enes-alatas) in[#645](https://github.com/enes-alatas/spool/pull/645)
Full Changelog: v0.4.3...v0.5.0
v0.4.3 #
What's Changed #
- refactor(egress): remove a loop's own egress entries by [@enes-alatas](https://github.com/enes-alatas) in[#633](https://github.com/enes-alatas/spool/pull/633)
- feat(loop): the loop view says when a rehome is pending by [@enes-alatas](https://github.com/enes-alatas) in[#635](https://github.com/enes-alatas/spool/pull/635)
- feat(web): a bare loop's Workstation panel moves it into a docker container by [@enes-alatas](https://github.com/enes-alatas) in[#637](https://github.com/enes-alatas/spool/pull/637)
- fix(itest): the empty-context check no longer races a new loop's first tick by [@enes-alatas](https://github.com/enes-alatas) in[#638](https://github.com/enes-alatas/spool/pull/638)
Full Changelog: v0.4.2...v0.4.3
v0.4.2 #
What's Changed #
- feat: every change to a connection is on an append-only record by [@enes-alatas](https://github.com/enes-alatas) in[#608](https://github.com/enes-alatas/spool/pull/608)
- feat: rotating a connection's value retires the old one and ends the sessions that held it by [@enes-alatas](https://github.com/enes-alatas) in[#612](https://github.com/enes-alatas/spool/pull/612)
- feat: revoking a connection takes it from every loop, refuses it from then on, and keeps it redacted by @enes-alatas in#613
- fix(telegram): a loop's reaction to a teammate's message reaches a supergroup by [@enes-alatas](https://github.com/enes-alatas) in[#615](https://github.com/enes-alatas/spool/pull/615)
- feat(web): a connection's record, rotate and revoke in the control room by [@enes-alatas](https://github.com/enes-alatas) in[#616](https://github.com/enes-alatas/spool/pull/616)
- feat(web): a loop's Telegram notes say where a reaction to a teammate shows by [@enes-alatas](https://github.com/enes-alatas) in[#619](https://github.com/enes-alatas/spool/pull/619)
- chore: remove the per-loop secrets routes; private env-var connections replace them by [@enes-alatas](https://github.com/enes-alatas) in[#618](https://github.com/enes-alatas/spool/pull/618)
- feat: the fleet's extra egress hosts are stored and apply without a restart by [@enes-alatas](https://github.com/enes-alatas) in[#620](https://github.com/enes-alatas/spool/pull/620)
- feat(web): Settings has an Egress section, the fleet's allowlist by [@enes-alatas](https://github.com/enes-alatas) in[#621](https://github.com/enes-alatas/spool/pull/621)
- feat: secrets a loop holds don't reach teammates or hide in encodings; ADR-0045 states the posture by @enes-alatas in#625
- feat(httpapi): the hub brokers a loop's http MCP servers by [@enes-alatas](https://github.com/enes-alatas) in[#627](https://github.com/enes-alatas/spool/pull/627)
- feat: a bare loop can be rehomed into a docker workstation by [@enes-alatas](https://github.com/enes-alatas) in[#631](https://github.com/enes-alatas/spool/pull/631)
Full Changelog: v0.4.1...v0.4.2
v0.4.1 #
What's Changed #
- fix(httpapi): a docker loop that cannot reach the loop listener is refused by [@enes-alatas](https://github.com/enes-alatas) in[#477](https://github.com/enes-alatas/spool/pull/477)
- docs(release): a release is titled with its tag alone by [@enes-alatas](https://github.com/enes-alatas) in[#486](https://github.com/enes-alatas/spool/pull/486)
- feat: channels exist in the hub, with the fleet channel named group by [@enes-alatas](https://github.com/enes-alatas) in[#488](https://github.com/enes-alatas/spool/pull/488)
- feat(web): a Channels page creates channels and chooses their loops by [@enes-alatas](https://github.com/enes-alatas) in[#490](https://github.com/enes-alatas/spool/pull/490)
- docs: a loop reaches only Spool's MCP server by [@enes-alatas](https://github.com/enes-alatas) in[#494](https://github.com/enes-alatas/spool/pull/494)
- fix(runtime): Spool builds on macOS, keeping Pdeathsig on Linux by [@enes-alatas](https://github.com/enes-alatas) in[#498](https://github.com/enes-alatas/spool/pull/498)
- fix(loop): a loop whose claude cannot reach the hub is down, and its work waits by [@enes-alatas](https://github.com/enes-alatas) in[#496](https://github.com/enes-alatas/spool/pull/496)
- fix(httpapi): a Docker Desktop workstation reaches the loop listener by [@enes-alatas](https://github.com/enes-alatas) in[#509](https://github.com/enes-alatas/spool/pull/509)
- feat(cmd): a docker hub puts its loop listener where workstations reach it by [@enes-alatas](https://github.com/enes-alatas) in[#511](https://github.com/enes-alatas/spool/pull/511)
- feat(web): name a claude that can't reach the hub, and offer no Power on for it by [@enes-alatas](https://github.com/enes-alatas) in[#510](https://github.com/enes-alatas/spool/pull/510)
- feat(loop): a loop posts to its channels by name by [@enes-alatas](https://github.com/enes-alatas) in[#512](https://github.com/enes-alatas/spool/pull/512)
- test(uifixture): the fixture seeds channels besides the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#513](https://github.com/enes-alatas/spool/pull/513)
- feat(telegram): a group is bound to a channel per loop, and carries it both ways by [@enes-alatas](https://github.com/enes-alatas) in[#516](https://github.com/enes-alatas/spool/pull/516)
- docs: a Report a problem form, triage rule, and CONTRIBUTING for outside reports by [@enes-alatas](https://github.com/enes-alatas) in[#522](https://github.com/enes-alatas/spool/pull/522)
- feat(loop): the loop view says what the session reaches through MCP by [@enes-alatas](https://github.com/enes-alatas) in[#525](https://github.com/enes-alatas/spool/pull/525)
- chore(ci): an issue from outside the team is labelled and welcomed by [@enes-alatas](https://github.com/enes-alatas) in[#528](https://github.com/enes-alatas/spool/pull/528)
- feat(web): the loop page lists its rooms and binds one to a channel by [@enes-alatas](https://github.com/enes-alatas) in[#530](https://github.com/enes-alatas/spool/pull/530)
- feat: reactions exist in the hub — ADR-0040, store and bus kind by [@enes-alatas](https://github.com/enes-alatas) in[#536](https://github.com/enes-alatas/spool/pull/536)
- feat(web): the loop page shows what the session reaches through MCP by [@enes-alatas](https://github.com/enes-alatas) in[#537](https://github.com/enes-alatas/spool/pull/537)
- feat(telegram): reactions travel both ways between a loop's bot and the hub by [@enes-alatas](https://github.com/enes-alatas) in[#538](https://github.com/enes-alatas/spool/pull/538)
- fix(web): no next wake is shown while a loop is in a turn by [@enes-alatas](https://github.com/enes-alatas) in[#539](https://github.com/enes-alatas/spool/pull/539)
- feat(loop): a loop reacts with send_message and hears of reactions on its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#540](https://github.com/enes-alatas/spool/pull/540)
- feat(web): one slider sets a loop's min wake, tick and max wake by [@enes-alatas](https://github.com/enes-alatas) in[#545](https://github.com/enes-alatas/spool/pull/545)
- feat(slack): reactions travel both ways between a loop's app and the hub by [@enes-alatas](https://github.com/enes-alatas) in[#544](https://github.com/enes-alatas/spool/pull/544)
- feat(web): the control room shows a message's reactions by [@enes-alatas](https://github.com/enes-alatas) in[#547](https://github.com/enes-alatas/spool/pull/547)
- chore(egress): a workstation can pip install from PyPI by [@enes-alatas](https://github.com/enes-alatas) in[#546](https://github.com/enes-alatas/spool/pull/546)
- feat: polls exist in the hub — ADR-0041, store, bus kind and the close by [@enes-alatas](https://github.com/enes-alatas) in[#555](https://github.com/enes-alatas/spool/pull/555)
- feat(telegram): a loop's poll is a native Telegram poll, and its votes reach the hub by [@enes-alatas](https://github.com/enes-alatas) in[#557](https://github.com/enes-alatas/spool/pull/557)
- feat(web): a loop's channels are tabs of its page, and Activity filters by channel by [@enes-alatas](https://github.com/enes-alatas) in[#556](https://github.com/enes-alatas/spool/pull/556)
- feat(slack): a loop's poll is its app's post with buttons, and clicks are votes by [@enes-alatas](https://github.com/enes-alatas) in[#558](https://github.com/enes-alatas/spool/pull/558)
- feat: a loop polls, votes and closes with send_message, and votes ride with its next turn by [@enes-alatas](https://github.com/enes-alatas) in[#560](https://github.com/enes-alatas/spool/pull/560)
- fix(telegram): an unanswered send is retried on a fresh connection within seconds by [@enes-alatas](https://github.com/enes-alatas) in[#562](https://github.com/enes-alatas/spool/pull/562)
- feat: the control room renders polls with their tallies by [@enes-alatas](https://github.com/enes-alatas) in[#563](https://github.com/enes-alatas/spool/pull/563)
- feat: the hub pins a PreToolUse hook that refuses the host's shared-state commands by [@enes-alatas](https://github.com/enes-alatas) in[#565](https://github.com/enes-alatas/spool/pull/565)
- feat: a loop dismisses a lost send, and is reminded of it until it does by [@enes-alatas](https://github.com/enes-alatas) in[#566](https://github.com/enes-alatas/spool/pull/566)
- feat: connections can be created, listed, described and deleted, secret write-only by [@enes-alatas](https://github.com/enes-alatas) in[#568](https://github.com/enes-alatas/spool/pull/568)
- feat(web): show when a loop dismissed or left a lost send by [@enes-alatas](https://github.com/enes-alatas) in[#569](https://github.com/enes-alatas/spool/pull/569)
- fix(telegram): the HTTP/1 transport offers only http/1.1 in its TLS handshake by [@enes-alatas](https://github.com/enes-alatas) in[#571](https://github.com/enes-alatas/spool/pull/571)
- feat: a connection is attached to and detached from loops, and an attached one can't be deleted by @enes-alatas in#573
- feat: a connection's env-var kind replaces env-credential by [@enes-alatas](https://github.com/enes-alatas) in[#575](https://github.com/enes-alatas/spool/pull/575)
- feat(web): Connections page and loop-page connections panel by [@enes-alatas](https://github.com/enes-alatas) in[#577](https://github.com/enes-alatas/spool/pull/577)
- feat: per-loop secrets are env-var connections, and an attached env-var is in the wake env by [@enes-alatas](https://github.com/enes-alatas) in[#578](https://github.com/enes-alatas/spool/pull/578)
- fix(web): connections say env variables reach their loop, and long names fit the panel by [@enes-alatas](https://github.com/enes-alatas) in[#583](https://github.com/enes-alatas/spool/pull/583)
- feat: GET /api/onboarding reports readiness per pillar by [@enes-alatas](https://github.com/enes-alatas) in[#584](https://github.com/enes-alatas/spool/pull/584)
- feat(web): first-run page for the three onboarding pillars by [@enes-alatas](https://github.com/enes-alatas) in[#586](https://github.com/enes-alatas/spool/pull/586)
- feat: the harness pillar checks the Claude login with one haiku answer by [@enes-alatas](https://github.com/enes-alatas) in[#587](https://github.com/enes-alatas/spool/pull/587)
- feat(web): the first-run Harness card sets up and checks the login in place by [@enes-alatas](https://github.com/enes-alatas) in[#590](https://github.com/enes-alatas/spool/pull/590)
- feat(slack): a Slack channel is a room bound to a channel per loop by [@enes-alatas](https://github.com/enes-alatas) in[#591](https://github.com/enes-alatas/spool/pull/591)
- feat(web): the first-run Loops and Chat surface cards open their steps in place by [@enes-alatas](https://github.com/enes-alatas) in[#592](https://github.com/enes-alatas/spool/pull/592)
- test: two docker itests stop racing other hubs on the shared daemon by [@enes-alatas](https://github.com/enes-alatas) in[#594](https://github.com/enes-alatas/spool/pull/594)
- feat(loop): an attached mcp-server is a server in the loop's mcp-config by [@enes-alatas](https://github.com/enes-alatas) in[#598](https://github.com/enes-alatas/spool/pull/598)
- feat(web): a Slack loop's channels are rooms on its page by [@enes-alatas](https://github.com/enes-alatas) in[#596](https://github.com/enes-alatas/spool/pull/596)
- feat(docker): an attached MCP server opens its host to its loop alone by [@enes-alatas](https://github.com/enes-alatas) in[#602](https://github.com/enes-alatas/spool/pull/602)
- feat: a connection can be private to one loop, and is shared one way by [@enes-alatas](https://github.com/enes-alatas) in[#603](https://github.com/enes-alatas/spool/pull/603)
- feat(web): the loop page's secrets are its private connections by [@enes-alatas](https://github.com/enes-alatas) in[#605](https://github.com/enes-alatas/spool/pull/605)
Full Changelog: v0.4.0...v0.4.1
v0.4.0 #
What's Changed #
- fix(web): the Undelivered pane restacks by its own width, not the viewport's by [@enes-alatas](https://github.com/enes-alatas) in[#417](https://github.com/enes-alatas/spool/pull/417)
- fix(loop): a refused Claude login is a named down reason, not silence by [@enes-alatas](https://github.com/enes-alatas) in[#416](https://github.com/enes-alatas/spool/pull/416)
- feat(surface): the owner hears once that a refused login stopped their loops by [@enes-alatas](https://github.com/enes-alatas) in[#421](https://github.com/enes-alatas/spool/pull/421)
- fix(web): a refused Claude login shows its cause and its fix by [@enes-alatas](https://github.com/enes-alatas) in[#422](https://github.com/enes-alatas/spool/pull/422)
- feat(surface/slack): each loop's Slack app holds a Socket Mode connection by [@enes-alatas](https://github.com/enes-alatas) in[#423](https://github.com/enes-alatas/spool/pull/423)
- fix(telegram): a native reply reaches its author whichever bot ingests it by [@enes-alatas](https://github.com/enes-alatas) in[#425](https://github.com/enes-alatas/spool/pull/425)
- fix(web): the New loop runtime select says why it is locked by [@enes-alatas](https://github.com/enes-alatas) in[#428](https://github.com/enes-alatas/spool/pull/428)
- feat(surface/slack): what a human says to a loop's app reaches the loop by [@enes-alatas](https://github.com/enes-alatas) in[#430](https://github.com/enes-alatas/spool/pull/430)
- fix(web): New loop locks the workspace field for a docker loop by [@enes-alatas](https://github.com/enes-alatas) in[#432](https://github.com/enes-alatas/spool/pull/432)
- fix(web): a Slack post in the fleet channel says it came via Slack by [@enes-alatas](https://github.com/enes-alatas) in[#433](https://github.com/enes-alatas/spool/pull/433)
- feat(surface/slack): a loop's words reach Slack: group mirrors, owner_dm, threads by [@enes-alatas](https://github.com/enes-alatas) in[#434](https://github.com/enes-alatas/spool/pull/434)
- feat(surface/slack): turned-away senders, the login notice and channel_name on Slack by [@enes-alatas](https://github.com/enes-alatas) in[#435](https://github.com/enes-alatas/spool/pull/435)
- fix(web): Slack attach and channel copy say what actually happened by [@enes-alatas](https://github.com/enes-alatas) in[#439](https://github.com/enes-alatas/spool/pull/439)
- fix(loop): a refused login keeps one tick, not a backlog of them by [@enes-alatas](https://github.com/enes-alatas) in[#436](https://github.com/enes-alatas/spool/pull/436)
- fix: a running hub settles every send it accepted by [@enes-alatas](https://github.com/enes-alatas) in[#440](https://github.com/enes-alatas/spool/pull/440)
- fix(cmd): a hub that stops serving exits 1 by [@enes-alatas](https://github.com/enes-alatas) in[#443](https://github.com/enes-alatas/spool/pull/443)
- fix(store): commits wait on the WAL, not on an fsync; a perf smoke asserts the envelope by [@enes-alatas](https://github.com/enes-alatas) in[#444](https://github.com/enes-alatas/spool/pull/444)
- docs(readme): Quick start is a numbered path that works end to end by [@enes-alatas](https://github.com/enes-alatas) in[#448](https://github.com/enes-alatas/spool/pull/448)
- feat(surface): a stopping hub fails its surfaces' held sends before the store closes by [@enes-alatas](https://github.com/enes-alatas) in[#449](https://github.com/enes-alatas/spool/pull/449)
- feat(attach): a photo or file sent to a loop reaches it as a path it can read by [@enes-alatas](https://github.com/enes-alatas) in[#454](https://github.com/enes-alatas/spool/pull/454)
- feat(attach): a loop sends a file with its message by [@enes-alatas](https://github.com/enes-alatas) in[#455](https://github.com/enes-alatas/spool/pull/455)
- fix(web): a context trend bar keeps its width at any turn count by [@enes-alatas](https://github.com/enes-alatas) in[#459](https://github.com/enes-alatas/spool/pull/459)
- feat(web): glyph and wordmark match the README banner by [@enes-alatas](https://github.com/enes-alatas) in[#461](https://github.com/enes-alatas/spool/pull/461)
- feat(slack): files cross Slack both ways by [@enes-alatas](https://github.com/enes-alatas) in[#458](https://github.com/enes-alatas/spool/pull/458)
- chore: the README opens with a demo video, rendered by make demo by [@enes-alatas](https://github.com/enes-alatas) in[#463](https://github.com/enes-alatas/spool/pull/463)
- feat(httpapi): the control room reads, serves and sends attachments by [@enes-alatas](https://github.com/enes-alatas) in[#467](https://github.com/enes-alatas/spool/pull/467)
- feat(web): messages carry files in the control room by [@enes-alatas](https://github.com/enes-alatas) in[#469](https://github.com/enes-alatas/spool/pull/469)
- docs(conventions): every slice of a multi-PR issue is its own sub-issue by [@enes-alatas](https://github.com/enes-alatas) in[#465](https://github.com/enes-alatas/spool/pull/465)
- docs(conventions): rare procedures move to skills by [@enes-alatas](https://github.com/enes-alatas) in[#473](https://github.com/enes-alatas/spool/pull/473)
- chore(deps-dev): Bump brace-expansion from 1.1.18 to 1.1.21 in /web in the npm_and_yarn group across 1 directory by @dependabot [bot] in#468
- docs(conventions): a bare #n may sit anywhere in a code comment by [@enes-alatas](https://github.com/enes-alatas) in[#478](https://github.com/enes-alatas/spool/pull/478)
- feat(web): allowing a pending sender takes the code they were sent by [@enes-alatas](https://github.com/enes-alatas) in[#482](https://github.com/enes-alatas/spool/pull/482)
New Contributors #
- @dependabot [bot] made their first contribution in#468
Full Changelog:
v0.3.0...v0.4.0
v0.3.0 #
What's Changed #
- chore: make ui-shots takes the screenshot set from fixture data by [@enes-alatas](https://github.com/enes-alatas) in[#260](https://github.com/enes-alatas/spool/pull/260)
- chore(secret-guard): a handle and a chat id are quotes of the group by [@enes-alatas](https://github.com/enes-alatas) in[#259](https://github.com/enes-alatas/spool/pull/259)
- feat(web): the Fleet row says how many of a loop's messages never arrived by [@enes-alatas](https://github.com/enes-alatas) in[#262](https://github.com/enes-alatas/spool/pull/262)
- fix(itest): two suites, two egress walls by [@enes-alatas](https://github.com/enes-alatas) in[#265](https://github.com/enes-alatas/spool/pull/265)
- fix(httpapi): a saved mission takes the loop's next session, not its next hour by [@enes-alatas](https://github.com/enes-alatas) in[#271](https://github.com/enes-alatas/spool/pull/271)
- feat(web): a loop's mission can be edited from its page by [@enes-alatas](https://github.com/enes-alatas) in[#268](https://github.com/enes-alatas/spool/pull/268)
- feat(httpapi): the fleet's undelivered sends have somewhere to be read by [@enes-alatas](https://github.com/enes-alatas) in[#273](https://github.com/enes-alatas/spool/pull/273)
- feat(web): the sends that failed get a tab of their own by [@enes-alatas](https://github.com/enes-alatas) in[#274](https://github.com/enes-alatas/spool/pull/274)
- feat(httpapi): a failed send can be retried or dismissed, and stops counting by [@enes-alatas](https://github.com/enes-alatas) in[#276](https://github.com/enes-alatas/spool/pull/276)
- feat(route): a loop's resend closes the failure it replaces by [@enes-alatas](https://github.com/enes-alatas) in[#278](https://github.com/enes-alatas/spool/pull/278)
- feat(web): a failed send can be retried or set aside, and says which by [@enes-alatas](https://github.com/enes-alatas) in[#279](https://github.com/enes-alatas/spool/pull/279)
- feat(httpapi): one loop's undelivered sends can be asked for alone by [@enes-alatas](https://github.com/enes-alatas) in[#290](https://github.com/enes-alatas/spool/pull/290)
- docs(adr): the fleet channel is the hub's; surfaces are attached and mirror it by [@enes-alatas](https://github.com/enes-alatas) in[#291](https://github.com/enes-alatas/spool/pull/291)
- fix(web): an Undelivered row says where it went and why, together by [@enes-alatas](https://github.com/enes-alatas) in[#292](https://github.com/enes-alatas/spool/pull/292)
- feat(telegram): nothing the operator writes leaves the hub by [@enes-alatas](https://github.com/enes-alatas) in[#293](https://github.com/enes-alatas/spool/pull/293)
- feat(web): Undelivered is a pane of the loop page, not a destination by [@enes-alatas](https://github.com/enes-alatas) in[#294](https://github.com/enes-alatas/spool/pull/294)
- docs(conventions): Activity stays out of every shot, and says it is a decision by [@enes-alatas](https://github.com/enes-alatas) in[#296](https://github.com/enes-alatas/spool/pull/296)
- feat(route): a loop outside the fleet channel has no group by [@enes-alatas](https://github.com/enes-alatas) in[#295](https://github.com/enes-alatas/spool/pull/295)
- feat(httpapi): the fleet channel has endpoints of its own by [@enes-alatas](https://github.com/enes-alatas) in[#297](https://github.com/enes-alatas/spool/pull/297)
- chore: the model lists know Opus 5.5 and Fable 5.1 by [@enes-alatas](https://github.com/enes-alatas) in[#299](https://github.com/enes-alatas/spool/pull/299)
- feat(web): the fleet channel is a tab of the Fleet page by [@enes-alatas](https://github.com/enes-alatas) in[#300](https://github.com/enes-alatas/spool/pull/300)
- feat: a message says whether it is on the surface too by [@enes-alatas](https://github.com/enes-alatas) in[#301](https://github.com/enes-alatas/spool/pull/301)
- fix(web): the missing-Claude-token banner is about contained loops only by [@enes-alatas](https://github.com/enes-alatas) in[#307](https://github.com/enes-alatas/spool/pull/307)
- feat(httpapi): a new loop starts outside the fleet channel when it is alone by [@enes-alatas](https://github.com/enes-alatas) in[#306](https://github.com/enes-alatas/spool/pull/306)
- chore(claude): TestedVersion is 2.1.281, the CLI the fleet runs on by [@enes-alatas](https://github.com/enes-alatas) in[#309](https://github.com/enes-alatas/spool/pull/309)
- feat(web): loop surfaces — attach Telegram after creation, fleet-channel membership by [@enes-alatas](https://github.com/enes-alatas) in[#308](https://github.com/enes-alatas/spool/pull/308)
- docs: add theme-aware Spool banner and badges by [@enes-alatas](https://github.com/enes-alatas) in[#310](https://github.com/enes-alatas/spool/pull/310)
- docs: reduce README banner width by [@enes-alatas](https://github.com/enes-alatas) in[#313](https://github.com/enes-alatas/spool/pull/313)
- feat(httpapi): the operator can reply in the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#311](https://github.com/enes-alatas/spool/pull/311)
- feat(web): the fleet channel says what stays on the hub and what failed to mirror by [@enes-alatas](https://github.com/enes-alatas) in[#312](https://github.com/enes-alatas/spool/pull/312)
- feat(web): reply to any message in the fleet channel by [@enes-alatas](https://github.com/enes-alatas) in[#317](https://github.com/enes-alatas/spool/pull/317)
- fix(workstation): a rebuild installs the newest Claude Code by [@enes-alatas](https://github.com/enes-alatas) in[#315](https://github.com/enes-alatas/spool/pull/315)
- feat(web): a favicon set and the spool glyph in the banner's proportion by [@enes-alatas](https://github.com/enes-alatas) in[#318](https://github.com/enes-alatas/spool/pull/318)
- feat(prompt): a loop is taught only the conversations it has by [@enes-alatas](https://github.com/enes-alatas) in[#321](https://github.com/enes-alatas/spool/pull/321)
- docs(conventions): an epic's children are attached as sub-issues by [@enes-alatas](https://github.com/enes-alatas) in[#324](https://github.com/enes-alatas/spool/pull/324)
- chore(web): upgrade vite, vitest and react-router past their advisories by [@enes-alatas](https://github.com/enes-alatas) in[#325](https://github.com/enes-alatas/spool/pull/325)
- docs(adr): ADR-0010 points to ADR-0018, which narrowed its Engine API clause by [@enes-alatas](https://github.com/enes-alatas) in[#327](https://github.com/enes-alatas/spool/pull/327)
- docs(conventions): a commit subject has one scope and no closing keyword by [@enes-alatas](https://github.com/enes-alatas) in[#328](https://github.com/enes-alatas/spool/pull/328)
- chore(ci): itest skips content it already passed on this PR by [@enes-alatas](https://github.com/enes-alatas) in[#326](https://github.com/enes-alatas/spool/pull/326)
- feat(web): the model list offers the four family aliases and no versions by [@enes-alatas](https://github.com/enes-alatas) in[#329](https://github.com/enes-alatas/spool/pull/329)
- feat(loop): a refused model holds the loop until the model is edited by [@enes-alatas](https://github.com/enes-alatas) in[#330](https://github.com/enes-alatas/spool/pull/330)
- feat(web): the control room's text uses no em dashes by [@enes-alatas](https://github.com/enes-alatas) in[#333](https://github.com/enes-alatas/spool/pull/333)
- feat(web): a refused model shows in the room, with what the CLI said by [@enes-alatas](https://github.com/enes-alatas) in[#335](https://github.com/enes-alatas/spool/pull/335)
- chore(web): move the control room to react-router 7 by [@enes-alatas](https://github.com/enes-alatas) in[#336](https://github.com/enes-alatas/spool/pull/336)
- docs: every new dependency is interviewed; binary ones also take an ADR by [@enes-alatas](https://github.com/enes-alatas) in[#337](https://github.com/enes-alatas/spool/pull/337)
- docs(adr): the hub resolves model aliases with a CLI run that can reach nothing by [@enes-alatas](https://github.com/enes-alatas) in[#338](https://github.com/enes-alatas/spool/pull/338)
- feat: the model list says what each name runs as on this hub by [@enes-alatas](https://github.com/enes-alatas) in[#340](https://github.com/enes-alatas/spool/pull/340)
- feat(web): model dropdowns show what each alias runs as by [@enes-alatas](https://github.com/enes-alatas) in[#339](https://github.com/enes-alatas/spool/pull/339)
- ci: the checks summary reports coverage of the PR's changed lines by [@enes-alatas](https://github.com/enes-alatas) in[#343](https://github.com/enes-alatas/spool/pull/343)
- feat(web): Surfaces offers Attach Slack, with the loop's app manifest by [@enes-alatas](https://github.com/enes-alatas) in[#346](https://github.com/enes-alatas/spool/pull/346)
- fix(itest): the hub's two listeners never draw the same port by [@enes-alatas](https://github.com/enes-alatas) in[#342](https://github.com/enes-alatas/spool/pull/342)
- fix(web): a disabled rule mutes its text, not its opacity by [@enes-alatas](https://github.com/enes-alatas) in[#355](https://github.com/enes-alatas/spool/pull/355)
- fix(web): a rule's buttons wrap under its title on a narrow screen by [@enes-alatas](https://github.com/enes-alatas) in[#358](https://github.com/enes-alatas/spool/pull/358)
- fix(httpapi): a loop's model cannot read as a CLI flag by [@enes-alatas](https://github.com/enes-alatas) in[#357](https://github.com/enes-alatas/spool/pull/357)
- fix(web): the loop page's model and pacing controls say what they set by [@enes-alatas](https://github.com/enes-alatas) in[#359](https://github.com/enes-alatas/spool/pull/359)
- fix(web): Access says when it is or could not load senders by [@enes-alatas](https://github.com/enes-alatas) in[#361](https://github.com/enes-alatas/spool/pull/361)
- fix(httpapi): a wrong method under /api/ is 405, not the control room by [@enes-alatas](https://github.com/enes-alatas) in[#362](https://github.com/enes-alatas/spool/pull/362)
- fix(web): Settings says it could not load, not that no token is set by [@enes-alatas](https://github.com/enes-alatas) in[#364](https://github.com/enes-alatas/spool/pull/364)
- fix(httpapi): a write whose subject vanished answers 404, not 500 by [@enes-alatas](https://github.com/enes-alatas) in[#365](https://github.com/enes-alatas/spool/pull/365)
- fix(web): the loop page says when its timeline could not load by [@enes-alatas](https://github.com/enes-alatas) in[#367](https://github.com/enes-alatas/spool/pull/367)
- fix(web): the loop page says when a loop is missing or failed to load by [@enes-alatas](https://github.com/enes-alatas) in[#369](https://github.com/enes-alatas/spool/pull/369)
- refactor(httpapi): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#370](https://github.com/enes-alatas/spool/pull/370)
- feat(web): the header's New loop is quiet white text, not a filled button by [@enes-alatas](https://github.com/enes-alatas) in[#371](https://github.com/enes-alatas/spool/pull/371)
- fix(sched): a tick due during a suspend fires once the machine resumes by [@enes-alatas](https://github.com/enes-alatas) in[#373](https://github.com/enes-alatas/spool/pull/373)
- fix(web): the room's errors are announced to a screen reader by [@enes-alatas](https://github.com/enes-alatas) in[#374](https://github.com/enes-alatas/spool/pull/374)
- feat(loop): a rotation tells the loop why it is happening by [@enes-alatas](https://github.com/enes-alatas) in[#376](https://github.com/enes-alatas/spool/pull/376)
- feat(ci): a browser smoke walks the control room on every web change by [@enes-alatas](https://github.com/enes-alatas) in[#375](https://github.com/enes-alatas/spool/pull/375)
- refactor(sqlite): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#377](https://github.com/enes-alatas/spool/pull/377)
- test(uifixture): the fixture hub signs in with a synthetic operator token by [@enes-alatas](https://github.com/enes-alatas) in[#379](https://github.com/enes-alatas/spool/pull/379)
- fix(web): the header switches to the narrow bar by text size, not width alone by [@enes-alatas](https://github.com/enes-alatas) in[#380](https://github.com/enes-alatas/spool/pull/380)
- refactor(loop): names say what they hold by [@enes-alatas](https://github.com/enes-alatas) in[#382](https://github.com/enes-alatas/spool/pull/382)
- fix(httpapi): health says the hub is up and nothing about the host by [@enes-alatas](https://github.com/enes-alatas) in[#385](https://github.com/enes-alatas/spool/pull/385)
- feat(web): Surfaces and Access gain the Slack surface's control-room half by [@enes-alatas](https://github.com/enes-alatas) in[#384](https://github.com/enes-alatas/spool/pull/384)
- docs(readme): Spool leads with what it makes easier by [@enes-alatas](https://github.com/enes-alatas) in[#387](https://github.com/enes-alatas/spool/pull/387)
- feat(store): a loop can hold a Slack identity and a Slack allowlist by [@enes-alatas](https://github.com/enes-alatas) in[#390](https://github.com/enes-alatas/spool/pull/390)
- docs(conventions): an issue's body is its spec of record by [@enes-alatas](https://github.com/enes-alatas) in[#3](https://github.com/enes-alatas/spool/issues/3) ...
[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.3.0)
v0.2.0 #
What's Changed #
- build(workstation): official spool-workstation image by [@enes-alatas](https://github.com/enes-alatas) in[#23](https://github.com/enes-alatas/spool/pull/23)
- docs(conventions): require self-contained comments by [@enes-alatas](https://github.com/enes-alatas) in[#25](https://github.com/enes-alatas/spool/pull/25)
- feat: Claude auth for workstations via setup-token by [@enes-alatas](https://github.com/enes-alatas) in[#26](https://github.com/enes-alatas/spool/pull/26)
- feat: per-loop secret env vars by [@enes-alatas](https://github.com/enes-alatas) in[#29](https://github.com/enes-alatas/spool/pull/29)
- docs(conventions): "fresh main" means fetched, not the local checkout by [@enes-alatas](https://github.com/enes-alatas) in[#28](https://github.com/enes-alatas/spool/pull/28)
- docs: GitHub is loop-native work, not a Spool surface by [@enes-alatas](https://github.com/enes-alatas) in[#32](https://github.com/enes-alatas/spool/pull/32)
- feat(web): workstation panel on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#35](https://github.com/enes-alatas/spool/pull/35)
- fix(runtime): drop bare.Start's kill-on-cancel watcher by [@enes-alatas](https://github.com/enes-alatas) in[#34](https://github.com/enes-alatas/spool/pull/34)
- chore(web): untrack tsconfig.tsbuildinfo by [@enes-alatas](https://github.com/enes-alatas) in[#40](https://github.com/enes-alatas/spool/pull/40)
- feat(web): context occupancy trend on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#49](https://github.com/enes-alatas/spool/pull/49)
- docs(conventions): delegated decisions are per-decision and quoted in the record by [@enes-alatas](https://github.com/enes-alatas) in[#41](https://github.com/enes-alatas/spool/pull/41)
- fix(telegram): dedup group and DM messages per bot, not per chat by [@enes-alatas](https://github.com/enes-alatas) in[#48](https://github.com/enes-alatas/spool/pull/48)
- feat(loop): operator power controls for a loop's workstation by [@enes-alatas](https://github.com/enes-alatas) in[#53](https://github.com/enes-alatas/spool/pull/53)
- feat(web): workstation power controls on loop detail by [@enes-alatas](https://github.com/enes-alatas) in[#50](https://github.com/enes-alatas/spool/pull/50)
- feat(httpapi): context usage on the loop view by [@enes-alatas](https://github.com/enes-alatas) in[#54](https://github.com/enes-alatas/spool/pull/54)
- feat(web): context fill against the model's window by [@enes-alatas](https://github.com/enes-alatas) in[#56](https://github.com/enes-alatas/spool/pull/56)
- feat(web): one fleet alert for a missing Claude token by [@enes-alatas](https://github.com/enes-alatas) in[#57](https://github.com/enes-alatas/spool/pull/57)
- fix(loop): rotate a session that can no longer be resumed by [@enes-alatas](https://github.com/enes-alatas) in[#59](https://github.com/enes-alatas/spool/pull/59)
- docs: record the proactive context rotation strategy (ADR-0022) by [@enes-alatas](https://github.com/enes-alatas) in[#62](https://github.com/enes-alatas/spool/pull/62)
- docs: separate coordination from human-facing traffic (ADR-0023) by [@enes-alatas](https://github.com/enes-alatas) in[#63](https://github.com/enes-alatas/spool/pull/63)
- feat(loop): proactive context rotation before the degradation zone by [@enes-alatas](https://github.com/enes-alatas) in[#64](https://github.com/enes-alatas/spool/pull/64)
- feat: fleet rules — storage, CRUD API, and prompt injection by [@enes-alatas](https://github.com/enes-alatas) in[#72](https://github.com/enes-alatas/spool/pull/72)
- feat(web): fleet rules panel by [@enes-alatas](https://github.com/enes-alatas) in[#58](https://github.com/enes-alatas/spool/pull/58)
- docs: define private DMs and explicit group delivery by [@enes-alatas](https://github.com/enes-alatas) in[#78](https://github.com/enes-alatas/spool/pull/78)
- docs: settle the loop sending contract (ADR-0026) by [@enes-alatas](https://github.com/enes-alatas) in[#81](https://github.com/enes-alatas/spool/pull/81)
- feat: conversation identity and per-loop MCP tokens by [@enes-alatas](https://github.com/enes-alatas) in[#82](https://github.com/enes-alatas/spool/pull/82)
- feat: hub MCP endpoint and the explicit send core by [@enes-alatas](https://github.com/enes-alatas) in[#83](https://github.com/enes-alatas/spool/pull/83)
- feat: fakeclaude scripted MCP sends by [@enes-alatas](https://github.com/enes-alatas) in[#84](https://github.com/enes-alatas/spool/pull/84)
- feat: runner wiring for explicit sends by [@enes-alatas](https://github.com/enes-alatas) in[#85](https://github.com/enes-alatas/spool/pull/85)
- feat(telegram): explicit delivery in, selective wake out by [@enes-alatas](https://github.com/enes-alatas) in[#86](https://github.com/enes-alatas/spool/pull/86)
- feat: web composer destination picker + read-only Activity by [@enes-alatas](https://github.com/enes-alatas) in[#87](https://github.com/enes-alatas/spool/pull/87)
- test: ADR-0025 scenario close-out and docs refresh by [@enes-alatas](https://github.com/enes-alatas) in[#88](https://github.com/enes-alatas/spool/pull/88)
- fix(loop): name an over-window payload for what it is by [@enes-alatas](https://github.com/enes-alatas) in[#90](https://github.com/enes-alatas/spool/pull/90)
- feat(web): semantic colour tokens for the control room by [@enes-alatas](https://github.com/enes-alatas) in[#92](https://github.com/enes-alatas/spool/pull/92)
- feat: operator control to rotate a session on demand by [@enes-alatas](https://github.com/enes-alatas) in[#93](https://github.com/enes-alatas/spool/pull/93)
- fix(loop): context is the last API call's occupancy, not the turn's summed usage by [@enes-alatas](https://github.com/enes-alatas) in[#95](https://github.com/enes-alatas/spool/pull/95)
- feat(web): responsive control-room navigation by [@enes-alatas](https://github.com/enes-alatas) in[#96](https://github.com/enes-alatas/spool/pull/96)
- feat: explicit message references and native group replies by [@enes-alatas](https://github.com/enes-alatas) in[#97](https://github.com/enes-alatas/spool/pull/97)
- fix(web): a fleet card's stats wrap instead of clipping by [@enes-alatas](https://github.com/enes-alatas) in[#99](https://github.com/enes-alatas/spool/pull/99)
- feat: a loop's owner is configured, and owner_dm reaches them by [@enes-alatas](https://github.com/enes-alatas) in[#102](https://github.com/enes-alatas/spool/pull/102)
- feat(web): choose a loop's owner and see whether it can DM them by [@enes-alatas](https://github.com/enes-alatas) in[#103](https://github.com/enes-alatas/spool/pull/103)
- feat: an identity catalog that says who a loop can address by [@enes-alatas](https://github.com/enes-alatas) in[#104](https://github.com/enes-alatas/spool/pull/104)
- fix: the retry ladder survives a failure-driven rotation by [@enes-alatas](https://github.com/enes-alatas) in[#109](https://github.com/enes-alatas/spool/pull/109)
- feat(web): Fleet is one list of loops, not a grid of cards by [@enes-alatas](https://github.com/enes-alatas) in[#105](https://github.com/enes-alatas/spool/pull/105)
- feat(web): the control room scales with the reader's text size by [@enes-alatas](https://github.com/enes-alatas) in[#111](https://github.com/enes-alatas/spool/pull/111)
- feat: [@ALL](https://github.com/ALL) addresses the group's eligible loops by[@enes-alatas](https://github.com/enes-alatas) in[#110](https://github.com/enes-alatas/spool/pull/110)
- fix(web): a state dot is an accent, not a second voice by [@enes-alatas](https://github.com/enes-alatas) in[#113](https://github.com/enes-alatas/spool/pull/113)
- refactor: one threshold rule, one threshold read, one fill signal by [@enes-alatas](https://github.com/enes-alatas) in[#114](https://github.com/enes-alatas/spool/pull/114)
- refactor(web): name the styles three pages were repeating inline by [@enes-alatas](https://github.com/enes-alatas) in[#115](https://github.com/enes-alatas/spool/pull/115)
- fix(loop): a rotation survives the orchestrator restarting by [@enes-alatas](https://github.com/enes-alatas) in[#116](https://github.com/enes-alatas/spool/pull/116)
- fix(web): centre the page column under its own header by [@enes-alatas](https://github.com/enes-alatas) in[#121](https://github.com/enes-alatas/spool/pull/121)
- fix(web): read an absent context fill as unreported, not as zero-length by [@enes-alatas](https://github.com/enes-alatas) in[#125](https://github.com/enes-alatas/spool/pull/125)
- feat(httpapi): read a loop's timeline from its newest end by [@enes-alatas](https://github.com/enes-alatas) in[#127](https://github.com/enes-alatas/spool/pull/127)
- feat(web): open the loop timeline on its newest window by [@enes-alatas](https://github.com/enes-alatas) in[#128](https://github.com/enes-alatas/spool/pull/128)
- test(itest): script a contained loop the way a bare one is scripted by [@enes-alatas](https://github.com/enes-alatas) in[#129](https://github.com/enes-alatas/spool/pull/129)
- fix(web): give a form page the column its content asks for by [@enes-alatas](https://github.com/enes-alatas) in[#133](https://github.com/enes-alatas/spool/pull/133)
- test(itest): port the m2 scenarios and the untested fakeclaude directives by [@enes-alatas](https://github.com/enes-alatas) in[#135](https://github.com/enes-alatas/spool/pull/135)
- fix(web): draw hairlines for the screen they land on by [@enes-alatas](https://github.com/enes-alatas) in[#137](https://github.com/enes-alatas/spool/pull/137)
- fix(web): let Fleet's STATE heading be a heading by [@enes-alatas](https://github.com/enes-alatas) in[#138](https://github.com/enes-alatas/spool/pull/138)
- feat(web): draw the scrollbars in the room's own vocabulary by [@enes-alatas](https://github.com/enes-alatas) in[#139](https://github.com/enes-alatas/spool/pull/139)
- refactor(telegram): the ingest-election margin becomes a value by [@enes-alatas](https://github.com/enes-alatas) in[#140](https://github.com/enes-alatas/spool/pull/140)
- feat(web): clamp a long mission, with the rest one click away by [@enes-alatas](https://github.com/enes-alatas) in[#142](https://github.com/enes-alatas/spool/pull/142)
- test(itest): port the m4 storm-guard and m6 worktree scenarios by [@enes-alatas](https://github.com/enes-alatas) in[#144](https://github.com/enes-alatas/spool/pull/144)
- feat(web): say what happened when a loop's session or machine changed by [@enes-alatas](https://github.com/enes-alatas) in[#145](https://github.com/enes-alatas/spool/pull/145)
- fix(telegram): keep the bot token out of transport errors by [@enes-alatas](https://github.com/enes-alatas) in[#155](https://github.com/enes-alatas/spool/pull/155)
- docs: secrets stay out of evidence, not just out of code by [@enes-alatas](https://github.com/enes-alatas) in[#156](https://github.com/enes-alatas/spool/pull/156)
- fix(telegram): a send that fails is retried, then recorded — never dropped by [@enes-alatas](https://github.com/enes-alatas) in[#148](https://github.com/enes-alatas/spool/pull/148)
- fix(datadir): spool's data is private to the account that runs it by [@enes-alatas](https://github.com/enes-alatas) in[#159](https://github.com/enes-alatas/spool/pull/159)
- feat(web): walk back through a loop's history, without losing the thread by [@enes-alatas](https://github.com/enes-alatas) in[#160](https://github.com/enes-alatas/spool/pull/160)
- fix(store): a single-field write stops reverting its neighbours by [@enes-alatas](https://github.com/enes-alatas) in[#163](https://github.com/enes-alatas/spool/pull/163)
- feat(web): rotate a loop's bot token from the loop page by [@enes-alatas](https://github.com/enes-alatas) in[#165](https://github.com/enes-alatas/spool/pull/165)
- fix(web): credential fields stop showing and stop offering themselves by [@enes-alatas](https://github.com/enes-alatas) in[#169](https://github.com/enes-alatas/spool/pull/169)
- fix(web): dress every control on the element, not on a wrapper by [@enes-alatas](https://github.com/enes-alatas) in[#170](https://github.com/enes-alatas/spool/pull/170)
- feat(loop): tell a running session when its standing instructions change by [@enes-alatas](https://github.com/enes-alatas) in[#174](https://github.com/enes-alatas/spool/pull/174)
- feat(web): the rotation thresholds are settings, so Settings edits them by [@enes-alatas](https://github.com/enes-alatas) in[#172](https://github.com/enes-alatas/spool/pull/172)
- chore(web): a unit test tier for the control room, on Vitest by [@enes-alatas](https://github.com/enes-alatas) in[#176](https://github.com/enes-alatas/spool/pull/176)
- feat(loop): a loop hears which of its own messages never arrived by [@enes-alatas](https://github.com/enes-alatas) in[#177](https://github.com/enes-alatas/spool/pull/177)
- fix(web): a disabled control mutes its text, and drops what well it has by [@enes-alatas](https://github.com/enes-alatas) in[#178](https://github.com/enes-alatas/spool/pull/178)
- fix(httpapi): saving a loop's settings stops reverting a concurrent bot write by [@enes-alatas](https://github.com/enes-alatas) in[#179](https://github.com/enes-alatas/spool/pull/179)
- chore(ci): spend Actions minutes on the jobs a change can break by [@enes-alatas](https://github.com/enes-alatas) in[https://github](https://github) ....
[Read more](https://github.com/enes-alatas/spool/releases/tag/v0.2.0)