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,684 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AggregateRunner = exports.TickSkipped = exports.MAX_CONSECUTIVE_FAILURES = exports.AGGREGATE_LIST_DEADLINE_MS = exports.DEFAULT_POLL_INTERVAL_MS = void 0;
|
|
4
|
+
exports.canonicalize = canonicalize;
|
|
5
|
+
exports.hashOf = hashOf;
|
|
6
|
+
exports.diffEnsembleSnapshot = diffEnsembleSnapshot;
|
|
7
|
+
exports.diffHostProfiles = diffHostProfiles;
|
|
8
|
+
exports.diffEnsembleSet = diffEnsembleSet;
|
|
9
|
+
/**
|
|
10
|
+
* Aggregate poll loop (SSE-PROTOCOL.md §8, §11; ADR 0004).
|
|
11
|
+
*
|
|
12
|
+
* **Job**: every `pollIntervalMs` (default 750), snapshot the cluster
|
|
13
|
+
* state via `TempoClient`, diff against the prior snapshot, and emit
|
|
14
|
+
* events to the per-ensemble + global buses. Subscriber count is
|
|
15
|
+
* irrelevant — every TUI / web-dashboard reads from the same one
|
|
16
|
+
* snapshot per tick.
|
|
17
|
+
*
|
|
18
|
+
* **Backpressure** (§8): serial-with-skip. If a tick's queries don't
|
|
19
|
+
* complete before the next 750 ms boundary, the next tick is skipped
|
|
20
|
+
* and a `[agent-tempo:aggregate]` warn-log fires. The daemon never
|
|
21
|
+
* has more than one in-flight aggregate fetch.
|
|
22
|
+
*
|
|
23
|
+
* **Coalescing** (§6, §8):
|
|
24
|
+
*
|
|
25
|
+
* - `player.added/removed` — diff player set
|
|
26
|
+
* - `player.phase_changed` — emitted whenever the phase string changes
|
|
27
|
+
* between polls. The 250 ms / playerId latest-wins debounce in §6
|
|
28
|
+
* collapses naturally to "one emit per poll" because the poll
|
|
29
|
+
* cadence (750 ms) is wider than the debounce window — no extra
|
|
30
|
+
* timer needed.
|
|
31
|
+
* - `chat.appended` — per new message id since last poll. Bus-level
|
|
32
|
+
* §8 chat cap collapses excess to `chat.compressed`.
|
|
33
|
+
* - `flags.changed` — only when `(paused, held)` tuple changes.
|
|
34
|
+
* - `schedules.changed` — SHA-256 diff of sorted-by-name JSON.
|
|
35
|
+
* - `host_profile.changed` — per-host SHA-256 diff (scrubbed payload
|
|
36
|
+
* from `listHosts` already; aggregate just hashes the projection).
|
|
37
|
+
* - `ensemble.created/destroyed` — diff ensemble-name set.
|
|
38
|
+
*/
|
|
39
|
+
const crypto_1 = require("crypto");
|
|
40
|
+
const event_bus_1 = require("./event-bus");
|
|
41
|
+
const event_id_1 = require("./event-id");
|
|
42
|
+
const snapshot_1 = require("./snapshot");
|
|
43
|
+
/** Default cadence per spec §8. */
|
|
44
|
+
exports.DEFAULT_POLL_INTERVAL_MS = 750;
|
|
45
|
+
/** How many chat-message ids to remember per ensemble. */
|
|
46
|
+
const CHAT_ID_MEMORY = 1024;
|
|
47
|
+
/** How many chat messages to fetch per poll — wider than the snapshot limit so bursts aren't silently lost. */
|
|
48
|
+
const POLL_CHAT_LIMIT = 200;
|
|
49
|
+
/**
|
|
50
|
+
* #336/#529 site 6 — correctness deadline for `listEnsemblesBounded()`
|
|
51
|
+
* inside `collect()`. 500ms < 750ms cadence leaves ≥250ms headroom for
|
|
52
|
+
* `applyDiff()` + per-ensemble fan-out + emission. Architect's call:
|
|
53
|
+
* the 15s `tickWatchdogMs` is the *liveness* guard; this 500ms is the
|
|
54
|
+
* *correctness* guard preventing partial-input ensemble diffs.
|
|
55
|
+
*/
|
|
56
|
+
exports.AGGREGATE_LIST_DEADLINE_MS = 500;
|
|
57
|
+
/**
|
|
58
|
+
* #550 — consecutive-failure cap for the per-ensemble fan-out
|
|
59
|
+
* carry-forward. After this many `'failed'` outcomes in a row, the
|
|
60
|
+
* ensemble is promoted from `'failed'` (carry-forward in liveNames) to
|
|
61
|
+
* `'gone'` (excluded → emits `ensemble.destroyed`). Matches the
|
|
62
|
+
* 15s tick watchdog ceiling at 750ms cadence: 20 ticks × 750ms ≈ 15s.
|
|
63
|
+
* Operator-coherent: "can't observe → eventually treat as gone."
|
|
64
|
+
*
|
|
65
|
+
* Counted as the threshold to EXCEED (cf > K) so the spec table holds:
|
|
66
|
+
* 20 consecutive `'failed'` ticks stay 'failed'; the 21st promotes.
|
|
67
|
+
*/
|
|
68
|
+
exports.MAX_CONSECUTIVE_FAILURES = 20;
|
|
69
|
+
const log = (...args) => console.error('[agent-tempo:aggregate]', ...args);
|
|
70
|
+
/**
|
|
71
|
+
* #336/#529 site 6 — sentinel thrown by `collect()` when the
|
|
72
|
+
* visibility-iterator deadline trips. `tick()`'s catch logs it calmly
|
|
73
|
+
* and SKIPS `applyDiff()`, preserving `knownEnsembles` so the next
|
|
74
|
+
* successful tick diffs against the truthful prior set rather than
|
|
75
|
+
* emitting phantom `ensemble.destroyed` events.
|
|
76
|
+
*
|
|
77
|
+
* Modeled as a distinct error class so the catch can branch
|
|
78
|
+
* informatively (`if (err instanceof TickSkipped)`) without parsing
|
|
79
|
+
* error messages — and so callers downstream don't accidentally
|
|
80
|
+
* propagate it as a fatal error.
|
|
81
|
+
*/
|
|
82
|
+
class TickSkipped extends Error {
|
|
83
|
+
name = 'TickSkipped';
|
|
84
|
+
constructor(reason) { super(reason); }
|
|
85
|
+
}
|
|
86
|
+
exports.TickSkipped = TickSkipped;
|
|
87
|
+
/**
|
|
88
|
+
* Stable JSON canonicalization — keys sorted, no extraneous whitespace.
|
|
89
|
+
* Used for SHA-256 diff suppression so reordered key emits don't
|
|
90
|
+
* produce false-positive `*.changed` events.
|
|
91
|
+
*/
|
|
92
|
+
function canonicalize(value) {
|
|
93
|
+
if (value === null || typeof value !== 'object')
|
|
94
|
+
return JSON.stringify(value);
|
|
95
|
+
if (Array.isArray(value)) {
|
|
96
|
+
return '[' + value.map(canonicalize).join(',') + ']';
|
|
97
|
+
}
|
|
98
|
+
const keys = Object.keys(value).sort();
|
|
99
|
+
return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalize(value[k])).join(',') + '}';
|
|
100
|
+
}
|
|
101
|
+
/** SHA-256 of `canonicalize(value)`. */
|
|
102
|
+
function hashOf(value) {
|
|
103
|
+
return (0, crypto_1.createHash)('sha256').update(canonicalize(value)).digest('hex');
|
|
104
|
+
}
|
|
105
|
+
function diffEnsembleSnapshot(prev, next, track, capturedAt) {
|
|
106
|
+
const events = [];
|
|
107
|
+
const ensemble = next.ensemble;
|
|
108
|
+
// Build maps for O(1) lookup.
|
|
109
|
+
const prevPlayers = new Map();
|
|
110
|
+
if (prev)
|
|
111
|
+
for (const p of prev.players)
|
|
112
|
+
prevPlayers.set(p.playerId, p);
|
|
113
|
+
const nextPlayers = new Map();
|
|
114
|
+
for (const p of next.players)
|
|
115
|
+
nextPlayers.set(p.playerId, p);
|
|
116
|
+
// player.added / player.phase_changed — iterate next.
|
|
117
|
+
for (const p of next.players) {
|
|
118
|
+
if (!prevPlayers.has(p.playerId)) {
|
|
119
|
+
events.push({ type: 'player.added', payload: p });
|
|
120
|
+
track.playerPhases.set(p.playerId, p.phase);
|
|
121
|
+
// #535 — record the adapter family so the prior reconstruction at
|
|
122
|
+
// the next tick (aggregate.ts ~L600) carries the real agentType
|
|
123
|
+
// instead of a hardcoded `'claude'` stand-in. Treated as immutable
|
|
124
|
+
// for the player's lifetime; cleared on `player.removed` below.
|
|
125
|
+
track.playerAgentTypes.set(p.playerId, p.agentType);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const lastPhase = track.playerPhases.get(p.playerId);
|
|
129
|
+
if (p.phase !== lastPhase) {
|
|
130
|
+
events.push({
|
|
131
|
+
type: 'player.phase_changed',
|
|
132
|
+
payload: {
|
|
133
|
+
playerId: p.playerId,
|
|
134
|
+
ensemble,
|
|
135
|
+
phase: p.phase,
|
|
136
|
+
...(p.lastHeartbeatAt ? { lastHeartbeatAt: p.lastHeartbeatAt } : {}),
|
|
137
|
+
...(p.processingSince ? { processingSince: p.processingSince } : {}),
|
|
138
|
+
at: capturedAt,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
track.playerPhases.set(p.playerId, p.phase);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// player.removed — iterate prev.
|
|
145
|
+
if (prev) {
|
|
146
|
+
for (const p of prev.players) {
|
|
147
|
+
if (!nextPlayers.has(p.playerId)) {
|
|
148
|
+
events.push({
|
|
149
|
+
type: 'player.removed',
|
|
150
|
+
payload: {
|
|
151
|
+
playerId: p.playerId,
|
|
152
|
+
ensemble,
|
|
153
|
+
removedAt: capturedAt,
|
|
154
|
+
// We can't tell from a snapshot diff whether the player was
|
|
155
|
+
// destroyed or moved to `gone` — pick the safer default.
|
|
156
|
+
// PR-2 callers shouldn't depend on this distinction; PR-3+
|
|
157
|
+
// can refine when there's a stronger signal source.
|
|
158
|
+
reason: 'gone',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
track.playerPhases.delete(p.playerId);
|
|
162
|
+
track.playerAgentTypes.delete(p.playerId);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// flags.changed — only on tuple change.
|
|
167
|
+
const prevFlags = track.flags;
|
|
168
|
+
if (!prevFlags ||
|
|
169
|
+
prevFlags.paused !== next.flags.paused ||
|
|
170
|
+
prevFlags.held !== next.flags.held) {
|
|
171
|
+
events.push({
|
|
172
|
+
type: 'flags.changed',
|
|
173
|
+
payload: {
|
|
174
|
+
ensemble,
|
|
175
|
+
paused: next.flags.paused,
|
|
176
|
+
held: next.flags.held,
|
|
177
|
+
at: capturedAt,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
track.flags = { ...next.flags };
|
|
181
|
+
}
|
|
182
|
+
// schedules.changed — SHA-256 of sorted-by-name JSON.
|
|
183
|
+
const sortedSchedules = [...next.schedules].sort((a, b) => a.name.localeCompare(b.name));
|
|
184
|
+
const newScheduleHash = hashOf(sortedSchedules);
|
|
185
|
+
if (track.schedulesHash !== newScheduleHash) {
|
|
186
|
+
events.push({
|
|
187
|
+
type: 'schedules.changed',
|
|
188
|
+
payload: { ensemble, schedules: sortedSchedules, at: capturedAt },
|
|
189
|
+
});
|
|
190
|
+
track.schedulesHash = newScheduleHash;
|
|
191
|
+
}
|
|
192
|
+
// chat.appended — every message whose id we haven't seen yet.
|
|
193
|
+
for (const msg of next.chat) {
|
|
194
|
+
if (track.chatIds.has(msg.id))
|
|
195
|
+
continue;
|
|
196
|
+
events.push({ type: 'chat.appended', payload: msg });
|
|
197
|
+
track.chatIds.add(msg.id);
|
|
198
|
+
track.chatIdOrder.push(msg.id);
|
|
199
|
+
while (track.chatIdOrder.length > CHAT_ID_MEMORY) {
|
|
200
|
+
const evicted = track.chatIdOrder.shift();
|
|
201
|
+
if (evicted !== undefined)
|
|
202
|
+
track.chatIds.delete(evicted);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return events;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Diff host profiles map → per-host events. Pure function.
|
|
209
|
+
*/
|
|
210
|
+
function diffHostProfiles(prevHashes, nextProfiles) {
|
|
211
|
+
const nextHashes = new Map();
|
|
212
|
+
const events = [];
|
|
213
|
+
for (const [hostname, profile] of Object.entries(nextProfiles)) {
|
|
214
|
+
const h = hashOf(profile);
|
|
215
|
+
nextHashes.set(hostname, h);
|
|
216
|
+
if (prevHashes.get(hostname) !== h) {
|
|
217
|
+
events.push({ type: 'host_profile.changed', payload: profile });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return { events, hashes: nextHashes };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Diff ensemble-name set → ensemble.created / ensemble.destroyed events.
|
|
224
|
+
*
|
|
225
|
+
* #550 — accepts the slim `{ensemble, hasConductor}` shape rather than
|
|
226
|
+
* a full `AggregateEnsembleSnapshot[]`. The full-snapshot type still
|
|
227
|
+
* satisfies it structurally, so existing tests passing
|
|
228
|
+
* `AggregateEnsembleSnapshot[]` continue to compile. The slimmer
|
|
229
|
+
* input lets `applyDiff` feed the `livePrelude` carry-forward set
|
|
230
|
+
* (which contains stub entries for `'failed'` outcomes that have no
|
|
231
|
+
* full snapshot this tick).
|
|
232
|
+
*/
|
|
233
|
+
function diffEnsembleSet(prev, nextEnsembles, capturedAt) {
|
|
234
|
+
const events = [];
|
|
235
|
+
const names = new Set();
|
|
236
|
+
const seen = new Map();
|
|
237
|
+
for (const e of nextEnsembles) {
|
|
238
|
+
names.add(e.ensemble);
|
|
239
|
+
seen.set(e.ensemble, e);
|
|
240
|
+
}
|
|
241
|
+
for (const name of names) {
|
|
242
|
+
if (!prev.has(name)) {
|
|
243
|
+
const e = seen.get(name);
|
|
244
|
+
events.push({
|
|
245
|
+
type: 'ensemble.created',
|
|
246
|
+
payload: {
|
|
247
|
+
ensemble: name,
|
|
248
|
+
createdAt: capturedAt,
|
|
249
|
+
hasConductor: e.hasConductor,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const name of prev) {
|
|
255
|
+
if (!names.has(name)) {
|
|
256
|
+
events.push({
|
|
257
|
+
type: 'ensemble.destroyed',
|
|
258
|
+
payload: { ensemble: name, destroyedAt: capturedAt },
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return { events, names };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Coordinates the poll loop and owns the per-ensemble + global buses.
|
|
266
|
+
* Daemon constructs one of these after `runDaemonBoot`. Hot path is
|
|
267
|
+
* `tick()` — production schedules it on a timer, tests call it
|
|
268
|
+
* directly for deterministic diffing.
|
|
269
|
+
*/
|
|
270
|
+
class AggregateRunner {
|
|
271
|
+
client;
|
|
272
|
+
bootEpoch;
|
|
273
|
+
pollIntervalMs;
|
|
274
|
+
now;
|
|
275
|
+
busFactory;
|
|
276
|
+
/** Per-ensemble bus + tracking state. */
|
|
277
|
+
tracks = new Map();
|
|
278
|
+
/** Global bus — handles `ensemble.created/destroyed` + `host_profile.changed`. */
|
|
279
|
+
_globalBus;
|
|
280
|
+
globalSeqAllocator;
|
|
281
|
+
/** Last seen ensemble names + host profile hashes for diff. */
|
|
282
|
+
knownEnsembles = new Set();
|
|
283
|
+
hostHashes = new Map();
|
|
284
|
+
/** Loop state. */
|
|
285
|
+
timer = null;
|
|
286
|
+
inFlight = false;
|
|
287
|
+
skipCount = 0;
|
|
288
|
+
/** Last skip-warning emit time — rate-limited so a wedged Temporal doesn't drown the log. */
|
|
289
|
+
lastSkipWarn = 0;
|
|
290
|
+
stopped = false;
|
|
291
|
+
/**
|
|
292
|
+
* Issue #433 — tick generation counter. Incremented each time a tick
|
|
293
|
+
* starts. The watchdog stamps the current generation when it force-clears
|
|
294
|
+
* `inFlight`; a late-arriving completion checks `myGen === currentGen`
|
|
295
|
+
* before resetting state so it doesn't clobber a fresh tick that the
|
|
296
|
+
* watchdog already handed to the loop.
|
|
297
|
+
*/
|
|
298
|
+
tickGen = 0;
|
|
299
|
+
/**
|
|
300
|
+
* Watchdog ceiling — if a tick exceeds this without finishing, the
|
|
301
|
+
* watchdog force-clears `inFlight` so the next scheduled tick can run.
|
|
302
|
+
* Default: 20 × poll interval (15s at the 750ms default). Should be
|
|
303
|
+
* comfortably larger than `pollIntervalMs × DEFAULT_QUERY_TIMEOUT_MS /
|
|
304
|
+
* pollInterval` so `queryHandleWithTimeout`-bounded ticks finish well
|
|
305
|
+
* inside the watchdog window in normal operation.
|
|
306
|
+
*/
|
|
307
|
+
tickWatchdogMs;
|
|
308
|
+
constructor(opts) {
|
|
309
|
+
this.client = opts.client;
|
|
310
|
+
this.bootEpoch = opts.bootEpoch;
|
|
311
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? exports.DEFAULT_POLL_INTERVAL_MS;
|
|
312
|
+
this.now = opts.now ?? Date.now;
|
|
313
|
+
this.tickWatchdogMs = opts.tickWatchdogMs ?? this.pollIntervalMs * 20;
|
|
314
|
+
this.busFactory = opts.busFactory
|
|
315
|
+
?? ((ensemble, allocator) => new event_bus_1.EnsembleEventBus({
|
|
316
|
+
scope: `ensemble:${ensemble}`, allocator, now: this.now,
|
|
317
|
+
}));
|
|
318
|
+
this.globalSeqAllocator = new event_id_1.SeqAllocator(opts.bootEpoch);
|
|
319
|
+
this._globalBus = new event_bus_1.EnsembleEventBus({
|
|
320
|
+
scope: 'global', allocator: this.globalSeqAllocator, now: this.now,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Begin polling on the configured cadence.
|
|
325
|
+
*
|
|
326
|
+
* **One-shot by design**: a single `start()` schedules the first
|
|
327
|
+
* tick immediately and chains subsequent ticks via `setTimeout` —
|
|
328
|
+
* each tick reschedules the next as it completes. Repeated
|
|
329
|
+
* `start()` calls are no-ops once the chain is running (and after
|
|
330
|
+
* `stop()` flips `stopped = true`, future `start()` calls are
|
|
331
|
+
* also no-ops). Tests that want to drive ticks deterministically
|
|
332
|
+
* skip `start()` and call `tick()` directly. PR #324 review nit
|
|
333
|
+
* folded in.
|
|
334
|
+
*/
|
|
335
|
+
start() {
|
|
336
|
+
if (this.timer || this.stopped)
|
|
337
|
+
return;
|
|
338
|
+
const tickAndSchedule = () => {
|
|
339
|
+
if (this.stopped)
|
|
340
|
+
return;
|
|
341
|
+
void this.tick();
|
|
342
|
+
this.timer = setTimeout(tickAndSchedule, this.pollIntervalMs);
|
|
343
|
+
this.timer.unref();
|
|
344
|
+
};
|
|
345
|
+
// Run once immediately so a fresh boot has events to serve.
|
|
346
|
+
tickAndSchedule();
|
|
347
|
+
}
|
|
348
|
+
/** Stop polling. Buses stay alive — daemon owns their close lifecycle. */
|
|
349
|
+
stop() {
|
|
350
|
+
this.stopped = true;
|
|
351
|
+
if (this.timer) {
|
|
352
|
+
clearTimeout(this.timer);
|
|
353
|
+
this.timer = null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** Drain every bus + clear tracking. */
|
|
357
|
+
close() {
|
|
358
|
+
this.stop();
|
|
359
|
+
for (const t of this.tracks.values())
|
|
360
|
+
t.bus.close();
|
|
361
|
+
this.tracks.clear();
|
|
362
|
+
this._globalBus.close();
|
|
363
|
+
}
|
|
364
|
+
/** The global bus — exposed for the SSE handler's `/v1/events` route. */
|
|
365
|
+
globalBus() { return this._globalBus; }
|
|
366
|
+
/**
|
|
367
|
+
* Look up (or lazily build) the per-ensemble bus. SSE handler uses
|
|
368
|
+
* this to subscribe a new client; aggregate diff uses it to emit
|
|
369
|
+
* `player.added`-and-friends.
|
|
370
|
+
*/
|
|
371
|
+
getOrCreateEnsembleBus(ensemble) {
|
|
372
|
+
let track = this.tracks.get(ensemble);
|
|
373
|
+
if (!track) {
|
|
374
|
+
const allocator = new event_id_1.SeqAllocator(this.bootEpoch);
|
|
375
|
+
const bus = this.busFactory(ensemble, allocator);
|
|
376
|
+
track = {
|
|
377
|
+
bus,
|
|
378
|
+
consecutiveFailures: 0,
|
|
379
|
+
playerPhases: new Map(),
|
|
380
|
+
playerAgentTypes: new Map(),
|
|
381
|
+
flags: null,
|
|
382
|
+
schedulesHash: null,
|
|
383
|
+
chatIds: new Set(),
|
|
384
|
+
chatIdOrder: [],
|
|
385
|
+
};
|
|
386
|
+
this.tracks.set(ensemble, track);
|
|
387
|
+
}
|
|
388
|
+
return track.bus;
|
|
389
|
+
}
|
|
390
|
+
/** Returns the bus for an ensemble if one exists, else `null`. */
|
|
391
|
+
getEnsembleBus(ensemble) {
|
|
392
|
+
return this.tracks.get(ensemble)?.bus ?? null;
|
|
393
|
+
}
|
|
394
|
+
/** Total live subscriber count across all buses — `/v1/health.subscriberCount`. */
|
|
395
|
+
totalSubscriberCount() {
|
|
396
|
+
let n = this._globalBus.subscriberCount();
|
|
397
|
+
for (const t of this.tracks.values())
|
|
398
|
+
n += t.bus.subscriberCount();
|
|
399
|
+
return n;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Run one diff pass. Production schedules this on a timer; tests
|
|
403
|
+
* call it directly. Serial-with-skip per §8 — if a previous tick
|
|
404
|
+
* is still in flight, this one skips with a warn-log.
|
|
405
|
+
*
|
|
406
|
+
* **Watchdog (#433)**. Each tick takes a generation stamp. After
|
|
407
|
+
* `tickWatchdogMs` elapses without completion, the watchdog bumps
|
|
408
|
+
* the generation and clears `inFlight` so the next scheduled tick
|
|
409
|
+
* can run. The hung tick's eventual completion checks its own
|
|
410
|
+
* generation against the current one; if it's stale (watchdog
|
|
411
|
+
* already advanced past it), it returns without touching `inFlight`
|
|
412
|
+
* — the new tick owns that flag now.
|
|
413
|
+
*/
|
|
414
|
+
async tick() {
|
|
415
|
+
if (this.inFlight) {
|
|
416
|
+
this.skipCount++;
|
|
417
|
+
const now = this.now();
|
|
418
|
+
// Throttle warn-logs to once per 5 s so a wedged Temporal doesn't flood.
|
|
419
|
+
if (now - this.lastSkipWarn >= 5_000) {
|
|
420
|
+
this.lastSkipWarn = now;
|
|
421
|
+
log(`tick skipped — prior tick still in flight (skipCount=${this.skipCount})`);
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
this.inFlight = true;
|
|
426
|
+
const myGen = ++this.tickGen;
|
|
427
|
+
// Watchdog — if we don't complete within `tickWatchdogMs`, force-clear
|
|
428
|
+
// `inFlight` so the next tick can run. Bumping `tickGen` ensures our
|
|
429
|
+
// own (eventually-arriving) finally clause sees a stale generation
|
|
430
|
+
// and skips the second `inFlight = false`.
|
|
431
|
+
let watchdogFired = false;
|
|
432
|
+
const watchdog = setTimeout(() => {
|
|
433
|
+
if (this.tickGen === myGen) {
|
|
434
|
+
watchdogFired = true;
|
|
435
|
+
this.tickGen++;
|
|
436
|
+
this.inFlight = false;
|
|
437
|
+
log(`tick watchdog fired after ${this.tickWatchdogMs}ms — ` +
|
|
438
|
+
`clearing inFlight so the loop can advance ` +
|
|
439
|
+
`(prior tick may still be pending in memory; see #433)`);
|
|
440
|
+
}
|
|
441
|
+
}, this.tickWatchdogMs);
|
|
442
|
+
watchdog.unref?.();
|
|
443
|
+
try {
|
|
444
|
+
const snapshot = await this.collect();
|
|
445
|
+
this.applyDiff(snapshot);
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
// #336/#529 site 6 — `TickSkipped` is the architect-approved
|
|
449
|
+
// signal that `collect()` deliberately bailed before applying
|
|
450
|
+
// any diff (e.g. visibility-iterator deadline tripped). It is
|
|
451
|
+
// expected and frequent under load; do NOT log noisily. Other
|
|
452
|
+
// throws are unexpected failures worth surfacing.
|
|
453
|
+
if (err instanceof TickSkipped) {
|
|
454
|
+
log(`tick=${myGen}: skipped — ${err.message}`);
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
log('tick error (non-fatal):', err instanceof Error ? err.message : err);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
clearTimeout(watchdog);
|
|
462
|
+
// Only release `inFlight` if the watchdog hasn't already done so —
|
|
463
|
+
// otherwise we'd clobber a fresh tick that the loop is in the
|
|
464
|
+
// middle of running on the next scheduled boundary.
|
|
465
|
+
if (!watchdogFired && this.tickGen === myGen) {
|
|
466
|
+
this.inFlight = false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/** Fetch the current cluster state via `TempoClient`. */
|
|
471
|
+
async collect() {
|
|
472
|
+
const tickGen = this.tickGen;
|
|
473
|
+
const preludeStart = this.now();
|
|
474
|
+
log(`collect tick=${tickGen}: prelude started`);
|
|
475
|
+
const capturedAt = new Date(this.now()).toISOString();
|
|
476
|
+
// #336/#529 site 6 — bounded variant prevents the 750ms poll from
|
|
477
|
+
// emitting phantom `ensemble.destroyed` events when the visibility
|
|
478
|
+
// iterator partially enumerates under load. On timeout, throw
|
|
479
|
+
// `TickSkipped` so `tick()`'s catch preserves `knownEnsembles`
|
|
480
|
+
// unchanged (no `applyDiff` runs). 500ms budget leaves >=250ms
|
|
481
|
+
// headroom for the rest of `collect()` + `applyDiff` + emission
|
|
482
|
+
// within the 750ms cadence (architect's call).
|
|
483
|
+
const listRes = await this.client.listEnsemblesBounded(exports.AGGREGATE_LIST_DEADLINE_MS);
|
|
484
|
+
if (listRes.timedOut) {
|
|
485
|
+
log(`collect tick=${tickGen}: listEnsembles deadline (` +
|
|
486
|
+
`${exports.AGGREGATE_LIST_DEADLINE_MS}ms, ${listRes.scanned} scanned) — skip diff`);
|
|
487
|
+
throw new TickSkipped('listEnsembles deadline');
|
|
488
|
+
}
|
|
489
|
+
const ensembles = listRes.items;
|
|
490
|
+
const hostProfiles = {};
|
|
491
|
+
try {
|
|
492
|
+
const hosts = await this.client.listHosts();
|
|
493
|
+
for (const h of hosts) {
|
|
494
|
+
if (h.profile)
|
|
495
|
+
hostProfiles[h.hostname] = h.profile;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch { /* fall through with empty hostProfiles */ }
|
|
499
|
+
const preludeMs = this.now() - preludeStart;
|
|
500
|
+
log(`collect tick=${tickGen}: prelude complete ` +
|
|
501
|
+
`(${preludeMs}ms, ensembles=${ensembles.length}, ` +
|
|
502
|
+
`hosts=${Object.keys(hostProfiles).length})`);
|
|
503
|
+
// Per-ensemble fan-out. #550 — returns `FanoutResult` discriminated
|
|
504
|
+
// union so the cluster diff downstream can distinguish "still alive
|
|
505
|
+
// (carry forward)" from "actually destroyed (emit destroy)."
|
|
506
|
+
const pollStart = this.now();
|
|
507
|
+
log(`collect tick=${tickGen}: poll started`);
|
|
508
|
+
const initialResults = await Promise.all(ensembles.map(async (e) => {
|
|
509
|
+
try {
|
|
510
|
+
// Reuse `buildEnsembleSnapshot` so the projection logic stays in
|
|
511
|
+
// one place. We only need a subset, but the full builder is cheap.
|
|
512
|
+
const snap = await (0, snapshot_1.buildEnsembleSnapshot)(this.client, e.name, {
|
|
513
|
+
now: () => new Date(this.now()),
|
|
514
|
+
});
|
|
515
|
+
// Replace chat with a wider window so the aggregate doesn't miss
|
|
516
|
+
// bursts between polls. The bus's §8 cap collapses excess.
|
|
517
|
+
let chat = snap.chat.messages;
|
|
518
|
+
try {
|
|
519
|
+
const wider = await this.client.getEnsembleChat(e.name, 0, POLL_CHAT_LIMIT);
|
|
520
|
+
chat = wider.messages;
|
|
521
|
+
}
|
|
522
|
+
catch { /* fall back to the snapshot's narrow slice */ }
|
|
523
|
+
return {
|
|
524
|
+
kind: 'ok',
|
|
525
|
+
snapshot: {
|
|
526
|
+
ensemble: e.name,
|
|
527
|
+
hasConductor: snap.hasConductor,
|
|
528
|
+
flags: snap.flags,
|
|
529
|
+
players: snap.players,
|
|
530
|
+
schedules: snap.schedules,
|
|
531
|
+
chat,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
// #550 — conservative classification: only `EnsembleNotFoundError`
|
|
537
|
+
// counts as a real removal. Everything else (query timeout,
|
|
538
|
+
// network blip, transient worker wedge) is `'failed'` →
|
|
539
|
+
// carry-forward. False-negative destroy events are the worst
|
|
540
|
+
// possible failure mode (SSE consumers may take destructive
|
|
541
|
+
// action), so be deliberate about what gets classified as
|
|
542
|
+
// genuinely gone.
|
|
543
|
+
if (err instanceof snapshot_1.EnsembleNotFoundError) {
|
|
544
|
+
return { kind: 'gone', name: e.name };
|
|
545
|
+
}
|
|
546
|
+
log(`collect: ensemble "${e.name}" failed (carry-forward):`, err instanceof Error ? err.message : err);
|
|
547
|
+
return { kind: 'failed', name: e.name };
|
|
548
|
+
}
|
|
549
|
+
}));
|
|
550
|
+
// #550 — Update per-ensemble `consecutiveFailures` counters and
|
|
551
|
+
// apply the K=20 promotion pass. Mutating tracks here is safe (we
|
|
552
|
+
// serialize after Promise.all completes; the tick is single-flight
|
|
553
|
+
// courtesy of `inFlight`). Promoting 'failed' → 'gone' triggers
|
|
554
|
+
// `applyDiff` to tear the track down.
|
|
555
|
+
let okCount = 0, goneCount = 0, failedCount = 0, promotedCount = 0;
|
|
556
|
+
const finalResults = initialResults.map((r) => {
|
|
557
|
+
if (r.kind === 'ok') {
|
|
558
|
+
const t = this.tracks.get(r.snapshot.ensemble);
|
|
559
|
+
if (t)
|
|
560
|
+
t.consecutiveFailures = 0;
|
|
561
|
+
okCount++;
|
|
562
|
+
return r;
|
|
563
|
+
}
|
|
564
|
+
if (r.kind === 'gone') {
|
|
565
|
+
goneCount++;
|
|
566
|
+
return r;
|
|
567
|
+
}
|
|
568
|
+
// r.kind === 'failed' — ensure a track exists so the counter has
|
|
569
|
+
// a home, then increment + maybe promote.
|
|
570
|
+
this.getOrCreateEnsembleBus(r.name);
|
|
571
|
+
const t = this.tracks.get(r.name);
|
|
572
|
+
t.consecutiveFailures++;
|
|
573
|
+
if (t.consecutiveFailures > exports.MAX_CONSECUTIVE_FAILURES) {
|
|
574
|
+
log(`collect tick=${tickGen}: ensemble "${r.name}" failed ` +
|
|
575
|
+
`${t.consecutiveFailures}× consecutively (cap=${exports.MAX_CONSECUTIVE_FAILURES}) — promote to 'gone'`);
|
|
576
|
+
promotedCount++;
|
|
577
|
+
// applyDiff's teardown loop will drop the track on the next tick.
|
|
578
|
+
return { kind: 'gone', name: r.name };
|
|
579
|
+
}
|
|
580
|
+
failedCount++;
|
|
581
|
+
return r;
|
|
582
|
+
});
|
|
583
|
+
// #550 — Build the cluster-diff input: ('ok' ∪ 'failed') with
|
|
584
|
+
// truthful `hasConductor` for each (from the fresh ok snapshot
|
|
585
|
+
// OR carried forward from the listEnsembles prelude for failed).
|
|
586
|
+
// 'gone' results (including K-promoted) are excluded so they
|
|
587
|
+
// trigger `ensemble.destroyed` downstream.
|
|
588
|
+
const preludeHasConductor = new Map(ensembles.map((e) => [e.name, e.hasConductor]));
|
|
589
|
+
const livePrelude = [];
|
|
590
|
+
const okSnapshots = [];
|
|
591
|
+
for (const r of finalResults) {
|
|
592
|
+
if (r.kind === 'ok') {
|
|
593
|
+
okSnapshots.push(r.snapshot);
|
|
594
|
+
livePrelude.push({ ensemble: r.snapshot.ensemble, hasConductor: r.snapshot.hasConductor });
|
|
595
|
+
}
|
|
596
|
+
else if (r.kind === 'failed') {
|
|
597
|
+
livePrelude.push({
|
|
598
|
+
ensemble: r.name,
|
|
599
|
+
hasConductor: preludeHasConductor.get(r.name) ?? false,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
// 'gone' — explicitly excluded.
|
|
603
|
+
}
|
|
604
|
+
const pollMs = this.now() - pollStart;
|
|
605
|
+
log(`collect tick=${tickGen}: poll complete (${pollMs}ms, ` +
|
|
606
|
+
`ok=${okCount}, failed=${failedCount}, gone=${goneCount}, promoted=${promotedCount})`);
|
|
607
|
+
return {
|
|
608
|
+
capturedAt,
|
|
609
|
+
ensembles: okSnapshots,
|
|
610
|
+
livePrelude,
|
|
611
|
+
hostProfiles,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/** Apply diff and emit events — exposed for tests that want to inject fixtures. */
|
|
615
|
+
applyDiff(snapshot) {
|
|
616
|
+
// Global: ensemble created/destroyed. #550 — diff against
|
|
617
|
+
// `snapshot.livePrelude` (ok ∪ failed) so a transient per-ensemble
|
|
618
|
+
// fan-out failure does NOT trigger `ensemble.destroyed`. The
|
|
619
|
+
// discrimination between "failed (carry forward)" and "gone (real
|
|
620
|
+
// removal)" was made upstream in `collect()` per architect's spec;
|
|
621
|
+
// `applyDiff` is intentionally ignorant of the failure mode and
|
|
622
|
+
// just consumes the resolved live set.
|
|
623
|
+
const { events: ensembleEvents, names } = diffEnsembleSet(this.knownEnsembles, snapshot.livePrelude, snapshot.capturedAt);
|
|
624
|
+
for (const ev of ensembleEvents)
|
|
625
|
+
this._globalBus.emit(ev.type, ev.payload);
|
|
626
|
+
this.knownEnsembles = names;
|
|
627
|
+
// Global: host profile diffs.
|
|
628
|
+
const hostDiff = diffHostProfiles(this.hostHashes, snapshot.hostProfiles);
|
|
629
|
+
for (const ev of hostDiff.events)
|
|
630
|
+
this._globalBus.emit(ev.type, ev.payload);
|
|
631
|
+
this.hostHashes = hostDiff.hashes;
|
|
632
|
+
// Per-ensemble: player.added/removed/phase_changed, chat.appended, flags.changed, schedules.changed.
|
|
633
|
+
for (const eState of snapshot.ensembles) {
|
|
634
|
+
const track = this.ensureTrack(eState.ensemble);
|
|
635
|
+
// Build prior aggregate-snapshot view from track state — only used
|
|
636
|
+
// for player set diff; flags/schedules/chat use their own track fields.
|
|
637
|
+
const prior = {
|
|
638
|
+
ensemble: eState.ensemble,
|
|
639
|
+
hasConductor: false, // not used by diffEnsembleSnapshot
|
|
640
|
+
flags: track.flags ?? { paused: false, held: false }, // not used; uses track.flags directly
|
|
641
|
+
players: [...track.playerPhases.keys()].map((id) => ({
|
|
642
|
+
// Minimal stand-in — only `playerId` is consulted by `diffEnsembleSnapshot`
|
|
643
|
+
// for `player.removed` events (see L154-L174). The other fields are
|
|
644
|
+
// zero-cost placeholders that satisfy `PlayerSummaryV1` typing without
|
|
645
|
+
// affecting any emitted event payload. `agentType` reads from the
|
|
646
|
+
// parallel track Map so the prior carries the player's real adapter
|
|
647
|
+
// family (#535) — pre-#535 this was hardcoded `'claude' as const`,
|
|
648
|
+
// which became a type lie once the wire union expanded to mirror
|
|
649
|
+
// `AgentType`. Fallback to `'claude'` covers the brief migration
|
|
650
|
+
// window where a long-running daemon's tracks predate the new Map.
|
|
651
|
+
playerId: id, ensemble: eState.ensemble, hostname: '', isConductor: false,
|
|
652
|
+
agentType: track.playerAgentTypes.get(id) ?? 'claude', part: '', workDir: '',
|
|
653
|
+
phase: track.playerPhases.get(id),
|
|
654
|
+
})),
|
|
655
|
+
schedules: [],
|
|
656
|
+
chat: [],
|
|
657
|
+
};
|
|
658
|
+
const events = diffEnsembleSnapshot(prior, eState, track, snapshot.capturedAt);
|
|
659
|
+
for (const ev of events)
|
|
660
|
+
track.bus.emit(ev.type, ev.payload);
|
|
661
|
+
}
|
|
662
|
+
// Tear down buses for ensembles that disappeared.
|
|
663
|
+
for (const name of [...this.tracks.keys()]) {
|
|
664
|
+
if (!this.knownEnsembles.has(name)) {
|
|
665
|
+
const t = this.tracks.get(name);
|
|
666
|
+
if (t) {
|
|
667
|
+
t.bus.close();
|
|
668
|
+
this.tracks.delete(name);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
ensureTrack(ensemble) {
|
|
674
|
+
let track = this.tracks.get(ensemble);
|
|
675
|
+
if (!track) {
|
|
676
|
+
this.getOrCreateEnsembleBus(ensemble);
|
|
677
|
+
track = this.tracks.get(ensemble);
|
|
678
|
+
}
|
|
679
|
+
return track;
|
|
680
|
+
}
|
|
681
|
+
/** Test-only — skip count for assertions. */
|
|
682
|
+
get _skipCount() { return this.skipCount; }
|
|
683
|
+
}
|
|
684
|
+
exports.AggregateRunner = AggregateRunner;
|