@loompad/cli 0.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +96 -11
- package/CHANGELOG.md +1054 -0
- package/README.md +1473 -44
- package/THIRD_PARTY_NOTICES.md +275 -0
- package/dist/adapters/antigravity-cli.d.ts +62 -0
- package/dist/adapters/antigravity-cli.js +258 -0
- package/dist/adapters/antigravity-cli.js.map +1 -0
- package/dist/adapters/base.d.ts +61 -9
- package/dist/adapters/base.js +266 -15
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/bridges/antigravity.d.ts +9 -12
- package/dist/adapters/bridges/antigravity.js +9 -12
- package/dist/adapters/bridges/antigravity.js.map +1 -1
- package/dist/adapters/echo.d.ts +1 -0
- package/dist/adapters/echo.js +59 -3
- package/dist/adapters/echo.js.map +1 -1
- package/dist/adapters/grok.d.ts +7 -0
- package/dist/adapters/grok.js +20 -10
- package/dist/adapters/grok.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -1
- package/dist/adapters/index.js +53 -7
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/model.d.ts +137 -0
- package/dist/adapters/model.js +527 -0
- package/dist/adapters/model.js.map +1 -0
- package/dist/adapters/opencode.d.ts +101 -1
- package/dist/adapters/opencode.js +874 -98
- package/dist/adapters/opencode.js.map +1 -1
- package/dist/cli/doctor.d.ts +32 -0
- package/dist/cli/doctor.js +89 -4
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +2651 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tui-model.js +3 -1
- package/dist/cli/tui-model.js.map +1 -1
- package/dist/cli/tui.js +44 -1
- package/dist/cli/tui.js.map +1 -1
- package/dist/cli/ui.d.ts +2 -0
- package/dist/cli/ui.js +13 -0
- package/dist/cli/ui.js.map +1 -1
- package/dist/core/ades.d.ts +32 -16
- package/dist/core/ades.js +57 -20
- package/dist/core/ades.js.map +1 -1
- package/dist/core/agent-state.d.ts +14 -0
- package/dist/core/agent-state.js +32 -0
- package/dist/core/agent-state.js.map +1 -0
- package/dist/core/approvals.d.ts +33 -0
- package/dist/core/approvals.js +29 -0
- package/dist/core/approvals.js.map +1 -0
- package/dist/core/backup.d.ts +19 -0
- package/dist/core/backup.js +86 -0
- package/dist/core/backup.js.map +1 -0
- package/dist/core/baton.d.ts +2 -2
- package/dist/core/baton.js.map +1 -1
- package/dist/core/brain-extract.d.ts +17 -0
- package/dist/core/brain-extract.js +28 -2
- package/dist/core/brain-extract.js.map +1 -1
- package/dist/core/brain-index.d.ts +71 -0
- package/dist/core/brain-index.js +204 -5
- package/dist/core/brain-index.js.map +1 -1
- package/dist/core/brain.d.ts +58 -2
- package/dist/core/brain.js +79 -1
- package/dist/core/brain.js.map +1 -1
- package/dist/core/chat-import.d.ts +71 -0
- package/dist/core/chat-import.js +419 -0
- package/dist/core/chat-import.js.map +1 -0
- package/dist/core/checkpoint.d.ts +107 -0
- package/dist/core/checkpoint.js +259 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/claude-cli.js +1 -1
- package/dist/core/claude-cli.js.map +1 -1
- package/dist/core/continuity/artifacts.d.ts +14 -0
- package/dist/core/continuity/artifacts.js +118 -0
- package/dist/core/continuity/artifacts.js.map +1 -0
- package/dist/core/continuity/capabilities.d.ts +65 -0
- package/dist/core/continuity/capabilities.js +129 -0
- package/dist/core/continuity/capabilities.js.map +1 -0
- package/dist/core/continuity/contracts.d.ts +352 -0
- package/dist/core/continuity/contracts.js +117 -0
- package/dist/core/continuity/contracts.js.map +1 -0
- package/dist/core/continuity/engine.d.ts +55 -0
- package/dist/core/continuity/engine.js +504 -0
- package/dist/core/continuity/engine.js.map +1 -0
- package/dist/core/continuity/store.d.ts +73 -0
- package/dist/core/continuity/store.js +438 -0
- package/dist/core/continuity/store.js.map +1 -0
- package/dist/core/conversations.d.ts +19 -0
- package/dist/core/conversations.js +87 -0
- package/dist/core/conversations.js.map +1 -0
- package/dist/core/crew-coordination.d.ts +6 -0
- package/dist/core/crew-coordination.js +24 -0
- package/dist/core/crew-coordination.js.map +1 -0
- package/dist/core/crew-policy.d.ts +18 -0
- package/dist/core/crew-policy.js +40 -0
- package/dist/core/crew-policy.js.map +1 -0
- package/dist/core/crew-protocol.d.ts +64 -0
- package/dist/core/crew-protocol.js +146 -0
- package/dist/core/crew-protocol.js.map +1 -0
- package/dist/core/crew-requests.d.ts +14 -0
- package/dist/core/crew-requests.js +22 -0
- package/dist/core/crew-requests.js.map +1 -0
- package/dist/core/crew.d.ts +395 -0
- package/dist/core/crew.js +1857 -0
- package/dist/core/crew.js.map +1 -0
- package/dist/core/digest.d.ts +40 -0
- package/dist/core/digest.js +94 -0
- package/dist/core/digest.js.map +1 -0
- package/dist/core/distill.js +3 -5
- package/dist/core/distill.js.map +1 -1
- package/dist/core/eventlog.d.ts +27 -25
- package/dist/core/eventlog.js +97 -150
- package/dist/core/eventlog.js.map +1 -1
- package/dist/core/events/contracts.d.ts +51 -0
- package/dist/core/events/contracts.js +2 -0
- package/dist/core/events/contracts.js.map +1 -0
- package/dist/core/events/jsonl-store.d.ts +23 -0
- package/dist/core/events/jsonl-store.js +90 -0
- package/dist/core/events/jsonl-store.js.map +1 -0
- package/dist/core/events/sqlite-store.d.ts +37 -0
- package/dist/core/events/sqlite-store.js +215 -0
- package/dist/core/events/sqlite-store.js.map +1 -0
- package/dist/core/forecast.d.ts +44 -0
- package/dist/core/forecast.js +46 -0
- package/dist/core/forecast.js.map +1 -0
- package/dist/core/free-pool.d.ts +65 -0
- package/dist/core/free-pool.js +171 -0
- package/dist/core/free-pool.js.map +1 -0
- package/dist/core/git.d.ts +30 -0
- package/dist/core/git.js +50 -2
- package/dist/core/git.js.map +1 -1
- package/dist/core/github-events.d.ts +53 -0
- package/dist/core/github-events.js +227 -0
- package/dist/core/github-events.js.map +1 -0
- package/dist/core/goal-lanes.d.ts +53 -0
- package/dist/core/goal-lanes.js +99 -0
- package/dist/core/goal-lanes.js.map +1 -0
- package/dist/core/hosted.d.ts +27 -0
- package/dist/core/hosted.js +50 -0
- package/dist/core/hosted.js.map +1 -0
- package/dist/core/http-server.d.ts +5 -0
- package/dist/core/http-server.js +8 -0
- package/dist/core/http-server.js.map +1 -0
- package/dist/core/invite-link.d.ts +31 -0
- package/dist/core/invite-link.js +45 -0
- package/dist/core/invite-link.js.map +1 -0
- package/dist/core/loops.d.ts +37 -0
- package/dist/core/loops.js +81 -0
- package/dist/core/loops.js.map +1 -0
- package/dist/core/mcp-catalog.d.ts +182 -0
- package/dist/core/mcp-catalog.js +431 -0
- package/dist/core/mcp-catalog.js.map +1 -0
- package/dist/core/mcp.d.ts +87 -0
- package/dist/core/mcp.js +181 -0
- package/dist/core/mcp.js.map +1 -0
- package/dist/core/memory.d.ts +1 -0
- package/dist/core/memory.js +5 -2
- package/dist/core/memory.js.map +1 -1
- package/dist/core/model-tools.d.ts +97 -0
- package/dist/core/model-tools.js +388 -0
- package/dist/core/model-tools.js.map +1 -0
- package/dist/core/notify.js +19 -1
- package/dist/core/notify.js.map +1 -1
- package/dist/core/orchestra.d.ts +635 -0
- package/dist/core/orchestra.js +2120 -0
- package/dist/core/orchestra.js.map +1 -0
- package/dist/core/permissions.d.ts +56 -0
- package/dist/core/permissions.js +122 -0
- package/dist/core/permissions.js.map +1 -0
- package/dist/core/preview-proxy.d.ts +70 -0
- package/dist/core/preview-proxy.js +563 -0
- package/dist/core/preview-proxy.js.map +1 -0
- package/dist/core/preview-shot.d.ts +46 -0
- package/dist/core/preview-shot.js +109 -0
- package/dist/core/preview-shot.js.map +1 -0
- package/dist/core/process.d.ts +36 -0
- package/dist/core/process.js +168 -0
- package/dist/core/process.js.map +1 -0
- package/dist/core/prompt-queue.d.ts +136 -0
- package/dist/core/prompt-queue.js +269 -0
- package/dist/core/prompt-queue.js.map +1 -0
- package/dist/core/prompts.d.ts +53 -0
- package/dist/core/prompts.js +127 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/proof.d.ts +82 -0
- package/dist/core/proof.js +216 -0
- package/dist/core/proof.js.map +1 -0
- package/dist/core/provider-text.d.ts +23 -0
- package/dist/core/provider-text.js +66 -0
- package/dist/core/provider-text.js.map +1 -0
- package/dist/core/providers.d.ts +152 -0
- package/dist/core/providers.js +343 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/quota.d.ts +33 -0
- package/dist/core/quota.js +74 -0
- package/dist/core/quota.js.map +1 -0
- package/dist/core/recipes.d.ts +50 -0
- package/dist/core/recipes.js +99 -0
- package/dist/core/recipes.js.map +1 -0
- package/dist/core/registry.d.ts +54 -0
- package/dist/core/registry.js +62 -5
- package/dist/core/registry.js.map +1 -1
- package/dist/core/relay-client.d.ts +67 -0
- package/dist/core/relay-client.js +184 -0
- package/dist/core/relay-client.js.map +1 -0
- package/dist/core/relay-protocol.d.ts +140 -0
- package/dist/core/relay-protocol.js +189 -0
- package/dist/core/relay-protocol.js.map +1 -0
- package/dist/core/replay.d.ts +18 -0
- package/dist/core/replay.js +191 -0
- package/dist/core/replay.js.map +1 -0
- package/dist/core/review-triage.d.ts +41 -0
- package/dist/core/review-triage.js +124 -0
- package/dist/core/review-triage.js.map +1 -0
- package/dist/core/router.js +2 -2
- package/dist/core/router.js.map +1 -1
- package/dist/core/routes.d.ts +39 -3
- package/dist/core/routes.js +137 -4
- package/dist/core/routes.js.map +1 -1
- package/dist/core/run-command.d.ts +14 -0
- package/dist/core/run-command.js +45 -0
- package/dist/core/run-command.js.map +1 -0
- package/dist/core/runner-setup.d.ts +36 -0
- package/dist/core/runner-setup.js +114 -0
- package/dist/core/runner-setup.js.map +1 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -1
- package/dist/core/semantic.d.ts +92 -0
- package/dist/core/semantic.js +227 -0
- package/dist/core/semantic.js.map +1 -0
- package/dist/core/servers.d.ts +115 -0
- package/dist/core/servers.js +305 -0
- package/dist/core/servers.js.map +1 -0
- package/dist/core/setup.js +64 -6
- package/dist/core/setup.js.map +1 -1
- package/dist/core/shield.d.ts +17 -0
- package/dist/core/shield.js +81 -0
- package/dist/core/shield.js.map +1 -0
- package/dist/core/shift.d.ts +69 -0
- package/dist/core/shift.js +120 -0
- package/dist/core/shift.js.map +1 -0
- package/dist/core/skill-install.d.ts +125 -0
- package/dist/core/skill-install.js +328 -0
- package/dist/core/skill-install.js.map +1 -0
- package/dist/core/skills.d.ts +116 -0
- package/dist/core/skills.js +223 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/step-conditions.d.ts +65 -0
- package/dist/core/step-conditions.js +102 -0
- package/dist/core/step-conditions.js.map +1 -0
- package/dist/core/subagents.d.ts +45 -0
- package/dist/core/subagents.js +90 -0
- package/dist/core/subagents.js.map +1 -0
- package/dist/core/team-canon.d.ts +42 -0
- package/dist/core/team-canon.js +121 -0
- package/dist/core/team-canon.js.map +1 -0
- package/dist/core/team-crypto.d.ts +97 -0
- package/dist/core/team-crypto.js +175 -0
- package/dist/core/team-crypto.js.map +1 -0
- package/dist/core/team-hub.d.ts +514 -0
- package/dist/core/team-hub.js +964 -0
- package/dist/core/team-hub.js.map +1 -0
- package/dist/core/team-landing.d.ts +239 -0
- package/dist/core/team-landing.js +504 -0
- package/dist/core/team-landing.js.map +1 -0
- package/dist/core/team-leases.d.ts +44 -0
- package/dist/core/team-leases.js +145 -0
- package/dist/core/team-leases.js.map +1 -0
- package/dist/core/team-memory.d.ts +42 -0
- package/dist/core/team-memory.js +84 -0
- package/dist/core/team-memory.js.map +1 -0
- package/dist/core/team-policy.d.ts +85 -0
- package/dist/core/team-policy.js +191 -0
- package/dist/core/team-policy.js.map +1 -0
- package/dist/core/thread-md.d.ts +6 -0
- package/dist/core/thread-md.js +42 -0
- package/dist/core/thread-md.js.map +1 -0
- package/dist/core/turn-stats.d.ts +61 -0
- package/dist/core/turn-stats.js +124 -0
- package/dist/core/turn-stats.js.map +1 -0
- package/dist/core/updater.d.ts +73 -0
- package/dist/core/updater.js +153 -0
- package/dist/core/updater.js.map +1 -0
- package/dist/core/usage.d.ts +75 -0
- package/dist/core/usage.js +163 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/core/windows-jobs.d.ts +15 -0
- package/dist/core/windows-jobs.js +170 -0
- package/dist/core/windows-jobs.js.map +1 -0
- package/dist/core/worktree-merge.d.ts +57 -0
- package/dist/core/worktree-merge.js +123 -0
- package/dist/core/worktree-merge.js.map +1 -0
- package/dist/core/worktree.d.ts +5 -0
- package/dist/core/worktree.js +45 -5
- package/dist/core/worktree.js.map +1 -1
- package/dist/core/worktrees.d.ts +6 -0
- package/dist/core/worktrees.js +21 -0
- package/dist/core/worktrees.js.map +1 -0
- package/dist/daemon/app-page.d.ts +0 -17
- package/dist/daemon/app-page.js +10 -6346
- package/dist/daemon/app-page.js.map +1 -1
- package/dist/daemon/auth.d.ts +24 -3
- package/dist/daemon/auth.js +66 -7
- package/dist/daemon/auth.js.map +1 -1
- package/dist/daemon/board.d.ts +7 -0
- package/dist/daemon/board.js +12 -1
- package/dist/daemon/board.js.map +1 -1
- package/dist/daemon/brand-icons.d.ts +6 -1
- package/dist/daemon/brand-icons.js +11 -3
- package/dist/daemon/brand-icons.js.map +1 -1
- package/dist/daemon/client.d.ts +665 -1
- package/dist/daemon/client.js +381 -3
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/delivery.d.ts +34 -0
- package/dist/daemon/delivery.js +63 -0
- package/dist/daemon/delivery.js.map +1 -0
- package/dist/daemon/deploys.d.ts +62 -0
- package/dist/daemon/deploys.js +159 -0
- package/dist/daemon/deploys.js.map +1 -0
- package/dist/daemon/landing.d.ts +192 -0
- package/dist/daemon/landing.js +992 -0
- package/dist/daemon/landing.js.map +1 -0
- package/dist/daemon/onboard.d.ts +150 -0
- package/dist/daemon/onboard.js +390 -0
- package/dist/daemon/onboard.js.map +1 -0
- package/dist/daemon/proof.d.ts +70 -0
- package/dist/daemon/proof.js +172 -0
- package/dist/daemon/proof.js.map +1 -0
- package/dist/daemon/push.d.ts +19 -1
- package/dist/daemon/push.js +90 -3
- package/dist/daemon/push.js.map +1 -1
- package/dist/daemon/relay.d.ts +82 -0
- package/dist/daemon/relay.js +351 -0
- package/dist/daemon/relay.js.map +1 -0
- package/dist/daemon/routes/agent-discovery.d.ts +4 -0
- package/dist/daemon/routes/agent-discovery.js +29 -0
- package/dist/daemon/routes/agent-discovery.js.map +1 -0
- package/dist/daemon/routes/agent-settings.d.ts +4 -0
- package/dist/daemon/routes/agent-settings.js +94 -0
- package/dist/daemon/routes/agent-settings.js.map +1 -0
- package/dist/daemon/routes/agents.d.ts +4 -0
- package/dist/daemon/routes/agents.js +105 -0
- package/dist/daemon/routes/agents.js.map +1 -0
- package/dist/daemon/routes/alerts.d.ts +4 -0
- package/dist/daemon/routes/alerts.js +137 -0
- package/dist/daemon/routes/alerts.js.map +1 -0
- package/dist/daemon/routes/approvals.d.ts +4 -0
- package/dist/daemon/routes/approvals.js +40 -0
- package/dist/daemon/routes/approvals.js.map +1 -0
- package/dist/daemon/routes/assets.d.ts +3 -0
- package/dist/daemon/routes/assets.js +64 -0
- package/dist/daemon/routes/assets.js.map +1 -0
- package/dist/daemon/routes/board.d.ts +4 -0
- package/dist/daemon/routes/board.js +147 -0
- package/dist/daemon/routes/board.js.map +1 -0
- package/dist/daemon/routes/brain.d.ts +4 -0
- package/dist/daemon/routes/brain.js +270 -0
- package/dist/daemon/routes/brain.js.map +1 -0
- package/dist/daemon/routes/chats.d.ts +4 -0
- package/dist/daemon/routes/chats.js +249 -0
- package/dist/daemon/routes/chats.js.map +1 -0
- package/dist/daemon/routes/clients.d.ts +4 -0
- package/dist/daemon/routes/clients.js +49 -0
- package/dist/daemon/routes/clients.js.map +1 -0
- package/dist/daemon/routes/cloud.d.ts +4 -0
- package/dist/daemon/routes/cloud.js +46 -0
- package/dist/daemon/routes/cloud.js.map +1 -0
- package/dist/daemon/routes/config.d.ts +4 -0
- package/dist/daemon/routes/config.js +35 -0
- package/dist/daemon/routes/config.js.map +1 -0
- package/dist/daemon/routes/context.d.ts +51 -0
- package/dist/daemon/routes/context.js +2 -0
- package/dist/daemon/routes/context.js.map +1 -0
- package/dist/daemon/routes/crews.d.ts +4 -0
- package/dist/daemon/routes/crews.js +149 -0
- package/dist/daemon/routes/crews.js.map +1 -0
- package/dist/daemon/routes/files.d.ts +3 -0
- package/dist/daemon/routes/files.js +253 -0
- package/dist/daemon/routes/files.js.map +1 -0
- package/dist/daemon/routes/git.d.ts +4 -0
- package/dist/daemon/routes/git.js +96 -0
- package/dist/daemon/routes/git.js.map +1 -0
- package/dist/daemon/routes/history.d.ts +4 -0
- package/dist/daemon/routes/history.js +101 -0
- package/dist/daemon/routes/history.js.map +1 -0
- package/dist/daemon/routes/integrations-project.d.ts +3 -0
- package/dist/daemon/routes/integrations-project.js +103 -0
- package/dist/daemon/routes/integrations-project.js.map +1 -0
- package/dist/daemon/routes/integrations.d.ts +3 -0
- package/dist/daemon/routes/integrations.js +111 -0
- package/dist/daemon/routes/integrations.js.map +1 -0
- package/dist/daemon/routes/logs.d.ts +3 -0
- package/dist/daemon/routes/logs.js +40 -0
- package/dist/daemon/routes/logs.js.map +1 -0
- package/dist/daemon/routes/messages.d.ts +4 -0
- package/dist/daemon/routes/messages.js +60 -0
- package/dist/daemon/routes/messages.js.map +1 -0
- package/dist/daemon/routes/observability.d.ts +4 -0
- package/dist/daemon/routes/observability.js +247 -0
- package/dist/daemon/routes/observability.js.map +1 -0
- package/dist/daemon/routes/onboard.d.ts +12 -0
- package/dist/daemon/routes/onboard.js +67 -0
- package/dist/daemon/routes/onboard.js.map +1 -0
- package/dist/daemon/routes/orchestra.d.ts +4 -0
- package/dist/daemon/routes/orchestra.js +110 -0
- package/dist/daemon/routes/orchestra.js.map +1 -0
- package/dist/daemon/routes/pairing.d.ts +4 -0
- package/dist/daemon/routes/pairing.js +155 -0
- package/dist/daemon/routes/pairing.js.map +1 -0
- package/dist/daemon/routes/preview.d.ts +24 -0
- package/dist/daemon/routes/preview.js +122 -0
- package/dist/daemon/routes/preview.js.map +1 -0
- package/dist/daemon/routes/project-actions.d.ts +4 -0
- package/dist/daemon/routes/project-actions.js +53 -0
- package/dist/daemon/routes/project-actions.js.map +1 -0
- package/dist/daemon/routes/project-team.d.ts +4 -0
- package/dist/daemon/routes/project-team.js +201 -0
- package/dist/daemon/routes/project-team.js.map +1 -0
- package/dist/daemon/routes/projects.d.ts +14 -0
- package/dist/daemon/routes/projects.js +138 -0
- package/dist/daemon/routes/projects.js.map +1 -0
- package/dist/daemon/routes/prompts.d.ts +4 -0
- package/dist/daemon/routes/prompts.js +44 -0
- package/dist/daemon/routes/prompts.js.map +1 -0
- package/dist/daemon/routes/proof.d.ts +4 -0
- package/dist/daemon/routes/proof.js +22 -0
- package/dist/daemon/routes/proof.js.map +1 -0
- package/dist/daemon/routes/providers.d.ts +3 -0
- package/dist/daemon/routes/providers.js +60 -0
- package/dist/daemon/routes/providers.js.map +1 -0
- package/dist/daemon/routes/public.d.ts +4 -0
- package/dist/daemon/routes/public.js +64 -0
- package/dist/daemon/routes/public.js.map +1 -0
- package/dist/daemon/routes/queue.d.ts +4 -0
- package/dist/daemon/routes/queue.js +140 -0
- package/dist/daemon/routes/queue.js.map +1 -0
- package/dist/daemon/routes/routing.d.ts +4 -0
- package/dist/daemon/routes/routing.js +61 -0
- package/dist/daemon/routes/routing.js.map +1 -0
- package/dist/daemon/routes/runtime-handler.d.ts +2 -0
- package/dist/daemon/routes/runtime-handler.js +63 -0
- package/dist/daemon/routes/runtime-handler.js.map +1 -0
- package/dist/daemon/routes/search.d.ts +4 -0
- package/dist/daemon/routes/search.js +17 -0
- package/dist/daemon/routes/search.js.map +1 -0
- package/dist/daemon/routes/servers.d.ts +4 -0
- package/dist/daemon/routes/servers.js +133 -0
- package/dist/daemon/routes/servers.js.map +1 -0
- package/dist/daemon/routes/setup.d.ts +3 -0
- package/dist/daemon/routes/setup.js +50 -0
- package/dist/daemon/routes/setup.js.map +1 -0
- package/dist/daemon/routes/shift.d.ts +4 -0
- package/dist/daemon/routes/shift.js +30 -0
- package/dist/daemon/routes/shift.js.map +1 -0
- package/dist/daemon/routes/specs.d.ts +4 -0
- package/dist/daemon/routes/specs.js +57 -0
- package/dist/daemon/routes/specs.js.map +1 -0
- package/dist/daemon/routes/task-delivery.d.ts +4 -0
- package/dist/daemon/routes/task-delivery.js +38 -0
- package/dist/daemon/routes/task-delivery.js.map +1 -0
- package/dist/daemon/routes/team.d.ts +4 -0
- package/dist/daemon/routes/team.js +193 -0
- package/dist/daemon/routes/team.js.map +1 -0
- package/dist/daemon/routes/terminals.d.ts +4 -0
- package/dist/daemon/routes/terminals.js +87 -0
- package/dist/daemon/routes/terminals.js.map +1 -0
- package/dist/daemon/routes/tools.d.ts +4 -0
- package/dist/daemon/routes/tools.js +180 -0
- package/dist/daemon/routes/tools.js.map +1 -0
- package/dist/daemon/routes/updates.d.ts +4 -0
- package/dist/daemon/routes/updates.js +89 -0
- package/dist/daemon/routes/updates.js.map +1 -0
- package/dist/daemon/routes/voice.d.ts +4 -0
- package/dist/daemon/routes/voice.js +53 -0
- package/dist/daemon/routes/voice.js.map +1 -0
- package/dist/daemon/runner.d.ts +186 -0
- package/dist/daemon/runner.js +612 -0
- package/dist/daemon/runner.js.map +1 -0
- package/dist/daemon/runtime/accounting.d.ts +130 -0
- package/dist/daemon/runtime/accounting.js +303 -0
- package/dist/daemon/runtime/accounting.js.map +1 -0
- package/dist/daemon/runtime/agents.d.ts +16 -0
- package/dist/daemon/runtime/agents.js +71 -0
- package/dist/daemon/runtime/agents.js.map +1 -0
- package/dist/daemon/runtime/briefings.d.ts +134 -0
- package/dist/daemon/runtime/briefings.js +329 -0
- package/dist/daemon/runtime/briefings.js.map +1 -0
- package/dist/daemon/runtime/native-usage.d.ts +21 -0
- package/dist/daemon/runtime/native-usage.js +71 -0
- package/dist/daemon/runtime/native-usage.js.map +1 -0
- package/dist/daemon/runtime/queue.d.ts +94 -0
- package/dist/daemon/runtime/queue.js +222 -0
- package/dist/daemon/runtime/queue.js.map +1 -0
- package/dist/daemon/runtime/relay.d.ts +93 -0
- package/dist/daemon/runtime/relay.js +240 -0
- package/dist/daemon/runtime/relay.js.map +1 -0
- package/dist/daemon/runtime/turns.d.ts +202 -0
- package/dist/daemon/runtime/turns.js +663 -0
- package/dist/daemon/runtime/turns.js.map +1 -0
- package/dist/daemon/runtime-support.d.ts +76 -0
- package/dist/daemon/runtime-support.js +134 -0
- package/dist/daemon/runtime-support.js.map +1 -0
- package/dist/daemon/runtime.d.ts +739 -70
- package/dist/daemon/runtime.js +2751 -470
- package/dist/daemon/runtime.js.map +1 -1
- package/dist/daemon/server.d.ts +82 -55
- package/dist/daemon/server.js +674 -1435
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shift.d.ts +70 -0
- package/dist/daemon/shift.js +170 -0
- package/dist/daemon/shift.js.map +1 -0
- package/dist/daemon/specs.d.ts +92 -0
- package/dist/daemon/specs.js +285 -0
- package/dist/daemon/specs.js.map +1 -0
- package/dist/daemon/system.d.ts +208 -0
- package/dist/daemon/system.js +557 -0
- package/dist/daemon/system.js.map +1 -0
- package/dist/daemon/tasks.js +1 -1
- package/dist/daemon/team-brain.d.ts +136 -0
- package/dist/daemon/team-brain.js +659 -0
- package/dist/daemon/team-brain.js.map +1 -0
- package/dist/daemon/team-coordinator.d.ts +78 -0
- package/dist/daemon/team-coordinator.js +408 -0
- package/dist/daemon/team-coordinator.js.map +1 -0
- package/dist/daemon/team.d.ts +339 -0
- package/dist/daemon/team.js +1433 -0
- package/dist/daemon/team.js.map +1 -0
- package/dist/daemon/terminals.d.ts +22 -3
- package/dist/daemon/terminals.js +236 -16
- package/dist/daemon/terminals.js.map +1 -1
- package/dist/hub/client.d.ts +120 -0
- package/dist/hub/client.js +228 -0
- package/dist/hub/client.js.map +1 -0
- package/dist/hub/server.d.ts +32 -0
- package/dist/hub/server.js +180 -0
- package/dist/hub/server.js.map +1 -0
- package/dist/hub/supabase-client.d.ts +220 -0
- package/dist/hub/supabase-client.js +843 -0
- package/dist/hub/supabase-client.js.map +1 -0
- package/dist/observability/ask.d.ts +91 -0
- package/dist/observability/ask.js +179 -0
- package/dist/observability/ask.js.map +1 -0
- package/dist/observability/clickhouse.d.ts +33 -0
- package/dist/observability/clickhouse.js +48 -0
- package/dist/observability/clickhouse.js.map +1 -0
- package/dist/observability/decisions.d.ts +176 -0
- package/dist/observability/decisions.js +377 -0
- package/dist/observability/decisions.js.map +1 -0
- package/dist/observability/index.d.ts +89 -0
- package/dist/observability/index.js +348 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/insights.d.ts +151 -0
- package/dist/observability/insights.js +319 -0
- package/dist/observability/insights.js.map +1 -0
- package/dist/observability/logs-query.d.ts +82 -0
- package/dist/observability/logs-query.js +102 -0
- package/dist/observability/logs-query.js.map +1 -0
- package/dist/observability/logs.d.ts +72 -0
- package/dist/observability/logs.js +295 -0
- package/dist/observability/logs.js.map +1 -0
- package/dist/observability/metrics.d.ts +86 -0
- package/dist/observability/metrics.js +303 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otlp.d.ts +127 -0
- package/dist/observability/otlp.js +242 -0
- package/dist/observability/otlp.js.map +1 -0
- package/dist/observability/snapshots.d.ts +43 -0
- package/dist/observability/snapshots.js +129 -0
- package/dist/observability/snapshots.js.map +1 -0
- package/dist/observability/triage.d.ts +49 -0
- package/dist/observability/triage.js +284 -0
- package/dist/observability/triage.js.map +1 -0
- package/dist/providers/adapter.d.ts +50 -0
- package/dist/providers/adapter.js +38 -0
- package/dist/providers/adapter.js.map +1 -0
- package/dist/providers/agent.d.ts +110 -0
- package/dist/providers/agent.js +487 -0
- package/dist/providers/agent.js.map +1 -0
- package/dist/providers/approvals.d.ts +24 -0
- package/dist/providers/approvals.js +78 -0
- package/dist/providers/approvals.js.map +1 -0
- package/dist/providers/attachments.d.ts +20 -0
- package/dist/providers/attachments.js +51 -0
- package/dist/providers/attachments.js.map +1 -0
- package/dist/providers/claude/adapter.d.ts +99 -0
- package/dist/providers/claude/adapter.js +790 -0
- package/dist/providers/claude/adapter.js.map +1 -0
- package/dist/providers/codex/adapter.d.ts +113 -0
- package/dist/providers/codex/adapter.js +881 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/instructions.d.ts +11 -0
- package/dist/providers/codex/instructions.js +154 -0
- package/dist/providers/codex/instructions.js.map +1 -0
- package/dist/providers/codex/rpc.d.ts +36 -0
- package/dist/providers/codex/rpc.js +104 -0
- package/dist/providers/codex/rpc.js.map +1 -0
- package/dist/providers/contracts.d.ts +339 -0
- package/dist/providers/contracts.js +19 -0
- package/dist/providers/contracts.js.map +1 -0
- package/dist/providers/directory.d.ts +70 -0
- package/dist/providers/directory.js +95 -0
- package/dist/providers/directory.js.map +1 -0
- package/dist/providers/errors.d.ts +47 -0
- package/dist/providers/errors.js +24 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +18 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/ingestion.d.ts +80 -0
- package/dist/providers/ingestion.js +330 -0
- package/dist/providers/ingestion.js.map +1 -0
- package/dist/providers/live.d.ts +22 -0
- package/dist/providers/live.js +56 -0
- package/dist/providers/live.js.map +1 -0
- package/dist/providers/process.d.ts +29 -0
- package/dist/providers/process.js +71 -0
- package/dist/providers/process.js.map +1 -0
- package/dist/providers/reaper.d.ts +33 -0
- package/dist/providers/reaper.js +65 -0
- package/dist/providers/reaper.js.map +1 -0
- package/dist/providers/service.d.ts +87 -0
- package/dist/providers/service.js +256 -0
- package/dist/providers/service.js.map +1 -0
- package/dist/sdk.d.ts +1 -1
- package/dist/sdk.js +1 -1
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +385 -6
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +7 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/dist/web/app.css +4646 -0
- package/dist/web/app.js +22640 -0
- package/dist/web/app.js.map +7 -0
- package/dist/web/shell.html +39 -0
- package/docs/README.md +21 -0
- package/docs/SETUP.md +10 -0
- package/docs/adapters.md +21 -8
- package/docs/brain-continuity.md +186 -0
- package/docs/cloud.md +108 -0
- package/docs/crews.md +62 -0
- package/docs/design-system.md +31 -2
- package/docs/integration-notes.md +128 -20
- package/docs/loompad.md +105 -0
- package/docs/observability.md +220 -0
- package/docs/play-store.md +123 -0
- package/docs/proposals/agent-teams.md +249 -0
- package/docs/proposals/brain-continuity-implementation.md +468 -0
- package/docs/proposals/brain-session-continuity.md +623 -0
- package/docs/proposals/t3code-port.md +81 -0
- package/docs/proposals/t3code-reference.md +95 -0
- package/docs/refactoring/AGENT-TEAMS-PROGRESS.md +88 -0
- package/docs/refactoring/BOUNDARIES.md +120 -0
- package/docs/refactoring/BRAIN-AUDIT.md +176 -0
- package/docs/refactoring/BRAIN-NOTES.md +390 -0
- package/docs/refactoring/BRAIN-TODO.md +214 -0
- package/docs/refactoring/NOTES.md +129 -0
- package/docs/refactoring/T3-PORT-NOTES.md +165 -0
- package/docs/refactoring/T3-PORT-TODO.md +123 -0
- package/docs/refactoring/TODO.md +46 -0
- package/docs/refactoring/brain-benchmark-2026-09-28.json +48 -0
- package/docs/refactoring/brain-benchmark-audit-2026-09-28.json +48 -0
- package/docs/release-notes/v0.2.0.md +35 -0
- package/docs/release-notes/v0.2.1.md +59 -0
- package/docs/release-notes/v0.2.3.md +77 -0
- package/docs/release-notes/v0.2.4.md +128 -0
- package/docs/release-notes/v0.2.5.md +153 -0
- package/docs/release-notes/v0.2.6.md +62 -0
- package/docs/release-notes/v1.0.0.md +120 -0
- package/docs/release-notes/v1.0.1.md +77 -0
- package/docs/release-notes/v1.1.0.md +71 -0
- package/docs/release-notes/v1.1.1.md +38 -0
- package/docs/release-notes/v1.1.2.md +21 -0
- package/docs/teams-architecture.md +697 -0
- package/docs/teams.md +171 -0
- package/docs/windows.md +85 -0
- package/package.json +22 -9
- package/dist/adapters/claude-code.d.ts +0 -26
- package/dist/adapters/claude-code.js +0 -209
- package/dist/adapters/claude-code.js.map +0 -1
- package/dist/adapters/codex.d.ts +0 -51
- package/dist/adapters/codex.js +0 -291
- package/dist/adapters/codex.js.map +0 -1
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code on the provider contract: one warm Agent SDK query per chat
|
|
3
|
+
* session, fed by a streaming-input prompt queue.
|
|
4
|
+
*
|
|
5
|
+
* query({ prompt: queue, options }) → initializationResult() (startSession)
|
|
6
|
+
* queue.push(user message) → … stream … → result (sendTurn, many per process)
|
|
7
|
+
* query.setModel(model) (model switch, in-session)
|
|
8
|
+
*
|
|
9
|
+
* The CLI stays up between turns, so a follow-up is one user message on an
|
|
10
|
+
* open stream rather than a process launch and a session resume. SDK messages
|
|
11
|
+
* are mapped onto the canonical runtime events; nothing above this file reads
|
|
12
|
+
* one.
|
|
13
|
+
*
|
|
14
|
+
* Ported from t3code (MIT, © T3 Tools Inc.):
|
|
15
|
+
* apps/server/src/provider/Layers/ClaudeAdapter.ts — startSession (prompt
|
|
16
|
+
* queue, query options, session id chosen up front), sendTurn (setModel, the
|
|
17
|
+
* user message carries the turn id), interruptTurn (a hard session boundary:
|
|
18
|
+
* the query is closed, because interrupt() can acknowledge while background
|
|
19
|
+
* tasks keep the CLI alive), canUseTool, and the stream/result handling.
|
|
20
|
+
* Loom spawns the CLI itself (spawnClaudeCodeProcess) so each session owns a
|
|
21
|
+
* process group, and stopping it proves every tool it started is gone.
|
|
22
|
+
*/
|
|
23
|
+
import { attachedImages } from "../attachments.js";
|
|
24
|
+
import fs from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import os from "node:os";
|
|
27
|
+
import { randomUUID } from "node:crypto";
|
|
28
|
+
/**
|
|
29
|
+
* The Agent SDK is ~30 MB resident once loaded. Every daemon imports this
|
|
30
|
+
* adapter (and continuity's harness probe imports claudeBin from it), but most
|
|
31
|
+
* never start a Claude session — so the SDK loads on the first one, not at boot.
|
|
32
|
+
*/
|
|
33
|
+
let sdk = null;
|
|
34
|
+
const loadSdk = () => (sdk ??= import("@anthropic-ai/claude-agent-sdk").catch((error) => { sdk = null; throw error; }));
|
|
35
|
+
import { guardNativeOutput } from "../../adapters/base.js";
|
|
36
|
+
import { EventHub } from "../adapter.js";
|
|
37
|
+
import { ProviderError } from "../errors.js";
|
|
38
|
+
import { spawnHarness, stopHarness } from "../process.js";
|
|
39
|
+
/**
|
|
40
|
+
* Where `claude` lives when it isn't on PATH. The installer puts it in
|
|
41
|
+
* ~/.local/bin, which a daemon started from the desktop app often doesn't
|
|
42
|
+
* have on PATH. PATH is still the last word.
|
|
43
|
+
*/
|
|
44
|
+
export function claudeBin(override) {
|
|
45
|
+
if (override)
|
|
46
|
+
return fs.existsSync(override) ? override : null;
|
|
47
|
+
const home = process.env.HOME ?? os.homedir();
|
|
48
|
+
const candidates = process.platform === "win32" ? [path.join(home, ".local", "bin", "claude.exe")] : [`${home}/.local/bin/claude`, "/opt/homebrew/bin/claude", "/usr/local/bin/claude", `${home}/.bun/bin/claude`, `${home}/.volta/bin/claude`];
|
|
49
|
+
for (const p of candidates) {
|
|
50
|
+
if (p && fs.existsSync(p))
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
return "claude";
|
|
54
|
+
}
|
|
55
|
+
const MISSING_SESSION = /No conversation found|session .* (?:not found|does not exist)/i;
|
|
56
|
+
const WINDOW_MINUTES = { five_hour: 300, seven_day: 10_080, seven_day_opus: 10_080, seven_day_sonnet: 10_080 };
|
|
57
|
+
const EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
58
|
+
/** What a person may answer to a Claude tool approval. */
|
|
59
|
+
const APPROVAL_OPTIONS = [
|
|
60
|
+
{ decision: "accept", label: "Allow" },
|
|
61
|
+
{ decision: "acceptForSession", label: "Allow for this session" },
|
|
62
|
+
{ decision: "decline", label: "Deny" },
|
|
63
|
+
];
|
|
64
|
+
/** `["--a", "1", "--b"]` → `{ a: "1", b: null }`, the SDK's extraArgs shape. */
|
|
65
|
+
export function extraArgsRecord(args) {
|
|
66
|
+
if (args === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (!Array.isArray(args) || args.some(a => typeof a !== "string"))
|
|
69
|
+
throw new Error("extraArgs must be a list of strings");
|
|
70
|
+
if (!args.length)
|
|
71
|
+
return undefined;
|
|
72
|
+
const out = {};
|
|
73
|
+
for (let i = 0; i < args.length; i++) {
|
|
74
|
+
const arg = args[i];
|
|
75
|
+
if (!arg.startsWith("--"))
|
|
76
|
+
continue;
|
|
77
|
+
const eq = arg.indexOf("=");
|
|
78
|
+
if (eq > 0) {
|
|
79
|
+
out[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const next = args[i + 1];
|
|
83
|
+
out[arg.slice(2)] = next !== undefined && !next.startsWith("--") ? (i++, next) : null;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
/** t3code's classifyToolItemType, for the tools Claude Code ships. */
|
|
88
|
+
export function claudeToolItemType(name) {
|
|
89
|
+
if (name === "Bash" || name === "BashOutput")
|
|
90
|
+
return "command_execution";
|
|
91
|
+
if (EDIT_TOOLS.has(name))
|
|
92
|
+
return "file_change";
|
|
93
|
+
if (name.startsWith("mcp__"))
|
|
94
|
+
return "mcp_tool_call";
|
|
95
|
+
if (name === "WebSearch" || name === "WebFetch")
|
|
96
|
+
return "web_search";
|
|
97
|
+
if (name === "Task" || name === "Agent")
|
|
98
|
+
return "collab_agent_tool_call";
|
|
99
|
+
return "dynamic_tool_call";
|
|
100
|
+
}
|
|
101
|
+
function requestTypeFor(name) {
|
|
102
|
+
const t = claudeToolItemType(name);
|
|
103
|
+
if (t === "command_execution")
|
|
104
|
+
return "command_execution_approval";
|
|
105
|
+
if (t === "file_change")
|
|
106
|
+
return "file_change_approval";
|
|
107
|
+
if (name === "Read" || name === "Glob" || name === "Grep" || name === "LS")
|
|
108
|
+
return "file_read_approval";
|
|
109
|
+
return "unknown";
|
|
110
|
+
}
|
|
111
|
+
export function summarizeToolInput(name, input) {
|
|
112
|
+
if (name === "TodoWrite" && Array.isArray(input.todos)) {
|
|
113
|
+
const todos = input.todos;
|
|
114
|
+
return `TodoWrite: ${todos.filter(t => t.status === "completed").length}/${todos.length} done`;
|
|
115
|
+
}
|
|
116
|
+
const interesting = input.file_path ?? input.notebook_path ?? input.command ?? input.query ?? input.pattern ?? input.url ?? input.description ?? input.prompt ?? "";
|
|
117
|
+
return `${name}: ${String(interesting).replace(/\s+/g, " ").slice(0, 160)}`;
|
|
118
|
+
}
|
|
119
|
+
/** Tokens now in context from a main-thread response's usage: its last
|
|
120
|
+
* server-side iteration when present, input (with cache) plus output. */
|
|
121
|
+
export function activeTokens(value) {
|
|
122
|
+
if (!value || typeof value !== "object")
|
|
123
|
+
return 0;
|
|
124
|
+
const usage = value;
|
|
125
|
+
const iterations = Array.isArray(usage.iterations) ? usage.iterations : [];
|
|
126
|
+
const u = (iterations.length ? iterations[iterations.length - 1] : usage);
|
|
127
|
+
const n = (k) => (typeof u[k] === "number" && Number.isFinite(u[k]) ? Math.max(0, u[k]) : 0);
|
|
128
|
+
if (n("total_tokens") > 0)
|
|
129
|
+
return n("total_tokens");
|
|
130
|
+
return n("input_tokens") + n("cache_read_input_tokens") + n("cache_creation_input_tokens") + n("output_tokens");
|
|
131
|
+
}
|
|
132
|
+
/** An unbounded async queue: the streaming-input prompt. */
|
|
133
|
+
class PromptQueue {
|
|
134
|
+
items = [];
|
|
135
|
+
waiting = null;
|
|
136
|
+
closed = false;
|
|
137
|
+
push(message) {
|
|
138
|
+
if (this.closed)
|
|
139
|
+
throw new Error("prompt queue is closed");
|
|
140
|
+
if (this.waiting) {
|
|
141
|
+
const w = this.waiting;
|
|
142
|
+
this.waiting = null;
|
|
143
|
+
w({ value: message, done: false });
|
|
144
|
+
}
|
|
145
|
+
else
|
|
146
|
+
this.items.push(message);
|
|
147
|
+
}
|
|
148
|
+
close() {
|
|
149
|
+
this.closed = true;
|
|
150
|
+
if (this.waiting) {
|
|
151
|
+
const w = this.waiting;
|
|
152
|
+
this.waiting = null;
|
|
153
|
+
w({ value: undefined, done: true });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
[Symbol.asyncIterator]() {
|
|
157
|
+
return {
|
|
158
|
+
next: () => {
|
|
159
|
+
const item = this.items.shift();
|
|
160
|
+
if (item)
|
|
161
|
+
return Promise.resolve({ value: item, done: false });
|
|
162
|
+
if (this.closed)
|
|
163
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
164
|
+
return new Promise(resolve => { this.waiting = resolve; });
|
|
165
|
+
},
|
|
166
|
+
return: () => { this.close(); return Promise.resolve({ value: undefined, done: true }); },
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export class ClaudeProviderAdapter {
|
|
171
|
+
instanceId;
|
|
172
|
+
options;
|
|
173
|
+
provider = "claude-code";
|
|
174
|
+
capabilities = { sessionModelSwitch: "in-session", supportsConversationRollback: false, manualCompaction: false };
|
|
175
|
+
sessions = new Map();
|
|
176
|
+
hub = new EventHub();
|
|
177
|
+
constructor(instanceId, options = {}) {
|
|
178
|
+
this.instanceId = instanceId;
|
|
179
|
+
this.options = options;
|
|
180
|
+
}
|
|
181
|
+
onEvent(listener) { return this.hub.subscribe(listener); }
|
|
182
|
+
listSessions() { return [...this.sessions.values()].map(s => ({ ...s.info })); }
|
|
183
|
+
hasSession(threadId) { return this.sessions.has(threadId); }
|
|
184
|
+
emit(threadId, type, payload, extra = {}) {
|
|
185
|
+
this.hub.publish({ eventId: randomUUID(), provider: this.provider, instanceId: this.instanceId, threadId, createdAt: Date.now(),
|
|
186
|
+
...(extra.turnId ? { turnId: extra.turnId } : {}), ...(extra.itemId ? { itemId: extra.itemId } : {}),
|
|
187
|
+
...(extra.requestId ? { requestId: extra.requestId } : {}), type, payload });
|
|
188
|
+
}
|
|
189
|
+
// ---- sessions ------------------------------------------------------------
|
|
190
|
+
async startSession(input) {
|
|
191
|
+
if (this.sessions.has(input.threadId))
|
|
192
|
+
await this.stopSession(input.threadId);
|
|
193
|
+
const fail = (code, message, cause) => new ProviderError(code, "startSession", message, { provider: this.provider, instanceId: this.instanceId, threadId: input.threadId, mayHaveStarted: false, cause });
|
|
194
|
+
const bin = claudeBin(this.options.bin);
|
|
195
|
+
if (!bin)
|
|
196
|
+
throw fail("transport", "claude CLI not found — install Claude Code or set its path");
|
|
197
|
+
let extraArgs;
|
|
198
|
+
try {
|
|
199
|
+
extraArgs = extraArgsRecord(this.options.extraArgs);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
throw fail("validation", error.message);
|
|
203
|
+
}
|
|
204
|
+
const resume = typeof input.resumeCursor === "string" && input.resumeCursor ? input.resumeCursor : undefined;
|
|
205
|
+
const sessionId = resume ?? randomUUID();
|
|
206
|
+
const prompts = new PromptQueue();
|
|
207
|
+
const model = input.modelSelection?.model;
|
|
208
|
+
const mcpServers = this.options.mcpServers?.();
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
const session = {
|
|
211
|
+
info: { provider: this.provider, instanceId: this.instanceId, threadId: input.threadId, status: "connecting",
|
|
212
|
+
runtimeMode: input.runtimeMode, cwd: input.cwd, resumeCursor: sessionId, createdAt: now, updatedAt: now, ...(model ? { model } : {}) },
|
|
213
|
+
sessionId, prompts, proc: null, turn: null, tools: new Map(), pending: new Map(), inputs: new Map(), planMode: false, compactions: 0, stopping: false,
|
|
214
|
+
};
|
|
215
|
+
const options = {
|
|
216
|
+
cwd: input.cwd,
|
|
217
|
+
pathToClaudeCodeExecutable: bin,
|
|
218
|
+
// Claude Code's own system prompt and the user's/project's settings, as
|
|
219
|
+
// an interactive `claude` would load them.
|
|
220
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
221
|
+
settingSources: ["user", "project", "local"],
|
|
222
|
+
includePartialMessages: true,
|
|
223
|
+
...this.permissions(session, input.runtimeMode),
|
|
224
|
+
...(resume ? { resume } : { sessionId }),
|
|
225
|
+
...(model ? { model } : {}),
|
|
226
|
+
...(input.modelSelection?.effort ? { effort: input.modelSelection.effort } : {}),
|
|
227
|
+
...(mcpServers && Object.keys(mcpServers).length ? { mcpServers } : {}),
|
|
228
|
+
...(extraArgs ? { extraArgs } : {}),
|
|
229
|
+
spawnClaudeCodeProcess: spawnOptions => {
|
|
230
|
+
const proc = spawnHarness(spawnOptions.command, spawnOptions.args, { cwd: spawnOptions.cwd ?? input.cwd,
|
|
231
|
+
env: spawnOptions.env });
|
|
232
|
+
session.proc = proc;
|
|
233
|
+
guardNativeOutput(proc.child, error => {
|
|
234
|
+
session.failure = error.message;
|
|
235
|
+
if (!session.turn)
|
|
236
|
+
this.emit(input.threadId, "runtime.error", { message: error.message, class: "transport_error" });
|
|
237
|
+
void this.stopSession(input.threadId).catch(() => { });
|
|
238
|
+
});
|
|
239
|
+
const onAbort = () => { void stopHarness(proc, 0).catch(() => { }); };
|
|
240
|
+
if (spawnOptions.signal.aborted)
|
|
241
|
+
onAbort();
|
|
242
|
+
else
|
|
243
|
+
spawnOptions.signal.addEventListener("abort", onAbort, { once: true });
|
|
244
|
+
return proc.child;
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
this.emit(input.threadId, "session.state.changed", { state: "starting" });
|
|
248
|
+
let q;
|
|
249
|
+
try {
|
|
250
|
+
q = (await loadSdk()).query({ prompt: prompts, options });
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
throw fail("transport", `claude could not start: ${error.message}`, error);
|
|
254
|
+
}
|
|
255
|
+
session.query = q;
|
|
256
|
+
session.done = this.consume(session);
|
|
257
|
+
const timeoutMs = this.options.startTimeoutMs ?? 60_000;
|
|
258
|
+
try {
|
|
259
|
+
// A resume of a session the CLI doesn't have fails here, before any
|
|
260
|
+
// prompt is read: "No conversation found with session ID".
|
|
261
|
+
await Promise.race([q.initializationResult(),
|
|
262
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`claude did not initialize within ${Math.round(timeoutMs / 1000)}s`)), timeoutMs).unref())]);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
session.stopping = true;
|
|
266
|
+
prompts.close();
|
|
267
|
+
try {
|
|
268
|
+
q.close();
|
|
269
|
+
}
|
|
270
|
+
catch { /* already closed */ }
|
|
271
|
+
if (session.proc)
|
|
272
|
+
await stopHarness(session.proc, 0).catch(() => { });
|
|
273
|
+
const stderr = session.proc?.stderr().trim() ?? "";
|
|
274
|
+
if (resume && MISSING_SESSION.test(`${error.message}\n${stderr}`))
|
|
275
|
+
throw fail("session_missing", `claude session ${resume} could not be resumed`, error);
|
|
276
|
+
const failure = fail("transport", `claude could not start: ${error.message}`.slice(0, 500), error);
|
|
277
|
+
if (stderr)
|
|
278
|
+
failure.details.stderr = stderr;
|
|
279
|
+
throw failure;
|
|
280
|
+
}
|
|
281
|
+
session.info = { ...session.info, status: "ready", updatedAt: Date.now() };
|
|
282
|
+
this.sessions.set(input.threadId, session);
|
|
283
|
+
this.emit(input.threadId, "session.started", resume ? { resume } : {});
|
|
284
|
+
this.emit(input.threadId, "thread.started", { providerThreadId: sessionId });
|
|
285
|
+
this.emit(input.threadId, "session.state.changed", { state: "ready" });
|
|
286
|
+
return { ...session.info };
|
|
287
|
+
}
|
|
288
|
+
/** Permission mode and approval callback from the runtime mode. */
|
|
289
|
+
/**
|
|
290
|
+
* Permission mode from the runtime mode, and a `canUseTool` in every mode (as
|
|
291
|
+
* t3code has it): questions (AskUserQuestion) and plans (ExitPlanMode) reach
|
|
292
|
+
* Loom whatever the mode. Other tools the CLI asks about are allowed in full
|
|
293
|
+
* access, put to a person in approval-required, and denied in auto — Loom's
|
|
294
|
+
* auto never stops to ask; a tool runs there only if Claude's settings allow it.
|
|
295
|
+
*/
|
|
296
|
+
permissions(s, mode) {
|
|
297
|
+
const permissionMode = this.options.permissionMode
|
|
298
|
+
?? (mode === "full-access" ? "bypassPermissions" : mode === "auto-accept-edits" ? "acceptEdits"
|
|
299
|
+
// Approval-required needs someone to ask; with nobody, it runs read-only (plan), never "allow".
|
|
300
|
+
: this.options.canAsk && !this.options.canAsk() ? "plan" : "default");
|
|
301
|
+
s.basePermissionMode = permissionMode;
|
|
302
|
+
const canUseTool = (tool, toolInput, { signal, toolUseID, suggestions }) => this.canUseTool(s, tool, toolInput, signal, toolUseID, suggestions);
|
|
303
|
+
return { permissionMode, canUseTool, ...(permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}) };
|
|
304
|
+
}
|
|
305
|
+
async canUseTool(s, tool, toolInput, signal, toolUseId, suggestions) {
|
|
306
|
+
if (s.stopping)
|
|
307
|
+
return { behavior: "deny", message: "Session stopped." };
|
|
308
|
+
if (tool === "AskUserQuestion")
|
|
309
|
+
return this.askUser(s, toolInput, signal, toolUseId);
|
|
310
|
+
if (tool === "ExitPlanMode") {
|
|
311
|
+
// t3code: the client captures the plan; the model stops and waits.
|
|
312
|
+
const plan = typeof toolInput.plan === "string" ? toolInput.plan.trim() : "";
|
|
313
|
+
if (plan)
|
|
314
|
+
this.emit(s.info.threadId, "turn.proposed.completed", { planMarkdown: plan }, s.turn ? { turnId: s.turn.id } : {});
|
|
315
|
+
return { behavior: "deny", message: "The client captured your proposed plan. Stop here and wait for the user's feedback or implementation request in a later turn." };
|
|
316
|
+
}
|
|
317
|
+
const base = s.basePermissionMode;
|
|
318
|
+
if (base === "bypassPermissions")
|
|
319
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
320
|
+
if (base === "acceptEdits" || base === "plan" || base === "dontAsk")
|
|
321
|
+
return { behavior: "deny", message: `${tool} is not allowed in this permission mode. Loom's Auto mode never stops to ask; allow the tool in Claude's settings or switch this agent to Always ask.` };
|
|
322
|
+
const requestId = randomUUID();
|
|
323
|
+
const requestType = requestTypeFor(tool);
|
|
324
|
+
const turnId = s.turn?.id;
|
|
325
|
+
const decision = await new Promise(resolve => {
|
|
326
|
+
s.pending.set(requestId, { requestType, resolve, ...(turnId ? { turnId } : {}), ...(toolUseId ? { toolUseId } : {}) });
|
|
327
|
+
const onAbort = () => this.resolvePending(s, requestId, "cancel");
|
|
328
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
329
|
+
this.emit(s.info.threadId, "request.opened", { requestType, detail: summarizeToolInput(tool, toolInput),
|
|
330
|
+
args: { tool, input: toolInput }, options: APPROVAL_OPTIONS }, { requestId, ...(turnId ? { turnId } : {}), ...(toolUseId ? { itemId: toolUseId } : {}) });
|
|
331
|
+
if (signal.aborted)
|
|
332
|
+
onAbort();
|
|
333
|
+
});
|
|
334
|
+
if (decision === "accept")
|
|
335
|
+
return { behavior: "allow", updatedInput: toolInput };
|
|
336
|
+
// t3code's toSessionPermissionUpdates: the CLI's own suggestions, scoped to this session.
|
|
337
|
+
if (decision === "acceptForSession")
|
|
338
|
+
return { behavior: "allow", updatedInput: toolInput,
|
|
339
|
+
updatedPermissions: suggestions?.length ? suggestions.map(u => ({ ...u, destination: "session" }))
|
|
340
|
+
: [{ type: "addRules", rules: [{ toolName: tool }], behavior: "allow", destination: "session" }] };
|
|
341
|
+
if (toolUseId) {
|
|
342
|
+
const t = s.tools.get(toolUseId);
|
|
343
|
+
if (t)
|
|
344
|
+
t.declined = true;
|
|
345
|
+
}
|
|
346
|
+
return { behavior: "deny", message: decision === "cancel" ? "User cancelled tool execution." : "Denied in Loom." };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* AskUserQuestion → `user-input.requested`, and the answers back as the
|
|
350
|
+
* tool's input (t3code's handleAskUserQuestion). A question's id is its
|
|
351
|
+
* text: the SDK looks answers up by question text.
|
|
352
|
+
*/
|
|
353
|
+
async askUser(s, toolInput, signal, toolUseId) {
|
|
354
|
+
const raw = Array.isArray(toolInput.questions) ? toolInput.questions : [];
|
|
355
|
+
const questions = raw.map((q, i) => ({
|
|
356
|
+
id: typeof q.question === "string" && q.question ? q.question : `q-${i}`,
|
|
357
|
+
header: typeof q.header === "string" ? q.header : `Question ${i + 1}`,
|
|
358
|
+
question: typeof q.question === "string" ? q.question : "",
|
|
359
|
+
options: (Array.isArray(q.options) ? q.options : [])
|
|
360
|
+
.map(o => ({ label: typeof o.label === "string" ? o.label : "", description: typeof o.description === "string" ? o.description : "" })),
|
|
361
|
+
multiSelect: q.multiSelect === true,
|
|
362
|
+
allowCustomAnswer: true,
|
|
363
|
+
}));
|
|
364
|
+
const requestId = randomUUID();
|
|
365
|
+
const turnId = s.turn?.id;
|
|
366
|
+
let aborted = false;
|
|
367
|
+
const answers = await new Promise(resolve => {
|
|
368
|
+
s.inputs.set(requestId, { resolve, ...(turnId ? { turnId } : {}) });
|
|
369
|
+
const onAbort = () => { aborted = true; this.resolveInput(s, requestId, {}); };
|
|
370
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
371
|
+
this.emit(s.info.threadId, "user-input.requested", { questions }, { requestId, ...(turnId ? { turnId } : {}), ...(toolUseId ? { itemId: toolUseId } : {}) });
|
|
372
|
+
if (signal.aborted)
|
|
373
|
+
onAbort();
|
|
374
|
+
});
|
|
375
|
+
if (aborted || s.stopping || !Object.keys(answers).length)
|
|
376
|
+
return { behavior: "deny", message: "User cancelled tool execution." };
|
|
377
|
+
const flat = Object.fromEntries(Object.entries(answers).map(([k, v]) => [k, Array.isArray(v) ? v.map(String).join(", ") : String(v)]));
|
|
378
|
+
return { behavior: "allow", updatedInput: { questions: toolInput.questions, answers: flat } };
|
|
379
|
+
}
|
|
380
|
+
resolveInput(s, requestId, answers) {
|
|
381
|
+
const pending = s.inputs.get(requestId);
|
|
382
|
+
if (!pending)
|
|
383
|
+
return false;
|
|
384
|
+
s.inputs.delete(requestId);
|
|
385
|
+
pending.resolve(answers);
|
|
386
|
+
this.emit(s.info.threadId, "user-input.resolved", { answers }, { requestId, ...(pending.turnId ? { turnId: pending.turnId } : {}) });
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
resolvePending(s, requestId, decision) {
|
|
390
|
+
const pending = s.pending.get(requestId);
|
|
391
|
+
if (!pending)
|
|
392
|
+
return false;
|
|
393
|
+
s.pending.delete(requestId);
|
|
394
|
+
pending.resolve(decision);
|
|
395
|
+
this.emit(s.info.threadId, "request.resolved", { requestType: pending.requestType, decision }, { requestId, ...(pending.turnId ? { turnId: pending.turnId } : {}) });
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
session(threadId, operation) {
|
|
399
|
+
const s = this.sessions.get(threadId);
|
|
400
|
+
if (!s || s.stopping)
|
|
401
|
+
throw new ProviderError("not_found", operation, `no live claude session for chat "${threadId}"`, { provider: this.provider, instanceId: this.instanceId, threadId, mayHaveStarted: false });
|
|
402
|
+
return s;
|
|
403
|
+
}
|
|
404
|
+
async sendTurn(input) {
|
|
405
|
+
const s = this.session(input.threadId, "sendTurn");
|
|
406
|
+
const refuse = (message, cause) => new ProviderError("request", "sendTurn", message, { provider: this.provider, instanceId: this.instanceId, threadId: input.threadId, mayHaveStarted: false, cause });
|
|
407
|
+
if (s.turn)
|
|
408
|
+
throw new ProviderError("validation", "sendTurn", "a claude turn is already running in this chat", { provider: this.provider, instanceId: this.instanceId, threadId: input.threadId, mayHaveStarted: false });
|
|
409
|
+
const model = input.modelSelection?.model;
|
|
410
|
+
if (model && model !== s.info.model) {
|
|
411
|
+
try {
|
|
412
|
+
await s.query.setModel(model);
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
throw refuse(`claude could not switch to model ${model}: ${error.message}`, error);
|
|
416
|
+
}
|
|
417
|
+
s.info = { ...s.info, model };
|
|
418
|
+
}
|
|
419
|
+
// Plan mode is Claude's own permission mode, switched per turn (t3code).
|
|
420
|
+
const wantPlan = input.interactionMode === "plan";
|
|
421
|
+
if (wantPlan !== s.planMode) {
|
|
422
|
+
try {
|
|
423
|
+
await s.query.setPermissionMode(wantPlan ? "plan" : s.basePermissionMode ?? "default");
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
throw refuse(`claude could not ${wantPlan ? "enter" : "leave"} plan mode: ${error.message}`, error);
|
|
427
|
+
}
|
|
428
|
+
s.planMode = wantPlan;
|
|
429
|
+
}
|
|
430
|
+
const turnId = randomUUID();
|
|
431
|
+
s.turn = { id: turnId, ...(s.info.model ? { model: s.info.model } : {}) };
|
|
432
|
+
s.info = { ...s.info, status: "running", activeTurnId: turnId, updatedAt: Date.now() };
|
|
433
|
+
this.emit(input.threadId, "turn.started", s.info.model ? { model: s.info.model } : {}, { turnId });
|
|
434
|
+
try {
|
|
435
|
+
// attached pictures go in as image blocks, so Claude sees them rather than a path it may not open
|
|
436
|
+
const pictures = attachedImages(input.input, s.info.cwd).map((im) => ({
|
|
437
|
+
type: "image", source: { type: "base64", media_type: im.mime, data: fs.readFileSync(im.abs).toString("base64") }
|
|
438
|
+
}));
|
|
439
|
+
s.prompts.push({ type: "user", message: { role: "user", content: [{ type: "text", text: input.input }, ...pictures] },
|
|
440
|
+
parent_tool_use_id: null, uuid: turnId });
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
s.turn = null;
|
|
444
|
+
delete s.info.activeTurnId;
|
|
445
|
+
this.emit(input.threadId, "turn.aborted", { reason: "the claude session closed before the turn was sent" }, { turnId });
|
|
446
|
+
throw refuse("the claude session closed before the turn was sent", error);
|
|
447
|
+
}
|
|
448
|
+
return { threadId: input.threadId, turnId, resumeCursor: s.sessionId };
|
|
449
|
+
}
|
|
450
|
+
/** A hard boundary, as t3code has it: close the query and its process group. The next turn resumes. */
|
|
451
|
+
async interruptTurn(threadId) {
|
|
452
|
+
if (!this.sessions.has(threadId))
|
|
453
|
+
return;
|
|
454
|
+
await this.stopSession(threadId);
|
|
455
|
+
}
|
|
456
|
+
async respondToRequest(threadId, requestId, decision) {
|
|
457
|
+
const s = this.session(threadId, "respondToRequest");
|
|
458
|
+
if (!this.resolvePending(s, requestId, decision))
|
|
459
|
+
throw new ProviderError("not_found", "respondToRequest", `no open claude request "${requestId}"`, { provider: this.provider, threadId });
|
|
460
|
+
}
|
|
461
|
+
async respondToUserInput(threadId, requestId, answers) {
|
|
462
|
+
const s = this.session(threadId, "respondToUserInput");
|
|
463
|
+
if (!this.resolveInput(s, requestId, answers))
|
|
464
|
+
throw new ProviderError("not_found", "respondToUserInput", `no open claude question "${requestId}"`, { provider: this.provider, threadId });
|
|
465
|
+
}
|
|
466
|
+
async stopSession(threadId) {
|
|
467
|
+
const s = this.sessions.get(threadId);
|
|
468
|
+
if (!s)
|
|
469
|
+
return;
|
|
470
|
+
if (!s.stopping) {
|
|
471
|
+
s.stopping = true;
|
|
472
|
+
for (const id of [...s.pending.keys()])
|
|
473
|
+
this.resolvePending(s, id, "cancel");
|
|
474
|
+
for (const id of [...s.inputs.keys()])
|
|
475
|
+
this.resolveInput(s, id, {});
|
|
476
|
+
s.prompts.close();
|
|
477
|
+
try {
|
|
478
|
+
s.query.close();
|
|
479
|
+
}
|
|
480
|
+
catch { /* already closed */ }
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
if (s.proc)
|
|
484
|
+
await stopHarness(s.proc, 0);
|
|
485
|
+
await Promise.race([s.done, new Promise(resolve => setTimeout(resolve, 3000).unref())]);
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
if (s.failure)
|
|
489
|
+
this.closed(s, s.failure, "error", "aborted");
|
|
490
|
+
else
|
|
491
|
+
this.closed(s, "session stopped", "graceful", "interrupted");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async stopAll() {
|
|
495
|
+
const results = await Promise.allSettled([...this.sessions.keys()].map(id => this.stopSession(id)));
|
|
496
|
+
const failed = results.find((r) => r.status === "rejected");
|
|
497
|
+
if (failed)
|
|
498
|
+
throw failed.reason;
|
|
499
|
+
}
|
|
500
|
+
/** The session is over: end its turn and report the exit (once). */
|
|
501
|
+
closed(s, reason, exitKind, turnEnd, stderr = "") {
|
|
502
|
+
const threadId = s.info.threadId;
|
|
503
|
+
if (this.sessions.get(threadId) !== s)
|
|
504
|
+
return;
|
|
505
|
+
this.sessions.delete(threadId);
|
|
506
|
+
if (s.turn) {
|
|
507
|
+
const turnId = s.turn.id;
|
|
508
|
+
s.turn = null;
|
|
509
|
+
if (turnEnd === "interrupted")
|
|
510
|
+
this.emit(threadId, "turn.completed", { state: "interrupted", stopReason: reason }, { turnId });
|
|
511
|
+
else
|
|
512
|
+
this.emit(threadId, "turn.aborted", { reason: `${reason} before the turn completed; native outcome is unknown`,
|
|
513
|
+
...(stderr ? { detail: { stderr } } : {}) }, { turnId });
|
|
514
|
+
}
|
|
515
|
+
delete s.info.activeTurnId;
|
|
516
|
+
s.info = { ...s.info, status: exitKind === "error" ? "error" : "closed", updatedAt: Date.now() };
|
|
517
|
+
this.emit(threadId, "session.exited", { reason, exitKind, recoverable: true });
|
|
518
|
+
}
|
|
519
|
+
/** Read the SDK stream until the CLI goes away. */
|
|
520
|
+
async consume(s) {
|
|
521
|
+
let failure = null;
|
|
522
|
+
try {
|
|
523
|
+
for await (const msg of s.query) {
|
|
524
|
+
try {
|
|
525
|
+
this.handle(s, msg);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
failure = `claude event handling failed: ${error.message}`;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
failure = error.message;
|
|
535
|
+
}
|
|
536
|
+
if (s.stopping)
|
|
537
|
+
return;
|
|
538
|
+
s.stopping = true;
|
|
539
|
+
for (const id of [...s.pending.keys()])
|
|
540
|
+
this.resolvePending(s, id, "cancel");
|
|
541
|
+
for (const id of [...s.inputs.keys()])
|
|
542
|
+
this.resolveInput(s, id, {});
|
|
543
|
+
s.prompts.close();
|
|
544
|
+
try {
|
|
545
|
+
s.query.close();
|
|
546
|
+
}
|
|
547
|
+
catch { /* already closed */ }
|
|
548
|
+
if (s.proc)
|
|
549
|
+
await stopHarness(s.proc, 0).catch(() => { });
|
|
550
|
+
const stderr = s.proc?.stderr().trim() ?? "";
|
|
551
|
+
const reason = `claude exited${failure ? `: ${failure}`.slice(0, 300) : ""}${stderr ? ` — ${stderr.slice(-300)}` : ""}`;
|
|
552
|
+
this.closed(s, reason, failure ? "error" : "graceful", "aborted", stderr);
|
|
553
|
+
}
|
|
554
|
+
// ---- SDK → canonical -----------------------------------------------------
|
|
555
|
+
handle(s, msg) {
|
|
556
|
+
const threadId = s.info.threadId;
|
|
557
|
+
const turnId = s.turn?.id;
|
|
558
|
+
const at = turnId ? { turnId } : {};
|
|
559
|
+
switch (msg.type) {
|
|
560
|
+
case "system":
|
|
561
|
+
if (msg.subtype === "init") {
|
|
562
|
+
if (typeof msg.model === "string" && msg.model)
|
|
563
|
+
s.info = { ...s.info, model: msg.model };
|
|
564
|
+
if (msg.session_id && msg.session_id !== s.sessionId) {
|
|
565
|
+
s.sessionId = msg.session_id;
|
|
566
|
+
s.info = { ...s.info, resumeCursor: msg.session_id };
|
|
567
|
+
this.emit(threadId, "thread.started", { providerThreadId: msg.session_id });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else if (msg.subtype === "status") {
|
|
571
|
+
if (msg.status === "compacting")
|
|
572
|
+
this.emit(threadId, "item.started", { itemType: "context_compaction", status: "inProgress" }, { ...at, itemId: `compaction:${++s.compactions}` });
|
|
573
|
+
if (msg.compact_result === "failed")
|
|
574
|
+
this.emit(threadId, "runtime.warning", { message: `compaction failed${msg.compact_error ? `: ${msg.compact_error}` : ""}` }, at);
|
|
575
|
+
}
|
|
576
|
+
else if (msg.subtype === "compact_boundary") {
|
|
577
|
+
// Native compaction: the session now holds a summary, not its history.
|
|
578
|
+
const meta = msg.compact_metadata;
|
|
579
|
+
this.emit(threadId, "thread.state.changed", { state: "compacted", trigger: meta.trigger === "manual" ? "manual" : "auto",
|
|
580
|
+
...(typeof meta.pre_tokens === "number" ? { beforeTokens: meta.pre_tokens } : {}),
|
|
581
|
+
...(typeof meta.post_tokens === "number" ? { afterTokens: meta.post_tokens } : {}) }, at);
|
|
582
|
+
if (typeof meta.post_tokens === "number" && meta.post_tokens > 0) {
|
|
583
|
+
s.used = meta.post_tokens;
|
|
584
|
+
this.contextUsage(s, at);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
case "stream_event": {
|
|
589
|
+
if (msg.parent_tool_use_id)
|
|
590
|
+
return; // a sub-agent's narration is not this chat's
|
|
591
|
+
const event = msg.event;
|
|
592
|
+
if (event.type === "message_start") {
|
|
593
|
+
if (s.turn && event.message?.id)
|
|
594
|
+
s.turn.streamingMessageId = event.message.id;
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (event.type !== "content_block_delta" || !s.turn)
|
|
598
|
+
return;
|
|
599
|
+
const d = event.delta;
|
|
600
|
+
const text = d?.type === "text_delta" ? d.text : d?.type === "thinking_delta" ? d.thinking : undefined;
|
|
601
|
+
if (!text)
|
|
602
|
+
return;
|
|
603
|
+
const itemId = s.turn.streamingMessageId !== undefined && event.index !== undefined ? `${s.turn.streamingMessageId}:${event.index}` : undefined;
|
|
604
|
+
this.emit(threadId, "content.delta", { streamKind: d?.type === "text_delta" ? "assistant_text" : "reasoning_text", delta: text,
|
|
605
|
+
...(event.index !== undefined ? { contentIndex: event.index } : {}) }, { ...at, ...(itemId ? { itemId } : {}) });
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
case "assistant": {
|
|
609
|
+
const main = msg.parent_tool_use_id === null;
|
|
610
|
+
const message = msg.message;
|
|
611
|
+
if (main) {
|
|
612
|
+
if (typeof message.model === "string" && message.model && s.turn)
|
|
613
|
+
s.turn.model = message.model;
|
|
614
|
+
const used = activeTokens(message.usage);
|
|
615
|
+
if (used > 0 && used !== s.used) {
|
|
616
|
+
s.used = used;
|
|
617
|
+
this.contextUsage(s, at);
|
|
618
|
+
}
|
|
619
|
+
if (msg.error === "authentication_failed")
|
|
620
|
+
this.emit(threadId, "runtime.error", { message: "claude not signed in — run `claude` and /login, then try again", class: "permission_error" }, at);
|
|
621
|
+
}
|
|
622
|
+
(message.content ?? []).forEach((block, index) => {
|
|
623
|
+
const itemId = message.id ? `${message.id}:${index}` : undefined;
|
|
624
|
+
const extra = { ...at, ...(itemId ? { itemId } : {}) };
|
|
625
|
+
if (block.type === "text" && typeof block.text === "string" && main) {
|
|
626
|
+
this.emit(threadId, "item.completed", { itemType: "assistant_message", status: "completed", detail: block.text }, extra);
|
|
627
|
+
}
|
|
628
|
+
else if (block.type === "thinking" && typeof block.thinking === "string" && main) {
|
|
629
|
+
this.emit(threadId, "item.completed", { itemType: "reasoning", status: "completed", detail: block.thinking }, extra);
|
|
630
|
+
}
|
|
631
|
+
else if (block.type === "tool_use" && typeof block.id === "string") {
|
|
632
|
+
const name = String(block.name ?? "tool");
|
|
633
|
+
const toolInput = (block.input ?? {});
|
|
634
|
+
let itemType = claudeToolItemType(name);
|
|
635
|
+
// A write outside the session's directory (plan mode's own plan file
|
|
636
|
+
// under ~/.claude/plans) is not a change to the project.
|
|
637
|
+
const target = toolInput.file_path ?? toolInput.notebook_path;
|
|
638
|
+
if (itemType === "file_change" && typeof target === "string" && outside(s.info.cwd, target))
|
|
639
|
+
itemType = "dynamic_tool_call";
|
|
640
|
+
const tool = { name, input: toolInput, itemType };
|
|
641
|
+
s.tools.set(block.id, tool);
|
|
642
|
+
this.emit(threadId, "item.started", { itemType: tool.itemType, status: "inProgress", ...describeTool(tool),
|
|
643
|
+
...(main ? {} : { parentToolUseId: msg.parent_tool_use_id }) }, { ...at, itemId: block.id });
|
|
644
|
+
// A todo list is the agent's plan: shown as a checklist that updates in place.
|
|
645
|
+
if (name === "TodoWrite" && main && Array.isArray(toolInput.todos)) {
|
|
646
|
+
const plan = toolInput.todos.map(t => ({ step: String(t.content ?? t.activeForm ?? ""),
|
|
647
|
+
status: t.status === "completed" ? "completed" : t.status === "in_progress" ? "inProgress" : "pending" })).filter(t => t.step);
|
|
648
|
+
if (plan.length)
|
|
649
|
+
this.emit(threadId, "turn.plan.updated", { plan }, at);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
case "user": {
|
|
656
|
+
const content = msg.message.content;
|
|
657
|
+
if (!Array.isArray(content))
|
|
658
|
+
return;
|
|
659
|
+
for (const block of content) {
|
|
660
|
+
if (block.type !== "tool_result" || typeof block.tool_use_id !== "string")
|
|
661
|
+
continue;
|
|
662
|
+
const tool = s.tools.get(block.tool_use_id);
|
|
663
|
+
if (!tool)
|
|
664
|
+
continue;
|
|
665
|
+
s.tools.delete(block.tool_use_id);
|
|
666
|
+
const output = toolResultText(block.content);
|
|
667
|
+
const images = toolResultImages(block.content);
|
|
668
|
+
const status = tool.declined ? "declined" : block.is_error === true ? "failed" : "completed";
|
|
669
|
+
this.emit(threadId, "item.completed", { itemType: tool.itemType, status, ...describeTool(tool, output, images, block.is_error === true),
|
|
670
|
+
...(msg.parent_tool_use_id ? { parentToolUseId: msg.parent_tool_use_id } : {}) }, { ...at, itemId: block.tool_use_id });
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
case "result": {
|
|
675
|
+
const windows = Object.values(msg.modelUsage ?? {}).map(u => u.contextWindow).filter(w => w > 0);
|
|
676
|
+
if (windows.length)
|
|
677
|
+
s.contextWindow = Math.max(...windows);
|
|
678
|
+
this.contextUsage(s, at);
|
|
679
|
+
const turn = s.turn;
|
|
680
|
+
if (!turn)
|
|
681
|
+
return; // a result for no turn of ours (a background task)
|
|
682
|
+
s.turn = null;
|
|
683
|
+
// A tool with no result by the end of the turn finished without saying how.
|
|
684
|
+
for (const [id, tool] of s.tools)
|
|
685
|
+
this.emit(threadId, "item.completed", { itemType: tool.itemType, ...describeTool(tool) }, { turnId: turn.id, itemId: id });
|
|
686
|
+
s.tools.clear();
|
|
687
|
+
delete s.info.activeTurnId;
|
|
688
|
+
s.info = { ...s.info, status: "ready", updatedAt: Date.now() };
|
|
689
|
+
const errors = msg.subtype === "success" ? [] : msg.errors ?? [];
|
|
690
|
+
const interrupted = !msg.is_error ? false : errors.some(e => /interrupt/i.test(e));
|
|
691
|
+
const errorMessage = msg.subtype === "success" ? (msg.is_error ? msg.result || "unknown error" : undefined) : errors.join("; ") || msg.subtype;
|
|
692
|
+
// Cache reads and creations count as input, so tokens aren't lost.
|
|
693
|
+
const usage = msg.usage;
|
|
694
|
+
this.emit(threadId, "turn.completed", {
|
|
695
|
+
state: !msg.is_error ? "completed" : interrupted ? "interrupted" : "failed",
|
|
696
|
+
...(msg.is_error && !interrupted && errorMessage ? { errorMessage } : {}),
|
|
697
|
+
...(msg.total_cost_usd !== undefined ? { totalCostUsd: msg.total_cost_usd } : {}),
|
|
698
|
+
...(turn.model ? { model: turn.model } : {}),
|
|
699
|
+
tokenUsage: { usageStatus: "complete", inputTokens: (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0),
|
|
700
|
+
outputTokens: usage.output_tokens ?? 0, ...(usage.cache_read_input_tokens !== undefined ? { cachedInputTokens: usage.cache_read_input_tokens } : {}),
|
|
701
|
+
...(usage.cache_creation_input_tokens !== undefined ? { cacheCreationTokens: usage.cache_creation_input_tokens } : {}) },
|
|
702
|
+
}, { turnId: turn.id });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
case "rate_limit_event": {
|
|
706
|
+
const info = msg.rate_limit_info;
|
|
707
|
+
// Overage the account has provisioned keeps the turn running.
|
|
708
|
+
const blocked = info.status === "rejected" && !(info.overageStatus === "allowed" || info.overageStatus === "allowed_warning"
|
|
709
|
+
|| info.isUsingOverage === true || info.overageInUse === true);
|
|
710
|
+
if (typeof info.utilization === "number" && info.rateLimitType) {
|
|
711
|
+
this.emit(threadId, "account.rate-limits.updated", { windows: [{ id: info.rateLimitType, usedPercent: Math.round(info.utilization * 1000) / 10,
|
|
712
|
+
...(WINDOW_MINUTES[info.rateLimitType] ? { windowMinutes: WINDOW_MINUTES[info.rateLimitType] } : {}),
|
|
713
|
+
...(typeof info.resetsAt === "number" ? { resetsAt: info.resetsAt * 1000 } : {}) }], ...(blocked ? { reached: info.rateLimitType } : {}) }, at);
|
|
714
|
+
}
|
|
715
|
+
// A rejected window parks the turn inside the CLI: no result arrives
|
|
716
|
+
// until it resets, so say why the turn is waiting.
|
|
717
|
+
if (blocked)
|
|
718
|
+
this.emit(threadId, "runtime.warning", { message: `Claude usage limit reached${info.rateLimitType ? ` (${info.rateLimitType})` : ""}${typeof info.resetsAt === "number" ? ` — resets ${new Date(info.resetsAt * 1000).toLocaleString()}` : ""}` }, at);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
default:
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
contextUsage(s, at) {
|
|
726
|
+
if (s.used === undefined)
|
|
727
|
+
return;
|
|
728
|
+
const max = s.contextWindow;
|
|
729
|
+
this.emit(s.info.threadId, "thread.token-usage.updated", { usage: { usedTokens: max ? Math.min(s.used, max) : s.used,
|
|
730
|
+
...(max ? { maxTokens: max } : {}), compactsAutomatically: true } }, at);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/** Title, detail and normalized data for a Claude tool call. */
|
|
734
|
+
function describeTool(tool, output, images, failed = false) {
|
|
735
|
+
const detail = summarizeToolInput(tool.name, tool.input);
|
|
736
|
+
switch (tool.itemType) {
|
|
737
|
+
case "command_execution": {
|
|
738
|
+
const command = String(tool.input.command ?? "");
|
|
739
|
+
return { detail: command, data: { command, ...(output !== undefined ? { output } : {}) } };
|
|
740
|
+
}
|
|
741
|
+
case "mcp_tool_call": {
|
|
742
|
+
// mcp__<server>__<tool>
|
|
743
|
+
const [, server = "", ...rest] = tool.name.split("__");
|
|
744
|
+
return { title: tool.name, detail: `${server} · ${rest.join("__") || tool.name}`, data: { tool: rest.join("__") || tool.name, server, input: tool.input,
|
|
745
|
+
...(output !== undefined ? (failed ? { error: output } : { output }) : {}), ...(images?.length ? { images } : {}) } };
|
|
746
|
+
}
|
|
747
|
+
case "file_change": {
|
|
748
|
+
const p = tool.input.file_path ?? tool.input.notebook_path;
|
|
749
|
+
return { title: tool.name, detail, data: { changes: p ? [{ path: String(p), kind: tool.name === "Write" ? "add_or_update" : "update" }] : [] } };
|
|
750
|
+
}
|
|
751
|
+
default:
|
|
752
|
+
return { title: tool.name, detail, data: { tool: tool.name, input: tool.input,
|
|
753
|
+
...(output !== undefined ? (failed ? { error: output } : { output }) : {}), ...(images?.length ? { images } : {}) } };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/** Image blocks in a tool result (Read on a PNG, an MCP screenshot): base64 for ingestion to file. */
|
|
757
|
+
function toolResultImages(content) {
|
|
758
|
+
if (!Array.isArray(content))
|
|
759
|
+
return [];
|
|
760
|
+
return content.flatMap(c => {
|
|
761
|
+
const src = c && c.type === "image" ? c.source : undefined;
|
|
762
|
+
return src && src.type === "base64" && typeof src.data === "string" ? [{ data: src.data, mime: String(src.media_type ?? "image/png") }] : [];
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
/** Is an absolute path outside `dir`? Symlinks resolved (macOS /tmp is /private/tmp). */
|
|
766
|
+
function outside(dir, file) {
|
|
767
|
+
if (!path.isAbsolute(file))
|
|
768
|
+
return false;
|
|
769
|
+
const real = (p) => { try {
|
|
770
|
+
return fs.realpathSync(p);
|
|
771
|
+
}
|
|
772
|
+
catch {
|
|
773
|
+
try {
|
|
774
|
+
return path.join(fs.realpathSync(path.dirname(p)), path.basename(p));
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
return p;
|
|
778
|
+
}
|
|
779
|
+
} };
|
|
780
|
+
const rel = path.relative(real(dir), real(file));
|
|
781
|
+
return rel.startsWith("..") || path.isAbsolute(rel);
|
|
782
|
+
}
|
|
783
|
+
function toolResultText(content) {
|
|
784
|
+
if (typeof content === "string")
|
|
785
|
+
return content;
|
|
786
|
+
if (!Array.isArray(content))
|
|
787
|
+
return undefined;
|
|
788
|
+
return content.map(c => (c && typeof c === "object" && typeof c.text === "string" ? c.text : "")).join("");
|
|
789
|
+
}
|
|
790
|
+
//# sourceMappingURL=adapter.js.map
|