interlatch 0.27.0__tar.gz → 0.27.2__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.
- {interlatch-0.27.0 → interlatch-0.27.2}/CHANGELOG.md +15 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/PKG-INFO +1 -1
- interlatch-0.27.2/changelog.d/hub-browser-sessions.md +4 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/analysis.md +5 -3
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/analysis.md +4 -3
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/bob.py +32 -1
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/settings.py +8 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/knowledge.py +2 -2
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/operations.py +4 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/recording.py +2 -2
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/setup.py +32 -2
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/common.py +17 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/hub_admin.py +8 -4
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/handler.py +8 -4
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/routes.py +1 -1
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/people.py +14 -5
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/app.css +15 -2
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/app.js +23 -3
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/ja.js +6 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_bob_analysis.py +29 -2
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_people.py +14 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_people_web.py +23 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_setup.py +36 -0
- interlatch-0.27.0/changelog.d/party-formations.md +0 -4
- {interlatch-0.27.0 → interlatch-0.27.2}/.github/dependabot.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.github/workflows/changelog.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.github/workflows/ci.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.github/workflows/docs.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.github/workflows/release.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.gitignore +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.pre-commit-config.yaml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/.python-version +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/CONTRIBUTING.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/LICENSE +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/README.ja.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/ROADMAP.ja.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/ROADMAP.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/SECURITY.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/careers/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/careers/boards.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/careers/pipeline.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/changelog.d/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/dev.sh +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/.dockerignore +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/.env.example +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/Caddyfile +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/Dockerfile +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/compose.yaml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docker/wheels/.gitignore +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/_site/hooks.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/cli.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/configuration.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/dashboard.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/demo/make_demo.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/demo/record_demo.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/design/ja/learning.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/design/learning.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/development.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/devices.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/diagrams/analysis.excalidraw.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/diagrams/architecture.excalidraw.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/docker.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/faq.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/images/demo.gif +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/images/home.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/images/map.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/images/palette.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/images/session.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/install.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/cli.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/configuration.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/dashboard.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/development.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/devices.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/docker.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/faq.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/install.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/join-a-hub.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/mcp.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/moving-from-chronicle.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/party.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/postgres.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/privacy.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/sources.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/suggestions.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/troubleshooting.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/ja/vscode.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/join-a-hub.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/mcp.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/moving-from-chronicle.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/party.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/postgres.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/privacy.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/sources.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/suggestions.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/troubleshooting.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/docs/vscode.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/.firebaserc +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/.gitignore +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/firebase.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/firestore.indexes.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/leaderboard/firestore.rules +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/mkdocs.yml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/art.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/filing.html +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/loops.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/loops.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/members.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/render.html +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/render.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/scenes-more.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/icons3d/scenes.html +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/Chronicle.icns +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/Interlatch.spec +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/build.sh +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/entitlements.plist +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/icon-3d.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/icon-blueprint-prompt.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/icon-blueprint.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/icon.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/interlatch_app.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/macos/make_icon.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/packaging/release.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/pyproject.toml +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/chronicle/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/chronicle/__main__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/chronicle/windows.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/__main__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/digest.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/language.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/queue.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/api.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/base.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/claude.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/codex.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/factory.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/process.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/runners/replies.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/screen.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/session.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/analysis/worker.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/application/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/application/records.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/application/search.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/application/workflows.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/artifacts/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/artifacts/files.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/artifacts/queries.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/cli.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/agents.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/artifact_records.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/config.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/diagrams.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/i18n/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/i18n/ja.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/machine.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/paths.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/pricing.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/process.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/redact.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/sessions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/core/util.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/analyses.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/archive.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/chat_import.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/deletion.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/history.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/memory.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/persistence.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/report.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/ingestion/service.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/archive.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/sharing.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/main.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/parser.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/exports/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/exports/diagrams.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/exports/markdown.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/exports/sessions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/exports/vault.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/mcp.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/artifacts.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/common.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/devices.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/knowledge.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/leaderboard.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/party.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/projects.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/sessions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/settings.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/sources.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/status.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/storage.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/api/team.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/app.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/assets.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/context.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/jobs.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/web/server.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/arena.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/bag.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/cases.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/formation.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/friction.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/glossary.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/instructions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/ladder.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/learning.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/market.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/party.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/reviews.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/roadmap.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/suggest.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/knowledge/synthesize.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/groups.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/labels.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/progress.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/routing.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/projects/systems.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/connectors.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/container.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/hooks.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/install.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/migrate.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/notify.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/platforms/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/platforms/desktop.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/platforms/menubar.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/platforms/windows.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/statusline.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/tailnet.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/runtime/update.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/access.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/arena.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/client.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/identity.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/leaderboard.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/market.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/news.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/projects.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/protocol.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/pull.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/push.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sharing/receiver.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/antigravity.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/bob.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/builder.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/chatgpt.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/chats.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/claude/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/claude/export.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/claude/files.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/claude/parser.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/codex/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/codex/cloud.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/codex/parser.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/copilot.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/discovery.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/opencode.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/sources/prompts.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/__init__.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/fts.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/kv.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/migrations.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/mirror.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/postgres.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/schema.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/sqlite.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/storage/team.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/AGENT-ICONS.txt +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/PLEX-OFL.txt +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/adventure.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-antigravity.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-bob.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-chatgpt.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-claude-ai.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-claude.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-codex.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-copilot.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/agent-opencode.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-book.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-camera.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-chip.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-clock.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-cloud.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-coins.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-container.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-dashboard.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-dex.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-document.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-flag.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-folder.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-gateway.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-gears.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-glitch.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-globe.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-laptop.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-log.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-lumi.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-magnifier.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-pip.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-plug.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-rack.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/art-tink.webp +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/battle.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/boot.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/favicon.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/i18n.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/icon-180.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/icon-192.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/icon-512.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/icon.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/index.html +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/innerlatch.css +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/innerlatch.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/learning.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/manifest.webmanifest +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/panel.css +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/panel.html +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/panel.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-mono-400.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-mono-500.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-mono-600.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-sans-400.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-sans-500.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-sans-600.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/plex-sans-700.woff2 +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/rough.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/scene.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/web/sprites.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/windows.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/antigravity_fixture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/codex_fixture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/conftest.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/copilot_fixture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/friction_fixture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/golden.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/cli.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/diagram.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/antigravity.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/bob.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/claude_code.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/codex.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/copilot.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/parsers/opencode.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/goldens/systems.json +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/opencode_fixture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_adventure_web.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_analysis.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_antigravity.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_architecture.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_arena.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_artifacts.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_bag.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_battle_web.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_boot_web.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_careers.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_case_files.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_cases.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_chat_import.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_cli_pins.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_codex.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_codex_analysis.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_codex_cloud.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_container.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_copilot_bob.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_dedicated_hub.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_dedupe.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_desktop.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_diagram.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_ee_boundary.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_formation.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_friction.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_glossary.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_group_sharing.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_home_agents.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_accept.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_news.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_projects.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_projects_listed.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_purge.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_hub_sharing_race.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_i18n_web.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_ingest.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_instructions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_integration.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_ladder.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_language.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_leaderboard.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_learning_glance.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_learning_interests.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_learning_web.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_leave_project.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_legacy_package.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_map_goldens.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_market.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_menubar.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_migrate.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_mirror.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_notify.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_opencode.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_parser.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_parser_goldens.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_party.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_party_map.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_people_hub.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_project_groups.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_project_progress.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_providers.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_queue.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_regressions.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_release.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_roadmap_catalog.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_route_matrix.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_screen.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_security.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_statusline.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_suggest.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_suggest_cli.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_systems.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_team.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_team_home.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_update.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/test_windows.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/tests/windows_smoke.py +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/uv.lock +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/.vscodeignore +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/LICENSE +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/README.md +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/extension.js +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/media/icon.png +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/media/interlatch.svg +0 -0
- {interlatch-0.27.0 → interlatch-0.27.2}/vscode-extension/package.json +0 -0
|
@@ -3,6 +3,21 @@
|
|
|
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.27.1 (2026-10-16)
|
|
7
|
+
|
|
8
|
+
- **IBM Bob as the analyzer:** a key that is only in your shell (`BOB_API_KEY`, or a provider's `*_API_KEY`) used to
|
|
9
|
+
leave sessions unanalyzed in the background while `interlatch install` called it ready; install now offers to save
|
|
10
|
+
it, and `interlatch status` says when background analysis can't see it. Bob is told it has no tools and a call that
|
|
11
|
+
reaches for one is tried once more, and failed knowledge bases are named by project instead of a cut-off path
|
|
12
|
+
([Analysis](docs/analysis.md)).
|
|
13
|
+
|
|
14
|
+
## 0.27.0 (2026-10-16)
|
|
15
|
+
|
|
16
|
+
- **Formations:** the Party page builds decks, teams of one to three agents in the order they fight, up to five
|
|
17
|
+
of them. The Arena fields the deck you pick, as a relay: lead against lead, and whoever wins a bout stays on with
|
|
18
|
+
the HP it has left. A deck of one is the old champion duel. Sheets now carry the lineup (format 3), so arena players
|
|
19
|
+
on older versions don't meet newer ones until they update ([Party](docs/party.md#formations)).
|
|
20
|
+
|
|
6
21
|
## 0.26.0 (2026-10-16)
|
|
7
22
|
|
|
8
23
|
- **Adventure:** a new page in Innerlatch puts every lesson that counts on a pixel world map, one region per attribute
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: interlatch
|
|
3
|
-
Version: 0.27.
|
|
3
|
+
Version: 0.27.2
|
|
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
|
+
- **One row per browser on Team › People:** opening the hub from your own dashboard again no longer leaves the
|
|
2
|
+
browser's previous session behind, so the same browser stops piling up as more and more rows. Older sessions already
|
|
3
|
+
there fold into one line under the browser, with one **Revoke them**, and the People table stacks each person on a
|
|
4
|
+
narrow window instead of cutting off its buttons.
|
|
@@ -54,9 +54,11 @@ instance, have no cache split (so no cost estimate), Bob tasks have no per-call
|
|
|
54
54
|
call is thrown away and the analysis counts as failed.
|
|
55
55
|
- **IBM Bob** (`backend = "bob"`): Bob Shell's `bob run --format stream-json --max-turns 1 --disable-mcp
|
|
56
56
|
--disable-subagents`, with every tool group disabled, in a throwaway workspace whose custom mode holds
|
|
57
|
-
Interlatch's instructions and no tools; as with Codex, a reply that follows any tool call is thrown away
|
|
58
|
-
runs need a Bob API key (the app's sign-in isn't used): `interlatch config set-key
|
|
59
|
-
**Status › Analysis
|
|
57
|
+
Interlatch's instructions and no tools; as with Codex, a reply that follows any tool call is thrown away (and the
|
|
58
|
+
call tried once more). Headless runs need a Bob API key (the app's sign-in isn't used): `interlatch config set-key
|
|
59
|
+
bob` or the IBM Bob tab in **Status › Analysis**. `BOB_API_KEY` works too, but only from a terminal that has it:
|
|
60
|
+
background analysis and the dashboard don't see your shell's variables, so `interlatch install` offers to save it.
|
|
61
|
+
Bob picks its own model. It keeps each analysis in its own task list;
|
|
60
62
|
Interlatch doesn't import those as sessions.
|
|
61
63
|
|
|
62
64
|
- **A model provider's API** (`backend = "anthropic"`, `"bedrock"`, `"openai"`, `"azure"`, `"openrouter"`,
|
|
@@ -49,9 +49,10 @@ Codex、Copilot、Bob、Antigravity、OpenCode のセッションも、ログに
|
|
|
49
49
|
ないため、Interlatch はイベントストリームも読みます:ツール呼び出しのあとに来た応答は捨てられ、その分析は失敗として扱われます。
|
|
50
50
|
- **IBM Bob**(`backend = "bob"`):Bob Shell の `bob run --format stream-json --max-turns 1 --disable-mcp
|
|
51
51
|
--disable-subagents`。ツールグループをすべて無効にし、Interlatch の指示だけを持つツールなしのカスタムモードを置いた使い捨ての
|
|
52
|
-
ワークスペースで実行します。Codex
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
ワークスペースで実行します。Codex と同じく、ツール呼び出しのあとに来た応答は捨てます(その呼び出しは一度だけやり直します)。
|
|
53
|
+
ヘッドレス実行には Bob の API キーが必要です(アプリのサインインは使われません):`interlatch config set-key bob` または
|
|
54
|
+
**Status › Analysis** の IBM Bob タブ。`BOB_API_KEY` も使えますが、それを持つターミナルからだけです。バックグラウンドの
|
|
55
|
+
分析とダッシュボードはシェルの変数を見ないため、`interlatch install` が保存を提案します。モデルは Bob が選びます。Bob は各分析を自身のタスク一覧に残しますが、Interlatch はそれをセッションとして
|
|
55
56
|
取り込みません。
|
|
56
57
|
|
|
57
58
|
- **モデルプロバイダーの API**(`backend = "anthropic"`、`"bedrock"`、`"openai"`、`"azure"`、`"openrouter"`、
|
|
@@ -18,6 +18,19 @@ from .process import run_sleep_aware
|
|
|
18
18
|
BOB_TOOL_GROUPS = ("artifact", "browser", "edit", "execute", "mcp", "mode", "plan", "read", "skill", "subagent",
|
|
19
19
|
"subtask", "todo")
|
|
20
20
|
BOB_MODE = "interlatch-analyst"
|
|
21
|
+
# Bob reads tool calls out of the reply's text, so a model can write one with every tool group off; saying so up front
|
|
22
|
+
# makes that rare, and a call that does it anyway is retried once (TOOL_TRIES).
|
|
23
|
+
NO_TOOLS = ("You have no tools here: don't call or write one (no list_files, read_file or the like). Everything you "
|
|
24
|
+
"need is in the message; answer it directly.")
|
|
25
|
+
TOOL_TRIES = 2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _ToolUsed(LLMError):
|
|
29
|
+
"""The model called a tool, so its reply was discarded; `cost_usd` is what the call cost."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, msg: str, cost_usd: float):
|
|
32
|
+
super().__init__(msg)
|
|
33
|
+
self.cost_usd = cost_usd
|
|
21
34
|
|
|
22
35
|
|
|
23
36
|
class BobRunner(Runner):
|
|
@@ -57,6 +70,23 @@ class BobRunner(Runner):
|
|
|
57
70
|
|
|
58
71
|
def _call(self, prompt: str, *, system: str, model: str | None, effort: str | None, timeout: int | None,
|
|
59
72
|
max_budget_usd: float | None) -> tuple[LLMResult, str]:
|
|
73
|
+
spent = 0.0
|
|
74
|
+
for attempt in range(1, TOOL_TRIES + 1):
|
|
75
|
+
try:
|
|
76
|
+
res, text = self._run(prompt, system=f"{system}\n\n{NO_TOOLS}", timeout=timeout,
|
|
77
|
+
max_budget_usd=max_budget_usd)
|
|
78
|
+
except _ToolUsed as exc:
|
|
79
|
+
spent += exc.cost_usd
|
|
80
|
+
if attempt == TOOL_TRIES:
|
|
81
|
+
raise
|
|
82
|
+
continue
|
|
83
|
+
res.cost_usd += spent
|
|
84
|
+
return res, text
|
|
85
|
+
raise AssertionError("unreachable")
|
|
86
|
+
|
|
87
|
+
def _run(self, prompt: str, *, system: str, timeout: int | None,
|
|
88
|
+
max_budget_usd: float | None) -> tuple[LLMResult, str]:
|
|
89
|
+
"""One `bob run`."""
|
|
60
90
|
if not self.bin:
|
|
61
91
|
raise LLMError("bob executable not found (set analysis.bob_bin in config.toml)")
|
|
62
92
|
key, _ = self._key()
|
|
@@ -103,7 +133,8 @@ class BobRunner(Runner):
|
|
|
103
133
|
stats = (result or {}).get("stats") or {}
|
|
104
134
|
if acted or stats.get("tool_calls"):
|
|
105
135
|
names = ", ".join(sorted(set(tools))) or f"{stats.get('tool_calls')} call(s)"
|
|
106
|
-
raise
|
|
136
|
+
raise _ToolUsed(f"bob used a tool ({names}); the reply was discarded",
|
|
137
|
+
float(stats.get("session_costs") or 0.0))
|
|
107
138
|
text = "".join(parts)
|
|
108
139
|
if returncode != 0 or not result or result.get("status") != "success" or not text:
|
|
109
140
|
msg = "; ".join(errors) or str((result or {}).get("error") or "") or html.unescape(stderr).strip()
|
|
@@ -89,6 +89,14 @@ def key_source(cfg: Config, provider: str) -> tuple[str | None, str]:
|
|
|
89
89
|
return None, ""
|
|
90
90
|
|
|
91
91
|
|
|
92
|
+
def env_only_key(cfg: Config, provider: str) -> str | None:
|
|
93
|
+
"""The environment variable `provider`'s key comes from when it isn't stored. The background jobs and the
|
|
94
|
+
dashboard run from launchd (Task Scheduler on Windows) without the shell's variables, so they don't see it:
|
|
95
|
+
analysis then runs only from a terminal that has it."""
|
|
96
|
+
key, source = key_source(cfg, provider)
|
|
97
|
+
return source[1:] if key and source.startswith("$") else None
|
|
98
|
+
|
|
99
|
+
|
|
92
100
|
def settings(cfg: Config, provider: str) -> dict:
|
|
93
101
|
"""[providers.<provider>] from config.toml, keeping only known keys of the right type."""
|
|
94
102
|
raw = cfg.providers.get(provider) or {}
|
|
@@ -6,7 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
import json
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
-
from ..common import analyzer_label, escape_markup, load_cfg, new_console, open_archive
|
|
9
|
+
from ..common import analyzer_label, escape_markup, failed_label, load_cfg, new_console, open_archive
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def cmd_analyze(args) -> int:
|
|
@@ -59,7 +59,7 @@ def cmd_analyze(args) -> int:
|
|
|
59
59
|
export=export_markdown, progress=lambda m: print(f" {m}"))
|
|
60
60
|
print(f"{report.summary()} · analysis cost {human_cost(report.cost_usd)}")
|
|
61
61
|
for sid, err in report.failed:
|
|
62
|
-
print(f" ! {sid
|
|
62
|
+
print(f" ! {failed_label(sid)}: {err[:300]}", file=sys.stderr)
|
|
63
63
|
return 0 if not report.failed else 1
|
|
64
64
|
|
|
65
65
|
|
{interlatch-0.27.0 → interlatch-0.27.2}/src/interlatch/interfaces/cli/commands/operations.py
RENAMED
|
@@ -13,6 +13,7 @@ from ..common import load_cfg, new_console, open_archive, wait_for_port
|
|
|
13
13
|
def cmd_status(args) -> int:
|
|
14
14
|
from ....analysis.queue import PAUSE_KEY, QUEUE_REASON_LABEL, count_pending
|
|
15
15
|
from ....analysis.runners.factory import make_runner
|
|
16
|
+
from ....analysis.runners.settings import env_only_key
|
|
16
17
|
from ....core.util import human_cost, local_str
|
|
17
18
|
from ....runtime.install import UI_LABEL, hooks_installed, launchd_status, mcp_registered, statusline_installed
|
|
18
19
|
from ....runtime.statusline import WINDOW_LABEL, plan_usage
|
|
@@ -53,6 +54,9 @@ def cmd_status(args) -> int:
|
|
|
53
54
|
runner = make_runner(cfg)
|
|
54
55
|
console.print(f" {ok(mcp_registered())} MCP server registered {ok(runner.available())} analysis by {runner.label}: "
|
|
55
56
|
f"{runner.where() if runner.available() else runner.unavailable_reason()}")
|
|
57
|
+
if runner.available() and (env := env_only_key(cfg, runner.name)):
|
|
58
|
+
console.print(f" [yellow]![/] its key is only in ${env}: background analysis and the dashboard don't see it "
|
|
59
|
+
f"(`interlatch config set-key {runner.name}` saves it)", highlight=False)
|
|
56
60
|
console.print(f" sessions: {sum(counts.values())} · " + " · ".join(f"{k}: {v}" for k, v in sorted(counts.items())))
|
|
57
61
|
console.print(f" analysis queue: {pending['ready']} ready now, {pending['queued']} queued in all, {pending['held']} held · "
|
|
58
62
|
f"model {runner.model_label()} · auto={'on' if cfg.analysis.auto else 'off'} · spent {human_cost(spent)}")
|
|
@@ -7,7 +7,7 @@ import argparse
|
|
|
7
7
|
import json
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
|
-
from ..common import escape_markup, load_cfg, new_console, open_archive, push_now
|
|
10
|
+
from ..common import escape_markup, failed_label, load_cfg, new_console, open_archive, push_now
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def cmd_hook(args) -> int:
|
|
@@ -91,7 +91,7 @@ def cmd_work(args) -> int:
|
|
|
91
91
|
progress=lambda m: print(f" {m}"))
|
|
92
92
|
print(report.summary())
|
|
93
93
|
for sid, err in report.failed:
|
|
94
|
-
print(f" ! {sid
|
|
94
|
+
print(f" ! {failed_label(sid)}: {err[:200]}", file=sys.stderr)
|
|
95
95
|
return 0 if not report.failed else 1
|
|
96
96
|
|
|
97
97
|
|
|
@@ -8,7 +8,7 @@ import json
|
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
10
|
from ....core.config import set_config_value
|
|
11
|
-
from ..common import analyzer_label, load_cfg, new_console, open_archive, wait_for_port
|
|
11
|
+
from ..common import analyzer_label, failed_label, load_cfg, new_console, open_archive, wait_for_port
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def _ask(question: str, default: bool) -> bool:
|
|
@@ -222,7 +222,7 @@ def _analyze_now(cfg, console, ids: list[str], waiting: int, later: str) -> str:
|
|
|
222
222
|
f"{terms} glossary term{'s' * (terms != 1)}"
|
|
223
223
|
+ (f", Map themes for {themed} categor{'ies' if themed != 1 else 'y'}" if themed else ""), highlight=False)
|
|
224
224
|
for target, err in report.failed[:3]:
|
|
225
|
-
console.print(f" [red]![/] {target
|
|
225
|
+
console.print(f" [red]![/] {failed_label(target)}: {err[:200]}", highlight=False)
|
|
226
226
|
if report.paused_until:
|
|
227
227
|
from ....core.util import local_str
|
|
228
228
|
|
|
@@ -293,6 +293,8 @@ def cmd_install(args) -> int:
|
|
|
293
293
|
console.print("[bold]Interlatch setup[/]\n")
|
|
294
294
|
analyzer = _choose_analyzer(cfg, console, ask=ask, dry_run=args.dry_run, conn=conn, interactive=interactive,
|
|
295
295
|
choice=args.backend)
|
|
296
|
+
if analyzer:
|
|
297
|
+
_keep_env_key(cfg, console, analyzer, ask=ask, interactive=interactive)
|
|
296
298
|
if " -m " in exe:
|
|
297
299
|
console.print(f"[yellow]`interlatch` is not on PATH[/]; hooks will run `{exe}`. "
|
|
298
300
|
"`uv tool install interlatch` gives it a stable path.\n", highlight=False)
|
|
@@ -443,13 +445,37 @@ def _runner_for(cfg, name: str):
|
|
|
443
445
|
return make_runner(replace(cfg, analysis=replace(cfg.analysis, backend=name)))
|
|
444
446
|
|
|
445
447
|
|
|
448
|
+
def _keep_env_key(cfg, console, runner, *, ask, interactive: bool) -> None:
|
|
449
|
+
"""Offer to save the analyzer's API key when it is only in this shell's environment, which the background jobs
|
|
450
|
+
and the dashboard don't see. Never saved without asking."""
|
|
451
|
+
import os
|
|
452
|
+
|
|
453
|
+
from ....analysis.runners.settings import env_only_key, set_key
|
|
454
|
+
|
|
455
|
+
env = env_only_key(cfg, runner.name)
|
|
456
|
+
if not env:
|
|
457
|
+
return
|
|
458
|
+
if interactive and ask(f"Your {runner.label} key is in ${env}, which background analysis and the dashboard don't "
|
|
459
|
+
"see. Save it in ~/.interlatch, readable by you only?", True):
|
|
460
|
+
set_key(cfg, runner.name, os.environ[env])
|
|
461
|
+
console.print(f"Saved your {runner.label} key.\n", highlight=False)
|
|
462
|
+
return
|
|
463
|
+
console.print(f"[yellow]Your {runner.label} key is only in ${env}[/]: `interlatch analyze` works from this "
|
|
464
|
+
"terminal, but background analysis and the dashboard don't see it. "
|
|
465
|
+
f"`interlatch config set-key {runner.name}` saves it.\n", highlight=False)
|
|
466
|
+
|
|
467
|
+
|
|
446
468
|
def _backend_state(runner) -> str:
|
|
447
469
|
"""Whether `runner` can analyze now, in a few words, for the list install shows."""
|
|
448
470
|
import re
|
|
449
471
|
|
|
450
472
|
from ....analysis.runners.factory import AGENTS
|
|
473
|
+
from ....analysis.runners.settings import env_only_key
|
|
451
474
|
|
|
452
475
|
if runner.available():
|
|
476
|
+
env = env_only_key(runner.cfg, runner.name)
|
|
477
|
+
if env:
|
|
478
|
+
return f"ready in this terminal only · ${env}"
|
|
453
479
|
if runner.name in AGENTS:
|
|
454
480
|
return "ready · your Bob API key" if runner.name == "bob" else "ready · your own login and plan"
|
|
455
481
|
return f"ready · {runner.model_label()}" + (" on this computer" if runner.local() else "")
|
|
@@ -772,10 +798,14 @@ def _config_set(cfg, key: str | None, value: str | None) -> int:
|
|
|
772
798
|
print(f"[{section}] {name} = {literal}")
|
|
773
799
|
if key == "analysis.backend":
|
|
774
800
|
from ....analysis.runners.factory import make_runner
|
|
801
|
+
from ....analysis.runners.settings import env_only_key
|
|
775
802
|
|
|
776
803
|
runner = make_runner(load_config(cfg.home))
|
|
777
804
|
if not runner.available():
|
|
778
805
|
print(f"note: {runner.unavailable_reason()}; analysis waits until it is set up")
|
|
806
|
+
elif env := env_only_key(runner.cfg, runner.name):
|
|
807
|
+
print(f"note: the {runner.label} key is only in ${env}, which background analysis and the dashboard don't "
|
|
808
|
+
f"see; `interlatch config set-key {runner.name}` saves it")
|
|
779
809
|
return 0
|
|
780
810
|
|
|
781
811
|
|
|
@@ -22,6 +22,23 @@ def open_archive(cfg):
|
|
|
22
22
|
return connect(cfg.db_path)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def failed_label(target: str) -> str:
|
|
26
|
+
"""What a worker run failed on, as its error lines name it: a session's short id, a project's folder name (from a
|
|
27
|
+
knowledge base, "glossary:<path>" or "themes:<category>"), or the global playbook."""
|
|
28
|
+
from ...knowledge.synthesize import GLOBAL
|
|
29
|
+
|
|
30
|
+
kind, sep, rest = target.partition(":")
|
|
31
|
+
if sep and kind == "glossary":
|
|
32
|
+
return f"glossary {failed_label(rest)}"
|
|
33
|
+
if sep and kind == "themes":
|
|
34
|
+
return f"themes {rest}"
|
|
35
|
+
if target == GLOBAL:
|
|
36
|
+
return "global playbook"
|
|
37
|
+
if "/" in target or "\\" in target:
|
|
38
|
+
return target.replace("\\", "/").rstrip("/").rsplit("/", 1)[-1] or target
|
|
39
|
+
return target[:8]
|
|
40
|
+
|
|
41
|
+
|
|
25
42
|
def new_console():
|
|
26
43
|
"""A rich console for a command's output."""
|
|
27
44
|
from rich.console import Console
|
|
@@ -135,11 +135,12 @@ def action_team_store_save(ctx: Context, body: dict) -> dict:
|
|
|
135
135
|
return {"ok": True, "store": team_store_info(ctx), "status": team_store_status(ctx)}
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def people_info(ctx: Context) -> dict:
|
|
138
|
+
def people_info(ctx: Context, session: str | None = None) -> dict:
|
|
139
|
+
"""The People page; `session` is the viewer's own browser session, which the page marks as this browser."""
|
|
139
140
|
from ....sharing import people
|
|
140
141
|
from ....sharing.projects import hub_projects
|
|
141
142
|
|
|
142
|
-
return {"people": people.listing(ctx.conn), "shared_token": ctx.cfg.hub_shared_token,
|
|
143
|
+
return {"people": people.listing(ctx.conn, session), "shared_token": ctx.cfg.hub_shared_token,
|
|
143
144
|
"address": ctx.cfg.hub_address or None, "audit": people.audit_log(ctx.conn, 50),
|
|
144
145
|
"roles": list(people.ROLES), "projects": hub_projects(ctx.conn)}
|
|
145
146
|
|
|
@@ -212,8 +213,11 @@ def action_people(ctx: Context, verb: str, body: dict, by: dict | None, address:
|
|
|
212
213
|
people.set_projects(ctx.conn, pid, _projects_body(ctx, body), by=by)
|
|
213
214
|
elif verb == "remove":
|
|
214
215
|
people.remove(ctx.conn, pid, by=by)
|
|
215
|
-
elif verb == "revoke":
|
|
216
|
-
|
|
216
|
+
elif verb == "revoke": # one token, or several (a browser's older sessions, at once)
|
|
217
|
+
tokens = body.get("tokens") if isinstance(body.get("tokens"), list) else [body.get("token")]
|
|
218
|
+
if not tokens or len(tokens) > 500:
|
|
219
|
+
return {"error": tr("no such computer or browser session")}, 400
|
|
220
|
+
if not sum(people.revoke(ctx.conn, pid, str(x or ""), by=by) for x in tokens):
|
|
217
221
|
return {"error": tr("no such computer or browser session")}, 400
|
|
218
222
|
else:
|
|
219
223
|
return {"error": tr("not found")}, 404
|
|
@@ -129,14 +129,17 @@ def make_handler(app: App, port: int):
|
|
|
129
129
|
and (self.headers.get("X-Forwarded-Proto") or "").strip().lower() == "https")
|
|
130
130
|
|
|
131
131
|
def _session(self) -> str:
|
|
132
|
+
return next(iter(self._sessions()), "")
|
|
133
|
+
|
|
134
|
+
def _sessions(self) -> tuple[str, ...]:
|
|
135
|
+
"""The session cookies this browser sent: the current one first, then one from before the rename."""
|
|
132
136
|
from http.cookies import CookieError, SimpleCookie
|
|
133
137
|
|
|
134
138
|
try:
|
|
135
139
|
jar = SimpleCookie(self.headers.get("Cookie") or "")
|
|
136
140
|
except CookieError:
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
return morsel.value if morsel else ""
|
|
141
|
+
return ()
|
|
142
|
+
return tuple(m.value for m in (jar.get(SESSION_COOKIE), jar.get(LEGACY_SESSION_COOKIE)) if m and m.value)
|
|
140
143
|
|
|
141
144
|
def _session_cookie(self, value: str, max_age: int) -> str:
|
|
142
145
|
secure = "; Secure" if self._proxied_https() else ""
|
|
@@ -205,7 +208,8 @@ def make_handler(app: App, port: int):
|
|
|
205
208
|
try:
|
|
206
209
|
if wait:
|
|
207
210
|
raise people.PeopleError(TOO_MANY_CODES, seconds=wait) # the code isn't looked at
|
|
208
|
-
_person, session = people.open_browser(app.conn, code, label=self.headers.get("User-Agent")
|
|
211
|
+
_person, session = people.open_browser(app.conn, code, label=self.headers.get("User-Agent"),
|
|
212
|
+
replaces=self._sessions()) # this browser's old session ends
|
|
209
213
|
except people.PeopleError as exc:
|
|
210
214
|
if isinstance(exc, people.UnknownCode):
|
|
211
215
|
app.code_attempts.wrong(who)
|
|
@@ -179,7 +179,7 @@ def _unseen_suggestions(h, ctx, m, q):
|
|
|
179
179
|
|
|
180
180
|
GET: list[Route] = [
|
|
181
181
|
Route("/api/me", ANYONE, lambda h, ctx, m, q: h._json(h._me())),
|
|
182
|
-
Route("/api/people", ADMIN, lambda h, ctx, m, q: h._json(api.hub_admin.people_info(ctx))),
|
|
182
|
+
Route("/api/people", ADMIN, lambda h, ctx, m, q: h._json(api.hub_admin.people_info(ctx, h._session()))),
|
|
183
183
|
Route("/api/overview", ANYONE, lambda h, ctx, m, q: h._json(api.status.overview(ctx, q, limited=h.limited))),
|
|
184
184
|
# a person's own computer only
|
|
185
185
|
Route("/api/leaderboard", LEADERBOARD, lambda h, ctx, m, q: h._json(api.leaderboard.leaderboard(ctx, q))),
|
|
@@ -265,9 +265,11 @@ def _admins(conn: sqlite3.Connection) -> list[int]:
|
|
|
265
265
|
return [r[0] for r in conn.execute("SELECT id FROM people WHERE role = 'admin' AND removed_at IS NULL")]
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
def listing(conn: sqlite3.Connection) -> list[dict]:
|
|
269
|
-
"""Everyone on the hub, with their computers, open browser sessions and unused invites (no secrets).
|
|
268
|
+
def listing(conn: sqlite3.Connection, session: str | None = None) -> list[dict]:
|
|
269
|
+
"""Everyone on the hub, with their computers, open browser sessions and unused invites (no secrets). The browser
|
|
270
|
+
session `session` (the viewer's own cookie) is marked `this`."""
|
|
270
271
|
now = _iso(_now())
|
|
272
|
+
mine = _hash(session) if session else None
|
|
271
273
|
out = []
|
|
272
274
|
for p in conn.execute("SELECT * FROM people WHERE removed_at IS NULL ORDER BY role = 'admin' DESC, name"):
|
|
273
275
|
p = dict(p)
|
|
@@ -278,7 +280,8 @@ def listing(conn: sqlite3.Connection) -> list[dict]:
|
|
|
278
280
|
"WHERE t.person_id = ? AND t.revoked_at IS NULL ORDER BY t.created_at", (p["id"],))]
|
|
279
281
|
p["computers"] = [{"id": t["token_hash"][:16], "machine_id": t["machine_id"], "name": t["machine_name"] or t["label"],
|
|
280
282
|
"since": t["created_at"], "last_used": t["last_used"]} for t in toks if t["kind"] == "computer"]
|
|
281
|
-
p["browsers"] = [{"id": t["token_hash"][:16], "label": t["label"], "since": t["created_at"], "last_used": t["last_used"]
|
|
283
|
+
p["browsers"] = [{"id": t["token_hash"][:16], "label": t["label"], "since": t["created_at"], "last_used": t["last_used"],
|
|
284
|
+
**({"this": True} if t["token_hash"] == mine else {})}
|
|
282
285
|
for t in toks if t["kind"] == "browser" and not (t["expires_at"] and t["expires_at"] < now)]
|
|
283
286
|
p["invites"] = [{"expires_at": r[0], "created_at": r[1]} for r in conn.execute(
|
|
284
287
|
"SELECT expires_at, created_at FROM people_codes WHERE person_id = ? AND kind = 'invite' AND used_at IS NULL "
|
|
@@ -416,11 +419,17 @@ def join_computer(conn: sqlite3.Connection, code: str, machine_id: str, machine_
|
|
|
416
419
|
return p, token
|
|
417
420
|
|
|
418
421
|
|
|
419
|
-
def open_browser(conn: sqlite3.Connection, code: str, label: str | None = None
|
|
420
|
-
|
|
422
|
+
def open_browser(conn: sqlite3.Connection, code: str, label: str | None = None,
|
|
423
|
+
replaces: tuple[str, ...] = ()) -> tuple[dict, str]:
|
|
424
|
+
"""Redeem an invite or a sign-in code in a browser: (person, session secret for the cookie). `replaces` are the
|
|
425
|
+
sessions that browser's cookies held: the new cookie takes their place, so they end here rather than linger,
|
|
426
|
+
unused, as more rows on the People page until they expire. Only once the code was good: a wrong one ends nothing."""
|
|
421
427
|
p = _take_code(conn, code, ("invite", "signin"))
|
|
422
428
|
session = secrets.token_urlsafe(32)
|
|
423
429
|
now = _now()
|
|
430
|
+
for old in {s for s in replaces if s}:
|
|
431
|
+
conn.execute("UPDATE people_tokens SET revoked_at = ? WHERE token_hash = ? AND kind = 'browser' AND revoked_at IS NULL",
|
|
432
|
+
(_iso(now), _hash(old)))
|
|
424
433
|
conn.execute("INSERT INTO people_tokens(token_hash, person_id, kind, label, created_at, expires_at, last_used) "
|
|
425
434
|
"VALUES (?, ?, 'browser', ?, ?, ?, ?)",
|
|
426
435
|
(_hash(session), p["id"], (label or "")[:200] or None, _iso(now), _iso(now + timedelta(days=BROWSER_DAYS)), _iso(now)))
|
|
@@ -2064,8 +2064,21 @@ a.ar-tile-media.empty:hover { background: var(--accent-wash); }
|
|
|
2064
2064
|
.pp-table select { max-width: 150px; }
|
|
2065
2065
|
.pp-tok { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 1px 0; }
|
|
2066
2066
|
.pp-tok .icon { width: 13px; height: 13px; color: var(--muted); flex: none; }
|
|
2067
|
-
.pp-actions {
|
|
2068
|
-
.pp-actions .btn
|
|
2067
|
+
.pp-actions > div { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px; } /* a narrow table stacks them */
|
|
2068
|
+
.pp-actions .btn { white-space: nowrap; }
|
|
2069
|
+
.pp-older { padding-left: 19px; font-size: 12.5px; }
|
|
2070
|
+
/* a narrow card: each person as a block (name and role, then projects, computers and browsers, actions), no sideways scroll */
|
|
2071
|
+
.pp-wrap { container-type: inline-size; }
|
|
2072
|
+
@container (max-width: 720px) {
|
|
2073
|
+
.pp-table thead { display: none; }
|
|
2074
|
+
.pp-table, .pp-table tbody, .pp-table td { display: block; }
|
|
2075
|
+
.pp-table tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; padding: 10px 0; border-bottom: 1px solid var(--grid); }
|
|
2076
|
+
.pp-table tr[hidden] { display: none; }
|
|
2077
|
+
.pp-table tr.pp-edit { display: block; padding-top: 0; }
|
|
2078
|
+
table.data.pp-table td { border: 0; padding: 0; min-width: 0; }
|
|
2079
|
+
.pp-table td:nth-child(n+3) { grid-column: 1 / -1; }
|
|
2080
|
+
.pp-actions > div { justify-content: flex-start; }
|
|
2081
|
+
}
|
|
2069
2082
|
.link-btn.danger { color: var(--critical-ink); }
|
|
2070
2083
|
.people-form { margin-bottom: 6px; }
|
|
2071
2084
|
.pp-scope { border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px 10px; margin: 0 0 12px; min-width: 0; }
|
|
@@ -6352,8 +6352,28 @@ function peopleCard(dv) {
|
|
|
6352
6352
|
} }, t("Revoke"));
|
|
6353
6353
|
return h("div", { class: "pp-tok" }, icon(kind === "computer" ? "devices" : "page"),
|
|
6354
6354
|
h("span", { title: kind === "browser" ? x.label || "" : x.machine_id || "" }, label),
|
|
6355
|
+
x.this ? h("span", { class: "muted" }, t("this browser")) : null,
|
|
6355
6356
|
h("span", { class: "muted" }, x.last_used ? t("used {ago}", { ago: ago(x.last_used) }) : t("since {ago}", { ago: ago(x.since) })), revoke);
|
|
6356
6357
|
};
|
|
6358
|
+
// a person's sessions in the same browser: the one in use (or used last) on its own row, the older ones folded into one
|
|
6359
|
+
// row with one Revoke (they pile up from sign-ins before a browser's new session ended its old one)
|
|
6360
|
+
const browserRows = (p, browsers) => {
|
|
6361
|
+
const groups = new Map();
|
|
6362
|
+
for (const b of browsers) groups.set(uaLabel(b.label), [...(groups.get(uaLabel(b.label)) || []), b]);
|
|
6363
|
+
const used = (x) => x.last_used || x.since || "";
|
|
6364
|
+
return [...groups.entries()].flatMap(([label, list]) => {
|
|
6365
|
+
const [kept, ...older] = [...list].sort((a, b) => Number(!!b.this) - Number(!!a.this) || (used(a) < used(b) ? 1 : used(a) > used(b) ? -1 : 0));
|
|
6366
|
+
if (!older.length) return [tokenRow(p, kept, "browser")];
|
|
6367
|
+
const revoke = h("button", { class: "link-btn danger", type: "button", onclick: async () => {
|
|
6368
|
+
if (!confirm(tn(older.length, "End {n} older session of {name} in {browser}? Only the one used last stays; any other browser still using one has to sign in again.",
|
|
6369
|
+
"End {n} older sessions of {name} in {browser}? Only the one used last stays; any other browser still using one has to sign in again.", { name: p.name, browser: label }))) return;
|
|
6370
|
+
revoke.disabled = true;
|
|
6371
|
+
if (await send("/api/people/revoke", { id: p.id, tokens: older.map((x) => x.id) })) { toast(t("Revoked.")); load(); } else revoke.disabled = false;
|
|
6372
|
+
} }, t("Revoke them"));
|
|
6373
|
+
return [tokenRow(p, kept, "browser"), h("div", { class: "pp-tok pp-older" },
|
|
6374
|
+
h("span", { class: "muted" }, tn(older.length, "+ {n} older session, last used {ago}", "+ {n} older sessions, last used {ago}", { ago: ago(used(older[0])) })), revoke)];
|
|
6375
|
+
});
|
|
6376
|
+
};
|
|
6357
6377
|
const personRow = (p, roles) => {
|
|
6358
6378
|
const me = ME?.viewer?.id != null && ME.viewer.id === p.id;
|
|
6359
6379
|
const sel = h("select", { "aria-label": t("Role of {name}", { name: p.name }), onchange: async () => {
|
|
@@ -6408,9 +6428,9 @@ function peopleCard(dv) {
|
|
|
6408
6428
|
h("td", null, sel),
|
|
6409
6429
|
scope,
|
|
6410
6430
|
h("td", null, computers.length || browsers.length
|
|
6411
|
-
? [...computers.map((c) => tokenRow(p, c, "computer")), ...
|
|
6431
|
+
? [...computers.map((c) => tokenRow(p, c, "computer")), ...browserRows(p, browsers)]
|
|
6412
6432
|
: h("span", { class: "muted" }, lastInvite ? t("Not joined yet") : t("No computer or browser yet: make a new invite"))),
|
|
6413
|
-
h("td", { class: "pp-actions" }, reinvite, remove)), editRow];
|
|
6433
|
+
h("td", { class: "pp-actions" }, h("div", null, reinvite, remove))), editRow];
|
|
6414
6434
|
};
|
|
6415
6435
|
|
|
6416
6436
|
const inviteForm = (roles, first) => {
|
|
@@ -6478,7 +6498,7 @@ function peopleCard(dv) {
|
|
|
6478
6498
|
box.replaceChildren(); append(box, [ // append() flattens the arrays and skips nulls
|
|
6479
6499
|
cardHead(t("People"), { iconName: "preference", hint: people.length ? tn(people.length, "{n} person", "{n} people") : null }),
|
|
6480
6500
|
people.length ? [
|
|
6481
|
-
h("div", { class: "table-wrap" }, h("table", { class: "data pp-table" },
|
|
6501
|
+
h("div", { class: "table-wrap pp-wrap" }, h("table", { class: "data pp-table" },
|
|
6482
6502
|
h("thead", null, h("tr", null, ...[t("Person"), t("Role"), t("Projects"), t("Computers and browsers"), ""].map((x) => h("th", null, x)))),
|
|
6483
6503
|
h("tbody", null, people.map((p) => personRow(p, roles))))),
|
|
6484
6504
|
h("p", { class: "muted" }, t("Admins invite people and change this hub's settings; members' computers send here and get their teammates' lessons back; read-only people only open this dashboard. Whoever is at this computer is always an admin."))]
|
|
@@ -1645,6 +1645,12 @@ window.INTERLATCH_JA = {
|
|
|
1645
1645
|
"Revoked.": "取り消しました。",
|
|
1646
1646
|
"Revoke": "取り消す",
|
|
1647
1647
|
"used {ago}": "最終使用 {ago}",
|
|
1648
|
+
"this browser": "このブラウザー",
|
|
1649
|
+
"Revoke them": "まとめて取り消す",
|
|
1650
|
+
"+ {n} older session, last used {ago}": "ほかに古いセッション {n} 件(最終使用 {ago})",
|
|
1651
|
+
"+ {n} older sessions, last used {ago}": "ほかに古いセッション {n} 件(最終使用 {ago})",
|
|
1652
|
+
"End {n} older session of {name} in {browser}? Only the one used last stays; any other browser still using one has to sign in again.": "{browser} での {name} さんの古いセッション {n} 件を終了しますか? 最後に使ったものだけが残ります。まだそれを使っているほかのブラウザーでは再びサインインが必要になります。",
|
|
1653
|
+
"End {n} older sessions of {name} in {browser}? Only the one used last stays; any other browser still using one has to sign in again.": "{browser} での {name} さんの古いセッション {n} 件を終了しますか? 最後に使ったものだけが残ります。まだそれを使っているほかのブラウザーでは再びサインインが必要になります。",
|
|
1648
1654
|
"since {ago}": "{ago}から",
|
|
1649
1655
|
"Role of {name}": "{name} さんのロール",
|
|
1650
1656
|
"{name} is now: {role}.": "{name} さんのロールを{role}にしました。",
|
|
@@ -12,7 +12,7 @@ import pytest
|
|
|
12
12
|
from interlatch.core.config import load_config, set_config_value
|
|
13
13
|
from interlatch.analysis.runners.base import LLMError, UsageLimitError
|
|
14
14
|
from interlatch.analysis.runners.factory import make_runner
|
|
15
|
-
from interlatch.analysis.runners.bob import BOB_TOOL_GROUPS, BobRunner
|
|
15
|
+
from interlatch.analysis.runners.bob import BOB_TOOL_GROUPS, NO_TOOLS, BobRunner
|
|
16
16
|
from interlatch.analysis.runners.settings import key_source, set_key
|
|
17
17
|
from interlatch.application.workflows import run_worker
|
|
18
18
|
|
|
@@ -28,8 +28,11 @@ modes = json.load(open(os.path.join(ws, ".bob", "custom_modes.yaml")))["customMo
|
|
|
28
28
|
mode = next(m for m in modes if m["slug"] == args[args.index("--mode") + 1])
|
|
29
29
|
with open(os.environ["FAKE_BOB_LOG"], "a") as fh:
|
|
30
30
|
fh.write(json.dumps({"args": args, "prompt_chars": len(prompt), "groups": mode["groups"], "cwd": os.getcwd(),
|
|
31
|
-
"key": os.environ.get("BOB_API_KEY"), "internal": os.environ.get("INTERLATCH_INTERNAL")
|
|
31
|
+
"key": os.environ.get("BOB_API_KEY"), "internal": os.environ.get("INTERLATCH_INTERNAL"),
|
|
32
|
+
"role": mode["roleDefinition"]}) + "\n")
|
|
32
33
|
how = os.environ.get("FAKE_BOB_MODE", "ok")
|
|
34
|
+
if how == "tool-once": # only the first call reaches for a tool
|
|
35
|
+
how = "tool" if sum(1 for _ in open(os.environ["FAKE_BOB_LOG"])) == 1 else "ok"
|
|
33
36
|
if how == "license":
|
|
34
37
|
print("Error: A license agreement is required. Please accept the license terms before proceeding.", file=sys.stderr)
|
|
35
38
|
sys.exit(1)
|
|
@@ -83,6 +86,7 @@ def test_bob_runs_headless_with_no_tools(bob_env):
|
|
|
83
86
|
assert args[args.index("--disable-tool-groups") + 1].split(",") == list(BOB_TOOL_GROUPS)
|
|
84
87
|
assert args[args.index("--max-cost") + 1] == "3.00"
|
|
85
88
|
assert call["groups"] == [] and call["key"] == "bob-test-key" and call["internal"] == "1"
|
|
89
|
+
assert call["role"].startswith("Summarize the session.") and call["role"].endswith(NO_TOOLS)
|
|
86
90
|
workspace = args[args.index("--workspace") + 1]
|
|
87
91
|
assert cfg.is_internal_path(workspace) and call["cwd"] == workspace
|
|
88
92
|
assert not (cfg.home / "workdir" / "bob").exists() or not any((cfg.home / "workdir" / "bob").iterdir()) # removed
|
|
@@ -92,6 +96,16 @@ def test_bob_reply_after_a_tool_call_is_discarded(bob_env, monkeypatch):
|
|
|
92
96
|
monkeypatch.setenv("FAKE_BOB_MODE", "tool")
|
|
93
97
|
with pytest.raises(LLMError, match=r"used a tool \(execute_command\); the reply was discarded"):
|
|
94
98
|
make_runner(bob_env["cfg"]).run("x", {"type": "object"}, system="Summarize the session.")
|
|
99
|
+
assert len(bob_calls(bob_env)) == 2 # tried again once
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_bob_retries_once_after_a_tool_call(bob_env, monkeypatch):
|
|
103
|
+
"""Bob reads tool calls out of the reply's text, so the model can write one with every group off: one more try."""
|
|
104
|
+
monkeypatch.setenv("FAKE_BOB_MODE", "tool-once")
|
|
105
|
+
res = make_runner(bob_env["cfg"]).run("<transcript>hello</transcript>", {"type": "object"},
|
|
106
|
+
system="Summarize the session.")
|
|
107
|
+
assert res.data["title"] == "Fixed login token expiry bug"
|
|
108
|
+
assert len(bob_calls(bob_env)) == 2 and res.cost_usd == pytest.approx(0.062) # the discarded call is paid for too
|
|
95
109
|
|
|
96
110
|
|
|
97
111
|
def test_bob_license_or_key_problem_pauses(bob_env, monkeypatch):
|
|
@@ -176,3 +190,16 @@ def test_dashboard_sets_agent_models(env):
|
|
|
176
190
|
assert load_config(env["cfg"].home).analysis.codex_model == ""
|
|
177
191
|
assert "error" in api.settings.action_agent(app.context(), {"agent": "bob", "settings": {"model": "x"}}) # Bob picks its own model
|
|
178
192
|
assert "error" in api.settings.action_agent(app.context(), {"agent": "claude", "settings": {"model": "sonnet\nbackend = 'x'"}})
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_failed_lines_name_projects_not_cut_paths():
|
|
196
|
+
"""A worker run's error lines: `! /Users/a: …` named a knowledge base that failed only by its path's first 8
|
|
197
|
+
characters."""
|
|
198
|
+
from interlatch.interfaces.cli.common import failed_label
|
|
199
|
+
|
|
200
|
+
assert failed_label("0eb2ccdb-58fc-420f-b7ac-cbdbabfe1f9a") == "0eb2ccdb"
|
|
201
|
+
assert failed_label("/Users/a/Projects/agents-chronicle") == "agents-chronicle"
|
|
202
|
+
assert failed_label("glossary:/Users/a/Projects/agents-chronicle/") == "glossary agents-chronicle"
|
|
203
|
+
assert failed_label(r"C:\Users\a\Projects\app") == "app"
|
|
204
|
+
assert failed_label("__global__") == "global playbook" and failed_label("themes:Tooling and CI") == "themes Tooling and CI"
|
|
205
|
+
assert failed_label("2026-W41") == "2026-W41"
|
|
@@ -74,6 +74,20 @@ def test_browser_sessions_signin_and_removal(conn):
|
|
|
74
74
|
assert people.add(conn, "Vic", "vic@example.com", "member")["role"] == "member" # can be added again
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def test_a_browser_signing_in_again_ends_its_old_session(conn):
|
|
78
|
+
vic = people.add(conn, "Vic", "vic@example.com", "readonly")
|
|
79
|
+
_, first = people.open_browser(conn, people.signin_code(conn, vic), "Edge")
|
|
80
|
+
_, other = people.open_browser(conn, people.signin_code(conn, vic), "Safari") # another browser keeps its own
|
|
81
|
+
with pytest.raises(people.PeopleError):
|
|
82
|
+
people.open_browser(conn, "AAAA-BBBB-CCCC", "Edge", replaces=(first,)) # a wrong code ends nothing
|
|
83
|
+
assert people.browser_person(conn, first)
|
|
84
|
+
_, second = people.open_browser(conn, people.signin_code(conn, vic), "Edge", replaces=(first, "", "not-a-session"))
|
|
85
|
+
assert people.browser_person(conn, first) is None and people.browser_person(conn, second) and people.browser_person(conn, other)
|
|
86
|
+
listed = people.listing(conn, second)[0]["browsers"]
|
|
87
|
+
assert [b["label"] for b in listed] == ["Safari", "Edge"] and [b.get("this") for b in listed] == [None, True]
|
|
88
|
+
assert not any(b.get("this") for b in people.listing(conn)[0]["browsers"])
|
|
89
|
+
|
|
90
|
+
|
|
77
91
|
def test_wrong_codes_make_an_address_wait():
|
|
78
92
|
t = [1000.0]
|
|
79
93
|
a = people.CodeAttempts(clock=lambda: t[0])
|