claudecode-omc 4.4.10 → 4.7.4
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-plugin/marketplace.json +18 -4
- package/.claude-plugin/plugin.json +14 -1
- package/.mcp.json +0 -4
- package/README.de.md +260 -0
- package/README.es.md +27 -41
- package/README.fr.md +260 -0
- package/README.it.md +260 -0
- package/README.ja.md +27 -41
- package/README.ko.md +45 -54
- package/README.md +247 -14
- package/README.pt.md +23 -6
- package/README.ru.md +260 -0
- package/README.tr.md +260 -0
- package/README.vi.md +23 -6
- package/README.zh.md +27 -41
- package/agents/harsh-critic.md +254 -0
- package/bridge/cli.cjs +67181 -0
- package/bridge/gyoshu_bridge.py +69 -19
- package/bridge/mcp-server.cjs +846 -526
- package/bridge/runtime-cli.cjs +2861 -367
- package/bridge/team-bridge.cjs +216 -60
- package/bridge/team-mcp.cjs +493 -255
- package/bridge/team.js +1460 -0
- package/dist/__tests__/agent-registry.test.js +13 -3
- package/dist/__tests__/agent-registry.test.js.map +1 -1
- package/dist/__tests__/auto-slash-aliases.test.js +12 -20
- package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
- package/dist/__tests__/auto-update.test.js +1 -1
- package/dist/__tests__/auto-update.test.js.map +1 -1
- package/dist/__tests__/bash-history.test.js.map +1 -1
- package/dist/__tests__/cleanup-validation.test.d.ts +2 -0
- package/dist/__tests__/cleanup-validation.test.d.ts.map +1 -0
- package/dist/__tests__/cleanup-validation.test.js +44 -0
- package/dist/__tests__/cleanup-validation.test.js.map +1 -0
- package/dist/__tests__/config-force-inherit-env.test.d.ts +5 -0
- package/dist/__tests__/config-force-inherit-env.test.d.ts.map +1 -0
- package/dist/__tests__/config-force-inherit-env.test.js +35 -0
- package/dist/__tests__/config-force-inherit-env.test.js.map +1 -0
- package/dist/__tests__/consolidation-contracts.test.js +4 -7
- package/dist/__tests__/consolidation-contracts.test.js.map +1 -1
- package/dist/__tests__/daemon-module-path.test.d.ts +2 -0
- package/dist/__tests__/daemon-module-path.test.d.ts.map +1 -0
- package/dist/__tests__/daemon-module-path.test.js +29 -0
- package/dist/__tests__/daemon-module-path.test.js.map +1 -0
- package/dist/__tests__/delegation-enforcement-levels.test.js.map +1 -1
- package/dist/__tests__/delegation-enforcer.test.js +172 -0
- package/dist/__tests__/delegation-enforcer.test.js.map +1 -1
- package/dist/__tests__/doctor-conflicts.test.js +144 -1
- package/dist/__tests__/doctor-conflicts.test.js.map +1 -1
- package/dist/__tests__/file-lock.test.d.ts +2 -0
- package/dist/__tests__/file-lock.test.d.ts.map +1 -0
- package/dist/__tests__/file-lock.test.js +209 -0
- package/dist/__tests__/file-lock.test.js.map +1 -0
- package/dist/__tests__/hooks.test.js +27 -56
- package/dist/__tests__/hooks.test.js.map +1 -1
- package/dist/__tests__/hud/defaults.test.js +3 -0
- package/dist/__tests__/hud/defaults.test.js.map +1 -1
- package/dist/__tests__/hud/limits-error.test.d.ts +5 -0
- package/dist/__tests__/hud/limits-error.test.d.ts.map +1 -0
- package/dist/__tests__/hud/limits-error.test.js +43 -0
- package/dist/__tests__/hud/limits-error.test.js.map +1 -0
- package/dist/__tests__/hud/max-width.test.d.ts +2 -0
- package/dist/__tests__/hud/max-width.test.d.ts.map +1 -0
- package/dist/__tests__/hud/max-width.test.js +149 -0
- package/dist/__tests__/hud/max-width.test.js.map +1 -0
- package/dist/__tests__/hud/rate-limits-error.test.d.ts +5 -0
- package/dist/__tests__/hud/rate-limits-error.test.d.ts.map +1 -0
- package/dist/__tests__/hud/rate-limits-error.test.js +76 -0
- package/dist/__tests__/hud/rate-limits-error.test.js.map +1 -0
- package/dist/__tests__/hud/render.test.js +245 -1
- package/dist/__tests__/hud/render.test.js.map +1 -1
- package/dist/__tests__/hud/state.test.js +15 -0
- package/dist/__tests__/hud/state.test.js.map +1 -1
- package/dist/__tests__/hud/usage-api.test.js +19 -5
- package/dist/__tests__/hud/usage-api.test.js.map +1 -1
- package/dist/__tests__/hud/version-display.test.js +3 -1
- package/dist/__tests__/hud/version-display.test.js.map +1 -1
- package/dist/__tests__/hud-agents.test.js.map +1 -1
- package/dist/__tests__/hud-api-key-source.test.d.ts +7 -0
- package/dist/__tests__/hud-api-key-source.test.d.ts.map +1 -0
- package/dist/__tests__/hud-api-key-source.test.js +112 -0
- package/dist/__tests__/hud-api-key-source.test.js.map +1 -0
- package/dist/__tests__/hud-build-guidance.test.d.ts +2 -0
- package/dist/__tests__/hud-build-guidance.test.d.ts.map +1 -0
- package/dist/__tests__/hud-build-guidance.test.js +26 -0
- package/dist/__tests__/hud-build-guidance.test.js.map +1 -0
- package/dist/__tests__/installer-hooks-merge.test.js +1 -1
- package/dist/__tests__/installer-hooks-merge.test.js.map +1 -1
- package/dist/__tests__/installer.test.js +4 -0
- package/dist/__tests__/installer.test.js.map +1 -1
- package/dist/__tests__/job-management.test.js +1 -1
- package/dist/__tests__/job-management.test.js.map +1 -1
- package/dist/__tests__/live-data.test.js +11 -5
- package/dist/__tests__/live-data.test.js.map +1 -1
- package/dist/__tests__/mcp-default-config.test.d.ts +2 -0
- package/dist/__tests__/mcp-default-config.test.d.ts.map +1 -0
- package/dist/__tests__/mcp-default-config.test.js +13 -0
- package/dist/__tests__/mcp-default-config.test.js.map +1 -0
- package/dist/__tests__/model-routing.test.js.map +1 -1
- package/dist/__tests__/non-claude-provider-detection.test.d.ts +11 -0
- package/dist/__tests__/non-claude-provider-detection.test.d.ts.map +1 -0
- package/dist/__tests__/non-claude-provider-detection.test.js +303 -0
- package/dist/__tests__/non-claude-provider-detection.test.js.map +1 -0
- package/dist/__tests__/omc-tools-server.test.js +5 -5
- package/dist/__tests__/package-dir-resolution-regression.test.d.ts +2 -0
- package/dist/__tests__/package-dir-resolution-regression.test.d.ts.map +1 -0
- package/dist/__tests__/package-dir-resolution-regression.test.js +63 -0
- package/dist/__tests__/package-dir-resolution-regression.test.js.map +1 -0
- package/dist/__tests__/pipeline-orchestrator.test.d.ts +5 -0
- package/dist/__tests__/pipeline-orchestrator.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-orchestrator.test.js +244 -0
- package/dist/__tests__/pipeline-orchestrator.test.js.map +1 -0
- package/dist/__tests__/plugin-setup-deps.test.d.ts +2 -0
- package/dist/__tests__/plugin-setup-deps.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-setup-deps.test.js +64 -0
- package/dist/__tests__/plugin-setup-deps.test.js.map +1 -0
- package/dist/__tests__/pre-tool-agent-prefix.test.d.ts +8 -0
- package/dist/__tests__/pre-tool-agent-prefix.test.d.ts.map +1 -0
- package/dist/__tests__/pre-tool-agent-prefix.test.js +131 -0
- package/dist/__tests__/pre-tool-agent-prefix.test.js.map +1 -0
- package/dist/__tests__/project-memory-merge.test.d.ts +2 -0
- package/dist/__tests__/project-memory-merge.test.d.ts.map +1 -0
- package/dist/__tests__/project-memory-merge.test.js +342 -0
- package/dist/__tests__/project-memory-merge.test.js.map +1 -0
- package/dist/__tests__/ralph-prd-mandatory.test.d.ts +2 -0
- package/dist/__tests__/ralph-prd-mandatory.test.d.ts.map +1 -0
- package/dist/__tests__/ralph-prd-mandatory.test.js +316 -0
- package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/daemon-bootstrap.test.d.ts +2 -0
- package/dist/__tests__/rate-limit-wait/daemon-bootstrap.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/daemon-bootstrap.test.js +94 -0
- package/dist/__tests__/rate-limit-wait/daemon-bootstrap.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.js.map +1 -1
- package/dist/__tests__/rate-limit-wait/integration.test.js +33 -25
- package/dist/__tests__/rate-limit-wait/integration.test.js.map +1 -1
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js +34 -26
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js.map +1 -1
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.js.map +1 -1
- package/dist/__tests__/resolve-node.test.js.map +1 -1
- package/dist/__tests__/resolve-transcript-path.test.d.ts +12 -0
- package/dist/__tests__/resolve-transcript-path.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-transcript-path.test.js +167 -0
- package/dist/__tests__/resolve-transcript-path.test.js.map +1 -0
- package/dist/__tests__/routing-force-inherit.test.d.ts +8 -0
- package/dist/__tests__/routing-force-inherit.test.d.ts.map +1 -0
- package/dist/__tests__/routing-force-inherit.test.js +180 -0
- package/dist/__tests__/routing-force-inherit.test.js.map +1 -0
- package/dist/__tests__/run-cjs-graceful-fallback.test.js +2 -2
- package/dist/__tests__/run-cjs-graceful-fallback.test.js.map +1 -1
- package/dist/__tests__/shared-memory-concurrency.test.d.ts +8 -0
- package/dist/__tests__/shared-memory-concurrency.test.d.ts.map +1 -0
- package/dist/__tests__/shared-memory-concurrency.test.js +132 -0
- package/dist/__tests__/shared-memory-concurrency.test.js.map +1 -0
- package/dist/__tests__/shared-memory.test.d.ts +2 -0
- package/dist/__tests__/shared-memory.test.d.ts.map +1 -0
- package/dist/__tests__/shared-memory.test.js +345 -0
- package/dist/__tests__/shared-memory.test.js.map +1 -0
- package/dist/__tests__/shell-path.test.d.ts +5 -0
- package/dist/__tests__/shell-path.test.d.ts.map +1 -0
- package/dist/__tests__/shell-path.test.js +70 -0
- package/dist/__tests__/shell-path.test.js.map +1 -0
- package/dist/__tests__/skills.test.js +13 -10
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/__tests__/slack-socket.test.d.ts +5 -0
- package/dist/__tests__/slack-socket.test.d.ts.map +1 -0
- package/dist/__tests__/slack-socket.test.js +252 -0
- package/dist/__tests__/slack-socket.test.js.map +1 -0
- package/dist/__tests__/smoke-functional.test.d.ts +8 -0
- package/dist/__tests__/smoke-functional.test.d.ts.map +1 -0
- package/dist/__tests__/smoke-functional.test.js +450 -0
- package/dist/__tests__/smoke-functional.test.js.map +1 -0
- package/dist/__tests__/smoke-pipeline-edge.test.d.ts +8 -0
- package/dist/__tests__/smoke-pipeline-edge.test.d.ts.map +1 -0
- package/dist/__tests__/smoke-pipeline-edge.test.js +435 -0
- package/dist/__tests__/smoke-pipeline-edge.test.js.map +1 -0
- package/dist/__tests__/smoke-slack-and-state.test.d.ts +13 -0
- package/dist/__tests__/smoke-slack-and-state.test.d.ts.map +1 -0
- package/dist/__tests__/smoke-slack-and-state.test.js +632 -0
- package/dist/__tests__/smoke-slack-and-state.test.js.map +1 -0
- package/dist/__tests__/smoke-team-worker.test.d.ts +15 -0
- package/dist/__tests__/smoke-team-worker.test.d.ts.map +1 -0
- package/dist/__tests__/smoke-team-worker.test.js +483 -0
- package/dist/__tests__/smoke-team-worker.test.js.map +1 -0
- package/dist/__tests__/ssrf-guard.test.d.ts +2 -0
- package/dist/__tests__/ssrf-guard.test.d.ts.map +1 -0
- package/dist/__tests__/ssrf-guard.test.js +96 -0
- package/dist/__tests__/ssrf-guard.test.js.map +1 -0
- package/dist/__tests__/task-continuation.test.js +1 -1
- package/dist/__tests__/task-continuation.test.js.map +1 -1
- package/dist/__tests__/tier0-contracts.test.js +5 -1
- package/dist/__tests__/tier0-contracts.test.js.map +1 -1
- package/dist/__tests__/tier0-docs-consistency.test.js +3 -4
- package/dist/__tests__/tier0-docs-consistency.test.js.map +1 -1
- package/dist/__tests__/tools/trace-tools.test.js +7 -3
- package/dist/__tests__/tools/trace-tools.test.js.map +1 -1
- package/dist/__tests__/types.test.js +4 -4
- package/dist/__tests__/types.test.js.map +1 -1
- package/dist/__tests__/worker-adapter.test.d.ts +5 -0
- package/dist/__tests__/worker-adapter.test.d.ts.map +1 -0
- package/dist/__tests__/worker-adapter.test.js +211 -0
- package/dist/__tests__/worker-adapter.test.js.map +1 -0
- package/dist/agents/definitions.d.ts +6 -5
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +11 -6
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/harsh-critic.d.ts +14 -0
- package/dist/agents/harsh-critic.d.ts.map +1 -0
- package/dist/agents/harsh-critic.js +42 -0
- package/dist/agents/harsh-critic.js.map +1 -0
- package/dist/agents/index.d.ts +1 -3
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -4
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/prompt-helpers.d.ts.map +1 -1
- package/dist/agents/prompt-helpers.js +21 -16
- package/dist/agents/prompt-helpers.js.map +1 -1
- package/dist/agents/prompt-sections/index.d.ts.map +1 -1
- package/dist/agents/prompt-sections/index.js +7 -3
- package/dist/agents/prompt-sections/index.js.map +1 -1
- package/dist/agents/utils.d.ts.map +1 -1
- package/dist/agents/utils.js +22 -13
- package/dist/agents/utils.js.map +1 -1
- package/dist/cli/__tests__/ask.test.d.ts +2 -0
- package/dist/cli/__tests__/ask.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ask.test.js +282 -0
- package/dist/cli/__tests__/ask.test.js.map +1 -0
- package/dist/cli/__tests__/cli-boot.test.d.ts +8 -0
- package/dist/cli/__tests__/cli-boot.test.d.ts.map +1 -0
- package/dist/cli/__tests__/cli-boot.test.js +82 -0
- package/dist/cli/__tests__/cli-boot.test.js.map +1 -0
- package/dist/cli/__tests__/launch.test.js +11 -2
- package/dist/cli/__tests__/launch.test.js.map +1 -1
- package/dist/cli/__tests__/team-command-branding.test.d.ts +2 -0
- package/dist/cli/__tests__/team-command-branding.test.d.ts.map +1 -0
- package/dist/cli/__tests__/team-command-branding.test.js +14 -0
- package/dist/cli/__tests__/team-command-branding.test.js.map +1 -0
- package/dist/cli/__tests__/team-help.test.d.ts +2 -0
- package/dist/cli/__tests__/team-help.test.d.ts.map +1 -0
- package/dist/cli/__tests__/team-help.test.js +19 -0
- package/dist/cli/__tests__/team-help.test.js.map +1 -0
- package/dist/cli/__tests__/team-runtime-boundary.test.d.ts +2 -0
- package/dist/cli/__tests__/team-runtime-boundary.test.d.ts.map +1 -0
- package/dist/cli/__tests__/team-runtime-boundary.test.js +11 -0
- package/dist/cli/__tests__/team-runtime-boundary.test.js.map +1 -0
- package/dist/cli/__tests__/team.test.d.ts +2 -0
- package/dist/cli/__tests__/team.test.d.ts.map +1 -0
- package/dist/cli/__tests__/team.test.js +335 -0
- package/dist/cli/__tests__/team.test.js.map +1 -0
- package/dist/cli/__tests__/tmux-utils.test.d.ts +11 -0
- package/dist/cli/__tests__/tmux-utils.test.d.ts.map +1 -0
- package/dist/cli/__tests__/tmux-utils.test.js +145 -0
- package/dist/cli/__tests__/tmux-utils.test.js.map +1 -0
- package/dist/cli/ask.d.ts +13 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +202 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/commands/__tests__/team.test.d.ts +2 -0
- package/dist/cli/commands/__tests__/team.test.d.ts.map +1 -0
- package/dist/cli/commands/__tests__/team.test.js +177 -0
- package/dist/cli/commands/__tests__/team.test.js.map +1 -0
- package/dist/cli/commands/__tests__/teleport.test.js.map +1 -1
- package/dist/cli/commands/doctor-conflicts.d.ts +14 -1
- package/dist/cli/commands/doctor-conflicts.d.ts.map +1 -1
- package/dist/cli/commands/doctor-conflicts.js +126 -12
- package/dist/cli/commands/doctor-conflicts.js.map +1 -1
- package/dist/cli/commands/team.d.ts +19 -0
- package/dist/cli/commands/team.d.ts.map +1 -0
- package/dist/cli/commands/team.js +510 -0
- package/dist/cli/commands/team.js.map +1 -0
- package/dist/cli/commands/teleport.d.ts.map +1 -1
- package/dist/cli/commands/teleport.js +6 -4
- package/dist/cli/commands/teleport.js.map +1 -1
- package/dist/cli/index.js +58 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/launch.d.ts.map +1 -1
- package/dist/cli/launch.js +12 -6
- package/dist/cli/launch.js.map +1 -1
- package/dist/cli/team.d.ts +76 -0
- package/dist/cli/team.d.ts.map +1 -0
- package/dist/cli/team.js +1117 -0
- package/dist/cli/team.js.map +1 -0
- package/dist/cli/tmux-utils.d.ts +10 -0
- package/dist/cli/tmux-utils.d.ts.map +1 -1
- package/dist/cli/tmux-utils.js +20 -1
- package/dist/cli/tmux-utils.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +134 -48
- package/dist/config/loader.js.map +1 -1
- package/dist/config/models.d.ts +36 -14
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +94 -0
- package/dist/config/models.js.map +1 -1
- package/dist/constants/names.d.ts +6 -3
- package/dist/constants/names.d.ts.map +1 -1
- package/dist/constants/names.js +7 -3
- package/dist/constants/names.js.map +1 -1
- package/dist/features/auto-update.js +5 -5
- package/dist/features/auto-update.js.map +1 -1
- package/dist/features/boulder-state/storage.d.ts +1 -1
- package/dist/features/boulder-state/storage.d.ts.map +1 -1
- package/dist/features/boulder-state/storage.js +20 -22
- package/dist/features/boulder-state/storage.js.map +1 -1
- package/dist/features/delegation-enforcer.d.ts +5 -0
- package/dist/features/delegation-enforcer.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.js +58 -5
- package/dist/features/delegation-enforcer.js.map +1 -1
- package/dist/features/model-routing/router.d.ts.map +1 -1
- package/dist/features/model-routing/router.js +11 -0
- package/dist/features/model-routing/router.js.map +1 -1
- package/dist/features/model-routing/types.d.ts +6 -0
- package/dist/features/model-routing/types.d.ts.map +1 -1
- package/dist/features/model-routing/types.js.map +1 -1
- package/dist/features/rate-limit-wait/daemon.d.ts +5 -0
- package/dist/features/rate-limit-wait/daemon.d.ts.map +1 -1
- package/dist/features/rate-limit-wait/daemon.js +38 -9
- package/dist/features/rate-limit-wait/daemon.js.map +1 -1
- package/dist/features/rate-limit-wait/rate-limit-monitor.d.ts.map +1 -1
- package/dist/features/rate-limit-wait/rate-limit-monitor.js +3 -2
- package/dist/features/rate-limit-wait/rate-limit-monitor.js.map +1 -1
- package/dist/features/rate-limit-wait/tmux-detector.d.ts.map +1 -1
- package/dist/features/rate-limit-wait/tmux-detector.js +8 -2
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
- package/dist/features/state-manager/__tests__/cache.test.js +161 -1
- package/dist/features/state-manager/__tests__/cache.test.js.map +1 -1
- package/dist/features/state-manager/index.d.ts +2 -0
- package/dist/features/state-manager/index.d.ts.map +1 -1
- package/dist/features/state-manager/index.js +167 -51
- package/dist/features/state-manager/index.js.map +1 -1
- package/dist/hooks/__tests__/bridge-routing.test.js +124 -1
- package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/bridge-security.test.js +1 -1
- package/dist/hooks/__tests__/bridge-security.test.js.map +1 -1
- package/dist/hooks/__tests__/bridge-team-worker-guard.test.d.ts +2 -0
- package/dist/hooks/__tests__/bridge-team-worker-guard.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/bridge-team-worker-guard.test.js +52 -0
- package/dist/hooks/__tests__/bridge-team-worker-guard.test.js.map +1 -0
- package/dist/hooks/__tests__/bridge.test.js.map +1 -1
- package/dist/hooks/__tests__/codebase-map.test.js.map +1 -1
- package/dist/hooks/__tests__/compaction-concurrency.test.js.map +1 -1
- package/dist/hooks/__tests__/stop-hook-openclaw-cooldown.test.d.ts +2 -0
- package/dist/hooks/__tests__/stop-hook-openclaw-cooldown.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/stop-hook-openclaw-cooldown.test.js +65 -0
- package/dist/hooks/__tests__/stop-hook-openclaw-cooldown.test.js.map +1 -0
- package/dist/hooks/auto-slash-command/live-data.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/live-data.js +54 -25
- package/dist/hooks/auto-slash-command/live-data.js.map +1 -1
- package/dist/hooks/autopilot/__tests__/cancel.test.js +1 -1
- package/dist/hooks/autopilot/__tests__/cancel.test.js.map +1 -1
- package/dist/hooks/autopilot/__tests__/pipeline.test.d.ts +2 -0
- package/dist/hooks/autopilot/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/hooks/autopilot/__tests__/pipeline.test.js +375 -0
- package/dist/hooks/autopilot/__tests__/pipeline.test.js.map +1 -0
- package/dist/hooks/autopilot/__tests__/state.test.js +2 -2
- package/dist/hooks/autopilot/__tests__/state.test.js.map +1 -1
- package/dist/hooks/autopilot/__tests__/summary.test.js.map +1 -1
- package/dist/hooks/autopilot/__tests__/transition.test.js.map +1 -1
- package/dist/hooks/autopilot/__tests__/validation.test.js +3 -3
- package/dist/hooks/autopilot/__tests__/validation.test.js.map +1 -1
- package/dist/hooks/autopilot/adapters/execution-adapter.d.ts +12 -0
- package/dist/hooks/autopilot/adapters/execution-adapter.d.ts.map +1 -0
- package/dist/hooks/autopilot/adapters/execution-adapter.js +110 -0
- package/dist/hooks/autopilot/adapters/execution-adapter.js.map +1 -0
- package/dist/hooks/autopilot/adapters/index.d.ts +22 -0
- package/dist/hooks/autopilot/adapters/index.d.ts.map +1 -0
- package/dist/hooks/autopilot/adapters/index.js +32 -0
- package/dist/hooks/autopilot/adapters/index.js.map +1 -0
- package/dist/hooks/autopilot/adapters/qa-adapter.d.ts +12 -0
- package/dist/hooks/autopilot/adapters/qa-adapter.d.ts.map +1 -0
- package/dist/hooks/autopilot/adapters/qa-adapter.js +33 -0
- package/dist/hooks/autopilot/adapters/qa-adapter.js.map +1 -0
- package/dist/hooks/autopilot/adapters/ralph-adapter.d.ts +15 -0
- package/dist/hooks/autopilot/adapters/ralph-adapter.d.ts.map +1 -0
- package/dist/hooks/autopilot/adapters/ralph-adapter.js +102 -0
- package/dist/hooks/autopilot/adapters/ralph-adapter.js.map +1 -0
- package/dist/hooks/autopilot/adapters/ralplan-adapter.d.ts +14 -0
- package/dist/hooks/autopilot/adapters/ralplan-adapter.d.ts.map +1 -0
- package/dist/hooks/autopilot/adapters/ralplan-adapter.js +81 -0
- package/dist/hooks/autopilot/adapters/ralplan-adapter.js.map +1 -0
- package/dist/hooks/autopilot/enforcement.d.ts.map +1 -1
- package/dist/hooks/autopilot/enforcement.js +162 -0
- package/dist/hooks/autopilot/enforcement.js.map +1 -1
- package/dist/hooks/autopilot/index.d.ts +4 -0
- package/dist/hooks/autopilot/index.d.ts.map +1 -1
- package/dist/hooks/autopilot/index.js +5 -0
- package/dist/hooks/autopilot/index.js.map +1 -1
- package/dist/hooks/autopilot/pipeline-types.d.ts +133 -0
- package/dist/hooks/autopilot/pipeline-types.d.ts.map +1 -0
- package/dist/hooks/autopilot/pipeline-types.js +44 -0
- package/dist/hooks/autopilot/pipeline-types.js.map +1 -0
- package/dist/hooks/autopilot/pipeline.d.ts +124 -0
- package/dist/hooks/autopilot/pipeline.d.ts.map +1 -0
- package/dist/hooks/autopilot/pipeline.js +407 -0
- package/dist/hooks/autopilot/pipeline.js.map +1 -0
- package/dist/hooks/autopilot/state.d.ts +6 -6
- package/dist/hooks/autopilot/state.d.ts.map +1 -1
- package/dist/hooks/autopilot/state.js +71 -129
- package/dist/hooks/autopilot/state.js.map +1 -1
- package/dist/hooks/autopilot/types.d.ts +21 -0
- package/dist/hooks/autopilot/types.d.ts.map +1 -1
- package/dist/hooks/autopilot/types.js.map +1 -1
- package/dist/hooks/bridge-normalize.d.ts.map +1 -1
- package/dist/hooks/bridge-normalize.js +13 -2
- package/dist/hooks/bridge-normalize.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +151 -27
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/comment-checker/index.js +1 -1
- package/dist/hooks/comment-checker/index.js.map +1 -1
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js +2 -2
- package/dist/hooks/empty-message-sanitizer/__tests__/index.test.js.map +1 -1
- package/dist/hooks/factcheck/__tests__/factcheck.test.d.ts +7 -0
- package/dist/hooks/factcheck/__tests__/factcheck.test.d.ts.map +1 -0
- package/dist/hooks/factcheck/__tests__/factcheck.test.js +153 -0
- package/dist/hooks/factcheck/__tests__/factcheck.test.js.map +1 -0
- package/dist/hooks/factcheck/__tests__/sentinel-gate.test.d.ts +5 -0
- package/dist/hooks/factcheck/__tests__/sentinel-gate.test.d.ts.map +1 -0
- package/dist/hooks/factcheck/__tests__/sentinel-gate.test.js +159 -0
- package/dist/hooks/factcheck/__tests__/sentinel-gate.test.js.map +1 -0
- package/dist/hooks/factcheck/__tests__/sentinel.test.d.ts +7 -0
- package/dist/hooks/factcheck/__tests__/sentinel.test.d.ts.map +1 -0
- package/dist/hooks/factcheck/__tests__/sentinel.test.js +117 -0
- package/dist/hooks/factcheck/__tests__/sentinel.test.js.map +1 -0
- package/dist/hooks/factcheck/checks.d.ts +36 -0
- package/dist/hooks/factcheck/checks.d.ts.map +1 -0
- package/dist/hooks/factcheck/checks.js +144 -0
- package/dist/hooks/factcheck/checks.js.map +1 -0
- package/dist/hooks/factcheck/config.d.ts +25 -0
- package/dist/hooks/factcheck/config.d.ts.map +1 -0
- package/dist/hooks/factcheck/config.js +125 -0
- package/dist/hooks/factcheck/config.js.map +1 -0
- package/dist/hooks/factcheck/index.d.ts +34 -0
- package/dist/hooks/factcheck/index.d.ts.map +1 -0
- package/dist/hooks/factcheck/index.js +120 -0
- package/dist/hooks/factcheck/index.js.map +1 -0
- package/dist/hooks/factcheck/sentinel.d.ts +32 -0
- package/dist/hooks/factcheck/sentinel.d.ts.map +1 -0
- package/dist/hooks/factcheck/sentinel.js +153 -0
- package/dist/hooks/factcheck/sentinel.js.map +1 -0
- package/dist/hooks/factcheck/types.d.ts +99 -0
- package/dist/hooks/factcheck/types.d.ts.map +1 -0
- package/dist/hooks/factcheck/types.js +27 -0
- package/dist/hooks/factcheck/types.js.map +1 -0
- package/dist/hooks/keyword-detector/__tests__/index.test.js +29 -80
- package/dist/hooks/keyword-detector/__tests__/index.test.js.map +1 -1
- package/dist/hooks/keyword-detector/index.d.ts +1 -1
- package/dist/hooks/keyword-detector/index.d.ts.map +1 -1
- package/dist/hooks/keyword-detector/index.js +11 -21
- package/dist/hooks/keyword-detector/index.js.map +1 -1
- package/dist/hooks/learner/bridge.d.ts.map +1 -1
- package/dist/hooks/learner/bridge.js +8 -0
- package/dist/hooks/learner/bridge.js.map +1 -1
- package/dist/hooks/learner/index.d.ts +15 -15
- package/dist/hooks/learner/index.d.ts.map +1 -1
- package/dist/hooks/learner/index.js +44 -38
- package/dist/hooks/learner/index.js.map +1 -1
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js +4 -2
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js.map +1 -1
- package/dist/hooks/mode-registry/index.d.ts +2 -13
- package/dist/hooks/mode-registry/index.d.ts.map +1 -1
- package/dist/hooks/mode-registry/index.js +77 -174
- package/dist/hooks/mode-registry/index.js.map +1 -1
- package/dist/hooks/mode-registry/types.d.ts +1 -1
- package/dist/hooks/mode-registry/types.d.ts.map +1 -1
- package/dist/hooks/notepad/index.d.ts.map +1 -1
- package/dist/hooks/notepad/index.js +83 -73
- package/dist/hooks/notepad/index.js.map +1 -1
- package/dist/hooks/omc-orchestrator/index.d.ts.map +1 -1
- package/dist/hooks/omc-orchestrator/index.js +2 -1
- package/dist/hooks/omc-orchestrator/index.js.map +1 -1
- package/dist/hooks/permission-handler/__tests__/index.test.js +2 -2
- package/dist/hooks/permission-handler/__tests__/index.test.js.map +1 -1
- package/dist/hooks/permission-handler/index.d.ts +1 -1
- package/dist/hooks/permission-handler/index.d.ts.map +1 -1
- package/dist/hooks/permission-handler/index.js +4 -8
- package/dist/hooks/permission-handler/index.js.map +1 -1
- package/dist/hooks/persistent-mode/__tests__/rate-limit-stop.test.js +23 -0
- package/dist/hooks/persistent-mode/__tests__/rate-limit-stop.test.js.map +1 -1
- package/dist/hooks/persistent-mode/__tests__/skill-state-stop.test.js.map +1 -1
- package/dist/hooks/persistent-mode/__tests__/tool-error.test.js +1 -1
- package/dist/hooks/persistent-mode/__tests__/tool-error.test.js.map +1 -1
- package/dist/hooks/persistent-mode/index.d.ts +3 -3
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +25 -12
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/persistent-mode/session-isolation.test.js +20 -4
- package/dist/hooks/persistent-mode/session-isolation.test.js.map +1 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.d.ts +2 -0
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.d.ts.map +1 -0
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +367 -0
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -0
- package/dist/hooks/pre-compact/index.d.ts +2 -14
- package/dist/hooks/pre-compact/index.d.ts.map +1 -1
- package/dist/hooks/pre-compact/index.js +8 -49
- package/dist/hooks/pre-compact/index.js.map +1 -1
- package/dist/hooks/preemptive-compaction/index.js +1 -1
- package/dist/hooks/preemptive-compaction/index.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/integration.test.js +4 -2
- package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
- package/dist/hooks/project-memory/index.d.ts +9 -9
- package/dist/hooks/project-memory/index.d.ts.map +1 -1
- package/dist/hooks/project-memory/index.js +25 -25
- package/dist/hooks/project-memory/index.js.map +1 -1
- package/dist/hooks/project-memory/learner.d.ts.map +1 -1
- package/dist/hooks/project-memory/learner.js +92 -86
- package/dist/hooks/project-memory/learner.js.map +1 -1
- package/dist/hooks/project-memory/storage.d.ts +9 -0
- package/dist/hooks/project-memory/storage.d.ts.map +1 -1
- package/dist/hooks/project-memory/storage.js +15 -0
- package/dist/hooks/project-memory/storage.js.map +1 -1
- package/dist/hooks/ralph/index.d.ts +1 -1
- package/dist/hooks/ralph/index.d.ts.map +1 -1
- package/dist/hooks/ralph/index.js +2 -0
- package/dist/hooks/ralph/index.js.map +1 -1
- package/dist/hooks/ralph/loop.d.ts +10 -2
- package/dist/hooks/ralph/loop.d.ts.map +1 -1
- package/dist/hooks/ralph/loop.js +64 -113
- package/dist/hooks/ralph/loop.js.map +1 -1
- package/dist/hooks/ralph/prd.d.ts.map +1 -1
- package/dist/hooks/ralph/prd.js +3 -2
- package/dist/hooks/ralph/prd.js.map +1 -1
- package/dist/hooks/ralph/progress.d.ts.map +1 -1
- package/dist/hooks/ralph/progress.js +6 -5
- package/dist/hooks/ralph/progress.js.map +1 -1
- package/dist/hooks/ralph/verifier.d.ts +3 -1
- package/dist/hooks/ralph/verifier.d.ts.map +1 -1
- package/dist/hooks/ralph/verifier.js +19 -7
- package/dist/hooks/ralph/verifier.js.map +1 -1
- package/dist/hooks/session-end/__tests__/openclaw-session-end.test.d.ts +2 -0
- package/dist/hooks/session-end/__tests__/openclaw-session-end.test.d.ts.map +1 -0
- package/dist/hooks/session-end/__tests__/openclaw-session-end.test.js +86 -0
- package/dist/hooks/session-end/__tests__/openclaw-session-end.test.js.map +1 -0
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +17 -17
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hooks/setup/index.d.ts.map +1 -1
- package/dist/hooks/setup/index.js +0 -2
- package/dist/hooks/setup/index.js.map +1 -1
- package/dist/hooks/skill-bridge.cjs +13 -4
- package/dist/hooks/skill-state/__tests__/skill-state.test.js.map +1 -1
- package/dist/hooks/skill-state/index.d.ts +0 -5
- package/dist/hooks/skill-state/index.d.ts.map +1 -1
- package/dist/hooks/skill-state/index.js +9 -55
- package/dist/hooks/skill-state/index.js.map +1 -1
- package/dist/hooks/subagent-tracker/__tests__/flush-race.test.js.map +1 -1
- package/dist/hooks/subagent-tracker/__tests__/index.test.js.map +1 -1
- package/dist/hooks/subagent-tracker/__tests__/session-replay.test.js +1 -1
- package/dist/hooks/subagent-tracker/__tests__/session-replay.test.js.map +1 -1
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/index.js +10 -22
- package/dist/hooks/subagent-tracker/index.js.map +1 -1
- package/dist/hooks/subagent-tracker/session-replay.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/session-replay.js +3 -2
- package/dist/hooks/subagent-tracker/session-replay.js.map +1 -1
- package/dist/hooks/task-size-detector/__tests__/index.test.js +7 -7
- package/dist/hooks/task-size-detector/__tests__/index.test.js.map +1 -1
- package/dist/hooks/task-size-detector/index.d.ts.map +1 -1
- package/dist/hooks/task-size-detector/index.js +0 -3
- package/dist/hooks/task-size-detector/index.js.map +1 -1
- package/dist/hooks/team-dispatch-hook.d.ts +65 -0
- package/dist/hooks/team-dispatch-hook.d.ts.map +1 -0
- package/dist/hooks/team-dispatch-hook.js +663 -0
- package/dist/hooks/team-dispatch-hook.js.map +1 -0
- package/dist/hooks/team-leader-nudge-hook.d.ts +36 -0
- package/dist/hooks/team-leader-nudge-hook.d.ts.map +1 -0
- package/dist/hooks/team-leader-nudge-hook.js +218 -0
- package/dist/hooks/team-leader-nudge-hook.js.map +1 -0
- package/dist/hooks/team-worker-hook.d.ts +45 -0
- package/dist/hooks/team-worker-hook.d.ts.map +1 -0
- package/dist/hooks/team-worker-hook.js +415 -0
- package/dist/hooks/team-worker-hook.js.map +1 -0
- package/dist/hooks/think-mode/__tests__/index.test.js.map +1 -1
- package/dist/hooks/todo-continuation/__tests__/isAuthenticationError.test.d.ts +2 -0
- package/dist/hooks/todo-continuation/__tests__/isAuthenticationError.test.d.ts.map +1 -0
- package/dist/hooks/todo-continuation/__tests__/isAuthenticationError.test.js +33 -0
- package/dist/hooks/todo-continuation/__tests__/isAuthenticationError.test.js.map +1 -0
- package/dist/hooks/todo-continuation/index.d.ts +12 -0
- package/dist/hooks/todo-continuation/index.d.ts.map +1 -1
- package/dist/hooks/todo-continuation/index.js +37 -1
- package/dist/hooks/todo-continuation/index.js.map +1 -1
- package/dist/hooks/ultrapilot/decomposer.d.ts.map +1 -1
- package/dist/hooks/ultrapilot/decomposer.js +3 -1
- package/dist/hooks/ultrapilot/decomposer.js.map +1 -1
- package/dist/hooks/ultrapilot/index.d.ts.map +1 -1
- package/dist/hooks/ultrapilot/index.js +3 -1
- package/dist/hooks/ultrapilot/index.js.map +1 -1
- package/dist/hooks/ultrapilot/state.d.ts +1 -1
- package/dist/hooks/ultrapilot/state.d.ts.map +1 -1
- package/dist/hooks/ultrapilot/state.js +45 -77
- package/dist/hooks/ultrapilot/state.js.map +1 -1
- package/dist/hooks/ultraqa/index.d.ts.map +1 -1
- package/dist/hooks/ultraqa/index.js +4 -77
- package/dist/hooks/ultraqa/index.js.map +1 -1
- package/dist/hooks/ultrawork/index.d.ts.map +1 -1
- package/dist/hooks/ultrawork/index.js +40 -82
- package/dist/hooks/ultrawork/index.js.map +1 -1
- package/dist/hooks/ultrawork/session-isolation.test.js +1 -1
- package/dist/hooks/ultrawork/session-isolation.test.js.map +1 -1
- package/dist/hud/elements/api-key-source.d.ts +30 -0
- package/dist/hud/elements/api-key-source.d.ts.map +1 -0
- package/dist/hud/elements/api-key-source.js +70 -0
- package/dist/hud/elements/api-key-source.js.map +1 -0
- package/dist/hud/elements/index.d.ts +1 -0
- package/dist/hud/elements/index.d.ts.map +1 -1
- package/dist/hud/elements/index.js +1 -0
- package/dist/hud/elements/index.js.map +1 -1
- package/dist/hud/elements/limits.d.ts +9 -1
- package/dist/hud/elements/limits.d.ts.map +1 -1
- package/dist/hud/elements/limits.js +18 -0
- package/dist/hud/elements/limits.js.map +1 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +40 -25
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/omc-state.d.ts.map +1 -1
- package/dist/hud/omc-state.js +6 -4
- package/dist/hud/omc-state.js.map +1 -1
- package/dist/hud/render.d.ts +9 -0
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +166 -15
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +23 -14
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +5 -2
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/types.d.ts +34 -3
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +19 -0
- package/dist/hud/types.js.map +1 -1
- package/dist/hud/usage-api.d.ts +9 -6
- package/dist/hud/usage-api.d.ts.map +1 -1
- package/dist/hud/usage-api.js +115 -46
- package/dist/hud/usage-api.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/installer/__tests__/claude-md-merge.test.js +29 -0
- package/dist/installer/__tests__/claude-md-merge.test.js.map +1 -1
- package/dist/installer/__tests__/safe-installer.test.js +1 -1
- package/dist/installer/__tests__/safe-installer.test.js.map +1 -1
- package/dist/installer/hooks.d.ts.map +1 -1
- package/dist/installer/hooks.js +11 -12
- package/dist/installer/hooks.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +20 -16
- package/dist/installer/index.js.map +1 -1
- package/dist/interop/__tests__/worker-adapter-integration.test.d.ts +2 -0
- package/dist/interop/__tests__/worker-adapter-integration.test.d.ts.map +1 -0
- package/dist/interop/__tests__/worker-adapter-integration.test.js +219 -0
- package/dist/interop/__tests__/worker-adapter-integration.test.js.map +1 -0
- package/dist/interop/__tests__/worker-adapter.test.d.ts +2 -0
- package/dist/interop/__tests__/worker-adapter.test.d.ts.map +1 -0
- package/dist/interop/__tests__/worker-adapter.test.js +408 -0
- package/dist/interop/__tests__/worker-adapter.test.js.map +1 -0
- package/dist/interop/adapter-types.d.ts +39 -0
- package/dist/interop/adapter-types.d.ts.map +1 -0
- package/dist/interop/adapter-types.js +9 -0
- package/dist/interop/adapter-types.js.map +1 -0
- package/dist/interop/worker-adapter.d.ts +116 -0
- package/dist/interop/worker-adapter.d.ts.map +1 -0
- package/dist/interop/worker-adapter.js +324 -0
- package/dist/interop/worker-adapter.js.map +1 -0
- package/dist/lib/__tests__/mode-state-io.test.d.ts +2 -0
- package/dist/lib/__tests__/mode-state-io.test.d.ts.map +1 -0
- package/dist/lib/__tests__/mode-state-io.test.js +194 -0
- package/dist/lib/__tests__/mode-state-io.test.js.map +1 -0
- package/dist/lib/__tests__/payload-limits.test.d.ts +2 -0
- package/dist/lib/__tests__/payload-limits.test.d.ts.map +1 -0
- package/dist/lib/__tests__/payload-limits.test.js +124 -0
- package/dist/lib/__tests__/payload-limits.test.js.map +1 -0
- package/dist/lib/__tests__/worktree-paths.test.js +5 -4
- package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
- package/dist/lib/file-lock.d.ts +75 -0
- package/dist/lib/file-lock.d.ts.map +1 -0
- package/dist/lib/file-lock.js +246 -0
- package/dist/lib/file-lock.js.map +1 -0
- package/dist/lib/mode-names.d.ts +10 -3
- package/dist/lib/mode-names.d.ts.map +1 -1
- package/dist/lib/mode-names.js +12 -17
- package/dist/lib/mode-names.js.map +1 -1
- package/dist/lib/mode-state-io.d.ts +41 -0
- package/dist/lib/mode-state-io.d.ts.map +1 -0
- package/dist/lib/mode-state-io.js +127 -0
- package/dist/lib/mode-state-io.js.map +1 -0
- package/dist/lib/payload-limits.d.ts +31 -0
- package/dist/lib/payload-limits.d.ts.map +1 -0
- package/dist/lib/payload-limits.js +82 -0
- package/dist/lib/payload-limits.js.map +1 -0
- package/dist/lib/project-memory-merge.d.ts +36 -0
- package/dist/lib/project-memory-merge.d.ts.map +1 -0
- package/dist/lib/project-memory-merge.js +154 -0
- package/dist/lib/project-memory-merge.js.map +1 -0
- package/dist/lib/shared-memory.d.ts +82 -0
- package/dist/lib/shared-memory.d.ts.map +1 -0
- package/dist/lib/shared-memory.js +305 -0
- package/dist/lib/shared-memory.js.map +1 -0
- package/dist/lib/worktree-paths.d.ts +31 -3
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +147 -12
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.d.ts +2 -0
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.js +84 -0
- package/dist/mcp/__tests__/team-server-artifact-convergence.test.js.map +1 -0
- package/dist/mcp/__tests__/team-server-deprecation.test.d.ts +2 -0
- package/dist/mcp/__tests__/team-server-deprecation.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/team-server-deprecation.test.js +54 -0
- package/dist/mcp/__tests__/team-server-deprecation.test.js.map +1 -0
- package/dist/mcp/omc-tools-server.d.ts +2 -0
- package/dist/mcp/omc-tools-server.d.ts.map +1 -1
- package/dist/mcp/omc-tools-server.js +9 -2
- package/dist/mcp/omc-tools-server.js.map +1 -1
- package/dist/mcp/team-job-convergence.d.ts +20 -0
- package/dist/mcp/team-job-convergence.d.ts.map +1 -0
- package/dist/mcp/team-job-convergence.js +101 -0
- package/dist/mcp/team-job-convergence.js.map +1 -0
- package/dist/mcp/team-server.d.ts +33 -10
- package/dist/mcp/team-server.d.ts.map +1 -1
- package/dist/mcp/team-server.js +219 -109
- package/dist/mcp/team-server.js.map +1 -1
- package/dist/notifications/__tests__/config.test.js +100 -6
- package/dist/notifications/__tests__/config.test.js.map +1 -1
- package/dist/notifications/__tests__/custom-integration.test.d.ts +8 -0
- package/dist/notifications/__tests__/custom-integration.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/custom-integration.test.js +297 -0
- package/dist/notifications/__tests__/custom-integration.test.js.map +1 -0
- package/dist/notifications/__tests__/dispatcher.test.js +195 -0
- package/dist/notifications/__tests__/dispatcher.test.js.map +1 -1
- package/dist/notifications/__tests__/redact.test.d.ts +2 -0
- package/dist/notifications/__tests__/redact.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/redact.test.js +111 -0
- package/dist/notifications/__tests__/redact.test.js.map +1 -0
- package/dist/notifications/__tests__/reply-listener.test.js +8 -1
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/__tests__/slack-socket.test.d.ts +2 -0
- package/dist/notifications/__tests__/slack-socket.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/slack-socket.test.js +293 -0
- package/dist/notifications/__tests__/slack-socket.test.js.map +1 -0
- package/dist/notifications/__tests__/template-engine.test.js +32 -0
- package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
- package/dist/notifications/config.d.ts +40 -0
- package/dist/notifications/config.d.ts.map +1 -1
- package/dist/notifications/config.js +201 -4
- package/dist/notifications/config.js.map +1 -1
- package/dist/notifications/dispatcher.d.ts +20 -1
- package/dist/notifications/dispatcher.d.ts.map +1 -1
- package/dist/notifications/dispatcher.js +183 -7
- package/dist/notifications/dispatcher.js.map +1 -1
- package/dist/notifications/hook-config-types.d.ts +1 -1
- package/dist/notifications/hook-config-types.d.ts.map +1 -1
- package/dist/notifications/index.d.ts +11 -2
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +13 -3
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/presets.d.ts +43 -0
- package/dist/notifications/presets.d.ts.map +1 -0
- package/dist/notifications/presets.js +122 -0
- package/dist/notifications/presets.js.map +1 -0
- package/dist/notifications/redact.d.ts +21 -0
- package/dist/notifications/redact.d.ts.map +1 -0
- package/dist/notifications/redact.js +33 -0
- package/dist/notifications/redact.js.map +1 -0
- package/dist/notifications/reply-listener.d.ts +54 -2
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +178 -15
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.d.ts +1 -1
- package/dist/notifications/session-registry.d.ts.map +1 -1
- package/dist/notifications/slack-socket.d.ts +242 -0
- package/dist/notifications/slack-socket.d.ts.map +1 -0
- package/dist/notifications/slack-socket.js +603 -0
- package/dist/notifications/slack-socket.js.map +1 -0
- package/dist/notifications/template-engine.d.ts.map +1 -1
- package/dist/notifications/template-engine.js +4 -0
- package/dist/notifications/template-engine.js.map +1 -1
- package/dist/notifications/template-variables.d.ts +26 -0
- package/dist/notifications/template-variables.d.ts.map +1 -0
- package/dist/notifications/template-variables.js +139 -0
- package/dist/notifications/template-variables.js.map +1 -0
- package/dist/notifications/types.d.ts +75 -2
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/validation.d.ts +24 -0
- package/dist/notifications/validation.d.ts.map +1 -0
- package/dist/notifications/validation.js +167 -0
- package/dist/notifications/validation.js.map +1 -0
- package/dist/openclaw/__tests__/dispatcher.test.js +0 -1
- package/dist/openclaw/__tests__/dispatcher.test.js.map +1 -1
- package/dist/openclaw/__tests__/index.test.js +82 -0
- package/dist/openclaw/__tests__/index.test.js.map +1 -1
- package/dist/openclaw/index.d.ts.map +1 -1
- package/dist/openclaw/index.js +24 -1
- package/dist/openclaw/index.js.map +1 -1
- package/dist/openclaw/types.d.ts +12 -0
- package/dist/openclaw/types.d.ts.map +1 -1
- package/dist/shared/types.d.ts +84 -20
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/team/__tests__/api-interop.command-dialect.test.d.ts +2 -0
- package/dist/team/__tests__/api-interop.command-dialect.test.d.ts.map +1 -0
- package/dist/team/__tests__/api-interop.command-dialect.test.js +26 -0
- package/dist/team/__tests__/api-interop.command-dialect.test.js.map +1 -0
- package/dist/team/__tests__/api-interop.compatibility.test.d.ts +2 -0
- package/dist/team/__tests__/api-interop.compatibility.test.d.ts.map +1 -0
- package/dist/team/__tests__/api-interop.compatibility.test.js +93 -0
- package/dist/team/__tests__/api-interop.compatibility.test.js.map +1 -0
- package/dist/team/__tests__/cli-path-resolution.test.d.ts +2 -0
- package/dist/team/__tests__/cli-path-resolution.test.d.ts.map +1 -0
- package/dist/team/__tests__/cli-path-resolution.test.js +281 -0
- package/dist/team/__tests__/cli-path-resolution.test.js.map +1 -0
- package/dist/team/__tests__/index.compat-exports.test.d.ts +2 -0
- package/dist/team/__tests__/index.compat-exports.test.d.ts.map +1 -0
- package/dist/team/__tests__/index.compat-exports.test.js +20 -0
- package/dist/team/__tests__/index.compat-exports.test.js.map +1 -0
- package/dist/team/__tests__/layout-stabilizer.test.d.ts +2 -0
- package/dist/team/__tests__/layout-stabilizer.test.d.ts.map +1 -0
- package/dist/team/__tests__/layout-stabilizer.test.js +217 -0
- package/dist/team/__tests__/layout-stabilizer.test.js.map +1 -0
- package/dist/team/__tests__/mcp-team-bridge.spawn-args.test.js +8 -4
- package/dist/team/__tests__/mcp-team-bridge.spawn-args.test.js.map +1 -1
- package/dist/team/__tests__/mcp-team-bridge.usage.test.js +4 -2
- package/dist/team/__tests__/mcp-team-bridge.usage.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +84 -8
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/pane-readiness.test.d.ts +2 -0
- package/dist/team/__tests__/pane-readiness.test.d.ts.map +1 -0
- package/dist/team/__tests__/pane-readiness.test.js +185 -0
- package/dist/team/__tests__/pane-readiness.test.js.map +1 -0
- package/dist/team/__tests__/prompt-sanitization.test.js +1 -1
- package/dist/team/__tests__/prompt-sanitization.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-cli.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-cli.test.js +159 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -0
- package/dist/team/__tests__/runtime-done-recovery.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-done-recovery.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-done-recovery.test.js +77 -0
- package/dist/team/__tests__/runtime-done-recovery.test.js.map +1 -0
- package/dist/team/__tests__/runtime-interop-spawn-regression.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-interop-spawn-regression.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-interop-spawn-regression.test.js +139 -0
- package/dist/team/__tests__/runtime-interop-spawn-regression.test.js.map +1 -0
- package/dist/team/__tests__/runtime-prompt-mode.test.js +167 -14
- package/dist/team/__tests__/runtime-prompt-mode.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.feature-flag.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-v2.feature-flag.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-v2.feature-flag.test.js +20 -0
- package/dist/team/__tests__/runtime-v2.feature-flag.test.js.map +1 -0
- package/dist/team/__tests__/runtime-watchdog-retry.test.d.ts +2 -0
- package/dist/team/__tests__/runtime-watchdog-retry.test.d.ts.map +1 -0
- package/dist/team/__tests__/runtime-watchdog-retry.test.js +408 -0
- package/dist/team/__tests__/runtime-watchdog-retry.test.js.map +1 -0
- package/dist/team/__tests__/shell-path.test.d.ts +2 -0
- package/dist/team/__tests__/shell-path.test.d.ts.map +1 -0
- package/dist/team/__tests__/shell-path.test.js +193 -0
- package/dist/team/__tests__/shell-path.test.js.map +1 -0
- package/dist/team/__tests__/state-paths.test.d.ts +2 -0
- package/dist/team/__tests__/state-paths.test.d.ts.map +1 -0
- package/dist/team/__tests__/state-paths.test.js +16 -0
- package/dist/team/__tests__/state-paths.test.js.map +1 -0
- package/dist/team/__tests__/task-file-ops.test.js +8 -2
- package/dist/team/__tests__/task-file-ops.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.create-team.test.js +15 -17
- package/dist/team/__tests__/tmux-session.create-team.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.kill-team-session.test.d.ts +2 -0
- package/dist/team/__tests__/tmux-session.kill-team-session.test.d.ts.map +1 -0
- package/dist/team/__tests__/tmux-session.kill-team-session.test.js +56 -0
- package/dist/team/__tests__/tmux-session.kill-team-session.test.js.map +1 -0
- package/dist/team/__tests__/tmux-session.spawn.test.js +9 -0
- package/dist/team/__tests__/tmux-session.spawn.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +83 -5
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/wait-for-shell-ready.test.d.ts +2 -0
- package/dist/team/__tests__/wait-for-shell-ready.test.d.ts.map +1 -0
- package/dist/team/__tests__/wait-for-shell-ready.test.js +242 -0
- package/dist/team/__tests__/wait-for-shell-ready.test.js.map +1 -0
- package/dist/team/__tests__/worker-bootstrap.test.js +11 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/api-interop.d.ts +20 -0
- package/dist/team/api-interop.d.ts.map +1 -0
- package/dist/team/api-interop.js +601 -0
- package/dist/team/api-interop.js.map +1 -0
- package/dist/team/contracts.d.ts +14 -0
- package/dist/team/contracts.d.ts.map +1 -0
- package/dist/team/contracts.js +32 -0
- package/dist/team/contracts.js.map +1 -0
- package/dist/team/dispatch-queue.d.ts +64 -0
- package/dist/team/dispatch-queue.d.ts.map +1 -0
- package/dist/team/dispatch-queue.js +288 -0
- package/dist/team/dispatch-queue.js.map +1 -0
- package/dist/team/events.d.ts +49 -0
- package/dist/team/events.d.ts.map +1 -0
- package/dist/team/events.js +113 -0
- package/dist/team/events.js.map +1 -0
- package/dist/team/index.d.ts +15 -2
- package/dist/team/index.d.ts.map +1 -1
- package/dist/team/index.js +14 -1
- package/dist/team/index.js.map +1 -1
- package/dist/team/layout-stabilizer.d.ts +23 -0
- package/dist/team/layout-stabilizer.d.ts.map +1 -0
- package/dist/team/layout-stabilizer.js +117 -0
- package/dist/team/layout-stabilizer.js.map +1 -0
- package/dist/team/mcp-comm.d.ts +99 -0
- package/dist/team/mcp-comm.d.ts.map +1 -0
- package/dist/team/mcp-comm.js +235 -0
- package/dist/team/mcp-comm.js.map +1 -0
- package/dist/team/mcp-team-bridge.d.ts +2 -2
- package/dist/team/mcp-team-bridge.d.ts.map +1 -1
- package/dist/team/mcp-team-bridge.js +263 -144
- package/dist/team/mcp-team-bridge.js.map +1 -1
- package/dist/team/model-contract.d.ts +28 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +143 -5
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/monitor.d.ts +45 -0
- package/dist/team/monitor.d.ts.map +1 -0
- package/dist/team/monitor.js +339 -0
- package/dist/team/monitor.js.map +1 -0
- package/dist/team/runtime-cli.d.ts +24 -0
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +230 -12
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime-v2.d.ts +122 -0
- package/dist/team/runtime-v2.d.ts.map +1 -0
- package/dist/team/runtime-v2.js +766 -0
- package/dist/team/runtime-v2.js.map +1 -0
- package/dist/team/runtime.d.ts +2 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +145 -52
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts +59 -0
- package/dist/team/scaling.d.ts.map +1 -0
- package/dist/team/scaling.js +311 -0
- package/dist/team/scaling.js.map +1 -0
- package/dist/team/sentinel-gate.d.ts +23 -0
- package/dist/team/sentinel-gate.d.ts.map +1 -0
- package/dist/team/sentinel-gate.js +127 -0
- package/dist/team/sentinel-gate.js.map +1 -0
- package/dist/team/shell-path.d.ts +21 -0
- package/dist/team/shell-path.d.ts.map +1 -0
- package/dist/team/shell-path.js +73 -0
- package/dist/team/shell-path.js.map +1 -0
- package/dist/team/state/tasks.d.ts +48 -0
- package/dist/team/state/tasks.d.ts.map +1 -0
- package/dist/team/state/tasks.js +184 -0
- package/dist/team/state/tasks.js.map +1 -0
- package/dist/team/state-paths.d.ts +20 -4
- package/dist/team/state-paths.d.ts.map +1 -1
- package/dist/team/state-paths.js +29 -6
- package/dist/team/state-paths.js.map +1 -1
- package/dist/team/task-file-ops.d.ts +2 -1
- package/dist/team/task-file-ops.d.ts.map +1 -1
- package/dist/team/task-file-ops.js +19 -0
- package/dist/team/task-file-ops.js.map +1 -1
- package/dist/team/team-ops.d.ts +44 -0
- package/dist/team/team-ops.d.ts.map +1 -0
- package/dist/team/team-ops.js +547 -0
- package/dist/team/team-ops.js.map +1 -0
- package/dist/team/tmux-comm.d.ts.map +1 -1
- package/dist/team/tmux-comm.js +107 -44
- package/dist/team/tmux-comm.js.map +1 -1
- package/dist/team/tmux-session.d.ts +5 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +81 -10
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/types.d.ts +303 -8
- package/dist/team/types.d.ts.map +1 -1
- package/dist/team/types.js +2 -1
- package/dist/team/types.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +73 -21
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/tools/__tests__/cancel-integration.test.d.ts +2 -0
- package/dist/tools/__tests__/cancel-integration.test.d.ts.map +1 -0
- package/dist/tools/__tests__/cancel-integration.test.js +248 -0
- package/dist/tools/__tests__/cancel-integration.test.js.map +1 -0
- package/dist/tools/__tests__/memory-tools.test.d.ts +2 -0
- package/dist/tools/__tests__/memory-tools.test.d.ts.map +1 -0
- package/dist/tools/__tests__/memory-tools.test.js +66 -0
- package/dist/tools/__tests__/memory-tools.test.js.map +1 -0
- package/dist/tools/__tests__/state-tools.test.js +73 -5
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/dist/tools/lsp/__tests__/client-eviction.test.js +6 -6
- package/dist/tools/lsp/__tests__/client-eviction.test.js.map +1 -1
- package/dist/tools/lsp/__tests__/client-timeout-env.test.d.ts +2 -0
- package/dist/tools/lsp/__tests__/client-timeout-env.test.d.ts.map +1 -0
- package/dist/tools/lsp/__tests__/client-timeout-env.test.js +39 -0
- package/dist/tools/lsp/__tests__/client-timeout-env.test.js.map +1 -0
- package/dist/tools/lsp/client.d.ts +2 -0
- package/dist/tools/lsp/client.d.ts.map +1 -1
- package/dist/tools/lsp/client.js +11 -1
- package/dist/tools/lsp/client.js.map +1 -1
- package/dist/tools/lsp/index.d.ts +1 -1
- package/dist/tools/lsp/index.d.ts.map +1 -1
- package/dist/tools/lsp/index.js +1 -1
- package/dist/tools/lsp/index.js.map +1 -1
- package/dist/tools/memory-tools.d.ts.map +1 -1
- package/dist/tools/memory-tools.js +2 -1
- package/dist/tools/memory-tools.js.map +1 -1
- package/dist/tools/python-repl/__tests__/tcp-fallback.test.d.ts +2 -0
- package/dist/tools/python-repl/__tests__/tcp-fallback.test.d.ts.map +1 -0
- package/dist/tools/python-repl/__tests__/tcp-fallback.test.js +138 -0
- package/dist/tools/python-repl/__tests__/tcp-fallback.test.js.map +1 -0
- package/dist/tools/python-repl/bridge-manager.d.ts.map +1 -1
- package/dist/tools/python-repl/bridge-manager.js +104 -24
- package/dist/tools/python-repl/bridge-manager.js.map +1 -1
- package/dist/tools/python-repl/paths.d.ts +8 -0
- package/dist/tools/python-repl/paths.d.ts.map +1 -1
- package/dist/tools/python-repl/paths.js +10 -0
- package/dist/tools/python-repl/paths.js.map +1 -1
- package/dist/tools/python-repl/socket-client.d.ts.map +1 -1
- package/dist/tools/python-repl/socket-client.js +9 -2
- package/dist/tools/python-repl/socket-client.js.map +1 -1
- package/dist/tools/shared-memory-tools.d.ts +55 -0
- package/dist/tools/shared-memory-tools.d.ts.map +1 -0
- package/dist/tools/shared-memory-tools.js +250 -0
- package/dist/tools/shared-memory-tools.js.map +1 -0
- package/dist/tools/state-tools.d.ts.map +1 -1
- package/dist/tools/state-tools.js +126 -101
- package/dist/tools/state-tools.js.map +1 -1
- package/dist/utils/daemon-module-path.d.ts +9 -0
- package/dist/utils/daemon-module-path.d.ts.map +1 -0
- package/dist/utils/daemon-module-path.js +23 -0
- package/dist/utils/daemon-module-path.js.map +1 -0
- package/dist/utils/jsonc.d.ts +16 -0
- package/dist/utils/jsonc.d.ts.map +1 -0
- package/dist/utils/jsonc.js +64 -0
- package/dist/utils/jsonc.js.map +1 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/ssrf-guard.d.ts +26 -0
- package/dist/utils/ssrf-guard.d.ts.map +1 -0
- package/dist/utils/ssrf-guard.js +103 -0
- package/dist/utils/ssrf-guard.js.map +1 -0
- package/dist/verification/tier-selector.d.ts +1 -1
- package/dist/verification/tier-selector.js +1 -1
- package/docs/ANALYTICS-SYSTEM.md +2 -5
- package/docs/CLAUDE.md +88 -212
- package/docs/MIGRATION.md +138 -161
- package/docs/PERFORMANCE-MONITORING.md +5 -5
- package/docs/REFERENCE.md +351 -286
- package/docs/fixes/agent-prefix-routing-fix.md +105 -0
- package/docs/ko/ARCHITECTURE.md +152 -0
- package/docs/ko/FEATURES.md +582 -0
- package/docs/ko/MIGRATION.md +1027 -0
- package/docs/ko/REFERENCE.md +745 -0
- package/docs/partials/mode-selection-guide.md +22 -12
- package/docs/shared/mode-selection-guide.md +22 -12
- package/package.json +11 -6
- package/scripts/ask-codex.sh +24 -0
- package/scripts/ask-gemini.sh +24 -0
- package/scripts/build-cli.mjs +47 -0
- package/scripts/build-runtime-cli.mjs +2 -0
- package/scripts/context-guard-stop.mjs +66 -3
- package/scripts/context-safety.mjs +67 -2
- package/scripts/keyword-detector.mjs +14 -36
- package/scripts/persistent-mode.cjs +106 -1
- package/scripts/persistent-mode.mjs +88 -2
- package/scripts/plugin-setup.mjs +46 -4
- package/scripts/pre-tool-enforcer.mjs +1 -0
- package/scripts/qa-tests/test-custom-integration.mjs +144 -0
- package/scripts/run-provider-advisor.js +220 -0
- package/scripts/session-start.mjs +42 -2
- package/skills/AGENTS.md +16 -20
- package/skills/ask-codex/SKILL.md +47 -0
- package/skills/ask-gemini/SKILL.md +47 -0
- package/skills/autopilot/SKILL.md +39 -5
- package/skills/cancel/SKILL.md +23 -105
- package/skills/ccg/SKILL.md +58 -75
- package/skills/configure-notifications/SKILL.md +276 -0
- package/skills/configure-openclaw/SKILL.md +63 -0
- package/skills/deep-interview/SKILL.md +551 -0
- package/skills/deepinit/SKILL.md +1 -1
- package/skills/hud/SKILL.md +35 -3
- package/skills/omc-doctor/SKILL.md +30 -13
- package/skills/omc-help/SKILL.md +1 -1
- package/skills/omc-setup/SKILL.md +20 -20
- package/skills/omc-teams/SKILL.md +60 -112
- package/skills/plan/SKILL.md +5 -5
- package/skills/quick-init-project/SKILL.md +333 -0
- package/skills/ralph/SKILL.md +96 -70
- package/skills/ralplan/SKILL.md +7 -7
- package/skills/security-review/SKILL.md +2 -2
- package/skills/team/SKILL.md +36 -2
- package/skills/ultrawork/SKILL.md +7 -7
- package/templates/hooks/keyword-detector.mjs +20 -28
- package/templates/hooks/persistent-mode.mjs +89 -2
- package/templates/hooks/pre-tool-use.mjs +79 -0
- package/templates/hooks/session-start.mjs +3 -3
- package/skills/pipeline/SKILL.md +0 -434
- package/skills/review/SKILL.md +0 -30
- package/skills/ultrapilot/SKILL.md +0 -632
package/bridge/runtime-cli.cjs
CHANGED
|
@@ -5,6 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
8
15
|
var __copyProps = (to, from, except, desc) => {
|
|
9
16
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
17
|
for (let key of __getOwnPropNames(from))
|
|
@@ -21,22 +28,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
28
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
29
|
mod
|
|
23
30
|
));
|
|
24
|
-
|
|
25
|
-
// src/team/runtime-cli.ts
|
|
26
|
-
var import_fs7 = require("fs");
|
|
27
|
-
var import_promises4 = require("fs/promises");
|
|
28
|
-
var import_path10 = require("path");
|
|
29
|
-
|
|
30
|
-
// src/team/runtime.ts
|
|
31
|
-
var import_promises3 = require("fs/promises");
|
|
32
|
-
var import_path9 = require("path");
|
|
33
|
-
var import_fs6 = require("fs");
|
|
34
|
-
|
|
35
|
-
// src/team/model-contract.ts
|
|
36
|
-
var import_child_process = require("child_process");
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
32
|
|
|
38
33
|
// src/team/team-name.ts
|
|
39
|
-
var TEAM_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,48}[a-z0-9]$/;
|
|
40
34
|
function validateTeamName(teamName) {
|
|
41
35
|
if (!TEAM_NAME_PATTERN.test(teamName)) {
|
|
42
36
|
throw new Error(
|
|
@@ -45,130 +39,40 @@ function validateTeamName(teamName) {
|
|
|
45
39
|
}
|
|
46
40
|
return teamName;
|
|
47
41
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
binary: "claude",
|
|
54
|
-
installInstructions: "Install Claude CLI: https://claude.ai/download",
|
|
55
|
-
buildLaunchArgs(model, extraFlags = []) {
|
|
56
|
-
const args = ["--dangerously-skip-permissions"];
|
|
57
|
-
if (model) args.push("--model", model);
|
|
58
|
-
return [...args, ...extraFlags];
|
|
59
|
-
},
|
|
60
|
-
parseOutput(rawOutput) {
|
|
61
|
-
return rawOutput.trim();
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
codex: {
|
|
65
|
-
agentType: "codex",
|
|
66
|
-
binary: "codex",
|
|
67
|
-
installInstructions: "Install Codex CLI: npm install -g @openai/codex",
|
|
68
|
-
supportsPromptMode: true,
|
|
69
|
-
// Codex accepts prompt as a positional argument (no flag needed):
|
|
70
|
-
// codex [OPTIONS] [PROMPT]
|
|
71
|
-
buildLaunchArgs(model, extraFlags = []) {
|
|
72
|
-
const args = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
73
|
-
if (model) args.push("--model", model);
|
|
74
|
-
return [...args, ...extraFlags];
|
|
75
|
-
},
|
|
76
|
-
parseOutput(rawOutput) {
|
|
77
|
-
const lines = rawOutput.trim().split("\n").filter(Boolean);
|
|
78
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(lines[i]);
|
|
81
|
-
if (parsed.type === "message" && parsed.role === "assistant") {
|
|
82
|
-
return parsed.content ?? rawOutput;
|
|
83
|
-
}
|
|
84
|
-
if (parsed.type === "result" || parsed.output) {
|
|
85
|
-
return parsed.output ?? parsed.result ?? rawOutput;
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return rawOutput.trim();
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
gemini: {
|
|
94
|
-
agentType: "gemini",
|
|
95
|
-
binary: "gemini",
|
|
96
|
-
installInstructions: "Install Gemini CLI: npm install -g @google/gemini-cli",
|
|
97
|
-
supportsPromptMode: true,
|
|
98
|
-
promptModeFlag: "-p",
|
|
99
|
-
buildLaunchArgs(model, extraFlags = []) {
|
|
100
|
-
const args = ["--yolo"];
|
|
101
|
-
if (model) args.push("--model", model);
|
|
102
|
-
return [...args, ...extraFlags];
|
|
103
|
-
},
|
|
104
|
-
parseOutput(rawOutput) {
|
|
105
|
-
return rawOutput.trim();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
function getContract(agentType) {
|
|
110
|
-
const contract = CONTRACTS[agentType];
|
|
111
|
-
if (!contract) {
|
|
112
|
-
throw new Error(`Unknown agent type: ${agentType}. Supported: ${Object.keys(CONTRACTS).join(", ")}`);
|
|
113
|
-
}
|
|
114
|
-
return contract;
|
|
115
|
-
}
|
|
116
|
-
function isCliAvailable(agentType) {
|
|
117
|
-
const contract = getContract(agentType);
|
|
118
|
-
try {
|
|
119
|
-
const result = (0, import_child_process.spawnSync)(contract.binary, ["--version"], { timeout: 5e3, shell: true });
|
|
120
|
-
return result.status === 0;
|
|
121
|
-
} catch {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function validateCliAvailable(agentType) {
|
|
126
|
-
if (!isCliAvailable(agentType)) {
|
|
127
|
-
const contract = getContract(agentType);
|
|
128
|
-
throw new Error(
|
|
129
|
-
`CLI agent '${agentType}' not found. ${contract.installInstructions}`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function buildLaunchArgs(agentType, config) {
|
|
134
|
-
return getContract(agentType).buildLaunchArgs(config.model, config.extraFlags);
|
|
135
|
-
}
|
|
136
|
-
function buildWorkerArgv(agentType, config) {
|
|
137
|
-
validateTeamName(config.teamName);
|
|
138
|
-
const contract = getContract(agentType);
|
|
139
|
-
const args = buildLaunchArgs(agentType, config);
|
|
140
|
-
return [contract.binary, ...args];
|
|
141
|
-
}
|
|
142
|
-
function getWorkerEnv(teamName, workerName2, agentType) {
|
|
143
|
-
validateTeamName(teamName);
|
|
144
|
-
return {
|
|
145
|
-
OMC_TEAM_WORKER: `${teamName}/${workerName2}`,
|
|
146
|
-
OMC_TEAM_NAME: teamName,
|
|
147
|
-
OMC_WORKER_AGENT_TYPE: agentType
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function isPromptModeAgent(agentType) {
|
|
151
|
-
const contract = getContract(agentType);
|
|
152
|
-
return !!contract.supportsPromptMode;
|
|
153
|
-
}
|
|
154
|
-
function getPromptModeArgs(agentType, instruction) {
|
|
155
|
-
const contract = getContract(agentType);
|
|
156
|
-
if (!contract.supportsPromptMode) {
|
|
157
|
-
return [];
|
|
158
|
-
}
|
|
159
|
-
if (contract.promptModeFlag) {
|
|
160
|
-
return [contract.promptModeFlag, instruction];
|
|
42
|
+
var TEAM_NAME_PATTERN;
|
|
43
|
+
var init_team_name = __esm({
|
|
44
|
+
"src/team/team-name.ts"() {
|
|
45
|
+
"use strict";
|
|
46
|
+
TEAM_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,48}[a-z0-9]$/;
|
|
161
47
|
}
|
|
162
|
-
|
|
163
|
-
}
|
|
48
|
+
});
|
|
164
49
|
|
|
165
50
|
// src/team/tmux-session.ts
|
|
166
|
-
var
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
51
|
+
var tmux_session_exports = {};
|
|
52
|
+
__export(tmux_session_exports, {
|
|
53
|
+
buildWorkerStartCommand: () => buildWorkerStartCommand,
|
|
54
|
+
createSession: () => createSession,
|
|
55
|
+
createTeamSession: () => createTeamSession,
|
|
56
|
+
getDefaultShell: () => getDefaultShell,
|
|
57
|
+
injectToLeaderPane: () => injectToLeaderPane,
|
|
58
|
+
isSessionAlive: () => isSessionAlive,
|
|
59
|
+
isUnixLikeOnWindows: () => isUnixLikeOnWindows,
|
|
60
|
+
isWorkerAlive: () => isWorkerAlive,
|
|
61
|
+
killSession: () => killSession,
|
|
62
|
+
killTeamSession: () => killTeamSession,
|
|
63
|
+
killWorkerPanes: () => killWorkerPanes,
|
|
64
|
+
listActiveSessions: () => listActiveSessions,
|
|
65
|
+
paneHasActiveTask: () => paneHasActiveTask,
|
|
66
|
+
paneLooksReady: () => paneLooksReady,
|
|
67
|
+
sanitizeName: () => sanitizeName,
|
|
68
|
+
sendToWorker: () => sendToWorker,
|
|
69
|
+
sessionName: () => sessionName,
|
|
70
|
+
shouldAttemptAdaptiveRetry: () => shouldAttemptAdaptiveRetry,
|
|
71
|
+
spawnBridgeInSession: () => spawnBridgeInSession,
|
|
72
|
+
spawnWorkerInPane: () => spawnWorkerInPane,
|
|
73
|
+
validateTmux: () => validateTmux,
|
|
74
|
+
waitForPaneReady: () => waitForPaneReady
|
|
75
|
+
});
|
|
172
76
|
function isUnixLikeOnWindows() {
|
|
173
77
|
return process.platform === "win32" && !!(process.env.MSYSTEM || process.env.MINGW_PREFIX);
|
|
174
78
|
}
|
|
@@ -189,7 +93,7 @@ function escapeForCmdSet(value) {
|
|
|
189
93
|
return value.replace(/"/g, '""');
|
|
190
94
|
}
|
|
191
95
|
function shellNameFromPath(shellPath) {
|
|
192
|
-
const shellName = (0,
|
|
96
|
+
const shellName = (0, import_path2.basename)(shellPath.replace(/\\/g, "/"));
|
|
193
97
|
return shellName.replace(/\.(exe|cmd|bat)$/i, "");
|
|
194
98
|
}
|
|
195
99
|
function shellEscape(value) {
|
|
@@ -200,8 +104,26 @@ function assertSafeEnvKey(key) {
|
|
|
200
104
|
throw new Error(`Invalid environment key: "${key}"`);
|
|
201
105
|
}
|
|
202
106
|
}
|
|
107
|
+
function isAbsoluteLaunchBinaryPath(value) {
|
|
108
|
+
return (0, import_path2.isAbsolute)(value) || import_path2.win32.isAbsolute(value);
|
|
109
|
+
}
|
|
110
|
+
function assertSafeLaunchBinary(launchBinary) {
|
|
111
|
+
if (launchBinary.trim().length === 0) {
|
|
112
|
+
throw new Error("Invalid launchBinary: value cannot be empty");
|
|
113
|
+
}
|
|
114
|
+
if (launchBinary !== launchBinary.trim()) {
|
|
115
|
+
throw new Error("Invalid launchBinary: value cannot have leading/trailing whitespace");
|
|
116
|
+
}
|
|
117
|
+
if (DANGEROUS_LAUNCH_BINARY_CHARS.test(launchBinary)) {
|
|
118
|
+
throw new Error("Invalid launchBinary: contains dangerous shell metacharacters");
|
|
119
|
+
}
|
|
120
|
+
if (/\s/.test(launchBinary) && !isAbsoluteLaunchBinaryPath(launchBinary)) {
|
|
121
|
+
throw new Error("Invalid launchBinary: paths with spaces must be absolute");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
203
124
|
function getLaunchWords(config) {
|
|
204
125
|
if (config.launchBinary) {
|
|
126
|
+
assertSafeLaunchBinary(config.launchBinary);
|
|
205
127
|
return [config.launchBinary, ...config.launchArgs ?? []];
|
|
206
128
|
}
|
|
207
129
|
if (config.launchCmd) {
|
|
@@ -212,6 +134,7 @@ function getLaunchWords(config) {
|
|
|
212
134
|
function buildWorkerStartCommand(config) {
|
|
213
135
|
const shell = getDefaultShell();
|
|
214
136
|
const launchWords = getLaunchWords(config);
|
|
137
|
+
const shouldSourceRc = process.env.OMC_TEAM_NO_RC !== "1";
|
|
215
138
|
if (process.platform === "win32" && !isUnixLikeOnWindows()) {
|
|
216
139
|
const envPrefix = Object.entries(config.envVars).map(([k, v]) => {
|
|
217
140
|
assertSafeEnvKey(k);
|
|
@@ -227,8 +150,9 @@ function buildWorkerStartCommand(config) {
|
|
|
227
150
|
return `${key}=${shellEscape(value)}`;
|
|
228
151
|
});
|
|
229
152
|
const shellName2 = shellNameFromPath(shell) || "bash";
|
|
153
|
+
const execArgsCommand = shellName2 === "fish" ? "exec $argv" : 'exec "$@"';
|
|
230
154
|
const rcFile2 = process.env.HOME ? `${process.env.HOME}/.${shellName2}rc` : "";
|
|
231
|
-
const script = rcFile2 ? `[ -f ${shellEscape(rcFile2)} ] && . ${shellEscape(rcFile2)};
|
|
155
|
+
const script = shouldSourceRc && rcFile2 ? `[ -f ${shellEscape(rcFile2)} ] && . ${shellEscape(rcFile2)}; ${execArgsCommand}` : execArgsCommand;
|
|
232
156
|
return [
|
|
233
157
|
"env",
|
|
234
158
|
...envAssignments,
|
|
@@ -245,9 +169,18 @@ function buildWorkerStartCommand(config) {
|
|
|
245
169
|
}).join(" ");
|
|
246
170
|
const shellName = shellNameFromPath(shell) || "bash";
|
|
247
171
|
const rcFile = process.env.HOME ? `${process.env.HOME}/.${shellName}rc` : "";
|
|
248
|
-
const sourceCmd = rcFile ? `[ -f "${rcFile}" ] && source "${rcFile}"; ` : "";
|
|
172
|
+
const sourceCmd = shouldSourceRc && rcFile ? `[ -f "${rcFile}" ] && source "${rcFile}"; ` : "";
|
|
249
173
|
return `env ${envString} ${shell} -c "${sourceCmd}exec ${launchWords[0]}"`;
|
|
250
174
|
}
|
|
175
|
+
function validateTmux() {
|
|
176
|
+
try {
|
|
177
|
+
(0, import_child_process2.execSync)("tmux -V", { encoding: "utf-8", timeout: 5e3, stdio: "pipe" });
|
|
178
|
+
} catch {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"tmux is not available. Install it:\n macOS: brew install tmux\n Ubuntu/Debian: sudo apt-get install tmux\n Fedora: sudo dnf install tmux\n Arch: sudo pacman -S tmux"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
251
184
|
function sanitizeName(name) {
|
|
252
185
|
const sanitized = name.replace(/[^a-zA-Z0-9-]/g, "");
|
|
253
186
|
if (sanitized.length === 0) {
|
|
@@ -258,18 +191,88 @@ function sanitizeName(name) {
|
|
|
258
191
|
}
|
|
259
192
|
return sanitized.slice(0, 50);
|
|
260
193
|
}
|
|
194
|
+
function sessionName(teamName, workerName2) {
|
|
195
|
+
return `${TMUX_SESSION_PREFIX}-${sanitizeName(teamName)}-${sanitizeName(workerName2)}`;
|
|
196
|
+
}
|
|
197
|
+
function createSession(teamName, workerName2, workingDirectory) {
|
|
198
|
+
const name = sessionName(teamName, workerName2);
|
|
199
|
+
try {
|
|
200
|
+
(0, import_child_process2.execFileSync)("tmux", ["kill-session", "-t", name], { stdio: "pipe", timeout: 5e3 });
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
const args = ["new-session", "-d", "-s", name, "-x", "200", "-y", "50"];
|
|
204
|
+
if (workingDirectory) {
|
|
205
|
+
args.push("-c", workingDirectory);
|
|
206
|
+
}
|
|
207
|
+
(0, import_child_process2.execFileSync)("tmux", args, { stdio: "pipe", timeout: 5e3 });
|
|
208
|
+
return name;
|
|
209
|
+
}
|
|
210
|
+
function killSession(teamName, workerName2) {
|
|
211
|
+
const name = sessionName(teamName, workerName2);
|
|
212
|
+
try {
|
|
213
|
+
(0, import_child_process2.execFileSync)("tmux", ["kill-session", "-t", name], { stdio: "pipe", timeout: 5e3 });
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function isSessionAlive(teamName, workerName2) {
|
|
218
|
+
const name = sessionName(teamName, workerName2);
|
|
219
|
+
try {
|
|
220
|
+
(0, import_child_process2.execFileSync)("tmux", ["has-session", "-t", name], { stdio: "pipe", timeout: 5e3 });
|
|
221
|
+
return true;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function listActiveSessions(teamName) {
|
|
227
|
+
const prefix = `${TMUX_SESSION_PREFIX}-${sanitizeName(teamName)}-`;
|
|
228
|
+
try {
|
|
229
|
+
const fmtArgs = ["list-sessions", "-F", "#{session_name}"];
|
|
230
|
+
const shellCmd = "tmux " + fmtArgs.map((a) => `"${a.replace(/"/g, '\\"')}"`).join(" ");
|
|
231
|
+
const output = (0, import_child_process2.execSync)(shellCmd, {
|
|
232
|
+
encoding: "utf-8",
|
|
233
|
+
timeout: 5e3,
|
|
234
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
235
|
+
});
|
|
236
|
+
return output.trim().split("\n").filter((s) => s.startsWith(prefix)).map((s) => s.slice(prefix.length));
|
|
237
|
+
} catch {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function spawnBridgeInSession(tmuxSession, bridgeScriptPath, configFilePath) {
|
|
242
|
+
const cmd = `node "${bridgeScriptPath}" --config "${configFilePath}"`;
|
|
243
|
+
(0, import_child_process2.execFileSync)("tmux", ["send-keys", "-t", tmuxSession, cmd, "Enter"], { stdio: "pipe", timeout: 5e3 });
|
|
244
|
+
}
|
|
261
245
|
async function createTeamSession(teamName, workerCount, cwd) {
|
|
262
246
|
const { execFile: execFile2 } = await import("child_process");
|
|
263
247
|
const { promisify: promisify2 } = await import("util");
|
|
264
248
|
const execFileAsync = promisify2(execFile2);
|
|
265
|
-
|
|
266
|
-
throw new Error("Team mode requires running inside tmux. Start one: tmux new-session");
|
|
267
|
-
}
|
|
249
|
+
const inTmux = Boolean(process.env.TMUX);
|
|
268
250
|
const envPaneIdRaw = (process.env.TMUX_PANE ?? "").trim();
|
|
269
251
|
const envPaneId = /^%\d+$/.test(envPaneIdRaw) ? envPaneIdRaw : "";
|
|
270
252
|
let sessionAndWindow = "";
|
|
271
253
|
let leaderPaneId = envPaneId;
|
|
272
|
-
if (
|
|
254
|
+
if (!inTmux) {
|
|
255
|
+
const detachedSessionName = `${TMUX_SESSION_PREFIX}-${sanitizeName(teamName)}-${Date.now().toString(36)}`;
|
|
256
|
+
const detachedResult = await execFileAsync("tmux", [
|
|
257
|
+
"new-session",
|
|
258
|
+
"-d",
|
|
259
|
+
"-P",
|
|
260
|
+
"-F",
|
|
261
|
+
"#S:0 #{pane_id}",
|
|
262
|
+
"-s",
|
|
263
|
+
detachedSessionName,
|
|
264
|
+
"-c",
|
|
265
|
+
cwd
|
|
266
|
+
]);
|
|
267
|
+
const detachedLine = detachedResult.stdout.trim();
|
|
268
|
+
const detachedMatch = detachedLine.match(/^(\S+)\s+(%\d+)$/);
|
|
269
|
+
if (!detachedMatch) {
|
|
270
|
+
throw new Error(`Failed to create detached tmux session: "${detachedLine}"`);
|
|
271
|
+
}
|
|
272
|
+
sessionAndWindow = detachedMatch[1];
|
|
273
|
+
leaderPaneId = detachedMatch[2];
|
|
274
|
+
}
|
|
275
|
+
if (inTmux && envPaneId) {
|
|
273
276
|
try {
|
|
274
277
|
const targetedContextResult = await execFileAsync("tmux", [
|
|
275
278
|
"display-message",
|
|
@@ -364,7 +367,7 @@ async function createTeamSession(teamName, workerCount, cwd) {
|
|
|
364
367
|
await new Promise((r) => setTimeout(r, 300));
|
|
365
368
|
return { sessionName: teamTarget, leaderPaneId, workerPaneIds };
|
|
366
369
|
}
|
|
367
|
-
async function spawnWorkerInPane(
|
|
370
|
+
async function spawnWorkerInPane(sessionName2, paneId, config) {
|
|
368
371
|
const { execFile: execFile2 } = await import("child_process");
|
|
369
372
|
const { promisify: promisify2 } = await import("util");
|
|
370
373
|
const execFileAsync = promisify2(execFile2);
|
|
@@ -415,6 +418,23 @@ function paneLooksReady(captured) {
|
|
|
415
418
|
);
|
|
416
419
|
return hasCodexHint;
|
|
417
420
|
}
|
|
421
|
+
async function waitForPaneReady(paneId, opts = {}) {
|
|
422
|
+
const envTimeout = Number.parseInt(process.env.OMC_SHELL_READY_TIMEOUT_MS ?? "", 10);
|
|
423
|
+
const timeoutMs = Number.isFinite(opts.timeoutMs) && (opts.timeoutMs ?? 0) > 0 ? Number(opts.timeoutMs) : Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : 1e4;
|
|
424
|
+
const pollIntervalMs = Number.isFinite(opts.pollIntervalMs) && (opts.pollIntervalMs ?? 0) > 0 ? Number(opts.pollIntervalMs) : 250;
|
|
425
|
+
const deadline = Date.now() + timeoutMs;
|
|
426
|
+
while (Date.now() < deadline) {
|
|
427
|
+
const captured = await capturePaneAsync(paneId, promisifiedExecFile);
|
|
428
|
+
if (paneLooksReady(captured) && !paneHasActiveTask(captured)) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
await sleep(pollIntervalMs);
|
|
432
|
+
}
|
|
433
|
+
console.warn(
|
|
434
|
+
`[tmux-session] waitForPaneReady: pane ${paneId} timed out after ${timeoutMs}ms (set OMC_SHELL_READY_TIMEOUT_MS to tune)`
|
|
435
|
+
);
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
418
438
|
function paneTailContainsLiteralLine(captured, text) {
|
|
419
439
|
return normalizeTmuxCapture(captured).includes(normalizeTmuxCapture(text));
|
|
420
440
|
}
|
|
@@ -427,7 +447,7 @@ async function paneInCopyMode(paneId, execFileAsync) {
|
|
|
427
447
|
}
|
|
428
448
|
}
|
|
429
449
|
function shouldAttemptAdaptiveRetry(args) {
|
|
430
|
-
if (process.env.
|
|
450
|
+
if (process.env.OMC_TEAM_AUTO_INTERRUPT_RETRY === "0") return false;
|
|
431
451
|
if (args.retriesAttempted >= 1) return false;
|
|
432
452
|
if (args.paneInCopyMode) return false;
|
|
433
453
|
if (!args.paneBusy) return false;
|
|
@@ -446,7 +466,7 @@ async function sendToWorker(_sessionName, paneId, message) {
|
|
|
446
466
|
const { execFile: execFile2 } = await import("child_process");
|
|
447
467
|
const { promisify: promisify2 } = await import("util");
|
|
448
468
|
const execFileAsync = promisify2(execFile2);
|
|
449
|
-
const
|
|
469
|
+
const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
450
470
|
const sendKey = async (key) => {
|
|
451
471
|
await execFileAsync("tmux", ["send-keys", "-t", paneId, key]);
|
|
452
472
|
};
|
|
@@ -457,28 +477,28 @@ async function sendToWorker(_sessionName, paneId, message) {
|
|
|
457
477
|
const paneBusy = paneHasActiveTask(initialCapture);
|
|
458
478
|
if (paneHasTrustPrompt(initialCapture)) {
|
|
459
479
|
await sendKey("C-m");
|
|
460
|
-
await
|
|
480
|
+
await sleep2(120);
|
|
461
481
|
await sendKey("C-m");
|
|
462
|
-
await
|
|
482
|
+
await sleep2(200);
|
|
463
483
|
}
|
|
464
484
|
await execFileAsync("tmux", ["send-keys", "-t", paneId, "-l", "--", message]);
|
|
465
|
-
await
|
|
485
|
+
await sleep2(150);
|
|
466
486
|
const submitRounds = 6;
|
|
467
487
|
for (let round = 0; round < submitRounds; round++) {
|
|
468
|
-
await
|
|
488
|
+
await sleep2(100);
|
|
469
489
|
if (round === 0 && paneBusy) {
|
|
470
490
|
await sendKey("Tab");
|
|
471
|
-
await
|
|
491
|
+
await sleep2(80);
|
|
472
492
|
await sendKey("C-m");
|
|
473
493
|
} else {
|
|
474
494
|
await sendKey("C-m");
|
|
475
|
-
await
|
|
495
|
+
await sleep2(200);
|
|
476
496
|
await sendKey("C-m");
|
|
477
497
|
}
|
|
478
|
-
await
|
|
498
|
+
await sleep2(140);
|
|
479
499
|
const checkCapture = await capturePaneAsync(paneId, execFileAsync);
|
|
480
500
|
if (!paneTailContainsLiteralLine(checkCapture, message)) return true;
|
|
481
|
-
await
|
|
501
|
+
await sleep2(140);
|
|
482
502
|
}
|
|
483
503
|
if (await paneInCopyMode(paneId, execFileAsync)) {
|
|
484
504
|
return false;
|
|
@@ -496,17 +516,17 @@ async function sendToWorker(_sessionName, paneId, message) {
|
|
|
496
516
|
return false;
|
|
497
517
|
}
|
|
498
518
|
await sendKey("C-u");
|
|
499
|
-
await
|
|
519
|
+
await sleep2(80);
|
|
500
520
|
if (await paneInCopyMode(paneId, execFileAsync)) {
|
|
501
521
|
return false;
|
|
502
522
|
}
|
|
503
523
|
await execFileAsync("tmux", ["send-keys", "-t", paneId, "-l", "--", message]);
|
|
504
|
-
await
|
|
524
|
+
await sleep2(120);
|
|
505
525
|
for (let round = 0; round < 4; round++) {
|
|
506
526
|
await sendKey("C-m");
|
|
507
|
-
await
|
|
527
|
+
await sleep2(180);
|
|
508
528
|
await sendKey("C-m");
|
|
509
|
-
await
|
|
529
|
+
await sleep2(140);
|
|
510
530
|
const retryCapture = await capturePaneAsync(paneId, execFileAsync);
|
|
511
531
|
if (!paneTailContainsLiteralLine(retryCapture, message)) return true;
|
|
512
532
|
}
|
|
@@ -515,13 +535,31 @@ async function sendToWorker(_sessionName, paneId, message) {
|
|
|
515
535
|
return false;
|
|
516
536
|
}
|
|
517
537
|
await sendKey("C-m");
|
|
518
|
-
await
|
|
538
|
+
await sleep2(120);
|
|
519
539
|
await sendKey("C-m");
|
|
520
540
|
return true;
|
|
521
541
|
} catch {
|
|
522
542
|
return false;
|
|
523
543
|
}
|
|
524
544
|
}
|
|
545
|
+
async function injectToLeaderPane(sessionName2, leaderPaneId, message) {
|
|
546
|
+
const prefixed = `[OMC_TMUX_INJECT] ${message}`.slice(0, 200);
|
|
547
|
+
try {
|
|
548
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
549
|
+
const { promisify: promisify2 } = await import("util");
|
|
550
|
+
const execFileAsync = promisify2(execFile2);
|
|
551
|
+
if (await paneInCopyMode(leaderPaneId, execFileAsync)) {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
const captured = await capturePaneAsync(leaderPaneId, execFileAsync);
|
|
555
|
+
if (paneHasActiveTask(captured)) {
|
|
556
|
+
await execFileAsync("tmux", ["send-keys", "-t", leaderPaneId, "C-c"]);
|
|
557
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
return sendToWorker(sessionName2, leaderPaneId, prefixed);
|
|
562
|
+
}
|
|
525
563
|
async function isWorkerAlive(paneId) {
|
|
526
564
|
try {
|
|
527
565
|
const { execFile: execFile2 } = await import("child_process");
|
|
@@ -539,11 +577,31 @@ async function isWorkerAlive(paneId) {
|
|
|
539
577
|
return false;
|
|
540
578
|
}
|
|
541
579
|
}
|
|
542
|
-
async function
|
|
580
|
+
async function killWorkerPanes(opts) {
|
|
581
|
+
const { paneIds, leaderPaneId, teamName, cwd, graceMs = 1e4 } = opts;
|
|
582
|
+
if (!paneIds.length) return;
|
|
583
|
+
const shutdownPath = (0, import_path2.join)(cwd, ".omc", "state", "team", teamName, "shutdown.json");
|
|
584
|
+
try {
|
|
585
|
+
await import_promises.default.writeFile(shutdownPath, JSON.stringify({ requestedAt: Date.now() }));
|
|
586
|
+
await sleep(graceMs);
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
590
|
+
const { promisify: promisify2 } = await import("util");
|
|
591
|
+
const execFileAsync = promisify2(execFile2);
|
|
592
|
+
for (const paneId of paneIds) {
|
|
593
|
+
if (paneId === leaderPaneId) continue;
|
|
594
|
+
try {
|
|
595
|
+
await execFileAsync("tmux", ["kill-pane", "-t", paneId]);
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async function killTeamSession(sessionName2, workerPaneIds, leaderPaneId) {
|
|
543
601
|
const { execFile: execFile2 } = await import("child_process");
|
|
544
602
|
const { promisify: promisify2 } = await import("util");
|
|
545
603
|
const execFileAsync = promisify2(execFile2);
|
|
546
|
-
if (
|
|
604
|
+
if (sessionName2.includes(":")) {
|
|
547
605
|
if (!workerPaneIds?.length) return;
|
|
548
606
|
for (const id of workerPaneIds) {
|
|
549
607
|
if (id === leaderPaneId) continue;
|
|
@@ -554,81 +612,347 @@ async function killTeamSession(sessionName, workerPaneIds, leaderPaneId) {
|
|
|
554
612
|
}
|
|
555
613
|
return;
|
|
556
614
|
}
|
|
615
|
+
if (process.env.OMC_TEAM_ALLOW_KILL_CURRENT_SESSION !== "1" && process.env.TMUX) {
|
|
616
|
+
try {
|
|
617
|
+
const current = await tmuxAsync(["display-message", "-p", "#S"]);
|
|
618
|
+
const currentSessionName = current.stdout.trim();
|
|
619
|
+
if (currentSessionName && currentSessionName === sessionName2) {
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
} catch {
|
|
623
|
+
}
|
|
624
|
+
}
|
|
557
625
|
try {
|
|
558
|
-
await execFileAsync("tmux", ["kill-session", "-t",
|
|
626
|
+
await execFileAsync("tmux", ["kill-session", "-t", sessionName2]);
|
|
559
627
|
} catch {
|
|
560
628
|
}
|
|
561
629
|
}
|
|
630
|
+
var import_child_process2, import_path2, import_util, import_promises, sleep, TMUX_SESSION_PREFIX, promisifiedExec, promisifiedExecFile, DANGEROUS_LAUNCH_BINARY_CHARS;
|
|
631
|
+
var init_tmux_session = __esm({
|
|
632
|
+
"src/team/tmux-session.ts"() {
|
|
633
|
+
"use strict";
|
|
634
|
+
import_child_process2 = require("child_process");
|
|
635
|
+
import_path2 = require("path");
|
|
636
|
+
import_util = require("util");
|
|
637
|
+
import_promises = __toESM(require("fs/promises"), 1);
|
|
638
|
+
init_team_name();
|
|
639
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
640
|
+
TMUX_SESSION_PREFIX = "omc-team";
|
|
641
|
+
promisifiedExec = (0, import_util.promisify)(import_child_process2.exec);
|
|
642
|
+
promisifiedExecFile = (0, import_util.promisify)(import_child_process2.execFile);
|
|
643
|
+
DANGEROUS_LAUNCH_BINARY_CHARS = /[;&|`$()<>\n\r\t\0]/;
|
|
644
|
+
}
|
|
645
|
+
});
|
|
562
646
|
|
|
563
|
-
// src/team/
|
|
564
|
-
var
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
647
|
+
// src/team/runtime-cli.ts
|
|
648
|
+
var runtime_cli_exports = {};
|
|
649
|
+
__export(runtime_cli_exports, {
|
|
650
|
+
checkWatchdogFailedMarker: () => checkWatchdogFailedMarker,
|
|
651
|
+
getTerminalStatus: () => getTerminalStatus,
|
|
652
|
+
writeResultArtifact: () => writeResultArtifact
|
|
653
|
+
});
|
|
654
|
+
module.exports = __toCommonJS(runtime_cli_exports);
|
|
655
|
+
var import_fs13 = require("fs");
|
|
656
|
+
var import_promises7 = require("fs/promises");
|
|
657
|
+
var import_path16 = require("path");
|
|
571
658
|
|
|
572
|
-
// src/
|
|
573
|
-
var
|
|
574
|
-
var
|
|
575
|
-
var
|
|
659
|
+
// src/team/runtime.ts
|
|
660
|
+
var import_promises3 = require("fs/promises");
|
|
661
|
+
var import_path10 = require("path");
|
|
662
|
+
var import_fs6 = require("fs");
|
|
576
663
|
|
|
577
|
-
// src/
|
|
578
|
-
var
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
664
|
+
// src/team/model-contract.ts
|
|
665
|
+
var import_child_process = require("child_process");
|
|
666
|
+
var import_path = require("path");
|
|
667
|
+
init_team_name();
|
|
668
|
+
var resolvedPathCache = /* @__PURE__ */ new Map();
|
|
669
|
+
var UNTRUSTED_PATH_PATTERNS = [
|
|
670
|
+
/^\/tmp(\/|$)/,
|
|
671
|
+
/^\/var\/tmp(\/|$)/,
|
|
672
|
+
/^\/dev\/shm(\/|$)/
|
|
673
|
+
];
|
|
674
|
+
function getTrustedPrefixes() {
|
|
675
|
+
const trusted = [
|
|
676
|
+
"/usr/local/bin",
|
|
677
|
+
"/usr/bin",
|
|
678
|
+
"/opt/homebrew/"
|
|
679
|
+
];
|
|
680
|
+
const home = process.env.HOME;
|
|
681
|
+
if (home) {
|
|
682
|
+
trusted.push(`${home}/.local/bin`);
|
|
683
|
+
trusted.push(`${home}/.nvm/`);
|
|
684
|
+
trusted.push(`${home}/.cargo/bin`);
|
|
685
|
+
}
|
|
686
|
+
const custom = (process.env.OMC_TRUSTED_CLI_DIRS ?? "").split(":").map((part) => part.trim()).filter(Boolean).filter((part) => (0, import_path.isAbsolute)(part));
|
|
687
|
+
trusted.push(...custom);
|
|
688
|
+
return trusted;
|
|
689
|
+
}
|
|
690
|
+
function isTrustedPrefix(resolvedPath) {
|
|
691
|
+
const normalized = (0, import_path.normalize)(resolvedPath);
|
|
692
|
+
return getTrustedPrefixes().some((prefix) => normalized.startsWith((0, import_path.normalize)(prefix)));
|
|
693
|
+
}
|
|
694
|
+
function assertBinaryName(binary) {
|
|
695
|
+
if (!/^[A-Za-z0-9._-]+$/.test(binary)) {
|
|
696
|
+
throw new Error(`Invalid CLI binary name: ${binary}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function resolveCliBinaryPath(binary) {
|
|
700
|
+
assertBinaryName(binary);
|
|
701
|
+
const cached = resolvedPathCache.get(binary);
|
|
702
|
+
if (cached) return cached;
|
|
703
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
704
|
+
const result = (0, import_child_process.spawnSync)(finder, [binary], {
|
|
705
|
+
timeout: 5e3,
|
|
706
|
+
env: process.env
|
|
707
|
+
});
|
|
708
|
+
if (result.status !== 0) {
|
|
709
|
+
throw new Error(`CLI binary '${binary}' not found in PATH`);
|
|
587
710
|
}
|
|
588
|
-
|
|
589
|
-
|
|
711
|
+
const stdout = result.stdout?.toString().trim() ?? "";
|
|
712
|
+
const firstLine = stdout.split("\n").map((line) => line.trim()).find(Boolean) ?? "";
|
|
713
|
+
if (!firstLine) {
|
|
714
|
+
throw new Error(`CLI binary '${binary}' not found in PATH`);
|
|
590
715
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
function getValidAgentRoles() {
|
|
595
|
-
if (_cachedRoles) return _cachedRoles;
|
|
596
|
-
try {
|
|
597
|
-
if (typeof __AGENT_ROLES__ !== "undefined" && Array.isArray(__AGENT_ROLES__) && __AGENT_ROLES__.length > 0) {
|
|
598
|
-
_cachedRoles = __AGENT_ROLES__;
|
|
599
|
-
return _cachedRoles;
|
|
600
|
-
}
|
|
601
|
-
} catch {
|
|
716
|
+
const resolvedPath = (0, import_path.normalize)(firstLine);
|
|
717
|
+
if (!(0, import_path.isAbsolute)(resolvedPath)) {
|
|
718
|
+
throw new Error(`Resolved CLI binary '${binary}' to relative path`);
|
|
602
719
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const files = (0, import_fs2.readdirSync)(agentsDir);
|
|
606
|
-
_cachedRoles = files.filter((f) => f.endsWith(".md")).map((f) => (0, import_path3.basename)(f, ".md")).sort();
|
|
607
|
-
} catch (err) {
|
|
608
|
-
console.error("[prompt-injection] CRITICAL: Could not scan agents/ directory for role discovery:", err);
|
|
609
|
-
_cachedRoles = [];
|
|
720
|
+
if (UNTRUSTED_PATH_PATTERNS.some((pattern) => pattern.test(resolvedPath))) {
|
|
721
|
+
throw new Error(`Resolved CLI binary '${binary}' to untrusted location: ${resolvedPath}`);
|
|
610
722
|
}
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
var VALID_AGENT_ROLES = getValidAgentRoles();
|
|
614
|
-
function sanitizePromptContent(content, maxLength = 4e3) {
|
|
615
|
-
if (!content) return "";
|
|
616
|
-
let sanitized = content.length > maxLength ? content.slice(0, maxLength) : content;
|
|
617
|
-
if (sanitized.length > 0) {
|
|
618
|
-
const lastCode = sanitized.charCodeAt(sanitized.length - 1);
|
|
619
|
-
if (lastCode >= 55296 && lastCode <= 56319) {
|
|
620
|
-
sanitized = sanitized.slice(0, -1);
|
|
621
|
-
}
|
|
723
|
+
if (!isTrustedPrefix(resolvedPath)) {
|
|
724
|
+
console.warn(`[omc:cli-security] CLI binary '${binary}' resolved to non-standard path: ${resolvedPath}`);
|
|
622
725
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
726
|
+
resolvedPathCache.set(binary, resolvedPath);
|
|
727
|
+
return resolvedPath;
|
|
728
|
+
}
|
|
729
|
+
var CONTRACTS = {
|
|
730
|
+
claude: {
|
|
731
|
+
agentType: "claude",
|
|
732
|
+
binary: "claude",
|
|
733
|
+
installInstructions: "Install Claude CLI: https://claude.ai/download",
|
|
734
|
+
buildLaunchArgs(model, extraFlags = []) {
|
|
735
|
+
const args = ["--dangerously-skip-permissions"];
|
|
736
|
+
if (model) args.push("--model", model);
|
|
737
|
+
return [...args, ...extraFlags];
|
|
738
|
+
},
|
|
739
|
+
parseOutput(rawOutput) {
|
|
740
|
+
return rawOutput.trim();
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
codex: {
|
|
744
|
+
agentType: "codex",
|
|
745
|
+
binary: "codex",
|
|
746
|
+
installInstructions: "Install Codex CLI: npm install -g @openai/codex",
|
|
747
|
+
supportsPromptMode: true,
|
|
748
|
+
// Codex accepts prompt as a positional argument (no flag needed):
|
|
749
|
+
// codex [OPTIONS] [PROMPT]
|
|
750
|
+
buildLaunchArgs(model, extraFlags = []) {
|
|
751
|
+
const args = ["--dangerously-bypass-approvals-and-sandbox"];
|
|
752
|
+
if (model) args.push("--model", model);
|
|
753
|
+
return [...args, ...extraFlags];
|
|
754
|
+
},
|
|
755
|
+
parseOutput(rawOutput) {
|
|
756
|
+
const lines = rawOutput.trim().split("\n").filter(Boolean);
|
|
757
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
758
|
+
try {
|
|
759
|
+
const parsed = JSON.parse(lines[i]);
|
|
760
|
+
if (parsed.type === "message" && parsed.role === "assistant") {
|
|
761
|
+
return parsed.content ?? rawOutput;
|
|
762
|
+
}
|
|
763
|
+
if (parsed.type === "result" || parsed.output) {
|
|
764
|
+
return parsed.output ?? parsed.result ?? rawOutput;
|
|
765
|
+
}
|
|
766
|
+
} catch {
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return rawOutput.trim();
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
gemini: {
|
|
773
|
+
agentType: "gemini",
|
|
774
|
+
binary: "gemini",
|
|
775
|
+
installInstructions: "Install Gemini CLI: npm install -g @google/gemini-cli",
|
|
776
|
+
supportsPromptMode: true,
|
|
777
|
+
promptModeFlag: "-i",
|
|
778
|
+
buildLaunchArgs(model, extraFlags = []) {
|
|
779
|
+
const args = ["--approval-mode", "yolo"];
|
|
780
|
+
if (model) args.push("--model", model);
|
|
781
|
+
return [...args, ...extraFlags];
|
|
782
|
+
},
|
|
783
|
+
parseOutput(rawOutput) {
|
|
784
|
+
return rawOutput.trim();
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
function getContract(agentType) {
|
|
789
|
+
const contract = CONTRACTS[agentType];
|
|
790
|
+
if (!contract) {
|
|
791
|
+
throw new Error(`Unknown agent type: ${agentType}. Supported: ${Object.keys(CONTRACTS).join(", ")}`);
|
|
792
|
+
}
|
|
793
|
+
return contract;
|
|
794
|
+
}
|
|
795
|
+
function validateBinaryRef(binary) {
|
|
796
|
+
if ((0, import_path.isAbsolute)(binary)) return;
|
|
797
|
+
if (/^[A-Za-z0-9._-]+$/.test(binary)) return;
|
|
798
|
+
throw new Error(`Unsafe CLI binary reference: ${binary}`);
|
|
799
|
+
}
|
|
800
|
+
function resolveBinaryPath(binary) {
|
|
801
|
+
validateBinaryRef(binary);
|
|
802
|
+
if ((0, import_path.isAbsolute)(binary)) return binary;
|
|
803
|
+
try {
|
|
804
|
+
const resolver = process.platform === "win32" ? "where" : "which";
|
|
805
|
+
const result = (0, import_child_process.spawnSync)(resolver, [binary], { timeout: 5e3, encoding: "utf8" });
|
|
806
|
+
if (result.status !== 0) return binary;
|
|
807
|
+
const lines = result.stdout?.split(/\r?\n/).map((line) => line.trim()).filter(Boolean) ?? [];
|
|
808
|
+
const firstPath = lines[0];
|
|
809
|
+
const isResolvedAbsolute = !!firstPath && ((0, import_path.isAbsolute)(firstPath) || import_path.win32.isAbsolute(firstPath));
|
|
810
|
+
return isResolvedAbsolute ? firstPath : binary;
|
|
811
|
+
} catch {
|
|
812
|
+
return binary;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function resolveValidatedBinaryPath(agentType) {
|
|
816
|
+
const contract = getContract(agentType);
|
|
817
|
+
return resolveCliBinaryPath(contract.binary);
|
|
818
|
+
}
|
|
819
|
+
function buildLaunchArgs(agentType, config) {
|
|
820
|
+
return getContract(agentType).buildLaunchArgs(config.model, config.extraFlags);
|
|
821
|
+
}
|
|
822
|
+
function buildWorkerArgv(agentType, config) {
|
|
823
|
+
validateTeamName(config.teamName);
|
|
824
|
+
const contract = getContract(agentType);
|
|
825
|
+
const binary = config.resolvedBinaryPath ? (() => {
|
|
826
|
+
validateBinaryRef(config.resolvedBinaryPath);
|
|
827
|
+
return config.resolvedBinaryPath;
|
|
828
|
+
})() : resolveBinaryPath(contract.binary);
|
|
829
|
+
const args = buildLaunchArgs(agentType, config);
|
|
830
|
+
return [binary, ...args];
|
|
831
|
+
}
|
|
832
|
+
function getWorkerEnv(teamName, workerName2, agentType) {
|
|
833
|
+
validateTeamName(teamName);
|
|
834
|
+
return {
|
|
835
|
+
OMC_TEAM_WORKER: `${teamName}/${workerName2}`,
|
|
836
|
+
OMC_TEAM_NAME: teamName,
|
|
837
|
+
OMC_WORKER_AGENT_TYPE: agentType
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
function isPromptModeAgent(agentType) {
|
|
841
|
+
const contract = getContract(agentType);
|
|
842
|
+
return !!contract.supportsPromptMode;
|
|
843
|
+
}
|
|
844
|
+
function getPromptModeArgs(agentType, instruction) {
|
|
845
|
+
const contract = getContract(agentType);
|
|
846
|
+
if (!contract.supportsPromptMode) {
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
if (contract.promptModeFlag) {
|
|
850
|
+
return [contract.promptModeFlag, instruction];
|
|
851
|
+
}
|
|
852
|
+
return [instruction];
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/team/runtime.ts
|
|
856
|
+
init_team_name();
|
|
857
|
+
init_tmux_session();
|
|
858
|
+
|
|
859
|
+
// src/team/worker-bootstrap.ts
|
|
860
|
+
var import_promises2 = require("fs/promises");
|
|
861
|
+
var import_path5 = require("path");
|
|
862
|
+
|
|
863
|
+
// src/agents/prompt-helpers.ts
|
|
864
|
+
var import_fs2 = require("fs");
|
|
865
|
+
var import_path4 = require("path");
|
|
866
|
+
var import_url2 = require("url");
|
|
867
|
+
|
|
868
|
+
// src/agents/utils.ts
|
|
869
|
+
var import_fs = require("fs");
|
|
870
|
+
var import_path3 = require("path");
|
|
871
|
+
var import_url = require("url");
|
|
872
|
+
|
|
873
|
+
// src/agents/prompt-helpers.ts
|
|
874
|
+
var import_meta = {};
|
|
875
|
+
function getPackageDir() {
|
|
876
|
+
if (typeof __dirname !== "undefined" && __dirname) {
|
|
877
|
+
const currentDirName = (0, import_path4.basename)(__dirname);
|
|
878
|
+
const parentDirName = (0, import_path4.basename)((0, import_path4.dirname)(__dirname));
|
|
879
|
+
if (currentDirName === "bridge") {
|
|
880
|
+
return (0, import_path4.join)(__dirname, "..");
|
|
881
|
+
}
|
|
882
|
+
if (currentDirName === "agents" && (parentDirName === "src" || parentDirName === "dist")) {
|
|
883
|
+
return (0, import_path4.join)(__dirname, "..", "..");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
const __filename = (0, import_url2.fileURLToPath)(import_meta.url);
|
|
888
|
+
const __dirname2 = (0, import_path4.dirname)(__filename);
|
|
889
|
+
return (0, import_path4.join)(__dirname2, "..", "..");
|
|
890
|
+
} catch {
|
|
891
|
+
}
|
|
892
|
+
return process.cwd();
|
|
893
|
+
}
|
|
894
|
+
var _cachedRoles = null;
|
|
895
|
+
function getValidAgentRoles() {
|
|
896
|
+
if (_cachedRoles) return _cachedRoles;
|
|
897
|
+
try {
|
|
898
|
+
if (typeof __AGENT_ROLES__ !== "undefined" && Array.isArray(__AGENT_ROLES__) && __AGENT_ROLES__.length > 0) {
|
|
899
|
+
_cachedRoles = __AGENT_ROLES__;
|
|
900
|
+
return _cachedRoles;
|
|
901
|
+
}
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
904
|
+
try {
|
|
905
|
+
const agentsDir = (0, import_path4.join)(getPackageDir(), "agents");
|
|
906
|
+
const files = (0, import_fs2.readdirSync)(agentsDir);
|
|
907
|
+
_cachedRoles = files.filter((f) => f.endsWith(".md")).map((f) => (0, import_path4.basename)(f, ".md")).sort();
|
|
908
|
+
} catch (err) {
|
|
909
|
+
console.error("[prompt-injection] CRITICAL: Could not scan agents/ directory for role discovery:", err);
|
|
910
|
+
_cachedRoles = [];
|
|
911
|
+
}
|
|
912
|
+
return _cachedRoles;
|
|
913
|
+
}
|
|
914
|
+
var VALID_AGENT_ROLES = getValidAgentRoles();
|
|
915
|
+
function sanitizePromptContent(content, maxLength = 4e3) {
|
|
916
|
+
if (!content) return "";
|
|
917
|
+
let sanitized = content.length > maxLength ? content.slice(0, maxLength) : content;
|
|
918
|
+
if (sanitized.length > 0) {
|
|
919
|
+
const lastCode = sanitized.charCodeAt(sanitized.length - 1);
|
|
920
|
+
if (lastCode >= 55296 && lastCode <= 56319) {
|
|
921
|
+
sanitized = sanitized.slice(0, -1);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
sanitized = sanitized.replace(/<(\/?)(TASK_SUBJECT)[^>]*>/gi, "[$1$2]");
|
|
925
|
+
sanitized = sanitized.replace(/<(\/?)(TASK_DESCRIPTION)[^>]*>/gi, "[$1$2]");
|
|
926
|
+
sanitized = sanitized.replace(/<(\/?)(INBOX_MESSAGE)[^>]*>/gi, "[$1$2]");
|
|
927
|
+
sanitized = sanitized.replace(/<(\/?)(INSTRUCTIONS)[^>]*>/gi, "[$1$2]");
|
|
928
|
+
sanitized = sanitized.replace(/<(\/?)(SYSTEM)[^>]*>/gi, "[$1$2]");
|
|
929
|
+
return sanitized;
|
|
629
930
|
}
|
|
630
931
|
|
|
631
932
|
// src/team/worker-bootstrap.ts
|
|
933
|
+
function agentTypeGuidance(agentType) {
|
|
934
|
+
switch (agentType) {
|
|
935
|
+
case "codex":
|
|
936
|
+
return [
|
|
937
|
+
"### Agent-Type Guidance (codex)",
|
|
938
|
+
"- Prefer short, explicit `omc team api ... --json` commands and parse outputs before next step.",
|
|
939
|
+
"- If a command fails, report the exact stderr to leader-fixed before retrying."
|
|
940
|
+
].join("\n");
|
|
941
|
+
case "gemini":
|
|
942
|
+
return [
|
|
943
|
+
"### Agent-Type Guidance (gemini)",
|
|
944
|
+
"- Execute task work in small, verifiable increments and report each milestone to leader-fixed.",
|
|
945
|
+
"- Keep commit-sized changes scoped to assigned files only; no broad refactors."
|
|
946
|
+
].join("\n");
|
|
947
|
+
case "claude":
|
|
948
|
+
default:
|
|
949
|
+
return [
|
|
950
|
+
"### Agent-Type Guidance (claude)",
|
|
951
|
+
"- Keep reasoning focused on assigned task IDs and send concise progress acks to leader-fixed.",
|
|
952
|
+
"- Before any risky command, send a blocker/proposal message to leader-fixed and wait for updated inbox instructions."
|
|
953
|
+
].join("\n");
|
|
954
|
+
}
|
|
955
|
+
}
|
|
632
956
|
function generateWorkerOverlay(params) {
|
|
633
957
|
const { teamName, workerName: workerName2, agentType, tasks, bootstrapInstructions } = params;
|
|
634
958
|
const sanitizedTasks = tasks.map((t) => ({
|
|
@@ -639,11 +963,15 @@ function generateWorkerOverlay(params) {
|
|
|
639
963
|
const sentinelPath = `.omc/state/team/${teamName}/workers/${workerName2}/.ready`;
|
|
640
964
|
const heartbeatPath = `.omc/state/team/${teamName}/workers/${workerName2}/heartbeat.json`;
|
|
641
965
|
const inboxPath = `.omc/state/team/${teamName}/workers/${workerName2}/inbox.md`;
|
|
966
|
+
const statusPath = `.omc/state/team/${teamName}/workers/${workerName2}/status.json`;
|
|
642
967
|
const taskDir = `.omc/state/team/${teamName}/tasks`;
|
|
643
|
-
const
|
|
644
|
-
|
|
968
|
+
const taskList = sanitizedTasks.length > 0 ? sanitizedTasks.map((t) => `- **Task ${t.id}**: ${t.subject}
|
|
969
|
+
Description: ${t.description}
|
|
970
|
+
Status: pending`).join("\n") : "- No tasks assigned yet. Check your inbox for assignments.";
|
|
645
971
|
return `# Team Worker Protocol
|
|
646
972
|
|
|
973
|
+
You are a **team worker**, not the team leader. Operate strictly within worker protocol.
|
|
974
|
+
|
|
647
975
|
## FIRST ACTION REQUIRED
|
|
648
976
|
Before doing anything else, write your ready sentinel file:
|
|
649
977
|
\`\`\`bash
|
|
@@ -659,89 +987,132 @@ mkdir -p $(dirname ${sentinelPath}) && touch ${sentinelPath}
|
|
|
659
987
|
## Your Tasks
|
|
660
988
|
${taskList}
|
|
661
989
|
|
|
662
|
-
## Task
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
990
|
+
## Task Lifecycle Protocol (CLI API)
|
|
991
|
+
Use the CLI API for all task lifecycle operations. Do NOT directly edit task files.
|
|
992
|
+
|
|
993
|
+
1. Read your task file at \`${taskDir}/task-{taskId}.json\`
|
|
994
|
+
2. Task id format: State/CLI APIs use task_id: "<id>" (example: "1"), not "task-1"
|
|
995
|
+
3. Claim a task via CLI interop:
|
|
996
|
+
\`omc team api claim-task --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"worker\\":\\"${workerName2}\\"}" --json\`
|
|
997
|
+
4. Do the work described in the task
|
|
998
|
+
5. On completion, transition via CLI interop (use the claim_token from step 3):
|
|
999
|
+
\`omc team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"completed\\",\\"claim_token\\":\\"<claim_token from step 3>\\"}" --json\`
|
|
1000
|
+
6. On failure, transition to "failed" with error (use the claim_token from step 3):
|
|
1001
|
+
\`omc team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"failed\\",\\"claim_token\\":\\"<claim_token from step 3>\\"}" --json\`
|
|
1002
|
+
7. Use \`omc team api release-task-claim --json\` only for rollback to pending
|
|
669
1003
|
|
|
670
1004
|
## Communication Protocol
|
|
671
1005
|
- **Inbox**: Read ${inboxPath} for new instructions
|
|
1006
|
+
- **Status**: Write to ${statusPath}:
|
|
1007
|
+
\`\`\`json
|
|
1008
|
+
{"state": "idle", "updated_at": "<ISO timestamp>"}
|
|
1009
|
+
\`\`\`
|
|
1010
|
+
States: "idle" | "working" | "blocked" | "done" | "failed"
|
|
672
1011
|
- **Heartbeat**: Update ${heartbeatPath} every few minutes:
|
|
673
1012
|
\`\`\`json
|
|
674
|
-
{"
|
|
1013
|
+
{"pid":<pid>,"last_turn_at":"<ISO timestamp>","turn_count":<n>,"alive":true}
|
|
675
1014
|
\`\`\`
|
|
676
1015
|
|
|
677
|
-
##
|
|
678
|
-
|
|
679
|
-
-
|
|
680
|
-
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1016
|
+
## Message Protocol
|
|
1017
|
+
Send messages via CLI API:
|
|
1018
|
+
- To leader: \`omc team api send-message --input "{\\"team_name\\":\\"${teamName}\\",\\"from_worker\\":\\"${workerName2}\\",\\"to_worker\\":\\"leader-fixed\\",\\"body\\":\\"<message>\\"}" --json\`
|
|
1019
|
+
- Check mailbox: \`omc team api mailbox-list --input "{\\"team_name\\":\\"${teamName}\\",\\"worker\\":\\"${workerName2}\\"}" --json\`
|
|
1020
|
+
- Mark delivered: \`omc team api mailbox-mark-delivered --input "{\\"team_name\\":\\"${teamName}\\",\\"worker\\":\\"${workerName2}\\",\\"message_id\\":\\"<id>\\"}" --json\`
|
|
1021
|
+
|
|
1022
|
+
## Startup Handshake (Required)
|
|
1023
|
+
Before doing any task work, send exactly one startup ACK to the leader:
|
|
1024
|
+
\`omc team api send-message --input "{\\"team_name\\":\\"${teamName}\\",\\"from_worker\\":\\"${workerName2}\\",\\"to_worker\\":\\"leader-fixed\\",\\"body\\":\\"ACK: ${workerName2} initialized\\"}" --json\`
|
|
684
1025
|
|
|
685
1026
|
## Shutdown Protocol
|
|
686
|
-
When you see a shutdown request
|
|
687
|
-
1.
|
|
688
|
-
2.
|
|
689
|
-
|
|
1027
|
+
When you see a shutdown request in your inbox:
|
|
1028
|
+
1. Write your decision to: .omc/state/team/${teamName}/workers/${workerName2}/shutdown-ack.json
|
|
1029
|
+
2. Format:
|
|
1030
|
+
- Accept: {"status":"accept","reason":"ok","updated_at":"<iso>"}
|
|
1031
|
+
- Reject: {"status":"reject","reason":"still working","updated_at":"<iso>"}
|
|
1032
|
+
3. Exit your session
|
|
1033
|
+
|
|
1034
|
+
## Rules
|
|
1035
|
+
- You are NOT the leader. Never run leader orchestration workflows.
|
|
1036
|
+
- Do NOT edit files outside the paths listed in your task description
|
|
1037
|
+
- Do NOT write lifecycle fields (status, owner, result, error) directly in task files; use CLI API
|
|
1038
|
+
- Do NOT spawn sub-agents. Complete work in this worker session only.
|
|
1039
|
+
- Do NOT create tmux panes/sessions (\`tmux split-window\`, \`tmux new-session\`, etc.).
|
|
1040
|
+
- Do NOT run team spawning/orchestration commands (for example: \`omc team ...\`, \`omx team ...\`, \`$team\`, \`$ultrawork\`, \`$autopilot\`, \`$ralph\`).
|
|
1041
|
+
- Worker-allowed control surface is only: \`omc team api ... --json\` (and equivalent \`omx team api ... --json\` where configured).
|
|
1042
|
+
- If blocked, write {"state": "blocked", "reason": "..."} to your status file
|
|
1043
|
+
|
|
1044
|
+
${agentTypeGuidance(agentType)}
|
|
690
1045
|
|
|
691
1046
|
${bootstrapInstructions ? `## Additional Instructions
|
|
692
1047
|
${bootstrapInstructions}
|
|
693
1048
|
` : ""}`;
|
|
694
1049
|
}
|
|
695
1050
|
async function composeInitialInbox(teamName, workerName2, content, cwd) {
|
|
696
|
-
const inboxPath = (0,
|
|
697
|
-
await (0, import_promises2.mkdir)((0,
|
|
1051
|
+
const inboxPath = (0, import_path5.join)(cwd, `.omc/state/team/${teamName}/workers/${workerName2}/inbox.md`);
|
|
1052
|
+
await (0, import_promises2.mkdir)((0, import_path5.dirname)(inboxPath), { recursive: true });
|
|
698
1053
|
await (0, import_promises2.writeFile)(inboxPath, content, "utf-8");
|
|
699
1054
|
}
|
|
700
1055
|
async function ensureWorkerStateDir(teamName, workerName2, cwd) {
|
|
701
|
-
const workerDir = (0,
|
|
1056
|
+
const workerDir = (0, import_path5.join)(cwd, `.omc/state/team/${teamName}/workers/${workerName2}`);
|
|
702
1057
|
await (0, import_promises2.mkdir)(workerDir, { recursive: true });
|
|
703
|
-
const mailboxDir = (0,
|
|
1058
|
+
const mailboxDir = (0, import_path5.join)(cwd, `.omc/state/team/${teamName}/mailbox`);
|
|
704
1059
|
await (0, import_promises2.mkdir)(mailboxDir, { recursive: true });
|
|
705
|
-
const tasksDir = (0,
|
|
1060
|
+
const tasksDir = (0, import_path5.join)(cwd, `.omc/state/team/${teamName}/tasks`);
|
|
706
1061
|
await (0, import_promises2.mkdir)(tasksDir, { recursive: true });
|
|
707
1062
|
}
|
|
708
1063
|
async function writeWorkerOverlay(params) {
|
|
709
1064
|
const { teamName, workerName: workerName2, cwd } = params;
|
|
710
1065
|
const overlay = generateWorkerOverlay(params);
|
|
711
|
-
const overlayPath = (0,
|
|
712
|
-
await (0, import_promises2.mkdir)((0,
|
|
1066
|
+
const overlayPath = (0, import_path5.join)(cwd, `.omc/state/team/${teamName}/workers/${workerName2}/AGENTS.md`);
|
|
1067
|
+
await (0, import_promises2.mkdir)((0, import_path5.dirname)(overlayPath), { recursive: true });
|
|
713
1068
|
await (0, import_promises2.writeFile)(overlayPath, overlay, "utf-8");
|
|
714
1069
|
return overlayPath;
|
|
715
1070
|
}
|
|
716
1071
|
|
|
717
1072
|
// src/team/task-file-ops.ts
|
|
718
1073
|
var import_fs5 = require("fs");
|
|
719
|
-
var
|
|
1074
|
+
var import_path9 = require("path");
|
|
720
1075
|
|
|
721
1076
|
// src/utils/paths.ts
|
|
722
|
-
var
|
|
1077
|
+
var import_path6 = require("path");
|
|
723
1078
|
var import_fs3 = require("fs");
|
|
724
1079
|
var import_os = require("os");
|
|
1080
|
+
function getConfigDir2() {
|
|
1081
|
+
if (process.platform === "win32") {
|
|
1082
|
+
return process.env.APPDATA || (0, import_path6.join)((0, import_os.homedir)(), "AppData", "Roaming");
|
|
1083
|
+
}
|
|
1084
|
+
return process.env.XDG_CONFIG_HOME || (0, import_path6.join)((0, import_os.homedir)(), ".config");
|
|
1085
|
+
}
|
|
725
1086
|
var STALE_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
|
|
726
1087
|
|
|
1088
|
+
// src/team/task-file-ops.ts
|
|
1089
|
+
init_tmux_session();
|
|
1090
|
+
|
|
727
1091
|
// src/team/fs-utils.ts
|
|
728
1092
|
var import_fs4 = require("fs");
|
|
729
|
-
var
|
|
1093
|
+
var import_path7 = require("path");
|
|
1094
|
+
function atomicWriteJson(filePath, data, mode = 384) {
|
|
1095
|
+
const dir = (0, import_path7.dirname)(filePath);
|
|
1096
|
+
if (!(0, import_fs4.existsSync)(dir)) (0, import_fs4.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
1097
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
1098
|
+
(0, import_fs4.writeFileSync)(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode });
|
|
1099
|
+
(0, import_fs4.renameSync)(tmpPath, filePath);
|
|
1100
|
+
}
|
|
730
1101
|
function ensureDirWithMode(dirPath, mode = 448) {
|
|
731
1102
|
if (!(0, import_fs4.existsSync)(dirPath)) (0, import_fs4.mkdirSync)(dirPath, { recursive: true, mode });
|
|
732
1103
|
}
|
|
733
1104
|
function canonicalizePath(p) {
|
|
734
|
-
const absInput = (0,
|
|
1105
|
+
const absInput = (0, import_path7.resolve)(p);
|
|
735
1106
|
const tail = [];
|
|
736
1107
|
let probe = absInput;
|
|
737
1108
|
while (true) {
|
|
738
1109
|
try {
|
|
739
1110
|
const realBase = (0, import_fs4.realpathSync)(probe);
|
|
740
|
-
return tail.reduce((acc, seg) => (0,
|
|
1111
|
+
return tail.reduce((acc, seg) => (0, import_path7.resolve)(acc, seg), realBase);
|
|
741
1112
|
} catch {
|
|
742
|
-
const parent = (0,
|
|
1113
|
+
const parent = (0, import_path7.dirname)(probe);
|
|
743
1114
|
if (parent === probe) return absInput;
|
|
744
|
-
tail.unshift((0,
|
|
1115
|
+
tail.unshift((0, import_path7.basename)(probe));
|
|
745
1116
|
probe = parent;
|
|
746
1117
|
}
|
|
747
1118
|
}
|
|
@@ -749,20 +1120,26 @@ function canonicalizePath(p) {
|
|
|
749
1120
|
function validateResolvedPath(resolvedPath, expectedBase) {
|
|
750
1121
|
const absResolved = canonicalizePath(resolvedPath);
|
|
751
1122
|
const absBase = canonicalizePath(expectedBase);
|
|
752
|
-
const rel = (0,
|
|
753
|
-
if (rel !== "" && (rel.startsWith("..") || (0,
|
|
1123
|
+
const rel = (0, import_path7.relative)(absBase, absResolved);
|
|
1124
|
+
if (rel !== "" && (rel.startsWith("..") || (0, import_path7.isAbsolute)(rel) || (0, import_path7.resolve)(absBase, rel) !== absResolved)) {
|
|
754
1125
|
throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
|
|
755
1126
|
}
|
|
756
1127
|
}
|
|
757
1128
|
|
|
758
1129
|
// src/team/state-paths.ts
|
|
759
|
-
var
|
|
1130
|
+
var import_path8 = require("path");
|
|
1131
|
+
function normalizeTaskFileStem(taskId) {
|
|
1132
|
+
const trimmed = String(taskId).trim().replace(/\.json$/i, "");
|
|
1133
|
+
if (/^task-\d+$/.test(trimmed)) return trimmed;
|
|
1134
|
+
if (/^\d+$/.test(trimmed)) return `task-${trimmed}`;
|
|
1135
|
+
return trimmed;
|
|
1136
|
+
}
|
|
760
1137
|
var TeamPaths = {
|
|
761
1138
|
root: (teamName) => `.omc/state/team/${teamName}`,
|
|
762
1139
|
config: (teamName) => `.omc/state/team/${teamName}/config.json`,
|
|
763
1140
|
shutdown: (teamName) => `.omc/state/team/${teamName}/shutdown.json`,
|
|
764
1141
|
tasks: (teamName) => `.omc/state/team/${teamName}/tasks`,
|
|
765
|
-
taskFile: (teamName, taskId) => `.omc/state/team/${teamName}/tasks/${taskId}.json`,
|
|
1142
|
+
taskFile: (teamName, taskId) => `.omc/state/team/${teamName}/tasks/${normalizeTaskFileStem(taskId)}.json`,
|
|
766
1143
|
workers: (teamName) => `.omc/state/team/${teamName}/workers`,
|
|
767
1144
|
workerDir: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}`,
|
|
768
1145
|
heartbeat: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/heartbeat.json`,
|
|
@@ -771,14 +1148,35 @@ var TeamPaths = {
|
|
|
771
1148
|
ready: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/.ready`,
|
|
772
1149
|
overlay: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/AGENTS.md`,
|
|
773
1150
|
shutdownAck: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/shutdown-ack.json`,
|
|
774
|
-
|
|
775
|
-
|
|
1151
|
+
mailbox: (teamName, workerName2) => `.omc/state/team/${teamName}/mailbox/${workerName2}.json`,
|
|
1152
|
+
mailboxLockDir: (teamName, workerName2) => `.omc/state/team/${teamName}/mailbox/.lock-${workerName2}`,
|
|
1153
|
+
dispatchRequests: (teamName) => `.omc/state/team/${teamName}/dispatch/requests.json`,
|
|
1154
|
+
dispatchLockDir: (teamName) => `.omc/state/team/${teamName}/dispatch/.lock`,
|
|
1155
|
+
workerStatus: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/status.json`,
|
|
1156
|
+
workerIdleNotify: (teamName) => `.omc/state/team/${teamName}/worker-idle-notify.json`,
|
|
1157
|
+
workerPrevNotifyState: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/prev-notify-state.json`,
|
|
1158
|
+
events: (teamName) => `.omc/state/team/${teamName}/events.jsonl`,
|
|
1159
|
+
approval: (teamName, taskId) => `.omc/state/team/${teamName}/approvals/${taskId}.json`,
|
|
1160
|
+
manifest: (teamName) => `.omc/state/team/${teamName}/manifest.json`,
|
|
1161
|
+
monitorSnapshot: (teamName) => `.omc/state/team/${teamName}/monitor-snapshot.json`,
|
|
1162
|
+
summarySnapshot: (teamName) => `.omc/state/team/${teamName}/summary-snapshot.json`,
|
|
1163
|
+
phaseState: (teamName) => `.omc/state/team/${teamName}/phase-state.json`,
|
|
1164
|
+
scalingLock: (teamName) => `.omc/state/team/${teamName}/.scaling-lock`,
|
|
1165
|
+
workerIdentity: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/identity.json`,
|
|
1166
|
+
workerAgentsMd: (teamName) => `.omc/state/team/${teamName}/worker-agents.md`,
|
|
1167
|
+
shutdownRequest: (teamName, workerName2) => `.omc/state/team/${teamName}/workers/${workerName2}/shutdown-request.json`
|
|
776
1168
|
};
|
|
1169
|
+
function absPath(cwd, relativePath) {
|
|
1170
|
+
return (0, import_path8.join)(cwd, relativePath);
|
|
1171
|
+
}
|
|
1172
|
+
function teamStateRoot(cwd, teamName) {
|
|
1173
|
+
return (0, import_path8.join)(cwd, TeamPaths.root(teamName));
|
|
1174
|
+
}
|
|
777
1175
|
function getTaskStoragePath(cwd, teamName, taskId) {
|
|
778
1176
|
if (taskId !== void 0) {
|
|
779
|
-
return (0,
|
|
1177
|
+
return (0, import_path8.join)(cwd, TeamPaths.taskFile(teamName, taskId));
|
|
780
1178
|
}
|
|
781
|
-
return (0,
|
|
1179
|
+
return (0, import_path8.join)(cwd, TeamPaths.tasks(teamName));
|
|
782
1180
|
}
|
|
783
1181
|
|
|
784
1182
|
// src/team/task-file-ops.ts
|
|
@@ -797,7 +1195,7 @@ function acquireTaskLock(teamName, taskId, opts) {
|
|
|
797
1195
|
const staleLockMs = opts?.staleLockMs ?? DEFAULT_STALE_LOCK_MS;
|
|
798
1196
|
const dir = canonicalTasksDir(teamName, opts?.cwd);
|
|
799
1197
|
ensureDirWithMode(dir);
|
|
800
|
-
const lockPath = (0,
|
|
1198
|
+
const lockPath = (0, import_path9.join)(dir, `${sanitizeTaskId(taskId)}.lock`);
|
|
801
1199
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
802
1200
|
try {
|
|
803
1201
|
const fd = (0, import_fs5.openSync)(lockPath, import_fs5.constants.O_CREAT | import_fs5.constants.O_EXCL | import_fs5.constants.O_WRONLY, 384);
|
|
@@ -868,9 +1266,35 @@ function sanitizeTaskId(taskId) {
|
|
|
868
1266
|
function canonicalTasksDir(teamName, cwd) {
|
|
869
1267
|
const root = cwd ?? process.cwd();
|
|
870
1268
|
const dir = getTaskStoragePath(root, sanitizeName(teamName));
|
|
871
|
-
validateResolvedPath(dir, (0,
|
|
1269
|
+
validateResolvedPath(dir, (0, import_path9.join)(root, ".omc", "state", "team"));
|
|
872
1270
|
return dir;
|
|
873
1271
|
}
|
|
1272
|
+
function failureSidecarPath(teamName, taskId, cwd) {
|
|
1273
|
+
return (0, import_path9.join)(canonicalTasksDir(teamName, cwd), `${sanitizeTaskId(taskId)}.failure.json`);
|
|
1274
|
+
}
|
|
1275
|
+
function writeTaskFailure(teamName, taskId, error, opts) {
|
|
1276
|
+
const filePath = failureSidecarPath(teamName, taskId, opts?.cwd);
|
|
1277
|
+
const existing = readTaskFailure(teamName, taskId, opts);
|
|
1278
|
+
const sidecar = {
|
|
1279
|
+
taskId,
|
|
1280
|
+
lastError: error,
|
|
1281
|
+
retryCount: existing ? existing.retryCount + 1 : 1,
|
|
1282
|
+
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1283
|
+
};
|
|
1284
|
+
atomicWriteJson(filePath, sidecar);
|
|
1285
|
+
return sidecar;
|
|
1286
|
+
}
|
|
1287
|
+
function readTaskFailure(teamName, taskId, opts) {
|
|
1288
|
+
const filePath = failureSidecarPath(teamName, taskId, opts?.cwd);
|
|
1289
|
+
if (!(0, import_fs5.existsSync)(filePath)) return null;
|
|
1290
|
+
try {
|
|
1291
|
+
const raw = (0, import_fs5.readFileSync)(filePath, "utf-8");
|
|
1292
|
+
return JSON.parse(raw);
|
|
1293
|
+
} catch {
|
|
1294
|
+
return null;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
var DEFAULT_MAX_TASK_RETRIES = 5;
|
|
874
1298
|
|
|
875
1299
|
// src/team/runtime.ts
|
|
876
1300
|
function workerName(index) {
|
|
@@ -878,19 +1302,37 @@ function workerName(index) {
|
|
|
878
1302
|
}
|
|
879
1303
|
function stateRoot(cwd, teamName) {
|
|
880
1304
|
validateTeamName(teamName);
|
|
881
|
-
return (0,
|
|
1305
|
+
return (0, import_path10.join)(cwd, `.omc/state/team/${teamName}`);
|
|
882
1306
|
}
|
|
883
1307
|
async function writeJson(filePath, data) {
|
|
884
|
-
await (0, import_promises3.mkdir)((0,
|
|
1308
|
+
await (0, import_promises3.mkdir)((0, import_path10.join)(filePath, ".."), { recursive: true });
|
|
885
1309
|
await (0, import_promises3.writeFile)(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
886
1310
|
}
|
|
887
1311
|
async function readJsonSafe(filePath) {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1312
|
+
const isDoneSignalPath = filePath.endsWith("done.json");
|
|
1313
|
+
const maxAttempts = isDoneSignalPath ? 4 : 1;
|
|
1314
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1315
|
+
try {
|
|
1316
|
+
const content = await (0, import_promises3.readFile)(filePath, "utf-8");
|
|
1317
|
+
try {
|
|
1318
|
+
return JSON.parse(content);
|
|
1319
|
+
} catch {
|
|
1320
|
+
if (!isDoneSignalPath || attempt === maxAttempts) {
|
|
1321
|
+
return null;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
const isMissingDoneSignal = isDoneSignalPath && typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1326
|
+
if (isMissingDoneSignal) {
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
if (!isDoneSignalPath || attempt === maxAttempts) {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
await new Promise((resolve5) => setTimeout(resolve5, 25));
|
|
893
1334
|
}
|
|
1335
|
+
return null;
|
|
894
1336
|
}
|
|
895
1337
|
function parseWorkerIndex(workerNameValue) {
|
|
896
1338
|
const match = workerNameValue.match(/^worker-(\d+)$/);
|
|
@@ -899,13 +1341,13 @@ function parseWorkerIndex(workerNameValue) {
|
|
|
899
1341
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
|
|
900
1342
|
}
|
|
901
1343
|
function taskPath(root, taskId) {
|
|
902
|
-
return (0,
|
|
1344
|
+
return (0, import_path10.join)(root, "tasks", `${taskId}.json`);
|
|
903
1345
|
}
|
|
904
1346
|
async function writePanesTrackingFileIfPresent(runtime) {
|
|
905
1347
|
const jobId = process.env.OMC_JOB_ID;
|
|
906
1348
|
const omcJobsDir = process.env.OMC_JOBS_DIR;
|
|
907
1349
|
if (!jobId || !omcJobsDir) return;
|
|
908
|
-
const panesPath = (0,
|
|
1350
|
+
const panesPath = (0, import_path10.join)(omcJobsDir, `${jobId}-panes.json`);
|
|
909
1351
|
const tempPath = `${panesPath}.tmp`;
|
|
910
1352
|
await (0, import_promises3.writeFile)(
|
|
911
1353
|
tempPath,
|
|
@@ -932,48 +1374,87 @@ async function markTaskInProgress(root, taskId, owner, teamName, cwd) {
|
|
|
932
1374
|
}, { cwd });
|
|
933
1375
|
return result ?? false;
|
|
934
1376
|
}
|
|
935
|
-
async function resetTaskToPending(root, taskId) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
task.
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1377
|
+
async function resetTaskToPending(root, taskId, teamName, cwd) {
|
|
1378
|
+
await withTaskLock(teamName, taskId, async () => {
|
|
1379
|
+
const task = await readTask(root, taskId);
|
|
1380
|
+
if (!task) return;
|
|
1381
|
+
task.status = "pending";
|
|
1382
|
+
task.owner = null;
|
|
1383
|
+
task.assignedAt = void 0;
|
|
1384
|
+
await writeTask(root, task);
|
|
1385
|
+
}, { cwd });
|
|
1386
|
+
}
|
|
1387
|
+
async function markTaskFromDone(root, teamName, cwd, taskId, status, summary) {
|
|
1388
|
+
await withTaskLock(teamName, taskId, async () => {
|
|
1389
|
+
const task = await readTask(root, taskId);
|
|
1390
|
+
if (!task) return;
|
|
1391
|
+
task.status = status;
|
|
1392
|
+
task.result = summary;
|
|
1393
|
+
task.summary = summary;
|
|
1394
|
+
if (status === "completed") {
|
|
1395
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1396
|
+
} else {
|
|
1397
|
+
task.failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1398
|
+
}
|
|
1399
|
+
await writeTask(root, task);
|
|
1400
|
+
}, { cwd });
|
|
1401
|
+
}
|
|
1402
|
+
async function applyDeadPaneTransition(runtime, workerNameValue, taskId) {
|
|
1403
|
+
const root = stateRoot(runtime.cwd, runtime.teamName);
|
|
1404
|
+
const transition = await withTaskLock(runtime.teamName, taskId, async () => {
|
|
1405
|
+
const task = await readTask(root, taskId);
|
|
1406
|
+
if (!task) return { action: "skipped" };
|
|
1407
|
+
if (task.status === "completed" || task.status === "failed") {
|
|
1408
|
+
return { action: "skipped" };
|
|
1409
|
+
}
|
|
1410
|
+
if (task.status !== "in_progress" || task.owner !== workerNameValue) {
|
|
1411
|
+
return { action: "skipped" };
|
|
1412
|
+
}
|
|
1413
|
+
const failure = await writeTaskFailure(
|
|
1414
|
+
runtime.teamName,
|
|
1415
|
+
taskId,
|
|
1416
|
+
`Worker pane died before done.json was written (${workerNameValue})`,
|
|
1417
|
+
{ cwd: runtime.cwd }
|
|
1418
|
+
);
|
|
1419
|
+
const retryCount = failure.retryCount;
|
|
1420
|
+
if (retryCount >= DEFAULT_MAX_TASK_RETRIES) {
|
|
1421
|
+
task.status = "failed";
|
|
1422
|
+
task.owner = workerNameValue;
|
|
1423
|
+
task.summary = `Worker pane died before done.json was written (${workerNameValue})`;
|
|
1424
|
+
task.result = task.summary;
|
|
1425
|
+
task.failedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1426
|
+
await writeTask(root, task);
|
|
1427
|
+
return { action: "failed", retryCount };
|
|
1428
|
+
}
|
|
1429
|
+
task.status = "pending";
|
|
1430
|
+
task.owner = null;
|
|
1431
|
+
task.assignedAt = void 0;
|
|
1432
|
+
await writeTask(root, task);
|
|
1433
|
+
return { action: "requeued", retryCount };
|
|
1434
|
+
}, { cwd: runtime.cwd });
|
|
1435
|
+
return transition ?? { action: "skipped" };
|
|
965
1436
|
}
|
|
966
1437
|
async function nextPendingTaskIndex(runtime) {
|
|
967
1438
|
const root = stateRoot(runtime.cwd, runtime.teamName);
|
|
1439
|
+
const transientReadRetryAttempts = 3;
|
|
1440
|
+
const transientReadRetryDelayMs = 15;
|
|
968
1441
|
for (let i = 0; i < runtime.config.tasks.length; i++) {
|
|
969
|
-
const
|
|
1442
|
+
const taskId = String(i + 1);
|
|
1443
|
+
let task = await readTask(root, taskId);
|
|
1444
|
+
if (!task) {
|
|
1445
|
+
for (let attempt = 1; attempt < transientReadRetryAttempts; attempt++) {
|
|
1446
|
+
await new Promise((resolve5) => setTimeout(resolve5, transientReadRetryDelayMs));
|
|
1447
|
+
task = await readTask(root, taskId);
|
|
1448
|
+
if (task) break;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
970
1451
|
if (task?.status === "pending") return i;
|
|
971
1452
|
}
|
|
972
1453
|
return null;
|
|
973
1454
|
}
|
|
974
|
-
async function notifyPaneWithRetry(
|
|
1455
|
+
async function notifyPaneWithRetry(sessionName2, paneId, message, maxAttempts = 6, retryDelayMs = 350) {
|
|
975
1456
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
976
|
-
if (await sendToWorker(
|
|
1457
|
+
if (await sendToWorker(sessionName2, paneId, message)) {
|
|
977
1458
|
return true;
|
|
978
1459
|
}
|
|
979
1460
|
if (attempt < maxAttempts) {
|
|
@@ -1010,16 +1491,17 @@ function buildInitialTaskInstruction(teamName, workerName2, task, taskId) {
|
|
|
1010
1491
|
async function startTeam(config) {
|
|
1011
1492
|
const { teamName, agentTypes, tasks, cwd } = config;
|
|
1012
1493
|
validateTeamName(teamName);
|
|
1494
|
+
const resolvedBinaryPaths = {};
|
|
1013
1495
|
for (const agentType of [...new Set(agentTypes)]) {
|
|
1014
|
-
|
|
1496
|
+
resolvedBinaryPaths[agentType] = resolveValidatedBinaryPath(agentType);
|
|
1015
1497
|
}
|
|
1016
1498
|
const root = stateRoot(cwd, teamName);
|
|
1017
|
-
await (0, import_promises3.mkdir)((0,
|
|
1018
|
-
await (0, import_promises3.mkdir)((0,
|
|
1019
|
-
await writeJson((0,
|
|
1499
|
+
await (0, import_promises3.mkdir)((0, import_path10.join)(root, "tasks"), { recursive: true });
|
|
1500
|
+
await (0, import_promises3.mkdir)((0, import_path10.join)(root, "mailbox"), { recursive: true });
|
|
1501
|
+
await writeJson((0, import_path10.join)(root, "config.json"), config);
|
|
1020
1502
|
for (let i = 0; i < tasks.length; i++) {
|
|
1021
1503
|
const taskId = String(i + 1);
|
|
1022
|
-
await writeJson((0,
|
|
1504
|
+
await writeJson((0, import_path10.join)(root, "tasks", `${taskId}.json`), {
|
|
1023
1505
|
id: taskId,
|
|
1024
1506
|
subject: tasks[i].subject,
|
|
1025
1507
|
description: tasks[i].description,
|
|
@@ -1053,7 +1535,8 @@ async function startTeam(config) {
|
|
|
1053
1535
|
workerPaneIds: session.workerPaneIds,
|
|
1054
1536
|
// initially empty []
|
|
1055
1537
|
activeWorkers: /* @__PURE__ */ new Map(),
|
|
1056
|
-
cwd
|
|
1538
|
+
cwd,
|
|
1539
|
+
resolvedBinaryPaths
|
|
1057
1540
|
};
|
|
1058
1541
|
const maxConcurrentWorkers = agentTypes.length;
|
|
1059
1542
|
for (let i = 0; i < maxConcurrentWorkers; i++) {
|
|
@@ -1071,10 +1554,10 @@ async function monitorTeam(teamName, cwd, workerPaneIds) {
|
|
|
1071
1554
|
const taskScanStartedAt = Date.now();
|
|
1072
1555
|
const taskCounts = { pending: 0, inProgress: 0, completed: 0, failed: 0 };
|
|
1073
1556
|
try {
|
|
1074
|
-
const { readdir } = await import("fs/promises");
|
|
1075
|
-
const taskFiles = await
|
|
1557
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1558
|
+
const taskFiles = await readdir2((0, import_path10.join)(root, "tasks"));
|
|
1076
1559
|
for (const f of taskFiles.filter((f2) => f2.endsWith(".json"))) {
|
|
1077
|
-
const task = await readJsonSafe((0,
|
|
1560
|
+
const task = await readJsonSafe((0, import_path10.join)(root, "tasks", f));
|
|
1078
1561
|
if (task?.status === "pending") taskCounts.pending++;
|
|
1079
1562
|
else if (task?.status === "in_progress") taskCounts.inProgress++;
|
|
1080
1563
|
else if (task?.status === "completed") taskCounts.completed++;
|
|
@@ -1090,7 +1573,7 @@ async function monitorTeam(teamName, cwd, workerPaneIds) {
|
|
|
1090
1573
|
const wName = `worker-${i + 1}`;
|
|
1091
1574
|
const paneId = workerPaneIds[i];
|
|
1092
1575
|
const alive = await isWorkerAlive(paneId);
|
|
1093
|
-
const heartbeatPath = (0,
|
|
1576
|
+
const heartbeatPath = (0, import_path10.join)(root, "workers", wName, "heartbeat.json");
|
|
1094
1577
|
const heartbeat = await readJsonSafe(heartbeatPath);
|
|
1095
1578
|
let stalled = false;
|
|
1096
1579
|
if (heartbeat?.updatedAt) {
|
|
@@ -1145,21 +1628,21 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
1145
1628
|
const root = stateRoot(runtime.cwd, runtime.teamName);
|
|
1146
1629
|
const [doneSignals, aliveResults] = await Promise.all([
|
|
1147
1630
|
Promise.all(workers.map(([wName]) => {
|
|
1148
|
-
const donePath = (0,
|
|
1631
|
+
const donePath = (0, import_path10.join)(root, "workers", wName, "done.json");
|
|
1149
1632
|
return readJsonSafe(donePath);
|
|
1150
1633
|
})),
|
|
1151
1634
|
Promise.all(workers.map(([, active]) => isWorkerAlive(active.paneId)))
|
|
1152
1635
|
]);
|
|
1153
1636
|
for (let i = 0; i < workers.length; i++) {
|
|
1154
1637
|
const [wName, active] = workers[i];
|
|
1155
|
-
const donePath = (0,
|
|
1638
|
+
const donePath = (0, import_path10.join)(root, "workers", wName, "done.json");
|
|
1156
1639
|
const signal = doneSignals[i];
|
|
1157
1640
|
if (signal) {
|
|
1158
1641
|
unresponsiveCounts.delete(wName);
|
|
1159
|
-
await markTaskFromDone(root, signal.taskId || active.taskId, signal.status, signal.summary);
|
|
1642
|
+
await markTaskFromDone(root, runtime.teamName, runtime.cwd, signal.taskId || active.taskId, signal.status, signal.summary);
|
|
1160
1643
|
try {
|
|
1161
|
-
const { unlink } = await import("fs/promises");
|
|
1162
|
-
await
|
|
1644
|
+
const { unlink: unlink2 } = await import("fs/promises");
|
|
1645
|
+
await unlink2(donePath);
|
|
1163
1646
|
} catch {
|
|
1164
1647
|
}
|
|
1165
1648
|
await killWorkerPane(runtime, wName, active.paneId);
|
|
@@ -1174,7 +1657,11 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
1174
1657
|
const alive = aliveResults[i];
|
|
1175
1658
|
if (!alive) {
|
|
1176
1659
|
unresponsiveCounts.delete(wName);
|
|
1177
|
-
await
|
|
1660
|
+
const transition = await applyDeadPaneTransition(runtime, wName, active.taskId);
|
|
1661
|
+
if (transition.action === "requeued") {
|
|
1662
|
+
const retryCount = transition.retryCount ?? 1;
|
|
1663
|
+
console.warn(`[watchdog] worker ${wName} dead pane \u2014 requeuing task ${active.taskId} (retry ${retryCount}/${DEFAULT_MAX_TASK_RETRIES})`);
|
|
1664
|
+
}
|
|
1178
1665
|
await killWorkerPane(runtime, wName, active.paneId);
|
|
1179
1666
|
if (!await allTasksTerminal(runtime)) {
|
|
1180
1667
|
const nextTaskIndexValue = await nextPendingTaskIndex(runtime);
|
|
@@ -1184,7 +1671,7 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
1184
1671
|
}
|
|
1185
1672
|
continue;
|
|
1186
1673
|
}
|
|
1187
|
-
const heartbeatPath = (0,
|
|
1674
|
+
const heartbeatPath = (0, import_path10.join)(root, "workers", wName, "heartbeat.json");
|
|
1188
1675
|
const heartbeat = await readJsonSafe(heartbeatPath);
|
|
1189
1676
|
const isStalled = heartbeat?.updatedAt ? Date.now() - new Date(heartbeat.updatedAt).getTime() > 6e4 : false;
|
|
1190
1677
|
if (isStalled) {
|
|
@@ -1195,7 +1682,10 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
1195
1682
|
} else {
|
|
1196
1683
|
console.warn(`[watchdog] worker ${wName} unresponsive ${count} consecutive ticks \u2014 killing and reassigning task ${active.taskId}`);
|
|
1197
1684
|
unresponsiveCounts.delete(wName);
|
|
1198
|
-
await
|
|
1685
|
+
const transition = await applyDeadPaneTransition(runtime, wName, active.taskId);
|
|
1686
|
+
if (transition.action === "requeued") {
|
|
1687
|
+
console.warn(`[watchdog] worker ${wName} stall-killed \u2014 requeuing task ${active.taskId} (retry ${transition.retryCount}/${DEFAULT_MAX_TASK_RETRIES})`);
|
|
1688
|
+
}
|
|
1199
1689
|
await killWorkerPane(runtime, wName, active.paneId);
|
|
1200
1690
|
if (!await allTasksTerminal(runtime)) {
|
|
1201
1691
|
const nextTaskIndexValue = await nextPendingTaskIndex(runtime);
|
|
@@ -1216,7 +1706,7 @@ function watchdogCliWorkers(runtime, intervalMs) {
|
|
|
1216
1706
|
console.warn(`[watchdog] ${consecutiveFailures} consecutive failures \u2014 marking team as failed`);
|
|
1217
1707
|
try {
|
|
1218
1708
|
const root = stateRoot(runtime.cwd, runtime.teamName);
|
|
1219
|
-
await writeJson((0,
|
|
1709
|
+
await writeJson((0, import_path10.join)(root, "watchdog-failed.json"), {
|
|
1220
1710
|
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1221
1711
|
consecutiveFailures,
|
|
1222
1712
|
lastError: err instanceof Error ? err.message : String(err)
|
|
@@ -1267,10 +1757,26 @@ async function spawnWorkerForTask(runtime, workerNameValue, taskIndex) {
|
|
|
1267
1757
|
await composeInitialInbox(runtime.teamName, workerNameValue, instruction, runtime.cwd);
|
|
1268
1758
|
const relInboxPath = `.omc/state/team/${runtime.teamName}/workers/${workerNameValue}/inbox.md`;
|
|
1269
1759
|
const envVars = getWorkerEnv(runtime.teamName, workerNameValue, agentType);
|
|
1760
|
+
const resolvedBinaryPath = runtime.resolvedBinaryPaths?.[agentType] ?? resolveValidatedBinaryPath(agentType);
|
|
1761
|
+
if (!runtime.resolvedBinaryPaths) {
|
|
1762
|
+
runtime.resolvedBinaryPaths = {};
|
|
1763
|
+
}
|
|
1764
|
+
runtime.resolvedBinaryPaths[agentType] = resolvedBinaryPath;
|
|
1765
|
+
const modelForAgent = (() => {
|
|
1766
|
+
if (agentType === "codex") {
|
|
1767
|
+
return process.env.OMC_EXTERNAL_MODELS_DEFAULT_CODEX_MODEL || process.env.OMC_CODEX_DEFAULT_MODEL || void 0;
|
|
1768
|
+
}
|
|
1769
|
+
if (agentType === "gemini") {
|
|
1770
|
+
return process.env.OMC_EXTERNAL_MODELS_DEFAULT_GEMINI_MODEL || process.env.OMC_GEMINI_DEFAULT_MODEL || void 0;
|
|
1771
|
+
}
|
|
1772
|
+
return void 0;
|
|
1773
|
+
})();
|
|
1270
1774
|
const [launchBinary, ...launchArgs] = buildWorkerArgv(agentType, {
|
|
1271
1775
|
teamName: runtime.teamName,
|
|
1272
1776
|
workerName: workerNameValue,
|
|
1273
|
-
cwd: runtime.cwd
|
|
1777
|
+
cwd: runtime.cwd,
|
|
1778
|
+
resolvedBinaryPath,
|
|
1779
|
+
model: modelForAgent
|
|
1274
1780
|
});
|
|
1275
1781
|
if (usePromptMode) {
|
|
1276
1782
|
const promptArgs = getPromptModeArgs(agentType, `Read and execute your task from: ${relInboxPath}`);
|
|
@@ -1296,12 +1802,17 @@ async function spawnWorkerForTask(runtime, workerNameValue, taskIndex) {
|
|
|
1296
1802
|
} catch {
|
|
1297
1803
|
}
|
|
1298
1804
|
if (!usePromptMode) {
|
|
1299
|
-
|
|
1805
|
+
const paneReady = await waitForPaneReady(paneId);
|
|
1806
|
+
if (!paneReady) {
|
|
1807
|
+
await killWorkerPane(runtime, workerNameValue, paneId);
|
|
1808
|
+
await resetTaskToPending(root, taskId, runtime.teamName, runtime.cwd);
|
|
1809
|
+
throw new Error(`worker_pane_not_ready:${workerNameValue}`);
|
|
1810
|
+
}
|
|
1300
1811
|
if (agentType === "gemini") {
|
|
1301
1812
|
const confirmed = await notifyPaneWithRetry(runtime.sessionName, paneId, "1");
|
|
1302
1813
|
if (!confirmed) {
|
|
1303
1814
|
await killWorkerPane(runtime, workerNameValue, paneId);
|
|
1304
|
-
await resetTaskToPending(root, taskId);
|
|
1815
|
+
await resetTaskToPending(root, taskId, runtime.teamName, runtime.cwd);
|
|
1305
1816
|
throw new Error(`worker_notify_failed:${workerNameValue}:trust-confirm`);
|
|
1306
1817
|
}
|
|
1307
1818
|
await new Promise((r) => setTimeout(r, 800));
|
|
@@ -1313,7 +1824,7 @@ async function spawnWorkerForTask(runtime, workerNameValue, taskIndex) {
|
|
|
1313
1824
|
);
|
|
1314
1825
|
if (!notified) {
|
|
1315
1826
|
await killWorkerPane(runtime, workerNameValue, paneId);
|
|
1316
|
-
await resetTaskToPending(root, taskId);
|
|
1827
|
+
await resetTaskToPending(root, taskId, runtime.teamName, runtime.cwd);
|
|
1317
1828
|
throw new Error(`worker_notify_failed:${workerNameValue}:initial-inbox`);
|
|
1318
1829
|
}
|
|
1319
1830
|
}
|
|
@@ -1337,13 +1848,13 @@ async function killWorkerPane(runtime, workerNameValue, paneId) {
|
|
|
1337
1848
|
} catch {
|
|
1338
1849
|
}
|
|
1339
1850
|
}
|
|
1340
|
-
async function shutdownTeam(teamName,
|
|
1851
|
+
async function shutdownTeam(teamName, sessionName2, cwd, timeoutMs = 3e4, workerPaneIds, leaderPaneId) {
|
|
1341
1852
|
const root = stateRoot(cwd, teamName);
|
|
1342
|
-
await writeJson((0,
|
|
1853
|
+
await writeJson((0, import_path10.join)(root, "shutdown.json"), {
|
|
1343
1854
|
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1344
1855
|
teamName
|
|
1345
1856
|
});
|
|
1346
|
-
const configData = await readJsonSafe((0,
|
|
1857
|
+
const configData = await readJsonSafe((0, import_path10.join)(root, "config.json"));
|
|
1347
1858
|
const CLI_AGENT_TYPES = /* @__PURE__ */ new Set(["claude", "codex", "gemini"]);
|
|
1348
1859
|
const agentTypes = configData?.agentTypes ?? [];
|
|
1349
1860
|
const isCliWorkerTeam = agentTypes.length > 0 && agentTypes.every((t) => CLI_AGENT_TYPES.has(t));
|
|
@@ -1353,7 +1864,7 @@ async function shutdownTeam(teamName, sessionName, cwd, timeoutMs = 3e4, workerP
|
|
|
1353
1864
|
const expectedAcks = Array.from({ length: workerCount }, (_, i) => `worker-${i + 1}`);
|
|
1354
1865
|
while (Date.now() < deadline && expectedAcks.length > 0) {
|
|
1355
1866
|
for (const wName of [...expectedAcks]) {
|
|
1356
|
-
const ackPath = (0,
|
|
1867
|
+
const ackPath = (0, import_path10.join)(root, "workers", wName, "shutdown-ack.json");
|
|
1357
1868
|
if ((0, import_fs6.existsSync)(ackPath)) {
|
|
1358
1869
|
expectedAcks.splice(expectedAcks.indexOf(wName), 1);
|
|
1359
1870
|
}
|
|
@@ -1363,31 +1874,1852 @@ async function shutdownTeam(teamName, sessionName, cwd, timeoutMs = 3e4, workerP
|
|
|
1363
1874
|
}
|
|
1364
1875
|
}
|
|
1365
1876
|
}
|
|
1366
|
-
await killTeamSession(
|
|
1877
|
+
await killTeamSession(sessionName2, workerPaneIds, leaderPaneId);
|
|
1367
1878
|
try {
|
|
1368
1879
|
await (0, import_promises3.rm)(root, { recursive: true, force: true });
|
|
1369
1880
|
} catch {
|
|
1370
1881
|
}
|
|
1371
1882
|
}
|
|
1372
1883
|
|
|
1373
|
-
// src/
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1884
|
+
// src/hooks/factcheck/checks.ts
|
|
1885
|
+
var import_fs7 = require("fs");
|
|
1886
|
+
var import_path11 = require("path");
|
|
1887
|
+
|
|
1888
|
+
// src/hooks/factcheck/types.ts
|
|
1889
|
+
var REQUIRED_FIELDS = /* @__PURE__ */ new Set([
|
|
1890
|
+
"schema_version",
|
|
1891
|
+
"run_id",
|
|
1892
|
+
"ts",
|
|
1893
|
+
"cwd",
|
|
1894
|
+
"mode",
|
|
1895
|
+
"files_modified",
|
|
1896
|
+
"files_created",
|
|
1897
|
+
"artifacts_expected",
|
|
1898
|
+
"gates"
|
|
1899
|
+
]);
|
|
1900
|
+
var REQUIRED_GATES = /* @__PURE__ */ new Set([
|
|
1901
|
+
"selftest_ran",
|
|
1902
|
+
"goldens_ran",
|
|
1903
|
+
"sentinel_stop_smoke_ran",
|
|
1904
|
+
"shadow_leak_check_ran"
|
|
1905
|
+
]);
|
|
1906
|
+
|
|
1907
|
+
// src/hooks/factcheck/checks.ts
|
|
1908
|
+
function checkMissingFields(claims) {
|
|
1909
|
+
const missing = [];
|
|
1910
|
+
for (const field of REQUIRED_FIELDS) {
|
|
1911
|
+
if (!(field in claims)) {
|
|
1912
|
+
missing.push(field);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
return missing.sort();
|
|
1916
|
+
}
|
|
1917
|
+
function checkMissingGates(claims) {
|
|
1918
|
+
const gates = claims.gates ?? {};
|
|
1919
|
+
const missing = [];
|
|
1920
|
+
for (const gate of REQUIRED_GATES) {
|
|
1921
|
+
if (!(gate in gates)) {
|
|
1922
|
+
missing.push(gate);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
return missing.sort();
|
|
1926
|
+
}
|
|
1927
|
+
function getFalseGates(claims) {
|
|
1928
|
+
const gates = claims.gates ?? {};
|
|
1929
|
+
const falseGates = [];
|
|
1930
|
+
for (const gate of REQUIRED_GATES) {
|
|
1931
|
+
if (gate in gates && !gates[gate]) {
|
|
1932
|
+
falseGates.push(gate);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return falseGates.sort();
|
|
1936
|
+
}
|
|
1937
|
+
function sourceFileCount(claims) {
|
|
1938
|
+
const modified = claims.files_modified ?? [];
|
|
1939
|
+
const created = claims.files_created ?? [];
|
|
1940
|
+
return modified.length + created.length;
|
|
1941
|
+
}
|
|
1942
|
+
function checkPaths(claims, policy) {
|
|
1943
|
+
const out = [];
|
|
1944
|
+
const allPaths = [
|
|
1945
|
+
...claims.files_modified ?? [],
|
|
1946
|
+
...claims.files_created ?? [],
|
|
1947
|
+
...claims.artifacts_expected ?? []
|
|
1948
|
+
];
|
|
1949
|
+
const deleted = new Set(claims.files_deleted ?? []);
|
|
1950
|
+
for (const pathStr of allPaths) {
|
|
1951
|
+
if (deleted.has(pathStr)) continue;
|
|
1952
|
+
let prefixBlocked = false;
|
|
1953
|
+
for (const prefix of policy.forbidden_path_prefixes) {
|
|
1954
|
+
if (pathStr.startsWith(prefix)) {
|
|
1955
|
+
out.push({ check: "H", severity: "FAIL", detail: `Forbidden path prefix: ${pathStr}` });
|
|
1956
|
+
prefixBlocked = true;
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
if (!prefixBlocked) {
|
|
1961
|
+
for (const fragment of policy.forbidden_path_substrings) {
|
|
1962
|
+
if (pathStr.includes(fragment)) {
|
|
1963
|
+
out.push({ check: "H", severity: "FAIL", detail: `Forbidden path fragment: ${pathStr}` });
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (!(0, import_fs7.existsSync)(pathStr)) {
|
|
1969
|
+
out.push({ check: "C", severity: "FAIL", detail: `File not found: ${pathStr}` });
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return out;
|
|
1973
|
+
}
|
|
1974
|
+
function checkCommands(claims, policy) {
|
|
1975
|
+
const out = [];
|
|
1976
|
+
const commands = (claims.commands_executed ?? []).map(String);
|
|
1977
|
+
for (const cmd of commands) {
|
|
1978
|
+
const hitPrefix = policy.forbidden_path_prefixes.some(
|
|
1979
|
+
(forbidden) => cmd.includes(forbidden)
|
|
1980
|
+
);
|
|
1981
|
+
if (!hitPrefix) continue;
|
|
1982
|
+
const stripped = cmd.trim().replace(/^\(/, "");
|
|
1983
|
+
const isReadOnly = policy.readonly_command_prefixes.some(
|
|
1984
|
+
(prefix) => stripped.startsWith(prefix)
|
|
1985
|
+
);
|
|
1986
|
+
if (!isReadOnly) {
|
|
1987
|
+
out.push({ check: "H", severity: "FAIL", detail: `Forbidden mutating command: ${cmd}` });
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
return out;
|
|
1991
|
+
}
|
|
1992
|
+
function checkCwdParity(claimsCwd, runtimeCwd, mode, policy) {
|
|
1993
|
+
const enforceCwd = policy.warn_on_cwd_mismatch && (mode !== "quick" || policy.enforce_cwd_parity_in_quick);
|
|
1994
|
+
if (!enforceCwd || !claimsCwd) return null;
|
|
1995
|
+
const claimsCwdCanonical = (0, import_path11.resolve)(claimsCwd);
|
|
1996
|
+
const runtimeCwdCanonical = (0, import_path11.resolve)(runtimeCwd);
|
|
1997
|
+
if (claimsCwdCanonical !== runtimeCwdCanonical) {
|
|
1998
|
+
const severity = mode === "strict" ? "FAIL" : "WARN";
|
|
1999
|
+
return {
|
|
2000
|
+
check: "argv_parity",
|
|
2001
|
+
severity,
|
|
2002
|
+
detail: `claims.cwd=${claimsCwdCanonical} runtime.cwd=${runtimeCwdCanonical}`
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
return null;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// src/hooks/factcheck/config.ts
|
|
2009
|
+
var import_os2 = require("os");
|
|
2010
|
+
|
|
2011
|
+
// src/config/loader.ts
|
|
2012
|
+
var import_fs8 = require("fs");
|
|
2013
|
+
var import_path12 = require("path");
|
|
2014
|
+
|
|
2015
|
+
// src/utils/jsonc.ts
|
|
2016
|
+
function parseJsonc(content) {
|
|
2017
|
+
const cleaned = stripJsoncComments(content);
|
|
2018
|
+
return JSON.parse(cleaned);
|
|
2019
|
+
}
|
|
2020
|
+
function stripJsoncComments(content) {
|
|
2021
|
+
let result = "";
|
|
2022
|
+
let i = 0;
|
|
2023
|
+
while (i < content.length) {
|
|
2024
|
+
if (content[i] === "/" && content[i + 1] === "/") {
|
|
2025
|
+
while (i < content.length && content[i] !== "\n") {
|
|
2026
|
+
i++;
|
|
2027
|
+
}
|
|
2028
|
+
continue;
|
|
2029
|
+
}
|
|
2030
|
+
if (content[i] === "/" && content[i + 1] === "*") {
|
|
2031
|
+
i += 2;
|
|
2032
|
+
while (i < content.length && !(content[i] === "*" && content[i + 1] === "/")) {
|
|
2033
|
+
i++;
|
|
2034
|
+
}
|
|
2035
|
+
i += 2;
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (content[i] === '"') {
|
|
2039
|
+
result += content[i];
|
|
2040
|
+
i++;
|
|
2041
|
+
while (i < content.length && content[i] !== '"') {
|
|
2042
|
+
if (content[i] === "\\" && content[i + 1] === '"') {
|
|
2043
|
+
result += content[i];
|
|
2044
|
+
i++;
|
|
2045
|
+
}
|
|
2046
|
+
result += content[i];
|
|
2047
|
+
i++;
|
|
2048
|
+
}
|
|
2049
|
+
if (i < content.length) {
|
|
2050
|
+
result += content[i];
|
|
2051
|
+
i++;
|
|
2052
|
+
}
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
result += content[i];
|
|
2056
|
+
i++;
|
|
2057
|
+
}
|
|
2058
|
+
return result;
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// src/utils/ssrf-guard.ts
|
|
2062
|
+
var BLOCKED_HOST_PATTERNS = [
|
|
2063
|
+
// Exact matches
|
|
2064
|
+
/^localhost$/i,
|
|
2065
|
+
/^127\.[0-9]+\.[0-9]+\.[0-9]+$/,
|
|
2066
|
+
// Loopback
|
|
2067
|
+
/^10\.[0-9]+\.[0-9]+\.[0-9]+$/,
|
|
2068
|
+
// Class A private
|
|
2069
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]+\.[0-9]+$/,
|
|
2070
|
+
// Class B private
|
|
2071
|
+
/^192\.168\.[0-9]+\.[0-9]+$/,
|
|
2072
|
+
// Class C private
|
|
2073
|
+
/^169\.254\.[0-9]+\.[0-9]+$/,
|
|
2074
|
+
// Link-local
|
|
2075
|
+
/^(0|22[4-9]|23[0-9])\.[0-9]+\.[0-9]+\.[0-9]+$/,
|
|
2076
|
+
// Multicast, reserved
|
|
2077
|
+
/^\[?::1\]?$/,
|
|
2078
|
+
// IPv6 loopback
|
|
2079
|
+
/^\[?fc00:/i,
|
|
2080
|
+
// IPv6 unique local
|
|
2081
|
+
/^\[?fe80:/i
|
|
2082
|
+
// IPv6 link-local
|
|
2083
|
+
];
|
|
2084
|
+
var ALLOWED_SCHEMES = ["https:", "http:"];
|
|
2085
|
+
function validateUrlForSSRF(urlString) {
|
|
2086
|
+
if (!urlString || typeof urlString !== "string") {
|
|
2087
|
+
return { allowed: false, reason: "URL is empty or invalid" };
|
|
2088
|
+
}
|
|
2089
|
+
let parsed;
|
|
2090
|
+
try {
|
|
2091
|
+
parsed = new URL(urlString);
|
|
2092
|
+
} catch {
|
|
2093
|
+
return { allowed: false, reason: "Invalid URL format" };
|
|
2094
|
+
}
|
|
2095
|
+
if (!ALLOWED_SCHEMES.includes(parsed.protocol)) {
|
|
2096
|
+
return { allowed: false, reason: `Protocol '${parsed.protocol}' is not allowed` };
|
|
2097
|
+
}
|
|
2098
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
2099
|
+
for (const pattern of BLOCKED_HOST_PATTERNS) {
|
|
2100
|
+
if (pattern.test(hostname)) {
|
|
2101
|
+
return {
|
|
2102
|
+
allowed: false,
|
|
2103
|
+
reason: `Hostname '${hostname}' resolves to a blocked internal/private address`
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
if (parsed.username || parsed.password) {
|
|
2108
|
+
return { allowed: false, reason: "URLs with embedded credentials are not allowed" };
|
|
2109
|
+
}
|
|
2110
|
+
const dangerousPaths = [
|
|
2111
|
+
"/metadata",
|
|
2112
|
+
"/meta-data",
|
|
2113
|
+
"/latest/meta-data",
|
|
2114
|
+
"/computeMetadata"
|
|
2115
|
+
];
|
|
2116
|
+
const pathLower = parsed.pathname.toLowerCase();
|
|
2117
|
+
for (const dangerous of dangerousPaths) {
|
|
2118
|
+
if (pathLower.startsWith(dangerous)) {
|
|
2119
|
+
return {
|
|
2120
|
+
allowed: false,
|
|
2121
|
+
reason: `Path '${parsed.pathname}' is blocked (cloud metadata access)`
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
return { allowed: true };
|
|
2126
|
+
}
|
|
2127
|
+
function validateAnthropicBaseUrl(urlString) {
|
|
2128
|
+
const result = validateUrlForSSRF(urlString);
|
|
2129
|
+
if (!result.allowed) {
|
|
2130
|
+
return result;
|
|
2131
|
+
}
|
|
2132
|
+
let parsed;
|
|
2133
|
+
try {
|
|
2134
|
+
parsed = new URL(urlString);
|
|
2135
|
+
} catch {
|
|
2136
|
+
return { allowed: false, reason: "Invalid URL" };
|
|
2137
|
+
}
|
|
2138
|
+
if (parsed.protocol === "http:") {
|
|
2139
|
+
console.warn("[SSRF Guard] Warning: Using HTTP instead of HTTPS for ANTHROPIC_BASE_URL");
|
|
2140
|
+
}
|
|
2141
|
+
return { allowed: true };
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/config/models.ts
|
|
2145
|
+
var BUILTIN_MODEL_HIGH = "claude-opus-4-6-20260205";
|
|
2146
|
+
var BUILTIN_MODEL_MEDIUM = "claude-sonnet-4-6-20260217";
|
|
2147
|
+
var BUILTIN_MODEL_LOW = "claude-haiku-4-5-20251001";
|
|
2148
|
+
function getDefaultModelHigh() {
|
|
2149
|
+
return process.env.OMC_MODEL_HIGH || BUILTIN_MODEL_HIGH;
|
|
2150
|
+
}
|
|
2151
|
+
function getDefaultModelMedium() {
|
|
2152
|
+
return process.env.OMC_MODEL_MEDIUM || BUILTIN_MODEL_MEDIUM;
|
|
2153
|
+
}
|
|
2154
|
+
function getDefaultModelLow() {
|
|
2155
|
+
return process.env.OMC_MODEL_LOW || BUILTIN_MODEL_LOW;
|
|
2156
|
+
}
|
|
2157
|
+
function isBedrock() {
|
|
2158
|
+
if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
|
|
2159
|
+
return true;
|
|
2160
|
+
}
|
|
2161
|
+
const modelId = process.env.CLAUDE_MODEL || process.env.ANTHROPIC_MODEL || "";
|
|
2162
|
+
if (modelId && /^((us|eu|ap|global)\.anthropic\.|anthropic\.claude)/i.test(modelId)) {
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
function isVertexAI() {
|
|
2168
|
+
if (process.env.CLAUDE_CODE_USE_VERTEX === "1") {
|
|
2169
|
+
return true;
|
|
2170
|
+
}
|
|
2171
|
+
const modelId = process.env.CLAUDE_MODEL || process.env.ANTHROPIC_MODEL || "";
|
|
2172
|
+
if (modelId && modelId.toLowerCase().startsWith("vertex_ai/")) {
|
|
2173
|
+
return true;
|
|
2174
|
+
}
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
function isNonClaudeProvider() {
|
|
2178
|
+
if (process.env.OMC_ROUTING_FORCE_INHERIT === "true") {
|
|
2179
|
+
return true;
|
|
2180
|
+
}
|
|
2181
|
+
if (isBedrock()) {
|
|
2182
|
+
return true;
|
|
2183
|
+
}
|
|
2184
|
+
if (isVertexAI()) {
|
|
2185
|
+
return true;
|
|
2186
|
+
}
|
|
2187
|
+
const modelId = process.env.CLAUDE_MODEL || process.env.ANTHROPIC_MODEL || "";
|
|
2188
|
+
if (modelId && !modelId.toLowerCase().includes("claude")) {
|
|
2189
|
+
return true;
|
|
2190
|
+
}
|
|
2191
|
+
const baseUrl = process.env.ANTHROPIC_BASE_URL || "";
|
|
2192
|
+
if (baseUrl) {
|
|
2193
|
+
const validation = validateAnthropicBaseUrl(baseUrl);
|
|
2194
|
+
if (!validation.allowed) {
|
|
2195
|
+
console.error(`[SSRF Guard] Rejecting ANTHROPIC_BASE_URL: ${validation.reason}`);
|
|
2196
|
+
return true;
|
|
2197
|
+
}
|
|
2198
|
+
if (!baseUrl.includes("anthropic.com")) {
|
|
2199
|
+
return true;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
return false;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// src/config/loader.ts
|
|
2206
|
+
var DEFAULT_CONFIG = {
|
|
2207
|
+
agents: {
|
|
2208
|
+
omc: { model: getDefaultModelHigh() },
|
|
2209
|
+
explore: { model: getDefaultModelLow() },
|
|
2210
|
+
analyst: { model: getDefaultModelHigh() },
|
|
2211
|
+
planner: { model: getDefaultModelHigh() },
|
|
2212
|
+
architect: { model: getDefaultModelHigh() },
|
|
2213
|
+
debugger: { model: getDefaultModelMedium() },
|
|
2214
|
+
executor: { model: getDefaultModelMedium() },
|
|
2215
|
+
verifier: { model: getDefaultModelMedium() },
|
|
2216
|
+
qualityReviewer: { model: getDefaultModelMedium() },
|
|
2217
|
+
securityReviewer: { model: getDefaultModelMedium() },
|
|
2218
|
+
codeReviewer: { model: getDefaultModelHigh() },
|
|
2219
|
+
deepExecutor: { model: getDefaultModelHigh() },
|
|
2220
|
+
testEngineer: { model: getDefaultModelMedium() },
|
|
2221
|
+
buildFixer: { model: getDefaultModelMedium() },
|
|
2222
|
+
designer: { model: getDefaultModelMedium() },
|
|
2223
|
+
writer: { model: getDefaultModelLow() },
|
|
2224
|
+
qaTester: { model: getDefaultModelMedium() },
|
|
2225
|
+
scientist: { model: getDefaultModelMedium() },
|
|
2226
|
+
gitMaster: { model: getDefaultModelMedium() },
|
|
2227
|
+
codeSimplifier: { model: getDefaultModelHigh() },
|
|
2228
|
+
critic: { model: getDefaultModelHigh() },
|
|
2229
|
+
documentSpecialist: { model: getDefaultModelMedium() }
|
|
2230
|
+
},
|
|
2231
|
+
features: {
|
|
2232
|
+
parallelExecution: true,
|
|
2233
|
+
lspTools: true,
|
|
2234
|
+
// Real LSP integration with language servers
|
|
2235
|
+
astTools: true,
|
|
2236
|
+
// Real AST tools using ast-grep
|
|
2237
|
+
continuationEnforcement: true,
|
|
2238
|
+
autoContextInjection: true
|
|
2239
|
+
},
|
|
2240
|
+
mcpServers: {
|
|
2241
|
+
exa: { enabled: true },
|
|
2242
|
+
context7: { enabled: true }
|
|
2243
|
+
},
|
|
2244
|
+
permissions: {
|
|
2245
|
+
allowBash: true,
|
|
2246
|
+
allowEdit: true,
|
|
2247
|
+
allowWrite: true,
|
|
2248
|
+
maxBackgroundTasks: 5
|
|
2249
|
+
},
|
|
2250
|
+
magicKeywords: {
|
|
2251
|
+
ultrawork: ["ultrawork", "ulw", "uw"],
|
|
2252
|
+
search: ["search", "find", "locate"],
|
|
2253
|
+
analyze: ["analyze", "investigate", "examine"],
|
|
2254
|
+
ultrathink: ["ultrathink", "think", "reason", "ponder"]
|
|
2255
|
+
},
|
|
2256
|
+
// Intelligent model routing configuration
|
|
2257
|
+
routing: {
|
|
2258
|
+
enabled: true,
|
|
2259
|
+
defaultTier: "MEDIUM",
|
|
2260
|
+
forceInherit: false,
|
|
2261
|
+
escalationEnabled: true,
|
|
2262
|
+
maxEscalations: 2,
|
|
2263
|
+
tierModels: {
|
|
2264
|
+
LOW: getDefaultModelLow(),
|
|
2265
|
+
MEDIUM: getDefaultModelMedium(),
|
|
2266
|
+
HIGH: getDefaultModelHigh()
|
|
2267
|
+
},
|
|
2268
|
+
agentOverrides: {
|
|
2269
|
+
architect: { tier: "HIGH", reason: "Advisory agent requires deep reasoning" },
|
|
2270
|
+
planner: { tier: "HIGH", reason: "Strategic planning requires deep reasoning" },
|
|
2271
|
+
critic: { tier: "HIGH", reason: "Critical review requires deep reasoning" },
|
|
2272
|
+
analyst: { tier: "HIGH", reason: "Pre-planning analysis requires deep reasoning" },
|
|
2273
|
+
explore: { tier: "LOW", reason: "Exploration is search-focused" },
|
|
2274
|
+
"writer": { tier: "LOW", reason: "Documentation is straightforward" }
|
|
2275
|
+
},
|
|
2276
|
+
escalationKeywords: [
|
|
2277
|
+
"critical",
|
|
2278
|
+
"production",
|
|
2279
|
+
"urgent",
|
|
2280
|
+
"security",
|
|
2281
|
+
"breaking",
|
|
2282
|
+
"architecture",
|
|
2283
|
+
"refactor",
|
|
2284
|
+
"redesign",
|
|
2285
|
+
"root cause"
|
|
2286
|
+
],
|
|
2287
|
+
simplificationKeywords: [
|
|
2288
|
+
"find",
|
|
2289
|
+
"list",
|
|
2290
|
+
"show",
|
|
2291
|
+
"where",
|
|
2292
|
+
"search",
|
|
2293
|
+
"locate",
|
|
2294
|
+
"grep"
|
|
2295
|
+
]
|
|
2296
|
+
},
|
|
2297
|
+
// External models configuration (Codex, Gemini)
|
|
2298
|
+
// Static defaults only — env var overrides applied in loadEnvConfig()
|
|
2299
|
+
externalModels: {
|
|
2300
|
+
defaults: {
|
|
2301
|
+
codexModel: "gpt-5.3-codex",
|
|
2302
|
+
geminiModel: "gemini-3.1-pro-preview"
|
|
2303
|
+
},
|
|
2304
|
+
fallbackPolicy: {
|
|
2305
|
+
onModelFailure: "provider_chain",
|
|
2306
|
+
allowCrossProvider: false,
|
|
2307
|
+
crossProviderOrder: ["codex", "gemini"]
|
|
2308
|
+
}
|
|
2309
|
+
},
|
|
2310
|
+
// Delegation routing configuration (opt-in feature for external model routing)
|
|
2311
|
+
delegationRouting: {
|
|
2312
|
+
enabled: false,
|
|
2313
|
+
// Opt-in feature
|
|
2314
|
+
defaultProvider: "claude",
|
|
2315
|
+
roles: {}
|
|
2316
|
+
},
|
|
2317
|
+
// Startup codebase map injection (issue #804)
|
|
2318
|
+
startupCodebaseMap: {
|
|
2319
|
+
enabled: true,
|
|
2320
|
+
maxFiles: 200,
|
|
2321
|
+
maxDepth: 4
|
|
2322
|
+
},
|
|
2323
|
+
// Task size detection (issue #790): prevent over-orchestration for small tasks
|
|
2324
|
+
taskSizeDetection: {
|
|
2325
|
+
enabled: true,
|
|
2326
|
+
smallWordLimit: 50,
|
|
2327
|
+
largeWordLimit: 200,
|
|
2328
|
+
suppressHeavyModesForSmallTasks: true
|
|
2329
|
+
}
|
|
2330
|
+
};
|
|
2331
|
+
function getConfigPaths() {
|
|
2332
|
+
const userConfigDir = getConfigDir2();
|
|
2333
|
+
return {
|
|
2334
|
+
user: (0, import_path12.join)(userConfigDir, "claude-omc", "config.jsonc"),
|
|
2335
|
+
project: (0, import_path12.join)(process.cwd(), ".claude", "omc.jsonc")
|
|
2336
|
+
};
|
|
2337
|
+
}
|
|
2338
|
+
function loadJsoncFile(path) {
|
|
2339
|
+
if (!(0, import_fs8.existsSync)(path)) {
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
try {
|
|
2343
|
+
const content = (0, import_fs8.readFileSync)(path, "utf-8");
|
|
2344
|
+
const result = parseJsonc(content);
|
|
2345
|
+
return result;
|
|
2346
|
+
} catch (error) {
|
|
2347
|
+
console.error(`Error loading config from ${path}:`, error);
|
|
2348
|
+
return null;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
function deepMerge(target, source) {
|
|
2352
|
+
const result = { ...target };
|
|
2353
|
+
for (const key of Object.keys(source)) {
|
|
2354
|
+
const sourceValue = source[key];
|
|
2355
|
+
const targetValue = result[key];
|
|
2356
|
+
if (sourceValue !== void 0 && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
|
|
2357
|
+
result[key] = deepMerge(
|
|
2358
|
+
targetValue,
|
|
2359
|
+
sourceValue
|
|
2360
|
+
);
|
|
2361
|
+
} else if (sourceValue !== void 0) {
|
|
2362
|
+
result[key] = sourceValue;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return result;
|
|
2366
|
+
}
|
|
2367
|
+
function loadEnvConfig() {
|
|
2368
|
+
const config = {};
|
|
2369
|
+
if (process.env.EXA_API_KEY) {
|
|
2370
|
+
config.mcpServers = {
|
|
2371
|
+
...config.mcpServers,
|
|
2372
|
+
exa: { enabled: true, apiKey: process.env.EXA_API_KEY }
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
if (process.env.OMC_PARALLEL_EXECUTION !== void 0) {
|
|
2376
|
+
config.features = {
|
|
2377
|
+
...config.features,
|
|
2378
|
+
parallelExecution: process.env.OMC_PARALLEL_EXECUTION === "true"
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
if (process.env.OMC_LSP_TOOLS !== void 0) {
|
|
2382
|
+
config.features = {
|
|
2383
|
+
...config.features,
|
|
2384
|
+
lspTools: process.env.OMC_LSP_TOOLS === "true"
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
if (process.env.OMC_MAX_BACKGROUND_TASKS) {
|
|
2388
|
+
const maxTasks = parseInt(process.env.OMC_MAX_BACKGROUND_TASKS, 10);
|
|
2389
|
+
if (!isNaN(maxTasks)) {
|
|
2390
|
+
config.permissions = {
|
|
2391
|
+
...config.permissions,
|
|
2392
|
+
maxBackgroundTasks: maxTasks
|
|
2393
|
+
};
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
if (process.env.OMC_ROUTING_ENABLED !== void 0) {
|
|
2397
|
+
config.routing = {
|
|
2398
|
+
...config.routing,
|
|
2399
|
+
enabled: process.env.OMC_ROUTING_ENABLED === "true"
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
if (process.env.OMC_ROUTING_FORCE_INHERIT !== void 0) {
|
|
2403
|
+
config.routing = {
|
|
2404
|
+
...config.routing,
|
|
2405
|
+
forceInherit: process.env.OMC_ROUTING_FORCE_INHERIT === "true"
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
if (process.env.OMC_ROUTING_DEFAULT_TIER) {
|
|
2409
|
+
const tier = process.env.OMC_ROUTING_DEFAULT_TIER.toUpperCase();
|
|
2410
|
+
if (tier === "LOW" || tier === "MEDIUM" || tier === "HIGH") {
|
|
2411
|
+
config.routing = {
|
|
2412
|
+
...config.routing,
|
|
2413
|
+
defaultTier: tier
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const aliasKeys = ["HAIKU", "SONNET", "OPUS"];
|
|
2418
|
+
const modelAliases = {};
|
|
2419
|
+
for (const key of aliasKeys) {
|
|
2420
|
+
const envVal = process.env[`OMC_MODEL_ALIAS_${key}`];
|
|
2421
|
+
if (envVal) {
|
|
2422
|
+
const lower = key.toLowerCase();
|
|
2423
|
+
modelAliases[lower] = envVal.toLowerCase();
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
if (Object.keys(modelAliases).length > 0) {
|
|
2427
|
+
config.routing = {
|
|
2428
|
+
...config.routing,
|
|
2429
|
+
modelAliases
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
if (process.env.OMC_ESCALATION_ENABLED !== void 0) {
|
|
2433
|
+
config.routing = {
|
|
2434
|
+
...config.routing,
|
|
2435
|
+
escalationEnabled: process.env.OMC_ESCALATION_ENABLED === "true"
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
const externalModelsDefaults = {};
|
|
2439
|
+
if (process.env.OMC_EXTERNAL_MODELS_DEFAULT_PROVIDER) {
|
|
2440
|
+
const provider = process.env.OMC_EXTERNAL_MODELS_DEFAULT_PROVIDER;
|
|
2441
|
+
if (provider === "codex" || provider === "gemini") {
|
|
2442
|
+
externalModelsDefaults.provider = provider;
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
if (process.env.OMC_EXTERNAL_MODELS_DEFAULT_CODEX_MODEL) {
|
|
2446
|
+
externalModelsDefaults.codexModel = process.env.OMC_EXTERNAL_MODELS_DEFAULT_CODEX_MODEL;
|
|
2447
|
+
} else if (process.env.OMC_CODEX_DEFAULT_MODEL) {
|
|
2448
|
+
externalModelsDefaults.codexModel = process.env.OMC_CODEX_DEFAULT_MODEL;
|
|
2449
|
+
}
|
|
2450
|
+
if (process.env.OMC_EXTERNAL_MODELS_DEFAULT_GEMINI_MODEL) {
|
|
2451
|
+
externalModelsDefaults.geminiModel = process.env.OMC_EXTERNAL_MODELS_DEFAULT_GEMINI_MODEL;
|
|
2452
|
+
} else if (process.env.OMC_GEMINI_DEFAULT_MODEL) {
|
|
2453
|
+
externalModelsDefaults.geminiModel = process.env.OMC_GEMINI_DEFAULT_MODEL;
|
|
2454
|
+
}
|
|
2455
|
+
const externalModelsFallback = {
|
|
2456
|
+
onModelFailure: "provider_chain"
|
|
2457
|
+
};
|
|
2458
|
+
if (process.env.OMC_EXTERNAL_MODELS_FALLBACK_POLICY) {
|
|
2459
|
+
const policy = process.env.OMC_EXTERNAL_MODELS_FALLBACK_POLICY;
|
|
2460
|
+
if (policy === "provider_chain" || policy === "cross_provider" || policy === "claude_only") {
|
|
2461
|
+
externalModelsFallback.onModelFailure = policy;
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
if (Object.keys(externalModelsDefaults).length > 0 || externalModelsFallback.onModelFailure !== "provider_chain") {
|
|
2465
|
+
config.externalModels = {
|
|
2466
|
+
defaults: externalModelsDefaults,
|
|
2467
|
+
fallbackPolicy: externalModelsFallback
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
if (process.env.OMC_DELEGATION_ROUTING_ENABLED !== void 0) {
|
|
2471
|
+
config.delegationRouting = {
|
|
2472
|
+
...config.delegationRouting,
|
|
2473
|
+
enabled: process.env.OMC_DELEGATION_ROUTING_ENABLED === "true"
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
if (process.env.OMC_DELEGATION_ROUTING_DEFAULT_PROVIDER) {
|
|
2477
|
+
const provider = process.env.OMC_DELEGATION_ROUTING_DEFAULT_PROVIDER;
|
|
2478
|
+
if (["claude", "codex", "gemini"].includes(provider)) {
|
|
2479
|
+
config.delegationRouting = {
|
|
2480
|
+
...config.delegationRouting,
|
|
2481
|
+
defaultProvider: provider
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return config;
|
|
2486
|
+
}
|
|
2487
|
+
function loadConfig() {
|
|
2488
|
+
const paths = getConfigPaths();
|
|
2489
|
+
let config = { ...DEFAULT_CONFIG };
|
|
2490
|
+
const userConfig = loadJsoncFile(paths.user);
|
|
2491
|
+
if (userConfig) {
|
|
2492
|
+
config = deepMerge(config, userConfig);
|
|
2493
|
+
}
|
|
2494
|
+
const projectConfig = loadJsoncFile(paths.project);
|
|
2495
|
+
if (projectConfig) {
|
|
2496
|
+
config = deepMerge(config, projectConfig);
|
|
2497
|
+
}
|
|
2498
|
+
const envConfig = loadEnvConfig();
|
|
2499
|
+
config = deepMerge(config, envConfig);
|
|
2500
|
+
if (config.routing?.forceInherit !== true && process.env.OMC_ROUTING_FORCE_INHERIT === void 0 && isNonClaudeProvider()) {
|
|
2501
|
+
config.routing = {
|
|
2502
|
+
...config.routing,
|
|
2503
|
+
forceInherit: true
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
return config;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/hooks/factcheck/config.ts
|
|
2510
|
+
var DEFAULT_FACTCHECK_POLICY = {
|
|
2511
|
+
enabled: false,
|
|
2512
|
+
mode: "quick",
|
|
2513
|
+
strict_project_patterns: [],
|
|
2514
|
+
forbidden_path_prefixes: ["${HOME}/.claude/plugins/cache/omc/"],
|
|
2515
|
+
forbidden_path_substrings: ["/.omc/", ".omc-config.json"],
|
|
2516
|
+
readonly_command_prefixes: [
|
|
2517
|
+
"ls ",
|
|
2518
|
+
"cat ",
|
|
2519
|
+
"find ",
|
|
2520
|
+
"grep ",
|
|
2521
|
+
"head ",
|
|
2522
|
+
"tail ",
|
|
2523
|
+
"stat ",
|
|
2524
|
+
"echo ",
|
|
2525
|
+
"wc "
|
|
2526
|
+
],
|
|
2527
|
+
warn_on_cwd_mismatch: true,
|
|
2528
|
+
enforce_cwd_parity_in_quick: false,
|
|
2529
|
+
warn_on_unverified_gates: true,
|
|
2530
|
+
warn_on_unverified_gates_when_no_source_files: false
|
|
2531
|
+
};
|
|
2532
|
+
var DEFAULT_SENTINEL_POLICY = {
|
|
2533
|
+
enabled: false,
|
|
2534
|
+
readiness: {
|
|
2535
|
+
min_pass_rate: 0.6,
|
|
2536
|
+
max_timeout_rate: 0.1,
|
|
2537
|
+
max_warn_plus_fail_rate: 0.4,
|
|
2538
|
+
min_reason_coverage_rate: 0.95
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
var DEFAULT_GUARDS_CONFIG = {
|
|
2542
|
+
factcheck: { ...DEFAULT_FACTCHECK_POLICY },
|
|
2543
|
+
sentinel: { ...DEFAULT_SENTINEL_POLICY }
|
|
2544
|
+
};
|
|
2545
|
+
function expandTokens(value, workspace) {
|
|
2546
|
+
const home = (0, import_os2.homedir)();
|
|
2547
|
+
const ws = workspace ?? process.env.OMC_WORKSPACE ?? process.cwd();
|
|
2548
|
+
return value.replace(/\$\{HOME\}/g, home).replace(/\$\{WORKSPACE\}/g, ws);
|
|
2549
|
+
}
|
|
2550
|
+
function expandTokensDeep(obj, workspace) {
|
|
2551
|
+
if (typeof obj === "string") {
|
|
2552
|
+
return expandTokens(obj, workspace);
|
|
2553
|
+
}
|
|
2554
|
+
if (Array.isArray(obj)) {
|
|
2555
|
+
return obj.map((item) => expandTokensDeep(item, workspace));
|
|
2556
|
+
}
|
|
2557
|
+
if (typeof obj === "object" && obj !== null) {
|
|
2558
|
+
const result = {};
|
|
2559
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2560
|
+
result[key] = expandTokensDeep(value, workspace);
|
|
2561
|
+
}
|
|
2562
|
+
return result;
|
|
2563
|
+
}
|
|
2564
|
+
return obj;
|
|
2565
|
+
}
|
|
2566
|
+
function deepMergeGuards(target, source) {
|
|
2567
|
+
const result = { ...target };
|
|
2568
|
+
if (source.factcheck) {
|
|
2569
|
+
result.factcheck = { ...result.factcheck, ...source.factcheck };
|
|
2570
|
+
}
|
|
2571
|
+
if (source.sentinel) {
|
|
2572
|
+
result.sentinel = {
|
|
2573
|
+
...result.sentinel,
|
|
2574
|
+
...source.sentinel,
|
|
2575
|
+
readiness: {
|
|
2576
|
+
...result.sentinel.readiness,
|
|
2577
|
+
...source.sentinel.readiness ?? {}
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
return result;
|
|
2582
|
+
}
|
|
2583
|
+
function loadGuardsConfig(workspace) {
|
|
2584
|
+
try {
|
|
2585
|
+
const fullConfig = loadConfig();
|
|
2586
|
+
const guardsRaw = fullConfig.guards ?? {};
|
|
2587
|
+
const merged = deepMergeGuards(DEFAULT_GUARDS_CONFIG, guardsRaw);
|
|
2588
|
+
return expandTokensDeep(merged, workspace);
|
|
2589
|
+
} catch {
|
|
2590
|
+
return expandTokensDeep({ ...DEFAULT_GUARDS_CONFIG }, workspace);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/hooks/factcheck/index.ts
|
|
2595
|
+
function severityRank(value) {
|
|
2596
|
+
if (value === "FAIL") return 2;
|
|
2597
|
+
if (value === "WARN") return 1;
|
|
2598
|
+
return 0;
|
|
2599
|
+
}
|
|
2600
|
+
function runChecks(claims, mode, policy, runtimeCwd) {
|
|
2601
|
+
const mismatches = [];
|
|
2602
|
+
const notes = [];
|
|
2603
|
+
const missingFields = checkMissingFields(claims);
|
|
2604
|
+
if (missingFields.length > 0) {
|
|
2605
|
+
mismatches.push({
|
|
2606
|
+
check: "A",
|
|
2607
|
+
severity: "FAIL",
|
|
2608
|
+
detail: `Missing required fields: ${JSON.stringify(missingFields)}`
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
const missingGates = checkMissingGates(claims);
|
|
2612
|
+
if (missingGates.length > 0) {
|
|
2613
|
+
mismatches.push({
|
|
2614
|
+
check: "A",
|
|
2615
|
+
severity: "FAIL",
|
|
2616
|
+
detail: `Missing required gates: ${JSON.stringify(missingGates)}`
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
const falseGates = getFalseGates(claims);
|
|
2620
|
+
const srcFiles = sourceFileCount(claims);
|
|
2621
|
+
if (mode === "strict" && falseGates.length > 0) {
|
|
2622
|
+
mismatches.push({
|
|
2623
|
+
check: "B",
|
|
2624
|
+
severity: "FAIL",
|
|
2625
|
+
detail: `Strict mode requires all gates true, got false: ${JSON.stringify(falseGates)}`
|
|
2626
|
+
});
|
|
2627
|
+
} else if ((mode === "declared" || mode === "manual") && falseGates.length > 0 && policy.warn_on_unverified_gates) {
|
|
2628
|
+
if (srcFiles > 0 || policy.warn_on_unverified_gates_when_no_source_files) {
|
|
2629
|
+
mismatches.push({
|
|
2630
|
+
check: "B",
|
|
2631
|
+
severity: "WARN",
|
|
2632
|
+
detail: `Unverified gates in declared/manual mode: ${JSON.stringify(falseGates)}`
|
|
2633
|
+
});
|
|
2634
|
+
} else {
|
|
2635
|
+
notes.push("No source files declared; unverified gates are ignored by policy");
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
mismatches.push(...checkPaths(claims, policy));
|
|
2639
|
+
mismatches.push(...checkCommands(claims, policy));
|
|
2640
|
+
const claimsCwd = String(claims.cwd ?? "").trim();
|
|
2641
|
+
const cwdMismatch = checkCwdParity(
|
|
2642
|
+
claimsCwd,
|
|
2643
|
+
runtimeCwd ?? process.cwd(),
|
|
2644
|
+
mode,
|
|
2645
|
+
policy
|
|
2646
|
+
);
|
|
2647
|
+
if (cwdMismatch) {
|
|
2648
|
+
mismatches.push(cwdMismatch);
|
|
2649
|
+
}
|
|
2650
|
+
const maxRank = mismatches.reduce(
|
|
2651
|
+
(max, m) => Math.max(max, severityRank(m.severity)),
|
|
2652
|
+
0
|
|
2653
|
+
);
|
|
2654
|
+
let verdict = "PASS";
|
|
2655
|
+
if (maxRank === 2) verdict = "FAIL";
|
|
2656
|
+
else if (maxRank === 1) verdict = "WARN";
|
|
2657
|
+
return {
|
|
2658
|
+
verdict,
|
|
2659
|
+
mode,
|
|
2660
|
+
mismatches,
|
|
2661
|
+
notes,
|
|
2662
|
+
claims_evidence: {
|
|
2663
|
+
source_files: srcFiles,
|
|
2664
|
+
commands_count: (claims.commands_executed ?? []).length,
|
|
2665
|
+
models_count: (claims.models_used ?? []).length
|
|
2666
|
+
}
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
function runFactcheck(claims, options) {
|
|
2670
|
+
const config = loadGuardsConfig(options?.workspace);
|
|
2671
|
+
const mode = options?.mode ?? config.factcheck.mode;
|
|
2672
|
+
return runChecks(claims, mode, config.factcheck, options?.runtimeCwd);
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// src/hooks/factcheck/sentinel.ts
|
|
2676
|
+
var import_fs9 = require("fs");
|
|
2677
|
+
function computeRate(numerator, denominator) {
|
|
2678
|
+
if (denominator === 0) return 0;
|
|
2679
|
+
return numerator / denominator;
|
|
2680
|
+
}
|
|
2681
|
+
function getPassRate(stats) {
|
|
2682
|
+
return computeRate(stats.pass_count, stats.total_runs);
|
|
2683
|
+
}
|
|
2684
|
+
function getTimeoutRate(stats) {
|
|
2685
|
+
return computeRate(stats.timeout_count, stats.total_runs);
|
|
2686
|
+
}
|
|
2687
|
+
function getWarnPlusFailRate(stats) {
|
|
2688
|
+
return computeRate(stats.warn_count + stats.fail_count, stats.total_runs);
|
|
2689
|
+
}
|
|
2690
|
+
function getReasonCoverageRate(stats) {
|
|
2691
|
+
return computeRate(stats.reason_coverage_count, stats.total_runs);
|
|
2692
|
+
}
|
|
2693
|
+
function extractVerdict(entry) {
|
|
2694
|
+
const raw = String(entry.verdict ?? "").toUpperCase().trim();
|
|
2695
|
+
if (raw === "PASS") return "PASS";
|
|
2696
|
+
if (raw === "WARN") return "WARN";
|
|
2697
|
+
return "FAIL";
|
|
2698
|
+
}
|
|
2699
|
+
function hasReason(entry) {
|
|
2700
|
+
return !!(entry.reason || entry.error || entry.message);
|
|
2701
|
+
}
|
|
2702
|
+
function isTimeout(entry) {
|
|
2703
|
+
if (entry.runtime?.timed_out === true) return true;
|
|
2704
|
+
if (entry.runtime?.global_timeout === true) return true;
|
|
2705
|
+
const reason = String(entry.reason ?? "").toLowerCase();
|
|
2706
|
+
return reason.includes("timeout");
|
|
2707
|
+
}
|
|
2708
|
+
function analyzeLog(logPath) {
|
|
2709
|
+
const stats = {
|
|
2710
|
+
total_runs: 0,
|
|
2711
|
+
pass_count: 0,
|
|
2712
|
+
warn_count: 0,
|
|
2713
|
+
fail_count: 0,
|
|
2714
|
+
timeout_count: 0,
|
|
2715
|
+
reason_coverage_count: 0
|
|
2716
|
+
};
|
|
2717
|
+
if (!(0, import_fs9.existsSync)(logPath)) {
|
|
2718
|
+
return stats;
|
|
2719
|
+
}
|
|
2720
|
+
let content;
|
|
2721
|
+
try {
|
|
2722
|
+
content = (0, import_fs9.readFileSync)(logPath, "utf-8");
|
|
2723
|
+
} catch {
|
|
2724
|
+
return stats;
|
|
2725
|
+
}
|
|
2726
|
+
const lines = content.split("\n").filter((line) => line.trim().length > 0);
|
|
2727
|
+
for (const line of lines) {
|
|
2728
|
+
let entry;
|
|
2729
|
+
try {
|
|
2730
|
+
entry = JSON.parse(line);
|
|
2731
|
+
} catch {
|
|
2732
|
+
continue;
|
|
2733
|
+
}
|
|
2734
|
+
stats.total_runs++;
|
|
2735
|
+
const verdict = extractVerdict(entry);
|
|
2736
|
+
if (verdict === "PASS") stats.pass_count++;
|
|
2737
|
+
else if (verdict === "WARN") stats.warn_count++;
|
|
2738
|
+
else stats.fail_count++;
|
|
2739
|
+
if (isTimeout(entry)) stats.timeout_count++;
|
|
2740
|
+
if (hasReason(entry)) stats.reason_coverage_count++;
|
|
2741
|
+
}
|
|
2742
|
+
return stats;
|
|
2743
|
+
}
|
|
2744
|
+
function isUpstreamReady(stats, policy) {
|
|
2745
|
+
const blockers = [];
|
|
2746
|
+
const passRate = getPassRate(stats);
|
|
2747
|
+
if (passRate < policy.min_pass_rate) {
|
|
2748
|
+
blockers.push(
|
|
2749
|
+
`pass_rate ${passRate.toFixed(3)} < min ${policy.min_pass_rate}`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
const timeoutRate = getTimeoutRate(stats);
|
|
2753
|
+
if (timeoutRate > policy.max_timeout_rate) {
|
|
2754
|
+
blockers.push(
|
|
2755
|
+
`timeout_rate ${timeoutRate.toFixed(3)} > max ${policy.max_timeout_rate}`
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
const warnFailRate = getWarnPlusFailRate(stats);
|
|
2759
|
+
if (warnFailRate > policy.max_warn_plus_fail_rate) {
|
|
2760
|
+
blockers.push(
|
|
2761
|
+
`warn_plus_fail_rate ${warnFailRate.toFixed(3)} > max ${policy.max_warn_plus_fail_rate}`
|
|
2762
|
+
);
|
|
2763
|
+
}
|
|
2764
|
+
const reasonRate = getReasonCoverageRate(stats);
|
|
2765
|
+
if (reasonRate < policy.min_reason_coverage_rate) {
|
|
2766
|
+
blockers.push(
|
|
2767
|
+
`reason_coverage_rate ${reasonRate.toFixed(3)} < min ${policy.min_reason_coverage_rate}`
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2770
|
+
return [blockers.length === 0, blockers];
|
|
2771
|
+
}
|
|
2772
|
+
function checkSentinelHealth(logPath, workspace) {
|
|
2773
|
+
const config = loadGuardsConfig(workspace);
|
|
2774
|
+
const stats = analyzeLog(logPath);
|
|
2775
|
+
const [ready, blockers] = isUpstreamReady(stats, config.sentinel.readiness);
|
|
2776
|
+
return { ready, blockers, stats };
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// src/team/sentinel-gate.ts
|
|
2780
|
+
function mapFactcheckToBlockers(result) {
|
|
2781
|
+
if (result.verdict === "PASS") {
|
|
2782
|
+
return [];
|
|
2783
|
+
}
|
|
2784
|
+
if (result.mismatches.length === 0) {
|
|
2785
|
+
return [`[factcheck] verdict ${result.verdict}`];
|
|
2786
|
+
}
|
|
2787
|
+
return result.mismatches.map(
|
|
2788
|
+
(mismatch) => `[factcheck] ${mismatch.severity} ${mismatch.check}: ${mismatch.detail}`
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
function coerceArray(value) {
|
|
2792
|
+
if (Array.isArray(value)) return value;
|
|
2793
|
+
if (value == null) return [];
|
|
2794
|
+
if (typeof value === "object" && !Array.isArray(value)) return [];
|
|
2795
|
+
return [value];
|
|
2796
|
+
}
|
|
2797
|
+
function sanitizeClaims(raw) {
|
|
2798
|
+
const out = { ...raw };
|
|
2799
|
+
const arrayFields = [
|
|
2800
|
+
"files_modified",
|
|
2801
|
+
"files_created",
|
|
2802
|
+
"files_deleted",
|
|
2803
|
+
"artifacts_expected",
|
|
2804
|
+
"commands_executed",
|
|
2805
|
+
"models_used"
|
|
2806
|
+
];
|
|
2807
|
+
for (const field of arrayFields) {
|
|
2808
|
+
if (field in out) {
|
|
2809
|
+
out[field] = coerceArray(out[field]);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return out;
|
|
2813
|
+
}
|
|
2814
|
+
function checkSentinelReadiness(options = {}) {
|
|
2815
|
+
const {
|
|
2816
|
+
logPath,
|
|
2817
|
+
workspace,
|
|
2818
|
+
claims,
|
|
2819
|
+
enabled = loadGuardsConfig(workspace).sentinel.enabled
|
|
2820
|
+
} = options;
|
|
2821
|
+
if (!enabled) {
|
|
2822
|
+
return {
|
|
2823
|
+
ready: true,
|
|
2824
|
+
blockers: [],
|
|
2825
|
+
skipped: true
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
const blockers = [];
|
|
2829
|
+
let ranCheck = false;
|
|
2830
|
+
if (logPath) {
|
|
2831
|
+
ranCheck = true;
|
|
2832
|
+
const health = checkSentinelHealth(logPath, workspace);
|
|
2833
|
+
blockers.push(...health.blockers);
|
|
2834
|
+
}
|
|
2835
|
+
if (claims) {
|
|
2836
|
+
ranCheck = true;
|
|
2837
|
+
try {
|
|
2838
|
+
const sanitized = sanitizeClaims(claims);
|
|
2839
|
+
const factcheck = runFactcheck(sanitized, { workspace });
|
|
2840
|
+
blockers.push(...mapFactcheckToBlockers(factcheck));
|
|
2841
|
+
} catch (err) {
|
|
2842
|
+
blockers.push(
|
|
2843
|
+
`[factcheck] execution error: ${err instanceof Error ? err.message : String(err)}`
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
if (!ranCheck) {
|
|
2848
|
+
return {
|
|
2849
|
+
ready: false,
|
|
2850
|
+
blockers: ["[sentinel] gate enabled but no logPath or claims provided \u2014 cannot verify readiness"],
|
|
2851
|
+
skipped: true
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
const dedupedBlockers = [...new Set(blockers)];
|
|
2855
|
+
return {
|
|
2856
|
+
ready: dedupedBlockers.length === 0,
|
|
2857
|
+
blockers: dedupedBlockers,
|
|
2858
|
+
skipped: false
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
async function waitForSentinelReadiness(options = {}) {
|
|
2862
|
+
const timeoutMs = Math.max(0, options.timeoutMs ?? 3e4);
|
|
2863
|
+
const pollIntervalMs = Math.max(50, options.pollIntervalMs ?? 250);
|
|
2864
|
+
const startedAt = Date.now();
|
|
2865
|
+
let attempts = 1;
|
|
2866
|
+
let latest = checkSentinelReadiness(options);
|
|
2867
|
+
if (latest.ready) {
|
|
2868
|
+
return {
|
|
2869
|
+
...latest,
|
|
2870
|
+
timedOut: false,
|
|
2871
|
+
elapsedMs: Date.now() - startedAt,
|
|
2872
|
+
attempts
|
|
2873
|
+
};
|
|
2874
|
+
}
|
|
2875
|
+
const deadline = startedAt + timeoutMs;
|
|
2876
|
+
while (Date.now() < deadline) {
|
|
2877
|
+
await new Promise((resolve5) => setTimeout(resolve5, pollIntervalMs));
|
|
2878
|
+
attempts += 1;
|
|
2879
|
+
latest = checkSentinelReadiness(options);
|
|
2880
|
+
if (latest.ready) {
|
|
2881
|
+
return {
|
|
2882
|
+
...latest,
|
|
2883
|
+
timedOut: false,
|
|
2884
|
+
elapsedMs: Date.now() - startedAt,
|
|
2885
|
+
attempts
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
const timeoutBlocker = `[sentinel] readiness check timed out after ${timeoutMs}ms`;
|
|
2890
|
+
const blockers = latest.blockers.includes(timeoutBlocker) ? latest.blockers : [...latest.blockers, timeoutBlocker];
|
|
2891
|
+
return {
|
|
2892
|
+
...latest,
|
|
2893
|
+
blockers,
|
|
2894
|
+
timedOut: true,
|
|
2895
|
+
elapsedMs: Date.now() - startedAt,
|
|
2896
|
+
attempts
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
// src/team/runtime-v2.ts
|
|
2901
|
+
var import_path15 = require("path");
|
|
2902
|
+
var import_fs12 = require("fs");
|
|
2903
|
+
var import_promises6 = require("fs/promises");
|
|
2904
|
+
var import_perf_hooks = require("perf_hooks");
|
|
2905
|
+
|
|
2906
|
+
// src/team/monitor.ts
|
|
2907
|
+
var import_fs10 = require("fs");
|
|
2908
|
+
var import_promises4 = require("fs/promises");
|
|
2909
|
+
var import_path13 = require("path");
|
|
2910
|
+
async function readJsonSafe2(filePath) {
|
|
2911
|
+
try {
|
|
2912
|
+
if (!(0, import_fs10.existsSync)(filePath)) return null;
|
|
2913
|
+
const raw = await (0, import_promises4.readFile)(filePath, "utf-8");
|
|
2914
|
+
return JSON.parse(raw);
|
|
2915
|
+
} catch {
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
async function writeAtomic(filePath, data) {
|
|
2920
|
+
const { writeFile: writeFile5 } = await import("fs/promises");
|
|
2921
|
+
await (0, import_promises4.mkdir)((0, import_path13.dirname)(filePath), { recursive: true });
|
|
2922
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
2923
|
+
await writeFile5(tmpPath, data, "utf-8");
|
|
2924
|
+
const { rename: rename3 } = await import("fs/promises");
|
|
2925
|
+
await rename3(tmpPath, filePath);
|
|
2926
|
+
}
|
|
2927
|
+
async function readTeamConfig(teamName, cwd) {
|
|
2928
|
+
return readJsonSafe2(absPath(cwd, TeamPaths.config(teamName)));
|
|
2929
|
+
}
|
|
2930
|
+
async function readWorkerStatus(teamName, workerName2, cwd) {
|
|
2931
|
+
const data = await readJsonSafe2(absPath(cwd, TeamPaths.workerStatus(teamName, workerName2)));
|
|
2932
|
+
return data ?? { state: "unknown", updated_at: "" };
|
|
2933
|
+
}
|
|
2934
|
+
async function readWorkerHeartbeat(teamName, workerName2, cwd) {
|
|
2935
|
+
return readJsonSafe2(absPath(cwd, TeamPaths.heartbeat(teamName, workerName2)));
|
|
2936
|
+
}
|
|
2937
|
+
async function readMonitorSnapshot(teamName, cwd) {
|
|
2938
|
+
const p = absPath(cwd, TeamPaths.monitorSnapshot(teamName));
|
|
2939
|
+
if (!(0, import_fs10.existsSync)(p)) return null;
|
|
2940
|
+
try {
|
|
2941
|
+
const raw = await (0, import_promises4.readFile)(p, "utf-8");
|
|
2942
|
+
const parsed = JSON.parse(raw);
|
|
2943
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
2944
|
+
const monitorTimings = (() => {
|
|
2945
|
+
const candidate = parsed.monitorTimings;
|
|
2946
|
+
if (!candidate || typeof candidate !== "object") return void 0;
|
|
2947
|
+
if (typeof candidate.list_tasks_ms !== "number" || typeof candidate.worker_scan_ms !== "number" || typeof candidate.mailbox_delivery_ms !== "number" || typeof candidate.total_ms !== "number" || typeof candidate.updated_at !== "string") {
|
|
2948
|
+
return void 0;
|
|
2949
|
+
}
|
|
2950
|
+
return candidate;
|
|
2951
|
+
})();
|
|
2952
|
+
return {
|
|
2953
|
+
taskStatusById: parsed.taskStatusById ?? {},
|
|
2954
|
+
workerAliveByName: parsed.workerAliveByName ?? {},
|
|
2955
|
+
workerStateByName: parsed.workerStateByName ?? {},
|
|
2956
|
+
workerTurnCountByName: parsed.workerTurnCountByName ?? {},
|
|
2957
|
+
workerTaskIdByName: parsed.workerTaskIdByName ?? {},
|
|
2958
|
+
mailboxNotifiedByMessageId: parsed.mailboxNotifiedByMessageId ?? {},
|
|
2959
|
+
completedEventTaskIds: parsed.completedEventTaskIds ?? {},
|
|
2960
|
+
monitorTimings
|
|
2961
|
+
};
|
|
2962
|
+
} catch {
|
|
2963
|
+
return null;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
async function writeMonitorSnapshot(teamName, snapshot, cwd) {
|
|
2967
|
+
await writeAtomic(absPath(cwd, TeamPaths.monitorSnapshot(teamName)), JSON.stringify(snapshot, null, 2));
|
|
2968
|
+
}
|
|
2969
|
+
async function writeShutdownRequest(teamName, workerName2, fromWorker, cwd) {
|
|
2970
|
+
const data = {
|
|
2971
|
+
from: fromWorker,
|
|
2972
|
+
requested_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2973
|
+
};
|
|
2974
|
+
await writeAtomic(absPath(cwd, TeamPaths.shutdownRequest(teamName, workerName2)), JSON.stringify(data, null, 2));
|
|
2975
|
+
}
|
|
2976
|
+
async function readShutdownAck(teamName, workerName2, cwd, requestedAfter) {
|
|
2977
|
+
const ack = await readJsonSafe2(
|
|
2978
|
+
absPath(cwd, TeamPaths.shutdownAck(teamName, workerName2))
|
|
2979
|
+
);
|
|
2980
|
+
if (!ack) return null;
|
|
2981
|
+
if (requestedAfter && ack.updated_at) {
|
|
2982
|
+
if (new Date(ack.updated_at).getTime() < new Date(requestedAfter).getTime()) {
|
|
2983
|
+
return null;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return ack;
|
|
2987
|
+
}
|
|
2988
|
+
async function listTasksFromFiles(teamName, cwd) {
|
|
2989
|
+
const tasksDir = absPath(cwd, TeamPaths.tasks(teamName));
|
|
2990
|
+
if (!(0, import_fs10.existsSync)(tasksDir)) return [];
|
|
2991
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
2992
|
+
const entries = await readdir2(tasksDir);
|
|
2993
|
+
const tasks = [];
|
|
2994
|
+
for (const entry of entries) {
|
|
2995
|
+
const match = /^(?:task-)?(\d+)\.json$/.exec(entry);
|
|
2996
|
+
if (!match) continue;
|
|
2997
|
+
const task = await readJsonSafe2(absPath(cwd, `${TeamPaths.tasks(teamName)}/${entry}`));
|
|
2998
|
+
if (task) tasks.push(task);
|
|
2999
|
+
}
|
|
3000
|
+
return tasks.sort((a, b) => Number(a.id) - Number(b.id));
|
|
3001
|
+
}
|
|
3002
|
+
async function writeWorkerInbox(teamName, workerName2, content, cwd) {
|
|
3003
|
+
await writeAtomic(absPath(cwd, TeamPaths.inbox(teamName, workerName2)), content);
|
|
3004
|
+
}
|
|
3005
|
+
async function saveTeamConfig(config, cwd) {
|
|
3006
|
+
await writeAtomic(absPath(cwd, TeamPaths.config(config.name)), JSON.stringify(config, null, 2));
|
|
3007
|
+
}
|
|
3008
|
+
async function cleanupTeamState(teamName, cwd) {
|
|
3009
|
+
const root = absPath(cwd, TeamPaths.root(teamName));
|
|
3010
|
+
const { rm: rm3 } = await import("fs/promises");
|
|
3011
|
+
try {
|
|
3012
|
+
await rm3(root, { recursive: true, force: true });
|
|
3013
|
+
} catch {
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// src/team/events.ts
|
|
3018
|
+
var import_crypto = require("crypto");
|
|
3019
|
+
var import_path14 = require("path");
|
|
3020
|
+
var import_promises5 = require("fs/promises");
|
|
3021
|
+
var import_fs11 = require("fs");
|
|
3022
|
+
async function appendTeamEvent(teamName, event, cwd) {
|
|
3023
|
+
const full = {
|
|
3024
|
+
event_id: (0, import_crypto.randomUUID)(),
|
|
3025
|
+
team: teamName,
|
|
3026
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3027
|
+
...event
|
|
3028
|
+
};
|
|
3029
|
+
const p = absPath(cwd, TeamPaths.events(teamName));
|
|
3030
|
+
await (0, import_promises5.mkdir)((0, import_path14.dirname)(p), { recursive: true });
|
|
3031
|
+
await (0, import_promises5.appendFile)(p, `${JSON.stringify(full)}
|
|
3032
|
+
`, "utf8");
|
|
3033
|
+
return full;
|
|
3034
|
+
}
|
|
3035
|
+
async function emitMonitorDerivedEvents(teamName, tasks, workers, previousSnapshot, cwd) {
|
|
3036
|
+
if (!previousSnapshot) return;
|
|
3037
|
+
const completedEventTaskIds = { ...previousSnapshot.completedEventTaskIds ?? {} };
|
|
3038
|
+
for (const task of tasks) {
|
|
3039
|
+
const prevStatus = previousSnapshot.taskStatusById?.[task.id];
|
|
3040
|
+
if (!prevStatus || prevStatus === task.status) continue;
|
|
3041
|
+
if (task.status === "completed" && !completedEventTaskIds[task.id]) {
|
|
3042
|
+
await appendTeamEvent(teamName, {
|
|
3043
|
+
type: "task_completed",
|
|
3044
|
+
worker: "leader-fixed",
|
|
3045
|
+
task_id: task.id,
|
|
3046
|
+
reason: `status_transition:${prevStatus}->${task.status}`
|
|
3047
|
+
}, cwd).catch(() => {
|
|
3048
|
+
});
|
|
3049
|
+
completedEventTaskIds[task.id] = true;
|
|
3050
|
+
} else if (task.status === "failed") {
|
|
3051
|
+
await appendTeamEvent(teamName, {
|
|
3052
|
+
type: "task_failed",
|
|
3053
|
+
worker: "leader-fixed",
|
|
3054
|
+
task_id: task.id,
|
|
3055
|
+
reason: `status_transition:${prevStatus}->${task.status}`
|
|
3056
|
+
}, cwd).catch(() => {
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
for (const worker of workers) {
|
|
3061
|
+
const prevAlive = previousSnapshot.workerAliveByName?.[worker.name];
|
|
3062
|
+
const prevState = previousSnapshot.workerStateByName?.[worker.name];
|
|
3063
|
+
if (prevAlive === true && !worker.alive) {
|
|
3064
|
+
await appendTeamEvent(teamName, {
|
|
3065
|
+
type: "worker_stopped",
|
|
3066
|
+
worker: worker.name,
|
|
3067
|
+
reason: "pane_exited"
|
|
3068
|
+
}, cwd).catch(() => {
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
if (prevState === "working" && worker.status.state === "idle") {
|
|
3072
|
+
await appendTeamEvent(teamName, {
|
|
3073
|
+
type: "worker_idle",
|
|
3074
|
+
worker: worker.name,
|
|
3075
|
+
reason: `state_transition:${prevState}->${worker.status.state}`
|
|
3076
|
+
}, cwd).catch(() => {
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// src/team/phase-controller.ts
|
|
3083
|
+
function inferPhase(tasks) {
|
|
3084
|
+
if (tasks.length === 0) return "initializing";
|
|
3085
|
+
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
3086
|
+
const pending = tasks.filter((t) => t.status === "pending");
|
|
3087
|
+
const permanentlyFailed = tasks.filter(
|
|
3088
|
+
(t) => t.status === "completed" && t.metadata?.permanentlyFailed === true
|
|
3089
|
+
);
|
|
3090
|
+
const genuinelyCompleted = tasks.filter(
|
|
3091
|
+
(t) => t.status === "completed" && !t.metadata?.permanentlyFailed
|
|
3092
|
+
);
|
|
3093
|
+
const explicitlyFailed = tasks.filter((t) => t.status === "failed");
|
|
3094
|
+
const allFailed = [...permanentlyFailed, ...explicitlyFailed];
|
|
3095
|
+
if (inProgress.length > 0) return "executing";
|
|
3096
|
+
if (pending.length === tasks.length && genuinelyCompleted.length === 0 && allFailed.length === 0) {
|
|
3097
|
+
return "planning";
|
|
3098
|
+
}
|
|
3099
|
+
if (pending.length > 0 && genuinelyCompleted.length > 0 && inProgress.length === 0) {
|
|
3100
|
+
return "executing";
|
|
3101
|
+
}
|
|
3102
|
+
if (allFailed.length > 0) {
|
|
3103
|
+
const hasRetriesRemaining = allFailed.some((t) => {
|
|
3104
|
+
const retryCount = t.metadata?.retryCount ?? 0;
|
|
3105
|
+
const maxRetries = t.metadata?.maxRetries ?? 3;
|
|
3106
|
+
return retryCount < maxRetries;
|
|
3107
|
+
});
|
|
3108
|
+
if (allFailed.length === tasks.length && !hasRetriesRemaining || pending.length === 0 && inProgress.length === 0 && genuinelyCompleted.length === 0 && !hasRetriesRemaining) {
|
|
3109
|
+
return "failed";
|
|
3110
|
+
}
|
|
3111
|
+
if (hasRetriesRemaining) return "fixing";
|
|
3112
|
+
}
|
|
3113
|
+
if (genuinelyCompleted.length === tasks.length && allFailed.length === 0) {
|
|
3114
|
+
return "completed";
|
|
3115
|
+
}
|
|
3116
|
+
return "executing";
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
// src/team/runtime-v2.ts
|
|
3120
|
+
init_team_name();
|
|
3121
|
+
init_tmux_session();
|
|
3122
|
+
function isRuntimeV2Enabled(env = process.env) {
|
|
3123
|
+
const raw = env.OMC_RUNTIME_V2;
|
|
3124
|
+
if (!raw) return true;
|
|
3125
|
+
const normalized = raw.trim().toLowerCase();
|
|
3126
|
+
return !["0", "false", "no", "off"].includes(normalized);
|
|
3127
|
+
}
|
|
3128
|
+
function sanitizeTeamName(name) {
|
|
3129
|
+
return name.replace(/[^a-z0-9-]/g, "").slice(0, 30);
|
|
3130
|
+
}
|
|
3131
|
+
async function isWorkerPaneAlive(paneId) {
|
|
3132
|
+
if (!paneId) return false;
|
|
3133
|
+
try {
|
|
3134
|
+
const { isWorkerAlive: isWorkerAlive2 } = await Promise.resolve().then(() => (init_tmux_session(), tmux_session_exports));
|
|
3135
|
+
return await isWorkerAlive2(paneId);
|
|
3136
|
+
} catch {
|
|
3137
|
+
return false;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
function buildV2TaskInstruction(teamName, workerName2, task, taskId) {
|
|
3141
|
+
return [
|
|
3142
|
+
`## Initial Task Assignment`,
|
|
3143
|
+
`Task ID: ${taskId}`,
|
|
3144
|
+
`Worker: ${workerName2}`,
|
|
3145
|
+
`Subject: ${task.subject}`,
|
|
3146
|
+
``,
|
|
3147
|
+
task.description,
|
|
3148
|
+
``,
|
|
3149
|
+
`## Task Lifecycle (CLI API)`,
|
|
3150
|
+
`1. Claim your task:`,
|
|
3151
|
+
` omc team api claim-task --input '{"team_name":"${teamName}","task_id":"${taskId}","worker":"${workerName2}"}' --json`,
|
|
3152
|
+
`2. Do the work described above`,
|
|
3153
|
+
`3. On completion (use the claim_token from step 1):`,
|
|
3154
|
+
` omc team api transition-task-status --input '{"team_name":"${teamName}","task_id":"${taskId}","from":"in_progress","to":"completed","claim_token":"<claim_token>"}' --json`,
|
|
3155
|
+
`4. On failure (use the claim_token from step 1):`,
|
|
3156
|
+
` omc team api transition-task-status --input '{"team_name":"${teamName}","task_id":"${taskId}","from":"in_progress","to":"failed","claim_token":"<claim_token>"}' --json`,
|
|
3157
|
+
``,
|
|
3158
|
+
`IMPORTANT: Use the CLI API commands above for all task state transitions.`,
|
|
3159
|
+
`Do NOT write done.json or edit task files directly.`,
|
|
3160
|
+
`After completing or failing the task, exit immediately.`
|
|
3161
|
+
].join("\n");
|
|
3162
|
+
}
|
|
3163
|
+
async function notifyPaneWithRetry2(sessionName2, paneId, message, maxAttempts = 6, retryDelayMs = 350) {
|
|
3164
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3165
|
+
if (await sendToWorker(sessionName2, paneId, message)) {
|
|
3166
|
+
return true;
|
|
3167
|
+
}
|
|
3168
|
+
if (attempt < maxAttempts) {
|
|
3169
|
+
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return false;
|
|
3173
|
+
}
|
|
3174
|
+
async function spawnV2Worker(opts) {
|
|
3175
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
3176
|
+
const { promisify: promisify2 } = await import("util");
|
|
3177
|
+
const execFileAsync = promisify2(execFile2);
|
|
3178
|
+
const splitTarget = opts.existingWorkerPaneIds.length === 0 ? opts.leaderPaneId : opts.existingWorkerPaneIds[opts.existingWorkerPaneIds.length - 1];
|
|
3179
|
+
const splitType = opts.existingWorkerPaneIds.length === 0 ? "-h" : "-v";
|
|
3180
|
+
const splitResult = await execFileAsync("tmux", [
|
|
3181
|
+
"split-window",
|
|
3182
|
+
splitType,
|
|
3183
|
+
"-t",
|
|
3184
|
+
splitTarget,
|
|
3185
|
+
"-d",
|
|
3186
|
+
"-P",
|
|
3187
|
+
"-F",
|
|
3188
|
+
"#{pane_id}",
|
|
3189
|
+
"-c",
|
|
3190
|
+
opts.cwd
|
|
3191
|
+
]);
|
|
3192
|
+
const paneId = splitResult.stdout.split("\n")[0]?.trim();
|
|
3193
|
+
if (!paneId) return null;
|
|
3194
|
+
const usePromptMode = isPromptModeAgent(opts.agentType);
|
|
3195
|
+
const instruction = buildV2TaskInstruction(
|
|
3196
|
+
opts.teamName,
|
|
3197
|
+
opts.workerName,
|
|
3198
|
+
opts.task,
|
|
3199
|
+
opts.taskId
|
|
3200
|
+
);
|
|
3201
|
+
await composeInitialInbox(opts.teamName, opts.workerName, instruction, opts.cwd);
|
|
3202
|
+
const relInboxPath = `.omc/state/team/${opts.teamName}/workers/${opts.workerName}/inbox.md`;
|
|
3203
|
+
const envVars = getWorkerEnv(opts.teamName, opts.workerName, opts.agentType);
|
|
3204
|
+
const resolvedBinaryPath = opts.resolvedBinaryPaths[opts.agentType] ?? resolveValidatedBinaryPath(opts.agentType);
|
|
3205
|
+
const modelForAgent = (() => {
|
|
3206
|
+
if (opts.agentType === "codex") {
|
|
3207
|
+
return process.env.OMC_EXTERNAL_MODELS_DEFAULT_CODEX_MODEL || process.env.OMC_CODEX_DEFAULT_MODEL || void 0;
|
|
3208
|
+
}
|
|
3209
|
+
if (opts.agentType === "gemini") {
|
|
3210
|
+
return process.env.OMC_EXTERNAL_MODELS_DEFAULT_GEMINI_MODEL || process.env.OMC_GEMINI_DEFAULT_MODEL || void 0;
|
|
3211
|
+
}
|
|
3212
|
+
return void 0;
|
|
3213
|
+
})();
|
|
3214
|
+
const [launchBinary, ...launchArgs] = buildWorkerArgv(opts.agentType, {
|
|
3215
|
+
teamName: opts.teamName,
|
|
3216
|
+
workerName: opts.workerName,
|
|
3217
|
+
cwd: opts.cwd,
|
|
3218
|
+
resolvedBinaryPath,
|
|
3219
|
+
model: modelForAgent
|
|
3220
|
+
});
|
|
3221
|
+
if (usePromptMode) {
|
|
3222
|
+
const promptArgs = getPromptModeArgs(
|
|
3223
|
+
opts.agentType,
|
|
3224
|
+
`Read and execute your task from: ${relInboxPath}`
|
|
3225
|
+
);
|
|
3226
|
+
launchArgs.push(...promptArgs);
|
|
3227
|
+
}
|
|
3228
|
+
const paneConfig = {
|
|
3229
|
+
teamName: opts.teamName,
|
|
3230
|
+
workerName: opts.workerName,
|
|
3231
|
+
envVars,
|
|
3232
|
+
launchBinary,
|
|
3233
|
+
launchArgs,
|
|
3234
|
+
cwd: opts.cwd
|
|
3235
|
+
};
|
|
3236
|
+
await spawnWorkerInPane(opts.sessionName, paneId, paneConfig);
|
|
3237
|
+
try {
|
|
3238
|
+
await execFileAsync("tmux", [
|
|
3239
|
+
"select-layout",
|
|
3240
|
+
"-t",
|
|
3241
|
+
opts.sessionName,
|
|
3242
|
+
"main-vertical"
|
|
3243
|
+
]);
|
|
3244
|
+
} catch {
|
|
3245
|
+
}
|
|
3246
|
+
if (!usePromptMode) {
|
|
3247
|
+
const paneReady = await waitForPaneReady(paneId);
|
|
3248
|
+
if (!paneReady) {
|
|
3249
|
+
try {
|
|
3250
|
+
await execFileAsync("tmux", ["kill-pane", "-t", paneId]);
|
|
3251
|
+
} catch {
|
|
3252
|
+
}
|
|
3253
|
+
return null;
|
|
3254
|
+
}
|
|
3255
|
+
if (opts.agentType === "gemini") {
|
|
3256
|
+
const confirmed = await notifyPaneWithRetry2(opts.sessionName, paneId, "1");
|
|
3257
|
+
if (!confirmed) {
|
|
3258
|
+
try {
|
|
3259
|
+
await execFileAsync("tmux", ["kill-pane", "-t", paneId]);
|
|
3260
|
+
} catch {
|
|
3261
|
+
}
|
|
3262
|
+
return null;
|
|
3263
|
+
}
|
|
3264
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
3265
|
+
}
|
|
3266
|
+
const notified = await notifyPaneWithRetry2(
|
|
3267
|
+
opts.sessionName,
|
|
3268
|
+
paneId,
|
|
3269
|
+
`Read and execute your task from: ${relInboxPath}`
|
|
3270
|
+
);
|
|
3271
|
+
if (!notified) {
|
|
3272
|
+
try {
|
|
3273
|
+
await execFileAsync("tmux", ["kill-pane", "-t", paneId]);
|
|
3274
|
+
} catch {
|
|
3275
|
+
}
|
|
3276
|
+
return null;
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
return paneId;
|
|
3280
|
+
}
|
|
3281
|
+
async function startTeamV2(config) {
|
|
3282
|
+
const sanitized = sanitizeTeamName(config.teamName);
|
|
3283
|
+
const leaderCwd = (0, import_path15.resolve)(config.cwd);
|
|
3284
|
+
validateTeamName(sanitized);
|
|
3285
|
+
const agentTypes = config.agentTypes;
|
|
3286
|
+
const resolvedBinaryPaths = {};
|
|
3287
|
+
for (const agentType of [...new Set(agentTypes)]) {
|
|
3288
|
+
resolvedBinaryPaths[agentType] = resolveValidatedBinaryPath(agentType);
|
|
3289
|
+
}
|
|
3290
|
+
await (0, import_promises6.mkdir)(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
3291
|
+
await (0, import_promises6.mkdir)(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
|
|
3292
|
+
await (0, import_promises6.mkdir)((0, import_path15.join)(leaderCwd, ".omc", "state", "team", sanitized, "mailbox"), { recursive: true });
|
|
3293
|
+
for (let i = 0; i < config.tasks.length; i++) {
|
|
3294
|
+
const taskId = String(i + 1);
|
|
3295
|
+
const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
|
|
3296
|
+
await (0, import_promises6.mkdir)((0, import_path15.join)(taskFilePath, ".."), { recursive: true });
|
|
3297
|
+
await (0, import_promises6.writeFile)(taskFilePath, JSON.stringify({
|
|
3298
|
+
id: taskId,
|
|
3299
|
+
subject: config.tasks[i].subject,
|
|
3300
|
+
description: config.tasks[i].description,
|
|
3301
|
+
status: "pending",
|
|
3302
|
+
owner: null,
|
|
3303
|
+
result: null,
|
|
3304
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3305
|
+
}, null, 2), "utf-8");
|
|
3306
|
+
}
|
|
3307
|
+
const workerNames = [];
|
|
3308
|
+
for (let i = 0; i < config.tasks.length; i++) {
|
|
3309
|
+
const wName = `worker-${i + 1}`;
|
|
3310
|
+
workerNames.push(wName);
|
|
3311
|
+
const agentType = agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? "claude";
|
|
3312
|
+
await ensureWorkerStateDir(sanitized, wName, leaderCwd);
|
|
3313
|
+
await writeWorkerOverlay({
|
|
3314
|
+
teamName: sanitized,
|
|
3315
|
+
workerName: wName,
|
|
3316
|
+
agentType,
|
|
3317
|
+
tasks: config.tasks.map((t, idx) => ({
|
|
3318
|
+
id: String(idx + 1),
|
|
3319
|
+
subject: t.subject,
|
|
3320
|
+
description: t.description
|
|
3321
|
+
})),
|
|
3322
|
+
cwd: leaderCwd
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
const session = await createTeamSession(sanitized, 0, leaderCwd);
|
|
3326
|
+
const sessionName2 = session.sessionName;
|
|
3327
|
+
const leaderPaneId = session.leaderPaneId;
|
|
3328
|
+
const workerPaneIds = [];
|
|
3329
|
+
const workersInfo = workerNames.map((wName, i) => ({
|
|
3330
|
+
name: wName,
|
|
3331
|
+
index: i + 1,
|
|
3332
|
+
role: agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? "claude",
|
|
3333
|
+
assigned_tasks: [],
|
|
3334
|
+
working_dir: leaderCwd
|
|
3335
|
+
}));
|
|
3336
|
+
const teamConfig = {
|
|
3337
|
+
name: sanitized,
|
|
3338
|
+
task: config.tasks.map((t) => t.subject).join("; "),
|
|
3339
|
+
agent_type: agentTypes[0] || "claude",
|
|
3340
|
+
worker_launch_mode: "interactive",
|
|
3341
|
+
worker_count: config.workerCount,
|
|
3342
|
+
max_workers: 20,
|
|
3343
|
+
workers: workersInfo,
|
|
3344
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3345
|
+
tmux_session: sessionName2,
|
|
3346
|
+
next_task_id: config.tasks.length + 1,
|
|
3347
|
+
leader_cwd: leaderCwd,
|
|
3348
|
+
team_state_root: teamStateRoot(leaderCwd, sanitized),
|
|
3349
|
+
leader_pane_id: leaderPaneId,
|
|
3350
|
+
hud_pane_id: null,
|
|
3351
|
+
resize_hook_name: null,
|
|
3352
|
+
resize_hook_target: null
|
|
3353
|
+
};
|
|
3354
|
+
await saveTeamConfig(teamConfig, leaderCwd);
|
|
3355
|
+
const maxConcurrent = Math.min(agentTypes.length, config.tasks.length);
|
|
3356
|
+
for (let i = 0; i < maxConcurrent; i++) {
|
|
3357
|
+
const wName = workerNames[i];
|
|
3358
|
+
const taskId = String(i + 1);
|
|
3359
|
+
const task = config.tasks[i];
|
|
3360
|
+
if (!task) break;
|
|
3361
|
+
const paneId = await spawnV2Worker({
|
|
3362
|
+
sessionName: sessionName2,
|
|
3363
|
+
leaderPaneId,
|
|
3364
|
+
existingWorkerPaneIds: workerPaneIds,
|
|
3365
|
+
teamName: sanitized,
|
|
3366
|
+
workerName: wName,
|
|
3367
|
+
workerIndex: i,
|
|
3368
|
+
agentType: agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? "claude",
|
|
3369
|
+
task,
|
|
3370
|
+
taskId,
|
|
3371
|
+
cwd: leaderCwd,
|
|
3372
|
+
resolvedBinaryPaths
|
|
3373
|
+
});
|
|
3374
|
+
if (paneId) {
|
|
3375
|
+
workerPaneIds.push(paneId);
|
|
3376
|
+
const workerInfo = workersInfo[i];
|
|
3377
|
+
if (workerInfo) {
|
|
3378
|
+
workerInfo.pane_id = paneId;
|
|
3379
|
+
workerInfo.assigned_tasks = [taskId];
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
teamConfig.workers = workersInfo;
|
|
3384
|
+
await saveTeamConfig(teamConfig, leaderCwd);
|
|
3385
|
+
await appendTeamEvent(sanitized, {
|
|
3386
|
+
type: "team_leader_nudge",
|
|
3387
|
+
worker: "leader-fixed",
|
|
3388
|
+
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
|
|
3389
|
+
}, leaderCwd);
|
|
3390
|
+
return {
|
|
3391
|
+
teamName: sanitized,
|
|
3392
|
+
sanitizedName: sanitized,
|
|
3393
|
+
sessionName: sessionName2,
|
|
3394
|
+
config: teamConfig,
|
|
3395
|
+
cwd: leaderCwd
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
async function monitorTeamV2(teamName, cwd) {
|
|
3399
|
+
const monitorStartMs = import_perf_hooks.performance.now();
|
|
3400
|
+
const sanitized = sanitizeTeamName(teamName);
|
|
3401
|
+
const config = await readTeamConfig(sanitized, cwd);
|
|
3402
|
+
if (!config) return null;
|
|
3403
|
+
const previousSnapshot = await readMonitorSnapshot(sanitized, cwd);
|
|
3404
|
+
const listTasksStartMs = import_perf_hooks.performance.now();
|
|
3405
|
+
const allTasks = await listTasksFromFiles(sanitized, cwd);
|
|
3406
|
+
const listTasksMs = import_perf_hooks.performance.now() - listTasksStartMs;
|
|
3407
|
+
const taskById = new Map(allTasks.map((task) => [task.id, task]));
|
|
3408
|
+
const inProgressByOwner = /* @__PURE__ */ new Map();
|
|
3409
|
+
for (const task of allTasks) {
|
|
3410
|
+
if (task.status !== "in_progress" || !task.owner) continue;
|
|
3411
|
+
const existing = inProgressByOwner.get(task.owner) || [];
|
|
3412
|
+
existing.push(task);
|
|
3413
|
+
inProgressByOwner.set(task.owner, existing);
|
|
3414
|
+
}
|
|
3415
|
+
const workers = [];
|
|
3416
|
+
const deadWorkers = [];
|
|
3417
|
+
const nonReportingWorkers = [];
|
|
3418
|
+
const recommendations = [];
|
|
3419
|
+
const workerScanStartMs = import_perf_hooks.performance.now();
|
|
3420
|
+
const workerSignals = await Promise.all(
|
|
3421
|
+
config.workers.map(async (worker) => {
|
|
3422
|
+
const alive = await isWorkerPaneAlive(worker.pane_id);
|
|
3423
|
+
const [status, heartbeat] = await Promise.all([
|
|
3424
|
+
readWorkerStatus(sanitized, worker.name, cwd),
|
|
3425
|
+
readWorkerHeartbeat(sanitized, worker.name, cwd)
|
|
3426
|
+
]);
|
|
3427
|
+
return { worker, alive, status, heartbeat };
|
|
3428
|
+
})
|
|
3429
|
+
);
|
|
3430
|
+
const workerScanMs = import_perf_hooks.performance.now() - workerScanStartMs;
|
|
3431
|
+
for (const { worker: w, alive, status, heartbeat } of workerSignals) {
|
|
3432
|
+
const currentTask = status.current_task_id ? taskById.get(status.current_task_id) ?? null : null;
|
|
3433
|
+
const previousTurns = previousSnapshot ? previousSnapshot.workerTurnCountByName[w.name] ?? 0 : null;
|
|
3434
|
+
const previousTaskId = previousSnapshot?.workerTaskIdByName[w.name] ?? "";
|
|
3435
|
+
const currentTaskId = status.current_task_id ?? "";
|
|
3436
|
+
const turnsWithoutProgress = heartbeat && previousTurns !== null && status.state === "working" && currentTask && (currentTask.status === "pending" || currentTask.status === "in_progress") && currentTaskId !== "" && previousTaskId === currentTaskId ? Math.max(0, heartbeat.turn_count - previousTurns) : 0;
|
|
3437
|
+
workers.push({
|
|
3438
|
+
name: w.name,
|
|
3439
|
+
alive,
|
|
3440
|
+
status,
|
|
3441
|
+
heartbeat,
|
|
3442
|
+
assignedTasks: w.assigned_tasks,
|
|
3443
|
+
turnsWithoutProgress
|
|
3444
|
+
});
|
|
3445
|
+
if (!alive) {
|
|
3446
|
+
deadWorkers.push(w.name);
|
|
3447
|
+
const deadWorkerTasks = inProgressByOwner.get(w.name) || [];
|
|
3448
|
+
for (const t of deadWorkerTasks) {
|
|
3449
|
+
recommendations.push(`Reassign task-${t.id} from dead ${w.name}`);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
if (alive && turnsWithoutProgress > 5) {
|
|
3453
|
+
nonReportingWorkers.push(w.name);
|
|
3454
|
+
recommendations.push(`Send reminder to non-reporting ${w.name}`);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
const taskCounts = {
|
|
3458
|
+
total: allTasks.length,
|
|
3459
|
+
pending: allTasks.filter((t) => t.status === "pending").length,
|
|
3460
|
+
blocked: allTasks.filter((t) => t.status === "blocked").length,
|
|
3461
|
+
in_progress: allTasks.filter((t) => t.status === "in_progress").length,
|
|
3462
|
+
completed: allTasks.filter((t) => t.status === "completed").length,
|
|
3463
|
+
failed: allTasks.filter((t) => t.status === "failed").length
|
|
3464
|
+
};
|
|
3465
|
+
const allTasksTerminal2 = taskCounts.pending === 0 && taskCounts.blocked === 0 && taskCounts.in_progress === 0;
|
|
3466
|
+
const phase = inferPhase(allTasks.map((t) => ({
|
|
3467
|
+
status: t.status,
|
|
3468
|
+
metadata: void 0
|
|
3469
|
+
})));
|
|
3470
|
+
await emitMonitorDerivedEvents(
|
|
3471
|
+
sanitized,
|
|
3472
|
+
allTasks,
|
|
3473
|
+
workers.map((w) => ({ name: w.name, alive: w.alive, status: w.status })),
|
|
3474
|
+
previousSnapshot,
|
|
3475
|
+
cwd
|
|
3476
|
+
);
|
|
3477
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3478
|
+
const totalMs = import_perf_hooks.performance.now() - monitorStartMs;
|
|
3479
|
+
await writeMonitorSnapshot(sanitized, {
|
|
3480
|
+
taskStatusById: Object.fromEntries(allTasks.map((t) => [t.id, t.status])),
|
|
3481
|
+
workerAliveByName: Object.fromEntries(workers.map((w) => [w.name, w.alive])),
|
|
3482
|
+
workerStateByName: Object.fromEntries(workers.map((w) => [w.name, w.status.state])),
|
|
3483
|
+
workerTurnCountByName: Object.fromEntries(workers.map((w) => [w.name, w.heartbeat?.turn_count ?? 0])),
|
|
3484
|
+
workerTaskIdByName: Object.fromEntries(workers.map((w) => [w.name, w.status.current_task_id ?? ""])),
|
|
3485
|
+
mailboxNotifiedByMessageId: previousSnapshot?.mailboxNotifiedByMessageId ?? {},
|
|
3486
|
+
completedEventTaskIds: previousSnapshot?.completedEventTaskIds ?? {},
|
|
3487
|
+
monitorTimings: {
|
|
3488
|
+
list_tasks_ms: Number(listTasksMs.toFixed(2)),
|
|
3489
|
+
worker_scan_ms: Number(workerScanMs.toFixed(2)),
|
|
3490
|
+
mailbox_delivery_ms: 0,
|
|
3491
|
+
total_ms: Number(totalMs.toFixed(2)),
|
|
3492
|
+
updated_at: updatedAt
|
|
3493
|
+
}
|
|
3494
|
+
}, cwd);
|
|
3495
|
+
return {
|
|
3496
|
+
teamName: sanitized,
|
|
3497
|
+
phase,
|
|
3498
|
+
workers,
|
|
3499
|
+
tasks: {
|
|
3500
|
+
...taskCounts,
|
|
3501
|
+
items: allTasks
|
|
3502
|
+
},
|
|
3503
|
+
allTasksTerminal: allTasksTerminal2,
|
|
3504
|
+
deadWorkers,
|
|
3505
|
+
nonReportingWorkers,
|
|
3506
|
+
recommendations,
|
|
3507
|
+
performance: {
|
|
3508
|
+
list_tasks_ms: Number(listTasksMs.toFixed(2)),
|
|
3509
|
+
worker_scan_ms: Number(workerScanMs.toFixed(2)),
|
|
3510
|
+
total_ms: Number(totalMs.toFixed(2)),
|
|
3511
|
+
updated_at: updatedAt
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
3516
|
+
const force = options.force === true;
|
|
3517
|
+
const ralph = options.ralph === true;
|
|
3518
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
3519
|
+
const sanitized = sanitizeTeamName(teamName);
|
|
3520
|
+
const config = await readTeamConfig(sanitized, cwd);
|
|
3521
|
+
if (!config) {
|
|
3522
|
+
await cleanupTeamState(sanitized, cwd);
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
if (!force) {
|
|
3526
|
+
const allTasks = await listTasksFromFiles(sanitized, cwd);
|
|
3527
|
+
const gate = {
|
|
3528
|
+
total: allTasks.length,
|
|
3529
|
+
pending: allTasks.filter((t) => t.status === "pending").length,
|
|
3530
|
+
blocked: allTasks.filter((t) => t.status === "blocked").length,
|
|
3531
|
+
in_progress: allTasks.filter((t) => t.status === "in_progress").length,
|
|
3532
|
+
completed: allTasks.filter((t) => t.status === "completed").length,
|
|
3533
|
+
failed: allTasks.filter((t) => t.status === "failed").length,
|
|
3534
|
+
allowed: false
|
|
3535
|
+
};
|
|
3536
|
+
gate.allowed = gate.pending === 0 && gate.blocked === 0 && gate.in_progress === 0 && gate.failed === 0;
|
|
3537
|
+
await appendTeamEvent(sanitized, {
|
|
3538
|
+
type: "shutdown_gate",
|
|
3539
|
+
worker: "leader-fixed",
|
|
3540
|
+
reason: `allowed=${gate.allowed} total=${gate.total} pending=${gate.pending} blocked=${gate.blocked} in_progress=${gate.in_progress} completed=${gate.completed} failed=${gate.failed}${ralph ? " policy=ralph" : ""}`
|
|
3541
|
+
}, cwd).catch(() => {
|
|
3542
|
+
});
|
|
3543
|
+
if (!gate.allowed) {
|
|
3544
|
+
const hasActiveWork = gate.pending > 0 || gate.blocked > 0 || gate.in_progress > 0;
|
|
3545
|
+
if (ralph && !hasActiveWork) {
|
|
3546
|
+
await appendTeamEvent(sanitized, {
|
|
3547
|
+
type: "team_leader_nudge",
|
|
3548
|
+
worker: "leader-fixed",
|
|
3549
|
+
reason: `gate_bypassed:pending=${gate.pending},blocked=${gate.blocked},in_progress=${gate.in_progress},failed=${gate.failed}`
|
|
3550
|
+
}, cwd).catch(() => {
|
|
3551
|
+
});
|
|
3552
|
+
} else {
|
|
3553
|
+
throw new Error(
|
|
3554
|
+
`shutdown_gate_blocked:pending=${gate.pending},blocked=${gate.blocked},in_progress=${gate.in_progress},failed=${gate.failed}`
|
|
3555
|
+
);
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
if (force) {
|
|
3560
|
+
await appendTeamEvent(sanitized, {
|
|
3561
|
+
type: "shutdown_gate_forced",
|
|
3562
|
+
worker: "leader-fixed",
|
|
3563
|
+
reason: "force_bypass"
|
|
3564
|
+
}, cwd).catch(() => {
|
|
3565
|
+
});
|
|
3566
|
+
}
|
|
3567
|
+
const shutdownRequestTimes = /* @__PURE__ */ new Map();
|
|
3568
|
+
for (const w of config.workers) {
|
|
3569
|
+
try {
|
|
3570
|
+
const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3571
|
+
await writeShutdownRequest(sanitized, w.name, "leader-fixed", cwd);
|
|
3572
|
+
shutdownRequestTimes.set(w.name, requestedAt);
|
|
3573
|
+
const shutdownInbox = `# Shutdown Request
|
|
3574
|
+
|
|
3575
|
+
All tasks are complete. Please wrap up and respond with a shutdown acknowledgement.
|
|
3576
|
+
|
|
3577
|
+
Write your ack to: ${TeamPaths.shutdownAck(sanitized, w.name)}
|
|
3578
|
+
Format: {"status":"accept","reason":"ok","updated_at":"<iso>"}
|
|
3579
|
+
|
|
3580
|
+
Then exit your session.
|
|
3581
|
+
`;
|
|
3582
|
+
await writeWorkerInbox(sanitized, w.name, shutdownInbox, cwd);
|
|
3583
|
+
} catch (err) {
|
|
3584
|
+
process.stderr.write(`[team/runtime-v2] shutdown request failed for ${w.name}: ${err}
|
|
3585
|
+
`);
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
const deadline = Date.now() + timeoutMs;
|
|
3589
|
+
const rejected = [];
|
|
3590
|
+
const ackedWorkers = /* @__PURE__ */ new Set();
|
|
3591
|
+
while (Date.now() < deadline) {
|
|
3592
|
+
for (const w of config.workers) {
|
|
3593
|
+
if (ackedWorkers.has(w.name)) continue;
|
|
3594
|
+
const ack = await readShutdownAck(sanitized, w.name, cwd, shutdownRequestTimes.get(w.name));
|
|
3595
|
+
if (ack) {
|
|
3596
|
+
ackedWorkers.add(w.name);
|
|
3597
|
+
await appendTeamEvent(sanitized, {
|
|
3598
|
+
type: "shutdown_ack",
|
|
3599
|
+
worker: w.name,
|
|
3600
|
+
reason: ack.status === "reject" ? `reject:${ack.reason || "no_reason"}` : "accept"
|
|
3601
|
+
}, cwd).catch(() => {
|
|
3602
|
+
});
|
|
3603
|
+
if (ack.status === "reject") {
|
|
3604
|
+
rejected.push({ worker: w.name, reason: ack.reason || "no_reason" });
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
if (rejected.length > 0 && !force) {
|
|
3609
|
+
const detail = rejected.map((r) => `${r.worker}:${r.reason}`).join(",");
|
|
3610
|
+
throw new Error(`shutdown_rejected:${detail}`);
|
|
3611
|
+
}
|
|
3612
|
+
const allDone = config.workers.every((w) => ackedWorkers.has(w.name));
|
|
3613
|
+
if (allDone) break;
|
|
3614
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
3615
|
+
}
|
|
3616
|
+
try {
|
|
3617
|
+
const { killWorkerPanes: killWorkerPanes2, killTeamSession: killTeamSession2 } = await Promise.resolve().then(() => (init_tmux_session(), tmux_session_exports));
|
|
3618
|
+
const workerPaneIds = config.workers.map((w) => w.pane_id).filter((p) => typeof p === "string" && p.trim().length > 0);
|
|
3619
|
+
await killWorkerPanes2({
|
|
3620
|
+
paneIds: workerPaneIds,
|
|
3621
|
+
leaderPaneId: config.leader_pane_id ?? void 0,
|
|
3622
|
+
teamName: sanitized,
|
|
3623
|
+
cwd
|
|
3624
|
+
});
|
|
3625
|
+
if (config.tmux_session && !config.tmux_session.includes(":")) {
|
|
3626
|
+
await killTeamSession2(config.tmux_session, [], void 0);
|
|
3627
|
+
}
|
|
3628
|
+
} catch (err) {
|
|
3629
|
+
process.stderr.write(`[team/runtime-v2] tmux cleanup: ${err}
|
|
3630
|
+
`);
|
|
3631
|
+
}
|
|
3632
|
+
if (ralph) {
|
|
3633
|
+
const finalTasks = await listTasksFromFiles(sanitized, cwd).catch(() => []);
|
|
3634
|
+
const completed = finalTasks.filter((t) => t.status === "completed").length;
|
|
3635
|
+
const failed = finalTasks.filter((t) => t.status === "failed").length;
|
|
3636
|
+
const pending = finalTasks.filter((t) => t.status === "pending").length;
|
|
3637
|
+
await appendTeamEvent(sanitized, {
|
|
3638
|
+
type: "team_leader_nudge",
|
|
3639
|
+
worker: "leader-fixed",
|
|
3640
|
+
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
|
|
3641
|
+
}, cwd).catch(() => {
|
|
3642
|
+
});
|
|
3643
|
+
}
|
|
3644
|
+
await cleanupTeamState(sanitized, cwd);
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
// src/team/runtime-cli.ts
|
|
3648
|
+
function getTerminalStatus(taskCounts, expectedTaskCount) {
|
|
3649
|
+
const active = taskCounts.pending + taskCounts.inProgress;
|
|
3650
|
+
const terminal = taskCounts.completed + taskCounts.failed;
|
|
3651
|
+
if (active !== 0 || terminal !== expectedTaskCount) return null;
|
|
3652
|
+
return taskCounts.failed > 0 ? "failed" : "completed";
|
|
3653
|
+
}
|
|
3654
|
+
function parseWatchdogFailedAt(marker) {
|
|
3655
|
+
if (typeof marker.failedAt === "number") return marker.failedAt;
|
|
3656
|
+
if (typeof marker.failedAt === "string") {
|
|
3657
|
+
const numeric = Number(marker.failedAt);
|
|
3658
|
+
if (Number.isFinite(numeric)) return numeric;
|
|
3659
|
+
const parsed = Date.parse(marker.failedAt);
|
|
3660
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
3661
|
+
}
|
|
3662
|
+
throw new Error("watchdog marker missing valid failedAt");
|
|
3663
|
+
}
|
|
3664
|
+
async function checkWatchdogFailedMarker(stateRoot2, startTime) {
|
|
3665
|
+
const markerPath = (0, import_path16.join)(stateRoot2, "watchdog-failed.json");
|
|
3666
|
+
let raw;
|
|
3667
|
+
try {
|
|
3668
|
+
raw = await (0, import_promises7.readFile)(markerPath, "utf-8");
|
|
3669
|
+
} catch (err) {
|
|
3670
|
+
const code = err.code;
|
|
3671
|
+
if (code === "ENOENT") return { failed: false };
|
|
3672
|
+
return { failed: true, reason: `Failed to read watchdog marker: ${err}` };
|
|
3673
|
+
}
|
|
3674
|
+
let marker;
|
|
3675
|
+
try {
|
|
3676
|
+
marker = JSON.parse(raw);
|
|
3677
|
+
} catch (err) {
|
|
3678
|
+
return { failed: true, reason: `Failed to parse watchdog marker: ${err}` };
|
|
3679
|
+
}
|
|
3680
|
+
let failedAt;
|
|
3681
|
+
try {
|
|
3682
|
+
failedAt = parseWatchdogFailedAt(marker);
|
|
3683
|
+
} catch (err) {
|
|
3684
|
+
return { failed: true, reason: `Invalid watchdog marker: ${err}` };
|
|
3685
|
+
}
|
|
3686
|
+
if (failedAt >= startTime) {
|
|
3687
|
+
return { failed: true, reason: `Watchdog marked team failed at ${new Date(failedAt).toISOString()}` };
|
|
3688
|
+
}
|
|
3689
|
+
try {
|
|
3690
|
+
await (0, import_promises7.unlink)(markerPath);
|
|
3691
|
+
} catch {
|
|
3692
|
+
}
|
|
3693
|
+
return { failed: false };
|
|
3694
|
+
}
|
|
3695
|
+
async function writeResultArtifact(output, finishedAt, jobId = process.env.OMC_JOB_ID, omcJobsDir = process.env.OMC_JOBS_DIR) {
|
|
3696
|
+
if (!jobId || !omcJobsDir) return;
|
|
3697
|
+
const resultPath = (0, import_path16.join)(omcJobsDir, `${jobId}-result.json`);
|
|
3698
|
+
const tmpPath = `${resultPath}.tmp`;
|
|
3699
|
+
await (0, import_promises7.writeFile)(
|
|
3700
|
+
tmpPath,
|
|
3701
|
+
JSON.stringify({ ...output, finishedAt }),
|
|
3702
|
+
"utf-8"
|
|
3703
|
+
);
|
|
3704
|
+
await (0, import_promises7.rename)(tmpPath, resultPath);
|
|
3705
|
+
}
|
|
3706
|
+
async function writePanesFile(jobId, paneIds, leaderPaneId) {
|
|
3707
|
+
const omcJobsDir = process.env.OMC_JOBS_DIR;
|
|
3708
|
+
if (!jobId || !omcJobsDir) return;
|
|
3709
|
+
const panesPath = (0, import_path16.join)(omcJobsDir, `${jobId}-panes.json`);
|
|
3710
|
+
await (0, import_promises7.writeFile)(
|
|
3711
|
+
panesPath + ".tmp",
|
|
3712
|
+
JSON.stringify({ paneIds: [...paneIds], leaderPaneId })
|
|
3713
|
+
);
|
|
3714
|
+
await (0, import_promises7.rename)(panesPath + ".tmp", panesPath);
|
|
3715
|
+
}
|
|
3716
|
+
function collectTaskResults(stateRoot2) {
|
|
3717
|
+
const tasksDir = (0, import_path16.join)(stateRoot2, "tasks");
|
|
3718
|
+
try {
|
|
3719
|
+
const files = (0, import_fs13.readdirSync)(tasksDir).filter((f) => f.endsWith(".json"));
|
|
3720
|
+
return files.map((f) => {
|
|
3721
|
+
try {
|
|
3722
|
+
const raw = (0, import_fs13.readFileSync)((0, import_path16.join)(tasksDir, f), "utf-8");
|
|
1391
3723
|
const task = JSON.parse(raw);
|
|
1392
3724
|
return {
|
|
1393
3725
|
taskId: task.id ?? f.replace(".json", ""),
|
|
@@ -1432,10 +3764,12 @@ async function main() {
|
|
|
1432
3764
|
agentTypes,
|
|
1433
3765
|
tasks,
|
|
1434
3766
|
cwd,
|
|
1435
|
-
pollIntervalMs = 5e3
|
|
3767
|
+
pollIntervalMs = 5e3,
|
|
3768
|
+
sentinelGateTimeoutMs = 3e4,
|
|
3769
|
+
sentinelGatePollIntervalMs = 250
|
|
1436
3770
|
} = input;
|
|
1437
3771
|
const workerCount = input.workerCount ?? agentTypes.length;
|
|
1438
|
-
const stateRoot2 = (0,
|
|
3772
|
+
const stateRoot2 = (0, import_path16.join)(cwd, `.omc/state/team/${teamName}`);
|
|
1439
3773
|
const config = {
|
|
1440
3774
|
teamName,
|
|
1441
3775
|
workerCount,
|
|
@@ -1443,6 +3777,7 @@ async function main() {
|
|
|
1443
3777
|
tasks,
|
|
1444
3778
|
cwd
|
|
1445
3779
|
};
|
|
3780
|
+
const useV2 = isRuntimeV2Enabled();
|
|
1446
3781
|
let runtime = null;
|
|
1447
3782
|
let finalStatus = "failed";
|
|
1448
3783
|
let pollActive = true;
|
|
@@ -1452,22 +3787,26 @@ async function main() {
|
|
|
1452
3787
|
async function doShutdown(status) {
|
|
1453
3788
|
pollActive = false;
|
|
1454
3789
|
finalStatus = status;
|
|
1455
|
-
if (runtime?.stopWatchdog) {
|
|
3790
|
+
if (!useV2 && runtime?.stopWatchdog) {
|
|
1456
3791
|
runtime.stopWatchdog();
|
|
1457
3792
|
}
|
|
1458
3793
|
const taskResults = collectTaskResults(stateRoot2);
|
|
1459
3794
|
if (runtime) {
|
|
1460
3795
|
try {
|
|
1461
|
-
|
|
1462
|
-
runtime.teamName,
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
3796
|
+
if (useV2) {
|
|
3797
|
+
await shutdownTeamV2(runtime.teamName, runtime.cwd, { force: true });
|
|
3798
|
+
} else {
|
|
3799
|
+
await shutdownTeam(
|
|
3800
|
+
runtime.teamName,
|
|
3801
|
+
runtime.sessionName,
|
|
3802
|
+
runtime.cwd,
|
|
3803
|
+
2e3,
|
|
3804
|
+
runtime.workerPaneIds,
|
|
3805
|
+
runtime.leaderPaneId
|
|
3806
|
+
);
|
|
3807
|
+
}
|
|
1469
3808
|
} catch (err) {
|
|
1470
|
-
process.stderr.write(`[runtime-cli]
|
|
3809
|
+
process.stderr.write(`[runtime-cli] shutdown error: ${err}
|
|
1471
3810
|
`);
|
|
1472
3811
|
}
|
|
1473
3812
|
}
|
|
@@ -1479,6 +3818,13 @@ async function main() {
|
|
|
1479
3818
|
duration,
|
|
1480
3819
|
workerCount
|
|
1481
3820
|
};
|
|
3821
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3822
|
+
try {
|
|
3823
|
+
await writeResultArtifact(output, finishedAt);
|
|
3824
|
+
} catch (err) {
|
|
3825
|
+
process.stderr.write(`[runtime-cli] Failed to persist result artifact: ${err}
|
|
3826
|
+
`);
|
|
3827
|
+
}
|
|
1482
3828
|
process.stdout.write(JSON.stringify(output) + "\n");
|
|
1483
3829
|
process.exit(exitCodeFor(status));
|
|
1484
3830
|
}
|
|
@@ -1491,22 +3837,128 @@ async function main() {
|
|
|
1491
3837
|
doShutdown("failed").catch(() => process.exit(1));
|
|
1492
3838
|
});
|
|
1493
3839
|
try {
|
|
1494
|
-
|
|
3840
|
+
if (useV2) {
|
|
3841
|
+
const v2Runtime = await startTeamV2({
|
|
3842
|
+
teamName,
|
|
3843
|
+
workerCount,
|
|
3844
|
+
agentTypes,
|
|
3845
|
+
tasks,
|
|
3846
|
+
cwd
|
|
3847
|
+
});
|
|
3848
|
+
const v2PaneIds = v2Runtime.config.workers.map((w) => w.pane_id).filter((p) => typeof p === "string");
|
|
3849
|
+
runtime = {
|
|
3850
|
+
teamName: v2Runtime.teamName,
|
|
3851
|
+
sessionName: v2Runtime.sessionName,
|
|
3852
|
+
leaderPaneId: v2Runtime.config.leader_pane_id || "",
|
|
3853
|
+
config,
|
|
3854
|
+
workerNames: v2Runtime.config.workers.map((w) => w.name),
|
|
3855
|
+
workerPaneIds: v2PaneIds,
|
|
3856
|
+
activeWorkers: /* @__PURE__ */ new Map(),
|
|
3857
|
+
cwd
|
|
3858
|
+
};
|
|
3859
|
+
} else {
|
|
3860
|
+
runtime = await startTeam(config);
|
|
3861
|
+
}
|
|
1495
3862
|
} catch (err) {
|
|
1496
3863
|
process.stderr.write(`[runtime-cli] startTeam failed: ${err}
|
|
1497
3864
|
`);
|
|
1498
3865
|
process.exit(1);
|
|
1499
3866
|
}
|
|
1500
3867
|
const jobId = process.env.OMC_JOB_ID;
|
|
3868
|
+
const expectedTaskCount = tasks.length;
|
|
3869
|
+
let mismatchStreak = 0;
|
|
1501
3870
|
try {
|
|
1502
3871
|
await writePanesFile(jobId, runtime.workerPaneIds, runtime.leaderPaneId);
|
|
1503
3872
|
} catch (err) {
|
|
1504
3873
|
process.stderr.write(`[runtime-cli] Failed to persist pane IDs: ${err}
|
|
1505
3874
|
`);
|
|
1506
3875
|
}
|
|
3876
|
+
if (useV2) {
|
|
3877
|
+
process.stderr.write("[runtime-cli] Using runtime v2 (event-driven, no watchdog)\n");
|
|
3878
|
+
while (pollActive) {
|
|
3879
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
3880
|
+
if (!pollActive) break;
|
|
3881
|
+
let snap;
|
|
3882
|
+
try {
|
|
3883
|
+
snap = await monitorTeamV2(teamName, cwd);
|
|
3884
|
+
} catch (err) {
|
|
3885
|
+
process.stderr.write(`[runtime-cli/v2] monitorTeamV2 error: ${err}
|
|
3886
|
+
`);
|
|
3887
|
+
continue;
|
|
3888
|
+
}
|
|
3889
|
+
if (!snap) {
|
|
3890
|
+
process.stderr.write("[runtime-cli/v2] monitorTeamV2 returned null (team config missing?)\n");
|
|
3891
|
+
await doShutdown("failed");
|
|
3892
|
+
return;
|
|
3893
|
+
}
|
|
3894
|
+
try {
|
|
3895
|
+
await writePanesFile(jobId, runtime.workerPaneIds, runtime.leaderPaneId);
|
|
3896
|
+
} catch {
|
|
3897
|
+
}
|
|
3898
|
+
process.stderr.write(
|
|
3899
|
+
`[runtime-cli/v2] phase=${snap.phase} pending=${snap.tasks.pending} in_progress=${snap.tasks.in_progress} completed=${snap.tasks.completed} failed=${snap.tasks.failed} dead=${snap.deadWorkers.length} totalMs=${snap.performance.total_ms}
|
|
3900
|
+
`
|
|
3901
|
+
);
|
|
3902
|
+
const v2Observed = snap.tasks.pending + snap.tasks.in_progress + snap.tasks.completed + snap.tasks.failed;
|
|
3903
|
+
if (v2Observed !== expectedTaskCount) {
|
|
3904
|
+
mismatchStreak += 1;
|
|
3905
|
+
process.stderr.write(
|
|
3906
|
+
`[runtime-cli/v2] Task-count mismatch observed=${v2Observed} expected=${expectedTaskCount} streak=${mismatchStreak}
|
|
3907
|
+
`
|
|
3908
|
+
);
|
|
3909
|
+
if (mismatchStreak >= 2) {
|
|
3910
|
+
process.stderr.write("[runtime-cli/v2] Persistent task-count mismatch \u2014 failing fast\n");
|
|
3911
|
+
await doShutdown("failed");
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
continue;
|
|
3915
|
+
}
|
|
3916
|
+
mismatchStreak = 0;
|
|
3917
|
+
if (snap.allTasksTerminal) {
|
|
3918
|
+
const hasFailures = snap.tasks.failed > 0;
|
|
3919
|
+
if (!hasFailures) {
|
|
3920
|
+
const sentinelLogPath = (0, import_path16.join)(cwd, "sentinel_stop.jsonl");
|
|
3921
|
+
const gateResult = await waitForSentinelReadiness({
|
|
3922
|
+
workspace: cwd,
|
|
3923
|
+
logPath: sentinelLogPath,
|
|
3924
|
+
timeoutMs: sentinelGateTimeoutMs,
|
|
3925
|
+
pollIntervalMs: sentinelGatePollIntervalMs
|
|
3926
|
+
});
|
|
3927
|
+
if (!gateResult.ready) {
|
|
3928
|
+
process.stderr.write(
|
|
3929
|
+
`[runtime-cli/v2] Sentinel gate blocked: ${gateResult.blockers.join("; ")}
|
|
3930
|
+
`
|
|
3931
|
+
);
|
|
3932
|
+
await doShutdown("failed");
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
await doShutdown("completed");
|
|
3936
|
+
} else {
|
|
3937
|
+
process.stderr.write("[runtime-cli/v2] Terminal failure detected from task counts\n");
|
|
3938
|
+
await doShutdown("failed");
|
|
3939
|
+
}
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
const allDead = runtime.workerPaneIds.length > 0 && snap.deadWorkers.length === runtime.workerPaneIds.length;
|
|
3943
|
+
const hasOutstanding = snap.tasks.pending + snap.tasks.in_progress > 0;
|
|
3944
|
+
if (allDead && hasOutstanding) {
|
|
3945
|
+
process.stderr.write("[runtime-cli/v2] All workers dead with outstanding work \u2014 failing\n");
|
|
3946
|
+
await doShutdown("failed");
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
1507
3952
|
while (pollActive) {
|
|
1508
3953
|
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1509
3954
|
if (!pollActive) break;
|
|
3955
|
+
const watchdogCheck = await checkWatchdogFailedMarker(stateRoot2, startTime);
|
|
3956
|
+
if (watchdogCheck.failed) {
|
|
3957
|
+
process.stderr.write(`[runtime-cli] ${watchdogCheck.reason ?? "Watchdog failure marker detected"}
|
|
3958
|
+
`);
|
|
3959
|
+
await doShutdown("failed");
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
1510
3962
|
let snap;
|
|
1511
3963
|
try {
|
|
1512
3964
|
snap = await monitorTeam(teamName, cwd, runtime.workerPaneIds);
|
|
@@ -1525,10 +3977,46 @@ async function main() {
|
|
|
1525
3977
|
`[runtime-cli] phase=${snap.phase} pending=${snap.taskCounts.pending} inProgress=${snap.taskCounts.inProgress} completed=${snap.taskCounts.completed} failed=${snap.taskCounts.failed} dead=${snap.deadWorkers.length} monitorMs=${snap.monitorPerformance.totalMs} tasksMs=${snap.monitorPerformance.listTasksMs} workerMs=${snap.monitorPerformance.workerScanMs}
|
|
1526
3978
|
`
|
|
1527
3979
|
);
|
|
1528
|
-
|
|
3980
|
+
const observedTaskCount = snap.taskCounts.pending + snap.taskCounts.inProgress + snap.taskCounts.completed + snap.taskCounts.failed;
|
|
3981
|
+
if (observedTaskCount !== expectedTaskCount) {
|
|
3982
|
+
mismatchStreak += 1;
|
|
3983
|
+
process.stderr.write(
|
|
3984
|
+
`[runtime-cli] Task-count mismatch observed=${observedTaskCount} expected=${expectedTaskCount} streak=${mismatchStreak}
|
|
3985
|
+
`
|
|
3986
|
+
);
|
|
3987
|
+
if (mismatchStreak >= 2) {
|
|
3988
|
+
process.stderr.write("[runtime-cli] Persistent task-count mismatch detected \u2014 failing fast\n");
|
|
3989
|
+
await doShutdown("failed");
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
continue;
|
|
3993
|
+
}
|
|
3994
|
+
mismatchStreak = 0;
|
|
3995
|
+
const terminalStatus = getTerminalStatus(snap.taskCounts, expectedTaskCount);
|
|
3996
|
+
if (terminalStatus === "completed") {
|
|
3997
|
+
const sentinelLogPath = (0, import_path16.join)(cwd, "sentinel_stop.jsonl");
|
|
3998
|
+
const gateResult = await waitForSentinelReadiness({
|
|
3999
|
+
workspace: cwd,
|
|
4000
|
+
logPath: sentinelLogPath,
|
|
4001
|
+
timeoutMs: sentinelGateTimeoutMs,
|
|
4002
|
+
pollIntervalMs: sentinelGatePollIntervalMs
|
|
4003
|
+
});
|
|
4004
|
+
if (!gateResult.ready) {
|
|
4005
|
+
process.stderr.write(
|
|
4006
|
+
`[runtime-cli] Sentinel gate blocked completion (timedOut=${gateResult.timedOut}, attempts=${gateResult.attempts}, elapsedMs=${gateResult.elapsedMs}): ${gateResult.blockers.join("; ")}
|
|
4007
|
+
`
|
|
4008
|
+
);
|
|
4009
|
+
await doShutdown("failed");
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
1529
4012
|
await doShutdown("completed");
|
|
1530
4013
|
return;
|
|
1531
4014
|
}
|
|
4015
|
+
if (terminalStatus === "failed") {
|
|
4016
|
+
process.stderr.write("[runtime-cli] Terminal failure detected from task counts\n");
|
|
4017
|
+
await doShutdown("failed");
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
1532
4020
|
const allWorkersDead = runtime.workerPaneIds.length > 0 && snap.deadWorkers.length === runtime.workerPaneIds.length;
|
|
1533
4021
|
const hasOutstandingWork = snap.taskCounts.pending + snap.taskCounts.inProgress > 0;
|
|
1534
4022
|
const deadWorkerFailure = allWorkersDead && hasOutstandingWork;
|
|
@@ -1548,3 +4036,9 @@ if (require.main === module) {
|
|
|
1548
4036
|
process.exit(1);
|
|
1549
4037
|
});
|
|
1550
4038
|
}
|
|
4039
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4040
|
+
0 && (module.exports = {
|
|
4041
|
+
checkWatchdogFailedMarker,
|
|
4042
|
+
getTerminalStatus,
|
|
4043
|
+
writeResultArtifact
|
|
4044
|
+
});
|