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,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `listHosts` — join layer for #274 host discovery.
|
|
3
|
+
*
|
|
4
|
+
* Three RPCs per invocation (AC6b):
|
|
5
|
+
* 1. `DescribeTaskQueue` on the shared queue with `TASK_QUEUE_TYPE_WORKFLOW`
|
|
6
|
+
* — populates `hasWorkflowWorker` per identity.
|
|
7
|
+
* 2. Same queue, `TASK_QUEUE_TYPE_ACTIVITY` — populates `hasActivityWorker`.
|
|
8
|
+
* 3. `DescribeTaskQueue` on each per-host queue
|
|
9
|
+
* (`agent-tempo-<hostname>`), `TASK_QUEUE_TYPE_ACTIVITY` — populates
|
|
10
|
+
* `hasHostQueueWorker`. Runs in parallel across discovered hostnames.
|
|
11
|
+
*
|
|
12
|
+
* Plus one `hostProfiles` query against the global maestro when it's
|
|
13
|
+
* running (AC6g). Absent → liveness-only with
|
|
14
|
+
* `profileStaleness: 'missing'`.
|
|
15
|
+
*
|
|
16
|
+
* Identity is parsed with dual-format tolerance (AC6d / M6):
|
|
17
|
+
* - `agent-tempo:<hostname>:<pid>:<version>` (set by v1.0+ workers)
|
|
18
|
+
* - `<pid>@<hostname>` (legacy SDK default, pre-#274 daemons)
|
|
19
|
+
* - Anything else is skipped silently as opaque third-party.
|
|
20
|
+
*
|
|
21
|
+
* Results are cached for `CACHE_TTL_MS` (AC6h) to keep rapid-fire CLI/TUI
|
|
22
|
+
* invocations cheap; pass `force: true` to bypass.
|
|
23
|
+
*/
|
|
24
|
+
import type { Client } from '@temporalio/client';
|
|
25
|
+
import { temporal } from '@temporalio/proto';
|
|
26
|
+
import type { HostInfo, HostProfile } from '../types';
|
|
27
|
+
/**
|
|
28
|
+
* Freshness threshold — pollers seen in the last minute are `'live'`,
|
|
29
|
+
* older ones are tagged `'stale'` and hidden by default. Temporal
|
|
30
|
+
* server-side LRU keeps pollers for ~5 min; 1-min gives a tighter
|
|
31
|
+
* "currently polling" signal and aligns with the daemon heartbeat
|
|
32
|
+
* cadence. AC6c.
|
|
33
|
+
*/
|
|
34
|
+
export declare const HOST_FRESHNESS_THRESHOLD_MS = 60000;
|
|
35
|
+
/** Read-through cache TTL for the join result. AC6h. */
|
|
36
|
+
export declare const CACHE_TTL_MS = 3000;
|
|
37
|
+
/**
|
|
38
|
+
* #281 — concurrency cap for the per-host RPC fan-out.
|
|
39
|
+
*
|
|
40
|
+
* `Promise.all(hostnames.map(describe...))` is unbounded; at >20 hosts that
|
|
41
|
+
* thunder-herds the Temporal frontend's per-namespace RPS budget. 8 keeps
|
|
42
|
+
* the common case (≤8 hosts) at full parallelism while preventing the worst
|
|
43
|
+
* case from spiking the backend.
|
|
44
|
+
*/
|
|
45
|
+
export declare const HOST_FANOUT_CONCURRENCY = 8;
|
|
46
|
+
/**
|
|
47
|
+
* Test hook — never call from production code.
|
|
48
|
+
*
|
|
49
|
+
* Convention: `__<verb><Noun>ForTests`, co-located with the module that owns
|
|
50
|
+
* the state. See `docs/adr/0006-test-hooks-naming.md`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function __resetHostsCacheForTests(): void;
|
|
53
|
+
export interface ParsedIdentity {
|
|
54
|
+
hostname: string;
|
|
55
|
+
pid: number;
|
|
56
|
+
version: string;
|
|
57
|
+
/** `true` when the identity was in the legacy `<pid>@<hostname>` SDK-default shape. */
|
|
58
|
+
legacy: boolean;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse a Temporal poller identity back into the daemon that emitted it.
|
|
62
|
+
*
|
|
63
|
+
* Returns `null` for opaque / third-party identities (e.g. Temporal's own
|
|
64
|
+
* system pollers or an unrelated worker sharing the namespace). Callers
|
|
65
|
+
* skip those silently.
|
|
66
|
+
*
|
|
67
|
+
* The v1.0 format (`agent-tempo:<hostname>:<pid>:<version>`) is
|
|
68
|
+
* guaranteed to have exactly 4 colon-delimited segments because every
|
|
69
|
+
* component has its own validation: hostname passes `PLAYER_NAME_REGEX`
|
|
70
|
+
* (no colons possible), pid is numeric, and version is a semver-ish
|
|
71
|
+
* string (no colons).
|
|
72
|
+
*
|
|
73
|
+
* Pre-v1.0 daemons emitted a `claude-tempo:` prefix. Under the v1.0 hard
|
|
74
|
+
* break those daemons can't reach v1.x task queues anyway, but if one does
|
|
75
|
+
* surface in a host listing we tag it `legacy: true` so operators can
|
|
76
|
+
* distinguish a still-running old daemon from a fresh v1.x worker.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseIdentity(identity: string): ParsedIdentity | null;
|
|
79
|
+
interface RawPoller {
|
|
80
|
+
identity: string;
|
|
81
|
+
lastAccessTimeMs: number;
|
|
82
|
+
lastAccessTimeIso: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Dep-injection seam used by tests. Production callers OMIT this entirely —
|
|
86
|
+
* grouping it under `deps` (vs. mixing into the top level of `ListHostsOpts`)
|
|
87
|
+
* keeps the user-facing surface (`force`, `namespace`, `taskQueue`) cleanly
|
|
88
|
+
* separated from internal test wiring. See #283.
|
|
89
|
+
*/
|
|
90
|
+
export interface ListHostsDeps {
|
|
91
|
+
/** `Date.now` replacement for deterministic tests. */
|
|
92
|
+
now?: () => number;
|
|
93
|
+
/**
|
|
94
|
+
* Dep-injected poller fetcher — tests stub to return canned rows.
|
|
95
|
+
* Production callers omit and the helper uses
|
|
96
|
+
* `client.workflowService.describeTaskQueue` directly.
|
|
97
|
+
*/
|
|
98
|
+
describePollers?: (client: Client, namespace: string, taskQueueName: string, taskQueueType: temporal.api.enums.v1.TaskQueueType) => Promise<RawPoller[]>;
|
|
99
|
+
/** Dep-injected profile fetcher. Tests stub to control profile state. */
|
|
100
|
+
fetchProfiles?: (client: Client) => Promise<Record<string, HostProfile> | null>;
|
|
101
|
+
}
|
|
102
|
+
export interface ListHostsOpts {
|
|
103
|
+
/** Bypass the 3s TTL cache. CLI/TUI refresh handlers pass `true`. */
|
|
104
|
+
force?: boolean;
|
|
105
|
+
/** Default `'default'`. */
|
|
106
|
+
namespace?: string;
|
|
107
|
+
/** Default `'agent-tempo'`. */
|
|
108
|
+
taskQueue?: string;
|
|
109
|
+
/** Test-only dep-injection seam. Production callers omit. */
|
|
110
|
+
deps?: ListHostsDeps;
|
|
111
|
+
}
|
|
112
|
+
export declare function listHosts(client: Client, opts?: ListHostsOpts): Promise<HostInfo[]>;
|
|
113
|
+
export {};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.HOST_FANOUT_CONCURRENCY = exports.CACHE_TTL_MS = exports.HOST_FRESHNESS_THRESHOLD_MS = void 0;
|
|
7
|
+
exports.__resetHostsCacheForTests = __resetHostsCacheForTests;
|
|
8
|
+
exports.parseIdentity = parseIdentity;
|
|
9
|
+
exports.listHosts = listHosts;
|
|
10
|
+
const proto_1 = require("@temporalio/proto");
|
|
11
|
+
const p_limit_1 = __importDefault(require("p-limit"));
|
|
12
|
+
const config_1 = require("../config");
|
|
13
|
+
/**
|
|
14
|
+
* Freshness threshold — pollers seen in the last minute are `'live'`,
|
|
15
|
+
* older ones are tagged `'stale'` and hidden by default. Temporal
|
|
16
|
+
* server-side LRU keeps pollers for ~5 min; 1-min gives a tighter
|
|
17
|
+
* "currently polling" signal and aligns with the daemon heartbeat
|
|
18
|
+
* cadence. AC6c.
|
|
19
|
+
*/
|
|
20
|
+
exports.HOST_FRESHNESS_THRESHOLD_MS = 60_000;
|
|
21
|
+
/** Read-through cache TTL for the join result. AC6h. */
|
|
22
|
+
exports.CACHE_TTL_MS = 3_000;
|
|
23
|
+
/**
|
|
24
|
+
* #281 — concurrency cap for the per-host RPC fan-out.
|
|
25
|
+
*
|
|
26
|
+
* `Promise.all(hostnames.map(describe...))` is unbounded; at >20 hosts that
|
|
27
|
+
* thunder-herds the Temporal frontend's per-namespace RPS budget. 8 keeps
|
|
28
|
+
* the common case (≤8 hosts) at full parallelism while preventing the worst
|
|
29
|
+
* case from spiking the backend.
|
|
30
|
+
*/
|
|
31
|
+
exports.HOST_FANOUT_CONCURRENCY = 8;
|
|
32
|
+
let cache = null;
|
|
33
|
+
/**
|
|
34
|
+
* Test hook — never call from production code.
|
|
35
|
+
*
|
|
36
|
+
* Convention: `__<verb><Noun>ForTests`, co-located with the module that owns
|
|
37
|
+
* the state. See `docs/adr/0006-test-hooks-naming.md`.
|
|
38
|
+
*/
|
|
39
|
+
function __resetHostsCacheForTests() {
|
|
40
|
+
cache = null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a Temporal poller identity back into the daemon that emitted it.
|
|
44
|
+
*
|
|
45
|
+
* Returns `null` for opaque / third-party identities (e.g. Temporal's own
|
|
46
|
+
* system pollers or an unrelated worker sharing the namespace). Callers
|
|
47
|
+
* skip those silently.
|
|
48
|
+
*
|
|
49
|
+
* The v1.0 format (`agent-tempo:<hostname>:<pid>:<version>`) is
|
|
50
|
+
* guaranteed to have exactly 4 colon-delimited segments because every
|
|
51
|
+
* component has its own validation: hostname passes `PLAYER_NAME_REGEX`
|
|
52
|
+
* (no colons possible), pid is numeric, and version is a semver-ish
|
|
53
|
+
* string (no colons).
|
|
54
|
+
*
|
|
55
|
+
* Pre-v1.0 daemons emitted a `claude-tempo:` prefix. Under the v1.0 hard
|
|
56
|
+
* break those daemons can't reach v1.x task queues anyway, but if one does
|
|
57
|
+
* surface in a host listing we tag it `legacy: true` so operators can
|
|
58
|
+
* distinguish a still-running old daemon from a fresh v1.x worker.
|
|
59
|
+
*/
|
|
60
|
+
function parseIdentity(identity) {
|
|
61
|
+
if (identity.startsWith('agent-tempo:') || identity.startsWith('claude-tempo:')) {
|
|
62
|
+
const legacy = identity.startsWith('claude-tempo:');
|
|
63
|
+
const parts = identity.split(':');
|
|
64
|
+
if (parts.length === 4) {
|
|
65
|
+
const [, hostname, pidStr, version] = parts;
|
|
66
|
+
const pid = Number(pidStr);
|
|
67
|
+
if (hostname.length > 0 && Number.isFinite(pid) && pid > 0 && version.length > 0) {
|
|
68
|
+
return { hostname, pid, version, legacy };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
// Legacy SDK default format: `<pid>@<hostname>`
|
|
74
|
+
const legacyMatch = identity.match(/^(\d+)@(.+)$/);
|
|
75
|
+
if (legacyMatch) {
|
|
76
|
+
const pid = Number(legacyMatch[1]);
|
|
77
|
+
const hostname = legacyMatch[2];
|
|
78
|
+
if (Number.isFinite(pid) && pid > 0 && hostname.length > 0) {
|
|
79
|
+
return { hostname, pid, version: 'unknown', legacy: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
85
|
+
// Low-level RPC + timestamp helpers — exposed for dep-injection in tests
|
|
86
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
87
|
+
/** Convert a protobuf Timestamp to ISO 8601. Returns `''` for unset timestamps. */
|
|
88
|
+
function timestampToIso(ts) {
|
|
89
|
+
// Protobuf Timestamp is `{seconds: Long | number | string, nanos: number}`.
|
|
90
|
+
// Protobuf.js may deliver `seconds` as a Long object, a raw number, or a
|
|
91
|
+
// string depending on the runtime — normalise defensively.
|
|
92
|
+
if (!ts || typeof ts !== 'object')
|
|
93
|
+
return '';
|
|
94
|
+
const t = ts;
|
|
95
|
+
const secondsRaw = t.seconds;
|
|
96
|
+
if (secondsRaw === undefined || secondsRaw === null)
|
|
97
|
+
return '';
|
|
98
|
+
const seconds = typeof secondsRaw === 'number' ? secondsRaw : Number(String(secondsRaw));
|
|
99
|
+
if (!Number.isFinite(seconds))
|
|
100
|
+
return '';
|
|
101
|
+
const ms = seconds * 1000 + Math.floor((t.nanos ?? 0) / 1_000_000);
|
|
102
|
+
return new Date(ms).toISOString();
|
|
103
|
+
}
|
|
104
|
+
async function describeQueuePollers(client, namespace, taskQueueName, taskQueueType) {
|
|
105
|
+
const res = await client.workflowService.describeTaskQueue({
|
|
106
|
+
namespace,
|
|
107
|
+
taskQueue: { name: taskQueueName },
|
|
108
|
+
taskQueueType,
|
|
109
|
+
});
|
|
110
|
+
const pollers = res.pollers ?? [];
|
|
111
|
+
const out = [];
|
|
112
|
+
for (const p of pollers) {
|
|
113
|
+
const identity = p.identity ?? '';
|
|
114
|
+
if (!identity)
|
|
115
|
+
continue;
|
|
116
|
+
const iso = timestampToIso(p.lastAccessTime);
|
|
117
|
+
const ms = iso ? Date.parse(iso) : 0;
|
|
118
|
+
out.push({ identity, lastAccessTimeMs: ms, lastAccessTimeIso: iso });
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* #280 — single combined query against the global maestro.
|
|
124
|
+
*
|
|
125
|
+
* Replaces the prior two-RPC dance (`handle.describe()` for liveness
|
|
126
|
+
* then `handle.query('hostProfiles')` for data) with one round trip.
|
|
127
|
+
* Reaching the handler proves the workflow is running, so the caller
|
|
128
|
+
* gets `{ exists: true, profiles }`. Any transport-level failure
|
|
129
|
+
* (workflow not found, terminated, namespace unreachable, query
|
|
130
|
+
* handler not registered on an older maestro) is caught and surfaced
|
|
131
|
+
* as `null`, which the join site treats as `profileStaleness: 'missing'`.
|
|
132
|
+
*
|
|
133
|
+
* Note: we deliberately fall back to `null` on ANY error rather than
|
|
134
|
+
* trying the legacy `hostProfiles` query as a second attempt — the
|
|
135
|
+
* combined query has been on every maestro since this commit, so a
|
|
136
|
+
* mismatched daemon/maestro pair is a deployment bug worth surfacing
|
|
137
|
+
* as missing-profile (which still lets the listing succeed) rather
|
|
138
|
+
* than masking with extra RPCs.
|
|
139
|
+
*/
|
|
140
|
+
async function fetchHostProfiles(client) {
|
|
141
|
+
try {
|
|
142
|
+
const handle = client.workflow.getHandle(config_1.GLOBAL_MAESTRO_WORKFLOW_ID);
|
|
143
|
+
const result = (await handle.query('hostProfilesWithExistence'));
|
|
144
|
+
if (!result || !result.exists)
|
|
145
|
+
return null;
|
|
146
|
+
return result.profiles;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function listHosts(client, opts = {}) {
|
|
153
|
+
const deps = opts.deps ?? {};
|
|
154
|
+
const now = deps.now ? deps.now() : Date.now();
|
|
155
|
+
if (!opts.force && cache && now - cache.timestamp < exports.CACHE_TTL_MS) {
|
|
156
|
+
return cache.hosts;
|
|
157
|
+
}
|
|
158
|
+
const namespace = opts.namespace ?? 'default';
|
|
159
|
+
const taskQueue = opts.taskQueue ?? 'agent-tempo';
|
|
160
|
+
const describe = deps.describePollers ?? describeQueuePollers;
|
|
161
|
+
const fetchProfiles = deps.fetchProfiles ?? fetchHostProfiles;
|
|
162
|
+
const { TASK_QUEUE_TYPE_WORKFLOW, TASK_QUEUE_TYPE_ACTIVITY } = proto_1.temporal.api.enums.v1.TaskQueueType;
|
|
163
|
+
// RPCs 1 + 2 — shared queue, both worker types. Parallel.
|
|
164
|
+
const [sharedWorkflow, sharedActivity] = await Promise.all([
|
|
165
|
+
describe(client, namespace, taskQueue, TASK_QUEUE_TYPE_WORKFLOW),
|
|
166
|
+
describe(client, namespace, taskQueue, TASK_QUEUE_TYPE_ACTIVITY),
|
|
167
|
+
]);
|
|
168
|
+
// ── Build InstanceInfo per (hostname, pid) as we consume each stream ──
|
|
169
|
+
// Each daemon sets the same identity on both the shared-workflow and
|
|
170
|
+
// shared-activity pollers AND on the per-host-activity worker, so the
|
|
171
|
+
// (hostname, pid) key merges naturally across streams. Opaque identities
|
|
172
|
+
// drop out of `parseIdentity` and are skipped silently (AC6d).
|
|
173
|
+
const hostInstances = new Map();
|
|
174
|
+
const markInstance = (hostname, pid, flag, ident, poller) => {
|
|
175
|
+
let byPid = hostInstances.get(hostname);
|
|
176
|
+
if (!byPid) {
|
|
177
|
+
byPid = new Map();
|
|
178
|
+
hostInstances.set(hostname, byPid);
|
|
179
|
+
}
|
|
180
|
+
let inst = byPid.get(pid);
|
|
181
|
+
if (!inst) {
|
|
182
|
+
inst = {
|
|
183
|
+
pid,
|
|
184
|
+
version: ident.version,
|
|
185
|
+
identity: poller.identity,
|
|
186
|
+
lastAccessTime: poller.lastAccessTimeIso,
|
|
187
|
+
hasWorkflowWorker: false,
|
|
188
|
+
hasActivityWorker: false,
|
|
189
|
+
hasHostQueueWorker: false,
|
|
190
|
+
...(ident.legacy ? { legacy: true } : {}),
|
|
191
|
+
};
|
|
192
|
+
byPid.set(pid, inst);
|
|
193
|
+
}
|
|
194
|
+
inst[flag] = true;
|
|
195
|
+
};
|
|
196
|
+
const consume = (pollers, flag) => {
|
|
197
|
+
for (const p of pollers) {
|
|
198
|
+
const ident = parseIdentity(p.identity);
|
|
199
|
+
if (!ident)
|
|
200
|
+
continue;
|
|
201
|
+
markInstance(ident.hostname, ident.pid, flag, ident, p);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
consume(sharedWorkflow, 'hasWorkflowWorker');
|
|
205
|
+
consume(sharedActivity, 'hasActivityWorker');
|
|
206
|
+
// Hostnames discovered from the shared queues drive the per-host RPC fan-out.
|
|
207
|
+
const hostnames = Array.from(hostInstances.keys()).sort();
|
|
208
|
+
// RPC 3 — per-host activity queue. Parallel with the profile fetch,
|
|
209
|
+
// but capped at HOST_FANOUT_CONCURRENCY so a deployment with many hosts
|
|
210
|
+
// doesn't thunder-herd the Temporal frontend (#281).
|
|
211
|
+
const limit = (0, p_limit_1.default)(exports.HOST_FANOUT_CONCURRENCY);
|
|
212
|
+
const [perHostActivityRows, profiles] = await Promise.all([
|
|
213
|
+
Promise.all(hostnames.map((hostname) => limit(async () => {
|
|
214
|
+
try {
|
|
215
|
+
const rows = await describe(client, namespace, (0, config_1.hostTaskQueue)(taskQueue, hostname), TASK_QUEUE_TYPE_ACTIVITY);
|
|
216
|
+
return [hostname, rows];
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Per-host-queue not registered yet → empty. Don't fail the whole call.
|
|
220
|
+
return [hostname, []];
|
|
221
|
+
}
|
|
222
|
+
}))),
|
|
223
|
+
fetchProfiles(client).catch(() => null),
|
|
224
|
+
]);
|
|
225
|
+
for (const [, rows] of perHostActivityRows) {
|
|
226
|
+
consume(rows, 'hasHostQueueWorker');
|
|
227
|
+
}
|
|
228
|
+
// ── Build HostInfo per hostname ──
|
|
229
|
+
const hosts = [];
|
|
230
|
+
for (const hostname of hostnames) {
|
|
231
|
+
const byPid = hostInstances.get(hostname);
|
|
232
|
+
if (!byPid || byPid.size === 0)
|
|
233
|
+
continue;
|
|
234
|
+
const instances = Array.from(byPid.values()).sort((a, b) => a.pid - b.pid);
|
|
235
|
+
const isFresh = (iso) => {
|
|
236
|
+
const ts = iso ? Date.parse(iso) : 0;
|
|
237
|
+
return ts > 0 && now - ts <= exports.HOST_FRESHNESS_THRESHOLD_MS;
|
|
238
|
+
};
|
|
239
|
+
const freshness = instances.some((i) => isFresh(i.lastAccessTime)) ? 'live' : 'stale';
|
|
240
|
+
const recruitReady = instances.some((i) => i.hasActivityWorker && i.hasHostQueueWorker && isFresh(i.lastAccessTime));
|
|
241
|
+
const profile = profiles?.[hostname];
|
|
242
|
+
const profileStaleness = determineStaleness(profile, instances, profiles);
|
|
243
|
+
hosts.push({ hostname, instances, recruitReady, freshness, profile, profileStaleness });
|
|
244
|
+
}
|
|
245
|
+
cache = { timestamp: now, hosts };
|
|
246
|
+
return hosts;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* M13 — identity-vs-profile reconciliation.
|
|
250
|
+
*
|
|
251
|
+
* - `'missing'` — maestro is absent OR this host has no profile entry
|
|
252
|
+
* - `'fresh'` — profile present AND some live instance's version
|
|
253
|
+
* matches `profile.version`
|
|
254
|
+
* - `'stale'` — profile present but no live instance matches its
|
|
255
|
+
* version (rolling upgrade, GC pending, etc.)
|
|
256
|
+
*/
|
|
257
|
+
function determineStaleness(profile, instances, profiles) {
|
|
258
|
+
if (!profiles)
|
|
259
|
+
return 'missing'; // maestro unreachable or absent
|
|
260
|
+
if (!profile)
|
|
261
|
+
return 'missing';
|
|
262
|
+
if (!profile.version)
|
|
263
|
+
return 'fresh'; // legacy profile with no version — trust as-is
|
|
264
|
+
return instances.some((i) => i.version === profile.version) ? 'fresh' : 'stale';
|
|
265
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installParentDeathWatchdog(): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Self-exit when the parent process goes away.
|
|
3
|
+
//
|
|
4
|
+
// Stdio-MCP children (and the Copilot bridge subprocess) are spawned by
|
|
5
|
+
// hosts that often die without sending SIGTERM — Claude Code on Windows
|
|
6
|
+
// just closes the pipe, and an abruptly-killed daemon leaves bridge
|
|
7
|
+
// subprocesses orphaned. Without this watchdog, those children stay
|
|
8
|
+
// alive forever holding the Temporal core-bridge `.node` open, which
|
|
9
|
+
// (a) leaks processes across host restarts and (b) blocks
|
|
10
|
+
// `npm install -g` upgrades with EBUSY on Windows.
|
|
11
|
+
//
|
|
12
|
+
// Two complementary signals:
|
|
13
|
+
// 1. stdin 'end'/'close' — fires the instant the parent closes the
|
|
14
|
+
// stdio pipe. Primary signal; immediate.
|
|
15
|
+
// 2. Parent-PID poll — fallback for when the pipe handle is inherited
|
|
16
|
+
// by another process (or stdio was 'ignore'). 30s cadence is a
|
|
17
|
+
// belt-and-braces interval — orphan dies within half a minute,
|
|
18
|
+
// no measurable syscall cost. `process.kill(pid, 0)` is a
|
|
19
|
+
// permission check, not a signal.
|
|
20
|
+
//
|
|
21
|
+
// PID reuse caveat: Windows recycles PIDs quickly. If the parent dies
|
|
22
|
+
// and an unrelated process inherits the same PID before our next poll,
|
|
23
|
+
// we falsely conclude the parent is alive. The stdin EOF path catches
|
|
24
|
+
// that case immediately, so this is purely a fallback.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.installParentDeathWatchdog = installParentDeathWatchdog;
|
|
27
|
+
const log = (...args) => console.error('[agent-tempo:watchdog]', ...args);
|
|
28
|
+
function installParentDeathWatchdog() {
|
|
29
|
+
const exit = (reason) => {
|
|
30
|
+
log('parent gone (', reason, ') — exiting');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
};
|
|
33
|
+
process.stdin.on('end', () => exit('stdin end'));
|
|
34
|
+
process.stdin.on('close', () => exit('stdin close'));
|
|
35
|
+
const parentPid = process.ppid;
|
|
36
|
+
if (parentPid && parentPid > 1) {
|
|
37
|
+
const timer = setInterval(() => {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(parentPid, 0);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
exit(`ppid ${parentPid} dead`);
|
|
43
|
+
}
|
|
44
|
+
}, 30_000);
|
|
45
|
+
timer.unref();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded `WorkflowHandle.query()` wrapper — Issue #433.
|
|
3
|
+
*
|
|
4
|
+
* **Problem.** `@temporalio/client@1.15` doesn't expose an `AbortSignal` or a
|
|
5
|
+
* per-call `deadline` on `WorkflowHandle.query()`. When the workflow is alive
|
|
6
|
+
* (`Running`) but the worker that polls its task queue is dead (orphaned
|
|
7
|
+
* adapter, wedged Copilot bridge, sticky-cache lock-up), `query()` never
|
|
8
|
+
* resolves — it sits on its gRPC stream until the connection is torn down.
|
|
9
|
+
*
|
|
10
|
+
* Until v0.26 this only manifested as background reconcile log noise. After
|
|
11
|
+
* #399 W2 layered `getPlayerWireMeta` (3 session queries × N players) into
|
|
12
|
+
* the snapshot fan-out, a single hung session also wedged the snapshot
|
|
13
|
+
* endpoint and the AggregateRunner's 750ms poll loop — the trigger for this
|
|
14
|
+
* fix.
|
|
15
|
+
*
|
|
16
|
+
* **Strategy.** Race `handle.query()` against a `setTimeout`. When the timeout
|
|
17
|
+
* wins, throw `QueryTimeoutError` so the caller's existing soft-failure path
|
|
18
|
+
* (`Promise.allSettled` / `try-catch` / `.catch(() => …)`) takes over. The
|
|
19
|
+
* underlying RPC stays pending in memory until it eventually resolves or the
|
|
20
|
+
* gRPC connection is closed — see "Leak characteristics" below for why
|
|
21
|
+
* that's bounded.
|
|
22
|
+
*
|
|
23
|
+
* **Leak characteristics.** The pending `handle.query()` Promise can't be
|
|
24
|
+
* cancelled in this SDK version. Instead we **dedupe in-flight queries** by
|
|
25
|
+
* `(workflowId, queryName)` so the AggregateRunner firing every 750ms against
|
|
26
|
+
* the same hung session doesn't accumulate one new dangling promise per tick
|
|
27
|
+
* — it gets the *same* shared promise back. Total memory cost is bounded by
|
|
28
|
+
* `unique-hung-workflows × distinct-queries-per-workflow` (typically <50
|
|
29
|
+
* entries even in degenerate fleets), and every entry is reclaimed when the
|
|
30
|
+
* RPC settles (workflow closes, namespace evicted, daemon restart). When
|
|
31
|
+
* `@temporalio/client` adds AbortSignal support, swap the race for a real
|
|
32
|
+
* cancellation and drop the dedup map.
|
|
33
|
+
*
|
|
34
|
+
* **Configurable.** Default timeout is `DEFAULT_QUERY_TIMEOUT_MS` (2000ms) —
|
|
35
|
+
* Temporal queries against a live worker round-trip in <100ms, so a 2s ceiling
|
|
36
|
+
* is two orders of magnitude larger than a healthy query. Pass a smaller value
|
|
37
|
+
* for tests; pass a larger value if you have a slow query handler that does
|
|
38
|
+
* meaningful work synchronously.
|
|
39
|
+
*/
|
|
40
|
+
import type { WorkflowHandle } from '@temporalio/client';
|
|
41
|
+
import type { QueryDefinition } from '@temporalio/common';
|
|
42
|
+
/**
|
|
43
|
+
* Default per-query timeout. 2 seconds — Temporal queries against a healthy
|
|
44
|
+
* worker round-trip in <100ms, so this is two orders of magnitude of
|
|
45
|
+
* headroom while still being short enough that a hung snapshot fan-out
|
|
46
|
+
* (~12 players × 3 queries) still completes well under 10s.
|
|
47
|
+
*/
|
|
48
|
+
export declare const DEFAULT_QUERY_TIMEOUT_MS = 2000;
|
|
49
|
+
/**
|
|
50
|
+
* Thrown by {@link queryHandleWithTimeout} when the per-query timeout fires
|
|
51
|
+
* before the underlying RPC settles. Subclasses `Error`; `name` is set so
|
|
52
|
+
* `err.name === 'QueryTimeoutError'` and `err instanceof QueryTimeoutError`
|
|
53
|
+
* both work for callers that switch on either.
|
|
54
|
+
*/
|
|
55
|
+
export declare class QueryTimeoutError extends Error {
|
|
56
|
+
readonly workflowId: string;
|
|
57
|
+
readonly queryName: string;
|
|
58
|
+
readonly timeoutMs: number;
|
|
59
|
+
constructor(workflowId: string, queryName: string, timeoutMs: number);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Test-only — clear the in-flight dedup table so each test case starts with
|
|
63
|
+
* an empty cache. Follows the `__<verb><Noun>ForTests` convention from
|
|
64
|
+
* ADR 0006: never call from production code.
|
|
65
|
+
*/
|
|
66
|
+
export declare function __resetInflightQueriesForTests(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Run `handle.query(queryDef, …(opts.args))` with a
|
|
69
|
+
* {@link DEFAULT_QUERY_TIMEOUT_MS} (or `opts.timeoutMs`) ceiling. Throws
|
|
70
|
+
* {@link QueryTimeoutError} if the timeout fires first. Multiple callers
|
|
71
|
+
* issuing the same `(workflowId, queryName)` while the prior call is
|
|
72
|
+
* still in flight share one underlying RPC — see the file header for
|
|
73
|
+
* leak characteristics.
|
|
74
|
+
*
|
|
75
|
+
* `queryDef` may be a typed `QueryDefinition` (preferred — caller gets
|
|
76
|
+
* type inference on `Ret`) or a bare string name (legacy call sites that
|
|
77
|
+
* predate the signal-defs file: `getMetadata`, `getPart`, `maestroPlayers`,
|
|
78
|
+
* etc.).
|
|
79
|
+
*
|
|
80
|
+
* `opts.args` forwards extra positional args to the SDK `query()` call.
|
|
81
|
+
* Used by `getEnsembleChat`'s `{ offset, limit }` payload — every other
|
|
82
|
+
* call site in this codebase issues a zero-arg query.
|
|
83
|
+
*
|
|
84
|
+
* **Dedup caveat with args**: the dedup key is `(workflowId, queryName)`
|
|
85
|
+
* only — args are NOT in the key. If two concurrent callers issue the
|
|
86
|
+
* same query name with *different* args, the second caller will receive
|
|
87
|
+
* the first caller's result. Acceptable today because `getEnsembleChat`
|
|
88
|
+
* is called with `(0, SNAPSHOT_CHAT_LIMIT)` from the snapshot fan-out
|
|
89
|
+
* and a wider `(0, POLL_CHAT_LIMIT)` from the aggregate poll — both
|
|
90
|
+
* are zero-offset reads, the wider window is a superset of the
|
|
91
|
+
* narrower, and the bus's §8 chat cap collapses excess. Revisit if a
|
|
92
|
+
* future args-passing caller issues a non-superset shape.
|
|
93
|
+
*
|
|
94
|
+
* **No retry**: caller decides whether to retry or fall back. Most call
|
|
95
|
+
* sites fall back (snapshot returns `null` wireMeta;
|
|
96
|
+
* `queryOrphanedSessions` skips the candidate; `getEnsembleMeta`
|
|
97
|
+
* substitutes sentinels) so a retry loop here would just delay the
|
|
98
|
+
* inevitable fallback.
|
|
99
|
+
*/
|
|
100
|
+
export declare function queryHandleWithTimeout<Ret, Args extends unknown[] = []>(handle: WorkflowHandle, queryDef: QueryDefinition<Ret, Args> | string, opts?: {
|
|
101
|
+
timeoutMs?: number;
|
|
102
|
+
args?: Args;
|
|
103
|
+
}): Promise<Ret>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryTimeoutError = exports.DEFAULT_QUERY_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.__resetInflightQueriesForTests = __resetInflightQueriesForTests;
|
|
5
|
+
exports.queryHandleWithTimeout = queryHandleWithTimeout;
|
|
6
|
+
/**
|
|
7
|
+
* Default per-query timeout. 2 seconds — Temporal queries against a healthy
|
|
8
|
+
* worker round-trip in <100ms, so this is two orders of magnitude of
|
|
9
|
+
* headroom while still being short enough that a hung snapshot fan-out
|
|
10
|
+
* (~12 players × 3 queries) still completes well under 10s.
|
|
11
|
+
*/
|
|
12
|
+
exports.DEFAULT_QUERY_TIMEOUT_MS = 2000;
|
|
13
|
+
/**
|
|
14
|
+
* Thrown by {@link queryHandleWithTimeout} when the per-query timeout fires
|
|
15
|
+
* before the underlying RPC settles. Subclasses `Error`; `name` is set so
|
|
16
|
+
* `err.name === 'QueryTimeoutError'` and `err instanceof QueryTimeoutError`
|
|
17
|
+
* both work for callers that switch on either.
|
|
18
|
+
*/
|
|
19
|
+
class QueryTimeoutError extends Error {
|
|
20
|
+
workflowId;
|
|
21
|
+
queryName;
|
|
22
|
+
timeoutMs;
|
|
23
|
+
constructor(workflowId, queryName, timeoutMs) {
|
|
24
|
+
super(`Temporal query timed out after ${timeoutMs}ms: ` +
|
|
25
|
+
`workflow="${workflowId}" query="${queryName}" ` +
|
|
26
|
+
`(worker may be down or wedged — see #433)`);
|
|
27
|
+
this.workflowId = workflowId;
|
|
28
|
+
this.queryName = queryName;
|
|
29
|
+
this.timeoutMs = timeoutMs;
|
|
30
|
+
this.name = 'QueryTimeoutError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.QueryTimeoutError = QueryTimeoutError;
|
|
34
|
+
/** Module-level dedup table keyed by `(workflowId, queryName)`. See JSDoc. */
|
|
35
|
+
const inflightQueries = new Map();
|
|
36
|
+
function inflightKey(workflowId, queryName) {
|
|
37
|
+
// NUL separator — workflow ids follow the project's `claude-{kind}-…`
|
|
38
|
+
// convention and query names are JS identifiers, so neither can contain
|
|
39
|
+
// a literal NUL byte. Guarantees no collision between e.g.
|
|
40
|
+
// workflow `a` + query `b\0foo` vs. workflow `a\0b` + query `foo`.
|
|
41
|
+
return `${workflowId}\x00${queryName}`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Test-only — clear the in-flight dedup table so each test case starts with
|
|
45
|
+
* an empty cache. Follows the `__<verb><Noun>ForTests` convention from
|
|
46
|
+
* ADR 0006: never call from production code.
|
|
47
|
+
*/
|
|
48
|
+
function __resetInflightQueriesForTests() {
|
|
49
|
+
inflightQueries.clear();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Run `handle.query(queryDef, …(opts.args))` with a
|
|
53
|
+
* {@link DEFAULT_QUERY_TIMEOUT_MS} (or `opts.timeoutMs`) ceiling. Throws
|
|
54
|
+
* {@link QueryTimeoutError} if the timeout fires first. Multiple callers
|
|
55
|
+
* issuing the same `(workflowId, queryName)` while the prior call is
|
|
56
|
+
* still in flight share one underlying RPC — see the file header for
|
|
57
|
+
* leak characteristics.
|
|
58
|
+
*
|
|
59
|
+
* `queryDef` may be a typed `QueryDefinition` (preferred — caller gets
|
|
60
|
+
* type inference on `Ret`) or a bare string name (legacy call sites that
|
|
61
|
+
* predate the signal-defs file: `getMetadata`, `getPart`, `maestroPlayers`,
|
|
62
|
+
* etc.).
|
|
63
|
+
*
|
|
64
|
+
* `opts.args` forwards extra positional args to the SDK `query()` call.
|
|
65
|
+
* Used by `getEnsembleChat`'s `{ offset, limit }` payload — every other
|
|
66
|
+
* call site in this codebase issues a zero-arg query.
|
|
67
|
+
*
|
|
68
|
+
* **Dedup caveat with args**: the dedup key is `(workflowId, queryName)`
|
|
69
|
+
* only — args are NOT in the key. If two concurrent callers issue the
|
|
70
|
+
* same query name with *different* args, the second caller will receive
|
|
71
|
+
* the first caller's result. Acceptable today because `getEnsembleChat`
|
|
72
|
+
* is called with `(0, SNAPSHOT_CHAT_LIMIT)` from the snapshot fan-out
|
|
73
|
+
* and a wider `(0, POLL_CHAT_LIMIT)` from the aggregate poll — both
|
|
74
|
+
* are zero-offset reads, the wider window is a superset of the
|
|
75
|
+
* narrower, and the bus's §8 chat cap collapses excess. Revisit if a
|
|
76
|
+
* future args-passing caller issues a non-superset shape.
|
|
77
|
+
*
|
|
78
|
+
* **No retry**: caller decides whether to retry or fall back. Most call
|
|
79
|
+
* sites fall back (snapshot returns `null` wireMeta;
|
|
80
|
+
* `queryOrphanedSessions` skips the candidate; `getEnsembleMeta`
|
|
81
|
+
* substitutes sentinels) so a retry loop here would just delay the
|
|
82
|
+
* inevitable fallback.
|
|
83
|
+
*/
|
|
84
|
+
async function queryHandleWithTimeout(handle, queryDef, opts = {}) {
|
|
85
|
+
const timeoutMs = opts.timeoutMs ?? exports.DEFAULT_QUERY_TIMEOUT_MS;
|
|
86
|
+
const queryName = typeof queryDef === 'string' ? queryDef : queryDef.name;
|
|
87
|
+
const key = inflightKey(handle.workflowId, queryName);
|
|
88
|
+
let underlying = inflightQueries.get(key);
|
|
89
|
+
if (!underlying) {
|
|
90
|
+
const args = (opts.args ?? []);
|
|
91
|
+
underlying = handle.query(queryDef, ...args).finally(() => {
|
|
92
|
+
// Free the slot once the RPC settles — success or failure both clear.
|
|
93
|
+
// Multiple racing callers all see the same settled value.
|
|
94
|
+
inflightQueries.delete(key);
|
|
95
|
+
});
|
|
96
|
+
inflightQueries.set(key, underlying);
|
|
97
|
+
}
|
|
98
|
+
let timer;
|
|
99
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
100
|
+
timer = setTimeout(() => {
|
|
101
|
+
reject(new QueryTimeoutError(handle.workflowId, queryName, timeoutMs));
|
|
102
|
+
}, timeoutMs);
|
|
103
|
+
// Don't let the timeout keep the process alive.
|
|
104
|
+
timer.unref?.();
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
return await Promise.race([underlying, timeoutPromise]);
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
if (timer)
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
}
|
|
113
|
+
}
|