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,632 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DAEMON_START_TIMEOUT_MS = exports.STALE_LOCK_MS = exports.DAEMON_LOCK_PATH = exports.HEARTBEAT_STALE_MULTIPLIER = exports.HEARTBEAT_INTERVAL_MS = exports.DAEMON_HEARTBEAT_PATH = exports.DAEMON_LOG_PATH = exports.DAEMON_PID_PATH = void 0;
|
|
37
|
+
exports.isDaemonRunning = isDaemonRunning;
|
|
38
|
+
exports.getDaemonStatus = getDaemonStatus;
|
|
39
|
+
exports.selectOrphans = selectOrphans;
|
|
40
|
+
exports.otherProfileHome = otherProfileHome;
|
|
41
|
+
exports.getOtherProfilePid = getOtherProfilePid;
|
|
42
|
+
exports.isOtherProfileLikelyRunning = isOtherProfileLikelyRunning;
|
|
43
|
+
exports.tryAcquireLockFile = tryAcquireLockFile;
|
|
44
|
+
exports.checkLockFileStale = checkLockFileStale;
|
|
45
|
+
exports.startDaemon = startDaemon;
|
|
46
|
+
exports.scanAgentTempoDaemons = scanAgentTempoDaemons;
|
|
47
|
+
exports.stopDaemon = stopDaemon;
|
|
48
|
+
/**
|
|
49
|
+
* Daemon process management utilities.
|
|
50
|
+
*
|
|
51
|
+
* The daemon runs Temporal workers in a detached background process,
|
|
52
|
+
* replacing the per-session workers. MCP sessions become pure clients.
|
|
53
|
+
*/
|
|
54
|
+
const fs = __importStar(require("fs"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const child_process_1 = require("child_process");
|
|
57
|
+
const os_1 = require("os");
|
|
58
|
+
const config_1 = require("../config");
|
|
59
|
+
const log = (...args) => console.error('[agent-tempo:daemon]', ...args);
|
|
60
|
+
exports.DAEMON_PID_PATH = path.join(config_1.AGENT_TEMPO_HOME, 'daemon.pid');
|
|
61
|
+
exports.DAEMON_LOG_PATH = path.join(config_1.AGENT_TEMPO_HOME, 'daemon.log');
|
|
62
|
+
/**
|
|
63
|
+
* Path to the daemon heartbeat file. The running daemon touches this file
|
|
64
|
+
* on a {@link HEARTBEAT_INTERVAL_MS} cadence so `daemon status` can
|
|
65
|
+
* distinguish "pid is alive AND main loop is serving" from "pid is alive
|
|
66
|
+
* but something hung" (#157 diagnostic improvement).
|
|
67
|
+
*/
|
|
68
|
+
exports.DAEMON_HEARTBEAT_PATH = path.join(config_1.AGENT_TEMPO_HOME, 'daemon.heartbeat');
|
|
69
|
+
/** How often the daemon touches {@link DAEMON_HEARTBEAT_PATH}. */
|
|
70
|
+
exports.HEARTBEAT_INTERVAL_MS = 60_000;
|
|
71
|
+
/**
|
|
72
|
+
* Multiplier applied to {@link HEARTBEAT_INTERVAL_MS} to decide whether a
|
|
73
|
+
* heartbeat is stale. A 2x buffer tolerates one missed tick (brief GC pause,
|
|
74
|
+
* synchronous activity) without false-flagging a healthy daemon.
|
|
75
|
+
*/
|
|
76
|
+
exports.HEARTBEAT_STALE_MULTIPLIER = 2;
|
|
77
|
+
/** Entry point for the daemon process (compiled JS). */
|
|
78
|
+
const DAEMON_ENTRY_PATH = path.resolve(__dirname, '..', 'daemon.js');
|
|
79
|
+
/**
|
|
80
|
+
* Probe whether a PID refers to a live process using `kill(pid, 0)`.
|
|
81
|
+
*
|
|
82
|
+
* EPERM is reported on Windows for foreign (UAC-isolated) processes and
|
|
83
|
+
* must be treated as "alive" — we just can't signal it. Anything else
|
|
84
|
+
* (ESRCH, ENOENT, invalid-pid) means the process is gone.
|
|
85
|
+
*/
|
|
86
|
+
function isPidAlive(pid) {
|
|
87
|
+
try {
|
|
88
|
+
process.kill(pid, 0);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return err.code === 'EPERM';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if the daemon is running by reading the PID file and probing the process.
|
|
97
|
+
* Cleans up stale PID files automatically.
|
|
98
|
+
*/
|
|
99
|
+
function isDaemonRunning() {
|
|
100
|
+
return getDaemonStatus().running;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get daemon status: running state and PID (if available).
|
|
104
|
+
*/
|
|
105
|
+
function getDaemonStatus() {
|
|
106
|
+
if (!fs.existsSync(exports.DAEMON_PID_PATH)) {
|
|
107
|
+
return { running: false };
|
|
108
|
+
}
|
|
109
|
+
let pid;
|
|
110
|
+
try {
|
|
111
|
+
pid = parseInt(fs.readFileSync(exports.DAEMON_PID_PATH, 'utf8').trim(), 10);
|
|
112
|
+
if (isNaN(pid)) {
|
|
113
|
+
fs.unlinkSync(exports.DAEMON_PID_PATH);
|
|
114
|
+
return { running: false };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { running: false };
|
|
119
|
+
}
|
|
120
|
+
if (isPidAlive(pid)) {
|
|
121
|
+
return { running: true, pid, heartbeatAge: readHeartbeatAge() };
|
|
122
|
+
}
|
|
123
|
+
// Process is dead — clean up stale PID file.
|
|
124
|
+
try {
|
|
125
|
+
fs.unlinkSync(exports.DAEMON_PID_PATH);
|
|
126
|
+
}
|
|
127
|
+
catch { /* ignore */ }
|
|
128
|
+
return { running: false };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Read the heartbeat file and return milliseconds since its mtime, or `null`
|
|
132
|
+
* if the file doesn't exist. Used by {@link getDaemonStatus} when the daemon
|
|
133
|
+
* is running — see the `heartbeatAge` field on {@link DaemonStatus}.
|
|
134
|
+
*
|
|
135
|
+
* Any other read/stat error is treated as "unknown" (`null`) rather than
|
|
136
|
+
* propagating — `daemon status` should degrade to silent-unknown rather than
|
|
137
|
+
* crash on an unexpected filesystem condition.
|
|
138
|
+
*/
|
|
139
|
+
function readHeartbeatAge() {
|
|
140
|
+
try {
|
|
141
|
+
const mtimeMs = fs.statSync(exports.DAEMON_HEARTBEAT_PATH).mtimeMs;
|
|
142
|
+
const age = Date.now() - mtimeMs;
|
|
143
|
+
return age >= 0 ? age : 0;
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @internal — exported for unit testing {@link DAEMON_CMDLINE_RE} edge cases
|
|
151
|
+
* without needing to spin up a real child process. Filters a scanner result
|
|
152
|
+
* to the "orphan" subset: matching processes that aren't tracked by any
|
|
153
|
+
* known PID file. Used by `daemon start`'s pre-flight check (#157 PR B).
|
|
154
|
+
*
|
|
155
|
+
* Accepts a single tracked PID (existing call shape) OR an array of known
|
|
156
|
+
* PIDs (ADR 0014 §5.6 — the dev profile passes the prod profile's PID
|
|
157
|
+
* here too so dev + prod daemons can coexist on the same machine without
|
|
158
|
+
* either misclassifying the other as an orphan).
|
|
159
|
+
*/
|
|
160
|
+
function selectOrphans(scanned, trackedPid) {
|
|
161
|
+
if (trackedPid === undefined)
|
|
162
|
+
return scanned;
|
|
163
|
+
if (Array.isArray(trackedPid)) {
|
|
164
|
+
const known = new Set(trackedPid.filter((p) => typeof p === 'number'));
|
|
165
|
+
if (known.size === 0)
|
|
166
|
+
return scanned;
|
|
167
|
+
return scanned.filter((p) => !known.has(p.pid));
|
|
168
|
+
}
|
|
169
|
+
return scanned.filter((p) => p.pid !== trackedPid);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolve the OPPOSITE profile's home directory. Used by cross-profile
|
|
173
|
+
* coexistence checks (ADR 0014 §5.6). Returns the prod home in dev mode
|
|
174
|
+
* and the dev home in prod mode.
|
|
175
|
+
*
|
|
176
|
+
* Exported so tests can verify the swap logic without fixture homedirs.
|
|
177
|
+
*/
|
|
178
|
+
function otherProfileHome() {
|
|
179
|
+
const base = (0, config_1.isDevMode)() ? config_1.PROD_HOME_DIR_NAME : config_1.DEV_HOME_DIR_NAME;
|
|
180
|
+
return path.join((0, os_1.homedir)(), base);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Return the live PID (if any) of the OPPOSITE profile's daemon.
|
|
184
|
+
*
|
|
185
|
+
* In dev mode, this returns the prod daemon's PID; in prod mode, the dev
|
|
186
|
+
* daemon's PID. Used by orphan detection to keep dev and prod daemons
|
|
187
|
+
* from misclassifying each other (ADR 0014 §5.6 — "they naturally
|
|
188
|
+
* coexist"). Returns `undefined` when no PID file exists, the contents
|
|
189
|
+
* are unparseable, or the recorded process is dead.
|
|
190
|
+
*
|
|
191
|
+
* Exported for unit testing — can be stubbed alongside `getDaemonStatus`
|
|
192
|
+
* in `evaluateStartPreflight` tests.
|
|
193
|
+
*/
|
|
194
|
+
function getOtherProfilePid() {
|
|
195
|
+
const otherPidPath = path.join(otherProfileHome(), 'daemon.pid');
|
|
196
|
+
if (!fs.existsSync(otherPidPath))
|
|
197
|
+
return undefined;
|
|
198
|
+
try {
|
|
199
|
+
const pid = parseInt(fs.readFileSync(otherPidPath, 'utf8').trim(), 10);
|
|
200
|
+
if (Number.isNaN(pid))
|
|
201
|
+
return undefined;
|
|
202
|
+
return isPidAlive(pid) ? pid : undefined;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Best-effort signal that the OPPOSITE profile may have a running daemon
|
|
210
|
+
* even if we couldn't pin a PID. Used by `stopDaemon` to suppress the
|
|
211
|
+
* zombie reaper when there's a non-trivial chance of cross-profile
|
|
212
|
+
* collateral damage (ADR 0014 §5.6).
|
|
213
|
+
*
|
|
214
|
+
* Returns true when either:
|
|
215
|
+
* - `daemon.pid` exists in the other profile's home AND contains a
|
|
216
|
+
* parseable, live PID (the strict case, also reported by
|
|
217
|
+
* {@link getOtherProfilePid}).
|
|
218
|
+
* - `daemon.port` exists in the other profile's home (weak signal:
|
|
219
|
+
* the file is written on daemon HTTP listener startup; it may
|
|
220
|
+
* linger across an uncleaned crash, but that's exactly the case
|
|
221
|
+
* where conservative behaviour matters).
|
|
222
|
+
*
|
|
223
|
+
* The heuristic deliberately errs on the side of NOT reaping. A false
|
|
224
|
+
* positive (port file exists, daemon long dead) costs zombie cleanup;
|
|
225
|
+
* a false negative (process killed because we missed evidence) costs
|
|
226
|
+
* the user their other profile's daemon.
|
|
227
|
+
*/
|
|
228
|
+
function isOtherProfileLikelyRunning() {
|
|
229
|
+
if (getOtherProfilePid() !== undefined)
|
|
230
|
+
return true;
|
|
231
|
+
const otherPortPath = path.join(otherProfileHome(), 'daemon.port');
|
|
232
|
+
return fs.existsSync(otherPortPath);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Start the daemon process. If already running, returns the existing PID.
|
|
236
|
+
*
|
|
237
|
+
* The daemon is spawned as a detached child process with stdout/stderr
|
|
238
|
+
* redirected to a log file. Config is passed via environment variables.
|
|
239
|
+
*/
|
|
240
|
+
/** Lock file path for preventing concurrent daemon starts. */
|
|
241
|
+
exports.DAEMON_LOCK_PATH = exports.DAEMON_PID_PATH + '.lock';
|
|
242
|
+
/**
|
|
243
|
+
* How long a start lock can sit on disk before we treat it as abandoned
|
|
244
|
+
* regardless of whether the recorded PID appears alive. Daemon startup
|
|
245
|
+
* should never take this long; longer values just delay recovery after a
|
|
246
|
+
* crashed starter (see issue #182).
|
|
247
|
+
*/
|
|
248
|
+
exports.STALE_LOCK_MS = 30_000;
|
|
249
|
+
/**
|
|
250
|
+
* Timeout for waiting on another starter's PID file — generous window for
|
|
251
|
+
* cold-start slack on fresh installs (npm-global, uncompiled native deps).
|
|
252
|
+
* Bumped from 10s per researcher recommendation for issue #182.
|
|
253
|
+
*/
|
|
254
|
+
exports.DAEMON_START_TIMEOUT_MS = 30_000;
|
|
255
|
+
/**
|
|
256
|
+
* Attempt to atomically create the start lock with {pid, mtime} contents.
|
|
257
|
+
*
|
|
258
|
+
* Returns `true` on success, `false` if the lock already exists. Any other
|
|
259
|
+
* error (EACCES, ENOSPC, …) propagates. Exported for unit testing the
|
|
260
|
+
* acquire path without spawning a real daemon.
|
|
261
|
+
*/
|
|
262
|
+
function tryAcquireLockFile(lockPath, pid = process.pid, now = Date.now()) {
|
|
263
|
+
try {
|
|
264
|
+
const content = { pid, mtime: now };
|
|
265
|
+
// `wx` = atomic create-or-fail with EEXIST on conflict. Combined with
|
|
266
|
+
// writeFileSync, this writes the content in the same syscall sequence,
|
|
267
|
+
// so a racing reader never observes a zero-byte lock.
|
|
268
|
+
fs.writeFileSync(lockPath, JSON.stringify(content), { flag: 'wx' });
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (err.code === 'EEXIST')
|
|
273
|
+
return false;
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Inspect a start lock and decide whether it's abandoned.
|
|
279
|
+
*
|
|
280
|
+
* A lock is stale when any of:
|
|
281
|
+
* - the file is missing (treat as stale so callers retry immediately)
|
|
282
|
+
* - the contents are malformed (not JSON, missing pid/mtime)
|
|
283
|
+
* - mtime is older than {@link STALE_LOCK_MS}
|
|
284
|
+
* - `process.kill(pid, 0)` reports ESRCH (or anything other than EPERM)
|
|
285
|
+
*
|
|
286
|
+
* EPERM on the PID probe is treated as "alive" to match
|
|
287
|
+
* {@link getDaemonStatus} — on Windows, UAC-isolated foreign processes
|
|
288
|
+
* return EPERM for a running PID, so we must not interpret it as dead.
|
|
289
|
+
*
|
|
290
|
+
* Exported for unit testing.
|
|
291
|
+
*/
|
|
292
|
+
function checkLockFileStale(lockPath, now = Date.now()) {
|
|
293
|
+
let raw;
|
|
294
|
+
try {
|
|
295
|
+
raw = fs.readFileSync(lockPath, 'utf8');
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
const code = err.code;
|
|
299
|
+
if (code === 'ENOENT') {
|
|
300
|
+
return { stale: true, reason: 'lock file missing', content: null };
|
|
301
|
+
}
|
|
302
|
+
return { stale: true, reason: `lock file unreadable (${code ?? 'unknown'})`, content: null };
|
|
303
|
+
}
|
|
304
|
+
let content = null;
|
|
305
|
+
try {
|
|
306
|
+
const parsed = JSON.parse(raw);
|
|
307
|
+
if (typeof parsed?.pid === 'number' && typeof parsed?.mtime === 'number') {
|
|
308
|
+
content = { pid: parsed.pid, mtime: parsed.mtime };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// fall through — malformed
|
|
313
|
+
}
|
|
314
|
+
if (!content) {
|
|
315
|
+
const preview = raw.length > 80 ? raw.slice(0, 80) + '…' : raw;
|
|
316
|
+
return {
|
|
317
|
+
stale: true,
|
|
318
|
+
reason: `malformed lock content: ${JSON.stringify(preview)}`,
|
|
319
|
+
content: null,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const ageMs = now - content.mtime;
|
|
323
|
+
if (ageMs > exports.STALE_LOCK_MS) {
|
|
324
|
+
return {
|
|
325
|
+
stale: true,
|
|
326
|
+
reason: `mtime ${Math.round(ageMs / 1000)}s old (>${exports.STALE_LOCK_MS / 1000}s threshold)`,
|
|
327
|
+
content,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (isPidAlive(content.pid)) {
|
|
331
|
+
return {
|
|
332
|
+
stale: false,
|
|
333
|
+
reason: `pid ${content.pid} alive, mtime ${ageMs}ms old`,
|
|
334
|
+
content,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
stale: true,
|
|
339
|
+
reason: `pid ${content.pid} not alive`,
|
|
340
|
+
content,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Wait for the PID file to appear and contain a valid, live PID.
|
|
345
|
+
* Used both by the spawning process and by processes waiting on the lock.
|
|
346
|
+
*/
|
|
347
|
+
async function waitForDaemonPid(timeoutMs) {
|
|
348
|
+
const POLL_MS = 200;
|
|
349
|
+
const deadline = Date.now() + timeoutMs;
|
|
350
|
+
while (Date.now() < deadline) {
|
|
351
|
+
await new Promise(resolve => setTimeout(resolve, POLL_MS));
|
|
352
|
+
if (fs.existsSync(exports.DAEMON_PID_PATH)) {
|
|
353
|
+
const status = getDaemonStatus();
|
|
354
|
+
if (status.running && status.pid) {
|
|
355
|
+
return status.pid;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
throw new Error('Daemon did not start within timeout. Check logs: ' + exports.DAEMON_LOG_PATH);
|
|
360
|
+
}
|
|
361
|
+
async function startDaemon(config) {
|
|
362
|
+
// Check if already running
|
|
363
|
+
const status = getDaemonStatus();
|
|
364
|
+
if (status.running && status.pid) {
|
|
365
|
+
log(`Daemon already running (pid ${status.pid})`);
|
|
366
|
+
return status.pid;
|
|
367
|
+
}
|
|
368
|
+
// Ensure daemon directory exists
|
|
369
|
+
fs.mkdirSync(config_1.AGENT_TEMPO_HOME, { recursive: true });
|
|
370
|
+
// Acquire exclusive start lock. If another process holds the lock, decide
|
|
371
|
+
// whether to wait for it (live starter) or auto-repair it (stale from a
|
|
372
|
+
// crashed prior attempt — issue #182).
|
|
373
|
+
let acquired = tryAcquireLockFile(exports.DAEMON_LOCK_PATH);
|
|
374
|
+
if (!acquired) {
|
|
375
|
+
const state = checkLockFileStale(exports.DAEMON_LOCK_PATH);
|
|
376
|
+
if (state.stale) {
|
|
377
|
+
// Loud log — silent self-healing hides bugs. If this fires repeatedly
|
|
378
|
+
// it's a signal that daemon startup is crashing before PID-file write.
|
|
379
|
+
log(`⚠️ Stale daemon start lock detected — auto-repairing (${state.reason})`);
|
|
380
|
+
if (state.content) {
|
|
381
|
+
log(`⚠️ Lock contents: pid=${state.content.pid}, mtime=${new Date(state.content.mtime).toISOString()}`);
|
|
382
|
+
}
|
|
383
|
+
log('⚠️ If this repeats, inspect ' + exports.DAEMON_LOG_PATH + ' for startup crashes.');
|
|
384
|
+
try {
|
|
385
|
+
fs.unlinkSync(exports.DAEMON_LOCK_PATH);
|
|
386
|
+
}
|
|
387
|
+
catch { /* already gone */ }
|
|
388
|
+
// Retry once. If we still lose, another process won the recovery race —
|
|
389
|
+
// fall through to the wait path.
|
|
390
|
+
acquired = tryAcquireLockFile(exports.DAEMON_LOCK_PATH);
|
|
391
|
+
if (!acquired) {
|
|
392
|
+
log('Lost stale-lock recovery race to another starter — waiting for its daemon...');
|
|
393
|
+
return waitForDaemonPid(exports.DAEMON_START_TIMEOUT_MS);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// Healthy concurrent starter — wait for its PID file.
|
|
398
|
+
log(`Another process is starting the daemon (${state.reason}) — waiting...`);
|
|
399
|
+
return waitForDaemonPid(exports.DAEMON_START_TIMEOUT_MS);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
// Re-check after acquiring lock — daemon may have started between our
|
|
404
|
+
// initial check and lock acquisition
|
|
405
|
+
const recheck = getDaemonStatus();
|
|
406
|
+
if (recheck.running && recheck.pid) {
|
|
407
|
+
log(`Daemon already running (pid ${recheck.pid})`);
|
|
408
|
+
return recheck.pid;
|
|
409
|
+
}
|
|
410
|
+
// Open log file for daemon stdout/stderr
|
|
411
|
+
const logFd = fs.openSync(exports.DAEMON_LOG_PATH, 'a');
|
|
412
|
+
// Pass Temporal config to daemon via environment variables
|
|
413
|
+
const env = {
|
|
414
|
+
...process.env,
|
|
415
|
+
[config_1.ENV.TEMPORAL_ADDRESS]: config.temporalAddress,
|
|
416
|
+
[config_1.ENV.TEMPORAL_NAMESPACE]: config.temporalNamespace,
|
|
417
|
+
[config_1.ENV.TASK_QUEUE]: config.taskQueue,
|
|
418
|
+
};
|
|
419
|
+
if (config.temporalApiKey)
|
|
420
|
+
env[config_1.ENV.TEMPORAL_API_KEY] = config.temporalApiKey;
|
|
421
|
+
if (config.temporalTlsCertPath)
|
|
422
|
+
env[config_1.ENV.TEMPORAL_TLS_CERT_PATH] = config.temporalTlsCertPath;
|
|
423
|
+
if (config.temporalTlsKeyPath)
|
|
424
|
+
env[config_1.ENV.TEMPORAL_TLS_KEY_PATH] = config.temporalTlsKeyPath;
|
|
425
|
+
const child = (0, child_process_1.spawn)(process.execPath, [DAEMON_ENTRY_PATH], {
|
|
426
|
+
detached: true,
|
|
427
|
+
stdio: ['ignore', logFd, logFd],
|
|
428
|
+
env,
|
|
429
|
+
});
|
|
430
|
+
child.unref();
|
|
431
|
+
fs.closeSync(logFd);
|
|
432
|
+
log(`Spawned daemon process (pid ${child.pid})`);
|
|
433
|
+
// Wait for PID file to appear (daemon writes it on startup). Generous
|
|
434
|
+
// timeout for cold-start slack on fresh installs (#182).
|
|
435
|
+
return await waitForDaemonPid(exports.DAEMON_START_TIMEOUT_MS);
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
// Release lock. We no longer hold an fd — writeFileSync closed it for us.
|
|
439
|
+
try {
|
|
440
|
+
fs.unlinkSync(exports.DAEMON_LOCK_PATH);
|
|
441
|
+
}
|
|
442
|
+
catch { /* ignore */ }
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Matches a node process running the compiled daemon entry — both global-install
|
|
447
|
+
* paths (`...\agent-tempo\dist\daemon.js`) and dev-tree paths. Narrow enough to
|
|
448
|
+
* exclude unrelated `node` processes on the system; never force-kills based on
|
|
449
|
+
* this match alone (self-healing is gated by explicit user action per #157).
|
|
450
|
+
*/
|
|
451
|
+
const DAEMON_CMDLINE_RE = /\bnode(?:\.exe)?\b.*\bagent-tempo\b.*[\\/]dist[\\/]daemon\.js\b/i;
|
|
452
|
+
/**
|
|
453
|
+
* Shell out to the platform process list and return any matching daemon
|
|
454
|
+
* processes. Hand-rolled per-platform to avoid a new dependency:
|
|
455
|
+
* - Windows: PowerShell `Get-CimInstance Win32_Process` (modern, robust) with a
|
|
456
|
+
* `tasklist` fallback for environments without PowerShell on PATH
|
|
457
|
+
* - POSIX: `ps -eo pid,command` (portable across macOS + Linux)
|
|
458
|
+
*
|
|
459
|
+
* Returns `[]` on any scanner failure — this is a best-effort observability
|
|
460
|
+
* helper, not a correctness guarantee. Errors are swallowed so a broken
|
|
461
|
+
* scanner can't itself take down `daemon status`.
|
|
462
|
+
*
|
|
463
|
+
* Exported for testing with a stubbed executor.
|
|
464
|
+
*/
|
|
465
|
+
function scanAgentTempoDaemons(exec = (cmd, args) => (0, child_process_1.execFileSync)(cmd, args, { encoding: 'utf8', windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }), platform = process.platform) {
|
|
466
|
+
try {
|
|
467
|
+
if (platform === 'win32') {
|
|
468
|
+
return scanWindows(exec);
|
|
469
|
+
}
|
|
470
|
+
return scanPosix(exec);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return [];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/** Windows scan via PowerShell → CSV. Falls back to `wmic` if PowerShell is missing. */
|
|
477
|
+
function scanWindows(exec) {
|
|
478
|
+
// PowerShell CSV output puts ProcessId in col 0, CommandLine in col 1.
|
|
479
|
+
// `-NoProfile` avoids loading user profile scripts that would slow startup.
|
|
480
|
+
const ps = "Get-CimInstance -ClassName Win32_Process -Filter \"Name='node.exe'\" | " +
|
|
481
|
+
'Select-Object ProcessId,CommandLine | ConvertTo-Csv -NoTypeInformation';
|
|
482
|
+
try {
|
|
483
|
+
const out = exec('powershell', ['-NoProfile', '-NonInteractive', '-Command', ps]);
|
|
484
|
+
return parseCsvMatches(out);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// Fall through to wmic — legacy, still present on most Windows SKUs.
|
|
488
|
+
const out = exec('wmic', ['process', 'where', "name='node.exe'", 'get', 'ProcessId,CommandLine', '/format:csv']);
|
|
489
|
+
return parseCsvMatches(out);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/** POSIX scan via `ps -eo pid,command`. */
|
|
493
|
+
function scanPosix(exec) {
|
|
494
|
+
const out = exec('ps', ['-eo', 'pid,command']);
|
|
495
|
+
const matches = [];
|
|
496
|
+
for (const line of out.split('\n')) {
|
|
497
|
+
// Skip header row (" PID COMMAND").
|
|
498
|
+
const trimmed = line.trim();
|
|
499
|
+
if (!trimmed || /^pid\b/i.test(trimmed))
|
|
500
|
+
continue;
|
|
501
|
+
const m = /^(\d+)\s+(.+)$/.exec(trimmed);
|
|
502
|
+
if (!m)
|
|
503
|
+
continue;
|
|
504
|
+
const pid = parseInt(m[1], 10);
|
|
505
|
+
const commandLine = m[2];
|
|
506
|
+
if (!isNaN(pid) && DAEMON_CMDLINE_RE.test(commandLine) && pid !== process.pid) {
|
|
507
|
+
matches.push({ pid, commandLine });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return matches;
|
|
511
|
+
}
|
|
512
|
+
/** Parse CSV output from PowerShell's ConvertTo-Csv or wmic and filter matches. */
|
|
513
|
+
function parseCsvMatches(csv) {
|
|
514
|
+
const matches = [];
|
|
515
|
+
for (const line of csv.split(/\r?\n/)) {
|
|
516
|
+
// CSV rows: quoted fields separated by commas. Both PowerShell and wmic
|
|
517
|
+
// emit a header row + blank lines. Keep the parser permissive — we only
|
|
518
|
+
// need ProcessId (numeric) and a line that contains the daemon signature.
|
|
519
|
+
if (!line || /^"?ProcessId\b/i.test(line) || /^Node,Command/i.test(line))
|
|
520
|
+
continue;
|
|
521
|
+
// Find a plausible ProcessId (column order differs between PS and wmic:
|
|
522
|
+
// PS puts it first, wmic puts it last). Take the first numeric token
|
|
523
|
+
// that isn't obviously part of the command line (i.e. inside a long
|
|
524
|
+
// quoted path). In practice both formats surface the pid as a bare or
|
|
525
|
+
// single-quoted token unadjacent to path-like text.
|
|
526
|
+
const pidMatch = line.match(/(?:^|,)"?(\d+)"?(?=,|$)/);
|
|
527
|
+
if (!pidMatch)
|
|
528
|
+
continue;
|
|
529
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
530
|
+
if (isNaN(pid) || pid === process.pid)
|
|
531
|
+
continue;
|
|
532
|
+
// Match the daemon signature against the FULL LINE. CSV quoting (both
|
|
533
|
+
// PowerShell's doubled-quote `""` form and backslash-escape variants)
|
|
534
|
+
// doesn't hide the literal `node.exe` and `agent-tempo\dist\daemon.js`
|
|
535
|
+
// substrings from a substring regex — splitting into quoted fields would
|
|
536
|
+
// wrongly separate `node.exe` from `agent-tempo\dist\daemon.js` when
|
|
537
|
+
// they're in different CSV columns (e.g. `"node.exe" "...\daemon.js"`).
|
|
538
|
+
if (DAEMON_CMDLINE_RE.test(line)) {
|
|
539
|
+
matches.push({ pid, commandLine: line });
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return matches;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Send the platform-appropriate stop signal to a single daemon PID.
|
|
546
|
+
* Swallows errors — the process may have already exited, or be UAC-isolated
|
|
547
|
+
* on Windows. Returns `true` if `process.kill` succeeded, `false` otherwise.
|
|
548
|
+
*/
|
|
549
|
+
function killDaemonPid(pid, killer = process.kill.bind(process), platform = process.platform) {
|
|
550
|
+
try {
|
|
551
|
+
if (platform === 'win32') {
|
|
552
|
+
// Windows doesn't support SIGTERM — just kill the process.
|
|
553
|
+
killer(pid);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
killer(pid, 'SIGTERM');
|
|
557
|
+
}
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
catch {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Stop the daemon process by sending SIGTERM (or killing on Windows). In
|
|
566
|
+
* addition to the daemon tracked by the PID file, this also reaps any
|
|
567
|
+
* **zombie** daemons — `node dist/daemon.js` processes detected by
|
|
568
|
+
* {@link scanAgentTempoDaemons} that the PID file doesn't know about.
|
|
569
|
+
*
|
|
570
|
+
* Why reap zombies on every stop? When a prior daemon loses PID-file
|
|
571
|
+
* tracking (crashed `daemon stop`, manual PID-file delete, surviving across
|
|
572
|
+
* a `down --destroy` from before this fix), the orphan keeps polling
|
|
573
|
+
* Temporal task queues and executing activities — sometimes with stale code
|
|
574
|
+
* from before the most recent rebuild. That has caused real user-visible
|
|
575
|
+
* incidents where activities ran the pre-fix `resume: true` spawn path
|
|
576
|
+
* because a zombie daemon held the cached pre-rebuild code in memory.
|
|
577
|
+
*
|
|
578
|
+
* Returns `true` if at least one process (tracked or zombie) was stopped.
|
|
579
|
+
*/
|
|
580
|
+
function stopDaemon(opts = {}) {
|
|
581
|
+
const scan = opts.scan ?? scanAgentTempoDaemons;
|
|
582
|
+
const killer = opts.killer ?? process.kill.bind(process);
|
|
583
|
+
const platform = opts.platform ?? process.platform;
|
|
584
|
+
const otherLikelyRunning = opts.isOtherProfileLikelyRunning ?? isOtherProfileLikelyRunning;
|
|
585
|
+
const otherPidLookup = opts.getOtherProfilePid ?? getOtherProfilePid;
|
|
586
|
+
const status = getDaemonStatus();
|
|
587
|
+
let stopped = false;
|
|
588
|
+
// Kill the tracked daemon first (if any). We do this even if `kill` fails —
|
|
589
|
+
// the PID file is invariant we own, so we always clean it up.
|
|
590
|
+
if (status.running && status.pid !== undefined) {
|
|
591
|
+
killDaemonPid(status.pid, killer, platform);
|
|
592
|
+
try {
|
|
593
|
+
fs.unlinkSync(exports.DAEMON_PID_PATH);
|
|
594
|
+
}
|
|
595
|
+
catch { /* ignore */ }
|
|
596
|
+
stopped = true;
|
|
597
|
+
}
|
|
598
|
+
// Now reap any zombies. `selectOrphans` filters the scan to "everything
|
|
599
|
+
// except the PIDs we know about", so we don't double-signal the tracked
|
|
600
|
+
// daemon (harmless but cleaner).
|
|
601
|
+
//
|
|
602
|
+
// ADR 0014 §5.6 — when cross-profile coexistence is plausible (the
|
|
603
|
+
// OPPOSITE profile shows any sign of running: PID file, port file),
|
|
604
|
+
// suppress the zombie reaper entirely. The reaper is a #157 safety
|
|
605
|
+
// net for the SINGLE-profile case where a crashed daemon left an
|
|
606
|
+
// untracked node process behind. With dev + prod coexisting on the
|
|
607
|
+
// same machine, the reaper would happily kill the other profile's
|
|
608
|
+
// daemon — losing the user's prod state because they ran
|
|
609
|
+
// `--dev daemon stop`. The trade-off is acceptable: an actual zombie
|
|
610
|
+
// can be reaped later (manual `kill <pid>` based on the printed
|
|
611
|
+
// command line), but a wrongly-killed prod daemon costs the user
|
|
612
|
+
// their state.
|
|
613
|
+
let zombies = [];
|
|
614
|
+
if (otherLikelyRunning()) {
|
|
615
|
+
log('cross-profile coexistence detected — skipping zombie reaper to avoid touching the other profile (ADR 0014 §5.6)');
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
try {
|
|
619
|
+
zombies = selectOrphans(scan(), [status.pid, otherPidLookup()]);
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
// Scanner failures are non-fatal — we already did the primary stop above.
|
|
623
|
+
zombies = [];
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
for (const z of zombies) {
|
|
627
|
+
if (killDaemonPid(z.pid, killer, platform)) {
|
|
628
|
+
stopped = true;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return stopped;
|
|
632
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CliOverrides } from '../config';
|
|
2
|
+
export interface DashboardCommandArgs extends CliOverrides {
|
|
3
|
+
/** Override the daemon port (defaults to the port-file value). */
|
|
4
|
+
port?: number;
|
|
5
|
+
/** Override the daemon bind address (defaults to 127.0.0.1). */
|
|
6
|
+
bind?: string;
|
|
7
|
+
/** When true, print the URL but don't launch the browser. */
|
|
8
|
+
noOpen?: boolean;
|
|
9
|
+
/** Mint a single-use pairing token + show QR for cross-device. */
|
|
10
|
+
pair?: boolean;
|
|
11
|
+
/** Bearer to include on the pair-create POST. */
|
|
12
|
+
bearer?: string;
|
|
13
|
+
/** Machine-parseable output (single-line JSON). */
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Public entry point — invoked by the CLI dispatcher. Returns void;
|
|
18
|
+
* exits the process with code 1 on error, 0 on success.
|
|
19
|
+
*/
|
|
20
|
+
export declare function dashboardCommand(args: DashboardCommandArgs): Promise<void>;
|