@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,663 @@
|
|
|
1
|
+
import { NotHolderError } from "../../core/baton.js";
|
|
2
|
+
import { triage } from "../../core/review-triage.js";
|
|
3
|
+
import * as checkpoints from "../../core/checkpoint.js";
|
|
4
|
+
import { push as gitPush, stageAndCommitFiles } from "../../core/git.js";
|
|
5
|
+
import { logbook } from "../../core/logbook.js";
|
|
6
|
+
import { writeMcpSession } from "../../core/mcp.js";
|
|
7
|
+
import { NO_CHANGES } from "../../core/step-conditions.js";
|
|
8
|
+
import { agentAllowed } from "../../core/team-policy.js";
|
|
9
|
+
import { diffSinceSnapshot, porcelainStatus } from "../../core/worktree.js";
|
|
10
|
+
import { MAIN_CHAT, isAdapter } from "../../types.js";
|
|
11
|
+
import { planModeBriefing } from '../runtime-support.js';
|
|
12
|
+
import { ProviderAgent } from "../../providers/agent.js";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { ContinuityError, NativeDispatchRejected } from "../../core/continuity/contracts.js";
|
|
15
|
+
import { isNativeKind } from "../../core/continuity/capabilities.js";
|
|
16
|
+
/** How long you asked the answer to be, this once ("" for the usual). */
|
|
17
|
+
function lengthLine(length) {
|
|
18
|
+
return length === "brief"
|
|
19
|
+
? "Keep this reply brief: the answer first, a few sentences at most, no preamble."
|
|
20
|
+
: length === "detailed"
|
|
21
|
+
? "Give a detailed reply this time: explain your reasoning and the trade-offs, with examples where they help."
|
|
22
|
+
: "";
|
|
23
|
+
}
|
|
24
|
+
/** Owns turns state for exactly one open project. */
|
|
25
|
+
export class RuntimeTurns {
|
|
26
|
+
host;
|
|
27
|
+
constructor(host) {
|
|
28
|
+
this.host = host;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Which conversation each agent's current turn belongs to. Set when a turn
|
|
32
|
+
* starts and left in place afterwards — an agent's trailing events (a late
|
|
33
|
+
* run_complete, a diff) still belong to the chat that prompted them.
|
|
34
|
+
*/
|
|
35
|
+
turnChat = new Map();
|
|
36
|
+
// A turn's cost lands on a `turn_cost` status just before its `run_complete`
|
|
37
|
+
// (the CLI reports it mid-stream). We hold it here so the completed turn — and
|
|
38
|
+
// therefore its exported gen_ai span — carries the real cost, not just tokens.
|
|
39
|
+
pendingCost = new Map();
|
|
40
|
+
// Turn text accumulated per agent (from its message events) so we can extract
|
|
41
|
+
// structured decisions once the turn completes. Reset after each run_complete.
|
|
42
|
+
turnText = new Map();
|
|
43
|
+
/** Pre-turn porcelain snapshots, for per-prompt diff attribution. */
|
|
44
|
+
preTurnTree = new Map();
|
|
45
|
+
/** The checkpoint taken before each agent's current turn (#101). */
|
|
46
|
+
turnCheckpoint = new Map();
|
|
47
|
+
/**
|
|
48
|
+
* The diff of each agent's most recent turn, as a promise.
|
|
49
|
+
*
|
|
50
|
+
* A route's step conditions ("run the reviewer if more than 200 lines
|
|
51
|
+
* changed") are decided the moment the turn completes, which is before the
|
|
52
|
+
* diff has finished being computed. Keeping the promise lets the route wait
|
|
53
|
+
* for the real numbers instead of reading the previous turn's.
|
|
54
|
+
*/
|
|
55
|
+
lastTurnDiff = new Map();
|
|
56
|
+
/** App-owned diff/commit writes finish before the next native writer starts. */
|
|
57
|
+
postTurn = new Map();
|
|
58
|
+
// -------------------------------------------------------------------------
|
|
59
|
+
// Stale sessions
|
|
60
|
+
// -------------------------------------------------------------------------
|
|
61
|
+
/**
|
|
62
|
+
* When each adapter's current turn started. Set at dispatch, cleared when its
|
|
63
|
+
* run_complete / error / interrupted lands. An entry much older than any
|
|
64
|
+
* plausible turn is a hung session: the process is alive enough to hold
|
|
65
|
+
* `busy` and dead enough to never finish, which blocks every dispatch with
|
|
66
|
+
* "is busy" until someone notices.
|
|
67
|
+
*/
|
|
68
|
+
busySince = new Map();
|
|
69
|
+
preparing = new Map();
|
|
70
|
+
/** Native requests between capture and settlement (or queueing). */
|
|
71
|
+
inFlight = new Set();
|
|
72
|
+
/**
|
|
73
|
+
* Write down what the files are, before a turn changes them (#101).
|
|
74
|
+
*
|
|
75
|
+
* Announced in the log so the thread can offer "put it back" on the turn
|
|
76
|
+
* that follows it, and so the list survives a daemon restart with the
|
|
77
|
+
* labels intact. Failing is not an error: a checkpoint is a courtesy, and
|
|
78
|
+
* a project that isn't a git repo simply doesn't get one. What it must
|
|
79
|
+
* never do is stop the turn.
|
|
80
|
+
*/
|
|
81
|
+
async checkpointBefore(agentId, prompt) {
|
|
82
|
+
try {
|
|
83
|
+
const label = prompt.replace(/\s+/g, " ").trim().slice(0, 120) || `a turn by ${agentId}`;
|
|
84
|
+
const cp = await checkpoints.capture(this.host.agentDir(agentId), label);
|
|
85
|
+
if (!cp || this.host.closed)
|
|
86
|
+
return;
|
|
87
|
+
// Carried onto this turn's turn_diff, so the card that shows what
|
|
88
|
+
// changed also knows the point to put it back to. Working it out in the
|
|
89
|
+
// client by "the checkpoint nearest above this card" would be right
|
|
90
|
+
// until the day two turns interleave.
|
|
91
|
+
this.turnCheckpoint.set(agentId, cp.id);
|
|
92
|
+
this.host.log.append({
|
|
93
|
+
kind: "checkpoint",
|
|
94
|
+
agentId,
|
|
95
|
+
payload: { id: cp.id, label: cp.label, at: cp.at, dirty: cp.dirty, branch: cp.branch, reason: "before_turn" },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/* never the reason a turn doesn't run */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Every point this project's files can be put back to, newest first. */
|
|
103
|
+
checkpoints() {
|
|
104
|
+
return checkpoints.list(this.host.info.dir);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Put the files back, and say what moved.
|
|
108
|
+
*
|
|
109
|
+
* Refused while an agent is mid-turn: rewinding the tree under a running
|
|
110
|
+
* agent gives it a working directory that contradicts everything it has
|
|
111
|
+
* read this turn, and the damage lands in whatever it writes next.
|
|
112
|
+
*/
|
|
113
|
+
async rewind(id) {
|
|
114
|
+
if (this.host.continuity?.store.activeReceipts().length)
|
|
115
|
+
throw new ContinuityError("recovery_required", "finish or reconcile native writers before rewinding files");
|
|
116
|
+
const busy = [...this.busySince.keys()];
|
|
117
|
+
if (busy.length) {
|
|
118
|
+
throw new Error(`${busy.join(", ")} ${busy.length === 1 ? "is" : "are"} mid-turn — stop the turn first, or the rewind lands underneath it`);
|
|
119
|
+
}
|
|
120
|
+
const out = await checkpoints.restore(this.host.info.dir, id);
|
|
121
|
+
this.host.log.append({
|
|
122
|
+
kind: "checkpoint",
|
|
123
|
+
payload: {
|
|
124
|
+
id: out.restored.id,
|
|
125
|
+
label: out.restored.label,
|
|
126
|
+
at: Date.now(),
|
|
127
|
+
reason: "rewound",
|
|
128
|
+
files: out.changed.length,
|
|
129
|
+
undo: out.undo.id,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
/** What an agent's last turn changed — for route step conditions. */
|
|
135
|
+
async turnFacts(agentId) {
|
|
136
|
+
const diff = await (this.lastTurnDiff.get(agentId) ?? Promise.resolve(null));
|
|
137
|
+
if (!diff)
|
|
138
|
+
return NO_CHANGES;
|
|
139
|
+
return { files: diff.files.map((f) => f.path), added: diff.added, removed: diff.removed };
|
|
140
|
+
}
|
|
141
|
+
/** After a turn: log which files that prompt changed (turn_diff), then learn. */
|
|
142
|
+
captureTurnDiff(agentId) {
|
|
143
|
+
this.postTurn.delete(agentId);
|
|
144
|
+
const before = this.preTurnTree.get(agentId);
|
|
145
|
+
if (before === undefined) {
|
|
146
|
+
// No snapshot (e.g. a turn with no pre-tree) — still worth reading.
|
|
147
|
+
// The previous turn's diff goes with it: a route asking what this turn
|
|
148
|
+
// changed must not be handed the last one's numbers.
|
|
149
|
+
this.lastTurnDiff.delete(agentId);
|
|
150
|
+
this.host.extractMemory(agentId, []);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.preTurnTree.delete(agentId);
|
|
154
|
+
const checkpoint = this.turnCheckpoint.get(agentId);
|
|
155
|
+
this.turnCheckpoint.delete(agentId);
|
|
156
|
+
// The chat the turn ran in, read now: by the time the diff is computed the
|
|
157
|
+
// agent may already be on its next turn somewhere else. Without it, every
|
|
158
|
+
// "changed N files" card landed in Main whatever thread asked.
|
|
159
|
+
const turnChat = this.turnChat.get(agentId);
|
|
160
|
+
const pending = diffSinceSnapshot(this.host.agentDir(agentId), before).catch(() => null);
|
|
161
|
+
this.lastTurnDiff.set(agentId, pending);
|
|
162
|
+
const finalized = pending
|
|
163
|
+
.then(async (diff) => {
|
|
164
|
+
if (this.host.closed)
|
|
165
|
+
return;
|
|
166
|
+
if (diff) {
|
|
167
|
+
this.host.log.append({
|
|
168
|
+
kind: "turn_diff",
|
|
169
|
+
...(turnChat && turnChat !== MAIN_CHAT ? { chat: turnChat } : {}),
|
|
170
|
+
agentId,
|
|
171
|
+
payload: {
|
|
172
|
+
files: diff.files,
|
|
173
|
+
added: diff.added,
|
|
174
|
+
removed: diff.removed,
|
|
175
|
+
patch: diff.patch,
|
|
176
|
+
truncated: diff.truncated,
|
|
177
|
+
// what to read first: the files ranked by what a reviewer would want to see (core/review-triage.ts)
|
|
178
|
+
triage: triage(diff.patch, diff.files).files.slice(0, 40).map(({ path, level, reasons }) => ({ path, level, reasons: reasons.slice(0, 3) })),
|
|
179
|
+
// The point these changes can be put back to, when there is one
|
|
180
|
+
// — a project that isn't a git repo has no checkpoint to offer.
|
|
181
|
+
...(checkpoint ? { checkpoint } : {}),
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
await this.commitTurn(agentId, diff.files.map((f) => f.path));
|
|
185
|
+
}
|
|
186
|
+
// Learn from the turn once we know which files it touched — the files
|
|
187
|
+
// sharpen candidate retrieval. Runs after the diff so recentTurnFiles
|
|
188
|
+
// isn't needed; the files are right here.
|
|
189
|
+
this.host.extractMemory(agentId, (diff?.files ?? []).map((f) => f.path));
|
|
190
|
+
})
|
|
191
|
+
.catch(() => this.host.extractMemory(agentId, []));
|
|
192
|
+
this.postTurn.set(agentId, finalized);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Opt-in: commit a turn's changes as they land, with the agent as co-author.
|
|
196
|
+
*
|
|
197
|
+
* git blame on a fleet's work answered "who wrote this" with whoever ran the
|
|
198
|
+
* daemon. With `git.commitPerTurn` on, each turn's changes become one commit —
|
|
199
|
+
* subject from the prompt that caused them, `Co-Authored-By: <agent> via
|
|
200
|
+
* Loom` so both git log and GitHub attribute the work.
|
|
201
|
+
*
|
|
202
|
+
* Off by default and per-project on purpose: committing is a policy, not a
|
|
203
|
+
* mechanic, and half-done turns land too. Only the files THIS turn touched
|
|
204
|
+
* are staged, so two agents finishing close together each commit their own
|
|
205
|
+
* work rather than whoever finishes second swallowing both.
|
|
206
|
+
*/
|
|
207
|
+
async commitTurn(agentId, files) {
|
|
208
|
+
const delivery = this.host.config.git?.delivery ?? "none";
|
|
209
|
+
if ((!this.host.config.git?.commitPerTurn && delivery === "none") || !files.length)
|
|
210
|
+
return;
|
|
211
|
+
try {
|
|
212
|
+
const events = this.host.log.list({ limit: 60 });
|
|
213
|
+
const prompt = [...events].reverse().find((e) => e.kind === "message" && !e.agentId)?.payload.text ?? "";
|
|
214
|
+
const subject = String(prompt).split("\n")[0].slice(0, 68) || `work by ${agentId}`;
|
|
215
|
+
const cfg = this.host.config.agents.find((a) => a.id === agentId);
|
|
216
|
+
const message = `${subject}\n\n` +
|
|
217
|
+
`Turn by ${agentId}${cfg ? ` (${cfg.kind})` : ""} in Loom.\n` +
|
|
218
|
+
`Co-Authored-By: ${agentId} <${agentId}@loom.local>`;
|
|
219
|
+
await stageAndCommitFiles(this.host.agentDir(agentId), files, message);
|
|
220
|
+
this.host.log.append({
|
|
221
|
+
kind: "status",
|
|
222
|
+
agentId,
|
|
223
|
+
payload: { state: "turn_committed", files: files.length, subject },
|
|
224
|
+
});
|
|
225
|
+
// "push" delivers each committed turn; "pr" leaves pushing to the
|
|
226
|
+
// orchestra's branch (a PR per turn is the flood teams complain about).
|
|
227
|
+
if (delivery === "push") {
|
|
228
|
+
const pushed = await gitPush(this.host.agentDir(agentId));
|
|
229
|
+
this.host.log.append({ kind: "status", agentId, payload: { state: "turn_pushed", branch: pushed.branch } });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
// A commit that can't happen (not a repo, hooks failed, nothing staged
|
|
234
|
+
// after filters) is a Console line, never a failed turn.
|
|
235
|
+
logbook.warn("git", `turn commit skipped for ${agentId}`, err instanceof Error ? err.message : String(err), this.host.info.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async sendMessage(text, agentId, opts = {}) {
|
|
239
|
+
if (this.host.continuity)
|
|
240
|
+
return this.sendContinuity(text, agentId, opts);
|
|
241
|
+
const source = opts.source ?? "user";
|
|
242
|
+
const chat = opts.chat ?? MAIN_CHAT;
|
|
243
|
+
// A thread that named an agent answers with that agent, whoever holds the
|
|
244
|
+
// baton — which is what lets two threads talk to two agents at once. An
|
|
245
|
+
// explicit target still wins: you asked for that one.
|
|
246
|
+
const bound = this.host.chatBinding(chat);
|
|
247
|
+
let target = agentId ?? bound.agentId ?? this.host.validHolder() ?? this.host.defaultAdapterId();
|
|
248
|
+
const agent = this.host.agent(target);
|
|
249
|
+
if (!isAdapter(agent)) {
|
|
250
|
+
throw new Error(`agent "${target}" is a bridge (read-only) — it cannot take turns`);
|
|
251
|
+
}
|
|
252
|
+
// Before anything is committed — the baton, the message in the thread, the
|
|
253
|
+
// process — check the agent can afford the turn. Refusing after the message
|
|
254
|
+
// is logged would leave a prompt in the conversation that nothing answers.
|
|
255
|
+
this.host.enforceQuarantine(target);
|
|
256
|
+
this.host.enforceBudget(target);
|
|
257
|
+
const kind = this.host.config.agents.find((a) => a.id === target)?.kind ?? "";
|
|
258
|
+
if (this.host.teamPolicy && !agentAllowed(this.host.teamPolicy, kind)) {
|
|
259
|
+
throw new Error(`team policy doesn't allow ${kind} on this repo (loom.team.json)`);
|
|
260
|
+
}
|
|
261
|
+
// The baton is the write lock for work that touches the repository. A
|
|
262
|
+
// thread pinned to an agent doesn't need it to answer a question, and
|
|
263
|
+
// taking it would stop the agent that IS working — so a pinned thread
|
|
264
|
+
// leaves it alone, and the thread that isn't pinned behaves as it always
|
|
265
|
+
// has.
|
|
266
|
+
const pinned = !agentId && bound.agentId === target;
|
|
267
|
+
if (!pinned) {
|
|
268
|
+
const holder = this.host.validHolder();
|
|
269
|
+
if (holder === null) {
|
|
270
|
+
this.host.baton.acquire(target);
|
|
271
|
+
}
|
|
272
|
+
else if (holder !== target) {
|
|
273
|
+
throw new NotHolderError(target, holder);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// The agent is mid-turn: queue the prompt, in order, and run it when the
|
|
277
|
+
// turn ends. It used to go straight to the adapter, which threw "busy" into
|
|
278
|
+
// an error event — the prompt was lost while the send had said 200.
|
|
279
|
+
// Answering while the agent is still busy is still answering.
|
|
280
|
+
if (source === "user")
|
|
281
|
+
this.host.releaseQuestionHold(target);
|
|
282
|
+
// A turn blocked on a question the agent asked through its tool won't end
|
|
283
|
+
// until that question is answered — so a message queued behind it would
|
|
284
|
+
// wait forever. What you type in that chat IS the answer: it goes to the
|
|
285
|
+
// question, and the turn carries on.
|
|
286
|
+
const open = source === "user" && !opts.fromQueue && this.busySince.has(target) ? this.host.openQuestion(target) : undefined;
|
|
287
|
+
if (open && open.chat === chat) {
|
|
288
|
+
this.host.log.append({ kind: "message", chat, payload: { text, author: "user", answers: open.requestId } });
|
|
289
|
+
await this.host.answerQuestion(target, chat, open.requestId, Object.fromEntries((open.ids.length ? open.ids : ["0"]).map((id) => [id, text])));
|
|
290
|
+
return { agentId: target, answered: true };
|
|
291
|
+
}
|
|
292
|
+
// It shows in the queue, editable, and enters the thread when it's sent.
|
|
293
|
+
if (!opts.fromQueue && this.busySince.has(target)) {
|
|
294
|
+
const item = this.host.queue.add({
|
|
295
|
+
text,
|
|
296
|
+
target: { kind: "agent", agentId: target },
|
|
297
|
+
chat,
|
|
298
|
+
source,
|
|
299
|
+
...(opts.plan ? { plan: true } : {}),
|
|
300
|
+
...(opts.length ? { length: opts.length } : {}),
|
|
301
|
+
});
|
|
302
|
+
return { agentId: target, queued: this.host.queue.length, queueId: item.id };
|
|
303
|
+
}
|
|
304
|
+
// A user reply to a paused route's question resumes the route — and to an
|
|
305
|
+
// agent's own question, the queue it was holding.
|
|
306
|
+
if (source === "user") {
|
|
307
|
+
this.host.routes.onUserMessage(target);
|
|
308
|
+
this.host.releaseQuestionHold(target);
|
|
309
|
+
}
|
|
310
|
+
// everything this turn produces belongs to the chat you sent from
|
|
311
|
+
this.turnChat.set(target, chat);
|
|
312
|
+
this.busySince.set(target, Date.now()); // the stale-session clock starts
|
|
313
|
+
this.host.log.append({
|
|
314
|
+
kind: "message",
|
|
315
|
+
chat,
|
|
316
|
+
payload: { text, author: source === "route" ? "loom" : "user", ...(opts.fromQueue ? { fromQueue: true } : {}) },
|
|
317
|
+
});
|
|
318
|
+
let mcp = null;
|
|
319
|
+
try {
|
|
320
|
+
await this.host.ensureStarted(target);
|
|
321
|
+
if (!this.host.isCurrentAgent(agent))
|
|
322
|
+
throw new Error(`agent "${target}" is no longer active`);
|
|
323
|
+
const pendingBriefing = this.host.consumePendingBriefing(target);
|
|
324
|
+
// Prepend the enabled skills so every turn carries them, alongside any
|
|
325
|
+
// one-shot handoff briefing. Empty when no skills are on.
|
|
326
|
+
// A provider agent plans in its own plan mode; the plan it proposes is
|
|
327
|
+
// saved under plans/ by the runtime. Other agents get Loom's briefing.
|
|
328
|
+
const nativePlan = Boolean(opts.plan) && agent instanceof ProviderAgent;
|
|
329
|
+
const briefing = [this.host.agentInstructions(target), this.host.activeSkillsBlock(), pendingBriefing, opts.plan && !nativePlan ? planModeBriefing(text) : "", lengthLine(opts.length)]
|
|
330
|
+
.filter(Boolean)
|
|
331
|
+
.join("\n")
|
|
332
|
+
.trim() || undefined;
|
|
333
|
+
// The project's configured MCP servers, rendered to a temp config file the
|
|
334
|
+
// adapter hands to its CLI. Null when nothing is configured — or when this
|
|
335
|
+
// adapter's CLI has no flag for it, because an "MCP attached" note on a
|
|
336
|
+
// turn that dropped the config would be the same lie in a new place.
|
|
337
|
+
mcp = agent.capabilities.mcp ? writeMcpSession(this.host.healthyMcps()) : null;
|
|
338
|
+
// The thread's model, only ever to an adapter that can act on it — see
|
|
339
|
+
// bindable(), which is where a model that couldn't be honoured is refused.
|
|
340
|
+
const perTurnModel = bound.agentId === target ? bound.model : undefined;
|
|
341
|
+
const input = {
|
|
342
|
+
text,
|
|
343
|
+
chat,
|
|
344
|
+
...(nativePlan ? { interactionMode: "plan" } : {}),
|
|
345
|
+
...(briefing ? { briefing } : {}),
|
|
346
|
+
...(perTurnModel ? { model: perTurnModel } : {}),
|
|
347
|
+
...(mcp ? { mcp: { configPath: mcp.configPath, servers: mcp.servers } } : {}),
|
|
348
|
+
};
|
|
349
|
+
if (mcp) {
|
|
350
|
+
this.host.log.append({
|
|
351
|
+
kind: "status",
|
|
352
|
+
agentId: target,
|
|
353
|
+
payload: { state: "mcp_attached", servers: mcp.servers.map((s) => s.name) },
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// Snapshot the tree so this prompt's changes can be attributed to it.
|
|
357
|
+
this.preTurnTree.set(target, await porcelainStatus(this.host.agentDir(target)));
|
|
358
|
+
// …and a checkpoint you can actually go back to. The porcelain snapshot
|
|
359
|
+
// above only says *which* paths changed; this holds their content, so
|
|
360
|
+
// "undo what that turn did" is a click rather than a re-typing (#101).
|
|
361
|
+
await this.checkpointBefore(target, text);
|
|
362
|
+
// Fire-and-notify: the turn runs in the background; progress streams
|
|
363
|
+
// into the log and completion lands as run_complete.
|
|
364
|
+
if (!this.host.isCurrentAgent(agent))
|
|
365
|
+
throw new Error(`agent "${target}" is no longer active`);
|
|
366
|
+
void Promise.resolve()
|
|
367
|
+
.then(() => {
|
|
368
|
+
if (!this.host.isCurrentAgent(agent))
|
|
369
|
+
throw new Error(`agent "${target}" is no longer active`);
|
|
370
|
+
return agent.send(input);
|
|
371
|
+
})
|
|
372
|
+
.catch((error) => this.host.dispatchFailed(agent, chat, error))
|
|
373
|
+
// The config file exists for exactly this turn. Cleaned up whether the
|
|
374
|
+
// turn succeeded, failed or was interrupted — a temp file per turn that
|
|
375
|
+
// nothing removes is a slow leak of the project's server URLs.
|
|
376
|
+
.finally(() => mcp?.cleanup());
|
|
377
|
+
return { agentId: target };
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
mcp?.cleanup();
|
|
381
|
+
this.host.dispatchFailed(agent, chat, error);
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async sendContinuity(text, agentId, opts) {
|
|
386
|
+
const brain = this.host.continuity;
|
|
387
|
+
const chat = opts.chat ?? MAIN_CHAT, source = opts.source ?? "user";
|
|
388
|
+
if (!this.host.chatExists(chat))
|
|
389
|
+
throw new ContinuityError("invalid", "conversation is missing or deleted; create a chat before dispatching");
|
|
390
|
+
const bound = this.host.chatBinding(chat);
|
|
391
|
+
const target = agentId ?? bound.agentId ?? this.host.validHolder() ?? this.host.defaultAdapterId();
|
|
392
|
+
const agent = this.host.agent(target);
|
|
393
|
+
const cfg = this.host.config.agents.find(a => a.id === target);
|
|
394
|
+
if (!isAdapter(agent) || !isNativeKind(cfg.kind))
|
|
395
|
+
throw new ContinuityError("unsupported", "native continuity supports Codex, Claude Code and OpenCode; bridges and model agents use the legacy workflow");
|
|
396
|
+
if (/^\[(?:image|file)\]\s/m.test(text))
|
|
397
|
+
throw new ContinuityError("unsupported", "attachment continuity is not verified for these CLI protocols; use an ordinary workspace file reference or the legacy attachment workflow");
|
|
398
|
+
if (Array.isArray(cfg.options?.extraArgs) && cfg.options.extraArgs.length)
|
|
399
|
+
throw new ContinuityError("unsupported", "arbitrary CLI arguments have unverified session/context semantics; remove extraArgs for native continuity");
|
|
400
|
+
this.host.enforceQuarantine(target);
|
|
401
|
+
this.host.enforceBudget(target);
|
|
402
|
+
if (this.host.teamPolicy && !agentAllowed(this.host.teamPolicy, cfg.kind))
|
|
403
|
+
throw new Error(`team policy doesn't allow ${cfg.kind}`);
|
|
404
|
+
// Every check that can refuse the turn runs before the request enters the
|
|
405
|
+
// conversation: a refused request must not become history.
|
|
406
|
+
const health = await this.host.harness(target);
|
|
407
|
+
if (!health.available)
|
|
408
|
+
throw new ContinuityError("unsupported", health.error ?? `${cfg.kind} CLI is not reachable`);
|
|
409
|
+
if (this.busySince.size && (!opts.requestId || !brain.store.request(opts.requestId)))
|
|
410
|
+
this.host.queue.assertCanAdd({ text });
|
|
411
|
+
const captured = brain.capture({ id: opts.requestId ?? randomUUID(), conversationId: chat, agentInstanceId: target,
|
|
412
|
+
text, source, model: opts.capturedModel !== undefined ? opts.capturedModel : bound.agentId === target ? bound.model ?? null : null,
|
|
413
|
+
plan: Boolean(opts.plan), targetAddedTokens: 6000 });
|
|
414
|
+
const request = captured.request;
|
|
415
|
+
const previous = brain.store.receipts(request.id).at(-1);
|
|
416
|
+
const status = (continuityStatus) => ({ agentId: target, requestId: request.id, continuityStatus,
|
|
417
|
+
...(previous ? { receiptId: previous.id, packetId: previous.packetId } : {}) });
|
|
418
|
+
// A retried ID is a no-op only while that request is live or was submitted.
|
|
419
|
+
// An unsent one (refused, overflowed, failed before launch) runs again.
|
|
420
|
+
if (!captured.created && !opts.fromQueue &&
|
|
421
|
+
(this.inFlight.has(request.id) || this.host.queue.snapshot().items.some(i => i.continuity?.requestId === request.id)))
|
|
422
|
+
return status(this.inFlight.has(request.id) ? "preparing" : "queued");
|
|
423
|
+
if (previous && (previous.status === "accepted" || previous.status === "submitting" || previous.status === "outcome_unknown"))
|
|
424
|
+
return status(previous.status);
|
|
425
|
+
// Default sequential foreground execution. A pinned chat does not bypass a
|
|
426
|
+
// workspace's writer lock. The queued target/model remain those captured now.
|
|
427
|
+
if (this.busySince.size) {
|
|
428
|
+
if (opts.fromQueue)
|
|
429
|
+
throw new ContinuityError("conflict", "another foreground turn is preparing or running");
|
|
430
|
+
const item = this.host.queue.add({ text, target: { kind: "agent", agentId: target }, chat, source,
|
|
431
|
+
...(opts.plan ? { plan: true } : {}), ...(opts.length ? { length: opts.length } : {}), continuity: { requestId: request.id, model: request.model } });
|
|
432
|
+
return { agentId: target, queued: this.host.queue.length, queueId: item.id, requestId: request.id };
|
|
433
|
+
}
|
|
434
|
+
this.busySince.set(target, Date.now());
|
|
435
|
+
this.turnChat.set(target, chat);
|
|
436
|
+
this.inFlight.add(request.id);
|
|
437
|
+
const preparation = new AbortController();
|
|
438
|
+
this.preparing.set(target, preparation);
|
|
439
|
+
const assertPrepared = () => {
|
|
440
|
+
if (preparation.signal.aborted || this.host.closed || !this.host.isCurrentAgent(agent))
|
|
441
|
+
throw new ContinuityError("conflict", "native dispatch preparation was cancelled or replaced");
|
|
442
|
+
};
|
|
443
|
+
let mcp = null, runId;
|
|
444
|
+
try {
|
|
445
|
+
const options = structuredClone(this.host.nativeOptions(target));
|
|
446
|
+
const holder = this.host.validHolder();
|
|
447
|
+
if (holder && holder !== target)
|
|
448
|
+
await this.host.handoff(target, { source });
|
|
449
|
+
else if (!holder)
|
|
450
|
+
this.host.baton.acquire(target);
|
|
451
|
+
await this.host.ensureStarted(target);
|
|
452
|
+
assertPrepared();
|
|
453
|
+
if (!this.host.isCurrentAgent(agent))
|
|
454
|
+
throw new Error("native target was replaced before dispatch");
|
|
455
|
+
// Snapshot/checkpoint is complete before the frozen context is observed.
|
|
456
|
+
this.preTurnTree.set(target, await porcelainStatus(this.host.agentDir(target)));
|
|
457
|
+
await this.checkpointBefore(target, text);
|
|
458
|
+
assertPrepared();
|
|
459
|
+
const nativePlan = Boolean(opts.plan) && agent instanceof ProviderAgent;
|
|
460
|
+
const supplement = [this.host.agentInstructions(target), this.host.activeSkillsBlock(), opts.plan && !nativePlan ? planModeBriefing(text) : "", lengthLine(opts.length)].filter(Boolean).join("\n");
|
|
461
|
+
let prepared = await brain.prepare({ ...request, targetAddedTokens: opts.contextTarget ?? request.targetAddedTokens }, cfg.kind, this.host.agentDir(target), options, supplement);
|
|
462
|
+
assertPrepared();
|
|
463
|
+
const finishOverflow = () => {
|
|
464
|
+
this.preparing.delete(target);
|
|
465
|
+
this.busySince.delete(target);
|
|
466
|
+
this.inFlight.delete(request.id);
|
|
467
|
+
mcp?.cleanup();
|
|
468
|
+
this.host.kickQueue();
|
|
469
|
+
return { agentId: target, requestId: request.id, receiptId: prepared.receipt.id,
|
|
470
|
+
packetId: prepared.packet.id, continuityStatus: "overflow" };
|
|
471
|
+
};
|
|
472
|
+
if (prepared.packet.budget.overflow === "mandatory")
|
|
473
|
+
return finishOverflow();
|
|
474
|
+
if (!this.host.isCurrentAgent(agent))
|
|
475
|
+
throw new Error("native target was replaced before submission");
|
|
476
|
+
mcp = agent.capabilities.mcp ? writeMcpSession(this.host.healthyMcps()) : null;
|
|
477
|
+
let continuity;
|
|
478
|
+
// Reassembly is safe only before submission intent exists. Never retry a
|
|
479
|
+
// submitted action automatically, even when its acknowledgement is lost.
|
|
480
|
+
for (let attempt = 0;; attempt++) {
|
|
481
|
+
try {
|
|
482
|
+
continuity = await brain.submit(prepared, preparation.signal);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
assertPrepared();
|
|
487
|
+
if (!(error instanceof ContinuityError) || error.code !== "stale" || attempt >= 2)
|
|
488
|
+
throw error;
|
|
489
|
+
prepared = await brain.prepare({ ...request, targetAddedTokens: opts.contextTarget ?? request.targetAddedTokens }, cfg.kind, this.host.agentDir(target), options, supplement);
|
|
490
|
+
assertPrepared();
|
|
491
|
+
if (prepared.packet.budget.overflow === "mandatory")
|
|
492
|
+
return finishOverflow();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
runId = continuity.runId;
|
|
496
|
+
this.preparing.delete(target);
|
|
497
|
+
const input = { text, chat, continuity, ...(nativePlan ? { interactionMode: "plan" } : {}),
|
|
498
|
+
...(request.model ? { model: request.model } : {}), ...(mcp ? { mcp: { configPath: mcp.configPath, servers: mcp.servers } } : {}) };
|
|
499
|
+
// Never consume a legacy handoff briefing into the new packet path.
|
|
500
|
+
this.host.pendingBriefings.delete(target);
|
|
501
|
+
if (source === "user") {
|
|
502
|
+
this.host.routes.onUserMessage(target);
|
|
503
|
+
this.host.releaseQuestionHold(target);
|
|
504
|
+
}
|
|
505
|
+
void Promise.resolve().then(() => {
|
|
506
|
+
if (preparation.signal.aborted || this.host.closed || !this.host.isCurrentAgent(agent))
|
|
507
|
+
throw new NativeDispatchRejected("dispatch cancelled before native process launch");
|
|
508
|
+
return agent.send(input);
|
|
509
|
+
}).then(() => {
|
|
510
|
+
if (!this.host.closed)
|
|
511
|
+
brain.settled(continuity.runId);
|
|
512
|
+
}, error => {
|
|
513
|
+
if (!this.host.closed)
|
|
514
|
+
brain.settled(continuity.runId, error);
|
|
515
|
+
this.host.dispatchFailed(agent, chat, error);
|
|
516
|
+
}).finally(async () => {
|
|
517
|
+
mcp?.cleanup();
|
|
518
|
+
await this.postTurn.get(target);
|
|
519
|
+
this.postTurn.delete(target);
|
|
520
|
+
// Native error events may arrive before the process exits. Only this
|
|
521
|
+
// settlement releases the runtime's foreground preparation barrier.
|
|
522
|
+
this.inFlight.delete(request.id);
|
|
523
|
+
if (!this.host.closed && this.host.isCurrentAgent(agent)) {
|
|
524
|
+
this.busySince.delete(target);
|
|
525
|
+
this.host.kickQueue();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
return { agentId: target, requestId: request.id, receiptId: prepared.receipt.id,
|
|
529
|
+
packetId: prepared.packet.id, continuityStatus: "submitting" };
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
this.preparing.delete(target);
|
|
533
|
+
this.inFlight.delete(request.id);
|
|
534
|
+
mcp?.cleanup();
|
|
535
|
+
if (runId)
|
|
536
|
+
brain.settled(runId, error);
|
|
537
|
+
this.host.dispatchFailed(agent, chat, error);
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** Adapters that look hung: busy far longer than any plausible turn. */
|
|
542
|
+
staleSessions(now = Date.now()) {
|
|
543
|
+
const out = [];
|
|
544
|
+
for (const [agentId, since] of this.busySince) {
|
|
545
|
+
const live = this.host.agents.get(agentId);
|
|
546
|
+
if (!live || !isAdapter(live) || !live.busy())
|
|
547
|
+
continue;
|
|
548
|
+
const busyMs = now - since;
|
|
549
|
+
if (busyMs >= this.host.staleTurnMs)
|
|
550
|
+
out.push({ agentId, busyMs });
|
|
551
|
+
}
|
|
552
|
+
return out;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Explicit baton pass: interrupt the current holder if mid-turn, project
|
|
556
|
+
* the log into the target's namespaced memory, arm the one-shot briefing.
|
|
557
|
+
* A *manual* handoff cancels any active route — the human outranks it.
|
|
558
|
+
*/
|
|
559
|
+
/**
|
|
560
|
+
* Re-run a failed turn on a different agent.
|
|
561
|
+
*
|
|
562
|
+
* When a turn failed, the only recovery was retyping the prompt at someone
|
|
563
|
+
* else — and the someone else started cold, not knowing an attempt had been
|
|
564
|
+
* made. This finds the failed turn's prompt, hands the baton to the chosen
|
|
565
|
+
* agent, and re-sends the same text with the failure attached as context, so
|
|
566
|
+
* the second agent knows what was tried and what it died of.
|
|
567
|
+
*
|
|
568
|
+
* The failure context rides the one-shot handoff briefing rather than the
|
|
569
|
+
* message text, so the thread shows the same clean prompt twice rather than
|
|
570
|
+
* a prompt wearing a stack trace.
|
|
571
|
+
*/
|
|
572
|
+
async retryTurn(toAgentId) {
|
|
573
|
+
const events = this.host.log.list({ limit: 200 });
|
|
574
|
+
// The last error, and the last user message before it: that pairing is the
|
|
575
|
+
// failed turn. Route-authored messages count too — a route step that died
|
|
576
|
+
// is exactly what you retry somewhere else.
|
|
577
|
+
let errorAt = -1;
|
|
578
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
579
|
+
if (events[i].kind === "error") {
|
|
580
|
+
errorAt = i;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (errorAt === -1)
|
|
585
|
+
throw new Error("no failed turn to retry");
|
|
586
|
+
const err = events[errorAt];
|
|
587
|
+
let prompt;
|
|
588
|
+
for (let i = errorAt - 1; i >= 0; i--) {
|
|
589
|
+
const e = events[i];
|
|
590
|
+
if (e.kind === "message" && !e.agentId) {
|
|
591
|
+
prompt = String(e.payload.text ?? "");
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (!prompt?.trim())
|
|
596
|
+
throw new Error("could not find the prompt that failed");
|
|
597
|
+
await this.host.handoff(toAgentId, { source: "user" });
|
|
598
|
+
const failedAgent = err.agentId ?? "the previous agent";
|
|
599
|
+
const failure = String(err.payload.message ?? "unknown error").slice(0, 500);
|
|
600
|
+
const prior = this.host.pendingBriefings.get(toAgentId);
|
|
601
|
+
this.host.pendingBriefings.set(toAgentId, [
|
|
602
|
+
prior,
|
|
603
|
+
`[Loom retry] "${failedAgent}" attempted this and failed with: ${failure}`,
|
|
604
|
+
"Do not repeat the failing approach without addressing the failure.",
|
|
605
|
+
]
|
|
606
|
+
.filter(Boolean)
|
|
607
|
+
.join("\n"));
|
|
608
|
+
await this.sendMessage(prompt, toAgentId, { chat: err.chat ?? MAIN_CHAT });
|
|
609
|
+
return { agentId: toAgentId, retried: prompt };
|
|
610
|
+
}
|
|
611
|
+
async interrupt(opts = {}) {
|
|
612
|
+
// Stop in one chat stops that chat's turn — not whatever the baton holder
|
|
613
|
+
// is doing in another thread.
|
|
614
|
+
if (opts.chat) {
|
|
615
|
+
for (const [id, chat] of this.turnChat) {
|
|
616
|
+
if ((chat ?? "main") !== opts.chat)
|
|
617
|
+
continue;
|
|
618
|
+
const a = this.host.agent(id);
|
|
619
|
+
if (isAdapter(a) && a.busy()) {
|
|
620
|
+
if ((opts.source ?? "user") === "user")
|
|
621
|
+
this.host.routes.onManualInterrupt();
|
|
622
|
+
await a.interrupt();
|
|
623
|
+
return { interrupted: id };
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const pendingHere = [...this.preparing.entries()].find(([id]) => (this.turnChat.get(id) ?? "main") === opts.chat);
|
|
627
|
+
if (pendingHere) {
|
|
628
|
+
pendingHere[1].abort();
|
|
629
|
+
return { interrupted: pendingHere[0] };
|
|
630
|
+
}
|
|
631
|
+
return { interrupted: null };
|
|
632
|
+
}
|
|
633
|
+
if ((opts.source ?? "user") === "user")
|
|
634
|
+
this.host.routes.onManualInterrupt();
|
|
635
|
+
const pending = this.preparing.entries().next().value;
|
|
636
|
+
if (pending) {
|
|
637
|
+
pending[1].abort();
|
|
638
|
+
if ((opts.source ?? "user") === "user" && this.host.queue.length)
|
|
639
|
+
this.host.queue.setPaused(true, "you pressed Stop during dispatch preparation");
|
|
640
|
+
return { interrupted: pending[0] };
|
|
641
|
+
}
|
|
642
|
+
const holder = this.host.validHolder();
|
|
643
|
+
if (!holder)
|
|
644
|
+
return { interrupted: null };
|
|
645
|
+
const agent = this.host.agent(holder);
|
|
646
|
+
// Stop means stop: what's queued doesn't start after it — it waits,
|
|
647
|
+
// paused, for you to resume, edit or clear it.
|
|
648
|
+
if ((opts.source ?? "user") === "user" && this.host.queue.length && !this.host.queue.paused) {
|
|
649
|
+
this.host.queue.setPaused(true, "you pressed Stop — resume to run what's queued");
|
|
650
|
+
this.host.log.append({ kind: "status", agentId: holder, payload: { state: "queue_paused", waiting: this.host.queue.length } });
|
|
651
|
+
}
|
|
652
|
+
if (isAdapter(agent) && agent.busy()) {
|
|
653
|
+
await agent.interrupt();
|
|
654
|
+
return { interrupted: holder };
|
|
655
|
+
}
|
|
656
|
+
return { interrupted: null };
|
|
657
|
+
}
|
|
658
|
+
/** The chat an agent's current (or last) turn belongs to, if any. */
|
|
659
|
+
chatOf(agentId) {
|
|
660
|
+
return this.turnChat.get(agentId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
//# sourceMappingURL=turns.js.map
|