lorenz 0.1.0
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/LICENSE +201 -0
- package/NOTICE +13 -0
- package/README.md +774 -0
- package/RELEASE-MANIFEST.json +211 -0
- package/apps/cli/bin/lorenz.js +25 -0
- package/apps/cli/dist/bin/cli.d.ts +3 -0
- package/apps/cli/dist/bin/cli.d.ts.map +1 -0
- package/apps/cli/dist/bin/cli.js +4 -0
- package/apps/cli/dist/bin/cli.js.map +1 -0
- package/apps/cli/dist/daemon.d.ts +76 -0
- package/apps/cli/dist/daemon.d.ts.map +1 -0
- package/apps/cli/dist/daemon.js +189 -0
- package/apps/cli/dist/daemon.js.map +1 -0
- package/apps/cli/dist/doctor.d.ts +40 -0
- package/apps/cli/dist/doctor.d.ts.map +1 -0
- package/apps/cli/dist/doctor.js +590 -0
- package/apps/cli/dist/doctor.js.map +1 -0
- package/apps/cli/dist/index.d.ts +32 -0
- package/apps/cli/dist/index.d.ts.map +1 -0
- package/apps/cli/dist/index.js +26 -0
- package/apps/cli/dist/index.js.map +1 -0
- package/apps/cli/dist/main.d.ts +40 -0
- package/apps/cli/dist/main.d.ts.map +1 -0
- package/apps/cli/dist/main.js +259 -0
- package/apps/cli/dist/main.js.map +1 -0
- package/apps/cli/dist/runs.d.ts +31 -0
- package/apps/cli/dist/runs.d.ts.map +1 -0
- package/apps/cli/dist/runs.js +281 -0
- package/apps/cli/dist/runs.js.map +1 -0
- package/apps/cli/dist/workerDriverLoader.d.ts +64 -0
- package/apps/cli/dist/workerDriverLoader.d.ts.map +1 -0
- package/apps/cli/dist/workerDriverLoader.js +211 -0
- package/apps/cli/dist/workerDriverLoader.js.map +1 -0
- package/apps/cli/package.json +57 -0
- package/apps/symphony-dashboard/dist/assets/index-B3owF3jd.css +1 -0
- package/apps/symphony-dashboard/dist/assets/index-DQ6XlL0d.js +227 -0
- package/apps/symphony-dashboard/dist/index.html +18 -0
- package/bin/lorenz +16 -0
- package/extensions/docker-worker/dist/index.d.ts +92 -0
- package/extensions/docker-worker/dist/index.d.ts.map +1 -0
- package/extensions/docker-worker/dist/index.js +283 -0
- package/extensions/docker-worker/dist/index.js.map +1 -0
- package/extensions/docker-worker/package.json +14 -0
- package/extensions/jira-tracker/dist/client.d.ts +50 -0
- package/extensions/jira-tracker/dist/client.d.ts.map +1 -0
- package/extensions/jira-tracker/dist/client.js +619 -0
- package/extensions/jira-tracker/dist/client.js.map +1 -0
- package/extensions/jira-tracker/dist/index.d.ts +5 -0
- package/extensions/jira-tracker/dist/index.d.ts.map +1 -0
- package/extensions/jira-tracker/dist/index.js +5 -0
- package/extensions/jira-tracker/dist/index.js.map +1 -0
- package/extensions/jira-tracker/dist/options.d.ts +38 -0
- package/extensions/jira-tracker/dist/options.d.ts.map +1 -0
- package/extensions/jira-tracker/dist/options.js +61 -0
- package/extensions/jira-tracker/dist/options.js.map +1 -0
- package/extensions/jira-tracker/dist/provider.d.ts +6 -0
- package/extensions/jira-tracker/dist/provider.d.ts.map +1 -0
- package/extensions/jira-tracker/dist/provider.js +178 -0
- package/extensions/jira-tracker/dist/provider.js.map +1 -0
- package/extensions/jira-tracker/dist/register.d.ts +10 -0
- package/extensions/jira-tracker/dist/register.d.ts.map +1 -0
- package/extensions/jira-tracker/dist/register.js +15 -0
- package/extensions/jira-tracker/dist/register.js.map +1 -0
- package/extensions/jira-tracker/package.json +16 -0
- package/extensions/linear-tracker/dist/client.d.ts +82 -0
- package/extensions/linear-tracker/dist/client.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/client.js +622 -0
- package/extensions/linear-tracker/dist/client.js.map +1 -0
- package/extensions/linear-tracker/dist/index.d.ts +8 -0
- package/extensions/linear-tracker/dist/index.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/index.js +7 -0
- package/extensions/linear-tracker/dist/index.js.map +1 -0
- package/extensions/linear-tracker/dist/options.d.ts +32 -0
- package/extensions/linear-tracker/dist/options.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/options.js +59 -0
- package/extensions/linear-tracker/dist/options.js.map +1 -0
- package/extensions/linear-tracker/dist/provider.d.ts +4 -0
- package/extensions/linear-tracker/dist/provider.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/provider.js +58 -0
- package/extensions/linear-tracker/dist/provider.js.map +1 -0
- package/extensions/linear-tracker/dist/register.d.ts +11 -0
- package/extensions/linear-tracker/dist/register.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/register.js +19 -0
- package/extensions/linear-tracker/dist/register.js.map +1 -0
- package/extensions/linear-tracker/dist/toolOps.d.ts +8 -0
- package/extensions/linear-tracker/dist/toolOps.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/toolOps.js +160 -0
- package/extensions/linear-tracker/dist/toolOps.js.map +1 -0
- package/extensions/linear-tracker/dist/tools.d.ts +7 -0
- package/extensions/linear-tracker/dist/tools.d.ts.map +1 -0
- package/extensions/linear-tracker/dist/tools.js +210 -0
- package/extensions/linear-tracker/dist/tools.js.map +1 -0
- package/extensions/linear-tracker/package.json +18 -0
- package/extensions/local-tracker/dist/boardStore.d.ts +116 -0
- package/extensions/local-tracker/dist/boardStore.d.ts.map +1 -0
- package/extensions/local-tracker/dist/boardStore.js +475 -0
- package/extensions/local-tracker/dist/boardStore.js.map +1 -0
- package/extensions/local-tracker/dist/client.d.ts +14 -0
- package/extensions/local-tracker/dist/client.d.ts.map +1 -0
- package/extensions/local-tracker/dist/client.js +27 -0
- package/extensions/local-tracker/dist/client.js.map +1 -0
- package/extensions/local-tracker/dist/index.d.ts +7 -0
- package/extensions/local-tracker/dist/index.d.ts.map +1 -0
- package/extensions/local-tracker/dist/index.js +7 -0
- package/extensions/local-tracker/dist/index.js.map +1 -0
- package/extensions/local-tracker/dist/options.d.ts +31 -0
- package/extensions/local-tracker/dist/options.d.ts.map +1 -0
- package/extensions/local-tracker/dist/options.js +69 -0
- package/extensions/local-tracker/dist/options.js.map +1 -0
- package/extensions/local-tracker/dist/provider.d.ts +9 -0
- package/extensions/local-tracker/dist/provider.d.ts.map +1 -0
- package/extensions/local-tracker/dist/provider.js +35 -0
- package/extensions/local-tracker/dist/provider.js.map +1 -0
- package/extensions/local-tracker/dist/register.d.ts +11 -0
- package/extensions/local-tracker/dist/register.d.ts.map +1 -0
- package/extensions/local-tracker/dist/register.js +19 -0
- package/extensions/local-tracker/dist/register.js.map +1 -0
- package/extensions/local-tracker/dist/resolveBoardDir.d.ts +24 -0
- package/extensions/local-tracker/dist/resolveBoardDir.d.ts.map +1 -0
- package/extensions/local-tracker/dist/resolveBoardDir.js +39 -0
- package/extensions/local-tracker/dist/resolveBoardDir.js.map +1 -0
- package/extensions/local-tracker/dist/toolOps.d.ts +9 -0
- package/extensions/local-tracker/dist/toolOps.d.ts.map +1 -0
- package/extensions/local-tracker/dist/toolOps.js +86 -0
- package/extensions/local-tracker/dist/toolOps.js.map +1 -0
- package/extensions/local-tracker/dist/tools.d.ts +7 -0
- package/extensions/local-tracker/dist/tools.d.ts.map +1 -0
- package/extensions/local-tracker/dist/tools.js +170 -0
- package/extensions/local-tracker/dist/tools.js.map +1 -0
- package/extensions/local-tracker/package.json +18 -0
- package/extensions/memory-tracker/dist/index.d.ts +24 -0
- package/extensions/memory-tracker/dist/index.d.ts.map +1 -0
- package/extensions/memory-tracker/dist/index.js +110 -0
- package/extensions/memory-tracker/dist/index.js.map +1 -0
- package/extensions/memory-tracker/package.json +16 -0
- package/extensions/slack-tracker/dist/client.d.ts +88 -0
- package/extensions/slack-tracker/dist/client.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/client.js +246 -0
- package/extensions/slack-tracker/dist/client.js.map +1 -0
- package/extensions/slack-tracker/dist/inMemoryTransport.d.ts +42 -0
- package/extensions/slack-tracker/dist/inMemoryTransport.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/inMemoryTransport.js +104 -0
- package/extensions/slack-tracker/dist/inMemoryTransport.js.map +1 -0
- package/extensions/slack-tracker/dist/index.d.ts +15 -0
- package/extensions/slack-tracker/dist/index.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/index.js +11 -0
- package/extensions/slack-tracker/dist/index.js.map +1 -0
- package/extensions/slack-tracker/dist/mapping.d.ts +27 -0
- package/extensions/slack-tracker/dist/mapping.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/mapping.js +109 -0
- package/extensions/slack-tracker/dist/mapping.js.map +1 -0
- package/extensions/slack-tracker/dist/operations.d.ts +41 -0
- package/extensions/slack-tracker/dist/operations.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/operations.js +97 -0
- package/extensions/slack-tracker/dist/operations.js.map +1 -0
- package/extensions/slack-tracker/dist/options.d.ts +30 -0
- package/extensions/slack-tracker/dist/options.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/options.js +49 -0
- package/extensions/slack-tracker/dist/options.js.map +1 -0
- package/extensions/slack-tracker/dist/provider.d.ts +9 -0
- package/extensions/slack-tracker/dist/provider.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/provider.js +74 -0
- package/extensions/slack-tracker/dist/provider.js.map +1 -0
- package/extensions/slack-tracker/dist/register.d.ts +11 -0
- package/extensions/slack-tracker/dist/register.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/register.js +19 -0
- package/extensions/slack-tracker/dist/register.js.map +1 -0
- package/extensions/slack-tracker/dist/threadState.d.ts +52 -0
- package/extensions/slack-tracker/dist/threadState.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/threadState.js +192 -0
- package/extensions/slack-tracker/dist/threadState.js.map +1 -0
- package/extensions/slack-tracker/dist/toolOps.d.ts +13 -0
- package/extensions/slack-tracker/dist/toolOps.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/toolOps.js +76 -0
- package/extensions/slack-tracker/dist/toolOps.js.map +1 -0
- package/extensions/slack-tracker/dist/tools.d.ts +8 -0
- package/extensions/slack-tracker/dist/tools.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/tools.js +266 -0
- package/extensions/slack-tracker/dist/tools.js.map +1 -0
- package/extensions/slack-tracker/dist/transport.d.ts +63 -0
- package/extensions/slack-tracker/dist/transport.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/transport.js +2 -0
- package/extensions/slack-tracker/dist/transport.js.map +1 -0
- package/extensions/slack-tracker/dist/webTransport.d.ts +44 -0
- package/extensions/slack-tracker/dist/webTransport.d.ts.map +1 -0
- package/extensions/slack-tracker/dist/webTransport.js +402 -0
- package/extensions/slack-tracker/dist/webTransport.js.map +1 -0
- package/extensions/slack-tracker/package.json +17 -0
- package/package.json +89 -0
- package/packages/acp/dist/childProcess.d.ts +4 -0
- package/packages/acp/dist/childProcess.d.ts.map +1 -0
- package/packages/acp/dist/childProcess.js +33 -0
- package/packages/acp/dist/childProcess.js.map +1 -0
- package/packages/acp/dist/index.d.ts +70 -0
- package/packages/acp/dist/index.d.ts.map +1 -0
- package/packages/acp/dist/index.js +701 -0
- package/packages/acp/dist/index.js.map +1 -0
- package/packages/acp/dist/options.d.ts +24 -0
- package/packages/acp/dist/options.d.ts.map +1 -0
- package/packages/acp/dist/options.js +92 -0
- package/packages/acp/dist/options.js.map +1 -0
- package/packages/acp/dist/toml.d.ts +2 -0
- package/packages/acp/dist/toml.d.ts.map +1 -0
- package/packages/acp/dist/toml.js +51 -0
- package/packages/acp/dist/toml.js.map +1 -0
- package/packages/acp/package.json +24 -0
- package/packages/agent-runner/dist/index.d.ts +58 -0
- package/packages/agent-runner/dist/index.d.ts.map +1 -0
- package/packages/agent-runner/dist/index.js +288 -0
- package/packages/agent-runner/dist/index.js.map +1 -0
- package/packages/agent-runner/package.json +19 -0
- package/packages/agent-sdk/dist/index.d.ts +2 -0
- package/packages/agent-sdk/dist/index.d.ts.map +1 -0
- package/packages/agent-sdk/dist/index.js +2 -0
- package/packages/agent-sdk/dist/index.js.map +1 -0
- package/packages/agent-sdk/dist/provider.d.ts +66 -0
- package/packages/agent-sdk/dist/provider.d.ts.map +1 -0
- package/packages/agent-sdk/dist/provider.js +38 -0
- package/packages/agent-sdk/dist/provider.js.map +1 -0
- package/packages/agent-sdk/package.json +14 -0
- package/packages/cli-kit/dist/index.d.ts +20 -0
- package/packages/cli-kit/dist/index.d.ts.map +1 -0
- package/packages/cli-kit/dist/index.js +72 -0
- package/packages/cli-kit/dist/index.js.map +1 -0
- package/packages/cli-kit/package.json +14 -0
- package/packages/config/dist/aliases.d.ts +10 -0
- package/packages/config/dist/aliases.d.ts.map +1 -0
- package/packages/config/dist/aliases.js +153 -0
- package/packages/config/dist/aliases.js.map +1 -0
- package/packages/config/dist/defaults.d.ts +12 -0
- package/packages/config/dist/defaults.d.ts.map +1 -0
- package/packages/config/dist/defaults.js +78 -0
- package/packages/config/dist/defaults.js.map +1 -0
- package/packages/config/dist/errors.d.ts +3 -0
- package/packages/config/dist/errors.d.ts.map +1 -0
- package/packages/config/dist/errors.js +56 -0
- package/packages/config/dist/errors.js.map +1 -0
- package/packages/config/dist/index.d.ts +5 -0
- package/packages/config/dist/index.d.ts.map +1 -0
- package/packages/config/dist/index.js +4 -0
- package/packages/config/dist/index.js.map +1 -0
- package/packages/config/dist/leaf-utils.d.ts +3 -0
- package/packages/config/dist/leaf-utils.d.ts.map +1 -0
- package/packages/config/dist/leaf-utils.js +9 -0
- package/packages/config/dist/leaf-utils.js.map +1 -0
- package/packages/config/dist/parse.d.ts +11 -0
- package/packages/config/dist/parse.d.ts.map +1 -0
- package/packages/config/dist/parse.js +821 -0
- package/packages/config/dist/parse.js.map +1 -0
- package/packages/config/dist/schemas.d.ts +214 -0
- package/packages/config/dist/schemas.d.ts.map +1 -0
- package/packages/config/dist/schemas.js +248 -0
- package/packages/config/dist/schemas.js.map +1 -0
- package/packages/config/package.json +19 -0
- package/packages/dispatch/dist/index.d.ts +22 -0
- package/packages/dispatch/dist/index.d.ts.map +1 -0
- package/packages/dispatch/dist/index.js +117 -0
- package/packages/dispatch/dist/index.js.map +1 -0
- package/packages/dispatch/package.json +16 -0
- package/packages/dispatch-coordinator/dist/coordinator.d.ts +158 -0
- package/packages/dispatch-coordinator/dist/coordinator.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/coordinator.js +529 -0
- package/packages/dispatch-coordinator/dist/coordinator.js.map +1 -0
- package/packages/dispatch-coordinator/dist/gate.d.ts +24 -0
- package/packages/dispatch-coordinator/dist/gate.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/gate.js +47 -0
- package/packages/dispatch-coordinator/dist/gate.js.map +1 -0
- package/packages/dispatch-coordinator/dist/index.d.ts +6 -0
- package/packages/dispatch-coordinator/dist/index.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/index.js +16 -0
- package/packages/dispatch-coordinator/dist/index.js.map +1 -0
- package/packages/dispatch-coordinator/dist/mcpEndpointManager.d.ts +28 -0
- package/packages/dispatch-coordinator/dist/mcpEndpointManager.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/mcpEndpointManager.js +54 -0
- package/packages/dispatch-coordinator/dist/mcpEndpointManager.js.map +1 -0
- package/packages/dispatch-coordinator/dist/nullEndpointManager.d.ts +18 -0
- package/packages/dispatch-coordinator/dist/nullEndpointManager.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/nullEndpointManager.js +40 -0
- package/packages/dispatch-coordinator/dist/nullEndpointManager.js.map +1 -0
- package/packages/dispatch-coordinator/dist/types.d.ts +119 -0
- package/packages/dispatch-coordinator/dist/types.d.ts.map +1 -0
- package/packages/dispatch-coordinator/dist/types.js +17 -0
- package/packages/dispatch-coordinator/dist/types.js.map +1 -0
- package/packages/dispatch-coordinator/package.json +16 -0
- package/packages/domain/dist/index.d.ts +775 -0
- package/packages/domain/dist/index.d.ts.map +1 -0
- package/packages/domain/dist/index.js +124 -0
- package/packages/domain/dist/index.js.map +1 -0
- package/packages/domain/package.json +14 -0
- package/packages/humanize/dist/index.d.ts +4 -0
- package/packages/humanize/dist/index.d.ts.map +1 -0
- package/packages/humanize/dist/index.js +347 -0
- package/packages/humanize/dist/index.js.map +1 -0
- package/packages/humanize/package.json +11 -0
- package/packages/issue/dist/index.d.ts +7 -0
- package/packages/issue/dist/index.d.ts.map +1 -0
- package/packages/issue/dist/index.js +147 -0
- package/packages/issue/dist/index.js.map +1 -0
- package/packages/issue/package.json +14 -0
- package/packages/log-file/dist/index.d.ts +10 -0
- package/packages/log-file/dist/index.d.ts.map +1 -0
- package/packages/log-file/dist/index.js +200 -0
- package/packages/log-file/dist/index.js.map +1 -0
- package/packages/log-file/package.json +15 -0
- package/packages/mcp/dist/agentEndpoint.d.ts +31 -0
- package/packages/mcp/dist/agentEndpoint.d.ts.map +1 -0
- package/packages/mcp/dist/agentEndpoint.js +270 -0
- package/packages/mcp/dist/agentEndpoint.js.map +1 -0
- package/packages/mcp/dist/auth.d.ts +7 -0
- package/packages/mcp/dist/auth.d.ts.map +1 -0
- package/packages/mcp/dist/auth.js +48 -0
- package/packages/mcp/dist/auth.js.map +1 -0
- package/packages/mcp/dist/filter.d.ts +70 -0
- package/packages/mcp/dist/filter.d.ts.map +1 -0
- package/packages/mcp/dist/filter.js +231 -0
- package/packages/mcp/dist/filter.js.map +1 -0
- package/packages/mcp/dist/index.d.ts +7 -0
- package/packages/mcp/dist/index.d.ts.map +1 -0
- package/packages/mcp/dist/index.js +5 -0
- package/packages/mcp/dist/index.js.map +1 -0
- package/packages/mcp/dist/server.d.ts +31 -0
- package/packages/mcp/dist/server.d.ts.map +1 -0
- package/packages/mcp/dist/server.js +176 -0
- package/packages/mcp/dist/server.js.map +1 -0
- package/packages/mcp/dist/tools/linear.d.ts +5 -0
- package/packages/mcp/dist/tools/linear.d.ts.map +1 -0
- package/packages/mcp/dist/tools/linear.js +192 -0
- package/packages/mcp/dist/tools/linear.js.map +1 -0
- package/packages/mcp/dist/tools/local.d.ts +5 -0
- package/packages/mcp/dist/tools/local.d.ts.map +1 -0
- package/packages/mcp/dist/tools/local.js +161 -0
- package/packages/mcp/dist/tools/local.js.map +1 -0
- package/packages/mcp/dist/tools/result.d.ts +5 -0
- package/packages/mcp/dist/tools/result.d.ts.map +1 -0
- package/packages/mcp/dist/tools/result.js +15 -0
- package/packages/mcp/dist/tools/result.js.map +1 -0
- package/packages/mcp/dist/tools.d.ts +14 -0
- package/packages/mcp/dist/tools.d.ts.map +1 -0
- package/packages/mcp/dist/tools.js +58 -0
- package/packages/mcp/dist/tools.js.map +1 -0
- package/packages/mcp/package.json +20 -0
- package/packages/orchestrator/dist/index.d.ts +171 -0
- package/packages/orchestrator/dist/index.d.ts.map +1 -0
- package/packages/orchestrator/dist/index.js +524 -0
- package/packages/orchestrator/dist/index.js.map +1 -0
- package/packages/orchestrator/package.json +18 -0
- package/packages/policies/dist/index.d.ts +11 -0
- package/packages/policies/dist/index.d.ts.map +1 -0
- package/packages/policies/dist/index.js +6 -0
- package/packages/policies/dist/index.js.map +1 -0
- package/packages/policies/dist/reconciliation.d.ts +5 -0
- package/packages/policies/dist/reconciliation.d.ts.map +1 -0
- package/packages/policies/dist/reconciliation.js +17 -0
- package/packages/policies/dist/reconciliation.js.map +1 -0
- package/packages/policies/dist/resume.d.ts +14 -0
- package/packages/policies/dist/resume.d.ts.map +1 -0
- package/packages/policies/dist/resume.js +7 -0
- package/packages/policies/dist/resume.js.map +1 -0
- package/packages/policies/dist/retry.d.ts +4 -0
- package/packages/policies/dist/retry.d.ts.map +1 -0
- package/packages/policies/dist/retry.js +7 -0
- package/packages/policies/dist/retry.js.map +1 -0
- package/packages/policies/dist/stopReason.d.ts +4 -0
- package/packages/policies/dist/stopReason.d.ts.map +1 -0
- package/packages/policies/dist/stopReason.js +11 -0
- package/packages/policies/dist/stopReason.js.map +1 -0
- package/packages/policies/dist/usage.d.ts +14 -0
- package/packages/policies/dist/usage.d.ts.map +1 -0
- package/packages/policies/dist/usage.js +38 -0
- package/packages/policies/dist/usage.js.map +1 -0
- package/packages/policies/dist/workerHost.d.ts +8 -0
- package/packages/policies/dist/workerHost.d.ts.map +1 -0
- package/packages/policies/dist/workerHost.js +20 -0
- package/packages/policies/dist/workerHost.js.map +1 -0
- package/packages/policies/package.json +21 -0
- package/packages/presenter/dist/index.d.ts +81 -0
- package/packages/presenter/dist/index.d.ts.map +1 -0
- package/packages/presenter/dist/index.js +421 -0
- package/packages/presenter/dist/index.js.map +1 -0
- package/packages/presenter/package.json +16 -0
- package/packages/projections/dist/index.d.ts +10 -0
- package/packages/projections/dist/index.d.ts.map +1 -0
- package/packages/projections/dist/index.js +30 -0
- package/packages/projections/dist/index.js.map +1 -0
- package/packages/projections/package.json +15 -0
- package/packages/prompt/dist/index.d.ts +9 -0
- package/packages/prompt/dist/index.d.ts.map +1 -0
- package/packages/prompt/dist/index.js +71 -0
- package/packages/prompt/dist/index.js.map +1 -0
- package/packages/prompt/package.json +16 -0
- package/packages/retry-scheduler/dist/index.d.ts +12 -0
- package/packages/retry-scheduler/dist/index.d.ts.map +1 -0
- package/packages/retry-scheduler/dist/index.js +39 -0
- package/packages/retry-scheduler/dist/index.js.map +1 -0
- package/packages/retry-scheduler/package.json +15 -0
- package/packages/runtime/dist/index.d.ts +157 -0
- package/packages/runtime/dist/index.d.ts.map +1 -0
- package/packages/runtime/dist/index.js +1074 -0
- package/packages/runtime/dist/index.js.map +1 -0
- package/packages/runtime/package.json +26 -0
- package/packages/runtime-events/dist/index.d.ts +110 -0
- package/packages/runtime-events/dist/index.d.ts.map +1 -0
- package/packages/runtime-events/dist/index.js +25 -0
- package/packages/runtime-events/dist/index.js.map +1 -0
- package/packages/runtime-events/package.json +14 -0
- package/packages/server/dist/index.d.ts +25 -0
- package/packages/server/dist/index.d.ts.map +1 -0
- package/packages/server/dist/index.js +213 -0
- package/packages/server/dist/index.js.map +1 -0
- package/packages/server/dist/issue-store.d.ts +26 -0
- package/packages/server/dist/issue-store.d.ts.map +1 -0
- package/packages/server/dist/issue-store.js +88 -0
- package/packages/server/dist/issue-store.js.map +1 -0
- package/packages/server/dist/path-params.d.ts +6 -0
- package/packages/server/dist/path-params.d.ts.map +1 -0
- package/packages/server/dist/path-params.js +15 -0
- package/packages/server/dist/path-params.js.map +1 -0
- package/packages/server/dist/source.d.ts +12 -0
- package/packages/server/dist/source.d.ts.map +1 -0
- package/packages/server/dist/source.js +2 -0
- package/packages/server/dist/source.js.map +1 -0
- package/packages/server/dist/trace-routes.d.ts +21 -0
- package/packages/server/dist/trace-routes.d.ts.map +1 -0
- package/packages/server/dist/trace-routes.js +66 -0
- package/packages/server/dist/trace-routes.js.map +1 -0
- package/packages/server/dist/ws.d.ts +18 -0
- package/packages/server/dist/ws.d.ts.map +1 -0
- package/packages/server/dist/ws.js +168 -0
- package/packages/server/dist/ws.js.map +1 -0
- package/packages/server/package.json +22 -0
- package/packages/ssh/dist/index.d.ts +33 -0
- package/packages/ssh/dist/index.d.ts.map +1 -0
- package/packages/ssh/dist/index.js +281 -0
- package/packages/ssh/dist/index.js.map +1 -0
- package/packages/ssh/package.json +15 -0
- package/packages/static-worker/dist/index.d.ts +73 -0
- package/packages/static-worker/dist/index.d.ts.map +1 -0
- package/packages/static-worker/dist/index.js +150 -0
- package/packages/static-worker/dist/index.js.map +1 -0
- package/packages/static-worker/package.json +14 -0
- package/packages/tool-sdk/dist/filter.d.ts +70 -0
- package/packages/tool-sdk/dist/filter.d.ts.map +1 -0
- package/packages/tool-sdk/dist/filter.js +231 -0
- package/packages/tool-sdk/dist/filter.js.map +1 -0
- package/packages/tool-sdk/dist/index.d.ts +6 -0
- package/packages/tool-sdk/dist/index.d.ts.map +1 -0
- package/packages/tool-sdk/dist/index.js +4 -0
- package/packages/tool-sdk/dist/index.js.map +1 -0
- package/packages/tool-sdk/dist/provider.d.ts +51 -0
- package/packages/tool-sdk/dist/provider.d.ts.map +1 -0
- package/packages/tool-sdk/dist/provider.js +2 -0
- package/packages/tool-sdk/dist/provider.js.map +1 -0
- package/packages/tool-sdk/dist/registry.d.ts +35 -0
- package/packages/tool-sdk/dist/registry.d.ts.map +1 -0
- package/packages/tool-sdk/dist/registry.js +85 -0
- package/packages/tool-sdk/dist/registry.js.map +1 -0
- package/packages/tool-sdk/dist/result.d.ts +5 -0
- package/packages/tool-sdk/dist/result.d.ts.map +1 -0
- package/packages/tool-sdk/dist/result.js +15 -0
- package/packages/tool-sdk/dist/result.js.map +1 -0
- package/packages/tool-sdk/package.json +14 -0
- package/packages/traceviz-emitter/dist/index.d.ts +19 -0
- package/packages/traceviz-emitter/dist/index.d.ts.map +1 -0
- package/packages/traceviz-emitter/dist/index.js +97 -0
- package/packages/traceviz-emitter/dist/index.js.map +1 -0
- package/packages/traceviz-emitter/package.json +17 -0
- package/packages/traceviz-server/dist/index.d.ts +14 -0
- package/packages/traceviz-server/dist/index.d.ts.map +1 -0
- package/packages/traceviz-server/dist/index.js +10 -0
- package/packages/traceviz-server/dist/index.js.map +1 -0
- package/packages/traceviz-server/dist/models/api.d.ts +51 -0
- package/packages/traceviz-server/dist/models/api.d.ts.map +1 -0
- package/packages/traceviz-server/dist/models/api.js +5 -0
- package/packages/traceviz-server/dist/models/api.js.map +1 -0
- package/packages/traceviz-server/dist/models/display-events.d.ts +58 -0
- package/packages/traceviz-server/dist/models/display-events.d.ts.map +1 -0
- package/packages/traceviz-server/dist/models/display-events.js +6 -0
- package/packages/traceviz-server/dist/models/display-events.js.map +1 -0
- package/packages/traceviz-server/dist/parser.d.ts +14 -0
- package/packages/traceviz-server/dist/parser.d.ts.map +1 -0
- package/packages/traceviz-server/dist/parser.js +363 -0
- package/packages/traceviz-server/dist/parser.js.map +1 -0
- package/packages/traceviz-server/dist/stats.d.ts +7 -0
- package/packages/traceviz-server/dist/stats.d.ts.map +1 -0
- package/packages/traceviz-server/dist/stats.js +81 -0
- package/packages/traceviz-server/dist/stats.js.map +1 -0
- package/packages/traceviz-server/dist/watcher.d.ts +54 -0
- package/packages/traceviz-server/dist/watcher.d.ts.map +1 -0
- package/packages/traceviz-server/dist/watcher.js +368 -0
- package/packages/traceviz-server/dist/watcher.js.map +1 -0
- package/packages/traceviz-server/package.json +16 -0
- package/packages/tracker-sdk/dist/index.d.ts +5 -0
- package/packages/tracker-sdk/dist/index.d.ts.map +1 -0
- package/packages/tracker-sdk/dist/index.js +4 -0
- package/packages/tracker-sdk/dist/index.js.map +1 -0
- package/packages/tracker-sdk/dist/options.d.ts +20 -0
- package/packages/tracker-sdk/dist/options.d.ts.map +1 -0
- package/packages/tracker-sdk/dist/options.js +46 -0
- package/packages/tracker-sdk/dist/options.js.map +1 -0
- package/packages/tracker-sdk/dist/provider.d.ts +104 -0
- package/packages/tracker-sdk/dist/provider.d.ts.map +1 -0
- package/packages/tracker-sdk/dist/provider.js +2 -0
- package/packages/tracker-sdk/dist/provider.js.map +1 -0
- package/packages/tracker-sdk/dist/registry.d.ts +26 -0
- package/packages/tracker-sdk/dist/registry.d.ts.map +1 -0
- package/packages/tracker-sdk/dist/registry.js +52 -0
- package/packages/tracker-sdk/dist/registry.js.map +1 -0
- package/packages/tracker-sdk/dist/toolPack.d.ts +10 -0
- package/packages/tracker-sdk/dist/toolPack.d.ts.map +1 -0
- package/packages/tracker-sdk/dist/toolPack.js +185 -0
- package/packages/tracker-sdk/dist/toolPack.js.map +1 -0
- package/packages/tracker-sdk/package.json +15 -0
- package/packages/tui/dist/index.d.ts +35 -0
- package/packages/tui/dist/index.d.ts.map +1 -0
- package/packages/tui/dist/index.js +354 -0
- package/packages/tui/dist/index.js.map +1 -0
- package/packages/tui/package.json +18 -0
- package/packages/worker-host-pool/dist/index.d.ts +33 -0
- package/packages/worker-host-pool/dist/index.d.ts.map +1 -0
- package/packages/worker-host-pool/dist/index.js +311 -0
- package/packages/worker-host-pool/dist/index.js.map +1 -0
- package/packages/worker-host-pool/package.json +14 -0
- package/packages/worker-pool/dist/index.d.ts +6 -0
- package/packages/worker-pool/dist/index.d.ts.map +1 -0
- package/packages/worker-pool/dist/index.js +15 -0
- package/packages/worker-pool/dist/index.js.map +1 -0
- package/packages/worker-pool/dist/lease.d.ts +36 -0
- package/packages/worker-pool/dist/lease.d.ts.map +1 -0
- package/packages/worker-pool/dist/lease.js +53 -0
- package/packages/worker-pool/dist/lease.js.map +1 -0
- package/packages/worker-pool/dist/ledger.d.ts +51 -0
- package/packages/worker-pool/dist/ledger.d.ts.map +1 -0
- package/packages/worker-pool/dist/ledger.js +165 -0
- package/packages/worker-pool/dist/ledger.js.map +1 -0
- package/packages/worker-pool/dist/mutex.d.ts +10 -0
- package/packages/worker-pool/dist/mutex.d.ts.map +1 -0
- package/packages/worker-pool/dist/mutex.js +22 -0
- package/packages/worker-pool/dist/mutex.js.map +1 -0
- package/packages/worker-pool/dist/pool.d.ts +33 -0
- package/packages/worker-pool/dist/pool.d.ts.map +1 -0
- package/packages/worker-pool/dist/pool.js +1727 -0
- package/packages/worker-pool/dist/pool.js.map +1 -0
- package/packages/worker-pool/dist/reaper.d.ts +94 -0
- package/packages/worker-pool/dist/reaper.d.ts.map +1 -0
- package/packages/worker-pool/dist/reaper.js +295 -0
- package/packages/worker-pool/dist/reaper.js.map +1 -0
- package/packages/worker-pool/dist/types.d.ts +249 -0
- package/packages/worker-pool/dist/types.d.ts.map +1 -0
- package/packages/worker-pool/dist/types.js +2 -0
- package/packages/worker-pool/dist/types.js.map +1 -0
- package/packages/worker-pool/package.json +16 -0
- package/packages/worker-sdk/dist/conformance.d.ts +64 -0
- package/packages/worker-sdk/dist/conformance.d.ts.map +1 -0
- package/packages/worker-sdk/dist/conformance.js +109 -0
- package/packages/worker-sdk/dist/conformance.js.map +1 -0
- package/packages/worker-sdk/dist/fake.d.ts +76 -0
- package/packages/worker-sdk/dist/fake.d.ts.map +1 -0
- package/packages/worker-sdk/dist/fake.js +142 -0
- package/packages/worker-sdk/dist/fake.js.map +1 -0
- package/packages/worker-sdk/dist/index.d.ts +5 -0
- package/packages/worker-sdk/dist/index.d.ts.map +1 -0
- package/packages/worker-sdk/dist/index.js +10 -0
- package/packages/worker-sdk/dist/index.js.map +1 -0
- package/packages/worker-sdk/dist/module.d.ts +46 -0
- package/packages/worker-sdk/dist/module.d.ts.map +1 -0
- package/packages/worker-sdk/dist/module.js +59 -0
- package/packages/worker-sdk/dist/module.js.map +1 -0
- package/packages/worker-sdk/dist/registry.d.ts +24 -0
- package/packages/worker-sdk/dist/registry.d.ts.map +1 -0
- package/packages/worker-sdk/dist/registry.js +49 -0
- package/packages/worker-sdk/dist/registry.js.map +1 -0
- package/packages/worker-sdk/dist/types.d.ts +138 -0
- package/packages/worker-sdk/dist/types.d.ts.map +1 -0
- package/packages/worker-sdk/dist/types.js +21 -0
- package/packages/worker-sdk/dist/types.js.map +1 -0
- package/packages/worker-sdk/package.json +15 -0
- package/packages/workflow/dist/index.d.ts +33 -0
- package/packages/workflow/dist/index.d.ts.map +1 -0
- package/packages/workflow/dist/index.js +125 -0
- package/packages/workflow/dist/index.js.map +1 -0
- package/packages/workflow/package.json +19 -0
- package/packages/workspace/dist/index.d.ts +70 -0
- package/packages/workspace/dist/index.d.ts.map +1 -0
- package/packages/workspace/dist/index.js +1016 -0
- package/packages/workspace/dist/index.js.map +1 -0
- package/packages/workspace/package.json +17 -0
- package/runtime-deps/anthropic-claude-agent-sdk/LICENSE.md +1 -0
- package/runtime-deps/anthropic-claude-agent-sdk/README.md +65 -0
- package/runtime-deps/anthropic-claude-agent-sdk/agentSdkTypes.d.ts +1 -0
- package/runtime-deps/anthropic-claude-agent-sdk/assistant.d.ts +135 -0
- package/runtime-deps/anthropic-claude-agent-sdk/assistant.mjs +190 -0
- package/runtime-deps/anthropic-claude-agent-sdk/bridge.d.ts +231 -0
- package/runtime-deps/anthropic-claude-agent-sdk/bridge.mjs +168 -0
- package/runtime-deps/anthropic-claude-agent-sdk/browser-sdk.d.ts +53 -0
- package/runtime-deps/anthropic-claude-agent-sdk/browser-sdk.js +93 -0
- package/runtime-deps/anthropic-claude-agent-sdk/extractFromBunfs.d.ts +1 -0
- package/runtime-deps/anthropic-claude-agent-sdk/extractFromBunfs.js +156 -0
- package/runtime-deps/anthropic-claude-agent-sdk/manifest.json +47 -0
- package/runtime-deps/anthropic-claude-agent-sdk/manifest.zst.json +55 -0
- package/runtime-deps/anthropic-claude-agent-sdk/node_modules/.bin/anthropic-ai-sdk +21 -0
- package/runtime-deps/anthropic-claude-agent-sdk/package.json +81 -0
- package/runtime-deps/anthropic-claude-agent-sdk/sdk-tools.d.ts +3170 -0
- package/runtime-deps/anthropic-claude-agent-sdk/sdk.d.ts +6000 -0
- package/runtime-deps/anthropic-claude-agent-sdk/sdk.mjs +119 -0
- package/runtime-deps/openai-codex/README.md +60 -0
- package/runtime-deps/openai-codex/bin/codex.js +229 -0
- package/runtime-deps/openai-codex/bin/rg +79 -0
- package/runtime-deps/openai-codex/package.json +22 -0
- package/vendor/claude-agent-acp/dist/acp-agent.d.ts +239 -0
- package/vendor/claude-agent-acp/dist/acp-agent.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/acp-agent.js +2693 -0
- package/vendor/claude-agent-acp/dist/bundle.js +41230 -0
- package/vendor/claude-agent-acp/dist/index.d.ts +3 -0
- package/vendor/claude-agent-acp/dist/index.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/index.js +67 -0
- package/vendor/claude-agent-acp/dist/lib.d.ts +6 -0
- package/vendor/claude-agent-acp/dist/lib.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/lib.js +5 -0
- package/vendor/claude-agent-acp/dist/settings.d.ts +68 -0
- package/vendor/claude-agent-acp/dist/settings.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/settings.js +182 -0
- package/vendor/claude-agent-acp/dist/tools.d.ts +103 -0
- package/vendor/claude-agent-acp/dist/tools.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/tools.js +713 -0
- package/vendor/claude-agent-acp/dist/utils.d.ts +16 -0
- package/vendor/claude-agent-acp/dist/utils.d.ts.map +1 -0
- package/vendor/claude-agent-acp/dist/utils.js +83 -0
- package/vendor/claude-agent-acp/package.json +23 -0
- package/vendor/codex-acp/dist/index.js +21280 -0
- package/vendor/codex-acp/package.json +17 -0
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
import { issueHasOpenBlockers, issueIsActive, routedToThisWorker, slotKey } from "@lorenz/dispatch";
|
|
2
|
+
import { reconciliationStopReason } from "@lorenz/policies/reconciliation";
|
|
3
|
+
import { isTerminalState } from "@lorenz/issue";
|
|
4
|
+
import { Orchestrator } from "@lorenz/orchestrator";
|
|
5
|
+
import { settingsForIssueState, validateDispatchConfig } from "@lorenz/config";
|
|
6
|
+
import { runAgentAttempt } from "@lorenz/agent-runner";
|
|
7
|
+
import { ProjectionActor } from "@lorenz/projections";
|
|
8
|
+
import { RetryScheduler } from "@lorenz/retry-scheduler";
|
|
9
|
+
import { workflowFileChanged, workflowStampsEqual } from "@lorenz/workflow";
|
|
10
|
+
import { durationMs, errorMessage, systemClock, withDerivedMaxInFlight, } from "@lorenz/domain";
|
|
11
|
+
import { checkSlotsPerMachineGate, createDispatchCoordinator, nullEndpointManager, } from "@lorenz/dispatch-coordinator";
|
|
12
|
+
export { RUNTIME_EVENT_TYPES, RUNTIME_RUN_OUTCOMES } from "@lorenz/runtime-events";
|
|
13
|
+
export { RUNTIME_RECONCILIATION_REASONS, } from "@lorenz/policies/reconciliation";
|
|
14
|
+
/**
|
|
15
|
+
* Classifies a run error into a worker outcome. Returns `poison` ONLY for typed worker-transport faults
|
|
16
|
+
* (the worker is bad and must be recycled); everything else is `healthy` (a local/config/agent fault
|
|
17
|
+
* that left the worker reusable). Matches typed PREFIXES, not arbitrary substrings, so that
|
|
18
|
+
* `invalid_ssh_timeout` (a local config fault) is never mistaken for the `ssh_timeout` transport
|
|
19
|
+
* fault even though one is a substring of the other.
|
|
20
|
+
*/
|
|
21
|
+
const POISON_WORKER_ERROR_PREFIXES = [
|
|
22
|
+
"ssh_timeout:",
|
|
23
|
+
"remote_home_lookup_failed:",
|
|
24
|
+
"workspace_prepare_failed:",
|
|
25
|
+
// A REMOTE workspace hook (run over SSH against the worker's workerHost) that exits
|
|
26
|
+
// non-zero throws `workspace hook failed with status N: ...`. That is a worker-side
|
|
27
|
+
// fault, so it poisons the worker. The LOCAL hook failure string is
|
|
28
|
+
// `hook failed with status N` (no `workspace ` prefix) and stays healthy.
|
|
29
|
+
"workspace hook failed with status ",
|
|
30
|
+
];
|
|
31
|
+
export function classifyWorkerOutcome(error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
return POISON_WORKER_ERROR_PREFIXES.some((prefix) => message.startsWith(prefix))
|
|
34
|
+
? "poison"
|
|
35
|
+
: "healthy";
|
|
36
|
+
}
|
|
37
|
+
function pollIntent(options = {}) {
|
|
38
|
+
return {
|
|
39
|
+
dryRun: options.dryRun === true,
|
|
40
|
+
waitForRuns: options.waitForRuns === true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function pollOptionsFromIntent(intent) {
|
|
44
|
+
const options = {};
|
|
45
|
+
if (intent.dryRun)
|
|
46
|
+
options.dryRun = true;
|
|
47
|
+
if (intent.waitForRuns)
|
|
48
|
+
options.waitForRuns = true;
|
|
49
|
+
return options;
|
|
50
|
+
}
|
|
51
|
+
function pollOptionsCover(active, requested) {
|
|
52
|
+
const activeIntent = pollIntent(active);
|
|
53
|
+
const requestedIntent = pollIntent(requested);
|
|
54
|
+
return ((!activeIntent.dryRun || requestedIntent.dryRun) &&
|
|
55
|
+
(activeIntent.waitForRuns || !requestedIntent.waitForRuns));
|
|
56
|
+
}
|
|
57
|
+
function mergePollOptions(existing, requested) {
|
|
58
|
+
if (!existing)
|
|
59
|
+
return pollOptionsFromIntent(pollIntent(requested));
|
|
60
|
+
const existingIntent = pollIntent(existing);
|
|
61
|
+
const requestedIntent = pollIntent(requested);
|
|
62
|
+
return pollOptionsFromIntent({
|
|
63
|
+
dryRun: existingIntent.dryRun && requestedIntent.dryRun,
|
|
64
|
+
waitForRuns: existingIntent.waitForRuns || requestedIntent.waitForRuns,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
class ActiveRunHandle {
|
|
68
|
+
key;
|
|
69
|
+
runId;
|
|
70
|
+
activeRuns;
|
|
71
|
+
controller = new AbortController();
|
|
72
|
+
/**
|
|
73
|
+
* Set when the run is force-finished externally (e.g. a stall reconciliation aborts it). The
|
|
74
|
+
* worker pool reads this so a stall-finished run poisons its worker even though the runner surfaces a
|
|
75
|
+
* generic `agent_run_aborted` (which would otherwise classify as healthy).
|
|
76
|
+
*/
|
|
77
|
+
reason = null;
|
|
78
|
+
constructor(key, runId, activeRuns) {
|
|
79
|
+
this.key = key;
|
|
80
|
+
this.runId = runId;
|
|
81
|
+
this.activeRuns = activeRuns;
|
|
82
|
+
}
|
|
83
|
+
get signal() {
|
|
84
|
+
return this.controller.signal;
|
|
85
|
+
}
|
|
86
|
+
get isActive() {
|
|
87
|
+
return this.activeRuns.get(this.key) === this;
|
|
88
|
+
}
|
|
89
|
+
abort() {
|
|
90
|
+
this.controller.abort();
|
|
91
|
+
}
|
|
92
|
+
finishExternally(reason = null) {
|
|
93
|
+
if (reason)
|
|
94
|
+
this.reason = reason;
|
|
95
|
+
this.abort();
|
|
96
|
+
this.release();
|
|
97
|
+
}
|
|
98
|
+
release() {
|
|
99
|
+
if (this.isActive)
|
|
100
|
+
this.activeRuns.delete(this.key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export class SymphonyRuntime {
|
|
104
|
+
input;
|
|
105
|
+
client;
|
|
106
|
+
orchestrator;
|
|
107
|
+
runner;
|
|
108
|
+
clock;
|
|
109
|
+
validateDispatch;
|
|
110
|
+
listeners = new Set();
|
|
111
|
+
retryScheduler;
|
|
112
|
+
inFlight = new Set();
|
|
113
|
+
stopped = false;
|
|
114
|
+
appStatus = "starting";
|
|
115
|
+
pollStatus = "idle";
|
|
116
|
+
candidates = 0;
|
|
117
|
+
eligible = 0;
|
|
118
|
+
lastPollAt = null;
|
|
119
|
+
nextPollAt = null;
|
|
120
|
+
lastError = null;
|
|
121
|
+
projection = new ProjectionActor();
|
|
122
|
+
activeRuns = new Map();
|
|
123
|
+
startupCleanupDone = false;
|
|
124
|
+
nextRunNumber = 1;
|
|
125
|
+
pollInProgress = null;
|
|
126
|
+
activePollOptions = null;
|
|
127
|
+
pendingPollOptions = null;
|
|
128
|
+
workerPoolDrained = false;
|
|
129
|
+
/**
|
|
130
|
+
* The reload-surviving coordinator singleton. Built ONCE here: either the
|
|
131
|
+
* pre-built `input.coordinator` (preferred), or a null-endpoint passthrough
|
|
132
|
+
* wrapping a bare `input.workerPool` (the low-churn path that keeps every existing
|
|
133
|
+
* workerPool-injecting site byte-identical at the runtime boundary). `undefined`
|
|
134
|
+
* when neither is supplied (the static/local path, byte-identical to today).
|
|
135
|
+
*/
|
|
136
|
+
coordinator;
|
|
137
|
+
constructor(input) {
|
|
138
|
+
this.input = input;
|
|
139
|
+
this.client =
|
|
140
|
+
input.client ?? input.clientFactory?.(input.workflow.settings) ?? missingRuntimeClient();
|
|
141
|
+
this.clock = input.clock ?? systemClock;
|
|
142
|
+
// Prefer the pre-built coordinator; otherwise wrap a bare workerPool in a
|
|
143
|
+
// null-endpoint passthrough so `acquireRunSlot`/`governs`/`canAcquire`/
|
|
144
|
+
// `reconcile`/`drain` drive a uniform surface (default slotsPerMachine=1 +
|
|
145
|
+
// mcpEndpoint=null make this a 1:1 passthrough over the pool). Built once: a
|
|
146
|
+
// reload reconciles it in place, never reconstructs it.
|
|
147
|
+
this.coordinator =
|
|
148
|
+
input.coordinator ?? wrapWorkerPoolInCoordinator(input.workerPool, input.workflow.settings);
|
|
149
|
+
const coordinator = this.coordinator;
|
|
150
|
+
this.orchestrator =
|
|
151
|
+
input.orchestrator ??
|
|
152
|
+
new Orchestrator(input.workflow.settings, this.clock, undefined,
|
|
153
|
+
// The coordinator IS the orchestrator's capacity authority (it satisfies
|
|
154
|
+
// the CapacityProbe shape directly). It is installed for the orchestrator's
|
|
155
|
+
// lifetime whenever it exists, but a reload can disable the underlying pool
|
|
156
|
+
// (draining it to zero) without tearing the authority down. `governs`
|
|
157
|
+
// tracks whether the live pool still governs capacity so a disabled pool
|
|
158
|
+
// falls through to static/local execution instead of permanently blocking
|
|
159
|
+
// dispatch as worker_host_capacity. The coordinator is a reload-surviving
|
|
160
|
+
// singleton (stable identity across reconcile) re-reading live pool state
|
|
161
|
+
// on each call.
|
|
162
|
+
coordinator);
|
|
163
|
+
// Poll nudge: when the pool frees capacity (a worker lands warm after the FIFO
|
|
164
|
+
// waiters had first claim), re-poll promptly so a capacity-skipped issue
|
|
165
|
+
// re-dispatches without waiting out polling.intervalMs.
|
|
166
|
+
coordinator?.onCapacityAvailable(() => this.nudgePollForFreedCapacity());
|
|
167
|
+
this.runner = input.runner ?? runAgentAttempt;
|
|
168
|
+
this.validateDispatch = input.validateDispatch ?? validateDispatchConfig;
|
|
169
|
+
this.retryScheduler = new RetryScheduler(this.clock);
|
|
170
|
+
this.appStatus = "idle";
|
|
171
|
+
}
|
|
172
|
+
get workflow() {
|
|
173
|
+
return this.input.workflow;
|
|
174
|
+
}
|
|
175
|
+
subscribe(listener) {
|
|
176
|
+
this.listeners.add(listener);
|
|
177
|
+
listener(this.snapshot());
|
|
178
|
+
return () => {
|
|
179
|
+
this.listeners.delete(listener);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
snapshot() {
|
|
183
|
+
const orchestration = this.orchestrator.snapshot();
|
|
184
|
+
return this.projection.snapshot({
|
|
185
|
+
appStatus: this.appStatus,
|
|
186
|
+
workflowPath: this.workflow.path,
|
|
187
|
+
poll: {
|
|
188
|
+
status: this.pollStatus,
|
|
189
|
+
candidates: this.candidates,
|
|
190
|
+
eligible: this.eligible,
|
|
191
|
+
lastPollAt: this.lastPollAt,
|
|
192
|
+
nextPollAt: this.nextPollAt,
|
|
193
|
+
lastError: this.lastError,
|
|
194
|
+
},
|
|
195
|
+
running: orchestration.running.map((entry) => runtimeRunningEntry(entry, this.activeRuns.get(slotKey(entry.issue.id, entry.slotIndex))?.runId)),
|
|
196
|
+
// In-acquire slots, surfaced honestly (host-less) instead of appearing in
|
|
197
|
+
// `running` with a placeholder host.
|
|
198
|
+
reserving: orchestration.reserving.map((entry) => ({ ...entry })),
|
|
199
|
+
retrying: orchestration.retrying.map(runtimeRetryEntry),
|
|
200
|
+
blocked: orchestration.blocked.map((entry) => ({ ...entry })),
|
|
201
|
+
usageTotals: orchestration.usageTotals,
|
|
202
|
+
rateLimits: orchestration.rateLimits,
|
|
203
|
+
logFile: this.workflow.settings.logging.logFile,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
async start(options = {}) {
|
|
207
|
+
this.stopped = false;
|
|
208
|
+
do {
|
|
209
|
+
if (options.once) {
|
|
210
|
+
await this.pollOnce({ dryRun: options.dryRun, waitForRuns: true });
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
// A thrown poll (e.g. tracker fetchCandidateIssues rejecting) must not
|
|
214
|
+
// terminate the recurring daemon loop. pollOnceUnlocked already records a
|
|
215
|
+
// poll_error event and surfaces the error on the snapshot before rethrowing,
|
|
216
|
+
// so swallow the rejection here and continue to the next interval.
|
|
217
|
+
try {
|
|
218
|
+
await this.pollOnce({ dryRun: options.dryRun });
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Intentionally ignored: the error is already logged as poll_error.
|
|
222
|
+
}
|
|
223
|
+
await delay(this.clock, this.workflow.settings.polling.intervalMs, () => this.stopped);
|
|
224
|
+
} while (!this.stopped);
|
|
225
|
+
}
|
|
226
|
+
stop() {
|
|
227
|
+
this.stopped = true;
|
|
228
|
+
this.appStatus = "stopping";
|
|
229
|
+
this.pendingPollOptions = null;
|
|
230
|
+
// finishExternally (abort + release) mirrors the other abort sites and clears
|
|
231
|
+
// isActive, so the resulting agent_run_aborted rejection is treated as a clean
|
|
232
|
+
// shutdown in runClaim rather than recorded as a failed run.
|
|
233
|
+
for (const handle of [...this.activeRuns.values()])
|
|
234
|
+
handle.finishExternally();
|
|
235
|
+
this.retryScheduler.stop();
|
|
236
|
+
this.emit();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Drains the worker pool once (idempotent). `stop()` stays synchronous (it only flips `stopped` and
|
|
240
|
+
* aborts handles), so this is invoked by the daemon's `finally` AFTER `start()` resolves to
|
|
241
|
+
* destroy paid cloud workers before process exit. A no-op when no pool is configured.
|
|
242
|
+
*/
|
|
243
|
+
async drainWorkerPool() {
|
|
244
|
+
if (this.workerPoolDrained)
|
|
245
|
+
return;
|
|
246
|
+
this.workerPoolDrained = true;
|
|
247
|
+
const deadlineMs = this.workflow.settings.worker.workerPool?.drainDeadlineMs ?? 30_000;
|
|
248
|
+
await this.coordinator?.drain({ deadlineMs });
|
|
249
|
+
}
|
|
250
|
+
async pollOnce(options = {}) {
|
|
251
|
+
if (this.pollInProgress) {
|
|
252
|
+
this.queuePendingPoll(options);
|
|
253
|
+
return this.pollInProgress;
|
|
254
|
+
}
|
|
255
|
+
const poll = this.pollUntilQueueDrained(options);
|
|
256
|
+
this.pollInProgress = poll;
|
|
257
|
+
try {
|
|
258
|
+
await poll;
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
if (this.pollInProgress === poll) {
|
|
262
|
+
this.pollInProgress = null;
|
|
263
|
+
this.activePollOptions = null;
|
|
264
|
+
this.pendingPollOptions = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async pollUntilQueueDrained(options) {
|
|
269
|
+
let nextOptions = options;
|
|
270
|
+
while (true) {
|
|
271
|
+
this.activePollOptions = nextOptions;
|
|
272
|
+
await this.pollOnceUnlocked(nextOptions);
|
|
273
|
+
this.activePollOptions = null;
|
|
274
|
+
const pending = this.pendingPollOptions;
|
|
275
|
+
this.pendingPollOptions = null;
|
|
276
|
+
if (!pending)
|
|
277
|
+
return;
|
|
278
|
+
nextOptions = pending;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Requests a prompt re-poll after the worker pool freed capacity. The pool fires the
|
|
283
|
+
* hook synchronously inside its settle/reconcile paths (under a per-worker mutex), so
|
|
284
|
+
* the nudge is deferred a microtask to never re-enter them on the same stack. A
|
|
285
|
+
* poll already in progress gets a FORCED follow-up poll queued (merged via the
|
|
286
|
+
* pendingPollOptions machinery) because the freed capacity may post-date that
|
|
287
|
+
* poll's eligibility pass.
|
|
288
|
+
*/
|
|
289
|
+
nudgePollForFreedCapacity() {
|
|
290
|
+
queueMicrotask(() => {
|
|
291
|
+
if (this.stopped)
|
|
292
|
+
return;
|
|
293
|
+
if (this.pollInProgress) {
|
|
294
|
+
this.queuePendingPoll({}, true);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.pollOnce().catch(() => {
|
|
298
|
+
// Intentionally ignored: pollOnceUnlocked already recorded poll_error.
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
queuePendingPoll(options = {}, force = false) {
|
|
303
|
+
if (!force &&
|
|
304
|
+
((this.activePollOptions && pollOptionsCover(this.activePollOptions, options)) ||
|
|
305
|
+
(this.pendingPollOptions && pollOptionsCover(this.pendingPollOptions, options)))) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this.pendingPollOptions = mergePollOptions(this.pendingPollOptions, options);
|
|
309
|
+
}
|
|
310
|
+
async pollOnceUnlocked(options = {}) {
|
|
311
|
+
this.pollStatus = "checking";
|
|
312
|
+
this.appStatus = this.inFlight.size > 0 ? "running" : "polling";
|
|
313
|
+
this.lastPollAt = this.clock.now().toISOString();
|
|
314
|
+
this.lastError = null;
|
|
315
|
+
this.emit();
|
|
316
|
+
const dispatched = [];
|
|
317
|
+
try {
|
|
318
|
+
await this.reloadWorkflowIfConfigured();
|
|
319
|
+
this.validateDispatch(this.workflow.settings);
|
|
320
|
+
await this.cleanupTerminalWorkspacesOnce();
|
|
321
|
+
this.reconcileStalledRuns();
|
|
322
|
+
await this.reconcileTrackedIssues();
|
|
323
|
+
const issues = await this.client.fetchCandidateIssues();
|
|
324
|
+
const eligibleIssues = this.orchestrator.eligibleIssues(issues);
|
|
325
|
+
if (!options.dryRun)
|
|
326
|
+
this.syncRetryTimersForIssues(issues);
|
|
327
|
+
this.candidates = issues.length;
|
|
328
|
+
this.eligible = eligibleIssues.length;
|
|
329
|
+
if (options.dryRun) {
|
|
330
|
+
this.addEvent("dry_run", `eligible=${eligibleIssues.length} candidates=${issues.length}`);
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
for (const issue of eligibleIssues) {
|
|
334
|
+
dispatched.push(...(await this.maybeDispatch(issue)));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (options.waitForRuns) {
|
|
338
|
+
await Promise.allSettled(dispatched);
|
|
339
|
+
}
|
|
340
|
+
this.pollStatus = "idle";
|
|
341
|
+
this.appStatus = this.inFlight.size > 0 ? "running" : "idle";
|
|
342
|
+
this.nextPollAt = new Date(this.clock.now().getTime() + this.workflow.settings.polling.intervalMs).toISOString();
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.pollStatus = "error";
|
|
346
|
+
this.appStatus = "error";
|
|
347
|
+
this.lastError = errorMessage(error);
|
|
348
|
+
this.addEvent("poll_error", this.lastError);
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
this.emit();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async maybeDispatch(issue) {
|
|
356
|
+
const refreshed = await this.fetchIssueForDispatch(issue);
|
|
357
|
+
if (!refreshed) {
|
|
358
|
+
this.addEvent("dispatch_skipped", `${issue.identifier} missing_before_dispatch`);
|
|
359
|
+
return [];
|
|
360
|
+
}
|
|
361
|
+
const claim = this.orchestrator.claim(refreshed);
|
|
362
|
+
if (!claim) {
|
|
363
|
+
this.addEvent("dispatch_skipped", `${refreshed.identifier} stale_before_dispatch`);
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
this.syncRetryTimer(refreshed.id);
|
|
367
|
+
const slotIndex = claim.kind === "running" ? claim.entry.slotIndex : claim.reservation.slotIndex;
|
|
368
|
+
const key = slotKey(refreshed.id, slotIndex);
|
|
369
|
+
const runId = `run-${this.nextRunNumber}`;
|
|
370
|
+
this.nextRunNumber += 1;
|
|
371
|
+
// The handle is registered for the WHOLE run lifecycle - including the reserved
|
|
372
|
+
// path's acquire window - so stop()/reconcile abort an in-acquire run (the
|
|
373
|
+
// signal reaches the pool's FIFO waiter) exactly as they abort a running one.
|
|
374
|
+
const handle = new ActiveRunHandle(key, runId, this.activeRuns);
|
|
375
|
+
this.activeRuns.set(key, handle);
|
|
376
|
+
// On the static/local path the run starts immediately. On the pool-governed
|
|
377
|
+
// path run_reserving marks dispatch intent and run_started moves AFTER
|
|
378
|
+
// bindReservation (inside runReservedClaim): a capacity-refused dispatch
|
|
379
|
+
// never emits a phantom run_started.
|
|
380
|
+
if (claim.kind === "running") {
|
|
381
|
+
this.addEvent("run_started", `${refreshed.identifier} slot=${slotIndex}`);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
this.addEvent("run_reserving", `${refreshed.identifier} slot=${slotIndex}`);
|
|
385
|
+
}
|
|
386
|
+
this.input.onIssueDispatched?.(refreshed);
|
|
387
|
+
const run = claim.kind === "running"
|
|
388
|
+
? this.runClaim(refreshed, claim.entry.slotIndex, claim.entry.agentKind, runId, claim.entry.workerHost ?? null, handle)
|
|
389
|
+
: this.runReservedClaim(refreshed, claim.reservation, runId, handle);
|
|
390
|
+
this.inFlight.add(run);
|
|
391
|
+
void run.finally(() => {
|
|
392
|
+
this.inFlight.delete(run);
|
|
393
|
+
this.appStatus = this.inFlight.size > 0 ? "running" : "idle";
|
|
394
|
+
this.emit();
|
|
395
|
+
});
|
|
396
|
+
this.emit();
|
|
397
|
+
return [run];
|
|
398
|
+
}
|
|
399
|
+
async fetchIssueForDispatch(issue) {
|
|
400
|
+
try {
|
|
401
|
+
const refreshed = await this.client.fetchIssuesByIds([issue.id]);
|
|
402
|
+
return refreshed[0] ?? null;
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
this.addEvent("dispatch_refresh_failed", `${issue.identifier} ${errorMessage(error)}`);
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Phase 1 -> negotiation -> phase 2 for a pool-governed (reserved) claim: drive the
|
|
411
|
+
* coordinator's acquire inside this detached per-run promise (a cold provision never blocks
|
|
412
|
+
* the poll thread), then either bind the reservation to the CONCRETE `slot.workerHost` and run,
|
|
413
|
+
* or cancel the reservation (restoring the consumed retry entry) on a capacity refusal /
|
|
414
|
+
* acquire fault. `run_started` is only emitted after a successful bind.
|
|
415
|
+
*/
|
|
416
|
+
async runReservedClaim(issue, reservation, runId, handle) {
|
|
417
|
+
const coordinator = this.coordinator;
|
|
418
|
+
if (!coordinator) {
|
|
419
|
+
// A reservation can only be minted while a capacity probe governs, which in
|
|
420
|
+
// production implies a coordinator. An injected probe without one (test
|
|
421
|
+
// wiring) is treated like an acquire fault: cancel and skip, never strand.
|
|
422
|
+
this.addEvent("dispatch_skipped", `${issue.identifier} worker_pool_acquire_error coordinator_missing`);
|
|
423
|
+
this.orchestrator.cancelReservation(reservation);
|
|
424
|
+
handle.release();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
let acquired;
|
|
428
|
+
try {
|
|
429
|
+
acquired = await coordinator.acquireRunSlot({
|
|
430
|
+
issueId: issue.id,
|
|
431
|
+
slotIndex: reservation.slotIndex,
|
|
432
|
+
labels: issue.labels,
|
|
433
|
+
// Sticky retry affinity travels on the reservation (the prior run's
|
|
434
|
+
// CONCRETE host from the consumed retry entry).
|
|
435
|
+
affinityKey: reservation.affinityHost,
|
|
436
|
+
timeoutMs: this.workflow.settings.worker.workerPool?.acquireTimeoutMs ?? 30_000,
|
|
437
|
+
signal: handle.signal,
|
|
438
|
+
// Thread the FULL workflow Settings (with server.port) so the per-run
|
|
439
|
+
// endpoint manager can build the remote endpoint; the WorkerPoolSettings the
|
|
440
|
+
// coordinator holds has no server.port and would fail every acquire.
|
|
441
|
+
settings: this.workflow.settings,
|
|
442
|
+
// The ACP executor - the only executor - consumes the per-run MCP
|
|
443
|
+
// endpoint over the reverse tunnel, so every run needs one. The flag
|
|
444
|
+
// stays on the request so a future executor that runs its tools
|
|
445
|
+
// in-process can skip minting the endpoint (and its tunnel-ceiling
|
|
446
|
+
// reservation) without an API change.
|
|
447
|
+
needsMcpEndpoint: true,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
// acquireRunSlot() REJECTED outside the no_capacity result path (ledger /
|
|
452
|
+
// filesystem / driver / endpoint-open fault). Handle it like a failed
|
|
453
|
+
// dispatch rather than letting the rejection strand the reservation: cancel
|
|
454
|
+
// it (restoring the consumed retry entry) so the slot is re-evaluated next
|
|
455
|
+
// poll, release the active handle, surface a clear error event (never
|
|
456
|
+
// swallowed), and return WITHOUT running or recording history. The
|
|
457
|
+
// coordinator already settled any just-bound lease healthy before throwing,
|
|
458
|
+
// so there is nothing to settle here.
|
|
459
|
+
this.addEvent("dispatch_skipped", `${issue.identifier} worker_pool_acquire_error ${errorMessage(error)}`);
|
|
460
|
+
this.orchestrator.cancelReservation(reservation);
|
|
461
|
+
this.syncRetryTimer(issue.id);
|
|
462
|
+
handle.release();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (acquired.status !== "bound") {
|
|
466
|
+
// No capacity within the acquire window (EVERY typed no_capacity reason maps
|
|
467
|
+
// to the SAME event - no per-reason differentiation, matching today): cancel
|
|
468
|
+
// the reservation with NO backoff. The consumed retry entry is RESTORED (its
|
|
469
|
+
// deadline already passed) so the issue is immediately re-eligible with its
|
|
470
|
+
// affinity and attempt counter intact; never record history for a run that
|
|
471
|
+
// did not start.
|
|
472
|
+
this.addEvent("dispatch_skipped", `${issue.identifier} worker_host_capacity`);
|
|
473
|
+
this.orchestrator.cancelReservation(reservation);
|
|
474
|
+
this.syncRetryTimer(issue.id);
|
|
475
|
+
handle.release();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const slot = acquired.slot;
|
|
479
|
+
const entry = this.orchestrator.bindReservation(reservation, slot.workerHost);
|
|
480
|
+
if (!entry) {
|
|
481
|
+
// The reservation was cancelled/expired during the acquire (cleanup, stop,
|
|
482
|
+
// or the expiry sweep): release the bound worker back to warm inventory (the
|
|
483
|
+
// slot's settled-once guard makes this exactly-once) and skip the run.
|
|
484
|
+
await slot.release("healthy");
|
|
485
|
+
this.addEvent("dispatch_skipped", `${issue.identifier} reservation_lapsed`);
|
|
486
|
+
handle.release();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
this.addEvent("run_started", `${issue.identifier} slot=${reservation.slotIndex}`);
|
|
490
|
+
await this.runClaim(issue, reservation.slotIndex, reservation.agentKind, runId, slot.workerHost, handle, slot);
|
|
491
|
+
}
|
|
492
|
+
async runClaim(issue, slotIndex, agentKind, runId, workerHost, handle, slot = null) {
|
|
493
|
+
const startedAt = this.clock.now().toISOString();
|
|
494
|
+
const effectiveWorkerHost = workerHost;
|
|
495
|
+
let workerOutcome = "healthy";
|
|
496
|
+
const heartbeatSlot = slot;
|
|
497
|
+
try {
|
|
498
|
+
const result = await this.runner({
|
|
499
|
+
issue,
|
|
500
|
+
workflow: this.workflow,
|
|
501
|
+
workerHost: effectiveWorkerHost,
|
|
502
|
+
slotIndex,
|
|
503
|
+
// Thread the bound slot's per-run MCP endpoint (or null on the local /
|
|
504
|
+
// non-pool / null-manager path) into the runner so the ACP executor
|
|
505
|
+
// consumes it and SKIPS its own acquire+release. The coordinator owns the
|
|
506
|
+
// whole lease and closes it via slot.release in this run's finally.
|
|
507
|
+
mcpEndpoint: slot?.mcpEndpoint ?? null,
|
|
508
|
+
// With more than one run slot per machine, two solo runs of the SAME
|
|
509
|
+
// issue could land on one worker and would otherwise share a workspace
|
|
510
|
+
// path; force the per-slot suffix whenever co-residence is possible.
|
|
511
|
+
// Single-tenant (default) keeps the bare path.
|
|
512
|
+
forceSlotSuffix: (this.workflow.settings.worker.workerPool?.slotsPerMachine ?? 1) > 1,
|
|
513
|
+
onUpdate: (update) => {
|
|
514
|
+
heartbeatSlot?.heartbeat();
|
|
515
|
+
this.orchestrator.applyUpdate(issue.id, slotIndex, update);
|
|
516
|
+
this.addEvent(update.type, agentUpdateRuntimeMessage(issue.identifier, update));
|
|
517
|
+
this.input.onAgentUpdate?.(issue, update);
|
|
518
|
+
},
|
|
519
|
+
fetchIssue: async (current) => {
|
|
520
|
+
const refreshed = await this.client.fetchIssuesByIds([current.id]);
|
|
521
|
+
return refreshed[0] ?? current;
|
|
522
|
+
},
|
|
523
|
+
abortSignal: handle.signal,
|
|
524
|
+
});
|
|
525
|
+
if (!handle.isActive)
|
|
526
|
+
return;
|
|
527
|
+
const finalIssue = result.finalIssue ?? (await this.fetchIssueOrSelf(issue));
|
|
528
|
+
if (!handle.isActive)
|
|
529
|
+
return;
|
|
530
|
+
const entry = this.runningEntry(issue.id, slotIndex);
|
|
531
|
+
this.orchestrator.finish(issue.id, slotIndex, true, undefined, "continuation");
|
|
532
|
+
this.syncRetryTimer(issue.id);
|
|
533
|
+
this.recordHistory(buildRunHistoryEntry({
|
|
534
|
+
id: runId,
|
|
535
|
+
issue,
|
|
536
|
+
state: finalIssue.state,
|
|
537
|
+
slotIndex,
|
|
538
|
+
agentKind,
|
|
539
|
+
outcome: "success",
|
|
540
|
+
turnCount: result.turnCount,
|
|
541
|
+
runningEntry: entry,
|
|
542
|
+
workspacePath: result.workspace,
|
|
543
|
+
startedAt,
|
|
544
|
+
endedAt: this.clock.now().toISOString(),
|
|
545
|
+
durationMs: durationMs(startedAt, this.clock.now().toISOString()),
|
|
546
|
+
}));
|
|
547
|
+
this.addEvent("run_completed", `${issue.identifier} turns=${result.turnCount}`);
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
// Classify the worker outcome BEFORE any early return so a run finished
|
|
551
|
+
// externally (e.g. a stall reconciliation aborted it -> the runner throws
|
|
552
|
+
// `agent_run_aborted`) still poisons the worker: a stall-finished run is
|
|
553
|
+
// treated as poison via `handle.reason`, otherwise typed transport faults.
|
|
554
|
+
workerOutcome = handle.reason === "stalled" ? "poison" : classifyWorkerOutcome(error);
|
|
555
|
+
// Skip runs that are no longer active: superseded, finished externally, or
|
|
556
|
+
// released by stop() during shutdown. In the shutdown case the runner
|
|
557
|
+
// rejects with agent_run_aborted; recording it as a failure would emit a
|
|
558
|
+
// run_failed event the TUI renders as a red error banner on Ctrl+C.
|
|
559
|
+
if (!handle.isActive)
|
|
560
|
+
return;
|
|
561
|
+
const entry = this.runningEntry(issue.id, slotIndex);
|
|
562
|
+
if (!handle.isActive)
|
|
563
|
+
return;
|
|
564
|
+
this.orchestrator.finish(issue.id, slotIndex, true, errorMessage(error), "failure");
|
|
565
|
+
this.syncRetryTimer(issue.id);
|
|
566
|
+
this.recordHistory(buildRunHistoryEntry({
|
|
567
|
+
id: runId,
|
|
568
|
+
issue,
|
|
569
|
+
slotIndex,
|
|
570
|
+
agentKind,
|
|
571
|
+
outcome: "failed",
|
|
572
|
+
turnCount: entry?.turnCount ?? 0,
|
|
573
|
+
runningEntry: entry,
|
|
574
|
+
startedAt,
|
|
575
|
+
endedAt: this.clock.now().toISOString(),
|
|
576
|
+
durationMs: durationMs(startedAt, this.clock.now().toISOString()),
|
|
577
|
+
error: errorMessage(error),
|
|
578
|
+
fallbackLastEvent: "turn_failed",
|
|
579
|
+
}));
|
|
580
|
+
this.addEvent("run_failed", `${issue.identifier} ${errorMessage(error)}`);
|
|
581
|
+
}
|
|
582
|
+
finally {
|
|
583
|
+
handle.release();
|
|
584
|
+
if (slot) {
|
|
585
|
+
// A stall reconciliation force-finished this run (handle.reason='stalled').
|
|
586
|
+
// The CATCH path already poisons on a rejected runner, but a runner that
|
|
587
|
+
// ignores the abort - or races to a SUCCESSFUL resolve after finishExternally -
|
|
588
|
+
// takes the success path's early return with workerOutcome still 'healthy'. Poison
|
|
589
|
+
// the worker here, BEFORE settling, whenever the run was stall-finished,
|
|
590
|
+
// independent of whether the runner resolved or rejected: a stalled worker must
|
|
591
|
+
// never be released healthy and reused.
|
|
592
|
+
if (handle.reason === "stalled")
|
|
593
|
+
workerOutcome = "poison";
|
|
594
|
+
// Settle the slot exactly once: close THIS slot's endpoint (a no-op in
|
|
595
|
+
// STEP 1's null-endpoint passthrough) THEN settle the wrapped lease. Lease
|
|
596
|
+
// ops are leaseId + settled + worker-state guarded inside the pool, so a stale
|
|
597
|
+
// generation's late resolve is a no-op that never touches inFlight.
|
|
598
|
+
if (workerOutcome === "poison") {
|
|
599
|
+
await slot.fail("worker_poisoned");
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
await slot.release("healthy");
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async fetchIssueOrSelf(issue) {
|
|
608
|
+
const refreshed = await this.client.fetchIssuesByIds([issue.id]);
|
|
609
|
+
return refreshed[0] ?? issue;
|
|
610
|
+
}
|
|
611
|
+
async reloadWorkflowIfConfigured() {
|
|
612
|
+
if (!this.input.reloadWorkflow)
|
|
613
|
+
return;
|
|
614
|
+
const prevWorkerPool = this.input.workflow.settings.worker.workerPool;
|
|
615
|
+
try {
|
|
616
|
+
if (!(await workflowFileChanged(this.input.workflow)))
|
|
617
|
+
return;
|
|
618
|
+
const previous = this.input.workflow;
|
|
619
|
+
const workflow = await this.input.reloadWorkflow();
|
|
620
|
+
if (workflow === previous || workflowStampsEqual(previous.stamp, workflow.stamp))
|
|
621
|
+
return;
|
|
622
|
+
// Enforce the SAME slots-per-machine co-residence gate the daemon runs at
|
|
623
|
+
// startup. The startup gate runs ONCE; without this a live daemon could
|
|
624
|
+
// reload max_in_flight 1 -> >1 WITHOUT the per-run-endpoint capability OR the
|
|
625
|
+
// co_residence opt-in, silently widening the shared-machine blast radius the
|
|
626
|
+
// startup gate rejects. Throwing here lands in the catch below: last-good
|
|
627
|
+
// settings are KEPT (not applied, the live pool is NOT reconciled onto the
|
|
628
|
+
// unsafe settings) and a workflow_reload_failed event carries the gate's
|
|
629
|
+
// message - mirroring the anti-double-capacity guard behavior.
|
|
630
|
+
const gateMessage = checkSlotsPerMachineGate(workflow.settings.worker.workerPool, this.coordinator?.capabilities);
|
|
631
|
+
if (gateMessage !== null)
|
|
632
|
+
throw new Error(gateMessage);
|
|
633
|
+
// TRANSACTIONAL reload: run EVERY throwing side effect FIRST (the gate above,
|
|
634
|
+
// then the coordinator/pool reconcile), and ONLY swap the runtime settings
|
|
635
|
+
// (this.input.workflow + this.orchestrator.settings + the client) AFTER they
|
|
636
|
+
// ALL succeed. If reconcile throws (e.g. driver unavailable / invalid
|
|
637
|
+
// driverOptions) the catch below leaves BOTH the runtime settings AND the
|
|
638
|
+
// pool/coordinator state on the PREVIOUS config - last-good is never partially
|
|
639
|
+
// applied, so dispatch can never use settings that do not match the live pool.
|
|
640
|
+
//
|
|
641
|
+
// The coordinator (and its pool) is a reload-surviving singleton: diff
|
|
642
|
+
// prev-vs-next worker-pool settings instead of being reconstructed. When the
|
|
643
|
+
// reload REMOVES the worker_pool block entirely (next === undefined), reconcile
|
|
644
|
+
// to a disabled-equivalent of the prior settings so the live pool drains to
|
|
645
|
+
// zero instead of leaking its (paid) workers unmanaged. A present block (even
|
|
646
|
+
// one with `enabled: false`) keeps the existing path: reconcile handles the
|
|
647
|
+
// disable-and-drain itself.
|
|
648
|
+
if (this.coordinator) {
|
|
649
|
+
const next = workflow.settings.worker.workerPool ?? disabledWorkerPoolSettings(prevWorkerPool);
|
|
650
|
+
// Awaited: reconcile is async so the coordinator's injected driverLoader
|
|
651
|
+
// can dynamic-import an out-of-tree driver module BEFORE the (still
|
|
652
|
+
// synchronous) pool reconcile. A rejection lands in the catch below,
|
|
653
|
+
// keeping last-good settings and emitting workflow_reload_failed.
|
|
654
|
+
if (next)
|
|
655
|
+
await this.coordinator.reconcile(next);
|
|
656
|
+
}
|
|
657
|
+
this.input.workflow = workflow;
|
|
658
|
+
this.orchestrator.settings = workflow.settings;
|
|
659
|
+
if (!this.input.client && this.input.clientFactory) {
|
|
660
|
+
this.client = this.input.clientFactory(workflow.settings);
|
|
661
|
+
}
|
|
662
|
+
this.addEvent("workflow_reloaded", workflow.path);
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
// Keeps last-good settings. errorMessage(error) already surfaces the
|
|
666
|
+
// anti-double-capacity guard message so operators learn why a reload that
|
|
667
|
+
// tried to enable the pool alongside ssh_hosts did not take effect.
|
|
668
|
+
this.addEvent("workflow_reload_failed", errorMessage(error));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async reconcileTrackedIssues() {
|
|
672
|
+
const snapshot = this.orchestrator.snapshot();
|
|
673
|
+
const tracked = new Map();
|
|
674
|
+
// Reserving (in-acquire) slots are tracked host-less so an issue that goes
|
|
675
|
+
// terminal mid-acquire is still aborted and cleaned up; running/retrying
|
|
676
|
+
// entries below override with their richer metadata when present.
|
|
677
|
+
for (const entry of snapshot.reserving)
|
|
678
|
+
tracked.set(entry.issueId, {
|
|
679
|
+
identifier: entry.identifier,
|
|
680
|
+
workerHost: null,
|
|
681
|
+
workspacePath: null,
|
|
682
|
+
});
|
|
683
|
+
for (const entry of snapshot.running)
|
|
684
|
+
tracked.set(entry.issue.id, {
|
|
685
|
+
identifier: entry.issue.identifier,
|
|
686
|
+
workerHost: entry.workerHost,
|
|
687
|
+
workspacePath: entry.workspacePath,
|
|
688
|
+
});
|
|
689
|
+
for (const entry of snapshot.retrying)
|
|
690
|
+
tracked.set(entry.issueId, {
|
|
691
|
+
identifier: entry.identifier,
|
|
692
|
+
workerHost: entry.workerHost,
|
|
693
|
+
workspacePath: entry.workspacePath,
|
|
694
|
+
});
|
|
695
|
+
if (tracked.size === 0)
|
|
696
|
+
return;
|
|
697
|
+
let refreshed;
|
|
698
|
+
try {
|
|
699
|
+
refreshed = await this.client.fetchIssuesByIds([...tracked.keys()]);
|
|
700
|
+
}
|
|
701
|
+
catch (error) {
|
|
702
|
+
this.addEvent("reconcile_refresh_failed", errorMessage(error));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const refreshedIds = new Set(refreshed.map((issue) => issue.id));
|
|
706
|
+
for (const issue of refreshed) {
|
|
707
|
+
if (issueIsActive(issue, this.workflow.settings) &&
|
|
708
|
+
routedToThisWorker(issue, this.workflow.settings) &&
|
|
709
|
+
!issueHasOpenBlockers(issue, this.workflow.settings)) {
|
|
710
|
+
this.orchestrator.refreshRunningIssue(issue);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
this.abortIssueRuns(issue.id);
|
|
714
|
+
this.orchestrator.cleanupIssue(issue.id);
|
|
715
|
+
this.clearRetryTimer(issue.id);
|
|
716
|
+
const reason = reconciliationStopReason(issue, this.workflow.settings);
|
|
717
|
+
if (isTerminalState(issue.state, this.workflow.settings.tracker.terminalStates)) {
|
|
718
|
+
await this.removeIssueWorkspaces(this.workflow.settings, issue.identifier || tracked.get(issue.id)?.identifier, tracked.get(issue.id)?.workerHost, issue);
|
|
719
|
+
this.addEvent("workspace_cleanup", `${issue.identifier} ${reason}`);
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
this.addEvent("run_reconciled", `${issue.identifier} ${reason}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
for (const [issueId, meta] of tracked.entries()) {
|
|
726
|
+
if (refreshedIds.has(issueId))
|
|
727
|
+
continue;
|
|
728
|
+
this.abortIssueRuns(issueId);
|
|
729
|
+
this.orchestrator.cleanupIssue(issueId);
|
|
730
|
+
this.clearRetryTimer(issueId);
|
|
731
|
+
this.addEvent("run_reconciled", `${meta.identifier} missing`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
reconcileStalledRuns() {
|
|
735
|
+
for (const snapshotEntry of this.orchestrator.snapshot().running) {
|
|
736
|
+
const currentEntry = this.runningEntry(snapshotEntry.issue.id, snapshotEntry.slotIndex);
|
|
737
|
+
if (!currentEntry)
|
|
738
|
+
continue;
|
|
739
|
+
const effective = settingsForIssueState(this.workflow.settings, currentEntry.issue.state);
|
|
740
|
+
const agent = effective.agents[currentEntry.agentKind];
|
|
741
|
+
if (!agent)
|
|
742
|
+
throw new Error(`agents.${currentEntry.agentKind} is required`);
|
|
743
|
+
const timeoutMs = agent.stallTimeoutMs;
|
|
744
|
+
if (timeoutMs <= 0)
|
|
745
|
+
continue;
|
|
746
|
+
const lastActivity = currentEntry.lastAgentTimestamp ?? currentEntry.startedAt;
|
|
747
|
+
const elapsedMs = this.clock.now().getTime() - lastActivity.getTime();
|
|
748
|
+
if (elapsedMs <= timeoutMs)
|
|
749
|
+
continue;
|
|
750
|
+
const key = slotKey(currentEntry.issue.id, currentEntry.slotIndex);
|
|
751
|
+
const activeHandle = this.activeRuns.get(key);
|
|
752
|
+
const runId = activeHandle?.runId ?? `stalled-${currentEntry.issue.id}-${currentEntry.slotIndex}`;
|
|
753
|
+
const error = `agent_stalled after ${timeoutMs}ms`;
|
|
754
|
+
const entry = this.runningEntry(snapshotEntry.issue.id, snapshotEntry.slotIndex);
|
|
755
|
+
if (!entry)
|
|
756
|
+
continue;
|
|
757
|
+
this.orchestrator.finish(entry.issue.id, entry.slotIndex, true, error, "failure");
|
|
758
|
+
this.syncRetryTimer(entry.issue.id);
|
|
759
|
+
activeHandle?.finishExternally("stalled");
|
|
760
|
+
const endedAt = this.clock.now().toISOString();
|
|
761
|
+
this.recordHistory(buildRunHistoryEntry({
|
|
762
|
+
id: runId,
|
|
763
|
+
issue: entry.issue,
|
|
764
|
+
issueIdentifier: entry.identifier,
|
|
765
|
+
slotIndex: entry.slotIndex,
|
|
766
|
+
agentKind: entry.agentKind,
|
|
767
|
+
outcome: "stalled",
|
|
768
|
+
turnCount: entry.turnCount,
|
|
769
|
+
runningEntry: entry,
|
|
770
|
+
startedAt: entry.startedAt.toISOString(),
|
|
771
|
+
endedAt,
|
|
772
|
+
durationMs: durationMs(entry.startedAt.toISOString(), endedAt),
|
|
773
|
+
error,
|
|
774
|
+
fallbackLastEvent: "agent_stalled",
|
|
775
|
+
}));
|
|
776
|
+
this.addEvent("run_stalled", `${entry.identifier} ${error}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
runningEntry(issueId, slotIndex) {
|
|
780
|
+
return this.orchestrator
|
|
781
|
+
.snapshot()
|
|
782
|
+
.running.find((entry) => entry.issue.id === issueId && entry.slotIndex === slotIndex);
|
|
783
|
+
}
|
|
784
|
+
// Cleanup is driven by what is actually on disk: list existing per-issue workspace
|
|
785
|
+
// directories and look up just those issues, instead of enumerating every terminal
|
|
786
|
+
// issue the tracker has ever seen (which scales with project history, not with
|
|
787
|
+
// leftover workspaces, and can blow the tracker request budget on large projects).
|
|
788
|
+
async cleanupTerminalWorkspacesOnce() {
|
|
789
|
+
if (this.startupCleanupDone)
|
|
790
|
+
return;
|
|
791
|
+
this.startupCleanupDone = true;
|
|
792
|
+
if (!this.input.listIssueWorkspaces)
|
|
793
|
+
return;
|
|
794
|
+
try {
|
|
795
|
+
const identifiers = await this.input.listIssueWorkspaces(this.workflow.settings);
|
|
796
|
+
if (identifiers.length === 0)
|
|
797
|
+
return;
|
|
798
|
+
const issues = await this.client.fetchIssuesByIds(identifiers);
|
|
799
|
+
let cleaned = 0;
|
|
800
|
+
for (const issue of issues) {
|
|
801
|
+
if (!isTerminalState(issue.state, this.workflow.settings.tracker.terminalStates))
|
|
802
|
+
continue;
|
|
803
|
+
await this.removeIssueWorkspaces(this.workflow.settings, issue.identifier, undefined, issue);
|
|
804
|
+
cleaned += 1;
|
|
805
|
+
}
|
|
806
|
+
if (cleaned > 0)
|
|
807
|
+
this.addEvent("startup_workspace_cleanup", `terminal=${cleaned}`);
|
|
808
|
+
}
|
|
809
|
+
catch (error) {
|
|
810
|
+
this.addEvent("startup_workspace_cleanup_failed", errorMessage(error));
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
abortIssueRuns(issueId) {
|
|
814
|
+
for (const [key, handle] of this.activeRuns.entries()) {
|
|
815
|
+
if (!key.startsWith(`${issueId}:`))
|
|
816
|
+
continue;
|
|
817
|
+
handle.finishExternally();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
syncRetryTimer(issueId) {
|
|
821
|
+
const retry = this.orchestrator.snapshot().retrying.find((entry) => entry.issueId === issueId);
|
|
822
|
+
this.syncRetryTimerEntry(issueId, retry);
|
|
823
|
+
}
|
|
824
|
+
syncRetryTimerEntry(issueId, retry) {
|
|
825
|
+
if (!retry) {
|
|
826
|
+
this.clearRetryTimer(issueId);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
this.retryScheduler.sync(runtimeRetryEntry(retry), (scheduled) => {
|
|
830
|
+
const current = this.orchestrator
|
|
831
|
+
.snapshot()
|
|
832
|
+
.retrying.find((entry) => entry.issueId === scheduled.issueId);
|
|
833
|
+
if (!current ||
|
|
834
|
+
current.attempt !== scheduled.attempt ||
|
|
835
|
+
current.dueAtIso !== scheduled.dueAtIso) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
this.addEvent("retry_timer_due", `${scheduled.issueIdentifier} attempt=${scheduled.attempt}`);
|
|
839
|
+
if (this.pollInProgress) {
|
|
840
|
+
this.queuePendingPoll({}, true);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
this.pollOnce().catch((error) => {
|
|
844
|
+
this.lastError = errorMessage(error);
|
|
845
|
+
this.addEvent("retry_timer_error", this.lastError);
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
clearRetryTimer(issueId) {
|
|
850
|
+
this.retryScheduler.clear(issueId);
|
|
851
|
+
}
|
|
852
|
+
syncRetryTimersForIssues(issues) {
|
|
853
|
+
const retryByIssueId = new Map();
|
|
854
|
+
for (const retry of this.orchestrator.snapshot().retrying) {
|
|
855
|
+
if (!retryByIssueId.has(retry.issueId))
|
|
856
|
+
retryByIssueId.set(retry.issueId, retry);
|
|
857
|
+
}
|
|
858
|
+
for (const issue of issues)
|
|
859
|
+
this.syncRetryTimerEntry(issue.id, retryByIssueId.get(issue.id));
|
|
860
|
+
}
|
|
861
|
+
recordHistory(entry) {
|
|
862
|
+
this.projection.recordRunHistory(entry);
|
|
863
|
+
}
|
|
864
|
+
addEvent(type, message) {
|
|
865
|
+
const event = { type, message, at: this.clock.now().toISOString() };
|
|
866
|
+
this.projection.recordEvent(event);
|
|
867
|
+
void this.appendLogEvent(this.workflow.settings.logging.logFile, {
|
|
868
|
+
at: event.at,
|
|
869
|
+
event: type,
|
|
870
|
+
message,
|
|
871
|
+
}).catch((err) => {
|
|
872
|
+
process.stderr.write(`appendLogEvent failed: ${err}\n`);
|
|
873
|
+
});
|
|
874
|
+
this.emit();
|
|
875
|
+
}
|
|
876
|
+
async removeIssueWorkspaces(settings, issueIdentifier, workerHost, issue) {
|
|
877
|
+
if (this.input.removeIssueWorkspaces) {
|
|
878
|
+
return this.input.removeIssueWorkspaces(settings, issueIdentifier, workerHost, issue, {
|
|
879
|
+
onHookEvent: (message) => this.addEvent("hook_execution", hookExecutionRuntimeMessage(issue?.identifier ?? issueIdentifier ?? "unknown", message)),
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
throw new Error("runtime_adapter_missing: removeIssueWorkspaces");
|
|
883
|
+
}
|
|
884
|
+
async appendLogEvent(logFile, event) {
|
|
885
|
+
if (!logFile)
|
|
886
|
+
return;
|
|
887
|
+
if (this.input.appendLogEvent)
|
|
888
|
+
return this.input.appendLogEvent(logFile, event);
|
|
889
|
+
}
|
|
890
|
+
emit() {
|
|
891
|
+
const snapshot = this.snapshot();
|
|
892
|
+
for (const listener of this.listeners)
|
|
893
|
+
listener(snapshot);
|
|
894
|
+
}
|
|
895
|
+
requestRefresh() {
|
|
896
|
+
const coalesced = this.pollStatus === "checking";
|
|
897
|
+
if (!coalesced) {
|
|
898
|
+
this.pollOnce().catch((error) => {
|
|
899
|
+
this.lastError = errorMessage(error);
|
|
900
|
+
this.addEvent("refresh_error", this.lastError);
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
requested_at: this.clock.now().toISOString(),
|
|
905
|
+
queued: true,
|
|
906
|
+
coalesced,
|
|
907
|
+
operations: ["poll", "reconcile"],
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function buildRunHistoryEntry(input) {
|
|
912
|
+
const entry = input.runningEntry;
|
|
913
|
+
const workspacePath = "workspacePath" in input ? input.workspacePath : entry?.workspacePath;
|
|
914
|
+
return {
|
|
915
|
+
id: input.id,
|
|
916
|
+
issueId: input.issue.id,
|
|
917
|
+
issueIdentifier: input.issueIdentifier ?? input.issue.identifier,
|
|
918
|
+
issueTitle: input.issue.title,
|
|
919
|
+
state: "state" in input ? input.state : input.issue.state,
|
|
920
|
+
slotIndex: input.slotIndex,
|
|
921
|
+
ensembleSize: entry?.ensembleSize,
|
|
922
|
+
agentKind: input.agentKind,
|
|
923
|
+
outcome: input.outcome,
|
|
924
|
+
turnCount: input.turnCount,
|
|
925
|
+
sessionId: entry?.sessionId,
|
|
926
|
+
executorPid: entry?.executorPid,
|
|
927
|
+
workspacePath,
|
|
928
|
+
workerHost: entry?.workerHost,
|
|
929
|
+
usageTotals: entry?.usageTotals,
|
|
930
|
+
startedAt: input.startedAt,
|
|
931
|
+
endedAt: input.endedAt,
|
|
932
|
+
durationMs: input.durationMs,
|
|
933
|
+
...(input.error !== undefined ? { error: input.error } : {}),
|
|
934
|
+
lastEvent: entry?.lastAgentEvent ?? input.fallbackLastEvent,
|
|
935
|
+
lastMessage: entry?.lastAgentMessage,
|
|
936
|
+
lastEventAt: entry?.lastAgentTimestamp?.toISOString() ?? null,
|
|
937
|
+
retryAttempt: entry?.retryAttempt,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function runtimeRunningEntry(entry, runId) {
|
|
941
|
+
return {
|
|
942
|
+
runId,
|
|
943
|
+
issueId: entry.issue.id,
|
|
944
|
+
issueIdentifier: entry.identifier,
|
|
945
|
+
issueUrl: entry.issue.url ?? null,
|
|
946
|
+
issueTitle: entry.issue.title,
|
|
947
|
+
state: entry.issue.state,
|
|
948
|
+
slotIndex: entry.slotIndex,
|
|
949
|
+
ensembleSize: entry.ensembleSize,
|
|
950
|
+
agentKind: entry.agentKind,
|
|
951
|
+
sessionId: entry.sessionId,
|
|
952
|
+
executorPid: entry.executorPid,
|
|
953
|
+
workerHost: entry.workerHost,
|
|
954
|
+
turnCount: entry.turnCount,
|
|
955
|
+
startedAt: entry.startedAt.toISOString(),
|
|
956
|
+
lastEvent: entry.lastAgentEvent,
|
|
957
|
+
lastMessage: entry.lastAgentMessage,
|
|
958
|
+
lastEventAt: entry.lastAgentTimestamp?.toISOString() ?? null,
|
|
959
|
+
workspacePath: entry.workspacePath,
|
|
960
|
+
usageTotals: { ...entry.usageTotals },
|
|
961
|
+
retryAttempt: entry.retryAttempt,
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
function runtimeRetryEntry(entry) {
|
|
965
|
+
return {
|
|
966
|
+
issueId: entry.issueId,
|
|
967
|
+
issueIdentifier: entry.identifier,
|
|
968
|
+
issueUrl: entry.issueUrl ?? null,
|
|
969
|
+
attempt: entry.attempt,
|
|
970
|
+
dueAtIso: entry.dueAtIso,
|
|
971
|
+
monotonicDeadlineMs: entry.monotonicDeadlineMs,
|
|
972
|
+
...(entry.error !== undefined ? { error: entry.error } : {}),
|
|
973
|
+
...(entry.slotIndex !== undefined ? { slotIndex: entry.slotIndex } : {}),
|
|
974
|
+
...(entry.workerHost !== undefined ? { workerHost: entry.workerHost } : {}),
|
|
975
|
+
...(entry.workspacePath !== undefined ? { workspacePath: entry.workspacePath } : {}),
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function agentUpdateRuntimeMessage(issueIdentifier, update) {
|
|
979
|
+
if (update.type !== "hook_execution")
|
|
980
|
+
return `${issueIdentifier} ${update.type}`;
|
|
981
|
+
return hookExecutionRuntimeMessage(issueIdentifier, update.message);
|
|
982
|
+
}
|
|
983
|
+
function hookExecutionRuntimeMessage(issueIdentifier, message) {
|
|
984
|
+
const hookName = message.hookName ?? "hook";
|
|
985
|
+
const parts = [
|
|
986
|
+
`${issueIdentifier} ${hookName} hook ${message.status}`,
|
|
987
|
+
`command=${inlineLogValue(message.command)}`,
|
|
988
|
+
];
|
|
989
|
+
if (message.exitCode !== undefined)
|
|
990
|
+
parts.push(`exit_code=${message.exitCode ?? "unknown"}`);
|
|
991
|
+
if (message.error) {
|
|
992
|
+
const suffix = message.errorTruncated ? " (truncated)" : "";
|
|
993
|
+
parts.push(`error=${inlineLogValue(message.error)}${suffix}`);
|
|
994
|
+
}
|
|
995
|
+
if (message.output) {
|
|
996
|
+
const suffix = message.outputTruncated ? " (truncated)" : "";
|
|
997
|
+
parts.push(`output=${inlineLogValue(message.output)}${suffix}`);
|
|
998
|
+
}
|
|
999
|
+
return parts.join(" ");
|
|
1000
|
+
}
|
|
1001
|
+
function inlineLogValue(value) {
|
|
1002
|
+
return JSON.stringify(value.replace(/\s+/g, " ").trim());
|
|
1003
|
+
}
|
|
1004
|
+
async function delay(clock, ms, stopped) {
|
|
1005
|
+
const stepMs = Math.min(Math.max(ms, 25), 250);
|
|
1006
|
+
let remaining = ms;
|
|
1007
|
+
while (remaining > 0 && !stopped()) {
|
|
1008
|
+
await new Promise((resolve) => clock.setTimeout(resolve, Math.min(stepMs, remaining)));
|
|
1009
|
+
remaining -= stepMs;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function missingRuntimeClient() {
|
|
1013
|
+
throw new Error("runtime tracker client or clientFactory is required");
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Builds a disabled-equivalent of the prior worker-pool settings so a reload that REMOVES the
|
|
1017
|
+
* `worker.worker_pool` block (next === undefined) can still reconcile the live pool to a drain
|
|
1018
|
+
* rather than leaking it. Preserves `drainDeadlineMs` from the prior settings so the drain
|
|
1019
|
+
* honors the operator's configured deadline. Returns `undefined` when there were no prior
|
|
1020
|
+
* settings (nothing to drain).
|
|
1021
|
+
*/
|
|
1022
|
+
function disabledWorkerPoolSettings(prev) {
|
|
1023
|
+
if (!prev)
|
|
1024
|
+
return undefined;
|
|
1025
|
+
// A bare spread would copy the enumerable `maxInFlight` getter as a plain data property that
|
|
1026
|
+
// could drift from `slotsPerMachine`; strip it and re-install the derived accessor, matching
|
|
1027
|
+
// the config package's parse/clone paths.
|
|
1028
|
+
const { maxInFlight: _maxInFlight, ...rest } = prev;
|
|
1029
|
+
return withDerivedMaxInFlight({ ...rest, enabled: false });
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Wraps a bare {@link WorkerPool} in a null-endpoint passthrough {@link DispatchCoordinator} so the
|
|
1033
|
+
* runtime drives every run through the uniform coordinator surface while a bare-pool injection
|
|
1034
|
+
* stays byte-identical at the runtime boundary. STEP 1's null manager mints nothing
|
|
1035
|
+
* (`perRunEndpoint=false`, every `RunSlot.mcpEndpoint=null`), so this is a 1:1 passthrough over
|
|
1036
|
+
* the pool: `acquireRunSlot` delegates to `pool.acquire`, settle delegates straight to the
|
|
1037
|
+
* `WorkerLease`, and `reconcile`/`drain`/`governs`/`canAcquire` forward verbatim. Returns
|
|
1038
|
+
* `undefined` when no pool is supplied (the static/local path).
|
|
1039
|
+
*
|
|
1040
|
+
* `settings` only needs to satisfy the coordinator's constructor; in STEP 1 the coordinator does
|
|
1041
|
+
* not read it past construction (the pool owns live settings), so the live `worker.workerPool`
|
|
1042
|
+
* settings are passed when present and a disabled placeholder otherwise.
|
|
1043
|
+
*/
|
|
1044
|
+
function wrapWorkerPoolInCoordinator(pool, settings) {
|
|
1045
|
+
if (!pool)
|
|
1046
|
+
return undefined;
|
|
1047
|
+
// A bare workerPool is only ever injected alongside a configured `worker.worker_pool`
|
|
1048
|
+
// block, so its settings are present in practice. The disabled placeholder is a
|
|
1049
|
+
// defensive fallback for the never-in-practice case; STEP 1's coordinator does
|
|
1050
|
+
// not read `settings` past construction (the pool owns live settings), so the
|
|
1051
|
+
// exact values are irrelevant to behavior - only the shape must satisfy the
|
|
1052
|
+
// constructor.
|
|
1053
|
+
const workerPoolSettings = settings.worker.workerPool ??
|
|
1054
|
+
withDerivedMaxInFlight({
|
|
1055
|
+
enabled: false,
|
|
1056
|
+
driver: "fake",
|
|
1057
|
+
min: 0,
|
|
1058
|
+
max: 0,
|
|
1059
|
+
warm: 0,
|
|
1060
|
+
slotsPerMachine: 1,
|
|
1061
|
+
ttlMs: 0,
|
|
1062
|
+
idleReapMs: 0,
|
|
1063
|
+
acquireTimeoutMs: 0,
|
|
1064
|
+
reapIntervalMs: 0,
|
|
1065
|
+
staleHeartbeatMs: 0,
|
|
1066
|
+
drainDeadlineMs: 0,
|
|
1067
|
+
});
|
|
1068
|
+
return createDispatchCoordinator({
|
|
1069
|
+
pool,
|
|
1070
|
+
mcpEndpointManager: nullEndpointManager,
|
|
1071
|
+
settings: workerPoolSettings,
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
//# sourceMappingURL=index.js.map
|