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,565 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copilot Bridge — allows GitHub Copilot CLI sessions to participate
|
|
4
|
+
* as players in a claude-tempo ensemble.
|
|
5
|
+
*
|
|
6
|
+
* The bridge:
|
|
7
|
+
* 1. Spawns a Copilot CLI session via the Copilot SDK
|
|
8
|
+
* 2. Configures it with claude-tempo as an MCP server (so it gets all tools)
|
|
9
|
+
* 3. Polls the Temporal workflow for pending messages
|
|
10
|
+
* 4. Injects messages as prompts via the SDK
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* npx ts-node src/copilot-bridge.ts
|
|
14
|
+
*
|
|
15
|
+
* Environment variables:
|
|
16
|
+
* CLAUDE_TEMPO_ENSEMBLE — ensemble name (default: "default")
|
|
17
|
+
* CLAUDE_TEMPO_PLAYER_NAME — player ID for workflow registration (set by spawner for deterministic workflow IDs)
|
|
18
|
+
* COPILOT_BRIDGE_NAME — player name for set_name (optional)
|
|
19
|
+
* COPILOT_BRIDGE_MODEL — model to use (optional)
|
|
20
|
+
* COPILOT_BRIDGE_SESSION_ID — deterministic session ID for resumable sessions (optional)
|
|
21
|
+
* GITHUB_TOKEN — GitHub auth token (optional, uses logged-in user by default)
|
|
22
|
+
*/
|
|
23
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
26
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
27
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
28
|
+
}
|
|
29
|
+
Object.defineProperty(o, k2, desc);
|
|
30
|
+
}) : (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
o[k2] = m[k];
|
|
33
|
+
}));
|
|
34
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
35
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
36
|
+
}) : function(o, v) {
|
|
37
|
+
o["default"] = v;
|
|
38
|
+
});
|
|
39
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
40
|
+
var ownKeys = function(o) {
|
|
41
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
42
|
+
var ar = [];
|
|
43
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
44
|
+
return ar;
|
|
45
|
+
};
|
|
46
|
+
return ownKeys(o);
|
|
47
|
+
};
|
|
48
|
+
return function (mod) {
|
|
49
|
+
if (mod && mod.__esModule) return mod;
|
|
50
|
+
var result = {};
|
|
51
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
52
|
+
__setModuleDefault(result, mod);
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
const fs = __importStar(require("fs"));
|
|
58
|
+
const path = __importStar(require("path"));
|
|
59
|
+
const client_1 = require("@temporalio/client");
|
|
60
|
+
const config_1 = require("./config");
|
|
61
|
+
const connection_1 = require("./connection");
|
|
62
|
+
// Optional dependency — must be installed separately: npm install @github/copilot-sdk
|
|
63
|
+
let CopilotClient;
|
|
64
|
+
let approveAll;
|
|
65
|
+
try {
|
|
66
|
+
const sdk = require('@github/copilot-sdk');
|
|
67
|
+
CopilotClient = sdk.CopilotClient;
|
|
68
|
+
approveAll = sdk.approveAll;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.error('Error: @github/copilot-sdk is not installed.\n' +
|
|
72
|
+
'Install it with: npm install @github/copilot-sdk\n' +
|
|
73
|
+
'See the Copilot CLI integration section in the README.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
// Unbuffered logging — fs.writeSync(2, ...) bypasses Node.js stream buffering,
|
|
77
|
+
// ensuring log output appears immediately even when stderr is redirected to a file.
|
|
78
|
+
const log = (...args) => {
|
|
79
|
+
const msg = `[copilot-bridge] ${args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ')}\n`;
|
|
80
|
+
fs.writeSync(2, msg);
|
|
81
|
+
};
|
|
82
|
+
/** Filter process.env to exclude undefined values (safe to spread as Record<string, string>). */
|
|
83
|
+
const cleanEnv = () => Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined));
|
|
84
|
+
const POLL_INTERVAL_MS = 2000;
|
|
85
|
+
const CREATE_SESSION_TIMEOUT_MS = 45_000;
|
|
86
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
87
|
+
const MAX_SESSION_RECREATIONS = 2;
|
|
88
|
+
/** Check workflow status every N polls (~30s at 2s interval). */
|
|
89
|
+
const WORKFLOW_STATUS_CHECK_INTERVAL = 15;
|
|
90
|
+
/** Proactively recreate the Copilot session after this idle period (ms). Default 60 min. */
|
|
91
|
+
const SESSION_MAX_IDLE_MS = 60 * 60 * 1000;
|
|
92
|
+
/** Wrap createSession with a timeout so auth/network hangs don't block forever. */
|
|
93
|
+
async function createSessionWithTimeout(copilotClient, sessionConfig, timeoutMs = CREATE_SESSION_TIMEOUT_MS) {
|
|
94
|
+
let timer;
|
|
95
|
+
const timeout = new Promise((_, reject) => {
|
|
96
|
+
timer = setTimeout(() => reject(new Error(`createSession timed out after ${timeoutMs / 1000}s — check Copilot auth and network connectivity`)), timeoutMs);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
return await Promise.race([
|
|
100
|
+
copilotClient.createSession(sessionConfig),
|
|
101
|
+
timeout,
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function main() {
|
|
109
|
+
const config = (0, config_1.getConfig)();
|
|
110
|
+
const playerName = process.env[config_1.ENV.BRIDGE_NAME];
|
|
111
|
+
const model = process.env[config_1.ENV.BRIDGE_MODEL];
|
|
112
|
+
const copilotSessionId = process.env[config_1.ENV.BRIDGE_SESSION_ID] || `tempo-${config.ensemble}-${playerName || 'unknown'}-${Date.now()}-${process.pid}`;
|
|
113
|
+
const workDir = process.cwd();
|
|
114
|
+
log(`Starting Copilot bridge in ${workDir} (ensemble: ${config.ensemble})`);
|
|
115
|
+
// Connect Temporal client (for polling only — the MCP server child process runs its own worker)
|
|
116
|
+
const connection = await (0, connection_1.createTemporalConnection)(config);
|
|
117
|
+
const client = new client_1.Client({
|
|
118
|
+
connection,
|
|
119
|
+
namespace: config.temporalNamespace,
|
|
120
|
+
});
|
|
121
|
+
// Determine the expected workflow ID. The MCP server uses the pattern
|
|
122
|
+
// `claude-session-{ensemble}-{playerId}`, where playerId comes from
|
|
123
|
+
// CLAUDE_TEMPO_PLAYER_NAME or a random hex. We pass CLAUDE_TEMPO_PLAYER_NAME
|
|
124
|
+
// to the MCP server env so both sides agree on the ID.
|
|
125
|
+
const isConductor = process.env[config_1.ENV.CONDUCTOR] === 'true';
|
|
126
|
+
const requestedName = process.env[config_1.ENV.PLAYER_NAME] || playerName || '';
|
|
127
|
+
const playerIdForWorkflow = isConductor
|
|
128
|
+
? 'conductor'
|
|
129
|
+
: (requestedName && requestedName !== 'conductor' ? requestedName : '') || `copilot-${Date.now()}`;
|
|
130
|
+
const expectedWorkflowId = `claude-session-${config.ensemble}-${playerIdForWorkflow}`;
|
|
131
|
+
// Build the MCP server command — always use the compiled dist/server.js
|
|
132
|
+
// Run `npm run build` (or `pnpm build`) before using the bridge.
|
|
133
|
+
const serverJsPath = path.resolve(__dirname, '..', 'dist', 'server.js');
|
|
134
|
+
if (!fs.existsSync(serverJsPath)) {
|
|
135
|
+
log(`ERROR: ${serverJsPath} not found. Run 'pnpm build' first.`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
log(`MCP server path: ${serverJsPath}`);
|
|
139
|
+
const serverCommand = 'node';
|
|
140
|
+
const serverArgs = [serverJsPath];
|
|
141
|
+
const mcpEnv = {
|
|
142
|
+
...cleanEnv(),
|
|
143
|
+
[config_1.ENV.ENSEMBLE]: config.ensemble,
|
|
144
|
+
[config_1.ENV.TEMPORAL_ADDRESS]: config.temporalAddress,
|
|
145
|
+
[config_1.ENV.TEMPORAL_NAMESPACE]: config.temporalNamespace,
|
|
146
|
+
[config_1.ENV.TASK_QUEUE]: config.taskQueue,
|
|
147
|
+
[config_1.ENV.CONDUCTOR]: isConductor ? 'true' : '',
|
|
148
|
+
[config_1.ENV.BRIDGE_MODE]: '1', // disable MCP server's message poller — bridge handles delivery
|
|
149
|
+
[config_1.ENV.PLAYER_NAME]: playerIdForWorkflow, // ensures MCP server uses same workflow ID
|
|
150
|
+
...(config.temporalApiKey ? { [config_1.ENV.TEMPORAL_API_KEY]: config.temporalApiKey } : {}),
|
|
151
|
+
...(config.temporalTlsCertPath ? { [config_1.ENV.TEMPORAL_TLS_CERT_PATH]: config.temporalTlsCertPath } : {}),
|
|
152
|
+
...(config.temporalTlsKeyPath ? { [config_1.ENV.TEMPORAL_TLS_KEY_PATH]: config.temporalTlsKeyPath } : {}),
|
|
153
|
+
};
|
|
154
|
+
// Spawn Copilot SDK client and session
|
|
155
|
+
const copilotClient = new CopilotClient({
|
|
156
|
+
logLevel: 'debug',
|
|
157
|
+
env: {
|
|
158
|
+
...cleanEnv(),
|
|
159
|
+
...(process.env.GITHUB_TOKEN ? { GITHUB_TOKEN: process.env.GITHUB_TOKEN } : {}),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
const sessionConfig = {
|
|
163
|
+
sessionId: copilotSessionId,
|
|
164
|
+
// approveAll is intentional: Copilot bridge sessions run headless with no
|
|
165
|
+
// interactive terminal, so there is no way to prompt for permission approval.
|
|
166
|
+
// All tool calls are auto-approved by design — the bridge operator accepts
|
|
167
|
+
// this when launching the bridge process.
|
|
168
|
+
onPermissionRequest: approveAll,
|
|
169
|
+
workingDirectory: workDir,
|
|
170
|
+
mcpServers: {
|
|
171
|
+
'claude-tempo': {
|
|
172
|
+
command: serverCommand,
|
|
173
|
+
args: serverArgs,
|
|
174
|
+
env: mcpEnv,
|
|
175
|
+
tools: ['*'],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
systemMessage: {
|
|
179
|
+
mode: 'append',
|
|
180
|
+
content: `You are part of the "${config.ensemble}" ensemble coordinated via Temporal. ` +
|
|
181
|
+
`You have MCP tools available — ALWAYS use these tools directly, NEVER try to run them as shell commands:\n` +
|
|
182
|
+
`- set_name: Set your player name (call this FIRST if instructed)\n` +
|
|
183
|
+
`- ensemble: List active sessions\n` +
|
|
184
|
+
`- cue: Send a message to another player\n` +
|
|
185
|
+
`- set_part: Update your status/description\n` +
|
|
186
|
+
`- listen: Check for pending messages\n` +
|
|
187
|
+
`- recruit: Spawn a new player session\n` +
|
|
188
|
+
`- report: Report to the conductor\n` +
|
|
189
|
+
`- stop: Stop a session\n\n` +
|
|
190
|
+
`When you receive a message from another session, treat it like a coworker asking for help — respond promptly using your MCP tools.`,
|
|
191
|
+
},
|
|
192
|
+
excludedTools: ['write_powershell', 'read_powershell', 'list_powershell'],
|
|
193
|
+
...(model ? { model } : {}),
|
|
194
|
+
};
|
|
195
|
+
log('Creating Copilot session...');
|
|
196
|
+
let session = await createSessionWithTimeout(copilotClient, sessionConfig);
|
|
197
|
+
log(`Copilot session created: ${session.sessionId}`);
|
|
198
|
+
// Track session health — resets to true on any successful interaction
|
|
199
|
+
let sessionAlive = true;
|
|
200
|
+
let lastEventTime = Date.now();
|
|
201
|
+
let lastEventType = 'session.created';
|
|
202
|
+
function attachEventLogger(s) {
|
|
203
|
+
s.on((event) => {
|
|
204
|
+
lastEventTime = Date.now();
|
|
205
|
+
lastEventType = event.type;
|
|
206
|
+
// Log tool calls and completions fully, truncate verbose events
|
|
207
|
+
if (event.type === 'tool.execution_start' || event.type === 'tool.execution_complete') {
|
|
208
|
+
log(`[event:${event.type}]`, JSON.stringify(event.data ?? event).substring(0, 800));
|
|
209
|
+
}
|
|
210
|
+
else if (event.type === 'assistant.message') {
|
|
211
|
+
const data = event.data ?? event;
|
|
212
|
+
const tools = data.toolRequests?.map((t) => t.name).join(', ') || 'none';
|
|
213
|
+
log(`[event:${event.type}] content="${(data.content || '').substring(0, 200)}" tools=[${tools}]`);
|
|
214
|
+
}
|
|
215
|
+
else if (event.type === 'session.info') {
|
|
216
|
+
log(`[session.info] ${JSON.stringify(event.data)}`);
|
|
217
|
+
}
|
|
218
|
+
else if (event.type === 'session.warning') {
|
|
219
|
+
log(`[session.warning] ${JSON.stringify(event.data)}`);
|
|
220
|
+
}
|
|
221
|
+
else if (event.type === 'session.mcp_servers_loaded') {
|
|
222
|
+
log(`[mcp_servers_loaded] ${JSON.stringify(event.data)}`);
|
|
223
|
+
}
|
|
224
|
+
else if (event.type === 'session.mcp_server_status_changed') {
|
|
225
|
+
log(`[mcp_server_status_changed] ${JSON.stringify(event.data)}`);
|
|
226
|
+
}
|
|
227
|
+
else if (event.type === 'session.idle') {
|
|
228
|
+
log(`[event:session.idle] Session is idle`);
|
|
229
|
+
}
|
|
230
|
+
else if (event.type?.includes('error') || event.type?.includes('disconnect')) {
|
|
231
|
+
log(`[event:${event.type}]`, JSON.stringify(event.data ?? event).substring(0, 500));
|
|
232
|
+
sessionAlive = false;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
log(`[event:${event.type}]`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
attachEventLogger(session);
|
|
240
|
+
// Send an initial prompt to trigger MCP server initialization.
|
|
241
|
+
// The Copilot SDK doesn't start MCP server subprocesses until the session
|
|
242
|
+
// processes a message that could use tools. We await this so the workflow
|
|
243
|
+
// registers before we try to find it, and so subsequent sendAndWait calls
|
|
244
|
+
// don't collide with this one.
|
|
245
|
+
log('Sending initial prompt to trigger MCP server startup...');
|
|
246
|
+
try {
|
|
247
|
+
const t0 = Date.now();
|
|
248
|
+
const initResult = await session.sendAndWait({ prompt: 'Call the ensemble tool to list active sessions. Respond in one short sentence.' }, 120_000);
|
|
249
|
+
log(`Initial prompt completed in ${Date.now() - t0}ms, result:`, JSON.stringify(initResult)?.substring(0, 300));
|
|
250
|
+
// Dump available tools for diagnostics
|
|
251
|
+
try {
|
|
252
|
+
const toolList = await session.rpc.tools.list({});
|
|
253
|
+
log('Available tools:', JSON.stringify(toolList.tools?.map((t) => t.name || t.namespacedName)));
|
|
254
|
+
}
|
|
255
|
+
catch (toolErr) {
|
|
256
|
+
log('Failed to list tools:', toolErr?.message);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
log(`Initial prompt error after ${Date.now()}ms:`, err?.message, err?.stack?.substring(0, 300));
|
|
261
|
+
}
|
|
262
|
+
// PID file paths — computed early so early-exit paths can clean up
|
|
263
|
+
const pidDir = path.join(workDir, 'logs');
|
|
264
|
+
const pidFile = path.join(pidDir, `${playerName || playerIdForWorkflow}.pid`);
|
|
265
|
+
// Wait for the MCP server's workflow to register in Temporal.
|
|
266
|
+
// We know the exact workflow ID because we pass CLAUDE_TEMPO_PLAYER_NAME to the
|
|
267
|
+
// MCP server — no need for a time-window heuristic that could misidentify workflows.
|
|
268
|
+
log(`Waiting for workflow ${expectedWorkflowId} to register...`);
|
|
269
|
+
let handle = client.workflow.getHandle(expectedWorkflowId);
|
|
270
|
+
let workflowReady = false;
|
|
271
|
+
let pinnedRunId;
|
|
272
|
+
for (let attempt = 0; attempt < 30; attempt++) {
|
|
273
|
+
try {
|
|
274
|
+
const desc = await handle.describe();
|
|
275
|
+
if (desc.status.name === 'RUNNING') {
|
|
276
|
+
workflowReady = true;
|
|
277
|
+
pinnedRunId = desc.runId;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// Workflow not yet started
|
|
283
|
+
}
|
|
284
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
285
|
+
if (attempt % 5 === 4)
|
|
286
|
+
log(`Still waiting... attempt ${attempt + 1}/30`);
|
|
287
|
+
}
|
|
288
|
+
if (!workflowReady) {
|
|
289
|
+
log(`ERROR: Workflow ${expectedWorkflowId} did not register within 30 seconds`);
|
|
290
|
+
await session.disconnect();
|
|
291
|
+
await copilotClient.stop();
|
|
292
|
+
// Clean up PID file to avoid stale entries in `claude-tempo status`
|
|
293
|
+
try {
|
|
294
|
+
fs.unlinkSync(pidFile);
|
|
295
|
+
}
|
|
296
|
+
catch { /* may not exist yet */ }
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
// Pin all subsequent interactions to the runId we observed — prevents the
|
|
300
|
+
// zombie-resurrection hazard from #102. If this run completes (e.g. destroy),
|
|
301
|
+
// a later USE_EXISTING start would spawn a new run with the same workflow ID;
|
|
302
|
+
// an unpinned handle would silently attach to the new run, but a pinned handle
|
|
303
|
+
// returns WorkflowNotFound and lets the bridge exit cleanly.
|
|
304
|
+
handle = client.workflow.getHandle(expectedWorkflowId, pinnedRunId);
|
|
305
|
+
log(`Workflow ready: ${expectedWorkflowId} (pinned runId ${pinnedRunId})`);
|
|
306
|
+
// Store sessionId in workflow metadata for future encore/resume
|
|
307
|
+
try {
|
|
308
|
+
await handle.signal('updateMetadata', { sessionId: copilotSessionId });
|
|
309
|
+
}
|
|
310
|
+
catch { /* workflow may not be ready yet */ }
|
|
311
|
+
// If a name was requested, send the set_name instruction
|
|
312
|
+
if (playerName) {
|
|
313
|
+
log(`Sending set_name instruction for "${playerName}"...`);
|
|
314
|
+
const t0 = Date.now();
|
|
315
|
+
await session.sendAndWait({ prompt: `Call set_name("${playerName}") immediately. Respond in one short sentence.` }, 120_000);
|
|
316
|
+
log(`set_name completed in ${Date.now() - t0}ms`);
|
|
317
|
+
}
|
|
318
|
+
const MAESTRO_ACK = '\n\n[IMPORTANT: This message is from a human (Maestro). Immediately cue the sender back with a brief acknowledgment and your planned next step before doing the work.]';
|
|
319
|
+
// Write PID file so callers can find/kill orphaned bridge processes
|
|
320
|
+
try {
|
|
321
|
+
fs.mkdirSync(pidDir, { recursive: true });
|
|
322
|
+
fs.writeFileSync(pidFile, String(process.pid));
|
|
323
|
+
log(`PID file written: ${pidFile}`);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
log(`Warning: could not write PID file: ${err?.message}`);
|
|
327
|
+
}
|
|
328
|
+
// Start message poller — inject messages into the Copilot session.
|
|
329
|
+
// Tracks consecutive failures and attempts session recreation before giving up.
|
|
330
|
+
let polling = true;
|
|
331
|
+
let processing = false;
|
|
332
|
+
let pollCount = 0;
|
|
333
|
+
let consecutiveFailures = 0;
|
|
334
|
+
let sessionRecreations = 0;
|
|
335
|
+
let proactiveRecreations = 0;
|
|
336
|
+
let lastActivityTime = Date.now();
|
|
337
|
+
// interval declared here, assigned after poll is defined
|
|
338
|
+
let interval;
|
|
339
|
+
// Shared cleanup — disconnects session, removes PID file, stops client.
|
|
340
|
+
// `signalTermination` controls whether we also signal the workflow to terminate
|
|
341
|
+
// (skip if the workflow is already gone).
|
|
342
|
+
let shuttingDown = false;
|
|
343
|
+
const cleanup = async (signalTermination) => {
|
|
344
|
+
if (shuttingDown)
|
|
345
|
+
return;
|
|
346
|
+
shuttingDown = true;
|
|
347
|
+
polling = false;
|
|
348
|
+
clearInterval(interval);
|
|
349
|
+
if (signalTermination) {
|
|
350
|
+
try {
|
|
351
|
+
await handle.signal('updateMetadata', { status: 'terminated', terminatedBy: 'system' });
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
// workflow may already be gone
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
try {
|
|
358
|
+
await session.disconnect();
|
|
359
|
+
}
|
|
360
|
+
catch { /* already disconnected */ }
|
|
361
|
+
try {
|
|
362
|
+
fs.unlinkSync(pidFile);
|
|
363
|
+
}
|
|
364
|
+
catch { /* already gone */ }
|
|
365
|
+
await copilotClient.stop();
|
|
366
|
+
};
|
|
367
|
+
/** Attempt to recreate the Copilot session after repeated failures. */
|
|
368
|
+
async function recreateSession() {
|
|
369
|
+
// Fixes #102: before recreating, check whether the pinned-runId workflow has
|
|
370
|
+
// been destroyed. If so, the user explicitly stopped this session — do NOT
|
|
371
|
+
// bring it back as a zombie. This also covers WorkflowNotFound (the pinned
|
|
372
|
+
// run has completed/terminated) since the query throws cleanly.
|
|
373
|
+
try {
|
|
374
|
+
const isDestroyed = await handle.query('isDestroyed');
|
|
375
|
+
if (isDestroyed) {
|
|
376
|
+
log('Workflow is destroyed — not reconnecting (session was intentionally stopped)');
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
const errName = err?.name || '';
|
|
382
|
+
const errMsg = err?.message || '';
|
|
383
|
+
if (errName.includes('WorkflowNotFound') || errMsg.includes('NOT_FOUND')) {
|
|
384
|
+
log('Workflow not found (likely terminated) — not reconnecting');
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
// Query failed for another reason — log and continue with recreation.
|
|
388
|
+
log(`isDestroyed query failed (${errMsg}), continuing with recreation`);
|
|
389
|
+
}
|
|
390
|
+
sessionRecreations++;
|
|
391
|
+
if (sessionRecreations > MAX_SESSION_RECREATIONS) {
|
|
392
|
+
log(`ERROR: Exceeded max session recreations (${MAX_SESSION_RECREATIONS}). Giving up.`);
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
log(`Attempting session recovery (${sessionRecreations}/${MAX_SESSION_RECREATIONS})...`);
|
|
396
|
+
try {
|
|
397
|
+
await session.disconnect().catch(() => { });
|
|
398
|
+
// Try resumeSession first to preserve conversation history
|
|
399
|
+
try {
|
|
400
|
+
const { sessionId: _discard, ...resumeConfig } = sessionConfig;
|
|
401
|
+
session = await copilotClient.resumeSession(copilotSessionId, resumeConfig);
|
|
402
|
+
attachEventLogger(session);
|
|
403
|
+
sessionAlive = true;
|
|
404
|
+
consecutiveFailures = 0;
|
|
405
|
+
lastActivityTime = Date.now();
|
|
406
|
+
log(`Session resumed successfully: ${session.sessionId}`);
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
catch (resumeErr) {
|
|
410
|
+
log(`resumeSession failed (${resumeErr?.message}), falling back to createSession`);
|
|
411
|
+
session = await createSessionWithTimeout(copilotClient, sessionConfig);
|
|
412
|
+
attachEventLogger(session);
|
|
413
|
+
sessionAlive = true;
|
|
414
|
+
consecutiveFailures = 0;
|
|
415
|
+
lastActivityTime = Date.now();
|
|
416
|
+
log(`Session recreated (fresh) successfully: ${session.sessionId}`);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
log(`Session recovery failed: ${err?.message}`);
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const poll = async () => {
|
|
426
|
+
if (!polling || processing)
|
|
427
|
+
return;
|
|
428
|
+
pollCount++;
|
|
429
|
+
// Periodic health check
|
|
430
|
+
if (pollCount % 30 === 0) { // every ~60 seconds
|
|
431
|
+
const silenceSec = ((Date.now() - lastEventTime) / 1000).toFixed(0);
|
|
432
|
+
log(`[health] poll #${pollCount}, sessionAlive=${sessionAlive}, lastEvent=${lastEventType} ${silenceSec}s ago`);
|
|
433
|
+
}
|
|
434
|
+
// Periodic workflow status check — detect external termination/completion
|
|
435
|
+
if (pollCount % WORKFLOW_STATUS_CHECK_INTERVAL === 0) {
|
|
436
|
+
try {
|
|
437
|
+
const desc = await handle.describe();
|
|
438
|
+
const wfStatus = desc.status.name;
|
|
439
|
+
if (wfStatus !== 'RUNNING') {
|
|
440
|
+
log(`Workflow status is ${wfStatus} — exiting cleanly`);
|
|
441
|
+
await cleanup(false); // workflow already gone, don't signal
|
|
442
|
+
process.exit(0);
|
|
443
|
+
}
|
|
444
|
+
// Also check the in-workflow destroy flag — the workflow may still be RUNNING
|
|
445
|
+
// (draining) but has been destroyed. Exit cleanly without attempting signals
|
|
446
|
+
// that would be rejected.
|
|
447
|
+
try {
|
|
448
|
+
const isDestroyed = await handle.query('isDestroyed');
|
|
449
|
+
if (isDestroyed) {
|
|
450
|
+
log('Workflow destroyed — exiting cleanly');
|
|
451
|
+
await cleanup(false);
|
|
452
|
+
process.exit(0);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch { /* isDestroyed query unavailable pre-upgrade — safe to ignore */ }
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
// If we can't describe (e.g., workflow not found), it was likely terminated
|
|
459
|
+
log(`Workflow describe failed: ${err?.message} — treating as terminated`);
|
|
460
|
+
await cleanup(false);
|
|
461
|
+
process.exit(0);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Proactive stale-session detection — recreate before the SDK server GCs the session
|
|
465
|
+
const idleMs = Date.now() - lastActivityTime;
|
|
466
|
+
if (idleMs > SESSION_MAX_IDLE_MS && !processing) {
|
|
467
|
+
try {
|
|
468
|
+
processing = true; // guard against overlapping polls during async recreation
|
|
469
|
+
log(`Session idle for ${(idleMs / 1000 / 60).toFixed(0)}min — proactively recreating`);
|
|
470
|
+
proactiveRecreations++;
|
|
471
|
+
const recovered = await recreateSession();
|
|
472
|
+
if (recovered) {
|
|
473
|
+
// Proactive recreation is lifecycle management, not failure recovery — restore failure budget
|
|
474
|
+
// but don't reset to 0: use proactiveRecreations to cap total lifecycle recreations
|
|
475
|
+
sessionRecreations = Math.max(0, sessionRecreations - 1);
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
// Session is almost certainly dead server-side — force immediate recovery on next message
|
|
479
|
+
// Use MAX - 1 so the next poll error increments to the threshold and triggers recovery
|
|
480
|
+
consecutiveFailures = MAX_CONSECUTIVE_FAILURES - 1;
|
|
481
|
+
sessionAlive = false;
|
|
482
|
+
log('ERROR: Proactive session recreation failed — will force recovery on next message');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
processing = false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const messages = await handle.query('pendingMessages');
|
|
491
|
+
if (messages.length === 0)
|
|
492
|
+
return;
|
|
493
|
+
processing = true;
|
|
494
|
+
const ids = messages.map((m) => m.id);
|
|
495
|
+
// Format messages into a single prompt, appending ack instruction for Maestro messages
|
|
496
|
+
const prompt = messages
|
|
497
|
+
.map((m) => {
|
|
498
|
+
const line = `[Message from ${m.from}]: ${m.text}`;
|
|
499
|
+
return m.isMaestro ? line + MAESTRO_ACK : line;
|
|
500
|
+
})
|
|
501
|
+
.join('\n\n');
|
|
502
|
+
log(`Injecting ${messages.length} message(s) into Copilot session`);
|
|
503
|
+
log(`Prompt: ${prompt.substring(0, 300)}`);
|
|
504
|
+
if (!sessionAlive) {
|
|
505
|
+
log('WARNING: session appears dead, sendAndWait may hang');
|
|
506
|
+
}
|
|
507
|
+
// Fixes #99: mark these messages as in-flight before the blocking LLM call so
|
|
508
|
+
// the workflow's stale detection doesn't misclassify a long tool call as dead.
|
|
509
|
+
// Fire-and-logged — don't block sendAndWait on the update roundtrip.
|
|
510
|
+
const processingMarkerId = ids[0]; // representative for the batch
|
|
511
|
+
handle.executeUpdate('processingStart', { args: [{ messageId: processingMarkerId }] })
|
|
512
|
+
.catch((err) => log(`processingStart update failed (non-fatal): ${err?.message}`));
|
|
513
|
+
const t0 = Date.now();
|
|
514
|
+
let result;
|
|
515
|
+
try {
|
|
516
|
+
result = await session.sendAndWait({ prompt }, 300_000); // 5 min timeout
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
// Always release the in-flight marker — a thrown error from sendAndWait
|
|
520
|
+
// must not leave the workflow pinned as "processing".
|
|
521
|
+
handle.executeUpdate('processingEnd', { args: [{ messageId: processingMarkerId }] })
|
|
522
|
+
.catch((err) => log(`processingEnd update failed (non-fatal): ${err?.message}`));
|
|
523
|
+
}
|
|
524
|
+
const elapsed = Date.now() - t0;
|
|
525
|
+
log(`sendAndWait completed in ${elapsed}ms`);
|
|
526
|
+
log(`Response: ${JSON.stringify(result)?.substring(0, 500)}`);
|
|
527
|
+
// Mark delivered only after successful send — failed messages stay in pending queue for retry
|
|
528
|
+
await handle.signal('markDelivered', ids);
|
|
529
|
+
// Success — reset failure tracking
|
|
530
|
+
consecutiveFailures = 0;
|
|
531
|
+
lastActivityTime = Date.now();
|
|
532
|
+
sessionAlive = true;
|
|
533
|
+
processing = false;
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
processing = false;
|
|
537
|
+
consecutiveFailures++;
|
|
538
|
+
log(`Poll error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${err?.message}`);
|
|
539
|
+
log(`Error stack: ${err?.stack?.substring(0, 300)}`);
|
|
540
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
541
|
+
log('Consecutive failure threshold reached — attempting session recovery');
|
|
542
|
+
const recovered = await recreateSession();
|
|
543
|
+
if (!recovered) {
|
|
544
|
+
log('ERROR: Session recovery failed. Shutting down bridge.');
|
|
545
|
+
await cleanup(true);
|
|
546
|
+
process.exit(2);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
interval = setInterval(poll, POLL_INTERVAL_MS);
|
|
552
|
+
log('Message poller started. Bridge is running.');
|
|
553
|
+
// Graceful shutdown on SIGINT/SIGTERM — signal the workflow before exiting
|
|
554
|
+
const shutdown = async () => {
|
|
555
|
+
log('Shutting down (signal received)...');
|
|
556
|
+
await cleanup(true);
|
|
557
|
+
process.exit(0);
|
|
558
|
+
};
|
|
559
|
+
process.on('SIGINT', shutdown);
|
|
560
|
+
process.on('SIGTERM', shutdown);
|
|
561
|
+
}
|
|
562
|
+
main().catch((err) => {
|
|
563
|
+
log('Fatal error:', err);
|
|
564
|
+
process.exit(1);
|
|
565
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test seam — production code passes nothing. Tests inject stubs to
|
|
3
|
+
* avoid spawning real processes / reading real package.json files.
|
|
4
|
+
*/
|
|
5
|
+
export interface ProbeAdapterVersionsDeps {
|
|
6
|
+
/**
|
|
7
|
+
* Run a CLI and return its stdout. Default: real `execFile` with a
|
|
8
|
+
* timeout. Tests pass a stub that returns canned output.
|
|
9
|
+
*/
|
|
10
|
+
runCommand?: (cmd: string, args: string[]) => Promise<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the Copilot SDK's package version. Default: dynamic
|
|
13
|
+
* `require('@github/copilot-sdk/package.json').version`. Tests stub
|
|
14
|
+
* to control "installed vs. not installed" + version values.
|
|
15
|
+
* Returns `undefined` when the dep isn't installed.
|
|
16
|
+
*/
|
|
17
|
+
resolveCopilotSdkVersion?: () => Promise<string | undefined>;
|
|
18
|
+
/** Optional log sink. Tests pass a no-op to silence output. */
|
|
19
|
+
log?: (...args: unknown[]) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Probe the upstream tool versions for every adapter this daemon ships.
|
|
23
|
+
*
|
|
24
|
+
* @returns A map keyed by adapter name. Adapters whose probe failed or
|
|
25
|
+
* whose output couldn't be parsed are omitted entirely. The map may be
|
|
26
|
+
* empty in environments without any of the supported adapters available.
|
|
27
|
+
*/
|
|
28
|
+
export declare function probeAdapterVersions(deps?: ProbeAdapterVersionsDeps): Promise<Record<string, string>>;
|
|
29
|
+
/**
|
|
30
|
+
* Synchronous companion to {@link defaultResolveCopilotSdkVersion}.
|
|
31
|
+
* Returns the Copilot SDK's `package.json#version` if the optional
|
|
32
|
+
* dependency is installed, or `undefined` when the require fails.
|
|
33
|
+
*
|
|
34
|
+
* Exported for #532 PR-2 — `src/daemon.ts` `computeHostProfile()` is
|
|
35
|
+
* synchronous (matches the existing `claude-code-headless` block at
|
|
36
|
+
* `daemon.ts:278-288`, which uses synchronous probes from
|
|
37
|
+
* `src/adapters/claude-code-headless/pre-flight.ts`). Awaiting an
|
|
38
|
+
* async helper from a sync function isn't possible without cascading
|
|
39
|
+
* the change to all `computeHostProfile` call sites; instead, this
|
|
40
|
+
* sync helper is the single require-source-of-truth, and the async
|
|
41
|
+
* `defaultResolveCopilotSdkVersion` (kept for the
|
|
42
|
+
* `ProbeAdapterVersionsDeps['resolveCopilotSdkVersion']` contract)
|
|
43
|
+
* delegates to it.
|
|
44
|
+
*
|
|
45
|
+
* Tests stub this directly via the `resolveCopilotSdkVersionSync` dep
|
|
46
|
+
* on `computeHostProfile` (see `test/daemon-boot.test.ts`).
|
|
47
|
+
*
|
|
48
|
+
* Never throws — a `try/catch` swallows MODULE_NOT_FOUND and any
|
|
49
|
+
* other failure mode to `undefined`. A throw here would crash the
|
|
50
|
+
* daemon-boot critical path.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveCopilotSdkVersionSync(): string | undefined;
|