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,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnsembleEventBus = exports.Subscription = exports.THROTTLE_SUPPRESS_MS = exports.RATE_LIMIT_WINDOW_MS = exports.DEFAULT_CHAT_RATE_LIMIT = exports.DEFAULT_ENSEMBLE_RATE_LIMIT = exports.DEFAULT_HEARTBEAT_SUPPRESS_MS = exports.DEFAULT_HEARTBEAT_MS = void 0;
|
|
4
|
+
exports.topicOf = topicOf;
|
|
5
|
+
exports.startHeartbeatTimer = startHeartbeatTimer;
|
|
6
|
+
/**
|
|
7
|
+
* `EnsembleEventBus` — fan-out + ring buffer + replay engine that backs
|
|
8
|
+
* the `/v1/events/:ensemble` SSE endpoint (SSE-PROTOCOL.md §6, §7, §8).
|
|
9
|
+
*
|
|
10
|
+
* **Responsibilities**:
|
|
11
|
+
* - Allocate `<bootEpoch>:<seq>` ids via {@link SeqAllocator}.
|
|
12
|
+
* - Buffer the most recent N events for `Last-Event-ID` replay.
|
|
13
|
+
* - Fan out to every live subscriber, with a per-subscription queue
|
|
14
|
+
* so a slow consumer doesn't backpressure faster ones.
|
|
15
|
+
* - Enforce wire-level rate caps (§8): ensemble-wide 50 ev/s ceiling
|
|
16
|
+
* produces a `throttled` event and pauses non-essential streams
|
|
17
|
+
* (`heartbeat`, `flags.changed`) for 1 s; chat 100 msg/s collapses
|
|
18
|
+
* to a single `chat.compressed`.
|
|
19
|
+
* - Heartbeat: emit `heartbeat` every `heartbeatMs` (default 10 s) on
|
|
20
|
+
* the bus, suppressed if any other event has been emitted in the
|
|
21
|
+
* last `heartbeatSuppressMs` (default 8 s).
|
|
22
|
+
*
|
|
23
|
+
* **NOT in this module**:
|
|
24
|
+
* - Coalescing (phase debounce, schedule SHA-256 diff, flag diff,
|
|
25
|
+
* host-profile hash compare) — those live in `aggregate.ts` and
|
|
26
|
+
* feed clean events to the bus.
|
|
27
|
+
* - SSE framing / `Last-Event-ID` parsing — `sse-handler.ts` owns
|
|
28
|
+
* that and consults the bus for replay slices.
|
|
29
|
+
*/
|
|
30
|
+
const ring_buffer_1 = require("./ring-buffer");
|
|
31
|
+
/** Default heartbeat cadence — locked in by §6 / §8. */
|
|
32
|
+
exports.DEFAULT_HEARTBEAT_MS = 10_000;
|
|
33
|
+
/** Default heartbeat suppression window — §6. */
|
|
34
|
+
exports.DEFAULT_HEARTBEAT_SUPPRESS_MS = 8_000;
|
|
35
|
+
/** Ensemble-wide rate ceiling — §8. */
|
|
36
|
+
exports.DEFAULT_ENSEMBLE_RATE_LIMIT = 50;
|
|
37
|
+
/** Chat-only rate ceiling — §8. */
|
|
38
|
+
exports.DEFAULT_CHAT_RATE_LIMIT = 100;
|
|
39
|
+
/** Window over which both rate caps are evaluated. */
|
|
40
|
+
exports.RATE_LIMIT_WINDOW_MS = 1_000;
|
|
41
|
+
/** How long to suppress non-essential streams after `throttled` fires. */
|
|
42
|
+
exports.THROTTLE_SUPPRESS_MS = 1_000;
|
|
43
|
+
/** Event types that are essential — never dropped under throttling. */
|
|
44
|
+
const ESSENTIAL_TYPES = new Set([
|
|
45
|
+
'snapshot',
|
|
46
|
+
'gap',
|
|
47
|
+
'throttled',
|
|
48
|
+
'chat.compressed',
|
|
49
|
+
'ensemble.created',
|
|
50
|
+
'ensemble.destroyed',
|
|
51
|
+
'player.added',
|
|
52
|
+
'player.removed',
|
|
53
|
+
'player.phase_changed',
|
|
54
|
+
'chat.appended',
|
|
55
|
+
'schedules.changed',
|
|
56
|
+
'host_profile.changed',
|
|
57
|
+
]);
|
|
58
|
+
/** Event types the bus suppresses for `THROTTLE_SUPPRESS_MS` after a `throttled` fires. */
|
|
59
|
+
const NON_ESSENTIAL_AFTER_THROTTLE = new Set([
|
|
60
|
+
'heartbeat',
|
|
61
|
+
'flags.changed',
|
|
62
|
+
]);
|
|
63
|
+
/** Maps a `SseEventKind` to its filterable topic, or null when essential. */
|
|
64
|
+
function topicOf(kind) {
|
|
65
|
+
switch (kind) {
|
|
66
|
+
case 'player.phase_changed': return 'phase';
|
|
67
|
+
case 'chat.appended':
|
|
68
|
+
case 'chat.compressed': return 'chat';
|
|
69
|
+
case 'flags.changed': return 'flags';
|
|
70
|
+
case 'schedules.changed': return 'schedules';
|
|
71
|
+
case 'heartbeat': return 'heartbeat';
|
|
72
|
+
default: return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* One open SSE connection's view of the bus. Async iterator yields
|
|
77
|
+
* events in the order the bus emitted them. `close()` is idempotent
|
|
78
|
+
* and must be called when the underlying socket goes away.
|
|
79
|
+
*/
|
|
80
|
+
class Subscription {
|
|
81
|
+
opts;
|
|
82
|
+
queue = [];
|
|
83
|
+
resolveNext = null;
|
|
84
|
+
closed = false;
|
|
85
|
+
/** Caller's hook — bus calls this on close so the parent can drop us from its set. */
|
|
86
|
+
onClose = null;
|
|
87
|
+
constructor(opts) {
|
|
88
|
+
this.opts = opts;
|
|
89
|
+
}
|
|
90
|
+
/** Push an event onto the consumer queue. Bus → subscription. */
|
|
91
|
+
push(e) {
|
|
92
|
+
if (this.closed)
|
|
93
|
+
return;
|
|
94
|
+
if (!this.passesTopicFilter(e))
|
|
95
|
+
return;
|
|
96
|
+
if (this.resolveNext) {
|
|
97
|
+
const r = this.resolveNext;
|
|
98
|
+
this.resolveNext = null;
|
|
99
|
+
r({ value: e, done: false });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.queue.push(e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
next() {
|
|
106
|
+
if (this.closed && this.queue.length === 0) {
|
|
107
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
108
|
+
}
|
|
109
|
+
if (this.queue.length > 0) {
|
|
110
|
+
return Promise.resolve({ value: this.queue.shift(), done: false });
|
|
111
|
+
}
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
this.resolveNext = resolve;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async return() {
|
|
117
|
+
this.close();
|
|
118
|
+
return { value: undefined, done: true };
|
|
119
|
+
}
|
|
120
|
+
[Symbol.asyncIterator]() {
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
close() {
|
|
124
|
+
if (this.closed)
|
|
125
|
+
return;
|
|
126
|
+
this.closed = true;
|
|
127
|
+
if (this.resolveNext) {
|
|
128
|
+
const r = this.resolveNext;
|
|
129
|
+
this.resolveNext = null;
|
|
130
|
+
r({ value: undefined, done: true });
|
|
131
|
+
}
|
|
132
|
+
if (this.onClose) {
|
|
133
|
+
const fn = this.onClose;
|
|
134
|
+
this.onClose = null;
|
|
135
|
+
try {
|
|
136
|
+
fn();
|
|
137
|
+
}
|
|
138
|
+
catch { /* ignore — bus internal */ }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** True when `close()` has been called. */
|
|
142
|
+
get isClosed() { return this.closed; }
|
|
143
|
+
/** Snapshot of unread items — exposed for tests + diagnostics only. */
|
|
144
|
+
get pending() { return this.queue; }
|
|
145
|
+
passesTopicFilter(e) {
|
|
146
|
+
if (!this.opts.topics)
|
|
147
|
+
return true;
|
|
148
|
+
const cat = topicOf(e.type);
|
|
149
|
+
if (cat === null)
|
|
150
|
+
return true; // essential / always-emit
|
|
151
|
+
return this.opts.topics.has(cat);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.Subscription = Subscription;
|
|
155
|
+
/** Concrete implementation. Same class powers per-ensemble + global buses. */
|
|
156
|
+
class EnsembleEventBus {
|
|
157
|
+
scope;
|
|
158
|
+
bootEpoch;
|
|
159
|
+
allocator;
|
|
160
|
+
ring;
|
|
161
|
+
now;
|
|
162
|
+
subs = new Set();
|
|
163
|
+
ensembleRateLimit;
|
|
164
|
+
chatRateLimit;
|
|
165
|
+
heartbeatMs;
|
|
166
|
+
heartbeatSuppressMs;
|
|
167
|
+
closed = false;
|
|
168
|
+
/** Sliding window of recent emit timestamps for the ensemble-wide cap. */
|
|
169
|
+
recentEmits = [];
|
|
170
|
+
/** Sliding window of recent chat emit timestamps. */
|
|
171
|
+
recentChat = [];
|
|
172
|
+
/** Last time *any* non-heartbeat event was emitted — drives §6 heartbeat suppression. */
|
|
173
|
+
lastNonHeartbeatAt = 0;
|
|
174
|
+
/** When set, suppress `heartbeat` and `flags.changed` until this timestamp (post-`throttled` window). */
|
|
175
|
+
nonEssentialSuppressUntil = 0;
|
|
176
|
+
/** Last time `throttled` was emitted — at most once per `THROTTLE_SUPPRESS_MS` window. */
|
|
177
|
+
lastThrottledAt = 0;
|
|
178
|
+
/**
|
|
179
|
+
* `chat.compressed` deferred-emission state (PR #324 review followup).
|
|
180
|
+
*
|
|
181
|
+
* **Why deferred**: §6 says "excess collapses into a single
|
|
182
|
+
* `chat.compressed` event" — singular. If we emitted on the first
|
|
183
|
+
* drop, the `dropped` field would always read `1` even when 50+
|
|
184
|
+
* messages got dropped in the same burst. Misleading metadata —
|
|
185
|
+
* eng-4's PR-3 wrapper might surface the count as "N messages
|
|
186
|
+
* dropped" or use it for retry logic.
|
|
187
|
+
*
|
|
188
|
+
* **Resolution**: track every drop in `chatDropCount`; on first
|
|
189
|
+
* drop schedule a `chat.compressed` emission `RATE_LIMIT_WINDOW_MS`
|
|
190
|
+
* later. Subsequent drops in the same burst just increment the
|
|
191
|
+
* counter — no second timer, no second event. When the timer
|
|
192
|
+
* fires, emit `chat.compressed { dropped: <total>, since: <iso> }`
|
|
193
|
+
* and reset. A 150-message burst at rate 1/s now yields one
|
|
194
|
+
* `chat.compressed { dropped: 149 }` event, matching the spec
|
|
195
|
+
* intent.
|
|
196
|
+
*
|
|
197
|
+
* Note: "compression event fired" (timer ran, message went on the
|
|
198
|
+
* wire) is separate from "drop count accumulated" (drops have
|
|
199
|
+
* happened but the timer is still pending). Tests differentiate
|
|
200
|
+
* the two by advancing the fake timer and re-draining.
|
|
201
|
+
*/
|
|
202
|
+
chatDropCount = 0;
|
|
203
|
+
chatCompressedTimer = null;
|
|
204
|
+
constructor(opts) {
|
|
205
|
+
this.scope = opts.scope;
|
|
206
|
+
this.allocator = opts.allocator;
|
|
207
|
+
this.bootEpoch = opts.allocator.bootEpoch;
|
|
208
|
+
this.ring = (0, ring_buffer_1.createRingBuffer)(opts.bufferCapacity ?? ring_buffer_1.RING_BUFFER_CAPACITY);
|
|
209
|
+
this.now = opts.now ?? Date.now;
|
|
210
|
+
this.ensembleRateLimit = opts.ensembleRateLimit ?? exports.DEFAULT_ENSEMBLE_RATE_LIMIT;
|
|
211
|
+
this.chatRateLimit = opts.chatRateLimit ?? exports.DEFAULT_CHAT_RATE_LIMIT;
|
|
212
|
+
this.heartbeatMs = opts.heartbeatMs ?? exports.DEFAULT_HEARTBEAT_MS;
|
|
213
|
+
this.heartbeatSuppressMs = opts.heartbeatSuppressMs ?? exports.DEFAULT_HEARTBEAT_SUPPRESS_MS;
|
|
214
|
+
}
|
|
215
|
+
oldestSeq() { return this.ring.oldestSeq; }
|
|
216
|
+
newestSeq() { return this.ring.newestSeq; }
|
|
217
|
+
nextSeq() { return this.allocator.peekNextSeq(); }
|
|
218
|
+
subscriberCount() { return this.subs.size; }
|
|
219
|
+
emit(type, payload) {
|
|
220
|
+
if (this.closed)
|
|
221
|
+
return null;
|
|
222
|
+
const now = this.now();
|
|
223
|
+
// Heartbeat path: respect §6 suppression — skip when any other
|
|
224
|
+
// event was emitted within the last `heartbeatSuppressMs`.
|
|
225
|
+
if (type === 'heartbeat') {
|
|
226
|
+
if (now - this.lastNonHeartbeatAt < this.heartbeatSuppressMs)
|
|
227
|
+
return null;
|
|
228
|
+
if (now < this.nonEssentialSuppressUntil)
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
// §8 chat 100/s cap — collapse excess to a single `chat.compressed`.
|
|
232
|
+
if (type === 'chat.appended') {
|
|
233
|
+
this.trimWindow(this.recentChat, now);
|
|
234
|
+
if (this.recentChat.length >= this.chatRateLimit) {
|
|
235
|
+
this.chatDropCount++;
|
|
236
|
+
if (!this.chatCompressedTimer) {
|
|
237
|
+
// Defer emission so a single `chat.compressed` can carry the
|
|
238
|
+
// total burst count, not just the first drop. See the
|
|
239
|
+
// `chatDropCount` field comment for the contract.
|
|
240
|
+
this.chatCompressedTimer = setTimeout(() => this.flushChatCompressed(), exports.RATE_LIMIT_WINDOW_MS);
|
|
241
|
+
if (typeof this.chatCompressedTimer.unref === 'function') {
|
|
242
|
+
this.chatCompressedTimer.unref();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
this.recentChat.push(now);
|
|
248
|
+
}
|
|
249
|
+
// §8 ensemble-wide 50/s cap — emit `throttled` once + suppress
|
|
250
|
+
// non-essential streams for 1 s.
|
|
251
|
+
if (NON_ESSENTIAL_AFTER_THROTTLE.has(type) && now < this.nonEssentialSuppressUntil) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
this.trimWindow(this.recentEmits, now);
|
|
255
|
+
if (!ESSENTIAL_TYPES.has(type) && this.recentEmits.length >= this.ensembleRateLimit) {
|
|
256
|
+
// Drop this non-essential event and emit `throttled` (capped per window).
|
|
257
|
+
if (now - this.lastThrottledAt >= exports.THROTTLE_SUPPRESS_MS) {
|
|
258
|
+
this.lastThrottledAt = now;
|
|
259
|
+
this.nonEssentialSuppressUntil = now + exports.THROTTLE_SUPPRESS_MS;
|
|
260
|
+
const since = new Date(now - exports.RATE_LIMIT_WINDOW_MS).toISOString();
|
|
261
|
+
this.emitInternal('throttled', { droppedSince: since, count: 1 }, now);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
this.recentEmits.push(now);
|
|
266
|
+
return this.emitInternal(type, payload, now);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Internal emit — bypasses the §8 gates so the bus can self-emit
|
|
270
|
+
* `throttled` and `chat.compressed` while the gate is denying their
|
|
271
|
+
* trigger. Caller has already updated the rate-limit windows.
|
|
272
|
+
*/
|
|
273
|
+
emitInternal(type, payload, now) {
|
|
274
|
+
const { eventId, tuple } = this.allocator.next();
|
|
275
|
+
const bufferable = type !== 'heartbeat'; // §7.1 — heartbeats are not replayed.
|
|
276
|
+
const event = { eventId, tuple, type, payload, emittedAt: now, bufferable };
|
|
277
|
+
if (bufferable)
|
|
278
|
+
this.ring.push({ seq: tuple.seq, payload: event });
|
|
279
|
+
if (type !== 'heartbeat')
|
|
280
|
+
this.lastNonHeartbeatAt = now;
|
|
281
|
+
for (const sub of this.subs)
|
|
282
|
+
sub.push(event);
|
|
283
|
+
return { eventId };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Emit the deferred `chat.compressed` event with the accumulated
|
|
287
|
+
* drop count, then reset. Called by the `setTimeout` scheduled in
|
|
288
|
+
* the chat-rate-cap path; tests may invoke it directly to bypass
|
|
289
|
+
* `vi.advanceTimersByTime` plumbing.
|
|
290
|
+
*/
|
|
291
|
+
flushChatCompressed() {
|
|
292
|
+
this.chatCompressedTimer = null;
|
|
293
|
+
if (this.chatDropCount === 0)
|
|
294
|
+
return;
|
|
295
|
+
const dropped = this.chatDropCount;
|
|
296
|
+
this.chatDropCount = 0;
|
|
297
|
+
const now = this.now();
|
|
298
|
+
this.emitInternal('chat.compressed', {
|
|
299
|
+
dropped,
|
|
300
|
+
since: new Date(now - exports.RATE_LIMIT_WINDOW_MS).toISOString(),
|
|
301
|
+
}, now);
|
|
302
|
+
}
|
|
303
|
+
/** Test seam — force a deferred `chat.compressed` to flush synchronously. */
|
|
304
|
+
_flushChatCompressedForTest() {
|
|
305
|
+
if (this.chatCompressedTimer) {
|
|
306
|
+
clearTimeout(this.chatCompressedTimer);
|
|
307
|
+
this.chatCompressedTimer = null;
|
|
308
|
+
}
|
|
309
|
+
this.flushChatCompressed();
|
|
310
|
+
}
|
|
311
|
+
replayFrom(afterSeq) {
|
|
312
|
+
return this.ring.sliceFrom(afterSeq + 1).map((b) => b.payload);
|
|
313
|
+
}
|
|
314
|
+
subscribe(opts = {}) {
|
|
315
|
+
const sub = new Subscription(opts);
|
|
316
|
+
if (this.closed) {
|
|
317
|
+
// New subscriber on a closed bus — yield no events, just close.
|
|
318
|
+
sub.close();
|
|
319
|
+
return sub;
|
|
320
|
+
}
|
|
321
|
+
if (typeof opts.afterSeq === 'number') {
|
|
322
|
+
const replay = this.replayFrom(opts.afterSeq);
|
|
323
|
+
for (const e of replay)
|
|
324
|
+
sub.push(e);
|
|
325
|
+
}
|
|
326
|
+
sub.onClose = () => { this.subs.delete(sub); };
|
|
327
|
+
this.subs.add(sub);
|
|
328
|
+
return sub;
|
|
329
|
+
}
|
|
330
|
+
tickHeartbeat() {
|
|
331
|
+
return this.emit('heartbeat', { at: new Date(this.now()).toISOString() });
|
|
332
|
+
}
|
|
333
|
+
close() {
|
|
334
|
+
if (this.closed)
|
|
335
|
+
return;
|
|
336
|
+
this.closed = true;
|
|
337
|
+
if (this.chatCompressedTimer) {
|
|
338
|
+
clearTimeout(this.chatCompressedTimer);
|
|
339
|
+
this.chatCompressedTimer = null;
|
|
340
|
+
}
|
|
341
|
+
for (const sub of [...this.subs])
|
|
342
|
+
sub.close();
|
|
343
|
+
this.subs.clear();
|
|
344
|
+
}
|
|
345
|
+
/** Drop entries older than `now - RATE_LIMIT_WINDOW_MS` from a sliding window. */
|
|
346
|
+
trimWindow(window, now) {
|
|
347
|
+
const cutoff = now - exports.RATE_LIMIT_WINDOW_MS;
|
|
348
|
+
while (window.length > 0 && window[0] < cutoff)
|
|
349
|
+
window.shift();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
exports.EnsembleEventBus = EnsembleEventBus;
|
|
353
|
+
/**
|
|
354
|
+
* Drive the heartbeat on a real timer. Returns a stop function. Call
|
|
355
|
+
* sites pass the bus's own `tickHeartbeat` so test wiring can stay
|
|
356
|
+
* synchronous.
|
|
357
|
+
*/
|
|
358
|
+
function startHeartbeatTimer(bus, intervalMs = exports.DEFAULT_HEARTBEAT_MS) {
|
|
359
|
+
const handle = setInterval(() => { bus.tickHeartbeat(); }, intervalMs);
|
|
360
|
+
// Don't keep the daemon alive solely on the heartbeat — workers + HTTP
|
|
361
|
+
// listener are the durable long-running references.
|
|
362
|
+
if (typeof handle.unref === 'function')
|
|
363
|
+
handle.unref();
|
|
364
|
+
return () => clearInterval(handle);
|
|
365
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<bootEpoch>:<seq>` event-id allocator (SSE-PROTOCOL.md §5).
|
|
3
|
+
*
|
|
4
|
+
* **Format**: `"<bootEpoch>:<seq>"`. Two decimal-ASCII parts separated by
|
|
5
|
+
* a single `:`. `bootEpoch` is the daemon process boot time as Unix
|
|
6
|
+
* epoch milliseconds, frozen for the process lifetime. `seq` is a
|
|
7
|
+
* uint64 monotonic counter (starting at `0`) owned by a specific bus.
|
|
8
|
+
*
|
|
9
|
+
* **Why**: a daemon restart must NOT silently lose the snapshot bridge.
|
|
10
|
+
* Without an epoch prefix, a client reconnecting with `Last-Event-ID:
|
|
11
|
+
* 1234` after a daemon restart would be `seq > ringStart=0` and the
|
|
12
|
+
* server would try to replay events that don't exist. The two-stage
|
|
13
|
+
* gate (epoch first → `gap epoch-mismatch`, then seq → `gap overflow`)
|
|
14
|
+
* fixes that.
|
|
15
|
+
*
|
|
16
|
+
* **Scope**: `bootEpoch` is process-global. `seq` is per-bus —
|
|
17
|
+
* `EnsembleEventBus` owns one counter per ensemble; the global bus
|
|
18
|
+
* owns its own. Both v2 upgrade paths in ADR 0004 preserve this
|
|
19
|
+
* contract through the `EnsembleEventBus` interface.
|
|
20
|
+
*/
|
|
21
|
+
/** Parsed `(epoch, seq)` tuple. */
|
|
22
|
+
export interface EventIdTuple {
|
|
23
|
+
epoch: number;
|
|
24
|
+
seq: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Format an `(epoch, seq)` tuple to the wire token. Both components
|
|
28
|
+
* MUST be non-negative integers; callers should never reach for
|
|
29
|
+
* floating-point arithmetic on these values.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatEventId(epoch: number, seq: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a wire token back into a tuple. Returns `null` for malformed
|
|
34
|
+
* input — caller treats that the same as an `epoch-mismatch` gap (the
|
|
35
|
+
* client is from a previous daemon process / a corrupt header / etc.).
|
|
36
|
+
*
|
|
37
|
+
* Permissive on whitespace because some HTTP intermediaries normalize
|
|
38
|
+
* `Last-Event-ID`; rejects anything outside `[0-9]:[0-9]`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseEventId(token: string | undefined | null): EventIdTuple | null;
|
|
41
|
+
/**
|
|
42
|
+
* Compare two event-id tuples lexicographically — epoch first, then seq.
|
|
43
|
+
* Returns negative / zero / positive following the standard
|
|
44
|
+
* comparator convention.
|
|
45
|
+
*/
|
|
46
|
+
export declare function compareEventIds(a: EventIdTuple, b: EventIdTuple): number;
|
|
47
|
+
/**
|
|
48
|
+
* Allocates `seq` values for one bus. Each call to {@link next} returns
|
|
49
|
+
* the next monotonic id token bound to the configured `bootEpoch`.
|
|
50
|
+
*
|
|
51
|
+
* `bootEpoch` is supplied by the caller — the daemon constructs one
|
|
52
|
+
* value (`Date.now()`) and threads it through every bus instance so
|
|
53
|
+
* every event in the same process lifetime shares the prefix.
|
|
54
|
+
*
|
|
55
|
+
* **Overflow**: `seq` is a JavaScript `number`, safe up to
|
|
56
|
+
* `Number.MAX_SAFE_INTEGER` (2^53 − 1). At a sustained 1 event/ms
|
|
57
|
+
* that's ~285,000 years before wrap, so practically unreachable.
|
|
58
|
+
* If a single bus ever did approach this limit, the bus would need
|
|
59
|
+
* to `continueAsNew`-style re-bootstrap with a fresh `bootEpoch` —
|
|
60
|
+
* documented here so the assumption is on the record rather than
|
|
61
|
+
* implicit in the type.
|
|
62
|
+
*/
|
|
63
|
+
export declare class SeqAllocator {
|
|
64
|
+
readonly bootEpoch: number;
|
|
65
|
+
private nextSeq;
|
|
66
|
+
constructor(bootEpoch: number);
|
|
67
|
+
/** Allocate the next `(epoch, seq)` and return the formatted token. */
|
|
68
|
+
next(): {
|
|
69
|
+
eventId: string;
|
|
70
|
+
tuple: EventIdTuple;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* The seq the next allocation will use — useful for ring buffer
|
|
74
|
+
* `ringStart` math without having to peek into a buffer.
|
|
75
|
+
*/
|
|
76
|
+
peekNextSeq(): number;
|
|
77
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `<bootEpoch>:<seq>` event-id allocator (SSE-PROTOCOL.md §5).
|
|
4
|
+
*
|
|
5
|
+
* **Format**: `"<bootEpoch>:<seq>"`. Two decimal-ASCII parts separated by
|
|
6
|
+
* a single `:`. `bootEpoch` is the daemon process boot time as Unix
|
|
7
|
+
* epoch milliseconds, frozen for the process lifetime. `seq` is a
|
|
8
|
+
* uint64 monotonic counter (starting at `0`) owned by a specific bus.
|
|
9
|
+
*
|
|
10
|
+
* **Why**: a daemon restart must NOT silently lose the snapshot bridge.
|
|
11
|
+
* Without an epoch prefix, a client reconnecting with `Last-Event-ID:
|
|
12
|
+
* 1234` after a daemon restart would be `seq > ringStart=0` and the
|
|
13
|
+
* server would try to replay events that don't exist. The two-stage
|
|
14
|
+
* gate (epoch first → `gap epoch-mismatch`, then seq → `gap overflow`)
|
|
15
|
+
* fixes that.
|
|
16
|
+
*
|
|
17
|
+
* **Scope**: `bootEpoch` is process-global. `seq` is per-bus —
|
|
18
|
+
* `EnsembleEventBus` owns one counter per ensemble; the global bus
|
|
19
|
+
* owns its own. Both v2 upgrade paths in ADR 0004 preserve this
|
|
20
|
+
* contract through the `EnsembleEventBus` interface.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.SeqAllocator = void 0;
|
|
24
|
+
exports.formatEventId = formatEventId;
|
|
25
|
+
exports.parseEventId = parseEventId;
|
|
26
|
+
exports.compareEventIds = compareEventIds;
|
|
27
|
+
/**
|
|
28
|
+
* Format an `(epoch, seq)` tuple to the wire token. Both components
|
|
29
|
+
* MUST be non-negative integers; callers should never reach for
|
|
30
|
+
* floating-point arithmetic on these values.
|
|
31
|
+
*/
|
|
32
|
+
function formatEventId(epoch, seq) {
|
|
33
|
+
if (!Number.isInteger(epoch) || epoch < 0) {
|
|
34
|
+
throw new Error(`formatEventId: invalid epoch ${epoch}`);
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isInteger(seq) || seq < 0) {
|
|
37
|
+
throw new Error(`formatEventId: invalid seq ${seq}`);
|
|
38
|
+
}
|
|
39
|
+
return `${epoch}:${seq}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a wire token back into a tuple. Returns `null` for malformed
|
|
43
|
+
* input — caller treats that the same as an `epoch-mismatch` gap (the
|
|
44
|
+
* client is from a previous daemon process / a corrupt header / etc.).
|
|
45
|
+
*
|
|
46
|
+
* Permissive on whitespace because some HTTP intermediaries normalize
|
|
47
|
+
* `Last-Event-ID`; rejects anything outside `[0-9]:[0-9]`.
|
|
48
|
+
*/
|
|
49
|
+
function parseEventId(token) {
|
|
50
|
+
if (token == null)
|
|
51
|
+
return null;
|
|
52
|
+
const trimmed = token.trim();
|
|
53
|
+
const m = /^(\d+):(\d+)$/.exec(trimmed);
|
|
54
|
+
if (!m)
|
|
55
|
+
return null;
|
|
56
|
+
const epoch = Number(m[1]);
|
|
57
|
+
const seq = Number(m[2]);
|
|
58
|
+
if (!Number.isFinite(epoch) || !Number.isFinite(seq))
|
|
59
|
+
return null;
|
|
60
|
+
if (!Number.isInteger(epoch) || !Number.isInteger(seq))
|
|
61
|
+
return null;
|
|
62
|
+
return { epoch, seq };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Compare two event-id tuples lexicographically — epoch first, then seq.
|
|
66
|
+
* Returns negative / zero / positive following the standard
|
|
67
|
+
* comparator convention.
|
|
68
|
+
*/
|
|
69
|
+
function compareEventIds(a, b) {
|
|
70
|
+
if (a.epoch !== b.epoch)
|
|
71
|
+
return a.epoch < b.epoch ? -1 : 1;
|
|
72
|
+
if (a.seq !== b.seq)
|
|
73
|
+
return a.seq < b.seq ? -1 : 1;
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Allocates `seq` values for one bus. Each call to {@link next} returns
|
|
78
|
+
* the next monotonic id token bound to the configured `bootEpoch`.
|
|
79
|
+
*
|
|
80
|
+
* `bootEpoch` is supplied by the caller — the daemon constructs one
|
|
81
|
+
* value (`Date.now()`) and threads it through every bus instance so
|
|
82
|
+
* every event in the same process lifetime shares the prefix.
|
|
83
|
+
*
|
|
84
|
+
* **Overflow**: `seq` is a JavaScript `number`, safe up to
|
|
85
|
+
* `Number.MAX_SAFE_INTEGER` (2^53 − 1). At a sustained 1 event/ms
|
|
86
|
+
* that's ~285,000 years before wrap, so practically unreachable.
|
|
87
|
+
* If a single bus ever did approach this limit, the bus would need
|
|
88
|
+
* to `continueAsNew`-style re-bootstrap with a fresh `bootEpoch` —
|
|
89
|
+
* documented here so the assumption is on the record rather than
|
|
90
|
+
* implicit in the type.
|
|
91
|
+
*/
|
|
92
|
+
class SeqAllocator {
|
|
93
|
+
bootEpoch;
|
|
94
|
+
nextSeq = 0;
|
|
95
|
+
constructor(bootEpoch) {
|
|
96
|
+
this.bootEpoch = bootEpoch;
|
|
97
|
+
if (!Number.isInteger(bootEpoch) || bootEpoch < 0) {
|
|
98
|
+
throw new Error(`SeqAllocator: invalid bootEpoch ${bootEpoch}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Allocate the next `(epoch, seq)` and return the formatted token. */
|
|
102
|
+
next() {
|
|
103
|
+
const seq = this.nextSeq++;
|
|
104
|
+
return {
|
|
105
|
+
eventId: formatEventId(this.bootEpoch, seq),
|
|
106
|
+
tuple: { epoch: this.bootEpoch, seq },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The seq the next allocation will use — useful for ring buffer
|
|
111
|
+
* `ringStart` math without having to peek into a buffer.
|
|
112
|
+
*/
|
|
113
|
+
peekNextSeq() {
|
|
114
|
+
return this.nextSeq;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.SeqAllocator = SeqAllocator;
|