@ra3orblade/swarm 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/icons.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // generated by tools/build.ts — pixelarticons (MIT), 24x24 grid
2
- const ICON_PATHS = {"squares-four":"<path d=\"M4 2h16v2H4zM2 4h2v16H2zm2 7h16v2H4zm16-7h2v16h-2z\"/>\n <path d=\"M11 4h2v18h-2z\"/>\n <path d=\"M4 20h16v2H4z\"/>","timer":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","coins":"<path d=\"M6 2h6v2H6zM4 4h2v2H4zm8 0h2v2h-2zm-8 8h2v2H4zm8 0h2v2h-2zm-6 2h6v2H6zM2 6h2v6H2zm12 0h2v6h-2z\"/>\n <path d=\"M14 8h4v2h-4zm-4 10h2v2h-2zm8-8h2v2h-2zm-6 10h2v2h-2zm6-2h2v2h-2z\"/>\n <path d=\"M12 20h6v2h-6zm-4-6h2v4H8zm12-2h2v6h-2zM7 6h4v2H7z\"/>\n <path d=\"M9 6h2v6H9zm6 8h2v4h-2zm-1-2h3v2h-3z\"/>","pulse":"<path d=\"M22 22H4v-2h18v2ZM4 20H2V2h2v18Zm4-6H6v-2h2v2Zm8 0h-2v-2h2v2Zm-6-2H8v-2h2v2Zm4 0h-2v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2h-2V8h2v2Zm8 0h-2V8h2v2Zm2-2h-2V6h2v2Z\"/>","folder-simple":"<path d=\"M4 4h6v2H4zm0 14h16v2H4zM20 8h2v10h-2zM2 6h2v12H2zm8 0h10v2H10z\"/>","folders":"<path d=\"M9 3H7v14h2zM5 7H3v14h2zm12-6H9v2h8zm4 4h-2v12h2zm-2 12H9v2h10zm-4 4H5v2h10zm2-18h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M13 7h6v2h-6zM5 5h2v2H5zm10 14h2v2h-2z\"/>","push-pin":"<path d=\"M7 2h10v2H7zM5 4h2v2H5zm14 0h-2v2h2zM7 17h2v2H7zm2 2h2v2H9zm6-2h2v2h-2zm-2 2h2v2h-2zm-2 2h2v2h-2zm-6-7h2v3H5zm12 0h2v3h-2zM3 6h2v8H3zm18 0h-2v8h2zM10 6h4v2h-4zM8 8h2v4H8zm2 4h4v2h-4zm4-4h2v4h-2z\"/>","push-pin-slash":"<path d=\"M7 2h10v2H7zM5 4h2v2H5zm14 0h-2v2h2zM7 17h2v2H7zm2 2h2v2H9zm6-2h2v2h-2zm-2 2h2v2h-2zm-2 2h2v2h-2zm-6-7h2v3H5zm12 0h2v3h-2zM3 6h2v8H3zm18 0h-2v8h2zM10 6h4v2h-4zM8 8h2v4H8zm2 4h4v2h-4zm4-4h2v4h-2z\"/>","x":"<path d=\"M7 19H5V17H7V19ZM19 19H17V17H19V19ZM9 15V17H7V15H9ZM17 17H15V15H17V17ZM11 15H9V13H11V15ZM15 15H13V13H15V15ZM13 13H11V11H13V13ZM11 11H9V9H11V11ZM15 11H13V9H15V11ZM9 9H7V7H9V9ZM17 9H15V7H17V9ZM7 7H5V5H7V7ZM19 7H17V5H19V7Z\"/>","plus":"<path d=\"M13 11h7v2h-7v7h-2v-7H4v-2h7V4h2v7Z\"/>","dots-three":"<path d=\"M3 9h2v2H3zm8 0h2v2h-2zm8 0h2v2h-2zM1 11h2v2H1zm8 0h2v2H9zm8 0h2v2h-2zM3 13h2v2H3zm8 0h2v2h-2zm8 0h2v2h-2zM5 11h2v2H5zm8 0h2v2h-2zm8 0h2v2h-2z\"/>","dots-three-vertical":"<path d=\"M15 3v2h-2V3zm0 8v2h-2v-2zm0 8v2h-2v-2zM13 1v2h-2V1zm0 8v2h-2V9zm0 8v2h-2v-2zM11 3v2H9V3zm0 8v2H9v-2zm0 8v2H9v-2zm2-14v2h-2V5zm0 8v2h-2v-2zm0 8v2h-2v-2z\"/>","keyboard":"<path d=\"M21 21H3v-2h18v2ZM3 19H1V5h2v14Zm20 0h-2V5h2v14Zm-5-2H6v-2h12v2Zm-9-4H7v-2h2v2Zm4 0h-2v-2h2v2Zm4 0h-2v-2h2v2ZM7 9H5V7h2v2Zm4 0H9V7h2v2Zm4 0h-2V7h2v2Zm4 0h-2V7h2v2Zm2-4H3V3h18v2Z\"/>","play":"<path d=\"M15 11h-2V9h2zm0 4h-2v-2h2zm-2 2h-2v-2h2zm0-8h-2V7h2zm-2-2H9V5h2zM9 21H7V3h2zm6-8h2v-2h-2zm-6 4h2v2H9z\"/>","tree-structure":"<path d=\"M6 4h2v2H6zm2-2h8v2H8zm10 4h2v4h-2zm2 4h2v6h-2zm-2 6h2v2h-2zM4 16h2v2H4zm-2-6h2v6H2zm4 8h12v2H6zM4 6h2v4H4z\"/>\n <path d=\"M11 18h2v4h-2zm5-14h2v2h-2z\"/>","robot":"<path d=\"M5 7h14v2H5zm0 12h14v2H5zM3 9h2v10H3zm16 0h2v10h-2zM1 13h2v2H1zm20 0h2v2h-2zM11 5h2v2h-2zM7 3h4v2H7zm1 9h2v4H8zm6 0h2v4h-2z\"/>","coin":"<path d=\"M6 2h6v2H6zM4 4h2v2H4zm8 0h2v2h-2zm-8 8h2v2H4zm8 0h2v2h-2zm-6 2h6v2H6zM2 6h2v6H2zm12 0h2v6h-2z\"/>\n <path d=\"M14 8h4v2h-4zm-4 10h2v2h-2zm8-8h2v2h-2zm-6 10h2v2h-2zm6-2h2v2h-2z\"/>\n <path d=\"M12 20h6v2h-6zm-4-6h2v4H8zm12-2h2v6h-2zM7 6h4v2H7z\"/>\n <path d=\"M9 6h2v6H9zm6 8h2v4h-2zm-1-2h3v2h-3z\"/>","cpu":"<path d=\"M5 3h14v2H5zm0 16h14v2H5zM3 5h2v14H3zm16 0h2v14h-2zM9 7h6v2H9zm0 8h6v2H9zM7 9h2v6H7zm8 0h2v6h-2zm-4-8h2v2h-2zm0 20h2v2h-2zM1 11h2v2H1zm20 0h2v2h-2zm0-4h2v2h-2zm0 8h2v2h-2zM1 15h2v2H1zm0-8h2v2H1zm6-6h2v2H7zm8 0h2v2h-2zm0 20h2v2h-2zm-8 0h2v2H7z\"/>","arrows-clockwise":"<path d=\"M13 22H11V15H13V22ZM5 21H3V19H5V21ZM21 19V21H19V19H21ZM7 19H5V17H7V19ZM19 19H17V17H19V19ZM9 17H7V15H9V17ZM17 17H15V15H17V17ZM9 13H2V11H9V13ZM22 13H15V11H22V13ZM9 9H7V7H9V9ZM13 9H11V2H13V9ZM17 9H15V7H17V9ZM7 7H5V5H7V7ZM19 7H17V5H19V7ZM5 5H3V3H5V5ZM21 5H19V3H21V5Z\"/>","lightning":"<path d=\"M4 20h16v2H4zM4 2h16v2H4zM2 4h2v16H2zm18 0h2v16h-2zM9 15h6v2H9zM7 9h2v6H7zm8 0h2v6h-2zm-4-2h2v5h-2z\"/>","clock":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","wrench":"<path d=\"M18 22H13V24H11V22H6V20H18V22ZM4 22H2V20H4V22ZM22 22H20V20H22V22ZM6 20H4V18H6V20ZM20 20H18V18H20V20ZM4 18H2V13H0V11H2V6H4V18ZM8 18H6V16H8V18ZM22 11H24V13H22V18H20V13H16V16H8V8H16V11H20V6H22V11ZM10 10V14H14V10H10ZM8 8H6V6H8V8ZM6 6H4V4H6V6ZM20 6H18V4H20V6ZM4 4H2V2H4V4ZM13 2H18V4H6V2H11V0H13V2ZM22 4H20V2H22V4Z\"/>","file-text":"<path d=\"M6 4H4v16h2zm10-2H6v2h10zm4 4h-2v14h2zm-2 14H6v2h12zM16 4h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M12 8h6v2h-6zm-4 8h8v2H8zm0-4h8v2H8zm0-4h2v2H8z\"/>","git-branch":"<path d=\"M2 14h8v2H2zm0 6h8v2H2zm0-4h2v4H2zm6 0h2v4H8zm6-14h8v2h-8zm0 6h8v2h-8zm0-4h2v4h-2zm6 0h2v4h-2zm-8 13h7v2h-7zm5-5h2v5h-2zM5 2h2v10H5z\"/>","git-commit":"<path d=\"M7 7h10v2H7zm0 2h2v6H7zm0 6h10v2H7zm8-6h2v6h-2zM0 11h5v2H0zm19 0h5v2h-5z\"/>","arrow-left":"<path d=\"M20 11v2H4v-2zM8 13v2H6v-2zm2 2v2H8v-2zm2 2v2h-2v-2zm-4-6V9H6v2z\"/>\n <path d=\"M10 15V7H8v8zm2 2V5h-2v12z\"/>","warning":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM20 4h2v16h-2zM2 4h2v16H2zm9 2h2v8h-2zm0 10h2v2h-2z\"/>","check":"<path d=\"M10 18H8v-2h2v2Zm-2-2H6v-2h2v2Zm4-2v2h-2v-2h2Zm-6 0H4v-2h2v2Zm8 0h-2v-2h2v2Zm2-2h-2v-2h2v2Zm2-2h-2V8h2v2Zm2-2h-2V6h2v2Z\"/>","copy":"<path d=\"M8 6h12v2H8zM4 2h12v2H4zm2 6h2v12H6zM2 4h2v12H2zm6 16h12v2H8zM20 8h2v12h-2zm-4-4h2v2h-2zM4 16h2v2H4z\"/>","arrow-square-out":"<path d=\"M11 5H5v2h6V5ZM5 7H3v12h2V7Zm12 12H5v2h12v-2Zm2-6h-2v6h2v-6Zm-8 0H9v2h2v-2Zm2-2h-2v2h2v-2Zm2-2h-2v2h2V9Zm2-2h-2v2h2V7Zm2-2h-2v2h2V5Zm2-2h-2v8h2V3Z\"/>\n <path d=\"M21 3h-8v2h8V3Z\"/>","moon":"<path d=\"M18 22H8v-2h10v2ZM8 20H6v-2h2v2Zm12 0h-2v-2h2v2ZM6 18H4v-2h2v2Zm16 0h-2v-4h-2v-2h2v-2h2v8ZM4 16H2V6h2v10Zm14 0h-6v-2h6v2Zm-6-2h-2v-2h2v2Zm-2-2H8V6h2v6ZM6 6H4V4h2v2Zm8-2h-2v2h-2V4H6V2h8v2Z\"/>","sun":"<path d=\"M13 22h-2v-3h2v3Zm-6-3H5v-2h2v2Zm12 0h-2v-2h2v2Zm-4-2H9v-2h6v2Zm-6-2H7V9h2v6Zm8 0h-2V9h2v6ZM5 13H2v-2h3v2Zm17 0h-3v-2h3v2Zm-7-4H9V7h6v2ZM7 7H5V5h2v2Zm12 0h-2V5h2v2Zm-6-2h-2V2h2v3Z\"/>","monitor":"<path d=\"M4 2h16v2H4zm0 14h16v2H4zM2 4h2v12H2zm18 0h2v12h-2zm-9 14h2v2h-2zm-3 2h8v2H8z\"/>","gear":"<path d=\"M18 22H13V24H11V22H6V20H18V22ZM4 22H2V20H4V22ZM22 22H20V20H22V22ZM6 20H4V18H6V20ZM20 20H18V18H20V20ZM4 18H2V13H0V11H2V6H4V18ZM8 18H6V16H8V18ZM22 11H24V13H22V18H20V13H16V16H8V8H16V11H20V6H22V11ZM10 10V14H14V10H10ZM8 8H6V6H8V8ZM6 6H4V4H6V6ZM20 6H18V4H20V6ZM4 4H2V2H4V4ZM13 2H18V4H6V2H11V0H13V2ZM22 4H20V2H22V4Z\"/>","book-open":"<path d=\"M2 3h9v2H2zM0 19h11v2H0zM13 3h9v2h-9zm0 16h11v2H13zM11 5h2v18h-2zM0 5h2v14H0zm22 0h2v14h-2zm-7 2h5v2h-5zm0 4h5v2h-5zm0 4h2v2h-2z\"/>","trash":"<path d=\"M18 22H6V20H18V22ZM9 6H15V4H17V6H22V8H20V20H18V8H6V20H4V8H2V6H7V4H9V6ZM15 4H9V2H15V4Z\"/>","magnifying-glass":"<path d=\"M22 22h-2v-2h2v2Zm-2-2h-2v-2h2v2Zm-6-2H6v-2h8v2Zm4 0h-2v-2h2v2ZM6 16H4v-2h2v2Zm10 0h-2v-2h2v2ZM4 14H2V6h2v8Zm14 0h-2V6h2v8ZM6 6H4V4h2v2Zm10 0h-2V4h2v2Zm-2-2H6V2h8v2Z\"/>","terminal-window":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM2 4h2v16H2zm18 0h2v16h-2zM6 16h2v2H6zm2-2h2v2H8zm-2-2h2v2H6z\"/>","stack":"<path d=\"M9 3H7v14h2zM5 7H3v14h2zm12-6H9v2h8zm4 4h-2v12h2zm-2 12H9v2h10zm-4 4H5v2h10zm2-18h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M13 7h6v2h-6zM5 5h2v2H5zm10 14h2v2h-2z\"/>","chart-bar":"<path d=\"M4 20h18v2H4zM2 2h2v18H2zm16 11v3h-2v-3zM8 13v3H6v-3zm8-2v2H8v-2zm0 5v2H8v-2zm4-12v3h-2V4zM8 4v3H6V4zm10-2v2H8V2zm0 5v2H8V7z\"/>","clock-counter-clockwise":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","brain":"<path d=\"M4 6h16v2H4zm0 14h16v2H4zM2 8h2v12H2zm18 0h2v12h-2z\"/>\n <path d=\"M11 4h2v4h-2zm-3 6h2v2H8zm6 0h4v2h-4zm-1-8h4v2h-4zM0 12h2v2H0zm22 0h2v2h-2zm-12 4h4v2h-4zm-2-2h2v2H8zm6 0h2v2h-2z\"/>","eye":"<path d=\"M16 20H8v-2h8v2Zm-8-2H4v-2h4v2Zm12 0h-4v-2h4v2ZM4 16H2v-2h2v2Zm10-6h-2v2h2v-2h2v4h-2v2h-4v-2H8v-4h2V8h4v2Zm8 6h-2v-2h2v2ZM2 14H0v-4h2v4Zm22 0h-2v-4h2v4ZM4 10H2V8h2v2Zm18 0h-2V8h2v2ZM8 8H4V6h4v2Zm12 0h-4V6h4v2Zm-4-2H8V4h8v2Z\"/>","pause":"<path d=\"M10 20H4V4h6v16Zm8-16v16h-6V4h6Zm-4 2v12h2V6h-2ZM6 18h2V6H6v12Z\"/>","stop":"<path d=\"M20 20H4V4H20V20ZM6 18H18V6H6V18ZM14 14H10V10H14V14Z\"/>","broadcast":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM20 4h2v16h-2zM2 4h2v16H2zm5 11h2v2H7zm0-4h4v2H7zm0-4h6v2H7zm4 6h2v4h-2zm4-2h2v6h-2zm-2-2h2v2h-2z\"/>","plugs-connected":"<path d=\"M16 18h-3v4h-2v-4H8v-2h8v2Zm-8-2H6v-2h2v2Zm10 0h-2v-2h2v2Zm-8-9h4V2h2v5h5v2h-1v5h-2V9H6v5H4V9H3V7h5V2h2v5Z\"/>","plugs":"<path d=\"M16 18h-3v4h-2v-4H8v-2h8v2Zm-8-2H6v-2h2v2Zm10 0h-2v-2h2v2Zm-8-9h4V2h2v5h5v2h-1v5h-2V9H6v5H4V9H3V7h5V2h2v5Z\"/>","currency-dollar":"<path d=\"M17 18h-4v4h-2v-4H7v-2h10v2Zm2-2h-2v-3h2v3Zm-2-3H7v-2h10v2ZM7 11H5V8h2v3Zm6-5h4v2H7V6h4V2h2v4Z\"/>","rows":"<path d=\"M10 5h12v2H10zm0 4h8v2h-8zm0 4h12v2H10zm0 4h8v2h-8zm-4-6H4V9h2v2ZM4 9H2V7h2v2Zm4 0H6V7h2v2ZM6 7H4V5h2v2Zm-2 6h2v2H4zm0 4h2v2H4zm-2 0v-2h2v2zm4 0v-2h2v2z\"/>","list-bullets":"<path d=\"M10 5h12v2H10zm0 4h8v2h-8zm0 4h12v2H10zm0 4h8v2h-8zm-4-6H4V9h2v2ZM4 9H2V7h2v2Zm4 0H6V7h2v2ZM6 7H4V5h2v2Zm-2 6h2v2H4zm0 4h2v2H4zm-2 0v-2h2v2zm4 0v-2h2v2z\"/>","sliders":"<path d=\"M8 14H7v6H5v-6H2v-2h6v2Zm5 6h-2V10h2v10Zm9-2h-3v2h-2v-2h-1v-2h6v2Zm-3-4h-2V4h2v10ZM7 10H5V4h2v6Zm6-4h2v2H9V6h2V4h2v2Z\"/>","comment-text":"<path d=\"M4 2h16v2H4zm0 14h14v2H4zm2-6h6v2H6zm0-4h12v2H6zM2 4h2v12H2zm18 0h2v18h-2zm-2 14h2v2h-2z\"/>","arrow-bar-left":"<path d=\"M4 4v16H2V4zm18 7v2H6v-2zm-12 2v2H8v-2zm2 2v2h-2v-2zm2 2v2h-2v-2zm-4-8v2H8V9zm2-2v2h-2V7zm2-2v2h-2V5z\"/>","arrow-bar-right":"<path d=\"M20 4v16h2V4zM2 11v2h16v-2zm12 2v2h2v-2zm-2 2v2h2v-2zm-2 2v2h2v-2zm4-8v2h2V9zm-2-2v2h2V7zm-2-2v2h2V5z\"/>","git-pull-request":"<path d=\"M2 10h8V8H2zm0-6h8V2H2zm0 4h2V4H2zm6 0h2V4H8zm6 14h8v-2h-8zm0-6h8v-2h-8zm0 4h2v-4h-2zm6 0h2v-4h-2zM12 7h5V5h-5zM5 22h2V12H5z\"/>","star":"<path d=\"M5 20H8V22H3V16H5V20ZM21 22H16V20H19V16H21V22ZM10 20H8V18H10V20ZM16 20H14V18H16V20ZM14 18H10V16H14V18ZM7 16H5V13H7V16ZM19 16H17V13H19V16ZM5 13H3V11H5V13ZM21 13H19V11H21V13ZM9 9H3V11H1V7H9V9ZM23 11H21V9H15V7H23V11ZM11 7H9V3H11V7ZM15 7H13V3H15V7ZM13 3H11V1H13V3Z\"/>","shield":"<path d=\"M4 2h16v2H4zM2 4h2v10H2zm18 0h2v10h-2zM4 14h2v2H4zm2 2h2v2H6zm4 4h4v2h-4zm10-6h-2v2h2zm-2 2h-2v2h2zm-2 2h-2v2h2zm-6 0H8v2h2z\"/>","bell":"<path d=\"M9 2h6v2H9zM7 4h2v2H7zm8 0h2v2h-2zM5 6h2v7H5zm12 0h2v7h-2zM3 13h2v4H3zm16 0h2v4h-2z\"/>\n <path d=\"M3 15h18v2H3zm5 3h2v2H8zm6 0h2v2h-2zm-4 2h4v2h-4z\"/>","arrow-right":"<path d=\"M4 11v2h16v-2zm12 2v2h2v-2zm-2 2v2h2v-2zm-2 2v2h2v-2zm4-6V9h2v2z\"/>\n <path d=\"M14 15V7h2v8zm-2 2V5h2v12z\"/>","chevron-down":"<path d=\"M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z\"/>"};
2
+ const ICON_PATHS = {"squares-four":"<path d=\"M4 2h16v2H4zM2 4h2v16H2zm2 7h16v2H4zm16-7h2v16h-2z\"/>\n <path d=\"M11 4h2v18h-2z\"/>\n <path d=\"M4 20h16v2H4z\"/>","timer":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","coins":"<path d=\"M6 2h6v2H6zM4 4h2v2H4zm8 0h2v2h-2zm-8 8h2v2H4zm8 0h2v2h-2zm-6 2h6v2H6zM2 6h2v6H2zm12 0h2v6h-2z\"/>\n <path d=\"M14 8h4v2h-4zm-4 10h2v2h-2zm8-8h2v2h-2zm-6 10h2v2h-2zm6-2h2v2h-2z\"/>\n <path d=\"M12 20h6v2h-6zm-4-6h2v4H8zm12-2h2v6h-2zM7 6h4v2H7z\"/>\n <path d=\"M9 6h2v6H9zm6 8h2v4h-2zm-1-2h3v2h-3z\"/>","pulse":"<path d=\"M22 22H4v-2h18v2ZM4 20H2V2h2v18Zm4-6H6v-2h2v2Zm8 0h-2v-2h2v2Zm-6-2H8v-2h2v2Zm4 0h-2v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2h-2V8h2v2Zm8 0h-2V8h2v2Zm2-2h-2V6h2v2Z\"/>","folder-simple":"<path d=\"M4 4h6v2H4zm0 14h16v2H4zM20 8h2v10h-2zM2 6h2v12H2zm8 0h10v2H10z\"/>","folders":"<path d=\"M9 3H7v14h2zM5 7H3v14h2zm12-6H9v2h8zm4 4h-2v12h2zm-2 12H9v2h10zm-4 4H5v2h10zm2-18h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M13 7h6v2h-6zM5 5h2v2H5zm10 14h2v2h-2z\"/>","push-pin":"<path d=\"M7 2h10v2H7zM5 4h2v2H5zm14 0h-2v2h2zM7 17h2v2H7zm2 2h2v2H9zm6-2h2v2h-2zm-2 2h2v2h-2zm-2 2h2v2h-2zm-6-7h2v3H5zm12 0h2v3h-2zM3 6h2v8H3zm18 0h-2v8h2zM10 6h4v2h-4zM8 8h2v4H8zm2 4h4v2h-4zm4-4h2v4h-2z\"/>","push-pin-slash":"<path d=\"M7 2h10v2H7zM5 4h2v2H5zm14 0h-2v2h2zM7 17h2v2H7zm2 2h2v2H9zm6-2h2v2h-2zm-2 2h2v2h-2zm-2 2h2v2h-2zm-6-7h2v3H5zm12 0h2v3h-2zM3 6h2v8H3zm18 0h-2v8h2zM10 6h4v2h-4zM8 8h2v4H8zm2 4h4v2h-4zm4-4h2v4h-2z\"/>","x":"<path d=\"M7 19H5V17H7V19ZM19 19H17V17H19V19ZM9 15V17H7V15H9ZM17 17H15V15H17V17ZM11 15H9V13H11V15ZM15 15H13V13H15V15ZM13 13H11V11H13V13ZM11 11H9V9H11V11ZM15 11H13V9H15V11ZM9 9H7V7H9V9ZM17 9H15V7H17V9ZM7 7H5V5H7V7ZM19 7H17V5H19V7Z\"/>","plus":"<path d=\"M13 11h7v2h-7v7h-2v-7H4v-2h7V4h2v7Z\"/>","dots-three":"<path d=\"M3 9h2v2H3zm8 0h2v2h-2zm8 0h2v2h-2zM1 11h2v2H1zm8 0h2v2H9zm8 0h2v2h-2zM3 13h2v2H3zm8 0h2v2h-2zm8 0h2v2h-2zM5 11h2v2H5zm8 0h2v2h-2zm8 0h2v2h-2z\"/>","dots-three-vertical":"<path d=\"M15 3v2h-2V3zm0 8v2h-2v-2zm0 8v2h-2v-2zM13 1v2h-2V1zm0 8v2h-2V9zm0 8v2h-2v-2zM11 3v2H9V3zm0 8v2H9v-2zm0 8v2H9v-2zm2-14v2h-2V5zm0 8v2h-2v-2zm0 8v2h-2v-2z\"/>","keyboard":"<path d=\"M21 21H3v-2h18v2ZM3 19H1V5h2v14Zm20 0h-2V5h2v14Zm-5-2H6v-2h12v2Zm-9-4H7v-2h2v2Zm4 0h-2v-2h2v2Zm4 0h-2v-2h2v2ZM7 9H5V7h2v2Zm4 0H9V7h2v2Zm4 0h-2V7h2v2Zm4 0h-2V7h2v2Zm2-4H3V3h18v2Z\"/>","play":"<path d=\"M15 11h-2V9h2zm0 4h-2v-2h2zm-2 2h-2v-2h2zm0-8h-2V7h2zm-2-2H9V5h2zM9 21H7V3h2zm6-8h2v-2h-2zm-6 4h2v2H9z\"/>","tree-structure":"<path d=\"M6 4h2v2H6zm2-2h8v2H8zm10 4h2v4h-2zm2 4h2v6h-2zm-2 6h2v2h-2zM4 16h2v2H4zm-2-6h2v6H2zm4 8h12v2H6zM4 6h2v4H4z\"/>\n <path d=\"M11 18h2v4h-2zm5-14h2v2h-2z\"/>","robot":"<path d=\"M5 7h14v2H5zm0 12h14v2H5zM3 9h2v10H3zm16 0h2v10h-2zM1 13h2v2H1zm20 0h2v2h-2zM11 5h2v2h-2zM7 3h4v2H7zm1 9h2v4H8zm6 0h2v4h-2z\"/>","coin":"<path d=\"M6 2h6v2H6zM4 4h2v2H4zm8 0h2v2h-2zm-8 8h2v2H4zm8 0h2v2h-2zm-6 2h6v2H6zM2 6h2v6H2zm12 0h2v6h-2z\"/>\n <path d=\"M14 8h4v2h-4zm-4 10h2v2h-2zm8-8h2v2h-2zm-6 10h2v2h-2zm6-2h2v2h-2z\"/>\n <path d=\"M12 20h6v2h-6zm-4-6h2v4H8zm12-2h2v6h-2zM7 6h4v2H7z\"/>\n <path d=\"M9 6h2v6H9zm6 8h2v4h-2zm-1-2h3v2h-3z\"/>","cpu":"<path d=\"M5 3h14v2H5zm0 16h14v2H5zM3 5h2v14H3zm16 0h2v14h-2zM9 7h6v2H9zm0 8h6v2H9zM7 9h2v6H7zm8 0h2v6h-2zm-4-8h2v2h-2zm0 20h2v2h-2zM1 11h2v2H1zm20 0h2v2h-2zm0-4h2v2h-2zm0 8h2v2h-2zM1 15h2v2H1zm0-8h2v2H1zm6-6h2v2H7zm8 0h2v2h-2zm0 20h2v2h-2zm-8 0h2v2H7z\"/>","arrows-clockwise":"<path d=\"M13 22H11V15H13V22ZM5 21H3V19H5V21ZM21 19V21H19V19H21ZM7 19H5V17H7V19ZM19 19H17V17H19V19ZM9 17H7V15H9V17ZM17 17H15V15H17V17ZM9 13H2V11H9V13ZM22 13H15V11H22V13ZM9 9H7V7H9V9ZM13 9H11V2H13V9ZM17 9H15V7H17V9ZM7 7H5V5H7V7ZM19 7H17V5H19V7ZM5 5H3V3H5V5ZM21 5H19V3H21V5Z\"/>","lightning":"<path d=\"M4 20h16v2H4zM4 2h16v2H4zM2 4h2v16H2zm18 0h2v16h-2zM9 15h6v2H9zM7 9h2v6H7zm8 0h2v6h-2zm-4-2h2v5h-2z\"/>","clock":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","wrench":"<path d=\"M18 22H13V24H11V22H6V20H18V22ZM4 22H2V20H4V22ZM22 22H20V20H22V22ZM6 20H4V18H6V20ZM20 20H18V18H20V20ZM4 18H2V13H0V11H2V6H4V18ZM8 18H6V16H8V18ZM22 11H24V13H22V18H20V13H16V16H8V8H16V11H20V6H22V11ZM10 10V14H14V10H10ZM8 8H6V6H8V8ZM6 6H4V4H6V6ZM20 6H18V4H20V6ZM4 4H2V2H4V4ZM13 2H18V4H6V2H11V0H13V2ZM22 4H20V2H22V4Z\"/>","file-text":"<path d=\"M6 4H4v16h2zm10-2H6v2h10zm4 4h-2v14h2zm-2 14H6v2h12zM16 4h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M12 8h6v2h-6zm-4 8h8v2H8zm0-4h8v2H8zm0-4h2v2H8z\"/>","git-branch":"<path d=\"M2 14h8v2H2zm0 6h8v2H2zm0-4h2v4H2zm6 0h2v4H8zm6-14h8v2h-8zm0 6h8v2h-8zm0-4h2v4h-2zm6 0h2v4h-2zm-8 13h7v2h-7zm5-5h2v5h-2zM5 2h2v10H5z\"/>","git-commit":"<path d=\"M7 7h10v2H7zm0 2h2v6H7zm0 6h10v2H7zm8-6h2v6h-2zM0 11h5v2H0zm19 0h5v2h-5z\"/>","arrow-left":"<path d=\"M20 11v2H4v-2zM8 13v2H6v-2zm2 2v2H8v-2zm2 2v2h-2v-2zm-4-6V9H6v2z\"/>\n <path d=\"M10 15V7H8v8zm2 2V5h-2v12z\"/>","warning":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM20 4h2v16h-2zM2 4h2v16H2zm9 2h2v8h-2zm0 10h2v2h-2z\"/>","check":"<path d=\"M10 18H8v-2h2v2Zm-2-2H6v-2h2v2Zm4-2v2h-2v-2h2Zm-6 0H4v-2h2v2Zm8 0h-2v-2h2v2Zm2-2h-2v-2h2v2Zm2-2h-2V8h2v2Zm2-2h-2V6h2v2Z\"/>","copy":"<path d=\"M8 6h12v2H8zM4 2h12v2H4zm2 6h2v12H6zM2 4h2v12H2zm6 16h12v2H8zM20 8h2v12h-2zm-4-4h2v2h-2zM4 16h2v2H4z\"/>","arrow-square-out":"<path d=\"M11 5H5v2h6V5ZM5 7H3v12h2V7Zm12 12H5v2h12v-2Zm2-6h-2v6h2v-6Zm-8 0H9v2h2v-2Zm2-2h-2v2h2v-2Zm2-2h-2v2h2V9Zm2-2h-2v2h2V7Zm2-2h-2v2h2V5Zm2-2h-2v8h2V3Z\"/>\n <path d=\"M21 3h-8v2h8V3Z\"/>","moon":"<path d=\"M18 22H8v-2h10v2ZM8 20H6v-2h2v2Zm12 0h-2v-2h2v2ZM6 18H4v-2h2v2Zm16 0h-2v-4h-2v-2h2v-2h2v8ZM4 16H2V6h2v10Zm14 0h-6v-2h6v2Zm-6-2h-2v-2h2v2Zm-2-2H8V6h2v6ZM6 6H4V4h2v2Zm8-2h-2v2h-2V4H6V2h8v2Z\"/>","sun":"<path d=\"M13 22h-2v-3h2v3Zm-6-3H5v-2h2v2Zm12 0h-2v-2h2v2Zm-4-2H9v-2h6v2Zm-6-2H7V9h2v6Zm8 0h-2V9h2v6ZM5 13H2v-2h3v2Zm17 0h-3v-2h3v2Zm-7-4H9V7h6v2ZM7 7H5V5h2v2Zm12 0h-2V5h2v2Zm-6-2h-2V2h2v3Z\"/>","monitor":"<path d=\"M4 2h16v2H4zm0 14h16v2H4zM2 4h2v12H2zm18 0h2v12h-2zm-9 14h2v2h-2zm-3 2h8v2H8z\"/>","gear":"<path d=\"M18 22H13V24H11V22H6V20H18V22ZM4 22H2V20H4V22ZM22 22H20V20H22V22ZM6 20H4V18H6V20ZM20 20H18V18H20V20ZM4 18H2V13H0V11H2V6H4V18ZM8 18H6V16H8V18ZM22 11H24V13H22V18H20V13H16V16H8V8H16V11H20V6H22V11ZM10 10V14H14V10H10ZM8 8H6V6H8V8ZM6 6H4V4H6V6ZM20 6H18V4H20V6ZM4 4H2V2H4V4ZM13 2H18V4H6V2H11V0H13V2ZM22 4H20V2H22V4Z\"/>","book-open":"<path d=\"M2 3h9v2H2zM0 19h11v2H0zM13 3h9v2h-9zm0 16h11v2H13zM11 5h2v18h-2zM0 5h2v14H0zm22 0h2v14h-2zm-7 2h5v2h-5zm0 4h5v2h-5zm0 4h2v2h-2z\"/>","trash":"<path d=\"M18 22H6V20H18V22ZM9 6H15V4H17V6H22V8H20V20H18V8H6V20H4V8H2V6H7V4H9V6ZM15 4H9V2H15V4Z\"/>","magnifying-glass":"<path d=\"M22 22h-2v-2h2v2Zm-2-2h-2v-2h2v2Zm-6-2H6v-2h8v2Zm4 0h-2v-2h2v2ZM6 16H4v-2h2v2Zm10 0h-2v-2h2v2ZM4 14H2V6h2v8Zm14 0h-2V6h2v8ZM6 6H4V4h2v2Zm10 0h-2V4h2v2Zm-2-2H6V2h8v2Z\"/>","terminal-window":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM2 4h2v16H2zm18 0h2v16h-2zM6 16h2v2H6zm2-2h2v2H8zm-2-2h2v2H6z\"/>","stack":"<path d=\"M9 3H7v14h2zM5 7H3v14h2zm12-6H9v2h8zm4 4h-2v12h2zm-2 12H9v2h10zm-4 4H5v2h10zm2-18h2v2h-2zm-4 0h2v6h-2z\"/>\n <path d=\"M13 7h6v2h-6zM5 5h2v2H5zm10 14h2v2h-2z\"/>","chart-bar":"<path d=\"M4 20h18v2H4zM2 2h2v18H2zm16 11v3h-2v-3zM8 13v3H6v-3zm8-2v2H8v-2zm0 5v2H8v-2zm4-12v3h-2V4zM8 4v3H6V4zm10-2v2H8V2zm0 5v2H8V7z\"/>","clock-counter-clockwise":"<path d=\"M6 2h12v2H6zM2 6h2v12H2zm18 0h2v12h-2zm-2-2h2v2h-2zM4 4h2v2H4zm2 18h12v-2H6zm12-2h2v-2h-2zM4 20h2v-2H4zm7-14h2v7h-2zm2 7h2v2h-2zm2 2h2v2h-2z\"/>","brain":"<path d=\"M4 6h16v2H4zm0 14h16v2H4zM2 8h2v12H2zm18 0h2v12h-2z\"/>\n <path d=\"M11 4h2v4h-2zm-3 6h2v2H8zm6 0h4v2h-4zm-1-8h4v2h-4zM0 12h2v2H0zm22 0h2v2h-2zm-12 4h4v2h-4zm-2-2h2v2H8zm6 0h2v2h-2z\"/>","eye":"<path d=\"M16 20H8v-2h8v2Zm-8-2H4v-2h4v2Zm12 0h-4v-2h4v2ZM4 16H2v-2h2v2Zm10-6h-2v2h2v-2h2v4h-2v2h-4v-2H8v-4h2V8h4v2Zm8 6h-2v-2h2v2ZM2 14H0v-4h2v4Zm22 0h-2v-4h2v4ZM4 10H2V8h2v2Zm18 0h-2V8h2v2ZM8 8H4V6h4v2Zm12 0h-4V6h4v2Zm-4-2H8V4h8v2Z\"/>","pause":"<path d=\"M10 20H4V4h6v16Zm8-16v16h-6V4h6Zm-4 2v12h2V6h-2ZM6 18h2V6H6v12Z\"/>","stop":"<path d=\"M20 20H4V4H20V20ZM6 18H18V6H6V18ZM14 14H10V10H14V14Z\"/>","broadcast":"<path d=\"M4 2h16v2H4zm0 18h16v2H4zM20 4h2v16h-2zM2 4h2v16H2zm5 11h2v2H7zm0-4h4v2H7zm0-4h6v2H7zm4 6h2v4h-2zm4-2h2v6h-2zm-2-2h2v2h-2z\"/>","plugs-connected":"<path d=\"M16 18h-3v4h-2v-4H8v-2h8v2Zm-8-2H6v-2h2v2Zm10 0h-2v-2h2v2Zm-8-9h4V2h2v5h5v2h-1v5h-2V9H6v5H4V9H3V7h5V2h2v5Z\"/>","plugs":"<path d=\"M16 18h-3v4h-2v-4H8v-2h8v2Zm-8-2H6v-2h2v2Zm10 0h-2v-2h2v2Zm-8-9h4V2h2v5h5v2h-1v5h-2V9H6v5H4V9H3V7h5V2h2v5Z\"/>","currency-dollar":"<path d=\"M17 18h-4v4h-2v-4H7v-2h10v2Zm2-2h-2v-3h2v3Zm-2-3H7v-2h10v2ZM7 11H5V8h2v3Zm6-5h4v2H7V6h4V2h2v4Z\"/>","rows":"<path d=\"M10 5h12v2H10zm0 4h8v2h-8zm0 4h12v2H10zm0 4h8v2h-8zm-4-6H4V9h2v2ZM4 9H2V7h2v2Zm4 0H6V7h2v2ZM6 7H4V5h2v2Zm-2 6h2v2H4zm0 4h2v2H4zm-2 0v-2h2v2zm4 0v-2h2v2z\"/>","list-bullets":"<path d=\"M10 5h12v2H10zm0 4h8v2h-8zm0 4h12v2H10zm0 4h8v2h-8zm-4-6H4V9h2v2ZM4 9H2V7h2v2Zm4 0H6V7h2v2ZM6 7H4V5h2v2Zm-2 6h2v2H4zm0 4h2v2H4zm-2 0v-2h2v2zm4 0v-2h2v2z\"/>","sliders":"<path d=\"M8 14H7v6H5v-6H2v-2h6v2Zm5 6h-2V10h2v10Zm9-2h-3v2h-2v-2h-1v-2h6v2Zm-3-4h-2V4h2v10ZM7 10H5V4h2v6Zm6-4h2v2H9V6h2V4h2v2Z\"/>","comment-text":"<path d=\"M4 2h16v2H4zm0 14h14v2H4zm2-6h6v2H6zm0-4h12v2H6zM2 4h2v12H2zm18 0h2v18h-2zm-2 14h2v2h-2z\"/>","arrow-bar-left":"<path d=\"M4 4v16H2V4zm18 7v2H6v-2zm-12 2v2H8v-2zm2 2v2h-2v-2zm2 2v2h-2v-2zm-4-8v2H8V9zm2-2v2h-2V7zm2-2v2h-2V5z\"/>","arrow-bar-right":"<path d=\"M20 4v16h2V4zM2 11v2h16v-2zm12 2v2h2v-2zm-2 2v2h2v-2zm-2 2v2h2v-2zm4-8v2h2V9zm-2-2v2h2V7zm-2-2v2h2V5z\"/>","git-pull-request":"<path d=\"M2 10h8V8H2zm0-6h8V2H2zm0 4h2V4H2zm6 0h2V4H8zm6 14h8v-2h-8zm0-6h8v-2h-8zm0 4h2v-4h-2zm6 0h2v-4h-2zM12 7h5V5h-5zM5 22h2V12H5z\"/>","star":"<path d=\"M5 20H8V22H3V16H5V20ZM21 22H16V20H19V16H21V22ZM10 20H8V18H10V20ZM16 20H14V18H16V20ZM14 18H10V16H14V18ZM7 16H5V13H7V16ZM19 16H17V13H19V16ZM5 13H3V11H5V13ZM21 13H19V11H21V13ZM9 9H3V11H1V7H9V9ZM23 11H21V9H15V7H23V11ZM11 7H9V3H11V7ZM15 7H13V3H15V7ZM13 3H11V1H13V3Z\"/>","gift":"<path d=\"M4 6h16v2H4zM2 8h2v4H2zm2 4h16v2H4zm16-4h2v4h-2zM6 4h2v2H6zm2-2h3v2H8zm3 2h2v2h-2zm2-2h3v2h-3zm3 2h2v2h-2zM4 14h2v6H4zm2 6h12v2H6zm12-6h2v6h-2zm-7-6h2v4h-2zm0 6h2v6h-2z\"/>","shield":"<path d=\"M4 2h16v2H4zM2 4h2v10H2zm18 0h2v10h-2zM4 14h2v2H4zm2 2h2v2H6zm4 4h4v2h-4zm10-6h-2v2h2zm-2 2h-2v2h2zm-2 2h-2v2h2zm-6 0H8v2h2z\"/>","bell":"<path d=\"M9 2h6v2H9zM7 4h2v2H7zm8 0h2v2h-2zM5 6h2v7H5zm12 0h2v7h-2zM3 13h2v4H3zm16 0h2v4h-2z\"/>\n <path d=\"M3 15h18v2H3zm5 3h2v2H8zm6 0h2v2h-2zm-4 2h4v2h-4z\"/>","arrow-right":"<path d=\"M4 11v2h16v-2zm12 2v2h2v-2zm-2 2v2h2v-2zm-2 2v2h2v-2zm4-6V9h2v2z\"/>\n <path d=\"M14 15V7h2v8zm-2 2V5h2v12z\"/>","chevron-down":"<path d=\"M13 16h-2v-2h2v2Zm-2-2H9v-2h2v2Zm4 0h-2v-2h2v2Zm-6-2H7v-2h2v2Zm8 0h-2v-2h2v2ZM7 10H5V8h2v2Zm12 0h-2V8h2v2Z\"/>"};
3
3
  window.icon = (name, size = 16, cls = "") => { const p = ICON_PATHS[name]; if (!p) return ""; return `<svg class="ph ${cls}" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor" shape-rendering="crispEdges" aria-hidden="true">${p}</svg>`; };
4
4
  window.ICON_PATHS = ICON_PATHS;
5
- window.ICON_NAMES = ["squares-four","timer","coins","pulse","folder-simple","folders","push-pin","push-pin-slash","x","plus","dots-three","dots-three-vertical","keyboard","play","tree-structure","robot","coin","cpu","arrows-clockwise","lightning","clock","wrench","file-text","git-branch","git-commit","arrow-left","warning","check","copy","arrow-square-out","moon","sun","monitor","gear","book-open","trash","magnifying-glass","terminal-window","stack","chart-bar","clock-counter-clockwise","brain","eye","pause","stop","broadcast","plugs-connected","plugs","currency-dollar","rows","list-bullets","sliders","comment-text","arrow-bar-left","arrow-bar-right","git-pull-request","star","shield","bell","arrow-right","chevron-down"];
5
+ window.ICON_NAMES = ["squares-four","timer","coins","pulse","folder-simple","folders","push-pin","push-pin-slash","x","plus","dots-three","dots-three-vertical","keyboard","play","tree-structure","robot","coin","cpu","arrows-clockwise","lightning","clock","wrench","file-text","git-branch","git-commit","arrow-left","warning","check","copy","arrow-square-out","moon","sun","monitor","gear","book-open","trash","magnifying-glass","terminal-window","stack","chart-bar","clock-counter-clockwise","brain","eye","pause","stop","broadcast","plugs-connected","plugs","currency-dollar","rows","list-bullets","sliders","comment-text","arrow-bar-left","arrow-bar-right","git-pull-request","star","gift","shield","bell","arrow-right","chevron-down"];
@@ -1,2 +1,2 @@
1
1
  // generated by tools/build.ts from CHANGELOG.md
2
- window.RELEASE_NOTES = {"0.14.0":{"date":"2026-09-12","html":"<p>The <b>Act</b> release: Swarm stops only watching agents and starts steering them — every new</p>\n<p>capability below reaches an <em>interactive</em> session, not just a spawned run.</p>\n<h4>Added</h4>\n<ul><li><b>The repair loop: a session cannot say it is done while a required gate fails.</b> With</li></ul><p><code>[gates] on_stop = \"block\"</code>, stopping inside a held worktree runs the executable required gates</p>\n<p>first, and while one fails the stop is refused — Claude Code keeps working, and what it reads is</p>\n<p>the gate's name and the tail of its output, not a hint. Bounded: at most <code>max_blocks</code> refusals</p>\n<p>per session (three by default), then the stop goes through and a <code>gate_failed</code> incident opens.</p>\n<p>Every refusal is a <code>gate.blocked</code> line in the session log. Default stays <code>record</code>, so nothing</p>\n<p>changes until a repo opts in; subagents are never refused; the wait is capped by <code>stop_timeout</code>.</p>\n<p>This is Claude Code's own <code>Stop</code> → <code>block</code> hook contract, verified against the current reference.</p>\n<ul><li><b>The permission card, for interactive sessions too.</b> Until now only spawned runs got the</li></ul><p>dashboard's Allow / Deny card; a session in your terminal asked there and nowhere else. Claude</p>\n<p>Code's <code>PermissionRequest</code> hook fires before the terminal dialog and holds it while the hook</p>\n<p>runs, so the daemon now parks the prompt as a card on the session page (the session shows as</p>\n<p><em>asking</em> on Fleet, and a desktop notification fires) and waits <code>[broker] interactive_wait</code></p>\n<p>seconds — 30 by default — for an answer. Allow or deny on the card and the terminal never asks;</p>\n<p>press <em>Answer in terminal</em> or let the countdown run out and the dialog appears there unchanged.</p>\n<p>The card names the rule that flagged the call when one did. Nothing waits unless a dashboard is</p>\n<p>open <b>and visible</b> — a background tab is not someone watching, and a terminal prompt is never</p>\n<p>held for a card nobody can see. <code>swarm install</code> registers the new hook; run it once after</p>\n<p>updating.</p>\n<ul><li><b>Rules that rewrite a call instead of refusing it, and your own rules.</b> Two new rules have a</li></ul><p>third answer besides <em>ask</em> and <em>deny</em>: <code>no_verify = \"rewrite\"</code> drops <code>--no-verify</code> /</p>\n<p><code>--no-gpg-sign</code> from any git command and lets it run; <code>dry_run_first = \"rewrite\"</code> turns the</p>\n<p>first <code>terraform apply</code>, <code>kubectl delete</code> or <code>helm uninstall</code> in a session into its dry-run form</p>\n<p>and allows the second. The agent is told what changed and why; the Incidents view shows the</p>\n<p>call that was asked for beside the one that ran, marked <em>Rewritten</em>, so rule effectiveness</p>\n<p>scores it like any rule. Both ship <code>off</code>. <code>[[rules.custom]]</code> is the promised DSL: <code>name</code>,</p>\n<p><code>match</code> (a regex over the command), <code>action</code> (<code>ask | deny | rewrite | off</code>), <code>replace</code>,</p>\n<p><code>reason</code> — evaluated in config order after the coordination rules, so a deny is never softened.</p>\n<p>Codify writes it, <code>swarm rules dryrun</code> replays history under it, spawned runs get the rewrite</p>\n<p>through the permission broker, and interactive sessions through Claude Code's <code>updatedInput</code>.</p>\n<p>A rewrite only ever touches a command it can reason about whole — one invocation, no <code>&amp;&amp;</code>, no</p>\n<p>pipe, no redirection, no substitution — and the rewritten command is put back through the rules</p>\n<p>before it runs, so a rewrite can neither smuggle a second command past the guards nor approve</p>\n<p>the call outright. Flags are stripped outside quotes only, so a commit message that mentions</p>\n<p><code>--no-verify</code> is left alone.</p>\n<ul><li><b>A heads-up the moment two live sessions edit the same file.</b> The collision graph could show</li></ul><p>it after the fact; nobody told the agent. Now, right after an edit lands, a session whose file</p>\n<p>another live session edited within the last 15 minutes reads who that was — task, branch, how</p>\n<p>long ago — and the suggestion to look at that diff before going further. Context on</p>\n<p><code>PostToolUse</code>, never a refusal; once per pair of sessions per file per window; each one a</p>\n<p><code>collision.warned</code> line in the session log. <code>[rules] collision_context = false</code> turns it off,</p>\n<p><code>collision_window</code> sets the minutes.</p>\n<ul><li><b>Messages wake an idle session.</b> A message from another agent or from you (<code>swarm msg send</code>,</li></ul><p><code>swarm_send</code>) and an answer to a session's question used to arrive on that session's next tool</p>\n<p>call, which for a session sitting idle at its prompt meant never. <code>swarm install</code> now arms a</p>\n<p>background hook after every turn — Claude Code's <code>asyncRewake</code> — that the daemon holds open and</p>\n<p>releases with the text the moment something is deliverable; Claude Code shows it as a system</p>\n<p>reminder and the session picks it up. Mid-turn delivery is unchanged. <code>[messages] wake = false</code></p>\n<p>turns it off; the session log shows <em>woke</em>. Run <code>swarm install</code> once after updating.</p>\n<ul><li><b>Codify is back, and it writes.</b> The React port had lost the Incidents feed's Codify action;</li></ul><p>it returns as a card with the CLAUDE.md lesson and the <code>.swarm.toml</code> rule, copy buttons, and a</p>\n<p>new <b>Apply → PR</b> button. Apply never touches your main checkout: the daemon creates a</p>\n<p>task-less worktree on <code>swarm/codify-&lt;n&gt;</code>, merges the lesson under a <em>Lessons from Swarm</em></p>\n<p>heading (once) and the rule into its section (ports unioned, custom rules appended, everything</p>\n<p>else byte for byte), commits, pushes and opens the PR prefilled with the incident, then removes</p>\n<p>the worktree. Without a forge remote the branch stays and the card shows the push line.</p>\n<p><code>[codify] target</code> picks the default file(s).</p>\n<ul><li><b>A status line inside Claude Code.</b> <code>swarm install --statusline</code> sets Claude Code's <code>statusLine</code></li></ul><p>to <code>swarm statusline</code>, and the footer of every session reads, for example,</p>\n<p><code>Opus · ctx 42% · $1.23 · 5h 24% · 7d 41% │ M12.2 41m · 2 incidents · waiting on you</code>. The left</p>\n<p>half is what Claude Code hands the command after each assistant message: model, context used,</p>\n<p>session cost and, for Pro / Max subscribers, the 5-hour and 7-day plan windows. The right half is</p>\n<p>what only the daemon knows: the task whose worktree you are in and its lease, the budget when it</p>\n<p>is not fine, un-acked incidents, a question of yours nobody has answered, an inbox. Same contract</p>\n<p>as the hooks — 400 ms, fails open to the left half, never starts the daemon. It is opt-in because</p>\n<p>a custom status line replaces Claude Code's footer hints, and it never replaces a status line you</p>\n<p>already set; <code>swarm uninstall</code> removes exactly ours. <code>swarm doctor</code> says which is the case.</p>\n<ul><li><b>Plan quota windows on Spend and in the status line.</b> Pro / Max plans are metered by a 5-hour</li></ul><p>and a 7-day window, not by dollars, and Claude Code reports both to its status line after every</p>\n<p>message. The daemon keeps those samples (a row when a window moves, never one per message) and</p>\n<p>Spend shows a tile per window: percent used, when it resets, the burn rate, and at the current</p>\n<p>pace whether the limit lands before the reset. The status line names the window that runs out</p>\n<p>first: <code>5h limit in 2h</code>. <code>[budget] window_warn_at = 0.8</code> opens a <code>budget</code> incident once per</p>\n<p>window per reset period, and again at 100%; <code>false</code> turns the warning off. Swarm has no</p>\n<p>account and never asks Anthropic for usage — the status line is the only source, so the tiles</p>\n<p>appear only while some session on a plan keeps reporting (API-key sessions never do).</p>\n<ul><li><b>Host or join a team from the app.</b> Running the team daemon meant a clone, environment</li></ul><p>variables and two CLI commands; joining meant a third. The dashboard has a <b>Team</b> panel now:</p>\n<p><b>Host a team</b> mints a shared secret, writes <code>~/.swarm/team.toml</code>, starts <code>swarm-teamd</code>, points</p>\n<p>this machine at it, registers, and shows an <b>invite link</b>; a teammate pastes that link into</p>\n<p><b>Join a team</b> and is done. The panel also shows forwarding lag, this machine's identity,</p>\n<p>whether its credentials registered, and a Leave button (with <em>stop hosting</em> when this machine is</p>\n<p>the host — stopped by pid, never by pattern). <code>swarm-teamd setup</code> writes the same file from a</p>\n<p>terminal, and the environment still overrides it, so existing deployments are unaffected. Two</p>\n<p>bugs found on the way: a wrong shared secret used to look like a successful join and then fail</p>\n<p>to forward forever, and a config file the daemon wrote itself stayed invisible to it for 30</p>\n<p>seconds.</p>\n<h4>Changed</h4>\n<ul><li><b>The desktop app always runs its own daemon.</b> It used to reuse any healthy daemon registered</li></ul><p>in <code>~/.swarm/daemon.json</code> — which meant a clone's <code>bun run dev</code> daemon, serving whatever</p>\n<p>dashboard bundle happened to be on that disk, was what the packaged app displayed, and</p>\n<p>restarting the app changed nothing because the daemon it borrowed never restarted. Now the app</p>\n<p>asks the registered daemon to stop over a new loopback <code>POST /v1/shutdown</code> (the same clean path</p>\n<p>as <code>swarm stop</code>: spawned runs stopped by pid, <code>daemon.json</code> cleared), starts its bundled daemon,</p>\n<p>and finds it by pid. The daemon keeps its preferred port — <code>[daemon] port</code>, else 7777 — and</p>\n<p>falls back to any free port when that is taken, so a blocked 7777 is never an error; and a</p>\n<p>process that merely listens on the port no longer counts as a daemon, since the app now checks</p>\n<p><code>/v1/health</code> rather than a TCP connect. The daemon the app starts also watches the app's pid and stops</p>\n<p>itself when the app is gone — quit, crashed or killed — so no invisible daemon survives to be</p>\n<p>evicted next time.</p>\n<h4>Fixed</h4>\n<ul><li><b>Codex and Gemini sessions stopped updating after the first look.</b> Both write their session id</li></ul><p>once, in a header line at the top of the file, and the first poll consumed it — so every later</p>\n<p>poll read the new lines, failed to tell which session they belonged to, and threw them away</p>\n<p>without even advancing its place in the file. A Codex session's usage sat frozen at whatever the</p>\n<p>daemon saw the first time it found the log. The daemon now remembers the session it recorded for</p>\n<p>each file and hands it back to the parser, so tailing continues for the life of the session.</p>\n<p>Codex turns are also identified by their own timestamp and usage now instead of a per-poll</p>\n<p>counter, so a re-read of the same event updates that turn rather than counting it twice.</p>\n<p>Thanks @roy-tong for the diagnosis and the fix (#145).</p>\n<ul><li><b>Linux has an AppImage again — and auto-updates with it.</b> The AppImage has been off since</li></ul><p>v0.2.2 because the bundler died every time, and Linux was the one platform the in-app updater</p>\n<p>could not reach. The bundler runs <code>patchelf</code> over every executable it finds inside the package,</p>\n<p>which quietly wrecks the daemon: it is compiled by bun, and bun keeps its program glued to the</p>\n<p>end of the file, exactly where <code>patchelf</code> writes. The next tool to inspect the wreckage aborted</p>\n<p>the build. Linux builds now carry the daemon compressed instead, which the bundler leaves alone,</p>\n<p>and the app unpacks it once per version into <code>~/.swarm/bin</code>. AppImage, <code>.deb</code> and <code>.rpm</code> all ship</p>\n<p>again; \"Check for Updates…\" installs updates in place for the AppImage and, for a <code>.deb</code>/<code>.rpm</code></p>\n<p>copy, now says to update through your package manager instead of failing halfway.</p>"},"0.13.2":{"date":"2026-09-04","html":"<h4>Changed</h4>\n<ul><li><b>The session log shows the newest entries first.</b> The latest tool call or turn is the top line</li></ul><p>now instead of the one at the end of a scroll. While you are at the top the log stays on the</p>\n<p>newest row as more arrive; scroll down into the history and it keeps the row you are reading</p>\n<p>under your eyes when new rows land above it.</p>\n<h4>Fixed</h4>\n<ul><li><b>The React port's regressions against the vanilla dashboard.</b> Every view was rendered from the</li></ul><p>retired vanilla dashboard beside the React one and pixel-diffed; ten things had slipped in the</p>\n<p>port. The session sidebar has its cost-per-turn strip, messages, waiting questions and the</p>\n<p>copyable transcript row back; chips are pills again (the global <code>&lt;button&gt;</code> reset had stripped</p>\n<p>them); branch and model columns are proportional, so <code>fable-5-1</code> fits; empty states draw the idle</p>\n<p>robot; the Board's Map/Table and Cards/Table toggles, the Graphs collisions hint, the Provenance</p>\n<p>\"N untracked\" pill and the Spend column menus are restored. The collapsed sidebar collapses to</p>\n<p>nothing — the 46px rail had been empty since the nav moved to the header.</p>\n<ul><li><b>The range switch shows which range is on.</b> 14d / 90d on Spend and Stats, 3h…72h on Timeline:</li></ul><p>the stylesheet styled <code>.seg a</code>, React renders <code>&lt;button&gt;</code>, so the buttons wore the global button</p>\n<p>chrome and the active one looked like the rest.</p>\n<ul><li><b>The project row's <code>⋯</code> menu has \"Settings…\" again.</b> The v0.8.0 drawer was never ported, so</li></ul><p>there was no way to change a project's icon, name, colour or pinned state from the dashboard.</p>\n<p>Emoji tiles, the full emoji grid behind \"…\", an image file downsized to 64px, the swatches and the</p>\n<p>pinned toggle are back, and the daemon's validation error shows in the footer instead of being</p>\n<p>swallowed. The emoji and swatch cells are buttons now, so the keyboard reaches them.</p>\n<ul><li><b>Pinned projects reorder with dnd-kit; the drop lands.</b> HTML5 drag started but the drop never</li></ul><p>fired inside the desktop app: Tauri installs its own drag-drop handler on the webview, which</p>\n<p>swallows <code>drop</code> and <code>dragend</code>. Pointer events get through. The held row follows the pointer and</p>\n<p>its neighbours slide out of the way, which the native drag could never animate.</p>\n<ul><li><b>The session page keeps consistent gutters.</b> The panels were <code>calc(100vh - 140px)</code>, a guess</li></ul><p>that drifted whenever the header wrapped or the stdin bar appeared, leaving ~35px at the bottom</p>\n<p>and the header crowding the panels. The page is a flex column now: 16px on top, 24px on the</p>\n<p>other sides, and the log scrolls inside its panel.</p>"},"0.13.1":{"date":"2026-09-01","html":"<h4>Changed</h4>\n<ul><li><b>The dashboard asks the daemon whether anything changed instead of re-downloading the answer.</b></li></ul><p><code>/v1/state</code> is ~195 KB and every client asked for all of it every 5 seconds, plus once per event</p>\n<p>nudge, then compared it against the last copy to decide whether to re-render. The response now</p>\n<p>carries an <code>ETag</code> and an unchanged poll comes back <code>304</code> with no body — no transfer, no parse, and</p>\n<p>the same \"nothing moved, render nothing\" outcome, decided before the bytes are sent.</p>\n<ul><li><b>The dashboard ships one copy of React.</b> The dropdown island was a second <code>&lt;script&gt;</code> with its</li></ul><p>own React bundled in, so the page loaded two runtimes: 737 KB of JavaScript where 549 KB would do.</p>\n<p>The island is imported by the app now — still its own React root, since fancy-menus renders</p>\n<p>outside the app tree — and the three remaining scripts no longer block the parser.</p>\n<ul><li>**The website's Downloads section reads a cached endpoint instead of calling GitHub from every</li></ul><p>visitor's browser.** The page made three cross-origin calls per visit — two to <code>api.github.com</code>,</p>\n<p>which is 60 requests/hour per IP unauthenticated, so a visitor who reloaded a few times (or shared</p>\n<p>an office NAT) got a 403 and an empty Downloads section. The release, its assets and both download</p>\n<p>counters now come from <code>/api/releases</code>, cached at Vercel's CDN and in the warm function instance,</p>\n<p>which also serves the last good payload if GitHub is rate-limiting or down.</p>\n<h4>Fixed</h4>\n<ul><li><b>A pill badge in a grid cell sat ~7px low and grew its row</b> (\"built-in\" in the MCP grid). A</li></ul><p>pill is an inline-block of text, so its baseline is its own last line box, not its bottom edge</p>\n<p>the way an SVG's is; the shared vertical offset assumed the latter.</p>\n<ul><li><b>The sidebar's <code>+</code> is a menu again, with the folder browser behind it.</b> The React port had</li></ul><p>replaced \"Browse folders… / Add by path…\" with a bare path box under the heading. Both are back</p>\n<p>and open the same picker: sub-folders listed with <code>git</code> badged, <code>..</code> to go up, a path box that</p>\n<p>navigates on Enter, and refusals shown in the footer instead of an <code>alert()</code>. A typed path that</p>\n<p>does not exist now answers \"no such folder\" — the daemon used to list <code>~</code> instead, silently.</p>\n<ul><li><b>Pinned projects drag to reorder again.</b> The rows lost their drag handlers in the React port;</li></ul><p>the order is previewed in place while dragging and saved to the daemon on drop, as before.</p>\n<ul><li><b>The sidebar is resizable.</b> Drag its right edge (double-click to reset, arrow keys when it has</li></ul><p>focus); the width persists. It was a fixed 240px, so a long project name was cut to an ellipsis</p>\n<p>with no way to see the rest.</p>\n<ul><li><b>A <code>git -C &lt;path&gt;</code> command walked straight past every git rule.</b> The guards matched on <code>git</code></li></ul><p>immediately followed by the subcommand, so any of git's global options in between — <code>-C</code>,</p>\n<p><code>--git-dir</code>, <code>--work-tree</code>, <code>-c</code> — turned <code>git -C /someone-elses/worktree reset --hard</code> into a</p>\n<p>command the rules did not recognise, which is precisely the command they exist to catch. They now</p>\n<p>read git's global options before the verb. <code>pkill --full</code> and <code>killall</code> count as pattern kills too.</p>\n<ul><li><b>A cold Outcomes read could hold a request open indefinitely.</b> <code>gh</code> was run once per project in</li></ul><p>sequence with no timeout, and a second request started a second full fan-out rather than waiting</p>\n<p>for the first: three overlapping requests measured 8 s, 1018 s and 2 s. Projects are now queried in</p>\n<p>parallel, callers share one in-flight run per project, and a CLI that stops answering is killed</p>\n<p>after 20 s. Scratch and deleted temp checkouts are skipped entirely — on a machine with 21 tracked</p>\n<p>repos, 13 of them had no remote and were still costing a subprocess per refresh.</p>\n<ul><li><b>The session list cost the whole ledger to build.</b> It joined every turn ever recorded before</li></ul><p>applying its 200-row limit, and built sparklines for every session rather than the ones on screen.</p>\n<p>Both queries are now scoped to the page: on a 200,000-turn ledger that is 111 ms → 11 ms.</p>\n<ul><li><b>\"Open on GitHub\" in a pull request's row menu did nothing in the desktop app.</b> It called</li></ul><p><code>window.open</code>, which the desktop webview discards; it goes through the same path as every other</p>\n<p>external link now.</p>\n<ul><li><b>Clicking a project in the sidebar did nothing while a session was open.</b> Picking a project</li></ul><p>scoped the app but did not leave the session page, so the highlight moved on the left and the</p>\n<p>same session stayed filling the pane — the click read as broken. Choosing a project now leaves</p>\n<p>the session page for the same reason choosing a view does, revealing whichever view was</p>\n<p>underneath, scoped to what you just picked.</p>"},"0.13.0":{"date":"2026-08-27","html":"<h4>Changed</h4>\n<ul><li><b>The dashboard is a React app now.</b> It used to be one 4,000-line <code>app.js</code> that rebuilt the page</li></ul><p>on every poll: 1,059 elements thrown away and recreated to update eleven text nodes, five seconds</p>\n<p>apart, which is the blinking you could see. The same poll now mutates nothing at all when nothing</p>\n<p>has changed — the tree only moves where the data did. Your sort order, column widths and open</p>\n<p>menus survive a refresh, because they are no longer destroyed by one.</p>\n<p>Underneath: one poll for the whole app instead of 21 hand-written change flags, <code>zustand</code> for</p>\n<p>state, typed routes over the same <code>@swarm/core</code> types the daemon answers with, and views split</p>\n<p>into components small enough to read. Long tables paginate, so the Board no longer builds three</p>\n<p>thousand DOM nodes to show you fifty rows.</p>\n<p>Nothing about the interface moved. Every view, every column, every action is where it was.</p>\n<ul><li>Long grids paginate at 25 rows by default, with the page size remembered per table.</li><li>The ⌘K palette, theme switching, UI zoom, desktop notifications, What's New, the update and star</li></ul><p>nudges and the feedback link are back. All of them lived in <code>app.js</code>; the rewrite ported every</p>\n<p>screen and none of the chrome around them.</p>\n<h4>Fixed</h4>\n<ul><li>A grid's last column had a resize handle hanging four pixels past the table, which put a</li></ul><p>horizontal scrollbar under every wide view.</p>\n<ul><li><b>The desktop app lost its window chrome.</b> On macOS Swarm runs with an overlay title bar, and</li></ul><p>the page is what pads the header clear of the traffic lights and what makes the header draggable.</p>\n<p>Both were wired in the old <code>app.js</code> and did not survive the rewrite: the mark sat under the</p>\n<p>traffic lights and the window could not be moved or double-click-zoomed.</p>\n<ul><li><b>The sidebar lost its padding, border and background.</b> React mounts inside a <code>#root</code> div.</li></ul><p>It is <code>display: contents</code>, so it disappears from the layout — but not from selectors, and every</p>\n<p><code>body &gt; aside</code> rule quietly stopped matching. <code>bun run check:classes</code> now fails on that selector</p>\n<p>shape rather than letting it go quiet again.</p>\n<ul><li><b>The Trials view threw on every visit.</b> <code>/v1/ab</code> answers <code>{ trials: [...] }</code>, and the view was</li></ul><p>annotated as receiving a bare array — so it walked past its own empty check and called <code>.filter</code></p>\n<p>on an object. Route builders now carry their response type, so <code>useResource</code> infers it and an</p>\n<p>annotation that disagrees with the endpoint is a compile error instead of a blank view.</p>\n<ul><li><b>A configured backlog could render as no backlog at all.</b> An external tracker is fetched in the</li></ul><p>background and the first request arrives before it answers, so the daemon returned <code>{ tasks: [] }</code></p>\n<p>and the Board drew no Tasks section — identical to a repo that configures no source. On a repo</p>\n<p>with 300 open issues it stayed that way until something happened to poll it again. Three other</p>\n<p>routes into the same silence are closed with it: a source that <em>failed</em> (<code>gh not installed</code>,</p>\n<p><code>LINEAR_API_KEY not set</code> — messages written to be acted on, and previously discarded), a markdown</p>\n<p>source naming a file that is not there, and a backlog that really is empty. A configured source</p>\n<p>now always renders, and always says which of those it is.</p>\n<ul><li><b>Branch names rendered in the wrong font, and badges sat flush against them.</b> <code>.br</code> — the</li></ul><p>monospace treatment for a machine-readable name — was only ever written as <code>td.br</code>, matching when</p>\n<p>the class sat on the table cell. React puts it on a <code>&lt;span&gt;</code> inside the cell, and uses it outside</p>\n<p>tables too, so the rule matched nothing: every branch, rule, tool and task ref quietly rendered as</p>\n<p>14px body sans, wide enough to crowd the badge beside it.</p>\n<ul><li><b>Copy silently did nothing in the desktop app again.</b> The webview has no async clipboard API;</li></ul><p>the fallback added in 0.12.1 was not carried across. It is now in one helper that everything uses.</p>"},"0.12.1":{"date":"2026-08-26","html":"<p><b>If you are on 0.12.0, update.</b> Its Board view was blank — see below.</p>\n<p>The rest of this release is about hygiene finally doing something. It has been reporting 50 GB held across 32 worktrees on my machine and offering nothing to reclaim, which reads as broken rather than as cautious. Two reasons, both fixed.</p>\n<h4>Fixed</h4>\n<ul><li><b>The Board was dead in 0.12.0.</b> A function added for the new resource graph had the same name as the one that renders the Board's runtime resources; the later declaration won, so the view threw before drawing anything and took the worktree list with it. Renamed, and there is now a check that no two top-level view functions share a name.</li><li><b>Copy never worked in the desktop app.</b> The webview has no async clipboard API and the helper quietly gave up. It falls back now, tells you whether it worked, and when it cannot copy it puts the text on screen selected rather than flashing \"copied\" at you.</li><li><b>Error reports in the desktop app were missing the error.</b> Chrome puts the message at the top of a stack trace and Safari does not, so the one line saying what went wrong was absent — in the app, which is where you would be reading it.</li></ul><h4>Added</h4>\n<ul><li><b>Clearing build output.</b> 31 GB of the 50 GB my worktrees hold is <code>node_modules</code>, Rust <code>target</code> and <code>dist</code> — things a rebuild recreates. Hygiene measures that per worktree now, shows it as its own column, and offers to clear it.</li></ul><p>This is deliberately not the same as removing a worktree: the checkout, the branch and anything uncommitted all survive, so a dirty tree is fine to clear. What it refuses is the main checkout, a worktree somebody holds a claim on, and one with a live session in it — those mean a build is probably running. A nested worktree's output belongs to that worktree and is offered there, not swept up by whatever contains it.</p>\n<h4>Changed</h4>\n<ul><li><b>A branch merged by squash now reads as merged.</b> Swarm decided this with <code>merge-base --is-ancestor</code>, which is the right test for a merge commit and useless for anything else: a squash rewrites the branch's commits into one new commit, so the originals never become ancestors of the base and the branch stays \"Clean\" forever. It also asks <code>git cherry</code> now, which compares by patch instead — if every change on the branch is already in the base, however it got there, it is merged.</li><li><b>The worktree table says which project each one is in.</b> With thirty-odd worktrees across a dozen repos, a branch name on its own does not tell you where it lives.</li><li><b>A worktree counts as stale after two days, not seven.</b> Seven never elapsed: on a machine where work lands daily you reuse or notice a merged worktree long before a week of silence. Nothing about <em>safety</em> changed — the ledger still refuses to offer anything with uncommitted or unpushed work, whatever its age. On my machine the offer went from nothing at all to 8 worktrees and 1.7 GB.</li><li>Task cards no longer carry a thick coloured stripe down their left edge; state colours the whole outline. Heading badges match the ones in tables. Inline icons sit on the centre of the text beside them rather than a pixel and a half below it.</li></ul>"},"0.12.0":{"date":"2026-08-26","html":"<p>The Observatory is finished. Swarm has spent this milestone answering questions about your fleet that used to need a person reading logs; this release adds the last five and, just as importantly, makes the dashboard admit when something has gone wrong instead of quietly showing you a stale screen.</p>\n<p>Two things I want to be straight about, because both are cases where the plan was wrong and the code says so.</p>\n<p>The roadmap wanted repeated tool cycles to feed the stuck detector. They should not. <code>Read → Edit → Read → Edit</code> is the single most ordinary thing an agent does, and flagging it would have fired on healthy work all day long. A cycle only counts when the calls inside it are <em>failing</em>, which is the same bar the stuck detector already sets for a plain repeat.</p>\n<p>And it wanted deadlock detection on held resources. A deadlock cannot happen here: claims fail closed, so a second claimer is refused rather than queued and nobody ever blocks. What can happen is contention — two agents each wanting what the other holds — and that needed a fact nobody was recording, so Swarm now records it.</p>\n<h4>Added</h4>\n<ul><li><b>Tool transitions.</b> What an agent reaches for after what, as a weighted matrix. Not a graph drawing: the transition graph is dense and cyclic, so a layered drawing turns into a hairball where nearly every edge doubles back. A matrix has no crossings, puts a tool following itself on the diagonal, and shows a lopsided pair of cells when <code>A → B</code> happens far more than <code>B → A</code>. On my machine: 10,198 transitions across 62 sessions, and <code>Bash → Bash</code> 6,881 of them.</li><li><b>File heat.</b> Where the fleet's attention actually goes — hottest files, hottest directories, and how much of every touch was a file being read again. Mine says <b>55%</b>, and 222 files were opened once and never returned to. It also looks for files several sessions keep re-reading and hardly ever edit, because those are the ones whose conclusion belongs in <code>CLAUDE.md</code> instead of being re-derived in every window. It found none, correctly: everything multiple sessions read here is also something they were editing.</li><li><b>Resource holding.</b> Claims, ports, leases and processes on one picture with whoever holds them, and a resource is orphaned when the <em>session</em> that took it ended — not when its owner looks idle, because an owner string outlives the run it belonged to. Refusals are now recorded, so the view can also show contention rings.</li><li><b>Security audit.</b> Hosts your agents reached for, packages they installed, and credential files they opened. Observation only — nothing here denies anything, and it reads what was <em>requested</em>, so a command one of your rules already blocked still shows up. It is a lint and not a sandbox, and the view says so: an obfuscated command will not match, and a comment mentioning <code>.env</code> will.</li><li><b>Rule effectiveness.</b> Whether a rule is teaching anyone anything. Firing once is a rule working; firing forty times on the same shaped command is friction, and either the habit needs changing or the rule does. Incidents are clustered by the shape of what they fired on, so that difference is visible at a glance. Swarm now also records when your rule set changes, so \"before this rule, after this rule\" becomes answerable from here on — it was not answerable before, because nothing knew when a rule landed.</li><li><b>An error boundary.</b> The dashboard is one long-lived page and it had no way to say it had broken: an exception left the previous screen up looking current, and a failed poll was swallowed. Now a view that throws shows the error with its stack, and there is a copyable report and a prefilled issue link. The report is the version, the view, the error and the last few failed requests — no session contents, no paths, no titles — and nothing leaves your machine unless you send it.</li></ul><h4>Fixed</h4>\n<ul><li><b>A 404 on a Swarm route is now named for what it usually is.</b> If the dashboard was updated and the daemon has not restarted, the page says exactly that and offers the restart, rather than spinning on a view that will never load.</li><li><b>Light mode was olive.</b> The accent was a yellow-green darkened in sRGB, which drains the colour and leaves khaki — every bar, sparkline and heatmap cell in the theme was that colour. The greens are derived in OKLCH now, which holds the hue as the lightness comes down, and text and chart fills are separate values because they answer to different contrast rules.</li><li>Badges had two pixels of vertical padding against eight horizontal, and read as squashed.</li><li>The project sidebar's icon sat a pixel above everything else in its row.</li><li>Several tables stretched three or four columns across the page with their numbers a screen away from what they described; those are lists now.</li></ul>"},"0.11.3":{"date":"2026-08-26","html":"<p>A proper robot.</p>\n<h4>Changed</h4>\n<ul><li><b>The robot is redrawn.</b> The old one was 23×28 in three tones and looked it — a blocky approximation of the thing it was meant to be. This one is 73×87 in seven, with a bevelled head, ear pods, a chest screen, a vent grille, segmented arms and claws. It is not hand-copied: the grid is recovered from the reference art itself, and its colours are re-derived as a straight scale of <code>#a3e635</code>, so every tone is the brand hue and the whole drawing recolours from one value.</li><li><b>Icons and the site's marks are the head alone.</b> The whole robot in a 512px tile is clutter — arms, claws, a vent grille and four buttons, none of which survives being an icon. The head is the top of the same drawing, and below about 128px an even simpler head takes over, because 47 columns of bevel and eye socket rendered a pixel each is noise rather than a robot.</li></ul><h4>Fixed</h4>\n<ul><li><b>Small icons were mushy.</b> They were scaled by canvas ÷ grid, which at 32px is 1.6 pixels a cell — so cells landed on two pixels or one depending where they fell, and the eyes came out different sizes. Cells are now always a whole number of pixels, at every size Swarm ships.</li><li><b>The macOS icon ignored Apple's icon grid.</b> It bled to the edge of its canvas, which makes an icon sit visibly larger in the Dock than everything beside it. The rounded square now takes about 80% of the canvas, as Apple specifies — except below 128px, where there are not enough pixels to spend on a margin.</li><li><b>The robot had holes in it.</b> Transparent cells showed through where the drawing should be solid: a blank row between the head and the neck, a hole in the neck itself, and hairline slits detaching each arm at the shoulder. The gaps that are meant to be there — the claw notch, between the legs, between the antennae — are untouched.</li><li><b><code>trimArt</code> deleted blank rows wherever they fell</b>, not only at the edges, so the drawing's one interior blank row was being dropped from every generated icon and the figure came out a cell short.</li></ul>"},"0.11.2":{"date":"2026-08-26","html":"<p>Two things 0.11.1 said it did and didn't.</p>\n<h4>Fixed</h4>\n<ul><li><b>The desktop app icon.</b> 0.11.1 put the robot in the dashboard, on the site and in the favicons, then left the macOS and Windows app icons as the old mark — so the thing in your Dock was the one place that still didn't match. All seventeen are generated from the same drawing now, by <code>tools/icons.ts</code>, read straight off the pixel grid in <code>core</code> with no image editor anywhere in the loop. That covers <code>.icns</code>, <code>.ico</code> and every Windows store size, and the iOS and Android sets if you have them checked out.</li><li><b>The hero animation.</b> What shipped in 0.11.1 was pixel columns marching down in lockstep over a static grid that never moved at all. It's a proper rain now — streams of glyphs, each falling at its own speed, bright leading character, trail dying out behind it. The columns sit at three depths, where size, pitch, speed and brightness all move together: the flat uniform grid is the part that reads as an impression of the film, so that is the part that had to go. One canvas and one animation-frame loop: 0.27ms a frame, paused when the hero scrolls off screen or the tab goes to the background, and never started at all under <code>prefers-reduced-motion</code>.</li></ul>"},"0.11.1":{"date":"2026-08-26","html":"<p>Mostly the look of the thing, plus one page that was genuinely slow.</p>\n<h4>Added</h4>\n<ul><li><b>The robot is the logo.</b> It replaces the abstract pixel glyph in the dashboard header, on the site, and in the favicons. The header mark also follows your theme now — it was hardcoded to one green and ignored light mode entirely.</li><li><b>Matrix rain in the hero.</b> Pixel columns falling at their own speeds, brightest at the leading edge, fading out of the middle so they never sit behind anything you have to read.</li></ul><h4>Fixed</h4>\n<ul><li><b>Provenance took nine seconds to open.</b> It was asking your forge about every project before drawing anything — on a machine with 21 of them that is 21 round trips, and whoever opened the page first every ten minutes paid for all of them. It now draws from what it already knows and lets the pull request state catch up a moment later, which took it from <b>8.9s to 0.7s</b>. It also pages 50 rows at a time instead of sending all 116.</li><li><b>The landing page read like documentation.</b> Config snippets, exit codes and words like \"fail-closed\" and \"orphaned\" in the middle of sentences meant to sell. Rewritten to say what the thing does for you; the syntax lives in the docs, where someone is looking for it.</li></ul>"},"0.11.0":{"date":"2026-08-26","html":"<p>Swarm could already tell you what your agents did. This release is about what it cost you — in money, in waiting around, in context burned re-reading the same file, and in work that shipped with nothing linking it back to a ticket.</p>\n<p>I pointed the new provenance view at my own machine and found 22 branches that had landed with no task behind them. One was a merged PR carrying $255 and 16 agent sessions. I had no idea it existed.</p>\n<h4>Added</h4>\n<ul><li><b>Waiting on you.</b> Agents spend real time stuck waiting for a human — a permission prompt, a question, a notification — and none of it was measured. Now it is. Fleet shows a <em>Waiting 12m</em> badge saying what's blocking, and Stats breaks it down by kind. If you close your laptop on a pending prompt, the clock stops when the session ended, not days later when you open it again.</li><li><b>Where your context goes.</b> A breakdown of what actually fills the window, by tool. The waste number is re-reads: read a file once and that's work, read it ten times and nine copies are just the cost of forgetting. One of my sessions had spent 11% of its window re-reading the same diff.</li><li><b>MCP server health.</b> Which servers are slow, which fail, and how long your agents sat waiting on each. Timing is measured between the hooks either side of a call, so a call stuck behind a permission prompt carries that wait too — which is why the view leads with p50 and p95 rather than the worst case.</li><li><b>Gate flakiness.</b> A gate is flaky when it gives <em>both</em> answers about the <em>same</em> task. Failing one task and passing another isn't flaky, that's the gate working. Gates now record how long they took, and old runs get their duration recovered from the text they used to hide it in.</li><li><b>Machine hygiene.</b> What your fleet left lying around: processes still holding a port after their session ended, dead entries in the registry, worktrees that merged days ago and still take up disk. It only offers to remove something the ledger itself would agree to remove, so nothing with uncommitted or unpushed work is ever on the list.</li><li><b>Session lineage.</b> Who spawned whom, who messaged whom, who picked up whose task — as a graph. When one session has 37 subagents they collapse into a single pill you can click open, because 37 lines fanning across the screen is a mess, not a picture.</li><li><b>Provenance.</b> Follow any piece of work backwards: ticket, claim, session, branch, pull request, merge. Six dots per row, filled up to the point the trail goes cold. It reads the chain from both ends, which is how it finds work that shipped with no ticket at all.</li><li><b>A/B trials.</b> Give one task to several models at once and see what each produced: cost, wall time, gates, how much they changed. A model only wins if it finished <em>and</em> passed every gate — a cheap wrong answer isn't an answer. Each model works in its own worktree, so nothing about the claim rules had to be relaxed to run the experiment.</li><li><b>A robot.</b> The empty states have a proper one now, and its head is the site's logo and favicon. There's one drawing, shared, with a test that stops the two copies drifting apart.</li></ul><h4>Fixed</h4>\n<p>Mostly things that had been quietly wrong for a while:</p>\n<ul><li>Long branch names painted straight over the badge next to them.</li><li>The Board said 20 incidents while the Guard badge said 57. The Board was wrong — it was counting a 20-row window.</li><li>The header never told you which of the ten views you were looking at.</li><li>Opening a menu highlighted the row under your cursor in the same green as the row you were already on, so neither read as current.</li><li>Big numbers were truncated to things like <code>134….</code> in columns that had room for them.</li><li>The ⋯ button vanished the moment you clicked it, and the row jumped as it went.</li><li>Pixel art was nearly invisible in light mode — the outline and the face it sat on were the same brightness to within 0.002.</li><li>Replay resized itself on every step, so Prev/Next slid out from under the cursor mid-click.</li><li>The transcript gave 204px of every row to a timestamp and a label like <code>pretooluse</code>, which repeats on every line and tells you nothing.</li><li>After upgrading, your browser could quietly keep running the previous version's dashboard — the files were served with no cache headers at all. That's why \"What's New\" could greet a 0.11 upgrade with 0.10's notes: the notes bundle was a stale copy, and the lookup silently fell back to the newest release it happened to have instead of admitting it didn't have yours.</li></ul><h4>Notes</h4>\n<ul><li>The database upgrades itself on first start. <code>swarm doctor</code> will tell you the schema version.</li><li>This is 13 of the 18 planned observatory features. Security auditing, rule effectiveness and three more graphs are still to come.</li><li>Two things are deliberately missing rather than guessed: how many tokens your MCP schemas cost, and how much of the window the system prompt takes. Swarm can see tool calls but not schemas or the prompt itself, so it doesn't pretend to know.</li></ul>"},"0.10.0":{"date":"2026-08-24","html":"<p>The team release: Swarm outgrows one laptop. A self-hosted team daemon gives a group one view — machines, cluster-wide claims, spend by person — while every laptop stays local-first and keeps working offline. Two more agent brands land (six total), and the dashboard starts reading the fleet's behaviour, not just its numbers: outcomes, stalls, collisions.</p>\n<h4>Added</h4>\n<ul><li><b>Team daemon</b> — <code>swarm-teamd</code> (<code>packages/team</code>), a second, self-hosted service your machines <em>forward</em> to: audit events, spend rollups and claims — never transcript text unless a machine opts in, and always after your redaction rules. The local daemon queues everything in an outbox (batched, at-least-once, never on the hook path) and drains it when the team daemon is reachable; <code>swarm doctor</code> shows the lag. One SQLite file of state, TLS by reverse proxy, <a href=\"https://getswarm.vercel.app/docs/11-teams\" target=\"_blank\" rel=\"noopener\">guide</a> (M8.3). <em>Licensing: this one package is source-available (<a href=\"https://github.com/ra3orblade/swarm/blob/main/packages/team/LICENSE.md\" target=\"_blank\" rel=\"noopener\">FSL-1.1-ALv2</a>, Apache-2.0 after two years); everything else is and stays Apache-2.0 — one machine free, a second person is the product (OQ-15).</em></li><li><b><code>swarm login</code></b> — OIDC device-code sign-in against the team daemon (which is the OAuth client — your laptop never holds an OIDC credential; a static shared token or open mode for labs). First user becomes admin; roles are viewer / developer / admin. Login registers the machine (its token is bound to you) and pins the org's policy signing key. <code>swarm install --config-url &lt;url&gt;</code> is the one-flag fleet onboarding (M8.3c/f).</li><li><b>Cluster-wide claims</b> — a claim taken on one machine registers upstream; a second machine claiming the same task is refused with the holder's name (<code>alice@her-laptop</code>), and if the cluster says someone else holds it, the local claim is revoked — the worktree is never touched. Offline degrades to local-only, fail-closed as ever (M8.3d).</li><li><b>Team dashboard</b> — served by the team daemon: machines (live / quiet), active cluster claims, spend today / by project / <b>by user</b> / by machine / by day, and the forwarded activity feed, live over SSE (M8.3e).</li><li><b>Signed org policy</b> — an admin posts the org's <code>policy.toml</code> once; every machine fetches it, verifies the ed25519 signature against the key pinned at login, and installs it as the org layer — locked rules included. A tampered policy is reported and never installed (M8.3f, closes the OQ-3 signing deferral).</li><li><b>Team budgets + chargeback</b> — org / user / project ceilings (daily + monthly) set on the team daemon and enforced with the same semantics as the local <code>[budget]</code>: warn incident, <code>ask</code> on spending tools, or stop spawned runs. Monthly export by user, machine, model or <b>task — your ticket ids</b> when the task source is GitHub Issues or Linear: <code>GET /t1/spend/export?month=…&amp;by=task&amp;format=csv</code> (M8.4).</li><li><b>Model allow-list</b> — <code>[models] allow = [\"claude-*\"]</code> (org-lockable): spawned runs and dispatch refuse a disallowed model; an interactive session on one opens a single incident — observed, never interrupted (M8.4).</li><li><b>Aider + opencode adapters</b> — six agents now show up with sessions, turns, tokens and cost: Claude Code, Codex, Grok, Gemini CLI, <b>Aider</b> (its own <code>.aider.chat.history.md</code>, one file holding many sessions) and <b>opencode</b> (its SQLite database, read-only). Both report their exact spend themselves, so their turns carry it verbatim and repricing never touches them (M5.4; Cline deferred).</li><li><b>Outcomes</b> — did the agent's work survive? Branches join sessions → PR → merged / reverted, with per-model and per-agent scorecards: merge rate, median session-start→merge, $ per merge. New <b>Outcomes</b> view under Insight (M9.2).</li><li><b>Stuck badge</b> — the daemon watches live sessions' recent tool calls for repeat-and-failing loops and all-failing streaks (conservative: <code>git status</code> polling never counts) and marks the session <b>Stuck</b> on Fleet with a desktop notification. A heuristic; nothing is interrupted (M9.3).</li><li><b>Collision graph</b> — a live bipartite graph of running sessions × the files they touch; a file two sessions hold with at least one writer glows red as a merge conflict waiting to happen. New <b>Graphs</b> view with a contested-count badge (M9.12).</li><li><b>Sidebar navigation + ⌘K</b> — the flat header links became a grouped sidebar nav (Observe / Work / Insight / Guard) that collapses to an icon rail, and ⌘K opens a palette over every view, project and session, falling through to Search (M9.1).</li><li><b>Ops</b> — <code>swarm backup</code> (a consistent <code>VACUUM INTO</code> snapshot of <code>~/.swarm</code>, zero downtime) and <code>swarm restore</code>; <code>swarm doctor --migrate</code>; Prometheus metrics at the team daemon's <code>/t1/metrics</code>; <code>[notify] webhook</code> POSTs every incident as Slack-compatible JSON (M8.5).</li></ul><h4>Fixed</h4>\n<ul><li>Replay, <em>Resume where it died</em> and the dry-run <em>Re-run</em> buttons were unreachable — dead click targets on the session page.</li><li>The user guide caught up: a Teams page, the M8 commands, all six agents, and the <code>SWARM_GUARD=off</code> description (org-locked rules stay enforced — true since 0.8, documented wrong until now).</li></ul><h4>Notes</h4>\n<ul><li>The privacy posture is unchanged and now stated precisely: with no <code>[team] url</code> and no <code>[notify] webhook</code> configured, nothing about your sessions leaves the machine — the <a href=\"https://getswarm.vercel.app/docs/10-privacy-and-faq\" target=\"_blank\" rel=\"noopener\">privacy page</a> lists the five opt-in egress paths.</li><li>The team daemon package is not yet on npm; run it from a clone (<code>bun packages/team/src/bin.ts</code>). Pricing for the paid tier is still open.</li></ul>"},"0.9.0":{"date":"2026-08-24","html":"<p>The crew release: the agents on your machine stop being strangers. They message each other and you, follow a declared workflow instead of a hopeful prompt, and every major CLI brand now shows up — Claude, Codex, Gemini, Grok. Plus the first-run and update experience a launch deserves.</p>\n<h4>Added</h4>\n<ul><li><b>Agent messaging</b> — <code>swarm_send(to, text)</code> reaches another session (id or unique prefix), whoever holds a task, or <code>\"lead\"</code> (your interactive session in the project). Delivery: on the recipient's next tool call as injected context, immediately over stdin to a spawned run, or pulled with <code>swarm_inbox</code> (which now returns answers <em>and</em> messages) — exactly once. A <b>messages</b> thread with compose box on every session page; <code>swarm msg send|ls</code> (M7.6, OQ-12 decided).</li><li><b>Workflows</b> — <code>[[workflows]] name = \"ship\" steps = [\"implement\", \"gate:tests\", \"gate:review\", \"pr\"]</code> in <code>.swarm.toml</code>; <code>swarm workflow ship &lt;task&gt;</code> and the daemon advances it: run steps spawn an agent in the task's worktree (told what the workflow will do itself), gate steps execute — only a pass advances — and <code>pr</code> pushes and opens the pull request from the ledger. A failed step stops with a <code>workflow_failed</code> incident; a daemon restart marks in-flight workflows stopped, honestly. <b>Workflows</b> on the Board with per-step chips (M7.8).</li><li><b>Gemini CLI adapter</b> — <code>~/.gemini</code> chat recordings are discovered and priced like every other agent: sessions, turns, tokens, cost, sparkline, Timeline, Spend. Schema from upstream source; first real-session validation pending (M5.4).</li><li><b>Timeline that shows the work</b> — bars are now a faint base with a tick per turn: bursts and idle stretches are visible instead of painted over. A thin claims lane per project shows lease spans (held / expired / orphaned). Recent gates carries a per-gate pass/fail history strip; every pinned project gets a 14-day spend sparkline in the sidebar (M5.7).</li><li><b><code>swarm demo</code></b> — a seeded demo dashboard on its own home and port: four agent brands, a live lease, an orphaned claim, gate history, incidents, a question, a message, a workflow mid-flight. Tailers are off in demo mode, so it never ingests your real logs — and your real data is never touched (delete <code>~/.swarm/demo</code> to reset).</li><li><b>Update that actually updates</b> — after an upgrade the dashboard notices the newer version on disk and offers a one-click daemon restart (the daemon re-execs into the new build and the page reloads). Long-lived tabs re-check every 5 minutes.</li><li><b>First-run onboarding</b> — an empty Fleet now explains the three steps (hook in — with a live <em>not installed</em> badge —, open any agent session, watch it appear) instead of showing a blank table.</li></ul><h4>Fixed</h4>\n<ul><li>External links in the desktop app (PR titles, Documentation, feedback, the just-opened PR) open in the browser — the webview silently swallowed them before.</li><li>Section-header actions (New worktree, Collect stale, timeline ranges) are proper small buttons instead of shouting uppercase with off-baseline icons.</li><li>The timeline claims lane no longer collides with the kanban's styles; a test file that broke lint on main is formatted.</li></ul><h4>Notes</h4>\n<ul><li>Windows builds are produced by CI as before but this release had no human Windows smoke test — reports welcome.</li></ul>"},"0.8.0":{"date":"2026-08-23","html":"<p>The trust release: Swarm becomes something a team and a security reviewer can rely on, without giving up local-first. An org can pin the rules that matter and they hold even when the daemon is down; every record says who did it; the daemon has a credential; what is stored is exportable and redactable; a second agent can be the reviewer. And the dashboard stopped looking like a list of tables.</p>\n<h4>Added</h4>\n<ul><li><b>Org policy layer</b> — a third config file, <code>~/.swarm/policy.toml</code> (or <code>$SWARM_POLICY</code>), sits under global and repo config and may declare <code>locked = [\"rules.destructive_git\", \"rules.protected\", …]</code>: dotted keys or whole subtrees the layers below cannot change. A locked key keeps the policy's value, every attempt to override it is a <code>policy</code> incident, and <code>swarm doctor</code> shows which file set each value and who tried to change it. <code>GET /v1/policy</code> exposes provenance (M8.1).</li><li><b>Tamper detection</b> — on every session start the daemon checks that all ten hook entries are still in <code>~/.claude/settings.json</code> with a sane timeout, that no lower config layer fights a locked key, and that <code>SWARM_GUARD=off</code> isn't set while the policy locks rules (it is then ignored). Each finding opens a <code>policy</code> incident once; <code>swarm doctor</code> prints the same (M8.1b).</li><li><b>Fail-closed for locked rules</b> — while the policy locks any rule the daemon keeps <code>~/.swarm/policy.cache.json</code> (locked modes + a snapshot of live sessions and held worktrees, integrity-hashed). If the daemon is unreachable on a tool call, the hook shim enforces exactly those rules from the cache; everything else still fails open (M8.1c, OQ-3 resolved).</li><li><b>Who did it</b> — every ledger record and every event carries an <code>actor</code> (<code>human</code> / <code>agent</code> / <code>run</code> / <code>daemon</code> + id); existing rows were back-filled from the owner strings clients always sent. Schema changes now go through versioned migrations (<code>/v1/health</code> and <code>doctor</code> report the schema version) (M8.2a).</li><li><b>Daemon token</b> — the daemon creates <code>~/.swarm/token</code> on first start; the CLI, MCP server, hook shim and <code>swarm ui</code> send it. Local callers may still omit it by default; <code>[daemon] auth = \"required\"</code> makes every call carry it, a wrong token is always refused, and anything that isn't loopback always needs it (M8.2b).</li><li><b>Audit log + export</b> — the ledger-changing subset of events (claims, worktrees, PRs, questions, dispatch, resources, processes, gates, handoffs, permissions, incidents, run results, session start/end), with actor: <code>swarm audit export [--since 30d] [-p] [--type …] [--format jsonl|csv|json]</code> and <code>GET /v1/audit</code>. Retention is split — <code>[events] retain_days = 30</code> for chatter, <code>[audit] retain_days = 0</code> (forever) for audit rows (M8.2c).</li><li><b>Privacy on ingest</b> — <code>[privacy] store_prompts = false</code> keeps the event but not the prompt text, <code>store_reasoning = false</code> keeps token counts but not assistant text, <code>redact = [\"ACME-[0-9]+\"]</code> scrubs stored strings; API-key-looking tokens and <code>Bearer …</code> credentials are always redacted. Global-only keys an org can lock (M8.2c).</li><li><b>Review as a gate</b> — <code>[gates.review] builtin = \"review\"</code> (optional <code>model</code>, <code>timeout</code>) spawns a read-only <code>claude -p</code> over the worktree's diff with a fixed rubric. The verdict is derived from the findings (any blocker/major fails, whatever the reviewer claims), findings are the evidence, and a reviewer that times out or won't answer in JSON is a fail with that reason. Same registry, logs, incidents and triggers as executed gates (M7.9).</li><li><b>Project settings</b> — sidebar menu → <b>Settings…</b>: name, icon (any emoji — a quick row, a browse-all grid, or the OS picker — or an image file, downsized to a square), a color slot, pinned. The glyph replaces the folder icon everywhere the project appears.</li><li><b>Board that is a board</b> — a KPI strip (live / held / worktrees / ready / incidents), tasks as a kanban (Ready · In progress · Blocked · Done), and a worktree map of tiles grouped by project and colored by live / dirty / unpushed / merged. Tasks and Worktrees keep a Cards | Table toggle.</li><li><b>Row menus</b> — every row on the Board, PRs and Incidents has one menu (hover kebab, right-click, or Enter) carrying its actions — open, diff, PR, run, claim, gates, release, stop, ack, codify, merge, copy — instead of inline links; destructive ones last and confirmed. Menus are wider and labels never ellipsize.</li></ul><h4>Changed</h4>\n<ul><li><code>doctor</code> reports the daemon and schema version, per-event hook coverage, and the policy file with its locked keys.</li><li><code>/v1/health</code> reports <code>schema</code> and <code>auth</code>.</li></ul><h4>Fixed</h4>\n<ul><li>Scratch repositories under the OS temp dir (test fixtures, spawned-run clones) no longer appear as projects in the sidebar.</li><li>Fleet's <code>now</code> column had no room; ended sessions show their last assistant line instead of \"session ended\".</li><li>Incidents show the command's gist (the leading <code>cd … &amp;&amp;</code> stripped) and <code>(removed)</code> instead of a raw id for a deleted project; Spend's attribution tables lost their empty first column.</li><li>Emoji tiles and the icon preview no longer clip in the desktop app's WebKit view.</li></ul>"},"0.7.0":{"date":"2026-08-23","html":"<p>The orchestrate release: Swarm runs a task end to end on its own and you stay in control. New worktrees start warm, gates execute instead of being vouched for, <code>swarm dispatch</code> hands ready tasks to autonomous runs whose outcome is derived from the ledger, an agent that needs a human decision can ask for one, and a budget keeps the bill in bounds.</p>\n<h4>Added</h4>\n<ul><li><b>Budgets</b> — <code>[budget] daily = 25</code> / <code>weekly = 100</code> in <code>.swarm.toml</code> puts a spend ceiling on a repo, judged from the same transcript-priced numbers as the Spend view. At <code>warn_at</code> (80%) a <code>budget</code> incident opens; past 100% <code>on_exceed</code> decides: <code>\"warn\"</code>, <code>\"ask\"</code> (every Bash / Edit / Write in the repo asks first), or <code>\"stop\"</code> (spawned runs stopped, dispatch queue cleared). One incident per level per day; a budget tile on the Spend view.</li><li><b>Run profiles</b> — <code>swarm run --profile no-edits|read-only</code> (and the Run / Dispatch drawers, <code>[dispatch] profile</code>) narrow what a spawned agent may do: <code>no-edits</code> keeps the shell but not the file tools, <code>read-only</code> keeps only read and search.</li><li><b><code>swarm_context</code></b> — an agent can re-read what Swarm told it at session start, current as of now (holds, lease left, handoff, gates, resources, rule modes), plus answers to its questions. <code>GET /v1/context</code>. And <code>swarm install</code> now registers the same MCP server with <b>Codex CLI</b> and <b>Gemini CLI</b> when they're installed, so they get the <code>swarm_*</code> tools too (M7.10).</li><li><b>Ask the human</b> — an agent that hits a decision only a person can make calls <code>swarm_ask</code> (with optional suggested answers). The question shows on the session page under <b>waiting on you</b> with the options as buttons, the session gets an <b>Asking</b> badge on Fleet, and a desktop notification fires. Answer there, or <code>swarm answer &lt;id&gt; &lt;text&gt;</code>; <code>swarm questions</code> lists what's open. The answer reaches the agent by itself — stdin for a spawned run, <code>[swarm]</code> context on the next tool call for an interactive session, <code>swarm_inbox</code> on demand — and a session starting later in the same task's worktree is told about open questions and answers that never arrived (M7.7).</li><li><b>Dispatch</b> — <code>swarm dispatch --ready</code> (or pick tasks; the <b>Dispatch</b> chip on the Board's Tasks; <code>swarm_dispatch</code> from a lead agent) hands ready tasks to autonomous runs: each gets its own claim and worktree and a <code>claude -p</code> run told to work there, run the gates, hand off and open the PR; <code>[dispatch] max_parallel</code> (default 2) run at once per repo, the rest queue. When a run ends Swarm derives the outcome from the ledger — executable gates re-run by the daemon, PR looked up on the forge — and reports <b>done</b>, <b>gates-failed</b>, <b>no-pr</b>, <b>crashed</b> or <b>stopped</b>; anything short of done opens a <code>dispatch_failed</code> incident and keeps the claim for you to resume or release. A dispatched run never edits the task list. <code>swarm dispatch status | clear</code>, <code>GET/POST/DELETE /v1/dispatch</code>, a <b>Dispatch</b> section on the Board (M7.5).</li><li><b>Diff and Open PR</b> — every worktree row on the Board (and a session page whose cwd is a worktree) gets <b>Diff</b>: the commits and files it carries beyond the main checkout's branch, uncommitted and untracked changes included, with a coloured unified diff per file. <b>PR</b> pushes the branch and opens a pull request (<code>gh</code>) or merge request (<code>glab</code>) prefilled from the task's title, the latest handoff, the required gates as a checklist and the file list — editable before it goes; refuses uncommitted changes, reuses an open PR for the branch. <code>swarm wt diff</code>, <code>swarm pr open [--dry-run]</code>, MCP <code>swarm_pr_open</code>; a <code>pr.opened</code> event on the Timeline (M7.3).</li><li><b>Gates that run themselves</b> — a gate with a command in <code>.swarm.toml</code> (<code>[gates.tests] cmd = \"bun test\"</code>, optional <code>timeout</code> / <code>cwd</code>) is executed rather than vouched for: <code>swarm gate run &lt;task&gt;</code> (or <code>swarm_gate_run</code> from the agent, or <b>Gates</b> on a held task row on the Board) runs every required gate that has a command inside the task's worktree and records the verdict — exit 0 passes, the rubric is the command and how it ended, the evidence is the output tail, the log lives in <code>~/.swarm/logs</code>. Runs go through the process registry and are killed at <code>timeout</code>. When a session in a held worktree ends, the daemon runs them on its own and writes the verdicts into that session's auto-handoff (<code>[gates] auto = \"session-end\" | \"stop\" | \"off\"</code>) (M7.4).</li><li><b>Worktrees without a task</b> — <code>swarm wt create &lt;name&gt;</code> makes a worktree for a spike or a review checkout (under <code>~/.swarm/worktrees/&lt;project&gt;/</code>, branch <code>wt/&lt;name&gt;</code>, bootstrapped like a claim); <code>swarm wt</code> lists every worktree with <b>drift</b> against the main checkout's branch (<em>N behind</em>, <em>merged</em>); <code>swarm wt open</code> opens it with <code>[worktree] open = \"code {path}\"</code> or the file manager; <code>swarm wt rm</code> removes it with the same refusals as <code>release</code> (dirty, unpushed, never the main checkout, never a held claim); <code>swarm wt gc [--apply]</code> finds worktrees whose branch was merged or whose claim was released and the folder left behind. The Board's Worktrees section gets the drift column, <b>Open</b> / <b>Remove</b> per row, <b>New worktree</b> and <b>Collect stale</b> (M7.2).</li><li><b>Warm worktrees</b> — <code>.swarm.toml [worktree] copy = [\".env.local\"]</code> and <code>setup = \"bun install\"</code> bootstrap every new worktree: the files are copied from the main checkout as the claim is made and <code>setup</code> runs inside the worktree in the background (log in <code>~/.swarm/logs/&lt;project&gt;/bootstrap-&lt;task&gt;.log</code>, a <code>worktree.bootstrapped</code> event on the Timeline). <code>swarm run</code> waits for it before starting the agent; an interactive <code>swarm claim</code> prints the log path and returns at once. A failing setup opens a <code>bootstrap_failed</code> incident but never takes the claim away. Paths are repo-relative only (M7.1).</li></ul><h4>Fixed</h4>\n<ul><li>The Fleet agent badge no longer renders a stray \"…\" after the pill: badge-only cells clip instead of ellipsizing, and the column got a few more pixels.</li></ul>"},"0.6.0":{"date":"2026-08-23","html":"<p>The learn release: the data Swarm has been collecting starts paying back. Replay what an agent did, see what each task cost, turn an incident into a rule, resume a session that died, try a rule on history before switching it on, read your backlog from GitHub or Linear, and search everything Swarm remembers.</p>\n<h4>Added</h4>\n<ul><li><b>Session Replay</b> — a <b>Replay</b> button on any session steps through its tool calls one at a time, showing the full input and output of each (Prev/Next, a slider, ←/→ keys). See exactly what an agent did, in order (M4.1).</li><li><b>Cost by task</b> — the Spend view attributes cost and tokens to each task (matched to a claim by the session's worktree), plus a <b>Context budget</b> table ranking sessions by how much context they re-processed — a signal for agents re-reading the same material. <code>GET /v1/attribution</code> (M4.2).</li><li><b>Codify an incident</b> — the Incidents feed has a <b>Codify</b> action that turns an incident into a <code>.swarm.toml</code> rule snippet and a CLAUDE.md lesson, both copyable. A rule that keeps firing as <code>ask</code> suggests hardening to <code>deny</code> (M4.3).</li><li><b>Desktop notifications</b> — opt-in native notifications (settings menu) for a spawned run waiting on a permission, or a claim orphaned with unfinished work; clicking opens the Allow/Deny card or the Board. Quiet while you're looking at the dashboard (M4.7).</li><li><b>Auto-handoff, and resume where it died</b> — whenever a session working in a claimed worktree pauses or ends, Swarm derives a handoff from what it did: files edited, the last verification-looking command, the last request, the last thing it said. One <code>auto:</code> handoff per session, replaced on every pause, silenced by a handoff left on purpose. An ended session's page gets <b>Resume where it died</b>, which spawns a run on the task from that handoff plus the session's last actions; <code>swarm run resume &lt;session-id&gt;</code>; <code>GET/POST /v1/sessions/:id/resume</code> (M4.4).</li><li><b>Rule dry-run</b> — <b>Dry-run rules</b> on the Incidents view replays a project's recorded tool calls through the rules under modes you pick: what would have been asked or denied, per rule, before you switch anything on. It also flags <b>flaky signals</b> — a rule that keeps firing on the same command that is then allowed through anyway. Nothing is recorded. <code>swarm rules dryrun [--set rule=mode,…]</code>; <code>GET /v1/rules/dryrun</code> (M4.6).</li><li><b>GitHub Issues and Linear as task sources</b> — <code>[tasks] source = \"github\"</code> reads the repo's issues through the logged-in <code>gh</code> (optional <code>labels</code> filter); <code>source = \"linear\"</code> reads Linear through its API with <code>LINEAR_API_KEY</code> from the daemon's environment (optional <code>team</code>). Both land in the Board's Tasks, <code>swarm tasks</code> and <code>swarm_next_task</code> like a markdown backlog: closed/completed is done, in-progress is active, <em>depends on #n</em> / <em>blocked by</em> become dependencies. Read-only; no credential stored (M4.8).</li><li><b>What's New in the app</b> — the dashboard shows the release notes for the running version: a <b>What's New</b> item in the settings menu, in the desktop app's <b>Swarm</b> menu, and in the tray. It also opens once on its own the first time you run a new version. Notes are parsed from <code>CHANGELOG.md</code> into <code>release-notes.js</code> at build time, so they work offline with no repo checkout.</li><li>The desktop <b>Check for Updates…</b> is in the system menu bar (Swarm menu), not only the tray.</li></ul>"},"0.5.0":{"date":"2026-08-22","html":"<p>The drive release: Swarm doesn't just watch agents now — it starts them, in a claimed worktree, and brokers what they're allowed to do. Plus the coordination primitives that make an autonomous run safe to leave alone: leases that renew themselves, gates that gate, and a handoff the next session reads on its own.</p>\n<h4>Added</h4>\n<ul><li><b><code>swarm run</code></b> — spawn an agent on a task. <code>swarm run --task login-form --prompt \"…\"</code> claims the task and the daemon starts <code>claude -p</code> in its worktree with stream-json on both ends. Steer it with <code>swarm run send</code>, stop it with <code>swarm run stop</code> (stdin closed, then the process registry's pid-based TERM/KILL — never by pattern), list with <code>swarm run ls</code>. The session shows in Fleet as ▶ spawned and is ingested like any other; every finished turn is a <code>run.result</code> event with cost and turns (M3.1).</li><li><b>Run from the dashboard</b> — Ready (or held) task rows on the Board get a <b>Run</b> action: a drawer with the prompt prefilled from the task, permission mode, model and max turns (⌘⏎ to submit). The spawned session opens with a stdin box to steer it and a <b>Stop</b> button (M3.3).</li><li><b>Permission broker</b> — a <code>swarm run</code> agent's tool-permission prompts go through the same rules as your interactive sessions: a rule <code>deny</code> auto-denies with the reason, an unflagged tool auto-allows so the agent can make progress, and anything the rules mark <code>ask</code> is held and surfaced on the session as an <b>Allow / Deny</b> card. No blocking on a terminal you can't see. Uses <code>--permission-prompt-tool stdio</code>; <code>POST /v1/runs/:id/permissions/:reqId</code> (M3.2).</li><li><b>Leases renew themselves.</b> A session working inside a claimed worktree extends the lease on any activity (hook or transcript growth) once it is past half-way — no more <code>swarm renew</code> in a long session. Expired leases whose worktree still holds uncommitted or unpushed work are marked <b>Orphaned</b> within a minute and open an <code>orphaned_claim</code> incident; nothing is removed automatically (M1.2).</li><li><b>Handoffs, injected on start</b> — <code>swarm handoff &lt;task&gt; --done … --remaining … [--files] [--verify]</code> (or <code>swarm_handoff</code>) records what the last holder leaves; <code>swarm resume</code> / <code>swarm_resume</code> reads it. The next session that starts inside that task's worktree gets it automatically as <code>SessionStart</code> context, along with what it holds and the lease left, gate status, held resources, and the repo's rule modes (M1.3).</li><li><b>Gates</b> — verification runs recorded against a task: <code>swarm gate record login-form review pass --rubric \"tests green, error paths read\"</code> (or <code>swarm_gate_record</code>). A run without a rubric is rejected; the latest run per gate decides; failed runs are never deleted and open a <code>gate_failed</code> incident. <code>.swarm.toml [gates] required = [\"review\"]</code> declares what every task must pass; the Board's Tasks grid shows ✓ / ✗ / — per gate and a <b>Recent gates</b> section lists the runs (M2.2).</li><li><b>The MCP tools finally connect.</b> <code>swarm_status</code>, <code>swarm_claim</code>, <code>swarm_next_task</code>, <code>swarm_handoff</code>, <code>swarm_gate_record</code>, <code>swarm_acquire_resource</code> and the rest are reachable from Claude Code — see the fix below.</li><li>Dashboard deep links (<code>?view=board&amp;project=&lt;id&gt;&amp;session=&lt;id&gt;</code>) and a screenshot carousel with a lightbox on the website; <code>tools/screens.ts</code> re-captures the shots with Playwright at 2×.</li></ul><h4>Fixed</h4>\n<ul><li><b>Swarm's MCP tools were never reachable.</b> <code>swarm install</code> wrote <code>mcpServers.swarm</code> into <code>~/.claude/settings.json</code>, which Claude Code ignores — user-scope MCP servers live in <code>~/.claude.json</code> (what <code>claude mcp add -s user</code> edits). Install now registers there (and cleans the stale settings.json entry); <code>claude mcp list</code> shows <code>swarm ✔ Connected</code>. <b>Re-run <code>swarm install</code> after upgrading.</b></li><li><b>The daemon reads global config from where its state lives.</b> <code>[rules]</code> / <code>[gates]</code> / <code>[tasks]</code> in <code>~/.swarm/config.toml</code> are resolved against the daemon's home (<code>SWARM_HOME</code>), matching the DB and logs — spawned runs, which execute in a worktree without the repo's <code>.swarm.toml</code>, still see machine-wide rules.</li></ul>"},"0.4.1":{"date":"2026-08-22","html":"<h4>Fixed</h4>\n<ul><li><b>PRs view went dark under the desktop app.</b> A daemon launched from the Dock gets macOS's bare GUI <code>PATH</code>, so Homebrew's <code>gh</code> / <code>glab</code> were invisible and the forge silently returned nothing. The daemon now also looks in <code>/opt/homebrew/bin</code>, <code>/usr/local/bin</code>, Linuxbrew and <code>~/.local/bin</code>; <code>swarm doctor</code> reports forge CLI auth and warns when <code>glab</code> relies on a shell-only <code>GITLAB_TOKEN</code>.</li><li>Icons are vertically centred on their text again (<code>vertical-align: middle</code> instead of a fixed <code>-3px</code> tuned for the old type scale).</li><li>npm publish moves to <b>trusted publishing</b> (OIDC, no <code>NPM_TOKEN</code>), the same setup as fancy-menus.</li></ul>"},"0.4.0":{"date":"2026-08-22","html":"<p>The enforcement release: rules that watch file writes, not just Bash; a backlog Swarm can read; servers Swarm starts and stops by pid; and an Incidents feed you can clear.</p>\n<h4>Added</h4>\n<ul><li><b>Rules on file writes</b> — two new rules evaluated on <code>Write</code> / <code>Edit</code> / <code>MultiEdit</code> / <code>NotebookEdit</code> paths (and Bash working directories), not only Bash commands. <code>no_foreign_worktree</code> (default <code>ask</code>) stops a session from editing inside a worktree another claim holds — <em>never touch a worktree you don't hold</em> is now a hook decision, with holding inferred from the session's cwd. <code>claim_required_to_write</code> (opt-in) makes a repo's shared checkout read-only without a claim: claim a task, get a worktree, write there. Both per repo as <code>ask | deny | off</code>.</li><li><b>Incidents view</b> — the denied-action feed as its own tab: Open / All, per-rule counts, reason and session per row, <b>Ack</b> and <b>Ack all</b>; the open count sits in the nav. <code>GET /v1/incidents?open=1&amp;project=</code>, <code>POST /v1/incidents/:seq/ack</code>, <code>POST /v1/incidents/ack</code>, <code>/v1/state.openIncidents</code>. The Board keeps a short open-only section.</li><li><b>Task source</b> — <code>.swarm.toml</code> <code>[tasks] source = \"docs/plan.md\"</code> points at a markdown file whose <code>ID | Task | Depends | Status</code> tables are the backlog (✅ / 🟡 / ⚪, dependencies by task id or milestone prefix). The Board gets a <b>Tasks</b> section (Ready / Open / All, <em>Claim</em> per row), the CLI <code>swarm tasks [--ready]</code>, and agents <code>swarm_next_task</code> — the first unclaimed task whose dependencies are done. Swarm's own roadmap is its task source. Markdown only (OQ-5 decided).</li><li><b><code>swarm serve</code> / <code>swarm proc</code></b> — <code>swarm serve start --name web -- npm run dev</code> allocates a free port (ledger + bind probe), runs the command detached with <code>PORT</code> set and logs under <code>~/.swarm/logs/&lt;project&gt;/</code>, registers pid + start time, and acquires the singleton — so a second <code>web</code> fails closed and the port is protected for every other session with no config. <code>serve ls | stop [name|pid]</code>, <code>proc start | ls | stop</code> for workers without a port. Stop signals registry pids only, verified by start time; nothing is ever killed by pattern. <code>POST /v1/ports/allocate</code>, <code>GET/POST/DELETE /v1/processes</code>; <b>Processes</b> section on the Board with <em>Stop</em>.</li><li><b>Star nudge</b> — once a month at most, never on first open, the dashboard asks for a GitHub star. <em>Later</em> snoozes 30 days, <em>Don't ask again</em> is final; localStorage only.</li><li><b>Sidebar drag-and-drop</b> — pinned projects reorder by dragging; the order persists on the daemon (<code>PUT /v1/projects/order</code>, <code>Project.order</code>).</li><li><b>Desktop app menu</b> — a real application menu (Swarm / Edit / View / Window): ⌘C/⌘V work, <b>View › Zoom In / Zoom Out / Actual Size</b> (<code>⌘+</code> <code>⌘−</code> <code>⌘0</code>) scale the dashboard (persisted), plus Reload and Full Screen.</li></ul><h4>Fixed</h4>\n<ul><li><b>Shared-tree rules no longer lose sight of a session mid-turn.</b> <code>shared_tree</code> / <code>destructive_git</code> keyed on a 2-minute last-seen window fed only by hooks, so a neighbour three minutes into a long turn became invisible — and its uncommitted work unguarded. Transcript growth now counts as activity (the tailer bumps <code>last_seen_at</code>), and the liveness window is the daemon's 10-minute idle threshold (<code>LIVE_WINDOW_MS</code>). A false positive costs one confirmation; a false negative cost someone's work.</li><li><code>destructive_git</code> also matches <code>git stash drop</code>, <code>git stash clear</code> and <code>git branch -D</code>.</li><li>Dashboard type scale is one step larger across the board (base 13 → 14 px; the smallest labels 10 → 11 px) — it had drifted too small, especially in the desktop app.</li><li>The <b>PRs</b> tab icon (and the branch/commit glyphs) were near-invisible at 15 px; they use the pixelarticons <em>sharp</em> variants now.</li><li>The daemon dot stayed red for up to 15 s after load on a healthy connection (the SSE stream sent nothing until its first heartbeat); the stream now flushes immediately.</li><li>The nav flashed \"Fleet\" before the restored tab was applied; session-detail event kinds (<code>userpromptsubmit</code>) no longer overflow into the message column; \"Unpinned · seen, not pinned\" keeps its spacing.</li></ul><h4>Changed — docs</h4>\n<ul><li>README and site now say what the code does: Codex CLI and Grok sessions are tailed alongside Claude Code; the requirements and architecture diagram list all three.</li><li>Rules are described as <b>guardrails against accidents, not a sandbox</b> — the guide has a new \"What rules are — and aren't\" section spelling out that a denied Bash command can be routed around (script, heredoc, direct file edit), and that worktree isolation via claims is the real fix. The site's feature cards lead with claims, rules and resources instead of Fleet and Spend.</li></ul><h4>Changed — performance</h4>\n<ul><li><b>Daemon never spawns <code>git</code> on a request.</b> Worktree status (<code>git worktree list</code> + <code>status</code>/<code>rev-list</code> per worktree, ~0.8 s across a fleet) moves to a 15 s background refresh with async <code>Bun.spawn</code>; <code>/v1/state</code> serves the cache (612 ms → ~15 ms). Claim/release invalidate it.</li><li><b>Hook round-trips are two indexed statements</b>, not two <code>git rev-parse</code> spawns plus a transcript-directory scan: <code>cwd → project</code> is cached 60 s, the inline transcript tail is debounced to once per 2 s per session (the 5 s tailer covers steady state), and subagent directories are re-listed only when their mtime moves.</li><li><b>Events store ~2 KB, not ~10 KB.</b> <code>tool_input</code> is clipped at 2 KB and <code>tool_response</code> at 4 KB in <code>payload</code> (<code>{truncated, bytes, preview}</code>), and the tool I/O is no longer duplicated in <code>raw</code>. Existing databases are rewritten once on boot and <code>VACUUM</code>ed (96 MB → 27 MB here). Retention: events older than 30 days are pruned daily (incidents kept), <code>raw</code> is cleared after 7 days.</li><li><b>Wire shape.</b> SSE frames, <code>GET /v1/events</code> replays and <code>GET /v1/sessions/:id/events</code> carry <code>seq/ts/type/projectId/sessionId/payload{hook,summary,…}</code> only — no <code>raw</code>, no tool I/O (a 5.5 MB session fetch is now ~150 KB). <code>GET /v1/events/:seq</code> returns one stored event in full; <code>?full=1</code> on the SSE replay does the same. <code>?since=0</code> replays the last 200 events, not the table.</li><li><b>Incremental session view</b> — <code>GET /v1/sessions/:id/events?after=&lt;seq&gt;&amp;afterTs=&lt;iso&gt;</code>; the dashboard appends instead of re-fetching 500 events + 500 turns on every hook.</li><li><b>Dashboard render loop</b> — one <code>requestAnimationFrame</code> scheduler, snapshot <code>seq</code> short-circuit, paused while the tab is hidden, exponential SSE reconnect backoff; session log merges two sorted lists and caches rendered rows; data-grid memoises persisted layout and uses one <code>Intl.Collator</code>; charts memoise the turn strip.</li><li>SQLite: indexes on <code>events(type, seq)</code> and <code>turns(ts)</code>, <code>mmap_size</code> 256 MB, cached prepared statements (<code>db.query</code>), <code>sessions</code>/<code>spend</code>/<code>incidents</code> memoised per write generation; <code>/v1/spend</code> is its own endpoint.</li><li>Background tick: Codex/Grok discovery every 15 s when idle; Grok <code>summary.json</code> re-read only on mtime change.</li></ul>"},"0.3.0":{"date":"2026-08-22","html":"<p>The coordination release: rules you can configure, runtime resources agents can hold, and the merge queue at the end of the loop.</p>\n<h4>Added</h4>\n<ul><li><b>Config system</b> — <code>~/.swarm/config.toml</code> (global) deep-merged with an optional <code>&lt;repo&gt;/.swarm.toml</code>. Lenient validation: bad config can never take the daemon down. Daemon port preference is <code>SWARM_PORT</code> &gt; config &gt; 7777. See <code>docs/13-config.md</code>.</li><li><b>Rule engine v2</b> — every rule is per-repo configurable as <code>ask | deny | off</code>: <code>shared_tree</code>, <code>destructive_git</code>, <code>pattern_kill</code>, and the new <code>protected_ports</code> (kill/free of a configured port — <code>lsof | kill</code>, <code>fuser -k</code>, <code>kill-port</code> — is asked or denied). <code>deny</code> is returned to Claude Code as a real permission denial.</li><li><b>Incidents</b> — every non-allow decision is recorded (<code>incident.opened</code>: rule, action, command, reason), exposed at <code>GET /v1/incidents</code>, included in <code>/v1/state</code>, and shown on the Board.</li><li><b>Runtime resources (Phase 1)</b> — named singletons for what agents fight over at runtime (dev servers, databases, ports). Fail-closed acquire: holdings live while their pid runs or their lease hasn't expired; the same owner refreshes; dead holdings reap instead of blocking. Release is fail-closed too (owner required, <code>--force</code> overrides). Held ports automatically join the protected-ports rule — acquiring <code>db</code> on 5432 guards <code>lsof -ti:5432 | xargs kill</code> for every other agent, no config needed. HTTP <code>GET/POST /v1/resources</code>, <code>DELETE /v1/resources/:name</code>; MCP <code>swarm_acquire_resource</code> / <code>swarm_release_resource</code> / <code>swarm_resources</code>; CLI <code>swarm res ls|acquire|release</code>.</li><li><b>PRs view</b> — one merge queue across GitHub and GitLab. Forge detection from the git remote (ssh/https, GitLab subgroups, self-hosted), polled through the locally-authenticated <code>gh</code> / <code>glab</code> CLIs with the project root as cwd — no tokens stored, 2-minute per-project cache floor. <code>GET /v1/prs</code>, <code>POST /v1/prs/merge</code> (squash). Merge is offered only on green, mergeable, non-draft rows, behind a confirm.</li><li><b>Board view</b> — Claims, Worktrees, Resources, and Incidents move out of Fleet into their own view; Fleet shows sessions only (Live + Earlier). Last view and project selection persist across reloads.</li><li><b>Stats view</b> — <code>GET /v1/stats</code>; activity line, calendar heatmap, and streaks (daily buckets in local time, DST-immune). <code>swarm stats</code> on the CLI.</li><li><b>Data-grid everywhere</b> — Claims, Worktrees, Resources, Incidents, PRs, and all six Spend tables render through the same sortable / resizable / reorderable grid with per-column filters, a column-visibility menu, and persisted layouts. Header ticks and tooltips make the affordances discoverable.</li><li><b>Desktop: Check for Updates…</b> in the tray menu, wired to the Tauri updater with native dialogs (available / up-to-date / failed) and install-and-restart on accept.</li><li><b>Agent badge</b> on every Fleet and session row, so mixed-agent fleets are labelled consistently.</li><li><b>Website</b> — getswarm.vercel.app: OS-detected downloads from the latest GitHub release, the <code>bunx</code> one-liner, sharing tags with hero art, and (this release) rendered docs and release notes.</li><li><b>Design tokens</b> — the dashboard's CSS contains zero raw hex / rgba / font-size / duration values; the system is documented in <code>docs/12-design-tokens.md</code> with a drift grep.</li><li>Swarm now dogfoods its own rules via the repo's <code>.swarm.toml</code> (<code>shared_tree</code> / <code>destructive_git</code> deny, daemon port protected).</li></ul><h4>Fixed</h4>\n<ul><li><b>Hook resilience</b> — the PreToolUse hook falls back to the default port when <code>daemon.json</code> points at a dead daemon, so a crashed daemon no longer silently disables the guard (this was the gap behind a real <code>git add -A</code> collision).</li><li>Resource liveness: pid 0 was treated as a live process (<code>kill(0)</code> addresses the process group), so those holdings never reaped; tracked pids are now <code>&gt; 0</code>. <code>heldPorts()</code> is one SELECT on the hook path; lazy reap on acquire, sweep on the 5 s tick. Unknown session IDs on acquire no longer mint phantom sessions.</li><li>Session view is two equal columns again (a bare <code>aside</code> selector in the sidebar-collapse CSS captured the session side panel); the log keeps your scroll position across live updates and follows the tail only when pinned to the bottom.</li><li>A pinned project whose root vanished is merged into the live same-name entry (repo renames produced duplicate sidebar rows); the sidebar <code>⋯</code> appears on hover in the count's slot and reserves no space.</li><li>Desktop: quit actually quits, window close hides (macOS convention) and the dock icon restores it, and the <code>swarmd</code> sidecar dies with the app. Dev builds serve the repo's live dashboard instead of a stale staged snapshot.</li><li>Release pipeline: npm publish is skipped cleanly when <code>NPM_TOKEN</code> is absent (since 0.4.1: trusted publishing, no token).</li></ul>"},"0.2.2":{"date":"2026-08-21","html":"<h4>Added</h4>\n<ul><li>Publishable <code>@ra3orblade/swarm</code> npm package (bundled bins + dashboard); <code>bunx @ra3orblade/swarm setup</code> onboarding.</li><li>Enterprise data-grid for Fleet with a collapsible sidebar; pixel-art icon set and bespoke empty-state illustrations; folder picker; green chart palette.</li><li>Desktop: macOS window chrome, animated pixel-logo splash, free-port daemon startup.</li></ul><h4>Fixed</h4>\n<ul><li>Release builds bundle every platform target; Linux ships <code>.deb</code> + <code>.rpm</code> (AppImage disabled until <code>linuxdeploy</code> on GitHub runners is debugged).</li></ul>"},"0.0.6":{"date":"2026-08-21","html":"<p>First signed and notarized macOS desktop build; <code>release.yml</code> became a three-OS matrix (macOS / Windows / Linux) with a native sidecar per runner.</p>"}};
2
+ window.RELEASE_NOTES = {"0.15.0":{"date":"2026-09-19","md":"The rules now cover every agent Swarm watches, not only Claude Code, and hold up over a long\nsession: Codex, Gemini CLI and Cursor get the same checks, five new rules for irreversible\ncommands ship switched off so you can see what they would catch first, a compacted session is\ntold again what it holds, and Swarm can export everything to your own OpenTelemetry backend.\nThe team daemon installs without a clone.\n\n## Added\n\n- **The rules hold on Codex, Gemini CLI and Cursor.** Swarm watched six agents and ruled one.\n `swarm install` now adds its hook to Codex (`PreToolUse` in `~/.codex/hooks.json` — trust it\n once with `/hooks`) and Gemini CLI (`BeforeTool` in `~/.gemini/settings.json`), beside your own.\n Cursor already ran Swarm's Claude Code hooks (it imports them), but its payload names the shell\n tool `Shell` and has no session id, so every call went through unchecked; Swarm now reads it.\n Codex's `apply_patch` is checked file by file. None of these agents can ask from a hook, so a\n rule set to `ask` refuses there and tells the agent to hand the command to you. The Rules view\n and `swarm doctor` show where the rules hold.\n- **Five rules for the commands nobody can take back.** `destructive_fs` (`rm -rf` of `/`, `~`,\n `..`, `\"$VAR\"/` or anything outside the repo; `mkfs`; `dd` onto a device), `destructive_infra`\n (`terraform destroy`, `kubectl delete ns`, `helm uninstall`, cloud deletes, SQL `DROP` /\n `TRUNCATE`), `pipe_to_shell` (`curl … | sh`), `secrets` (reading, printing or copying `.env`,\n keys and cloud credentials — the `Read` tool included — and writing `.env*` / key files) and\n `config_tamper` (editing Claude Code's settings, `~/.swarm`, `.swarm.toml`, or `swarm uninstall`).\n All five ship **off**. Meanwhile the Security view shows what each would have caught, with an\n example and its current mode, so you can see whether turning one on stops something real\n before you do. Set any of them to `\"ask\"` or `\"deny\"` under `[rules]`; an org policy can lock\n them, and then the hook enforces them even with the daemon down.\n- **Worktrees Claude Code makes are claimed too.** A session started with `claude --worktree`,\n a subagent with `isolation: \"worktree\"` or a background session works in a worktree Claude Code\n created under `<repo>/.claude/worktrees/`, and the ledger used to know nothing about it. Now the\n first hook from inside one claims it as `cc/<name>` for that session: the status line shows it,\n the session is told what it holds at startup, and another session writing\n into it gets the same `no_foreign_worktree` question as for any claimed worktree. The claim\n ends when the session does. Swarm never removes these worktrees — releasing, reaping and\n `swarm wt gc` only end the record; whether the directory stays is Claude Code's call.\n- **Three strikes, and Swarm says what it knows.** When a Claude Code session runs the same\n failing Bash command a third time, the failure comes back with the verify command from the\n held task's handoff and the lesson from any incident on the same command — through the\n `PostToolUseFailure` hook, which Swarm now installs (re-run `swarm install`; `swarm doctor`\n lists it as missing until you do). It speaks once per command, and not at all when it has\n nothing to add. A failed call now shows in the session log as *failed*.\n- **A compacted session is told again what it holds.** Compaction drops the context Swarm gave a\n session at startup. Claude Code fires `SessionStart` again with `source: \"compact\"` after it\n compacts, and Swarm's answer now leads with *context was compacted* before the held task, the\n lease, the handoff and the rules — so the agent knows it is being reminded, not told something\n new.\n- **OTLP export.** Set `[otel] endpoint` and Swarm sends every session — Claude Code, Codex,\n Gemini CLI, Grok, Aider, opencode — to your own OpenTelemetry backend: one trace per session,\n a span per tool call (failed ones marked), a span for each wait on you, claims, incidents and\n gates as events, plus token and cost metrics. It speaks the OpenTelemetry GenAI conventions by\n default, or Claude Code's own metric names with `compat = \"claude-code\"`. Commands and paths\n stay out unless `include_content = true`. A collector that is down loses nothing, and `swarm\n doctor` shows whether the last export got through. Off unless you set the endpoint.\n- **Host a team without a clone.** The team daemon now ships three ways, each the same version\n as the app: `npm i -g @ra3orblade/swarm-team`, the `ghcr.io/ra3orblade/swarm-teamd` image,\n and a `swarm-teamd` binary per platform on every GitHub release. The Team panel shows its\n license (FSL-1.1-ALv2, source-available, not the app's Apache-2.0) and, once you accept,\n downloads the binary for your machine, checked against the release's checksums, into\n `~/.swarm/bin`. It is never bundled into the app.\n- **The team dashboard has Settings.** Admins change roles, remove members (which ends their\n sessions), revoke machines, and edit the org policy right there — *Sign and publish* signs it\n with the team's key. A team always keeps an admin. With one shared secret, its holder is the\n admin.\n- **Join finds teams on your network.** A team daemon announces itself over mDNS, and the Team\n panel's Join lists what it hears — one click fills in the address. Names and addresses only: a\n shared secret still arrives in the invite link.\n- **An agent's question is a question you can answer.** When a Claude Code session called\n `AskUserQuestion`, the permission card showed `questions=[{\"question\":\"Which…` — the tool's\n input as JSON, cut off at eighty characters — above *Allow* and *Deny*, neither of which means\n anything for a question. The card now draws the questions: header, text, each option with its\n description, *pick any* where several are allowed, and a *Something else…* field. **Answer**\n sends the picks back the way the hooks reference documents it — the original input echoed as\n `updatedInput` with `answers` mapping each question's text to the chosen label(s) — so the agent\n carries on without anyone touching the terminal; *Answer in terminal* and the countdown are\n still there. The session log and the desktop notification say the question instead of the JSON\n too. Any other tool without a one-line summary (every MCP tool) shows its input as named\n fields, with JSON indented — including JSON that arrived as a string.\n- **A badge when something waits on you.** The desktop app's dock icon carries the number of\n agents blocked on a person — questions asked through `swarm_ask` plus parked permission\n prompts — and clears when they are answered. A browser tab has no icon badge, so it gets the\n count in its title: `(2) Swarm`. The dock badge needs the new\n `core:window:allow-set-badge-count` capability, so it needs this release's desktop build; an\n older shell just shows no badge.\n\n## Changed\n\n- **The README shows the robot.** Every mark Swarm ships is generated from the one grid in\n `core/src/art.ts` — the app icons, the tray template, the favicons, the site's hero, the share\n image, the desktop splash. `docs/art/swarm-icon.svg` and `swarm-mark.svg` were the two that never\n were: hand-authored from an earlier motif entirely and left untouched when the robot was drawn,\n so the repository's front page was the last place still showing the old brand. `tools/icons.ts`\n now writes them like the rest. The text caught up too — the status line, the interactive\n permission card, the repair loop, rewrite rules and wake had all shipped without reaching the\n README, `swarm statusline`, `workflow`, `msg` and `swarm_send` were missing from the command and\n tool lists, and \"eleven releases\" was written at v0.7.0. Six screenshots are three.\n\n- **The Team panel stops shouting.** It was built out of `.perm` — the amber card that means *an\n agent needs your approval* — so a page whose only job is to offer to start a daemon read as two\n stacked warnings, and the paragraph explaining the team daemon was set in `.perm-t`, a card\n *title*, at full brightness across the whole window. The port field matched no rule in the\n stylesheet at all (`.stdin input` was the only styled input in it), so it fell back to the\n browser's own white control in a dark UI, and `.perm-b` never set `align-items`, so \"port\" sat\n off the button's centre line. Host and Join are now a neutral `.card` bounded at a readable\n measure, the explanation is a paragraph, the two fields share one input rule, the rows align,\n and Join and Leave have lost the box that wrapped nothing.\n\n- **Markdown renders as markdown.** Everything an agent writes is markdown, and until now the\n dashboard showed the source: a session-log line arrived with its asterisks and backticks intact,\n and a fenced code block was a wall of text between two rows of backticks. Assistant and subagent\n turns are now rendered — paragraphs, headings, bullet and numbered lists, fenced code, quotes,\n tables, inline code, bold, links. Where there is room for\n one line and no more (Fleet's *now* column, search snippets, the prompt/question/answer/message\n summaries) the marks come off instead, because a `<b>` inside a one-line cell is just noise.\n\n- **What's New reads like release notes.** The panel used to inject HTML generated from\n `CHANGELOG.md` at build time, which wrapped every *source* line in its own paragraph — a\n sentence the changelog had wrapped arrived as three. It now ships the changelog's markdown and\n renders it with the same renderer as the log. The renderer builds elements and checks every\n link, so text a model wrote never reaches `dangerouslySetInnerHTML`.\n\n## Fixed\n\n- **The team daemon kept its database outside `SWARM_HOME`.** `team.toml` followed `SWARM_HOME`,\n `team.db` did not; it now sits beside the settings wherever they are.\n- **A session's log no longer shows `<task-notification>` and calls it \"you\".** Claude Code fires\n `UserPromptSubmit` for more than what a person types: a background task finishing, a subagent\n handing back its report and a message from another session all arrive as prompts, wrapped in a\n tag. The summary was the prompt's first line, so those rows read `<task-notification>` or\n `<agent-message from=\"aac244…\">` and nothing else — and they were labelled and coloured as the\n reader's own words. `core/src/prompt.ts` now reads the wrapper: a task row says what finished\n (`Agent \"…\" finished`, plus the status when it is not `completed`), a report row shows the first\n line of the report rather than the harness's preamble, a cross-session row leads with the sender,\n and the event carries `origin` so the log labels them `task` / `report` / `msg` in the muted\n style. Pasted markup (`<a href=…>`) is still yours. Migration v3 re-derives the rows already\n stored from their kept prompt. The auto-handoff had the same blind spot — its \"last request\"\n could be a task notification — and now only quotes something a person typed.\n\n- **Sessions that died without a `SessionEnd` no longer haunt the dashboard.** A session's row only\n reached `ended` when the hook fired, so a closed terminal, a crash, a reboot or a slept laptop\n left it non-ended for ever — `idle` is a display label with no upper bound, and Fleet was listing\n sessions last seen days ago as though they were live. `sweepStaleSessions()` now ends anything\n that has gone `SESSION_STALE_MS` (6 hours) without a single event, on daemon boot and once a\n minute, stamping `ended_at` with the moment the session was last actually heard from rather than\n the moment the sweep noticed. It covers every agent, not only the hook-driven ones: `ingestLog`\n gives up on a transcript it can no longer read, which strands Codex / Grok / Gemini rows the same\n way. On one real machine this ended 11 of 15 stranded sessions, the oldest last seen 25 days\n earlier, and left every genuinely live one alone.\n- **`leadSession` and `sessionForTask` can no longer resolve to a dead session.** Both selected on\n `state != 'ended'` with no time bound, so once every real session had ended cleanly a stranded\n one won the `ORDER BY last_seen_at` and messages addressed to the project went nowhere.\n- **A cancelled task is no longer offered as work.** `statusOf` only recognised \"done\" and \"active\";\n everything else fell through to `todo`, so a row marked `❌ dropped` came back `ready: true`, sat\n in the Board's **Ready** lane with a green badge, and was eligible for `nextTask`,\n `swarm_next_task` and dispatch — Swarm would hand an agent work the backlog had explicitly\n cancelled. `dropped` is now its own `TaskStatus` (`❌`, `🚫`, `[-]`, `~~struck~~`, \"cancelled\",\n \"wontfix\", \"abandoned\", …): never ready, rejected by dispatch with the reason `dropped`, shown\n with a **Dropped** badge, and excluded from the open count. A dropped *dependency* counts as\n resolved, so it does not block its dependents for ever. Linear's `canceled` issues map to\n `dropped` rather than `done`.\n- **Lineage showed the wrong fortnight.** The graph kept the *best-connected* sessions when it hit\n its node cap, so a hub from two weeks ago outranked every session started today, and the ones\n that made the cut were ordered by id — the picture looked stale because it was. It could also keep\n a parent and drop its only child, leaving a dot with nothing attached. The cap now keeps the\n newest relationships and never strands a node, roots run newest-first, and the heading says how\n many older sessions were left out instead of hiding the count.\n- **Lineage is drawn as a tree, not two columns joined by a bundle.** A parent now sits level with\n the first thing it started, siblings stack beneath, and the layer gap gives an edge room to bend;\n the straight ones are straight. A collapsed group's tooltip carries what its children cost, and\n the outcome ring the legend promised finally appears — the node was always sent without one.\n- **The `+` on a collapsed group sits where it should.** The pill was sized from an estimate of the\n label's width, so the `+` landed 2px from one end and 13px from the other. Labels are measured\n now and the `+` is drawn on the row's centre line.\n- **A collapsed group on Lineage opens when clicked.** Two faults, either enough: the pill sent\n its parent's id where the engine expected the group's own, and with no project selected the\n request went to `/lineage&expand=…`, a path that does not exist.\n- **Graph tabs no longer jump when clicked.** A count appeared on the chip just switched to (only\n the open tab has data), widening it and pushing the others sideways. The numbers moved to the\n heading, where each tab has a summary line of its own."},"0.14.0":{"date":"2026-09-12","md":"The **Act** release: Swarm stops only watching agents and starts steering them — every new\ncapability below reaches an *interactive* session, not just a spawned run.\n\n## Added\n\n- **The repair loop: a session cannot say it is done while a required gate fails.** With\n `[gates] on_stop = \"block\"`, stopping inside a held worktree runs the executable required gates\n first, and while one fails the stop is refused — Claude Code keeps working, and what it reads is\n the gate's name and the tail of its output, not a hint. Bounded: at most `max_blocks` refusals\n per session (three by default), then the stop goes through and a `gate_failed` incident opens.\n Every refusal is a `gate.blocked` line in the session log. Default stays `record`, so nothing\n changes until a repo opts in; subagents are never refused; the wait is capped by `stop_timeout`.\n This is Claude Code's own `Stop` → `block` hook contract, verified against the current reference.\n\n- **The permission card, for interactive sessions too.** Until now only spawned runs got the\n dashboard's Allow / Deny card; a session in your terminal asked there and nowhere else. Claude\n Code's `PermissionRequest` hook fires before the terminal dialog and holds it while the hook\n runs, so the daemon now parks the prompt as a card on the session page (the session shows as\n *asking* on Fleet, and a desktop notification fires) and waits `[broker] interactive_wait`\n seconds — 30 by default — for an answer. Allow or deny on the card and the terminal never asks;\n press *Answer in terminal* or let the countdown run out and the dialog appears there unchanged.\n The card names the rule that flagged the call when one did. Nothing waits unless a dashboard is\n open **and visible** — a background tab is not someone watching, and a terminal prompt is never\n held for a card nobody can see. `swarm install` registers the new hook; run it once after\n updating.\n\n- **Rules that rewrite a call instead of refusing it, and your own rules.** Two new rules have a\n third answer besides *ask* and *deny*: `no_verify = \"rewrite\"` drops `--no-verify` /\n `--no-gpg-sign` from any git command and lets it run; `dry_run_first = \"rewrite\"` turns the\n first `terraform apply`, `kubectl delete` or `helm uninstall` in a session into its dry-run form\n and allows the second. The agent is told what changed and why; the Incidents view shows the\n call that was asked for beside the one that ran, marked *Rewritten*, so rule effectiveness\n scores it like any rule. Both ship `off`. `[[rules.custom]]` is the promised DSL: `name`,\n `match` (a regex over the command), `action` (`ask | deny | rewrite | off`), `replace`,\n `reason` — evaluated in config order after the coordination rules, so a deny is never softened.\n Codify writes it, `swarm rules dryrun` replays history under it, spawned runs get the rewrite\n through the permission broker, and interactive sessions through Claude Code's `updatedInput`.\n A rewrite only ever touches a command it can reason about whole — one invocation, no `&&`, no\n pipe, no redirection, no substitution — and the rewritten command is put back through the rules\n before it runs, so a rewrite can neither smuggle a second command past the guards nor approve\n the call outright. Flags are stripped outside quotes only, so a commit message that mentions\n `--no-verify` is left alone.\n\n- **A heads-up the moment two live sessions edit the same file.** The collision graph could show\n it after the fact; nobody told the agent. Now, right after an edit lands, a session whose file\n another live session edited within the last 15 minutes reads who that was — task, branch, how\n long ago — and the suggestion to look at that diff before going further. Context on\n `PostToolUse`, never a refusal; once per pair of sessions per file per window; each one a\n `collision.warned` line in the session log. `[rules] collision_context = false` turns it off,\n `collision_window` sets the minutes.\n\n- **Messages wake an idle session.** A message from another agent or from you (`swarm msg send`,\n `swarm_send`) and an answer to a session's question used to arrive on that session's next tool\n call, which for a session sitting idle at its prompt meant never. `swarm install` now arms a\n background hook after every turn — Claude Code's `asyncRewake` — that the daemon holds open and\n releases with the text the moment something is deliverable; Claude Code shows it as a system\n reminder and the session picks it up. Mid-turn delivery is unchanged. `[messages] wake = false`\n turns it off; the session log shows *woke*. Run `swarm install` once after updating.\n\n- **Codify is back, and it writes.** The React port had lost the Incidents feed's Codify action;\n it returns as a card with the CLAUDE.md lesson and the `.swarm.toml` rule, copy buttons, and a\n new **Apply → PR** button. Apply never touches your main checkout: the daemon creates a\n task-less worktree on `swarm/codify-<n>`, merges the lesson under a *Lessons from Swarm*\n heading (once) and the rule into its section (ports unioned, custom rules appended, everything\n else byte for byte), commits, pushes and opens the PR prefilled with the incident, then removes\n the worktree. Without a forge remote the branch stays and the card shows the push line.\n `[codify] target` picks the default file(s).\n\n- **A status line inside Claude Code.** `swarm install --statusline` sets Claude Code's `statusLine`\n to `swarm statusline`, and the footer of every session reads, for example,\n `Opus · ctx 42% · $1.23 · 5h 24% · 7d 41% │ M12.2 41m · 2 incidents · waiting on you`. The left\n half is what Claude Code hands the command after each assistant message: model, context used,\n session cost and, for Pro / Max subscribers, the 5-hour and 7-day plan windows. The right half is\n what only the daemon knows: the task whose worktree you are in and its lease, the budget when it\n is not fine, un-acked incidents, a question of yours nobody has answered, an inbox. Same contract\n as the hooks — 400 ms, fails open to the left half, never starts the daemon. It is opt-in because\n a custom status line replaces Claude Code's footer hints, and it never replaces a status line you\n already set; `swarm uninstall` removes exactly ours. `swarm doctor` says which is the case.\n\n- **Plan quota windows on Spend and in the status line.** Pro / Max plans are metered by a 5-hour\n and a 7-day window, not by dollars, and Claude Code reports both to its status line after every\n message. The daemon keeps those samples (a row when a window moves, never one per message) and\n Spend shows a tile per window: percent used, when it resets, the burn rate, and at the current\n pace whether the limit lands before the reset. The status line names the window that runs out\n first: `5h limit in 2h`. `[budget] window_warn_at = 0.8` opens a `budget` incident once per\n window per reset period, and again at 100%; `false` turns the warning off. Swarm has no\n account and never asks Anthropic for usage — the status line is the only source, so the tiles\n appear only while some session on a plan keeps reporting (API-key sessions never do).\n\n- **Host or join a team from the app.** Running the team daemon meant a clone, environment\n variables and two CLI commands; joining meant a third. The dashboard has a **Team** panel now:\n **Host a team** mints a shared secret, writes `~/.swarm/team.toml`, starts `swarm-teamd`, points\n this machine at it, registers, and shows an **invite link**; a teammate pastes that link into\n **Join a team** and is done. The panel also shows forwarding lag, this machine's identity,\n whether its credentials registered, and a Leave button (with *stop hosting* when this machine is\n the host — stopped by pid, never by pattern). `swarm-teamd setup` writes the same file from a\n terminal, and the environment still overrides it, so existing deployments are unaffected. Two\n bugs found on the way: a wrong shared secret used to look like a successful join and then fail\n to forward forever, and a config file the daemon wrote itself stayed invisible to it for 30\n seconds.\n\n## Changed\n\n- **The desktop app always runs its own daemon.** It used to reuse any healthy daemon registered\n in `~/.swarm/daemon.json` — which meant a clone's `bun run dev` daemon, serving whatever\n dashboard bundle happened to be on that disk, was what the packaged app displayed, and\n restarting the app changed nothing because the daemon it borrowed never restarted. Now the app\n asks the registered daemon to stop over a new loopback `POST /v1/shutdown` (the same clean path\n as `swarm stop`: spawned runs stopped by pid, `daemon.json` cleared), starts its bundled daemon,\n and finds it by pid. The daemon keeps its preferred port — `[daemon] port`, else 7777 — and\n falls back to any free port when that is taken, so a blocked 7777 is never an error; and a\n process that merely listens on the port no longer counts as a daemon, since the app now checks\n `/v1/health` rather than a TCP connect. The daemon the app starts also watches the app's pid and stops\n itself when the app is gone — quit, crashed or killed — so no invisible daemon survives to be\n evicted next time.\n\n## Fixed\n\n- **Codex and Gemini sessions stopped updating after the first look.** Both write their session id\n once, in a header line at the top of the file, and the first poll consumed it — so every later\n poll read the new lines, failed to tell which session they belonged to, and threw them away\n without even advancing its place in the file. A Codex session's usage sat frozen at whatever the\n daemon saw the first time it found the log. The daemon now remembers the session it recorded for\n each file and hands it back to the parser, so tailing continues for the life of the session.\n Codex turns are also identified by their own timestamp and usage now instead of a per-poll\n counter, so a re-read of the same event updates that turn rather than counting it twice.\n Thanks @roy-tong for the diagnosis and the fix (#145).\n\n- **Linux has an AppImage again — and auto-updates with it.** The AppImage has been off since\n v0.2.2 because the bundler died every time, and Linux was the one platform the in-app updater\n could not reach. The bundler runs `patchelf` over every executable it finds inside the package,\n which quietly wrecks the daemon: it is compiled by bun, and bun keeps its program glued to the\n end of the file, exactly where `patchelf` writes. The next tool to inspect the wreckage aborted\n the build. Linux builds now carry the daemon compressed instead, which the bundler leaves alone,\n and the app unpacks it once per version into `~/.swarm/bin`. AppImage, `.deb` and `.rpm` all ship\n again; \"Check for Updates…\" installs updates in place for the AppImage and, for a `.deb`/`.rpm`\n copy, now says to update through your package manager instead of failing halfway."},"0.13.2":{"date":"2026-09-04","md":"## Changed\n\n- **The session log shows the newest entries first.** The latest tool call or turn is the top line\n now instead of the one at the end of a scroll. While you are at the top the log stays on the\n newest row as more arrive; scroll down into the history and it keeps the row you are reading\n under your eyes when new rows land above it.\n\n## Fixed\n\n- **The React port's regressions against the vanilla dashboard.** Every view was rendered from the\n retired vanilla dashboard beside the React one and pixel-diffed; ten things had slipped in the\n port. The session sidebar has its cost-per-turn strip, messages, waiting questions and the\n copyable transcript row back; chips are pills again (the global `<button>` reset had stripped\n them); branch and model columns are proportional, so `fable-5-1` fits; empty states draw the idle\n robot; the Board's Map/Table and Cards/Table toggles, the Graphs collisions hint, the Provenance\n \"N untracked\" pill and the Spend column menus are restored. The collapsed sidebar collapses to\n nothing — the 46px rail had been empty since the nav moved to the header.\n- **The range switch shows which range is on.** 14d / 90d on Spend and Stats, 3h…72h on Timeline:\n the stylesheet styled `.seg a`, React renders `<button>`, so the buttons wore the global button\n chrome and the active one looked like the rest.\n- **The project row's `⋯` menu has \"Settings…\" again.** The v0.8.0 drawer was never ported, so\n there was no way to change a project's icon, name, colour or pinned state from the dashboard.\n Emoji tiles, the full emoji grid behind \"…\", an image file downsized to 64px, the swatches and the\n pinned toggle are back, and the daemon's validation error shows in the footer instead of being\n swallowed. The emoji and swatch cells are buttons now, so the keyboard reaches them.\n- **Pinned projects reorder with dnd-kit; the drop lands.** HTML5 drag started but the drop never\n fired inside the desktop app: Tauri installs its own drag-drop handler on the webview, which\n swallows `drop` and `dragend`. Pointer events get through. The held row follows the pointer and\n its neighbours slide out of the way, which the native drag could never animate.\n- **The session page keeps consistent gutters.** The panels were `calc(100vh - 140px)`, a guess\n that drifted whenever the header wrapped or the stdin bar appeared, leaving ~35px at the bottom\n and the header crowding the panels. The page is a flex column now: 16px on top, 24px on the\n other sides, and the log scrolls inside its panel."},"0.13.1":{"date":"2026-09-01","md":"## Changed\n\n- **The dashboard asks the daemon whether anything changed instead of re-downloading the answer.**\n `/v1/state` is ~195 KB and every client asked for all of it every 5 seconds, plus once per event\n nudge, then compared it against the last copy to decide whether to re-render. The response now\n carries an `ETag` and an unchanged poll comes back `304` with no body — no transfer, no parse, and\n the same \"nothing moved, render nothing\" outcome, decided before the bytes are sent.\n- **The dashboard ships one copy of React.** The dropdown island was a second `<script>` with its\n own React bundled in, so the page loaded two runtimes: 737 KB of JavaScript where 549 KB would do.\n The island is imported by the app now — still its own React root, since fancy-menus renders\n outside the app tree — and the three remaining scripts no longer block the parser.\n\n- **The website's Downloads section reads a cached endpoint instead of calling GitHub from every\n visitor's browser.** The page made three cross-origin calls per visit — two to `api.github.com`,\n which is 60 requests/hour per IP unauthenticated, so a visitor who reloaded a few times (or shared\n an office NAT) got a 403 and an empty Downloads section. The release, its assets and both download\n counters now come from `/api/releases`, cached at Vercel's CDN and in the warm function instance,\n which also serves the last good payload if GitHub is rate-limiting or down.\n\n## Fixed\n\n- **A pill badge in a grid cell sat ~7px low and grew its row** (\"built-in\" in the MCP grid). A\n pill is an inline-block of text, so its baseline is its own last line box, not its bottom edge\n the way an SVG's is; the shared vertical offset assumed the latter.\n- **The sidebar's `+` is a menu again, with the folder browser behind it.** The React port had\n replaced \"Browse folders… / Add by path…\" with a bare path box under the heading. Both are back\n and open the same picker: sub-folders listed with `git` badged, `..` to go up, a path box that\n navigates on Enter, and refusals shown in the footer instead of an `alert()`. A typed path that\n does not exist now answers \"no such folder\" — the daemon used to list `~` instead, silently.\n- **Pinned projects drag to reorder again.** The rows lost their drag handlers in the React port;\n the order is previewed in place while dragging and saved to the daemon on drop, as before.\n- **The sidebar is resizable.** Drag its right edge (double-click to reset, arrow keys when it has\n focus); the width persists. It was a fixed 240px, so a long project name was cut to an ellipsis\n with no way to see the rest.\n\n- **A `git -C <path>` command walked straight past every git rule.** The guards matched on `git`\n immediately followed by the subcommand, so any of git's global options in between — `-C`,\n `--git-dir`, `--work-tree`, `-c` — turned `git -C /someone-elses/worktree reset --hard` into a\n command the rules did not recognise, which is precisely the command they exist to catch. They now\n read git's global options before the verb. `pkill --full` and `killall` count as pattern kills too.\n- **A cold Outcomes read could hold a request open indefinitely.** `gh` was run once per project in\n sequence with no timeout, and a second request started a second full fan-out rather than waiting\n for the first: three overlapping requests measured 8 s, 1018 s and 2 s. Projects are now queried in\n parallel, callers share one in-flight run per project, and a CLI that stops answering is killed\n after 20 s. Scratch and deleted temp checkouts are skipped entirely — on a machine with 21 tracked\n repos, 13 of them had no remote and were still costing a subprocess per refresh.\n- **The session list cost the whole ledger to build.** It joined every turn ever recorded before\n applying its 200-row limit, and built sparklines for every session rather than the ones on screen.\n Both queries are now scoped to the page: on a 200,000-turn ledger that is 111 ms → 11 ms.\n- **\"Open on GitHub\" in a pull request's row menu did nothing in the desktop app.** It called\n `window.open`, which the desktop webview discards; it goes through the same path as every other\n external link now.\n\n- **Clicking a project in the sidebar did nothing while a session was open.** Picking a project\n scoped the app but did not leave the session page, so the highlight moved on the left and the\n same session stayed filling the pane — the click read as broken. Choosing a project now leaves\n the session page for the same reason choosing a view does, revealing whichever view was\n underneath, scoped to what you just picked."},"0.13.0":{"date":"2026-08-27","md":"## Changed\n\n- **The dashboard is a React app now.** It used to be one 4,000-line `app.js` that rebuilt the page\n on every poll: 1,059 elements thrown away and recreated to update eleven text nodes, five seconds\n apart, which is the blinking you could see. The same poll now mutates nothing at all when nothing\n has changed — the tree only moves where the data did. Your sort order, column widths and open\n menus survive a refresh, because they are no longer destroyed by one.\n\n Underneath: one poll for the whole app instead of 21 hand-written change flags, `zustand` for\n state, typed routes over the same `@swarm/core` types the daemon answers with, and views split\n into components small enough to read. Long tables paginate, so the Board no longer builds three\n thousand DOM nodes to show you fifty rows.\n\n Nothing about the interface moved. Every view, every column, every action is where it was.\n\n- Long grids paginate at 25 rows by default, with the page size remembered per table.\n\n- The ⌘K palette, theme switching, UI zoom, desktop notifications, What's New, the update and star\n nudges and the feedback link are back. All of them lived in `app.js`; the rewrite ported every\n screen and none of the chrome around them.\n\n## Fixed\n\n- A grid's last column had a resize handle hanging four pixels past the table, which put a\n horizontal scrollbar under every wide view.\n\n- **The desktop app lost its window chrome.** On macOS Swarm runs with an overlay title bar, and\n the page is what pads the header clear of the traffic lights and what makes the header draggable.\n Both were wired in the old `app.js` and did not survive the rewrite: the mark sat under the\n traffic lights and the window could not be moved or double-click-zoomed.\n\n- **The sidebar lost its padding, border and background.** React mounts inside a `#root` div.\n It is `display: contents`, so it disappears from the layout — but not from selectors, and every\n `body > aside` rule quietly stopped matching. `bun run check:classes` now fails on that selector\n shape rather than letting it go quiet again.\n\n- **The Trials view threw on every visit.** `/v1/ab` answers `{ trials: [...] }`, and the view was\n annotated as receiving a bare array — so it walked past its own empty check and called `.filter`\n on an object. Route builders now carry their response type, so `useResource` infers it and an\n annotation that disagrees with the endpoint is a compile error instead of a blank view.\n\n- **A configured backlog could render as no backlog at all.** An external tracker is fetched in the\n background and the first request arrives before it answers, so the daemon returned `{ tasks: [] }`\n and the Board drew no Tasks section — identical to a repo that configures no source. On a repo\n with 300 open issues it stayed that way until something happened to poll it again. Three other\n routes into the same silence are closed with it: a source that *failed* (`gh not installed`,\n `LINEAR_API_KEY not set` — messages written to be acted on, and previously discarded), a markdown\n source naming a file that is not there, and a backlog that really is empty. A configured source\n now always renders, and always says which of those it is.\n\n- **Branch names rendered in the wrong font, and badges sat flush against them.** `.br` — the\n monospace treatment for a machine-readable name — was only ever written as `td.br`, matching when\n the class sat on the table cell. React puts it on a `<span>` inside the cell, and uses it outside\n tables too, so the rule matched nothing: every branch, rule, tool and task ref quietly rendered as\n 14px body sans, wide enough to crowd the badge beside it.\n\n- **Copy silently did nothing in the desktop app again.** The webview has no async clipboard API;\n the fallback added in 0.12.1 was not carried across. It is now in one helper that everything uses."},"0.12.1":{"date":"2026-08-26","md":"**If you are on 0.12.0, update.** Its Board view was blank — see below.\n\nThe rest of this release is about hygiene finally doing something. It has been reporting 50 GB held across 32 worktrees on my machine and offering nothing to reclaim, which reads as broken rather than as cautious. Two reasons, both fixed.\n\n## Fixed\n\n- **The Board was dead in 0.12.0.** A function added for the new resource graph had the same name as the one that renders the Board's runtime resources; the later declaration won, so the view threw before drawing anything and took the worktree list with it. Renamed, and there is now a check that no two top-level view functions share a name.\n\n- **Copy never worked in the desktop app.** The webview has no async clipboard API and the helper quietly gave up. It falls back now, tells you whether it worked, and when it cannot copy it puts the text on screen selected rather than flashing \"copied\" at you.\n\n- **Error reports in the desktop app were missing the error.** Chrome puts the message at the top of a stack trace and Safari does not, so the one line saying what went wrong was absent — in the app, which is where you would be reading it.\n\n## Added\n\n- **Clearing build output.** 31 GB of the 50 GB my worktrees hold is `node_modules`, Rust `target` and `dist` — things a rebuild recreates. Hygiene measures that per worktree now, shows it as its own column, and offers to clear it.\n\n This is deliberately not the same as removing a worktree: the checkout, the branch and anything uncommitted all survive, so a dirty tree is fine to clear. What it refuses is the main checkout, a worktree somebody holds a claim on, and one with a live session in it — those mean a build is probably running. A nested worktree's output belongs to that worktree and is offered there, not swept up by whatever contains it.\n\n## Changed\n\n- **A branch merged by squash now reads as merged.** Swarm decided this with `merge-base --is-ancestor`, which is the right test for a merge commit and useless for anything else: a squash rewrites the branch's commits into one new commit, so the originals never become ancestors of the base and the branch stays \"Clean\" forever. It also asks `git cherry` now, which compares by patch instead — if every change on the branch is already in the base, however it got there, it is merged.\n\n- **The worktree table says which project each one is in.** With thirty-odd worktrees across a dozen repos, a branch name on its own does not tell you where it lives.\n\n- **A worktree counts as stale after two days, not seven.** Seven never elapsed: on a machine where work lands daily you reuse or notice a merged worktree long before a week of silence. Nothing about *safety* changed — the ledger still refuses to offer anything with uncommitted or unpushed work, whatever its age. On my machine the offer went from nothing at all to 8 worktrees and 1.7 GB.\n\n- Task cards no longer carry a thick coloured stripe down their left edge; state colours the whole outline. Heading badges match the ones in tables. Inline icons sit on the centre of the text beside them rather than a pixel and a half below it."},"0.12.0":{"date":"2026-08-26","md":"The Observatory is finished. Swarm has spent this milestone answering questions about your fleet that used to need a person reading logs; this release adds the last five and, just as importantly, makes the dashboard admit when something has gone wrong instead of quietly showing you a stale screen.\n\nTwo things I want to be straight about, because both are cases where the plan was wrong and the code says so.\n\nThe roadmap wanted repeated tool cycles to feed the stuck detector. They should not. `Read → Edit → Read → Edit` is the single most ordinary thing an agent does, and flagging it would have fired on healthy work all day long. A cycle only counts when the calls inside it are *failing*, which is the same bar the stuck detector already sets for a plain repeat.\n\nAnd it wanted deadlock detection on held resources. A deadlock cannot happen here: claims fail closed, so a second claimer is refused rather than queued and nobody ever blocks. What can happen is contention — two agents each wanting what the other holds — and that needed a fact nobody was recording, so Swarm now records it.\n\n## Added\n\n- **Tool transitions.** What an agent reaches for after what, as a weighted matrix. Not a graph drawing: the transition graph is dense and cyclic, so a layered drawing turns into a hairball where nearly every edge doubles back. A matrix has no crossings, puts a tool following itself on the diagonal, and shows a lopsided pair of cells when `A → B` happens far more than `B → A`. On my machine: 10,198 transitions across 62 sessions, and `Bash → Bash` 6,881 of them.\n\n- **File heat.** Where the fleet's attention actually goes — hottest files, hottest directories, and how much of every touch was a file being read again. Mine says **55%**, and 222 files were opened once and never returned to. It also looks for files several sessions keep re-reading and hardly ever edit, because those are the ones whose conclusion belongs in `CLAUDE.md` instead of being re-derived in every window. It found none, correctly: everything multiple sessions read here is also something they were editing.\n\n- **Resource holding.** Claims, ports, leases and processes on one picture with whoever holds them, and a resource is orphaned when the *session* that took it ended — not when its owner looks idle, because an owner string outlives the run it belonged to. Refusals are now recorded, so the view can also show contention rings.\n\n- **Security audit.** Hosts your agents reached for, packages they installed, and credential files they opened. Observation only — nothing here denies anything, and it reads what was *requested*, so a command one of your rules already blocked still shows up. It is a lint and not a sandbox, and the view says so: an obfuscated command will not match, and a comment mentioning `.env` will.\n\n- **Rule effectiveness.** Whether a rule is teaching anyone anything. Firing once is a rule working; firing forty times on the same shaped command is friction, and either the habit needs changing or the rule does. Incidents are clustered by the shape of what they fired on, so that difference is visible at a glance. Swarm now also records when your rule set changes, so \"before this rule, after this rule\" becomes answerable from here on — it was not answerable before, because nothing knew when a rule landed.\n\n- **An error boundary.** The dashboard is one long-lived page and it had no way to say it had broken: an exception left the previous screen up looking current, and a failed poll was swallowed. Now a view that throws shows the error with its stack, and there is a copyable report and a prefilled issue link. The report is the version, the view, the error and the last few failed requests — no session contents, no paths, no titles — and nothing leaves your machine unless you send it.\n\n## Fixed\n\n- **A 404 on a Swarm route is now named for what it usually is.** If the dashboard was updated and the daemon has not restarted, the page says exactly that and offers the restart, rather than spinning on a view that will never load.\n- **Light mode was olive.** The accent was a yellow-green darkened in sRGB, which drains the colour and leaves khaki — every bar, sparkline and heatmap cell in the theme was that colour. The greens are derived in OKLCH now, which holds the hue as the lightness comes down, and text and chart fills are separate values because they answer to different contrast rules.\n- Badges had two pixels of vertical padding against eight horizontal, and read as squashed.\n- The project sidebar's icon sat a pixel above everything else in its row.\n- Several tables stretched three or four columns across the page with their numbers a screen away from what they described; those are lists now."},"0.11.3":{"date":"2026-08-26","md":"A proper robot.\n\n## Changed\n- **The robot is redrawn.** The old one was 23×28 in three tones and looked it — a blocky approximation of the thing it was meant to be. This one is 73×87 in seven, with a bevelled head, ear pods, a chest screen, a vent grille, segmented arms and claws. It is not hand-copied: the grid is recovered from the reference art itself, and its colours are re-derived as a straight scale of `#a3e635`, so every tone is the brand hue and the whole drawing recolours from one value.\n- **Icons and the site's marks are the head alone.** The whole robot in a 512px tile is clutter — arms, claws, a vent grille and four buttons, none of which survives being an icon. The head is the top of the same drawing, and below about 128px an even simpler head takes over, because 47 columns of bevel and eye socket rendered a pixel each is noise rather than a robot.\n\n## Fixed\n- **Small icons were mushy.** They were scaled by canvas ÷ grid, which at 32px is 1.6 pixels a cell — so cells landed on two pixels or one depending where they fell, and the eyes came out different sizes. Cells are now always a whole number of pixels, at every size Swarm ships.\n- **The macOS icon ignored Apple's icon grid.** It bled to the edge of its canvas, which makes an icon sit visibly larger in the Dock than everything beside it. The rounded square now takes about 80% of the canvas, as Apple specifies — except below 128px, where there are not enough pixels to spend on a margin.\n- **The robot had holes in it.** Transparent cells showed through where the drawing should be solid: a blank row between the head and the neck, a hole in the neck itself, and hairline slits detaching each arm at the shoulder. The gaps that are meant to be there — the claw notch, between the legs, between the antennae — are untouched.\n- **`trimArt` deleted blank rows wherever they fell**, not only at the edges, so the drawing's one interior blank row was being dropped from every generated icon and the figure came out a cell short."},"0.11.2":{"date":"2026-08-26","md":"Two things 0.11.1 said it did and didn't.\n\n## Fixed\n- **The desktop app icon.** 0.11.1 put the robot in the dashboard, on the site and in the favicons, then left the macOS and Windows app icons as the old mark — so the thing in your Dock was the one place that still didn't match. All seventeen are generated from the same drawing now, by `tools/icons.ts`, read straight off the pixel grid in `core` with no image editor anywhere in the loop. That covers `.icns`, `.ico` and every Windows store size, and the iOS and Android sets if you have them checked out.\n- **The hero animation.** What shipped in 0.11.1 was pixel columns marching down in lockstep over a static grid that never moved at all. It's a proper rain now — streams of glyphs, each falling at its own speed, bright leading character, trail dying out behind it. The columns sit at three depths, where size, pitch, speed and brightness all move together: the flat uniform grid is the part that reads as an impression of the film, so that is the part that had to go. One canvas and one animation-frame loop: 0.27ms a frame, paused when the hero scrolls off screen or the tab goes to the background, and never started at all under `prefers-reduced-motion`."},"0.11.1":{"date":"2026-08-26","md":"Mostly the look of the thing, plus one page that was genuinely slow.\n\n## Added\n- **The robot is the logo.** It replaces the abstract pixel glyph in the dashboard header, on the site, and in the favicons. The header mark also follows your theme now — it was hardcoded to one green and ignored light mode entirely.\n- **Matrix rain in the hero.** Pixel columns falling at their own speeds, brightest at the leading edge, fading out of the middle so they never sit behind anything you have to read.\n\n## Fixed\n- **Provenance took nine seconds to open.** It was asking your forge about every project before drawing anything — on a machine with 21 of them that is 21 round trips, and whoever opened the page first every ten minutes paid for all of them. It now draws from what it already knows and lets the pull request state catch up a moment later, which took it from **8.9s to 0.7s**. It also pages 50 rows at a time instead of sending all 116.\n- **The landing page read like documentation.** Config snippets, exit codes and words like \"fail-closed\" and \"orphaned\" in the middle of sentences meant to sell. Rewritten to say what the thing does for you; the syntax lives in the docs, where someone is looking for it."},"0.11.0":{"date":"2026-08-26","md":"Swarm could already tell you what your agents did. This release is about what it cost you — in money, in waiting around, in context burned re-reading the same file, and in work that shipped with nothing linking it back to a ticket.\n\nI pointed the new provenance view at my own machine and found 22 branches that had landed with no task behind them. One was a merged PR carrying $255 and 16 agent sessions. I had no idea it existed.\n\n## Added\n\n- **Waiting on you.** Agents spend real time stuck waiting for a human — a permission prompt, a question, a notification — and none of it was measured. Now it is. Fleet shows a *Waiting 12m* badge saying what's blocking, and Stats breaks it down by kind. If you close your laptop on a pending prompt, the clock stops when the session ended, not days later when you open it again.\n\n- **Where your context goes.** A breakdown of what actually fills the window, by tool. The waste number is re-reads: read a file once and that's work, read it ten times and nine copies are just the cost of forgetting. One of my sessions had spent 11% of its window re-reading the same diff.\n\n- **MCP server health.** Which servers are slow, which fail, and how long your agents sat waiting on each. Timing is measured between the hooks either side of a call, so a call stuck behind a permission prompt carries that wait too — which is why the view leads with p50 and p95 rather than the worst case.\n\n- **Gate flakiness.** A gate is flaky when it gives *both* answers about the *same* task. Failing one task and passing another isn't flaky, that's the gate working. Gates now record how long they took, and old runs get their duration recovered from the text they used to hide it in.\n\n- **Machine hygiene.** What your fleet left lying around: processes still holding a port after their session ended, dead entries in the registry, worktrees that merged days ago and still take up disk. It only offers to remove something the ledger itself would agree to remove, so nothing with uncommitted or unpushed work is ever on the list.\n\n- **Session lineage.** Who spawned whom, who messaged whom, who picked up whose task — as a graph. When one session has 37 subagents they collapse into a single pill you can click open, because 37 lines fanning across the screen is a mess, not a picture.\n\n- **Provenance.** Follow any piece of work backwards: ticket, claim, session, branch, pull request, merge. Six dots per row, filled up to the point the trail goes cold. It reads the chain from both ends, which is how it finds work that shipped with no ticket at all.\n\n- **A/B trials.** Give one task to several models at once and see what each produced: cost, wall time, gates, how much they changed. A model only wins if it finished *and* passed every gate — a cheap wrong answer isn't an answer. Each model works in its own worktree, so nothing about the claim rules had to be relaxed to run the experiment.\n\n- **A robot.** The empty states have a proper one now, and its head is the site's logo and favicon. There's one drawing, shared, with a test that stops the two copies drifting apart.\n\n## Fixed\n\nMostly things that had been quietly wrong for a while:\n\n- Long branch names painted straight over the badge next to them.\n- The Board said 20 incidents while the Guard badge said 57. The Board was wrong — it was counting a 20-row window.\n- The header never told you which of the ten views you were looking at.\n- Opening a menu highlighted the row under your cursor in the same green as the row you were already on, so neither read as current.\n- Big numbers were truncated to things like `134….` in columns that had room for them.\n- The ⋯ button vanished the moment you clicked it, and the row jumped as it went.\n- Pixel art was nearly invisible in light mode — the outline and the face it sat on were the same brightness to within 0.002.\n- Replay resized itself on every step, so Prev/Next slid out from under the cursor mid-click.\n- The transcript gave 204px of every row to a timestamp and a label like `pretooluse`, which repeats on every line and tells you nothing.\n- After upgrading, your browser could quietly keep running the previous version's dashboard — the files were served with no cache headers at all. That's why \"What's New\" could greet a 0.11 upgrade with 0.10's notes: the notes bundle was a stale copy, and the lookup silently fell back to the newest release it happened to have instead of admitting it didn't have yours.\n\n## Notes\n\n- The database upgrades itself on first start. `swarm doctor` will tell you the schema version.\n- This is 13 of the 18 planned observatory features. Security auditing, rule effectiveness and three more graphs are still to come.\n- Two things are deliberately missing rather than guessed: how many tokens your MCP schemas cost, and how much of the window the system prompt takes. Swarm can see tool calls but not schemas or the prompt itself, so it doesn't pretend to know."},"0.10.0":{"date":"2026-08-24","md":"The team release: Swarm outgrows one laptop. A self-hosted team daemon gives a group one view — machines, cluster-wide claims, spend by person — while every laptop stays local-first and keeps working offline. Two more agent brands land (six total), and the dashboard starts reading the fleet's behaviour, not just its numbers: outcomes, stalls, collisions.\n\n## Added\n- **Team daemon** — `swarm-teamd` (`packages/team`), a second, self-hosted service your machines *forward* to: audit events, spend rollups and claims — never transcript text unless a machine opts in, and always after your redaction rules. The local daemon queues everything in an outbox (batched, at-least-once, never on the hook path) and drains it when the team daemon is reachable; `swarm doctor` shows the lag. One SQLite file of state, TLS by reverse proxy, [guide](https://getswarm.vercel.app/docs/11-teams) (M8.3). *Licensing: this one package is source-available ([FSL-1.1-ALv2](https://github.com/ra3orblade/swarm/blob/main/packages/team/LICENSE.md), Apache-2.0 after two years); everything else is and stays Apache-2.0 — one machine free, a second person is the product (OQ-15).*\n- **`swarm login`** — OIDC device-code sign-in against the team daemon (which is the OAuth client — your laptop never holds an OIDC credential; a static shared token or open mode for labs). First user becomes admin; roles are viewer / developer / admin. Login registers the machine (its token is bound to you) and pins the org's policy signing key. `swarm install --config-url <url>` is the one-flag fleet onboarding (M8.3c/f).\n- **Cluster-wide claims** — a claim taken on one machine registers upstream; a second machine claiming the same task is refused with the holder's name (`alice@her-laptop`), and if the cluster says someone else holds it, the local claim is revoked — the worktree is never touched. Offline degrades to local-only, fail-closed as ever (M8.3d).\n- **Team dashboard** — served by the team daemon: machines (live / quiet), active cluster claims, spend today / by project / **by user** / by machine / by day, and the forwarded activity feed, live over SSE (M8.3e).\n- **Signed org policy** — an admin posts the org's `policy.toml` once; every machine fetches it, verifies the ed25519 signature against the key pinned at login, and installs it as the org layer — locked rules included. A tampered policy is reported and never installed (M8.3f, closes the OQ-3 signing deferral).\n- **Team budgets + chargeback** — org / user / project ceilings (daily + monthly) set on the team daemon and enforced with the same semantics as the local `[budget]`: warn incident, `ask` on spending tools, or stop spawned runs. Monthly export by user, machine, model or **task — your ticket ids** when the task source is GitHub Issues or Linear: `GET /t1/spend/export?month=…&by=task&format=csv` (M8.4).\n- **Model allow-list** — `[models] allow = [\"claude-*\"]` (org-lockable): spawned runs and dispatch refuse a disallowed model; an interactive session on one opens a single incident — observed, never interrupted (M8.4).\n- **Aider + opencode adapters** — six agents now show up with sessions, turns, tokens and cost: Claude Code, Codex, Grok, Gemini CLI, **Aider** (its own `.aider.chat.history.md`, one file holding many sessions) and **opencode** (its SQLite database, read-only). Both report their exact spend themselves, so their turns carry it verbatim and repricing never touches them (M5.4; Cline deferred).\n- **Outcomes** — did the agent's work survive? Branches join sessions → PR → merged / reverted, with per-model and per-agent scorecards: merge rate, median session-start→merge, $ per merge. New **Outcomes** view under Insight (M9.2).\n- **Stuck badge** — the daemon watches live sessions' recent tool calls for repeat-and-failing loops and all-failing streaks (conservative: `git status` polling never counts) and marks the session **Stuck** on Fleet with a desktop notification. A heuristic; nothing is interrupted (M9.3).\n- **Collision graph** — a live bipartite graph of running sessions × the files they touch; a file two sessions hold with at least one writer glows red as a merge conflict waiting to happen. New **Graphs** view with a contested-count badge (M9.12).\n- **Sidebar navigation + ⌘K** — the flat header links became a grouped sidebar nav (Observe / Work / Insight / Guard) that collapses to an icon rail, and ⌘K opens a palette over every view, project and session, falling through to Search (M9.1).\n- **Ops** — `swarm backup` (a consistent `VACUUM INTO` snapshot of `~/.swarm`, zero downtime) and `swarm restore`; `swarm doctor --migrate`; Prometheus metrics at the team daemon's `/t1/metrics`; `[notify] webhook` POSTs every incident as Slack-compatible JSON (M8.5).\n\n## Fixed\n- Replay, *Resume where it died* and the dry-run *Re-run* buttons were unreachable — dead click targets on the session page.\n- The user guide caught up: a Teams page, the M8 commands, all six agents, and the `SWARM_GUARD=off` description (org-locked rules stay enforced — true since 0.8, documented wrong until now).\n\n## Notes\n- The privacy posture is unchanged and now stated precisely: with no `[team] url` and no `[notify] webhook` configured, nothing about your sessions leaves the machine — the [privacy page](https://getswarm.vercel.app/docs/10-privacy-and-faq) lists the five opt-in egress paths.\n- The team daemon package is not yet on npm; run it from a clone (`bun packages/team/src/bin.ts`). Pricing for the paid tier is still open."},"0.9.0":{"date":"2026-08-24","md":"The crew release: the agents on your machine stop being strangers. They message each other and you, follow a declared workflow instead of a hopeful prompt, and every major CLI brand now shows up — Claude, Codex, Gemini, Grok. Plus the first-run and update experience a launch deserves.\n\n## Added\n- **Agent messaging** — `swarm_send(to, text)` reaches another session (id or unique prefix), whoever holds a task, or `\"lead\"` (your interactive session in the project). Delivery: on the recipient's next tool call as injected context, immediately over stdin to a spawned run, or pulled with `swarm_inbox` (which now returns answers *and* messages) — exactly once. A **messages** thread with compose box on every session page; `swarm msg send|ls` (M7.6, OQ-12 decided).\n- **Workflows** — `[[workflows]] name = \"ship\" steps = [\"implement\", \"gate:tests\", \"gate:review\", \"pr\"]` in `.swarm.toml`; `swarm workflow ship <task>` and the daemon advances it: run steps spawn an agent in the task's worktree (told what the workflow will do itself), gate steps execute — only a pass advances — and `pr` pushes and opens the pull request from the ledger. A failed step stops with a `workflow_failed` incident; a daemon restart marks in-flight workflows stopped, honestly. **Workflows** on the Board with per-step chips (M7.8).\n- **Gemini CLI adapter** — `~/.gemini` chat recordings are discovered and priced like every other agent: sessions, turns, tokens, cost, sparkline, Timeline, Spend. Schema from upstream source; first real-session validation pending (M5.4).\n- **Timeline that shows the work** — bars are now a faint base with a tick per turn: bursts and idle stretches are visible instead of painted over. A thin claims lane per project shows lease spans (held / expired / orphaned). Recent gates carries a per-gate pass/fail history strip; every pinned project gets a 14-day spend sparkline in the sidebar (M5.7).\n- **`swarm demo`** — a seeded demo dashboard on its own home and port: four agent brands, a live lease, an orphaned claim, gate history, incidents, a question, a message, a workflow mid-flight. Tailers are off in demo mode, so it never ingests your real logs — and your real data is never touched (delete `~/.swarm/demo` to reset).\n- **Update that actually updates** — after an upgrade the dashboard notices the newer version on disk and offers a one-click daemon restart (the daemon re-execs into the new build and the page reloads). Long-lived tabs re-check every 5 minutes.\n- **First-run onboarding** — an empty Fleet now explains the three steps (hook in — with a live *not installed* badge —, open any agent session, watch it appear) instead of showing a blank table.\n\n## Fixed\n- External links in the desktop app (PR titles, Documentation, feedback, the just-opened PR) open in the browser — the webview silently swallowed them before.\n- Section-header actions (New worktree, Collect stale, timeline ranges) are proper small buttons instead of shouting uppercase with off-baseline icons.\n- The timeline claims lane no longer collides with the kanban's styles; a test file that broke lint on main is formatted.\n\n## Notes\n- Windows builds are produced by CI as before but this release had no human Windows smoke test — reports welcome."},"0.8.0":{"date":"2026-08-23","md":"The trust release: Swarm becomes something a team and a security reviewer can rely on, without giving up local-first. An org can pin the rules that matter and they hold even when the daemon is down; every record says who did it; the daemon has a credential; what is stored is exportable and redactable; a second agent can be the reviewer. And the dashboard stopped looking like a list of tables.\n\n## Added\n- **Org policy layer** — a third config file, `~/.swarm/policy.toml` (or `$SWARM_POLICY`), sits under global and repo config and may declare `locked = [\"rules.destructive_git\", \"rules.protected\", …]`: dotted keys or whole subtrees the layers below cannot change. A locked key keeps the policy's value, every attempt to override it is a `policy` incident, and `swarm doctor` shows which file set each value and who tried to change it. `GET /v1/policy` exposes provenance (M8.1).\n- **Tamper detection** — on every session start the daemon checks that all ten hook entries are still in `~/.claude/settings.json` with a sane timeout, that no lower config layer fights a locked key, and that `SWARM_GUARD=off` isn't set while the policy locks rules (it is then ignored). Each finding opens a `policy` incident once; `swarm doctor` prints the same (M8.1b).\n- **Fail-closed for locked rules** — while the policy locks any rule the daemon keeps `~/.swarm/policy.cache.json` (locked modes + a snapshot of live sessions and held worktrees, integrity-hashed). If the daemon is unreachable on a tool call, the hook shim enforces exactly those rules from the cache; everything else still fails open (M8.1c, OQ-3 resolved).\n- **Who did it** — every ledger record and every event carries an `actor` (`human` / `agent` / `run` / `daemon` + id); existing rows were back-filled from the owner strings clients always sent. Schema changes now go through versioned migrations (`/v1/health` and `doctor` report the schema version) (M8.2a).\n- **Daemon token** — the daemon creates `~/.swarm/token` on first start; the CLI, MCP server, hook shim and `swarm ui` send it. Local callers may still omit it by default; `[daemon] auth = \"required\"` makes every call carry it, a wrong token is always refused, and anything that isn't loopback always needs it (M8.2b).\n- **Audit log + export** — the ledger-changing subset of events (claims, worktrees, PRs, questions, dispatch, resources, processes, gates, handoffs, permissions, incidents, run results, session start/end), with actor: `swarm audit export [--since 30d] [-p] [--type …] [--format jsonl|csv|json]` and `GET /v1/audit`. Retention is split — `[events] retain_days = 30` for chatter, `[audit] retain_days = 0` (forever) for audit rows (M8.2c).\n- **Privacy on ingest** — `[privacy] store_prompts = false` keeps the event but not the prompt text, `store_reasoning = false` keeps token counts but not assistant text, `redact = [\"ACME-[0-9]+\"]` scrubs stored strings; API-key-looking tokens and `Bearer …` credentials are always redacted. Global-only keys an org can lock (M8.2c).\n- **Review as a gate** — `[gates.review] builtin = \"review\"` (optional `model`, `timeout`) spawns a read-only `claude -p` over the worktree's diff with a fixed rubric. The verdict is derived from the findings (any blocker/major fails, whatever the reviewer claims), findings are the evidence, and a reviewer that times out or won't answer in JSON is a fail with that reason. Same registry, logs, incidents and triggers as executed gates (M7.9).\n- **Project settings** — sidebar menu → **Settings…**: name, icon (any emoji — a quick row, a browse-all grid, or the OS picker — or an image file, downsized to a square), a color slot, pinned. The glyph replaces the folder icon everywhere the project appears.\n- **Board that is a board** — a KPI strip (live / held / worktrees / ready / incidents), tasks as a kanban (Ready · In progress · Blocked · Done), and a worktree map of tiles grouped by project and colored by live / dirty / unpushed / merged. Tasks and Worktrees keep a Cards | Table toggle.\n- **Row menus** — every row on the Board, PRs and Incidents has one menu (hover kebab, right-click, or Enter) carrying its actions — open, diff, PR, run, claim, gates, release, stop, ack, codify, merge, copy — instead of inline links; destructive ones last and confirmed. Menus are wider and labels never ellipsize.\n\n## Changed\n- `doctor` reports the daemon and schema version, per-event hook coverage, and the policy file with its locked keys.\n- `/v1/health` reports `schema` and `auth`.\n\n## Fixed\n- Scratch repositories under the OS temp dir (test fixtures, spawned-run clones) no longer appear as projects in the sidebar.\n- Fleet's `now` column had no room; ended sessions show their last assistant line instead of \"session ended\".\n- Incidents show the command's gist (the leading `cd … &&` stripped) and `(removed)` instead of a raw id for a deleted project; Spend's attribution tables lost their empty first column.\n- Emoji tiles and the icon preview no longer clip in the desktop app's WebKit view."},"0.7.0":{"date":"2026-08-23","md":"The orchestrate release: Swarm runs a task end to end on its own and you stay in control. New worktrees start warm, gates execute instead of being vouched for, `swarm dispatch` hands ready tasks to autonomous runs whose outcome is derived from the ledger, an agent that needs a human decision can ask for one, and a budget keeps the bill in bounds.\n\n## Added\n- **Budgets** — `[budget] daily = 25` / `weekly = 100` in `.swarm.toml` puts a spend ceiling on a repo, judged from the same transcript-priced numbers as the Spend view. At `warn_at` (80%) a `budget` incident opens; past 100% `on_exceed` decides: `\"warn\"`, `\"ask\"` (every Bash / Edit / Write in the repo asks first), or `\"stop\"` (spawned runs stopped, dispatch queue cleared). One incident per level per day; a budget tile on the Spend view.\n- **Run profiles** — `swarm run --profile no-edits|read-only` (and the Run / Dispatch drawers, `[dispatch] profile`) narrow what a spawned agent may do: `no-edits` keeps the shell but not the file tools, `read-only` keeps only read and search.\n- **`swarm_context`** — an agent can re-read what Swarm told it at session start, current as of now (holds, lease left, handoff, gates, resources, rule modes), plus answers to its questions. `GET /v1/context`. And `swarm install` now registers the same MCP server with **Codex CLI** and **Gemini CLI** when they're installed, so they get the `swarm_*` tools too (M7.10).\n- **Ask the human** — an agent that hits a decision only a person can make calls `swarm_ask` (with optional suggested answers). The question shows on the session page under **waiting on you** with the options as buttons, the session gets an **Asking** badge on Fleet, and a desktop notification fires. Answer there, or `swarm answer <id> <text>`; `swarm questions` lists what's open. The answer reaches the agent by itself — stdin for a spawned run, `[swarm]` context on the next tool call for an interactive session, `swarm_inbox` on demand — and a session starting later in the same task's worktree is told about open questions and answers that never arrived (M7.7).\n- **Dispatch** — `swarm dispatch --ready` (or pick tasks; the **Dispatch** chip on the Board's Tasks; `swarm_dispatch` from a lead agent) hands ready tasks to autonomous runs: each gets its own claim and worktree and a `claude -p` run told to work there, run the gates, hand off and open the PR; `[dispatch] max_parallel` (default 2) run at once per repo, the rest queue. When a run ends Swarm derives the outcome from the ledger — executable gates re-run by the daemon, PR looked up on the forge — and reports **done**, **gates-failed**, **no-pr**, **crashed** or **stopped**; anything short of done opens a `dispatch_failed` incident and keeps the claim for you to resume or release. A dispatched run never edits the task list. `swarm dispatch status | clear`, `GET/POST/DELETE /v1/dispatch`, a **Dispatch** section on the Board (M7.5).\n- **Diff and Open PR** — every worktree row on the Board (and a session page whose cwd is a worktree) gets **Diff**: the commits and files it carries beyond the main checkout's branch, uncommitted and untracked changes included, with a coloured unified diff per file. **PR** pushes the branch and opens a pull request (`gh`) or merge request (`glab`) prefilled from the task's title, the latest handoff, the required gates as a checklist and the file list — editable before it goes; refuses uncommitted changes, reuses an open PR for the branch. `swarm wt diff`, `swarm pr open [--dry-run]`, MCP `swarm_pr_open`; a `pr.opened` event on the Timeline (M7.3).\n- **Gates that run themselves** — a gate with a command in `.swarm.toml` (`[gates.tests] cmd = \"bun test\"`, optional `timeout` / `cwd`) is executed rather than vouched for: `swarm gate run <task>` (or `swarm_gate_run` from the agent, or **Gates** on a held task row on the Board) runs every required gate that has a command inside the task's worktree and records the verdict — exit 0 passes, the rubric is the command and how it ended, the evidence is the output tail, the log lives in `~/.swarm/logs`. Runs go through the process registry and are killed at `timeout`. When a session in a held worktree ends, the daemon runs them on its own and writes the verdicts into that session's auto-handoff (`[gates] auto = \"session-end\" | \"stop\" | \"off\"`) (M7.4).\n- **Worktrees without a task** — `swarm wt create <name>` makes a worktree for a spike or a review checkout (under `~/.swarm/worktrees/<project>/`, branch `wt/<name>`, bootstrapped like a claim); `swarm wt` lists every worktree with **drift** against the main checkout's branch (*N behind*, *merged*); `swarm wt open` opens it with `[worktree] open = \"code {path}\"` or the file manager; `swarm wt rm` removes it with the same refusals as `release` (dirty, unpushed, never the main checkout, never a held claim); `swarm wt gc [--apply]` finds worktrees whose branch was merged or whose claim was released and the folder left behind. The Board's Worktrees section gets the drift column, **Open** / **Remove** per row, **New worktree** and **Collect stale** (M7.2).\n- **Warm worktrees** — `.swarm.toml [worktree] copy = [\".env.local\"]` and `setup = \"bun install\"` bootstrap every new worktree: the files are copied from the main checkout as the claim is made and `setup` runs inside the worktree in the background (log in `~/.swarm/logs/<project>/bootstrap-<task>.log`, a `worktree.bootstrapped` event on the Timeline). `swarm run` waits for it before starting the agent; an interactive `swarm claim` prints the log path and returns at once. A failing setup opens a `bootstrap_failed` incident but never takes the claim away. Paths are repo-relative only (M7.1).\n\n## Fixed\n- The Fleet agent badge no longer renders a stray \"…\" after the pill: badge-only cells clip instead of ellipsizing, and the column got a few more pixels."},"0.6.0":{"date":"2026-08-23","md":"The learn release: the data Swarm has been collecting starts paying back. Replay what an agent did, see what each task cost, turn an incident into a rule, resume a session that died, try a rule on history before switching it on, read your backlog from GitHub or Linear, and search everything Swarm remembers.\n\n## Added\n- **Session Replay** — a **Replay** button on any session steps through its tool calls one at a time, showing the full input and output of each (Prev/Next, a slider, ←/→ keys). See exactly what an agent did, in order (M4.1).\n- **Cost by task** — the Spend view attributes cost and tokens to each task (matched to a claim by the session's worktree), plus a **Context budget** table ranking sessions by how much context they re-processed — a signal for agents re-reading the same material. `GET /v1/attribution` (M4.2).\n- **Codify an incident** — the Incidents feed has a **Codify** action that turns an incident into a `.swarm.toml` rule snippet and a CLAUDE.md lesson, both copyable. A rule that keeps firing as `ask` suggests hardening to `deny` (M4.3).\n- **Desktop notifications** — opt-in native notifications (settings menu) for a spawned run waiting on a permission, or a claim orphaned with unfinished work; clicking opens the Allow/Deny card or the Board. Quiet while you're looking at the dashboard (M4.7).\n- **Auto-handoff, and resume where it died** — whenever a session working in a claimed worktree pauses or ends, Swarm derives a handoff from what it did: files edited, the last verification-looking command, the last request, the last thing it said. One `auto:` handoff per session, replaced on every pause, silenced by a handoff left on purpose. An ended session's page gets **Resume where it died**, which spawns a run on the task from that handoff plus the session's last actions; `swarm run resume <session-id>`; `GET/POST /v1/sessions/:id/resume` (M4.4).\n- **Rule dry-run** — **Dry-run rules** on the Incidents view replays a project's recorded tool calls through the rules under modes you pick: what would have been asked or denied, per rule, before you switch anything on. It also flags **flaky signals** — a rule that keeps firing on the same command that is then allowed through anyway. Nothing is recorded. `swarm rules dryrun [--set rule=mode,…]`; `GET /v1/rules/dryrun` (M4.6).\n- **GitHub Issues and Linear as task sources** — `[tasks] source = \"github\"` reads the repo's issues through the logged-in `gh` (optional `labels` filter); `source = \"linear\"` reads Linear through its API with `LINEAR_API_KEY` from the daemon's environment (optional `team`). Both land in the Board's Tasks, `swarm tasks` and `swarm_next_task` like a markdown backlog: closed/completed is done, in-progress is active, *depends on #n* / *blocked by* become dependencies. Read-only; no credential stored (M4.8).\n- **What's New in the app** — the dashboard shows the release notes for the running version: a **What's New** item in the settings menu, in the desktop app's **Swarm** menu, and in the tray. It also opens once on its own the first time you run a new version. Notes are parsed from `CHANGELOG.md` into `release-notes.js` at build time, so they work offline with no repo checkout.\n- The desktop **Check for Updates…** is in the system menu bar (Swarm menu), not only the tray."},"0.5.0":{"date":"2026-08-22","md":"The drive release: Swarm doesn't just watch agents now — it starts them, in a claimed worktree, and brokers what they're allowed to do. Plus the coordination primitives that make an autonomous run safe to leave alone: leases that renew themselves, gates that gate, and a handoff the next session reads on its own.\n\n## Added\n- **`swarm run`** — spawn an agent on a task. `swarm run --task login-form --prompt \"…\"` claims the task and the daemon starts `claude -p` in its worktree with stream-json on both ends. Steer it with `swarm run send`, stop it with `swarm run stop` (stdin closed, then the process registry's pid-based TERM/KILL — never by pattern), list with `swarm run ls`. The session shows in Fleet as ▶ spawned and is ingested like any other; every finished turn is a `run.result` event with cost and turns (M3.1).\n- **Run from the dashboard** — Ready (or held) task rows on the Board get a **Run** action: a drawer with the prompt prefilled from the task, permission mode, model and max turns (⌘⏎ to submit). The spawned session opens with a stdin box to steer it and a **Stop** button (M3.3).\n- **Permission broker** — a `swarm run` agent's tool-permission prompts go through the same rules as your interactive sessions: a rule `deny` auto-denies with the reason, an unflagged tool auto-allows so the agent can make progress, and anything the rules mark `ask` is held and surfaced on the session as an **Allow / Deny** card. No blocking on a terminal you can't see. Uses `--permission-prompt-tool stdio`; `POST /v1/runs/:id/permissions/:reqId` (M3.2).\n- **Leases renew themselves.** A session working inside a claimed worktree extends the lease on any activity (hook or transcript growth) once it is past half-way — no more `swarm renew` in a long session. Expired leases whose worktree still holds uncommitted or unpushed work are marked **Orphaned** within a minute and open an `orphaned_claim` incident; nothing is removed automatically (M1.2).\n- **Handoffs, injected on start** — `swarm handoff <task> --done … --remaining … [--files] [--verify]` (or `swarm_handoff`) records what the last holder leaves; `swarm resume` / `swarm_resume` reads it. The next session that starts inside that task's worktree gets it automatically as `SessionStart` context, along with what it holds and the lease left, gate status, held resources, and the repo's rule modes (M1.3).\n- **Gates** — verification runs recorded against a task: `swarm gate record login-form review pass --rubric \"tests green, error paths read\"` (or `swarm_gate_record`). A run without a rubric is rejected; the latest run per gate decides; failed runs are never deleted and open a `gate_failed` incident. `.swarm.toml [gates] required = [\"review\"]` declares what every task must pass; the Board's Tasks grid shows ✓ / ✗ / — per gate and a **Recent gates** section lists the runs (M2.2).\n- **The MCP tools finally connect.** `swarm_status`, `swarm_claim`, `swarm_next_task`, `swarm_handoff`, `swarm_gate_record`, `swarm_acquire_resource` and the rest are reachable from Claude Code — see the fix below.\n- Dashboard deep links (`?view=board&project=<id>&session=<id>`) and a screenshot carousel with a lightbox on the website; `tools/screens.ts` re-captures the shots with Playwright at 2×.\n\n## Fixed\n- **Swarm's MCP tools were never reachable.** `swarm install` wrote `mcpServers.swarm` into `~/.claude/settings.json`, which Claude Code ignores — user-scope MCP servers live in `~/.claude.json` (what `claude mcp add -s user` edits). Install now registers there (and cleans the stale settings.json entry); `claude mcp list` shows `swarm ✔ Connected`. **Re-run `swarm install` after upgrading.**\n- **The daemon reads global config from where its state lives.** `[rules]` / `[gates]` / `[tasks]` in `~/.swarm/config.toml` are resolved against the daemon's home (`SWARM_HOME`), matching the DB and logs — spawned runs, which execute in a worktree without the repo's `.swarm.toml`, still see machine-wide rules."},"0.4.1":{"date":"2026-08-22","md":"## Fixed\n- **PRs view went dark under the desktop app.** A daemon launched from the Dock gets macOS's bare GUI `PATH`, so Homebrew's `gh` / `glab` were invisible and the forge silently returned nothing. The daemon now also looks in `/opt/homebrew/bin`, `/usr/local/bin`, Linuxbrew and `~/.local/bin`; `swarm doctor` reports forge CLI auth and warns when `glab` relies on a shell-only `GITLAB_TOKEN`.\n- Icons are vertically centred on their text again (`vertical-align: middle` instead of a fixed `-3px` tuned for the old type scale).\n- npm publish moves to **trusted publishing** (OIDC, no `NPM_TOKEN`), the same setup as fancy-menus."},"0.4.0":{"date":"2026-08-22","md":"The enforcement release: rules that watch file writes, not just Bash; a backlog Swarm can read; servers Swarm starts and stops by pid; and an Incidents feed you can clear.\n\n## Added\n- **Rules on file writes** — two new rules evaluated on `Write` / `Edit` / `MultiEdit` / `NotebookEdit` paths (and Bash working directories), not only Bash commands. `no_foreign_worktree` (default `ask`) stops a session from editing inside a worktree another claim holds — *never touch a worktree you don't hold* is now a hook decision, with holding inferred from the session's cwd. `claim_required_to_write` (opt-in) makes a repo's shared checkout read-only without a claim: claim a task, get a worktree, write there. Both per repo as `ask | deny | off`.\n- **Incidents view** — the denied-action feed as its own tab: Open / All, per-rule counts, reason and session per row, **Ack** and **Ack all**; the open count sits in the nav. `GET /v1/incidents?open=1&project=`, `POST /v1/incidents/:seq/ack`, `POST /v1/incidents/ack`, `/v1/state.openIncidents`. The Board keeps a short open-only section.\n- **Task source** — `.swarm.toml` `[tasks] source = \"docs/plan.md\"` points at a markdown file whose `ID | Task | Depends | Status` tables are the backlog (✅ / 🟡 / ⚪, dependencies by task id or milestone prefix). The Board gets a **Tasks** section (Ready / Open / All, *Claim* per row), the CLI `swarm tasks [--ready]`, and agents `swarm_next_task` — the first unclaimed task whose dependencies are done. Swarm's own roadmap is its task source. Markdown only (OQ-5 decided).\n- **`swarm serve` / `swarm proc`** — `swarm serve start --name web -- npm run dev` allocates a free port (ledger + bind probe), runs the command detached with `PORT` set and logs under `~/.swarm/logs/<project>/`, registers pid + start time, and acquires the singleton — so a second `web` fails closed and the port is protected for every other session with no config. `serve ls | stop [name|pid]`, `proc start | ls | stop` for workers without a port. Stop signals registry pids only, verified by start time; nothing is ever killed by pattern. `POST /v1/ports/allocate`, `GET/POST/DELETE /v1/processes`; **Processes** section on the Board with *Stop*.\n- **Star nudge** — once a month at most, never on first open, the dashboard asks for a GitHub star. *Later* snoozes 30 days, *Don't ask again* is final; localStorage only.\n- **Sidebar drag-and-drop** — pinned projects reorder by dragging; the order persists on the daemon (`PUT /v1/projects/order`, `Project.order`).\n- **Desktop app menu** — a real application menu (Swarm / Edit / View / Window): ⌘C/⌘V work, **View › Zoom In / Zoom Out / Actual Size** (`⌘+` `⌘−` `⌘0`) scale the dashboard (persisted), plus Reload and Full Screen.\n\n## Fixed\n- **Shared-tree rules no longer lose sight of a session mid-turn.** `shared_tree` / `destructive_git` keyed on a 2-minute last-seen window fed only by hooks, so a neighbour three minutes into a long turn became invisible — and its uncommitted work unguarded. Transcript growth now counts as activity (the tailer bumps `last_seen_at`), and the liveness window is the daemon's 10-minute idle threshold (`LIVE_WINDOW_MS`). A false positive costs one confirmation; a false negative cost someone's work.\n- `destructive_git` also matches `git stash drop`, `git stash clear` and `git branch -D`.\n- Dashboard type scale is one step larger across the board (base 13 → 14 px; the smallest labels 10 → 11 px) — it had drifted too small, especially in the desktop app.\n- The **PRs** tab icon (and the branch/commit glyphs) were near-invisible at 15 px; they use the pixelarticons *sharp* variants now.\n- The daemon dot stayed red for up to 15 s after load on a healthy connection (the SSE stream sent nothing until its first heartbeat); the stream now flushes immediately.\n- The nav flashed \"Fleet\" before the restored tab was applied; session-detail event kinds (`userpromptsubmit`) no longer overflow into the message column; \"Unpinned · seen, not pinned\" keeps its spacing.\n\n## Changed — docs\n- README and site now say what the code does: Codex CLI and Grok sessions are tailed alongside Claude Code; the requirements and architecture diagram list all three.\n- Rules are described as **guardrails against accidents, not a sandbox** — the guide has a new \"What rules are — and aren't\" section spelling out that a denied Bash command can be routed around (script, heredoc, direct file edit), and that worktree isolation via claims is the real fix. The site's feature cards lead with claims, rules and resources instead of Fleet and Spend.\n\n## Changed — performance\n- **Daemon never spawns `git` on a request.** Worktree status (`git worktree list` + `status`/`rev-list` per worktree, ~0.8 s across a fleet) moves to a 15 s background refresh with async `Bun.spawn`; `/v1/state` serves the cache (612 ms → ~15 ms). Claim/release invalidate it.\n- **Hook round-trips are two indexed statements**, not two `git rev-parse` spawns plus a transcript-directory scan: `cwd → project` is cached 60 s, the inline transcript tail is debounced to once per 2 s per session (the 5 s tailer covers steady state), and subagent directories are re-listed only when their mtime moves.\n- **Events store ~2 KB, not ~10 KB.** `tool_input` is clipped at 2 KB and `tool_response` at 4 KB in `payload` (`{truncated, bytes, preview}`), and the tool I/O is no longer duplicated in `raw`. Existing databases are rewritten once on boot and `VACUUM`ed (96 MB → 27 MB here). Retention: events older than 30 days are pruned daily (incidents kept), `raw` is cleared after 7 days.\n- **Wire shape.** SSE frames, `GET /v1/events` replays and `GET /v1/sessions/:id/events` carry `seq/ts/type/projectId/sessionId/payload{hook,summary,…}` only — no `raw`, no tool I/O (a 5.5 MB session fetch is now ~150 KB). `GET /v1/events/:seq` returns one stored event in full; `?full=1` on the SSE replay does the same. `?since=0` replays the last 200 events, not the table.\n- **Incremental session view** — `GET /v1/sessions/:id/events?after=<seq>&afterTs=<iso>`; the dashboard appends instead of re-fetching 500 events + 500 turns on every hook.\n- **Dashboard render loop** — one `requestAnimationFrame` scheduler, snapshot `seq` short-circuit, paused while the tab is hidden, exponential SSE reconnect backoff; session log merges two sorted lists and caches rendered rows; data-grid memoises persisted layout and uses one `Intl.Collator`; charts memoise the turn strip.\n- SQLite: indexes on `events(type, seq)` and `turns(ts)`, `mmap_size` 256 MB, cached prepared statements (`db.query`), `sessions`/`spend`/`incidents` memoised per write generation; `/v1/spend` is its own endpoint.\n- Background tick: Codex/Grok discovery every 15 s when idle; Grok `summary.json` re-read only on mtime change."},"0.3.0":{"date":"2026-08-22","md":"The coordination release: rules you can configure, runtime resources agents can hold, and the merge queue at the end of the loop.\n\n## Added\n- **Config system** — `~/.swarm/config.toml` (global) deep-merged with an optional `<repo>/.swarm.toml`. Lenient validation: bad config can never take the daemon down. Daemon port preference is `SWARM_PORT` > config > 7777. See `docs/13-config.md`.\n- **Rule engine v2** — every rule is per-repo configurable as `ask | deny | off`: `shared_tree`, `destructive_git`, `pattern_kill`, and the new `protected_ports` (kill/free of a configured port — `lsof | kill`, `fuser -k`, `kill-port` — is asked or denied). `deny` is returned to Claude Code as a real permission denial.\n- **Incidents** — every non-allow decision is recorded (`incident.opened`: rule, action, command, reason), exposed at `GET /v1/incidents`, included in `/v1/state`, and shown on the Board.\n- **Runtime resources (Phase 1)** — named singletons for what agents fight over at runtime (dev servers, databases, ports). Fail-closed acquire: holdings live while their pid runs or their lease hasn't expired; the same owner refreshes; dead holdings reap instead of blocking. Release is fail-closed too (owner required, `--force` overrides). Held ports automatically join the protected-ports rule — acquiring `db` on 5432 guards `lsof -ti:5432 | xargs kill` for every other agent, no config needed. HTTP `GET/POST /v1/resources`, `DELETE /v1/resources/:name`; MCP `swarm_acquire_resource` / `swarm_release_resource` / `swarm_resources`; CLI `swarm res ls|acquire|release`.\n- **PRs view** — one merge queue across GitHub and GitLab. Forge detection from the git remote (ssh/https, GitLab subgroups, self-hosted), polled through the locally-authenticated `gh` / `glab` CLIs with the project root as cwd — no tokens stored, 2-minute per-project cache floor. `GET /v1/prs`, `POST /v1/prs/merge` (squash). Merge is offered only on green, mergeable, non-draft rows, behind a confirm.\n- **Board view** — Claims, Worktrees, Resources, and Incidents move out of Fleet into their own view; Fleet shows sessions only (Live + Earlier). Last view and project selection persist across reloads.\n- **Stats view** — `GET /v1/stats`; activity line, calendar heatmap, and streaks (daily buckets in local time, DST-immune). `swarm stats` on the CLI.\n- **Data-grid everywhere** — Claims, Worktrees, Resources, Incidents, PRs, and all six Spend tables render through the same sortable / resizable / reorderable grid with per-column filters, a column-visibility menu, and persisted layouts. Header ticks and tooltips make the affordances discoverable.\n- **Desktop: Check for Updates…** in the tray menu, wired to the Tauri updater with native dialogs (available / up-to-date / failed) and install-and-restart on accept.\n- **Agent badge** on every Fleet and session row, so mixed-agent fleets are labelled consistently.\n- **Website** — getswarm.vercel.app: OS-detected downloads from the latest GitHub release, the `bunx` one-liner, sharing tags with hero art, and (this release) rendered docs and release notes.\n- **Design tokens** — the dashboard's CSS contains zero raw hex / rgba / font-size / duration values; the system is documented in `docs/12-design-tokens.md` with a drift grep.\n- Swarm now dogfoods its own rules via the repo's `.swarm.toml` (`shared_tree` / `destructive_git` deny, daemon port protected).\n\n## Fixed\n- **Hook resilience** — the PreToolUse hook falls back to the default port when `daemon.json` points at a dead daemon, so a crashed daemon no longer silently disables the guard (this was the gap behind a real `git add -A` collision).\n- Resource liveness: pid 0 was treated as a live process (`kill(0)` addresses the process group), so those holdings never reaped; tracked pids are now `> 0`. `heldPorts()` is one SELECT on the hook path; lazy reap on acquire, sweep on the 5 s tick. Unknown session IDs on acquire no longer mint phantom sessions.\n- Session view is two equal columns again (a bare `aside` selector in the sidebar-collapse CSS captured the session side panel); the log keeps your scroll position across live updates and follows the tail only when pinned to the bottom.\n- A pinned project whose root vanished is merged into the live same-name entry (repo renames produced duplicate sidebar rows); the sidebar `⋯` appears on hover in the count's slot and reserves no space.\n- Desktop: quit actually quits, window close hides (macOS convention) and the dock icon restores it, and the `swarmd` sidecar dies with the app. Dev builds serve the repo's live dashboard instead of a stale staged snapshot.\n- Release pipeline: npm publish is skipped cleanly when `NPM_TOKEN` is absent (since 0.4.1: trusted publishing, no token)."},"0.2.2":{"date":"2026-08-21","md":"## Added\n- Publishable `@ra3orblade/swarm` npm package (bundled bins + dashboard); `bunx @ra3orblade/swarm setup` onboarding.\n- Enterprise data-grid for Fleet with a collapsible sidebar; pixel-art icon set and bespoke empty-state illustrations; folder picker; green chart palette.\n- Desktop: macOS window chrome, animated pixel-logo splash, free-port daemon startup.\n\n## Fixed\n- Release builds bundle every platform target; Linux ships `.deb` + `.rpm` (AppImage disabled until `linuxdeploy` on GitHub runners is debugged)."},"0.0.6":{"date":"2026-08-21","md":"First signed and notarized macOS desktop build; `release.yml` became a three-OS matrix (macOS / Windows / Linux) with a native sidecar per runner."}};