@telora/daemon 0.12.33
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/.env.example +64 -0
- package/README.md +229 -0
- package/build-info.json +4 -0
- package/dist/activity-tracker.d.ts +13 -0
- package/dist/activity-tracker.d.ts.map +1 -0
- package/dist/activity-tracker.js +19 -0
- package/dist/activity-tracker.js.map +1 -0
- package/dist/agent-state.d.ts +45 -0
- package/dist/agent-state.d.ts.map +1 -0
- package/dist/agent-state.js +61 -0
- package/dist/agent-state.js.map +1 -0
- package/dist/audit-hooks.d.ts +12 -0
- package/dist/audit-hooks.d.ts.map +1 -0
- package/dist/audit-hooks.js +45 -0
- package/dist/audit-hooks.js.map +1 -0
- package/dist/auto-update.d.ts +42 -0
- package/dist/auto-update.d.ts.map +1 -0
- package/dist/auto-update.js +96 -0
- package/dist/auto-update.js.map +1 -0
- package/dist/branch-status.d.ts +40 -0
- package/dist/branch-status.d.ts.map +1 -0
- package/dist/branch-status.js +107 -0
- package/dist/branch-status.js.map +1 -0
- package/dist/completion-detector.d.ts +87 -0
- package/dist/completion-detector.d.ts.map +1 -0
- package/dist/completion-detector.js +160 -0
- package/dist/completion-detector.js.map +1 -0
- package/dist/completion-handler.d.ts +48 -0
- package/dist/completion-handler.d.ts.map +1 -0
- package/dist/completion-handler.js +200 -0
- package/dist/completion-handler.js.map +1 -0
- package/dist/condition-evaluators.d.ts +31 -0
- package/dist/condition-evaluators.d.ts.map +1 -0
- package/dist/condition-evaluators.js +416 -0
- package/dist/condition-evaluators.js.map +1 -0
- package/dist/config.d.ts +55 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +311 -0
- package/dist/config.js.map +1 -0
- package/dist/control-state.d.ts +41 -0
- package/dist/control-state.d.ts.map +1 -0
- package/dist/control-state.js +204 -0
- package/dist/control-state.js.map +1 -0
- package/dist/crash-recovery-cleanup.d.ts +21 -0
- package/dist/crash-recovery-cleanup.d.ts.map +1 -0
- package/dist/crash-recovery-cleanup.js +198 -0
- package/dist/crash-recovery-cleanup.js.map +1 -0
- package/dist/crash-recovery-scan.d.ts +19 -0
- package/dist/crash-recovery-scan.d.ts.map +1 -0
- package/dist/crash-recovery-scan.js +145 -0
- package/dist/crash-recovery-scan.js.map +1 -0
- package/dist/crash-recovery-types.d.ts +54 -0
- package/dist/crash-recovery-types.d.ts.map +1 -0
- package/dist/crash-recovery-types.js +13 -0
- package/dist/crash-recovery-types.js.map +1 -0
- package/dist/crash-recovery.d.ts +88 -0
- package/dist/crash-recovery.d.ts.map +1 -0
- package/dist/crash-recovery.js +448 -0
- package/dist/crash-recovery.js.map +1 -0
- package/dist/daemon-logs.d.ts +19 -0
- package/dist/daemon-logs.d.ts.map +1 -0
- package/dist/daemon-logs.js +81 -0
- package/dist/daemon-logs.js.map +1 -0
- package/dist/daemon-process.d.ts +154 -0
- package/dist/daemon-process.d.ts.map +1 -0
- package/dist/daemon-process.js +427 -0
- package/dist/daemon-process.js.map +1 -0
- package/dist/dag-validator.d.ts +52 -0
- package/dist/dag-validator.d.ts.map +1 -0
- package/dist/dag-validator.js +199 -0
- package/dist/dag-validator.js.map +1 -0
- package/dist/delivery-guards.d.ts +41 -0
- package/dist/delivery-guards.d.ts.map +1 -0
- package/dist/delivery-guards.js +195 -0
- package/dist/delivery-guards.js.map +1 -0
- package/dist/delivery-lifecycle.d.ts +110 -0
- package/dist/delivery-lifecycle.d.ts.map +1 -0
- package/dist/delivery-lifecycle.js +353 -0
- package/dist/delivery-lifecycle.js.map +1 -0
- package/dist/delivery-merge.d.ts +17 -0
- package/dist/delivery-merge.d.ts.map +1 -0
- package/dist/delivery-merge.js +89 -0
- package/dist/delivery-merge.js.map +1 -0
- package/dist/dependency-resolver.d.ts +77 -0
- package/dist/dependency-resolver.d.ts.map +1 -0
- package/dist/dependency-resolver.js +337 -0
- package/dist/dependency-resolver.js.map +1 -0
- package/dist/evaluation-context.d.ts +49 -0
- package/dist/evaluation-context.d.ts.map +1 -0
- package/dist/evaluation-context.js +98 -0
- package/dist/evaluation-context.js.map +1 -0
- package/dist/git-activity.d.ts +24 -0
- package/dist/git-activity.d.ts.map +1 -0
- package/dist/git-activity.js +97 -0
- package/dist/git-activity.js.map +1 -0
- package/dist/git-branch.d.ts +33 -0
- package/dist/git-branch.d.ts.map +1 -0
- package/dist/git-branch.js +88 -0
- package/dist/git-branch.js.map +1 -0
- package/dist/git-integration.d.ts +27 -0
- package/dist/git-integration.d.ts.map +1 -0
- package/dist/git-integration.js +82 -0
- package/dist/git-integration.js.map +1 -0
- package/dist/git-merge-helpers.d.ts +48 -0
- package/dist/git-merge-helpers.d.ts.map +1 -0
- package/dist/git-merge-helpers.js +105 -0
- package/dist/git-merge-helpers.js.map +1 -0
- package/dist/git-merge-lock.d.ts +67 -0
- package/dist/git-merge-lock.d.ts.map +1 -0
- package/dist/git-merge-lock.js +157 -0
- package/dist/git-merge-lock.js.map +1 -0
- package/dist/git-merge-strategies.d.ts +39 -0
- package/dist/git-merge-strategies.d.ts.map +1 -0
- package/dist/git-merge-strategies.js +127 -0
- package/dist/git-merge-strategies.js.map +1 -0
- package/dist/git-merge.d.ts +80 -0
- package/dist/git-merge.d.ts.map +1 -0
- package/dist/git-merge.js +373 -0
- package/dist/git-merge.js.map +1 -0
- package/dist/git-state-detector.d.ts +24 -0
- package/dist/git-state-detector.d.ts.map +1 -0
- package/dist/git-state-detector.js +122 -0
- package/dist/git-state-detector.js.map +1 -0
- package/dist/git-types.d.ts +40 -0
- package/dist/git-types.d.ts.map +1 -0
- package/dist/git-types.js +23 -0
- package/dist/git-types.js.map +1 -0
- package/dist/git-utils.d.ts +28 -0
- package/dist/git-utils.d.ts.map +1 -0
- package/dist/git-utils.js +57 -0
- package/dist/git-utils.js.map +1 -0
- package/dist/git.d.ts +24 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +64 -0
- package/dist/git.js.map +1 -0
- package/dist/guard-engine.d.ts +19 -0
- package/dist/guard-engine.d.ts.map +1 -0
- package/dist/guard-engine.js +21 -0
- package/dist/guard-engine.js.map +1 -0
- package/dist/guard-evaluator.d.ts +47 -0
- package/dist/guard-evaluator.d.ts.map +1 -0
- package/dist/guard-evaluator.js +193 -0
- package/dist/guard-evaluator.js.map +1 -0
- package/dist/heartbeat.d.ts +73 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +306 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +493 -0
- package/dist/index.js.map +1 -0
- package/dist/listener-auto-advance.d.ts +29 -0
- package/dist/listener-auto-advance.d.ts.map +1 -0
- package/dist/listener-auto-advance.js +172 -0
- package/dist/listener-auto-advance.js.map +1 -0
- package/dist/listener-review.d.ts +37 -0
- package/dist/listener-review.d.ts.map +1 -0
- package/dist/listener-review.js +217 -0
- package/dist/listener-review.js.map +1 -0
- package/dist/listener.d.ts +57 -0
- package/dist/listener.d.ts.map +1 -0
- package/dist/listener.js +361 -0
- package/dist/listener.js.map +1 -0
- package/dist/log-manager.d.ts +18 -0
- package/dist/log-manager.d.ts.map +1 -0
- package/dist/log-manager.js +18 -0
- package/dist/log-manager.js.map +1 -0
- package/dist/otlp-log-parser.d.ts +21 -0
- package/dist/otlp-log-parser.d.ts.map +1 -0
- package/dist/otlp-log-parser.js +143 -0
- package/dist/otlp-log-parser.js.map +1 -0
- package/dist/otlp-metric-parser.d.ts +20 -0
- package/dist/otlp-metric-parser.d.ts.map +1 -0
- package/dist/otlp-metric-parser.js +113 -0
- package/dist/otlp-metric-parser.js.map +1 -0
- package/dist/otlp-port-manager.d.ts +26 -0
- package/dist/otlp-port-manager.d.ts.map +1 -0
- package/dist/otlp-port-manager.js +130 -0
- package/dist/otlp-port-manager.js.map +1 -0
- package/dist/otlp-receiver.d.ts +51 -0
- package/dist/otlp-receiver.d.ts.map +1 -0
- package/dist/otlp-receiver.js +663 -0
- package/dist/otlp-receiver.js.map +1 -0
- package/dist/otlp-types.d.ts +92 -0
- package/dist/otlp-types.d.ts.map +1 -0
- package/dist/otlp-types.js +133 -0
- package/dist/otlp-types.js.map +1 -0
- package/dist/output-monitor.d.ts +33 -0
- package/dist/output-monitor.d.ts.map +1 -0
- package/dist/output-monitor.js +67 -0
- package/dist/output-monitor.js.map +1 -0
- package/dist/planning-prompt-builder.d.ts +67 -0
- package/dist/planning-prompt-builder.d.ts.map +1 -0
- package/dist/planning-prompt-builder.js +515 -0
- package/dist/planning-prompt-builder.js.map +1 -0
- package/dist/prompt-builder.d.ts +14 -0
- package/dist/prompt-builder.d.ts.map +1 -0
- package/dist/prompt-builder.js +174 -0
- package/dist/prompt-builder.js.map +1 -0
- package/dist/qa-crash-recovery.d.ts +77 -0
- package/dist/qa-crash-recovery.d.ts.map +1 -0
- package/dist/qa-crash-recovery.js +243 -0
- package/dist/qa-crash-recovery.js.map +1 -0
- package/dist/qa-dev-server.d.ts +73 -0
- package/dist/qa-dev-server.d.ts.map +1 -0
- package/dist/qa-dev-server.js +279 -0
- package/dist/qa-dev-server.js.map +1 -0
- package/dist/qa-orchestrator.d.ts +79 -0
- package/dist/qa-orchestrator.d.ts.map +1 -0
- package/dist/qa-orchestrator.js +349 -0
- package/dist/qa-orchestrator.js.map +1 -0
- package/dist/qa-port-allocator.d.ts +34 -0
- package/dist/qa-port-allocator.d.ts.map +1 -0
- package/dist/qa-port-allocator.js +75 -0
- package/dist/qa-port-allocator.js.map +1 -0
- package/dist/qa-provisioner.d.ts +33 -0
- package/dist/qa-provisioner.d.ts.map +1 -0
- package/dist/qa-provisioner.js +141 -0
- package/dist/qa-provisioner.js.map +1 -0
- package/dist/qa-state.d.ts +93 -0
- package/dist/qa-state.d.ts.map +1 -0
- package/dist/qa-state.js +74 -0
- package/dist/qa-state.js.map +1 -0
- package/dist/queries/control-state.d.ts +25 -0
- package/dist/queries/control-state.d.ts.map +1 -0
- package/dist/queries/control-state.js +34 -0
- package/dist/queries/control-state.js.map +1 -0
- package/dist/queries/daemon-connection.d.ts +25 -0
- package/dist/queries/daemon-connection.d.ts.map +1 -0
- package/dist/queries/daemon-connection.js +28 -0
- package/dist/queries/daemon-connection.js.map +1 -0
- package/dist/queries/deliveries.d.ts +100 -0
- package/dist/queries/deliveries.d.ts.map +1 -0
- package/dist/queries/deliveries.js +184 -0
- package/dist/queries/deliveries.js.map +1 -0
- package/dist/queries/git-activity.d.ts +20 -0
- package/dist/queries/git-activity.d.ts.map +1 -0
- package/dist/queries/git-activity.js +22 -0
- package/dist/queries/git-activity.js.map +1 -0
- package/dist/queries/guards.d.ts +47 -0
- package/dist/queries/guards.d.ts.map +1 -0
- package/dist/queries/guards.js +138 -0
- package/dist/queries/guards.js.map +1 -0
- package/dist/queries/index.d.ts +19 -0
- package/dist/queries/index.d.ts.map +1 -0
- package/dist/queries/index.js +17 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/issues.d.ts +41 -0
- package/dist/queries/issues.d.ts.map +1 -0
- package/dist/queries/issues.js +67 -0
- package/dist/queries/issues.js.map +1 -0
- package/dist/queries/qa.d.ts +79 -0
- package/dist/queries/qa.d.ts.map +1 -0
- package/dist/queries/qa.js +85 -0
- package/dist/queries/qa.js.map +1 -0
- package/dist/queries/roles.d.ts +13 -0
- package/dist/queries/roles.d.ts.map +1 -0
- package/dist/queries/roles.js +39 -0
- package/dist/queries/roles.js.map +1 -0
- package/dist/queries/schemas.d.ts +777 -0
- package/dist/queries/schemas.d.ts.map +1 -0
- package/dist/queries/schemas.js +391 -0
- package/dist/queries/schemas.js.map +1 -0
- package/dist/queries/sessions.d.ts +64 -0
- package/dist/queries/sessions.d.ts.map +1 -0
- package/dist/queries/sessions.js +100 -0
- package/dist/queries/sessions.js.map +1 -0
- package/dist/queries/shared.d.ts +61 -0
- package/dist/queries/shared.d.ts.map +1 -0
- package/dist/queries/shared.js +187 -0
- package/dist/queries/shared.js.map +1 -0
- package/dist/queries/strategies.d.ts +69 -0
- package/dist/queries/strategies.d.ts.map +1 -0
- package/dist/queries/strategies.js +80 -0
- package/dist/queries/strategies.js.map +1 -0
- package/dist/queries/workflows.d.ts +17 -0
- package/dist/queries/workflows.d.ts.map +1 -0
- package/dist/queries/workflows.js +49 -0
- package/dist/queries/workflows.js.map +1 -0
- package/dist/queries/worktrees.d.ts +38 -0
- package/dist/queries/worktrees.d.ts.map +1 -0
- package/dist/queries/worktrees.js +37 -0
- package/dist/queries/worktrees.js.map +1 -0
- package/dist/self-update.d.ts +94 -0
- package/dist/self-update.d.ts.map +1 -0
- package/dist/self-update.js +438 -0
- package/dist/self-update.js.map +1 -0
- package/dist/session-lifecycle.d.ts +77 -0
- package/dist/session-lifecycle.d.ts.map +1 -0
- package/dist/session-lifecycle.js +379 -0
- package/dist/session-lifecycle.js.map +1 -0
- package/dist/shutdown-state.d.ts +17 -0
- package/dist/shutdown-state.d.ts.map +1 -0
- package/dist/shutdown-state.js +22 -0
- package/dist/shutdown-state.js.map +1 -0
- package/dist/spawn-cooldown.d.ts +14 -0
- package/dist/spawn-cooldown.d.ts.map +1 -0
- package/dist/spawn-cooldown.js +34 -0
- package/dist/spawn-cooldown.js.map +1 -0
- package/dist/spawn-environment.d.ts +35 -0
- package/dist/spawn-environment.d.ts.map +1 -0
- package/dist/spawn-environment.js +48 -0
- package/dist/spawn-environment.js.map +1 -0
- package/dist/spawner-liveness.d.ts +23 -0
- package/dist/spawner-liveness.d.ts.map +1 -0
- package/dist/spawner-liveness.js +99 -0
- package/dist/spawner-liveness.js.map +1 -0
- package/dist/spawner-resolution.d.ts +27 -0
- package/dist/spawner-resolution.d.ts.map +1 -0
- package/dist/spawner-resolution.js +99 -0
- package/dist/spawner-resolution.js.map +1 -0
- package/dist/spawner-timeout.d.ts +32 -0
- package/dist/spawner-timeout.d.ts.map +1 -0
- package/dist/spawner-timeout.js +124 -0
- package/dist/spawner-timeout.js.map +1 -0
- package/dist/spawner.d.ts +77 -0
- package/dist/spawner.d.ts.map +1 -0
- package/dist/spawner.js +734 -0
- package/dist/spawner.js.map +1 -0
- package/dist/strategy-completion.d.ts +110 -0
- package/dist/strategy-completion.d.ts.map +1 -0
- package/dist/strategy-completion.js +434 -0
- package/dist/strategy-completion.js.map +1 -0
- package/dist/strategy-engine.d.ts +47 -0
- package/dist/strategy-engine.d.ts.map +1 -0
- package/dist/strategy-engine.js +419 -0
- package/dist/strategy-engine.js.map +1 -0
- package/dist/strategy-executor.d.ts +93 -0
- package/dist/strategy-executor.d.ts.map +1 -0
- package/dist/strategy-executor.js +775 -0
- package/dist/strategy-executor.js.map +1 -0
- package/dist/strategy-lifecycle.d.ts +61 -0
- package/dist/strategy-lifecycle.d.ts.map +1 -0
- package/dist/strategy-lifecycle.js +516 -0
- package/dist/strategy-lifecycle.js.map +1 -0
- package/dist/strategy-merge.d.ts +72 -0
- package/dist/strategy-merge.d.ts.map +1 -0
- package/dist/strategy-merge.js +371 -0
- package/dist/strategy-merge.js.map +1 -0
- package/dist/strategy-prompt-builder.d.ts +62 -0
- package/dist/strategy-prompt-builder.d.ts.map +1 -0
- package/dist/strategy-prompt-builder.js +538 -0
- package/dist/strategy-prompt-builder.js.map +1 -0
- package/dist/strategy-provisioning.d.ts +16 -0
- package/dist/strategy-provisioning.d.ts.map +1 -0
- package/dist/strategy-provisioning.js +119 -0
- package/dist/strategy-provisioning.js.map +1 -0
- package/dist/strategy-team-state.d.ts +24 -0
- package/dist/strategy-team-state.d.ts.map +1 -0
- package/dist/strategy-team-state.js +43 -0
- package/dist/strategy-team-state.js.map +1 -0
- package/dist/strategy-teardown.d.ts +24 -0
- package/dist/strategy-teardown.d.ts.map +1 -0
- package/dist/strategy-teardown.js +158 -0
- package/dist/strategy-teardown.js.map +1 -0
- package/dist/strategy-worktree-state.d.ts +47 -0
- package/dist/strategy-worktree-state.d.ts.map +1 -0
- package/dist/strategy-worktree-state.js +104 -0
- package/dist/strategy-worktree-state.js.map +1 -0
- package/dist/supabase.d.ts +36 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +50 -0
- package/dist/supabase.js.map +1 -0
- package/dist/task-converter.d.ts +61 -0
- package/dist/task-converter.d.ts.map +1 -0
- package/dist/task-converter.js +286 -0
- package/dist/task-converter.js.map +1 -0
- package/dist/task-dag-builder.d.ts +14 -0
- package/dist/task-dag-builder.d.ts.map +1 -0
- package/dist/task-dag-builder.js +17 -0
- package/dist/task-dag-builder.js.map +1 -0
- package/dist/team-prompt-base.d.ts +114 -0
- package/dist/team-prompt-base.d.ts.map +1 -0
- package/dist/team-prompt-base.js +531 -0
- package/dist/team-prompt-base.js.map +1 -0
- package/dist/team-prompt-variants.d.ts +27 -0
- package/dist/team-prompt-variants.d.ts.map +1 -0
- package/dist/team-prompt-variants.js +134 -0
- package/dist/team-prompt-variants.js.map +1 -0
- package/dist/team-spawner.d.ts +50 -0
- package/dist/team-spawner.d.ts.map +1 -0
- package/dist/team-spawner.js +410 -0
- package/dist/team-spawner.js.map +1 -0
- package/dist/telemetry-writer.d.ts +66 -0
- package/dist/telemetry-writer.d.ts.map +1 -0
- package/dist/telemetry-writer.js +96 -0
- package/dist/telemetry-writer.js.map +1 -0
- package/dist/trigger-executor.d.ts +56 -0
- package/dist/trigger-executor.d.ts.map +1 -0
- package/dist/trigger-executor.js +313 -0
- package/dist/trigger-executor.js.map +1 -0
- package/dist/types/config.d.ts +60 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/dag.d.ts +53 -0
- package/dist/types/dag.d.ts.map +1 -0
- package/dist/types/dag.js +5 -0
- package/dist/types/dag.js.map +1 -0
- package/dist/types/delivery.d.ts +71 -0
- package/dist/types/delivery.d.ts.map +1 -0
- package/dist/types/delivery.js +5 -0
- package/dist/types/delivery.js.map +1 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +15 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/issue.d.ts +22 -0
- package/dist/types/issue.d.ts.map +1 -0
- package/dist/types/issue.js +5 -0
- package/dist/types/issue.js.map +1 -0
- package/dist/types/merge.d.ts +28 -0
- package/dist/types/merge.d.ts.map +1 -0
- package/dist/types/merge.js +5 -0
- package/dist/types/merge.js.map +1 -0
- package/dist/types/session.d.ts +98 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +5 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/strategy.d.ts +175 -0
- package/dist/types/strategy.d.ts.map +1 -0
- package/dist/types/strategy.js +5 -0
- package/dist/types/strategy.js.map +1 -0
- package/dist/types/workflow.d.ts +34 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +9 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/unified-init.d.ts +16 -0
- package/dist/unified-init.d.ts.map +1 -0
- package/dist/unified-init.js +183 -0
- package/dist/unified-init.js.map +1 -0
- package/dist/unified-shell-config.d.ts +34 -0
- package/dist/unified-shell-config.d.ts.map +1 -0
- package/dist/unified-shell-config.js +238 -0
- package/dist/unified-shell-config.js.map +1 -0
- package/dist/unified-shell-status.d.ts +15 -0
- package/dist/unified-shell-status.d.ts.map +1 -0
- package/dist/unified-shell-status.js +100 -0
- package/dist/unified-shell-status.js.map +1 -0
- package/dist/unified-shell.d.ts +50 -0
- package/dist/unified-shell.d.ts.map +1 -0
- package/dist/unified-shell.js +682 -0
- package/dist/unified-shell.js.map +1 -0
- package/dist/version-check.d.ts +19 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +67 -0
- package/dist/version-check.js.map +1 -0
- package/dist/workflow-engine.d.ts +95 -0
- package/dist/workflow-engine.d.ts.map +1 -0
- package/dist/workflow-engine.js +165 -0
- package/dist/workflow-engine.js.map +1 -0
- package/dist/worktree-merge.d.ts +23 -0
- package/dist/worktree-merge.d.ts.map +1 -0
- package/dist/worktree-merge.js +57 -0
- package/dist/worktree-merge.js.map +1 -0
- package/dist/worktree-safety.d.ts +48 -0
- package/dist/worktree-safety.d.ts.map +1 -0
- package/dist/worktree-safety.js +113 -0
- package/dist/worktree-safety.js.map +1 -0
- package/dist/worktree-strategy.d.ts +69 -0
- package/dist/worktree-strategy.d.ts.map +1 -0
- package/dist/worktree-strategy.js +214 -0
- package/dist/worktree-strategy.js.map +1 -0
- package/dist/worktree.d.ts +159 -0
- package/dist/worktree.d.ts.map +1 -0
- package/dist/worktree.js +512 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +76 -0
- package/scripts/telora-daemon-wrapper.sh +31 -0
package/dist/spawner.js
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code spawner - manages spawning and tracking agent processes.
|
|
3
|
+
*
|
|
4
|
+
* Spawns agents at delivery level with full context including:
|
|
5
|
+
* - Product info (name, vision, description)
|
|
6
|
+
* - Delivery details (name, description, acceptance criteria, tech context)
|
|
7
|
+
* - All context groups and tasks within the delivery
|
|
8
|
+
*
|
|
9
|
+
* Each agent runs in an isolated git worktree created from the integration branch.
|
|
10
|
+
* On successful completion, the agent's branch is merged into integration.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from 'node:child_process';
|
|
13
|
+
import { mkdirSync, existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { runGitSync } from './git-types.js';
|
|
16
|
+
import { updateSession, createSession, getDeliveryContext, fetchEffectiveWorkflow, getResolvedTransitionBlockForDelivery, reportGitState } from './supabase.js';
|
|
17
|
+
import { buildSpawnEnvironment } from './spawn-environment.js';
|
|
18
|
+
import { sanitizeGitSegment } from './git-utils.js';
|
|
19
|
+
import { createWorktree, removeWorktree, syncIntegrationWithMain } from './git.js';
|
|
20
|
+
import { recordSessionCompleted, recordActivity } from './heartbeat.js';
|
|
21
|
+
import { getStageByName } from './workflow-engine.js';
|
|
22
|
+
import { StreamJsonParser, sendMessage, buildStreamJsonArgs, stripClaudeCodeEnvVars, createLogStream } from '@telora/daemon-core';
|
|
23
|
+
import { CompletionDetector } from './completion-detector.js';
|
|
24
|
+
import { buildDeliveryPrompt } from './prompt-builder.js';
|
|
25
|
+
import { handleAgentCompletion } from './completion-handler.js';
|
|
26
|
+
import { formatEventForLog } from '@telora/daemon-core';
|
|
27
|
+
import { ActivityTracker } from './activity-tracker.js';
|
|
28
|
+
// Running agents map: sessionId -> RunningAgent
|
|
29
|
+
const runningAgents = new Map();
|
|
30
|
+
// Resource governor (optional, injected by StrategyEngine)
|
|
31
|
+
let governor = null;
|
|
32
|
+
/** Inject the resource governor for global concurrency limiting. */
|
|
33
|
+
export function initGovernor(gov) {
|
|
34
|
+
governor = gov;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get map of running agents.
|
|
38
|
+
*/
|
|
39
|
+
export function getRunningAgents() {
|
|
40
|
+
return runningAgents;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Safely send a signal to a process, ignoring errors if the process has already exited.
|
|
44
|
+
*/
|
|
45
|
+
function safeSendSignal(pid, signal) {
|
|
46
|
+
try {
|
|
47
|
+
process.kill(pid, signal);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.debug(`[spawner] safeSendSignal(${pid}, ${signal}) failed (process may have exited):`, e.message);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Safely close a writable stdin stream, ignoring errors if already closed.
|
|
55
|
+
*/
|
|
56
|
+
function safeCloseStdin(stdin) {
|
|
57
|
+
try {
|
|
58
|
+
stdin.end();
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
console.debug('[spawner] safeCloseStdin failed (stdin may already be closed):', e.message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build Claude Code CLI arguments for stream-json I/O.
|
|
66
|
+
*
|
|
67
|
+
* Uses the shared base args from daemon-core, then appends daemon-specific flags.
|
|
68
|
+
*/
|
|
69
|
+
function buildClaudeCodeArgs(config) {
|
|
70
|
+
const args = buildStreamJsonArgs({ mcpConfigPath: config.mcpConfigPath });
|
|
71
|
+
// Skip user-level settings (hooks etc.) that can block headless execution
|
|
72
|
+
args.push('--setting-sources', 'project,local');
|
|
73
|
+
console.log(` Stream-JSON mode (structured I/O)`);
|
|
74
|
+
// Agent Teams always enabled -- strategy-level execution model
|
|
75
|
+
args.push('--teammate-mode', 'in-process');
|
|
76
|
+
console.log(` + Agent Teams (in-process teammates)`);
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set up per-agent timeout enforcement with warning at 80% and escalating shutdown.
|
|
81
|
+
*
|
|
82
|
+
* Returns a `timedOut` accessor function (closure over mutable state).
|
|
83
|
+
*/
|
|
84
|
+
function setupTimeoutEnforcement(config, runningAgent, deliveryName, sessionId) {
|
|
85
|
+
let timedOut = false;
|
|
86
|
+
const timeoutMs = config.sessionTimeoutMs;
|
|
87
|
+
if (timeoutMs <= 0) {
|
|
88
|
+
return { getTimedOut: () => timedOut };
|
|
89
|
+
}
|
|
90
|
+
// Warning at 80% of timeout -- send warning via stdin
|
|
91
|
+
const warningMs = Math.floor(timeoutMs * 0.8);
|
|
92
|
+
runningAgent.warningTimer = setTimeout(() => {
|
|
93
|
+
const remaining = Math.ceil((timeoutMs - warningMs) / 60000);
|
|
94
|
+
console.warn(`[timeout] Agent for "${deliveryName}" at 80% of timeout limit. ` +
|
|
95
|
+
`Sending warning via stdin. ~${remaining} min remaining.`);
|
|
96
|
+
sendMessage(runningAgent.stdin, `WARNING: You have approximately ${remaining} minutes remaining before the session ` +
|
|
97
|
+
`is terminated. Please wrap up your current work, commit changes, and prepare to exit.`);
|
|
98
|
+
}, warningMs);
|
|
99
|
+
// Hard timeout -- escalating shutdown: message -> close stdin -> SIGTERM -> SIGKILL
|
|
100
|
+
runningAgent.timeoutTimer = setTimeout(() => {
|
|
101
|
+
timedOut = true;
|
|
102
|
+
console.error(`[timeout] Agent for "${deliveryName}" exceeded ${timeoutMs / 60000} min timeout. ` +
|
|
103
|
+
`Sending shutdown message...`);
|
|
104
|
+
runningAgent.streamState = 'shutting_down';
|
|
105
|
+
sendMessage(runningAgent.stdin, `TIMEOUT: Session time limit reached. Stop all work immediately, commit any changes, and exit now.`);
|
|
106
|
+
scheduleTimeoutEscalation(runningAgent, deliveryName, sessionId);
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
return { getTimedOut: () => timedOut };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Schedule escalating shutdown steps after the initial timeout message:
|
|
112
|
+
* stdin close -> SIGTERM -> SIGKILL
|
|
113
|
+
*/
|
|
114
|
+
function scheduleTimeoutEscalation(agent, deliveryName, sessionId) {
|
|
115
|
+
// Close stdin after a short grace period to signal EOF
|
|
116
|
+
setTimeout(() => safeCloseStdin(agent.stdin), 10000);
|
|
117
|
+
// SIGTERM after stdin close grace period
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
if (!runningAgents.has(sessionId))
|
|
120
|
+
return;
|
|
121
|
+
console.error(`[timeout] Agent for "${deliveryName}" did not exit after stdin close. Sending SIGTERM...`);
|
|
122
|
+
safeSendSignal(agent.pid, 'SIGTERM');
|
|
123
|
+
}, 20000);
|
|
124
|
+
// SIGKILL as absolute last resort
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
if (!runningAgents.has(sessionId))
|
|
127
|
+
return;
|
|
128
|
+
console.error(`[timeout] Agent for "${deliveryName}" still running. Sending SIGKILL...`);
|
|
129
|
+
safeSendSignal(agent.pid, 'SIGKILL');
|
|
130
|
+
}, 30000);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Fetch the guard directive for a delivery, if one exists.
|
|
134
|
+
* When a delivery resumes after a transition block is resolved,
|
|
135
|
+
* the directive template is injected into the agent prompt.
|
|
136
|
+
* Returns null if no directive is found or on any error.
|
|
137
|
+
*/
|
|
138
|
+
async function fetchGuardDirective(deliveryId) {
|
|
139
|
+
try {
|
|
140
|
+
const resolvedBlock = await getResolvedTransitionBlockForDelivery(deliveryId);
|
|
141
|
+
if (!resolvedBlock?.directive_template)
|
|
142
|
+
return null;
|
|
143
|
+
console.log(` Injecting guard directive from block ${resolvedBlock.block.id} (path: ${resolvedBlock.block.decision_path})`);
|
|
144
|
+
return resolvedBlock.directive_template;
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
console.debug(`[spawner] Guard directive lookup failed for delivery ${deliveryId} (non-fatal):`, e.message);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Generate branch name for delivery work.
|
|
153
|
+
*/
|
|
154
|
+
function generateBranchName(role, deliveryName) {
|
|
155
|
+
const sanitizedRoleName = sanitizeGitSegment(role.name);
|
|
156
|
+
const sanitizedDeliveryName = sanitizeGitSegment(deliveryName);
|
|
157
|
+
return `agent/${sanitizedRoleName}/${sanitizedDeliveryName}`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Spawn a Claude Code agent to work on a delivery.
|
|
161
|
+
*
|
|
162
|
+
* The agent runs in an isolated git worktree created from the integration branch.
|
|
163
|
+
* On successful completion, the agent's changes are merged back to integration.
|
|
164
|
+
*/
|
|
165
|
+
export async function spawnAgentForDelivery(config, deliveryId, role, strategyId) {
|
|
166
|
+
// Get full delivery context — failures are caught and skipped (retried next cycle)
|
|
167
|
+
let context;
|
|
168
|
+
try {
|
|
169
|
+
context = await getDeliveryContext(deliveryId);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
console.error(`Error fetching context for delivery ${deliveryId}:`, err.message);
|
|
173
|
+
return; // Will be retried next polling cycle
|
|
174
|
+
}
|
|
175
|
+
if (!context) {
|
|
176
|
+
console.error(`Failed to get context for delivery ${deliveryId}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// Fetch effective workflow (cached for this session's lifetime)
|
|
180
|
+
let workflow;
|
|
181
|
+
try {
|
|
182
|
+
workflow = await fetchEffectiveWorkflow(deliveryId);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
console.error(`Error fetching workflow for delivery ${deliveryId}:`, err.message);
|
|
186
|
+
return; // Will be retried next polling cycle
|
|
187
|
+
}
|
|
188
|
+
const branchName = generateBranchName(role, context.delivery.name);
|
|
189
|
+
// Ensure log directory exists with restrictive permissions (owner-only)
|
|
190
|
+
if (!existsSync(config.logDir)) {
|
|
191
|
+
mkdirSync(config.logDir, { recursive: true, mode: 0o700 });
|
|
192
|
+
}
|
|
193
|
+
// Check for resolved transition block directive (when resuming after human routing)
|
|
194
|
+
const guardDirective = await fetchGuardDirective(deliveryId);
|
|
195
|
+
// Build prompt with full delivery context (+ optional guard directive)
|
|
196
|
+
const prompt = buildDeliveryPrompt(role, context, guardDirective);
|
|
197
|
+
// Log file paths
|
|
198
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
199
|
+
const stdoutPath = join(config.logDir, `${branchName.replace(/\//g, '-')}-${timestamp}.stdout.log`);
|
|
200
|
+
const stderrPath = join(config.logDir, `${branchName.replace(/\//g, '-')}-${timestamp}.stderr.log`);
|
|
201
|
+
// Create session in database — failures are caught and skipped
|
|
202
|
+
let session;
|
|
203
|
+
try {
|
|
204
|
+
const codingStageForSession = getStageByName(workflow, 'coding');
|
|
205
|
+
session = await createSession({
|
|
206
|
+
organizationId: context.delivery.organization_id,
|
|
207
|
+
roleId: role.id,
|
|
208
|
+
issueId: null, // No single issue - working on whole delivery
|
|
209
|
+
deliveryId,
|
|
210
|
+
branchName,
|
|
211
|
+
workflowStageId: codingStageForSession?.id ?? null,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error(`Error creating session for delivery "${context.delivery.name}":`, err.message);
|
|
216
|
+
return; // Will be retried next polling cycle
|
|
217
|
+
}
|
|
218
|
+
recordActivity();
|
|
219
|
+
console.log(`Spawning agent for delivery "${context.delivery.name}"`);
|
|
220
|
+
console.log(` Role: ${role.name}`);
|
|
221
|
+
console.log(` Session: ${session.id}`);
|
|
222
|
+
console.log(` Branch: ${branchName}`);
|
|
223
|
+
console.log(` Workflow: ${workflow.id} (${workflow.stages.map(s => s.name).join(' -> ')})`);
|
|
224
|
+
console.log(` Context Groups/Items: ${context.contextGroups.length}`);
|
|
225
|
+
// Sync integration branch with main before creating worktree
|
|
226
|
+
try {
|
|
227
|
+
const syncResult = await syncIntegrationWithMain(config);
|
|
228
|
+
if (syncResult.success) {
|
|
229
|
+
console.log(` Synced integration with main before worktree creation`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
console.warn(` Could not sync integration with main: ${syncResult.error}`);
|
|
233
|
+
// Continue anyway — stale code is better than not spawning
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
console.warn(` Sync failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
|
|
238
|
+
}
|
|
239
|
+
// Create worktree for this agent (branches from integration)
|
|
240
|
+
let worktreePath;
|
|
241
|
+
try {
|
|
242
|
+
worktreePath = await createWorktree(config, branchName);
|
|
243
|
+
console.log(` Worktree: ${worktreePath}`);
|
|
244
|
+
reportGitState(deliveryId, 'worktree_created', branchName).catch(err => console.warn('[spawner] reportGitState worktree_created failed:', err.message));
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
console.error(`Failed to create worktree for ${branchName}:`, err instanceof Error ? err.message : String(err));
|
|
248
|
+
await updateSession(session.id, {
|
|
249
|
+
status: 'failed',
|
|
250
|
+
exit_reason: 'Failed to create worktree',
|
|
251
|
+
ended_at: new Date().toISOString(),
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Build Claude Code arguments and spawn environment
|
|
256
|
+
const args = buildClaudeCodeArgs(config);
|
|
257
|
+
const spawnEnv = buildSpawnEnvironment(config, {
|
|
258
|
+
orgId: config.organizationId,
|
|
259
|
+
strategyId,
|
|
260
|
+
deliveryId,
|
|
261
|
+
sessionId: session.id,
|
|
262
|
+
});
|
|
263
|
+
// Update session to starting
|
|
264
|
+
await updateSession(session.id, {
|
|
265
|
+
status: 'starting',
|
|
266
|
+
started_at: new Date().toISOString(),
|
|
267
|
+
stdout_path: stdoutPath,
|
|
268
|
+
stderr_path: stderrPath,
|
|
269
|
+
});
|
|
270
|
+
// Open log files with restrictive permissions (owner-only read/write)
|
|
271
|
+
const stdoutLogStream = createLogStream(stdoutPath);
|
|
272
|
+
const stderrStream = createLogStream(stderrPath);
|
|
273
|
+
// Raw NDJSON stream log for all agents
|
|
274
|
+
const jsonlPath = join(config.logDir, `${branchName.replace(/\//g, '-')}-${timestamp}.stream.jsonl`);
|
|
275
|
+
const jsonlStream = createLogStream(jsonlPath);
|
|
276
|
+
// Acquire governor slot (if governor is configured)
|
|
277
|
+
if (governor) {
|
|
278
|
+
try {
|
|
279
|
+
await governor.acquireSlot('strategy');
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
console.warn(`[spawner] Governor denied slot for "${context.delivery.name}":`, err.message);
|
|
283
|
+
await updateSession(session.id, {
|
|
284
|
+
status: 'failed',
|
|
285
|
+
exit_reason: 'Governor denied slot',
|
|
286
|
+
ended_at: new Date().toISOString(),
|
|
287
|
+
});
|
|
288
|
+
removeWorktree(config.repoPath, worktreePath, {
|
|
289
|
+
branchName,
|
|
290
|
+
integrationBranch: config.integrationBranch,
|
|
291
|
+
deliveryName: context.delivery.name,
|
|
292
|
+
mergeSucceeded: false,
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Spawn Claude Code — stdin is always piped for stream-json prompt delivery
|
|
298
|
+
const proc = spawn(config.claudeCodePath, args, {
|
|
299
|
+
cwd: worktreePath,
|
|
300
|
+
env: spawnEnv,
|
|
301
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
302
|
+
});
|
|
303
|
+
// Track running agent — declared before parser setup so closures can reference it
|
|
304
|
+
const streamParser = new StreamJsonParser();
|
|
305
|
+
const completionDetector = new CompletionDetector();
|
|
306
|
+
const runningAgent = {
|
|
307
|
+
sessionId: session.id,
|
|
308
|
+
roleId: role.id,
|
|
309
|
+
roleName: role.name,
|
|
310
|
+
strategyId,
|
|
311
|
+
deliveryId,
|
|
312
|
+
deliveryName: context.delivery.name,
|
|
313
|
+
workflow,
|
|
314
|
+
branchName,
|
|
315
|
+
worktreePath,
|
|
316
|
+
repoPath: config.repoPath,
|
|
317
|
+
pid: proc.pid,
|
|
318
|
+
startedAt: new Date(),
|
|
319
|
+
stdoutPath,
|
|
320
|
+
stderrPath,
|
|
321
|
+
enableTeams: true,
|
|
322
|
+
stdin: proc.stdin,
|
|
323
|
+
streamParser,
|
|
324
|
+
streamState: 'starting',
|
|
325
|
+
};
|
|
326
|
+
runningAgents.set(session.id, runningAgent);
|
|
327
|
+
// Wire up stream parsing, logging, and event tracking
|
|
328
|
+
const activityTracker = attachStreamHandlers({
|
|
329
|
+
proc, streamParser, completionDetector, runningAgent,
|
|
330
|
+
deliveryId, deliveryName: context.delivery.name, branchName,
|
|
331
|
+
stdoutLogStream, jsonlStream,
|
|
332
|
+
});
|
|
333
|
+
// Send the initial prompt via stdin as stream-json
|
|
334
|
+
sendMessage(proc.stdin, prompt);
|
|
335
|
+
// Stderr always pipes directly to log file
|
|
336
|
+
proc.stderr?.pipe(stderrStream);
|
|
337
|
+
// Set up per-agent timeout enforcement
|
|
338
|
+
const { getTimedOut } = setupTimeoutEnforcement(config, runningAgent, context.delivery.name, session.id);
|
|
339
|
+
// Update session with PID
|
|
340
|
+
await updateSession(session.id, {
|
|
341
|
+
status: 'running',
|
|
342
|
+
pid: proc.pid,
|
|
343
|
+
});
|
|
344
|
+
// Handle process exit and error — delegates to completion-handler module
|
|
345
|
+
attachProcessHandlers({
|
|
346
|
+
proc, config, context: context, runningAgent, workflow,
|
|
347
|
+
branchName, worktreePath, deliveryId,
|
|
348
|
+
sessionId: session.id, completionDetector, activityTracker,
|
|
349
|
+
stdoutLogStream, stderrStream, jsonlStream,
|
|
350
|
+
getTimedOut,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Attach stream parser, logging, activity tracking, and completion detection
|
|
355
|
+
* event handlers to a spawned agent process.
|
|
356
|
+
*/
|
|
357
|
+
function attachStreamHandlers(params) {
|
|
358
|
+
const { proc, streamParser, completionDetector, runningAgent, deliveryId, deliveryName, branchName, stdoutLogStream, jsonlStream } = params;
|
|
359
|
+
streamParser.attach(proc.stdout);
|
|
360
|
+
completionDetector.attach(streamParser);
|
|
361
|
+
const activityTracker = new ActivityTracker(runningAgent.sessionId);
|
|
362
|
+
activityTracker.attach(streamParser);
|
|
363
|
+
// Raw NDJSON log
|
|
364
|
+
streamParser.on('event', (event) => {
|
|
365
|
+
jsonlStream.write(JSON.stringify(event) + '\n');
|
|
366
|
+
});
|
|
367
|
+
// Human-readable log
|
|
368
|
+
streamParser.on('event', (event) => {
|
|
369
|
+
const line = formatEventForLog(event);
|
|
370
|
+
if (line)
|
|
371
|
+
stdoutLogStream.write(line + '\n');
|
|
372
|
+
});
|
|
373
|
+
// Session state tracking
|
|
374
|
+
streamParser.on('init', (event) => {
|
|
375
|
+
runningAgent.claudeSessionId = event.session_id;
|
|
376
|
+
runningAgent.streamState = 'active';
|
|
377
|
+
console.log(` Session initialized (model: ${event.model}, tools: ${event.tools.length})`);
|
|
378
|
+
reportGitState(deliveryId, 'worktree_active', branchName).catch(err => console.warn('[spawner] reportGitState worktree_active failed:', err.message));
|
|
379
|
+
});
|
|
380
|
+
streamParser.on('teammate', (event) => {
|
|
381
|
+
if (event.subtype === 'teammate_spawned') {
|
|
382
|
+
console.log(` [team] Teammate spawned: ${event.agent_name}`);
|
|
383
|
+
}
|
|
384
|
+
else if (event.subtype === 'teammate_completed') {
|
|
385
|
+
const status = event.is_error ? 'FAILED' : 'completed';
|
|
386
|
+
console.log(` [team] Teammate ${status}: ${event.agent_name}`);
|
|
387
|
+
}
|
|
388
|
+
const state = completionDetector.getState();
|
|
389
|
+
runningAgent.activeTeammateCount = state.activeTeammateCount;
|
|
390
|
+
runningAgent.activeTeammates = state.activeTeammates;
|
|
391
|
+
});
|
|
392
|
+
streamParser.on('result', (result) => {
|
|
393
|
+
const cost = result.total_cost_usd?.toFixed(4) ?? '?';
|
|
394
|
+
console.log(` [result] ${result.is_error ? 'ERROR' : 'Success'}: ${result.num_turns} turns, $${cost}`);
|
|
395
|
+
});
|
|
396
|
+
streamParser.on('error', (err, line) => {
|
|
397
|
+
console.warn(` [stream-json] Parse error: ${err.message} — line: ${line.slice(0, 200)}`);
|
|
398
|
+
});
|
|
399
|
+
completionDetector.on('complete', (info) => {
|
|
400
|
+
console.log(`Agent for "${deliveryName}" completed: ` +
|
|
401
|
+
`${info.turnCount} turns, $${info.totalCostUsd.toFixed(4)}, ` +
|
|
402
|
+
`all teammates done: ${info.allTeammatesComplete}`);
|
|
403
|
+
runningAgent.streamState = 'idle';
|
|
404
|
+
initiateGracefulShutdown(runningAgent, deliveryName);
|
|
405
|
+
});
|
|
406
|
+
completionDetector.on('idle', (info) => {
|
|
407
|
+
console.warn(`Agent for "${deliveryName}" idle for ${info.idleDurationMs / 1000}s ` +
|
|
408
|
+
`with ${info.activeTeammateCount} active teammates`);
|
|
409
|
+
});
|
|
410
|
+
return activityTracker;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Attach process close and error handlers to a spawned agent.
|
|
414
|
+
*/
|
|
415
|
+
function attachProcessHandlers(params) {
|
|
416
|
+
const { proc, config, context, runningAgent, workflow, branchName, worktreePath, deliveryId, sessionId, completionDetector, activityTracker, stdoutLogStream, stderrStream, jsonlStream, getTimedOut, } = params;
|
|
417
|
+
const codingStage = getStageByName(workflow, 'coding');
|
|
418
|
+
const currentStageId = codingStage?.id;
|
|
419
|
+
proc.on('close', async (code, signal) => {
|
|
420
|
+
governor?.releaseSlot('strategy');
|
|
421
|
+
try {
|
|
422
|
+
await activityTracker.finalFlush();
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
console.warn('[spawner] activityTracker.finalFlush failed:', err.message);
|
|
426
|
+
}
|
|
427
|
+
handleAgentCompletion(code, signal, {
|
|
428
|
+
config,
|
|
429
|
+
context,
|
|
430
|
+
runningAgent,
|
|
431
|
+
workflow,
|
|
432
|
+
branchName,
|
|
433
|
+
currentStageId,
|
|
434
|
+
timedOut: getTimedOut(),
|
|
435
|
+
timeoutMs: config.sessionTimeoutMs,
|
|
436
|
+
sessionId,
|
|
437
|
+
deliveryId,
|
|
438
|
+
completionDetector,
|
|
439
|
+
stdoutLogStream,
|
|
440
|
+
stderrStream,
|
|
441
|
+
jsonlStream,
|
|
442
|
+
runningAgents,
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
proc.on('error', async (err) => {
|
|
446
|
+
console.error(`Agent for "${context.delivery.name}" error:`, err.message);
|
|
447
|
+
governor?.releaseSlot('strategy');
|
|
448
|
+
if (runningAgent.warningTimer)
|
|
449
|
+
clearTimeout(runningAgent.warningTimer);
|
|
450
|
+
if (runningAgent.timeoutTimer)
|
|
451
|
+
clearTimeout(runningAgent.timeoutTimer);
|
|
452
|
+
completionDetector?.destroy();
|
|
453
|
+
runningAgents.delete(sessionId);
|
|
454
|
+
removeWorktree(config.repoPath, worktreePath, {
|
|
455
|
+
branchName,
|
|
456
|
+
integrationBranch: config.integrationBranch,
|
|
457
|
+
deliveryName: context.delivery.name,
|
|
458
|
+
mergeSucceeded: false,
|
|
459
|
+
});
|
|
460
|
+
await updateSession(sessionId, {
|
|
461
|
+
status: 'failed',
|
|
462
|
+
exit_reason: 'Agent process error',
|
|
463
|
+
ended_at: new Date().toISOString(),
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Initiate graceful shutdown for an agent session.
|
|
469
|
+
*
|
|
470
|
+
* Sends a wrap-up message via stdin, waits for the process to exit,
|
|
471
|
+
* and escalates to SIGTERM/SIGKILL if needed.
|
|
472
|
+
*/
|
|
473
|
+
function initiateGracefulShutdown(agent, deliveryName) {
|
|
474
|
+
const GRACE_PERIOD_MS = 60000; // 60s for agent to wrap up
|
|
475
|
+
const ESCALATION_STEP_MS = 10000; // 10s between escalation steps
|
|
476
|
+
// Send wrap-up message
|
|
477
|
+
sendMessage(agent.stdin, 'All work is complete. Please commit any remaining changes, clean up, and exit.');
|
|
478
|
+
// Flat escalation chain using absolute offsets from now
|
|
479
|
+
// Step 1: Close stdin after grace period
|
|
480
|
+
const graceTimer = setTimeout(() => {
|
|
481
|
+
if (!runningAgents.has(agent.sessionId))
|
|
482
|
+
return;
|
|
483
|
+
console.warn(`[shutdown] Agent for "${deliveryName}" did not exit within grace period. Closing stdin...`);
|
|
484
|
+
safeCloseStdin(agent.stdin);
|
|
485
|
+
}, GRACE_PERIOD_MS);
|
|
486
|
+
// Step 2: SIGTERM after grace period + one escalation step
|
|
487
|
+
setTimeout(() => {
|
|
488
|
+
if (!runningAgents.has(agent.sessionId))
|
|
489
|
+
return;
|
|
490
|
+
console.warn(`[shutdown] Agent for "${deliveryName}" did not exit after stdin close. Sending SIGTERM...`);
|
|
491
|
+
safeSendSignal(agent.pid, 'SIGTERM');
|
|
492
|
+
}, GRACE_PERIOD_MS + ESCALATION_STEP_MS);
|
|
493
|
+
// Step 3: SIGKILL after grace period + two escalation steps
|
|
494
|
+
setTimeout(() => {
|
|
495
|
+
if (!runningAgents.has(agent.sessionId))
|
|
496
|
+
return;
|
|
497
|
+
console.error(`[shutdown] Agent for "${deliveryName}" still running. Sending SIGKILL...`);
|
|
498
|
+
safeSendSignal(agent.pid, 'SIGKILL');
|
|
499
|
+
}, GRACE_PERIOD_MS + ESCALATION_STEP_MS * 2);
|
|
500
|
+
// Clean up timer if process exits before grace period
|
|
501
|
+
const checkExit = setInterval(() => {
|
|
502
|
+
if (!runningAgents.has(agent.sessionId)) {
|
|
503
|
+
clearTimeout(graceTimer);
|
|
504
|
+
clearInterval(checkExit);
|
|
505
|
+
}
|
|
506
|
+
}, 1000);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Terminate a running agent.
|
|
510
|
+
* Sends a shutdown message via stdin, then escalates to SIGTERM/SIGKILL.
|
|
511
|
+
*/
|
|
512
|
+
export function terminateAgent(sessionId) {
|
|
513
|
+
const agent = runningAgents.get(sessionId);
|
|
514
|
+
if (!agent)
|
|
515
|
+
return false;
|
|
516
|
+
// Clear timeout timers since we're manually terminating
|
|
517
|
+
if (agent.warningTimer)
|
|
518
|
+
clearTimeout(agent.warningTimer);
|
|
519
|
+
if (agent.timeoutTimer)
|
|
520
|
+
clearTimeout(agent.timeoutTimer);
|
|
521
|
+
try {
|
|
522
|
+
// Send shutdown message, then escalate with flat setTimeout chain
|
|
523
|
+
agent.streamState = 'shutting_down';
|
|
524
|
+
sendMessage(agent.stdin, 'Session is being terminated. Commit your work and exit immediately.');
|
|
525
|
+
// Step 1: Close stdin after 5s
|
|
526
|
+
setTimeout(() => safeCloseStdin(agent.stdin), 5000);
|
|
527
|
+
// Step 2: SIGTERM after 10s (5s after stdin close)
|
|
528
|
+
setTimeout(() => {
|
|
529
|
+
if (!runningAgents.has(sessionId))
|
|
530
|
+
return;
|
|
531
|
+
safeSendSignal(agent.pid, 'SIGTERM');
|
|
532
|
+
}, 10000);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
console.error(`Failed to terminate agent ${sessionId}:`, err instanceof Error ? err.message : String(err));
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Terminate all running agents.
|
|
542
|
+
*/
|
|
543
|
+
export function terminateAllAgents() {
|
|
544
|
+
for (const [sessionId] of runningAgents) {
|
|
545
|
+
terminateAgent(sessionId);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Check if an agent is running for a session.
|
|
550
|
+
*/
|
|
551
|
+
export function isAgentRunning(sessionId) {
|
|
552
|
+
return runningAgents.has(sessionId);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Get count of running agents.
|
|
556
|
+
*/
|
|
557
|
+
export function getRunningAgentCount() {
|
|
558
|
+
return runningAgents.size;
|
|
559
|
+
}
|
|
560
|
+
// ── Merge Conflict Resolution Agent ──────────────────────────────────
|
|
561
|
+
/** Timeout for the resolution agent (10 minutes). */
|
|
562
|
+
const RESOLUTION_AGENT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
563
|
+
/**
|
|
564
|
+
* Spawn a short-lived, single-purpose Claude Code agent for merge conflict resolution.
|
|
565
|
+
*
|
|
566
|
+
* Used when no active team exists for a strategy but merge conflicts need resolution.
|
|
567
|
+
* The agent receives a focused prompt with conflict details, resolves in the existing
|
|
568
|
+
* worktree, and exits. No session DB record, no streaming, no team mode.
|
|
569
|
+
*/
|
|
570
|
+
export async function spawnResolutionAgent(params) {
|
|
571
|
+
const { config, worktreePath, branchName, integrationBranch, conflictFiles, strategyDescription } = params;
|
|
572
|
+
const prompt = [
|
|
573
|
+
`You are resolving merge conflicts in a git worktree.`,
|
|
574
|
+
``,
|
|
575
|
+
`**Worktree:** ${worktreePath}`,
|
|
576
|
+
`**Branch:** ${branchName}`,
|
|
577
|
+
`**Merge source:** origin/${integrationBranch}`,
|
|
578
|
+
``,
|
|
579
|
+
`**Conflicted files:**`,
|
|
580
|
+
...conflictFiles.map(f => `- ${f}`),
|
|
581
|
+
``,
|
|
582
|
+
`**Strategy context:** ${strategyDescription}`,
|
|
583
|
+
``,
|
|
584
|
+
`**Instructions:**`,
|
|
585
|
+
`1. Open each conflicted file and resolve the conflict markers (<<<<<<< / ======= / >>>>>>>)`,
|
|
586
|
+
`2. Stage each resolved file with: git add <file>`,
|
|
587
|
+
`3. Commit the merge resolution: git commit --no-edit`,
|
|
588
|
+
`4. Run: npm run build && npm test`,
|
|
589
|
+
`5. If build or test fails, fix the issues, commit, and re-run until passing`,
|
|
590
|
+
``,
|
|
591
|
+
`Do NOT run git merge --abort. Your goal is to produce a clean merge commit that passes build and tests.`,
|
|
592
|
+
`Follow the coding standards in CLAUDE.md.`,
|
|
593
|
+
].join('\n');
|
|
594
|
+
const args = [
|
|
595
|
+
'--print',
|
|
596
|
+
'--dangerously-skip-permissions',
|
|
597
|
+
'--setting-sources', 'project,local',
|
|
598
|
+
prompt,
|
|
599
|
+
];
|
|
600
|
+
// Build a clean environment (strip Claude Code session vars)
|
|
601
|
+
const spawnEnv = stripClaudeCodeEnvVars(process.env);
|
|
602
|
+
console.log(`[spawner] Spawning resolution agent for branch ${branchName} (${conflictFiles.length} conflicts)`);
|
|
603
|
+
return new Promise((resolve) => {
|
|
604
|
+
const proc = spawn(config.claudeCodePath, args, {
|
|
605
|
+
cwd: worktreePath,
|
|
606
|
+
env: spawnEnv,
|
|
607
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
608
|
+
});
|
|
609
|
+
// Close stdin immediately -- prompt is passed as CLI arg
|
|
610
|
+
proc.stdin?.end();
|
|
611
|
+
// Timeout with escalating shutdown
|
|
612
|
+
const timeout = setTimeout(() => {
|
|
613
|
+
console.warn(`[spawner] Resolution agent timed out after ${RESOLUTION_AGENT_TIMEOUT_MS / 1000}s, killing`);
|
|
614
|
+
try {
|
|
615
|
+
process.kill(proc.pid, 'SIGTERM');
|
|
616
|
+
}
|
|
617
|
+
catch { /* ignore */ }
|
|
618
|
+
setTimeout(() => {
|
|
619
|
+
try {
|
|
620
|
+
process.kill(proc.pid, 'SIGKILL');
|
|
621
|
+
}
|
|
622
|
+
catch { /* ignore */ }
|
|
623
|
+
}, 10000);
|
|
624
|
+
}, RESOLUTION_AGENT_TIMEOUT_MS);
|
|
625
|
+
proc.on('close', (code) => {
|
|
626
|
+
clearTimeout(timeout);
|
|
627
|
+
// Verify resolution by checking for remaining unmerged files
|
|
628
|
+
const unmergedResult = runGitSync(['diff', '--name-only', '--diff-filter=U'], worktreePath);
|
|
629
|
+
const unmergedFiles = unmergedResult.success
|
|
630
|
+
? unmergedResult.output.split('\n').filter(Boolean)
|
|
631
|
+
: [];
|
|
632
|
+
if (unmergedFiles.length === 0) {
|
|
633
|
+
console.log(`[spawner] Resolution agent succeeded for branch ${branchName} (exit code: ${code})`);
|
|
634
|
+
resolve({ success: true });
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
const msg = `Conflicts remain in: ${unmergedFiles.join(', ')}`;
|
|
638
|
+
console.warn(`[spawner] Resolution agent failed for branch ${branchName}: ${msg}`);
|
|
639
|
+
resolve({ success: false, error: msg });
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
proc.on('error', (err) => {
|
|
643
|
+
clearTimeout(timeout);
|
|
644
|
+
console.error(`[spawner] Resolution agent spawn error: ${err.message}`);
|
|
645
|
+
resolve({ success: false, error: `Spawn error: ${err.message}` });
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
// ── Stale Agent Process Detection ────────────────────────────────────
|
|
650
|
+
/**
|
|
651
|
+
* Check if a process is alive. Returns false if the process doesn't exist
|
|
652
|
+
* or is a zombie on Linux.
|
|
653
|
+
*/
|
|
654
|
+
export function isProcessAlive(pid) {
|
|
655
|
+
// Check if PID exists
|
|
656
|
+
try {
|
|
657
|
+
process.kill(pid, 0);
|
|
658
|
+
}
|
|
659
|
+
catch (e) {
|
|
660
|
+
console.debug(`[spawner] isProcessAlive(${pid}): process not found (ESRCH):`, e.message);
|
|
661
|
+
return false;
|
|
662
|
+
}
|
|
663
|
+
// On Linux, check for zombie state via /proc/{pid}/status
|
|
664
|
+
if (process.platform === 'linux') {
|
|
665
|
+
try {
|
|
666
|
+
const status = readFileSync(`/proc/${pid}/status`, 'utf-8');
|
|
667
|
+
const stateMatch = status.match(/^State:\s+(\S)/m);
|
|
668
|
+
if (stateMatch && stateMatch[1] === 'Z') {
|
|
669
|
+
return false; // Zombie — won't produce an exit event
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch (e) {
|
|
673
|
+
console.debug(`[spawner] isProcessAlive(${pid}): /proc read failed, treating as dead:`, e.message);
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Check all running agents for PID liveness.
|
|
681
|
+
*
|
|
682
|
+
* If an agent's process has disappeared (OOM kill, external kill -9, etc.)
|
|
683
|
+
* without firing a 'close' event, clean up the agent entry and update the
|
|
684
|
+
* session in the database.
|
|
685
|
+
*
|
|
686
|
+
* Guards against double-cleanup by checking runningAgents.has() before acting.
|
|
687
|
+
*/
|
|
688
|
+
export async function checkAgentLiveness(config) {
|
|
689
|
+
if (runningAgents.size === 0)
|
|
690
|
+
return;
|
|
691
|
+
// Snapshot keys to avoid mutating the map while iterating
|
|
692
|
+
const entries = [...runningAgents.entries()];
|
|
693
|
+
for (const [sessionId, agent] of entries) {
|
|
694
|
+
// Guard: agent may have been cleaned up by a normal exit event since we started
|
|
695
|
+
if (!runningAgents.has(sessionId))
|
|
696
|
+
continue;
|
|
697
|
+
if (isProcessAlive(agent.pid))
|
|
698
|
+
continue;
|
|
699
|
+
// PID is gone — stale agent detected
|
|
700
|
+
const runtime = Math.round((Date.now() - agent.startedAt.getTime()) / 1000);
|
|
701
|
+
console.error(`[liveness] Stale agent detected: "${agent.deliveryName}" ` +
|
|
702
|
+
`(session: ${sessionId}, PID: ${agent.pid}, runtime: ${runtime}s). ` +
|
|
703
|
+
`Process disappeared — possible OOM or external kill.`);
|
|
704
|
+
// Clear timeout timers
|
|
705
|
+
if (agent.warningTimer)
|
|
706
|
+
clearTimeout(agent.warningTimer);
|
|
707
|
+
if (agent.timeoutTimer)
|
|
708
|
+
clearTimeout(agent.timeoutTimer);
|
|
709
|
+
// Remove from tracking map
|
|
710
|
+
runningAgents.delete(sessionId);
|
|
711
|
+
// Update session in database
|
|
712
|
+
try {
|
|
713
|
+
await updateSession(sessionId, {
|
|
714
|
+
status: 'failed',
|
|
715
|
+
exit_reason: 'Process disappeared — possible OOM or external kill',
|
|
716
|
+
ended_at: new Date().toISOString(),
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
console.error(`[liveness] Failed to update session ${sessionId}:`, err.message);
|
|
721
|
+
}
|
|
722
|
+
// Record session completion for heartbeat counters
|
|
723
|
+
recordSessionCompleted(0, 0);
|
|
724
|
+
// Clean up worktree (safety guards in removeWorktree protect uncommitted work)
|
|
725
|
+
// Use agent.repoPath (captured at spawn time) so multi-product agents clean up in the correct repo
|
|
726
|
+
removeWorktree(agent.repoPath, agent.worktreePath, {
|
|
727
|
+
branchName: agent.branchName,
|
|
728
|
+
integrationBranch: config.integrationBranch,
|
|
729
|
+
deliveryName: agent.deliveryName,
|
|
730
|
+
mergeSucceeded: false,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
//# sourceMappingURL=spawner.js.map
|