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/contest.js
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tournament orchestration (stage 3; the three review rounds' findings are
|
|
3
|
+
* the spec). What lives here and what deliberately does not:
|
|
4
|
+
*
|
|
5
|
+
* - the race digest and the JOINT approval digest (finding 31): the
|
|
6
|
+
* approval a person gives covers the scope AND the race terms as one
|
|
7
|
+
* hash — editing either strands it;
|
|
8
|
+
* - planning: turning asked-for agents and dollars into approved-terms
|
|
9
|
+
* rows, refusing anything the money machinery cannot bound (finding
|
|
10
|
+
* 24: eligibility comes from the capability matrix, budgets from the
|
|
11
|
+
* pinned prices, and every agent carries its own overrun reserve);
|
|
12
|
+
* - admission: ONE transaction that proves everything or admits nothing
|
|
13
|
+
* (finding 3/19/22) — the claim, the digests re-derived from stored
|
|
14
|
+
* rows, worker-process slots, quota by distinct key, the money
|
|
15
|
+
* arithmetic — then creates the contest skeleton;
|
|
16
|
+
* - the ready barrier (finding 19): worktrees and setup finish for every
|
|
17
|
+
* agent before anything spawns; the barrier is a state CAS;
|
|
18
|
+
* - child finalization (findings 1/17): an agent finishing NEVER touches
|
|
19
|
+
* the task, the parent claim, or publication — it writes its own row
|
|
20
|
+
* and asks, inside the same transaction, whether the whole tournament
|
|
21
|
+
* just reached its boundary (maybeAggregate);
|
|
22
|
+
* - crash recovery (finding 27): a daemon death mid-dispatch or mid-race
|
|
23
|
+
* converts the contest to 'interrupted' by the same generation CAS the
|
|
24
|
+
* finalizers use — whoever wins, wins alone — and never charges an
|
|
25
|
+
* agent that never started.
|
|
26
|
+
*
|
|
27
|
+
* NOT here: the pick and abandon ceremonies (stage 5), decision-wait
|
|
28
|
+
* re-admission and checkout custody (stage 4), cleanup (stage 6).
|
|
29
|
+
*/
|
|
30
|
+
import { profileDigestOf } from "./scope.js";
|
|
31
|
+
import { contestantProfileOf } from "./store.js";
|
|
32
|
+
import { createHash } from "node:crypto";
|
|
33
|
+
import { headWithin, taskBranch } from "./names.js";
|
|
34
|
+
import { release } from "./claim.js";
|
|
35
|
+
import { buildPriceOf, oneCallTailMicrousd, BUILD_PRICE_VERSION } from "./pricing.js";
|
|
36
|
+
import { MONEY_CAPABILITIES, isProviderId, reportsCost, validateSpec } from "./provider.js";
|
|
37
|
+
export const MIN_AGENTS = 2;
|
|
38
|
+
export const MAX_AGENTS = 4;
|
|
39
|
+
// ---------------------------------------------------------------- digests
|
|
40
|
+
/** Canonical and order-preserving: the agents race in the order approved.
|
|
41
|
+
* v2 (foundations finding 8): each lane's FULL execution profile enters the
|
|
42
|
+
* fingerprint — permissions and limits included, not just routing — via the
|
|
43
|
+
* same profile digest every other approval binds. Stored v1 fingerprints
|
|
44
|
+
* keep verifying byte-for-byte (admission compares stored values; nothing
|
|
45
|
+
* recomputes a v1). */
|
|
46
|
+
export function raceDigestOf(terms) {
|
|
47
|
+
const canonical = JSON.stringify({
|
|
48
|
+
v: 2,
|
|
49
|
+
agents: terms.agents.map(agent => [
|
|
50
|
+
agent.provider,
|
|
51
|
+
agent.model,
|
|
52
|
+
agent.repairModel,
|
|
53
|
+
profileDigestOf(contestantProfileOf(agent.provider, agent.model, agent.repairModel, agent.permissionMode)),
|
|
54
|
+
]),
|
|
55
|
+
per: terms.perAgentBudgetMicrousd,
|
|
56
|
+
total: terms.totalBudgetMicrousd,
|
|
57
|
+
prices: terms.priceVersion,
|
|
58
|
+
retries: 0,
|
|
59
|
+
publication: terms.publicationPolicy,
|
|
60
|
+
});
|
|
61
|
+
return createHash("sha256").update(`race/v2\u0000${canonical}`).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
/** tournament-approval/v1 (finding 31): one yes covers both documents.
|
|
64
|
+
* Comparisons reuse it verbatim — their fingerprint rides the same stored
|
|
65
|
+
* column, and the domains below keep race and comparison terms from ever
|
|
66
|
+
* colliding. */
|
|
67
|
+
export function jointApprovalDigest(scopeDigest, raceDigest) {
|
|
68
|
+
return createHash("sha256").update(`tournament-approval/v1\u0000${scopeDigest}\u0000${raceDigest}`).digest("hex");
|
|
69
|
+
}
|
|
70
|
+
/** comparison/v1 (Phase 3 slice B): order-preserving, each lane's FULL
|
|
71
|
+
* profile digest included, NO money fields — none exist on a comparison. */
|
|
72
|
+
export function comparisonDigestOf(terms) {
|
|
73
|
+
const canonical = JSON.stringify({
|
|
74
|
+
v: 1,
|
|
75
|
+
agents: terms.agents.map(agent => [
|
|
76
|
+
agent.provider,
|
|
77
|
+
agent.model,
|
|
78
|
+
agent.repairModel,
|
|
79
|
+
profileDigestOf(contestantProfileOf(agent.provider, agent.model, agent.repairModel, agent.permissionMode)),
|
|
80
|
+
]),
|
|
81
|
+
publication: terms.publicationPolicy,
|
|
82
|
+
});
|
|
83
|
+
return createHash("sha256").update(`comparison/v1\u0000${canonical}`).digest("hex");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A comparison admits any registered provider — that is its purpose — but
|
|
87
|
+
* NEVER lets the budget ceremony be bypassed: if every asked lane could
|
|
88
|
+
* hold a native dollar cap, the refusal points at the tournament road
|
|
89
|
+
* (spec D3, the discipline gate). No dollar fields exist here at all;
|
|
90
|
+
* each lane's bound is its sealed profile's wall clock, said in words.
|
|
91
|
+
*/
|
|
92
|
+
export function planComparison(input) {
|
|
93
|
+
if (input.agents.length < MIN_AGENTS || input.agents.length > MAX_AGENTS) {
|
|
94
|
+
return { ok: false, reason: "bad-count", message: `a comparison runs ${MIN_AGENTS} to ${MAX_AGENTS} agents` };
|
|
95
|
+
}
|
|
96
|
+
const agents = [];
|
|
97
|
+
const laneWords = [];
|
|
98
|
+
let unmeasuredLanes = 0;
|
|
99
|
+
for (const asked of input.agents) {
|
|
100
|
+
if (!isProviderId(asked.provider)) {
|
|
101
|
+
return { ok: false, reason: "unknown-provider", message: `unknown provider \`${asked.provider}\`` };
|
|
102
|
+
}
|
|
103
|
+
const valid = validateSpec({ provider: asked.provider, model: asked.model });
|
|
104
|
+
if (!valid.ok) {
|
|
105
|
+
return { ok: false, reason: "bad-model", message: valid.problem };
|
|
106
|
+
}
|
|
107
|
+
if (asked.model === "") {
|
|
108
|
+
return { ok: false, reason: "bad-model", message: "every comparison lane names its exact model — the lanes ARE the terms" };
|
|
109
|
+
}
|
|
110
|
+
const capability = MONEY_CAPABILITIES[asked.provider];
|
|
111
|
+
if (!capability.tournamentEligible)
|
|
112
|
+
unmeasuredLanes++;
|
|
113
|
+
const repairModel = asked.repairModel ?? asked.model;
|
|
114
|
+
agents.push({ provider: asked.provider, model: asked.model, repairModel, ...(asked.permissionMode === undefined ? {} : { permissionMode: asked.permissionMode }) });
|
|
115
|
+
laneWords.push(reportsCost(asked.provider)
|
|
116
|
+
? `${asked.provider} · ${asked.model} — spend measured in dollars; no cap on a comparison, the clock is the bound`
|
|
117
|
+
: `${asked.provider} · ${asked.model} — reports tokens only; dollar exposure unmeasured; the clock is the bound`);
|
|
118
|
+
}
|
|
119
|
+
// Comparisons file exact models for EVERY lane (the filing invariant):
|
|
120
|
+
// claude and codex get the same rule gemini/openrouter already carry.
|
|
121
|
+
for (const agent of agents) {
|
|
122
|
+
if (agent.model === "") {
|
|
123
|
+
return { ok: false, reason: "bad-model", message: "every comparison lane names its exact model" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (unmeasuredLanes === 0) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
reason: "all-lanes-raceable",
|
|
130
|
+
message: "a comparison is not a budget bypass — every one of these agents can hold an enforced dollar cap, so file a tournament for this lineup (it will also need each model's pinned price)",
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const plan = {
|
|
134
|
+
kind: "comparison",
|
|
135
|
+
agents,
|
|
136
|
+
publicationPolicy: input.publicationPolicy ?? "none",
|
|
137
|
+
comparisonDigest: "",
|
|
138
|
+
laneWords,
|
|
139
|
+
};
|
|
140
|
+
plan.comparisonDigest = comparisonDigestOf(plan);
|
|
141
|
+
return { ok: true, plan };
|
|
142
|
+
}
|
|
143
|
+
/** One words helper for every surface that speaks about a contest —
|
|
144
|
+
* screens, holds, notifications, decision subjects (spec E6). */
|
|
145
|
+
export function contestNoun(kind) {
|
|
146
|
+
return kind === "comparison" ? "comparison" : "tournament";
|
|
147
|
+
}
|
|
148
|
+
export function planTournament(input) {
|
|
149
|
+
if (input.agents.length < MIN_AGENTS || input.agents.length > MAX_AGENTS) {
|
|
150
|
+
return { ok: false, reason: "bad-count", message: `a tournament races ${MIN_AGENTS} to ${MAX_AGENTS} agents` };
|
|
151
|
+
}
|
|
152
|
+
if (!Number.isFinite(input.perAgentBudgetUsd) || input.perAgentBudgetUsd <= 0) {
|
|
153
|
+
return { ok: false, reason: "bad-budget", message: "each agent needs a positive dollar budget" };
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isFinite(input.totalBudgetUsd) || input.totalBudgetUsd <= 0) {
|
|
156
|
+
return { ok: false, reason: "bad-budget", message: "the tournament needs a positive total dollar budget" };
|
|
157
|
+
}
|
|
158
|
+
const agents = [];
|
|
159
|
+
const reserves = [];
|
|
160
|
+
for (const asked of input.agents) {
|
|
161
|
+
const capability = MONEY_CAPABILITIES[asked.provider];
|
|
162
|
+
if (capability === undefined || !capability.tournamentEligible) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: "provider-ineligible",
|
|
166
|
+
message: capability?.whyIneligible ?? `${asked.provider} cannot hold a dollar cap, so it cannot race`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const model = asked.model;
|
|
170
|
+
const repairModel = asked.repairModel ?? model;
|
|
171
|
+
for (const candidate of [model, repairModel]) {
|
|
172
|
+
if (buildPriceOf(candidate) === null) {
|
|
173
|
+
return {
|
|
174
|
+
ok: false,
|
|
175
|
+
reason: "unpriced-model",
|
|
176
|
+
message: `${candidate} has no pinned price — a budget nobody can compute is not a budget`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const tail = oneCallTailMicrousd(model);
|
|
181
|
+
const repairTail = oneCallTailMicrousd(repairModel);
|
|
182
|
+
if (tail === null || repairTail === null) {
|
|
183
|
+
return { ok: false, reason: "unpriced-model", message: `${model} has no pinned overrun price` };
|
|
184
|
+
}
|
|
185
|
+
agents.push({ provider: asked.provider, model, repairModel, ...(asked.permissionMode === undefined ? {} : { permissionMode: asked.permissionMode }) });
|
|
186
|
+
reserves.push(Math.max(tail, repairTail));
|
|
187
|
+
}
|
|
188
|
+
const perAgentBudgetMicrousd = Math.round(input.perAgentBudgetUsd * 1_000_000);
|
|
189
|
+
const totalBudgetMicrousd = Math.round(input.totalBudgetUsd * 1_000_000);
|
|
190
|
+
const committed = reserves.reduce((sum, reserve) => sum + perAgentBudgetMicrousd + reserve, 0);
|
|
191
|
+
if (committed > totalBudgetMicrousd) {
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
reason: "over-total",
|
|
195
|
+
message: `the true worst case is $${(committed / 1_000_000).toFixed(2)} ` +
|
|
196
|
+
`(each agent's budget plus its overrun reserve) — the total must cover it`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const plan = {
|
|
200
|
+
agents,
|
|
201
|
+
perAgentBudgetMicrousd,
|
|
202
|
+
overrunReserveMicrousd: Math.max(...reserves),
|
|
203
|
+
perAgentReserveMicrousd: reserves,
|
|
204
|
+
totalBudgetMicrousd,
|
|
205
|
+
priceVersion: BUILD_PRICE_VERSION,
|
|
206
|
+
publicationPolicy: input.publicationPolicy ?? "none",
|
|
207
|
+
raceDigest: "",
|
|
208
|
+
};
|
|
209
|
+
plan.raceDigest = raceDigestOf(plan);
|
|
210
|
+
return { ok: true, plan };
|
|
211
|
+
}
|
|
212
|
+
/** Unique per tournament — retained loser branches never collide. */
|
|
213
|
+
export function contestBranch(taskId, contestId, ordinal) {
|
|
214
|
+
return `${taskBranch(taskId)}/contest-${contestId}/c${ordinal}`;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* All or none (finding 3): every check inside one reentrant transaction
|
|
218
|
+
* with the claim acquisition the caller already made — the caller passes
|
|
219
|
+
* its live claim, and a refusal here obliges the caller to release it.
|
|
220
|
+
*/
|
|
221
|
+
export function admitContest(store, args, now) {
|
|
222
|
+
return store.transact(() => {
|
|
223
|
+
// D1 (external dispatch): external work does not race in this release.
|
|
224
|
+
if (store.mirrorByTask(args.taskId) !== null) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
reason: "contest-open",
|
|
228
|
+
message: "external work races in a follow-up release — file the tournament on a local task",
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (store.openContestFor(args.taskRef) !== null) {
|
|
232
|
+
return { ok: false, reason: "contest-open", message: "a tournament is already running on this task" };
|
|
233
|
+
}
|
|
234
|
+
// Steering and tournaments stay closed in BOTH directions (arc 1): a
|
|
235
|
+
// pending note would reach exactly one racer and silently miss the
|
|
236
|
+
// rest, so neither side starts while the other is open.
|
|
237
|
+
if (store.pendingSteerCount(args.taskRef) > 0) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
reason: "steering-pending",
|
|
241
|
+
message: "a steering note is still waiting to reach an agent — let it land or withdraw it before racing agents on this task",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const terms = store.activeTournamentTerms(args.taskRef);
|
|
245
|
+
if (terms === null) {
|
|
246
|
+
return { ok: false, reason: "no-terms", message: "no tournament terms are filed for this task" };
|
|
247
|
+
}
|
|
248
|
+
// Re-derive, never trust columns (the H1 precedent): the stored digest
|
|
249
|
+
// must equal the digest of the stored terms, and the approval must
|
|
250
|
+
// bind exactly that digest — plus the scope's own approval.
|
|
251
|
+
const derived = terms.kind === "comparison" ? comparisonDigestOf(terms) : raceDigestOf(terms);
|
|
252
|
+
if (derived !== terms.raceDigest) {
|
|
253
|
+
return { ok: false, reason: "digest-drift", message: `the stored ${contestNoun(terms.kind)} terms do not match their own fingerprint` };
|
|
254
|
+
}
|
|
255
|
+
if (!args.scopeApproved || terms.approvedDigest !== terms.raceDigest) {
|
|
256
|
+
return { ok: false, reason: "not-approved", message: `the ${contestNoun(terms.kind)} terms await approval` };
|
|
257
|
+
}
|
|
258
|
+
// Quota, grouped by distinct key; a half-open key may appear ONCE.
|
|
259
|
+
const seen = new Map();
|
|
260
|
+
for (const agent of terms.agents) {
|
|
261
|
+
const key = `${agent.provider}\u0000${agent.model}`;
|
|
262
|
+
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
263
|
+
}
|
|
264
|
+
for (const [key, count] of seen) {
|
|
265
|
+
const [provider = "", model = ""] = key.split("\u0000");
|
|
266
|
+
const blocked = args.quotaBlocked(provider, model);
|
|
267
|
+
if (blocked === "half-open" && count > 1) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
reason: "duplicate-provider-model",
|
|
271
|
+
message: `${provider} · ${model} is easing back from a limit — one probe exists, and this tournament asks for ${count}`,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (blocked !== null && blocked !== "half-open") {
|
|
275
|
+
return { ok: false, reason: "quota", message: `${provider} · ${model} is ${blocked} — nothing was started` };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Worker-process capacity (processes mode only; 'tasks' mode keeps the
|
|
279
|
+
// claim-counted contract untouched).
|
|
280
|
+
if (args.capacity !== null && store.liveSlotCount(args.runner) + terms.n > args.capacity) {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
reason: "capacity",
|
|
284
|
+
message: `this machine runs ${args.capacity} worker processes at most — ${terms.n} more will not fit right now`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const contestId = store.createContest({ taskRef: args.taskRef, terms: terms.id, scopeDigest: args.scopeDigest, raceDigest: terms.raceDigest, kind: terms.kind }, now);
|
|
288
|
+
store.stampContestLease(contestId, args.leaseId, args.runner, args.incarnation);
|
|
289
|
+
const reserves = terms.kind === "comparison" ? [] : perAgentReserves(terms);
|
|
290
|
+
const contestantIds = store.createContestants(contestId, terms.agents.map((agent, index) => ({
|
|
291
|
+
provider: agent.provider,
|
|
292
|
+
model: agent.model,
|
|
293
|
+
repairModel: agent.repairModel,
|
|
294
|
+
...(agent.permissionMode === undefined ? {} : { permissionMode: agent.permissionMode }),
|
|
295
|
+
branch: contestBranch(args.taskId, contestId, index + 1),
|
|
296
|
+
// Comparison lanes carry no dollar terms; unmeasured lanes are
|
|
297
|
+
// pre-latched as a fact of the lane (spec D6).
|
|
298
|
+
budgetMicrousd: terms.kind === "comparison" ? 0 : terms.perAgentBudgetMicrousd,
|
|
299
|
+
reserveMicrousd: terms.kind === "comparison" ? 0 : (reserves[index] ?? terms.overrunReserveMicrousd),
|
|
300
|
+
...(terms.kind === "comparison" && !reportsCost(agent.provider)
|
|
301
|
+
? { unknownSpend: true }
|
|
302
|
+
: {}),
|
|
303
|
+
})));
|
|
304
|
+
const slotIds = store.reserveExecutionSlots(args.runner, terms.n, now);
|
|
305
|
+
slotIds.forEach((slot, index) => {
|
|
306
|
+
const contestant = contestantIds[index];
|
|
307
|
+
if (contestant !== undefined)
|
|
308
|
+
store.assignSlotContestant(slot, contestant);
|
|
309
|
+
});
|
|
310
|
+
return { ok: true, contestId, contestantIds, slotIds };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
function perAgentReserves(terms) {
|
|
314
|
+
return terms.agents.map(agent => {
|
|
315
|
+
const tail = oneCallTailMicrousd(agent.model);
|
|
316
|
+
const repairTail = oneCallTailMicrousd(agent.repairModel);
|
|
317
|
+
return Math.max(tail ?? terms.overrunReserveMicrousd, repairTail ?? terms.overrunReserveMicrousd);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
// ---------------------------------------------------------------- barrier
|
|
321
|
+
/** Everything prepared, nothing spawned: pending→ready per agent, then one
|
|
322
|
+
* dispatching→racing CAS. Any failure leaves the contest for recovery. */
|
|
323
|
+
export function crossReadyBarrier(store, contest, contestantIds) {
|
|
324
|
+
return store.transact(() => {
|
|
325
|
+
for (const id of contestantIds) {
|
|
326
|
+
const row = store.getContestant(id);
|
|
327
|
+
if (row === null || !store.casContestantState(id, ["pending"], "ready", row.generation))
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
return store.casContestState(contest.id, ["dispatching"], "racing", contest.generation);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The child finalizer (findings 1/17): writes the agent's own row, frees
|
|
335
|
+
* its worker-process slot, and — inside this same transaction — asks
|
|
336
|
+
* whether the tournament just reached its boundary. The parent claim and
|
|
337
|
+
* the task are NEVER touched here; only the aggregate boundary releases
|
|
338
|
+
* the claim, exactly once, by CAS.
|
|
339
|
+
*/
|
|
340
|
+
export function finalizeContestant(store, args, now) {
|
|
341
|
+
return store.transact(() => {
|
|
342
|
+
const contestant = store.getContestant(args.contestantId);
|
|
343
|
+
if (contestant === null)
|
|
344
|
+
return { aggregated: null };
|
|
345
|
+
store.casContestantState(args.contestantId, ["building", "ready"], args.outcome, contestant.generation);
|
|
346
|
+
store.releaseContestantRun(args.contestantId, args.runId);
|
|
347
|
+
if (args.slotId !== null)
|
|
348
|
+
store.releaseExecutionSlot(args.slotId, now);
|
|
349
|
+
if (args.measuredMicrousd !== null)
|
|
350
|
+
store.recordContestantSpend(args.contestantId, args.measuredMicrousd);
|
|
351
|
+
else
|
|
352
|
+
store.latchContestantUnknownSpend(args.contestantId);
|
|
353
|
+
return { aggregated: maybeAggregate(store, args.contestId, now) };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The aggregate boundary — runs INSIDE a child/recovery transaction. When
|
|
358
|
+
* no agent remains active: parked-with-open-question → 'decision-wait';
|
|
359
|
+
* at least one 'built' → 'pick-wait'; none → 'exhausted'. The parent
|
|
360
|
+
* claim releases conditionally; the contest-owned hold takes over the
|
|
361
|
+
* task's stillness so the board says "compare and pick", not "vanished".
|
|
362
|
+
*/
|
|
363
|
+
export function maybeAggregate(store, contestId, now) {
|
|
364
|
+
const contest = store.getContest(contestId);
|
|
365
|
+
if (contest === null || contest.state !== "racing")
|
|
366
|
+
return null;
|
|
367
|
+
const all = store.contestants(contestId);
|
|
368
|
+
const active = all.filter(one => one.state === "pending" || one.state === "ready" || one.state === "building");
|
|
369
|
+
if (active.length > 0)
|
|
370
|
+
return null;
|
|
371
|
+
const parkedWaiting = all.filter(one => one.state === "parked" && store.openDecisionForContestant(one.id) !== null);
|
|
372
|
+
const target = parkedWaiting.length > 0 ? "decision-wait" : all.some(one => one.state === "built") ? "pick-wait" : "exhausted";
|
|
373
|
+
if (!store.casContestState(contestId, ["racing"], target, contest.generation))
|
|
374
|
+
return null;
|
|
375
|
+
if (contest.currentLeaseId !== null)
|
|
376
|
+
release(store, contest.currentLeaseId, now);
|
|
377
|
+
store.holdOwned({
|
|
378
|
+
taskRef: contest.taskRef,
|
|
379
|
+
ownerKind: "contest",
|
|
380
|
+
ownerId: String(contestId),
|
|
381
|
+
reason: target === "pick-wait"
|
|
382
|
+
? `${contestNoun(contest.kind)} finished — compare the results and pick one`
|
|
383
|
+
: target === "decision-wait"
|
|
384
|
+
? "an agent is waiting on your answer"
|
|
385
|
+
: `${contestNoun(contest.kind)} finished with nothing to pick — decide what happens next`,
|
|
386
|
+
until: null,
|
|
387
|
+
}, now);
|
|
388
|
+
// The boundary pages once (dedupe-keyed): a tournament that finished is
|
|
389
|
+
// exactly the "needs me" moment connected messaging exists for. The
|
|
390
|
+
// decision-wait case stays quiet here — the question itself already pages.
|
|
391
|
+
if (target !== "decision-wait") {
|
|
392
|
+
const taskId = store.externalIdFor(contest.taskRef) ?? `ref ${contest.taskRef}`;
|
|
393
|
+
store.enqueueNotification({
|
|
394
|
+
source: { taskRef: contest.taskRef },
|
|
395
|
+
dedupeKey: `contest-${target}:${contestId}`,
|
|
396
|
+
kind: "contest-finished",
|
|
397
|
+
// Only a PICKABLE finish pushes (arc 3): exhausted has nothing to tap.
|
|
398
|
+
...(target === "pick-wait" ? { pushClass: "pick", link: `/contest/${contestId}` } : {}),
|
|
399
|
+
subject: target === "pick-wait"
|
|
400
|
+
? `${contestNoun(contest.kind)} finished: ${taskId} — compare and pick`
|
|
401
|
+
: `${contestNoun(contest.kind)} finished with nothing to pick: ${taskId}`,
|
|
402
|
+
body: target === "pick-wait"
|
|
403
|
+
? `All ${all.length} agents finished. Compare the results and pick one at /contest/${contestId} — nothing continues until you do.`
|
|
404
|
+
: `All ${all.length} agents finished without a pickable result. Decide what happens next at /contest/${contestId}.`,
|
|
405
|
+
}, now);
|
|
406
|
+
}
|
|
407
|
+
return target;
|
|
408
|
+
}
|
|
409
|
+
// ---------------------------------------------------------------- recovery
|
|
410
|
+
/**
|
|
411
|
+
* Crash and reap recovery (finding 27): both CAS on the same generations
|
|
412
|
+
* the finalizers use. An agent that never crossed provider start settles
|
|
413
|
+
* at ZERO; only a started-and-silent one latches its full reservation.
|
|
414
|
+
*/
|
|
415
|
+
export function recoverContests(store, now) {
|
|
416
|
+
let recovered = 0;
|
|
417
|
+
for (const contest of store.contestsInStates(["dispatching", "racing"])) {
|
|
418
|
+
const lease = contest.currentLeaseId === null ? null : store.liveClaimByLease(contest.currentLeaseId, now);
|
|
419
|
+
if (lease !== null)
|
|
420
|
+
continue; // still honestly owned
|
|
421
|
+
const fresh = store.getContest(contest.id);
|
|
422
|
+
if (fresh === null)
|
|
423
|
+
continue;
|
|
424
|
+
const moved = store.transact(() => {
|
|
425
|
+
if (!store.casContestState(contest.id, ["dispatching", "racing"], "interrupted", fresh.generation))
|
|
426
|
+
return false;
|
|
427
|
+
for (const contestant of store.contestants(contest.id)) {
|
|
428
|
+
if (contestant.state === "pending" || contestant.state === "ready" || contestant.state === "building") {
|
|
429
|
+
store.casContestantState(contestant.id, [contestant.state], "stopped", contestant.generation);
|
|
430
|
+
const started = contestant.activeRun !== null && store.getRun(contestant.activeRun)?.providerStartedAt != null;
|
|
431
|
+
if (started)
|
|
432
|
+
store.latchContestantUnknownSpend(contestant.id);
|
|
433
|
+
// Never started: accounted stays where it is (zero when untouched).
|
|
434
|
+
if (contestant.activeRun !== null)
|
|
435
|
+
store.releaseContestantRun(contestant.id, contestant.activeRun);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
store.releaseSlotsForContest(contest.id, now);
|
|
439
|
+
store.holdOwned({
|
|
440
|
+
taskRef: contest.taskRef,
|
|
441
|
+
ownerKind: "contest",
|
|
442
|
+
ownerId: String(contest.id),
|
|
443
|
+
reason: `the ${contestNoun(contest.kind)} was interrupted — review it, then abandon or start it again`,
|
|
444
|
+
until: null,
|
|
445
|
+
}, now);
|
|
446
|
+
return true;
|
|
447
|
+
});
|
|
448
|
+
if (moved)
|
|
449
|
+
recovered++;
|
|
450
|
+
}
|
|
451
|
+
return recovered;
|
|
452
|
+
}
|
|
453
|
+
// ---------------------------------------------------------------- the pick
|
|
454
|
+
import { readVerifiedArtifact } from "./evidence.js";
|
|
455
|
+
/**
|
|
456
|
+
* Pickability, exactly as ruled (round-3 finding 20): a real result with
|
|
457
|
+
* verified, unredacted, untruncated evidence whose typed capture verdict
|
|
458
|
+
* says ok — never a prose parse, never an automatic selection.
|
|
459
|
+
*/
|
|
460
|
+
export function buildPickView(store, evidenceRoot, contestId) {
|
|
461
|
+
const contest = store.getContest(contestId);
|
|
462
|
+
if (contest === null)
|
|
463
|
+
return null;
|
|
464
|
+
const agents = store.contestants(contestId).map(contestant => {
|
|
465
|
+
// The lineage's final run: the newest run carrying this agent.
|
|
466
|
+
const runs = store
|
|
467
|
+
.runsFor(contest.taskRef)
|
|
468
|
+
.filter(one => one.contestant === contestant.id)
|
|
469
|
+
.sort((a, b) => b.id - a.id);
|
|
470
|
+
const run = runs[0] ?? null;
|
|
471
|
+
const artifacts = run === null ? [] : store.artifactsFor(run.id);
|
|
472
|
+
const diff = artifacts.find(one => one.kind === "terminal-diff") ?? null;
|
|
473
|
+
const stat = artifacts.find(one => one.kind === "diff-stat") ?? null;
|
|
474
|
+
// Plain words only — the internal state/outcome tokens never reach the
|
|
475
|
+
// comparison screen (round-4 finding 17). The tuple digest binds the
|
|
476
|
+
// raw facts separately; these strings are display, not authority.
|
|
477
|
+
const stateWords = {
|
|
478
|
+
pending: "was never started",
|
|
479
|
+
ready: "was set up but never ran",
|
|
480
|
+
building: "is still building",
|
|
481
|
+
parked: "stopped at a question",
|
|
482
|
+
failed: "failed",
|
|
483
|
+
stopped: "was stopped",
|
|
484
|
+
};
|
|
485
|
+
const outcomeWords = {
|
|
486
|
+
failed: "the attempt failed",
|
|
487
|
+
refused: "a gate refused the attempt",
|
|
488
|
+
parked: "the attempt stopped at a question",
|
|
489
|
+
};
|
|
490
|
+
let reason = null;
|
|
491
|
+
if (contestant.state !== "built")
|
|
492
|
+
reason = `did not finish — ${stateWords[contestant.state] ?? "the records have the detail"}`;
|
|
493
|
+
else if (run === null || run.outcome === null)
|
|
494
|
+
reason = "no finished attempt";
|
|
495
|
+
else if (run.outcome === "built" && run.committed !== true)
|
|
496
|
+
reason = "finished without committing";
|
|
497
|
+
else if (run.outcome === "no-change" && run.headRevision !== null && contest.baseSha !== null && run.headRevision !== contest.baseSha)
|
|
498
|
+
reason = "claims no change but the branch moved";
|
|
499
|
+
else if (run.outcome !== "built" && run.outcome !== "no-change")
|
|
500
|
+
reason = outcomeWords[run.outcome] ?? "the attempt did not finish cleanly";
|
|
501
|
+
else if (diff === null || stat === null)
|
|
502
|
+
reason = "evidence was not captured";
|
|
503
|
+
else if (diff.captureStatus !== "ok" || stat.captureStatus !== "ok")
|
|
504
|
+
reason = "evidence capture did not verify";
|
|
505
|
+
else if (diff.redacted)
|
|
506
|
+
reason = "the diff was redacted (something credential-shaped) — unpickable, like unpublishable";
|
|
507
|
+
else if (diff.truncated)
|
|
508
|
+
reason = "the diff was cut to fit — the comparison here would be partial";
|
|
509
|
+
else {
|
|
510
|
+
const provenDiff = readVerifiedArtifact(evidenceRoot, diff);
|
|
511
|
+
const provenStat = readVerifiedArtifact(evidenceRoot, stat);
|
|
512
|
+
if (!provenDiff.ok || !provenStat.ok)
|
|
513
|
+
reason = "the evidence bytes no longer verify";
|
|
514
|
+
}
|
|
515
|
+
return { contestant, run, diff, stat, pickable: reason === null, unpickableReason: reason };
|
|
516
|
+
});
|
|
517
|
+
return { contest, agents };
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* contest-pick/v1 — the canonical tuple the ceremony's nonce binds
|
|
521
|
+
* (round-1 finding 8 as amended): everything the password authorizes,
|
|
522
|
+
* rebuilt from live state on the POST, never accepted from a client.
|
|
523
|
+
*/
|
|
524
|
+
export function pickTupleDigest(view, selection, publication) {
|
|
525
|
+
const tuple = {
|
|
526
|
+
v: "contest-pick/v1",
|
|
527
|
+
contest: view.contest.id,
|
|
528
|
+
generation: view.contest.generation,
|
|
529
|
+
// v27 (Codex slice-B finding 10): kind changes the ceremony's money
|
|
530
|
+
// semantics, so the password binds it. Open nonces at upgrade simply
|
|
531
|
+
// re-mint — they live fifteen minutes.
|
|
532
|
+
kind: view.contest.kind,
|
|
533
|
+
taskRef: view.contest.taskRef,
|
|
534
|
+
scope: view.contest.scopeDigest,
|
|
535
|
+
race: view.contest.raceDigest,
|
|
536
|
+
base: view.contest.baseSha,
|
|
537
|
+
setup: view.contest.setupDigest,
|
|
538
|
+
agents: view.agents.map(agent => ({
|
|
539
|
+
id: agent.contestant.id,
|
|
540
|
+
ordinal: agent.contestant.ordinal,
|
|
541
|
+
provider: agent.contestant.provider,
|
|
542
|
+
model: agent.contestant.model,
|
|
543
|
+
state: agent.contestant.state,
|
|
544
|
+
run: agent.run?.id ?? null,
|
|
545
|
+
outcome: agent.run?.outcome ?? null,
|
|
546
|
+
committed: agent.run?.committed ?? null,
|
|
547
|
+
head: agent.run?.headRevision ?? null,
|
|
548
|
+
diff: agent.diff === null ? null : { id: agent.diff.id, sha: agent.diff.sha256, bytes: agent.diff.bytesStored, capture: agent.diff.captureStatus, redacted: agent.diff.redacted, truncated: agent.diff.truncated },
|
|
549
|
+
stat: agent.stat === null ? null : { id: agent.stat.id, sha: agent.stat.sha256, bytes: agent.stat.bytesStored, capture: agent.stat.captureStatus, redacted: agent.stat.redacted, truncated: agent.stat.truncated },
|
|
550
|
+
accountedMicrousd: agent.contestant.accountedMicrousd,
|
|
551
|
+
unknownSpend: agent.contestant.unknownSpend,
|
|
552
|
+
})),
|
|
553
|
+
selection,
|
|
554
|
+
publication: publication.grant === null
|
|
555
|
+
? "none"
|
|
556
|
+
: {
|
|
557
|
+
grant: publication.grant.id,
|
|
558
|
+
githubRepo: publication.grant.githubRepo,
|
|
559
|
+
remote: publication.grant.remote,
|
|
560
|
+
base: publication.grant.base,
|
|
561
|
+
headPrefix: publication.grant.headPrefix,
|
|
562
|
+
selector: publication.grant.selector,
|
|
563
|
+
draft: publication.grant.draft,
|
|
564
|
+
head: publication.head,
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
return createHash("sha256").update(JSON.stringify(tuple)).digest("hex");
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* One derivation for both moments — the POST that mints the nonce and the
|
|
571
|
+
* POST that consumes it. Publishability is decided here with exactly the
|
|
572
|
+
* tick path's predicate: committed real change, live grant, branch under
|
|
573
|
+
* the grant's prefix, selector satisfied.
|
|
574
|
+
*/
|
|
575
|
+
export function computePickPlan(store, view, contestantId, repo, refOrigin) {
|
|
576
|
+
const chosen = view.agents.find(agent => agent.contestant.id === contestantId);
|
|
577
|
+
if (chosen === undefined || !chosen.pickable || chosen.run === null) {
|
|
578
|
+
return { ok: false, message: chosen?.unpickableReason ?? "that agent cannot be picked" };
|
|
579
|
+
}
|
|
580
|
+
const run = chosen.run;
|
|
581
|
+
const grant = repo === null ? null : store.publicationGrantFor(repo);
|
|
582
|
+
const committed = run.outcome === "built" && run.committed === true;
|
|
583
|
+
// Contest runs are builders — a branchless (reviewer) row can never be
|
|
584
|
+
// picked, and the null check keeps that a typed fact, not an assumption.
|
|
585
|
+
const publishable = committed &&
|
|
586
|
+
grant !== null &&
|
|
587
|
+
run.headRevision !== null &&
|
|
588
|
+
run.branch !== null &&
|
|
589
|
+
headWithin(run.branch, grant.headPrefix) &&
|
|
590
|
+
(grant.selector === "all" || refOrigin === "ours");
|
|
591
|
+
const digest = pickTupleDigest(view, { contestantId }, { grant: publishable ? grant : null, head: publishable ? run.branch : null });
|
|
592
|
+
return { ok: true, chosen: { ...chosen, run }, grant, publishable, digest };
|
|
593
|
+
}
|
|
594
|
+
export function nonceHashOf(value) {
|
|
595
|
+
return createHash("sha256").update(value).digest("hex");
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* The pick, one transaction (round-1 finding 5 + round-3 findings 8/21):
|
|
599
|
+
* nonce consumed conditionally against the REBUILT tuple digest, contest
|
|
600
|
+
* CAS'd, winner proven again byte-for-byte, losers marked for cleanup,
|
|
601
|
+
* the hold lifted, the task done, and the publication intent created —
|
|
602
|
+
* now and only now — under exactly the tick's own predicate.
|
|
603
|
+
*/
|
|
604
|
+
export function finalizeContestPick(store, args, now) {
|
|
605
|
+
return store.transact(() => {
|
|
606
|
+
const view = buildPickView(store, args.evidenceRoot, args.contestId);
|
|
607
|
+
if (view === null || view.contest.state !== "pick-wait") {
|
|
608
|
+
return { ok: false, reason: "not-waiting", message: "this tournament is not waiting for a pick" };
|
|
609
|
+
}
|
|
610
|
+
const plan = computePickPlan(store, view, args.contestantId, args.repo, args.refOrigin);
|
|
611
|
+
if (!plan.ok) {
|
|
612
|
+
return { ok: false, reason: "unpickable", message: plan.message };
|
|
613
|
+
}
|
|
614
|
+
const { chosen, grant, publishable, digest } = plan;
|
|
615
|
+
if (!store.consumeCeremonyNonce(nonceHashOf(args.nonceValue), args.approver, "contest-pick", args.contestId, digest, now)) {
|
|
616
|
+
return { ok: false, reason: "stale", message: "the evidence changed since you read it, or this screen was already used — look again" };
|
|
617
|
+
}
|
|
618
|
+
// The winner's evidence, proved AGAIN inside the transaction: the nonce
|
|
619
|
+
// said "these bytes"; the pick lands only if they are still those bytes.
|
|
620
|
+
if (chosen.diff === null || !readVerifiedArtifact(args.evidenceRoot, chosen.diff).ok) {
|
|
621
|
+
return { ok: false, reason: "evidence", message: "the winner's evidence no longer verifies" };
|
|
622
|
+
}
|
|
623
|
+
if (!store.casContestState(args.contestId, ["pick-wait"], "picked", view.contest.generation)) {
|
|
624
|
+
return { ok: false, reason: "raced", message: "somebody else picked first" };
|
|
625
|
+
}
|
|
626
|
+
store.setContestWinner(args.contestId, args.contestantId, args.approver, now);
|
|
627
|
+
for (const agent of view.agents)
|
|
628
|
+
store.setContestantCleanup(agent.contestant.id, "pending");
|
|
629
|
+
store.releaseOwnedHold("contest", String(args.contestId));
|
|
630
|
+
const done = store.setTaskState(args.taskId, "done", now);
|
|
631
|
+
// With mirrors barred from tournaments (D1), a refusal here is a bug
|
|
632
|
+
// by construction — surface it, never publish over it.
|
|
633
|
+
if (!done.ok)
|
|
634
|
+
throw new Error(`the picked task could not be marked done (${done.reason})`);
|
|
635
|
+
let intent = null;
|
|
636
|
+
if (publishable && grant !== null && chosen.run.headRevision !== null && chosen.run.branch !== null) {
|
|
637
|
+
intent = store.createPublicationIntent({
|
|
638
|
+
run: chosen.run.id,
|
|
639
|
+
taskRef: view.contest.taskRef,
|
|
640
|
+
githubRepo: grant.githubRepo,
|
|
641
|
+
remote: grant.remote,
|
|
642
|
+
base: grant.base,
|
|
643
|
+
head: chosen.run.branch,
|
|
644
|
+
headSha: chosen.run.headRevision,
|
|
645
|
+
bodyHash: "",
|
|
646
|
+
draft: grant.draft,
|
|
647
|
+
}, now);
|
|
648
|
+
}
|
|
649
|
+
return { ok: true, state: "picked", publicationIntent: intent };
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
/** Abandon: password + nonce like the pick (it cancels paid-for work and
|
|
653
|
+
* re-opens the task), everything retained, nothing published. */
|
|
654
|
+
export function abandonContest(store, args, now) {
|
|
655
|
+
return store.transact(() => {
|
|
656
|
+
const view = buildPickView(store, args.evidenceRoot, args.contestId);
|
|
657
|
+
if (view === null || !["pick-wait", "exhausted", "interrupted", "decision-wait"].includes(view.contest.state)) {
|
|
658
|
+
return { ok: false, reason: "not-open", message: "this tournament is not in a state an operator can abandon" };
|
|
659
|
+
}
|
|
660
|
+
const digest = pickTupleDigest(view, { abandon: true }, { grant: null, head: null });
|
|
661
|
+
if (!store.consumeCeremonyNonce(nonceHashOf(args.nonceValue), args.approver, "contest-abandon", args.contestId, digest, now)) {
|
|
662
|
+
return { ok: false, reason: "stale", message: "the tournament changed since you read it — look again" };
|
|
663
|
+
}
|
|
664
|
+
if (!store.casContestState(args.contestId, [view.contest.state], "abandoned", view.contest.generation)) {
|
|
665
|
+
return { ok: false, reason: "raced", message: "the tournament moved first" };
|
|
666
|
+
}
|
|
667
|
+
for (const agent of view.agents)
|
|
668
|
+
store.setContestantCleanup(agent.contestant.id, "pending");
|
|
669
|
+
store.releaseOwnedHold("contest", String(args.contestId));
|
|
670
|
+
store.setTaskState(args.taskId, "failed", now);
|
|
671
|
+
return { ok: true };
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
// ---------------------------------------------------------------- stage 6
|
|
675
|
+
/** Fourteen days: the escalation threshold for an undecided tournament. */
|
|
676
|
+
export const CONTEST_OVERDUE_MS = 14 * 24 * 60 * 60_000;
|
|
677
|
+
/**
|
|
678
|
+
* The cleanup pass (stage 6): once a tournament is DECIDED — picked or
|
|
679
|
+
* abandoned — every agent's checkout goes back to the pool, winners and
|
|
680
|
+
* losers alike (branches and evidence survive; a checkout is only a
|
|
681
|
+
* checkout). Custody holds: only the runner that owns a checkout releases
|
|
682
|
+
* it, and one that will not release cleanly (dirty, uninspectable) is
|
|
683
|
+
* marked for attention and paged once rather than cleaned by force.
|
|
684
|
+
*/
|
|
685
|
+
export async function sweepContestCleanup(store, releaseWorktree, runner, now) {
|
|
686
|
+
let released = 0;
|
|
687
|
+
let attention = 0;
|
|
688
|
+
for (const contestant of store.contestantsForCleanup()) {
|
|
689
|
+
if (contestant.worktree === null) {
|
|
690
|
+
store.setContestantCleanup(contestant.id, "done");
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
const row = store.getWorktree(contestant.worktree);
|
|
694
|
+
if (row === null) {
|
|
695
|
+
// The pool never knew it, or the row is gone (reimage): nothing to
|
|
696
|
+
// release, nothing to keep waiting on.
|
|
697
|
+
store.setContestantCleanup(contestant.id, "done");
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (row.runner !== null && row.runner !== runner)
|
|
701
|
+
continue; // another machine's custody
|
|
702
|
+
const freed = await releaseWorktree(contestant.worktree);
|
|
703
|
+
if (freed.ok) {
|
|
704
|
+
store.setContestantCleanup(contestant.id, "done");
|
|
705
|
+
released += 1;
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
store.setContestantCleanup(contestant.id, "attention");
|
|
709
|
+
attention += 1;
|
|
710
|
+
store.enqueueNotification({
|
|
711
|
+
source: { taskRef: store.getContest(contestant.contest)?.taskRef ?? -1 },
|
|
712
|
+
dedupeKey: `contest-cleanup:${contestant.id}`,
|
|
713
|
+
kind: "contest-cleanup",
|
|
714
|
+
subject: `a tournament checkout needs a look: agent ${contestant.ordinal}`,
|
|
715
|
+
body: `The checkout at ${contestant.worktree} would not go back to the pool` +
|
|
716
|
+
` (${freed.message ?? freed.reason ?? "unknown"}). It was kept, not cleaned — look before anything is removed.`,
|
|
717
|
+
}, now);
|
|
718
|
+
}
|
|
719
|
+
return { released, attention };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Overdue escalation (stage 6): a tournament waiting on a person for
|
|
723
|
+
* fourteen days pages ONCE more — never auto-abandons, never re-pages.
|
|
724
|
+
* The age is derived from the moment the contest-owned hold took the
|
|
725
|
+
* task's stillness, which is exactly when the waiting began.
|
|
726
|
+
*/
|
|
727
|
+
export function escalateOverdueContests(store, now) {
|
|
728
|
+
let paged = 0;
|
|
729
|
+
for (const contest of store.contestsInStates(["pick-wait", "exhausted", "interrupted"])) {
|
|
730
|
+
if (contest.overduePaged)
|
|
731
|
+
continue;
|
|
732
|
+
const hold = store
|
|
733
|
+
.activeHolds(contest.taskRef, now)
|
|
734
|
+
.find(one => one.ownerKind === "contest" && one.ownerId === String(contest.id));
|
|
735
|
+
const since = hold?.heldAt ?? contest.createdAt;
|
|
736
|
+
if (now.getTime() - new Date(since).getTime() < CONTEST_OVERDUE_MS)
|
|
737
|
+
continue;
|
|
738
|
+
if (!store.markContestOverduePaged(contest.id))
|
|
739
|
+
continue; // someone else paged first
|
|
740
|
+
const taskId = store.externalIdFor(contest.taskRef) ?? `ref ${contest.taskRef}`;
|
|
741
|
+
const days = Math.floor((now.getTime() - new Date(since).getTime()) / 86_400_000);
|
|
742
|
+
store.enqueueNotification({
|
|
743
|
+
source: { taskRef: contest.taskRef },
|
|
744
|
+
dedupeKey: `contest-overdue:${contest.id}`,
|
|
745
|
+
kind: "contest-overdue",
|
|
746
|
+
subject: `a tournament has waited ${days} days: ${taskId}`,
|
|
747
|
+
body: `The tournament on ${taskId} has been waiting for your decision for ${days} days. ` +
|
|
748
|
+
`Nothing happens on its own — compare and decide at /contest/${contest.id}, or abandon it there.`,
|
|
749
|
+
}, now);
|
|
750
|
+
paged += 1;
|
|
751
|
+
}
|
|
752
|
+
return paged;
|
|
753
|
+
}
|