toolroll 0.5.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 +21 -0
- package/README.md +1113 -0
- package/THIRD_PARTY_NOTICES.md +449 -0
- package/dist/accent-colors.d.ts +48 -0
- package/dist/accent-colors.js +122 -0
- package/dist/action-ledger.d.ts +26 -0
- package/dist/action-ledger.js +75 -0
- package/dist/agent-fence.d.ts +42 -0
- package/dist/agent-fence.js +183 -0
- package/dist/agentconfig.d.ts +216 -0
- package/dist/agentconfig.js +454 -0
- package/dist/api-tokens.d.ts +16 -0
- package/dist/api-tokens.js +27 -0
- package/dist/approval-policy.d.ts +69 -0
- package/dist/approval-policy.js +137 -0
- package/dist/approval-rules-ui.d.ts +25 -0
- package/dist/approval-rules-ui.js +39 -0
- package/dist/assignment-adapters.d.ts +180 -0
- package/dist/assignment-adapters.js +239 -0
- package/dist/assignment-brief.d.ts +71 -0
- package/dist/assignment-brief.js +129 -0
- package/dist/assignment-delivery.d.ts +56 -0
- package/dist/assignment-delivery.js +160 -0
- package/dist/assignment-presentation.d.ts +26 -0
- package/dist/assignment-presentation.js +80 -0
- package/dist/assignment-status.d.ts +62 -0
- package/dist/assignment-status.js +152 -0
- package/dist/assignment-ui.d.ts +71 -0
- package/dist/assignment-ui.js +103 -0
- package/dist/assignment.d.ts +222 -0
- package/dist/assignment.js +399 -0
- package/dist/attest.d.ts +56 -0
- package/dist/attest.js +153 -0
- package/dist/backend.d.ts +97 -0
- package/dist/backend.js +166 -0
- package/dist/backup-ui.d.ts +22 -0
- package/dist/backup-ui.js +59 -0
- package/dist/backup.d.ts +84 -0
- package/dist/backup.js +421 -0
- package/dist/beads.d.ts +34 -0
- package/dist/beads.js +135 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +22 -0
- package/dist/board.d.ts +150 -0
- package/dist/board.js +210 -0
- package/dist/boot-identity.d.ts +63 -0
- package/dist/boot-identity.js +99 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
- package/dist/browser/workspace.css +4 -0
- package/dist/browser/workspace.js +225 -0
- package/dist/browser-crew.d.ts +11 -0
- package/dist/browser-crew.js +50 -0
- package/dist/browser-shell.d.ts +8 -0
- package/dist/browser-shell.js +60 -0
- package/dist/browser-workspace.d.ts +780 -0
- package/dist/browser-workspace.js +47 -0
- package/dist/budget-alerts.d.ts +19 -0
- package/dist/budget-alerts.js +46 -0
- package/dist/builder.d.ts +370 -0
- package/dist/builder.js +3262 -0
- package/dist/capscan.d.ts +35 -0
- package/dist/capscan.js +113 -0
- package/dist/chat-acceptance.d.ts +71 -0
- package/dist/chat-acceptance.js +120 -0
- package/dist/chat-actions.d.ts +265 -0
- package/dist/chat-actions.js +1273 -0
- package/dist/chat-channel.d.ts +109 -0
- package/dist/chat-channel.js +652 -0
- package/dist/chat-continuity.d.ts +32 -0
- package/dist/chat-continuity.js +317 -0
- package/dist/chat-controls.d.ts +113 -0
- package/dist/chat-controls.js +53 -0
- package/dist/chat-delivery-state.d.ts +159 -0
- package/dist/chat-delivery-state.js +309 -0
- package/dist/chat-delivery.d.ts +38 -0
- package/dist/chat-delivery.js +693 -0
- package/dist/chat-display.d.ts +1 -0
- package/dist/chat-display.js +18 -0
- package/dist/chat-evidence.d.ts +123 -0
- package/dist/chat-evidence.js +207 -0
- package/dist/chat-flow.d.ts +44 -0
- package/dist/chat-flow.js +215 -0
- package/dist/chat-inbox.d.ts +57 -0
- package/dist/chat-inbox.js +123 -0
- package/dist/chat-polish.d.ts +16 -0
- package/dist/chat-polish.js +135 -0
- package/dist/chat-review.d.ts +46 -0
- package/dist/chat-review.js +98 -0
- package/dist/chat-rooms.d.ts +123 -0
- package/dist/chat-rooms.js +216 -0
- package/dist/chat-task-actions.d.ts +44 -0
- package/dist/chat-task-actions.js +79 -0
- package/dist/check-progress.d.ts +51 -0
- package/dist/check-progress.js +290 -0
- package/dist/child-database.d.ts +12 -0
- package/dist/child-database.js +30 -0
- package/dist/claim.d.ts +688 -0
- package/dist/claim.js +1740 -0
- package/dist/cli.d.ts +137 -0
- package/dist/cli.js +1461 -0
- package/dist/codex-limits.d.ts +15 -0
- package/dist/codex-limits.js +145 -0
- package/dist/coding-context.d.ts +55 -0
- package/dist/coding-context.js +92 -0
- package/dist/coding-handoff.d.ts +57 -0
- package/dist/coding-handoff.js +277 -0
- package/dist/coding-provider.d.ts +72 -0
- package/dist/coding-provider.js +424 -0
- package/dist/coding-shipping-ui.d.ts +4 -0
- package/dist/coding-shipping-ui.js +12 -0
- package/dist/coding-types.d.ts +62 -0
- package/dist/coding-types.js +1 -0
- package/dist/coding-ui.d.ts +17 -0
- package/dist/coding-ui.js +364 -0
- package/dist/coding-update.d.ts +15 -0
- package/dist/coding-update.js +149 -0
- package/dist/coding-workspace.d.ts +124 -0
- package/dist/coding-workspace.js +768 -0
- package/dist/container-state.d.ts +5 -0
- package/dist/container-state.js +62 -0
- package/dist/containment.d.ts +197 -0
- package/dist/containment.js +559 -0
- package/dist/contest.d.ts +318 -0
- package/dist/contest.js +753 -0
- package/dist/control-setup.d.ts +35 -0
- package/dist/control-setup.js +40 -0
- package/dist/control-ui.d.ts +29 -0
- package/dist/control-ui.js +53 -0
- package/dist/controller-service.d.ts +1 -0
- package/dist/controller-service.js +19 -0
- package/dist/controller-supervisor.d.ts +30 -0
- package/dist/controller-supervisor.js +104 -0
- package/dist/converse.d.ts +304 -0
- package/dist/converse.js +849 -0
- package/dist/coordinator-proposals.d.ts +29 -0
- package/dist/coordinator-proposals.js +137 -0
- package/dist/coordinator.d.ts +186 -0
- package/dist/coordinator.js +447 -0
- package/dist/credentials-ui.d.ts +25 -0
- package/dist/credentials-ui.js +48 -0
- package/dist/daemon.d.ts +209 -0
- package/dist/daemon.js +604 -0
- package/dist/decision.d.ts +120 -0
- package/dist/decision.js +388 -0
- package/dist/demo.d.ts +55 -0
- package/dist/demo.js +992 -0
- package/dist/desktop-access.d.ts +28 -0
- package/dist/desktop-access.js +88 -0
- package/dist/desktop-bundle.d.ts +16 -0
- package/dist/desktop-bundle.js +74 -0
- package/dist/desktop-host.d.ts +54 -0
- package/dist/desktop-host.js +508 -0
- package/dist/desktop-update-gate.d.ts +12 -0
- package/dist/desktop-update-gate.js +91 -0
- package/dist/desktop-update-recovery.d.ts +14 -0
- package/dist/desktop-update-recovery.js +243 -0
- package/dist/desktop-update.d.ts +110 -0
- package/dist/desktop-update.js +740 -0
- package/dist/discord-api.d.ts +20 -0
- package/dist/discord-api.js +138 -0
- package/dist/discord-chat.d.ts +16 -0
- package/dist/discord-chat.js +381 -0
- package/dist/discord-settings.d.ts +7 -0
- package/dist/discord-settings.js +75 -0
- package/dist/discord.d.ts +10 -0
- package/dist/discord.js +169 -0
- package/dist/discover.d.ts +75 -0
- package/dist/discover.js +150 -0
- package/dist/dispatch.d.ts +100 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispose.d.ts +161 -0
- package/dist/dispose.js +803 -0
- package/dist/email-settings.d.ts +34 -0
- package/dist/email-settings.js +50 -0
- package/dist/envelope.d.ts +47 -0
- package/dist/envelope.js +105 -0
- package/dist/evidence-pack.d.ts +185 -0
- package/dist/evidence-pack.js +258 -0
- package/dist/evidence.d.ts +345 -0
- package/dist/evidence.js +782 -0
- package/dist/exec.d.ts +285 -0
- package/dist/exec.js +1841 -0
- package/dist/exhaustion.d.ts +85 -0
- package/dist/exhaustion.js +141 -0
- package/dist/export-ui.d.ts +4 -0
- package/dist/export-ui.js +17 -0
- package/dist/export.d.ts +48 -0
- package/dist/export.js +305 -0
- package/dist/flow-actions.d.ts +77 -0
- package/dist/flow-actions.js +203 -0
- package/dist/flow-code.d.ts +50 -0
- package/dist/flow-code.js +159 -0
- package/dist/flow-draft.d.ts +38 -0
- package/dist/flow-draft.js +80 -0
- package/dist/flow-engine.d.ts +66 -0
- package/dist/flow-engine.js +416 -0
- package/dist/flow-insights.d.ts +95 -0
- package/dist/flow-insights.js +149 -0
- package/dist/flow-live.d.ts +24 -0
- package/dist/flow-live.js +150 -0
- package/dist/flow-people.d.ts +24 -0
- package/dist/flow-people.js +91 -0
- package/dist/flow-replies.d.ts +48 -0
- package/dist/flow-replies.js +113 -0
- package/dist/flow-scripts.d.ts +38 -0
- package/dist/flow-scripts.js +73 -0
- package/dist/flow-secrets.d.ts +13 -0
- package/dist/flow-secrets.js +45 -0
- package/dist/flow-sort.d.ts +82 -0
- package/dist/flow-sort.js +153 -0
- package/dist/flow-steps.d.ts +40 -0
- package/dist/flow-steps.js +408 -0
- package/dist/flow-triggers.d.ts +244 -0
- package/dist/flow-triggers.js +959 -0
- package/dist/flows-ui.d.ts +20 -0
- package/dist/flows-ui.js +175 -0
- package/dist/flows.d.ts +229 -0
- package/dist/flows.js +968 -0
- package/dist/fonts.d.ts +19 -0
- package/dist/fonts.js +19 -0
- package/dist/gaps.d.ts +35 -0
- package/dist/gaps.js +101 -0
- package/dist/gate-failure.d.ts +40 -0
- package/dist/gate-failure.js +66 -0
- package/dist/git.d.ts +54 -0
- package/dist/git.js +94 -0
- package/dist/google-mail.d.ts +58 -0
- package/dist/google-mail.js +151 -0
- package/dist/grant.d.ts +136 -0
- package/dist/grant.js +238 -0
- package/dist/graph.d.ts +164 -0
- package/dist/graph.js +383 -0
- package/dist/guides.d.ts +22 -0
- package/dist/guides.js +363 -0
- package/dist/held.d.ts +150 -0
- package/dist/held.js +799 -0
- package/dist/invoke.d.ts +112 -0
- package/dist/invoke.js +780 -0
- package/dist/issues.d.ts +41 -0
- package/dist/issues.js +131 -0
- package/dist/job-object-helper.ps1 +252 -0
- package/dist/jsonl-discriminants.d.ts +11 -0
- package/dist/jsonl-discriminants.js +93 -0
- package/dist/keys.d.ts +104 -0
- package/dist/keys.js +229 -0
- package/dist/kits-ui.d.ts +17 -0
- package/dist/kits-ui.js +46 -0
- package/dist/kits.d.ts +86 -0
- package/dist/kits.js +215 -0
- package/dist/knowledge-cli.d.ts +101 -0
- package/dist/knowledge-cli.js +92 -0
- package/dist/knowledge-ui.d.ts +24 -0
- package/dist/knowledge-ui.js +40 -0
- package/dist/lead-context.d.ts +8 -0
- package/dist/lead-context.js +56 -0
- package/dist/lead-follow.d.ts +22 -0
- package/dist/lead-follow.js +167 -0
- package/dist/lead-status.d.ts +75 -0
- package/dist/lead-status.js +285 -0
- package/dist/ledger-chain.d.ts +46 -0
- package/dist/ledger-chain.js +187 -0
- package/dist/ledger-csv.d.ts +4 -0
- package/dist/ledger-csv.js +11 -0
- package/dist/ledger-view.d.ts +24 -0
- package/dist/ledger-view.js +58 -0
- package/dist/limits-ui.d.ts +26 -0
- package/dist/limits-ui.js +67 -0
- package/dist/link.d.ts +72 -0
- package/dist/link.js +217 -0
- package/dist/live.d.ts +96 -0
- package/dist/live.js +366 -0
- package/dist/liveness.d.ts +30 -0
- package/dist/liveness.js +42 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +25 -0
- package/dist/mailbox.d.ts +57 -0
- package/dist/mailbox.js +150 -0
- package/dist/maintenance.d.ts +11 -0
- package/dist/maintenance.js +35 -0
- package/dist/mate-cli.d.ts +52 -0
- package/dist/mate-cli.js +345 -0
- package/dist/mate-contract.d.ts +10 -0
- package/dist/mate-contract.js +30 -0
- package/dist/mate-doors.d.ts +69 -0
- package/dist/mate-doors.js +548 -0
- package/dist/mate-progress.d.ts +29 -0
- package/dist/mate-progress.js +121 -0
- package/dist/mate-tools.d.ts +94 -0
- package/dist/mate-tools.js +1878 -0
- package/dist/mate.d.ts +92 -0
- package/dist/mate.js +435 -0
- package/dist/mcp-connect.d.ts +103 -0
- package/dist/mcp-connect.js +252 -0
- package/dist/mcp.d.ts +27 -0
- package/dist/mcp.js +651 -0
- package/dist/memory-cli.d.ts +466 -0
- package/dist/memory-cli.js +111 -0
- package/dist/memory-pass.d.ts +160 -0
- package/dist/memory-pass.js +409 -0
- package/dist/metrics.d.ts +3 -0
- package/dist/metrics.js +56 -0
- package/dist/mobile-viewport.d.ts +3 -0
- package/dist/mobile-viewport.js +37 -0
- package/dist/model-catalog.d.ts +118 -0
- package/dist/model-catalog.js +376 -0
- package/dist/models-cli.d.ts +102 -0
- package/dist/models-cli.js +66 -0
- package/dist/models-ui.d.ts +34 -0
- package/dist/models-ui.js +68 -0
- package/dist/modes.d.ts +80 -0
- package/dist/modes.js +182 -0
- package/dist/monitoring-settings.d.ts +44 -0
- package/dist/monitoring-settings.js +173 -0
- package/dist/monitoring-ui.d.ts +20 -0
- package/dist/monitoring-ui.js +51 -0
- package/dist/monitoring.d.ts +34 -0
- package/dist/monitoring.js +221 -0
- package/dist/names.d.ts +67 -0
- package/dist/names.js +102 -0
- package/dist/observations.d.ts +40 -0
- package/dist/observations.js +211 -0
- package/dist/oidc.d.ts +71 -0
- package/dist/oidc.js +182 -0
- package/dist/onboard.d.ts +108 -0
- package/dist/onboard.js +325 -0
- package/dist/openrouter-models.d.ts +30 -0
- package/dist/openrouter-models.js +120 -0
- package/dist/operate.d.ts +118 -0
- package/dist/operate.js +11321 -0
- package/dist/peek-cli.d.ts +97 -0
- package/dist/peek-cli.js +215 -0
- package/dist/peek.d.ts +134 -0
- package/dist/peek.js +578 -0
- package/dist/phase-routing.d.ts +307 -0
- package/dist/phase-routing.js +658 -0
- package/dist/plan-auto.d.ts +14 -0
- package/dist/plan-auto.js +92 -0
- package/dist/plan.d.ts +186 -0
- package/dist/plan.js +401 -0
- package/dist/planner-source.d.ts +251 -0
- package/dist/planner-source.js +460 -0
- package/dist/planner.d.ts +75 -0
- package/dist/planner.js +992 -0
- package/dist/policy-ui.d.ts +20 -0
- package/dist/policy-ui.js +41 -0
- package/dist/policy.d.ts +111 -0
- package/dist/policy.js +231 -0
- package/dist/prepared-evidence.d.ts +16 -0
- package/dist/prepared-evidence.js +96 -0
- package/dist/pricing.d.ts +45 -0
- package/dist/pricing.js +77 -0
- package/dist/principal.d.ts +42 -0
- package/dist/principal.js +82 -0
- package/dist/probe.d.ts +46 -0
- package/dist/probe.js +79 -0
- package/dist/process-custody.d.ts +17 -0
- package/dist/process-custody.js +91 -0
- package/dist/process-liveness.d.ts +10 -0
- package/dist/process-liveness.js +81 -0
- package/dist/process-recovery-anchor.d.ts +75 -0
- package/dist/process-recovery-anchor.js +188 -0
- package/dist/process-recovery-coalition.d.ts +25 -0
- package/dist/process-recovery-coalition.js +105 -0
- package/dist/process-recovery-eligibility.d.ts +178 -0
- package/dist/process-recovery-eligibility.js +170 -0
- package/dist/process-recovery-native.d.ts +172 -0
- package/dist/process-recovery-native.js +592 -0
- package/dist/process-recovery-provenance.d.ts +163 -0
- package/dist/process-recovery-provenance.js +292 -0
- package/dist/process-recovery-services.d.ts +56 -0
- package/dist/process-recovery-services.js +191 -0
- package/dist/process-recovery-settlement.d.ts +32 -0
- package/dist/process-recovery-settlement.js +132 -0
- package/dist/process-recovery.d.ts +41 -0
- package/dist/process-recovery.js +91 -0
- package/dist/process-tree.d.ts +41 -0
- package/dist/process-tree.js +264 -0
- package/dist/project-access.d.ts +14 -0
- package/dist/project-access.js +34 -0
- package/dist/project-cli.d.ts +18 -0
- package/dist/project-cli.js +104 -0
- package/dist/project-delete-ui.d.ts +22 -0
- package/dist/project-delete-ui.js +48 -0
- package/dist/project-delete.d.ts +55 -0
- package/dist/project-delete.js +413 -0
- package/dist/project-knowledge.d.ts +111 -0
- package/dist/project-knowledge.js +241 -0
- package/dist/project-learning.d.ts +100 -0
- package/dist/project-learning.js +438 -0
- package/dist/project-memory.d.ts +81 -0
- package/dist/project-memory.js +264 -0
- package/dist/project-skills.d.ts +152 -0
- package/dist/project-skills.js +660 -0
- package/dist/project-tools.d.ts +219 -0
- package/dist/project-tools.js +796 -0
- package/dist/project.d.ts +61 -0
- package/dist/project.js +125 -0
- package/dist/prompt.d.ts +22 -0
- package/dist/prompt.js +72 -0
- package/dist/proof.d.ts +513 -0
- package/dist/proof.js +1140 -0
- package/dist/proposal.d.ts +82 -0
- package/dist/proposal.js +210 -0
- package/dist/provider-connection.d.ts +22 -0
- package/dist/provider-connection.js +98 -0
- package/dist/provider-limits.d.ts +35 -0
- package/dist/provider-limits.js +95 -0
- package/dist/provider.d.ts +279 -0
- package/dist/provider.js +935 -0
- package/dist/publish.d.ts +107 -0
- package/dist/publish.js +650 -0
- package/dist/pulls.d.ts +118 -0
- package/dist/pulls.js +240 -0
- package/dist/push.d.ts +95 -0
- package/dist/push.js +353 -0
- package/dist/quality.d.ts +8 -0
- package/dist/quality.js +6 -0
- package/dist/recipe-ui.d.ts +18 -0
- package/dist/recipe-ui.js +152 -0
- package/dist/recipes.d.ts +81 -0
- package/dist/recipes.js +332 -0
- package/dist/remote.d.ts +67 -0
- package/dist/remote.js +122 -0
- package/dist/render.d.ts +64 -0
- package/dist/render.js +587 -0
- package/dist/report-summary.d.ts +13 -0
- package/dist/report-summary.js +15 -0
- package/dist/repos.d.ts +65 -0
- package/dist/repos.js +277 -0
- package/dist/repository-context-ui.d.ts +2 -0
- package/dist/repository-context-ui.js +10 -0
- package/dist/repository-context.d.ts +67 -0
- package/dist/repository-context.js +376 -0
- package/dist/restart-certification.d.ts +134 -0
- package/dist/restart-certification.js +226 -0
- package/dist/result-actions.d.ts +28 -0
- package/dist/result-actions.js +270 -0
- package/dist/result-completion.d.ts +18 -0
- package/dist/result-completion.js +66 -0
- package/dist/result-review.d.ts +213 -0
- package/dist/result-review.js +430 -0
- package/dist/retention-ui.d.ts +12 -0
- package/dist/retention-ui.js +39 -0
- package/dist/retention.d.ts +72 -0
- package/dist/retention.js +288 -0
- package/dist/review-context.d.ts +243 -0
- package/dist/review-context.js +1043 -0
- package/dist/review-evidence.d.ts +33 -0
- package/dist/review-evidence.js +56 -0
- package/dist/reviewer.d.ts +97 -0
- package/dist/reviewer.js +202 -0
- package/dist/routine.d.ts +228 -0
- package/dist/routine.js +764 -0
- package/dist/runner.d.ts +266 -0
- package/dist/runner.js +399 -0
- package/dist/scan.d.ts +32 -0
- package/dist/scan.js +92 -0
- package/dist/scope.d.ts +695 -0
- package/dist/scope.js +1176 -0
- package/dist/scout-report.d.ts +42 -0
- package/dist/scout-report.js +108 -0
- package/dist/scout.d.ts +69 -0
- package/dist/scout.js +351 -0
- package/dist/serve.d.ts +313 -0
- package/dist/serve.js +21776 -0
- package/dist/session-brief.d.ts +8 -0
- package/dist/session-brief.js +24 -0
- package/dist/session-cli.d.ts +13 -0
- package/dist/session-cli.js +284 -0
- package/dist/session-contract.d.ts +183 -0
- package/dist/session-contract.js +122 -0
- package/dist/session-http.d.ts +19 -0
- package/dist/session-http.js +154 -0
- package/dist/session-server.d.ts +12 -0
- package/dist/session-server.js +37 -0
- package/dist/session-service.d.ts +22 -0
- package/dist/session-service.js +157 -0
- package/dist/setup-guide.d.ts +39 -0
- package/dist/setup-guide.js +93 -0
- package/dist/sign-in-guard.d.ts +45 -0
- package/dist/sign-in-guard.js +74 -0
- package/dist/skills-ui.d.ts +11 -0
- package/dist/skills-ui.js +58 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.js +276 -0
- package/dist/slack-api.d.ts +55 -0
- package/dist/slack-api.js +229 -0
- package/dist/slack-chat.d.ts +28 -0
- package/dist/slack-chat.js +413 -0
- package/dist/slack-settings.d.ts +7 -0
- package/dist/slack-settings.js +75 -0
- package/dist/slack-state.d.ts +13 -0
- package/dist/slack-state.js +9 -0
- package/dist/slack.d.ts +13 -0
- package/dist/slack.js +167 -0
- package/dist/spend-ui.d.ts +28 -0
- package/dist/spend-ui.js +95 -0
- package/dist/spend.d.ts +142 -0
- package/dist/spend.js +313 -0
- package/dist/sqlite-runtime.d.ts +3 -0
- package/dist/sqlite-runtime.js +6 -0
- package/dist/sso-settings.d.ts +33 -0
- package/dist/sso-settings.js +103 -0
- package/dist/sso-ui.d.ts +22 -0
- package/dist/sso-ui.js +39 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.js +84 -0
- package/dist/store.d.ts +7754 -0
- package/dist/store.js +23279 -0
- package/dist/structured-output.d.ts +58 -0
- package/dist/structured-output.js +103 -0
- package/dist/style-asset.d.ts +7 -0
- package/dist/style-asset.js +41 -0
- package/dist/subscription-chat.d.ts +46 -0
- package/dist/subscription-chat.js +275 -0
- package/dist/summary.d.ts +33 -0
- package/dist/summary.js +71 -0
- package/dist/supervisor.mjs +297 -0
- package/dist/surface.d.ts +63 -0
- package/dist/surface.js +352 -0
- package/dist/sync.d.ts +94 -0
- package/dist/sync.js +279 -0
- package/dist/task-composer.d.ts +13 -0
- package/dist/task-composer.js +52 -0
- package/dist/task-control.d.ts +165 -0
- package/dist/task-control.js +214 -0
- package/dist/task-outcome-cli.d.ts +10 -0
- package/dist/task-outcome-cli.js +92 -0
- package/dist/task-text.d.ts +30 -0
- package/dist/task-text.js +41 -0
- package/dist/team-cli.d.ts +15 -0
- package/dist/team-cli.js +618 -0
- package/dist/team-contract.d.ts +92 -0
- package/dist/team-contract.js +1 -0
- package/dist/team-http.d.ts +17 -0
- package/dist/team-http.js +144 -0
- package/dist/team-leads.d.ts +81 -0
- package/dist/team-leads.js +565 -0
- package/dist/team-runtime.d.ts +27 -0
- package/dist/team-runtime.js +264 -0
- package/dist/team-ui.d.ts +3 -0
- package/dist/team-ui.js +9 -0
- package/dist/team-updates.d.ts +8 -0
- package/dist/team-updates.js +57 -0
- package/dist/teammate-admin.d.ts +53 -0
- package/dist/teammate-admin.js +111 -0
- package/dist/teammate-desk.d.ts +60 -0
- package/dist/teammate-desk.js +154 -0
- package/dist/teammate-memory.d.ts +64 -0
- package/dist/teammate-memory.js +196 -0
- package/dist/teammate-question.d.ts +59 -0
- package/dist/teammate-question.js +175 -0
- package/dist/teammate-tools.d.ts +108 -0
- package/dist/teammate-tools.js +271 -0
- package/dist/teammate-week.d.ts +68 -0
- package/dist/teammate-week.js +132 -0
- package/dist/teammate-work.d.ts +76 -0
- package/dist/teammate-work.js +280 -0
- package/dist/teammates-ui.d.ts +20 -0
- package/dist/teammates-ui.js +166 -0
- package/dist/teammates.d.ts +180 -0
- package/dist/teammates.js +253 -0
- package/dist/teams-api.d.ts +33 -0
- package/dist/teams-api.js +204 -0
- package/dist/teams-chat.d.ts +26 -0
- package/dist/teams-chat.js +215 -0
- package/dist/teams-settings.d.ts +12 -0
- package/dist/teams-settings.js +57 -0
- package/dist/teams.d.ts +21 -0
- package/dist/teams.js +124 -0
- package/dist/telegram-flow.d.ts +49 -0
- package/dist/telegram-flow.js +110 -0
- package/dist/telegram-mate.d.ts +137 -0
- package/dist/telegram-mate.js +707 -0
- package/dist/telegram-progress.d.ts +14 -0
- package/dist/telegram-progress.js +129 -0
- package/dist/telegram-settings.d.ts +9 -0
- package/dist/telegram-settings.js +36 -0
- package/dist/telegram-status.d.ts +75 -0
- package/dist/telegram-status.js +265 -0
- package/dist/telegram-team.d.ts +85 -0
- package/dist/telegram-team.js +359 -0
- package/dist/telegram.d.ts +230 -0
- package/dist/telegram.js +1931 -0
- package/dist/templates.d.ts +65 -0
- package/dist/templates.js +118 -0
- package/dist/tool-launcher.d.ts +1 -0
- package/dist/tool-launcher.js +101 -0
- package/dist/tools-ui.d.ts +30 -0
- package/dist/tools-ui.js +40 -0
- package/dist/transitions-recipes.d.ts +1 -0
- package/dist/transitions-recipes.js +203 -0
- package/dist/tree-proof.d.ts +34 -0
- package/dist/tree-proof.js +58 -0
- package/dist/verification-evidence.d.ts +44 -0
- package/dist/verification-evidence.js +238 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +10 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +283 -0
- package/dist/work-index.d.ts +68 -0
- package/dist/work-index.js +384 -0
- package/dist/work-summary.d.ts +57 -0
- package/dist/work-summary.js +116 -0
- package/dist/workspace-motion.d.ts +4 -0
- package/dist/workspace-motion.js +133 -0
- package/dist/workspace-revision.d.ts +19 -0
- package/dist/workspace-revision.js +92 -0
- package/dist/workspace-ui.d.ts +204 -0
- package/dist/workspace-ui.js +411 -0
- package/dist/worktree-notices.d.ts +13 -0
- package/dist/worktree-notices.js +17 -0
- package/dist/worktree.d.ts +249 -0
- package/dist/worktree.js +720 -0
- package/package.json +122 -0
- package/scripts/canary-assertions.mjs +42 -0
- package/scripts/crash-canary.mjs +253 -0
- package/scripts/fixtures/crash-process.mjs +74 -0
- package/scripts/fixtures/pilot-scenarios.mjs +85 -0
- package/scripts/fixtures/restart-service.mjs +27 -0
- package/scripts/launchd-certification.mjs +143 -0
- package/scripts/pilot.mjs +121 -0
- package/scripts/proof-preflight.mjs +140 -0
- package/scripts/provider-canary.mjs +379 -0
- package/scripts/recovery-canary.mjs +135 -0
- package/scripts/restart-certification.mjs +98 -0
package/dist/held.js
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HeldSessionCoordinator (Parity II Phase 2, spec v2 S0 as amended
|
|
3
|
+
* through v6): the ONE owner of held attended sessions in an `up` process.
|
|
4
|
+
*
|
|
5
|
+
* Ownership transfer is the whole design: build() reaches its spawn point,
|
|
6
|
+
* hands this coordinator the captured settlement record and returns `held`
|
|
7
|
+
* WITHOUT settling anything — the watch loop keeps dispatching, and the
|
|
8
|
+
* session's lease heartbeats, exact timers, turn ledger, and eventual
|
|
9
|
+
* settlement all live here. A held run completes through the SAME
|
|
10
|
+
* settleProviderOutcome → disposeBuildOutcome pair every other road uses,
|
|
11
|
+
* or it is fenced to `interrupted` — never a third way.
|
|
12
|
+
*
|
|
13
|
+
* Phase 2D scope note, stated: this coordinator runs the attended
|
|
14
|
+
* authorization road end-to-end — final proof, custody, the brief as turn
|
|
15
|
+
* one, per-turn acceptance and marginal-delta settlement, expiry/lapse/
|
|
16
|
+
* turn-timeout/lease fences, crash custody — and ends the hold at the
|
|
17
|
+
* agent's first settled turn (terminal handoff, park, or silence, each
|
|
18
|
+
* classified by the shared settlement). The multi-turn conversation loop
|
|
19
|
+
* (operator turn box, answer injection, held repair) arrives with its
|
|
20
|
+
* console surfaces in the next slice; nothing here forecloses it.
|
|
21
|
+
*/
|
|
22
|
+
import { randomBytes } from "node:crypto";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { connect } from "node:net";
|
|
25
|
+
import { attendedLivenessState, BEAT_MS } from "./liveness.js";
|
|
26
|
+
import { settleProviderOutcome } from "./builder.js";
|
|
27
|
+
import { disposeBuildOutcome } from "./dispose.js";
|
|
28
|
+
import { heldSocketPathProblem, startClaudeHeldSession } from "./exec.js";
|
|
29
|
+
import { invokeHeldAgent } from "./invoke.js";
|
|
30
|
+
import { claudeHeldArgv } from "./provider.js";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
32
|
+
import { finalizeParkHeld, heartbeat, release } from "./claim.js";
|
|
33
|
+
import { parseDecision, repairPrompt } from "./decision.js";
|
|
34
|
+
import { captureParkEvidence, readMailbox, storeEvidence } from "./evidence.js";
|
|
35
|
+
import { profileDigestOf } from "./scope.js";
|
|
36
|
+
import { heartbeat as runnerHeartbeat } from "./runner.js";
|
|
37
|
+
const dollarsToMicrousd = (dollars) => typeof dollars === "number" && Number.isFinite(dollars) ? Math.round(dollars * 1_000_000) : null;
|
|
38
|
+
/** Thrown INSIDE the final proof to roll the whole transaction back. */
|
|
39
|
+
class ConsumeRaced extends Error {
|
|
40
|
+
}
|
|
41
|
+
export class HeldSessionCoordinator {
|
|
42
|
+
sessions = new Map();
|
|
43
|
+
count() {
|
|
44
|
+
return this.sessions.size;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The final proof, custody intent, brief recording, spawn, and timer
|
|
48
|
+
* arming — one road, refusals typed. Returns once the session is HELD
|
|
49
|
+
* (or refused); never awaits the hold itself.
|
|
50
|
+
*/
|
|
51
|
+
async launch(args) {
|
|
52
|
+
const { store, captured, runId, leaseId, runner, clock } = args;
|
|
53
|
+
const socketPath = join(args.socketDir, `so-${randomBytes(6).toString("hex")}.sock`);
|
|
54
|
+
const pathProblem = heldSocketPathProblem(socketPath);
|
|
55
|
+
if (pathProblem !== null)
|
|
56
|
+
return { ok: false, reason: "attended-unsupported", message: pathProblem };
|
|
57
|
+
const cookie = randomBytes(16).toString("hex");
|
|
58
|
+
// ---- the FINAL transactional proof (v2 S2d, v3 R6): rederived against
|
|
59
|
+
// live state AFTER worktree setup read the actual HEAD, consuming the
|
|
60
|
+
// one attempt and writing the custody intent immediately before spawn.
|
|
61
|
+
let proof;
|
|
62
|
+
try {
|
|
63
|
+
proof = store.transact(() => {
|
|
64
|
+
const live = store.readAuthorization(args.authorization.id);
|
|
65
|
+
if (live === null || live.closedAt !== null) {
|
|
66
|
+
return { ok: false, reason: "attended-only", message: "the authorization closed before dispatch" };
|
|
67
|
+
}
|
|
68
|
+
// The one attempt was consumed and bound at ADMISSION (atomic
|
|
69
|
+
// authority closure): the authorization names this very run, or it
|
|
70
|
+
// was spent elsewhere and nothing here spawns.
|
|
71
|
+
if (live.attemptRun !== runId) {
|
|
72
|
+
return { ok: false, reason: "attended-only", message: live.attemptRun === null ? "the authorization's attempt was never bound to this run at admission" : "the authorization's one attempt is already spent" };
|
|
73
|
+
}
|
|
74
|
+
let terms;
|
|
75
|
+
try {
|
|
76
|
+
terms = JSON.parse(live.termsJson);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { ok: false, reason: "stale-authorization", message: "the signed terms cannot be parsed" };
|
|
80
|
+
}
|
|
81
|
+
const liveScopeDigest = captured.scope?.digest ?? "";
|
|
82
|
+
if (terms.scopeDigest !== liveScopeDigest) {
|
|
83
|
+
return { ok: false, reason: "stale-authorization", message: "the scope moved since the authorization was signed" };
|
|
84
|
+
}
|
|
85
|
+
const liveProfileDigest = profileDigestOf(captured.effective.profile);
|
|
86
|
+
if (terms.profileDigest !== liveProfileDigest) {
|
|
87
|
+
return { ok: false, reason: "stale-authorization", message: "the execution profile moved since the authorization was signed" };
|
|
88
|
+
}
|
|
89
|
+
if (terms.repo !== args.dispose.repo) {
|
|
90
|
+
return { ok: false, reason: "stale-authorization", message: "the repository moved since the authorization was signed" };
|
|
91
|
+
}
|
|
92
|
+
if (terms.head !== captured.baseRevision) {
|
|
93
|
+
return { ok: false, reason: "stale-authorization", message: `the head moved since the authorization was signed (${terms.head.slice(0, 12)} → ${captured.baseRevision.slice(0, 12)})` };
|
|
94
|
+
}
|
|
95
|
+
if (live.runner !== runner) {
|
|
96
|
+
return { ok: false, reason: "attended-held", message: `the authorization names ${live.runner}` };
|
|
97
|
+
}
|
|
98
|
+
// Custody FIRST, consumption LAST (parallel-sessions round 1,
|
|
99
|
+
// finding 4): a refusal returned from inside transact() COMMITS —
|
|
100
|
+
// only a throw rolls back — so the old consume-then-custody order
|
|
101
|
+
// left a run-held refusal with the one attempt already spent.
|
|
102
|
+
// Every refusal below this line must leave attempt_run null.
|
|
103
|
+
if (args.maxHeldSessions !== undefined && store.openHeldSessionCount(runner) >= args.maxHeldSessions) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: "session-cap",
|
|
107
|
+
message: `this machine holds ${store.openHeldSessionCount(runner)} of ${args.maxHeldSessions} attended sessions — end one, or raise --max-held-sessions`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const custody = store.openHeldSession({
|
|
111
|
+
run: runId,
|
|
112
|
+
authorizationId: live.id,
|
|
113
|
+
runner,
|
|
114
|
+
leaseId,
|
|
115
|
+
upIncarnation: args.upIncarnation,
|
|
116
|
+
cookie,
|
|
117
|
+
socketPath,
|
|
118
|
+
now: clock(),
|
|
119
|
+
});
|
|
120
|
+
if (!custody.ok) {
|
|
121
|
+
return { ok: false, reason: custody.reason, message: "this run already holds a session" };
|
|
122
|
+
}
|
|
123
|
+
// The binding is re-proved in this same transaction: the run row
|
|
124
|
+
// carries the authorization it was admitted under, and the
|
|
125
|
+
// authorization still names the run. A disagreement rolls the
|
|
126
|
+
// custody insert back with the transaction — nothing spawns on a
|
|
127
|
+
// pair that moved.
|
|
128
|
+
const bound = store.readAuthorization(live.id);
|
|
129
|
+
const row = store.getRun(runId);
|
|
130
|
+
if (bound === null || bound.attemptRun !== runId || row === null || (row.attendedAuthorization ?? null) !== live.id) {
|
|
131
|
+
throw new ConsumeRaced();
|
|
132
|
+
}
|
|
133
|
+
return { ok: true };
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof ConsumeRaced) {
|
|
138
|
+
proof = { ok: false, reason: "attended-only", message: "another dispatch consumed the attempt first" };
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!proof.ok) {
|
|
145
|
+
// The one attempt was spent at ADMISSION (atomic authority closure):
|
|
146
|
+
// a refusal here cannot un-spend it, so the authorization closes in
|
|
147
|
+
// the refusal's words rather than standing open-and-spent until its
|
|
148
|
+
// expiry — the operator sees why, and authorizes again if they wish.
|
|
149
|
+
store.closeAuthorization(args.authorization.id, `refused:${proof.reason}`, clock());
|
|
150
|
+
return proof;
|
|
151
|
+
}
|
|
152
|
+
// ---- the brief is turn one, recorded before any byte is written.
|
|
153
|
+
const recorded = store.recordSessionTurn({ run: runId, sourceKind: "brief", text: args.brief, now: clock() });
|
|
154
|
+
if (!recorded.ok) {
|
|
155
|
+
store.endHeldSession(runId, `refused:${recorded.reason}`, clock());
|
|
156
|
+
return { ok: false, reason: recorded.reason, message: `the brief could not be recorded (${recorded.reason})` };
|
|
157
|
+
}
|
|
158
|
+
// Answers riding the brief (round-6 finding 2): the builder's ordinary
|
|
159
|
+
// pre-spawn attach claimed delivery the agent has not proven — withdraw
|
|
160
|
+
// it, CAS each decision's delivery to the brief turn, and let
|
|
161
|
+
// acceptance re-attach exactly like an answer turn.
|
|
162
|
+
store.bindBriefDeliveries(runId, recorded.turn.id, captured.answers.map(one => one.decision.id));
|
|
163
|
+
// v105: and no more than what's left of a monthly budget this work counts toward, when it bills a key.
|
|
164
|
+
const monthly = store.budgetGate(new Date())({ ...store.budgetSubject(args.captured.taskRef), agents: store.agentsFor([captured.effective.profile.provider]) }).remainingMicrousd;
|
|
165
|
+
const remainingMicrousd = Math.min(args.authorization.budgetMicrousd - store.authorizationSpendMicrousd(args.authorization.id), monthly ?? Infinity);
|
|
166
|
+
const argv = claudeHeldArgv({
|
|
167
|
+
phase: "build",
|
|
168
|
+
model: captured.effective.model,
|
|
169
|
+
maxTurns: captured.effective.maxTurns ?? 40,
|
|
170
|
+
permissionMode: captured.effective.profile.provider === "claude" && captured.effective.profile.permissionArgv !== "bypassPermissions"
|
|
171
|
+
? captured.effective.profile.permissionArgv
|
|
172
|
+
: "auto",
|
|
173
|
+
skipPermissions: captured.effective.skipPermissions,
|
|
174
|
+
resumeSession: null,
|
|
175
|
+
maxBudgetUsd: Math.max(0.01, remainingMicrousd / 1_000_000),
|
|
176
|
+
});
|
|
177
|
+
const controller = this.makeController(args, recorded.turn, socketPath, cookie);
|
|
178
|
+
const start = await invokeHeldAgent(store, runId, { provider: "claude", model: captured.effective.model }, argv, {
|
|
179
|
+
cwd: args.cwd,
|
|
180
|
+
omitEnv: args.omitEnv,
|
|
181
|
+
socketPath,
|
|
182
|
+
cookie,
|
|
183
|
+
...(args.graceMs === undefined ? {} : { graceMs: args.graceMs }),
|
|
184
|
+
...(args.starter === undefined ? {} : { starter: args.starter }),
|
|
185
|
+
events: {
|
|
186
|
+
onTurnInit: seq => this.onTurnInit(args, seq),
|
|
187
|
+
onTurnResult: (seq, event) => void this.onTurnResult(args, seq, event),
|
|
188
|
+
onStreamEvent: event => args.liveLog?.observe(event),
|
|
189
|
+
onExit: info => void this.onExit(args, info.code),
|
|
190
|
+
},
|
|
191
|
+
clock,
|
|
192
|
+
});
|
|
193
|
+
if (!start.ok) {
|
|
194
|
+
// Spawn failure with no pid ever stamped (v5's adopted F7 edge):
|
|
195
|
+
// close custody and settle explicitly — no permanent orphan.
|
|
196
|
+
store.settleTurnTerminal(recorded.turn.id, "cancelled", clock());
|
|
197
|
+
store.endHeldSession(runId, "spawn-failed", clock());
|
|
198
|
+
store.closeAuthorization(args.authorization.id, "spawn-failed", clock());
|
|
199
|
+
store.finishRun(runId, { outcome: "failed", reason: "spawn-failed", now: clock() });
|
|
200
|
+
release(store, leaseId, clock());
|
|
201
|
+
await args.releaseWorktree(args.cwd);
|
|
202
|
+
args.liveLog?.close();
|
|
203
|
+
this.drop(runId);
|
|
204
|
+
return { ok: false, reason: "spawn-failed", message: start.message };
|
|
205
|
+
}
|
|
206
|
+
controller.handle = start.handle;
|
|
207
|
+
if (!store.stampHeldSession(runId, start.handle.supervisorPid ?? -1, start.handle.agentPgid ?? -1)) {
|
|
208
|
+
// The custody row closed underneath the spawn: kill synchronously,
|
|
209
|
+
// never leave an unrecorded provider alive (v2 S1e).
|
|
210
|
+
start.handle.killHard();
|
|
211
|
+
this.drop(runId);
|
|
212
|
+
return { ok: false, reason: "fenced", message: "custody closed underneath the spawn" };
|
|
213
|
+
}
|
|
214
|
+
// The turn reaches stdin only after custody is stamped.
|
|
215
|
+
const turnJson = JSON.stringify({
|
|
216
|
+
type: "user",
|
|
217
|
+
message: { role: "user", content: [{ type: "text", text: args.brief }] },
|
|
218
|
+
});
|
|
219
|
+
if (start.handle.writeTurn(turnJson)) {
|
|
220
|
+
store.markTurnWritten(recorded.turn.id, clock());
|
|
221
|
+
this.armTurnTimer(args, controller);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
void this.fence(args, "write-failed");
|
|
225
|
+
}
|
|
226
|
+
return { ok: true };
|
|
227
|
+
}
|
|
228
|
+
/** Shutdown: fence every controller under ONE absolute deadline (v2 S0f
|
|
229
|
+
* + parallel-sessions round 1, finding 7): the deadline timer is cleared
|
|
230
|
+
* when the fences win so it cannot hold the process open, and the runs
|
|
231
|
+
* that did NOT settle in time are RETURNED for the caller to page —
|
|
232
|
+
* silence here contradicted up's "settled or paged" promise. */
|
|
233
|
+
async close() {
|
|
234
|
+
const all = [...this.sessions.keys()];
|
|
235
|
+
let deadline;
|
|
236
|
+
const timedOut = new Promise(pass => {
|
|
237
|
+
deadline = setTimeout(() => pass("deadline"), 30_000);
|
|
238
|
+
deadline.unref?.();
|
|
239
|
+
});
|
|
240
|
+
const winner = await Promise.race([
|
|
241
|
+
Promise.all(all.map(runId => this.fenceByRun(runId, "shutdown"))).then(() => "fenced"),
|
|
242
|
+
timedOut,
|
|
243
|
+
]);
|
|
244
|
+
if (deadline !== undefined)
|
|
245
|
+
clearTimeout(deadline);
|
|
246
|
+
return winner === "fenced" ? [] : [...this.sessions.keys()];
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The exact-run stop (v52): fence ONE held session now, through the
|
|
250
|
+
* supervisor's own handle — EOF → grace → kill, conservative settlement,
|
|
251
|
+
* the worktree preserved, the stop settled as `held` by the run's ending.
|
|
252
|
+
* The console calls this after the durable request; the lapse interval
|
|
253
|
+
* takes the same road for a stop filed by another process. Idempotent:
|
|
254
|
+
* a session already fencing or gone settles nothing twice.
|
|
255
|
+
*/
|
|
256
|
+
stop(runId) {
|
|
257
|
+
return this.fenceByRun(runId, "stopped");
|
|
258
|
+
}
|
|
259
|
+
/** The beat endpoint, answers, and revocation poke this directly (v2 S1d). */
|
|
260
|
+
poke(runId) {
|
|
261
|
+
const controller = this.sessions.get(runId);
|
|
262
|
+
if (controller === undefined)
|
|
263
|
+
return;
|
|
264
|
+
const args = this.launches.get(runId);
|
|
265
|
+
if (args !== undefined)
|
|
266
|
+
void this.injectPendingAnswers(args);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The operator's own turn (v2 S1g, ruling 12-as-accepted): session-grade
|
|
270
|
+
* speech, recorded through the SAME gated transaction every injection
|
|
271
|
+
* takes — cap, single-flight, budget, open-decision, lease — so every
|
|
272
|
+
* refusal is typed and atomic.
|
|
273
|
+
*/
|
|
274
|
+
injectOperatorTurn(runId, author, text) {
|
|
275
|
+
const args = this.launches.get(runId);
|
|
276
|
+
const controller = this.sessions.get(runId);
|
|
277
|
+
if (args === undefined || controller === undefined || controller.fenceStarted || controller.concluded) {
|
|
278
|
+
return { ok: false, reason: "no-held-session" };
|
|
279
|
+
}
|
|
280
|
+
return this.injectTurn(args, controller, { sourceKind: "operator", sourceId: null, author, text });
|
|
281
|
+
}
|
|
282
|
+
injectTurn(args, controller, turn) {
|
|
283
|
+
const recorded = args.store.recordSessionTurn({
|
|
284
|
+
run: args.runId,
|
|
285
|
+
sourceKind: turn.sourceKind,
|
|
286
|
+
sourceId: turn.sourceId,
|
|
287
|
+
author: turn.author,
|
|
288
|
+
text: turn.text,
|
|
289
|
+
repairLimit: 2,
|
|
290
|
+
now: args.clock(),
|
|
291
|
+
});
|
|
292
|
+
if (!recorded.ok)
|
|
293
|
+
return { ok: false, reason: recorded.reason };
|
|
294
|
+
const json = JSON.stringify({
|
|
295
|
+
type: "user",
|
|
296
|
+
message: { role: "user", content: [{ type: "text", text: turn.text }] },
|
|
297
|
+
});
|
|
298
|
+
if (controller.handle === null || !controller.handle.writeTurn(json)) {
|
|
299
|
+
args.store.settleTurnTerminal(recorded.turn.id, "cancelled", args.clock());
|
|
300
|
+
return { ok: false, reason: "write-failed" };
|
|
301
|
+
}
|
|
302
|
+
args.store.markTurnWritten(recorded.turn.id, args.clock());
|
|
303
|
+
this.armTurnTimer(args, controller);
|
|
304
|
+
return { ok: true };
|
|
305
|
+
}
|
|
306
|
+
async injectPendingAnswers(args) {
|
|
307
|
+
const controller = this.sessions.get(args.runId);
|
|
308
|
+
if (controller === undefined || controller.fenceStarted || controller.concluded)
|
|
309
|
+
return;
|
|
310
|
+
for (const decision of args.store.undeliveredDecisionsOf(args.runId)) {
|
|
311
|
+
const text = [
|
|
312
|
+
`The operator answered your question (decision ${decision.id}):`,
|
|
313
|
+
`chose "${decision.choice ?? decision.recommendation}"${decision.note === null || decision.note === "" ? "" : ` — ${decision.note}`}.`,
|
|
314
|
+
"Continue the work within the approved scope. When you finish, write your handoff exactly as the brief instructed.",
|
|
315
|
+
].join(" ");
|
|
316
|
+
const injected = this.injectTurn(args, controller, {
|
|
317
|
+
sourceKind: "answer",
|
|
318
|
+
sourceId: decision.id,
|
|
319
|
+
author: null,
|
|
320
|
+
text,
|
|
321
|
+
});
|
|
322
|
+
// One at a time: single-flight means a second undelivered answer
|
|
323
|
+
// waits for the next scan after this turn settles.
|
|
324
|
+
if (injected.ok)
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ---- internals -----------------------------------------------------------
|
|
329
|
+
launches = new Map();
|
|
330
|
+
makeController(args, brief, socketPath, cookie) {
|
|
331
|
+
let finish = () => { };
|
|
332
|
+
const done = new Promise(pass => {
|
|
333
|
+
finish = pass;
|
|
334
|
+
});
|
|
335
|
+
const controller = {
|
|
336
|
+
runId: args.runId,
|
|
337
|
+
fenceStarted: false,
|
|
338
|
+
concluded: false,
|
|
339
|
+
handle: null,
|
|
340
|
+
timers: [],
|
|
341
|
+
intervals: [],
|
|
342
|
+
turnTimer: null,
|
|
343
|
+
done,
|
|
344
|
+
finish,
|
|
345
|
+
};
|
|
346
|
+
this.sessions.set(args.runId, controller);
|
|
347
|
+
this.launches.set(args.runId, args);
|
|
348
|
+
// Exact timers (v2 S1d): absolute expiry, liveness lapse, lease pulse.
|
|
349
|
+
const now = args.clock().getTime();
|
|
350
|
+
const untilExpiry = Math.max(0, Date.parse(args.authorization.absoluteExpiry) - now);
|
|
351
|
+
const expiry = setTimeout(() => void this.fence(args, "expired"), untilExpiry);
|
|
352
|
+
expiry.unref?.();
|
|
353
|
+
controller.timers.push(expiry);
|
|
354
|
+
const lapse = setInterval(() => {
|
|
355
|
+
// The operator's exact-run stop (v52) takes the same fence road as a
|
|
356
|
+
// revocation: EOF → grace → kill through the supervisor's handle,
|
|
357
|
+
// conservative settlement, the worktree preserved.
|
|
358
|
+
if (args.store.applicableStopFor(args.runId) !== null) {
|
|
359
|
+
void this.fence(args, "stopped");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const live = args.store.readAuthorization(args.authorization.id);
|
|
363
|
+
if (live === null || live.closedAt !== null) {
|
|
364
|
+
void this.fence(args, live?.endReason === "revoked" ? "revoked" : "lapsed");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const state = attendedLivenessState(live.lastBeatAt === null ? null : Date.parse(live.lastBeatAt), args.clock().getTime(), Date.parse(live.absoluteExpiry));
|
|
368
|
+
if (state === "lapsed" || state === "expired")
|
|
369
|
+
void this.fence(args, state);
|
|
370
|
+
}, 5_000);
|
|
371
|
+
lapse.unref?.();
|
|
372
|
+
controller.intervals.push(lapse);
|
|
373
|
+
const lease = setInterval(() => {
|
|
374
|
+
try {
|
|
375
|
+
const answer = heartbeat(args.store, args.leaseId, args.clock());
|
|
376
|
+
if (!answer.ok) {
|
|
377
|
+
void this.fence(args, "lease-lost");
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (args.runnerToken !== undefined) {
|
|
381
|
+
const alive = runnerHeartbeat(args.store, args.runner, args.runnerToken, args.clock());
|
|
382
|
+
if (!alive.ok) {
|
|
383
|
+
void this.fence(args, "lease-lost");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// Answers persisted by ANY surface reach the live session here
|
|
388
|
+
// (v3 R3): the delivery-CAS inside recordSessionTurn makes the
|
|
389
|
+
// injection exactly-once whatever raced.
|
|
390
|
+
void this.injectPendingAnswers(args);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
void this.fence(args, "lease-lost");
|
|
394
|
+
}
|
|
395
|
+
}, BEAT_MS);
|
|
396
|
+
lease.unref?.();
|
|
397
|
+
controller.intervals.push(lease);
|
|
398
|
+
return controller;
|
|
399
|
+
}
|
|
400
|
+
armTurnTimer(args, controller) {
|
|
401
|
+
if (controller.turnTimer !== null)
|
|
402
|
+
clearTimeout(controller.turnTimer);
|
|
403
|
+
const timer = setTimeout(() => void this.fence(args, "turn-timeout"), args.captured.effective.timeoutMs);
|
|
404
|
+
timer.unref?.();
|
|
405
|
+
controller.turnTimer = timer;
|
|
406
|
+
}
|
|
407
|
+
onTurnInit(args, seq) {
|
|
408
|
+
const turn = args.store.sessionTurnsOf(args.runId).find(one => one.seq === seq);
|
|
409
|
+
if (turn !== undefined)
|
|
410
|
+
args.store.markTurnAccepted(turn.id, args.clock());
|
|
411
|
+
// The brief's acceptance is the held road's receipt (round-6 finding
|
|
412
|
+
// 6): attached steering settles delivered on the same proof the
|
|
413
|
+
// one-shot transport's onReceipt carried.
|
|
414
|
+
if (seq === 1)
|
|
415
|
+
void args.store.settleSteerDelivered(args.runId, args.clock());
|
|
416
|
+
}
|
|
417
|
+
async onTurnResult(args, seq, event) {
|
|
418
|
+
const controller = this.sessions.get(args.runId);
|
|
419
|
+
if (controller === undefined || controller.fenceStarted || controller.concluded)
|
|
420
|
+
return;
|
|
421
|
+
const turn = args.store.sessionTurnsOf(args.runId).find(one => one.seq === seq);
|
|
422
|
+
if (turn === undefined)
|
|
423
|
+
return;
|
|
424
|
+
const usage = event["usage"];
|
|
425
|
+
const settled = args.store.settleTurn(turn.id, {
|
|
426
|
+
cumulativeMicrousd: dollarsToMicrousd(event["total_cost_usd"]),
|
|
427
|
+
outputTokens: typeof usage?.output_tokens === "number" ? usage.output_tokens : null,
|
|
428
|
+
now: args.clock(),
|
|
429
|
+
});
|
|
430
|
+
if (!settled.ok && settled.reason === "telemetry") {
|
|
431
|
+
await this.fence(args, "telemetry");
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (controller.turnTimer !== null) {
|
|
435
|
+
clearTimeout(controller.turnTimer);
|
|
436
|
+
controller.turnTimer = null;
|
|
437
|
+
}
|
|
438
|
+
// CLASSIFY (v2 S1f, v6 W4): a terminal handoff concludes through the
|
|
439
|
+
// shared settlement; a park records the decision and the session STAYS
|
|
440
|
+
// HELD for the answer; a budget-exhausted result ends the hold; and a
|
|
441
|
+
// settled turn that left neither file is a conversation pause — the
|
|
442
|
+
// session waits for the operator.
|
|
443
|
+
const donePath = join(args.cwd, args.captured.done);
|
|
444
|
+
const mailboxPath = join(args.cwd, args.captured.mailbox);
|
|
445
|
+
if (String(event["subtype"] ?? "") === "error_max_budget_usd") {
|
|
446
|
+
await this.fence(args, "budget-exhausted");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (existsSync(donePath)) {
|
|
450
|
+
await this.conclude(args, event);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (existsSync(mailboxPath)) {
|
|
454
|
+
await this.heldPark(args, turn.id, event);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Idle: held, watching, awaiting the next turn.
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* A park inside a held session (v2 S1f + round-6 finding 3): payload
|
|
461
|
+
* preserved as evidence and unlinked exactly like the ordinary road,
|
|
462
|
+
* the decision recorded WITHOUT ending anything, causally linked to the
|
|
463
|
+
* turn that produced it. A malformed payload takes the held repair road
|
|
464
|
+
* — a machine-authored ledger turn in the SAME session, correlated to
|
|
465
|
+
* the producing turn (a malformed mailbox has no decision id), bounded;
|
|
466
|
+
* exhaustion disposes through the shared malformed machinery.
|
|
467
|
+
*/
|
|
468
|
+
async heldPark(args, producingTurn, lastResult) {
|
|
469
|
+
const controller = this.sessions.get(args.runId);
|
|
470
|
+
if (controller === undefined || controller.fenceStarted || controller.concluded)
|
|
471
|
+
return;
|
|
472
|
+
const store = args.store;
|
|
473
|
+
const path = join(args.cwd, args.captured.mailbox);
|
|
474
|
+
const read = readMailbox(path);
|
|
475
|
+
if (!read.ok) {
|
|
476
|
+
if (!read.missing) {
|
|
477
|
+
try {
|
|
478
|
+
const { unlinkSync } = await import("node:fs");
|
|
479
|
+
unlinkSync(path);
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// Unremovable is survivable; the commit path excludes the name.
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
storeEvidence(store, args.captured.root, args.runId, "park-payload", "park.json", read.raw, `mailbox ${args.captured.mailbox}`, args.clock());
|
|
488
|
+
try {
|
|
489
|
+
const { unlinkSync } = await import("node:fs");
|
|
490
|
+
unlinkSync(path);
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
// The bytes are already in evidence.
|
|
494
|
+
}
|
|
495
|
+
const parsed = parseDecision(read.raw.toString("utf8"));
|
|
496
|
+
if (parsed.ok) {
|
|
497
|
+
const evidence = await captureParkEvidence(store, args.captured.git, args.cwd, args.captured.baseRevision, args.captured.root, args.runId, args.clock());
|
|
498
|
+
const payload = store.artifactsFor(args.runId).find(artifact => artifact.kind === "park-payload");
|
|
499
|
+
const sealed = finalizeParkHeld(store, {
|
|
500
|
+
runId: args.runId,
|
|
501
|
+
taskId: args.captured.taskId,
|
|
502
|
+
decision: parsed.decision,
|
|
503
|
+
artifactIds: [...(payload === undefined ? [] : [payload.id]), ...evidence],
|
|
504
|
+
sessionTurn: producingTurn,
|
|
505
|
+
now: args.clock(),
|
|
506
|
+
});
|
|
507
|
+
if (!sealed.ok)
|
|
508
|
+
await this.fence(args, sealed.reason);
|
|
509
|
+
return; // held: the answer arrives as the next turn
|
|
510
|
+
}
|
|
511
|
+
// Malformed: the held repair road (round-6 finding 3).
|
|
512
|
+
const repair = this.injectTurn(args, controller, {
|
|
513
|
+
sourceKind: "repair",
|
|
514
|
+
sourceId: producingTurn,
|
|
515
|
+
author: null,
|
|
516
|
+
text: repairPrompt(parsed.problems, args.captured.mailbox),
|
|
517
|
+
});
|
|
518
|
+
if (!repair.ok && repair.reason === "repair-exhausted") {
|
|
519
|
+
controller.concluded = true;
|
|
520
|
+
this.clearClocks(controller);
|
|
521
|
+
args.store.endHeldSession(args.runId, "malformed-decision", args.clock());
|
|
522
|
+
controller.handle?.endInput();
|
|
523
|
+
await Promise.race([
|
|
524
|
+
controller.handle?.exited ?? Promise.resolve({ code: null }),
|
|
525
|
+
new Promise(pass => setTimeout(() => {
|
|
526
|
+
controller.handle?.killHard();
|
|
527
|
+
pass({ code: null });
|
|
528
|
+
}, (args.graceMs ?? 10_000) + 10_000)),
|
|
529
|
+
]);
|
|
530
|
+
const disposition = disposeBuildOutcome({
|
|
531
|
+
store: args.store,
|
|
532
|
+
policy: args.dispose.policy ?? "tick",
|
|
533
|
+
leaseId: args.leaseId,
|
|
534
|
+
runId: args.runId,
|
|
535
|
+
taskId: args.captured.taskId,
|
|
536
|
+
taskRef: args.captured.taskRef,
|
|
537
|
+
runner: args.runner,
|
|
538
|
+
repo: args.dispose.repo,
|
|
539
|
+
branch: args.captured.branch,
|
|
540
|
+
origin: args.dispose.origin,
|
|
541
|
+
provider: args.dispose.provider,
|
|
542
|
+
model: args.dispose.model,
|
|
543
|
+
worktreePath: args.cwd,
|
|
544
|
+
clock: args.clock,
|
|
545
|
+
}, { ok: false, reason: "malformed-decision", message: "the agent parked, but the payload is not a decision — twice over", problems: parsed.problems });
|
|
546
|
+
await args.releaseWorktree(args.cwd);
|
|
547
|
+
args.store.closeAuthorization(args.authorization.id, "malformed-decision", args.clock());
|
|
548
|
+
args.liveLog?.close();
|
|
549
|
+
args.onDisposed?.(disposition);
|
|
550
|
+
this.drop(args.runId);
|
|
551
|
+
}
|
|
552
|
+
void lastResult;
|
|
553
|
+
}
|
|
554
|
+
async onExit(args, code) {
|
|
555
|
+
const controller = this.sessions.get(args.runId);
|
|
556
|
+
if (controller === undefined || controller.fenceStarted || controller.concluded)
|
|
557
|
+
return;
|
|
558
|
+
// The process died with no settled result: conservative settlement.
|
|
559
|
+
await this.fence(args, code === null ? "exited" : `exited:${code}`);
|
|
560
|
+
}
|
|
561
|
+
async conclude(args, lastResult) {
|
|
562
|
+
const controller = this.sessions.get(args.runId);
|
|
563
|
+
if (controller === undefined || controller.concluded || controller.fenceStarted)
|
|
564
|
+
return;
|
|
565
|
+
controller.concluded = true;
|
|
566
|
+
this.clearClocks(controller);
|
|
567
|
+
// The admission door closes BEFORE anything awaits (round-6 finding
|
|
568
|
+
// 1): with ended_at stamped, a late external turn recording refuses
|
|
569
|
+
// inside ITS transaction, and a late result cannot re-open settlement
|
|
570
|
+
// (its turn is already settled or terminal).
|
|
571
|
+
args.store.endHeldSession(args.runId, "finished", args.clock());
|
|
572
|
+
// End the hold: EOF, let the supervisor fence its own child, bounded.
|
|
573
|
+
controller.handle?.endInput();
|
|
574
|
+
const exited = await Promise.race([
|
|
575
|
+
controller.handle?.exited ?? Promise.resolve({ code: 0 }),
|
|
576
|
+
new Promise(pass => setTimeout(() => {
|
|
577
|
+
controller.handle?.killHard();
|
|
578
|
+
pass({ code: null });
|
|
579
|
+
}, (args.graceMs ?? 10_000) + 20_000)),
|
|
580
|
+
]);
|
|
581
|
+
const isError = lastResult["is_error"] === true || String(lastResult["subtype"] ?? "") !== "success";
|
|
582
|
+
const outcome = {
|
|
583
|
+
code: isError ? 1 : (exited.code ?? 0),
|
|
584
|
+
stderr: isError ? String(lastResult["result"] ?? lastResult["subtype"] ?? "error result") : "",
|
|
585
|
+
timedOut: false,
|
|
586
|
+
notFound: false,
|
|
587
|
+
sessionId: args.store.getRun(args.runId)?.sessionId ?? null,
|
|
588
|
+
finalMessage: typeof lastResult["result"] === "string" ? lastResult["result"] : null,
|
|
589
|
+
usage: { tokensIn: null, tokensOut: null, costUsd: null },
|
|
590
|
+
initFailed: false,
|
|
591
|
+
};
|
|
592
|
+
const result = await settleProviderOutcome(args.captured, outcome);
|
|
593
|
+
const disposition = disposeBuildOutcome({
|
|
594
|
+
store: args.store,
|
|
595
|
+
policy: args.dispose.policy ?? "tick",
|
|
596
|
+
leaseId: args.leaseId,
|
|
597
|
+
runId: args.runId,
|
|
598
|
+
taskId: args.captured.taskId,
|
|
599
|
+
taskRef: args.captured.taskRef,
|
|
600
|
+
runner: args.runner,
|
|
601
|
+
repo: args.dispose.repo,
|
|
602
|
+
branch: args.captured.branch,
|
|
603
|
+
origin: args.dispose.origin,
|
|
604
|
+
provider: args.dispose.provider,
|
|
605
|
+
model: args.dispose.model,
|
|
606
|
+
worktreePath: args.cwd,
|
|
607
|
+
evidenceRoot: args.captured.root,
|
|
608
|
+
clock: args.clock,
|
|
609
|
+
}, result);
|
|
610
|
+
await args.releaseWorktree(args.cwd);
|
|
611
|
+
args.store.closeAuthorization(args.authorization.id, "finished", args.clock());
|
|
612
|
+
args.liveLog?.close();
|
|
613
|
+
args.onDisposed?.(disposition);
|
|
614
|
+
this.drop(args.runId);
|
|
615
|
+
}
|
|
616
|
+
fenceByRun(runId, reason) {
|
|
617
|
+
const args = this.launches.get(runId);
|
|
618
|
+
if (args === undefined)
|
|
619
|
+
return Promise.resolve();
|
|
620
|
+
return this.fence(args, reason);
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* The interruption road (v2 S2e): stop admission, EOF → grace → kill via
|
|
624
|
+
* the supervisor, conservative settlement, `interrupted`, worktree
|
|
625
|
+
* preserved, authorization closed with the reason. Idempotent.
|
|
626
|
+
*/
|
|
627
|
+
async fence(args, reason) {
|
|
628
|
+
const controller = this.sessions.get(args.runId);
|
|
629
|
+
if (controller === undefined || controller.fenceStarted || controller.concluded)
|
|
630
|
+
return;
|
|
631
|
+
controller.fenceStarted = true;
|
|
632
|
+
this.clearClocks(controller);
|
|
633
|
+
// TERMINAL SEIZURE before any waiting (round-6 finding 1): the CAS to
|
|
634
|
+
// 'fencing' + the lease fence make every late admission and every
|
|
635
|
+
// stale write refuse in ITS OWN transaction during the grace window —
|
|
636
|
+
// in-memory flags guard only this process. Losing the CAS means an
|
|
637
|
+
// external fencer owns settlement: kill our handle and step aside.
|
|
638
|
+
const fencer = `coordinator:${process.pid}`;
|
|
639
|
+
const seized = args.store.seizeHeldSession(args.runId, fencer, new Date(args.clock().getTime() + 120_000), args.clock());
|
|
640
|
+
if (!seized.ok) {
|
|
641
|
+
controller.handle?.terminate();
|
|
642
|
+
args.liveLog?.close();
|
|
643
|
+
this.drop(args.runId);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
controller.handle?.terminate();
|
|
647
|
+
await Promise.race([
|
|
648
|
+
controller.handle?.exited ?? Promise.resolve({ code: null }),
|
|
649
|
+
new Promise(pass => setTimeout(() => {
|
|
650
|
+
controller.handle?.killHard();
|
|
651
|
+
pass({ code: null });
|
|
652
|
+
}, (args.graceMs ?? 10_000) + 10_000)),
|
|
653
|
+
]);
|
|
654
|
+
const store = args.store;
|
|
655
|
+
for (const turn of store.sessionTurnsOf(args.runId)) {
|
|
656
|
+
if (turn.state === "recorded")
|
|
657
|
+
store.settleTurnTerminal(turn.id, "cancelled", args.clock());
|
|
658
|
+
else if (turn.state === "written" || turn.state === "accepted") {
|
|
659
|
+
store.settleTurnTerminal(turn.id, "uncertain", args.clock());
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// The run's ending settles a pending stop as `held` (v52): by here the
|
|
663
|
+
// supervisor's exit was awaited or its hard kill ordered.
|
|
664
|
+
store.finishRun(args.runId, { outcome: "interrupted", reason, now: args.clock(), stopSettlement: "held" });
|
|
665
|
+
store.closeHeldFencing(args.runId, fencer, reason, args.clock());
|
|
666
|
+
store.closeAuthorization(args.authorization.id, reason, args.clock());
|
|
667
|
+
// The worktree is PRESERVED on interruption (released to the pool
|
|
668
|
+
// unverified — same as every failure road; the work stays on disk).
|
|
669
|
+
await args.releaseWorktree(args.cwd);
|
|
670
|
+
args.liveLog?.close();
|
|
671
|
+
args.onDisposed?.({ kind: "interrupted", reason });
|
|
672
|
+
store.settleRunStop(args.runId, "held", args.clock());
|
|
673
|
+
this.drop(args.runId);
|
|
674
|
+
}
|
|
675
|
+
clearClocks(controller) {
|
|
676
|
+
for (const timer of controller.timers)
|
|
677
|
+
clearTimeout(timer);
|
|
678
|
+
for (const interval of controller.intervals)
|
|
679
|
+
clearInterval(interval);
|
|
680
|
+
controller.timers = [];
|
|
681
|
+
controller.intervals = [];
|
|
682
|
+
}
|
|
683
|
+
drop(runId) {
|
|
684
|
+
const controller = this.sessions.get(runId);
|
|
685
|
+
controller?.finish();
|
|
686
|
+
this.sessions.delete(runId);
|
|
687
|
+
this.launches.delete(runId);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* The DB-side orphan fence (v4 Q5 + v5 P3/P4 + v6 W6): sweep every open
|
|
692
|
+
* custody row whose recorded lease is NO LONGER the task's current live
|
|
693
|
+
* lease, seize it (CAS open→fencing + lease fence in one transaction),
|
|
694
|
+
* order the kill through the supervisor's socket — the kernel-stable
|
|
695
|
+
* road — and settle. A supervisor that cannot be reached keeps custody
|
|
696
|
+
* and PAGES (never a guessed PID kill); a later sweep resumes from
|
|
697
|
+
* wherever the CASes stand. Runs BEFORE generic recovery at every entry
|
|
698
|
+
* point; recovery excludes fenced-open held rows.
|
|
699
|
+
*/
|
|
700
|
+
export async function sweepHeldOrphans(store, fencer, now) {
|
|
701
|
+
// TWO PHASES (parallel-sessions round 1, finding 6): seize everything
|
|
702
|
+
// first — fast, transactional, per-run CAS — then run the kill orders
|
|
703
|
+
// CONCURRENTLY. The old serial loop paid up to eight seconds per
|
|
704
|
+
// unreachable socket BEFORE up could register its runner; thirty
|
|
705
|
+
// orphans meant four minutes of startup. Now the wall clock is one
|
|
706
|
+
// socket timeout, not their sum.
|
|
707
|
+
const owned = [];
|
|
708
|
+
for (const session of store.openHeldSessions()) {
|
|
709
|
+
const run = store.getRun(session.run);
|
|
710
|
+
if (run === null)
|
|
711
|
+
continue;
|
|
712
|
+
const currentLease = store.currentLiveLease(run.taskRef, now());
|
|
713
|
+
if (currentLease === session.leaseId)
|
|
714
|
+
continue; // a live owner — leave it be
|
|
715
|
+
if (session.state === "open") {
|
|
716
|
+
const seized = store.seizeHeldSession(session.run, fencer, new Date(now().getTime() + 120_000), now());
|
|
717
|
+
if (!seized.ok)
|
|
718
|
+
continue; // another fencer took it between reads
|
|
719
|
+
owned.push({ session: seized.session, run });
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
// 'fencing' already: help only when the prior fencer's deadline is gone.
|
|
723
|
+
if (!store.takeoverHeldFencing(session.run, fencer, new Date(now().getTime() + 120_000), now()))
|
|
724
|
+
continue;
|
|
725
|
+
owned.push({ session, run });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
let fenced = 0;
|
|
729
|
+
let paged = 0;
|
|
730
|
+
await Promise.all(owned.map(async ({ session, run }) => {
|
|
731
|
+
const killed = store.runCustodyProvenGone(session.run) ? { settled: true } : await orderKill(session.socketPath, session.cookie);
|
|
732
|
+
if (!killed.settled) {
|
|
733
|
+
// Page, keep custody, leave 'fencing' for a later helper (v5 P4).
|
|
734
|
+
store.enqueueNotification({
|
|
735
|
+
source: { run: session.run },
|
|
736
|
+
dedupeKey: `held:${session.run}:unkillable`,
|
|
737
|
+
kind: "attention",
|
|
738
|
+
subject: `run ${session.run}: a held agent process could not be stopped`,
|
|
739
|
+
body: `The supervisor at ${session.socketPath} is unreachable. Stop the recorded processes by hand (supervisor pid ${session.supervisorPid ?? "?"}, agent group ${session.agentPgid ?? "?"}), then reconcile.`,
|
|
740
|
+
}, now());
|
|
741
|
+
paged += 1;
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
for (const turn of store.sessionTurnsOf(session.run)) {
|
|
745
|
+
if (turn.state === "recorded")
|
|
746
|
+
store.settleTurnTerminal(turn.id, "cancelled", now());
|
|
747
|
+
else if (turn.state === "written" || turn.state === "accepted") {
|
|
748
|
+
store.settleTurnTerminal(turn.id, "uncertain", now());
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (run.outcome === null) {
|
|
752
|
+
store.finishRun(session.run, { outcome: "interrupted", reason: "orphaned", now: now() });
|
|
753
|
+
}
|
|
754
|
+
store.closeHeldFencing(session.run, fencer, "orphaned", now());
|
|
755
|
+
store.closeAuthorization(session.authorizationId, "orphaned", now());
|
|
756
|
+
fenced += 1;
|
|
757
|
+
}));
|
|
758
|
+
return { fenced, paged };
|
|
759
|
+
}
|
|
760
|
+
/** One cookie-authenticated kill order; unreachable = not settled. */
|
|
761
|
+
function orderKill(socketPath, cookie) {
|
|
762
|
+
return new Promise(pass => {
|
|
763
|
+
let answered = false;
|
|
764
|
+
const settle = (settled) => {
|
|
765
|
+
if (answered)
|
|
766
|
+
return;
|
|
767
|
+
answered = true;
|
|
768
|
+
pass({ settled });
|
|
769
|
+
};
|
|
770
|
+
let wire;
|
|
771
|
+
try {
|
|
772
|
+
wire = connect(socketPath, () => {
|
|
773
|
+
wire.write(`${JSON.stringify({ cookie, verb: "kill" })}\n`);
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
settle(false);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
let answer = "";
|
|
781
|
+
wire.setTimeout(8_000, () => {
|
|
782
|
+
wire.destroy();
|
|
783
|
+
settle(false);
|
|
784
|
+
});
|
|
785
|
+
wire.on("data", chunk => {
|
|
786
|
+
answer += String(chunk);
|
|
787
|
+
});
|
|
788
|
+
wire.on("end", () => {
|
|
789
|
+
try {
|
|
790
|
+
const parsed = JSON.parse(answer);
|
|
791
|
+
settle(parsed.ok === true && parsed.settled === true);
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
settle(false);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
wire.on("error", () => settle(false));
|
|
798
|
+
});
|
|
799
|
+
}
|