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,21 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="dark" data-density="6" data-accent="terracotta">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="color-scheme" content="dark light" />
|
|
7
|
+
<meta name="description" content="agent-tempo Maestro Dashboard" />
|
|
8
|
+
<title>agent-tempo · Maestro</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
+
<link
|
|
12
|
+
rel="stylesheet"
|
|
13
|
+
href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap"
|
|
14
|
+
/>
|
|
15
|
+
<script type="module" crossorigin src="/dashboard/assets/index-_5jV0Znu.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-CB78ToNE.css">
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="root"></div>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-tempo-dashboard",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Web dashboard for agent-tempo. Bundled into the npm package; served by the daemon at /dashboard/*.",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite",
|
|
9
|
+
"build": "tsc -b && vite build",
|
|
10
|
+
"build:overflow": "tsc -b && vite build --mode overflow",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"lint": "eslint src/ tests/",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:e2e": "playwright test",
|
|
16
|
+
"test:e2e:install": "playwright install chromium",
|
|
17
|
+
"test:overflow": "playwright test --config tests-overflow/playwright.config.ts"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@radix-ui/react-dialog": "~1.1.15",
|
|
21
|
+
"@tanstack/react-query": "5.100.5",
|
|
22
|
+
"react": "19.2.5",
|
|
23
|
+
"react-dom": "19.2.5",
|
|
24
|
+
"react-router-dom": "7.14.2",
|
|
25
|
+
"zustand": "^5.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^9.0.0",
|
|
29
|
+
"@playwright/test": "^1.50.0",
|
|
30
|
+
"@tailwindcss/vite": "4.2.4",
|
|
31
|
+
"@testing-library/dom": "^10.0.0",
|
|
32
|
+
"@testing-library/jest-dom": "^6.6.0",
|
|
33
|
+
"@testing-library/react": "^16.1.0",
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"@types/react": "^19.2.0",
|
|
36
|
+
"@types/react-dom": "^19.2.0",
|
|
37
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
38
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
39
|
+
"eslint": "^9.0.0",
|
|
40
|
+
"eslint-plugin-react-hooks": "^5.1.0",
|
|
41
|
+
"jsdom": "^25.0.0",
|
|
42
|
+
"tailwindcss": "4.2.4",
|
|
43
|
+
"typescript": "^5.7.0",
|
|
44
|
+
"vite": "8.0.10",
|
|
45
|
+
"vitest": "^2.1.9"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AgentType } from '../types';
|
|
2
|
+
export interface HardTerminateInput {
|
|
3
|
+
/**
|
|
4
|
+
* Ensemble name. Load-bearing: matched against the `ENSEMBLE_SENTINEL_FLAG
|
|
5
|
+
* <ensemble>` pair in each candidate's CommandLine so two ensembles sharing a
|
|
6
|
+
* lineup template (identical player names) don't kill each other's processes
|
|
7
|
+
* on `destroy --all` (issue #180). The sentinel is injected by
|
|
8
|
+
* `src/activities/outbox.ts` on every Claude Code spawn. See src/constants.ts.
|
|
9
|
+
*/
|
|
10
|
+
ensemble: string;
|
|
11
|
+
/** Player name the session was spawned as. Matched against `claude.exe -n <name>` in the search path. */
|
|
12
|
+
playerName: string;
|
|
13
|
+
/** Adapter type — controls PID-file lookup and expected process-name verification. */
|
|
14
|
+
agent: AgentType;
|
|
15
|
+
/** Session's workDir — used to locate the copilot bridge PID file (`<workDir>/logs/<playerName>.pid`). */
|
|
16
|
+
workDir: string;
|
|
17
|
+
/** Optional explicit logDir override; falls back to `<workDir>/logs`. */
|
|
18
|
+
logDir?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface HardTerminateResult {
|
|
21
|
+
/** PIDs that were signaled/taskkilled. Empty on the "nothing to do" path. */
|
|
22
|
+
killedPids: number[];
|
|
23
|
+
/** Which code path produced the kill: PID-file lookup, command-line search, or no-op. */
|
|
24
|
+
strategy: 'pidfile' | 'search' | 'none';
|
|
25
|
+
/** Short human-readable notes recorded by the activity — surfaced in workflow logs. */
|
|
26
|
+
notes: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort OS-level process-tree termination. Never throws — returns a result describing
|
|
30
|
+
* what happened so callers can record it in workflow history without blocking state flips.
|
|
31
|
+
*/
|
|
32
|
+
export declare function hardTerminateAttachment(input: HardTerminateInput): Promise<HardTerminateResult>;
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hardTerminateAttachment = hardTerminateAttachment;
|
|
4
|
+
/**
|
|
5
|
+
* OS-level process-tree termination for a detaching session.
|
|
6
|
+
*
|
|
7
|
+
* Fix for issue #159 Gap 2: workflow-side `forceDetach` / drainingDeadline only flip the
|
|
8
|
+
* phase — they do *not* kill the child process that adapter was driving. On Windows that
|
|
9
|
+
* leaves an orphaned `claude.exe` holding the session ID, and the next `-n <name>` spawn
|
|
10
|
+
* collides with its own past self ("Error: Session ID <uuid> is already in use").
|
|
11
|
+
*
|
|
12
|
+
* This activity runs on the target's per-host task queue (`agent-tempo-{hostname}`) so
|
|
13
|
+
* the kill happens on the machine where the process actually lives. Callers:
|
|
14
|
+
* - workflow main-loop §9.5.c drainingDeadline (best-effort, workflow keeps flipping state
|
|
15
|
+
* on failure so it doesn't get wedged in `draining` forever)
|
|
16
|
+
* - `deliverRestart` activity on the force path — strict "kill first, then flip state"
|
|
17
|
+
* order per the conductor's steering on #159.
|
|
18
|
+
*
|
|
19
|
+
* Strategy per adapter:
|
|
20
|
+
* - **Copilot bridge**: PID file at `<logDir>/<playerName>.pid` is authoritative.
|
|
21
|
+
* Verifies the PID still resolves to `node`/`node.exe` before firing (guard against
|
|
22
|
+
* PID reuse after the bridge already exited).
|
|
23
|
+
* - **Claude Code (interactive)**: no useful PID is captured at spawn time — the spawn
|
|
24
|
+
* returns the transient `cmd.exe` / `osascript` / `bash` launcher, not the eventual
|
|
25
|
+
* `claude.exe`. Search running processes for `claude` (or `.exe` on Windows) whose
|
|
26
|
+
* command line contains `-n <playerName>`. This is the operator workaround from #159
|
|
27
|
+
* ("Get-CimInstance ... Where CommandLine -match '<session-name>'") turned into
|
|
28
|
+
* automation.
|
|
29
|
+
*/
|
|
30
|
+
const child_process_1 = require("child_process");
|
|
31
|
+
const fs_1 = require("fs");
|
|
32
|
+
const path_1 = require("path");
|
|
33
|
+
const constants_1 = require("../constants");
|
|
34
|
+
const log = (...args) => console.error('[agent-tempo:hard-terminate]', ...args);
|
|
35
|
+
/**
|
|
36
|
+
* Best-effort OS-level process-tree termination. Never throws — returns a result describing
|
|
37
|
+
* what happened so callers can record it in workflow history without blocking state flips.
|
|
38
|
+
*/
|
|
39
|
+
async function hardTerminateAttachment(input) {
|
|
40
|
+
const { ensemble, playerName, agent, workDir, logDir } = input;
|
|
41
|
+
const notes = [];
|
|
42
|
+
const killedPids = [];
|
|
43
|
+
log(`hardTerminate start — ensemble=${ensemble} player=${playerName} agent=${agent}`);
|
|
44
|
+
// ── Copilot bridge: PID file is authoritative ──
|
|
45
|
+
if (agent === 'copilot') {
|
|
46
|
+
const pidDir = logDir || (0, path_1.join)(workDir, 'logs');
|
|
47
|
+
const pidPath = (0, path_1.join)(pidDir, `${playerName}.pid`);
|
|
48
|
+
if ((0, fs_1.existsSync)(pidPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const pidStr = (0, fs_1.readFileSync)(pidPath, 'utf8').trim();
|
|
51
|
+
const pid = parseInt(pidStr, 10);
|
|
52
|
+
if (Number.isFinite(pid) && pid > 0) {
|
|
53
|
+
const expected = process.platform === 'win32' ? 'node.exe' : 'node';
|
|
54
|
+
if (processMatchesExpected(pid, expected)) {
|
|
55
|
+
const killed = await killProcessTree(pid);
|
|
56
|
+
if (killed) {
|
|
57
|
+
killedPids.push(pid);
|
|
58
|
+
notes.push(`Killed copilot bridge PID ${pid}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
notes.push(`kill of copilot PID ${pid} reported non-fatal error; process may have self-exited mid-call`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
notes.push(`Skipped copilot PID ${pid} — process no longer matches "${expected}" (likely already exited; PID-reuse guard).`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
notes.push(`Copilot PID file contained invalid value "${pidStr}"`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
(0, fs_1.unlinkSync)(pidPath);
|
|
73
|
+
}
|
|
74
|
+
catch { /* best-effort cleanup */ }
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
notes.push(`Copilot PID-file handling failed: ${errMsg(err)}`);
|
|
78
|
+
}
|
|
79
|
+
log(`hardTerminate done (pidfile) — killedPids=[${killedPids.join(',')}]`);
|
|
80
|
+
return { killedPids, strategy: 'pidfile', notes };
|
|
81
|
+
}
|
|
82
|
+
notes.push(`No copilot PID file at ${pidPath}; falling through to command-line search.`);
|
|
83
|
+
}
|
|
84
|
+
// ── Command-line search path (interactive claude.exe, or copilot fallback) ──
|
|
85
|
+
const binaryName = agent === 'copilot'
|
|
86
|
+
? (process.platform === 'win32' ? 'node.exe' : 'node')
|
|
87
|
+
: (process.platform === 'win32' ? 'claude.exe' : 'claude');
|
|
88
|
+
const pids = findProcessesByCommandLine(binaryName, playerName, ensemble);
|
|
89
|
+
if (pids.length === 0) {
|
|
90
|
+
notes.push(`No ${binaryName} processes found matching playerName="${playerName}" ensemble="${ensemble}" — nothing to kill.`);
|
|
91
|
+
log(`hardTerminate done (none) — nothing to kill`);
|
|
92
|
+
return { killedPids, strategy: 'none', notes };
|
|
93
|
+
}
|
|
94
|
+
for (const pid of pids) {
|
|
95
|
+
try {
|
|
96
|
+
if (await killProcessTree(pid))
|
|
97
|
+
killedPids.push(pid);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
notes.push(`kill(${pid}) failed: ${errMsg(err)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
notes.push(`Killed ${killedPids.length} ${binaryName} process(es) matching "${playerName}" in ensemble "${ensemble}": [${killedPids.join(', ')}]`);
|
|
104
|
+
log(`hardTerminate done (search) — killedPids=[${killedPids.join(',')}]`);
|
|
105
|
+
return { killedPids, strategy: 'search', notes };
|
|
106
|
+
}
|
|
107
|
+
// ────────────────────────────────────────────────────────────────────────────────────────────
|
|
108
|
+
// Platform helpers
|
|
109
|
+
// ────────────────────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Verify that `pid` resolves to a process whose executable image name matches `expected`.
|
|
112
|
+
* Guards against Windows PID reuse after the target bridge/claude process has already exited
|
|
113
|
+
* — without this check, we might taskkill an unrelated process that happened to inherit the PID.
|
|
114
|
+
*/
|
|
115
|
+
function processMatchesExpected(pid, expected) {
|
|
116
|
+
try {
|
|
117
|
+
if (process.platform === 'win32') {
|
|
118
|
+
// `tasklist /FI "PID eq <pid>" /FO CSV /NH` prints a single CSV line naming the image,
|
|
119
|
+
// or "INFO: No tasks running" when the PID is gone. No PowerShell dependency required.
|
|
120
|
+
const out = (0, child_process_1.execFileSync)('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], {
|
|
121
|
+
encoding: 'utf8',
|
|
122
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
123
|
+
windowsHide: true,
|
|
124
|
+
});
|
|
125
|
+
// First CSV field is the image name, quoted.
|
|
126
|
+
const m = out.match(/^"([^"]+)"/);
|
|
127
|
+
if (!m)
|
|
128
|
+
return false;
|
|
129
|
+
return m[1].toLowerCase() === expected.toLowerCase();
|
|
130
|
+
}
|
|
131
|
+
// Unix: liveness probe first, then /proc/<pid>/comm if available.
|
|
132
|
+
process.kill(pid, 0); // throws if the pid doesn't exist
|
|
133
|
+
try {
|
|
134
|
+
const comm = (0, fs_1.readFileSync)(`/proc/${pid}/comm`, 'utf8').trim();
|
|
135
|
+
if (comm)
|
|
136
|
+
return comm === expected;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// /proc not available (macOS, BSD) — fall back to `ps` lookup.
|
|
140
|
+
}
|
|
141
|
+
const psOut = (0, child_process_1.execFileSync)('ps', ['-p', String(pid), '-o', 'comm='], {
|
|
142
|
+
encoding: 'utf8',
|
|
143
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
144
|
+
}).trim();
|
|
145
|
+
return psOut === expected || psOut.endsWith(`/${expected}`);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Kill the process whose PID is `pid` AND all of its descendants. Returns `true` when the
|
|
153
|
+
* process is dead or was never running by the time this returns; `false` when the kill
|
|
154
|
+
* command ran but the process stubbornly refused to exit within the grace window.
|
|
155
|
+
*
|
|
156
|
+
* On Windows `taskkill /T /F` is synchronous and walks PPID → children; callers can rely on
|
|
157
|
+
* the return value. On Unix we SIGTERM the process group first (since spawns use
|
|
158
|
+
* `detached: true`), poll for exit for 500ms, then SIGKILL, poll again — so by the time
|
|
159
|
+
* this function returns the process really is gone, not "scheduled to die soon". That
|
|
160
|
+
* matters for the `forceDetachUpdate` strict-ordering path: a fresh `recruit` immediately
|
|
161
|
+
* after must see the session ID unlocked.
|
|
162
|
+
*/
|
|
163
|
+
async function killProcessTree(pid) {
|
|
164
|
+
if (process.platform === 'win32') {
|
|
165
|
+
// /T walks PPID → children; /F forces immediate termination. 128 = not found,
|
|
166
|
+
// 255 = already gone. Treat those as success for idempotence.
|
|
167
|
+
const result = (0, child_process_1.spawnSync)('taskkill', ['/T', '/F', '/PID', String(pid)], {
|
|
168
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
169
|
+
encoding: 'utf8',
|
|
170
|
+
windowsHide: true,
|
|
171
|
+
});
|
|
172
|
+
if (result.status === 0)
|
|
173
|
+
return true;
|
|
174
|
+
const stderr = (result.stderr || '').toString();
|
|
175
|
+
if (/not found|not running|no tasks/i.test(stderr))
|
|
176
|
+
return false;
|
|
177
|
+
log(`taskkill /T /F /PID ${pid} → status ${result.status}, stderr: ${stderr.trim()}`);
|
|
178
|
+
// status is known to be non-zero here (line 184 handled the 0 case) and not a recognized
|
|
179
|
+
// "already gone" signature — report kill failure to the caller.
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
// Unix: SIGTERM → brief poll → SIGKILL → brief poll. Process-group signal first
|
|
183
|
+
// (negative pid) because spawnInTerminal uses `detached: true`; fall back to the bare pid.
|
|
184
|
+
// Catch ESRCH (already gone) and EPERM (permission) silently — both mean "nothing more to do".
|
|
185
|
+
const killPair = (sig) => {
|
|
186
|
+
try {
|
|
187
|
+
process.kill(-pid, sig);
|
|
188
|
+
}
|
|
189
|
+
catch { /* process-group not applicable */ }
|
|
190
|
+
try {
|
|
191
|
+
process.kill(pid, sig);
|
|
192
|
+
}
|
|
193
|
+
catch { /* already gone */ }
|
|
194
|
+
};
|
|
195
|
+
const pollUntilDead = async (maxMs) => {
|
|
196
|
+
const deadline = Date.now() + maxMs;
|
|
197
|
+
while (Date.now() < deadline) {
|
|
198
|
+
try {
|
|
199
|
+
process.kill(pid, 0);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
process.kill(pid, 0);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
killPair('SIGTERM');
|
|
215
|
+
if (await pollUntilDead(500))
|
|
216
|
+
return true;
|
|
217
|
+
killPair('SIGKILL');
|
|
218
|
+
return pollUntilDead(500);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Search the live process table for entries whose image matches `binaryName` and whose command
|
|
222
|
+
* line contains BOTH `-n <playerName>` AND `--remote-control-session-name-prefix <ensemble>`.
|
|
223
|
+
* Returns the set of matching PIDs, or `[]` when nothing matches (including when the native
|
|
224
|
+
* lookup tool is missing).
|
|
225
|
+
*
|
|
226
|
+
* The command-line match mirrors the operator workaround documented in #159 — every spawn
|
|
227
|
+
* we care about passes `-n <playerName>` (see `src/activities/outbox.ts` spawnArgs). The
|
|
228
|
+
* ensemble-prefix sentinel was added in #180 so two ensembles sharing a lineup template
|
|
229
|
+
* (identical player names) don't clobber each other on `destroy --all`.
|
|
230
|
+
*/
|
|
231
|
+
function findProcessesByCommandLine(binaryName, playerName, ensemble) {
|
|
232
|
+
// Defensive: bail out on absurd inputs so we never inject into the PowerShell/pgrep expression.
|
|
233
|
+
if (!playerName || !/^[A-Za-z0-9._\-]+$/.test(playerName)) {
|
|
234
|
+
log(`findProcessesByCommandLine: refusing lookup for playerName="${playerName}" (failed regex guard)`);
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
if (!ensemble || !/^[A-Za-z0-9._\-]+$/.test(ensemble)) {
|
|
238
|
+
log(`findProcessesByCommandLine: refusing lookup for ensemble="${ensemble}" (failed regex guard)`);
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
if (process.platform === 'win32') {
|
|
242
|
+
// Prefer PowerShell's CIM provider — reliable CommandLine access without admin.
|
|
243
|
+
// Fall back to wmic if PowerShell is missing (older Windows without it).
|
|
244
|
+
//
|
|
245
|
+
// The regex matches `-n` followed by the playerName, tolerant of the Windows quoted
|
|
246
|
+
// arg form. In production the spawned `claude.exe` receives argv re-serialized with
|
|
247
|
+
// CRT-style quoting, so its CommandLine as visible to Win32_Process looks like:
|
|
248
|
+
// ... "server:agent-tempo" "-n" "<playerName>" "--session-id" ...
|
|
249
|
+
// Between `-n` and `<playerName>` there is literally `" "` — close-quote, space,
|
|
250
|
+
// open-quote — which `\s+` alone does NOT match. The character class `[\s"']+`
|
|
251
|
+
// accepts any combination of whitespace and quote characters, covering both the
|
|
252
|
+
// bare `-n <name>` form (used by tests and some launchers) and the quoted
|
|
253
|
+
// `"-n" "<name>"` form (real production). Same treatment on the trailing boundary
|
|
254
|
+
// so `"<name>"` terminates cleanly. This was the root cause of the smoke-run
|
|
255
|
+
// failure discovered behind #164+#165: the activity compiled and ran, but its
|
|
256
|
+
// regex never matched any real `claude.exe`, making the whole #159 kill path a
|
|
257
|
+
// silent no-op in production.
|
|
258
|
+
//
|
|
259
|
+
// The regex guard at the top of this function has already established that
|
|
260
|
+
// `playerName` is `[A-Za-z0-9._-]+`, so direct interpolation into the PowerShell
|
|
261
|
+
// string is safe. `.` and `-` are escaped as regex metachars before embedding
|
|
262
|
+
// into the `-match` pattern.
|
|
263
|
+
//
|
|
264
|
+
// Parent-walk (issue #165): for each matched process, look up its parent exactly one
|
|
265
|
+
// level via `ParentProcessId`. If that parent is `cmd.exe` AND its own CommandLine
|
|
266
|
+
// contains the same `-n <playerName>` sentinel, include the parent PID in the kill
|
|
267
|
+
// list. This clears the Windows Terminal tab when sessions are spawned via
|
|
268
|
+
// `cmd.exe /c start "" wt.exe ... cmd /k <innerCmd>` (see spawn.ts WT branch) — the
|
|
269
|
+
// inner `cmd /k` shell is the claude.exe parent; without killing it, WT leaves an
|
|
270
|
+
// unresponsive tab with cmd.exe alive on the prompt. Scope is strictly one level:
|
|
271
|
+
// grandparents are WT.exe / conhost.exe and must not be touched. The sentinel check
|
|
272
|
+
// reuses the same regex pattern used for the primary match — only cmd.exe shells
|
|
273
|
+
// that we spawned via the #159 pipeline can match.
|
|
274
|
+
const escapedName = (0, constants_1.escapeNameForRegex)(playerName);
|
|
275
|
+
const escapedEnsemble = (0, constants_1.escapeNameForRegex)(ensemble);
|
|
276
|
+
try {
|
|
277
|
+
// Emit PARENT PIDs before child PIDs. taskkill /T /F cascades to descendants,
|
|
278
|
+
// so killing cmd.exe first also kills its claude.exe child in the same call —
|
|
279
|
+
// fewer WMI round-trips, and we correctly credit the parent kill instead of
|
|
280
|
+
// losing it to a race where claude.exe dies first and cmd.exe exits on its
|
|
281
|
+
// own (e.g. when its console has no more input). Ordering here flows directly
|
|
282
|
+
// into the kill loop via `parsePids`, which preserves insertion order.
|
|
283
|
+
//
|
|
284
|
+
// PS single-quoted string literal escapes `'` by doubling it: `''`. The bracket
|
|
285
|
+
// class `[\s"'']` therefore denotes the set { whitespace, `"`, `'` } in the
|
|
286
|
+
// compiled regex. Double-quote needs no escaping inside a PS single-quoted
|
|
287
|
+
// literal.
|
|
288
|
+
//
|
|
289
|
+
// Two patterns are required to match (both AND-ed in the Where clause) so we
|
|
290
|
+
// never kill a process from a sibling ensemble that happens to share the same
|
|
291
|
+
// player name (#180). The ensemble sentinel is
|
|
292
|
+
// `--remote-control-session-name-prefix <ensemble>`, injected by outbox.ts.
|
|
293
|
+
// The parent-walk check applies the same pair so parent cmd.exe wrappers that
|
|
294
|
+
// don't carry the ensemble sentinel are left alone.
|
|
295
|
+
const psScript = [
|
|
296
|
+
`$procs = Get-CimInstance Win32_Process -Filter "Name='${binaryName}'";`,
|
|
297
|
+
`$pattern = '-n[\\s"'']+${escapedName}([\\s"'']|$)';`,
|
|
298
|
+
`$ensemblePattern = '${constants_1.ENSEMBLE_SENTINEL_FLAG}[\\s"'']+${escapedEnsemble}([\\s"'']|$)';`,
|
|
299
|
+
`$matched = $procs | Where-Object { $_.CommandLine -match $pattern -and $_.CommandLine -match $ensemblePattern };`,
|
|
300
|
+
`$result = @();`,
|
|
301
|
+
`foreach ($p in @($matched)) {`,
|
|
302
|
+
` $ppid = $p.ParentProcessId;`,
|
|
303
|
+
` if ($ppid) {`,
|
|
304
|
+
` $parent = Get-CimInstance Win32_Process -Filter "ProcessId=$ppid" -ErrorAction SilentlyContinue;`,
|
|
305
|
+
` if ($parent -and $parent.Name -ieq 'cmd.exe' -and $parent.CommandLine -match $pattern -and $parent.CommandLine -match $ensemblePattern) {`,
|
|
306
|
+
` $result += [int]$parent.ProcessId;`,
|
|
307
|
+
` }`,
|
|
308
|
+
` }`,
|
|
309
|
+
` $result += [int]$p.ProcessId;`,
|
|
310
|
+
`}`,
|
|
311
|
+
`$result | Select-Object -Unique`,
|
|
312
|
+
].join(' ');
|
|
313
|
+
const out = (0, child_process_1.execFileSync)('powershell', ['-NoProfile', '-Command', psScript], {
|
|
314
|
+
encoding: 'utf8',
|
|
315
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
316
|
+
windowsHide: true,
|
|
317
|
+
});
|
|
318
|
+
return parsePids(out);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
log(`powershell lookup failed (${errMsg(err)}); falling back to wmic`);
|
|
322
|
+
}
|
|
323
|
+
// wmic fallback — older Windows without PowerShell. Parent-walk is best-effort here:
|
|
324
|
+
// we do a second wmic call to resolve parents for any matched child PIDs. If wmic
|
|
325
|
+
// itself is missing (Windows 11 24H2+ removed it), we return the child PIDs only —
|
|
326
|
+
// the WT orphan-tab fix won't engage, but the primary #159 kill still works.
|
|
327
|
+
//
|
|
328
|
+
// LIKE uses a single `%` between `-n` and `<playerName>` so the filter accepts any
|
|
329
|
+
// intervening characters (space, `" "`, `' '`). This intentionally overmatches —
|
|
330
|
+
// we then pull CommandLine back and post-filter with the same tolerate-quotes
|
|
331
|
+
// regex used in the PowerShell path so non-ours matches are rejected. Without
|
|
332
|
+
// this widening, the quoted production form (`"-n" "<name>"`) slips past the
|
|
333
|
+
// stricter `%-n <name>%` LIKE pattern entirely, which is the root cause this
|
|
334
|
+
// fix addresses.
|
|
335
|
+
//
|
|
336
|
+
// Both patterns (player name AND ensemble sentinel) must match for the block to
|
|
337
|
+
// survive the filter — mirrors the PowerShell path's AND guard for #180.
|
|
338
|
+
const tolerantPatterns = [
|
|
339
|
+
new RegExp(`-n[\\s"']+${escapedName}(?:[\\s"']|$)`),
|
|
340
|
+
new RegExp(`${constants_1.ENSEMBLE_SENTINEL_FLAG}[\\s"']+${escapedEnsemble}(?:[\\s"']|$)`),
|
|
341
|
+
];
|
|
342
|
+
try {
|
|
343
|
+
const out = (0, child_process_1.execFileSync)('wmic', [
|
|
344
|
+
'process',
|
|
345
|
+
'where',
|
|
346
|
+
`Name='${binaryName}' and CommandLine like '%-n%${playerName}%' and CommandLine like '%${constants_1.ENSEMBLE_SENTINEL_FLAG}%${ensemble}%'`,
|
|
347
|
+
'get',
|
|
348
|
+
'CommandLine,ProcessId,ParentProcessId',
|
|
349
|
+
'/format:value',
|
|
350
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
351
|
+
const { pids: childPids, ppids } = parseWmicPidPpidFiltered(out, tolerantPatterns);
|
|
352
|
+
const parentPids = [];
|
|
353
|
+
for (const ppid of ppids) {
|
|
354
|
+
try {
|
|
355
|
+
const parentOut = (0, child_process_1.execFileSync)('wmic', [
|
|
356
|
+
'process',
|
|
357
|
+
'where',
|
|
358
|
+
`ProcessId=${ppid} and Name='cmd.exe' and CommandLine like '%-n%${playerName}%' and CommandLine like '%${constants_1.ENSEMBLE_SENTINEL_FLAG}%${ensemble}%'`,
|
|
359
|
+
'get',
|
|
360
|
+
'CommandLine,ProcessId,ParentProcessId',
|
|
361
|
+
'/format:value',
|
|
362
|
+
], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
363
|
+
const { pids: matched } = parseWmicPidPpidFiltered(parentOut, tolerantPatterns);
|
|
364
|
+
parentPids.push(...matched);
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
/* no matching parent — leave it */
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Parents first so `taskkill /T /F` cascades to children in one call.
|
|
371
|
+
return [...new Set([...parentPids, ...childPids])];
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
log(`wmic lookup failed (${errMsg(err)}); giving up on Windows search`);
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Unix: pgrep -f returns PIDs whose full cmdline matches the pattern.
|
|
379
|
+
// pgrep uses POSIX ERE — `\s` is NOT a metacharacter (it matches literal 's').
|
|
380
|
+
// Use `[[:space:]"']` for the whitespace/quote class instead.
|
|
381
|
+
//
|
|
382
|
+
// The combined pattern requires BOTH `-n <playerName>` AND
|
|
383
|
+
// `--remote-control-session-name-prefix <ensemble>` to be present, matching the
|
|
384
|
+
// Windows AND guard for #180. argv order is deterministic because spawnArgs in
|
|
385
|
+
// src/activities/outbox.ts places the ensemble sentinel before the name args.
|
|
386
|
+
try {
|
|
387
|
+
const escapedNameU = (0, constants_1.escapeNameForRegex)(playerName);
|
|
388
|
+
const escapedEnsembleU = (0, constants_1.escapeNameForRegex)(ensemble);
|
|
389
|
+
const pattern = `${binaryName}.*${constants_1.ENSEMBLE_SENTINEL_FLAG}[[:space:]"']+${escapedEnsembleU}([[:space:]"']|$)` +
|
|
390
|
+
`.*-n[[:space:]"']+${escapedNameU}([[:space:]"']|$)`;
|
|
391
|
+
const out = (0, child_process_1.execFileSync)('pgrep', ['-f', pattern], {
|
|
392
|
+
encoding: 'utf8',
|
|
393
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
394
|
+
});
|
|
395
|
+
return parsePids(out);
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// pgrep exits non-zero when no matches.
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function parsePids(raw) {
|
|
403
|
+
const pids = new Set();
|
|
404
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
405
|
+
const m = line.match(/\b(\d{2,})\b/);
|
|
406
|
+
if (!m)
|
|
407
|
+
continue;
|
|
408
|
+
const pid = parseInt(m[1], 10);
|
|
409
|
+
if (Number.isFinite(pid) && pid > 0 && pid !== process.pid)
|
|
410
|
+
pids.add(pid);
|
|
411
|
+
}
|
|
412
|
+
return [...pids];
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Parse wmic `/format:value` output that requested `CommandLine`, `ProcessId`, and
|
|
416
|
+
* `ParentProcessId`, and retain only blocks whose CommandLine matches EVERY pattern
|
|
417
|
+
* in `cmdPatterns`.
|
|
418
|
+
*
|
|
419
|
+
* The caller widens the LIKE filter to `%-n%<name>%` so the quoted production form
|
|
420
|
+
* `"-n" "<name>"` passes the SQL-style match. Because that filter overmatches
|
|
421
|
+
* (it would also accept e.g. `-name foo<name>bar`), we post-filter per-block here
|
|
422
|
+
* using the same tolerate-quotes regex used in the PowerShell path. Blocks whose
|
|
423
|
+
* CommandLine fails any pattern are discarded — their PID/PPID never enter the kill
|
|
424
|
+
* list. Output blocks look like:
|
|
425
|
+
* CommandLine=<cmdline>\r\nParentProcessId=<n>\r\nProcessId=<m>\r\n\r\n
|
|
426
|
+
* `wmic` key ordering is alphabetical so CommandLine always precedes the IDs.
|
|
427
|
+
*
|
|
428
|
+
* Multi-pattern matching supports the #180 AND guard: callers pass [`-n <name>`,
|
|
429
|
+
* `--remote-control-session-name-prefix <ensemble>`] and both must match before
|
|
430
|
+
* the block's PID is eligible to kill.
|
|
431
|
+
*/
|
|
432
|
+
function parseWmicPidPpidFiltered(raw, cmdPatterns) {
|
|
433
|
+
const pids = new Set();
|
|
434
|
+
const ppids = new Set();
|
|
435
|
+
// Split on blank line separator — wmic uses \r\n\r\n between records.
|
|
436
|
+
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
437
|
+
if (!block.trim())
|
|
438
|
+
continue;
|
|
439
|
+
const cmdLineMatch = block.match(/^CommandLine=(.*)$/m);
|
|
440
|
+
const pidMatch = block.match(/^ProcessId=(\d+)$/m);
|
|
441
|
+
const ppidMatch = block.match(/^ParentProcessId=(\d+)$/m);
|
|
442
|
+
if (!cmdLineMatch || !pidMatch)
|
|
443
|
+
continue;
|
|
444
|
+
const cmdLine = cmdLineMatch[1];
|
|
445
|
+
if (!cmdPatterns.every((p) => p.test(cmdLine)))
|
|
446
|
+
continue;
|
|
447
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
448
|
+
if (Number.isFinite(pid) && pid > 0 && pid !== process.pid)
|
|
449
|
+
pids.add(pid);
|
|
450
|
+
if (ppidMatch) {
|
|
451
|
+
const ppid = parseInt(ppidMatch[1], 10);
|
|
452
|
+
if (Number.isFinite(ppid) && ppid > 0 && ppid !== process.pid)
|
|
453
|
+
ppids.add(ppid);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return { pids: [...pids], ppids: [...ppids] };
|
|
457
|
+
}
|
|
458
|
+
function errMsg(err) {
|
|
459
|
+
return err instanceof Error ? err.message : String(err);
|
|
460
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Client } from '@temporalio/client';
|
|
2
|
+
import { HistoryEntry, MaestroPlayerInfo, Message, SentMessage, EnsembleChatMessage, ChatHighWater } from '../types';
|
|
3
|
+
export interface RelayCommandInput {
|
|
4
|
+
ensemble: string;
|
|
5
|
+
text: string;
|
|
6
|
+
source: string;
|
|
7
|
+
replyTo?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RelayCommandResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FetchConductorHistoryInput {
|
|
14
|
+
ensemble: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FetchConductorHistoryResult {
|
|
17
|
+
success: boolean;
|
|
18
|
+
history: HistoryEntry[];
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DeliverMaestroMessageInput {
|
|
22
|
+
ensemble: string;
|
|
23
|
+
to: string;
|
|
24
|
+
text: string;
|
|
25
|
+
source: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DeliverMaestroMessageResult {
|
|
28
|
+
success: boolean;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface FetchPlayerMessagesInput {
|
|
32
|
+
ensemble: string;
|
|
33
|
+
playerId: string;
|
|
34
|
+
}
|
|
35
|
+
export interface FetchPlayerMessagesResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
messages: Array<(Message & {
|
|
38
|
+
direction?: 'received';
|
|
39
|
+
}) | (SentMessage & {
|
|
40
|
+
direction: 'sent';
|
|
41
|
+
})>;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface FetchEnsembleChatInput {
|
|
45
|
+
ensemble: string;
|
|
46
|
+
/** Known message counts to enable delta returns. */
|
|
47
|
+
knownCounts?: ChatHighWater;
|
|
48
|
+
}
|
|
49
|
+
export interface FetchEnsembleChatResult {
|
|
50
|
+
success: boolean;
|
|
51
|
+
/** Only NEW messages since the known counts. */
|
|
52
|
+
newMessages: EnsembleChatMessage[];
|
|
53
|
+
/** Updated counts for next call. */
|
|
54
|
+
currentCounts: ChatHighWater;
|
|
55
|
+
hasConductor: boolean;
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Activity interface — used by proxyActivities in the Maestro workflow. */
|
|
59
|
+
export interface MaestroActivities {
|
|
60
|
+
refreshEnsembleState(ensemble: string): Promise<MaestroPlayerInfo[]>;
|
|
61
|
+
fetchConductorHistory(input: FetchConductorHistoryInput): Promise<FetchConductorHistoryResult>;
|
|
62
|
+
relayCommandToConductor(input: RelayCommandInput): Promise<RelayCommandResult>;
|
|
63
|
+
discoverEnsembles(): Promise<string[]>;
|
|
64
|
+
deliverMaestroMessage(input: DeliverMaestroMessageInput): Promise<DeliverMaestroMessageResult>;
|
|
65
|
+
fetchPlayerMessages(input: FetchPlayerMessagesInput): Promise<FetchPlayerMessagesResult>;
|
|
66
|
+
fetchEnsembleChat(input: FetchEnsembleChatInput): Promise<FetchEnsembleChatResult>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create the Maestro activity implementations bound to a Temporal client.
|
|
70
|
+
* Registered with the shared worker.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createMaestroActivities(client: Client): MaestroActivities;
|