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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan-session query — shared by `reconcileOnBoot()` in `src/daemon.ts` and
|
|
3
|
+
* the `agent-tempo restore` CLI command (`src/cli/commands.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Design §10.1: a session is an **orphan** when the workflow is `Running` but
|
|
6
|
+
* no adapter process is alive to own its attachment. Two candidate shapes
|
|
7
|
+
* matter:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Active-host sessions** — `AgentTempoAttachedHost = local` AND phase
|
|
10
|
+
* is `attached` / `processing` / `awaiting` / `draining`. The attachment
|
|
11
|
+
* exists but the adapter process may have died.
|
|
12
|
+
* 2. **Detached-home sessions** — `AgentTempoAttachmentState = detached` AND
|
|
13
|
+
* `AgentTempoHostname = local`. No adapter at all; the home host is us.
|
|
14
|
+
*
|
|
15
|
+
* For each candidate we query `attachmentInfo` + `orphanSummary`. If the
|
|
16
|
+
* adapter process is alive (`isAdapterProcessAlive` returns true) we skip —
|
|
17
|
+
* that's the daemon-restarted-under-a-live-adapter case, not an orphan.
|
|
18
|
+
*
|
|
19
|
+
* `isAdapterProcessAlive` is stubbed as `() => false` for v0.25.0-beta.1 per
|
|
20
|
+
* PR-E engineer brief §8 answer 1. No adapter PID file convention exists yet
|
|
21
|
+
* (only the daemon process has `daemon.pid`; Copilot bridges write their own
|
|
22
|
+
* per-session file but Claude Code CLI does not). A conservative always-dead
|
|
23
|
+
* stub is safe: false negatives cost an extra `claimAttachment` attempt,
|
|
24
|
+
* which the caller catches as `AttachmentConflict` and backs off silently
|
|
25
|
+
* (design §10.6). False positives — skipping a session that needs restore —
|
|
26
|
+
* are the worse failure mode.
|
|
27
|
+
*/
|
|
28
|
+
import type { Client } from '@temporalio/client';
|
|
29
|
+
import type { AttachmentInfo, AttachmentPhase, OrphanSummary } from '../types';
|
|
30
|
+
/**
|
|
31
|
+
* A session workflow observed to be an orphan: the workflow is alive but no
|
|
32
|
+
* adapter is owning its attachment on this host.
|
|
33
|
+
*/
|
|
34
|
+
export interface OrphanCandidate {
|
|
35
|
+
workflowId: string;
|
|
36
|
+
info: AttachmentInfo;
|
|
37
|
+
summary: OrphanSummary;
|
|
38
|
+
}
|
|
39
|
+
/** Filter options for {@link queryOrphanedSessions}. */
|
|
40
|
+
export interface OrphanQueryFilter {
|
|
41
|
+
/** Local hostname to match against `AgentTempoAttachedHost` / `AgentTempoHostname`. */
|
|
42
|
+
hostname: string;
|
|
43
|
+
/** Optional ensemble narrowing pushed into the visibility query. */
|
|
44
|
+
ensemble?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Restrict the visibility query to specific attachment phases. Defaults to
|
|
47
|
+
* the broad live-phase set (`attached | processing | awaiting | draining`)
|
|
48
|
+
* for daemon reconcile-on-boot, which must treat every live phase as
|
|
49
|
+
* presumed-orphan (no PID memory post-crash). User-invoked `/restore`
|
|
50
|
+
* narrows this to `['detached']` so a healthy attached session is never
|
|
51
|
+
* flagged as an orphan candidate.
|
|
52
|
+
*
|
|
53
|
+
* The `detached` home-host clause is always included when `phases`
|
|
54
|
+
* contains `'detached'`; the active-host clause is included when `phases`
|
|
55
|
+
* contains any of the live phases.
|
|
56
|
+
*/
|
|
57
|
+
phases?: AttachmentPhase[];
|
|
58
|
+
/**
|
|
59
|
+
* #151 cluster-view: when `true`, drop the `AgentTempoAttachedHost` /
|
|
60
|
+
* `AgentTempoHostname` predicates from the visibility query so the result
|
|
61
|
+
* spans **every** host's orphans, not just the local one. Used by
|
|
62
|
+
* `agent-tempo restore --all-hosts` to surface dormant remote orphans
|
|
63
|
+
* the operator would otherwise need per-host SSH to see. `hostname` is
|
|
64
|
+
* still required (it's the join key for the cross-host record) but no
|
|
65
|
+
* longer filters the query.
|
|
66
|
+
*/
|
|
67
|
+
allHosts?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* When set, override `isAdapterProcessAlive`. Default stub returns `false`
|
|
70
|
+
* (always assume dead, conservative always-restore). Tests pass a custom
|
|
71
|
+
* predicate; production callers omit.
|
|
72
|
+
*/
|
|
73
|
+
isAdapterProcessAlive?: (hostname: string, workflowId: string) => boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Options shape for {@link buildOrphanQuery}. */
|
|
76
|
+
export interface BuildOrphanQueryOpts {
|
|
77
|
+
hostname: string;
|
|
78
|
+
ensemble?: string;
|
|
79
|
+
phases?: AttachmentPhase[];
|
|
80
|
+
/**
|
|
81
|
+
* #151: drop hostname predicates from the emitted query. The shape becomes
|
|
82
|
+
* `WorkflowType=... AND ExecutionStatus="Running" AND AgentTempoAttachmentState IN (...)`
|
|
83
|
+
* (no host clause), returning every orphan in the namespace. The `hostname`
|
|
84
|
+
* field is ignored when this is `true`.
|
|
85
|
+
*/
|
|
86
|
+
allHosts?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stub per §8 answer 1 — always reports dead. Exported for test wiring even
|
|
90
|
+
* though production callers use the default via {@link queryOrphanedSessions}.
|
|
91
|
+
*/
|
|
92
|
+
export declare function isAdapterProcessAliveStub(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Build the visibility-query string matching the §10.1 candidate set for the
|
|
95
|
+
* given hostname. Exposed (rather than inlined) so tests can introspect the
|
|
96
|
+
* query shape without a live Temporal connection.
|
|
97
|
+
*
|
|
98
|
+
* The opts-object form carries a `phases` filter — used by user-invoked
|
|
99
|
+
* `/restore` to narrow the visibility query to `detached` only so live
|
|
100
|
+
* sessions are never flagged as orphan candidates.
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildOrphanQuery(opts: BuildOrphanQueryOpts): string;
|
|
103
|
+
/**
|
|
104
|
+
* Query Temporal for orphan candidates matching the filter. Runs the
|
|
105
|
+
* visibility query, then fetches `attachmentInfo` + `orphanSummary` per
|
|
106
|
+
* candidate. Skips candidates whose adapter process the liveness predicate
|
|
107
|
+
* reports as alive.
|
|
108
|
+
*
|
|
109
|
+
* Defensive: any per-candidate failure (workflow completed between list +
|
|
110
|
+
* query, query handler throws) is logged and the candidate is skipped — the
|
|
111
|
+
* result array always reflects only the candidates that could be fully
|
|
112
|
+
* resolved at query time.
|
|
113
|
+
*
|
|
114
|
+
* **Deadline (#336/#529):** the visibility iterator is bounded by
|
|
115
|
+
* `deadlineMs`. Default is `VISIBILITY_DEADLINES_MS.orphanQueryBoot`
|
|
116
|
+
* (60s) — suitable for the daemon's one-shot boot reconcile, which can
|
|
117
|
+
* afford a generous budget to enumerate thousands of workflows. The
|
|
118
|
+
* periodic 6h cleanup loop should pass
|
|
119
|
+
* `VISIBILITY_DEADLINES_MS.orphanQueryCleanup` (30s) since it runs
|
|
120
|
+
* frequently enough to amortize partial scans. On timeout, returns the
|
|
121
|
+
* partial result and emits a warn log: the function's existing comment
|
|
122
|
+
* states partial enumeration is acceptable ("next tick will retry"),
|
|
123
|
+
* so the boot path can call `restoreOrphansOnce` again on a subsequent
|
|
124
|
+
* cycle to pick up missed candidates.
|
|
125
|
+
*/
|
|
126
|
+
export declare function queryOrphanedSessions(client: Client, filter: OrphanQueryFilter, log?: (...args: unknown[]) => void, deadlineMs?: number): Promise<OrphanCandidate[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Options for {@link restoreOrphansOnce}. `policy: 'never'` is NOT included
|
|
129
|
+
* here — callers that want a silent no-op skip the helper entirely. The
|
|
130
|
+
* daemon's `reconcileOnBoot` short-circuits on `restorePolicy === 'never'`
|
|
131
|
+
* before reaching this helper.
|
|
132
|
+
*/
|
|
133
|
+
export interface RestoreOrphansOpts {
|
|
134
|
+
/** Local hostname — matched against the orphan query filter and used as
|
|
135
|
+
* the default target when a candidate's `preferredHost` is unset. */
|
|
136
|
+
hostname: string;
|
|
137
|
+
/** Passed through to `TempoClient.restart(...)` as the operator identity
|
|
138
|
+
* (e.g. `'daemon'`, `'cli'`, or a specific player name). */
|
|
139
|
+
invokerPlayerId: string;
|
|
140
|
+
/** `'auto'` — call `restart` on each eligible candidate.
|
|
141
|
+
* `'prompt'` — log each candidate; do not call `restart`. */
|
|
142
|
+
policy: 'auto' | 'prompt';
|
|
143
|
+
/**
|
|
144
|
+
* #151 listing mode. `'local'` (default) keeps the pre-existing behavior:
|
|
145
|
+
* orphan query is filtered to this host, and `policy: 'auto'` restarts
|
|
146
|
+
* locally-preferred candidates. `'all-hosts-readonly'` widens the query
|
|
147
|
+
* to every host (cluster-view) AND short-circuits `policy` so `restart`
|
|
148
|
+
* is NEVER called — every visible orphan is emitted as
|
|
149
|
+
* `{ kind: 'skipped', reason: 'crossHost', detail: <preferredHost> }`
|
|
150
|
+
* for the CLI's `--all-hosts` discovery formatter. Recovery still
|
|
151
|
+
* happens through the existing safety valve (the remote daemon's own
|
|
152
|
+
* `reconcileOnBoot`, or a deliberate `restart --host <local> --force
|
|
153
|
+
* --confirm-steal-from-host <remote>`).
|
|
154
|
+
*/
|
|
155
|
+
mode?: 'local' | 'all-hosts-readonly';
|
|
156
|
+
/** Optional narrowing filter — only consider orphans in this ensemble. */
|
|
157
|
+
ensemble?: string;
|
|
158
|
+
/**
|
|
159
|
+
* Optional phase narrowing — forwarded to {@link queryOrphanedSessions}.
|
|
160
|
+
* Defaults to the broad live-phase set for daemon reconcile-on-boot +
|
|
161
|
+
* CLI `up --resume`. User-invoked `/restore` narrows this to
|
|
162
|
+
* `['detached']` so a healthy attached session is never flagged.
|
|
163
|
+
*/
|
|
164
|
+
phases?: AttachmentPhase[];
|
|
165
|
+
/** Orphans whose `detachedSince` exceeds this window are skipped.
|
|
166
|
+
* Default: 24 hours. Ignored when `policy === 'prompt'`. */
|
|
167
|
+
autoRestoreMaxAgeHours?: number;
|
|
168
|
+
/** Glob allowlist for ensemble names (`isEnsembleAllowed`). Empty /
|
|
169
|
+
* undefined → allow all. Ignored when `policy === 'prompt'`. */
|
|
170
|
+
autoRestoreEnsembles?: string[];
|
|
171
|
+
/** Injectable clock for tests. Default: `Date.now`. */
|
|
172
|
+
now?: () => number;
|
|
173
|
+
/** Override `createTempoClient` for tests. Default: production factory. */
|
|
174
|
+
tempoClientFactory?: (client: Client) => {
|
|
175
|
+
restart: (ensemble: string, playerId: string, opts: {
|
|
176
|
+
host: string;
|
|
177
|
+
invokerPlayerId: string;
|
|
178
|
+
}) => Promise<{
|
|
179
|
+
entryId: string;
|
|
180
|
+
playerId: string;
|
|
181
|
+
}>;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Structured outcome for a single orphan — discriminated union so callers
|
|
186
|
+
* can dispatch on `outcome.kind` without parsing strings. Use
|
|
187
|
+
* {@link formatRestoreOutcome} to render a human-readable form.
|
|
188
|
+
*/
|
|
189
|
+
export type RestoreOutcome = {
|
|
190
|
+
kind: 'queued';
|
|
191
|
+
entryId: string;
|
|
192
|
+
} | {
|
|
193
|
+
kind: 'skipped';
|
|
194
|
+
reason: 'preferredHost'
|
|
195
|
+
/**
|
|
196
|
+
* #151: cluster-view listing entry. Emitted by `restoreOrphansOnce`
|
|
197
|
+
* when `mode === 'all-hosts-readonly'` — semantically distinct from
|
|
198
|
+
* `preferredHost`, which fires on an *active* restore attempt that
|
|
199
|
+
* stepped back because the remote daemon owns recovery. `crossHost`
|
|
200
|
+
* is a passive observation for `agent-tempo restore --all-hosts`:
|
|
201
|
+
* "this orphan exists, here's its preferred host, we did nothing."
|
|
202
|
+
* Same data shape (`detail` carries the preferred host); the
|
|
203
|
+
* different label lets CLI / dashboard surfaces distinguish
|
|
204
|
+
* "skipped-during-auto-restore" from "shown-during-readonly-listing".
|
|
205
|
+
*/
|
|
206
|
+
| 'crossHost' | 'ageWindow' | 'ensembleAllowlist' | 'attachmentConflict' | 'prompt';
|
|
207
|
+
/** Extra context, e.g. the remote `preferredHost` value. */
|
|
208
|
+
detail?: string;
|
|
209
|
+
} | {
|
|
210
|
+
kind: 'failed';
|
|
211
|
+
error: string;
|
|
212
|
+
};
|
|
213
|
+
/** Per-orphan outcome produced by {@link restoreOrphansOnce}. */
|
|
214
|
+
export interface RestoreOrphanDetail {
|
|
215
|
+
playerId: string;
|
|
216
|
+
ensemble: string;
|
|
217
|
+
outcome: RestoreOutcome;
|
|
218
|
+
}
|
|
219
|
+
/** Render a {@link RestoreOutcome} for human-readable logs / CLI output. */
|
|
220
|
+
export declare function formatRestoreOutcome(o: RestoreOutcome): string;
|
|
221
|
+
/** Summary returned by {@link restoreOrphansOnce}. */
|
|
222
|
+
export interface RestoreOrphansSummary {
|
|
223
|
+
/** Orphans that successfully enqueued a `restart` outbox entry. */
|
|
224
|
+
reattached: number;
|
|
225
|
+
/** Orphans skipped (cross-host, age window, allowlist, AttachmentConflict,
|
|
226
|
+
* or `policy: 'prompt'`). */
|
|
227
|
+
skipped: number;
|
|
228
|
+
/** Orphans whose `restart` call threw a non-conflict error. */
|
|
229
|
+
failed: number;
|
|
230
|
+
/** Per-orphan details in visit order. Useful for UI rendering. */
|
|
231
|
+
details: RestoreOrphanDetail[];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Run one pass of orphan recovery. Shared by:
|
|
235
|
+
* - daemon's `reconcileOnBoot` (passes `invokerPlayerId: 'daemon'`)
|
|
236
|
+
* - CLI `up` option 2 and `conduct --resume` (passes `invokerPlayerId: 'cli'`)
|
|
237
|
+
*
|
|
238
|
+
* Flow (matches the former body of `reconcileOnBoot`):
|
|
239
|
+
* 1. Query orphan candidates on this host.
|
|
240
|
+
* 2. Filter out candidates whose `preferredHost` points elsewhere
|
|
241
|
+
* (PR-F §16 — remote host's daemon is the authoritative restorer).
|
|
242
|
+
* 3. If `policy === 'prompt'`: log each surviving candidate and return.
|
|
243
|
+
* 4. If `policy === 'auto'`: for each surviving candidate, apply the
|
|
244
|
+
* age window + ensemble allowlist filters, then call
|
|
245
|
+
* `TempoClient.restart` to enqueue the restart outbox entry.
|
|
246
|
+
*
|
|
247
|
+
* Never throws — all per-candidate failures are captured in
|
|
248
|
+
* {@link RestoreOrphansSummary.details}. The caller picks whether to log,
|
|
249
|
+
* surface to UI, or aggregate across multiple hosts.
|
|
250
|
+
*
|
|
251
|
+
* `AttachmentConflict` is counted as `skipped` (not `failed`) because the
|
|
252
|
+
* only cause is another operator or daemon having restored concurrently —
|
|
253
|
+
* the user's intent was satisfied, just not by us.
|
|
254
|
+
*/
|
|
255
|
+
export declare function restoreOrphansOnce(client: Client, opts: RestoreOrphansOpts, log?: (...args: unknown[]) => void): Promise<RestoreOrphansSummary>;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAdapterProcessAliveStub = isAdapterProcessAliveStub;
|
|
4
|
+
exports.buildOrphanQuery = buildOrphanQuery;
|
|
5
|
+
exports.queryOrphanedSessions = queryOrphanedSessions;
|
|
6
|
+
exports.formatRestoreOutcome = formatRestoreOutcome;
|
|
7
|
+
exports.restoreOrphansOnce = restoreOrphansOnce;
|
|
8
|
+
const signals_1 = require("../workflows/signals");
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
const client_1 = require("../client");
|
|
11
|
+
const query_timeout_1 = require("../utils/query-timeout");
|
|
12
|
+
const visibility_deadline_1 = require("../utils/visibility-deadline");
|
|
13
|
+
/**
|
|
14
|
+
* Default broad phase set used by daemon reconcile-on-boot and CLI
|
|
15
|
+
* `up --resume`. Both paths have no PID memory after a crash and must
|
|
16
|
+
* treat every live phase as a presumed orphan — the post-query
|
|
17
|
+
* `isAdapterProcessAlive` predicate (and, in practice, the restart outbox
|
|
18
|
+
* raising `AttachmentConflict` against a live adapter) is what protects
|
|
19
|
+
* genuinely-attached sessions from being torn down.
|
|
20
|
+
*
|
|
21
|
+
* Includes `'detached'` so the broad query emits both §10.1 clauses:
|
|
22
|
+
* - active-host live-phase (`AgentTempoAttachedHost = host AND state IN (...)`)
|
|
23
|
+
* - detached home-host (`state = "detached" AND AgentTempoHostname = host`)
|
|
24
|
+
*
|
|
25
|
+
* User-invoked `/restore` narrows this to `['detached']` via the `phases`
|
|
26
|
+
* filter so a healthy live session is never flagged as an orphan candidate.
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_ORPHAN_PHASES = [
|
|
29
|
+
'attached',
|
|
30
|
+
'processing',
|
|
31
|
+
'awaiting',
|
|
32
|
+
'draining',
|
|
33
|
+
'detached',
|
|
34
|
+
];
|
|
35
|
+
/**
|
|
36
|
+
* Stub per §8 answer 1 — always reports dead. Exported for test wiring even
|
|
37
|
+
* though production callers use the default via {@link queryOrphanedSessions}.
|
|
38
|
+
*/
|
|
39
|
+
function isAdapterProcessAliveStub() {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Escape a value for interpolation into a Temporal visibility query string.
|
|
44
|
+
* Mirrors the helper in `src/client/index.ts`.
|
|
45
|
+
*/
|
|
46
|
+
function sanitizeQueryValue(value) {
|
|
47
|
+
return value.replace(/["\\\n\r]/g, '');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the visibility-query string matching the §10.1 candidate set for the
|
|
51
|
+
* given hostname. Exposed (rather than inlined) so tests can introspect the
|
|
52
|
+
* query shape without a live Temporal connection.
|
|
53
|
+
*
|
|
54
|
+
* The opts-object form carries a `phases` filter — used by user-invoked
|
|
55
|
+
* `/restore` to narrow the visibility query to `detached` only so live
|
|
56
|
+
* sessions are never flagged as orphan candidates.
|
|
57
|
+
*/
|
|
58
|
+
function buildOrphanQuery(opts) {
|
|
59
|
+
const h = sanitizeQueryValue(opts.hostname);
|
|
60
|
+
const ensembleClause = opts.ensemble
|
|
61
|
+
? ` AND AgentTempoEnsemble = "${sanitizeQueryValue(opts.ensemble)}"`
|
|
62
|
+
: '';
|
|
63
|
+
const phases = opts.phases && opts.phases.length > 0
|
|
64
|
+
? opts.phases
|
|
65
|
+
: DEFAULT_ORPHAN_PHASES;
|
|
66
|
+
const livePhases = phases.filter((p) => p !== 'detached');
|
|
67
|
+
const includeDetached = phases.includes('detached');
|
|
68
|
+
const clauses = [];
|
|
69
|
+
if (opts.allHosts) {
|
|
70
|
+
// #151 cluster-view — drop the per-host predicates entirely. Phase filter
|
|
71
|
+
// is still respected so we don't sweep up genuinely-live sessions when
|
|
72
|
+
// the caller narrowed to `['detached']`.
|
|
73
|
+
if (livePhases.length > 0) {
|
|
74
|
+
const liveList = livePhases.map((p) => `"${p}"`).join(',');
|
|
75
|
+
clauses.push(`AgentTempoAttachmentState IN (${liveList})`);
|
|
76
|
+
}
|
|
77
|
+
if (includeDetached) {
|
|
78
|
+
clauses.push(`AgentTempoAttachmentState = "detached"`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
if (livePhases.length > 0) {
|
|
83
|
+
const liveList = livePhases.map((p) => `"${p}"`).join(',');
|
|
84
|
+
clauses.push(`(AgentTempoAttachedHost = "${h}" AND AgentTempoAttachmentState IN (${liveList}))`);
|
|
85
|
+
}
|
|
86
|
+
if (includeDetached) {
|
|
87
|
+
clauses.push(`(AgentTempoAttachmentState = "detached" AND AgentTempoHostname = "${h}")`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Safety net — both arms empty means caller passed `phases: []`. Fall back
|
|
91
|
+
// to the default broad set rather than emit an invalid `AND ()` clause.
|
|
92
|
+
if (clauses.length === 0) {
|
|
93
|
+
return buildOrphanQuery({ ...opts, phases: DEFAULT_ORPHAN_PHASES });
|
|
94
|
+
}
|
|
95
|
+
return (`WorkflowType = "agentSessionWorkflow" ` +
|
|
96
|
+
`AND ExecutionStatus = "Running" ` +
|
|
97
|
+
`AND (${clauses.join(' OR ')})${ensembleClause}`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Query Temporal for orphan candidates matching the filter. Runs the
|
|
101
|
+
* visibility query, then fetches `attachmentInfo` + `orphanSummary` per
|
|
102
|
+
* candidate. Skips candidates whose adapter process the liveness predicate
|
|
103
|
+
* reports as alive.
|
|
104
|
+
*
|
|
105
|
+
* Defensive: any per-candidate failure (workflow completed between list +
|
|
106
|
+
* query, query handler throws) is logged and the candidate is skipped — the
|
|
107
|
+
* result array always reflects only the candidates that could be fully
|
|
108
|
+
* resolved at query time.
|
|
109
|
+
*
|
|
110
|
+
* **Deadline (#336/#529):** the visibility iterator is bounded by
|
|
111
|
+
* `deadlineMs`. Default is `VISIBILITY_DEADLINES_MS.orphanQueryBoot`
|
|
112
|
+
* (60s) — suitable for the daemon's one-shot boot reconcile, which can
|
|
113
|
+
* afford a generous budget to enumerate thousands of workflows. The
|
|
114
|
+
* periodic 6h cleanup loop should pass
|
|
115
|
+
* `VISIBILITY_DEADLINES_MS.orphanQueryCleanup` (30s) since it runs
|
|
116
|
+
* frequently enough to amortize partial scans. On timeout, returns the
|
|
117
|
+
* partial result and emits a warn log: the function's existing comment
|
|
118
|
+
* states partial enumeration is acceptable ("next tick will retry"),
|
|
119
|
+
* so the boot path can call `restoreOrphansOnce` again on a subsequent
|
|
120
|
+
* cycle to pick up missed candidates.
|
|
121
|
+
*/
|
|
122
|
+
async function queryOrphanedSessions(client, filter, log = () => { }, deadlineMs = visibility_deadline_1.VISIBILITY_DEADLINES_MS.orphanQueryBoot) {
|
|
123
|
+
const isAlive = filter.isAdapterProcessAlive ?? isAdapterProcessAliveStub;
|
|
124
|
+
const query = buildOrphanQuery({
|
|
125
|
+
hostname: filter.hostname,
|
|
126
|
+
...(filter.ensemble !== undefined ? { ensemble: filter.ensemble } : {}),
|
|
127
|
+
...(filter.phases !== undefined ? { phases: filter.phases } : {}),
|
|
128
|
+
...(filter.allHosts ? { allHosts: true } : {}),
|
|
129
|
+
});
|
|
130
|
+
// Defense-in-depth: even though the visibility query filters by phase,
|
|
131
|
+
// re-check `info.phase` after fetching the fresh `attachmentInfo`. A
|
|
132
|
+
// session may have transitioned between list + query, and we never want a
|
|
133
|
+
// live `attached`/`processing`/`awaiting` session to be returned to a
|
|
134
|
+
// caller that narrowed to `['detached']`.
|
|
135
|
+
const phaseAllowlist = filter.phases && filter.phases.length > 0
|
|
136
|
+
? new Set(filter.phases)
|
|
137
|
+
: null;
|
|
138
|
+
const orphans = [];
|
|
139
|
+
try {
|
|
140
|
+
for await (const wf of (0, visibility_deadline_1.iterateWithDeadline)(client.workflow.list({ query }), deadlineMs, 'queryOrphanedSessions')) {
|
|
141
|
+
const handle = client.workflow.getHandle(wf.workflowId);
|
|
142
|
+
try {
|
|
143
|
+
// Issue #433 — bound each per-workflow query so a hung session
|
|
144
|
+
// (workflow `Running` but worker dead) can't block the orphan scan
|
|
145
|
+
// forever. The `try/catch` already treats query failure as "skip
|
|
146
|
+
// this candidate"; `QueryTimeoutError` falls into the same path.
|
|
147
|
+
const info = await (0, query_timeout_1.queryHandleWithTimeout)(handle, signals_1.attachmentInfoQuery);
|
|
148
|
+
// Phase allowlist re-check (see above).
|
|
149
|
+
if (phaseAllowlist && info.phase && !phaseAllowlist.has(info.phase)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Live adapter — not an orphan.
|
|
153
|
+
if (info.currentAttachment && isAlive(info.currentAttachment.hostname, wf.workflowId)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const summary = await (0, query_timeout_1.queryHandleWithTimeout)(handle, signals_1.orphanSummaryQuery);
|
|
157
|
+
orphans.push({ workflowId: wf.workflowId, info, summary });
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
// Workflow may have completed between list + query, a query handler
|
|
161
|
+
// threw, or the per-query timeout fired (#433 — wedged worker).
|
|
162
|
+
// Skip — not every candidate will be reachable, and partial results
|
|
163
|
+
// are acceptable for reconcile (next tick will retry).
|
|
164
|
+
log(`orphan-query skip ${wf.workflowId}:`, err instanceof Error ? err.message : String(err));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if ((0, visibility_deadline_1.isVisibilityTimeout)(err)) {
|
|
170
|
+
// #336/#529 — partial-tolerant: warn-log and return what we have.
|
|
171
|
+
// The caller (reconcile-on-boot / cleanup loop) treats partial
|
|
172
|
+
// results as best-effort; the next sweep picks up any missed
|
|
173
|
+
// candidates.
|
|
174
|
+
log(`queryOrphanedSessions: ${err.message} — returning partial (${orphans.length} orphans)`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return orphans;
|
|
181
|
+
}
|
|
182
|
+
/** Render a {@link RestoreOutcome} for human-readable logs / CLI output. */
|
|
183
|
+
function formatRestoreOutcome(o) {
|
|
184
|
+
switch (o.kind) {
|
|
185
|
+
case 'queued': return `queued (outbox ${o.entryId})`;
|
|
186
|
+
case 'failed': return `failed: ${o.error}`;
|
|
187
|
+
case 'skipped':
|
|
188
|
+
switch (o.reason) {
|
|
189
|
+
case 'preferredHost': return `skipped: preferredHost=${o.detail ?? '(unknown)'}`;
|
|
190
|
+
case 'crossHost': return `cross-host orphan: preferredHost=${o.detail ?? '(unknown)'}`;
|
|
191
|
+
case 'ageWindow': return 'skipped: age window';
|
|
192
|
+
case 'ensembleAllowlist': return 'skipped: ensemble allowlist';
|
|
193
|
+
case 'attachmentConflict': return 'skipped: AttachmentConflict';
|
|
194
|
+
case 'prompt': return 'prompt';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Run one pass of orphan recovery. Shared by:
|
|
200
|
+
* - daemon's `reconcileOnBoot` (passes `invokerPlayerId: 'daemon'`)
|
|
201
|
+
* - CLI `up` option 2 and `conduct --resume` (passes `invokerPlayerId: 'cli'`)
|
|
202
|
+
*
|
|
203
|
+
* Flow (matches the former body of `reconcileOnBoot`):
|
|
204
|
+
* 1. Query orphan candidates on this host.
|
|
205
|
+
* 2. Filter out candidates whose `preferredHost` points elsewhere
|
|
206
|
+
* (PR-F §16 — remote host's daemon is the authoritative restorer).
|
|
207
|
+
* 3. If `policy === 'prompt'`: log each surviving candidate and return.
|
|
208
|
+
* 4. If `policy === 'auto'`: for each surviving candidate, apply the
|
|
209
|
+
* age window + ensemble allowlist filters, then call
|
|
210
|
+
* `TempoClient.restart` to enqueue the restart outbox entry.
|
|
211
|
+
*
|
|
212
|
+
* Never throws — all per-candidate failures are captured in
|
|
213
|
+
* {@link RestoreOrphansSummary.details}. The caller picks whether to log,
|
|
214
|
+
* surface to UI, or aggregate across multiple hosts.
|
|
215
|
+
*
|
|
216
|
+
* `AttachmentConflict` is counted as `skipped` (not `failed`) because the
|
|
217
|
+
* only cause is another operator or daemon having restored concurrently —
|
|
218
|
+
* the user's intent was satisfied, just not by us.
|
|
219
|
+
*/
|
|
220
|
+
async function restoreOrphansOnce(client, opts, log = () => { }) {
|
|
221
|
+
const summary = {
|
|
222
|
+
reattached: 0,
|
|
223
|
+
skipped: 0,
|
|
224
|
+
failed: 0,
|
|
225
|
+
details: [],
|
|
226
|
+
};
|
|
227
|
+
const now = opts.now ?? Date.now;
|
|
228
|
+
const mode = opts.mode ?? 'local';
|
|
229
|
+
const allHostsReadonly = mode === 'all-hosts-readonly';
|
|
230
|
+
let orphans;
|
|
231
|
+
try {
|
|
232
|
+
orphans = await queryOrphanedSessions(client, {
|
|
233
|
+
hostname: opts.hostname,
|
|
234
|
+
...(opts.ensemble ? { ensemble: opts.ensemble } : {}),
|
|
235
|
+
...(opts.phases ? { phases: opts.phases } : {}),
|
|
236
|
+
...(allHostsReadonly ? { allHosts: true } : {}),
|
|
237
|
+
}, log);
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
log('restoreOrphansOnce: orphan query failed (non-fatal):', err instanceof Error ? err.message : String(err));
|
|
241
|
+
return summary;
|
|
242
|
+
}
|
|
243
|
+
if (orphans.length === 0) {
|
|
244
|
+
return summary;
|
|
245
|
+
}
|
|
246
|
+
// Shared emitter so every outcome is recorded + logged identically.
|
|
247
|
+
const record = (o, outcome) => {
|
|
248
|
+
if (outcome.kind === 'queued')
|
|
249
|
+
summary.reattached++;
|
|
250
|
+
else if (outcome.kind === 'failed')
|
|
251
|
+
summary.failed++;
|
|
252
|
+
else
|
|
253
|
+
summary.skipped++;
|
|
254
|
+
summary.details.push({
|
|
255
|
+
playerId: o.summary.playerId,
|
|
256
|
+
ensemble: o.summary.ensemble,
|
|
257
|
+
outcome,
|
|
258
|
+
});
|
|
259
|
+
log(`restoreOrphansOnce: ${o.workflowId} — ${formatRestoreOutcome(outcome)}`);
|
|
260
|
+
};
|
|
261
|
+
// #151: cluster-view readonly mode. Never calls `restart` — emits every
|
|
262
|
+
// visible orphan as a structured `crossHost` skip so the CLI's
|
|
263
|
+
// `--all-hosts` formatter can group, annotate, and surface the action
|
|
264
|
+
// edge. Recovery happens out-of-band: the remote daemon's reconcile-on-
|
|
265
|
+
// boot when it returns, or a deliberate TUI `/migrate <player> <host>
|
|
266
|
+
// --force`. The post-query phase allowlist (live `attached` not flagged
|
|
267
|
+
// as orphan candidate) and ensemble narrowing are both applied here too.
|
|
268
|
+
if (allHostsReadonly) {
|
|
269
|
+
for (const o of orphans) {
|
|
270
|
+
if (opts.ensemble && o.summary.ensemble !== opts.ensemble)
|
|
271
|
+
continue;
|
|
272
|
+
// `detail` carries the preferred host when set, falling back to the
|
|
273
|
+
// candidate's home hostname (from `OrphanSummary.lastAdapter.hostname`)
|
|
274
|
+
// and finally `(unknown)`. The CLI groups by this value.
|
|
275
|
+
const detail = o.summary.preferredHost ?? o.summary.lastAdapter?.hostname ?? '(unknown)';
|
|
276
|
+
record(o, { kind: 'skipped', reason: 'crossHost', detail });
|
|
277
|
+
}
|
|
278
|
+
return summary;
|
|
279
|
+
}
|
|
280
|
+
// Filter: (1) ensemble narrowing from the caller, (2) PR-F cross-host
|
|
281
|
+
// filter (design §16 — remote orphans are the remote daemon's problem).
|
|
282
|
+
const candidates = [];
|
|
283
|
+
for (const o of orphans) {
|
|
284
|
+
if (opts.ensemble && o.summary.ensemble !== opts.ensemble)
|
|
285
|
+
continue;
|
|
286
|
+
if (o.summary.preferredHost && o.summary.preferredHost !== opts.hostname) {
|
|
287
|
+
record(o, { kind: 'skipped', reason: 'preferredHost', detail: o.summary.preferredHost });
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
candidates.push(o);
|
|
291
|
+
}
|
|
292
|
+
if (candidates.length === 0) {
|
|
293
|
+
return summary;
|
|
294
|
+
}
|
|
295
|
+
if (opts.policy === 'prompt') {
|
|
296
|
+
for (const o of candidates)
|
|
297
|
+
record(o, { kind: 'skipped', reason: 'prompt' });
|
|
298
|
+
return summary;
|
|
299
|
+
}
|
|
300
|
+
// policy === 'auto'
|
|
301
|
+
const ageWindowMs = (opts.autoRestoreMaxAgeHours ?? 24) * 60 * 60 * 1000;
|
|
302
|
+
const allowlist = opts.autoRestoreEnsembles ?? [];
|
|
303
|
+
const nowMs = now();
|
|
304
|
+
const tempo = (opts.tempoClientFactory ?? client_1.createTempoClient)(client);
|
|
305
|
+
for (const o of candidates) {
|
|
306
|
+
// Age filter — skip orphans older than the restore window.
|
|
307
|
+
if (o.summary.detachedSince) {
|
|
308
|
+
const detachedAt = Date.parse(o.summary.detachedSince);
|
|
309
|
+
if (Number.isFinite(detachedAt) && nowMs - detachedAt > ageWindowMs) {
|
|
310
|
+
record(o, { kind: 'skipped', reason: 'ageWindow' });
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// Ensemble allowlist (empty → allow all).
|
|
315
|
+
if (!(0, config_1.isEnsembleAllowed)(o.summary.ensemble, allowlist)) {
|
|
316
|
+
record(o, { kind: 'skipped', reason: 'ensembleAllowlist' });
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const targetHost = o.summary.preferredHost ?? opts.hostname;
|
|
320
|
+
try {
|
|
321
|
+
const result = await tempo.restart(o.summary.ensemble, o.summary.playerId, {
|
|
322
|
+
host: targetHost,
|
|
323
|
+
invokerPlayerId: opts.invokerPlayerId,
|
|
324
|
+
});
|
|
325
|
+
record(o, { kind: 'queued', entryId: result.entryId });
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
// §10.6: `AttachmentConflict` is a silent backoff — another restorer
|
|
329
|
+
// won the race concurrently.
|
|
330
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
331
|
+
if (msg.includes('AttachmentConflict')) {
|
|
332
|
+
record(o, { kind: 'skipped', reason: 'attachmentConflict' });
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
record(o, { kind: 'failed', error: msg });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return summary;
|
|
340
|
+
}
|