interlatch 0.26.0__tar.gz → 0.27.0__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 (458) hide show
  1. {interlatch-0.26.0 → interlatch-0.27.0}/CHANGELOG.md +26 -0
  2. {interlatch-0.26.0 → interlatch-0.27.0}/PKG-INFO +1 -1
  3. interlatch-0.27.0/changelog.d/party-formations.md +4 -0
  4. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/party.md +16 -6
  5. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/privacy.md +1 -1
  6. {interlatch-0.26.0 → interlatch-0.27.0}/docs/party.md +17 -6
  7. {interlatch-0.26.0 → interlatch-0.27.0}/docs/privacy.md +1 -1
  8. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/firestore.rules +13 -5
  9. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/i18n/ja.py +5 -0
  10. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/leaderboard.py +13 -6
  11. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/party.py +57 -1
  12. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/handler.py +3 -3
  13. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/routes.py +7 -5
  14. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/arena.py +74 -47
  15. interlatch-0.27.0/src/interlatch/knowledge/formation.py +165 -0
  16. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/arena.py +59 -26
  17. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/leaderboard.py +19 -15
  18. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/innerlatch.css +79 -2
  19. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/innerlatch.js +202 -63
  20. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/ja.js +44 -6
  21. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/scene.js +17 -11
  22. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_arena.py +119 -31
  23. interlatch-0.27.0/tests/test_formation.py +154 -0
  24. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_route_matrix.py +4 -2
  25. interlatch-0.26.0/changelog.d/adventure.md +0 -5
  26. interlatch-0.26.0/changelog.d/group-page.md +0 -7
  27. interlatch-0.26.0/changelog.d/hub-join-http-client.md +0 -2
  28. interlatch-0.26.0/changelog.d/install-choose-analyzer.md +0 -4
  29. interlatch-0.26.0/changelog.d/town-and-gear.md +0 -5
  30. {interlatch-0.26.0 → interlatch-0.27.0}/.github/dependabot.yml +0 -0
  31. {interlatch-0.26.0 → interlatch-0.27.0}/.github/workflows/changelog.yml +0 -0
  32. {interlatch-0.26.0 → interlatch-0.27.0}/.github/workflows/ci.yml +0 -0
  33. {interlatch-0.26.0 → interlatch-0.27.0}/.github/workflows/docs.yml +0 -0
  34. {interlatch-0.26.0 → interlatch-0.27.0}/.github/workflows/release.yml +0 -0
  35. {interlatch-0.26.0 → interlatch-0.27.0}/.gitignore +0 -0
  36. {interlatch-0.26.0 → interlatch-0.27.0}/.pre-commit-config.yaml +0 -0
  37. {interlatch-0.26.0 → interlatch-0.27.0}/.python-version +0 -0
  38. {interlatch-0.26.0 → interlatch-0.27.0}/CONTRIBUTING.md +0 -0
  39. {interlatch-0.26.0 → interlatch-0.27.0}/LICENSE +0 -0
  40. {interlatch-0.26.0 → interlatch-0.27.0}/README.ja.md +0 -0
  41. {interlatch-0.26.0 → interlatch-0.27.0}/README.md +0 -0
  42. {interlatch-0.26.0 → interlatch-0.27.0}/ROADMAP.ja.md +0 -0
  43. {interlatch-0.26.0 → interlatch-0.27.0}/ROADMAP.md +0 -0
  44. {interlatch-0.26.0 → interlatch-0.27.0}/SECURITY.md +0 -0
  45. {interlatch-0.26.0 → interlatch-0.27.0}/careers/README.md +0 -0
  46. {interlatch-0.26.0 → interlatch-0.27.0}/careers/boards.json +0 -0
  47. {interlatch-0.26.0 → interlatch-0.27.0}/careers/pipeline.py +0 -0
  48. {interlatch-0.26.0 → interlatch-0.27.0}/changelog.d/README.md +0 -0
  49. {interlatch-0.26.0 → interlatch-0.27.0}/dev.sh +0 -0
  50. {interlatch-0.26.0 → interlatch-0.27.0}/docker/.dockerignore +0 -0
  51. {interlatch-0.26.0 → interlatch-0.27.0}/docker/.env.example +0 -0
  52. {interlatch-0.26.0 → interlatch-0.27.0}/docker/Caddyfile +0 -0
  53. {interlatch-0.26.0 → interlatch-0.27.0}/docker/Dockerfile +0 -0
  54. {interlatch-0.26.0 → interlatch-0.27.0}/docker/compose.yaml +0 -0
  55. {interlatch-0.26.0 → interlatch-0.27.0}/docker/wheels/.gitignore +0 -0
  56. {interlatch-0.26.0 → interlatch-0.27.0}/docs/README.md +0 -0
  57. {interlatch-0.26.0 → interlatch-0.27.0}/docs/_site/hooks.py +0 -0
  58. {interlatch-0.26.0 → interlatch-0.27.0}/docs/analysis.md +0 -0
  59. {interlatch-0.26.0 → interlatch-0.27.0}/docs/cli.md +0 -0
  60. {interlatch-0.26.0 → interlatch-0.27.0}/docs/configuration.md +0 -0
  61. {interlatch-0.26.0 → interlatch-0.27.0}/docs/dashboard.md +0 -0
  62. {interlatch-0.26.0 → interlatch-0.27.0}/docs/demo/make_demo.py +0 -0
  63. {interlatch-0.26.0 → interlatch-0.27.0}/docs/demo/record_demo.py +0 -0
  64. {interlatch-0.26.0 → interlatch-0.27.0}/docs/design/ja/learning.md +0 -0
  65. {interlatch-0.26.0 → interlatch-0.27.0}/docs/design/learning.md +0 -0
  66. {interlatch-0.26.0 → interlatch-0.27.0}/docs/development.md +0 -0
  67. {interlatch-0.26.0 → interlatch-0.27.0}/docs/devices.md +0 -0
  68. {interlatch-0.26.0 → interlatch-0.27.0}/docs/diagrams/analysis.excalidraw.svg +0 -0
  69. {interlatch-0.26.0 → interlatch-0.27.0}/docs/diagrams/architecture.excalidraw.svg +0 -0
  70. {interlatch-0.26.0 → interlatch-0.27.0}/docs/docker.md +0 -0
  71. {interlatch-0.26.0 → interlatch-0.27.0}/docs/faq.md +0 -0
  72. {interlatch-0.26.0 → interlatch-0.27.0}/docs/images/demo.gif +0 -0
  73. {interlatch-0.26.0 → interlatch-0.27.0}/docs/images/home.png +0 -0
  74. {interlatch-0.26.0 → interlatch-0.27.0}/docs/images/map.png +0 -0
  75. {interlatch-0.26.0 → interlatch-0.27.0}/docs/images/palette.png +0 -0
  76. {interlatch-0.26.0 → interlatch-0.27.0}/docs/images/session.png +0 -0
  77. {interlatch-0.26.0 → interlatch-0.27.0}/docs/install.md +0 -0
  78. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/README.md +0 -0
  79. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/analysis.md +0 -0
  80. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/cli.md +0 -0
  81. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/configuration.md +0 -0
  82. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/dashboard.md +0 -0
  83. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/development.md +0 -0
  84. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/devices.md +0 -0
  85. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/docker.md +0 -0
  86. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/faq.md +0 -0
  87. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/install.md +0 -0
  88. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/join-a-hub.md +0 -0
  89. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/mcp.md +0 -0
  90. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/moving-from-chronicle.md +0 -0
  91. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/postgres.md +0 -0
  92. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/sources.md +0 -0
  93. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/suggestions.md +0 -0
  94. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/troubleshooting.md +0 -0
  95. {interlatch-0.26.0 → interlatch-0.27.0}/docs/ja/vscode.md +0 -0
  96. {interlatch-0.26.0 → interlatch-0.27.0}/docs/join-a-hub.md +0 -0
  97. {interlatch-0.26.0 → interlatch-0.27.0}/docs/mcp.md +0 -0
  98. {interlatch-0.26.0 → interlatch-0.27.0}/docs/moving-from-chronicle.md +0 -0
  99. {interlatch-0.26.0 → interlatch-0.27.0}/docs/postgres.md +0 -0
  100. {interlatch-0.26.0 → interlatch-0.27.0}/docs/sources.md +0 -0
  101. {interlatch-0.26.0 → interlatch-0.27.0}/docs/suggestions.md +0 -0
  102. {interlatch-0.26.0 → interlatch-0.27.0}/docs/troubleshooting.md +0 -0
  103. {interlatch-0.26.0 → interlatch-0.27.0}/docs/vscode.md +0 -0
  104. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/.firebaserc +0 -0
  105. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/.gitignore +0 -0
  106. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/README.md +0 -0
  107. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/firebase.json +0 -0
  108. {interlatch-0.26.0 → interlatch-0.27.0}/leaderboard/firestore.indexes.json +0 -0
  109. {interlatch-0.26.0 → interlatch-0.27.0}/mkdocs.yml +0 -0
  110. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/README.md +0 -0
  111. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/art.json +0 -0
  112. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/filing.html +0 -0
  113. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/loops.json +0 -0
  114. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/loops.py +0 -0
  115. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/members.js +0 -0
  116. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/render.html +0 -0
  117. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/render.py +0 -0
  118. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/scenes-more.js +0 -0
  119. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/icons3d/scenes.html +0 -0
  120. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/Chronicle.icns +0 -0
  121. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/Interlatch.spec +0 -0
  122. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/build.sh +0 -0
  123. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/entitlements.plist +0 -0
  124. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/icon-3d.webp +0 -0
  125. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/icon-blueprint-prompt.md +0 -0
  126. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/icon-blueprint.webp +0 -0
  127. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/icon.png +0 -0
  128. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/interlatch_app.py +0 -0
  129. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/macos/make_icon.py +0 -0
  130. {interlatch-0.26.0 → interlatch-0.27.0}/packaging/release.py +0 -0
  131. {interlatch-0.26.0 → interlatch-0.27.0}/pyproject.toml +0 -0
  132. {interlatch-0.26.0 → interlatch-0.27.0}/src/chronicle/__init__.py +0 -0
  133. {interlatch-0.26.0 → interlatch-0.27.0}/src/chronicle/__main__.py +0 -0
  134. {interlatch-0.26.0 → interlatch-0.27.0}/src/chronicle/windows.py +0 -0
  135. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/__init__.py +0 -0
  136. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/__main__.py +0 -0
  137. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/__init__.py +0 -0
  138. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/digest.py +0 -0
  139. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/language.py +0 -0
  140. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/queue.py +0 -0
  141. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/__init__.py +0 -0
  142. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/api.py +0 -0
  143. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/base.py +0 -0
  144. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/bob.py +0 -0
  145. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/claude.py +0 -0
  146. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/codex.py +0 -0
  147. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/factory.py +0 -0
  148. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/process.py +0 -0
  149. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/replies.py +0 -0
  150. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/runners/settings.py +0 -0
  151. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/screen.py +0 -0
  152. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/session.py +0 -0
  153. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/analysis/worker.py +0 -0
  154. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/application/__init__.py +0 -0
  155. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/application/records.py +0 -0
  156. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/application/search.py +0 -0
  157. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/application/workflows.py +0 -0
  158. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/artifacts/__init__.py +0 -0
  159. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/artifacts/files.py +0 -0
  160. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/artifacts/queries.py +0 -0
  161. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/cli.py +0 -0
  162. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/__init__.py +0 -0
  163. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/agents.py +0 -0
  164. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/artifact_records.py +0 -0
  165. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/config.py +0 -0
  166. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/diagrams.py +0 -0
  167. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/i18n/__init__.py +0 -0
  168. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/machine.py +0 -0
  169. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/paths.py +0 -0
  170. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/pricing.py +0 -0
  171. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/process.py +0 -0
  172. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/redact.py +0 -0
  173. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/sessions.py +0 -0
  174. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/core/util.py +0 -0
  175. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/__init__.py +0 -0
  176. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/analyses.py +0 -0
  177. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/archive.py +0 -0
  178. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/chat_import.py +0 -0
  179. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/deletion.py +0 -0
  180. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/history.py +0 -0
  181. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/memory.py +0 -0
  182. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/persistence.py +0 -0
  183. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/report.py +0 -0
  184. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/ingestion/service.py +0 -0
  185. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/__init__.py +0 -0
  186. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/__init__.py +0 -0
  187. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/__init__.py +0 -0
  188. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/archive.py +0 -0
  189. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/knowledge.py +0 -0
  190. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/operations.py +0 -0
  191. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/recording.py +0 -0
  192. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/setup.py +0 -0
  193. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/commands/sharing.py +0 -0
  194. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/common.py +0 -0
  195. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/main.py +0 -0
  196. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/cli/parser.py +0 -0
  197. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/exports/__init__.py +0 -0
  198. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/exports/diagrams.py +0 -0
  199. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/exports/markdown.py +0 -0
  200. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/exports/sessions.py +0 -0
  201. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/exports/vault.py +0 -0
  202. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/mcp.py +0 -0
  203. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/__init__.py +0 -0
  204. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/__init__.py +0 -0
  205. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/artifacts.py +0 -0
  206. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/common.py +0 -0
  207. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/devices.py +0 -0
  208. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/hub_admin.py +0 -0
  209. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/knowledge.py +0 -0
  210. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/projects.py +0 -0
  211. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/sessions.py +0 -0
  212. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/settings.py +0 -0
  213. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/sources.py +0 -0
  214. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/status.py +0 -0
  215. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/storage.py +0 -0
  216. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/api/team.py +0 -0
  217. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/app.py +0 -0
  218. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/assets.py +0 -0
  219. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/context.py +0 -0
  220. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/jobs.py +0 -0
  221. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/interfaces/web/server.py +0 -0
  222. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/__init__.py +0 -0
  223. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/bag.py +0 -0
  224. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/cases.py +0 -0
  225. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/friction.py +0 -0
  226. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/glossary.py +0 -0
  227. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/instructions.py +0 -0
  228. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/ladder.py +0 -0
  229. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/learning.py +0 -0
  230. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/market.json +0 -0
  231. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/party.py +0 -0
  232. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/reviews.py +0 -0
  233. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/roadmap.json +0 -0
  234. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/suggest.py +0 -0
  235. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/knowledge/synthesize.py +0 -0
  236. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/__init__.py +0 -0
  237. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/groups.py +0 -0
  238. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/labels.py +0 -0
  239. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/progress.py +0 -0
  240. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/routing.py +0 -0
  241. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/projects/systems.py +0 -0
  242. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/__init__.py +0 -0
  243. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/connectors.py +0 -0
  244. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/container.py +0 -0
  245. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/hooks.py +0 -0
  246. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/install.py +0 -0
  247. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/migrate.py +0 -0
  248. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/notify.py +0 -0
  249. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/platforms/__init__.py +0 -0
  250. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/platforms/desktop.py +0 -0
  251. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/platforms/menubar.py +0 -0
  252. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/platforms/windows.py +0 -0
  253. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/statusline.py +0 -0
  254. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/tailnet.py +0 -0
  255. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/runtime/update.py +0 -0
  256. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/__init__.py +0 -0
  257. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/access.py +0 -0
  258. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/client.py +0 -0
  259. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/identity.py +0 -0
  260. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/market.py +0 -0
  261. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/news.py +0 -0
  262. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/people.py +0 -0
  263. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/projects.py +0 -0
  264. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/protocol.py +0 -0
  265. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/pull.py +0 -0
  266. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/push.py +0 -0
  267. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sharing/receiver.py +0 -0
  268. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/__init__.py +0 -0
  269. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/antigravity.py +0 -0
  270. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/bob.py +0 -0
  271. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/builder.py +0 -0
  272. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/chatgpt.py +0 -0
  273. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/chats.py +0 -0
  274. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/claude/__init__.py +0 -0
  275. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/claude/export.py +0 -0
  276. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/claude/files.py +0 -0
  277. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/claude/parser.py +0 -0
  278. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/codex/__init__.py +0 -0
  279. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/codex/cloud.py +0 -0
  280. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/codex/parser.py +0 -0
  281. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/copilot.py +0 -0
  282. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/discovery.py +0 -0
  283. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/opencode.py +0 -0
  284. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/sources/prompts.py +0 -0
  285. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/__init__.py +0 -0
  286. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/fts.py +0 -0
  287. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/kv.py +0 -0
  288. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/migrations.py +0 -0
  289. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/mirror.py +0 -0
  290. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/postgres.py +0 -0
  291. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/schema.py +0 -0
  292. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/sqlite.py +0 -0
  293. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/storage/team.py +0 -0
  294. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/AGENT-ICONS.txt +0 -0
  295. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/PLEX-OFL.txt +0 -0
  296. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/adventure.js +0 -0
  297. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-antigravity.svg +0 -0
  298. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-bob.svg +0 -0
  299. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-chatgpt.svg +0 -0
  300. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-claude-ai.svg +0 -0
  301. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-claude.svg +0 -0
  302. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-codex.svg +0 -0
  303. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-copilot.svg +0 -0
  304. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/agent-opencode.svg +0 -0
  305. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/app.css +0 -0
  306. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/app.js +0 -0
  307. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-book.webp +0 -0
  308. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-camera.webp +0 -0
  309. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-chip.webp +0 -0
  310. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-clock.webp +0 -0
  311. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-cloud.webp +0 -0
  312. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-coins.webp +0 -0
  313. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-container.webp +0 -0
  314. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-dashboard.webp +0 -0
  315. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-dex.webp +0 -0
  316. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-document.webp +0 -0
  317. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-flag.webp +0 -0
  318. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-folder.webp +0 -0
  319. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-gateway.webp +0 -0
  320. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-gears.webp +0 -0
  321. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-glitch.webp +0 -0
  322. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-globe.webp +0 -0
  323. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-laptop.webp +0 -0
  324. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-log.webp +0 -0
  325. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-lumi.webp +0 -0
  326. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-magnifier.webp +0 -0
  327. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-pip.webp +0 -0
  328. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-plug.webp +0 -0
  329. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-rack.webp +0 -0
  330. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/art-tink.webp +0 -0
  331. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/battle.js +0 -0
  332. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/boot.js +0 -0
  333. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/favicon.png +0 -0
  334. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/i18n.js +0 -0
  335. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/icon-180.png +0 -0
  336. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/icon-192.png +0 -0
  337. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/icon-512.png +0 -0
  338. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/icon.png +0 -0
  339. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/index.html +0 -0
  340. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/learning.js +0 -0
  341. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/manifest.webmanifest +0 -0
  342. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/panel.css +0 -0
  343. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/panel.html +0 -0
  344. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/panel.js +0 -0
  345. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-mono-400.woff2 +0 -0
  346. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-mono-500.woff2 +0 -0
  347. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-mono-600.woff2 +0 -0
  348. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-sans-400.woff2 +0 -0
  349. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-sans-500.woff2 +0 -0
  350. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-sans-600.woff2 +0 -0
  351. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/plex-sans-700.woff2 +0 -0
  352. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/rough.js +0 -0
  353. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/web/sprites.js +0 -0
  354. {interlatch-0.26.0 → interlatch-0.27.0}/src/interlatch/windows.py +0 -0
  355. {interlatch-0.26.0 → interlatch-0.27.0}/tests/antigravity_fixture.py +0 -0
  356. {interlatch-0.26.0 → interlatch-0.27.0}/tests/codex_fixture.py +0 -0
  357. {interlatch-0.26.0 → interlatch-0.27.0}/tests/conftest.py +0 -0
  358. {interlatch-0.26.0 → interlatch-0.27.0}/tests/copilot_fixture.py +0 -0
  359. {interlatch-0.26.0 → interlatch-0.27.0}/tests/friction_fixture.py +0 -0
  360. {interlatch-0.26.0 → interlatch-0.27.0}/tests/golden.py +0 -0
  361. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/cli.json +0 -0
  362. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/diagram.json +0 -0
  363. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/antigravity.json +0 -0
  364. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/bob.json +0 -0
  365. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/claude_code.json +0 -0
  366. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/codex.json +0 -0
  367. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/copilot.json +0 -0
  368. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/parsers/opencode.json +0 -0
  369. {interlatch-0.26.0 → interlatch-0.27.0}/tests/goldens/systems.json +0 -0
  370. {interlatch-0.26.0 → interlatch-0.27.0}/tests/opencode_fixture.py +0 -0
  371. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_adventure_web.py +0 -0
  372. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_analysis.py +0 -0
  373. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_antigravity.py +0 -0
  374. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_architecture.py +0 -0
  375. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_artifacts.py +0 -0
  376. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_bag.py +0 -0
  377. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_battle_web.py +0 -0
  378. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_bob_analysis.py +0 -0
  379. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_boot_web.py +0 -0
  380. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_careers.py +0 -0
  381. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_case_files.py +0 -0
  382. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_cases.py +0 -0
  383. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_chat_import.py +0 -0
  384. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_cli_pins.py +0 -0
  385. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_codex.py +0 -0
  386. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_codex_analysis.py +0 -0
  387. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_codex_cloud.py +0 -0
  388. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_container.py +0 -0
  389. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_copilot_bob.py +0 -0
  390. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_dedicated_hub.py +0 -0
  391. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_dedupe.py +0 -0
  392. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_desktop.py +0 -0
  393. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_diagram.py +0 -0
  394. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_ee_boundary.py +0 -0
  395. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_friction.py +0 -0
  396. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_glossary.py +0 -0
  397. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_group_sharing.py +0 -0
  398. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_home_agents.py +0 -0
  399. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub.py +0 -0
  400. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_accept.py +0 -0
  401. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_news.py +0 -0
  402. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_projects.py +0 -0
  403. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_projects_listed.py +0 -0
  404. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_purge.py +0 -0
  405. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_hub_sharing_race.py +0 -0
  406. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_i18n_web.py +0 -0
  407. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_ingest.py +0 -0
  408. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_instructions.py +0 -0
  409. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_integration.py +0 -0
  410. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_ladder.py +0 -0
  411. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_language.py +0 -0
  412. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_leaderboard.py +0 -0
  413. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_learning_glance.py +0 -0
  414. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_learning_interests.py +0 -0
  415. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_learning_web.py +0 -0
  416. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_leave_project.py +0 -0
  417. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_legacy_package.py +0 -0
  418. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_map_goldens.py +0 -0
  419. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_market.py +0 -0
  420. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_menubar.py +0 -0
  421. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_migrate.py +0 -0
  422. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_mirror.py +0 -0
  423. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_notify.py +0 -0
  424. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_opencode.py +0 -0
  425. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_parser.py +0 -0
  426. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_parser_goldens.py +0 -0
  427. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_party.py +0 -0
  428. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_party_map.py +0 -0
  429. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_people.py +0 -0
  430. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_people_hub.py +0 -0
  431. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_people_web.py +0 -0
  432. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_project_groups.py +0 -0
  433. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_project_progress.py +0 -0
  434. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_providers.py +0 -0
  435. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_queue.py +0 -0
  436. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_regressions.py +0 -0
  437. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_release.py +0 -0
  438. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_roadmap_catalog.py +0 -0
  439. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_screen.py +0 -0
  440. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_security.py +0 -0
  441. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_setup.py +0 -0
  442. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_statusline.py +0 -0
  443. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_suggest.py +0 -0
  444. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_suggest_cli.py +0 -0
  445. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_systems.py +0 -0
  446. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_team.py +0 -0
  447. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_team_home.py +0 -0
  448. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_update.py +0 -0
  449. {interlatch-0.26.0 → interlatch-0.27.0}/tests/test_windows.py +0 -0
  450. {interlatch-0.26.0 → interlatch-0.27.0}/tests/windows_smoke.py +0 -0
  451. {interlatch-0.26.0 → interlatch-0.27.0}/uv.lock +0 -0
  452. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/.vscodeignore +0 -0
  453. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/LICENSE +0 -0
  454. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/README.md +0 -0
  455. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/extension.js +0 -0
  456. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/media/icon.png +0 -0
  457. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/media/interlatch.svg +0 -0
  458. {interlatch-0.26.0 → interlatch-0.27.0}/vscode-extension/package.json +0 -0
@@ -3,6 +3,32 @@
3
3
  <!-- Don't add lines here: a pull request puts its line in a new file in changelog.d/ (see changelog.d/README.md),
4
4
  and each release moves the files it took into a dated section below. -->
5
5
 
6
+ ## 0.26.0 (2026-10-16)
7
+
8
+ - **Adventure:** a new page in Innerlatch puts every lesson that counts on a pixel world map, one region per attribute
9
+ and one territory per roadmap module, fogged where no lesson reached yet. Click a monster for a turn-based battle
10
+ (Attack, Skill, Defend, Run) led by the agent that taught it; a win marks it cleared, and battles never change
11
+ levels. The Party page keeps the characters and quests, and a lesson link anywhere in Innerlatch now opens the lesson
12
+ in a window over the page instead of leaving for the dashboard ([Party](docs/party.md#adventure)).
13
+ - **Choose what analyzes sessions at install:** `interlatch install` now lists every choice (Claude Code, Codex,
14
+ IBM Bob and the model providers' APIs, Ollama included) with whether each is ready, and sets up the one you pick:
15
+ its endpoint, API key and model, then one test call. `--backend <name>` chooses without asking
16
+ ([Install](docs/install.md#command-line)).
17
+ - **Joining a hub works again:** `interlatch hub join` no longer stops with "module 'http' has no attribute 'client'"
18
+ on a fresh install.
19
+ - **A group's page:** **Analyze** on a project group's heading on the Projects page (or **The whole group** in the
20
+ sidebar) reads the group's projects as one big project. It shows each project's share of the group's time, cost and
21
+ knowledge, what waits on you across all of them, where their last sessions left off, each folder in git, their
22
+ roadmaps and their work week by week, with every item named by its project. A **Map** draws the projects and the
23
+ services and platforms they use, hand-drawn, with the ones they share easy to spot and an Excalidraw download; Usage
24
+ charts each project's time week by week. Each project's knowledge base and architecture sketch sit beside the
25
+ others, and the glossary terms the projects share come first ([Project groups](docs/dashboard.md#project-groups)).
26
+ - **Towns and gear:** each Adventure region has a town whose shop sells gear for its roadmap modules (a weapon, armor
27
+ and charm per module, in three tiers that open as your agents reach Lv 1, 3 and 5 in that skill) and battle supplies.
28
+ A lesson's first win pays gold and may drop loot of its kind; battles gain an **Item** command, and each agent
29
+ wears three slots, changed on its page or in the new **Bag**. Gear only changes battle numbers, never levels
30
+ ([Party](docs/party.md#towns-and-gear)).
31
+
6
32
  ## 0.25.0 (2026-10-15)
7
33
 
8
34
  - **Innerlatch:** the Party, the Leaderboard, the Arena and their Google sign-in moved out of the dashboard into
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: interlatch
3
- Version: 0.26.0
3
+ Version: 0.27.0
4
4
  Summary: Shared memory for your coding agents: records every session (Claude Code, Codex, Copilot, Bob, Antigravity, OpenCode) locally and keeps what they learned, each lesson linked to the session it came from.
5
5
  Project-URL: Homepage, https://interlatch.com
6
6
  Project-URL: Documentation, https://interlatch.com/docs/
@@ -0,0 +1,4 @@
1
+ - **Formations:** the Party page builds decks, teams of one to three agents in the order they fight, up to five
2
+ of them. The Arena fields the deck you pick, as a relay: lead against lead, and whoever wins a bout stays on with
3
+ the HP it has left. A deck of one is the old champion duel. Sheets now carry the lineup (format 3), so arena players
4
+ on older versions don't meet newer ones until they update ([Party](docs/party.md#formations)).
@@ -85,18 +85,28 @@ Lv 1 に達すると I 段階、Lv 3 で II 段階、Lv 5 で III 段階が店
85
85
  身につけます。エージェントのページか**バッグ**で付け替え、バッグでは要らないものを売れます。装備で変わるのは戦闘の数値だけで、
86
86
  レベルや経験値は教訓からだけ得られます。
87
87
 
88
+ ## 編成
89
+
90
+ パーティー画面の**編成**で**デッキ**を作れます。デッキは 1〜3 人のエージェントを戦う順番(先鋒・中堅・大将)に並べたチームで、
91
+ 名前をつけて 5 つまで持てます。枠の下の**控え**からエージェントを入れ、矢印かドラッグで順番を入れ替えます。控えのエージェントを
92
+ 枠にドラッグすると入れ替わります。何も変えないうちは、レベルの高い 3 人のデッキが 1 つあります(デッキができる前にチャンピオンを
93
+ 選んでいた場合は、そのエージェントとレベルの高い 2 人)。アリーナに出すデッキは、ここかアリーナのページの
94
+ **アリーナに出場させる**で選びます。デッキで変わるのは誰がどの順番で戦うかだけで、レベルは変わりません。
95
+
88
96
  ## アリーナ
89
97
 
90
98
  リーダーボードに参加している人は、パーティーを**アリーナ**に出すこともできます。アリーナは Innerlatch の独立したページです。
91
99
  Innerlatch のレールで**アリーナ**を開き、**アリーナに入る**を選びます(先にボードに参加します)。それからは同期のたびに、パーティーのキャラクター
92
- シートを公開します。コーディングエージェントそれぞれのレベル、ランク、クラス、6 つの能力値のレベルと、どのエージェントが
93
- チャンピオンか、ボードに表示する名前と画像、コンピューターを見分けるコード(名前ではなくハッシュ)です。教訓、プロジェクト、
100
+ シートを公開します。コーディングエージェントそれぞれのレベル、ランク、クラス、6 つの能力値のレベルと、出場させるチーム
101
+ (どのエージェントをどの順番で出すか)、ボードに表示する名前と画像、コンピューターを見分けるコード(名前ではなくハッシュ)です。教訓、プロジェクト、
94
102
  セッション、タイトル、プロンプトは送りません。
95
103
 
96
- アリーナはチャンピオン同士の一騎打ちです。チャンピオンは、アリーナのカードのタイルでほかを選ぶまでは、いちばんレベルの高い
97
- エージェントです。毎週、ほかのすべてのプレイヤーのチャンピオンと 1 回ずつ戦い、アリーナのページは勝利数と、各プレイヤーの
98
- チャンピオンとそのレベルを並べます。ライバルをクリックすると一騎打ちを見られます。左があなたのチャンピオン、右が相手で、
99
- ラウンドごとに進み、同じ戦いが下に記録として書かれ、その下に両方のパーティーが並びます。対戦は公開された 2 つのシートとその週から、あなた自身の
104
+ アリーナはチーム同士の勝ち抜き戦です。あなたのチームは、出場させているデッキです([編成](#編成))。毎週、ほかのすべての
105
+ プレイヤーのチームと 1 回ずつ戦います。先鋒どうしがラウンドごとに 1 対 1 で戦い、勝ったほうは残りの HP のまま相手の次の
106
+ エージェントと戦います。40 ラウンドたっても決着しないときは、HP の残りが少ないほうが敗退します。先にエージェントがいなくなった
107
+ チームの負けです。1 人のデッキ同士なら、ふつうの一騎打ちです。アリーナのページは勝利数と、各プレイヤーのチームを並べます。
108
+ ライバルをクリックすると対戦を見られます。左があなたのチーム、右が相手で、同じ戦いが下に記録として書かれ、その下に両方の
109
+ パーティーが並びます。対戦は公開された 2 つのシートとその週から、あなた自身の
100
110
  コンピューターで計算されるので、2 人とも同じ結果を見ます。数字は自己申告で、記録ではなくゲームです。**アリーナを出る**と
101
111
  シートは削除され、ボードには残ります。ボードから抜けるとアリーナからも出ます。
102
112
 
@@ -18,7 +18,7 @@ Interlatch の Firebase プロジェクトから、公開されている求人
18
18
  このリクエストに含まれるのはプロジェクトの公開ウェブキーだけで、サインインを含め、あなたに関する情報は何も送りません。
19
19
  さらにアリーナのページで**アリーナに入る**と、同期のたびにパーティーのキャラクターシートを同じ Firebase プロジェクトに
20
20
  公開し、誰でも読めるようになります。コーディングエージェントそれぞれのレベル、ランク、クラス、6 つの能力値のレベルと、
21
- どのエージェントがチャンピオンか、ボードの名前と画像、コンピューターを見分けるハッシュです。教訓、プロジェクト、セッション、タイトル、プロンプトは送りません。
21
+ 出場させるチーム(どのエージェントをどの順番で出すか)、ボードの名前と画像、コンピューターを見分けるハッシュです。教訓、プロジェクト、セッション、タイトル、プロンプトは送りません。
22
22
  対戦は公開されたシートからあなたのコンピューターで計算されます。**アリーナを出る**(またはボードから抜ける)とシートは削除されます
23
23
  ([アリーナ](party.md#アリーナ))。
24
24
  claude.ai や ChatGPT のエクスポートを取り込むときはチャットだけを読み、アカウントのファイル(`users.json`、`user.json`)は開きません。
@@ -95,18 +95,29 @@ ADR Scroll from a decision. Use supplies and loot from the **Item** command in a
95
95
  (weapon, armor, charm): change them on the agent's page or in the **Bag**, where you can also sell what you don't need.
96
96
  Gear only changes battle numbers: levels and XP still come from lessons alone.
97
97
 
98
+ ## Formations
99
+
100
+ On the Party page, **Formation** builds **decks**: teams of one to three of your agents, in the order they fight
101
+ (lead, middle, anchor). Keep up to five, each with a name of your choice. Add an agent from the **Bench** below the
102
+ slots, reorder with the arrows or by dragging, and drag a bench agent onto a slot to swap it in. Until you change
103
+ anything, your party has one deck: your three highest-level agents (or, if you picked a champion before decks
104
+ existed, that agent and then the two highest). One deck is the one the Arena fields: pick it with **Field in the
105
+ Arena**, here or on the Arena page. A deck changes who fights and in what order, never a level.
106
+
98
107
  ## The arena
99
108
 
100
109
  People on the leaderboard can also put their parties in the **Arena**, a page of its own in Innerlatch. Open
101
110
  **Arena** in Innerlatch's rail and choose **Enter the arena** (you join the board first). From then on each sync publishes your party's
102
- character sheet: for each of your coding agents its level, tier, class and six attribute levels, and which agent
103
- is your champion, with the name and picture you show on the board and the code that tells your computers apart (a
111
+ character sheet: for each of your coding agents its level, tier, class and six attribute levels, and the team you
112
+ field (which agents, in what order), with the name and picture you show on the board and the code that tells your computers apart (a
104
113
  hash, not a name). No lessons, projects, sessions, titles or prompts go.
105
114
 
106
- The arena is champion against champion. Your champion is your highest-level agent until you pick another from the
107
- tiles on your arena card. Every week it duels every other player's champion once, and the Arena page ranks you by
108
- wins, with each player's champion and level. Click a rival to watch the duel: your champion on the left, theirs on
109
- the right, round by round, with the same fight written out as a log underneath and both full parties below. Each battle is worked out on your own
115
+ The arena is team against team, in a relay. Your team is the deck you field ([Formations](#formations)). Every
116
+ week it fights every other player's team once: the two leads fight one on one, round by round; whoever wins the bout
117
+ stays on, with the HP it has left, against the other side's next agent; a bout still going after 40 rounds puts out
118
+ the one with less of its HP left. The team that runs out of agents loses. A deck of one against a deck of one is a
119
+ plain duel. The Arena page ranks you by wins, with each player's team. Click a rival to watch the battle: your team on
120
+ the left, theirs on the right, with the same fight written out as a log underneath and both full parties below. Each battle is worked out on your own
110
121
  computer from the two published sheets and the week, so both players see the same result. The numbers are
111
122
  self-reported: it's a game, not a record. **Leave the arena** deletes your sheet; you stay on the board. Leaving the
112
123
  board leaves the arena too.
@@ -20,7 +20,7 @@ public job-market numbers (open roles per roadmap track) from Interlatch's Fireb
20
20
  That request carries the project's public web key and nothing about you, not even your sign-in.
21
21
  If you also **Enter the arena** on the Arena page, each sync publishes your party's character sheet to the same
22
22
  Firebase project, readable by anyone: for each of your coding agents its level, tier, class and six attribute
23
- levels, and which agent is your champion, with your board name and picture and a hash that tells your computers apart. No lessons, projects, sessions,
23
+ levels, and the team you field (which agents, in what order), with your board name and picture and a hash that tells your computers apart. No lessons, projects, sessions,
24
24
  titles or prompts. Battles are worked out on your computer from the published sheets; **Leave the arena** (or leaving
25
25
  the board) deletes your sheet ([The arena](party.md#the-arena)).
26
26
  Importing a claude.ai or ChatGPT export reads only the chats, never the account files (`users.json`, `user.json`).
@@ -111,12 +111,14 @@ service cloud.firestore {
111
111
  && data.at == request.time;
112
112
  }
113
113
 
114
- // The arena: a party's sheet (src/interlatch/sharing/arena.py writes it). Fields (all required, no others):
115
- // name, photo, machine: as for players; v: int, the sheet format (2);
114
+ // The arena: a party's sheet (src/interlatch/sharing/arena.py writes it). Fields (all required but lineup, no others):
115
+ // name, photo, machine: as for players; v: int, the sheet format (2, or 3 with a lineup);
116
116
  // agents: a map of the coding agents, each {level: int 1-200, tier: novice|adept|expert|master,
117
117
  // cls: null or a track id (<= 64), attrs: {craft, data, ops, guard, arcana, lore: int}}: always the first
118
118
  // five, and opencode from Interlatch versions that know it (sheets from older ones have no opencode);
119
- // champion: which of them fights for the party;
119
+ // champion: which of them fights first for the party;
120
+ // lineup (v 3 only, and required there): the deck it fields, 1-3 of its agents in order, each once, the champion
121
+ // first (v 2 sheets, from versions before decks, field the champion alone);
120
122
  // at: timestamp, the server time of the upload (request.time). No lessons, projects or titles.
121
123
  // Cheap on purpose: a request may evaluate at most 1000 expressions, and the full bounds on every attribute (as
122
124
  // before) went over it with five agents, so no sheet could be written. The values' bounds are left to the readers,
@@ -132,17 +134,23 @@ service cloud.firestore {
132
134
  && a.attrs.guard is int && a.attrs.arcana is int && a.attrs.lore is int;
133
135
  }
134
136
 
137
+ function isValidArenaLineup(data) {
138
+ return data.lineup is list && data.lineup.size() >= 1 && data.lineup.size() <= 3
139
+ && data.lineup.toSet().size() == data.lineup.size() && data.lineup.hasOnly(data.agents.keys())
140
+ && data.lineup[0] == data.champion;
141
+ }
142
+
135
143
  function isValidArena(data) {
136
144
  let fields = ['name', 'photo', 'machine', 'v', 'agents', 'champion', 'at'];
137
145
  let agents = ['claude', 'codex', 'copilot', 'bob', 'antigravity', 'opencode'];
138
146
  let required = ['claude', 'codex', 'copilot', 'bob', 'antigravity'];
139
- return hasOnlyAllowedFields(fields) && hasRequiredFields(fields)
147
+ return hasOnlyAllowedFields(fields.concat(['lineup'])) && hasRequiredFields(fields)
140
148
  && data.name is string && data.name.size() >= 1 && data.name.size() <= 64
141
149
  && data.photo is string && data.photo.size() <= 500
142
150
  && (data.photo == ''
143
151
  || data.photo.matches('^https://(lh3[.]googleusercontent[.]com|avatars[.]githubusercontent[.]com)/[^\\s]+$'))
144
152
  && data.machine is string && data.machine.matches('^[A-Za-z0-9-]{8,64}$')
145
- && data.v == 2
153
+ && ((data.v == 2 && !('lineup' in data)) || (data.v == 3 && isValidArenaLineup(data)))
146
154
  && data.champion in agents
147
155
  && data.agents is map && data.agents.keys().hasOnly(agents) && data.agents.keys().hasAll(required)
148
156
  && isValidArenaAgent(data.agents.claude) && isValidArenaAgent(data.agents.codex)
@@ -373,6 +373,11 @@ JA: dict[str, str] = {
373
373
  "that doesn't go in this slot": "それはこの装備欄には付けられません",
374
374
  "only consumables and loot can be used": "使えるのは消耗品と戦利品だけです",
375
375
  "none left": "もう残っていません",
376
+ # ---- formations (formation.py)
377
+ "no such deck": "そのデッキはありません",
378
+ "a party holds at most 5 decks": "デッキは 5 つまでです",
379
+ "a party keeps at least one deck": "デッキは最低 1 つ必要です",
380
+ "a deck holds one to three of your agents, each once": "デッキには 1〜3 人のエージェントを、重複なしで入れてください",
376
381
  "{tier} {noun} of {module}": "{tier}の{noun}({module})",
377
382
  "+{atk} attack, and +{skill} to the {module} skill, in battle only": "攻撃 +{atk}、{module} のスキル +{skill}(戦闘中のみ)",
378
383
  "+{hp} HP, and {cut}% less damage taken, in battle only": "HP +{hp}、受けるダメージ {cut}% 減(戦闘中のみ)",
@@ -23,8 +23,8 @@ def leaderboard(ctx: Context, q: dict) -> dict:
23
23
  try:
24
24
  out["rows"] = arena.board(me.get("uid") if me.get("arena") else None)
25
25
  out["week"] = arena.week()
26
- if me.get("signed_in"): # who this computer fields (picked, or the highest level)
27
- out["me"] = {**me, "champion": arena.champion(ctx.cfg, ctx.conn, lb.read_state(ctx.cfg))}
26
+ if me.get("signed_in"): # the team this computer fields, and the decks it can pick from
27
+ out["me"] = {**me, **_fielding(ctx)}
28
28
  except lb.LeaderboardError as e:
29
29
  out["error"] = str(e)
30
30
  return out
@@ -101,13 +101,20 @@ def action_arena(ctx: Context, which: str) -> dict:
101
101
  return lb.status(ctx.cfg)
102
102
 
103
103
 
104
- def action_arena_champion(ctx: Context, body: dict) -> dict:
105
- """Field another champion: one of the five coding agents; sent now when in the arena."""
104
+ def _fielding(ctx: Context) -> dict:
106
105
  from ....sharing import arena
107
106
  from ....sharing import leaderboard as lb
108
107
 
109
- arena.pick(ctx.cfg, ctx.conn, body.get("agent"))
110
- return {**lb.status(ctx.cfg), "champion": arena.champion(ctx.cfg, ctx.conn, lb.read_state(ctx.cfg))}
108
+ return arena.fielding(ctx.cfg, ctx.conn, lb.read_state(ctx.cfg))
109
+
110
+
111
+ def action_arena_deck(ctx: Context, body: dict) -> dict:
112
+ """Field another of this computer's decks; sent now when in the arena."""
113
+ from ....sharing import arena
114
+ from ....sharing import leaderboard as lb
115
+
116
+ arena.pick(ctx.cfg, ctx.conn, body.get("deck"))
117
+ return {**lb.status(ctx.cfg), **_fielding(ctx)}
111
118
 
112
119
 
113
120
  def action_leaderboard_join(ctx: Context) -> dict:
@@ -47,7 +47,25 @@ def party(ctx: Context, viewer: dict | None, *, here: bool) -> dict:
47
47
  return {"signed_in": False, **gate, **({"preview": pt.preview(ctx.conn, _here_id(ctx))} if here else {})}
48
48
  data = pt.compute(ctx.conn, _here_id(ctx), viewer)
49
49
  # who's hiring for the classes it shows: read from Firebase only now, signed in (the numbers are public)
50
- return {"signed_in": True, **gate, "who": me, **data, "market": pt.market_block(pt.payload_tracks(data))}
50
+ return {"signed_in": True, **gate, "who": me, **data, "market": pt.market_block(pt.payload_tracks(data)),
51
+ "formations": _formations(ctx, viewer, data)}
52
+
53
+
54
+ def _levels(data: dict) -> dict[str, int]:
55
+ return {a["agent"]: a["level"] for a in data.get("agents") or []}
56
+
57
+
58
+ def _lead(ctx: Context, viewer: dict | None):
59
+ """The champion picked on this computer before decks existed: the first of the deck made for its party."""
60
+ from ....sharing import leaderboard as lb
61
+
62
+ return (lb.read_state(ctx.cfg) or {}).get("champion") if viewer is None else None
63
+
64
+
65
+ def _formations(ctx: Context, viewer: dict | None, data: dict) -> dict:
66
+ from ....knowledge import formation
67
+
68
+ return formation.formations(ctx.conn, viewer, _levels(data), _lead(ctx, viewer))
51
69
 
52
70
 
53
71
  def _refused() -> tuple[dict, int]:
@@ -160,6 +178,44 @@ def action_party_bag(ctx: Context, viewer: dict | None, what: str, body: dict, *
160
178
  return {"bag": got}, 200
161
179
 
162
180
 
181
+ def action_party_deck(ctx: Context, viewer: dict | None, body: dict, *, here: bool) -> tuple[dict, int]:
182
+ """POST /api/party/deck: add a deck (no "id"), rename it or change its members in order ("name", "members"),
183
+ delete it ("delete": true), or field it in the Arena ("arena": true, this computer's own party only). Answers the
184
+ decks; when the team this computer fields changed and it is in the arena, the sheet is sent now."""
185
+ from ....knowledge import formation
186
+ from ....knowledge import party as pt
187
+
188
+ if who(ctx, viewer) is None:
189
+ return _refused()
190
+ deck = body.get("id")
191
+ levels = _levels(pt.compute(ctx.conn, _here_id(ctx), viewer))
192
+ lead = _lead(ctx, viewer)
193
+ before = formation.formations(ctx.conn, viewer, levels, lead)
194
+ try:
195
+ if deck is not None and not isinstance(deck, str):
196
+ raise pt.PartyError(formation.NO_DECK)
197
+ if body.get("delete"):
198
+ got = formation.delete(ctx.conn, viewer, levels, deck, lead=lead)
199
+ elif body.get("arena"):
200
+ if viewer is not None: # a hub person's party never enters the arena
201
+ raise pt.PartyError(formation.NO_DECK)
202
+ got = formation.field(ctx.conn, viewer, levels, deck, lead=lead)
203
+ else:
204
+ got = formation.save(ctx.conn, viewer, levels, deck, name=body.get("name"), members=body.get("members"),
205
+ lead=lead)
206
+ except pt.PartyError as exc:
207
+ return {"error": tr(str(exc))}, 400
208
+ if viewer is None and _fielded(before) != _fielded(got):
209
+ from ....sharing import arena
210
+
211
+ arena.resend(ctx.cfg, ctx.conn)
212
+ return {"formations": got}, 200
213
+
214
+
215
+ def _fielded(f: dict) -> list[str]:
216
+ return next((d["members"] for d in f["decks"] if d["id"] == f["arena"]), [])
217
+
218
+
163
219
  def action_party_quest(ctx: Context, viewer: dict | None, body: dict, *, here: bool) -> tuple[dict, int]:
164
220
  """Mark a quest done or not, in the viewer's own log; answers the party as it is now."""
165
221
  from ....knowledge import party as pt
@@ -28,10 +28,10 @@ LEGACY_SESSION_COOKIE = "chronicle_session" # the same, signed in before the re
28
28
  PROXY_HEADERS = ("X-Forwarded-For", "Tailscale-User-Login", "X-Forwarded-Proto", "X-Real-IP", "Forwarded")
29
29
  HUB_PUSH = ("/api/hub/file", "/api/hub/sessions", "/api/hub/analyses", "/api/hub/done", "/api/hub/hello",
30
30
  "/api/hub/withdraw")
31
- # POSTs any person signed in to a hub may make, not only admins: their own Party quest log, goal, cleared encounters
32
- # and bag (routes.PARTY decides)
31
+ # POSTs any person signed in to a hub may make, not only admins: their own Party quest log, goal, cleared encounters,
32
+ # bag and decks (routes.PARTY decides)
33
33
  PARTY_OWN = ("/api/party/quest", "/api/party/goal", "/api/party/cleared", "/api/party/buy", "/api/party/sell",
34
- "/api/party/equip", "/api/party/use")
34
+ "/api/party/equip", "/api/party/use", "/api/party/deck")
35
35
 
36
36
  log = logging.getLogger("interlatch.server")
37
37
  SIGNIN_HELP = ("Ask an admin of this hub for a new invite, or open the hub's dashboard again from your own Interlatch "
@@ -293,8 +293,8 @@ def _arena(h, ctx, m, body): # what this computer publishes about its party: on
293
293
  from ...sharing.leaderboard import LeaderboardError
294
294
 
295
295
  try:
296
- if m.group(1) == "champion":
297
- return h._json(api.leaderboard.action_arena_champion(ctx, _body(body)))
296
+ if m.group(1) == "deck":
297
+ return h._json(api.leaderboard.action_arena_deck(ctx, _body(body)))
298
298
  return h._json(api.leaderboard.action_arena(ctx, m.group(1)))
299
299
  except LeaderboardError as e:
300
300
  return h._json({"error": str(e)}, 502)
@@ -311,7 +311,9 @@ def _party_sign(h, ctx, m, body): # the Party's Google sign-in: the leaderboard
311
311
  return h._json({"error": str(e)}, 502)
312
312
 
313
313
 
314
- def _party_own(h, ctx, m, body): # the viewer's own quest log, goal, cleared encounters and bag
314
+ def _party_own(h, ctx, m, body): # the viewer's own quest log, goal, cleared encounters, bag and decks
315
+ if m.group(1) == "deck":
316
+ return h._json(*api.party.action_party_deck(ctx, h.viewer, _body(body), here=h._from_here()))
315
317
  if m.group(1) in ("buy", "sell", "equip", "use"):
316
318
  return h._json(*api.party.action_party_bag(ctx, h.viewer, m.group(1), _body(body), here=h._from_here()))
317
319
  act = {"quest": api.party.action_party_quest, "goal": api.party.action_party_goal,
@@ -369,11 +371,11 @@ def _suggestions_refresh(h, ctx, m, body):
369
371
  POST: list[Route] = [
370
372
  Route("/api/sync", ANYONE, lambda h, ctx, m, b: h._json({"started": api.status.action_sync(ctx)})),
371
373
  Route(r"/api/leaderboard/(signin|finish|cancel|join|send|name|leave)", LEADERBOARD, _leaderboard),
372
- Route(r"/api/leaderboard/arena/(join|leave|champion)", LEADERBOARD, _arena),
374
+ Route(r"/api/leaderboard/arena/(join|leave|deck)", LEADERBOARD, _arena),
373
375
  # a Google sign-in kept on this computer: only from here
374
376
  Route(r"/api/party/(signin|signout)", HERE, _party_sign),
375
377
  # the viewer's own quest log: from here, or any person signed in to a hub, not only admins (handler.PARTY_OWN)
376
- Route(r"/api/party/(quest|goal|cleared|buy|sell|equip|use)", PARTY, _party_own),
378
+ Route(r"/api/party/(quest|goal|cleared|buy|sell|equip|use|deck)", PARTY, _party_own),
377
379
  Route(r"/api/artifacts/(\d+)/reveal", LOCAL_FILES, _reveal),
378
380
  # a password: at the hub itself, or an admin of a hub with people
379
381
  Route("/api/team-store/test", OWNER, lambda h, ctx, m, b: h._json(api.hub_admin.action_team_store_test(ctx, b))),
@@ -1,10 +1,13 @@
1
- """The Arena: champion against champion. Two published party sheets each name one of their five agents as champion,
2
- and the two champions fight a deterministic duel, so everyone computes the same result for the same pair and week
3
- (sharing/arena.py publishes and reads the sheets). Pure functions: no clock, no global random.
1
+ """The Arena: team against team. Two published party sheets each field a lineup, one to TEAM_MAX of their agents in
2
+ order (a deck from the Party page, knowledge/formation.py), and the two lineups fight a deterministic relay, so everyone
3
+ computes the same result for the same pair and week (sharing/arena.py publishes and reads the sheets). Pure functions:
4
+ no clock, no global random.
4
5
 
5
6
  A sheet holds, per coding agent, its level (1..200), tier, class (a track id or None) and six attribute levels (0..100),
6
- and which agent is the champion: nothing about any lesson. Stats come from those (stats()); a duel is rounds of turns
7
- by speed until one champion falls or MAX_ROUNDS pass (then the one with more of its HP left wins).
7
+ its lineup, and its champion (the lineup's first): nothing about any lesson. Stats come from those (stats()). Each side's
8
+ first agent fights the other's in a bout: rounds of turns by speed until one falls or MAX_ROUNDS pass (then the one with
9
+ less of its HP left is out). The one left standing stays on, with the HP it has, against the next agent of the side
10
+ that lost one; the side that runs out of agents loses. A lineup of one against a lineup of one is a plain duel.
8
11
  """
9
12
 
10
13
  from __future__ import annotations
@@ -16,13 +19,14 @@ from datetime import date
16
19
 
17
20
  from ..core.agents import CODING_AGENTS
18
21
 
19
- SHEET_V = 2 # 2: a champion
22
+ SHEET_V = 3 # 2: a champion; 3: a lineup (its first is the champion)
20
23
  ATTRS = ("craft", "data", "ops", "guard", "arcana", "lore")
21
24
  TIERS = ("novice", "adept", "expert", "master")
22
25
  LEVEL_MAX, ATTR_MAX = 200, 100
23
- MAX_ROUNDS = 40
26
+ TEAM_MAX = 3 # agents in a lineup
27
+ MAX_ROUNDS = 40 # a bout's
24
28
  LOG_MAX = 400
25
- HEAL_EVERY = 3 # a champion with lore >= HEAL_LORE heals itself on every 3rd turn of its own
29
+ HEAL_EVERY = 3 # a fighter with lore >= HEAL_LORE heals itself on every 3rd turn of its own
26
30
  HEAL_LORE = 10
27
31
  SPECIALS = {"adept": "Second wind", "expert": "Focus", "master": "Overdrive"}
28
32
  OVERDRIVE = 2.5 # the master's special: one hit this much harder
@@ -65,6 +69,14 @@ def champion_of(agents: dict, picked) -> str:
65
69
  return picked if picked in CODING_AGENTS else default_champion(agents)
66
70
 
67
71
 
72
+ def lineup_of(agents: dict, lineup, champion=None) -> list[str]:
73
+ """`lineup` as the rules take it: distinct coding agents in its order, at most TEAM_MAX; none of them, the champion
74
+ alone (a sheet without a lineup)."""
75
+ got = list(dict.fromkeys(a for a in lineup if isinstance(a, str) and a in CODING_AGENTS))[:TEAM_MAX] \
76
+ if isinstance(lineup, list) else []
77
+ return got or [champion_of(agents, champion)]
78
+
79
+
68
80
  def power(agents: dict) -> int:
69
81
  """A party's power, as the board shows it: the sum of its agents' levels."""
70
82
  return sum(clean_agent(agents.get(n)).get("level", 1) for n in CODING_AGENTS)
@@ -82,15 +94,15 @@ def seed_for(week: str, uid_a: str, uid_b: str) -> str:
82
94
 
83
95
 
84
96
  def match(uid_1: str, sheet_1: dict, uid_2: str, sheet_2: dict, week: str) -> dict:
85
- """This week's duel between two players (sheets: {"agents", "champion"}): sides a and b are the two uids sorted,
86
- so whichever of them asks, it is the same duel. Adds "a" and "b" (their uids) and "winner_uid"."""
97
+ """This week's battle between two players (sheets: {"agents", "lineup", "champion"}): sides a and b are the two
98
+ uids sorted, so whichever of them asks, it is the same battle. Adds "a" and "b" (their uids) and "winner_uid"."""
87
99
  (ua, sa), (ub, sb) = sorted(((uid_1, sheet_1), (uid_2, sheet_2)), key=lambda p: p[0])
88
100
  out = battle(sa, sb, seed_for(week, ua, ub))
89
101
  return {**out, "a": ua, "b": ub, "winner_uid": ua if out["winner"] == "a" else ub}
90
102
 
91
103
 
92
104
  def standings(sheets: dict[str, dict], week: str) -> dict[str, dict]:
93
- """{uid: {"wins", "losses"}}: every champion against every other this week (sheets: {uid: sheet})."""
105
+ """{uid: {"wins", "losses"}}: every lineup against every other this week (sheets: {uid: sheet})."""
94
106
  out = {u: {"wins": 0, "losses": 0} for u in sheets}
95
107
  uids = sorted(sheets)
96
108
  for i, ua in enumerate(uids):
@@ -101,9 +113,9 @@ def standings(sheets: dict[str, dict], week: str) -> dict[str, dict]:
101
113
  return out
102
114
 
103
115
 
104
- # ------------------------------------------------------------------ the duel
116
+ # ------------------------------------------------------------------ the battle
105
117
  def stats(agent: dict) -> dict:
106
- """A champion's stats from its level and attribute levels."""
118
+ """A fighter's stats from its level and attribute levels."""
107
119
  a = clean_agent(agent)
108
120
  lv, at = a["level"], a["attrs"]
109
121
  return {"hp": 20 + 4 * lv + 2 * at["guard"], "atk": 5 + at["craft"] + at["arcana"] // 2,
@@ -112,16 +124,18 @@ def stats(agent: dict) -> dict:
112
124
 
113
125
 
114
126
  def battle(a_sheet: dict, b_sheet: dict, seed: str) -> dict:
115
- """`{"winner": "a"|"b", "rounds", "log", "units"}`: side a's champion against side b's, from `seed` (hex)."""
127
+ """`{"winner": "a"|"b", "rounds", "log", "champions", "lineups", "units"}`: side a's lineup against side b's, bout
128
+ after bout, from `seed` (hex). Round numbers run on across bouts; a newcomer's entry is an "in" event, and a fighter
129
+ out of time an "out" one."""
116
130
  rng = random.Random(int(seed, 16))
117
- units = {}
131
+ teams: dict[str, list[dict]] = {}
118
132
  for side, sheet in (("a", a_sheet), ("b", b_sheet)):
119
133
  agents = (sheet or {}).get("agents") or {}
120
- name = champion_of(agents, (sheet or {}).get("champion"))
121
- s = stats(agents.get(name))
122
- uid = f"{side}:{name}"
123
- units[side] = {**s, "id": uid, "agent": name, "max": s["hp"], "turns": 0, "special": False, "focus": False,
124
- "key": rng.random()}
134
+ teams[side] = []
135
+ for i, name in enumerate(lineup_of(agents, (sheet or {}).get("lineup"), (sheet or {}).get("champion"))):
136
+ s = stats(agents.get(name))
137
+ teams[side].append({**s, "id": f"{side}:{name}", "agent": name, "order": i, "max": s["hp"], "turns": 0,
138
+ "special": False, "focus": False, "out": False, "key": rng.random()})
125
139
  log: list[dict] = []
126
140
 
127
141
  def emit(r, t, src, dst, n, hp, name=None):
@@ -139,7 +153,7 @@ def battle(a_sheet: dict, b_sheet: dict, seed: str) -> dict:
139
153
  elif not dst["special"] and dst["hp"] * 2 < dst["max"] and dst["tier"] in SPECIALS:
140
154
  special(r, dst, src)
141
155
 
142
- def special(r, u, foe): # once per duel, when the champion first drops below half its HP
156
+ def special(r, u, foe): # once per battle, when the fighter first drops below half its HP
143
157
  u["special"] = True
144
158
  name = SPECIALS[u["tier"]]
145
159
  if u["tier"] == "adept":
@@ -152,31 +166,44 @@ def battle(a_sheet: dict, b_sheet: dict, seed: str) -> dict:
152
166
  else: # one hit, OVERDRIVE times harder
153
167
  hurt(r, u, foe, max(1, int(roll(u, foe) * OVERDRIVE * rng.uniform(0.85, 1.15))), "special", name)
154
168
 
155
- winner, rounds = None, 0
156
- for r in range(1, MAX_ROUNDS + 1):
157
- rounds = r
158
- for u in sorted(units.values(), key=lambda x: (-x["spd"], x["key"])):
159
- foe = units["b" if u["id"].startswith("a:") else "a"]
160
- if u["hp"] <= 0 or foe["hp"] <= 0:
161
- continue
162
- u["turns"] += 1
163
- if u["heal"] and u["turns"] % HEAL_EVERY == 0 and u["hp"] < u["max"]:
164
- gain = min(u["max"] - u["hp"], u["heal"])
165
- u["hp"] += gain
166
- emit(r, "heal", u["id"], u["id"], gain, u["hp"])
167
- crit = rng.random() * 100 < u["crit"]
168
- dmg = max(1, int(roll(u, foe) * (2 if crit else 1) * (2 if u["focus"] else 1) * rng.uniform(0.85, 1.15)))
169
- u["focus"] = False
170
- hurt(r, u, foe, dmg, "crit" if crit else "hit")
171
- if units["a"]["hp"] <= 0 or units["b"]["hp"] <= 0:
172
- winner = "a" if units["a"]["hp"] > 0 else "b"
173
- break
174
- if winner is None: # time: the one with more of its HP left; level with it, the seed decides
175
- left = {s: units[s]["hp"] / units[s]["max"] for s in "ab"}
176
- winner = "a" if left["a"] > left["b"] else "b" if left["b"] > left["a"] else rng.choice("ab")
169
+ down = lambda u: u["hp"] <= 0 or u["out"] # noqa: E731
170
+ ia = ib = rounds = 0
171
+ while ia < len(teams["a"]) and ib < len(teams["b"]):
172
+ pair = (teams["a"][ia], teams["b"][ib])
173
+ if rounds: # a later bout: who stepped in
174
+ for u in pair:
175
+ if not u["turns"]:
176
+ emit(rounds, "in", u["id"], None, 0, u["hp"])
177
+ for _ in range(MAX_ROUNDS):
178
+ rounds += 1
179
+ for u in sorted(pair, key=lambda x: (-x["spd"], x["key"])):
180
+ foe = pair[1] if u is pair[0] else pair[0]
181
+ if u["hp"] <= 0 or foe["hp"] <= 0:
182
+ continue
183
+ u["turns"] += 1
184
+ if u["heal"] and u["turns"] % HEAL_EVERY == 0 and u["hp"] < u["max"]:
185
+ gain = min(u["max"] - u["hp"], u["heal"])
186
+ u["hp"] += gain
187
+ emit(rounds, "heal", u["id"], u["id"], gain, u["hp"])
188
+ crit = rng.random() * 100 < u["crit"]
189
+ dmg = max(1, int(roll(u, foe) * (2 if crit else 1) * (2 if u["focus"] else 1) * rng.uniform(0.85, 1.15)))
190
+ u["focus"] = False
191
+ hurt(rounds, u, foe, dmg, "crit" if crit else "hit")
192
+ if pair[0]["hp"] <= 0 or pair[1]["hp"] <= 0:
193
+ break
194
+ else: # time: the one with less of its HP left is out; level with it, the seed decides
195
+ left = [u["hp"] / u["max"] for u in pair]
196
+ gone = pair[0] if left[0] < left[1] else pair[1] if left[1] < left[0] else rng.choice(pair)
197
+ gone["out"] = True
198
+ emit(rounds, "out", (pair[1] if gone is pair[0] else pair[0])["id"], gone["id"], 0, gone["hp"])
199
+ ia += down(pair[0])
200
+ ib += down(pair[1])
201
+ winner = "a" if ia < len(teams["a"]) else "b"
177
202
  log.append({"r": rounds, "t": "end", "src": None, "dst": None, "n": 0, "hp": 0, "name": winner})
178
203
  return {"winner": winner, "rounds": rounds, "log": log,
179
- "champions": {s: units[s]["agent"] for s in "ab"},
180
- # end_hp: where each champion ended, also when the log was cut short (LOG_MAX)
181
- "units": {u["id"]: {"hp": u["max"], "end_hp": u["hp"], "tier": u["tier"], "level": u["level"]}
182
- for u in units.values()}}
204
+ "champions": {s: teams[s][0]["agent"] for s in "ab"},
205
+ "lineups": {s: [u["agent"] for u in teams[s]] for s in "ab"},
206
+ # end_hp: where each fighter ended, also when the log was cut short (LOG_MAX); out: beaten on time
207
+ "units": {u["id"]: {"hp": u["max"], "end_hp": u["hp"], "tier": u["tier"], "level": u["level"],
208
+ "order": u["order"], "out": u["out"]}
209
+ for s in "ab" for u in teams[s]}}