agent-switchboard 2.2.0__tar.gz → 2.2.1__tar.gz

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.
Files changed (263) hide show
  1. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/PKG-INFO +1 -1
  2. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/adaptive-timing.md +1 -1
  3. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/claude-code.md +59 -0
  4. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/concepts.md +13 -10
  5. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/quickstart.md +3 -3
  6. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/upgrading.md +44 -0
  7. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/viewer.md +10 -0
  8. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/web/index.html +7 -0
  9. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/web/render.js +50 -0
  10. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/pyproject.toml +1 -1
  11. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/__init__.py +1 -1
  12. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/claude_session.py +24 -1
  13. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/cli.py +240 -4
  14. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/client.py +45 -25
  15. agent_switchboard-2.2.1/src/switchboard/desktop.py +292 -0
  16. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/handoff.py +7 -2
  17. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/mcp_server.py +8 -1
  18. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/conftest.py +6 -0
  19. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_claude_session.py +30 -0
  20. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_config.py +59 -18
  21. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_dash_ids.py +43 -2
  22. agent_switchboard-2.2.1/tests/test_desktop.py +211 -0
  23. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_handoff.py +18 -0
  24. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_web_page.py +49 -0
  25. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.dockerignore +0 -0
  26. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/ci.yml +0 -0
  27. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/deploy.yml +0 -0
  28. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/pages.yml +0 -0
  29. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/publish-viewer.yml +0 -0
  30. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/publish.yml +0 -0
  31. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.github/workflows/roadmap.yml +0 -0
  32. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/.gitignore +0 -0
  33. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/AGENTS.md +0 -0
  34. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/ARCS.md +0 -0
  35. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/CONTRIBUTING.md +0 -0
  36. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/Dockerfile +0 -0
  37. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/LICENSE +0 -0
  38. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/README.md +0 -0
  39. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/demo/README.md +0 -0
  40. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/demo/chess.sh +0 -0
  41. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/demo/run.sh +0 -0
  42. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docker-compose.yml +0 -0
  43. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/api.md +0 -0
  44. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/codex-cli.md +0 -0
  45. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/deployment.md +0 -0
  46. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/drills.md +0 -0
  47. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/encryption.md +0 -0
  48. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/environments.md +0 -0
  49. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/images/viewer.png +0 -0
  50. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/layers.md +0 -0
  51. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/managed-hub.md +0 -0
  52. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/model.md +0 -0
  53. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/seam.md +0 -0
  54. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/testing.md +0 -0
  55. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/docs/why-this-exists.md +0 -0
  56. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/examples/coordinated_worker.py +0 -0
  57. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/session-capsule/README.md +0 -0
  58. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/session-capsule/cc.sh +0 -0
  59. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/session-capsule/claude_session_capsule.py +0 -0
  60. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/README.md +0 -0
  61. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/pyproject.toml +0 -0
  62. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/__init__.py +0 -0
  63. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/__main__.py +0 -0
  64. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/viewer.py +0 -0
  65. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/web/switchboard-open.js +0 -0
  66. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/extras/viewer/switchboard_viewer/web/switchboard-room.js +0 -0
  67. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/README.md +0 -0
  68. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/ROADMAP.md +0 -0
  69. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/arcs/distribution.yaml +0 -0
  70. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/arcs/hub-boundary.yaml +0 -0
  71. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/arcs/setup-and-first-run.yaml +0 -0
  72. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/arcs/ttl-ceilings.yaml +0 -0
  73. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/a-lobby-derived-from-the-key.yaml +0 -0
  74. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/abuse-control-after-authorization.yaml +0 -0
  75. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/board-ttl-ceiling.yaml +0 -0
  76. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/ci-workspace-is-public.yaml +0 -0
  77. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/clients-that-cannot-post.yaml +0 -0
  78. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/connect-failure-message.yaml +0 -0
  79. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/cross-key-rendezvous.yaml +0 -0
  80. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/every-silent-failure-looks-like-a-quiet-room.yaml +0 -0
  81. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/hooks-warning-false-positive.yaml +0 -0
  82. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/hub-origin-reachable-bypassing-the-edge.yaml +0 -0
  83. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/identity-rebinds-on-branch-change.yaml +0 -0
  84. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/init-writes-rooms-file.yaml +0 -0
  85. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/intermittent-suite-failure.yaml +0 -0
  86. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/joining-agent-sees-empty-inbox.yaml +0 -0
  87. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/known-rooms-address-book.yaml +0 -0
  88. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/one-resolved-context-across-surfaces.yaml +0 -0
  89. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/presence-ttl-is-not-one-size.yaml +0 -0
  90. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/provisioned-token-is-stale-and-nothing-says-so.yaml +0 -0
  91. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/publish-hub-container-image.yaml +0 -0
  92. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/read-only-rooms.yaml +0 -0
  93. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/robots-policy-for-public-hosts.yaml +0 -0
  94. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/roles-and-authority-between-agents.yaml +0 -0
  95. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/seal-agent-meta.yaml +0 -0
  96. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/selective-wake-for-the-listener.yaml +0 -0
  97. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/stale-resolver-references.yaml +0 -0
  98. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/stale-token-in-session-env.yaml +0 -0
  99. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/standing-checks-that-nothing-runs.yaml +0 -0
  100. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/timing-cold-start-in-ephemeral-environments.yaml +0 -0
  101. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/ttl-clamped-silently.yaml +0 -0
  102. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/unread-dms-not-shown-outside-mcp.yaml +0 -0
  103. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/roadmap/items/write-parity-across-surfaces.yaml +0 -0
  104. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/scripts/deploy.sh +0 -0
  105. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/auth.py +0 -0
  106. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/config.py +0 -0
  107. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/crypto.py +0 -0
  108. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/drill.py +0 -0
  109. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/guidance.py +0 -0
  110. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/holds.py +0 -0
  111. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/invite.py +0 -0
  112. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/knownrooms.py +0 -0
  113. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/load.py +0 -0
  114. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/notify.py +0 -0
  115. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/peers.py +0 -0
  116. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/rendezvous.py +0 -0
  117. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/rooms.py +0 -0
  118. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/scripts/wake-on-message.sh +0 -0
  119. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/server.py +0 -0
  120. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/signing.py +0 -0
  121. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/001-SKILL.md +0 -0
  122. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/002-SKILL.md +0 -0
  123. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/003-SKILL.md +0 -0
  124. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/004-SKILL.md +0 -0
  125. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/005-SKILL.md +0 -0
  126. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/006-SKILL.md +0 -0
  127. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/007-SKILL.md +0 -0
  128. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/008-SKILL.md +0 -0
  129. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/009-SKILL.md +0 -0
  130. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/010-SKILL.md +0 -0
  131. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/011-SKILL.md +0 -0
  132. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/012-SKILL.md +0 -0
  133. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/013-SKILL.md +0 -0
  134. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/014-SKILL.md +0 -0
  135. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/015-SKILL.md +0 -0
  136. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/016-SKILL.md +0 -0
  137. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/017-SKILL.md +0 -0
  138. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/018-SKILL.md +0 -0
  139. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/019-SKILL.md +0 -0
  140. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/020-SKILL.md +0 -0
  141. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/021-SKILL.md +0 -0
  142. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/022-SKILL.md +0 -0
  143. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/023-SKILL.md +0 -0
  144. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/024-SKILL.md +0 -0
  145. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/history/025-SKILL.md +0 -0
  146. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/skill/switchboard-coordinate/SKILL.md +0 -0
  147. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/spec.py +0 -0
  148. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/store.py +0 -0
  149. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/testing.py +0 -0
  150. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/timing.py +0 -0
  151. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/src/switchboard/writekey.py +0 -0
  152. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/README.md +0 -0
  153. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/__init__.py +0 -0
  154. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/analysis.py +0 -0
  155. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/economy.py +0 -0
  156. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/flow.py +0 -0
  157. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/llm.py +0 -0
  158. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/manager.py +0 -0
  159. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/report.py +0 -0
  160. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/run.py +0 -0
  161. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier1.json +0 -0
  162. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier1_labour.json +0 -0
  163. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier1_rounds.json +0 -0
  164. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_bound.json +0 -0
  165. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_built.json +0 -0
  166. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_free.json +0 -0
  167. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_paid.json +0 -0
  168. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_spend.json +0 -0
  169. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed1_told.json +0 -0
  170. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/tier2_seed41_bound.json +0 -0
  171. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter/traders.py +0 -0
  172. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter_experiment.py +0 -0
  173. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/barter_llm_experiment.py +0 -0
  174. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_harness.py +0 -0
  175. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/RESULTS.md +0 -0
  176. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_A_s31337.txt +0 -0
  177. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_A_s4242.txt +0 -0
  178. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_A_s7777.txt +0 -0
  179. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_B_s31337.txt +0 -0
  180. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_B_s4242.txt +0 -0
  181. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_B_s7777.txt +0 -0
  182. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_C_s31337.txt +0 -0
  183. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_C_s4242.txt +0 -0
  184. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/prompt_C_s7777.txt +0 -0
  185. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_A_s31337.json +0 -0
  186. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_A_s4242.json +0 -0
  187. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_A_s7777.json +0 -0
  188. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_B_s31337.json +0 -0
  189. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_B_s4242.json +0 -0
  190. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_B_s7777.json +0 -0
  191. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_C_s31337.json +0 -0
  192. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_C_s4242.json +0 -0
  193. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_haiku_C_s7777.json +0 -0
  194. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_A_s31337.json +0 -0
  195. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_A_s4242.json +0 -0
  196. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_A_s7777.json +0 -0
  197. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_B_s31337.json +0 -0
  198. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_B_s4242.json +0 -0
  199. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_B_s7777.json +0 -0
  200. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_C_s31337.json +0 -0
  201. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_C_s4242.json +0 -0
  202. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_opus_C_s7777.json +0 -0
  203. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_A_s31337.json +0 -0
  204. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_A_s4242.json +0 -0
  205. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_A_s7777.json +0 -0
  206. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_B_s31337.json +0 -0
  207. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_B_s4242.json +0 -0
  208. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_B_s7777.json +0 -0
  209. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_C_s31337.json +0 -0
  210. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_C_s4242.json +0 -0
  211. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_pilot/response_sonnet_C_s7777.json +0 -0
  212. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/tier2_run.py +0 -0
  213. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/experiments/timing_experiment.py +0 -0
  214. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_api.py +0 -0
  215. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_arrive.py +0 -0
  216. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_auth.py +0 -0
  217. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_barter.py +0 -0
  218. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_barter_llm.py +0 -0
  219. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_board.py +0 -0
  220. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_checkin.py +0 -0
  221. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_config.py +0 -0
  222. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_global_flags.py +0 -0
  223. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_init.py +0 -0
  224. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_serve.py +0 -0
  225. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_session.py +0 -0
  226. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cli_timing.py +0 -0
  227. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_client_invite.py +0 -0
  228. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_cors_write_headers.py +0 -0
  229. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_crypto.py +0 -0
  230. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_declare.py +0 -0
  231. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_deployment.py +0 -0
  232. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_drill.py +0 -0
  233. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_evening_replayed.py +0 -0
  234. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_example_viewer.py +0 -0
  235. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_failure_messages.py +0 -0
  236. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_help.py +0 -0
  237. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_invite.py +0 -0
  238. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_isolated_hub.py +0 -0
  239. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_known_rooms.py +0 -0
  240. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_leave_a_ghost.py +0 -0
  241. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_load.py +0 -0
  242. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_lobby.py +0 -0
  243. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_mcp.py +0 -0
  244. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_mcp_session.py +0 -0
  245. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_notify.py +0 -0
  246. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_rendezvous.py +0 -0
  247. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_rooms.py +0 -0
  248. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_spec.py +0 -0
  249. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_store.py +0 -0
  250. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_surface_parity.py +0 -0
  251. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_testing.py +0 -0
  252. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_tier2_harness.py +0 -0
  253. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_timing.py +0 -0
  254. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_timing_experiment.py +0 -0
  255. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_transient_failures.py +0 -0
  256. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_version.py +0 -0
  257. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_wake_listener.py +0 -0
  258. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_web_invite.py +0 -0
  259. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_web_reader.py +0 -0
  260. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_web_snapshot.py +0 -0
  261. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_whisper.py +0 -0
  262. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_workflow_token.py +0 -0
  263. {agent_switchboard-2.2.0 → agent_switchboard-2.2.1}/tests/test_write_keys.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agent-switchboard
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: An ephemeral orchestration hub for AI coding agents: presence, leases, messaging and a shared blackboard, all with TTLs.
5
5
  Project-URL: Homepage, https://github.com/gald33/switchboard
6
6
  Project-URL: Issues, https://github.com/gald33/switchboard/issues
@@ -48,7 +48,7 @@ is never a commitment, and no agent is required to obey another's forecast.
48
48
  machines. It is shared by every repo on that machine, but every read and
49
49
  write is keyed by `(agent_id, workspace)` — and `workspace` defaults to
50
50
  the git remote's org/repo — so history never bleeds from one repo into
51
- another's forecasts. `agent_id` derives from kind/branch/hostname, so the
51
+ another's forecasts. `agent_id` derives from kind/hostname/session, so the
52
52
  same person on two laptops builds two independent histories that converge
53
53
  separately. All of that is deliberate: the premise is that timing is
54
54
  local and personal, and a shared or committed history would be neither.
@@ -466,6 +466,65 @@ are the two halves with no hub between them: a capsule file, created `0600`
466
466
  because it is as private as the transcript it carries, that you move however
467
467
  you like.
468
468
 
469
+ ### If you work in the desktop app
470
+
471
+ Installing the transcript is the whole job for a CLI user and half of one for
472
+ everybody else. The desktop app keeps its own list of sessions beside the
473
+ transcripts, so a conversation whose transcript is installed but whose record
474
+ is not exists on disk and is invisible in the app — which is exactly what
475
+ happened the first time a session was handed from a cloud agent to a laptop:
476
+ it resumed perfectly on the command line and could not be found in the app the
477
+ recipient actually used.
478
+
479
+ So `switchboard init` asks, once, on a machine that runs the app: should a
480
+ session handed to you also appear in its sidebar? Say yes and `receive` writes
481
+ the record too. The question is asked at setup rather than per handoff for two
482
+ reasons — it writes into another program's store, which is a thing to agree to
483
+ deliberately, and the account and project the row is filed under are ids that
484
+ app assigns, discoverable only where it runs. A cloud runner has neither, so
485
+ the step is silently skipped there.
486
+
487
+ `switchboard session register <id>` does it for a session already installed,
488
+ and `session unregister <id>` is the undo. Prefer that over deleting the row
489
+ inside the app: in-app delete and archive both write a release marker next to
490
+ the transcript which the CLI acts on, while unregister removes only the record.
491
+
492
+ Two things worth knowing. The app reads its store at launch, so a registered
493
+ session appears after the next restart rather than immediately. And this is an
494
+ undocumented format belonging to another program: `switchboard.desktop` says
495
+ which app version it was verified against, keeps every detail in that one
496
+ module, and answers "not here" rather than guessing when the shape is not what
497
+ it expects.
498
+
499
+ ### What travels, and what a reader can take out
500
+
501
+ The subagent transcripts in the `<id>/` sidecar travel by default, and that
502
+ is the trade worth naming because it is not the obvious one. They are most
503
+ of a capsule's bytes — three quarters of it is normal — and none of its
504
+ resume: `claude --resume` reads the main transcript alone, so the sidecar
505
+ costs the receiver no context at cold start. What it buys is retrievability.
506
+ A subagent's transcript is finished work, often millions of tokens of it,
507
+ that the receiving session can go and read *if it needs to* rather than
508
+ re-derive at model prices. Bytes on a board with a ten-minute TTL are the
509
+ cheap side of that trade, so the default is on.
510
+
511
+ `--no-subagents` (`no_subagents` on `session_handoff`) drops them, for a slow
512
+ link or a receiver that only needs the conversation continued. The capsule
513
+ records that it did, as `omitted_subagent_files`, because a session that
514
+ never spawned a subagent and one whose subagents were left behind both
515
+ arrive as a single file, and a receiver deciding whether to ask again needs
516
+ to tell those apart.
517
+
518
+ A capsule can also be collected with no Switchboard on the machine at all.
519
+ The [viewer](../extras/viewer/) already holds the plaintext — it fetched the
520
+ board entry and opened it with the room key, which is why a sealed value is
521
+ legible there — so a board entry it could open offers a **save** button that
522
+ writes the value out as a file. Feed that file to `switchboard session
523
+ import FILE` wherever the tooling *is* installed. It asks the hub for
524
+ nothing extra, and it is the one path to a handoff for a human with a
525
+ browser and no checkout. Where the page could not open a value it offers
526
+ nothing: a file of envelope bytes would look like the value and is not.
527
+
469
528
  ### What is trusted, and what is not
470
529
 
471
530
  A transcript is instructions. Whoever resumes it runs a conversation
@@ -43,20 +43,23 @@ An agent registers itself (id, name, kind, branch, current task) and then
43
43
  heartbeats. If it stops heartbeating, it disappears from the roster within its
44
44
  TTL — two minutes by default.
45
45
 
46
- The agent id is inferred from `kind`, git branch and hostname, so a session
47
- that restarts on the same branch reclaims its own identity rather than
48
- appearing as a stranger. Override it with `SWITCHBOARD_AGENT_ID` when you want
49
- something specific.
46
+ The agent id is inferred from `kind`, hostname and a hash of the session id,
47
+ so a session that resumes reclaims its own identity — its leases, its read
48
+ cursor — rather than appearing as a stranger. Two sessions on one machine get
49
+ different ids, because the session is part of the derivation. Override it with
50
+ `SWITCHBOARD_AGENT_ID` when you want something specific.
50
51
 
51
52
  `kind` is auto-detected as `ci` (in GitHub Actions), `cloud` (in a remote
52
53
  Claude Code session or a Codespace) or `local`.
53
54
 
54
- Because the inferred id has no process or session component, two sessions
55
- that share `kind`, branch and hostname — two terminals on the same checkout,
56
- say — collide on the same agent id. They are indistinguishable to the hub:
57
- same presence row, same lease ownership, same message cursor. If you run
58
- more than one session against the same branch and host, set
59
- `SWITCHBOARD_AGENT_ID` explicitly for at least one of them.
55
+ **The branch is deliberately not part of the id.** It is registered as its own
56
+ field, which is what `dm` resolves a typed branch name against — so it says
57
+ where an agent is *now* and follows it across a checkout, while the id stays
58
+ put. An id has to outlive the session's whole life: it is what the signer
59
+ socket is keyed on, what the roster stores a public key against, what holds a
60
+ lease and what owns a read cursor. Folding the branch in meant that checking
61
+ one out — the most ordinary thing an agent does — minted a second identity and
62
+ orphaned all four.
60
63
 
61
64
  ## Leases
62
65
 
@@ -19,7 +19,7 @@ switchboard serve --port 8787
19
19
  You should see:
20
20
 
21
21
  ```
22
- switchboard 2.2.0 → http://127.0.0.1:8787 db=switchboard.db
22
+ switchboard 2.2.1 → http://127.0.0.1:8787 db=switchboard.db
23
23
  ```
24
24
 
25
25
  Leave it running.
@@ -35,7 +35,7 @@ export SWITCHBOARD_WORKSPACE=demo
35
35
  ```
36
36
 
37
37
  In the second terminal, give the agent a distinct identity so you can tell them
38
- apart (normally this is inferred from branch and hostname):
38
+ apart (normally this is inferred from your host and session):
39
39
 
40
40
  ```bash
41
41
  export SWITCHBOARD_AGENT_ID=beta
@@ -47,7 +47,7 @@ export SWITCHBOARD_AGENT_ID=beta
47
47
 
48
48
  ```bash
49
49
  $ switchboard health
50
- { "ok": true, "version": "2.2.0", "auth": true }
50
+ { "ok": true, "version": "2.2.1", "auth": true }
51
51
 
52
52
  $ switchboard register -c build
53
53
  registered local-main-yourhost (local) in demo
@@ -3,6 +3,50 @@
3
3
  What changes for you between releases that are not backwards compatible, in
4
4
  the order you will hit them. Everything not listed here kept its behaviour.
5
5
 
6
+ ## 2.2.0 → 2.2.1
7
+
8
+ **Your agent ids change once.** An id was `kind-branch-host-session`; the
9
+ branch is gone from it, so it is now `kind-host-session`. The branch was in
10
+ there from when it was the only thing telling two sessions apart — the
11
+ session suffix took that job and left the branch behind as decoration, and
12
+ decoration with teeth: an id is what the signer socket is keyed on, what the
13
+ roster stores a public key against, what holds a lease and what owns a read
14
+ cursor. Checking out a branch, the most ordinary thing an agent does, minted
15
+ a second identity and orphaned all four.
16
+
17
+ Nothing to do about it. On first announce after upgrading, an agent appears
18
+ under its new id; the row under the old one stops being refreshed and lapses
19
+ on the presence TTL like any other. **Leases and read cursors held by the old
20
+ id are not transferred** — a lease expires on its own TTL (15 minutes by
21
+ default) and a cursor's cost is one repeated read. If you cannot wait for a
22
+ lease to lapse, release it before upgrading, or pin `SWITCHBOARD_AGENT_ID`
23
+ across the upgrade to keep the id you had.
24
+
25
+ Branch-based lookup is unaffected: `dm` and the other recipient resolvers
26
+ match the roster's own `branch` field, registered on every announce, which
27
+ follows an agent across a checkout in a way an id baked at startup never
28
+ did. A name still says the branch if you registered it that way.
29
+
30
+ `client.identity_drift_warning` is now `client.rootless_warning`, and warns
31
+ about a different thing. It existed for this bug's directory half — outside a
32
+ checkout the branch read as `detached` and the id moved — which no longer
33
+ happens. What still drifts outside a checkout is the *workspace*, derived
34
+ from the directory when there is no remote to read, which puts the agent in a
35
+ different room entirely; that is what the renamed function now says.
36
+
37
+ Also fixed: an option value beginning with `-` no longer has to be escaped
38
+ by hand. `switchboard session receive --from <agent-id>` and `board list
39
+ --prefix <key>` died on "expected one argument" for about one id in
40
+ sixty-six, because ids and board keys are base64url and argparse will not
41
+ read a dash-leading token as an option's value. Both now work as typed —
42
+ the same repair `dm -yLAoQ63…` already had for positionals, extended to the
43
+ other half of the problem.
44
+
45
+ Additive in the same release: `--no-subagents` on `session export`,
46
+ `handoff` and `publish` (`no_subagents` on `session_handoff`) leaves the
47
+ subagent transcripts out of a capsule, and the viewer can save an opened
48
+ board value to a file. Neither changes an existing default.
49
+
6
50
  ## 2.1.0 → 2.2.0
7
51
 
8
52
  Additive. A whole Claude Code session can be moved between environments
@@ -104,6 +104,16 @@ the rest: the whole key, who wrote it and when, and a value that keeps its own
104
104
  line breaks instead of being folded to fit. The sidebar itself now widens with
105
105
  the screen rather than staying 340px on every one.
106
106
 
107
+ An opened entry also offers **save**, which writes the value out as a file
108
+ named after its key. The page is already holding the plaintext — it fetched
109
+ the entry and opened it with the room key, which is the whole reason a sealed
110
+ value is legible here — so this asks the hub for nothing and needs no CLI. It
111
+ is how a person with a browser and no checkout collects a
112
+ [session capsule](claude-code.md#7-hand-a-whole-session-to-another-environment)
113
+ and feeds it to `switchboard session import` somewhere else. A value this page
114
+ could not open offers no button: a file of envelope bytes would look like the
115
+ value and is not.
116
+
107
117
  Each of the three side panels says how much is inside it and folds away, and
108
118
  what you fold stays folded. On a window too narrow for two columns they stop
109
119
  being a stack under the conversation — where, the talking being long and never
@@ -139,6 +139,13 @@
139
139
  .detail .meta { color: var(--dim); font-size: 12px; margin: 4px 0 14px;
140
140
  display: flex; flex-wrap: wrap; gap: 4px 12px; }
141
141
  .detail .body, .detail pre { font-size: 13px; }
142
+ /* Quiet until wanted: a board value is usually read, not taken, and a
143
+ download button competing with the value would say otherwise. */
144
+ .detail .meta .save { border: 1px solid var(--line); border-radius: 6px;
145
+ background: transparent; color: var(--dim); font: inherit;
146
+ font-size: 12px; padding: 0 8px; cursor: pointer; }
147
+ .detail .meta .save:hover, .detail .meta .save:focus-visible {
148
+ color: var(--accent); border-color: var(--accent); }
142
149
  .detail .nothing { color: var(--dim); font-size: 13px; }
143
150
  /* A key is the row's own text, so it reads as text — and as something that
144
151
  does something only where it does something. */
@@ -513,6 +513,8 @@ function drawDetail(s) {
513
513
  <span>${who(entry.updated_by)}</span>
514
514
  <span>${stamp(entry.updated_at)}</span>
515
515
  <span>expires in ${countdown(entry.expires_in, s.generated_at)}</span>
516
+ ${readable(entry) ? `<button class="save" type="button"
517
+ data-save="${esc(entry.key)}">save</button>` : ""}
516
518
  </div>
517
519
  ${bodyHtml(entry.value)}`;
518
520
  if (pane.dataset.html !== html) {
@@ -521,6 +523,49 @@ function drawDetail(s) {
521
523
  }
522
524
  }
523
525
 
526
+ /** Whether this page holds the entry's plaintext at all.
527
+ *
528
+ * A value that could not be opened arrives as `null` — the builder drops the
529
+ * envelope rather than let ciphertext render as content — and that is the
530
+ * condition to check, not `sealed`, which only says whether the *key* stayed
531
+ * a blinded token. Without a room key nothing is unblinded, so `sealed` is
532
+ * false for every entry in a room this page cannot read: exactly the rooms
533
+ * where offering to save one would write out a file that is not the value.
534
+ */
535
+ function readable(entry) {
536
+ return Boolean(entry) && entry.value !== null && entry.value !== undefined;
537
+ }
538
+
539
+ /** Hand the reader the value they are already looking at, as a file.
540
+ *
541
+ * The page has the plaintext: it fetched the entry and opened it with the
542
+ * room key, which is the whole reason a sealed value is legible here at all.
543
+ * Writing it out asks the hub for nothing and needs no checkout, no CLI and
544
+ * no agent — which is the point. A session capsule saved from here goes
545
+ * straight into `switchboard session import`, so a human with a browser can
546
+ * collect a handoff that no tooling on their machine could.
547
+ *
548
+ * Named after the key so the file says what it is, and `.json` when the
549
+ * value is structured, because everything worth saving from a board is.
550
+ */
551
+ function saveEntry(key) {
552
+ const entry = (last?.board || []).find((e) => e.key === key);
553
+ if (!readable(entry)) return;
554
+ const structured = typeof entry.value !== "string";
555
+ const text = structured ? JSON.stringify(entry.value, null, 2) : entry.value;
556
+ const name = `${key.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "")
557
+ || "board-entry"}${structured ? ".json" : ".txt"}`;
558
+ const url = URL.createObjectURL(
559
+ new Blob([text], { type: structured ? "application/json" : "text/plain" }));
560
+ const a = document.createElement("a");
561
+ a.href = url;
562
+ a.download = name;
563
+ a.click();
564
+ // Same turn would race the click on some browsers; the next one is safe and
565
+ // still prompt enough that nothing accumulates.
566
+ setTimeout(() => URL.revokeObjectURL(url), 0);
567
+ }
568
+
524
569
  export function render(s, { onRoom, onClose } = {}) {
525
570
  last = s;
526
571
  if (onRoom || onClose) handlers = { onRoom, onClose };
@@ -784,6 +829,11 @@ export function render(s, { onRoom, onClose } = {}) {
784
829
  scopeChanged();
785
830
  return;
786
831
  }
832
+ const save = e.target.closest("[data-save]");
833
+ if (save) {
834
+ saveEntry(save.dataset.save);
835
+ return;
836
+ }
787
837
  const entry = e.target.closest("[data-board]");
788
838
  if (entry) {
789
839
  opened = entry.dataset.board;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agent-switchboard"
3
- version = "2.2.0"
3
+ version = "2.2.1"
4
4
  description = "An ephemeral orchestration hub for AI coding agents: presence, leases, messaging and a shared blackboard, all with TTLs."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -47,7 +47,7 @@ from .store import (
47
47
  from .timing import unwrap_forecast, wrap_forecast
48
48
  from .writekey import RoomWriteKey, generate_write_key
49
49
 
50
- __version__ = "2.2.0"
50
+ __version__ = "2.2.1"
51
51
 
52
52
  __all__ = [
53
53
  "Invite",
@@ -268,6 +268,7 @@ def package(
268
268
  *,
269
269
  config_dir: str | os.PathLike[str] | None = None,
270
270
  cwd: str | os.PathLike[str] | None = None,
271
+ subagents: bool = True,
271
272
  ) -> dict[str, Any]:
272
273
  """Build a capsule for a session found under this machine's config dir.
273
274
 
@@ -275,6 +276,19 @@ def package(
275
276
  (rare: it means the session was already imported here from elsewhere).
276
277
  Nothing is modified; the transcript is read as it is, which inside a live
277
278
  session means up to the tool call that asked for the export.
279
+
280
+ ``subagents`` carries the sidecar directory — every subagent's own
281
+ transcript — and defaults to on, which looks expensive and is not. Those
282
+ files are most of the bytes (three quarters, on a session that ran a
283
+ couple of workflows) and none of them are needed to resume: ``--resume``
284
+ reads the main transcript alone, so they cost the receiving model no
285
+ context at all. What they are is the work already done, at rest: one
286
+ session here carried 153 subagent transcripts holding some five million
287
+ tokens of finished analysis for six megabytes on the wire. Dropping them
288
+ saves a transfer nobody notices and throws away the answer to "why did we
289
+ reject that finding?", which otherwise has to be bought again. Turn it
290
+ off for a slow link or a receiver that only needs the conversation; the
291
+ capsule then says so, rather than arriving quietly short.
278
292
  """
279
293
  session_id = valid_session_id(session_id)
280
294
  cfg = _resolve_config_dir(config_dir)
@@ -307,10 +321,14 @@ def package(
307
321
  })
308
322
 
309
323
  add(transcript, f"{session_id}.jsonl")
324
+ omitted = 0
310
325
  if sidecar.is_dir():
311
326
  for path in sorted(sidecar.rglob("*")):
312
327
  if path.is_file() and not path.is_symlink():
313
- add(path, f"{session_id}/{path.relative_to(sidecar).as_posix()}")
328
+ if subagents:
329
+ add(path, f"{session_id}/{path.relative_to(sidecar).as_posix()}")
330
+ else:
331
+ omitted += 1
314
332
 
315
333
  meta = transcript_metadata(transcript)
316
334
  return {
@@ -323,6 +341,11 @@ def package(
323
341
  "exported_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
324
342
  "stats": {k: meta[k] for k in ("records", "user_messages", "assistant_messages")},
325
343
  "files": files,
344
+ # Recorded rather than inferred from the file list: a session that
345
+ # never spawned a subagent and one whose subagents were dropped both
346
+ # arrive with a single file, and a receiver deciding whether to ask
347
+ # for them again needs to tell those apart.
348
+ **({"omitted_subagent_files": omitted} if omitted else {}),
326
349
  }
327
350
 
328
351