agent-tempo 1.0.1
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/CLAUDE.md +213 -0
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/assets/icon-32.png +0 -0
- package/assets/icon-512.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/icon-dark-32.png +0 -0
- package/assets/icon-dark-64.png +0 -0
- package/assets/icon-dark.svg +9 -0
- package/assets/icon.svg +9 -0
- package/assets/logo-dark.svg +11 -0
- package/assets/logo-light.svg +11 -0
- package/dashboard/README.md +91 -0
- package/dashboard/dist/assets/index-CB78ToNE.css +2 -0
- package/dashboard/dist/assets/index-_5jV0Znu.js +62 -0
- package/dashboard/dist/assets/index-_5jV0Znu.js.map +1 -0
- package/dashboard/dist/index.html +21 -0
- package/dashboard/package.json +47 -0
- package/dist/activities/hard-terminate.d.ts +32 -0
- package/dist/activities/hard-terminate.js +460 -0
- package/dist/activities/maestro.d.ts +72 -0
- package/dist/activities/maestro.js +254 -0
- package/dist/activities/outbox.d.ts +188 -0
- package/dist/activities/outbox.js +849 -0
- package/dist/activities/resolve.d.ts +64 -0
- package/dist/activities/resolve.js +129 -0
- package/dist/activities/schedule-fire.d.ts +36 -0
- package/dist/activities/schedule-fire.js +147 -0
- package/dist/adapters/base.d.ts +426 -0
- package/dist/adapters/base.js +1270 -0
- package/dist/adapters/claude-api/adapter.d.ts +168 -0
- package/dist/adapters/claude-api/adapter.js +797 -0
- package/dist/adapters/claude-api/api-error.d.ts +96 -0
- package/dist/adapters/claude-api/api-error.js +191 -0
- package/dist/adapters/claude-api/index.d.ts +16 -0
- package/dist/adapters/claude-api/index.js +21 -0
- package/dist/adapters/claude-api/mcp-bridge.d.ts +50 -0
- package/dist/adapters/claude-api/mcp-bridge.js +157 -0
- package/dist/adapters/claude-code/adapter.d.ts +133 -0
- package/dist/adapters/claude-code/adapter.js +274 -0
- package/dist/adapters/claude-code/index.d.ts +15 -0
- package/dist/adapters/claude-code/index.js +20 -0
- package/dist/adapters/claude-code-headless/adapter.d.ts +131 -0
- package/dist/adapters/claude-code-headless/adapter.js +710 -0
- package/dist/adapters/claude-code-headless/error-mapper.d.ts +107 -0
- package/dist/adapters/claude-code-headless/error-mapper.js +281 -0
- package/dist/adapters/claude-code-headless/index.d.ts +17 -0
- package/dist/adapters/claude-code-headless/index.js +26 -0
- package/dist/adapters/claude-code-headless/pre-flight.d.ts +51 -0
- package/dist/adapters/claude-code-headless/pre-flight.js +207 -0
- package/dist/adapters/claude-code-headless/prompt.d.ts +93 -0
- package/dist/adapters/claude-code-headless/prompt.js +79 -0
- package/dist/adapters/claude-code-headless/stream-json.d.ts +242 -0
- package/dist/adapters/claude-code-headless/stream-json.js +208 -0
- package/dist/adapters/claude-code-headless/types.d.ts +28 -0
- package/dist/adapters/claude-code-headless/types.js +36 -0
- package/dist/adapters/copilot/adapter.d.ts +100 -0
- package/dist/adapters/copilot/adapter.js +730 -0
- package/dist/adapters/copilot/index.d.ts +15 -0
- package/dist/adapters/copilot/index.js +20 -0
- package/dist/adapters/index.d.ts +42 -0
- package/dist/adapters/index.js +115 -0
- package/dist/adapters/opencode/adapter.d.ts +82 -0
- package/dist/adapters/opencode/adapter.js +710 -0
- package/dist/adapters/opencode/config.d.ts +90 -0
- package/dist/adapters/opencode/config.js +137 -0
- package/dist/adapters/opencode/helpers.d.ts +40 -0
- package/dist/adapters/opencode/helpers.js +144 -0
- package/dist/adapters/opencode/index.d.ts +12 -0
- package/dist/adapters/opencode/index.js +17 -0
- package/dist/adapters/opencode/server-bridge.d.ts +124 -0
- package/dist/adapters/opencode/server-bridge.js +216 -0
- package/dist/adapters/sdk/base.d.ts +95 -0
- package/dist/adapters/sdk/base.js +134 -0
- package/dist/adapters/sdk/system-prompt.d.ts +64 -0
- package/dist/adapters/sdk/system-prompt.js +78 -0
- package/dist/adapters/terminal-error.d.ts +27 -0
- package/dist/adapters/terminal-error.js +39 -0
- package/dist/channel.d.ts +3 -0
- package/dist/channel.js +48 -0
- package/dist/cli/commands.d.ts +245 -0
- package/dist/cli/commands.js +2438 -0
- package/dist/cli/config-command.d.ts +8 -0
- package/dist/cli/config-command.js +254 -0
- package/dist/cli/daemon-command.d.ts +57 -0
- package/dist/cli/daemon-command.js +493 -0
- package/dist/cli/daemon.d.ts +217 -0
- package/dist/cli/daemon.js +632 -0
- package/dist/cli/dashboard-command.d.ts +20 -0
- package/dist/cli/dashboard-command.js +241 -0
- package/dist/cli/dev-banner.d.ts +107 -0
- package/dist/cli/dev-banner.js +190 -0
- package/dist/cli/dev-mode-bootstrap.d.ts +29 -0
- package/dist/cli/dev-mode-bootstrap.js +36 -0
- package/dist/cli/dev-verbs.d.ts +43 -0
- package/dist/cli/dev-verbs.js +254 -0
- package/dist/cli/help-text.d.ts +1 -0
- package/dist/cli/help-text.js +158 -0
- package/dist/cli/legacy-migration.d.ts +35 -0
- package/dist/cli/legacy-migration.js +335 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +63 -0
- package/dist/cli/output.d.ts +12 -0
- package/dist/cli/output.js +37 -0
- package/dist/cli/preflight.d.ts +9 -0
- package/dist/cli/preflight.js +96 -0
- package/dist/cli/removed-verbs.d.ts +9 -0
- package/dist/cli/removed-verbs.js +78 -0
- package/dist/cli/sa-preflight.d.ts +99 -0
- package/dist/cli/sa-preflight.js +183 -0
- package/dist/cli/scenarios-command.d.ts +6 -0
- package/dist/cli/scenarios-command.js +167 -0
- package/dist/cli/startup.d.ts +112 -0
- package/dist/cli/startup.js +641 -0
- package/dist/cli/upgrade-command.d.ts +5 -0
- package/dist/cli/upgrade-command.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +680 -0
- package/dist/client/core.d.ts +33 -0
- package/dist/client/core.js +1260 -0
- package/dist/client/ensure-conductor-spawned.d.ts +35 -0
- package/dist/client/ensure-conductor-spawned.js +48 -0
- package/dist/client/index.d.ts +32 -0
- package/dist/client/index.js +22 -0
- package/dist/client/interface.d.ts +461 -0
- package/dist/client/interface.js +2 -0
- package/dist/client/subscribe.d.ts +108 -0
- package/dist/client/subscribe.js +598 -0
- package/dist/client/with-spawn.d.ts +27 -0
- package/dist/client/with-spawn.js +87 -0
- package/dist/config.d.ts +323 -0
- package/dist/config.js +593 -0
- package/dist/connection.d.ts +7 -0
- package/dist/connection.js +46 -0
- package/dist/constants.d.ts +50 -0
- package/dist/constants.js +74 -0
- package/dist/copilot-bridge.d.ts +22 -0
- package/dist/copilot-bridge.js +565 -0
- package/dist/daemon-adapter-versions.d.ts +52 -0
- package/dist/daemon-adapter-versions.js +170 -0
- package/dist/daemon.d.ts +275 -0
- package/dist/daemon.js +989 -0
- package/dist/ensemble/agent-types.d.ts +23 -0
- package/dist/ensemble/agent-types.js +132 -0
- package/dist/ensemble/loader.d.ts +14 -0
- package/dist/ensemble/loader.js +140 -0
- package/dist/ensemble/saver.d.ts +49 -0
- package/dist/ensemble/saver.js +201 -0
- package/dist/ensemble/schema.d.ts +71 -0
- package/dist/ensemble/schema.js +3 -0
- package/dist/git-info.d.ts +4 -0
- package/dist/git-info.js +29 -0
- package/dist/http/aggregate.d.ts +319 -0
- package/dist/http/aggregate.js +684 -0
- package/dist/http/auth.d.ts +67 -0
- package/dist/http/auth.js +177 -0
- package/dist/http/body.d.ts +71 -0
- package/dist/http/body.js +121 -0
- package/dist/http/catalog.d.ts +67 -0
- package/dist/http/catalog.js +209 -0
- package/dist/http/cors.d.ts +42 -0
- package/dist/http/cors.js +111 -0
- package/dist/http/dashboard-pair.d.ts +94 -0
- package/dist/http/dashboard-pair.js +148 -0
- package/dist/http/dashboard.d.ts +20 -0
- package/dist/http/dashboard.js +160 -0
- package/dist/http/event-bus.d.ts +217 -0
- package/dist/http/event-bus.js +365 -0
- package/dist/http/event-id.d.ts +77 -0
- package/dist/http/event-id.js +117 -0
- package/dist/http/event-types.d.ts +348 -0
- package/dist/http/event-types.js +36 -0
- package/dist/http/fixtures/chat-stress.d.ts +8 -0
- package/dist/http/fixtures/chat-stress.js +63 -0
- package/dist/http/fixtures/conductor-leaving.d.ts +8 -0
- package/dist/http/fixtures/conductor-leaving.js +80 -0
- package/dist/http/fixtures/constants.d.ts +10 -0
- package/dist/http/fixtures/constants.js +13 -0
- package/dist/http/fixtures/eight-player-broadcast.d.ts +10 -0
- package/dist/http/fixtures/eight-player-broadcast.js +81 -0
- package/dist/http/fixtures/empty-ensemble.d.ts +6 -0
- package/dist/http/fixtures/empty-ensemble.js +26 -0
- package/dist/http/fixtures/index.d.ts +55 -0
- package/dist/http/fixtures/index.js +110 -0
- package/dist/http/fixtures/single-conductor.d.ts +7 -0
- package/dist/http/fixtures/single-conductor.js +46 -0
- package/dist/http/fixtures/sse-reconnect.d.ts +8 -0
- package/dist/http/fixtures/sse-reconnect.js +77 -0
- package/dist/http/index.d.ts +21 -0
- package/dist/http/index.js +61 -0
- package/dist/http/port-file.d.ts +22 -0
- package/dist/http/port-file.js +132 -0
- package/dist/http/responses.d.ts +27 -0
- package/dist/http/responses.js +40 -0
- package/dist/http/ring-buffer.d.ts +41 -0
- package/dist/http/ring-buffer.js +80 -0
- package/dist/http/server.d.ts +122 -0
- package/dist/http/server.js +459 -0
- package/dist/http/snapshot.d.ts +85 -0
- package/dist/http/snapshot.js +180 -0
- package/dist/http/sse-handler.d.ts +87 -0
- package/dist/http/sse-handler.js +294 -0
- package/dist/http/writes.d.ts +55 -0
- package/dist/http/writes.js +240 -0
- package/dist/palette/index.d.ts +138 -0
- package/dist/palette/index.js +221 -0
- package/dist/reconcile/orphans.d.ts +255 -0
- package/dist/reconcile/orphans.js +340 -0
- package/dist/scripts/258-spotcheck.js +303 -0
- package/dist/scripts/check-components-css-sync.js +199 -0
- package/dist/scripts/run-shard.js +121 -0
- package/dist/scripts/verify-daemon-isolation-guard.js +128 -0
- package/dist/server-tools.d.ts +87 -0
- package/dist/server-tools.js +146 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +366 -0
- package/dist/spawn.d.ts +296 -0
- package/dist/spawn.js +747 -0
- package/dist/tools/agent-types.d.ts +2 -0
- package/dist/tools/agent-types.js +21 -0
- package/dist/tools/attachment-info.d.ts +4 -0
- package/dist/tools/attachment-info.js +48 -0
- package/dist/tools/broadcast.d.ts +4 -0
- package/dist/tools/broadcast.js +76 -0
- package/dist/tools/cancel-stage.d.ts +3 -0
- package/dist/tools/cancel-stage.js +20 -0
- package/dist/tools/clear-state.d.ts +3 -0
- package/dist/tools/clear-state.js +37 -0
- package/dist/tools/coat-check-evict.d.ts +4 -0
- package/dist/tools/coat-check-evict.js +43 -0
- package/dist/tools/coat-check-get.d.ts +4 -0
- package/dist/tools/coat-check-get.js +56 -0
- package/dist/tools/coat-check-list.d.ts +4 -0
- package/dist/tools/coat-check-list.js +60 -0
- package/dist/tools/coat-check-put.d.ts +4 -0
- package/dist/tools/coat-check-put.js +53 -0
- package/dist/tools/cue.d.ts +44 -0
- package/dist/tools/cue.js +201 -0
- package/dist/tools/destroy.d.ts +4 -0
- package/dist/tools/destroy.js +188 -0
- package/dist/tools/detach.d.ts +4 -0
- package/dist/tools/detach.js +45 -0
- package/dist/tools/encore.d.ts +4 -0
- package/dist/tools/encore.js +31 -0
- package/dist/tools/ensemble.d.ts +32 -0
- package/dist/tools/ensemble.js +198 -0
- package/dist/tools/evaluate-gate.d.ts +3 -0
- package/dist/tools/evaluate-gate.js +32 -0
- package/dist/tools/fetch-state.d.ts +13 -0
- package/dist/tools/fetch-state.js +78 -0
- package/dist/tools/gates.d.ts +3 -0
- package/dist/tools/gates.js +41 -0
- package/dist/tools/helpers.d.ts +21 -0
- package/dist/tools/helpers.js +25 -0
- package/dist/tools/hosts.d.ts +4 -0
- package/dist/tools/hosts.js +40 -0
- package/dist/tools/listen.d.ts +3 -0
- package/dist/tools/listen.js +22 -0
- package/dist/tools/load-lineup.d.ts +5 -0
- package/dist/tools/load-lineup.js +381 -0
- package/dist/tools/migrate.d.ts +4 -0
- package/dist/tools/migrate.js +60 -0
- package/dist/tools/pause-ensemble.d.ts +4 -0
- package/dist/tools/pause-ensemble.js +58 -0
- package/dist/tools/pause.d.ts +4 -0
- package/dist/tools/pause.js +36 -0
- package/dist/tools/play.d.ts +4 -0
- package/dist/tools/play.js +57 -0
- package/dist/tools/quality-gate.d.ts +3 -0
- package/dist/tools/quality-gate.js +26 -0
- package/dist/tools/recall.d.ts +3 -0
- package/dist/tools/recall.js +32 -0
- package/dist/tools/recruit.d.ts +38 -0
- package/dist/tools/recruit.js +447 -0
- package/dist/tools/release.d.ts +4 -0
- package/dist/tools/release.js +98 -0
- package/dist/tools/report.d.ts +3 -0
- package/dist/tools/report.js +29 -0
- package/dist/tools/resolve.d.ts +1 -0
- package/dist/tools/resolve.js +7 -0
- package/dist/tools/restart.d.ts +35 -0
- package/dist/tools/restart.js +131 -0
- package/dist/tools/restore.d.ts +4 -0
- package/dist/tools/restore.js +107 -0
- package/dist/tools/resume-ensemble.d.ts +4 -0
- package/dist/tools/resume-ensemble.js +79 -0
- package/dist/tools/save-lineup.d.ts +4 -0
- package/dist/tools/save-lineup.js +36 -0
- package/dist/tools/save-state.d.ts +3 -0
- package/dist/tools/save-state.js +57 -0
- package/dist/tools/schedule.d.ts +4 -0
- package/dist/tools/schedule.js +152 -0
- package/dist/tools/schedules.d.ts +4 -0
- package/dist/tools/schedules.js +54 -0
- package/dist/tools/set-ensemble-description.d.ts +4 -0
- package/dist/tools/set-ensemble-description.js +37 -0
- package/dist/tools/set-name.d.ts +4 -0
- package/dist/tools/set-name.js +45 -0
- package/dist/tools/set-part.d.ts +3 -0
- package/dist/tools/set-part.js +20 -0
- package/dist/tools/shutdown.d.ts +4 -0
- package/dist/tools/shutdown.js +54 -0
- package/dist/tools/stage.d.ts +3 -0
- package/dist/tools/stage.js +28 -0
- package/dist/tools/stages.d.ts +3 -0
- package/dist/tools/stages.js +35 -0
- package/dist/tools/stop.d.ts +4 -0
- package/dist/tools/stop.js +29 -0
- package/dist/tools/unschedule.d.ts +4 -0
- package/dist/tools/unschedule.js +35 -0
- package/dist/tools/who-am-i.d.ts +3 -0
- package/dist/tools/who-am-i.js +34 -0
- package/dist/tools/worktree.d.ts +4 -0
- package/dist/tools/worktree.js +181 -0
- package/dist/tui/App.d.ts +85 -0
- package/dist/tui/App.js +1791 -0
- package/dist/tui/bootstrap-types.d.ts +46 -0
- package/dist/tui/bootstrap-types.js +7 -0
- package/dist/tui/client.d.ts +6 -0
- package/dist/tui/client.js +9 -0
- package/dist/tui/commands.d.ts +71 -0
- package/dist/tui/commands.js +1375 -0
- package/dist/tui/components/ActivityLog.d.ts +16 -0
- package/dist/tui/components/ActivityLog.js +36 -0
- package/dist/tui/components/ChatView.d.ts +35 -0
- package/dist/tui/components/ChatView.js +54 -0
- package/dist/tui/components/CommandOverlay.d.ts +15 -0
- package/dist/tui/components/CommandOverlay.js +34 -0
- package/dist/tui/components/CommandPalette.d.ts +21 -0
- package/dist/tui/components/CommandPalette.js +67 -0
- package/dist/tui/components/ConductorChat.d.ts +16 -0
- package/dist/tui/components/ConductorChat.js +32 -0
- package/dist/tui/components/ConversationStream.d.ts +114 -0
- package/dist/tui/components/ConversationStream.js +307 -0
- package/dist/tui/components/CreateEnsembleWizard.d.ts +19 -0
- package/dist/tui/components/CreateEnsembleWizard.js +223 -0
- package/dist/tui/components/DestroyConfirmModal.d.ts +17 -0
- package/dist/tui/components/DestroyConfirmModal.js +62 -0
- package/dist/tui/components/EnsembleListView.d.ts +14 -0
- package/dist/tui/components/EnsembleListView.js +32 -0
- package/dist/tui/components/EnsemblePanel.d.ts +12 -0
- package/dist/tui/components/EnsemblePanel.js +40 -0
- package/dist/tui/components/ErrorView.d.ts +31 -0
- package/dist/tui/components/ErrorView.js +129 -0
- package/dist/tui/components/HomeView.d.ts +54 -0
- package/dist/tui/components/HomeView.js +306 -0
- package/dist/tui/components/InputBar.d.ts +13 -0
- package/dist/tui/components/InputBar.js +58 -0
- package/dist/tui/components/LoadLineupModal.d.ts +18 -0
- package/dist/tui/components/LoadLineupModal.js +79 -0
- package/dist/tui/components/MainView.d.ts +21 -0
- package/dist/tui/components/MainView.js +107 -0
- package/dist/tui/components/NewEnsembleModal.d.ts +9 -0
- package/dist/tui/components/NewEnsembleModal.js +73 -0
- package/dist/tui/components/Picker.d.ts +23 -0
- package/dist/tui/components/Picker.js +70 -0
- package/dist/tui/components/PlayerDetailView.d.ts +26 -0
- package/dist/tui/components/PlayerDetailView.js +118 -0
- package/dist/tui/components/PromptArea.d.ts +50 -0
- package/dist/tui/components/PromptArea.js +303 -0
- package/dist/tui/components/RecruitWizard.d.ts +17 -0
- package/dist/tui/components/RecruitWizard.js +221 -0
- package/dist/tui/components/RestoreConfirmModal.d.ts +18 -0
- package/dist/tui/components/RestoreConfirmModal.js +71 -0
- package/dist/tui/components/ScheduleOverlay.d.ts +13 -0
- package/dist/tui/components/ScheduleOverlay.js +113 -0
- package/dist/tui/components/ScheduleWizard.d.ts +19 -0
- package/dist/tui/components/ScheduleWizard.js +259 -0
- package/dist/tui/components/Splash.d.ts +23 -0
- package/dist/tui/components/Splash.js +221 -0
- package/dist/tui/components/StatusBar.d.ts +48 -0
- package/dist/tui/components/StatusBar.js +128 -0
- package/dist/tui/components/StatusOverlay.d.ts +15 -0
- package/dist/tui/components/StatusOverlay.js +76 -0
- package/dist/tui/components/TitleBar.d.ts +10 -0
- package/dist/tui/components/TitleBar.js +21 -0
- package/dist/tui/components/TopBar.d.ts +12 -0
- package/dist/tui/components/TopBar.js +15 -0
- package/dist/tui/core-api.d.ts +26 -0
- package/dist/tui/core-api.js +67 -0
- package/dist/tui/hooks/useEnsembleDiscovery.d.ts +3 -0
- package/dist/tui/hooks/useEnsembleDiscovery.js +30 -0
- package/dist/tui/hooks/useMaestroPoller.d.ts +3 -0
- package/dist/tui/hooks/useMaestroPoller.js +36 -0
- package/dist/tui/hooks/useSendCommand.d.ts +7 -0
- package/dist/tui/hooks/useSendCommand.js +29 -0
- package/dist/tui/index.d.ts +15 -0
- package/dist/tui/index.js +156 -0
- package/dist/tui/ink-context.d.ts +18 -0
- package/dist/tui/ink-context.js +59 -0
- package/dist/tui/ink-loader.d.ts +26 -0
- package/dist/tui/ink-loader.js +42 -0
- package/dist/tui/removed-commands.d.ts +9 -0
- package/dist/tui/removed-commands.js +22 -0
- package/dist/tui/sse-handler.d.ts +52 -0
- package/dist/tui/sse-handler.js +157 -0
- package/dist/tui/store.d.ts +598 -0
- package/dist/tui/store.js +753 -0
- package/dist/tui/utils/format.d.ts +56 -0
- package/dist/tui/utils/format.js +155 -0
- package/dist/tui/utils/fullscreen.d.ts +23 -0
- package/dist/tui/utils/fullscreen.js +71 -0
- package/dist/tui/utils/history.d.ts +10 -0
- package/dist/tui/utils/history.js +85 -0
- package/dist/tui/utils/platform.d.ts +45 -0
- package/dist/tui/utils/platform.js +258 -0
- package/dist/tui/utils/theme.d.ts +21 -0
- package/dist/tui/utils/theme.js +24 -0
- package/dist/types.d.ts +1020 -0
- package/dist/types.js +39 -0
- package/dist/utils/attachment-format.d.ts +22 -0
- package/dist/utils/attachment-format.js +32 -0
- package/dist/utils/default-part.d.ts +43 -0
- package/dist/utils/default-part.js +104 -0
- package/dist/utils/duration.d.ts +30 -0
- package/dist/utils/duration.js +69 -0
- package/dist/utils/ensemble-ops.d.ts +61 -0
- package/dist/utils/ensemble-ops.js +77 -0
- package/dist/utils/format-hosts.d.ts +21 -0
- package/dist/utils/format-hosts.js +73 -0
- package/dist/utils/hosts.d.ts +113 -0
- package/dist/utils/hosts.js +265 -0
- package/dist/utils/parent-death-watchdog.d.ts +1 -0
- package/dist/utils/parent-death-watchdog.js +47 -0
- package/dist/utils/query-timeout.d.ts +103 -0
- package/dist/utils/query-timeout.js +113 -0
- package/dist/utils/recall-format.d.ts +78 -0
- package/dist/utils/recall-format.js +105 -0
- package/dist/utils/restore-format.d.ts +49 -0
- package/dist/utils/restore-format.js +91 -0
- package/dist/utils/safe-path.d.ts +10 -0
- package/dist/utils/safe-path.js +43 -0
- package/dist/utils/sdk-probe.d.ts +9 -0
- package/dist/utils/sdk-probe.js +45 -0
- package/dist/utils/search-attributes.d.ts +76 -0
- package/dist/utils/search-attributes.js +86 -0
- package/dist/utils/validation.d.ts +113 -0
- package/dist/utils/validation.js +163 -0
- package/dist/utils/visibility-deadline.d.ts +186 -0
- package/dist/utils/visibility-deadline.js +158 -0
- package/dist/utils/worktree.d.ts +103 -0
- package/dist/utils/worktree.js +327 -0
- package/dist/worker.d.ts +14 -0
- package/dist/worker.js +146 -0
- package/dist/workflows/attachment-math.d.ts +56 -0
- package/dist/workflows/attachment-math.js +47 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +11 -0
- package/dist/workflows/maestro-signals.d.ts +217 -0
- package/dist/workflows/maestro-signals.js +155 -0
- package/dist/workflows/maestro.d.ts +3 -0
- package/dist/workflows/maestro.js +812 -0
- package/dist/workflows/scheduler-signals.d.ts +10 -0
- package/dist/workflows/scheduler-signals.js +14 -0
- package/dist/workflows/scheduler.d.ts +17 -0
- package/dist/workflows/scheduler.js +143 -0
- package/dist/workflows/session.d.ts +2 -0
- package/dist/workflows/session.js +1638 -0
- package/dist/workflows/signals.d.ts +297 -0
- package/dist/workflows/signals.js +239 -0
- package/examples/agents/tempo-composer.md +56 -0
- package/examples/agents/tempo-conductor.md +117 -0
- package/examples/agents/tempo-critic.md +73 -0
- package/examples/agents/tempo-improv.md +74 -0
- package/examples/agents/tempo-liner.md +75 -0
- package/examples/agents/tempo-roadie.md +61 -0
- package/examples/agents/tempo-soloist.md +71 -0
- package/examples/agents/tempo-tuner.md +94 -0
- package/examples/ensembles/tempo-big-band.yaml +146 -0
- package/examples/ensembles/tempo-dev-team.yaml +58 -0
- package/examples/ensembles/tempo-headless-jam.yaml +77 -0
- package/examples/ensembles/tempo-jam-session.yaml +41 -0
- package/examples/ensembles/tempo-mock-jam.yaml +79 -0
- package/examples/ensembles/tempo-review-squad.yaml +32 -0
- package/package.json +172 -0
- package/packaging/launchd/com.agent.tempo.plist +46 -0
- package/packaging/systemd/agent-tempo.service +32 -0
- package/packaging/windows/install-task.ps1 +71 -0
- package/scenarios/conductor-recruit-mock.yaml +33 -0
- package/scenarios/echo-roundtrip.yaml +15 -0
- package/scenarios/multi-player-handoff.yaml +38 -0
- package/scenarios/recruit-cascade.yaml +38 -0
- package/scenarios/two-player-conversation.yaml +33 -0
- package/workflow-bundle.js +14146 -0
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createOutboxActivities = createOutboxActivities;
|
|
37
|
+
const client_1 = require("@temporalio/client");
|
|
38
|
+
const activity_1 = require("@temporalio/activity");
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
const config_1 = require("../config");
|
|
42
|
+
const validation_1 = require("../utils/validation");
|
|
43
|
+
const constants_1 = require("../constants");
|
|
44
|
+
const git_info_1 = require("../git-info");
|
|
45
|
+
const spawn_1 = require("../spawn");
|
|
46
|
+
const config_2 = require("../config");
|
|
47
|
+
const resolve_1 = require("./resolve");
|
|
48
|
+
const agent_types_1 = require("../ensemble/agent-types");
|
|
49
|
+
const default_part_1 = require("../utils/default-part");
|
|
50
|
+
const adapters_1 = require("../adapters");
|
|
51
|
+
const hard_terminate_1 = require("./hard-terminate");
|
|
52
|
+
const signals_1 = require("../workflows/signals");
|
|
53
|
+
const validation_2 = require("../utils/validation");
|
|
54
|
+
const log = (...args) => console.error('[agent-tempo:outbox]', ...args);
|
|
55
|
+
/**
|
|
56
|
+
* Classify a Temporal client error raised by `handle.query` / `handle.signal`
|
|
57
|
+
* / `handle.executeUpdate` as retryable (transient) vs permanent (#140).
|
|
58
|
+
*
|
|
59
|
+
* ## Contract
|
|
60
|
+
* - Returns `true` → caller should **re-throw the underlying Error** so the
|
|
61
|
+
* activity's retry policy can back off and retry (per-worker config).
|
|
62
|
+
* - Returns `false` → caller should wrap in `ApplicationFailure.nonRetryable`
|
|
63
|
+
* so the outbox surfaces a permanent failure and stops retrying.
|
|
64
|
+
*
|
|
65
|
+
* ## Safety posture
|
|
66
|
+
* **Conservative default: unknown → non-retryable.** Over-classifying as
|
|
67
|
+
* retryable causes infinite retry loops on genuinely permanent errors. The
|
|
68
|
+
* activity will fail fast on unknown cases; a follow-up PR can whitelist more
|
|
69
|
+
* transient signatures if we see false-permanent rates in the wild.
|
|
70
|
+
*
|
|
71
|
+
* ## Why name/message sniffing, not `instanceof`
|
|
72
|
+
* Matches the established pattern in `src/adapters/terminal-error.ts`
|
|
73
|
+
* `isTerminalWorkflowError`: the Temporal Node SDK surfaces slightly different
|
|
74
|
+
* error shapes between `@temporalio/client`, the gRPC layer, and
|
|
75
|
+
* `WorkflowUpdateFailedError` wrappers. Sniffing on name + message is resilient
|
|
76
|
+
* across those shapes. Activity-side classification is kept separate here so
|
|
77
|
+
* `src/activities/` has no adapter-module dependency.
|
|
78
|
+
*/
|
|
79
|
+
function isRetryableTemporalError(err) {
|
|
80
|
+
// ApplicationFailure instances have already been classified by the thrower
|
|
81
|
+
// (nonRetryable=true/false). The calling code paths in this module only ask
|
|
82
|
+
// about non-ApplicationFailure errors, but this guard makes the helper safe
|
|
83
|
+
// to call unconditionally.
|
|
84
|
+
if (err instanceof activity_1.ApplicationFailure)
|
|
85
|
+
return false;
|
|
86
|
+
const e = err;
|
|
87
|
+
const name = e?.name ?? '';
|
|
88
|
+
const msg = e?.message ?? '';
|
|
89
|
+
// ── Permanent: workflow is genuinely gone or validator rejected the op. ──
|
|
90
|
+
if (name.includes('WorkflowNotFound') ||
|
|
91
|
+
name.includes('WorkflowExecutionAlreadyCompleted') ||
|
|
92
|
+
// Update rejected by the workflow-side validator (e.g. `WorkflowGone`
|
|
93
|
+
// thrown from `claimAttachment`'s validator on a destroyed session).
|
|
94
|
+
// A retry won't make the validator change its mind.
|
|
95
|
+
name.includes('WorkflowUpdateFailed') ||
|
|
96
|
+
msg.includes('WorkflowGone') ||
|
|
97
|
+
msg.includes('workflow execution already completed'))
|
|
98
|
+
return false;
|
|
99
|
+
// ── Transient: RPC / network / temporary SDK unavailability. ──
|
|
100
|
+
if (name.includes('TransportError') ||
|
|
101
|
+
name.includes('TimeoutError') ||
|
|
102
|
+
msg.includes('DEADLINE_EXCEEDED') ||
|
|
103
|
+
msg.includes('UNAVAILABLE') ||
|
|
104
|
+
msg.includes('RESOURCE_EXHAUSTED') ||
|
|
105
|
+
msg.includes('CANCELLED') ||
|
|
106
|
+
/\bECONNRESET\b/.test(msg) ||
|
|
107
|
+
/\bECONNREFUSED\b/.test(msg) ||
|
|
108
|
+
/\bETIMEDOUT\b/.test(msg) ||
|
|
109
|
+
/\bENOTFOUND\b/.test(msg) ||
|
|
110
|
+
/\bEAI_AGAIN\b/.test(msg))
|
|
111
|
+
return true;
|
|
112
|
+
// Unknown shape — stay permanent (see "Safety posture" above).
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Standard shape for the 3 §8.2 deliver activities' catch-all tail.
|
|
117
|
+
* Centralises the branch so each activity body stays concise.
|
|
118
|
+
*
|
|
119
|
+
* - If `err` is already an `ApplicationFailure` (typed permanent — e.g. the
|
|
120
|
+
* explicit "not found" / "destroyed" throws), re-throw as-is.
|
|
121
|
+
* - If `err` is retryable per {@link isRetryableTemporalError}, re-throw the
|
|
122
|
+
* original `Error` so the activity retry policy handles it.
|
|
123
|
+
* - Otherwise wrap in `ApplicationFailure.nonRetryable` with a caller-supplied
|
|
124
|
+
* context prefix (e.g. `Detach failed for "alice"`).
|
|
125
|
+
*/
|
|
126
|
+
function classifyAndRethrow(err, contextPrefix) {
|
|
127
|
+
if (err instanceof activity_1.ApplicationFailure)
|
|
128
|
+
throw err;
|
|
129
|
+
if (isRetryableTemporalError(err)) {
|
|
130
|
+
// Re-throw the original so the activity retry policy backs off and retries.
|
|
131
|
+
// Normalise non-Error throwables (extremely rare) into Error form.
|
|
132
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
133
|
+
}
|
|
134
|
+
throw activity_1.ApplicationFailure.nonRetryable(`${contextPrefix}: ${err instanceof Error ? err.message : String(err)}`);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Create outbox delivery activities bound to a Temporal client and config.
|
|
138
|
+
* The returned object is registered with the worker as activities.
|
|
139
|
+
*/
|
|
140
|
+
function createOutboxActivities(client, config) {
|
|
141
|
+
return {
|
|
142
|
+
async deliverCue(input) {
|
|
143
|
+
const { ensemble, fromPlayerId, targetPlayerId, message, broadcastId, attachmentTicket } = input;
|
|
144
|
+
try {
|
|
145
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
146
|
+
if (!handle) {
|
|
147
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
|
|
148
|
+
}
|
|
149
|
+
// #357 + #318: thread broadcastId / attachmentTicket onto the
|
|
150
|
+
// receiver's `receiveMessage` signal payload. Both fields are
|
|
151
|
+
// additive optionals — direct cues omit one or both.
|
|
152
|
+
await handle.signal('receiveMessage', {
|
|
153
|
+
from: fromPlayerId,
|
|
154
|
+
text: message,
|
|
155
|
+
...(broadcastId !== undefined ? { broadcastId } : {}),
|
|
156
|
+
...(attachmentTicket !== undefined ? { attachmentTicket } : {}),
|
|
157
|
+
});
|
|
158
|
+
return { success: true };
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
// #236: transient RPC errors (e.g. DEADLINE_EXCEEDED on the signal call)
|
|
162
|
+
// retry per the activity policy; WorkflowNotFound / validator rejections
|
|
163
|
+
// stay permanent. Unknown errors default to non-retryable.
|
|
164
|
+
classifyAndRethrow(err, `Cue failed for "${targetPlayerId}"`);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
async deliverReport(input) {
|
|
168
|
+
const { ensemble, fromPlayerId, text, reportType } = input;
|
|
169
|
+
try {
|
|
170
|
+
const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
171
|
+
const handle = client.workflow.getHandle(conductorId);
|
|
172
|
+
await handle.describe(); // throws if conductor workflow is not running
|
|
173
|
+
await handle.signal('playerReport', { playerId: fromPlayerId, text, type: reportType });
|
|
174
|
+
return { success: true };
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
// #236: describe() / signal() hitting a transient RPC error now retries;
|
|
178
|
+
// WorkflowNotFound (conductor gone) stays permanent as before.
|
|
179
|
+
classifyAndRethrow(err, 'Failed to deliver report to conductor');
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
async terminateSession(input) {
|
|
183
|
+
const { ensemble, targetPlayerId, terminatedBy } = input;
|
|
184
|
+
try {
|
|
185
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
186
|
+
if (!handle) {
|
|
187
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
|
|
188
|
+
}
|
|
189
|
+
// PR-C commit 4: use the V2 `destroy` update — explicit operator termination
|
|
190
|
+
// per §2.5 (abandon in-flight, phase=gone, COMPLETE). The former
|
|
191
|
+
// `updateMetadata({ status: 'terminated' })` signal path was retired.
|
|
192
|
+
await handle.executeUpdate('destroy', {
|
|
193
|
+
args: [{ reason: 'stop via tool', terminatedBy }],
|
|
194
|
+
});
|
|
195
|
+
// Notify conductor about the termination (best effort)
|
|
196
|
+
try {
|
|
197
|
+
const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
198
|
+
const conductorHandle = client.workflow.getHandle(conductorId);
|
|
199
|
+
await conductorHandle.signal('receiveMessage', {
|
|
200
|
+
from: 'system',
|
|
201
|
+
text: `Session "${targetPlayerId}" was terminated by ${terminatedBy}.`,
|
|
202
|
+
responseRequested: false,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Conductor may not exist — that's fine
|
|
207
|
+
}
|
|
208
|
+
return { success: true };
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
// #236: transient RPC on the destroy update now retries; validator rejection
|
|
212
|
+
// (WorkflowGone, AttachmentMismatch) stays permanent.
|
|
213
|
+
classifyAndRethrow(err, `Terminate failed for "${targetPlayerId}"`);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
async startRecruitedSession(input) {
|
|
217
|
+
const { ensemble, targetName, workDir, isConductor, initialMessage, fromPlayerId, agent, systemPrompt, taskQueue, agentDefinition, agentDefinitionDescription, held, model } = input;
|
|
218
|
+
try {
|
|
219
|
+
const workflowId = isConductor
|
|
220
|
+
? (0, config_1.conductorWorkflowId)(ensemble)
|
|
221
|
+
: (0, config_1.sessionWorkflowId)(ensemble, targetName);
|
|
222
|
+
const { gitRoot, gitBranch } = (0, git_info_1.getGitInfo)(workDir);
|
|
223
|
+
// Generate a UUID for the session — used for deterministic --resume on encore
|
|
224
|
+
const sessionId = crypto.randomUUID();
|
|
225
|
+
// Warm hold: process will spawn and go active, but outbox is locked and
|
|
226
|
+
// the initial message is deferred. A standby message is sent instead.
|
|
227
|
+
const standbyMessage = held
|
|
228
|
+
? 'You are on standby. Your ensemble is loading — other players are still connecting. Wait for your task assignment. Do not start work or send messages yet.'
|
|
229
|
+
: undefined;
|
|
230
|
+
const sessionInput = {
|
|
231
|
+
metadata: {
|
|
232
|
+
playerId: targetName,
|
|
233
|
+
ensemble,
|
|
234
|
+
hostname: os.hostname(),
|
|
235
|
+
workDir,
|
|
236
|
+
gitRoot,
|
|
237
|
+
gitBranch,
|
|
238
|
+
isConductor,
|
|
239
|
+
agentType: agent,
|
|
240
|
+
// PR-B (v0.25 step 2/7): populate adapterId on fresh recruits so the
|
|
241
|
+
// session workflow and dispatch path can resolve the adapter descriptor
|
|
242
|
+
// from the registry without falling back to the legacy agentType field.
|
|
243
|
+
adapterId: adapters_1.registry.resolveFromAgentType(agent),
|
|
244
|
+
sessionId,
|
|
245
|
+
// #131 / #449 Phase C — persist the claude-api / opencode model
|
|
246
|
+
// on durable metadata so restart can recover the original choice
|
|
247
|
+
// across CAN. Both adapters use the same `model` metadata field
|
|
248
|
+
// (different value shapes — bare vs `provider/model`) — the spawn
|
|
249
|
+
// path inspects `metadata.agentType` to know which env var to set.
|
|
250
|
+
...((agent === 'claude-api' || agent === 'opencode') && model ? { model } : {}),
|
|
251
|
+
...(agentDefinition ? { playerType: agentDefinition } : {}),
|
|
252
|
+
...(agentDefinitionDescription ? { playerTypeDescription: agentDefinitionDescription } : {}),
|
|
253
|
+
recruitedBy: fromPlayerId,
|
|
254
|
+
},
|
|
255
|
+
// Issue #450 — derive default `part` from the recruited
|
|
256
|
+
// player type so a freshly recruited session reads as e.g.
|
|
257
|
+
// `'Engineer session'` instead of the role-agnostic
|
|
258
|
+
// `'Session in <basename>'` placeholder.
|
|
259
|
+
autoSummary: (0, default_part_1.defaultPart)({
|
|
260
|
+
playerType: agentDefinition,
|
|
261
|
+
isConductor,
|
|
262
|
+
workDir,
|
|
263
|
+
adapterType: agent,
|
|
264
|
+
}),
|
|
265
|
+
disableStaleDetection: true,
|
|
266
|
+
// When held: store the initial message for delivery on release, inject standby message instead
|
|
267
|
+
...(held ? { outboxLocked: true, heldMessage: initialMessage } : {}),
|
|
268
|
+
messages: held
|
|
269
|
+
? [{
|
|
270
|
+
id: crypto.randomUUID(),
|
|
271
|
+
from: 'system',
|
|
272
|
+
text: standbyMessage,
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
delivered: false,
|
|
275
|
+
}]
|
|
276
|
+
: (initialMessage ? [{
|
|
277
|
+
id: crypto.randomUUID(),
|
|
278
|
+
from: fromPlayerId,
|
|
279
|
+
text: initialMessage,
|
|
280
|
+
timestamp: new Date().toISOString(),
|
|
281
|
+
delivered: false,
|
|
282
|
+
}] : undefined),
|
|
283
|
+
};
|
|
284
|
+
await client.workflow.start('agentSessionWorkflow', {
|
|
285
|
+
workflowId,
|
|
286
|
+
taskQueue,
|
|
287
|
+
args: [sessionInput],
|
|
288
|
+
workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
|
|
289
|
+
searchAttributes: {
|
|
290
|
+
...(gitRoot ? { AgentTempoGitRoot: [gitRoot] } : {}),
|
|
291
|
+
AgentTempoHostname: [os.hostname()],
|
|
292
|
+
AgentTempoEnsemble: [ensemble],
|
|
293
|
+
AgentTempoPlayerId: [targetName],
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
log(`Pre-created workflow ${workflowId} for recruit "${targetName}" (sessionId=${sessionId}, held=${!!held})`);
|
|
297
|
+
return { success: true, sessionId };
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
// #236: transient RPC during workflow.start (e.g. temporal server flap)
|
|
301
|
+
// now retries; WorkflowNotFound / validation / auth failures stay permanent.
|
|
302
|
+
// Note: this activity's pre-#236 catch was missing the ApplicationFailure
|
|
303
|
+
// passthrough guard — `classifyAndRethrow` restores it for free.
|
|
304
|
+
classifyAndRethrow(err, `Failed to start recruited session "${targetName}"`);
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
async spawnProcess(input) {
|
|
308
|
+
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume, sessionId, allowedTools, claudeBin, attachmentId, attachmentRunId, adapterId, mockMode, mockScenario, model, permissionMode, dangerouslySkipPermissions } = input;
|
|
309
|
+
// Read secrets from the worker's config closure — never from workflow state
|
|
310
|
+
const { temporalApiKey, temporalTlsCertPath, temporalTlsKeyPath } = config;
|
|
311
|
+
try {
|
|
312
|
+
if (agent === 'mock') {
|
|
313
|
+
// ADR 0014 PR-2 — mock adapter spawns headless. No terminal,
|
|
314
|
+
// no Claude binary, no MCP server child. Talks to Temporal
|
|
315
|
+
// directly and posts every action through the standard outbox.
|
|
316
|
+
const { spawnMockAdapter } = await Promise.resolve().then(() => __importStar(require('../spawn')));
|
|
317
|
+
const { pid } = spawnMockAdapter({
|
|
318
|
+
name: targetName,
|
|
319
|
+
ensemble,
|
|
320
|
+
temporalAddress,
|
|
321
|
+
temporalNamespace,
|
|
322
|
+
temporalApiKey,
|
|
323
|
+
temporalTlsCertPath,
|
|
324
|
+
temporalTlsKeyPath,
|
|
325
|
+
isConductor,
|
|
326
|
+
workDir,
|
|
327
|
+
mockMode,
|
|
328
|
+
mockScenario,
|
|
329
|
+
attachmentId,
|
|
330
|
+
attachmentRunId,
|
|
331
|
+
adapterId,
|
|
332
|
+
});
|
|
333
|
+
log(`Spawned mock adapter (pid ${pid}) in ${workDir} as "${targetName}" (mode=${mockMode ?? 'echo'}${attachmentId ? `, attachmentId=${attachmentId}` : ''})`);
|
|
334
|
+
}
|
|
335
|
+
else if (agent === 'copilot') {
|
|
336
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
337
|
+
log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for copilot agent "${targetName}" — copilot bridge does not support --allowedTools, skipping`);
|
|
338
|
+
}
|
|
339
|
+
const { pid } = (0, spawn_1.spawnCopilotBridge)({
|
|
340
|
+
name: targetName,
|
|
341
|
+
ensemble,
|
|
342
|
+
temporalAddress,
|
|
343
|
+
temporalNamespace,
|
|
344
|
+
temporalApiKey,
|
|
345
|
+
temporalTlsCertPath,
|
|
346
|
+
temporalTlsKeyPath,
|
|
347
|
+
isConductor,
|
|
348
|
+
workDir,
|
|
349
|
+
sessionId,
|
|
350
|
+
attachmentId,
|
|
351
|
+
attachmentRunId,
|
|
352
|
+
adapterId,
|
|
353
|
+
});
|
|
354
|
+
log(`Spawned copilot-bridge (pid ${pid}) in ${workDir} as "${targetName}"${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
355
|
+
}
|
|
356
|
+
else if (agent === 'claude-api') {
|
|
357
|
+
// #131 Phase C — headless Anthropic Messages API adapter. No
|
|
358
|
+
// terminal, no Claude binary, no MCP-server child process. The
|
|
359
|
+
// adapter boots an in-process MCP server + paired client and
|
|
360
|
+
// talks to Anthropic via @anthropic-ai/sdk. Tool surface is
|
|
361
|
+
// MCP-only in v1; file-edit/shell/web tools deferred to Phase 2.
|
|
362
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
363
|
+
log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for claude-api agent "${targetName}" — claude-api adapter does not gate tools per recruit, skipping`);
|
|
364
|
+
}
|
|
365
|
+
const { pid } = (0, spawn_1.spawnClaudeApiAdapter)({
|
|
366
|
+
name: targetName,
|
|
367
|
+
ensemble,
|
|
368
|
+
temporalAddress,
|
|
369
|
+
temporalNamespace,
|
|
370
|
+
temporalApiKey,
|
|
371
|
+
temporalTlsCertPath,
|
|
372
|
+
temporalTlsKeyPath,
|
|
373
|
+
isConductor,
|
|
374
|
+
workDir,
|
|
375
|
+
model,
|
|
376
|
+
attachmentId,
|
|
377
|
+
attachmentRunId,
|
|
378
|
+
adapterId,
|
|
379
|
+
});
|
|
380
|
+
log(`Spawned claude-api adapter (pid ${pid}) in ${workDir} as "${targetName}"${model ? ` (model=${model})` : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
381
|
+
}
|
|
382
|
+
else if (agent === 'opencode') {
|
|
383
|
+
// #449 Phase C — headless multi-provider adapter via OpenCode.
|
|
384
|
+
// No terminal, no Claude binary. The adapter manages its own
|
|
385
|
+
// `opencode serve` subprocess (probed-free port, hardcoded
|
|
386
|
+
// loopback bind); tools dispatch via OpenCode's MCP-native config
|
|
387
|
+
// block (it spawns dist/server.js as its own MCP child). Per-tool
|
|
388
|
+
// allowlists don't apply — opencode players have full file/shell/
|
|
389
|
+
// web access via OpenCode's built-in tool registry.
|
|
390
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
391
|
+
log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for opencode agent "${targetName}" — opencode adapter does not gate tools per recruit, skipping`);
|
|
392
|
+
}
|
|
393
|
+
const { pid } = (0, spawn_1.spawnOpenCodeAdapter)({
|
|
394
|
+
name: targetName,
|
|
395
|
+
ensemble,
|
|
396
|
+
temporalAddress,
|
|
397
|
+
temporalNamespace,
|
|
398
|
+
temporalApiKey,
|
|
399
|
+
temporalTlsCertPath,
|
|
400
|
+
temporalTlsKeyPath,
|
|
401
|
+
isConductor,
|
|
402
|
+
workDir,
|
|
403
|
+
model,
|
|
404
|
+
attachmentId,
|
|
405
|
+
attachmentRunId,
|
|
406
|
+
adapterId,
|
|
407
|
+
});
|
|
408
|
+
log(`Spawned opencode adapter (pid ${pid}) in ${workDir} as "${targetName}"${model ? ` (model=${model})` : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
409
|
+
}
|
|
410
|
+
else if (agent === 'claude-code-headless') {
|
|
411
|
+
// #520 — headless Claude Code adapter. Spawns the host's `claude` CLI
|
|
412
|
+
// as a per-turn subprocess; uses the operator's existing OAuth login
|
|
413
|
+
// so turns bill against subscription extra-usage credits. The adapter
|
|
414
|
+
// process itself is a Node subprocess (this branch); the per-turn
|
|
415
|
+
// `claude -p` invocations happen inside the adapter's invokeSdk loop
|
|
416
|
+
// (PR-3). Per-tool allowlists don't apply — claude-code-headless
|
|
417
|
+
// players inherit the full Claude Code tool surface.
|
|
418
|
+
if (allowedTools && allowedTools.length > 0) {
|
|
419
|
+
log(`Warning: allowedTools [${allowedTools.join(', ')}] specified for claude-code-headless agent "${targetName}" — claude-code-headless adapter does not gate tools per recruit, skipping`);
|
|
420
|
+
}
|
|
421
|
+
const { pid } = (0, spawn_1.spawnClaudeCodeHeadlessAdapter)({
|
|
422
|
+
name: targetName,
|
|
423
|
+
ensemble,
|
|
424
|
+
temporalAddress,
|
|
425
|
+
temporalNamespace,
|
|
426
|
+
temporalApiKey,
|
|
427
|
+
temporalTlsCertPath,
|
|
428
|
+
temporalTlsKeyPath,
|
|
429
|
+
isConductor,
|
|
430
|
+
workDir,
|
|
431
|
+
permissionMode,
|
|
432
|
+
dangerouslySkipPermissions,
|
|
433
|
+
attachmentId,
|
|
434
|
+
attachmentRunId,
|
|
435
|
+
adapterId,
|
|
436
|
+
});
|
|
437
|
+
log(`Spawned claude-code-headless adapter (pid ${pid}) in ${workDir} as "${targetName}"${permissionMode ? ` (permissionMode=${permissionMode})` : ''}${dangerouslySkipPermissions ? ' (dangerouslySkipPermissions=true)' : ''}${attachmentId ? ` (attachmentId=${attachmentId})` : ''}`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
// Resolve agent flags: --agent (native) > --system-prompt (shipped/legacy)
|
|
441
|
+
let agentFlags = [];
|
|
442
|
+
if (agentDefinition && nativeResolvable) {
|
|
443
|
+
agentFlags = ['--agent', agentDefinition];
|
|
444
|
+
}
|
|
445
|
+
else if (agentDefinitionPath) {
|
|
446
|
+
agentFlags = ['--system-prompt', agentDefinitionPath];
|
|
447
|
+
}
|
|
448
|
+
else if (systemPrompt) {
|
|
449
|
+
agentFlags = ['--system-prompt', systemPrompt];
|
|
450
|
+
}
|
|
451
|
+
// Use --resume for encore (reconnect to existing session) or -n for new sessions.
|
|
452
|
+
// For encore: use UUID for deterministic --resume (no interactive picker).
|
|
453
|
+
// For new sessions: use --session-id to track the UUID for future encores.
|
|
454
|
+
const nameArgs = resume
|
|
455
|
+
? ['--resume', sessionId || targetName]
|
|
456
|
+
: ['-n', targetName, ...(sessionId ? ['--session-id', sessionId] : [])];
|
|
457
|
+
// Build --allowedTools flag from agent definition frontmatter
|
|
458
|
+
const allowedToolsFlags = allowedTools && allowedTools.length > 0
|
|
459
|
+
? ['--allowedTools', ...allowedTools]
|
|
460
|
+
: [];
|
|
461
|
+
// ENSEMBLE_SENTINEL_FLAG carries the ensemble name into the spawned
|
|
462
|
+
// claude.exe's CommandLine so hard-terminate can scope `destroy --all`
|
|
463
|
+
// kills by ensemble (issue #180). See src/constants.ts for details.
|
|
464
|
+
const spawnArgs = [
|
|
465
|
+
'--dangerously-skip-permissions',
|
|
466
|
+
'--dangerously-load-development-channels', 'server:agent-tempo',
|
|
467
|
+
constants_1.ENSEMBLE_SENTINEL_FLAG, ensemble,
|
|
468
|
+
...nameArgs,
|
|
469
|
+
...agentFlags,
|
|
470
|
+
...allowedToolsFlags,
|
|
471
|
+
];
|
|
472
|
+
const envVars = {
|
|
473
|
+
[config_2.ENV.ENSEMBLE]: ensemble,
|
|
474
|
+
[config_2.ENV.CONDUCTOR]: isConductor ? 'true' : '',
|
|
475
|
+
[config_2.ENV.PLAYER_NAME]: targetName,
|
|
476
|
+
[config_2.ENV.TEMPORAL_ADDRESS]: temporalAddress,
|
|
477
|
+
[config_2.ENV.TEMPORAL_NAMESPACE]: temporalNamespace,
|
|
478
|
+
};
|
|
479
|
+
if (agentDefinition)
|
|
480
|
+
envVars[config_2.ENV.PLAYER_TYPE] = agentDefinition;
|
|
481
|
+
if (temporalApiKey)
|
|
482
|
+
envVars[config_2.ENV.TEMPORAL_API_KEY] = temporalApiKey;
|
|
483
|
+
if (temporalTlsCertPath)
|
|
484
|
+
envVars[config_2.ENV.TEMPORAL_TLS_CERT_PATH] = temporalTlsCertPath;
|
|
485
|
+
if (temporalTlsKeyPath)
|
|
486
|
+
envVars[config_2.ENV.TEMPORAL_TLS_KEY_PATH] = temporalTlsKeyPath;
|
|
487
|
+
// PR-D: forward pre-claimed attachment so the adapter renews rather than fresh-claims.
|
|
488
|
+
if (attachmentId)
|
|
489
|
+
envVars[config_2.ENV.ATTACHMENT_ID] = attachmentId;
|
|
490
|
+
if (attachmentRunId)
|
|
491
|
+
envVars[config_2.ENV.ATTACHMENT_RUN_ID] = attachmentRunId;
|
|
492
|
+
if (adapterId)
|
|
493
|
+
envVars[config_2.ENV.ADAPTER_ID] = adapterId;
|
|
494
|
+
const { pid } = (0, spawn_1.spawnInTerminal)(spawnArgs, workDir, envVars, { claudeBin });
|
|
495
|
+
log(`Spawned claude process (pid ${pid}) in ${workDir} as "${targetName}" (resume=${!!resume}${attachmentId ? `, attachmentId=${attachmentId}` : ''})`);
|
|
496
|
+
}
|
|
497
|
+
return { success: true };
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
// #236: spawnProcess throws predominantly OS-side errors (ENOENT/EACCES
|
|
501
|
+
// on the claude binary, EAGAIN on process-table overflow). The classifier
|
|
502
|
+
// is tuned for Temporal RPC; OS errors don't match its transient
|
|
503
|
+
// signatures, so they still flow through as non-retryable — byte-for-byte
|
|
504
|
+
// behavior preservation. The upside of going through the helper: if a
|
|
505
|
+
// future OS error surfaces a transient shape we add to the classifier,
|
|
506
|
+
// spawnProcess benefits automatically.
|
|
507
|
+
classifyAndRethrow(err, `Failed to spawn process for "${targetName}"`);
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
async releasePlayer(input) {
|
|
511
|
+
const { ensemble, targetPlayerId } = input;
|
|
512
|
+
try {
|
|
513
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
514
|
+
if (!handle) {
|
|
515
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
|
|
516
|
+
}
|
|
517
|
+
// Check if the session is actually held (outbox locked)
|
|
518
|
+
const isLocked = await handle.query('outboxLocked');
|
|
519
|
+
if (!isLocked) {
|
|
520
|
+
throw activity_1.ApplicationFailure.nonRetryable(`Cannot release "${targetPlayerId}" — session is not held (outbox not locked).`);
|
|
521
|
+
}
|
|
522
|
+
// Signal the session to release — unlocks outbox and delivers held message
|
|
523
|
+
await handle.signal('releaseHeld');
|
|
524
|
+
log(`Released held session "${targetPlayerId}"`);
|
|
525
|
+
return { success: true };
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
// #236: transient RPC on outboxLocked query / releaseHeld signal now
|
|
529
|
+
// retries; WorkflowNotFound / not-held validation stay permanent.
|
|
530
|
+
classifyAndRethrow(err, `Release failed for "${targetPlayerId}"`);
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
/**
|
|
534
|
+
* PR-D `deliverDetach` — resolve target session and signal `requestDetach`.
|
|
535
|
+
* Thin wrapper so the `detach` tool can enqueue through the outbox instead
|
|
536
|
+
* of firing a signal directly from tool code (QA B1).
|
|
537
|
+
*/
|
|
538
|
+
async deliverDetach(input) {
|
|
539
|
+
const { ensemble, targetPlayerId, reason = 'user-stop', deadlineMs = 5_000 } = input;
|
|
540
|
+
try {
|
|
541
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
542
|
+
if (!handle) {
|
|
543
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
|
|
544
|
+
}
|
|
545
|
+
const info = await handle.query(signals_1.attachmentInfoQuery);
|
|
546
|
+
if (info.phase === 'detached') {
|
|
547
|
+
log(`Detach skipped for "${targetPlayerId}" — already detached`);
|
|
548
|
+
return { success: true };
|
|
549
|
+
}
|
|
550
|
+
if (info.phase === 'gone') {
|
|
551
|
+
throw activity_1.ApplicationFailure.nonRetryable(`Cannot detach "${targetPlayerId}" — session is destroyed`);
|
|
552
|
+
}
|
|
553
|
+
await handle.signal(signals_1.requestDetachSignal, { reason, deadlineMs });
|
|
554
|
+
log(`Detach signaled for "${targetPlayerId}" (deadline=${deadlineMs}ms)`);
|
|
555
|
+
return { success: true };
|
|
556
|
+
}
|
|
557
|
+
catch (err) {
|
|
558
|
+
// #140: re-throw transient RPC/network errors so the activity retry
|
|
559
|
+
// policy handles them; permanent cases (validator rejection, workflow
|
|
560
|
+
// gone, unknown) become `ApplicationFailure.nonRetryable`.
|
|
561
|
+
classifyAndRethrow(err, `Detach failed for "${targetPlayerId}"`);
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
/**
|
|
565
|
+
* PR-D `deliverDestroy` — execute `destroyUpdate` on the target and
|
|
566
|
+
* optionally notify the ensemble conductor via `receiveMessageSignal`
|
|
567
|
+
* (typed constant, not a string literal per QA B2).
|
|
568
|
+
*/
|
|
569
|
+
async deliverDestroy(input) {
|
|
570
|
+
const { ensemble, targetPlayerId, reason, terminatedBy, notifyConductor = true } = input;
|
|
571
|
+
try {
|
|
572
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
573
|
+
if (!handle) {
|
|
574
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No session found for "${targetPlayerId}"`);
|
|
575
|
+
}
|
|
576
|
+
await handle.executeUpdate(signals_1.destroyUpdate, {
|
|
577
|
+
args: [{
|
|
578
|
+
reason: reason ?? 'destroyed via destroy tool',
|
|
579
|
+
terminatedBy,
|
|
580
|
+
}],
|
|
581
|
+
});
|
|
582
|
+
log(`Destroyed "${targetPlayerId}"${reason ? ` (reason: ${reason})` : ''}`);
|
|
583
|
+
if (notifyConductor) {
|
|
584
|
+
try {
|
|
585
|
+
const condId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
586
|
+
const condHandle = client.workflow.getHandle(condId);
|
|
587
|
+
await condHandle.signal(signals_1.receiveMessageSignal, {
|
|
588
|
+
from: 'system',
|
|
589
|
+
text: `Session "${targetPlayerId}" was destroyed by ${terminatedBy}${reason ? ` (reason: ${reason})` : ''}.`,
|
|
590
|
+
responseRequested: false,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
// Conductor may not exist — non-fatal.
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return { success: true };
|
|
598
|
+
}
|
|
599
|
+
catch (err) {
|
|
600
|
+
// #140: transient errors (network, RPC timeout) become retryable;
|
|
601
|
+
// permanent cases (WorkflowNotFound, validator rejection) stay
|
|
602
|
+
// non-retryable. Unknown errors default to non-retryable.
|
|
603
|
+
classifyAndRethrow(err, `Destroy failed for "${targetPlayerId}"`);
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
/**
|
|
607
|
+
* PR-D `deliverRestart` — owns the §8.2 restart algorithm on the target.
|
|
608
|
+
* Graceful `requestDetach` → re-query phase → `forceDetach` (if --force
|
|
609
|
+
* OR already was the TOCTOU case) → `claimAttachment` → optional context
|
|
610
|
+
* replay via `receiveMessage` → `enqueueSpawn` on the target's outbox.
|
|
611
|
+
*
|
|
612
|
+
* Mid-algorithm failures surface as ApplicationFailures and retry per the
|
|
613
|
+
* activity's policy. QA B3 — replaces the pre-PR-D tool-side
|
|
614
|
+
* `performRestart` helper so no multi-step cross-workflow mutation happens
|
|
615
|
+
* outside the outbox pattern.
|
|
616
|
+
*/
|
|
617
|
+
async deliverRestart(input) {
|
|
618
|
+
const { ensemble, targetPlayerId, invokerPlayerId, force = false, host, fresh = false, contextMessages = 10, loadFromState, transcript } = input;
|
|
619
|
+
try {
|
|
620
|
+
const handle = await (0, resolve_1.resolveSession)(client, ensemble, targetPlayerId);
|
|
621
|
+
if (!handle) {
|
|
622
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No workflow for "${targetPlayerId}". Use recruit to start a fresh session.`);
|
|
623
|
+
}
|
|
624
|
+
// Step 1 — inspect phase. `gone` means the workflow COMPLETEd.
|
|
625
|
+
const info = await handle.query(signals_1.attachmentInfoQuery);
|
|
626
|
+
if (info.phase === 'gone') {
|
|
627
|
+
throw activity_1.ApplicationFailure.nonRetryable(`"${targetPlayerId}" was destroyed. Use recruit to start a fresh session.`);
|
|
628
|
+
}
|
|
629
|
+
// Step 2 — reap current attachment.
|
|
630
|
+
if (info.phase !== 'detached' && info.phase !== 'booting') {
|
|
631
|
+
if (info.phase === 'attached' || info.phase === 'awaiting' || info.phase === 'processing') {
|
|
632
|
+
try {
|
|
633
|
+
await handle.signal(signals_1.requestDetachSignal, {
|
|
634
|
+
reason: 'restart',
|
|
635
|
+
deadlineMs: validation_1.DEFAULT_RESTART_DETACH_DEADLINE_MS,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
// Best-effort; force path handles it below.
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const info2 = await handle.query(signals_1.attachmentInfoQuery);
|
|
643
|
+
if (info2.phase !== 'detached' && info2.phase !== 'booting') {
|
|
644
|
+
if (!force) {
|
|
645
|
+
const holder = info2.currentAttachment?.hostname ?? 'unknown host';
|
|
646
|
+
throw activity_1.ApplicationFailure.nonRetryable(`"${targetPlayerId}" has a live attachment on ${holder} (phase: ${info2.phase}). ` +
|
|
647
|
+
`Use force=true to steal the lease.`);
|
|
648
|
+
}
|
|
649
|
+
// #159 Gap 2: OS-level kill is owned by the `forceDetachUpdate` handler itself
|
|
650
|
+
// — it invokes `hardTerminateAttachment` on the reaped host's per-host queue
|
|
651
|
+
// *before* flipping workflow state. That keeps the "kill first, then state"
|
|
652
|
+
// ordering inside the durable workflow layer where it belongs; deliverRestart
|
|
653
|
+
// just awaits the update and surfaces retryable errors to the caller.
|
|
654
|
+
await handle.executeUpdate(signals_1.forceDetachUpdate, {
|
|
655
|
+
args: [{
|
|
656
|
+
reason: 'restart',
|
|
657
|
+
...(info2.currentAttachment ? { expectedAttachmentId: info2.currentAttachment.attachmentId } : {}),
|
|
658
|
+
gracePeriodMs: 0,
|
|
659
|
+
}],
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// Step 3 — metadata + adapter routing.
|
|
664
|
+
const metadata = await handle.query(signals_1.getMetadataQuery);
|
|
665
|
+
// ADR 0014 PR-2 — mock adapter restart path. `mock` is dev-mode only;
|
|
666
|
+
// a metadata.agentType='mock' here implies the dev daemon previously
|
|
667
|
+
// spawned this player and is now restarting it (encore / migrate).
|
|
668
|
+
// Prod restart never lands here for `mock` because gate 3 in
|
|
669
|
+
// src/tools/recruit.ts rejected the original recruit.
|
|
670
|
+
const rawAgent = metadata.agentType;
|
|
671
|
+
// #131 / #449 Phase C — claude-api / opencode join copilot / mock
|
|
672
|
+
// as recognized sdk-class agents here so restart/encore/migrate of
|
|
673
|
+
// a headless player resolves to the right adapterId + class. Without
|
|
674
|
+
// this branch, restart falls through to 'claude' → 'interactive'
|
|
675
|
+
// and the spawn path picks the terminal Claude Code path.
|
|
676
|
+
const agentType = rawAgent === 'copilot'
|
|
677
|
+
? 'copilot'
|
|
678
|
+
: rawAgent === 'mock'
|
|
679
|
+
? 'mock'
|
|
680
|
+
: rawAgent === 'claude-api'
|
|
681
|
+
? 'claude-api'
|
|
682
|
+
: rawAgent === 'opencode'
|
|
683
|
+
? 'opencode'
|
|
684
|
+
: rawAgent === 'claude-code-headless'
|
|
685
|
+
? 'claude-code-headless'
|
|
686
|
+
: 'claude';
|
|
687
|
+
const adapterId = metadata.adapterId
|
|
688
|
+
|| (agentType === 'copilot'
|
|
689
|
+
? 'copilot'
|
|
690
|
+
: agentType === 'mock'
|
|
691
|
+
? 'mock'
|
|
692
|
+
: agentType === 'claude-api'
|
|
693
|
+
? 'claude-api'
|
|
694
|
+
: agentType === 'opencode'
|
|
695
|
+
? 'opencode'
|
|
696
|
+
: agentType === 'claude-code-headless'
|
|
697
|
+
? 'claude-code-headless'
|
|
698
|
+
: 'claude-code');
|
|
699
|
+
const adapterClass = agentType === 'claude' ? 'interactive' : 'sdk';
|
|
700
|
+
const targetHost = host ?? info.preferredHost ?? metadata.hostname;
|
|
701
|
+
// Step 4 — claim fresh attachment.
|
|
702
|
+
const token = await handle.executeUpdate(signals_1.claimAttachmentUpdate, {
|
|
703
|
+
args: [{
|
|
704
|
+
host: targetHost,
|
|
705
|
+
adapterId,
|
|
706
|
+
adapterClass,
|
|
707
|
+
leaseMs: validation_1.DEFAULT_RESTART_LEASE_MS,
|
|
708
|
+
}],
|
|
709
|
+
});
|
|
710
|
+
// Step 5 — context seed (saved state and/or transcript replay).
|
|
711
|
+
//
|
|
712
|
+
// #334 PR-2: `loadFromState` lets the player choose to seed the new
|
|
713
|
+
// session from a curated state slot instead of (or alongside) the
|
|
714
|
+
// existing transcript replay. The semantics matrix (design §4.4):
|
|
715
|
+
//
|
|
716
|
+
// loadFromState set → seed from saved state, suppress replay by default
|
|
717
|
+
// loadFromState + 'replay' → stack: saved state first, then transcript
|
|
718
|
+
// loadFromState set, slot empty → graceful fallback to transcript replay
|
|
719
|
+
// loadFromState absent → existing behaviour (replay governed by `fresh`)
|
|
720
|
+
//
|
|
721
|
+
// Backward compat is structural (no `patched()` marker — see ADR 0011
|
|
722
|
+
// §Consequences and design §7.3): old outbox entries omit
|
|
723
|
+
// `loadFromState`, `wantsState` evaluates falsy, and execution falls
|
|
724
|
+
// through to the original transcript-replay block bit-for-bit.
|
|
725
|
+
const wantsState = loadFromState !== undefined;
|
|
726
|
+
const wantsTranscriptByFlag = transcript === 'replay';
|
|
727
|
+
let saved = null;
|
|
728
|
+
if (wantsState) {
|
|
729
|
+
const stateKey = typeof loadFromState === 'string' ? loadFromState : validation_2.PLAYER_STATE_DEFAULT_KEY;
|
|
730
|
+
saved = await handle.query(signals_1.playerStateQuery, { key: stateKey });
|
|
731
|
+
if (saved) {
|
|
732
|
+
await handle.signal(signals_1.receiveMessageSignal, {
|
|
733
|
+
from: 'self-restart',
|
|
734
|
+
text: `🎵 **Restored state — "${stateKey}"** (saved ${saved.savedAt} by ${saved.savedBy})\n\n${saved.content}`,
|
|
735
|
+
responseRequested: false,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
log(`Restart loadFromState requested for slot "${stateKey}" but slot is empty — falling back to transcript replay`);
|
|
740
|
+
// UX-friendly fallback per design §4.4: replay the transcript as
|
|
741
|
+
// if the caller had not passed `loadFromState`. The fall-through
|
|
742
|
+
// below picks this up via `wantsState && !saved`.
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// Replay the transcript when (a) loadFromState was not requested, OR
|
|
746
|
+
// (b) caller opted into stacking via `transcript: 'replay'`, OR
|
|
747
|
+
// (c) loadFromState was requested but the slot was empty (fallback).
|
|
748
|
+
// The pre-existing `fresh` flag still wins — `fresh: true` skips
|
|
749
|
+
// replay regardless of state-seeding outcome.
|
|
750
|
+
const wantsTranscript = !wantsState || wantsTranscriptByFlag || (wantsState && !saved);
|
|
751
|
+
if (!fresh && wantsTranscript) {
|
|
752
|
+
const [part, allMessages] = await Promise.all([
|
|
753
|
+
handle.query(signals_1.getPartQuery),
|
|
754
|
+
handle.query(signals_1.allMessagesQuery),
|
|
755
|
+
]);
|
|
756
|
+
const recent = allMessages.slice(-contextMessages);
|
|
757
|
+
const summary = recent.length > 0
|
|
758
|
+
? recent.map((m) => `[${m.from}] ${m.text.slice(0, validation_1.PREVIEW_MAX_LENGTH)}`).join('\n')
|
|
759
|
+
: '(no recent messages)';
|
|
760
|
+
const contextMessage = [
|
|
761
|
+
`🎵 **Restart** — you've been revived by ${invokerPlayerId}.`,
|
|
762
|
+
part ? `Your last status: ${part}` : '',
|
|
763
|
+
`Recent messages (last ${recent.length}):`,
|
|
764
|
+
summary,
|
|
765
|
+
'',
|
|
766
|
+
'Resume where you left off. Use `ensemble` to see who is active.',
|
|
767
|
+
].filter(Boolean).join('\n');
|
|
768
|
+
await handle.signal(signals_1.receiveMessageSignal, {
|
|
769
|
+
from: invokerPlayerId,
|
|
770
|
+
text: contextMessage,
|
|
771
|
+
responseRequested: false,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
// Step 6 — enqueue the spawn.
|
|
775
|
+
//
|
|
776
|
+
// Issue #183: Claude Code rejects `--session-id <uuid>` when a transcript
|
|
777
|
+
// already exists at `~/.claude/projects/<encoded-path>/<uuid>.jsonl`
|
|
778
|
+
// ("Session ID already in use"). A prior failed spawn can leave that
|
|
779
|
+
// file behind, wedging every subsequent `fresh` restart that reuses the
|
|
780
|
+
// stored sessionId.
|
|
781
|
+
//
|
|
782
|
+
// #306: `/restart` ALWAYS spawns a fresh Claude Code process — never
|
|
783
|
+
// `--resume <id>`. The transcript `.jsonl` for the prior `spawnSessionId`
|
|
784
|
+
// is NOT guaranteed to have been flushed to disk before the prior
|
|
785
|
+
// adapter was hard-terminated (Windows `taskkill /T /F` is synchronous
|
|
786
|
+
// and unconditional). Claude Code then errors out with "No conversation
|
|
787
|
+
// found with session ID" and the new terminal drops to shell.
|
|
788
|
+
//
|
|
789
|
+
// Context preservation is already handled by the Step 5 replay above —
|
|
790
|
+
// we re-send recent messages to the fresh session. That's authoritative;
|
|
791
|
+
// the session-id `--resume` path was only ever a bonus on top. So we
|
|
792
|
+
// mint a new UUID on every restart, persist it to metadata, and never
|
|
793
|
+
// pass `resume: true` to the spawn.
|
|
794
|
+
const spawnSessionId = crypto.randomUUID();
|
|
795
|
+
await handle.signal(signals_1.updateMetadataSignal, { sessionId: spawnSessionId });
|
|
796
|
+
// Issue #184: re-resolve on the invoker host against the session's
|
|
797
|
+
// workDir (NOT the daemon's process.cwd — daemon runs elsewhere than
|
|
798
|
+
// the session's project, so the project-tier lookup needs the session's
|
|
799
|
+
// own cwd). `nativeResolvable` means the target can `--agent <name>`
|
|
800
|
+
// from its own tier lookup; the path fallback is shipped-relative so
|
|
801
|
+
// it exists on any host with agent-tempo installed — safe cross-host.
|
|
802
|
+
const resolved = metadata.playerType
|
|
803
|
+
? (0, agent_types_1.resolveAgentType)(metadata.playerType, metadata.workDir)
|
|
804
|
+
: null;
|
|
805
|
+
const { spawnEntryId } = await handle.executeUpdate(signals_1.enqueueSpawnUpdate, {
|
|
806
|
+
args: [{
|
|
807
|
+
host: targetHost,
|
|
808
|
+
attachmentId: token.attachmentId,
|
|
809
|
+
runId: token.runId,
|
|
810
|
+
// #306: `/restart` is always a fresh spawn (see comment above).
|
|
811
|
+
resume: false,
|
|
812
|
+
sessionId: spawnSessionId,
|
|
813
|
+
adapterId,
|
|
814
|
+
// #131 Phase C — claude-api adapter model carried across restart.
|
|
815
|
+
// Read from durable session metadata so the restarted player runs
|
|
816
|
+
// the same model the original recruit chose. Absent for non-claude-api
|
|
817
|
+
// sessions and for legacy claude-api sessions recruited before the
|
|
818
|
+
// metadata.model field landed (which fall back to the env / default
|
|
819
|
+
// chain inside the adapter).
|
|
820
|
+
...(metadata.model !== undefined ? { model: metadata.model } : {}),
|
|
821
|
+
...(resolved ? {
|
|
822
|
+
agentDefinition: resolved.name,
|
|
823
|
+
agentDefinitionPath: resolved.path,
|
|
824
|
+
nativeResolvable: resolved.nativeResolvable,
|
|
825
|
+
} : {}),
|
|
826
|
+
}],
|
|
827
|
+
});
|
|
828
|
+
log(`Restart prepared for "${targetPlayerId}" — attachmentId=${token.attachmentId}, spawnEntryId=${spawnEntryId}, host=${targetHost}, fresh sessionId=${spawnSessionId}${fresh ? ' (context replay skipped)' : ''}`);
|
|
829
|
+
return { success: true };
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
// #140: the §8.2 restart algorithm fires many RPCs; any of them may
|
|
833
|
+
// hit a transient network/RPC error. Those get retried. Validator
|
|
834
|
+
// rejections (e.g. claim race), workflow-gone, and unknown errors
|
|
835
|
+
// stay permanent to avoid wedging the outbox on a dead target.
|
|
836
|
+
classifyAndRethrow(err, `Restart failed for "${targetPlayerId}"`);
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
/**
|
|
840
|
+
* #159 Gap 2 — OS-level process-tree kill. Registered on the per-host task queue so
|
|
841
|
+
* it runs on the machine that actually hosts the child process. Never throws: the
|
|
842
|
+
* returned `HardTerminateResult` tells the caller what happened (strategy, PIDs,
|
|
843
|
+
* notes), which the workflow can log without blocking the state flip.
|
|
844
|
+
*/
|
|
845
|
+
async hardTerminateAttachment(input) {
|
|
846
|
+
return (0, hard_terminate_1.hardTerminateAttachment)(input);
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
}
|