@humwick/server 0.0.0-stage → 0.130.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/CLA.md +66 -0
- package/CONTRIBUTING.md +144 -0
- package/LICENSE +167 -0
- package/README.md +511 -2
- package/SECURITY.md +67 -0
- package/THIRD_PARTY_NOTICES.md +144 -0
- package/bundled/prompts/README.md +10 -0
- package/bundled/prompts/context.advisory.compact.md +1 -0
- package/bundled/prompts/context.advisory.warn.md +1 -0
- package/bundled/prompts/continuation.envelope.md +18 -0
- package/bundled/prompts/manifest.json +838 -0
- package/bundled/prompts/memory.capture.boundary.post.md +1 -0
- package/bundled/prompts/memory.capture.boundary.pre.md +1 -0
- package/bundled/prompts/memory.charter.md +3 -0
- package/bundled/prompts/memory.native-off.md +0 -0
- package/bundled/prompts/notify.agent.exited.md +1 -0
- package/bundled/prompts/notify.agent.never-ready.md +1 -0
- package/bundled/prompts/notify.agent.ready.md +1 -0
- package/bundled/prompts/notify.automation.budget-warning.md +1 -0
- package/bundled/prompts/notify.land.failure.md +1 -0
- package/bundled/prompts/notify.land.steward-taken.coordinator.md +1 -0
- package/bundled/prompts/notify.land.steward-taken.md +1 -0
- package/bundled/prompts/notify.land.success.md +1 -0
- package/bundled/prompts/notify.routing.policy-changed.md +2 -0
- package/bundled/prompts/notify.spawn.failed.md +1 -0
- package/bundled/prompts/notify.spawn.queued.md +1 -0
- package/bundled/prompts/notify.spawn.recovered.md +1 -0
- package/bundled/prompts/notify.spawn.unconfirmed.md +1 -0
- package/bundled/prompts/profile.coordinator.charter.md +17 -0
- package/bundled/prompts/profile.debugger.system.md +12 -0
- package/bundled/prompts/profile.device-qa.system.md +10 -0
- package/bundled/prompts/profile.image-gen.append.0.md +2 -0
- package/bundled/prompts/profile.image-gen.system.md +4 -0
- package/bundled/prompts/profile.ops-assistant.brief.md +4 -0
- package/bundled/prompts/profile.probe.system.md +5 -0
- package/bundled/prompts/profile.qa.system.md +10 -0
- package/bundled/prompts/profile.sec-ops.system.md +14 -0
- package/bundled/prompts/profile.social-media.system.md +7 -0
- package/bundled/prompts/profile.software-design.system.md +26 -0
- package/bundled/prompts/profile.sub-coordinator.charter.md +2 -0
- package/bundled/prompts/profile.worker-gui.system.md +7 -0
- package/bundled/prompts/profile.worker.system.md +2 -0
- package/bundled/prompts/relay.agent-core.delivery.md +13 -0
- package/bundled/prompts/relay.agent-core.engineering.md +13 -0
- package/bundled/prompts/relay.agent-core.md +22 -0
- package/bundled/prompts/relay.context.claude.md +1 -0
- package/bundled/prompts/relay.context.claude.readonly.md +3 -0
- package/bundled/prompts/relay.context.codex.md +1 -0
- package/bundled/prompts/relay.context.opencode.md +1 -0
- package/bundled/prompts/relay.manual.claude.md +8 -0
- package/bundled/prompts/relay.search.fff.md +1 -0
- package/bundled/prompts/relay.temp-dir.md +1 -0
- package/bundled/prompts/relay.web.brave.md +1 -0
- package/bundled/prompts/relay.web.fetch.md +1 -0
- package/bundled/prompts/skill.device-lane.md +20 -0
- package/bundled/prompts/skill.security-code-review.md +10 -0
- package/bundled/prompts/skill.security-hardening.md +10 -0
- package/bundled/prompts/skill.security-host-audit.md +11 -0
- package/bundled/prompts/spawn.primer.md +6 -0
- package/bundled/prompts/spawn.reportup.child.md +3 -0
- package/bundled/prompts/spawn.reportup.coordinator.md +2 -0
- package/bundled/prompts/spawn.reportup.workflow-step.md +4 -0
- package/bundled/prompts/spawn.validation.delivery.md +1 -0
- package/bundled/prompts/spawn.validation.iterate.generic.md +1 -0
- package/bundled/prompts/spawn.validation.iterate.imperative.md +1 -0
- package/bundled/prompts/spawn.validation.landgates.header.md +1 -0
- package/bundled/prompts/steward.merge-stall.md +1 -0
- package/bundled/prompts/steward.prompt.md +20 -0
- package/bundled/prompts/steward.wake.md +1 -0
- package/bundled/prompts/task.claim.instruction.md +1 -0
- package/bundled/prompts/task.hydration.md +23 -0
- package/bundled/prompts/user.response.brief.md +1 -0
- package/bundled/prompts/user.response.normal.md +1 -0
- package/bundled/prompts/user.style.neutral.md +1 -0
- package/bundled/prompts/user.style.warm.md +1 -0
- package/bundled/prompts/workspace.deps.command.md +1 -0
- package/bundled/prompts/workspace.deps.none.md +1 -0
- package/bundled/prompts/workspace.deps.symlink.md +1 -0
- package/bundled/prompts/workspace.lifecycle.md +4 -0
- package/bundled/prompts/workspace.mcp.instructions.md +7 -0
- package/bundled/prompts/workspace.phase.abandoned.headline.md +1 -0
- package/bundled/prompts/workspace.phase.abandoned.hint.md +1 -0
- package/bundled/prompts/workspace.phase.active.headline.md +1 -0
- package/bundled/prompts/workspace.phase.active.hint.md +1 -0
- package/bundled/prompts/workspace.phase.conflict.headline.md +1 -0
- package/bundled/prompts/workspace.phase.conflict.hint.md +1 -0
- package/bundled/prompts/workspace.phase.merge-planned.headline.md +1 -0
- package/bundled/prompts/workspace.phase.merge-planned.hint.md +1 -0
- package/bundled/prompts/workspace.phase.merged.headline.md +1 -0
- package/bundled/prompts/workspace.phase.merged.hint.md +1 -0
- package/bundled/prompts/workspace.phase.ready.headline.md +1 -0
- package/bundled/prompts/workspace.phase.ready.hint.md +1 -0
- package/bundled/prompts/workspace.ready.contract.md +6 -0
- package/bundled/prompts/workspace.symlinks.md +1 -0
- package/bundled/roles/builder.md +18 -0
- package/bundled/roles/design-reviewer.md +13 -0
- package/bundled/roles/designer.md +11 -0
- package/bundled/roles/plan-auditor.md +29 -0
- package/bundled/roles/reviewer.md +36 -0
- package/bundled/roles/slice-qa.md +13 -0
- package/bundled/roles/verifier.md +13 -0
- package/bundled/skills/agent-browser/SKILL.md +72 -0
- package/bundled/skills/test-audit/SKILL.md +75 -0
- package/bundled/workflows/build-review.yaml +8 -0
- package/bundled/workflows/design-review.yaml +60 -0
- package/bundled/workflows/plan-conformance.yaml +5 -0
- package/bundled/workflows/slice-proof.yaml +85 -0
- package/bundled/workflows/verify-deployed.yaml +140 -0
- package/docs/guides/sub-coordinators.md +114 -0
- package/docs/guides/tools-administration.md +83 -0
- package/docs/guides/tools-agents.md +137 -0
- package/docs/guides/tools-coordination.md +228 -0
- package/docs/guides/tools-execution.md +48 -0
- package/docs/guides/tools-issues.md +69 -0
- package/docs/guides/tools-memory.md +150 -0
- package/docs/guides/tools-messages.md +192 -0
- package/docs/guides/tools-operations.md +133 -0
- package/docs/guides/tools-tasks.md +235 -0
- package/docs/guides/workflows.md +324 -0
- package/docs/license-faq.md +37 -0
- package/docs/openapi.json +18531 -0
- package/examples/integrations/github-issue.ts +48 -0
- package/examples/integrations/ops-alert.sh +27 -0
- package/examples/integrations/prometheus-alertmanager.ts +55 -0
- package/examples/integrations/support-ticket.sh +28 -0
- package/node_modules/@humwick/kernel/LICENSE +167 -0
- package/node_modules/@humwick/kernel/async-entity-persistence.ts +344 -0
- package/node_modules/@humwick/kernel/async-event-log.ts +107 -0
- package/node_modules/@humwick/kernel/automations.pilot.ts +147 -0
- package/node_modules/@humwick/kernel/config.ts +111 -0
- package/node_modules/@humwick/kernel/contract.ts +572 -0
- package/node_modules/@humwick/kernel/control-authz.ts +281 -0
- package/node_modules/@humwick/kernel/control.ts +645 -0
- package/node_modules/@humwick/kernel/effects.ts +493 -0
- package/node_modules/@humwick/kernel/event-log.ts +319 -0
- package/node_modules/@humwick/kernel/http-transport.ts +384 -0
- package/node_modules/@humwick/kernel/identity-grammar.ts +20 -0
- package/node_modules/@humwick/kernel/identity.ts +248 -0
- package/node_modules/@humwick/kernel/index.ts +28 -0
- package/node_modules/@humwick/kernel/kernel-persistence.ts +181 -0
- package/node_modules/@humwick/kernel/landing-incident.ts +306 -0
- package/node_modules/@humwick/kernel/landing.ts +953 -0
- package/node_modules/@humwick/kernel/manifest-validation.ts +460 -0
- package/node_modules/@humwick/kernel/mcp-transport.ts +109 -0
- package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +200 -0
- package/node_modules/@humwick/kernel/message-authority.ts +281 -0
- package/node_modules/@humwick/kernel/module-host.ts +722 -0
- package/node_modules/@humwick/kernel/openapi-init.ts +14 -0
- package/node_modules/@humwick/kernel/package.json +47 -0
- package/node_modules/@humwick/kernel/persistence-schema.ts +248 -0
- package/node_modules/@humwick/kernel/persistence.ts +510 -0
- package/node_modules/@humwick/kernel/registry.ts +60 -0
- package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +499 -0
- package/node_modules/@humwick/kernel/sqlite-module-host.ts +87 -0
- package/node_modules/@humwick/kernel/timers-chaos-worker.ts +186 -0
- package/node_modules/@humwick/kernel/timers.pilot.ts +1411 -0
- package/node_modules/@humwick/kernel/workflow-activation.ts +25 -0
- package/node_modules/@humwick/kernel/workflow-backstop.ts +40 -0
- package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +20 -0
- package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +213 -0
- package/node_modules/@humwick/kernel/workflow-composition.ts +231 -0
- package/node_modules/@humwick/kernel/workflow-container.ts +72 -0
- package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +42 -0
- package/node_modules/@humwick/kernel/workflow-control.ts +178 -0
- package/node_modules/@humwick/kernel/workflow-definition.ts +507 -0
- package/node_modules/@humwick/kernel/workflow-dispatch.ts +60 -0
- package/node_modules/@humwick/kernel/workflow-foreach.ts +167 -0
- package/node_modules/@humwick/kernel/workflow-journal-codec.ts +339 -0
- package/node_modules/@humwick/kernel/workflow-journal.ts +922 -0
- package/node_modules/@humwick/kernel/workflow-lane.ts +48 -0
- package/node_modules/@humwick/kernel/workflow-lease-owner.ts +60 -0
- package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +19 -0
- package/node_modules/@humwick/kernel/workflow-runtime.ts +938 -0
- package/node_modules/@humwick/kernel/workflow-script.ts +11 -0
- package/node_modules/@humwick/kernel/workflow-state.ts +299 -0
- package/node_modules/@humwick/kernel/workflow-step-commit.ts +195 -0
- package/node_modules/@humwick/kernel/workflow-transitions.ts +317 -0
- package/node_modules/@humwick/kernel/workflow-values.ts +153 -0
- package/node_modules/@humwick/kernel/workflow.pilot.ts +376 -0
- package/node_modules/@humwick/offload/LICENSE +167 -0
- package/node_modules/@humwick/offload/README.md +57 -0
- package/node_modules/@humwick/offload/dist/async-runner.d.ts +18 -0
- package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/async-runner.js +158 -0
- package/node_modules/@humwick/offload/dist/async-runner.js.map +1 -0
- package/node_modules/@humwick/offload/dist/envelope.d.ts +25 -0
- package/node_modules/@humwick/offload/dist/envelope.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/envelope.js +80 -0
- package/node_modules/@humwick/offload/dist/envelope.js.map +1 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +10 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +59 -0
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +1 -0
- package/node_modules/@humwick/offload/dist/index.d.ts +5 -0
- package/node_modules/@humwick/offload/dist/index.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/index.js +5 -0
- package/node_modules/@humwick/offload/dist/index.js.map +1 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +62 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.js +295 -0
- package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +1 -0
- package/node_modules/@humwick/offload/dist/types.d.ts +111 -0
- package/node_modules/@humwick/offload/dist/types.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/types.js +176 -0
- package/node_modules/@humwick/offload/dist/types.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts +23 -0
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-pool.js +435 -0
- package/node_modules/@humwick/offload/dist/worker-pool.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +2 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.js +27 -0
- package/node_modules/@humwick/offload/dist/worker-runtime.js.map +1 -0
- package/node_modules/@humwick/offload/dist/worker.d.ts +5 -0
- package/node_modules/@humwick/offload/dist/worker.d.ts.map +1 -0
- package/node_modules/@humwick/offload/dist/worker.js +1 -0
- package/node_modules/@humwick/offload/dist/worker.js.map +1 -0
- package/node_modules/@humwick/offload/package.json +46 -0
- package/node_modules/@humwick/offload/src/async-runner.ts +170 -0
- package/node_modules/@humwick/offload/src/envelope.ts +92 -0
- package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +49 -0
- package/node_modules/@humwick/offload/src/index.ts +4 -0
- package/node_modules/@humwick/offload/src/subprocess-runner.ts +334 -0
- package/node_modules/@humwick/offload/src/types.ts +235 -0
- package/node_modules/@humwick/offload/src/worker-pool.ts +463 -0
- package/node_modules/@humwick/offload/src/worker-runtime.ts +32 -0
- package/node_modules/@humwick/offload/src/worker.ts +5 -0
- package/package.json +229 -4
- package/public/assets/MessageBubble-BWpoBjl4.js +4 -0
- package/public/assets/PlanGraphPanel-C0auck3n.js +2 -0
- package/public/assets/TraceView-DBnOx7AQ.js +5 -0
- package/public/assets/VaultDecisionCard-0kp2oNBC.js +2 -0
- package/public/assets/activity-TMzFOdh2.js +2 -0
- package/public/assets/activity-yLztXEBt.js +2 -0
- package/public/assets/agent-lineage-BbJDR1dW.js +2 -0
- package/public/assets/agent-liveness-DyqxfB-t.js +2 -0
- package/public/assets/agent-name-CTZBrSD9.js +2 -0
- package/public/assets/agent-profiles-BpI_jTxq.js +2 -0
- package/public/assets/agent-project-badge-9TqzUUYE.js +2 -0
- package/public/assets/agent-tools-D4n40pn8.js +2 -0
- package/public/assets/agent-type-icon-D6FUarOf.js +2 -0
- package/public/assets/agents-BYDslkJc.js +2 -0
- package/public/assets/analytics-B-XSbEMC.js +910 -0
- package/public/assets/andromeeda-vGVdxbeo.js +2 -0
- package/public/assets/angular-html-BVBpGdXr.js +2 -0
- package/public/assets/angular-ts-BfdufMKP.js +2 -0
- package/public/assets/api-CIIVBtAV.js +10 -0
- package/public/assets/archive-CIuPSvmx.js +2 -0
- package/public/assets/arrow-right-vFsRqG8V.js +2 -0
- package/public/assets/arrow-up-a-z-CKC2RCjD.js +2 -0
- package/public/assets/artifact-viewer-DdWIfV6M.js +2 -0
- package/public/assets/artifacts-DPN1q4pA.js +2 -0
- package/public/assets/artifacts-oBOqks-2.js +2 -0
- package/public/assets/astro-JKRPP03v.js +2 -0
- package/public/assets/aurora-x-CDeNXAV0.js +2 -0
- package/public/assets/automation-4KcRFXEu.js +2 -0
- package/public/assets/ayu-dark-DluEY0Gj.js +2 -0
- package/public/assets/ayu-light-C3h-C4tm.js +2 -0
- package/public/assets/ayu-mirage-Bqwy1Gya.js +2 -0
- package/public/assets/backlog-Cw9xPTCF.js +2 -0
- package/public/assets/badge-DqJRtj_p.js +2 -0
- package/public/assets/bell-_Yzbxxrt.js +2 -0
- package/public/assets/blade-DA8yMPsw.js +2 -0
- package/public/assets/bot-B0NpDNhr.js +2 -0
- package/public/assets/boxes-Cv7Wq3h4.js +2 -0
- package/public/assets/braces-BOks9SsY.js +2 -0
- package/public/assets/brain-CagXMEKE.js +2 -0
- package/public/assets/brain-circuit-CpQeSmp3.js +2 -0
- package/public/assets/bundle-web-ifbJd0t-.js +14 -0
- package/public/assets/busy-health-CTu32MZt.js +2 -0
- package/public/assets/button-Do6Z3vvn.js +2 -0
- package/public/assets/c-Wt1voDr2.js +2 -0
- package/public/assets/calendar-QJjoZ1JU.js +2 -0
- package/public/assets/calendar-clock-CDG_KBhx.js +2 -0
- package/public/assets/card-C4QJ4hCW.js +2 -0
- package/public/assets/catppuccin-frappe-3VR1Za6u.js +2 -0
- package/public/assets/catppuccin-latte-DwIHMF0Q.js +2 -0
- package/public/assets/catppuccin-macchiato-DYnBP6_5.js +2 -0
- package/public/assets/catppuccin-mocha-DYhrFGRu.js +2 -0
- package/public/assets/channels-BiXzg1G9.js +2 -0
- package/public/assets/chart-column-S02ZGfTR.js +2 -0
- package/public/assets/chat-Ctk2qwvA.js +6 -0
- package/public/assets/check-B3M9Ug1P.js +2 -0
- package/public/assets/chevron-down-BoG3JCZn.js +2 -0
- package/public/assets/chevron-right-iljytE0x.js +2 -0
- package/public/assets/chevron-up-Bv3yC5ma.js +2 -0
- package/public/assets/circle-UESLMi2-.js +2 -0
- package/public/assets/circle-alert-CryagaZd.js +2 -0
- package/public/assets/circle-arrow-up-B432r0Kk.js +2 -0
- package/public/assets/circle-check-CkUkN96J.js +2 -0
- package/public/assets/circle-x-CZ0erS7Q.js +2 -0
- package/public/assets/clipboard-list-W2YeUsxT.js +2 -0
- package/public/assets/clock-3-BH47318N.js +2 -0
- package/public/assets/clock-BWPPBh1_.js +2 -0
- package/public/assets/code-preview-Bm1wWqAp.js +3 -0
- package/public/assets/coffee-CThvmt4R.js +2 -0
- package/public/assets/connectors-C36ax4Vw.js +2 -0
- package/public/assets/copy-DFxhHRtV.js +2 -0
- package/public/assets/copy-button-CjOj4HEf.js +2 -0
- package/public/assets/cosmos-Dn8XrlnS.css +1 -0
- package/public/assets/cosmos-Jj9LK2Wf.js +3993 -0
- package/public/assets/cpp-C5WNSHb9.js +2 -0
- package/public/assets/cpu-Dndy84IU.js +2 -0
- package/public/assets/crate-agent-H6S7-B9T.js +2 -0
- package/public/assets/css-DJp_X0uY.js +2 -0
- package/public/assets/csv-Dx-8-gkx.js +2 -0
- package/public/assets/dark-plus-Cs2F2srj.js +2 -0
- package/public/assets/dependency-graph-Cd1E23wa.js +2 -0
- package/public/assets/deploys-Bwfq146q.js +2 -0
- package/public/assets/dialog-uuJ9_geR.js +6 -0
- package/public/assets/display-CfO7rTuF.js +3 -0
- package/public/assets/dist-B2h2GeWt.js +2 -0
- package/public/assets/dist-BeVYU67E.js +2 -0
- package/public/assets/dist-Bmm-RKub.js +2 -0
- package/public/assets/dist-C1xmD7qJ.js +2 -0
- package/public/assets/dist-C2J943E6.js +2 -0
- package/public/assets/dist-C6lpJDoA.js +2 -0
- package/public/assets/dist-CoWL6B0c.js +2 -0
- package/public/assets/dist-D93SmTNC.js +2 -0
- package/public/assets/dist-Da7HrHNA.js +2 -0
- package/public/assets/dist-DeW2Y1R5.js +2 -0
- package/public/assets/dist-DwBdqQ6H.js +2 -0
- package/public/assets/dist-Dz182zaW.js +2 -0
- package/public/assets/dist-H3DLHJ0C.js +2 -0
- package/public/assets/dist-RrY9C4SI.js +2 -0
- package/public/assets/download-KgtpDbt5.js +2 -0
- package/public/assets/dracula-BHWKrbxM.js +2 -0
- package/public/assets/dracula-soft-5eyTD99u.js +2 -0
- package/public/assets/dropdown-menu-BXiNephE.js +2 -0
- package/public/assets/es2015-DMmZ8vbD.js +42 -0
- package/public/assets/esm-ZSuD7QuY.js +2 -0
- package/public/assets/everforest-dark-sB-x3p7T.js +2 -0
- package/public/assets/everforest-light-Df2xbC6M.js +2 -0
- package/public/assets/external-link-BRfu-fHG.js +2 -0
- package/public/assets/eye-BAP6fHrX.js +2 -0
- package/public/assets/file-code-corner-CXhIR1vB.js +2 -0
- package/public/assets/file-pen-Crunre5f.js +2 -0
- package/public/assets/file-text-CKuoMR2U.js +2 -0
- package/public/assets/files-DhhZI2Ih.js +3 -0
- package/public/assets/flag-CnEG9PH-.js +2 -0
- package/public/assets/flame-CxBuz46L.js +2 -0
- package/public/assets/fleet-BL4UXS_k.js +2 -0
- package/public/assets/fleet-metrics-BkHP-P3R.js +2 -0
- package/public/assets/folder-git-2-CJOmTT06.js +2 -0
- package/public/assets/folder-open-CBUCMSlu.js +2 -0
- package/public/assets/folder-tree-BG3Y9nqH.js +2 -0
- package/public/assets/formatted-body-Cm1RrzmN.js +3 -0
- package/public/assets/formatted-body-impl-DQiOuOD1.js +3 -0
- package/public/assets/gauge-BIODY-BQ.js +2 -0
- package/public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/public/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/public/assets/git-branch-yxhV0m8E.js +2 -0
- package/public/assets/git-commit-horizontal-DS1pJ_SS.js +2 -0
- package/public/assets/git-merge-C7eIG425.js +2 -0
- package/public/assets/github-dark-C-LZuMrd.js +2 -0
- package/public/assets/github-dark-default-DXG-b-1a.js +2 -0
- package/public/assets/github-dark-dimmed-Bx1FflLF.js +2 -0
- package/public/assets/github-dark-high-contrast-B_tTalzw.js +2 -0
- package/public/assets/github-light-EUqPIrTm.js +2 -0
- package/public/assets/github-light-default-BXViO-2h.js +2 -0
- package/public/assets/github-light-high-contrast-B68TUdTA.js +2 -0
- package/public/assets/globe-Cl5fXtqs.js +2 -0
- package/public/assets/glsl-Dv5r7kPw.js +2 -0
- package/public/assets/graphql-CcFwQwm0.js +2 -0
- package/public/assets/gruvbox-dark-hard-C820rvS2.js +2 -0
- package/public/assets/gruvbox-dark-medium-BPjhmG05.js +2 -0
- package/public/assets/gruvbox-dark-soft-MrdJrrXF.js +2 -0
- package/public/assets/gruvbox-light-hard-BC_s9l72.js +2 -0
- package/public/assets/gruvbox-light-medium-BAWPOn9u.js +2 -0
- package/public/assets/gruvbox-light-soft-BSMLrYjP.js +2 -0
- package/public/assets/haml-Bg4I2hiD.js +2 -0
- package/public/assets/handlebars-DnwbnL5R.js +2 -0
- package/public/assets/hash-CbtKih3g.js +2 -0
- package/public/assets/horizon-CE9ld1lL.js +2 -0
- package/public/assets/horizon-bright-DSNQnXHK.js +2 -0
- package/public/assets/host-mTgRw7xh.js +2 -0
- package/public/assets/host-providers-dialog-CHqwXTov.js +2 -0
- package/public/assets/hourglass-txu4ax6n.js +2 -0
- package/public/assets/houston-CsvMBhTu.js +2 -0
- package/public/assets/html-C8UlPnhE.js +2 -0
- package/public/assets/html-derivative-CY6NRz-J.js +2 -0
- package/public/assets/http-CoW620Fi.js +2 -0
- package/public/assets/humwick-lockup-on-dark-Ds44Y7BK.svg +19 -0
- package/public/assets/humwick-lockup-on-light-ClALZAHt.svg +19 -0
- package/public/assets/hurl-CSSETujw.js +2 -0
- package/public/assets/imba-DsUTQ-LC.js +2 -0
- package/public/assets/inbox-Dfwk3Gt8.js +2 -0
- package/public/assets/index-B7ZerHLY.css +2 -0
- package/public/assets/index-BK0WV6hF.js +15 -0
- package/public/assets/input-CsTB6jIx.js +2 -0
- package/public/assets/insights-fH1EL-qZ.js +2 -0
- package/public/assets/integrations-C5Oix6ng.js +2 -0
- package/public/assets/issue-ref-chip-ODyondIa.js +3 -0
- package/public/assets/java-CGc3VwQr.js +2 -0
- package/public/assets/javascript-CUt1pgmJ.js +2 -0
- package/public/assets/jinja-CD-Z-FLd.js +2 -0
- package/public/assets/jison-imPNup1l.js +2 -0
- package/public/assets/json-qhed-kSA.js +2 -0
- package/public/assets/json5-BR5RXkoi.js +2 -0
- package/public/assets/jsonc-CYpm1nAK.js +2 -0
- package/public/assets/jsonl-CmCQp5Yx.js +2 -0
- package/public/assets/jsx-CY6oMTks.js +2 -0
- package/public/assets/julia-CjffhBEM.js +2 -0
- package/public/assets/kanagawa-dragon-CXtmUGW6.js +2 -0
- package/public/assets/kanagawa-lotus-BN08jTvb.js +2 -0
- package/public/assets/kanagawa-wave-CTweb8Dz.js +2 -0
- package/public/assets/kernel-Bqr0CZGv.js +2 -0
- package/public/assets/key-round-CcSxl5qr.js +2 -0
- package/public/assets/keyboard-DcmEulph.js +2 -0
- package/public/assets/label-BPBzm9FJ.js +2 -0
- package/public/assets/laserwave-C_8bwKvT.js +2 -0
- package/public/assets/layers-DEfClY-v.js +2 -0
- package/public/assets/legacy-storage-carry-C-tBSxRA.js +2 -0
- package/public/assets/less-DVTAwKKz.js +2 -0
- package/public/assets/lib-D7DTGM1F.js +2 -0
- package/public/assets/lib-G-25QXXf.js +30 -0
- package/public/assets/light-plus-DVQuIRkW.js +2 -0
- package/public/assets/lightbulb-RDhZ4p-D.js +2 -0
- package/public/assets/link-2-off-DfRF9Hwb.js +2 -0
- package/public/assets/link-6RR7Hy3-.js +2 -0
- package/public/assets/list-checks-CwnS3hly.js +2 -0
- package/public/assets/list-ordered-D8nCM7rT.js +2 -0
- package/public/assets/list-todo-9qT0fgxZ.js +2 -0
- package/public/assets/loader-circle-BfPd4kOD.js +2 -0
- package/public/assets/lock-BW8MPKw_.js +2 -0
- package/public/assets/log-viewer-Cvfy6PW7.js +4 -0
- package/public/assets/mail-DyaDmwfY.js +2 -0
- package/public/assets/maintenance-D8CDH5wZ.js +2 -0
- package/public/assets/managed-agents-egllbpij.js +4 -0
- package/public/assets/markdown-BYOwaDjH.js +2 -0
- package/public/assets/markdown-preview-CYSFgVgK.js +3 -0
- package/public/assets/markdown-preview-impl-BteVIjX4.js +2 -0
- package/public/assets/marko-DvhNOisQ.js +2 -0
- package/public/assets/material-theme-Bm3Qr25_.js +2 -0
- package/public/assets/material-theme-darker-2IIEA8gg.js +2 -0
- package/public/assets/material-theme-lighter-uhdI0v04.js +2 -0
- package/public/assets/material-theme-ocean-CHQ94UKr.js +2 -0
- package/public/assets/material-theme-palenight-B5W6OYN7.js +2 -0
- package/public/assets/mdc-CazklSAj.js +2 -0
- package/public/assets/mdx-DQZ5AkYe.js +2 -0
- package/public/assets/memory-C8WmXsyr.js +2 -0
- package/public/assets/message-circle-wjztgHBe.js +2 -0
- package/public/assets/message-square-C-1G8dMM.js +2 -0
- package/public/assets/message-square-reply-B9Z4cbQx.js +2 -0
- package/public/assets/messages-BzQdqlO6.js +2 -0
- package/public/assets/messages-square-DVM1ZtcI.js +2 -0
- package/public/assets/min-dark-BSWPekZh.js +2 -0
- package/public/assets/min-light-DDpmG2fV.js +2 -0
- package/public/assets/minimize-2-BkK6XoMP.js +2 -0
- package/public/assets/monokai-CdkpiU2Y.js +2 -0
- package/public/assets/night-owl-DhmEMT88.js +2 -0
- package/public/assets/night-owl-light-eJ-hLW7d.js +2 -0
- package/public/assets/nord-Cb4Vim4T.js +2 -0
- package/public/assets/octagon-x-B5lnUY-8.js +2 -0
- package/public/assets/one-dark-pro-CLwyXe_n.js +2 -0
- package/public/assets/one-light-D7Lr4KcI.js +2 -0
- package/public/assets/orchestrators-B1HxzkNw.js +2 -0
- package/public/assets/overview-DCUnI4Z7.js +2 -0
- package/public/assets/package-open-ckPgcjpv.js +2 -0
- package/public/assets/package-plus-C1AKMjsl.js +2 -0
- package/public/assets/pairs-ftCd2XcD.js +2 -0
- package/public/assets/paperclip-CNpKZKum.js +2 -0
- package/public/assets/pause-DttnHD4c.js +2 -0
- package/public/assets/pencil-B1Hf2V_J.js +2 -0
- package/public/assets/php-CBqsN0ai.js +2 -0
- package/public/assets/pin-DcsKeXxr.js +2 -0
- package/public/assets/plastic-DQwYfKfQ.js +2 -0
- package/public/assets/play-BbT8m7A0.js +2 -0
- package/public/assets/plug-vIyO9_P8.js +2 -0
- package/public/assets/plug-zap-V0wMDJ-G.js +2 -0
- package/public/assets/plus-D5Ek4e2S.js +2 -0
- package/public/assets/poimandres-DRFjx7u4.js +2 -0
- package/public/assets/popover-8w1TWKMG.js +2 -0
- package/public/assets/postcss-BXeXVLqQ.js +2 -0
- package/public/assets/power-6tZOhYPg.js +2 -0
- package/public/assets/preload-helper-HclGiUj8.js +1 -0
- package/public/assets/profile-icon-B2zH8DMH.js +2 -0
- package/public/assets/projects-B_lmcqfO.js +2 -0
- package/public/assets/prompt-settings-Bftq9czX.js +2 -0
- package/public/assets/provider-brand-icon-_67JR7Zh.js +2 -0
- package/public/assets/provider-display-DbLWpP3j.js +2 -0
- package/public/assets/pug-C5hz5LQ7.js +2 -0
- package/public/assets/python-gzcpVVnB.js +2 -0
- package/public/assets/r-Dzep-lPg.js +2 -0
- package/public/assets/radio-Bn0_qc0G.js +2 -0
- package/public/assets/react-dom-DSFxiC7T.js +2 -0
- package/public/assets/red-CJ3rzSJv.js +2 -0
- package/public/assets/refresh-cw-CfpvQcUn.js +2 -0
- package/public/assets/regexp-Omp9DhTb.js +2 -0
- package/public/assets/registry-DWCU5c8q.js +4 -0
- package/public/assets/repeat-hl4p_Klo.js +2 -0
- package/public/assets/rocket-BmO1qSu-.js +2 -0
- package/public/assets/rolldown-runtime-QTnfLwEv.js +1 -0
- package/public/assets/rose-pine-BthvhNj6.js +2 -0
- package/public/assets/rose-pine-dawn-Dg85fqjY.js +2 -0
- package/public/assets/rose-pine-moon-hon4tzzS.js +2 -0
- package/public/assets/rotate-ccw-CtD77qkX.js +2 -0
- package/public/assets/rotate-cw-FPtkDewA.js +2 -0
- package/public/assets/run-card-CO_bguQf.js +2 -0
- package/public/assets/sass-DXrisJhu.js +2 -0
- package/public/assets/save-B_PGqSm4.js +2 -0
- package/public/assets/scroll-area-CIN4KQse.js +2 -0
- package/public/assets/scss-DdSxiZKl.js +2 -0
- package/public/assets/search-DjzbnTbf.js +2 -0
- package/public/assets/secrets-C7GIUATz.js +2 -0
- package/public/assets/security-BUFau5-N.js +3 -0
- package/public/assets/select-DTQMP4Dk.js +2 -0
- package/public/assets/separator-t1cLZErD.js +2 -0
- package/public/assets/server-CZIRjFyY.js +2 -0
- package/public/assets/service-access-settings-C3aujqTG.js +2 -0
- package/public/assets/settings-C6sZ9eBa.js +6 -0
- package/public/assets/shellscript-CLZ0U2zV.js +2 -0
- package/public/assets/shield-CtXNUA0i.js +2 -0
- package/public/assets/shield-alert-B9jCWcKW.js +2 -0
- package/public/assets/shipping-Bu8p1gEw.js +2 -0
- package/public/assets/shipping-stats-BiMTsA4A.js +2 -0
- package/public/assets/slack-dark-DnToyrRv.js +2 -0
- package/public/assets/slack-ochin-B2OO5cIa.js +2 -0
- package/public/assets/smile-plus-q6EQBXHZ.js +2 -0
- package/public/assets/snazzy-light-4G7pJPwS.js +2 -0
- package/public/assets/solarized-dark-DV17i1UV.js +2 -0
- package/public/assets/solarized-light-DSh2HLQt.js +2 -0
- package/public/assets/sparkles-CzwR10ZU.js +2 -0
- package/public/assets/sql-DNssxck8.js +2 -0
- package/public/assets/square-Bqly37YM.js +2 -0
- package/public/assets/stats-V9Z-GxUd.js +2 -0
- package/public/assets/status-dot-CVv0iGHL.js +2 -0
- package/public/assets/stethoscope-SCBrnQQ8.js +2 -0
- package/public/assets/store-BvtFdlGX.js +9 -0
- package/public/assets/stylus-B6D30XZt.js +2 -0
- package/public/assets/svelte-DOdLCIlh.js +2 -0
- package/public/assets/switch-DuCA7mrB.js +2 -0
- package/public/assets/synthwave-84-nFMaYfgc.js +2 -0
- package/public/assets/tabs-jZgNvNHr.js +2 -0
- package/public/assets/task-chip-CYj0mpoL.js +2 -0
- package/public/assets/tasks-BIyeAaPC.js +2 -0
- package/public/assets/tasks-CXU75HRF.js +11 -0
- package/public/assets/teams-BPf976zb.js +2 -0
- package/public/assets/terminal-Bl2u7KxT.js +2 -0
- package/public/assets/terminal-viewer-DE9BOe-8.js +3 -0
- package/public/assets/terminal-viewer-impl-BrP-ENHg.css +1 -0
- package/public/assets/terminal-viewer-impl-D04pjmGh.js +143 -0
- package/public/assets/textarea-hs6Mpyfl.js +2 -0
- package/public/assets/themes-G5j9m1Ji.js +2 -0
- package/public/assets/tokyo-night-oM2G3aXe.js +2 -0
- package/public/assets/trash-2-FbcSq927.js +2 -0
- package/public/assets/triangle-alert-BBNhfgMi.js +2 -0
- package/public/assets/ts-tags-Ca73dJJY.js +2 -0
- package/public/assets/tsx-5Eka4NBX.js +2 -0
- package/public/assets/typescript-DOu2WMV5.js +2 -0
- package/public/assets/use-agent-terminal-B1yR456R.js +2 -0
- package/public/assets/use-decisions-D-S4tpDf.js +2 -0
- package/public/assets/use-project-list-B-T2Bnnn.js +2 -0
- package/public/assets/use-selectors-CnrAk2YP.js +6 -0
- package/public/assets/user-avatar-nBFIcR4M.js +2 -0
- package/public/assets/user-cog-CKcouW1J.js +2 -0
- package/public/assets/user-plus-D9749q73.js +2 -0
- package/public/assets/users-C7B3u9jv.js +2 -0
- package/public/assets/versioned-prompt-editor-Diu8BIaM.js +8 -0
- package/public/assets/vesper-DdrHHSXu.js +2 -0
- package/public/assets/vitesse-black-fwtXNY1n.js +2 -0
- package/public/assets/vitesse-dark-BZCL-v6S.js +2 -0
- package/public/assets/vitesse-light-VbXTXTou.js +2 -0
- package/public/assets/volume-2-XrcRKD9Z.js +2 -0
- package/public/assets/vue-Bfx0ffIq.js +2 -0
- package/public/assets/vue-html-BeluIYX0.js +2 -0
- package/public/assets/vue-vine-DGUAbOCX.js +2 -0
- package/public/assets/warehouse-BanzOanY.js +2 -0
- package/public/assets/wasm-BnjxR4X6.js +2 -0
- package/public/assets/wasm-ByWQv1Qj.js +2 -0
- package/public/assets/waypoints-CIzINtLT.js +2 -0
- package/public/assets/wgsl-BsKzXJz4.js +2 -0
- package/public/assets/wit-DdvCle-K.js +2 -0
- package/public/assets/work-queue-TCjJxeT-.js +2 -0
- package/public/assets/workflow-marker-Cf3Hf2fN.js +2 -0
- package/public/assets/workflows-CXQX20Qj.js +6 -0
- package/public/assets/workspaces-qSYzKYMA.js +3 -0
- package/public/assets/wrench-C7-4CgnY.js +2 -0
- package/public/assets/x-Bge17hWJ.js +2 -0
- package/public/assets/xml-DIqSwXR3.js +2 -0
- package/public/assets/yaml-rwi0_p6S.js +2 -0
- package/public/assets/zap-DGuDTw4w.js +2 -0
- package/public/assets/zoom-in-Bs8Qv8VL.js +2 -0
- package/public/assets/zoomable-image-B3Og53nj.js +2 -0
- package/public/cosmos/audio/README.md +50 -0
- package/public/cosmos/audio/alert.mp3 +0 -0
- package/public/cosmos/audio/ambient-bed.mp3 +0 -0
- package/public/cosmos/audio/comet.mp3 +0 -0
- package/public/cosmos/audio/cross-galaxy.mp3 +0 -0
- package/public/cosmos/audio/deploy.mp3 +0 -0
- package/public/cosmos/audio/galaxy-dissipate.mp3 +0 -0
- package/public/cosmos/audio/galaxy-form.mp3 +0 -0
- package/public/cosmos/audio/land.mp3 +0 -0
- package/public/cosmos/audio/reap.mp3 +0 -0
- package/public/cosmos/audio/spawn.mp3 +0 -0
- package/public/cosmos.html +34 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +10 -0
- package/public/icons/apple-touch-icon.png +0 -0
- package/public/icons/humwick-192.png +0 -0
- package/public/icons/humwick-512.png +0 -0
- package/public/icons/humwick.svg +19 -0
- package/public/index.html +188 -0
- package/public/manifest.webmanifest +45 -0
- package/public/sw.js +160 -0
- package/recipes/code-review.yaml +26 -0
- package/recipes/debug.yaml +20 -0
- package/recipes/feature.yaml +26 -0
- package/recipes/refactor.yaml +20 -0
- package/recipes/test.yaml +20 -0
- package/runner/LICENSE +167 -0
- package/runner/agent-bin/agent-relay-browser +10 -0
- package/runner/agent-bin/humwick +10 -0
- package/runner/hooks/fff-search-redirect/fff-search-redirect.cjs +161 -0
- package/runner/hooks/fff-search-redirect/hooks.json +13 -0
- package/runner/hooks/rg-r-guard/hooks.json +18 -0
- package/runner/hooks/rg-r-guard/rg-r-guard.cjs +144 -0
- package/runner/hooks/web-tools-redirect/hooks.json +13 -0
- package/runner/hooks/web-tools-redirect/web-tools-redirect.cjs +84 -0
- package/runner/plugins/browser/skills/playwright-cli/SKILL.md +53 -0
- package/runner/plugins/claude/.claude-plugin/plugin.json +8 -0
- package/runner/plugins/claude/claude-statusline-probe.cjs +144 -0
- package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +914 -0
- package/runner/plugins/claude/hooks/gate-run-guard.sh +9 -0
- package/runner/plugins/claude/hooks/gate-run-guard.ts +237 -0
- package/runner/plugins/claude/hooks/hooks.json +158 -0
- package/runner/plugins/claude/hooks/kill-guard.provisioned.mjs +7027 -0
- package/runner/plugins/claude/hooks/kill-guard.sh +341 -0
- package/runner/plugins/claude/hooks/kill-guard.ts +337 -0
- package/runner/plugins/claude/hooks/permission-request.sh +22 -0
- package/runner/plugins/claude/hooks/post-compact.sh +10 -0
- package/runner/plugins/claude/hooks/post-tool-use.sh +8 -0
- package/runner/plugins/claude/hooks/pre-compact.sh +12 -0
- package/runner/plugins/claude/hooks/read-only-bash-guard.sh +587 -0
- package/runner/plugins/claude/hooks/relay-status.sh +373 -0
- package/runner/plugins/claude/hooks/session-end.sh +26 -0
- package/runner/plugins/claude/hooks/session-start.sh +31 -0
- package/runner/plugins/claude/hooks/shell-trust.ts +28 -0
- package/runner/plugins/claude/hooks/stop-failure.sh +47 -0
- package/runner/plugins/claude/hooks/stop.sh +48 -0
- package/runner/plugins/claude/hooks/subagent-guard.cjs +26 -0
- package/runner/plugins/claude/hooks/subagent-skill-policy.cjs +160 -0
- package/runner/plugins/claude/hooks/subagent-start.sh +13 -0
- package/runner/plugins/claude/hooks/subagent-stop.sh +19 -0
- package/runner/plugins/claude/hooks/user-prompt-submit.sh +18 -0
- package/runner/plugins/claude/hooks/wait-loop-guard.ts +120 -0
- package/runner/plugins/claude/monitors/monitors.json +7 -0
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +786 -0
- package/runner/plugins/claude/monitors/relay-monitor.ts +58 -0
- package/runner/plugins/claude/skills/disconnect/SKILL.md +16 -0
- package/runner/plugins/claude/skills/label/SKILL.md +23 -0
- package/runner/plugins/claude/skills/message/SKILL.md +40 -0
- package/runner/plugins/claude/skills/pair/SKILL.md +30 -0
- package/runner/plugins/claude/skills/react/SKILL.md +18 -0
- package/runner/plugins/claude/skills/read-message/SKILL.md +24 -0
- package/runner/plugins/claude/skills/reply/SKILL.md +44 -0
- package/runner/plugins/claude/skills/send-claimable/SKILL.md +39 -0
- package/runner/plugins/claude/skills/slice-first/SKILL.md +32 -0
- package/runner/plugins/claude/skills/slice-first/references/plan-recipe.md +46 -0
- package/runner/plugins/claude/skills/status/SKILL.md +16 -0
- package/runner/plugins/claude/skills/tags/SKILL.md +25 -0
- package/runner/plugins/claude/skills/task/SKILL.md +28 -0
- package/runner/plugins/claude-output-style/.claude-plugin/plugin.json +5 -0
- package/runner/plugins/claude-output-style/output-styles/agent-relay.md +5 -0
- package/runner/plugins/codex/skills/disconnect/SKILL.md +15 -0
- package/runner/plugins/codex/skills/guide/SKILL.md +15 -0
- package/runner/plugins/codex/skills/label/SKILL.md +22 -0
- package/runner/plugins/codex/skills/message/SKILL.md +40 -0
- package/runner/plugins/codex/skills/pair/SKILL.md +29 -0
- package/runner/plugins/codex/skills/react/SKILL.md +17 -0
- package/runner/plugins/codex/skills/read-message/SKILL.md +23 -0
- package/runner/plugins/codex/skills/reply/SKILL.md +44 -0
- package/runner/plugins/codex/skills/send-claimable/SKILL.md +39 -0
- package/runner/plugins/codex/skills/slice-first/SKILL.md +32 -0
- package/runner/plugins/codex/skills/slice-first/references/plan-recipe.md +46 -0
- package/runner/plugins/codex/skills/status/SKILL.md +15 -0
- package/runner/plugins/codex/skills/tags/SKILL.md +24 -0
- package/runner/plugins/codex/skills/task/SKILL.md +28 -0
- package/runner/src/adapter.ts +1061 -0
- package/runner/src/adapters/__fixtures__/codex-app-server.ts +47 -0
- package/runner/src/adapters/claude-api.ts +243 -0
- package/runner/src/adapters/claude-context-command.ts +16 -0
- package/runner/src/adapters/claude-credentials.ts +22 -0
- package/runner/src/adapters/claude-delivery-lanes.ts +28 -0
- package/runner/src/adapters/claude-delivery.ts +166 -0
- package/runner/src/adapters/claude-effort.ts +94 -0
- package/runner/src/adapters/claude-exit.ts +290 -0
- package/runner/src/adapters/claude-headless-mirror.ts +88 -0
- package/runner/src/adapters/claude-headless-refusal.ts +51 -0
- package/runner/src/adapters/claude-headless.ts +617 -0
- package/runner/src/adapters/claude-monitor-delivery-plan.ts +317 -0
- package/runner/src/adapters/claude-permission.ts +243 -0
- package/runner/src/adapters/claude-prompt-gates.ts +269 -0
- package/runner/src/adapters/claude-prompt-hook.ts +145 -0
- package/runner/src/adapters/claude-session-capture.ts +293 -0
- package/runner/src/adapters/claude-session-probe.ts +414 -0
- package/runner/src/adapters/claude-shutdown.ts +190 -0
- package/runner/src/adapters/claude-status-detectors.ts +426 -0
- package/runner/src/adapters/claude-subagents.ts +93 -0
- package/runner/src/adapters/claude-submit-confirmation.ts +287 -0
- package/runner/src/adapters/claude-tmux.ts +548 -0
- package/runner/src/adapters/claude-token-usage-capture.ts +253 -0
- package/runner/src/adapters/claude-tool-policy.ts +165 -0
- package/runner/src/adapters/claude-transcript-tail.ts +193 -0
- package/runner/src/adapters/claude-transcript.ts +812 -0
- package/runner/src/adapters/claude.ts +1097 -0
- package/runner/src/adapters/codex-agent-message-capture.ts +189 -0
- package/runner/src/adapters/codex-app-server.ts +70 -0
- package/runner/src/adapters/codex-client.ts +362 -0
- package/runner/src/adapters/codex-effective-config.ts +87 -0
- package/runner/src/adapters/codex-launch-config.ts +153 -0
- package/runner/src/adapters/codex-rollout-context.ts +272 -0
- package/runner/src/adapters/codex-skill-discovery.ts +18 -0
- package/runner/src/adapters/codex-token-usage-capture.ts +163 -0
- package/runner/src/adapters/codex-tool-output.ts +53 -0
- package/runner/src/adapters/codex-turn-outcome.ts +27 -0
- package/runner/src/adapters/codex-version.ts +64 -0
- package/runner/src/adapters/codex.ts +1442 -0
- package/runner/src/adapters/incremental-file.ts +110 -0
- package/runner/src/adapters/opencode-api.ts +71 -0
- package/runner/src/adapters/opencode-context.ts +277 -0
- package/runner/src/adapters/opencode-model-registry.ts +126 -0
- package/runner/src/adapters/opencode-provider.ts +169 -0
- package/runner/src/adapters/opencode-server-retry.ts +50 -0
- package/runner/src/adapters/opencode-soft-steer.ts +302 -0
- package/runner/src/adapters/opencode-spawn.ts +145 -0
- package/runner/src/adapters/opencode-subscribe.ts +62 -0
- package/runner/src/adapters/opencode-token-usage-capture.ts +187 -0
- package/runner/src/adapters/opencode-tool-events.ts +302 -0
- package/runner/src/adapters/opencode-tools-config.ts +17 -0
- package/runner/src/adapters/opencode.ts +2264 -0
- package/runner/src/adapters/project-shell.ts +80 -0
- package/runner/src/adapters/provider-launch-config.ts +21 -0
- package/runner/src/adapters/provider-quota-harvest.ts +127 -0
- package/runner/src/adapters/service-access.ts +153 -0
- package/runner/src/adapters/tmux-offload.ts +207 -0
- package/runner/src/adapters/tool-summary.ts +214 -0
- package/runner/src/agent-redact.ts +75 -0
- package/runner/src/agent-runner/commands/dispatch.ts +185 -0
- package/runner/src/agent-runner/commands/lifecycle.ts +84 -0
- package/runner/src/agent-runner/commands/prompt.ts +284 -0
- package/runner/src/agent-runner/credentials.ts +208 -0
- package/runner/src/agent-runner/http-liveness.ts +85 -0
- package/runner/src/agent-runner/lifecycle-milestones.ts +207 -0
- package/runner/src/agent-runner/lifecycle-phase.ts +80 -0
- package/runner/src/agent-runner/registration.ts +163 -0
- package/runner/src/agent-runner/runner-context.ts +158 -0
- package/runner/src/agent-runner/session-mirror.ts +278 -0
- package/runner/src/agent-runner/task-claims.ts +116 -0
- package/runner/src/agent-runner/token-usage-coverage.ts +54 -0
- package/runner/src/agent-runner/turn-report.ts +269 -0
- package/runner/src/api-budget-accounting.ts +91 -0
- package/runner/src/area-memory.ts +255 -0
- package/runner/src/artifact-path-materialize.ts +52 -0
- package/runner/src/async-guard.ts +35 -0
- package/runner/src/attachment-cache-sweep.ts +31 -0
- package/runner/src/attachment-cache.ts +195 -0
- package/runner/src/background-script-reconcile.ts +106 -0
- package/runner/src/bounded-log.ts +59 -0
- package/runner/src/bounded-spawn-sync.ts +18 -0
- package/runner/src/busy-reconciler.ts +282 -0
- package/runner/src/claim-tracker.ts +140 -0
- package/runner/src/claude-settings.ts +167 -0
- package/runner/src/claude-workspace-trust.ts +289 -0
- package/runner/src/config.ts +547 -0
- package/runner/src/context-boundary.ts +101 -0
- package/runner/src/context-command.ts +151 -0
- package/runner/src/context-probe-state.ts +6 -0
- package/runner/src/continuation-archive.ts +179 -0
- package/runner/src/control-server.ts +730 -0
- package/runner/src/delivery-policy.ts +9 -0
- package/runner/src/delivery-result.ts +24 -0
- package/runner/src/effort-command.ts +125 -0
- package/runner/src/empty-turn.ts +816 -0
- package/runner/src/free-port.ts +25 -0
- package/runner/src/fs-offload.ts +26 -0
- package/runner/src/gate-run-config.ts +35 -0
- package/runner/src/gate-run-lease-probe.ts +38 -0
- package/runner/src/host-asset-plan.ts +450 -0
- package/runner/src/host-claude-config-worker.ts +34 -0
- package/runner/src/host-sources.ts +40 -0
- package/runner/src/inbox-carrier.ts +7 -0
- package/runner/src/index.ts +386 -0
- package/runner/src/launch-assembly.ts +955 -0
- package/runner/src/liveness.ts +105 -0
- package/runner/src/logger.ts +108 -0
- package/runner/src/mcp-outbox.ts +204 -0
- package/runner/src/mcp-preflight.ts +86 -0
- package/runner/src/mcp-source-selection.ts +54 -0
- package/runner/src/mcp-tool-deferral.ts +35 -0
- package/runner/src/mcp-vault-env.ts +25 -0
- package/runner/src/message-body-config/config.ts +16 -0
- package/runner/src/message-body-config.ts +1 -0
- package/runner/src/mid-turn-delivery.ts +43 -0
- package/runner/src/native-memory-lever.ts +329 -0
- package/runner/src/native-self-resume.ts +244 -0
- package/runner/src/outbox.ts +453 -0
- package/runner/src/park-post.ts +17 -0
- package/runner/src/pending-permission-store.ts +148 -0
- package/runner/src/process-meta.ts +9 -0
- package/runner/src/profile-home.ts +419 -0
- package/runner/src/profile-projection.ts +411 -0
- package/runner/src/project-hooks.ts +65 -0
- package/runner/src/project-instructions.ts +119 -0
- package/runner/src/provider-double.ts +59 -0
- package/runner/src/provider-hold.ts +228 -0
- package/runner/src/provider-home-marker.ts +3 -0
- package/runner/src/provider-home-tombstone.ts +4 -0
- package/runner/src/provider-input-queue.ts +134 -0
- package/runner/src/provider-integration.ts +93 -0
- package/runner/src/provider-processes.ts +17 -0
- package/runner/src/provider-submit-failure.ts +15 -0
- package/runner/src/providers.ts +35 -0
- package/runner/src/provisioning.ts +703 -0
- package/runner/src/question-decision.ts +18 -0
- package/runner/src/quota.ts +22 -0
- package/runner/src/rate-limit.ts +351 -0
- package/runner/src/refused-prompt-store.ts +36 -0
- package/runner/src/relay-injection-events.ts +161 -0
- package/runner/src/relay-instructions.ts +107 -0
- package/runner/src/relay-mcp-proxy.long-turn-e2e.ts +183 -0
- package/runner/src/relay-mcp-proxy.ts +704 -0
- package/runner/src/relay-mcp.ts +583 -0
- package/runner/src/reply-obligation-cache.ts +211 -0
- package/runner/src/response-capture-report.ts +111 -0
- package/runner/src/response-routing.ts +6 -0
- package/runner/src/runner-core.ts +2955 -0
- package/runner/src/runner-exit.ts +12 -0
- package/runner/src/runner-helpers.ts +582 -0
- package/runner/src/runner-insights.ts +105 -0
- package/runner/src/runner-mcp-proxy.ts +46 -0
- package/runner/src/runner-spawn-config.ts +68 -0
- package/runner/src/runner-stale-scratch-sweep.ts +21 -0
- package/runner/src/runner.ts +9 -0
- package/runner/src/runtime-token-file.ts +45 -0
- package/runner/src/runtime-token-remint.ts +56 -0
- package/runner/src/service-access.ts +88 -0
- package/runner/src/session-destroy.ts +27 -0
- package/runner/src/session-insights.ts +118 -0
- package/runner/src/session-scratch.ts +296 -0
- package/runner/src/spawn-input.ts +147 -0
- package/runner/src/startup-config-fetch.ts +48 -0
- package/runner/src/subreaper.ts +113 -0
- package/runner/src/template-resolver.ts +29 -0
- package/runner/src/test-run-dedup.ts +435 -0
- package/runner/src/transcript-context.ts +99 -0
- package/runner/src/transport-errors.ts +48 -0
- package/runner/src/turn-opener.ts +55 -0
- package/runner/src/turn-reconcile-event.ts +88 -0
- package/runner/src/vault-proxy-hop.ts +99 -0
- package/runner/src/version.ts +44 -0
- package/runner/src/web-tool-call-proxy.ts +128 -0
- package/runner/src/web-tools.ts +59 -0
- package/runner/test-support/codex-web-config-proof.ts +149 -0
- package/runner/test-support/effective-launch.ts +189 -0
- package/runner/test-support/native-context-sources.ts +77 -0
- package/runner/test-support/provisioning-context.ts +55 -0
- package/runner/test-support/recorder-double.ts +130 -0
- package/runner/test-support/recording-provider.ts +242 -0
- package/runner/test-support/runner-harness.ts +249 -0
- package/runner/test-support/service-access-fixture.ts +12 -0
- package/scripts/agent-vault/bootstrap.py +87 -0
- package/scripts/agent-vault/install.py +200 -0
- package/scripts/agent-vault/install_refresh.py +98 -0
- package/scripts/agent-vault/refresh.py +116 -0
- package/scripts/agent-vault/refresh_provision.py +109 -0
- package/scripts/agent-vault/refresh_store.py +54 -0
- package/scripts/agent-vault/runtime.py +253 -0
- package/scripts/agent-vault/sec-ops-activation.json +23 -0
- package/scripts/artifact-checkout-gc.ts +51 -0
- package/scripts/cd-config.ts +224 -0
- package/scripts/cd-deploy-debounce.ts +94 -0
- package/scripts/cd-deploy-worktree-gc.ts +235 -0
- package/scripts/cd-fleet-membership.ts +48 -0
- package/scripts/cd-host-epochs.ts +101 -0
- package/scripts/cd-hub-wake.ts +40 -0
- package/scripts/cd-land-boundary.ts +49 -0
- package/scripts/cd-watcher.ts +985 -0
- package/scripts/dashboard-build-marker.ts +71 -0
- package/scripts/fleet-sha-coverage.ts +66 -0
- package/scripts/gate-run-lease.ts +329 -0
- package/scripts/gate-slot +9 -0
- package/scripts/gate-slot-fleet-health.ts +60 -0
- package/scripts/gate.sh +6 -0
- package/scripts/install-bin-shim.cjs +149 -0
- package/scripts/orchestrator-spawn-smoke.ts +398 -0
- package/scripts/provisioning-matrix.ts +72 -0
- package/scripts/release-cd-stamp.ts +146 -0
- package/scripts/release-health.ts +133 -0
- package/scripts/release-report.ts +102 -0
- package/scripts/release-upgrade.ts +1296 -0
- package/scripts/steward-policy-cleanup.ts +73 -0
- package/scripts/wait-for-deploy.ts +43 -0
- package/src/agent-authz.ts +219 -0
- package/src/agent-branch-state.ts +45 -0
- package/src/agent-busy-health.ts +158 -0
- package/src/agent-card-projection.ts +86 -0
- package/src/agent-channel-binding.ts +49 -0
- package/src/agent-command-activity.ts +39 -0
- package/src/agent-core.ts +29 -0
- package/src/agent-death-diagnosis.ts +314 -0
- package/src/agent-drive-arbiter.ts +112 -0
- package/src/agent-exit-alarm.ts +10 -0
- package/src/agent-exit-cleanup-async.ts +69 -0
- package/src/agent-exit-cleanup.ts +99 -0
- package/src/agent-exit-report.ts +28 -0
- package/src/agent-issue-repo.ts +53 -0
- package/src/agent-launch-shape-redaction.ts +72 -0
- package/src/agent-lifecycle-events.ts +428 -0
- package/src/agent-principal.ts +14 -0
- package/src/agent-profile-catalog.ts +188 -0
- package/src/agent-ref.ts +340 -0
- package/src/agent-spawn.ts +45 -0
- package/src/agent-status-events.ts +41 -0
- package/src/agent-system-tags.ts +38 -0
- package/src/artifact-media.ts +34 -0
- package/src/artifact-storage.ts +245 -0
- package/src/artifact-text.ts +31 -0
- package/src/async-deadline.ts +59 -0
- package/src/auth/admin-routes.ts +36 -0
- package/src/auth/index.ts +23 -0
- package/src/auth/login-rate-limiter.ts +153 -0
- package/src/auth/password.ts +142 -0
- package/src/auth/provider.ts +60 -0
- package/src/auth/role-scopes.ts +15 -0
- package/src/auth/session-cookie.ts +35 -0
- package/src/auth/session-refresh.ts +61 -0
- package/src/auth/session.test-fixture.ts +10 -0
- package/src/auth/session.ts +107 -0
- package/src/authority-audit.ts +56 -0
- package/src/authz-meta-keys.ts +64 -0
- package/src/auto-spawn-dispatch.ts +154 -0
- package/src/automations/constants.ts +14 -0
- package/src/automations/cron.ts +129 -0
- package/src/automations/crud.ts +129 -0
- package/src/automations/dispatch.ts +299 -0
- package/src/automations/index.ts +5 -0
- package/src/automations/precheck.ts +124 -0
- package/src/automations/reconcile.ts +314 -0
- package/src/automations/storage.ts +200 -0
- package/src/automations/types.ts +16 -0
- package/src/automations/validation.ts +216 -0
- package/src/automations.ts +1 -0
- package/src/branch-landed.ts +761 -0
- package/src/browser-authz.ts +18 -0
- package/src/browser-config.ts +31 -0
- package/src/bundled-workflows.ts +48 -0
- package/src/bus-outbox-async.ts +16 -0
- package/src/bus-outbox.ts +265 -0
- package/src/bus-resume-commands.ts +18 -0
- package/src/bus.ts +792 -0
- package/src/canonical-repo-root.ts +131 -0
- package/src/cd-deploy-notify.ts +59 -0
- package/src/cd-host-deferrals.ts +23 -0
- package/src/cd-host-lag-notify.ts +36 -0
- package/src/cd-repository.ts +49 -0
- package/src/channel-output-control.ts +43 -0
- package/src/channel-output-validation.ts +11 -0
- package/src/channel-policy-health.ts +20 -0
- package/src/channel-target.ts +40 -0
- package/src/channels-host-service.ts +219 -0
- package/src/ci/bun-test-log.ts +41 -0
- package/src/ci/failed-test-files.ts +52 -0
- package/src/ci/gate-history.ts +66 -0
- package/src/ci/github-actions.ts +145 -0
- package/src/ci/incidents.ts +175 -0
- package/src/ci/index.ts +6 -0
- package/src/ci/job-verdict.ts +27 -0
- package/src/ci/land-boundary-ci.ts +104 -0
- package/src/ci/monitor.ts +528 -0
- package/src/ci/not-triggered.ts +116 -0
- package/src/ci/notifications.ts +234 -0
- package/src/ci/provider.ts +87 -0
- package/src/ci/root-missing.ts +35 -0
- package/src/ci/transition-notify.ts +279 -0
- package/src/ci-monitor.ts +7 -0
- package/src/claude-api-budget-async.ts +11 -0
- package/src/claude-api-budget-config.ts +23 -0
- package/src/claude-api-expiry.ts +70 -0
- package/src/claude-api-overflow.ts +146 -0
- package/src/claude-app/config.ts +13 -0
- package/src/claude-app/oauth-http.ts +127 -0
- package/src/claude-app/oauth-provider.ts +136 -0
- package/src/claude-app/passphrase.ts +14 -0
- package/src/claude-app-auth.ts +65 -0
- package/src/claude-app-gateway.ts +68 -0
- package/src/cli/_shared.ts +136 -0
- package/src/cli/agent-detect.ts +175 -0
- package/src/cli/agent-guide.ts +152 -0
- package/src/cli/agent-keep.ts +37 -0
- package/src/cli/agent-meta.ts +111 -0
- package/src/cli/artifact.ts +154 -0
- package/src/cli/browser.ts +56 -0
- package/src/cli/channel-output.ts +17 -0
- package/src/cli/channels-host.ts +13 -0
- package/src/cli/channels.ts +16 -0
- package/src/cli/claude-api-budget.ts +41 -0
- package/src/cli/context-probe.ts +90 -0
- package/src/cli/credentials.ts +1 -0
- package/src/cli/daemon.ts +125 -0
- package/src/cli/dev.ts +185 -0
- package/src/cli/ephemeral.ts +159 -0
- package/src/cli/hosts.ts +41 -0
- package/src/cli/index.ts +442 -0
- package/src/cli/introspect.ts +74 -0
- package/src/cli/maintenance.ts +24 -0
- package/src/cli/merges.ts +78 -0
- package/src/cli/message.ts +229 -0
- package/src/cli/orchestrator.ts +384 -0
- package/src/cli/pair.ts +135 -0
- package/src/cli/profile.ts +35 -0
- package/src/cli/project.ts +52 -0
- package/src/cli/prompts.ts +23 -0
- package/src/cli/provider-enablement.ts +42 -0
- package/src/cli/provider.ts +230 -0
- package/src/cli/recipe.ts +110 -0
- package/src/cli/reply.ts +130 -0
- package/src/cli/roles.ts +60 -0
- package/src/cli/routing-policy.ts +20 -0
- package/src/cli/run.ts +96 -0
- package/src/cli/secrets-backup.ts +85 -0
- package/src/cli/secrets.ts +55 -0
- package/src/cli/service-access.ts +23 -0
- package/src/cli/setup.ts +57 -0
- package/src/cli/shutdown.ts +16 -0
- package/src/cli/spawn.ts +31 -0
- package/src/cli/state.ts +73 -0
- package/src/cli/steward.ts +58 -0
- package/src/cli/task.ts +151 -0
- package/src/cli/team.ts +18 -0
- package/src/cli/timers.ts +53 -0
- package/src/cli/token.ts +72 -0
- package/src/cli/upgrade.ts +200 -0
- package/src/cli/web-tool-calls.ts +17 -0
- package/src/cli/workflow.ts +46 -0
- package/src/cli/workspace.ts +544 -0
- package/src/cli.ts +4 -0
- package/src/codex-reset-consume.ts +274 -0
- package/src/codex-wham-quota.ts +177 -0
- package/src/command-events.ts +69 -0
- package/src/command-secrets.ts +198 -0
- package/src/command-ttl.ts +63 -0
- package/src/commands-db.ts +1196 -0
- package/src/commands-landing-async.ts +166 -0
- package/src/commands-write.ts +160 -0
- package/src/comment-blocks.ts +84 -0
- package/src/compaction-watch.ts +259 -0
- package/src/config-model-selection.ts +14 -0
- package/src/config-store-write.ts +39 -0
- package/src/config-store.ts +1253 -0
- package/src/config.ts +939 -0
- package/src/connector-boot-health.ts +27 -0
- package/src/connector-paths.ts +47 -0
- package/src/connectors.ts +547 -0
- package/src/content-security.ts +12 -0
- package/src/context-advisory.ts +279 -0
- package/src/context-block-rendered-at.ts +33 -0
- package/src/context-injection-channel.ts +49 -0
- package/src/context-router.ts +121 -0
- package/src/context-threshold-config.ts +52 -0
- package/src/continuation-replay-edge.ts +30 -0
- package/src/continuity-working-state.ts +107 -0
- package/src/contracts.ts +109 -0
- package/src/coordinator-authority.ts +100 -0
- package/src/coordinator-digest-config.ts +20 -0
- package/src/cosmos-events.ts +164 -0
- package/src/daemon.ts +570 -0
- package/src/dashboard-notifications.ts +169 -0
- package/src/dashboard-presence.ts +86 -0
- package/src/db/activity-async.ts +58 -0
- package/src/db/activity-read-ops.ts +42 -0
- package/src/db/activity-write-ops.ts +43 -0
- package/src/db/activity.ts +454 -0
- package/src/db/agent-card-read.ts +98 -0
- package/src/db/agent-effort-state.ts +146 -0
- package/src/db/agent-exit-cleanup.ts +94 -0
- package/src/db/agent-exit-report.ts +33 -0
- package/src/db/agent-gate-runs-write-ops.ts +7 -0
- package/src/db/agent-gate-runs.ts +96 -0
- package/src/db/agent-identity-archive.test-fixture.ts +12 -0
- package/src/db/agent-identity-archive.ts +75 -0
- package/src/db/agent-keep.ts +21 -0
- package/src/db/agent-lifecycle-write-ops.ts +92 -0
- package/src/db/agent-meta.ts +74 -0
- package/src/db/agent-outstanding-work.ts +154 -0
- package/src/db/agent-ownership-async.ts +45 -0
- package/src/db/agent-ownership-read-ops.ts +12 -0
- package/src/db/agent-ownership-write-ops.ts +50 -0
- package/src/db/agent-ownership.ts +200 -0
- package/src/db/agent-predicates.ts +64 -0
- package/src/db/agent-read.ts +39 -0
- package/src/db/agent-search-async.ts +21 -0
- package/src/db/agent-search-read-ops.ts +8 -0
- package/src/db/agent-search.ts +162 -0
- package/src/db/agent-shutdown-in-flight.ts +21 -0
- package/src/db/agents-async.ts +333 -0
- package/src/db/agents-prune-write-op.ts +37 -0
- package/src/db/agents-read-ops.ts +33 -0
- package/src/db/agents-write-ops.ts +174 -0
- package/src/db/agents.ts +1284 -0
- package/src/db/api-budget-schema.ts +144 -0
- package/src/db/api-budget-usage-migration.ts +31 -0
- package/src/db/api-budget-usage.ts +40 -0
- package/src/db/artifact-browse.ts +145 -0
- package/src/db/artifact-retention.ts +115 -0
- package/src/db/artifacts-async.ts +81 -0
- package/src/db/artifacts-read-ops.ts +24 -0
- package/src/db/artifacts-write-ops.ts +46 -0
- package/src/db/artifacts.ts +307 -0
- package/src/db/auth-identities.ts +128 -0
- package/src/db/backlog.ts +172 -0
- package/src/db/blackboard.ts +171 -0
- package/src/db/broadcast-scope.ts +63 -0
- package/src/db/broker-memory-migration.ts +71 -0
- package/src/db/browser-sessions.ts +89 -0
- package/src/db/budget-lineage.ts +26 -0
- package/src/db/bus-outbox-read-ops.ts +29 -0
- package/src/db/bus-outbox-write-ops.ts +57 -0
- package/src/db/bus-prune-async.ts +16 -0
- package/src/db/busy-regression.worker.ts +48 -0
- package/src/db/caller-lifecycle.ts +24 -0
- package/src/db/carry-pending-marker.ts +129 -0
- package/src/db/carry-pending.ts +145 -0
- package/src/db/channel-binding-delete.ts +15 -0
- package/src/db/channel-egress-allowlist.ts +200 -0
- package/src/db/channel-identity.ts +38 -0
- package/src/db/channel-runtime.ts +19 -0
- package/src/db/channels-async.ts +55 -0
- package/src/db/channels-read-ops.ts +13 -0
- package/src/db/channels-session-fence.ts +18 -0
- package/src/db/channels-write-ops.ts +42 -0
- package/src/db/channels.ts +654 -0
- package/src/db/checkout-refresh.ts +36 -0
- package/src/db/ci-gate-runs.ts +92 -0
- package/src/db/ci-incidents-async.ts +76 -0
- package/src/db/ci-incidents-read-ops.ts +15 -0
- package/src/db/ci-incidents-write-ops.ts +57 -0
- package/src/db/ci-incidents.ts +667 -0
- package/src/db/ci-sha-observations-schema.ts +33 -0
- package/src/db/claim-sql.ts +44 -0
- package/src/db/claude-api-budget-ops.ts +15 -0
- package/src/db/claude-api-budget-reconcile.ts +39 -0
- package/src/db/claude-api-budget.ts +198 -0
- package/src/db/claude-app-oauth.ts +74 -0
- package/src/db/claude-catalog-reconcile.ts +27 -0
- package/src/db/codex-reset-consumes-async.ts +9 -0
- package/src/db/codex-reset-consumes-read-ops.ts +5 -0
- package/src/db/codex-reset-consumes-write-ops.ts +19 -0
- package/src/db/codex-reset-consumes.ts +122 -0
- package/src/db/command-agent-profiles.ts +58 -0
- package/src/db/command-row.ts +88 -0
- package/src/db/command-terminal-status.ts +33 -0
- package/src/db/command-vault-pruning.ts +30 -0
- package/src/db/commands-cancel-write.ts +45 -0
- package/src/db/commands-claim-admission.ts +144 -0
- package/src/db/commands-effort-read.ts +58 -0
- package/src/db/commands-host-land-admission.ts +106 -0
- package/src/db/commands-kernel-landing.ts +268 -0
- package/src/db/commands-land-occupancy.ts +40 -0
- package/src/db/commands-land-wait.ts +38 -0
- package/src/db/commands-landing-completeness.ts +66 -0
- package/src/db/commands-landing-tuples.ts +22 -0
- package/src/db/commands-landing.ts +1046 -0
- package/src/db/commands-leaseless-sweep.ts +72 -0
- package/src/db/commands-list-query.ts +47 -0
- package/src/db/commands-planner-stats.test-fixture.ts +97 -0
- package/src/db/commands-read-ops.ts +49 -0
- package/src/db/commands-scheduler-recovery.ts +71 -0
- package/src/db/commands-spawn-read.ts +33 -0
- package/src/db/commands-write-ops.ts +286 -0
- package/src/db/config-store-write-ops.ts +58 -0
- package/src/db/connection-instrumentation.ts +76 -0
- package/src/db/connection.ts +532 -0
- package/src/db/context-reset-carry.ts +144 -0
- package/src/db/continuation-archive-purge.ts +48 -0
- package/src/db/continuation-archive-search.ts +249 -0
- package/src/db/continuation-archives-async.ts +59 -0
- package/src/db/continuation-archives-read-ops.ts +10 -0
- package/src/db/continuation-archives-write-ops.ts +15 -0
- package/src/db/continuation-archives.ts +158 -0
- package/src/db/continuations.ts +201 -0
- package/src/db/coordinator-delivery.ts +10 -0
- package/src/db/coordinator-digest-async.ts +13 -0
- package/src/db/coordinator-digest-flush.ts +6 -0
- package/src/db/coordinator-digest.ts +145 -0
- package/src/db/coordinator-seat.ts +130 -0
- package/src/db/crash-reports.ts +115 -0
- package/src/db/db-error-registry.ts +290 -0
- package/src/db/db-reader-worker.ts +91 -0
- package/src/db/db-writer-worker.ts +66 -0
- package/src/db/deferred-lineage-report-queries.ts +28 -0
- package/src/db/deferred-lineage-reports.ts +44 -0
- package/src/db/delivery-async.ts +102 -0
- package/src/db/delivery-expiration-health.ts +97 -0
- package/src/db/delivery-expiration.ts +52 -0
- package/src/db/delivery-quality-async.ts +23 -0
- package/src/db/delivery-quality.ts +116 -0
- package/src/db/delivery-read-ops.ts +26 -0
- package/src/db/delivery-report-echo.ts +53 -0
- package/src/db/delivery-write-ops.ts +72 -0
- package/src/db/delivery.ts +545 -0
- package/src/db/deploy-events-async.ts +66 -0
- package/src/db/deploy-events-read-ops.ts +25 -0
- package/src/db/deploy-events-write-ops.ts +81 -0
- package/src/db/deploy-events.ts +554 -0
- package/src/db/deploy-task-completion.ts +48 -0
- package/src/db/drive-leases.ts +165 -0
- package/src/db/durable-payloads.ts +92 -0
- package/src/db/fixtures/writer-shutdown.ts +28 -0
- package/src/db/fts-query.ts +8 -0
- package/src/db/gate-policy.ts +27 -0
- package/src/db/gate-runs.ts +181 -0
- package/src/db/gateway.ts +513 -0
- package/src/db/generation-launch.ts +82 -0
- package/src/db/guides-search.ts +18 -0
- package/src/db/history-delivery-settle.ts +57 -0
- package/src/db/hosts-async.ts +150 -0
- package/src/db/hosts-backfill.ts +91 -0
- package/src/db/hosts-read-ops.ts +21 -0
- package/src/db/hosts-registration.ts +452 -0
- package/src/db/hosts-views.ts +259 -0
- package/src/db/hosts-write-ops.ts +94 -0
- package/src/db/hosts.ts +251 -0
- package/src/db/human-address.ts +6 -0
- package/src/db/idle-digest.ts +54 -0
- package/src/db/idle-self-care.ts +100 -0
- package/src/db/inbox-async.ts +29 -0
- package/src/db/inbox-read-ops.ts +8 -0
- package/src/db/inbox-write-ops.ts +25 -0
- package/src/db/inbox.ts +197 -0
- package/src/db/index.ts +147 -0
- package/src/db/injection-attempts.ts +41 -0
- package/src/db/insights-read-ops.ts +12 -0
- package/src/db/insights-write-ops.ts +15 -0
- package/src/db/integrations.ts +297 -0
- package/src/db/issue-associations.ts +136 -0
- package/src/db/issue-lifecycle.ts +199 -0
- package/src/db/issue-refs.ts +312 -0
- package/src/db/kernel-event-log-schema.ts +14 -0
- package/src/db/kernel-landing-admission.ts +15 -0
- package/src/db/kernel-landing-command-authority.ts +222 -0
- package/src/db/kernel-persistence-async.ts +48 -0
- package/src/db/kernel-persistence-read-ops.ts +24 -0
- package/src/db/kernel-persistence-write-ops.ts +43 -0
- package/src/db/land-boundaries.ts +138 -0
- package/src/db/land-executing.ts +147 -0
- package/src/db/land-gate-attestations.ts +161 -0
- package/src/db/land-incident-eligibility.ts +44 -0
- package/src/db/land-queue-trace.ts +28 -0
- package/src/db/land-stack.ts +80 -0
- package/src/db/land-telemetry-schema.ts +50 -0
- package/src/db/land-telemetry-write-ops.ts +16 -0
- package/src/db/land-telemetry.ts +114 -0
- package/src/db/land-withdrawal-state.ts +26 -0
- package/src/db/landed-merge-evidence.ts +72 -0
- package/src/db/landed-task-attribution.ts +23 -0
- package/src/db/landing-audit.ts +18 -0
- package/src/db/landing-controllers.ts +67 -0
- package/src/db/landing-ingress-async.ts +102 -0
- package/src/db/landing-ingress-read-ops.ts +8 -0
- package/src/db/landing-ingress-write-ops.ts +42 -0
- package/src/db/landing-ingress.ts +380 -0
- package/src/db/landing-read-ops.ts +18 -0
- package/src/db/landing-receipts.ts +206 -0
- package/src/db/landing-schema.ts +479 -0
- package/src/db/launch-facts.ts +60 -0
- package/src/db/legacy-landing-publication.ts +103 -0
- package/src/db/legacy-message-backfill.ts +90 -0
- package/src/db/lifecycle-seq.ts +127 -0
- package/src/db/lifecycle-write-window.ts +216 -0
- package/src/db/lineage-pair-dedupe-sql.ts +80 -0
- package/src/db/lineage-startup-captures.ts +43 -0
- package/src/db/long-running-work-async.ts +12 -0
- package/src/db/long-running-work-write-ops.ts +8 -0
- package/src/db/long-running-work.ts +78 -0
- package/src/db/macos-privacy.ts +72 -0
- package/src/db/main-connection-writes.ts +44 -0
- package/src/db/main-thread-state.ts +38 -0
- package/src/db/maintenance-health.ts +560 -0
- package/src/db/maintenance-indexes.ts +37 -0
- package/src/db/maintenance-job-runs-async.ts +15 -0
- package/src/db/maintenance-job-runs.ts +62 -0
- package/src/db/maintenance-job-write-ops.ts +10 -0
- package/src/db/maintenance-pauses.ts +224 -0
- package/src/db/maintenance-worker.ts +111 -0
- package/src/db/mappers.ts +422 -0
- package/src/db/memory-preamble-read.ts +13 -0
- package/src/db/memory-resolve.ts +209 -0
- package/src/db/memory-retirement-plan.ts +31 -0
- package/src/db/memory-retirement.ts +15 -0
- package/src/db/merge-drain-async.ts +8 -0
- package/src/db/merge-lease.ts +225 -0
- package/src/db/message-claims.ts +16 -0
- package/src/db/message-queries.ts +16 -0
- package/src/db/message-read-mark.ts +32 -0
- package/src/db/message-reads-async.ts +46 -0
- package/src/db/message-reads-read-ops.ts +35 -0
- package/src/db/message-reads-write-ops.ts +24 -0
- package/src/db/message-reads.ts +553 -0
- package/src/db/message-send-suppression.ts +58 -0
- package/src/db/message-send.ts +76 -0
- package/src/db/message-thread.ts +15 -0
- package/src/db/message-write-ops.ts +96 -0
- package/src/db/messages-async.ts +31 -0
- package/src/db/messages-read-ops.ts +14 -0
- package/src/db/messages.ts +1201 -0
- package/src/db/migration-marker.ts +13 -0
- package/src/db/migrations.ts +1761 -0
- package/src/db/notification-subscriptions.ts +282 -0
- package/src/db/on-loop-write-transaction.ts +34 -0
- package/src/db/operator-actions-schema.ts +10 -0
- package/src/db/operator-decisions.ts +180 -0
- package/src/db/orchestrators-async.ts +57 -0
- package/src/db/orchestrators-read-ops.ts +7 -0
- package/src/db/orchestrators-write-ops.ts +52 -0
- package/src/db/orchestrators.ts +485 -0
- package/src/db/outbox-codec.ts +16 -0
- package/src/db/pair-maintenance.ts +9 -0
- package/src/db/pairs-async.ts +10 -0
- package/src/db/pairs-write-ops.ts +8 -0
- package/src/db/pairs.ts +264 -0
- package/src/db/pending-relay-runs.ts +73 -0
- package/src/db/plan-blurb-migration.ts +158 -0
- package/src/db/plan-ci-blocks.ts +28 -0
- package/src/db/plan-completion.ts +55 -0
- package/src/db/plan-conformance.ts +104 -0
- package/src/db/plan-dispatch-schema.ts +17 -0
- package/src/db/plan-dispatch-store.ts +112 -0
- package/src/db/plan-external.ts +79 -0
- package/src/db/plan-graph-async.ts +23 -0
- package/src/db/plan-graph-read-ops.ts +16 -0
- package/src/db/plan-graph-write-ops.ts +37 -0
- package/src/db/plan-graph.ts +403 -0
- package/src/db/plan-import-schema.ts +19 -0
- package/src/db/plan-import.ts +211 -0
- package/src/db/plan-task-create.ts +44 -0
- package/src/db/planner-stats.ts +123 -0
- package/src/db/plans-async.ts +22 -0
- package/src/db/plans-read-ops.ts +11 -0
- package/src/db/plans-write-ops.ts +9 -0
- package/src/db/plans.ts +385 -0
- package/src/db/policy-runtime.ts +14 -0
- package/src/db/presence-buffer.ts +111 -0
- package/src/db/presence-loop.ts +58 -0
- package/src/db/presence-write-ops.ts +28 -0
- package/src/db/presence-write.ts +38 -0
- package/src/db/profile-artifact-backfill-migration.ts +32 -0
- package/src/db/profile-host-sources-migration.ts +150 -0
- package/src/db/profile-legacy-project-instructions-migration.ts +41 -0
- package/src/db/profile-name-case-migration.ts +46 -0
- package/src/db/profile-override-migration.ts +200 -0
- package/src/db/profile-project-ack-migration.ts +55 -0
- package/src/db/profile-slim-migration.ts +125 -0
- package/src/db/profile-slim-plan.ts +190 -0
- package/src/db/project-entries-async.ts +18 -0
- package/src/db/project-entries-read-ops.ts +28 -0
- package/src/db/project-entries.ts +514 -0
- package/src/db/project-entry-search.ts +63 -0
- package/src/db/project-identity.ts +16 -0
- package/src/db/project-journal.ts +256 -0
- package/src/db/project-lifecycle.ts +80 -0
- package/src/db/project-memory-group-migration.ts +76 -0
- package/src/db/project-phantom-merge.ts +228 -0
- package/src/db/project-root-schema.ts +30 -0
- package/src/db/project-trust-approvals.ts +98 -0
- package/src/db/projects-async.ts +50 -0
- package/src/db/projects-read-ops.ts +59 -0
- package/src/db/projects-write-ops.ts +74 -0
- package/src/db/projects.ts +629 -0
- package/src/db/prompt-templates-schema.ts +23 -0
- package/src/db/prompt-templates.ts +138 -0
- package/src/db/prompt-version-migration.ts +115 -0
- package/src/db/prompt-versions-ops.ts +16 -0
- package/src/db/prompt-versions.ts +167 -0
- package/src/db/provider-catalog-async.ts +31 -0
- package/src/db/provider-catalog-read-ops.ts +8 -0
- package/src/db/provider-catalog-write-ops.ts +16 -0
- package/src/db/provider-config-history.ts +49 -0
- package/src/db/provider-config-secrets.ts +117 -0
- package/src/db/provider-flap-messages-async.ts +6 -0
- package/src/db/provider-flap-messages-write-ops.ts +12 -0
- package/src/db/provider-flap-messages.ts +19 -0
- package/src/db/provider-quota-advisory-types.ts +39 -0
- package/src/db/provider-quota-burn.ts +322 -0
- package/src/db/provider-quota-effect-occurrences.ts +13 -0
- package/src/db/provider-quota-lease-write-ops.ts +23 -0
- package/src/db/provider-quota-leases-async.ts +35 -0
- package/src/db/provider-quota-leases.ts +174 -0
- package/src/db/provider-quota-matrix-cache.ts +85 -0
- package/src/db/provider-quota-matrix-fetch.ts +85 -0
- package/src/db/provider-quota-merge.ts +90 -0
- package/src/db/provider-quota-notification-write.ts +98 -0
- package/src/db/provider-quota-prune.ts +35 -0
- package/src/db/provider-quota-read-async.ts +77 -0
- package/src/db/provider-quota-read-ops.ts +56 -0
- package/src/db/provider-quota-recipients.ts +38 -0
- package/src/db/provider-quota-state.ts +139 -0
- package/src/db/provider-quota-visibility.ts +22 -0
- package/src/db/provider-quota-write-ops.ts +69 -0
- package/src/db/provider-quotas.ts +726 -0
- package/src/db/provider-secret-scrub.ts +64 -0
- package/src/db/provisioning-assets-migration.ts +85 -0
- package/src/db/provisioning-async.ts +116 -0
- package/src/db/provisioning-callmux.ts +184 -0
- package/src/db/provisioning-github-api.ts +16 -0
- package/src/db/provisioning-github-import-target.ts +29 -0
- package/src/db/provisioning-mcp-validation.ts +103 -0
- package/src/db/provisioning-read-ops.ts +23 -0
- package/src/db/provisioning-registry.ts +1122 -0
- package/src/db/provisioning-resolve.ts +321 -0
- package/src/db/provisioning-write-ops.ts +43 -0
- package/src/db/qa-skill-seed.ts +27 -0
- package/src/db/quota-lease-busy.worker.ts +62 -0
- package/src/db/read-op-marshaling-fixtures.ts +724 -0
- package/src/db/read-ops.ts +266 -0
- package/src/db/reader-pool.ts +252 -0
- package/src/db/recipe-read-ops.ts +15 -0
- package/src/db/recipe-write-ops.ts +68 -0
- package/src/db/reply-obligations.ts +265 -0
- package/src/db/report-suppression.ts +126 -0
- package/src/db/restart-epoch-admin-async.ts +14 -0
- package/src/db/restart-epoch-admin.ts +67 -0
- package/src/db/restart-epoch-admission.ts +114 -0
- package/src/db/restart-epoch-async.ts +80 -0
- package/src/db/restart-epoch-begin.ts +177 -0
- package/src/db/restart-epoch-read-ops.ts +40 -0
- package/src/db/restart-epoch-release.ts +248 -0
- package/src/db/restart-epoch-write-ops.ts +117 -0
- package/src/db/restart-epoch.test-fixture.ts +37 -0
- package/src/db/restart-epoch.ts +623 -0
- package/src/db/restart-sensitive.ts +81 -0
- package/src/db/retention.ts +423 -0
- package/src/db/review-ref-cleanups.ts +306 -0
- package/src/db/routing-policy.ts +64 -0
- package/src/db/scheduled-timer-projection.ts +123 -0
- package/src/db/scheduled-timer-quota-chaos-worker.ts +84 -0
- package/src/db/scheduled-timers.ts +521 -0
- package/src/db/schema.ts +1472 -0
- package/src/db/scoped-message-reads.ts +31 -0
- package/src/db/secrets-broker-async.ts +31 -0
- package/src/db/secrets-broker-read-ops.ts +8 -0
- package/src/db/secrets-broker-write-ops.ts +23 -0
- package/src/db/secrets-broker.ts +221 -0
- package/src/db/secrets-vault-import.ts +37 -0
- package/src/db/secrets-vault-key-lifecycle.ts +64 -0
- package/src/db/secrets-vault-metadata.ts +37 -0
- package/src/db/secrets-vault-migration.ts +53 -0
- package/src/db/secrets-vault-write.ts +97 -0
- package/src/db/secrets-vault.ts +69 -0
- package/src/db/self-effort-permissions.ts +50 -0
- package/src/db/service-access-read-ops.ts +10 -0
- package/src/db/services-async.ts +29 -0
- package/src/db/services-read-ops.ts +134 -0
- package/src/db/services-write-ops.ts +71 -0
- package/src/db/shared.ts +7 -0
- package/src/db/shipping-board-async.ts +6 -0
- package/src/db/shipping-board.ts +319 -0
- package/src/db/shipping-stats-async.ts +24 -0
- package/src/db/shipping-stats-histogram.ts +45 -0
- package/src/db/shipping-stats-schema.ts +8 -0
- package/src/db/shipping-stats.ts +265 -0
- package/src/db/snapshot-ledger-prune.ts +53 -0
- package/src/db/spawn-budget.ts +145 -0
- package/src/db/spawn-command-busy.worker.ts +71 -0
- package/src/db/spawn-delegation-state.ts +124 -0
- package/src/db/spawn-delegation.ts +114 -0
- package/src/db/spawn-host-cap.ts +29 -0
- package/src/db/spawn-in-flight-count.ts +37 -0
- package/src/db/spawn-in-flight.ts +15 -0
- package/src/db/spawn-process-evidence.ts +68 -0
- package/src/db/spawn-project-target.ts +57 -0
- package/src/db/spawn-registration-confirmed-backfill.ts +71 -0
- package/src/db/spawn-request-id-migration.ts +36 -0
- package/src/db/spawn-request-id.ts +35 -0
- package/src/db/spawn-request-reuse.ts +28 -0
- package/src/db/spawn-tree-cleanup.ts +89 -0
- package/src/db/spawn-tree.ts +29 -0
- package/src/db/sql-audit-indexes.ts +77 -0
- package/src/db/stalled-message-delivery.ts +52 -0
- package/src/db/startup-write-lock.ts +36 -0
- package/src/db/stats-async.ts +235 -0
- package/src/db/stats-health-read-model.ts +225 -0
- package/src/db/stats-read-ops.ts +25 -0
- package/src/db/stats.ts +633 -0
- package/src/db/target-host.ts +25 -0
- package/src/db/task-async.ts +165 -0
- package/src/db/task-comment-through-async.ts +14 -0
- package/src/db/task-comment-through-read-ops.ts +4 -0
- package/src/db/task-comment-through-write-ops.ts +7 -0
- package/src/db/task-comment-through.ts +123 -0
- package/src/db/task-core.ts +44 -0
- package/src/db/task-deliverables-async.ts +60 -0
- package/src/db/task-deliverables-read-ops.ts +4 -0
- package/src/db/task-deliverables-write-ops.ts +37 -0
- package/src/db/task-deliverables.ts +387 -0
- package/src/db/task-dependency-cycle.worker.ts +43 -0
- package/src/db/task-detail-projection.ts +91 -0
- package/src/db/task-entity-async.ts +252 -0
- package/src/db/task-entity-read-ops.ts +51 -0
- package/src/db/task-entity-write-ops.ts +197 -0
- package/src/db/task-entity.ts +1143 -0
- package/src/db/task-execution.ts +20 -0
- package/src/db/task-issue-relation.ts +23 -0
- package/src/db/task-issue-source.ts +46 -0
- package/src/db/task-land-withdraw-async.ts +10 -0
- package/src/db/task-land-withdraw-read-ops.ts +13 -0
- package/src/db/task-land-withdraw-read.ts +47 -0
- package/src/db/task-land-withdraw-write-ops.ts +31 -0
- package/src/db/task-land-withdraw.ts +423 -0
- package/src/db/task-list.ts +113 -0
- package/src/db/task-maintenance.ts +25 -0
- package/src/db/task-memory-async.ts +19 -0
- package/src/db/task-memory-read-ops.ts +9 -0
- package/src/db/task-memory-write-ops.ts +23 -0
- package/src/db/task-memory.ts +170 -0
- package/src/db/task-owner.ts +126 -0
- package/src/db/task-read-ops.ts +44 -0
- package/src/db/task-review-hold-async.ts +20 -0
- package/src/db/task-review-hold-delivery.ts +58 -0
- package/src/db/task-review-hold-write-ops.ts +26 -0
- package/src/db/task-review-hold.ts +615 -0
- package/src/db/task-state-reconcile-async.ts +12 -0
- package/src/db/task-state-reconcile.ts +163 -0
- package/src/db/task-validation.ts +71 -0
- package/src/db/task-write-ops.ts +145 -0
- package/src/db/tasks.ts +528 -0
- package/src/db/team-epics.ts +37 -0
- package/src/db/team-idle.ts +35 -0
- package/src/db/team-project.ts +69 -0
- package/src/db/team-spawn-delegation-read.ts +36 -0
- package/src/db/team-spawn-delegation.ts +49 -0
- package/src/db/teams-async.ts +58 -0
- package/src/db/teams-read-ops.ts +27 -0
- package/src/db/teams-write-ops.ts +17 -0
- package/src/db/teams.ts +504 -0
- package/src/db/terminal-events.ts +113 -0
- package/src/db/timer-async.ts +523 -0
- package/src/db/timer-cutover-core.ts +545 -0
- package/src/db/timer-cutover-report-read-ops.ts +211 -0
- package/src/db/timer-cutover-write-ops.ts +275 -0
- package/src/db/timer-ingress-payload.ts +160 -0
- package/src/db/timer-ingress-read-ops.ts +39 -0
- package/src/db/timer-ingress-request-keys.ts +282 -0
- package/src/db/timer-ingress-write-ops.ts +145 -0
- package/src/db/timer-ingress.ts +515 -0
- package/src/db/timer-message-cutover.ts +322 -0
- package/src/db/timer-message-fire-deadletter.ts +136 -0
- package/src/db/timer-message-fire.ts +209 -0
- package/src/db/timer-message-flip-chaos-worker.ts +89 -0
- package/src/db/timer-message-flip-consistency.ts +147 -0
- package/src/db/timer-message-flip.ts +676 -0
- package/src/db/timer-message-participant.ts +138 -0
- package/src/db/timer-migrate-input.ts +92 -0
- package/src/db/timer-operator-async.ts +177 -0
- package/src/db/timer-operator-read-ops.ts +21 -0
- package/src/db/timer-operator-stage10.ts +119 -0
- package/src/db/timer-operator-write-ops.ts +102 -0
- package/src/db/timer-operator.ts +393 -0
- package/src/db/timer-read-ops.ts +147 -0
- package/src/db/timer-transfer-errors.ts +10 -0
- package/src/db/timer-transfer-write-ops.ts +39 -0
- package/src/db/timer-verifier-schema.ts +29 -0
- package/src/db/timer-verifier.ts +379 -0
- package/src/db/timer-write-ops.ts +277 -0
- package/src/db/token-db-write-ops.ts +92 -0
- package/src/db/token-standing.ts +84 -0
- package/src/db/token-usage-read-ops.ts +8 -0
- package/src/db/token-usage-write-ops.ts +35 -0
- package/src/db/token-usage.ts +338 -0
- package/src/db/tool-groups-empty-migration.ts +59 -0
- package/src/db/urgent-intake.ts +30 -0
- package/src/db/user-invites.ts +107 -0
- package/src/db/user-settings-read-ops.ts +19 -0
- package/src/db/user-settings-write-ops.ts +42 -0
- package/src/db/users.ts +173 -0
- package/src/db/vault-backup.ts +59 -0
- package/src/db/vault-expiry.ts +71 -0
- package/src/db/vault-health.ts +99 -0
- package/src/db/vault-token-launch.ts +77 -0
- package/src/db/vault-token-ops.ts +14 -0
- package/src/db/vault-tokens.ts +101 -0
- package/src/db/viewer-scope.ts +117 -0
- package/src/db/wal-checkpointer-worker.ts +48 -0
- package/src/db/wal-checkpointer.ts +107 -0
- package/src/db/wal-index.ts +53 -0
- package/src/db/wal-live-log.ts +68 -0
- package/src/db/wal-reclaim-async.ts +45 -0
- package/src/db/wal-reclaim.ts +140 -0
- package/src/db/web-tool-call-ops.ts +15 -0
- package/src/db/web-tool-calls.ts +87 -0
- package/src/db/withdraw-land-error.ts +18 -0
- package/src/db/work-evidence.ts +43 -0
- package/src/db/workflow-budget.ts +44 -0
- package/src/db/workflow-contact.ts +173 -0
- package/src/db/workflow-execution.ts +10 -0
- package/src/db/workflow-journal-async.ts +64 -0
- package/src/db/workflow-journal-prune.ts +71 -0
- package/src/db/workflow-journal-read-ops.ts +21 -0
- package/src/db/workflow-journal-schema.ts +9 -0
- package/src/db/workflow-journal-write-ops.ts +33 -0
- package/src/db/workflow-journal.ts +324 -0
- package/src/db/workflow-spawn-quota.ts +57 -0
- package/src/db/workflow-stats-read-ops.ts +177 -0
- package/src/db/workflow-step-tasks.ts +74 -0
- package/src/db/workflow-task-read-ops.ts +187 -0
- package/src/db/workspace-land-state.ts +65 -0
- package/src/db/workspace-merge-hold.ts +28 -0
- package/src/db/workspace-persistence.ts +77 -0
- package/src/db/workspace-rekey-write-ops.ts +11 -0
- package/src/db/workspace-rekey.ts +148 -0
- package/src/db/workspace-steward-cancel.ts +85 -0
- package/src/db/workspace-steward-fence.ts +30 -0
- package/src/db/workspace-write-ops.ts +26 -0
- package/src/db/workspace-writes-async.ts +46 -0
- package/src/db/workspace-writes.ts +196 -0
- package/src/db/workspaces.ts +617 -0
- package/src/db/write-ops.ts +594 -0
- package/src/db/writer-drain.ts +27 -0
- package/src/db/writer-fault-seam.ts +101 -0
- package/src/db/writer-gateway.ts +840 -0
- package/src/db/writer-ledger.ts +56 -0
- package/src/db.ts +4 -0
- package/src/deploy-executor.ts +139 -0
- package/src/dev.ts +541 -0
- package/src/dispatch-availability.ts +55 -0
- package/src/dispatch-match.ts +21 -0
- package/src/ephemeral/host-landing-slots-proof-worker.ts +45 -0
- package/src/ephemeral/index.ts +58 -0
- package/src/ephemeral/instance.ts +832 -0
- package/src/ephemeral/landing-ingress-proof-worker.ts +30 -0
- package/src/ephemeral/ports.ts +40 -0
- package/src/ephemeral/profile.ts +323 -0
- package/src/ephemeral/rehearsal-stamps.ts +149 -0
- package/src/ephemeral/rehearsal.ts +213 -0
- package/src/ephemeral/snapshot-kernel.ts +41 -0
- package/src/ephemeral/snapshot.ts +753 -0
- package/src/ephemeral/sweep.ts +40 -0
- package/src/event-loop-lag.ts +584 -0
- package/src/event-loop-yield.ts +27 -0
- package/src/events.ts +140 -0
- package/src/execution-mode.ts +63 -0
- package/src/fleet-convergence.ts +123 -0
- package/src/gate-resolver.ts +151 -0
- package/src/gate-run-capture.ts +71 -0
- package/src/git-sha.ts +58 -0
- package/src/guides.ts +73 -0
- package/src/host/capacity.ts +79 -0
- package/src/host/gate-capacity-alert.ts +90 -0
- package/src/host/identity.ts +128 -0
- package/src/host/presentation.ts +234 -0
- package/src/host/pressure-details.ts +89 -0
- package/src/host/pressure.ts +216 -0
- package/src/host/state.ts +132 -0
- package/src/host/utilization-buffer.ts +71 -0
- package/src/host-awareness.ts +57 -0
- package/src/host-identity.ts +39 -0
- package/src/host-provider-enablement.ts +75 -0
- package/src/host-provider-store.ts +102 -0
- package/src/host-tools.ts +39 -0
- package/src/http-body.ts +49 -0
- package/src/http-compression.ts +16 -0
- package/src/hub-metrics-report.ts +67 -0
- package/src/hub-metrics.ts +166 -0
- package/src/idle-digest-config.ts +40 -0
- package/src/idle-self-care.ts +58 -0
- package/src/index.ts +35 -0
- package/src/injection-sweep-pacing.ts +24 -0
- package/src/insights-async.ts +16 -0
- package/src/insights-db.ts +188 -0
- package/src/internal-spawn-model.ts +18 -0
- package/src/invite-link.ts +11 -0
- package/src/issue-tracker/adapter.ts +58 -0
- package/src/issue-tracker/github.ts +342 -0
- package/src/issue-tracker/index.ts +12 -0
- package/src/issue-tracker/registry.ts +26 -0
- package/src/journal-preamble.ts +350 -0
- package/src/kernel-correlation.ts +28 -0
- package/src/land-authorization.ts +73 -0
- package/src/land-block.ts +59 -0
- package/src/land-failure-event-key.ts +8 -0
- package/src/land-gate-failure-routing.ts +69 -0
- package/src/land-gate-notifications.ts +173 -0
- package/src/land-gate-queue-progress.ts +54 -0
- package/src/land-gates-source.ts +126 -0
- package/src/land-gates.ts +29 -0
- package/src/land-lifecycle-drives.ts +15 -0
- package/src/land-settle-dispatch.ts +134 -0
- package/src/land-stack-depth.ts +11 -0
- package/src/land-telemetry.ts +40 -0
- package/src/landing-base-ref.ts +3 -0
- package/src/landing-host-safety.ts +38 -0
- package/src/landing-lane-key.ts +74 -0
- package/src/launch-mode-support.ts +11 -0
- package/src/launch-mode.ts +58 -0
- package/src/legacy-landing.test-fixture.ts +56 -0
- package/src/lifecycle-context-pressure.ts +134 -0
- package/src/lifecycle-manager.ts +831 -0
- package/src/lifecycle-signal-registry.ts +87 -0
- package/src/live-runs-section.ts +38 -0
- package/src/loop-block-attribution.ts +442 -0
- package/src/loop-block-regression-alarm.ts +156 -0
- package/src/loop-task-stamps.ts +62 -0
- package/src/main-thread-clock.ts +95 -0
- package/src/maintenance/agent-identity-archive-job.ts +15 -0
- package/src/maintenance/artifact-checkout-gc-job.ts +14 -0
- package/src/maintenance/artifact-checkout-gc.ts +33 -0
- package/src/maintenance/artifact-sweep.ts +23 -0
- package/src/maintenance/breaker.ts +200 -0
- package/src/maintenance/cd-watcher.ts +176 -0
- package/src/maintenance/checkout-refresh.ts +83 -0
- package/src/maintenance/ci-monitor.ts +14 -0
- package/src/maintenance/claude-api-budget-job.ts +16 -0
- package/src/maintenance/constants.ts +134 -0
- package/src/maintenance/continuation-archive-search-job.ts +27 -0
- package/src/maintenance/critical-job-watchdog.ts +252 -0
- package/src/maintenance/db-planner-stats-job.ts +19 -0
- package/src/maintenance/db-wal-reclaim-job.ts +82 -0
- package/src/maintenance/events.ts +28 -0
- package/src/maintenance/gate-capacity-sample-job.ts +16 -0
- package/src/maintenance/index.ts +10 -0
- package/src/maintenance/jobs.ts +857 -0
- package/src/maintenance/long-running-work.ts +41 -0
- package/src/maintenance/orphaned-sessions.ts +126 -0
- package/src/maintenance/pauses.ts +174 -0
- package/src/maintenance/provider-secret-scrub-job.ts +16 -0
- package/src/maintenance/queued-message-expiration.ts +23 -0
- package/src/maintenance/red-main-controller-job.ts +22 -0
- package/src/maintenance/reply-obligation-parity-job.ts +27 -0
- package/src/maintenance/restart-epoch-hold-health.ts +62 -0
- package/src/maintenance/run-registry.ts +194 -0
- package/src/maintenance/scheduler.ts +778 -0
- package/src/maintenance/service-access-job.ts +31 -0
- package/src/maintenance/shipping-stats-rollup-job.ts +23 -0
- package/src/maintenance/snapshot-ledger-prune-job.ts +17 -0
- package/src/maintenance/stalled-delivery-pager.ts +125 -0
- package/src/maintenance/steward-handback.ts +11 -0
- package/src/maintenance/task-state-reconcile-job.ts +22 -0
- package/src/maintenance/teams.ts +426 -0
- package/src/maintenance/types.ts +124 -0
- package/src/maintenance/vault-backup-job.ts +34 -0
- package/src/maintenance/vault-expiry-job.ts +29 -0
- package/src/maintenance/web-tool-call-prune-job.ts +13 -0
- package/src/maintenance/workflow-backstop-job.ts +12 -0
- package/src/maintenance/workflow-journal-prune-job.ts +29 -0
- package/src/maintenance/workspaces.ts +784 -0
- package/src/maintenance.ts +1 -0
- package/src/managed-policy-escalation.ts +123 -0
- package/src/managed-policy.ts +236 -0
- package/src/managed-provider-gate.ts +27 -0
- package/src/managed-spawn-reservation.ts +23 -0
- package/src/markdown-frontmatter.ts +14 -0
- package/src/mcp/activity-output.ts +57 -0
- package/src/mcp/artifact-upload.ts +53 -0
- package/src/mcp/context-output.ts +73 -0
- package/src/mcp/dispatch-shape-schema.ts +13 -0
- package/src/mcp/guides.ts +33 -0
- package/src/mcp/http.ts +27 -0
- package/src/mcp/identity-projection.ts +121 -0
- package/src/mcp/identity.ts +235 -0
- package/src/mcp/index.ts +355 -0
- package/src/mcp/issue-selector-schema.ts +12 -0
- package/src/mcp/message-read.ts +87 -0
- package/src/mcp/output-descriptions.ts +72 -0
- package/src/mcp/output-levels.ts +71 -0
- package/src/mcp/output-projection.ts +88 -0
- package/src/mcp/plan-presentation.ts +166 -0
- package/src/mcp/presentation-values.ts +11 -0
- package/src/mcp/presentation.ts +653 -0
- package/src/mcp/project-entry-search-schema.ts +7 -0
- package/src/mcp/prompts.ts +36 -0
- package/src/mcp/runtime-output.ts +84 -0
- package/src/mcp/schema-budget.ts +28 -0
- package/src/mcp/self-care-visibility.ts +23 -0
- package/src/mcp/service-access.ts +18 -0
- package/src/mcp/spawn-targets-matrix.ts +68 -0
- package/src/mcp/spawn-targets.test-fixture.ts +21 -0
- package/src/mcp/task-validation-presentation.ts +19 -0
- package/src/mcp/tool-generation.ts +8 -0
- package/src/mcp/tool-groups.ts +136 -0
- package/src/mcp/tools-artifacts.ts +82 -0
- package/src/mcp/tools-messaging.ts +541 -0
- package/src/mcp/tools-permission.ts +88 -0
- package/src/mcp/tools-run.ts +73 -0
- package/src/mcp/tools-scheduler.ts +156 -0
- package/src/mcp/tools-spawn.ts +418 -0
- package/src/mcp/tools-web-tool-calls.ts +28 -0
- package/src/mcp/tools-workspace.ts +260 -0
- package/src/mcp/types.ts +68 -0
- package/src/mcp/validation.ts +66 -0
- package/src/mcp-agent-action.ts +96 -0
- package/src/mcp-agent-keep.ts +37 -0
- package/src/mcp-backlog-tools.ts +137 -0
- package/src/mcp-blackboard-tools.ts +131 -0
- package/src/mcp-channel-binding-tools.ts +27 -0
- package/src/mcp-claude-api-budget.ts +35 -0
- package/src/mcp-codex-reset.ts +43 -0
- package/src/mcp-decision-tools.ts +96 -0
- package/src/mcp-dispatch-tools.ts +142 -0
- package/src/mcp-egress-tools.ts +168 -0
- package/src/mcp-errors.ts +7 -0
- package/src/mcp-host-tools.ts +61 -0
- package/src/mcp-interrupt-agent.ts +47 -0
- package/src/mcp-issue-activity-tools.ts +114 -0
- package/src/mcp-issue-association-tools.ts +103 -0
- package/src/mcp-issue-lifecycle-tools.ts +49 -0
- package/src/mcp-issue-ref-tools.ts +214 -0
- package/src/mcp-maintenance-tools.ts +113 -0
- package/src/mcp-merge-tools.ts +66 -0
- package/src/mcp-notification-subscription-tools.ts +297 -0
- package/src/mcp-output-levels.test-fixture.ts +95 -0
- package/src/mcp-plan-tools.ts +347 -0
- package/src/mcp-profile-trust-tools.ts +47 -0
- package/src/mcp-project-journal-tools.ts +160 -0
- package/src/mcp-project-tools.ts +420 -0
- package/src/mcp-provider-catalog.ts +9 -0
- package/src/mcp-release-tools.ts +33 -0
- package/src/mcp-routing-policy-tools.ts +35 -0
- package/src/mcp-secrets-tools.ts +87 -0
- package/src/mcp-task-deliver-tools.ts +166 -0
- package/src/mcp-task-land-withdraw-tools.ts +49 -0
- package/src/mcp-task-memory-tools.ts +119 -0
- package/src/mcp-task-review-hold-tools.ts +57 -0
- package/src/mcp-task-tools.ts +342 -0
- package/src/mcp-task-validation-tools.ts +30 -0
- package/src/mcp-task-weave-tools.ts +63 -0
- package/src/mcp-team-template-tools.ts +141 -0
- package/src/mcp-team-tools.ts +197 -0
- package/src/mcp-tool-allowlist.ts +88 -0
- package/src/mcp-workflow-tools.ts +158 -0
- package/src/mcp.ts +1 -0
- package/src/memory-admission-rank.ts +55 -0
- package/src/memory-area-injection.ts +216 -0
- package/src/memory-boundary-capture.ts +206 -0
- package/src/memory-entry-safety.ts +28 -0
- package/src/memory-inject-filter.ts +190 -0
- package/src/memory-injection.ts +954 -0
- package/src/memory-preamble-admission.ts +158 -0
- package/src/memory-preamble-async.ts +13 -0
- package/src/memory-preamble.ts +1142 -0
- package/src/memory-retirement.ts +11 -0
- package/src/memory-seam.ts +273 -0
- package/src/memory-tokens.ts +91 -0
- package/src/merge-drain.ts +101 -0
- package/src/merge-pause-maintenance.ts +19 -0
- package/src/message-visibility.ts +50 -0
- package/src/messaging-policy.ts +94 -0
- package/src/native-memory.ts +59 -0
- package/src/notification-prefs.ts +25 -0
- package/src/notification-role-policy.ts +14 -0
- package/src/notification-router.ts +329 -0
- package/src/notification-rules.ts +390 -0
- package/src/notification-settings-store.ts +47 -0
- package/src/notification-settings.ts +166 -0
- package/src/notification-subscription-rules.ts +11 -0
- package/src/notification-types.ts +312 -0
- package/src/notify.ts +55 -0
- package/src/observed-reset-boundary.ts +23 -0
- package/src/observed-reset-refresh.ts +21 -0
- package/src/operator-alarm.ts +41 -0
- package/src/operator-decisions-section.ts +29 -0
- package/src/orchestrator-lookup.ts +29 -0
- package/src/orchestrator-self-report-authz.ts +34 -0
- package/src/package-pack.ts +246 -0
- package/src/post-bind-startup.ts +58 -0
- package/src/process-guard.ts +31 -0
- package/src/profile-cells.ts +305 -0
- package/src/profile-effective-survey.ts +121 -0
- package/src/profile-host-sources.ts +73 -0
- package/src/profile-instruction-blocks.ts +11 -0
- package/src/profile-project-validation.ts +23 -0
- package/src/profile-provider-options.ts +59 -0
- package/src/profile-role-templates.ts +26 -0
- package/src/profile-workflow-policy.ts +14 -0
- package/src/project-claudemd-sweep.ts +176 -0
- package/src/project-file-source.ts +54 -0
- package/src/project-ingest.ts +195 -0
- package/src/project-instructions.ts +700 -0
- package/src/project-mcp.ts +18 -0
- package/src/project-role-assets.ts +109 -0
- package/src/project-roles-validate.ts +53 -0
- package/src/project-roles.ts +364 -0
- package/src/project-settings.ts +5 -0
- package/src/project-skills.ts +5 -0
- package/src/project-toolchain.ts +58 -0
- package/src/project-trust-fingerprint.ts +174 -0
- package/src/project-trust.ts +262 -0
- package/src/project-workflows.ts +185 -0
- package/src/prompt-resolver.ts +697 -0
- package/src/prompt-template-fallback.ts +15 -0
- package/src/prompt-version-errors.ts +13 -0
- package/src/provider-catalog-store.ts +393 -0
- package/src/provider-config-store.ts +308 -0
- package/src/provider-enablement.ts +36 -0
- package/src/provider-home-gc.ts +58 -0
- package/src/provider-model-seed.ts +18 -0
- package/src/provider-quota-async.ts +152 -0
- package/src/provider-quota-config-store.ts +60 -0
- package/src/provider-secrets.test-fixture.ts +34 -0
- package/src/provider-state.ts +93 -0
- package/src/provider-timeline-event.ts +76 -0
- package/src/quota-advisory.ts +359 -0
- package/src/quota-band-delivery.ts +13 -0
- package/src/quota-band-projection.ts +149 -0
- package/src/quota-band-transition.ts +316 -0
- package/src/quota-pacing.ts +46 -0
- package/src/quota-threshold-config.ts +46 -0
- package/src/quota.ts +61 -0
- package/src/ratchet-files.ts +202 -0
- package/src/recipe-async.ts +50 -0
- package/src/recipe-db.ts +260 -0
- package/src/recipe-loader.ts +132 -0
- package/src/recipe-runner.ts +259 -0
- package/src/recipe-validator.ts +107 -0
- package/src/relay-event-drain.ts +39 -0
- package/src/relay-host-locality.ts +20 -0
- package/src/relay-run-lease.ts +17 -0
- package/src/repo-config-source.ts +209 -0
- package/src/report-verbosity.ts +5 -0
- package/src/restart-epoch-deploy-hold.ts +221 -0
- package/src/retention-config-store.ts +32 -0
- package/src/retention-config.ts +70 -0
- package/src/reviewer-pipeline.ts +238 -0
- package/src/routes/_route.ts +70 -0
- package/src/routes/_shared.ts +513 -0
- package/src/routes/activity.ts +70 -0
- package/src/routes/agent-effort.ts +69 -0
- package/src/routes/agent-keep.ts +27 -0
- package/src/routes/agent-profiles.ts +169 -0
- package/src/routes/agent-reporting.ts +19 -0
- package/src/routes/agent-self-effort.ts +51 -0
- package/src/routes/agent-sessions.ts +650 -0
- package/src/routes/agent-sharing.ts +23 -0
- package/src/routes/agents-spawn.ts +258 -0
- package/src/routes/agents.ts +452 -0
- package/src/routes/artifacts.ts +340 -0
- package/src/routes/auth.ts +123 -0
- package/src/routes/authority-audit.ts +14 -0
- package/src/routes/automations.ts +83 -0
- package/src/routes/backlog.ts +112 -0
- package/src/routes/blackboard.ts +54 -0
- package/src/routes/browser.ts +73 -0
- package/src/routes/channel-binding-delete.ts +21 -0
- package/src/routes/channel-output.ts +17 -0
- package/src/routes/claude-api-budget.ts +65 -0
- package/src/routes/commands-listing.ts +50 -0
- package/src/routes/commands.ts +586 -0
- package/src/routes/config.ts +75 -0
- package/src/routes/connectors.ts +108 -0
- package/src/routes/context-resets.ts +35 -0
- package/src/routes/continuation-archives.ts +37 -0
- package/src/routes/coordinator-teams.ts +14 -0
- package/src/routes/decisions.ts +82 -0
- package/src/routes/deploys.ts +15 -0
- package/src/routes/fleet-convergence.ts +5 -0
- package/src/routes/guides.ts +13 -0
- package/src/routes/host-providers.ts +73 -0
- package/src/routes/host-resource-control.ts +45 -0
- package/src/routes/host-secret-files.ts +38 -0
- package/src/routes/hosts.ts +240 -0
- package/src/routes/inbox.ts +151 -0
- package/src/routes/index.ts +614 -0
- package/src/routes/insights.ts +86 -0
- package/src/routes/integrations.ts +636 -0
- package/src/routes/issue-activity.ts +78 -0
- package/src/routes/issue-associations.ts +76 -0
- package/src/routes/issue-lifecycle.ts +34 -0
- package/src/routes/issue-refs.ts +163 -0
- package/src/routes/kernel-timers-operator.ts +57 -0
- package/src/routes/memory.ts +25 -0
- package/src/routes/merges.ts +109 -0
- package/src/routes/message-viewer.ts +4 -0
- package/src/routes/messages.ts +621 -0
- package/src/routes/notifications.ts +36 -0
- package/src/routes/orchestrator-bootstrap.ts +101 -0
- package/src/routes/orchestrator-host-registration.ts +125 -0
- package/src/routes/orchestrator-host-utilization.ts +255 -0
- package/src/routes/orchestrator-proxy.ts +174 -0
- package/src/routes/orchestrator.ts +699 -0
- package/src/routes/pairs.ts +214 -0
- package/src/routes/plans.ts +98 -0
- package/src/routes/project-onboarding.ts +56 -0
- package/src/routes/project-trust.ts +73 -0
- package/src/routes/projects.ts +296 -0
- package/src/routes/prompt-templates.ts +106 -0
- package/src/routes/provider-config.ts +232 -0
- package/src/routes/provider-quota-config.ts +48 -0
- package/src/routes/provider-quotas.ts +312 -0
- package/src/routes/provisioning-registry.ts +192 -0
- package/src/routes/recipes.ts +129 -0
- package/src/routes/restart-epochs.ts +98 -0
- package/src/routes/routing-policy.ts +30 -0
- package/src/routes/run-command.ts +25 -0
- package/src/routes/secrets-backup.ts +53 -0
- package/src/routes/secrets-import.ts +32 -0
- package/src/routes/secrets-page.ts +98 -0
- package/src/routes/secrets-vault.ts +121 -0
- package/src/routes/secrets.ts +165 -0
- package/src/routes/service-access.ts +57 -0
- package/src/routes/settings.ts +191 -0
- package/src/routes/shipping.ts +56 -0
- package/src/routes/spawn-policy.ts +299 -0
- package/src/routes/spec.ts +58 -0
- package/src/routes/sse.ts +18 -0
- package/src/routes/stats.ts +159 -0
- package/src/routes/steward.ts +49 -0
- package/src/routes/task-graph.ts +30 -0
- package/src/routes/task-http.ts +27 -0
- package/src/routes/task-issues.ts +81 -0
- package/src/routes/task-reads.ts +139 -0
- package/src/routes/task-validation.ts +22 -0
- package/src/routes/tasks.ts +553 -0
- package/src/routes/team-templates.ts +65 -0
- package/src/routes/teams-config.ts +15 -0
- package/src/routes/teams.ts +141 -0
- package/src/routes/token-usage.ts +167 -0
- package/src/routes/tokens.ts +393 -0
- package/src/routes/users.ts +248 -0
- package/src/routes/viewer-read.ts +27 -0
- package/src/routes/web-tool-calls.ts +38 -0
- package/src/routes/workflows.ts +142 -0
- package/src/routes/workspaces.ts +642 -0
- package/src/routes.ts +3 -0
- package/src/routing-eligibility.ts +42 -0
- package/src/routing-model-aliases.ts +21 -0
- package/src/routing-policy-normalize.ts +412 -0
- package/src/routing-policy-push.ts +154 -0
- package/src/routing-policy-service.ts +19 -0
- package/src/routing-policy-store.ts +16 -0
- package/src/routing-preferences.ts +82 -0
- package/src/routing-step-defaults.ts +31 -0
- package/src/runtime-grant.ts +164 -0
- package/src/runtime-prefix.ts +8 -0
- package/src/runtime-tokens.ts +395 -0
- package/src/search-query.ts +10 -0
- package/src/secret-keys.ts +24 -0
- package/src/secrets-broker/config.ts +53 -0
- package/src/secrets-broker/error.ts +8 -0
- package/src/secrets-broker/infisical.ts +259 -0
- package/src/secrets-broker/reader-infisical.ts +41 -0
- package/src/secrets-broker/reader.ts +101 -0
- package/src/secrets-broker/service.ts +242 -0
- package/src/secrets-broker/tool-credentials.ts +9 -0
- package/src/secrets-vault/backend-conformance.ts +26 -0
- package/src/secrets-vault/backend.ts +102 -0
- package/src/secrets-vault/backup.ts +103 -0
- package/src/secrets-vault/config.ts +7 -0
- package/src/secrets-vault/expiry.ts +10 -0
- package/src/secrets-vault/identity.ts +27 -0
- package/src/secrets-vault/import.ts +90 -0
- package/src/secrets-vault/key-files.ts +91 -0
- package/src/secrets-vault/keys.ts +79 -0
- package/src/secrets-vault/metadata.ts +51 -0
- package/src/secrets-vault/provider-env.ts +25 -0
- package/src/secrets-vault/resolve.ts +101 -0
- package/src/secrets-vault/restore.ts +57 -0
- package/src/secrets-vault/scope.ts +25 -0
- package/src/secrets-vault/service.ts +192 -0
- package/src/secrets-vault/shared.ts +10 -0
- package/src/security.ts +772 -0
- package/src/self-effort-rule.ts +36 -0
- package/src/self-resume-carry.ts +131 -0
- package/src/self-resume-command.ts +24 -0
- package/src/self-resume-config.ts +54 -0
- package/src/self-resume-identity.ts +40 -0
- package/src/self-resume-supersede.ts +19 -0
- package/src/self-resume.ts +884 -0
- package/src/server-background.ts +24 -0
- package/src/server.ts +726 -0
- package/src/service-access/config.ts +3 -0
- package/src/service-access/health.ts +151 -0
- package/src/service-access/host-health.ts +62 -0
- package/src/service-access/host.ts +63 -0
- package/src/service-access/operations.ts +21 -0
- package/src/service-access/policy.ts +30 -0
- package/src/service-access/probe.ts +62 -0
- package/src/service-access/request-logs.ts +93 -0
- package/src/service-access/scope.ts +7 -0
- package/src/service-access/service.ts +185 -0
- package/src/service-access/tokens.ts +174 -0
- package/src/service-access/validation.ts +75 -0
- package/src/service-access/vault.ts +59 -0
- package/src/services/agent-action.ts +238 -0
- package/src/services/agent-control-host.ts +74 -0
- package/src/services/agent-effort.ts +60 -0
- package/src/services/agent-hang-watchdog-job.ts +513 -0
- package/src/services/agent-hang-watchdog-wiring.ts +487 -0
- package/src/services/agent-hang-watchdog.ts +395 -0
- package/src/services/agent-keep.ts +72 -0
- package/src/services/agent-message-fanout.ts +91 -0
- package/src/services/agent-outstanding-work.ts +66 -0
- package/src/services/agent-reporting.ts +27 -0
- package/src/services/artifact-access.ts +39 -0
- package/src/services/artifact-multipart.ts +70 -0
- package/src/services/artifact-path-host.ts +55 -0
- package/src/services/artifact-read.ts +88 -0
- package/src/services/artifact-write.ts +25 -0
- package/src/services/auth-context.ts +117 -0
- package/src/services/automatic-disposal.ts +33 -0
- package/src/services/backlog.ts +149 -0
- package/src/services/browser-inventory.ts +52 -0
- package/src/services/browser-session-state.ts +41 -0
- package/src/services/browser-session.ts +167 -0
- package/src/services/bus-command-update.ts +84 -0
- package/src/services/bus-expiration-death.ts +57 -0
- package/src/services/canonical-root.ts +284 -0
- package/src/services/captured-delivery-gate.ts +12 -0
- package/src/services/carry-pending.ts +5 -0
- package/src/services/channel-binding-delete.ts +20 -0
- package/src/services/channel-suppression-receipt.ts +28 -0
- package/src/services/checkout-refresh-state.ts +18 -0
- package/src/services/checkout-refresh.test-fixture.ts +12 -0
- package/src/services/child-work-evidence.ts +77 -0
- package/src/services/claude-app.ts +91 -0
- package/src/services/command-claim-rejection.ts +23 -0
- package/src/services/command-host.ts +42 -0
- package/src/services/coordinator-digest.ts +30 -0
- package/src/services/decision-audience.ts +26 -0
- package/src/services/delivery-gate-policy.ts +98 -0
- package/src/services/delivery-tip.test-fixture.ts +25 -0
- package/src/services/diff-gate.ts +110 -0
- package/src/services/dispatch-command.ts +388 -0
- package/src/services/effective-launch-driver.ts +240 -0
- package/src/services/effective-launch-harness.ts +257 -0
- package/src/services/errors.ts +31 -0
- package/src/services/git-process.ts +176 -0
- package/src/services/git-remote.ts +100 -0
- package/src/services/harness-subagents.ts +12 -0
- package/src/services/host-changed-paths.test-fixture.ts +134 -0
- package/src/services/host-landing-placement.ts +65 -0
- package/src/services/host-project-readiness.ts +49 -0
- package/src/services/host-providers.ts +114 -0
- package/src/services/host-utilization.ts +70 -0
- package/src/services/idle-agent-digest.ts +318 -0
- package/src/services/idle-epics.ts +105 -0
- package/src/services/idle-self-care.ts +47 -0
- package/src/services/interrupt-agent.ts +146 -0
- package/src/services/isolated-workspace-placement.ts +258 -0
- package/src/services/issue-activity.ts +23 -0
- package/src/services/issue-associations.ts +72 -0
- package/src/services/issue-completion-drain.ts +24 -0
- package/src/services/issue-completion.ts +24 -0
- package/src/services/issue-lifecycle-comments.ts +12 -0
- package/src/services/issue-lifecycle.ts +620 -0
- package/src/services/issue-refs.ts +65 -0
- package/src/services/kernel-command-bridge.ts +219 -0
- package/src/services/kernel-control-host.ts +78 -0
- package/src/services/kernel-control-http.ts +119 -0
- package/src/services/kernel-control-mcp.ts +193 -0
- package/src/services/kernel-landing-command.ts +28 -0
- package/src/services/kernel-landing-ingress.ts +65 -0
- package/src/services/kernel-timers-authoritative-ingress.ts +48 -0
- package/src/services/kernel-timers-authz.ts +229 -0
- package/src/services/kernel-timers-command.ts +182 -0
- package/src/services/kernel-timers-cutover-chaos-worker.ts +209 -0
- package/src/services/kernel-timers-cutover.ts +39 -0
- package/src/services/kernel-timers-ingress-apply.ts +39 -0
- package/src/services/kernel-timers-ingress-chaos-worker.ts +312 -0
- package/src/services/kernel-timers-ingress.ts +702 -0
- package/src/services/kernel-timers-message-authority-chaos-worker.ts +97 -0
- package/src/services/kernel-timers-migrate.ts +158 -0
- package/src/services/kernel-timers-operator-chaos-worker.ts +259 -0
- package/src/services/kernel-timers-operator-preflight.ts +18 -0
- package/src/services/kernel-timers-operator.ts +424 -0
- package/src/services/kernel-timers-outbox.ts +138 -0
- package/src/services/kernel-timers-request-key-migration-chaos-worker.ts +77 -0
- package/src/services/kernel-timers-stage10-operator.ts +210 -0
- package/src/services/kernel-timers-stage10.ts +85 -0
- package/src/services/kernel-timers-stage9.ts +57 -0
- package/src/services/kernel-timers-transfer-chaos-worker.ts +118 -0
- package/src/services/kernel-timers-transfer.ts +525 -0
- package/src/services/kernel-timers-verifier.ts +39 -0
- package/src/services/kernel-timers.ts +247 -0
- package/src/services/land-boundary-settle.ts +32 -0
- package/src/services/land-gate-placement.ts +60 -0
- package/src/services/land-issue-relations.ts +46 -0
- package/src/services/land-restart-retry.ts +77 -0
- package/src/services/landed-ancestry.ts +417 -0
- package/src/services/landed-task-attribution.ts +14 -0
- package/src/services/landing-terminal-report.ts +119 -0
- package/src/services/legacy-landing-backfill.ts +159 -0
- package/src/services/legacy-landing-host.ts +291 -0
- package/src/services/legacy-landing-publication.ts +35 -0
- package/src/services/legacy-landing-recovery.ts +111 -0
- package/src/services/macos-privacy.ts +34 -0
- package/src/services/managed-running.ts +189 -0
- package/src/services/memory-areas.ts +172 -0
- package/src/services/memory-identity.ts +162 -0
- package/src/services/merge-pause.ts +37 -0
- package/src/services/message-delivery-actions.ts +11 -0
- package/src/services/operator-decisions.ts +260 -0
- package/src/services/orchestrator-landing-recovery.ts +55 -0
- package/src/services/orchestrator-registration-recovery.ts +12 -0
- package/src/services/orchestrator-upgrade.ts +83 -0
- package/src/services/parity-harness.ts +141 -0
- package/src/services/permission-decision.ts +129 -0
- package/src/services/plan-ci-recovery.ts +76 -0
- package/src/services/plan-conformance.ts +105 -0
- package/src/services/plan-dispatch-policy.ts +79 -0
- package/src/services/plan-dispatch.ts +372 -0
- package/src/services/plan-external-view.ts +7 -0
- package/src/services/plan-frontier.ts +34 -0
- package/src/services/plan-graph.ts +703 -0
- package/src/services/plan-import.ts +66 -0
- package/src/services/plan-live-bindings.ts +139 -0
- package/src/services/plan-task-create.ts +80 -0
- package/src/services/plan-terminal-refs.ts +83 -0
- package/src/services/plan-wake-digest.ts +88 -0
- package/src/services/plan-wake-policy.ts +12 -0
- package/src/services/profile-trust.ts +54 -0
- package/src/services/project-checkout-binding.ts +65 -0
- package/src/services/project-checkout-enrollment.ts +27 -0
- package/src/services/project-cwd.ts +41 -0
- package/src/services/project-onboarding.ts +165 -0
- package/src/services/project-repo-create.ts +27 -0
- package/src/services/project-role.test-fixture.ts +74 -0
- package/src/services/project.ts +191 -0
- package/src/services/prompt-templates.ts +46 -0
- package/src/services/prompt-version-writer.ts +42 -0
- package/src/services/provider-catalog.ts +7 -0
- package/src/services/provider-hold-flap.ts +130 -0
- package/src/services/provider-hold-nudge.ts +191 -0
- package/src/services/provider-hold-record.ts +137 -0
- package/src/services/provider-state-audit.ts +214 -0
- package/src/services/provisioning-harness/analyze.ts +285 -0
- package/src/services/provisioning-harness/budget-baseline.json +391 -0
- package/src/services/provisioning-harness/capture.ts +197 -0
- package/src/services/provisioning-harness/config.ts +55 -0
- package/src/services/provisioning-harness/context-sources.ts +85 -0
- package/src/services/provisioning-harness/driver.ts +156 -0
- package/src/services/provisioning-harness/expect.ts +81 -0
- package/src/services/provisioning-harness/fixture-matrix.ts +38 -0
- package/src/services/provisioning-harness/fixtures/project/.agent-relay/land-gates.json +1 -0
- package/src/services/provisioning-harness/fixtures/project/.claude/skills/harness-skill/SKILL.md +6 -0
- package/src/services/provisioning-harness/fixtures/project/.mcp.json +1 -0
- package/src/services/provisioning-harness/fixtures/project/AGENTS.md +1 -0
- package/src/services/provisioning-harness/fixtures/project/CLAUDE.md +1 -0
- package/src/services/provisioning-harness/fixtures/project/mcp-server.ts +33 -0
- package/src/services/provisioning-harness/fixtures/provider-home/AGENTS.md +1 -0
- package/src/services/provisioning-harness/fixtures/provider-home/CLAUDE.md +1 -0
- package/src/services/provisioning-harness/host-fixtures.ts +25 -0
- package/src/services/provisioning-harness/index.ts +4 -0
- package/src/services/provisioning-harness/inventory-types.ts +8 -0
- package/src/services/provisioning-harness/inventory.ts +33 -0
- package/src/services/provisioning-harness/lifecycle.ts +193 -0
- package/src/services/provisioning-harness/live.ts +61 -0
- package/src/services/provisioning-harness/manifests.ts +118 -0
- package/src/services/provisioning-harness/prepare.ts +113 -0
- package/src/services/provisioning-harness/redundancy-report.ts +32 -0
- package/src/services/provisioning-harness/redundancy.ts +137 -0
- package/src/services/provisioning-harness/report.ts +129 -0
- package/src/services/provisioning-harness/types.ts +174 -0
- package/src/services/provisioning-registry.ts +15 -0
- package/src/services/rate-limit-resume.ts +220 -0
- package/src/services/reap-verdict.ts +52 -0
- package/src/services/red-main-auto-revert.ts +103 -0
- package/src/services/red-main-controller-deps.ts +80 -0
- package/src/services/red-main-controller.ts +262 -0
- package/src/services/red-main-incident.ts +44 -0
- package/src/services/red-main-quarantine.ts +92 -0
- package/src/services/register-agent.ts +447 -0
- package/src/services/release-readiness.ts +219 -0
- package/src/services/reported-exit-death.ts +29 -0
- package/src/services/response-report-admission.ts +17 -0
- package/src/services/restart-epoch-driver.ts +290 -0
- package/src/services/restart-epoch-reconciler.ts +326 -0
- package/src/services/restart-epoch-stale-command-recovery.ts +133 -0
- package/src/services/restart-instructions.ts +86 -0
- package/src/services/restart-project-role.ts +74 -0
- package/src/services/restart-secret-refs.ts +14 -0
- package/src/services/restart-spawn.ts +510 -0
- package/src/services/review-publish.ts +76 -0
- package/src/services/review-ref-reconcile.ts +337 -0
- package/src/services/run-command-input.ts +22 -0
- package/src/services/run-command.ts +510 -0
- package/src/services/run-completion-message.ts +70 -0
- package/src/services/run-log.ts +212 -0
- package/src/services/run-queue.ts +63 -0
- package/src/services/scheduler-command-cancel.ts +49 -0
- package/src/services/scheduler-command.ts +590 -0
- package/src/services/scheduler-create.ts +25 -0
- package/src/services/scheduler-plan.ts +202 -0
- package/src/services/scheduler-recovery.ts +31 -0
- package/src/services/scheduler.ts +406 -0
- package/src/services/self-care-state.ts +10 -0
- package/src/services/self-effort-grant.ts +93 -0
- package/src/services/self-effort.ts +152 -0
- package/src/services/send-message.ts +548 -0
- package/src/services/server-credentials.ts +53 -0
- package/src/services/shipping-board.ts +111 -0
- package/src/services/shipping-project-config.ts +86 -0
- package/src/services/shipping-stats.ts +86 -0
- package/src/services/shutdown-agent.ts +463 -0
- package/src/services/shutdown-watchdog.ts +119 -0
- package/src/services/spawn-agent.ts +967 -0
- package/src/services/spawn-approval.ts +26 -0
- package/src/services/spawn-brief.ts +39 -0
- package/src/services/spawn-chain-inspector.ts +286 -0
- package/src/services/spawn-cwd-placement.ts +250 -0
- package/src/services/spawn-identity.ts +127 -0
- package/src/services/spawn-instruction-inputs.ts +114 -0
- package/src/services/spawn-issue-activity.ts +102 -0
- package/src/services/spawn-launch-decision.ts +32 -0
- package/src/services/spawn-launch.ts +116 -0
- package/src/services/spawn-loss-escalation.ts +253 -0
- package/src/services/spawn-placement.test-fixture.ts +30 -0
- package/src/services/spawn-project-role.ts +133 -0
- package/src/services/spawn-registration-watchdog.ts +352 -0
- package/src/services/spawn-replay.ts +61 -0
- package/src/services/spawn-repo-probe.ts +118 -0
- package/src/services/spawn-tree-cleanup.ts +37 -0
- package/src/services/spawn-warnings.ts +13 -0
- package/src/services/steward-reaper.ts +244 -0
- package/src/services/task-context-hydration.ts +413 -0
- package/src/services/task-delivery-close.ts +26 -0
- package/src/services/task-delivery-event.ts +11 -0
- package/src/services/task-delivery-land-policy.ts +35 -0
- package/src/services/task-delivery-summary.ts +38 -0
- package/src/services/task-delivery.ts +762 -0
- package/src/services/task-dependency-graph.ts +116 -0
- package/src/services/task-dispatch.ts +178 -0
- package/src/services/task-done-when.ts +45 -0
- package/src/services/task-entity.ts +758 -0
- package/src/services/task-from-issue.ts +80 -0
- package/src/services/task-issue-digest.ts +45 -0
- package/src/services/task-issue-link.ts +158 -0
- package/src/services/task-issue-weave.ts +570 -0
- package/src/services/task-land-withdraw.ts +462 -0
- package/src/services/task-lifecycle.ts +668 -0
- package/src/services/task-nothing-to-land.ts +37 -0
- package/src/services/task-plan-view.ts +172 -0
- package/src/services/task-review-hold.ts +244 -0
- package/src/services/task-semantic-events.ts +396 -0
- package/src/services/task-stage-wake.ts +15 -0
- package/src/services/task-state-reconcile.ts +169 -0
- package/src/services/task-validation.ts +42 -0
- package/src/services/team-claim-race.worker.ts +72 -0
- package/src/services/team-spawn-delegation.ts +30 -0
- package/src/services/team-templates.ts +172 -0
- package/src/services/team.ts +320 -0
- package/src/services/terminal-provider-exit.ts +57 -0
- package/src/services/timer-message-fire-chaos-worker.ts +29 -0
- package/src/services/timer-message-fire-outbox.ts +184 -0
- package/src/services/transient-agent-reaper.ts +412 -0
- package/src/services/turn-reconcile-audit.ts +236 -0
- package/src/services/user-onboarding.ts +98 -0
- package/src/services/user-profile.ts +57 -0
- package/src/services/web-search.ts +83 -0
- package/src/services/web-tool-calls.ts +65 -0
- package/src/services/withdraw-steward.ts +57 -0
- package/src/services/work-finality.ts +34 -0
- package/src/services/workflow-agent-obligation.ts +35 -0
- package/src/services/workflow-auto-start.ts +126 -0
- package/src/services/workflow-backstop.ts +38 -0
- package/src/services/workflow-budget.test-fixture.ts +10 -0
- package/src/services/workflow-caller.ts +32 -0
- package/src/services/workflow-catalog.ts +43 -0
- package/src/services/workflow-child-proof-worker.ts +76 -0
- package/src/services/workflow-child.test-fixture.ts +51 -0
- package/src/services/workflow-ci-main.ts +93 -0
- package/src/services/workflow-control-proof-worker.ts +149 -0
- package/src/services/workflow-coordinator-decision-proof-worker.ts +123 -0
- package/src/services/workflow-coordinator.ts +8 -0
- package/src/services/workflow-decision-answer.ts +18 -0
- package/src/services/workflow-decision-proof-worker.ts +92 -0
- package/src/services/workflow-decision.test-fixture.ts +58 -0
- package/src/services/workflow-decisions.ts +107 -0
- package/src/services/workflow-deferred-cleanup.ts +40 -0
- package/src/services/workflow-dispatch-result.ts +20 -0
- package/src/services/workflow-effect-driver.ts +165 -0
- package/src/services/workflow-effects.ts +544 -0
- package/src/services/workflow-foreach-proof-worker.ts +209 -0
- package/src/services/workflow-land-notice.ts +18 -0
- package/src/services/workflow-land.ts +105 -0
- package/src/services/workflow-notice.ts +32 -0
- package/src/services/workflow-overdue-notice.ts +19 -0
- package/src/services/workflow-pins.ts +162 -0
- package/src/services/workflow-reconcile.ts +210 -0
- package/src/services/workflow-resume-pins.ts +30 -0
- package/src/services/workflow-run-chaos-worker.ts +67 -0
- package/src/services/workflow-run-view.ts +71 -0
- package/src/services/workflow-run.test-fixture.ts +165 -0
- package/src/services/workflow-runs.ts +370 -0
- package/src/services/workflow-scripts.ts +100 -0
- package/src/services/workflow-slice-proof.ts +14 -0
- package/src/services/workflow-stats.ts +208 -0
- package/src/services/workflow-supervisor-state.ts +117 -0
- package/src/services/workflow-supervisor.ts +122 -0
- package/src/services/workflow-verification.ts +68 -0
- package/src/services/workspace-recovery.ts +168 -0
- package/src/services/workspace-task-binding.ts +22 -0
- package/src/settings/modules.ts +149 -0
- package/src/settings/registry.ts +629 -0
- package/src/settings/user-settings.ts +81 -0
- package/src/setup.ts +199 -0
- package/src/shared-mcp.ts +87 -0
- package/src/shutdown.ts +33 -0
- package/src/spawn-admission.ts +48 -0
- package/src/spawn-budget.ts +18 -0
- package/src/spawn-command.ts +373 -0
- package/src/spawn-env.ts +27 -0
- package/src/spawn-intent.ts +17 -0
- package/src/spawn-lease.ts +41 -0
- package/src/spawn-models.ts +22 -0
- package/src/spawn-placement.ts +348 -0
- package/src/spawn-profile-errors.ts +9 -0
- package/src/spawn-quota.ts +15 -0
- package/src/spawn-targets.ts +679 -0
- package/src/sse-agent-status.ts +35 -0
- package/src/sse-outbox-cursor.ts +28 -0
- package/src/sse-visibility.ts +36 -0
- package/src/sse.ts +684 -0
- package/src/startup-context-injection.ts +518 -0
- package/src/startup-timing.ts +30 -0
- package/src/state-adoption/artifacts.ts +27 -0
- package/src/state-adoption/cd-cutover.ts +125 -0
- package/src/state-adoption/files.ts +31 -0
- package/src/state-adoption/index.ts +166 -0
- package/src/state-adoption/linux-plan.ts +67 -0
- package/src/state-adoption/linux.ts +104 -0
- package/src/state-adoption/macos-plan.ts +58 -0
- package/src/state-adoption/macos.ts +112 -0
- package/src/steward-claim-release.ts +38 -0
- package/src/steward-handoff.ts +28 -0
- package/src/steward-identity.ts +54 -0
- package/src/steward-policy-cleanup.ts +222 -0
- package/src/steward-policy-model-reconcile.ts +33 -0
- package/src/steward-policy-name.ts +13 -0
- package/src/steward-provenance.ts +59 -0
- package/src/steward-queue.ts +85 -0
- package/src/steward-slot-recovery.ts +34 -0
- package/src/steward-spawn-guard.ts +344 -0
- package/src/steward.test-fixture.ts +11 -0
- package/src/steward.ts +199 -0
- package/src/stream-proxy.ts +84 -0
- package/src/task-authz.ts +40 -0
- package/src/task-completion.ts +39 -0
- package/src/task-dispatch-live.ts +70 -0
- package/src/task-dispatcher-ranking.ts +136 -0
- package/src/task-dispatcher.ts +841 -0
- package/src/task-owner-notices.ts +35 -0
- package/src/task-owner.ts +63 -0
- package/src/task-plan-status.ts +21 -0
- package/src/task-verification.ts +35 -0
- package/src/team-idle-tracker.ts +39 -0
- package/src/team-template-config.ts +100 -0
- package/src/teams-config-store.ts +22 -0
- package/src/terminal-authz.ts +49 -0
- package/src/token-db-write.ts +44 -0
- package/src/token-db.ts +487 -0
- package/src/token-usage-async.ts +40 -0
- package/src/transport-subscriptions.ts +57 -0
- package/src/types.ts +1 -0
- package/src/upgrade-install-verify.ts +179 -0
- package/src/upgrade-local-id.ts +3 -0
- package/src/upgrade-services.ts +125 -0
- package/src/upgrade.ts +1006 -0
- package/src/user-awaiting-reply.ts +42 -0
- package/src/user-path.ts +246 -0
- package/src/user-settings-async.ts +33 -0
- package/src/user-settings-store.ts +106 -0
- package/src/user-style.ts +83 -0
- package/src/utils.ts +42 -0
- package/src/validation.ts +324 -0
- package/src/vault-backup-config.ts +28 -0
- package/src/vault-expiry-config.ts +12 -0
- package/src/versioned-prompts.ts +57 -0
- package/src/workflow-capabilities.ts +10 -0
- package/src/workflow-prompt-templates.ts +20 -0
- package/src/workspace-action-audit.ts +42 -0
- package/src/workspace-actions.ts +689 -0
- package/src/workspace-auto-merge.ts +1055 -0
- package/src/workspace-changed-paths-client.ts +97 -0
- package/src/workspace-claim.ts +45 -0
- package/src/workspace-cleanup-settle.ts +51 -0
- package/src/workspace-cleanup-stale.ts +231 -0
- package/src/workspace-command-settle.ts +589 -0
- package/src/workspace-config.ts +7 -0
- package/src/workspace-conflict-resolver.ts +182 -0
- package/src/workspace-dead-owner-recovery.ts +19 -0
- package/src/workspace-delivery-tip.ts +51 -0
- package/src/workspace-escalation.ts +107 -0
- package/src/workspace-git-state-client.ts +42 -0
- package/src/workspace-host-client.ts +86 -0
- package/src/workspace-host-target.ts +5 -0
- package/src/workspace-host.ts +61 -0
- package/src/workspace-human-escalation.ts +70 -0
- package/src/workspace-land-gate-watchdog.ts +76 -0
- package/src/workspace-land-gate.ts +99 -0
- package/src/workspace-land-result.ts +12 -0
- package/src/workspace-land-retry.ts +82 -0
- package/src/workspace-land-stopped.ts +57 -0
- package/src/workspace-merge-dispatch.ts +27 -0
- package/src/workspace-merge-progress.ts +369 -0
- package/src/workspace-merge-retry-policy.ts +59 -0
- package/src/workspace-merge.ts +614 -0
- package/src/workspace-nothing-to-land-notice.ts +27 -0
- package/src/workspace-orphans.ts +927 -0
- package/src/workspace-phase.ts +507 -0
- package/src/workspace-pr-completion.ts +285 -0
- package/src/workspace-probe.ts +212 -0
- package/src/workspace-recorded-land.ts +47 -0
- package/src/workspace-recovery-authz.ts +88 -0
- package/src/workspace-rekey.ts +106 -0
- package/src/workspace-steward-authority.ts +16 -0
- package/src/workspace-steward-failure.ts +42 -0
- package/src/workspace-steward-wake.ts +150 -0
- package/src/workspace-steward-watchdog.ts +240 -0
|
@@ -0,0 +1,2264 @@
|
|
|
1
|
+
import { relayEnv } from "@humwick/sdk/env";
|
|
2
|
+
import { chmodSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { assertOpenCodeApprovalMode, buildOpenCodeSpawnArgs } from "./opencode-spawn";
|
|
4
|
+
export { assertOpenCodeApprovalMode, assertOpenCodeManagedServerArgs, assertOpenCodeSafeModel, resolveGhConfigDir } from "./opencode-spawn";
|
|
5
|
+
import { subscribeOpenCodeEvents } from "./opencode-subscribe";
|
|
6
|
+
import { terminateProviderProcesses } from "../provider-processes";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { errMessage, isRecord, stringValue, type ContextState, type Message, type ProviderState } from "@humwick/sdk";
|
|
10
|
+
import { killPid } from "@humwick/sdk/process-utils";
|
|
11
|
+
import { shellEscape } from "@humwick/sdk/shell-utils";
|
|
12
|
+
import { logger } from "../logger";
|
|
13
|
+
import { fireAndForget } from "../async-guard";
|
|
14
|
+
import { buildProviderRefusalHold, buildRateLimitProviderState, isProviderOverload, isProviderThrottle, isProviderUsageLimit } from "../rate-limit";
|
|
15
|
+
import { PromptAcceptedError, providerMessageText, type DeliverResult, type ManagedProcess, type PromptOfferOptions, type ProviderAdapter, type ProviderConfig, type ProviderDeliverContext, type ProviderInputObserver, type ProviderSessionEvent, type ProviderSessionTurnContext, type ProviderStatusUpdate, type ProviderTokenUsageReporter, type RunnerSpawnConfig, type ShutdownOutcome, type SpawnArgs, type TerminalAttachSpec } from "../adapter";
|
|
16
|
+
import { freshContextState } from "../transcript-context";
|
|
17
|
+
import { partitionMidTurnDelivery, resolveActiveTurnMessageDelivery } from "../mid-turn-delivery";
|
|
18
|
+
import { isOpenCodeServerRetryWall, openCodeServerRetryState, parseOpenCodeServerRetry, type OpenCodeServerRetry } from "./opencode-server-retry";
|
|
19
|
+
import { consumeOpenCodeContextAcceptance, openCodeContextState, prepareOpenCodeRelayContext, sendOpenCodeRelayTurn } from "./opencode-context";
|
|
20
|
+
import { captureOpenCodeApiUsage, spawnOpenCodeServer, openCodeApiKey, OpenCodeApiBudget } from "./opencode-api";
|
|
21
|
+
import { OpenCodeTokenUsagePoster, openCodeMessageFinished, openCodeMessageModel, openCodeMessageObservedAt, scheduleOpenCodeTokenUsage } from "./opencode-token-usage-capture";
|
|
22
|
+
import { decodeOpenCodeReasoningPart, decodeOpenCodeToolPart, openCodeAssistantReasoningSteps, openCodeAssistantText, openCodeReasoningSessionEvent, openCodeToolSessionEvent, openCodeToolStatusRank, openCodeTurnFinalEvents, type OpenCodeReasoningStep } from "./opencode-tool-events";
|
|
23
|
+
import { openCodeBackend, openCodeContextThreshold, openCodeContextWindowTokens, openCodeLaunchModel, openCodeOnLaunchModel, openCodeModelRecord, openCodeQualifiedModel, openCodeTurnDescriptor } from "./opencode-provider";
|
|
24
|
+
import { adoptOpenCodeTerminalModel, resolveOpenCodeLaunchModel } from "./opencode-model-registry";
|
|
25
|
+
import { OPENCODE_SOFT_STEER_POST_TIMEOUT_MS, OPENCODE_SOFT_STEER_RECONCILE_TIMEOUT_MS, OPENCODE_SOFT_STEER_WAKE_TEXT, OpenCodeSoftSteerLedger, composeSoftSteerText, newSoftSteerMessageID } from "./opencode-soft-steer";
|
|
26
|
+
|
|
27
|
+
export { openCodeContextThreshold, openCodeContextWindowTokens, openCodeModelDescriptor, openCodeModelRecord, openCodeQualifiedModel } from "./opencode-provider";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* #2459 — the non-secret attachment generation for a web-terminal guest: a short one-way
|
|
31
|
+
* digest of the ACTIVE OpenCode session id. It changes exactly when the session rotates
|
|
32
|
+
* (clearContext / the #2397 retry re-roll) and lets the orchestrator detect a stale guest
|
|
33
|
+
* TUI — attached to a dead conversation — WITHOUT ever seeing the session id itself or any
|
|
34
|
+
* credential (a digest of a 122-bit-random uuid is not reversible in practice). Rides the
|
|
35
|
+
* attach spec, the runner's GET /terminal/generation, and the orchestrator's guest registry;
|
|
36
|
+
* never persisted by Relay, never logged, never sent to the dashboard.
|
|
37
|
+
*/
|
|
38
|
+
export function openCodeTerminalGeneration(sessionId: string): string {
|
|
39
|
+
return createHash("sha256").update("opencode-attach-generation\0").update(sessionId).digest("hex").slice(0, 16);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OpenCodeServerEvent {
|
|
43
|
+
type: string;
|
|
44
|
+
properties?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type OpenCodeFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* #2428 — Bun extends its fetch init with a per-request `timeout` field that the standard
|
|
51
|
+
* RequestInit type does not declare (`number | false`; `false` disables Bun's native HTTP idle
|
|
52
|
+
* watchdog for that one request). Neither @types/node's nor bun-types 1.3.14's ambient
|
|
53
|
+
* RequestInit/BunFetchRequestInit declares the field, so it is declared here rather than reached
|
|
54
|
+
* through a cast. Verified empirically against this repo's Bun 1.3.14: with
|
|
55
|
+
* BUN_CONFIG_HTTP_IDLE_TIMEOUT compressed, a plain fetch to a server that sends no bytes dies with
|
|
56
|
+
* TimeoutError "The operation timed out" while the identical request plus `timeout: false`
|
|
57
|
+
* completes — and an explicit AbortSignal still fires on its own schedule either way, so the two
|
|
58
|
+
* mechanisms compose.
|
|
59
|
+
*/
|
|
60
|
+
export type OpenCodeRequestInit = RequestInit & { timeout?: number | false };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* #2425 — whole-turn budget for `OpenCodeClient.sendMessage`'s synchronous POST (see its doc).
|
|
64
|
+
* Env-tunable so an operator can trade a tighter bound for faster abandonment-detection on a
|
|
65
|
+
* flaky network, or a longer one for a model/workload that legitimately runs longer than an hour.
|
|
66
|
+
* The default is intentionally large rather than unbounded — `AbortSignal.timeout` requires a
|
|
67
|
+
* finite duration — and is far above the ~294s limit the incident was cut off at.
|
|
68
|
+
*/
|
|
69
|
+
export const OPENCODE_TURN_TIMEOUT_DEFAULT_MS = 60 * 60 * 1000;
|
|
70
|
+
|
|
71
|
+
export function openCodeTurnTimeoutMsFromEnv(): number {
|
|
72
|
+
const parsed = Number(process.env.OPENCODE_TURN_TIMEOUT_MS);
|
|
73
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : OPENCODE_TURN_TIMEOUT_DEFAULT_MS;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Small, pinned-protocol client for the v1.18.21 loopback server. */
|
|
77
|
+
export class OpenCodeClient {
|
|
78
|
+
constructor(
|
|
79
|
+
readonly baseUrl: string,
|
|
80
|
+
private readonly password: string,
|
|
81
|
+
private readonly fetchImpl: OpenCodeFetch = fetch,
|
|
82
|
+
private readonly provider = "opencode",
|
|
83
|
+
private readonly retryDelay?: (ms: number, signal: AbortSignal) => Promise<unknown>,
|
|
84
|
+
) {}
|
|
85
|
+
|
|
86
|
+
private headers(extra: HeadersInit = {}): Headers {
|
|
87
|
+
const headers = new Headers(extra);
|
|
88
|
+
headers.set("Authorization", `Basic ${Buffer.from(`opencode:${this.password}`).toString("base64")}`);
|
|
89
|
+
return headers;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async request(path: string, init: OpenCodeRequestInit = {}): Promise<Response> {
|
|
93
|
+
const response = await this.fetchImpl(new URL(path, this.baseUrl), {
|
|
94
|
+
...init,
|
|
95
|
+
headers: this.headers(init.headers),
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) throw new Error(`OpenCode server ${path} failed: HTTP ${response.status}`);
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The loopback server logs "listening" as soon as its socket is bound, well before the
|
|
103
|
+
* Basic-auth-gated HTTP handler is actually wired up. A request that lands in that window is
|
|
104
|
+
* neither refused nor answered — the connection is accepted and then left open, so an
|
|
105
|
+
* unbounded fetch can hang far longer than any overall deadline (reproduced: a single such
|
|
106
|
+
* request outlived a 40s wait). Each attempt therefore gets its own short-lived AbortSignal so
|
|
107
|
+
* a stuck connection can never swallow the whole retry budget — only a real, authenticated
|
|
108
|
+
* `/global/health` 200 resolves the wait.
|
|
109
|
+
*/
|
|
110
|
+
async waitForReady(timeoutMs = 20_000, attemptTimeoutMs = 2_000): Promise<void> {
|
|
111
|
+
const deadline = Date.now() + timeoutMs;
|
|
112
|
+
let lastError: unknown;
|
|
113
|
+
while (Date.now() < deadline) {
|
|
114
|
+
const remaining = deadline - Date.now();
|
|
115
|
+
try {
|
|
116
|
+
await this.request("/global/health", { signal: AbortSignal.timeout(Math.min(attemptTimeoutMs, remaining)) });
|
|
117
|
+
return;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
lastError = error;
|
|
120
|
+
await Bun.sleep(Math.min(100, Math.max(0, deadline - Date.now())));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`OpenCode server did not become ready within ${timeoutMs}ms${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async createSession(title: string): Promise<string> {
|
|
127
|
+
const response = await this.request("/session", {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: { "Content-Type": "application/json" },
|
|
130
|
+
body: JSON.stringify({ title }),
|
|
131
|
+
});
|
|
132
|
+
const body = await response.json().catch(() => undefined);
|
|
133
|
+
const id = isRecord(body) ? stringValue(body.id) : undefined;
|
|
134
|
+
if (!id) throw new Error("OpenCode server created a session without an id");
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* #2389 — this POST is the SYNCHRONOUS endpoint: it returns only once the assistant message is
|
|
140
|
+
* complete, so its status and duration describe the whole turn, not just the hand-off.
|
|
141
|
+
*
|
|
142
|
+
* It used to be entirely silent on success — no status, no timing, no access log — so when a
|
|
143
|
+
* spawned agent idled forever there was no way to establish whether its first prompt had even
|
|
144
|
+
* been accepted; that took reading the provider's own retained SQLite. Log it.
|
|
145
|
+
*
|
|
146
|
+
* #2425 — this call used to carry NO abort signal at all, yet a live, still-running turn was
|
|
147
|
+
* observed being cut off client-side at roughly 294s regardless. Combined with the old PERMANENT
|
|
148
|
+
* idle suppression, that stranded the agent's status at "idle" forever even though the server
|
|
149
|
+
* kept working — see the `suppressProviderIdle` field doc on `OpenCodeAdapter`. Passing an
|
|
150
|
+
* EXPLICIT, much longer bound (`openCodeTurnTimeoutMsFromEnv`) means a legitimate long turn is
|
|
151
|
+
* never the thing that trips it, and gives `send()` a deterministic abandonment point to recover
|
|
152
|
+
* from if it ever does.
|
|
153
|
+
*
|
|
154
|
+
* #2428 — ROOT CAUSED (Bun repro): that ~294s cutoff is Bun's native HTTP idle watchdog — a
|
|
155
|
+
* fetch whose response sends no bytes for BUN_CONFIG_HTTP_IDLE_TIMEOUT (default 300s) dies
|
|
156
|
+
* client-side with TimeoutError "The operation timed out" INDEPENDENTLY of the AbortSignal
|
|
157
|
+
* above, which bounds only its own much longer duration. This endpoint is synchronous, so any
|
|
158
|
+
* turn longer than that window was aborted client-side while the server kept working (and
|
|
159
|
+
* RunnerCore's retry re-POSTed into the still-running turn). The fix passes Bun-only
|
|
160
|
+
* `timeout: false` on exactly this request (see OpenCodeRequestInit): verified empirically that
|
|
161
|
+
* it disables only that watchdog while the explicit deadline above keeps bounding the whole
|
|
162
|
+
* call. Deliberately NOT set globally (no BUN_CONFIG_HTTP_IDLE_TIMEOUT env change) and not on
|
|
163
|
+
* the short bounded calls — waitForReady/createSession/abort keep their default watchdog plus
|
|
164
|
+
* their own signals.
|
|
165
|
+
*/
|
|
166
|
+
async sendMessage(sessionId: string, text: string, model: string, timeoutMs: number = openCodeTurnTimeoutMsFromEnv()): Promise<OpenCodeCompletion> {
|
|
167
|
+
const startedAt = Date.now();
|
|
168
|
+
try {
|
|
169
|
+
const response = await this.request(`/session/${encodeURIComponent(sessionId)}/message`, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
body: JSON.stringify({ model: openCodeTurnDescriptor(model, this.provider), parts: [{ type: "text", text }] }),
|
|
173
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
174
|
+
// #2428 — disable Bun's native HTTP idle watchdog for this whole-turn request only; the
|
|
175
|
+
// signal above remains the sole deadline. See OpenCodeRequestInit for the verified semantics.
|
|
176
|
+
timeout: false,
|
|
177
|
+
});
|
|
178
|
+
const completion = openCodeCompletion(await response.json().catch(() => undefined));
|
|
179
|
+
logger.info("opencode", `session ${sessionId} message accepted: HTTP ${response.status} (${text.length} chars, ${Date.now() - startedAt}ms)${describeCompletion(completion)}`);
|
|
180
|
+
return completion;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.error("opencode", `session ${sessionId} message failed after ${Date.now() - startedAt}ms (${text.length} chars): ${errMessage(error)}`);
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* #2534 — bounded read of a session's persisted messages, used ONLY by the acceptance
|
|
189
|
+
* reconciliation when a context-carrying turn's completion await failed: a user message whose
|
|
190
|
+
* persisted text equals the outbound prompt is positive proof the server ACCEPTED it, even
|
|
191
|
+
* though the await never resolved. Kept on a short, explicit deadline — this runs on a failure
|
|
192
|
+
* path and must never stretch the caller's own recovery by more than a few seconds.
|
|
193
|
+
*/
|
|
194
|
+
async sessionMessages(sessionId: string, timeoutMs = 5_000): Promise<unknown> {
|
|
195
|
+
const response = await this.request(`/session/${encodeURIComponent(sessionId)}/message`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
196
|
+
return response.json().catch(() => undefined);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* #2539 Phase B — the soft-steer persist primitive: POST /session/:id/message with a CALLER
|
|
201
|
+
* messageID + noReply:true. LIVE-PROVEN on pinned 1.18.21: persists the user row in single-digit
|
|
202
|
+
* milliseconds WITHOUT joining/cancelling an in-flight run (does not touch the runner effect that
|
|
203
|
+
* prompt_async would join), and the response body IS the persisted user message — so
|
|
204
|
+
* `info.id === messageID` is positive persistence identity straight from the POST, with no
|
|
205
|
+
* reconcile read on the clean path. Tri-state outcome: "persisted" (row exists, identity
|
|
206
|
+
* confirmed), "absent" (the server DEFINITIVELY refused — 4xx; the row does not exist), or
|
|
207
|
+
* "unknown" (timeout/transport/5xx — the POST may or may not have landed; the caller must
|
|
208
|
+
* reconcile by exact id via {@link hasMessageWithId} and never blind-retry). Bounded deadline
|
|
209
|
+
* (5s default) — a soft steer must never hang on the whole-turn budget.
|
|
210
|
+
*/
|
|
211
|
+
async sendMessageNoReply(sessionId: string, text: string, messageID: string, timeoutMs = 5_000): Promise<"persisted" | "absent" | "unknown"> {
|
|
212
|
+
try {
|
|
213
|
+
const response = await this.fetchImpl(new URL(`/session/${encodeURIComponent(sessionId)}/message`, this.baseUrl), {
|
|
214
|
+
method: "POST",
|
|
215
|
+
headers: this.headers({ "Content-Type": "application/json" }),
|
|
216
|
+
body: JSON.stringify({ parts: [{ type: "text", text }], messageID, noReply: true }),
|
|
217
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
218
|
+
});
|
|
219
|
+
if (response.status >= 500) return "unknown";
|
|
220
|
+
if (!response.ok) return "absent";
|
|
221
|
+
const body = await response.json().catch(() => undefined);
|
|
222
|
+
const id = isRecord(body) && isRecord(body.info) ? stringValue(body.info.id) : undefined;
|
|
223
|
+
return id === messageID ? "persisted" : "unknown";
|
|
224
|
+
} catch {
|
|
225
|
+
return "unknown";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* #2539 Phase B — the ambiguous-outcome reconcile: does the session's persisted message list
|
|
231
|
+
* contain EXACTLY this caller messageID? true/false are definitive (the GET succeeded and the
|
|
232
|
+
* full session listing either has the row or does not); undefined means the listing itself was
|
|
233
|
+
* unavailable — still ambiguous. Matching is by exact id, never by text (a resend must never
|
|
234
|
+
* pass reconciliation by colliding with its own previous attempt's text).
|
|
235
|
+
*/
|
|
236
|
+
async hasMessageWithId(sessionId: string, messageID: string, timeoutMs = 5_000): Promise<boolean | undefined> {
|
|
237
|
+
try {
|
|
238
|
+
const messages = await this.sessionMessages(sessionId, timeoutMs);
|
|
239
|
+
if (!Array.isArray(messages)) return undefined;
|
|
240
|
+
return messages.some((entry) => isRecord(entry) && isRecord(entry.info) && stringValue(entry.info.id) === messageID);
|
|
241
|
+
} catch {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async abort(sessionId: string): Promise<void> {
|
|
247
|
+
await this.request(`/session/${encodeURIComponent(sessionId)}/abort`, { method: "POST" });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* #2440 — OpenCode's own native session-compaction endpoint (a prior effort, #2431, wrongly
|
|
252
|
+
* concluded it did not exist and shipped clear-inject only). Like `sendMessage`, this may run
|
|
253
|
+
* long server-side, so it gets the same Bun idle-watchdog bypass (`timeout: false`) plus an
|
|
254
|
+
* explicit bounded `AbortSignal` — see `sendMessage`'s doc for the verified #2428 semantics.
|
|
255
|
+
* A session-not-found / compact-already-in-progress / server error all surface as the same
|
|
256
|
+
* `request()`-thrown `HTTP <status>` error; the adapter's caller resets its busy hold on catch.
|
|
257
|
+
*/
|
|
258
|
+
async compact(sessionId: string, timeoutMs: number = openCodeTurnTimeoutMsFromEnv()): Promise<void> {
|
|
259
|
+
const startedAt = Date.now();
|
|
260
|
+
try {
|
|
261
|
+
const response = await this.request(`/session/${encodeURIComponent(sessionId)}/compact`, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
264
|
+
timeout: false,
|
|
265
|
+
});
|
|
266
|
+
logger.info("opencode", `session ${sessionId} compact accepted: HTTP ${response.status} (${Date.now() - startedAt}ms)`);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.error("opencode", `session ${sessionId} compact failed after ${Date.now() - startedAt}ms: ${errMessage(error)}`);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Consume SSE without assuming every event shape is stable outside the pinned version. */
|
|
274
|
+
async subscribe(signal: AbortSignal, onEvent: (event: OpenCodeServerEvent) => void): Promise<void> {
|
|
275
|
+
return subscribeOpenCodeEvents(() => this.request("/event", { headers: { Accept: "text/event-stream" }, signal }), signal, onEvent, this.retryDelay);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Retry/re-roll policy for a retryable turn-ending provider error (#2397). Direct measurement
|
|
282
|
+
* (issue #2397 comment) showed these failures are BIMODAL per OpenCode session — a session either
|
|
283
|
+
* succeeds on every attempt or fails on every attempt for its whole life — so retrying harder
|
|
284
|
+
* into the SAME session eventually accomplishes nothing. Bound the in-session retry tightly, then
|
|
285
|
+
* re-roll to a fresh session (new session id, same server process) exactly once before giving up.
|
|
286
|
+
*/
|
|
287
|
+
const OPENCODE_ERROR_RETRY_MAX_ATTEMPTS = 3;
|
|
288
|
+
const OPENCODE_ERROR_RETRY_BASE_DELAY_MS = 1_000;
|
|
289
|
+
|
|
290
|
+
/** Exponential backoff for retry attempt N (1-indexed): 1s, 2s, 4s at the defaults above. Driven
|
|
291
|
+
* entirely by our own policy — OpenRouter sends neither `retry-after` nor `x-ratelimit-*` for
|
|
292
|
+
* this error class (confirmed live, #2397), so there is nothing external to read. */
|
|
293
|
+
export function openCodeErrorRetryDelayMs(attempt: number): number {
|
|
294
|
+
return OPENCODE_ERROR_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* #2397 — a parsed assistant-message error off the pinned v1.18.21 wire.
|
|
299
|
+
*
|
|
300
|
+
* `AssistantMessage.error` is a union (ProviderAuthError | UnknownError |
|
|
301
|
+
* MessageOutputLengthError | MessageAbortedError | ApiError — see @opencode-ai/sdk's generated
|
|
302
|
+
* types). Only `ApiError` carries a structured `statusCode`/`isRetryable`; OpenCode falls back to
|
|
303
|
+
* `UnknownError` for anything it doesn't recognize. Verified live (#2397): a real OpenRouter 429
|
|
304
|
+
* arrived as `UnknownError` with the raw provider response embedded as a JSON STRING in
|
|
305
|
+
* `data.message` (`{"code":429,"message":"...","metadata":{"error_type":"rate_limit_exceeded"}}`),
|
|
306
|
+
* not as a structured ApiError. So this parses defensively — peeling that nested JSON when
|
|
307
|
+
* present — rather than trusting a shape that is usually absent for the errors that matter most.
|
|
308
|
+
*/
|
|
309
|
+
/** #2619 — how one `send()` turn ended; see its doc. */
|
|
310
|
+
type OpenCodeSendOutcome = "ok" | "failed" | "held";
|
|
311
|
+
|
|
312
|
+
export interface OpenCodeAssistantError {
|
|
313
|
+
name: string;
|
|
314
|
+
statusCode?: number;
|
|
315
|
+
errorType?: string;
|
|
316
|
+
message: string;
|
|
317
|
+
isRetryable: boolean;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function parseOpenCodeAssistantError(raw: unknown): OpenCodeAssistantError | undefined {
|
|
321
|
+
if (!isRecord(raw)) return undefined;
|
|
322
|
+
const name = stringValue(raw.name) ?? "UnknownError";
|
|
323
|
+
const data = isRecord(raw.data) ? raw.data : {};
|
|
324
|
+
let statusCode = typeof data.statusCode === "number" ? data.statusCode : undefined;
|
|
325
|
+
let message = stringValue(data.message) ?? "";
|
|
326
|
+
let errorType: string | undefined;
|
|
327
|
+
const trimmed = message.trim();
|
|
328
|
+
if (trimmed.startsWith("{")) {
|
|
329
|
+
try {
|
|
330
|
+
const nested = JSON.parse(trimmed) as unknown;
|
|
331
|
+
if (isRecord(nested)) {
|
|
332
|
+
if (statusCode === undefined && typeof nested.code === "number") statusCode = nested.code;
|
|
333
|
+
if (typeof nested.message === "string" && nested.message) message = nested.message;
|
|
334
|
+
if (isRecord(nested.metadata) && typeof nested.metadata.error_type === "string") errorType = nested.metadata.error_type;
|
|
335
|
+
}
|
|
336
|
+
} catch { /* not JSON — keep the raw (already-human) message */ }
|
|
337
|
+
}
|
|
338
|
+
const isRetryable = data.isRetryable === true
|
|
339
|
+
|| statusCode === 429
|
|
340
|
+
|| (typeof statusCode === "number" && statusCode >= 500)
|
|
341
|
+
|| errorType === "rate_limit_exceeded";
|
|
342
|
+
return {
|
|
343
|
+
name,
|
|
344
|
+
...(statusCode !== undefined ? { statusCode } : {}),
|
|
345
|
+
...(errorType ? { errorType } : {}),
|
|
346
|
+
message: message || `OpenCode reported ${name}`,
|
|
347
|
+
isRetryable,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* #2443 — a 404 from OpenCode's own session-message endpoint means OpenRouter has no route for
|
|
353
|
+
* the requested provider/model at all (retired, mistyped, or never released) — generic across
|
|
354
|
+
* ANY OpenRouter-routed model, never a specific one. Classifying it as the SDK's canonical
|
|
355
|
+
* `model-unavailable` reason (already used by the Claude adapter for its own "model doesn't
|
|
356
|
+
* exist" stop-failure) instead of the catch-all `opencode-provider-error` is what lets a terminal
|
|
357
|
+
* failure surface as "model unavailable" rather than an undifferentiated provider error.
|
|
358
|
+
*/
|
|
359
|
+
export function openCodeTerminalFailureReason(error: Pick<OpenCodeAssistantError, "statusCode">): string {
|
|
360
|
+
return error.statusCode === 404 ? "model-unavailable" : "opencode-provider-error";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* #2397 — the operator-visible providerState for "still trying" (retrying in-session, or
|
|
365
|
+
* re-rolled to a fresh session and retrying), reusing `buildRateLimitProviderState`'s shape
|
|
366
|
+
* (label/kind/source/errorType/timestamps) rather than a bespoke one. `reason` is overridden away
|
|
367
|
+
* from `RATE_LIMIT_BLOCK_REASON` ("rate_limit") deliberately: that reason is special-cased in
|
|
368
|
+
* runner-core.ts for Claude's idle+blocked usage-limit hold (and is unconditionally cleared by any
|
|
369
|
+
* `status: "busy"` update — "forward progress lifts the hold"), which would erase this state on
|
|
370
|
+
* the very same busy event that carries it. A distinct reason avoids that collision while the
|
|
371
|
+
* `state: "blocked"` + `providerState` metadata still surfaces normally through the adapter's
|
|
372
|
+
* ordinary busy-claim path (the same path `permission.asked` already uses below).
|
|
373
|
+
*/
|
|
374
|
+
export function openCodeUpstreamRetryState(input: { attempt: number; rerolled: boolean; message: string; errorType?: string }): ProviderState {
|
|
375
|
+
// #2397 round 2 (F7) — only call it a rate limit when the parsed error actually said so.
|
|
376
|
+
// `errorType` used to default to "rate_limit_exceeded" here, and the label was hardcoded to
|
|
377
|
+
// "Rate-limited upstream" regardless of cause, so a 500/503 (isRetryable via the >=500 branch
|
|
378
|
+
// in parseOpenCodeAssistantError, with no errorType at all) was misreported as a rate limit.
|
|
379
|
+
const rateLimited = input.errorType === "rate_limit_exceeded";
|
|
380
|
+
const base = buildRateLimitProviderState({
|
|
381
|
+
kind: "transient_overload",
|
|
382
|
+
errorType: input.errorType ?? "provider_error",
|
|
383
|
+
message: input.message,
|
|
384
|
+
source: "opencode",
|
|
385
|
+
});
|
|
386
|
+
const upstream = rateLimited ? "Rate-limited upstream" : "OpenCode upstream provider error";
|
|
387
|
+
return {
|
|
388
|
+
...base,
|
|
389
|
+
reason: "provider-error",
|
|
390
|
+
label: input.rerolled
|
|
391
|
+
? input.attempt > 0
|
|
392
|
+
? `${upstream} — re-rolled to a new OpenCode session, retrying (attempt ${input.attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…`
|
|
393
|
+
: `${upstream} — re-rolled to a new OpenCode session, retrying…`
|
|
394
|
+
: `${upstream}, retrying (attempt ${input.attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…`,
|
|
395
|
+
resumeAttempt: input.attempt,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* #2389 — what the synchronous `/message` POST hands back: the completed assistant message.
|
|
401
|
+
*
|
|
402
|
+
* This is ground truth for the incident and needs no inference at all. The stranded agent's turn
|
|
403
|
+
* came back `finish=stop` with no text part, no tool part and zero tokens — an empty completion the
|
|
404
|
+
* runner then reported as a clean idle. The response body carrying exactly that was being discarded.
|
|
405
|
+
*
|
|
406
|
+
* Read defensively: OpenCode's wire is only pinned at v1.18.21, so an unrecognised shape yields
|
|
407
|
+
* `undefined` counts and simply contributes no signal, leaving the cross-provider ledger to decide.
|
|
408
|
+
*
|
|
409
|
+
* #2397 — also carries the assistant message's `error`, when positively parseable. This is a
|
|
410
|
+
* STRONGER signal than `empty`: a turn can end with a non-empty `parts` count (the live incident's
|
|
411
|
+
* completion carried one bare step-start part) and still be a turn-ending PROVIDER failure, so
|
|
412
|
+
* `error` is checked ahead of (and independently of) the `empty` heuristic by every reader.
|
|
413
|
+
*/
|
|
414
|
+
export interface OpenCodeCompletion {
|
|
415
|
+
parts?: number;
|
|
416
|
+
/**
|
|
417
|
+
* #2397 round 3 (HIGH-1) — `parts` count MINUS bookkeeping-only parts (`step-start`,
|
|
418
|
+
* `step-finish`), which the server emits on every turn — including one that produced nothing
|
|
419
|
+
* but a provider error. The live incident this issue was filed on captured exactly that shape:
|
|
420
|
+
* one bare `step-start` part, zero tokens, a 429. `parts` alone is 1, which reads as "the model
|
|
421
|
+
* produced output"; `outputParts` is 0, which correctly does not. Present whenever `parts` is.
|
|
422
|
+
*/
|
|
423
|
+
outputParts?: number;
|
|
424
|
+
tokens?: number;
|
|
425
|
+
finish?: string;
|
|
426
|
+
error?: OpenCodeAssistantError;
|
|
427
|
+
/**
|
|
428
|
+
* #2413 — the terminal assistant message's TEXT, concatenated from its text parts. This
|
|
429
|
+
* synchronous POST returns the completed assistant message (ground truth, #2389), so the
|
|
430
|
+
* turn-final response is read from it directly rather than reconstructed from a possibly-late
|
|
431
|
+
* or lost SSE frame. Present only when the completed message carried assistant text; absent for
|
|
432
|
+
* a tools-only or empty turn.
|
|
433
|
+
*/
|
|
434
|
+
text?: string;
|
|
435
|
+
/** True only when the shape WAS understood and it says the model produced nothing. */
|
|
436
|
+
empty: boolean;
|
|
437
|
+
/**
|
|
438
|
+
* #2605 — the terminal assistant message's REASONING as per-part steps (`{stepId, body}[]`,
|
|
439
|
+
* wire order). Present only when the completed message carried non-empty reasoning; absent
|
|
440
|
+
* for text/tools-only or empty turns. Per-part identity (not a joined string) is what lets
|
|
441
|
+
* the turn-final emit upsert the live SSE rows instead of duplicating them — the server keys
|
|
442
|
+
* step rows on stepId ONLY. Read from the same POST body as `text`, so a turn whose SSE
|
|
443
|
+
* reasoning frames were never observed still surfaces its thinking in the trace.
|
|
444
|
+
*/
|
|
445
|
+
reasoningSteps?: OpenCodeReasoningStep[];
|
|
446
|
+
/**
|
|
447
|
+
* #2605 — the same reasoning as one joined string (the steps' bodies, `\n\n` join mirroring
|
|
448
|
+
* `text`). Kept for the completion log line only — the EMIT path reads `reasoningSteps`.
|
|
449
|
+
*/
|
|
450
|
+
reasoning?: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** #2397 round 3 (HIGH-1) — a bookkeeping marker the server emits around every turn regardless of
|
|
454
|
+
* outcome, never evidence the model produced anything. An unrecognised part shape is counted as
|
|
455
|
+
* output (the conservative default): under-counting risks re-treating real work as empty and
|
|
456
|
+
* re-executing it, which is the worse failure. */
|
|
457
|
+
function isOutputPart(part: unknown): boolean {
|
|
458
|
+
if (!isRecord(part)) return true;
|
|
459
|
+
const type = stringValue(part.type);
|
|
460
|
+
return type !== "step-start" && type !== "step-finish";
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Whether a parsed completion is proof the model produced something — the ONE predicate both the
|
|
464
|
+
* error branch (HIGH-1) and the ordinary success branch must agree on, so a step-start-only body
|
|
465
|
+
* can never read as "produced output" from either path. */
|
|
466
|
+
function completionProducedOutput(completion: OpenCodeCompletion): boolean {
|
|
467
|
+
return (completion.outputParts ?? completion.parts ?? 0) > 0 || (completion.tokens ?? 0) > 0;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function countTokens(value: unknown): number | undefined {
|
|
471
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
472
|
+
if (!isRecord(value)) return undefined;
|
|
473
|
+
// `{ input, output, reasoning, cache: {...} }` on the pinned version, plus — confirmed against a
|
|
474
|
+
// real captured v1.18.21 session (#2355) — a top-level `total` that is ALREADY the sum of every
|
|
475
|
+
// other leaf here (`input + output + reasoning + cache.read + cache.write`). When present it is
|
|
476
|
+
// authoritative and must be returned directly: falling through to the leaf sum below as well
|
|
477
|
+
// would double-count it (`total` + its own constituent parts).
|
|
478
|
+
if (typeof value.total === "number" && Number.isFinite(value.total)) return value.total;
|
|
479
|
+
// Sum the numeric leaves so a renamed or added field neither breaks the read nor silently zeroes
|
|
480
|
+
// it.
|
|
481
|
+
let total: number | undefined;
|
|
482
|
+
for (const entry of Object.values(value)) {
|
|
483
|
+
const nested = countTokens(entry);
|
|
484
|
+
if (nested !== undefined) total = (total ?? 0) + nested;
|
|
485
|
+
}
|
|
486
|
+
return total;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function openCodeCompletion(body: unknown): OpenCodeCompletion {
|
|
490
|
+
if (!isRecord(body)) return { empty: false };
|
|
491
|
+
// Either `{ info, parts }` or the message object itself, depending on the endpoint's mood.
|
|
492
|
+
const info = isRecord(body.info) ? body.info : body;
|
|
493
|
+
const parts = Array.isArray(body.parts) ? body.parts : Array.isArray(info.parts) ? info.parts : undefined;
|
|
494
|
+
const tokens = countTokens(info.tokens);
|
|
495
|
+
// `info.time` is ALWAYS present on an AssistantMessage, so guarding the read on its absence made
|
|
496
|
+
// this always yield undefined. The result was that the one log line written to capture
|
|
497
|
+
// `finish=stop` never printed it.
|
|
498
|
+
const finish = stringValue(info.finish);
|
|
499
|
+
const error = parseOpenCodeAssistantError(info.error);
|
|
500
|
+
const outputParts = parts?.filter(isOutputPart).length;
|
|
501
|
+
const text = openCodeAssistantText(parts);
|
|
502
|
+
const reasoningSteps = openCodeAssistantReasoningSteps(parts);
|
|
503
|
+
const reasoning = reasoningSteps.length ? reasoningSteps.map((step) => step.body).join("\n\n") : undefined;
|
|
504
|
+
return {
|
|
505
|
+
...(parts ? { parts: parts.length, outputParts: outputParts! } : {}),
|
|
506
|
+
...(tokens !== undefined ? { tokens } : {}),
|
|
507
|
+
...(finish ? { finish } : {}),
|
|
508
|
+
...(error ? { error } : {}),
|
|
509
|
+
...(text ? { text } : {}),
|
|
510
|
+
...(reasoningSteps.length ? { reasoningSteps } : {}),
|
|
511
|
+
...(reasoning ? { reasoning } : {}),
|
|
512
|
+
// Only claim emptiness when the shape was actually understood: a body we could not parse must
|
|
513
|
+
// never be reported as an empty completion.
|
|
514
|
+
// #2397 round 3 review (LOW-4r) — must agree with `completionProducedOutput`'s `outputParts`,
|
|
515
|
+
// not raw `parts`, or a bookkeeping-only body (step-start, tokens:0, no error) slips past BOTH
|
|
516
|
+
// predicates in send(): not `producedOutput` (correctly), but also not `empty` — landing on
|
|
517
|
+
// "if (!completion.empty) return true" with no turnActivityCb, no retry-hold clear, and no
|
|
518
|
+
// deterministic idle close.
|
|
519
|
+
empty: outputParts !== undefined && outputParts === 0 && tokens !== undefined && tokens === 0,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function describeCompletion(completion: OpenCodeCompletion): string {
|
|
524
|
+
const bits: string[] = [];
|
|
525
|
+
if (completion.parts !== undefined) bits.push(`${completion.parts} part(s)${completion.outputParts !== undefined && completion.outputParts !== completion.parts ? ` (${completion.outputParts} output)` : ""}${completion.reasoningSteps?.length ? ` (${completion.reasoningSteps.length} reasoning)` : ""}`);
|
|
526
|
+
if (completion.tokens !== undefined) bits.push(`${completion.tokens} token(s)`);
|
|
527
|
+
if (completion.finish) bits.push(`finish=${completion.finish}`);
|
|
528
|
+
if (completion.error) bits.push(`error=${completion.error.name}${completion.error.statusCode !== undefined ? ` ${completion.error.statusCode}` : ""}`);
|
|
529
|
+
if (!bits.length) return "";
|
|
530
|
+
return ` → ${bits.join(", ")}${completion.empty ? " — EMPTY COMPLETION" : ""}`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/** Bound on the live per-session bookkeeping in `OpenCodeAdapter` (#2347): a headless agent can
|
|
534
|
+
* run for days across many turns, so unbounded maps keyed by message/part id would leak. Both
|
|
535
|
+
* `messageRoles` and `emittedTextParts` are pruned back to this size, oldest first, whenever a
|
|
536
|
+
* write would exceed it. */
|
|
537
|
+
const OPENCODE_EVENT_STATE_LIMIT = 500;
|
|
538
|
+
|
|
539
|
+
function pruneToLimit(map: Map<string, unknown> | Set<string>, limit: number): void {
|
|
540
|
+
const overflow = map.size - limit;
|
|
541
|
+
if (overflow <= 0) return;
|
|
542
|
+
const it = map.keys();
|
|
543
|
+
for (let i = 0; i < overflow; i++) {
|
|
544
|
+
const next = it.next();
|
|
545
|
+
if (next.done) break;
|
|
546
|
+
map.delete(next.value);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export class OpenCodeAdapter implements ProviderAdapter {
|
|
551
|
+
readonly reportsContextResets = true;
|
|
552
|
+
readonly provider: string;
|
|
553
|
+
readonly statusSourceId: string;
|
|
554
|
+
// #2440 — unlocks `context.resume = "native"` capability advertisement (see
|
|
555
|
+
// runtimeProviderContextCapabilities). `opts.instructions` is deliberately NOT threaded into the
|
|
556
|
+
// `/compact` HTTP call itself (see `performCompact`'s doc) — the relay's own reactive
|
|
557
|
+
// continuation inject already delivers it as a follow-up message either way, exactly like
|
|
558
|
+
// Codex's own compact() (also instructions-blind) relies on for its forced-resume path.
|
|
559
|
+
// IMPORTANT semantic gap this flag papers over: unlike Claude's real `/compact <instructions>`,
|
|
560
|
+
// OpenCode's `/compact` endpoint is NOT instruction-steerable — `performCompact` never sends the
|
|
561
|
+
// continuation on the wire at all, so the summarizer itself never sees it. Setting this flag true
|
|
562
|
+
// is what makes `resume:"native"` selectable; the actual delivery guarantee comes entirely from
|
|
563
|
+
// the reactive inject, not from this flag. Follow-up #2442 tracks a cleaner capability bit that
|
|
564
|
+
// distinguishes "native resume is offered" from "the compact call itself accepts steering text",
|
|
565
|
+
// so a future reader doesn't read this as "the summary is instructable" when it is not.
|
|
566
|
+
readonly compactSupportsInstructions = true;
|
|
567
|
+
private statusCb: (status: ProviderStatusUpdate) => void = () => {};
|
|
568
|
+
private sessionEventCb: (event: ProviderSessionEvent) => void = () => {};
|
|
569
|
+
private turnActivityCb: () => void = () => {};
|
|
570
|
+
// `message.updated` (role) always precedes that message's `message.part.updated` events on the
|
|
571
|
+
// real v1.18.21 wire (verified live against OpenRouter meta/muse-spark-1.3-contributor, #2347) — spawn()
|
|
572
|
+
// subscribes before any prompt is sent, so there is no race for the normal launch flow.
|
|
573
|
+
private readonly messageRoles = new Map<string, string>();
|
|
574
|
+
private readonly emittedTextParts = new Set<string>();
|
|
575
|
+
// #2354 — stepId → highest lifecycle rank already emitted for that tool part, so a duplicate or
|
|
576
|
+
// reordered `message.part.updated` (a late `running` after `completed`, a repeated frame) never
|
|
577
|
+
// regresses or double-emits the step. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
|
|
578
|
+
private readonly emittedToolSteps = new Map<string, number>();
|
|
579
|
+
// #2437 — the ToolPart wire shape only carries `input` while `running`; a `completed` decode has
|
|
580
|
+
// none. Cache the redacted params captured at running, keyed by stepId, and carry them forward
|
|
581
|
+
// onto the terminal event so the row's params don't vanish when the server upserts it in place
|
|
582
|
+
// (upsertSessionStepInPlace replaces the whole payload with the incoming one, not a merge).
|
|
583
|
+
private readonly toolStepParams = new Map<string, unknown>();
|
|
584
|
+
// #2605 — reasoning part id → full body already emitted for that part, so a repeated or
|
|
585
|
+
// finalized `message.part.updated` frame for the same reasoning part emits the CURRENT full
|
|
586
|
+
// text (the server upserts the trace row in place by stepId) instead of appending a duplicate
|
|
587
|
+
// row. Pruned to OPENCODE_EVENT_STATE_LIMIT like the maps above.
|
|
588
|
+
private readonly emittedReasoningParts = new Map<string, string>();
|
|
589
|
+
// #2413 review (HIGH-1) — a genuinely PER-SEND turn id, minted at the top of each `send()` and
|
|
590
|
+
// used for the busy edge, narration, tool steps, and the turn-final response alike. Was
|
|
591
|
+
// `process.meta.sessionId`, which PERSISTS across sends: two turns in one session then shared a
|
|
592
|
+
// turnId, so lineage dedup (promoteLineageCapturedResponse/flushWorkFinalReports) forwarded the
|
|
593
|
+
// FIRST turn's body and dropped the SECOND — the real final report never reached the coordinator.
|
|
594
|
+
private sendTurnId: string | undefined;
|
|
595
|
+
// #2397 round 2 (F3/F4) — live-confirmed: the real server emits its OWN busy/idle
|
|
596
|
+
// session.status pair around EVERY attempt, including failed ones (two `idle` events for one
|
|
597
|
+
// errored turn). Left alone, each failed attempt's idle looks like a real provider-turn close
|
|
598
|
+
// to runner-core's #2389 seam: it clears `currentTurnId` and judges that blip as an empty turn,
|
|
599
|
+
// then the next attempt's busy mints a brand-new turn id — up to
|
|
600
|
+
// `OPENCODE_ERROR_RETRY_MAX_ATTEMPTS * 2` extra empty-turn closes the operator sees, and a
|
|
601
|
+
// premature nudge/escalation mid-retry. Raised for the WHOLE `send()` call (see the loop entry),
|
|
602
|
+
// this swallows every transient idle for the call's own turn so the whole retry sequence stays
|
|
603
|
+
// ONE turn from the runner's view.
|
|
604
|
+
// #2397 round 5 review (MED-1) — this is now a STICKY suppression, not cleared at close time:
|
|
605
|
+
// `closeSuppressedTurn` used to lower it as it fired, so a HEALTHY turn's own trailing real SSE
|
|
606
|
+
// idle — if processed after the POST already resolved — sailed through unsuppressed and closed
|
|
607
|
+
// the same turn a SECOND time (duplicate reInjectPendingObligation, duplicate
|
|
608
|
+
// completeObservedTaskClaims/Task-done). The invariant that actually holds: an idle belonging to
|
|
609
|
+
// a turn `send()` already closed must stay swallowed; only an idle for a turn that STARTS AFTER
|
|
610
|
+
// that close may pass.
|
|
611
|
+
// #2397 round 6 review (MED-1r) — round 5 had `handleEvent`'s BUSY door lower this flag whenever
|
|
612
|
+
// `turnInFlight` was false, on the assumption that such a busy edge could only be a genuinely new
|
|
613
|
+
// turn. That is a timing assumption, not an invariant: `subscribe()` dispatches a whole SSE chunk
|
|
614
|
+
// synchronously in one loop, and the SSE/POST sockets have no cross-ordering guarantee, so a late
|
|
615
|
+
// own-turn busy+idle pair batched together AFTER the POST resolves cleared suppression via the
|
|
616
|
+
// busy half and then let the idle half sail straight through — double-closing the turn.
|
|
617
|
+
// #2397 round 7 review (MED-1/MED-2/LOW-1) — round 6 only made the reopen one-shot (the FIRST
|
|
618
|
+
// post-close idle lowered this flag again), which still let a SECOND trailing idle in the same
|
|
619
|
+
// window (two late idles; a 429-retry where both attempts' own idles land after send() returns;
|
|
620
|
+
// a foreign child session's busy+idle, since `handleEvent` never filters `properties.sessionID`)
|
|
621
|
+
// double-close. There is no legitimate case this flag ever needs to go back to `false` FOR A
|
|
622
|
+
// DETERMINISTIC turn outcome: OpenCode is headless with no server-initiated turns, and every
|
|
623
|
+
// real turn-boundary transition for a turn `send()` actually SAW END is already emitted
|
|
624
|
+
// EXPLICITLY by `send()` (its own `busy` at each attempt's start) and by
|
|
625
|
+
// `closeSuppressedTurn`/`failTurn` (their own `idle`/`error`) — never by this SSE echo.
|
|
626
|
+
// #2425 — that reasoning has exactly one hole: it assumes `send()` always learns how its own
|
|
627
|
+
// turn ended. `sendMessage`'s POST can instead THROW with the server-side turn's outcome
|
|
628
|
+
// unknown (a client-side timeout on a turn that keeps running upstream) — see `turnAbandoned`
|
|
629
|
+
// below. In that one case ONLY, staying permanent forever would swallow every subsequent real
|
|
630
|
+
// busy/idle for the rest of this adapter instance's life, freezing status at whatever
|
|
631
|
+
// `send()`'s own `finally` guessed. Both SSE doors below therefore also check `turnAbandoned`
|
|
632
|
+
// — the per-session filter (also added for #2425) is what makes that safe: a foreign session's
|
|
633
|
+
// busy/idle can never reach here regardless of `turnAbandoned`, so lifting suppression for OUR
|
|
634
|
+
// OWN session's real events reopens no hazard round 7 was guarding against.
|
|
635
|
+
private suppressProviderIdle = false;
|
|
636
|
+
// #2425 — true once THIS call's own `finally` closed the turn WITHOUT any deterministic
|
|
637
|
+
// success/failure branch having run first (i.e. `sendMessage` or something else in the retry
|
|
638
|
+
// loop threw — a client-side timeout being the motivating case, #2425's incident). The server-
|
|
639
|
+
// side outcome is genuinely unknown in that case, unlike a normal completion or `failTurn`
|
|
640
|
+
// (both of which set `turnClosed` themselves before the `finally` runs, so `turnAbandoned` stays
|
|
641
|
+
// false for them — see the `finally` block). While true, the two SSE doors above stop enforcing
|
|
642
|
+
// the sticky `suppressProviderIdle` for this session's real events, letting the server's own
|
|
643
|
+
// busy/idle re-open and then close the turn — self-healing instead of freezing idle forever.
|
|
644
|
+
// Reset to `false` at the top of every `send()` call: once a NEW call takes over the turn
|
|
645
|
+
// boundary, its own explicit busy/idle is authoritative again, and any stray leftover event from
|
|
646
|
+
// a truly-abandoned previous turn must stay suppressed rather than corrupt the new turn's state.
|
|
647
|
+
private turnAbandoned = false;
|
|
648
|
+
// #2619 — a session re-roll already spent in the current provider-hold episode; cleared by the
|
|
649
|
+
// first turn that goes through (send() → "ok"). Bounds a long wall to ONE history-dropping re-roll.
|
|
650
|
+
private rerolledThisHoldEpisode = false;
|
|
651
|
+
// #2775 — a wall OpenCode's own retry hit during this send(); the session is aborted and send() holds.
|
|
652
|
+
private serverRetryWall?: OpenCodeServerRetry;
|
|
653
|
+
// #2397 round 5 review (MED-1/LOW-2) — true from the moment `send()` is actually about to run an
|
|
654
|
+
// attempt (past the readiness guard, so a "session not ready" throw never sets it) through the
|
|
655
|
+
// call's `finally`.
|
|
656
|
+
// #2397 round 7 review — no longer read by either SSE door (both now key only on the permanent
|
|
657
|
+
// `suppressProviderIdle` above). Still captured by `send()`'s `finally` (round 6, MED-2r) to
|
|
658
|
+
// decide whether THIS call ever actually started an attempt, so a "session not ready" throw
|
|
659
|
+
// never triggers a close for a turn that never began.
|
|
660
|
+
private turnInFlight = false;
|
|
661
|
+
// #2397 round 5 review (MED-1) — true once THIS call's own close has already fired (via
|
|
662
|
+
// `closeSuppressedTurn` or `failTurn`), reset at the top of every `send()` call. Distinct from
|
|
663
|
+
// `suppressProviderIdle` (which deliberately stays raised after firing, per above): this is the
|
|
664
|
+
// idempotency guard that stops the SAME call's multiple close sites — an explicit branch call
|
|
665
|
+
// plus the `finally` fallback — from emitting a second idle for one call.
|
|
666
|
+
// #2425 — also doubles as the double-close guard for the `turnAbandoned` escape hatch: once
|
|
667
|
+
// abandonment force-closes a turn, this stays true until a genuine same-session busy re-arms it
|
|
668
|
+
// (see the busy door), so a stray trailing idle for the SAME abandoned turn finds it already
|
|
669
|
+
// closed and is a no-op rather than a second `idle` dispatch.
|
|
670
|
+
private turnClosed = false;
|
|
671
|
+
// #2431 round 1 (HIGH) — FIFO serializer making `clearContext()` safe against an IN-FLIGHT turn.
|
|
672
|
+
// `agent.clearContext` is dispatched fire-and-forget (dispatchCommand holds no provider-input
|
|
673
|
+
// ticket for it — PROVIDER_INPUT_COMMAND_TYPES is inject-only) and OpenCode's `/message` POST is
|
|
674
|
+
// SYNCHRONOUS (#2389), so the model calling relay_compact_and_resume from within its own turn
|
|
675
|
+
// means the command genuinely runs WHILE send() is still awaiting that POST. send()'s retry loop
|
|
676
|
+
// re-reads process.meta.sessionId every iteration and cannot tell its own #2397 reroll from an
|
|
677
|
+
// external swap, so an unserialized clear redirected the live turn's retries/rerolls — and their
|
|
678
|
+
// reportContextUsage writes — straight into the freshly cleared session. Every writer of that
|
|
679
|
+
// per-call state therefore goes through this queue: both send() call sites (deliverInitialPrompt/
|
|
680
|
+
// deliver — already mutually serialized upstream via providerInput.run, so uncontended between
|
|
681
|
+
// themselves) and clearContext() itself, so a clear lands strictly BETWEEN turns: the live turn
|
|
682
|
+
// completes or fails wholly on the session it started with, then the swap happens, then the next
|
|
683
|
+
// delivery — the reactive continuation inject included — starts on the new session. The wait is
|
|
684
|
+
// bounded by the in-flight turn's own AbortSignal deadline (openCodeTurnTimeoutMsFromEnv), the
|
|
685
|
+
// same bound every turn already carries; there is deliberately no separate timer to fail the
|
|
686
|
+
// command early, because "clear anyway on timeout" would reintroduce exactly this bug.
|
|
687
|
+
private turnQueue: Promise<unknown> = Promise.resolve();
|
|
688
|
+
|
|
689
|
+
// ─── #2539 Phase B — non-destructive soft-steer lane (default-OFF kill switch) ───
|
|
690
|
+
// Durable-until-consumed ledger for positively-persisted soft batches (persistence != delivery).
|
|
691
|
+
private readonly softSteer = new OpenCodeSoftSteerLedger();
|
|
692
|
+
// The soft lane's OWN one-in-flight FIFO (fast loopback POSTs) — deliberately NOT turnQueue:
|
|
693
|
+
// serializing soft steers behind whole turns is the 84-minute #2539 delay this lane exists to
|
|
694
|
+
// remove. Only the POST+reconcile run here; the wake turn (rare, fallback) goes through
|
|
695
|
+
// turnQueue like any normal turn so it owns a real busy/idle pair.
|
|
696
|
+
private softSteerQueue: Promise<unknown> = Promise.resolve();
|
|
697
|
+
// Most recent live process, retained so the turn-close wake hook can reach the session without
|
|
698
|
+
// threading a process through closeSuppressedTurn/closeEventTurn. Best-effort only: a stale
|
|
699
|
+
// process fails safely in send()'s own readiness guard.
|
|
700
|
+
private softSteerProcess: ManagedProcess | undefined;
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* #2544 — the operator-facing capability bit. DEFAULT-ON since the review-gated flip: the
|
|
704
|
+
* deployed-fleet canary soak (one opencode/zai agent, 2026-09-11) proved the end-to-end path
|
|
705
|
+
* through A's seam — fold-in, durable-until-consumed settlement, no dup/loss/hot-loop, no run
|
|
706
|
+
* disruption. The per-offer policy reads `ProviderConfig.activeTurnMessageDelivery` via
|
|
707
|
+
* resolveActiveTurnMessageDelivery (the shared config seam; this adapter's default is "steer",
|
|
708
|
+
* an explicit "defer" stays the per-agent kill switch). Both opencode and zai inherit — one
|
|
709
|
+
* switch, no per-provider settings (A's design A: advertise only enabled+proven).
|
|
710
|
+
*/
|
|
711
|
+
readonly supportsMidTurnSteer = true;
|
|
712
|
+
|
|
713
|
+
// #2459 — event-driven turn identity for TERMINAL-origin turns. OpenCode is headless with no
|
|
714
|
+
// server-initiated turns (the #2397 round-7 invariant), so a session.status:busy arriving for
|
|
715
|
+
// the active session OUTSIDE send() can only be a prompt typed into an attached web-terminal
|
|
716
|
+
// TUI. send() is not running then, so its sticky `suppressProviderIdle` would otherwise swallow
|
|
717
|
+
// the whole turn, and the events would fall back to the STALE `sendTurnId` of the last headless
|
|
718
|
+
// turn — mislabeled grouping. Instead: mint a provider turn id on that busy, associate the
|
|
719
|
+
// turn's user/assistant/tool message ids with it, and close it on the matching idle.
|
|
720
|
+
private eventTurnId: string | undefined;
|
|
721
|
+
// Assistant text accumulated (finalized text parts, active session) while the event turn is
|
|
722
|
+
// open, so its close can emit the turn-final response exactly like send()'s POST path does.
|
|
723
|
+
private eventTurnText: string[] = [];
|
|
724
|
+
// #2605 — reasoning observed this event turn, keyed by part id to the LATEST body seen. The
|
|
725
|
+
// terminal-origin path has no completed-message POST to read back from, so the close must
|
|
726
|
+
// reconcile from what the live door saw — but keyed per part (not a body pool): a growth
|
|
727
|
+
// frame for an already-emitted part only needs the newest snapshot, and an identical body in
|
|
728
|
+
// a LATER turn must never read as already-seen. Cleared with eventTurnText on mint/close.
|
|
729
|
+
// Bounded: part ids are server-unique per turn and the map is reset every turn, so it holds
|
|
730
|
+
// at most one entry per reasoning part of the CURRENT turn — no prefix scan, no growth.
|
|
731
|
+
private eventTurnReasoning = new Map<string, string>();
|
|
732
|
+
// #2459 round 1 (review D) — the EVENT-SOURCE discriminator replacing the round-0 time
|
|
733
|
+
// window. A terminal-origin turn is always PRECEDED by its prompt: the attached TUI's
|
|
734
|
+
// POST creates the user message on the active session before/while the turn's busy edge
|
|
735
|
+
// flows, so the message.updated(role=user) door arms this flag. The busy door admits a
|
|
736
|
+
// terminal turn only when armed (or when no headless turn ever suppressed, or #2425
|
|
737
|
+
// abandonment). A HEADLESS echo — the buffered busy+idle pair of a turn send() just
|
|
738
|
+
// closed — has NO such fresh user message by construction, so it stays suppressed with NO
|
|
739
|
+
// time bound: a delayed echo of any age is indistinguishable from a stale one and both are
|
|
740
|
+
// dropped, restoring #2397 round-7 permanence positively instead of heuristically.
|
|
741
|
+
private pendingTerminalPrompt = false;
|
|
742
|
+
// #2459 round 2 (review D) — the terminal user prompt BUFFER. The [user message.updated →
|
|
743
|
+
// user part → busy] wire ordering means the prompt can arrive BEFORE the busy that mints
|
|
744
|
+
// the terminal turn id; buffering keeps the prompt from publishing a raw session id as its
|
|
745
|
+
// turnId and from splitting off from its trace/final. SINGLE SLOT (the bound): a newer
|
|
746
|
+
// prompt flushes the older one under the session pseudo-turn first. Flush points: busy
|
|
747
|
+
// admission (minted id — the primary path), first trace activity, any active-session idle,
|
|
748
|
+
// send() takeover, and the clear/re-roll swap — the fallback id is ALWAYS the session
|
|
749
|
+
// pseudo-turn or the minted id, never a stale headless sendTurnId.
|
|
750
|
+
private bufferedTerminalPrompt: { body: string; occurredAt: number } | undefined;
|
|
751
|
+
// #2459 round 1 (review D) — permanent marker that at least one headless turn ran on this
|
|
752
|
+
// process. Once true, the busy door admits a terminal-origin turn ONLY through the armed
|
|
753
|
+
// `pendingTerminalPrompt` (fresh first-seen user message), with NO time bound: a headless
|
|
754
|
+
// busy echo of any age stays suppressed. Fresh adapters (no headless turn yet) admit freely,
|
|
755
|
+
// preserving pure-terminal sessions whose busy arrives before any user-message echo.
|
|
756
|
+
private headlessTurnSeen = false;
|
|
757
|
+
// #2459 round 1 (review D) — user-message ids already observed on this process (bounded like
|
|
758
|
+
// the other event-state maps). A REPEAT user message.updated is a buffered duplicate of a
|
|
759
|
+
// turn that already ran (send()'s own POST echo delivered late) and must never re-arm the
|
|
760
|
+
// terminal gate; only a FIRST-SEEN user message is evidence of a freshly typed TUI prompt.
|
|
761
|
+
private seenUserMessageIds = new Set<string>();
|
|
762
|
+
|
|
763
|
+
private readonly sleepImpl: (ms: number) => Promise<void>;
|
|
764
|
+
|
|
765
|
+
// #2484 — the token-usage spine transport, wired by runner-core (Claude's #2482/#2491 seam).
|
|
766
|
+
private readonly apiBudgets = new WeakMap<ManagedProcess, OpenCodeApiBudget>();
|
|
767
|
+
|
|
768
|
+
private tokenUsageReporter?: ProviderTokenUsageReporter;
|
|
769
|
+
// #2484 round 1 review (F2/F3) — collapses the message.updated door's streaming echoes to one
|
|
770
|
+
// spine post per message; flushed early at real turn boundaries (see flushAll() call sites).
|
|
771
|
+
private readonly tokenUsagePoster = new OpenCodeTokenUsagePoster(() => this.tokenUsageReporter);
|
|
772
|
+
|
|
773
|
+
constructor(
|
|
774
|
+
providerOrSleep: string | ((ms: number) => Promise<void>) = "opencode",
|
|
775
|
+
sleepImpl: (ms: number) => Promise<void> = (ms) => Bun.sleep(ms),
|
|
776
|
+
) {
|
|
777
|
+
this.provider = typeof providerOrSleep === "string" ? providerOrSleep : "opencode";
|
|
778
|
+
this.statusSourceId = this.provider;
|
|
779
|
+
this.sleepImpl = typeof providerOrSleep === "function" ? providerOrSleep : sleepImpl;
|
|
780
|
+
openCodeBackend(this.provider);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
onStatusChange(cb: (status: ProviderStatusUpdate) => void): void {
|
|
784
|
+
this.statusCb = cb;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
onTurnActivity(cb: () => void): void {
|
|
788
|
+
this.turnActivityCb = cb;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
onSessionEvent(cb: (event: ProviderSessionEvent) => void): void {
|
|
792
|
+
this.sessionEventCb = cb;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
configureTokenUsageReporter(reporter: ProviderTokenUsageReporter): void {
|
|
796
|
+
this.tokenUsageReporter = reporter;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// #2484 round 2 review (MED-1) — the runner's ONE awaited, time-boxed teardown seam
|
|
800
|
+
// (RunnerCore.settleTerminalTokenUsage, wired at stop() / restartUnexpectedProviderExit /
|
|
801
|
+
// runPreSessionDestroy) previously no-op'd for OpenCode because it bails on a falsy
|
|
802
|
+
// transcriptPath — a Claude-shaped precondition OpenCode never has (it has no transcript file
|
|
803
|
+
// to tail; #2484's capture is event-sourced from the message.updated SSE door). Owning that
|
|
804
|
+
// bail here — ignore transcriptPath, just settle the poster — means the crash-exit and
|
|
805
|
+
// pre-session-destroy paths (which never call adapter.shutdown()) now reach the same debounced
|
|
806
|
+
// report shutdown() already flushes, instead of losing the session's final token counts.
|
|
807
|
+
async captureFinalTokenUsage(_transcriptPath: string | undefined, _ctx: ProviderSessionTurnContext): Promise<void> {
|
|
808
|
+
await this.tokenUsagePoster.flushAll();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
supportsUnexpectedExitRestart(): boolean {
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
async spawn(config: RunnerSpawnConfig): Promise<ManagedProcess> {
|
|
816
|
+
assertOpenCodeApprovalMode(config.approvalMode);
|
|
817
|
+
const launch = await resolveOpenCodeLaunchModel(config, config.providerConfig as ProviderConfig, this.provider);
|
|
818
|
+
openCodeApiKey(config, launch.qualified);
|
|
819
|
+
const args = this.buildSpawnArgs(config, config.providerConfig as ProviderConfig, launch);
|
|
820
|
+
const apiBudget = launch.backendID === "openai" ? new OpenCodeApiBudget(config) : undefined;
|
|
821
|
+
await apiBudget?.start(relayEnv(config.env, "AGENT_RELAY_PROVIDER_SESSION_ID") ?? crypto.randomUUID());
|
|
822
|
+
const server = await spawnOpenCodeServer(args, apiBudget);
|
|
823
|
+
const password = args.env.OPENCODE_SERVER_PASSWORD!;
|
|
824
|
+
const client = new OpenCodeClient(args.env.OPENCODE_SERVER_URL!, password, fetch, this.provider);
|
|
825
|
+
try {
|
|
826
|
+
await client.waitForReady();
|
|
827
|
+
const sessionTitle = config.label ?? config.agentId;
|
|
828
|
+
const sessionId = await client.createSession(sessionTitle);
|
|
829
|
+
const events = new AbortController();
|
|
830
|
+
// sessionTitle is also read by a #2397 session re-roll (a fresh session created mid-flight
|
|
831
|
+
// after repeated retryable errors); sessionId is MUTATED IN PLACE there, so every other
|
|
832
|
+
// reader of process.meta.sessionId (send/interrupt/shutdown) picks the new session up too.
|
|
833
|
+
// #2459 — the launch metadata terminalAttachSpec() needs is retained here as well:
|
|
834
|
+
// resolved executable, loopback server URL, agent cwd and the server password. These live
|
|
835
|
+
// ONLY in this runner-local memory (and the password ONLY re-emerges as the guest process
|
|
836
|
+
// env inside the attach spec); they are never persisted, logged, or returned anywhere else.
|
|
837
|
+
// #2599 — the payload record (when present) pins meta.model to the server-computed
|
|
838
|
+
// qualified name, so later reportContextUsage calls keep payload precedence instead of
|
|
839
|
+
// re-deriving from a possibly-ambiguous alias. Manifest path unchanged when absent.
|
|
840
|
+
// #2659 — meta.model is the normalized launch model; turns split it verbatim, never re-resolve.
|
|
841
|
+
const process: ManagedProcess = { pid: server.pid, process: server, meta: { client, sessionId, events, model: launch.qualified, launchModel: launch.qualified, sessionTitle, config, relayContextSent: false, launchContextSent: false, attachCommand: args.command, cwd: args.cwd, serverUrl: args.env.OPENCODE_SERVER_URL!, serverPassword: password, stateDir: args.env.XDG_STATE_HOME } };
|
|
842
|
+
if (apiBudget) this.apiBudgets.set(process, apiBudget);
|
|
843
|
+
// #2443 — both used to be a bare `void promise.catch(...)`: a synchronous throw from
|
|
844
|
+
// `this.statusCb`/`handleEvent` inside the fulfillment or catch handler produced a SECOND,
|
|
845
|
+
// truly unhandled rejection nothing consumed (Bun exits(1) on that; nothing respawns the
|
|
846
|
+
// runner — #1618). `fireAndForget` guarantees the rejection — from either the initial call
|
|
847
|
+
// or the handler itself — is always logged, never escapes.
|
|
848
|
+
void fireAndForget(`opencode SSE subscribe (session ${sessionId})`, () => client.subscribe(events.signal, (event) => this.handleEvent(event, process)), (message) => logger.error("opencode", message));
|
|
849
|
+
void fireAndForget(`opencode server exit status callback (session ${sessionId})`, async () => {
|
|
850
|
+
const code = await server.exited;
|
|
851
|
+
await this.tokenUsagePoster.flushAll();
|
|
852
|
+
await apiBudget?.settle().catch(() => {});
|
|
853
|
+
this.statusCb(code === 0 ? "offline" : "error");
|
|
854
|
+
}, (message) => logger.error("opencode", message));
|
|
855
|
+
return process;
|
|
856
|
+
} catch (error) {
|
|
857
|
+
killPid(server.pid, "SIGKILL");
|
|
858
|
+
await server.exited;
|
|
859
|
+
await apiBudget?.settle().catch(() => {});
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// #2659 — spawn() passes the registry-normalized launch model; direct callers resolve statically.
|
|
865
|
+
buildSpawnArgs(config: RunnerSpawnConfig, providerConfig: ProviderConfig, launch = openCodeLaunchModel(config.model, this.provider, openCodeModelRecord(config.modelRecord, this.provider))): SpawnArgs {
|
|
866
|
+
return buildOpenCodeSpawnArgs(config, providerConfig, this.provider, launch);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* #2459 — the web-terminal attach spec for an interactive `opencode attach` guest TUI,
|
|
871
|
+
* reusing the codex guest-terminal path downstream (runner /terminal/attach-spec →
|
|
872
|
+
* orchestrator guest tmux → Relay's authenticated terminal proxy → dashboard xterm).
|
|
873
|
+
*
|
|
874
|
+
* Fail-closed BEFORE readiness/session: every input is read from the launch metadata
|
|
875
|
+
* retained at spawn() and a missing piece throws rather than degrading. The command is
|
|
876
|
+
* exactly the pinned-CLI seam the #2459 feasibility verified against v1.18.21:
|
|
877
|
+
* `opencode attach <loopback server URL> --session <active session id> --dir <agent cwd>
|
|
878
|
+
* --pure`. `--dir` MUST equal the server/session cwd — OpenCode sessions are
|
|
879
|
+
* project-directory scoped and attach fails (or silently diverges) when the client and
|
|
880
|
+
* server directories differ.
|
|
881
|
+
*
|
|
882
|
+
* SECRET HANDLING (round-1 review A): the generated server password NEVER travels in the
|
|
883
|
+
* spec — not as `--password`, not in the URL, and (since round 1) not in a response-body
|
|
884
|
+
* env record either. terminalAttachSpec() pre-stages it in a 0600 env file under the
|
|
885
|
+
* runner's own XDG state dir and the spec carries only that PATH (`envFile`); the
|
|
886
|
+
* orchestrator's guest wrapper sources the file into the guest process environment, so the
|
|
887
|
+
* secret materializes solely in the guest process env. The server URL is loopback-only.
|
|
888
|
+
*/
|
|
889
|
+
async terminalAttachSpec(process: ManagedProcess): Promise<TerminalAttachSpec> {
|
|
890
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
891
|
+
if (!sessionId) throw new Error("OpenCode session is not ready");
|
|
892
|
+
const serverUrl = typeof process.meta?.serverUrl === "string" && process.meta.serverUrl ? process.meta.serverUrl : "";
|
|
893
|
+
if (!serverUrl) throw new Error("OpenCode server URL is unavailable");
|
|
894
|
+
const serverPassword = typeof process.meta?.serverPassword === "string" && process.meta.serverPassword ? process.meta.serverPassword : "";
|
|
895
|
+
if (!serverPassword) throw new Error("OpenCode server password is unavailable");
|
|
896
|
+
const stateDir = typeof process.meta?.stateDir === "string" && process.meta.stateDir ? process.meta.stateDir : "";
|
|
897
|
+
if (!stateDir) throw new Error("OpenCode state dir is unavailable");
|
|
898
|
+
const attachCommand = typeof process.meta?.attachCommand === "string" && process.meta.attachCommand ? process.meta.attachCommand : "opencode";
|
|
899
|
+
const cwd = typeof process.meta?.cwd === "string" && process.meta.cwd ? process.meta.cwd : globalThis.process.cwd();
|
|
900
|
+
// Pre-stage the credential where the guest can read it: 0600 file in the runner-owned
|
|
901
|
+
// 0700 XDG state dir. Path-only propagation — the contents never leave this user's
|
|
902
|
+
// file system boundary, and nothing here lands in argv, HTTP bodies, logs, or persistence.
|
|
903
|
+
const envFile = join(stateDir, "terminal-env");
|
|
904
|
+
writeFileSync(envFile, `OPENCODE_SERVER_PASSWORD=${shellEscape(serverPassword)}\n`, { mode: 0o600 });
|
|
905
|
+
chmodSync(envFile, 0o600);
|
|
906
|
+
return {
|
|
907
|
+
mode: "guest",
|
|
908
|
+
provider: this.provider,
|
|
909
|
+
cwd,
|
|
910
|
+
command: [attachCommand, "attach", serverUrl, "--session", sessionId, "--dir", cwd, "--pure"],
|
|
911
|
+
envFile,
|
|
912
|
+
title: "OpenCode terminal",
|
|
913
|
+
ttlMs: 60 * 60 * 1000,
|
|
914
|
+
generation: openCodeTerminalGeneration(sessionId),
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* #2459 — the CURRENT attachment generation (derived from the active session id), read by
|
|
920
|
+
* the runner control server's GET /terminal/generation so the orchestrator can detect a
|
|
921
|
+
* session rotation without learning the session id or any credential. undefined when the
|
|
922
|
+
* session is not ready (nothing to check).
|
|
923
|
+
*/
|
|
924
|
+
terminalAttachGeneration(process: ManagedProcess): string | undefined {
|
|
925
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
926
|
+
return sessionId ? openCodeTerminalGeneration(sessionId) : undefined;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* #2431 round 1 (HIGH) — run `operation` as the exclusive holder of the turn queue (see the
|
|
931
|
+
* `turnQueue` field doc for why this exists). FIFO: an operation enqueued while a turn is
|
|
932
|
+
* mid-flight starts only after that turn's promise settles, success or failure.
|
|
933
|
+
*/
|
|
934
|
+
private enqueueTurn<T>(operation: () => Promise<T>): Promise<T> {
|
|
935
|
+
const settled = this.turnQueue.then(operation, operation);
|
|
936
|
+
this.turnQueue = settled.then(() => undefined, () => undefined);
|
|
937
|
+
return settled;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
async deliverInitialPrompt(process: ManagedProcess, prompt: string, options?: PromptOfferOptions): Promise<void | "held"> {
|
|
941
|
+
// #2397 round 3 review (LOW-5r) — `send()`'s boolean is intentionally NOT surfaced as a throw
|
|
942
|
+
// here: this method's `Promise<void>` contract is shared by every adapter, and a throw would
|
|
943
|
+
// route into attemptInitialPromptDelivery's (runner-core.ts) generic retry-and-eventually-
|
|
944
|
+
// give-up path, re-sending the SAME prompt on top of the retry+reroll budget `send()` already
|
|
945
|
+
// exhausted. Instead, runner-core reads `this.terminalFailure` (set by failTurn's own `error`
|
|
946
|
+
// status, cleared by the `busy` edge this very call started with) right after this resolves, so
|
|
947
|
+
// it can log the terminal outcome correctly without inventing a second retry loop.
|
|
948
|
+
// #2619 — a refusal under a provider hold is surfaced ("held"): the runner parks the prompt.
|
|
949
|
+
if (await this.enqueueTurn(() => this.send(process, prompt, options?.inputObserver)) === "held") return "held";
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
async deliver(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<DeliverResult> {
|
|
953
|
+
// #2539 Phase B — the paste-instant soft-steer routing (Phase A's single-call-path pattern,
|
|
954
|
+
// codex.ts:435): a mid-turn offer with steerable content goes through the adapter's
|
|
955
|
+
// deliverMidTurn (noReply persist, settlement deferred through ctx); boundary-only classes
|
|
956
|
+
// and the lane-off/idle cases keep the turn-end path below. Policy resolves per offer from
|
|
957
|
+
// the spawn config; this adapter's default is "steer" (#2544 flip), "defer" the kill switch.
|
|
958
|
+
if (messages.length > 0 && this.softSteerPolicyFor(process) === "steer" && ctx?.isRecipientMidTurn?.() === true) {
|
|
959
|
+
// r3 HIGH — steer ONLY the steerable partition, never the whole batch: the omitted ids
|
|
960
|
+
// (claimables and synthetic memory/resume injections) stay in this
|
|
961
|
+
// call's un-acked tail, so the runner's settle requeues them untouched onto the turn-end
|
|
962
|
+
// path. Folding the full batch into the one noReply row would carry a claimable's claim
|
|
963
|
+
// lease into the hours-long awaiting-consumption window with no renewal (lease expiry →
|
|
964
|
+
// duplicate task delivery), reorder injections, —
|
|
965
|
+
// violating partitionMidTurnDelivery's contract. (codex.ts does the same partition INSIDE
|
|
966
|
+
// its deliverMidTurn.)
|
|
967
|
+
const { steerable } = partitionMidTurnDelivery(messages);
|
|
968
|
+
if (steerable.length > 0) return this.deliverMidTurn(process, steerable, ctx);
|
|
969
|
+
}
|
|
970
|
+
// #2397 round 3 (MED-3) — only a message `send()` actually got through (no terminal failure)
|
|
971
|
+
// is acknowledged. runner-core marks every acknowledged id read and re-queues everything else
|
|
972
|
+
// (runner-core.ts ~969-995); returning ALL ids unconditionally — the old behavior — marked a
|
|
973
|
+
// message that provoked 8 failed POSTs and a terminal failure as delivered+read though the
|
|
974
|
+
// model never processed it: recorded delivered, silently lost. #2397 round 3 (MED-6) — once a
|
|
975
|
+
// message fails terminally the session is now `error`/offline, so stop rather than burn the
|
|
976
|
+
// same ~16-minute worst-case retry budget on every remaining queued message in this batch.
|
|
977
|
+
// #2397 round 3 review (MED-3r) — `terminalFailureIds` tells runner-core WHY this id was not
|
|
978
|
+
// acknowledged, so its re-drain paces with bounded exponential backoff instead of hammering an
|
|
979
|
+
// upstream that is already rate-limiting on the very next tick.
|
|
980
|
+
const acknowledgedIds: number[] = [];
|
|
981
|
+
const terminalFailureIds: number[] = [];
|
|
982
|
+
const providerHeldIds: number[] = [];
|
|
983
|
+
for (const message of messages) {
|
|
984
|
+
let disarmOpener: (() => void) | undefined; // #2840 — each send is its own turn, opened by this message
|
|
985
|
+
try {
|
|
986
|
+
const outcome = await this.enqueueTurn(() => { disarmOpener = ctx?.openTurnFor?.([message]); return this.send(process, providerMessageText([message]), ctx?.inputObserver); });
|
|
987
|
+
if (outcome !== "ok") disarmOpener?.();
|
|
988
|
+
if (outcome === "failed") {
|
|
989
|
+
terminalFailureIds.push(message.id);
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
// #2619 — refused under a provider hold: neither delivered nor failed. Stop the batch and
|
|
993
|
+
// hand the refused id AND the untried tail to the runner to park — they would only hit
|
|
994
|
+
// the same wall; the runner redelivers them once a provider turn goes through again.
|
|
995
|
+
if (outcome === "held") {
|
|
996
|
+
for (const rest of messages.slice(messages.indexOf(message))) providerHeldIds.push(rest.id);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
// #2534 review (LOW-3) — symmetric with runner-core's attemptInitialPromptDelivery: a
|
|
1001
|
+
// PromptAcceptedError means the provider ACCEPTED this queued message's POST and the
|
|
1002
|
+
// completion await failed/aborted on a still-running turn. Propagating it reached
|
|
1003
|
+
// runner-core's drain, which requeued the whole batch and double-POSTed into that live
|
|
1004
|
+
// turn — acknowledge the accepted message instead (its output streams via the normal
|
|
1005
|
+
// session events). Any other failure still propagates: nothing delivered, whole batch
|
|
1006
|
+
// requeued.
|
|
1007
|
+
if (!(error instanceof PromptAcceptedError)) { disarmOpener?.(); throw error; }
|
|
1008
|
+
}
|
|
1009
|
+
acknowledgedIds.push(message.id);
|
|
1010
|
+
}
|
|
1011
|
+
return { acknowledgedIds, terminalFailureIds, ...(providerHeldIds.length ? { providerHeldIds } : {}) };
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* #2539 Phase B — the soft-steer op (see the adapter-interface doc): persist the batch into
|
|
1016
|
+
* the in-flight turn WITHOUT touching any send() turn state. Non-negotiable invariants:
|
|
1017
|
+
* - send()/its instance fields stay non-reentrant: this path must not read or write
|
|
1018
|
+
* sendTurnId/turnInFlight/suppressProviderIdle/turnClosed/retry/reroll and must never emit a
|
|
1019
|
+
* second busy/idle (the ongoing turn's pair is the only truthful one).
|
|
1020
|
+
* - ONE composed OpenCode user row per batch (sorted by Relay id), one caller messageID, one
|
|
1021
|
+
* loopback POST at a time (adapter-local FIFO, not turnQueue).
|
|
1022
|
+
* - Session fencing from intent time: the sessionId is captured at entry and re-checked
|
|
1023
|
+
* immediately before AND after the POST — a pre-POST swap defers the batch (never acked onto
|
|
1024
|
+
* a session that is gone); a swap INSIDE the POST window (batch already persisted/tracked)
|
|
1025
|
+
* targeted-reoffers it to the normal queue — never stranded on the dead session, never
|
|
1026
|
+
* reposted into the new one.
|
|
1027
|
+
* - Paste-instant authority: ctx.isRecipientMidTurn() is consulted at the actual POST instant;
|
|
1028
|
+
* a turn that already ended defers (or, when it ends inside the post-POST window, persists +
|
|
1029
|
+
* arms the one bounded idle wake) — never a claimed in-turn success from a stale precheck.
|
|
1030
|
+
* - At-least-once with explicit ambiguity: one POST, then (only on ambiguity) ONE bounded
|
|
1031
|
+
* exact-id reconcile; a positive persistence is NEVER re-POSTed.
|
|
1032
|
+
*
|
|
1033
|
+
* #2539 Phase A reconciliation — the result speaks the shared DeliverResult vocabulary:
|
|
1034
|
+
* persisted/ambiguous ids return as `awaitingConsumptionIds` (their settlement completes later
|
|
1035
|
+
* through ctx.settleMidTurnConsumed / ctx.reofferMidTurnIds — persistence is not delivery);
|
|
1036
|
+
* everything NOT accepted (lane off, boundary-only, cap overflow, pre-POST fencing loss) is
|
|
1037
|
+
* simply omitted, so the runner's settle requeues it for turn-end delivery. A
|
|
1038
|
+
* reconciled-ABSENT POST (definitive non-persistence) throws so the runner paces the retry.
|
|
1039
|
+
*/
|
|
1040
|
+
async deliverMidTurn(process: ManagedProcess, messages: Message[], ctx?: ProviderDeliverContext): Promise<DeliverResult> {
|
|
1041
|
+
const nothingAccepted: DeliverResult = { acknowledgedIds: [], awaitingConsumptionIds: [], terminalFailureIds: [] };
|
|
1042
|
+
const allIds = messages.map((message) => message.id);
|
|
1043
|
+
if (this.softSteerPolicyFor(process) !== "steer" || messages.length === 0) {
|
|
1044
|
+
return nothingAccepted;
|
|
1045
|
+
}
|
|
1046
|
+
const client = process.meta?.client as OpenCodeClient | undefined;
|
|
1047
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
1048
|
+
if (!client || !sessionId) {
|
|
1049
|
+
return nothingAccepted;
|
|
1050
|
+
}
|
|
1051
|
+
this.softSteerProcess = process;
|
|
1052
|
+
this.softSteer.setCallbacks({ settleMidTurnConsumed: ctx?.settleMidTurnConsumed, reofferMidTurnIds: ctx?.reofferMidTurnIds });
|
|
1053
|
+
const composed = composeSoftSteerText(messages);
|
|
1054
|
+
if (composed.included.length === 0) {
|
|
1055
|
+
return nothingAccepted;
|
|
1056
|
+
}
|
|
1057
|
+
const settled = this.softSteerQueue.then(
|
|
1058
|
+
() => this.softSteerDeliverBatch(process, client, sessionId, ctx, composed),
|
|
1059
|
+
() => this.softSteerDeliverBatch(process, client, sessionId, ctx, composed),
|
|
1060
|
+
);
|
|
1061
|
+
this.softSteerQueue = settled.then(() => undefined, () => undefined);
|
|
1062
|
+
return settled;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
private async softSteerDeliverBatch(process: ManagedProcess, client: OpenCodeClient, sessionIdAtIntent: string, ctx: ProviderDeliverContext | undefined, composed: ReturnType<typeof composeSoftSteerText>): Promise<DeliverResult> {
|
|
1066
|
+
const nothingAccepted: DeliverResult = { acknowledgedIds: [], awaitingConsumptionIds: [], terminalFailureIds: [] };
|
|
1067
|
+
const deferredOverflow = composed.deferred.map((message) => message.id);
|
|
1068
|
+
const included = composed.included;
|
|
1069
|
+
const includedIds = included.map((message) => message.id);
|
|
1070
|
+
// Fencing from intent time: a clear/reroll swap between the seam's decision and this POST
|
|
1071
|
+
// means the turn this batch was composed for is gone.
|
|
1072
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
1073
|
+
if (sessionId !== sessionIdAtIntent || ctx?.isRecipientMidTurn?.() !== true) {
|
|
1074
|
+
return nothingAccepted;
|
|
1075
|
+
}
|
|
1076
|
+
const messageID = newSoftSteerMessageID();
|
|
1077
|
+
ctx?.inputObserver?.offered(Date.now(), true);
|
|
1078
|
+
let outcome = await client.sendMessageNoReply(sessionId, composed.text, messageID, OPENCODE_SOFT_STEER_POST_TIMEOUT_MS);
|
|
1079
|
+
if (outcome === "unknown") {
|
|
1080
|
+
// One bounded exact-id reconcile before ANY reoffer decision; never double-POST after
|
|
1081
|
+
// positive persistence.
|
|
1082
|
+
const found = await client.hasMessageWithId(sessionId, messageID, OPENCODE_SOFT_STEER_RECONCILE_TIMEOUT_MS);
|
|
1083
|
+
if (found === true) outcome = "persisted";
|
|
1084
|
+
else if (found === false) outcome = "absent";
|
|
1085
|
+
}
|
|
1086
|
+
if (outcome === "persisted") {
|
|
1087
|
+
const tracked = this.softSteer.noteBatch(messageID, includedIds, sessionId, { inputObserver: ctx?.inputObserver });
|
|
1088
|
+
logger.info("opencode", `soft-steer batch ${messageID} persisted (${includedIds.length} relay id(s), ${composed.text.length} chars) on session ${sessionId}`);
|
|
1089
|
+
if (tracked) {
|
|
1090
|
+
// Post-POST race windows (the row is persisted either way):
|
|
1091
|
+
// - Session swapped INSIDE the POST→noteBatch window: the swap's own sessionReplaced
|
|
1092
|
+
// scan ran before this noteBatch, so the batch is stranded on the dead session —
|
|
1093
|
+
// targeted reoffer now (see reofferStaleSessionBatches).
|
|
1094
|
+
// - Turn ended on the SAME session: the row strands until the next turn (live-proven
|
|
1095
|
+
// at-stop behavior) — arm the bounded wake.
|
|
1096
|
+
const sessionIdNow = stringValue(process.meta?.sessionId);
|
|
1097
|
+
if (sessionIdNow !== sessionId) await this.reofferStaleSessionBatches(sessionIdNow, sessionId);
|
|
1098
|
+
else if (ctx?.isRecipientMidTurn?.() !== true) this.maybeArmSoftSteerWake(process);
|
|
1099
|
+
return { acknowledgedIds: [], awaitingConsumptionIds: [...includedIds], terminalFailureIds: [] };
|
|
1100
|
+
}
|
|
1101
|
+
// Over the ledger's backpressure cap: the row IS persisted, so it must not be re-posted,
|
|
1102
|
+
// but the lane cannot track it to settlement — omit it from the result so the normal lane
|
|
1103
|
+
// carries it (duplicate text at the next boundary is the designed degradation, not silent
|
|
1104
|
+
// loss).
|
|
1105
|
+
return nothingAccepted;
|
|
1106
|
+
}
|
|
1107
|
+
if (outcome === "absent") {
|
|
1108
|
+
// Definitive non-persistence after the bounded reconcile: nothing was accepted, so throw —
|
|
1109
|
+
// the runner's drain catch requeues the whole batch and paces the retry at its 1s failure
|
|
1110
|
+
// cadence (the same defer-by-throw shape codex uses), instead of an unpaced drain-tick loop.
|
|
1111
|
+
throw new Error(`opencode soft-steer POST ${messageID} did not persist (reconciled absent) on session ${sessionId}`);
|
|
1112
|
+
}
|
|
1113
|
+
// Unknown after POST + reconcile: hold the ids in the ledger (the row may exist) — resolved by
|
|
1114
|
+
// the consumption door, session replacement, or shutdown reoffer. Never an immediate repost.
|
|
1115
|
+
// Force-tracked even at the ledger cap: the cap gates new POSITIVE persists (backpressure to
|
|
1116
|
+
// the normal queue), never the RESOLVABILITY of an already-attempted POST — an untracked
|
|
1117
|
+
// unknown batch could never be resolved by any ledger exit while this contract reports it as
|
|
1118
|
+
// held (silent orphan under exactly the long-turn + one-timeout scenario #2539 exists for).
|
|
1119
|
+
this.softSteer.noteBatch(messageID, includedIds, sessionId, { force: true, inputObserver: ctx?.inputObserver });
|
|
1120
|
+
// Same swap-inside-the-POST-window fence as the persisted path: a batch noted onto a dead
|
|
1121
|
+
// session can never be resolved there (the same-session consumption door and the
|
|
1122
|
+
// new-session wake both miss it), so reoffer immediately instead of holding it until the
|
|
1123
|
+
// NEXT swap/shutdown — the delayed silent-loss shape for non-claimable messages. The row
|
|
1124
|
+
// (if it exists) stays on the dead session, unconsumed — at-least-once holds.
|
|
1125
|
+
const sessionIdAfterReconcile = stringValue(process.meta?.sessionId);
|
|
1126
|
+
if (sessionIdAfterReconcile !== sessionId) await this.reofferStaleSessionBatches(sessionIdAfterReconcile, sessionId);
|
|
1127
|
+
else logger.warn("opencode", `soft-steer batch ${messageID} outcome UNKNOWN after POST + reconcile; holding ${includedIds.length} relay id(s) until consumption or session end`);
|
|
1128
|
+
return { acknowledgedIds: [], awaitingConsumptionIds: [...includedIds], terminalFailureIds: [] };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// The spawn config the adapter was launched with (meta.config is the RunnerSpawnConfig handed
|
|
1132
|
+
// to spawn()); the steer policy reads its providerConfig — never a provider-name branch.
|
|
1133
|
+
// Same shape as codex's spawnProviderConfig (kept per-adapter, matching the landed precedent).
|
|
1134
|
+
// #2544: the absent-config default is "steer" (default-on); an explicit "defer" is the
|
|
1135
|
+
// per-agent kill switch, resolved through the shared seam like codex's "steer" default.
|
|
1136
|
+
private softSteerPolicyFor(process: ManagedProcess): "steer" | "defer" {
|
|
1137
|
+
const config = (process.meta?.config as { providerConfig?: ProviderConfig } | undefined)?.providerConfig;
|
|
1138
|
+
return resolveActiveTurnMessageDelivery(config, "steer");
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Targeted post-POST reoffer for the swap-inside-the-POST-window race: the clear/reroll
|
|
1143
|
+
* swap's own sessionReplaced(newId) scan ran BEFORE the just-attempted batch was noted, so
|
|
1144
|
+
* that batch (and any other stale-session entry) would sit stranded on the dead session —
|
|
1145
|
+
* invisible to the same-session consumption door, unarmable by the new-session wake. Reoffer
|
|
1146
|
+
* everything not bound to the live session now (an empty live id reoffers all: nothing is
|
|
1147
|
+
* consumable without a live session). NEVER a repost into the new session. The deliverMidTurn
|
|
1148
|
+
* result still reports the POST's own truthful outcome; the onReoffer settlement is the
|
|
1149
|
+
* authoritative resolution — the same interplay as the clear/reroll call sites, whose
|
|
1150
|
+
* reoffers arrive after earlier results already reported persistedIds.
|
|
1151
|
+
*/
|
|
1152
|
+
private async reofferStaleSessionBatches(liveSessionId: string | undefined, deadSessionId: string): Promise<number[]> {
|
|
1153
|
+
// An undefined/empty live id reoffers ALL entries: nothing is consumable without a live session.
|
|
1154
|
+
const reoffered = await this.softSteer.sessionReplaced(liveSessionId ?? "");
|
|
1155
|
+
if (reoffered.length > 0) {
|
|
1156
|
+
logger.info("opencode", `soft-steer: session swapped inside the POST window (${deadSessionId} → ${liveSessionId || "<none>"}); reoffered ${reoffered.length} relay id(s) to the normal queue`);
|
|
1157
|
+
}
|
|
1158
|
+
return reoffered;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* #2539 Phase B — the bounded idle-wake fallback: the provider just went idle with
|
|
1163
|
+
* positively-persisted-but-unconsumed soft batches (mid-run reload missed them — the live
|
|
1164
|
+
* at-stop strand, or an unknown-outcome batch). Arm EXACTLY ONE content-free wake turn through
|
|
1165
|
+
* turnQueue (a NORMAL send() turn: real busy/idle pair, full retry semantics — no second turn
|
|
1166
|
+
* machinery, no resend of the steer payload). Event-driven only: armed from turn-close edges
|
|
1167
|
+
* and the post-POST race check, never from a wall-clock timer. Its completion settles the
|
|
1168
|
+
* batches it was armed for (their rows are provably in the session context by then); the
|
|
1169
|
+
* consumption door remains the primary settle path and stays idempotent with this one.
|
|
1170
|
+
*/
|
|
1171
|
+
private maybeArmSoftSteerWake(process?: ManagedProcess): void {
|
|
1172
|
+
if (this.softSteer.isWakePending()) return;
|
|
1173
|
+
const target = process ?? this.softSteerProcess;
|
|
1174
|
+
const sessionId = stringValue(target?.meta?.sessionId);
|
|
1175
|
+
if (!target || !sessionId || !this.softSteer.hasAwaiting(sessionId)) return;
|
|
1176
|
+
const armed = this.softSteer.armWake(sessionId);
|
|
1177
|
+
if (!armed) return;
|
|
1178
|
+
logger.info("opencode", `soft-steer wake armed on session ${sessionId} for ${armed.targets.length} unconsumed batch(es)`);
|
|
1179
|
+
void this.enqueueTurn(() => this.send(target, OPENCODE_SOFT_STEER_WAKE_TEXT))
|
|
1180
|
+
.then(async (ok) => {
|
|
1181
|
+
// Failed wake turn: leave the entries pending — session replacement / shutdown reoffer
|
|
1182
|
+
// them to the normal queue (a failed session must not mark anything delivered, and the
|
|
1183
|
+
// old delete-then-maybe-notify order here silently dropped them).
|
|
1184
|
+
if (ok !== "ok") return;
|
|
1185
|
+
const settled = await this.softSteer.settleWakeTargets(armed);
|
|
1186
|
+
if (settled.length > 0) logger.info("opencode", `soft-steer wake settled ${settled.length} relay id(s) on session ${sessionId}`);
|
|
1187
|
+
})
|
|
1188
|
+
.catch((error) => logger.warn("opencode", `soft-steer wake turn failed: ${errMessage(error)}`))
|
|
1189
|
+
.finally(() => this.softSteer.endWake());
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
async interrupt(process: ManagedProcess): Promise<Record<string, unknown>> {
|
|
1193
|
+
const client = process.meta?.client as OpenCodeClient | undefined;
|
|
1194
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
1195
|
+
if (!client || !sessionId) throw new Error("OpenCode session is not ready");
|
|
1196
|
+
await client.abort(sessionId);
|
|
1197
|
+
return { sessionId, method: "session.abort" };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* #2431 — self-resume clear-inject support: mint a fresh, history-less session and make it the
|
|
1202
|
+
* active one. This REUSES the proven #2397 re-roll seam (the same `POST /session` via
|
|
1203
|
+
* `createSession`, which explicitly drops conversation history, and the same in-place
|
|
1204
|
+
* `process.meta.sessionId` swap so every reader — send/interrupt/shutdown and the SSE doors'
|
|
1205
|
+
* foreign-session filter — picks the new id up with no separate bookkeeping); it does not invent
|
|
1206
|
+
* a second session-minting mechanism.
|
|
1207
|
+
*
|
|
1208
|
+
* Ordering/composition with the turn state machine (#2397/#2425/#2427/#2428) is ENFORCED, not
|
|
1209
|
+
* assumed (round 1 HIGH): the whole swap runs inside `enqueueTurn`, so a clear that arrives while
|
|
1210
|
+
* a turn is mid-flight — which DOES happen, since the model calls relay_compact_and_resume from
|
|
1211
|
+
* within its own synchronous `/message` turn and dispatchCommand gives agent.clearContext no
|
|
1212
|
+
* provider-input ticket — waits for that turn to settle and completes it wholly on the session
|
|
1213
|
+
* it started with before minting anything. The reactive continuation inject is separately
|
|
1214
|
+
* serialized behind deliveries through providerInput.run (`PROVIDER_INPUT_COMMAND_TYPES`), so the
|
|
1215
|
+
* first thing that touches the new session is a delivery, never a half-swapped retry. The last
|
|
1216
|
+
* closed turn left `turnClosed` set and `suppressProviderIdle` sticky per round-5 MED-1; those
|
|
1217
|
+
* fields are deliberately NOT touched — resetting them could double-close an already-closed turn,
|
|
1218
|
+
* and any trailing SSE event belonging to the PRE-clear turn carries the OLD sessionID, which the
|
|
1219
|
+
* #2425 foreign-session filter now drops against the NEW active id. Same reasoning leaves
|
|
1220
|
+
* `messageRoles`/`emittedTextParts`/`emittedToolSteps` alone: ids are server-global on this one
|
|
1221
|
+
* process and pruned to OPENCODE_EVENT_STATE_LIMIT regardless (the #2397 re-roll leaves them
|
|
1222
|
+
* too). The SSE subscription is per-server, not per-session, so the new session stays fully
|
|
1223
|
+
* observable through the existing wiring.
|
|
1224
|
+
*
|
|
1225
|
+
* Only relayContextSent re-arms after the fresh session exists, so the first delivery carries
|
|
1226
|
+
* the generic Relay context again. The systemPromptAppend launch block is intentionally NOT
|
|
1227
|
+
* re-sent here: server startup-context and memory-injection seams own post-boundary charter
|
|
1228
|
+
* and memory re-delivery.
|
|
1229
|
+
* The fresh ContextState mirrors Codex's clearContext shape: utilization 0 IS ground truth
|
|
1230
|
+
* immediately after a wipe.
|
|
1231
|
+
*/
|
|
1232
|
+
async clearContext(process: ManagedProcess): Promise<Record<string, unknown>> {
|
|
1233
|
+
return this.enqueueTurn(() => this.swapSessionForClear(process));
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
private async swapSessionForClear(process: ManagedProcess): Promise<Record<string, unknown>> {
|
|
1237
|
+
const client = process.meta?.client as OpenCodeClient | undefined;
|
|
1238
|
+
const previousSessionId = stringValue(process.meta?.sessionId);
|
|
1239
|
+
if (!client || !previousSessionId) throw new Error("OpenCode session is not ready");
|
|
1240
|
+
// #2459 — the session swap is also an attachment-generation rotation: any open event-driven
|
|
1241
|
+
// terminal turn belongs to the OLD session (its matching idle carries the old sessionID and
|
|
1242
|
+
// would be dropped by the foreign-session filter, stranding runner-core busy). Close it on
|
|
1243
|
+
// the old session's truth before the swap; the orchestrator's generation check kills the
|
|
1244
|
+
// stale guest TUI on its next input/stream contact.
|
|
1245
|
+
// #2459 round 2 (review D) — flush a buffered terminal prompt under the OLD session
|
|
1246
|
+
// pseudo-turn before the swap (the operator typed there; the buffer must not leak into
|
|
1247
|
+
// the new session), then close any open terminal turn as in send() takeover.
|
|
1248
|
+
this.flushBufferedPrompt(previousSessionId || undefined);
|
|
1249
|
+
if (this.eventTurnId) this.closeEventTurn();
|
|
1250
|
+
this.statusCb({ status: "busy", reason: "provider-turn", timeline: { status: "clearing-context", timestamp: Date.now() } });
|
|
1251
|
+
let newSessionId: string;
|
|
1252
|
+
try {
|
|
1253
|
+
newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
// The old session is still intact and active — restore idle so the failed command doesn't
|
|
1256
|
+
// strand a provider-turn busy hold (same recovery shape as Codex's clearContext).
|
|
1257
|
+
this.statusCb({ status: "idle", reason: "provider-turn" });
|
|
1258
|
+
throw error;
|
|
1259
|
+
}
|
|
1260
|
+
const clearedAt = Date.now();
|
|
1261
|
+
if (process.meta) {
|
|
1262
|
+
process.meta.sessionId = newSessionId;
|
|
1263
|
+
// #2539 Phase B — soft-steer fencing: batches accepted-but-unconsumed on the replaced
|
|
1264
|
+
// session can never be consumed on the fresh one; their relay ids go BACK to the runner's
|
|
1265
|
+
// normal queue (the settlement callback fires inside sessionReplaced — never reposted into
|
|
1266
|
+
// the cleared session).
|
|
1267
|
+
const softSteerOrphans = await this.softSteer.sessionReplaced(newSessionId);
|
|
1268
|
+
if (softSteerOrphans.length > 0) {
|
|
1269
|
+
logger.info("opencode", `soft-steer: reoffering ${softSteerOrphans.length} relay id(s) after clear swapped session`);
|
|
1270
|
+
}
|
|
1271
|
+
process.meta.relayContextSent = false;
|
|
1272
|
+
// Boundary charter/memory delivery belongs to the server inject seams.
|
|
1273
|
+
process.meta.launchContextSent = true;
|
|
1274
|
+
process.meta.context = freshContextState(clearedAt);
|
|
1275
|
+
}
|
|
1276
|
+
this.statusCb({ status: "idle", reason: "provider-turn", timeline: { status: "context-cleared", timestamp: clearedAt } });
|
|
1277
|
+
return { previousSessionId, sessionId: newSessionId };
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* #2440 — native self-resume compaction via OpenCode's own `POST /session/{id}/compact`
|
|
1282
|
+
* (mistakenly believed unavailable when #2431 shipped clear-inject only). Reuses the SAME
|
|
1283
|
+
* turnQueue FIFO serializer clearContext() established: `agent.compact` is dispatched
|
|
1284
|
+
* fire-and-forget with no provider-input ticket (like `agent.clearContext`), and OpenCode's
|
|
1285
|
+
* `/message` POST is synchronous, so a compact triggered from inside the model's own turn
|
|
1286
|
+
* (relay_compact_and_resume) genuinely races an in-flight send() without it. See the
|
|
1287
|
+
* `turnQueue` field doc for the full race shape this closes off.
|
|
1288
|
+
*/
|
|
1289
|
+
async compact(process: ManagedProcess, opts?: { instructions?: string }): Promise<Record<string, unknown>> {
|
|
1290
|
+
await this.apiBudgets.get(process)?.prepare();
|
|
1291
|
+
return this.enqueueTurn(() => this.performCompact(process, opts));
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* `opts.instructions` (the relay-built continuation note) is deliberately NOT threaded into the
|
|
1296
|
+
* `/compact` request body: this pinned server version's wire shape for a steering instruction on
|
|
1297
|
+
* that endpoint is unconfirmed, and guessing wrong risks a hard 4xx in place of a clean
|
|
1298
|
+
* compaction. This is safe to skip — OpenCode does not couple continuation delivery in-process
|
|
1299
|
+
* (no `couplesClearInjectContinuation`, same as its clear-inject path today), so the relay's own
|
|
1300
|
+
* reactive continuation inject (`enqueueContinuationInjectionAfterSelfResume`, src/self-resume.ts)
|
|
1301
|
+
* delivers the SAME continuation as a follow-up `agent.injectContext` once this command succeeds
|
|
1302
|
+
* — the identical path clear-inject already relies on for OpenCode (see the #2431 test "the first
|
|
1303
|
+
* post-clear delivery lands in the NEW session ... through the same deliver()/send() path").
|
|
1304
|
+
*/
|
|
1305
|
+
private async performCompact(process: ManagedProcess, _opts?: { instructions?: string }): Promise<Record<string, unknown>> {
|
|
1306
|
+
const client = process.meta?.client as OpenCodeClient | undefined;
|
|
1307
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
1308
|
+
if (!client || !sessionId) throw new Error("OpenCode session is not ready");
|
|
1309
|
+
this.statusCb({ status: "busy", reason: "provider-turn", timeline: { status: "compacting", timestamp: Date.now() } });
|
|
1310
|
+
try {
|
|
1311
|
+
await client.compact(sessionId);
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
// The session is still intact and active — restore idle so a failed command doesn't strand
|
|
1314
|
+
// a provider-turn busy hold (same recovery shape as clearContext's own catch above).
|
|
1315
|
+
this.statusCb({ status: "idle", reason: "provider-turn" });
|
|
1316
|
+
throw error;
|
|
1317
|
+
}
|
|
1318
|
+
const compactedAt = Date.now();
|
|
1319
|
+
const currentContext = process.meta?.context as ContextState | undefined;
|
|
1320
|
+
if (process.meta) {
|
|
1321
|
+
// #2440 round 2 — stamp unconditionally, mirroring swapSessionForClear's shape: a compact
|
|
1322
|
+
// that lands before any context state was ever reported (e.g. immediately after spawn) must
|
|
1323
|
+
// still record the compaction rather than silently no-oping, exactly like clearContext's
|
|
1324
|
+
// always-fresh stamp below. Falls back to clearContext's own baseline defaults (utilization
|
|
1325
|
+
// 0, no warm topics/active memories, source "api"/confidence "reported") when there is no
|
|
1326
|
+
// prior state to carry forward; lifecycleState is "cooling" (not "fresh") because — unlike a
|
|
1327
|
+
// clear — compact does not wipe conversation history.
|
|
1328
|
+
process.meta.context = {
|
|
1329
|
+
...(currentContext ?? { utilization: 0, warmTopics: [], source: "api" as const, confidence: "reported" as const }),
|
|
1330
|
+
lifecycleState: "cooling",
|
|
1331
|
+
tasksSinceCompact: 0,
|
|
1332
|
+
lastCompactedAt: compactedAt,
|
|
1333
|
+
lastUpdatedAt: compactedAt,
|
|
1334
|
+
} satisfies ContextState;
|
|
1335
|
+
}
|
|
1336
|
+
this.statusCb({ status: "idle", reason: "provider-turn", timeline: { status: "compacted", timestamp: compactedAt } });
|
|
1337
|
+
return { sessionId };
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
async shutdown(process: ManagedProcess, opts: { graceful: boolean; timeoutMs: number }): Promise<ShutdownOutcome> {
|
|
1341
|
+
// #2539 Phase B — anything accepted-but-unconsumed goes back to the runner's normal queue via
|
|
1342
|
+
// the settlement callback (inside reofferAll) before teardown; the ledger itself never drops
|
|
1343
|
+
// an id silently — a failed callback re-inserts the entries for the next exit.
|
|
1344
|
+
const softSteerLeftovers = await this.softSteer.reofferAll();
|
|
1345
|
+
if (softSteerLeftovers.length > 0) logger.info("opencode", `soft-steer: shutdown reoffering ${softSteerLeftovers.length} relay id(s)`);
|
|
1346
|
+
this.softSteerProcess = undefined;
|
|
1347
|
+
// #2484 round 1 review (F2/F3) — session end: force any still-pending debounced token-usage
|
|
1348
|
+
// report out now, rather than losing it to a timer that never gets to fire. Round 2 (MED-1)
|
|
1349
|
+
// — awaited, not fire-and-forget: this covers the controlled-teardown paths that call
|
|
1350
|
+
// adapter.shutdown() directly (restartProvider, shutdownProvider) without going through
|
|
1351
|
+
// runner-core's settleTerminalTokenUsage seam first.
|
|
1352
|
+
await this.tokenUsagePoster.flushAll();
|
|
1353
|
+
(process.meta?.events as AbortController | undefined)?.abort();
|
|
1354
|
+
if (opts.graceful) await this.interrupt(process).catch(() => {});
|
|
1355
|
+
const outcome = await terminateProviderProcesses(process.process ? [process.process] : [], opts);
|
|
1356
|
+
if ((process.process as Bun.Subprocess | undefined)?.exitCode !== null) await this.apiBudgets.get(process)?.settle().catch(() => {});
|
|
1357
|
+
return outcome;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Exported behavior through the adapter so fixture contracts can pin the v1.18.21 event shapes.
|
|
1362
|
+
*
|
|
1363
|
+
* Reproduced live against a real server (#2347): the wire shapes here were captured directly
|
|
1364
|
+
* off `/event`, not inferred from docs. Two things the previous implementation got wrong:
|
|
1365
|
+
*
|
|
1366
|
+
* 1. `session.status` carries a nested `{ status: { type: "busy" | "idle" } }`, not a bare
|
|
1367
|
+
* `properties.status` string — so idle was never detected and a finished turn never told
|
|
1368
|
+
* Relay it was done. `session.updated` never carries a status field at all; it no longer
|
|
1369
|
+
* needs to be treated as a status source.
|
|
1370
|
+
* 2. `message.updated` only ever carries message metadata (`properties.info`, with `role`) —
|
|
1371
|
+
* never the reply text. The assistant's text streams separately as `message.part.updated`
|
|
1372
|
+
* events (`properties.part`), one per part, finalized once `part.time.end` is set. A
|
|
1373
|
+
* message's `message.updated` (role) event always arrives before that message's part
|
|
1374
|
+
* events, so `messageRoles` is populated before any part needs to look a role up.
|
|
1375
|
+
*/
|
|
1376
|
+
handleEvent(event: OpenCodeServerEvent, process?: ManagedProcess): void {
|
|
1377
|
+
const properties = event.properties ?? {};
|
|
1378
|
+
if (event.type === "session.status") {
|
|
1379
|
+
// #2425 — a foreign-session event must never affect this adapter's turn/idle state, full
|
|
1380
|
+
// stop, regardless of suppression state. This is what makes it safe for the two doors below
|
|
1381
|
+
// to stop enforcing PERMANENT suppression once `turnAbandoned`: an unfiltered foreign child
|
|
1382
|
+
// session's busy/idle was exactly the hazard round 7 made suppression permanent to guard
|
|
1383
|
+
// against, and this filter closes it directly instead.
|
|
1384
|
+
// #2459 round 1 (review C) — FAIL CLOSED: whenever the active session id is known (a live
|
|
1385
|
+
// spawn always carries one), an event WITHOUT a sessionID is rejected too — a session-
|
|
1386
|
+
// less status cannot be positively attributed to the active conversation, and acting on
|
|
1387
|
+
// it could open/close a turn for a dead session. Test-only call sites that pass no
|
|
1388
|
+
// process (hence no active id) keep the lenient behavior.
|
|
1389
|
+
const sessionID = stringValue(properties.sessionID);
|
|
1390
|
+
const activeSessionId = stringValue(process?.meta?.sessionId);
|
|
1391
|
+
if (activeSessionId && sessionID !== activeSessionId) return;
|
|
1392
|
+
const statusValue = properties.status;
|
|
1393
|
+
const status = stringValue(isRecord(statusValue) ? statusValue.type : statusValue);
|
|
1394
|
+
if (status === "retry") {
|
|
1395
|
+
this.onServerRetry(parseOpenCodeServerRetry(statusValue), process);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (status === "busy") {
|
|
1399
|
+
// send() owns the turn boundary while a headless delivery is in flight — its own
|
|
1400
|
+
// explicit busy (with its minted id) is authoritative, exactly as before #2459. Only
|
|
1401
|
+
// busy events OUTSIDE send() can open a terminal-origin turn below.
|
|
1402
|
+
if (this.turnInFlight) return;
|
|
1403
|
+
// A terminal turn is already open — its matching idle owns the close; a duplicate
|
|
1404
|
+
// busy must not re-mint.
|
|
1405
|
+
if (this.eventTurnId) return;
|
|
1406
|
+
// #2425 — re-arm the close guard: this real busy is evidence the turn (from
|
|
1407
|
+
// `send()`'s perspective, already force-closed to idle by the abandonment path) is
|
|
1408
|
+
// actually still running, so the eventual matching idle must be allowed to close it
|
|
1409
|
+
// again.
|
|
1410
|
+
if (this.turnAbandoned) this.turnClosed = false;
|
|
1411
|
+
// #2459 round 1 (review D) — #2397 round-7 echo guard, restored to PERMANENT and made
|
|
1412
|
+
// POSITIVE (no time heuristic): once any headless turn has run on this process, a busy
|
|
1413
|
+
// is admitted ONLY when a fresh terminal user prompt armed `pendingTerminalPrompt` (the
|
|
1414
|
+
// TUI's POST creates the user message before the turn's busy flows). The buffered
|
|
1415
|
+
// own-turn echo has no fresh user message by construction — at ANY delay — so it stays
|
|
1416
|
+
// swallowed. (#2425 abandonment keeps this door open — those server events are the only
|
|
1417
|
+
// remaining proof of the turn's real state.)
|
|
1418
|
+
if (!this.turnAbandoned && this.headlessTurnSeen && !this.pendingTerminalPrompt) return;
|
|
1419
|
+
// #2459 round 1 (review D) — a genuinely new, terminal-origin turn: mint its provider turn id and hand it
|
|
1420
|
+
// to runner-core (which keys currentTurnId off this busy), then associate every user/
|
|
1421
|
+
// assistant/tool message id of the turn with it (see activeTurnId()) until the matching
|
|
1422
|
+
// idle closes it. Un-stick the idle suppression so this turn's own idle can pass.
|
|
1423
|
+
this.pendingTerminalPrompt = false; // consumed
|
|
1424
|
+
this.eventTurnId = `opencode-turn-${crypto.randomUUID()}`;
|
|
1425
|
+
this.eventTurnText = [];
|
|
1426
|
+
this.eventTurnReasoning = new Map();
|
|
1427
|
+
this.suppressProviderIdle = false;
|
|
1428
|
+
// #2459 round 2 (review D) — a prompt buffered by the [user message → user part →
|
|
1429
|
+
// busy] ordering is emitted NOW under the freshly minted id, so prompt → trace →
|
|
1430
|
+
// final-response all share one opencode-turn-* id.
|
|
1431
|
+
this.flushBufferedPrompt(this.eventTurnId);
|
|
1432
|
+
this.statusCb({ status: "busy", reason: "provider-turn", id: this.eventTurnId });
|
|
1433
|
+
}
|
|
1434
|
+
if (status === "idle") {
|
|
1435
|
+
// send() closes its own turn deterministically (closeSuppressedTurn / failTurn / the
|
|
1436
|
+
// finally) — an SSE idle while a headless delivery is in flight stays swallowed, as
|
|
1437
|
+
// since #2397 round 2.
|
|
1438
|
+
if (this.turnInFlight) return;
|
|
1439
|
+
// #2459 round 2 (review D) — bounded fallback: an active-session idle with a prompt
|
|
1440
|
+
// still buffered means the turn ended without us seeing its busy (or it was itself an
|
|
1441
|
+
// echo). Flush under the session pseudo-turn so the operator's input is never stranded
|
|
1442
|
+
// in the buffer; RunnerCore's injection dedup guards the chat-echo case.
|
|
1443
|
+
if (this.bufferedTerminalPrompt && !this.eventTurnId) {
|
|
1444
|
+
this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
|
|
1445
|
+
}
|
|
1446
|
+
// #2459 — the matching close of an event-driven (terminal) turn: emit the turn-final
|
|
1447
|
+
// response accumulated from the turn's live assistant text parts (mirroring send()'s
|
|
1448
|
+
// emitTurnFinalResponse → close ordering) and then the idle edge that closes
|
|
1449
|
+
// runner-core's turn.
|
|
1450
|
+
if (this.eventTurnId) {
|
|
1451
|
+
this.closeEventTurn();
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
// #2397 round 2 (F4) — a transient idle from a failed attempt, still mid-retry-loop.
|
|
1455
|
+
// #2397 round 7 review (MED-2/LOW-1) — round 6's one-shot reopen (lower the flag on the
|
|
1456
|
+
// FIRST post-close idle, keyed on `turnInFlight`) let a SECOND idle in the same trailing-
|
|
1457
|
+
// echo window pass through and double-close. There is no legitimate turn boundary this SSE
|
|
1458
|
+
// door needs to report that `send()`/`closeSuppressedTurn`/`failTurn` don't already emit
|
|
1459
|
+
// explicitly for a DETERMINISTIC outcome (see the field comment) — so there is no reopen on
|
|
1460
|
+
// that path: suppression stays permanent for any turn `send()` itself closed knowingly.
|
|
1461
|
+
// #2459 — EXCEPT the terminal-origin path above: an idle outside send() with no event
|
|
1462
|
+
// turn open is still an echo/stray (the busy that minted a turn would have set one), so
|
|
1463
|
+
// the permanent suppression holds exactly as before for chat turns.
|
|
1464
|
+
// #2425 — the one exception is the same `turnAbandoned` escape hatch as the busy door
|
|
1465
|
+
// above; `turnClosed` (not `suppressProviderIdle`) is what still prevents a double-close
|
|
1466
|
+
// here — either the abandonment path's own forced idle already closed it (this real idle
|
|
1467
|
+
// is then a no-op confirmation), or a real busy re-armed it above and this is the genuine
|
|
1468
|
+
// close.
|
|
1469
|
+
if (this.suppressProviderIdle && !this.turnAbandoned) return;
|
|
1470
|
+
if (this.turnClosed) return;
|
|
1471
|
+
this.turnClosed = true;
|
|
1472
|
+
this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
|
|
1473
|
+
}
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (event.type === "permission.asked") {
|
|
1477
|
+
if (!this.turnInFlight && !this.eventTurnId) return;
|
|
1478
|
+
// `opencode.json` grants every permission for this open-only adapter. Keep the event
|
|
1479
|
+
// observable in case a future server version ignores the config; never surface a misleading
|
|
1480
|
+
// guarded approval form or silently reinterpret the session's requested policy.
|
|
1481
|
+
this.statusCb({ status: "busy", reason: "provider-turn", providerState: { state: "active", reason: "openPermission", label: "OpenCode permission auto-allowed by approvalMode=open", source: "opencode", updatedAt: Date.now() } });
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (event.type === "message.updated") {
|
|
1485
|
+
const info = isRecord(properties.info) ? properties.info : undefined;
|
|
1486
|
+
const id = info ? stringValue(info.id) : undefined;
|
|
1487
|
+
const role = info ? stringValue(info.role) : undefined;
|
|
1488
|
+
if (id && role) {
|
|
1489
|
+
this.messageRoles.set(id, role);
|
|
1490
|
+
pruneToLimit(this.messageRoles, OPENCODE_EVENT_STATE_LIMIT);
|
|
1491
|
+
}
|
|
1492
|
+
// #2427 — the SSE echo of a finalized assistant message carries the SAME per-message
|
|
1493
|
+
// `tokens` totals the synchronous POST resolves with (send()'s #2355 report), but on a long
|
|
1494
|
+
// turn that POST can die client-side or stay in-flight forever, so usage reported ONLY from
|
|
1495
|
+
// it never lands (#2427's blank context-window). Report from this door instead: it fires
|
|
1496
|
+
// DURING the turn regardless of POST survival, mirroring how Codex consumes its
|
|
1497
|
+
// `thread/tokenUsage/updated` push. Idempotent with the POST path — last write wins and both
|
|
1498
|
+
// doors feed the same openCodeContextState no-signal rules (0/unparsable → no-op), so neither
|
|
1499
|
+
// can regress a good reading — and same-session-filtered like #2425's session.status door.
|
|
1500
|
+
// #2459 round 1 (review C) — FAIL CLOSED: with the active session id known, a usage echo
|
|
1501
|
+
// WITHOUT a sessionID is rejected too (an unattributable reading must never overwrite the
|
|
1502
|
+
// active session's context state). Test-only process-less call sites keep the lenient path.
|
|
1503
|
+
const sessionID = stringValue(properties.sessionID);
|
|
1504
|
+
const activeSessionId = stringValue(process?.meta?.sessionId);
|
|
1505
|
+
const tokens = countTokens(info?.tokens);
|
|
1506
|
+
const sessionUnattributable = Boolean(activeSessionId && sessionID !== activeSessionId);
|
|
1507
|
+
if (process && role === "assistant" && tokens !== undefined && !sessionUnattributable) {
|
|
1508
|
+
const model = stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider);
|
|
1509
|
+
this.reportContextUsage(process, model, tokens);
|
|
1510
|
+
}
|
|
1511
|
+
// #2484 — same door, reused for the spine report: model/createdAt prefer the message's
|
|
1512
|
+
// own info (round 1 review F5/F6) over the session's configured model / receipt time.
|
|
1513
|
+
// #3351 — outside the context guard so a finished message without counts reports unknown.
|
|
1514
|
+
if (process && role === "assistant" && id) captureOpenCodeApiUsage(this.apiBudgets.get(process), info, sessionID ?? activeSessionId, stringValue(process.meta?.model), id);
|
|
1515
|
+
if (process && role === "assistant" && id && !sessionUnattributable) {
|
|
1516
|
+
scheduleOpenCodeTokenUsage(this.tokenUsagePoster, {
|
|
1517
|
+
provider: this.provider,
|
|
1518
|
+
sessionId: activeSessionId ?? sessionID,
|
|
1519
|
+
model: openCodeMessageModel(info) ?? stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider),
|
|
1520
|
+
messageId: id,
|
|
1521
|
+
tokens: info?.tokens,
|
|
1522
|
+
createdAt: openCodeMessageObservedAt(info),
|
|
1523
|
+
finished: openCodeMessageFinished(info),
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
// #2459 round 1 (review D) — ARM the terminal-turn gate: a FIRST-SEEN user message on the
|
|
1527
|
+
// ACTIVE session observed outside send() is the event-source proof that a human typed into
|
|
1528
|
+
// the attached TUI. The next active-session busy is then admitted as terminal-origin even
|
|
1529
|
+
// after a headless close (whose own buffered echo carries no fresh user message — its user
|
|
1530
|
+
// echo was already seen live during send(), so the dedup set prevents the late duplicate
|
|
1531
|
+
// from arming). Fail-closed session-filtered like the usage read above, never armed while
|
|
1532
|
+
// send() is in flight (the chat delivery's own user echo must not open a terminal turn
|
|
1533
|
+
// behind send()'s back) nor while a terminal turn is already open.
|
|
1534
|
+
// #2539 Phase B — soft-steer consumption door: an assistant message whose info.parentID
|
|
1535
|
+
// equals a soft batch's user row id is positive proof the active loop CONSUMED that batch
|
|
1536
|
+
// (the loop reloads full messages each step and re-parents subsequent assistant steps to
|
|
1537
|
+
// the newest user row — live-verified on pinned 1.18.21). FIFO settle: the matched batch
|
|
1538
|
+
// plus every OLDER pending batch, ids marked once. Same-session fail-closed like the usage
|
|
1539
|
+
// read above — a stale session's echo must never settle a live batch.
|
|
1540
|
+
const parentID = isRecord(info) ? stringValue(info.parentID) : undefined;
|
|
1541
|
+
if (process && role === "assistant" && activeSessionId && parentID) {
|
|
1542
|
+
// Settlement callbacks fire inside consumeByParent (consumed → reoffer fallback on
|
|
1543
|
+
// callback failure); this door only logs the outcome. Terminal .catch satisfies the
|
|
1544
|
+
// unguarded-async ratchet (the settle path already never rejects).
|
|
1545
|
+
void this.softSteer.consumeByParent(parentID, activeSessionId).then((consumed) => {
|
|
1546
|
+
if (consumed.length > 0) logger.info("opencode", `soft-steer batch consumed on session ${activeSessionId}: settling ${consumed.length} relay id(s)`);
|
|
1547
|
+
}).catch((error) => logger.warn("opencode", `soft-steer consumption door failed: ${errMessage(error)}`));
|
|
1548
|
+
}
|
|
1549
|
+
if (role === "user" && id) {
|
|
1550
|
+
const firstSeen = !this.seenUserMessageIds.has(id);
|
|
1551
|
+
this.seenUserMessageIds.add(id);
|
|
1552
|
+
pruneToLimit(this.seenUserMessageIds, OPENCODE_EVENT_STATE_LIMIT);
|
|
1553
|
+
// #2539 Phase B — relay's OWN soft-steer user rows never arm the terminal-origin gate:
|
|
1554
|
+
// they are first-seen user messages OUTSIDE send() (the whole point of the lane), and
|
|
1555
|
+
// arming here would misclassify the wake turn (or any user's next turn) as terminal.
|
|
1556
|
+
const terminalOrigin = firstSeen && !this.turnInFlight && !this.eventTurnId && !sessionUnattributable && !this.softSteer.isTrackedUserId(id);
|
|
1557
|
+
if (terminalOrigin) this.pendingTerminalPrompt = true;
|
|
1558
|
+
if (terminalOrigin) adoptOpenCodeTerminalModel(process?.meta, info); // #2659 — keep a model picked in the TUI
|
|
1559
|
+
}
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (event.type === "message.part.updated") {
|
|
1563
|
+
const part = isRecord(properties.part) ? properties.part : undefined;
|
|
1564
|
+
if (!part) return;
|
|
1565
|
+
// #2534 — acceptance door: a persisted user text part whose text equals the armed
|
|
1566
|
+
// context-carrying outbound prompt is positive proof OpenCode ACCEPTED it. Consuming here
|
|
1567
|
+
// marks the first-turn markers MID-TURN — seconds after dispatch — so a first turn that
|
|
1568
|
+
// later runs past the whole-turn deadline can never leave them false (the await-then-mark
|
|
1569
|
+
// flaw that re-prepended the ~48KB launch briefing to every queued message). Session and
|
|
1570
|
+
// text identity are fail-closed inside consumeOpenCodeContextAcceptance: a stale-session
|
|
1571
|
+
// echo or an operator-typed prompt never marks.
|
|
1572
|
+
consumeOpenCodeContextAcceptance(process, stringValue(properties.sessionID), stringValue(part.text));
|
|
1573
|
+
// #2389 — ANY part on an assistant message is proof the model produced something, including
|
|
1574
|
+
// the tool parts this adapter does not mirror (their internal shape is not pinned, so they are
|
|
1575
|
+
// reported as bare activity rather than published as a half-understood trace step). Without
|
|
1576
|
+
// this a tools-only OpenCode turn emits no session events at all and reads as empty.
|
|
1577
|
+
// #2397 round 3 review (HIGH-1r) — EXCEPT a bare step-start/step-finish bookkeeping part,
|
|
1578
|
+
// which the server emits on every turn regardless of outcome. `isOutputPart` is the same
|
|
1579
|
+
// predicate `completionProducedOutput` uses on the POST-body door (HIGH-1); this is the SSE
|
|
1580
|
+
// door, and both must agree or the issue's own incident (a step-start-only turn) still
|
|
1581
|
+
// discharges #2389's escalation through whichever door fires first. `noteOutput()` is called
|
|
1582
|
+
// with no event here (onTurnActivity takes no args), so MED-5's `diagnostic` filter cannot
|
|
1583
|
+
// see this path at all — it must be excluded at the source instead.
|
|
1584
|
+
if (isOutputPart(part) && this.messageRoles.get(stringValue(part.messageID) ?? "") === "assistant") this.turnActivityCb();
|
|
1585
|
+
// #2459 round 2 (review D) — no-busy-seen fallback, bounded: when trace activity
|
|
1586
|
+
// (tool/narration/reasoning) arrives while a terminal prompt is still buffered and no
|
|
1587
|
+
// terminal turn ever opened, flush the prompt FIRST under the same session pseudo-turn
|
|
1588
|
+
// the trace will use, keeping prompt→trace grouped instead of stranding the buffer.
|
|
1589
|
+
if (this.bufferedTerminalPrompt && !this.eventTurnId && !this.turnInFlight) {
|
|
1590
|
+
this.flushBufferedPrompt(stringValue(process?.meta?.sessionId) || undefined);
|
|
1591
|
+
}
|
|
1592
|
+
// #2354 — surface tool calls in chat/timeline the way Codex does: decode the ToolPart and
|
|
1593
|
+
// emit a running/completed/failed/cancelled session step (upserted in place by stepId). This
|
|
1594
|
+
// must run before the text-only early-return below, which used to drop every non-text part.
|
|
1595
|
+
if (stringValue(part.type) === "tool") {
|
|
1596
|
+
this.emitToolStep(part, process);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
// #2605 — surface the model's thinking as a `reasoning` trace row the way Codex/Claude do:
|
|
1600
|
+
// decode the ReasoningPart and emit it (upserted in place by stepId = stable part id). Runs
|
|
1601
|
+
// before the text-only early-return below, which is also what used to drop these parts.
|
|
1602
|
+
if (stringValue(part.type) === "reasoning") {
|
|
1603
|
+
this.emitReasoningStep(part, process);
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (stringValue(part.type) !== "text") return;
|
|
1607
|
+
const partId = stringValue(part.id);
|
|
1608
|
+
const messageId = stringValue(part.messageID);
|
|
1609
|
+
const text = stringValue(part.text);
|
|
1610
|
+
const finalized = isRecord(part.time) && part.time.end !== undefined;
|
|
1611
|
+
if (!partId || !messageId || !text || !finalized) return;
|
|
1612
|
+
// #2459 — a finalized role:user text part on the ACTIVE server session is a prompt typed
|
|
1613
|
+
// into an attached web-terminal TUI: mirror it into chat as a terminal-origin prompt.
|
|
1614
|
+
// RunnerCore's recordInjectedPrompt already consumes the echo of every Relay-injected
|
|
1615
|
+
// prompt (chat deliveries), so terminal prompts appear once while dashboard-injected
|
|
1616
|
+
// prompts do not duplicate. #2459 round 1 (review C) — FAIL CLOSED: with the active
|
|
1617
|
+
// session id known, a part WITHOUT a sessionID is rejected too — after a clearContext()
|
|
1618
|
+
// or retry re-roll an unattributable/old-session prompt belongs (at best) to a dead
|
|
1619
|
+
// conversation, and the generation boundary kills that stale TUI anyway.
|
|
1620
|
+
if (this.messageRoles.get(messageId) === "user") {
|
|
1621
|
+
const eventSessionId = stringValue(properties.sessionID);
|
|
1622
|
+
const activeSessionId = stringValue(process?.meta?.sessionId);
|
|
1623
|
+
if (activeSessionId && eventSessionId !== activeSessionId) return;
|
|
1624
|
+
if (this.emittedTextParts.has(partId)) return;
|
|
1625
|
+
this.emittedTextParts.add(partId);
|
|
1626
|
+
pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
|
|
1627
|
+
// #2459 round 2 (review D) — turn-id ordering. While send() is in flight, emit
|
|
1628
|
+
// immediately under send()'s id (RunnerCore dedups the chat-injection echo). While a
|
|
1629
|
+
// terminal turn is open, emit under its minted id. Otherwise — the [user message →
|
|
1630
|
+
// user part → busy] wire ordering, where the TUI's POST creates the user message
|
|
1631
|
+
// BEFORE the busy edge mints the terminal turn id — BUFFER the prompt: emitting now
|
|
1632
|
+
// would publish the raw session id as a turnId and split the prompt from its
|
|
1633
|
+
// trace/final. The buffer is flushed under the minted id at busy admission, and the
|
|
1634
|
+
// bounded no-busy fallbacks (trace activity, any active-session idle, chat delivery
|
|
1635
|
+
// takeover, session swap) flush it under the session pseudo-turn — NEVER under a
|
|
1636
|
+
// stale headless sendTurnId.
|
|
1637
|
+
if (this.turnInFlight) {
|
|
1638
|
+
const turnId = this.sendTurnId ?? activeSessionId;
|
|
1639
|
+
this.sessionEventCb({ type: "prompt", origin: "terminal", body: text, ...(turnId ? { turnId } : {}) });
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (this.eventTurnId) {
|
|
1643
|
+
this.sessionEventCb({ type: "prompt", origin: "terminal", body: text, turnId: this.eventTurnId });
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (this.bufferedTerminalPrompt) this.flushBufferedPrompt(activeSessionId || undefined);
|
|
1647
|
+
this.bufferedTerminalPrompt = { body: text, occurredAt: Date.now() };
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (this.messageRoles.get(messageId) !== "assistant") return;
|
|
1651
|
+
if (this.emittedTextParts.has(partId)) return;
|
|
1652
|
+
this.emittedTextParts.add(partId);
|
|
1653
|
+
pruneToLimit(this.emittedTextParts, OPENCODE_EVENT_STATE_LIMIT);
|
|
1654
|
+
// #2459 — while an event-driven terminal turn is open, its finalized assistant text also
|
|
1655
|
+
// accumulates toward the turn-final response emitted by closeEventTurn().
|
|
1656
|
+
if (this.eventTurnId) this.eventTurnText.push(text.trim());
|
|
1657
|
+
const turnId = this.activeTurnId() ?? stringValue(process?.meta?.sessionId);
|
|
1658
|
+
this.sessionEventCb({ type: "narration", origin: "provider", body: text, ...(turnId ? { turnId } : {}) });
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* #2605 — decode one ReasoningPart and emit its trace row, with in-place upsert semantics: a
|
|
1664
|
+
* reasoning part GROWS as the model thinks (SSE emits the part repeatedly, each frame carrying
|
|
1665
|
+
* the FULL text so far), so a frame for an already-emitted part id re-emits the current full
|
|
1666
|
+
* body — the server upserts it into the same row by stepId. Only assistant-message reasoning
|
|
1667
|
+
* parts are surfaced (same fail-closed role gate as emitToolStep). `streaming` stays true
|
|
1668
|
+
* while the part is unfinished (`time.end` unset), false on finalize — mirroring the tool
|
|
1669
|
+
* step's running→terminal upsert. Reasoning arriving while an event-driven terminal turn is
|
|
1670
|
+
* open also accumulates toward the turn-final reasoning emitted by closeEventTurn().
|
|
1671
|
+
*/
|
|
1672
|
+
private emitReasoningStep(part: Record<string, unknown>, process?: ManagedProcess): void {
|
|
1673
|
+
const messageId = stringValue(part.messageID);
|
|
1674
|
+
if (!messageId || this.messageRoles.get(messageId) !== "assistant") return;
|
|
1675
|
+
const step = decodeOpenCodeReasoningPart(part);
|
|
1676
|
+
// Empty reasoning text is backend muzak (the provider's own SQLite shows sessions whose
|
|
1677
|
+
// first persisted reasoning part is `""`) — not operator signal. Dropping it here (rather
|
|
1678
|
+
// than emitting an empty row) also keeps noteOutput's `!body.trim()` filter from ever
|
|
1679
|
+
// seeing it. A part that STARTS empty still gets its row the moment text arrives.
|
|
1680
|
+
if (!step) return;
|
|
1681
|
+
const prev = this.emittedReasoningParts.get(step.stepId);
|
|
1682
|
+
if (prev === step.body) return;
|
|
1683
|
+
this.emittedReasoningParts.set(step.stepId, step.body);
|
|
1684
|
+
pruneToLimit(this.emittedReasoningParts, OPENCODE_EVENT_STATE_LIMIT);
|
|
1685
|
+
// #2605 — record the latest snapshot per part for the event-turn close below. Unconditional
|
|
1686
|
+
// (not just on emit): the close's reconcile must see the newest body even for frames that
|
|
1687
|
+
// were live-emitted, so it can compare per-part and emit nothing already traced.
|
|
1688
|
+
if (this.eventTurnId) this.eventTurnReasoning.set(step.stepId, step.body);
|
|
1689
|
+
const finalized = isRecord(part.time) && part.time.end !== undefined;
|
|
1690
|
+
this.sessionEventCb(openCodeReasoningSessionEvent(step, this.activeTurnId() ?? stringValue(process?.meta?.sessionId), !finalized));
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* #2354 — decode one ToolPart and emit its session step, with dedup + terminal-lock: a
|
|
1695
|
+
* duplicate frame or a reordered update (a late `running` after `completed`) is dropped rather
|
|
1696
|
+
* than regressing or double-appending the row. Only assistant-message tool parts are surfaced.
|
|
1697
|
+
* `stepId` = the stable part id, so the server upserts running→completed IN PLACE (one row),
|
|
1698
|
+
* exactly like Codex's app-server item-id upsert.
|
|
1699
|
+
*/
|
|
1700
|
+
private emitToolStep(part: Record<string, unknown>, process?: ManagedProcess): void {
|
|
1701
|
+
// #2354 review (LOW) — fail CLOSED: a tool part lacking a messageID (so its role can't be
|
|
1702
|
+
// confirmed assistant) is dropped, not surfaced.
|
|
1703
|
+
const messageId = stringValue(part.messageID);
|
|
1704
|
+
if (!messageId || this.messageRoles.get(messageId) !== "assistant") return;
|
|
1705
|
+
const step = decodeOpenCodeToolPart(part);
|
|
1706
|
+
if (!step) return;
|
|
1707
|
+
const rank = openCodeToolStatusRank(step.status);
|
|
1708
|
+
const prevRank = this.emittedToolSteps.get(step.stepId);
|
|
1709
|
+
if (prevRank !== undefined && rank <= prevRank) return;
|
|
1710
|
+
this.emittedToolSteps.set(step.stepId, rank);
|
|
1711
|
+
pruneToLimit(this.emittedToolSteps, OPENCODE_EVENT_STATE_LIMIT);
|
|
1712
|
+
if (step.params !== undefined) this.toolStepParams.set(step.stepId, step.params);
|
|
1713
|
+
const params = step.params !== undefined ? step.params : this.toolStepParams.get(step.stepId);
|
|
1714
|
+
pruneToLimit(this.toolStepParams, OPENCODE_EVENT_STATE_LIMIT);
|
|
1715
|
+
// #2459 — tool steps attach to the live turn id (send()'s while a headless delivery runs,
|
|
1716
|
+
// the event-driven terminal turn otherwise), never to a stale prior headless turn id.
|
|
1717
|
+
this.sessionEventCb(openCodeToolSessionEvent({ ...step, params }, this.activeTurnId() ?? stringValue(process?.meta?.sessionId)));
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* #2413 — mark the turn-final assistant text as a `response` (not `narration`), so the runner's
|
|
1722
|
+
* Final Response capture identifies it, auto-forwards an OpenCode child's report to its
|
|
1723
|
+
* coordinator, and the chat styles it apart from reasoning — parity with Claude/Codex, which
|
|
1724
|
+
* OpenCode lacked entirely (everything read as thinking, nothing auto-forwarded).
|
|
1725
|
+
*
|
|
1726
|
+
* #2605 — also emits the turn-final `reasoning` rows from the SAME completed assistant message
|
|
1727
|
+
* (`completion.reasoningSteps`, via `openCodeTurnFinalEvents` in opencode-tool-events.ts), so
|
|
1728
|
+
* the model's thinking survives in the trace even when the SSE reasoning frames were never
|
|
1729
|
+
* observed (a POST-only reader, a dropped subscription, a turn whose frames raced the close):
|
|
1730
|
+
* the completed message persists every part with its full text. Each row carries its OWN part
|
|
1731
|
+
* id as stepId — the exact key the live SSE door already emitted — so the server upserts
|
|
1732
|
+
* live+final into ONE row (the upsert keys on stepId ONLY); a merged body under a synthetic
|
|
1733
|
+
* stepId would store the turn's thinking twice. The ledger below is the per-part
|
|
1734
|
+
* emitted-body record the SSE door shares, scoped to part ids — never a global body pool.
|
|
1735
|
+
*
|
|
1736
|
+
* The text is read from the synchronous POST's completed assistant message (`completion.text`,
|
|
1737
|
+
* ground truth per #2389) rather than reconstructed from SSE. `confidence:"definitive"` because
|
|
1738
|
+
* the POST returning IS the provider-native turn-end edge; `final:true` is what makes it a
|
|
1739
|
+
* turn-final capture downstream. The same text already streamed live as `narration` (per text
|
|
1740
|
+
* part); the dashboard's per-turn narration/response dedup (keyed on the shared `turnId`)
|
|
1741
|
+
* suppresses the duplicate, exactly as it does for Codex.
|
|
1742
|
+
*/
|
|
1743
|
+
private emitTurnFinalResponse(process: ManagedProcess, completion: OpenCodeCompletion): void {
|
|
1744
|
+
const turnId = this.sendTurnId ?? stringValue(process.meta?.sessionId);
|
|
1745
|
+
const emittedBodies: Record<string, string> = {};
|
|
1746
|
+
for (const [stepId, body] of this.emittedReasoningParts) emittedBodies[stepId] = body;
|
|
1747
|
+
for (const event of openCodeTurnFinalEvents(completion, turnId, emittedBodies)) this.emitReasoningFinal(event);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/** #2605 — ledger write for a turn-final reasoning row, shared by both turn-close seams. */
|
|
1751
|
+
private emitReasoningFinal(event: ProviderSessionEvent): void {
|
|
1752
|
+
if (event.stepId) {
|
|
1753
|
+
this.emittedReasoningParts.set(event.stepId, event.body);
|
|
1754
|
+
pruneToLimit(this.emittedReasoningParts, OPENCODE_EVENT_STATE_LIMIT);
|
|
1755
|
+
}
|
|
1756
|
+
this.sessionEventCb(event);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* #2397 — turn-ending PROVIDER errors (e.g. an OpenRouter 429 on a throttled preview model)
|
|
1761
|
+
* are read off THIS synchronous POST's response body (`completion.error`, via
|
|
1762
|
+
* `openCodeCompletion`), not off a separate SSE listener. #2389 already established that this
|
|
1763
|
+
* response is ground truth and deterministic (no dependency on an SSE frame's timing); reusing
|
|
1764
|
+
* that one seam here — instead of also inspecting `message.updated`'s `info.error` — keeps
|
|
1765
|
+
* OpenCode's turn-outcome detection in exactly one place, as the standing one-seam rule in this
|
|
1766
|
+
* repo's CLAUDE.md requires.
|
|
1767
|
+
*
|
|
1768
|
+
* Retry/re-roll runs entirely inside this loop (bounded: `OPENCODE_ERROR_RETRY_MAX_ATTEMPTS`
|
|
1769
|
+
* retries per session, one session re-roll) so `deliverInitialPrompt`/`deliver` see one turn
|
|
1770
|
+
* outcome regardless of how many attempts it took — matching this endpoint's existing contract
|
|
1771
|
+
* of "does not return until the turn is over".
|
|
1772
|
+
*/
|
|
1773
|
+
/**
|
|
1774
|
+
* @returns `"ok"` when the turn ended in output (or a positively-parsed empty completion that
|
|
1775
|
+
* is not itself a failure — #2389's normal empty-turn path), `"failed"` only when `failTurn` was
|
|
1776
|
+
* called — i.e. the message provoked a terminal, unrecoverable failure — and `"held"` (#2619)
|
|
1777
|
+
* when the provider refused the turn and it ended in the rate-limit hold: not delivered, not
|
|
1778
|
+
* failed. #2397 round 3 (MED-3): the caller uses this to decide whether the message may be
|
|
1779
|
+
* marked delivered/read.
|
|
1780
|
+
*
|
|
1781
|
+
* #2397 round 5 review (INFO) — this method is non-reentrant BY CONSTRUCTION, and the
|
|
1782
|
+
* `suppressProviderIdle`/`turnInFlight`/`turnClosed`/`turnAbandoned` instance fields it owns
|
|
1783
|
+
* assume that: every `adapter.deliver`/`deliverInitialPrompt` call site in runner-core runs inside
|
|
1784
|
+
* `providerInput.run`, which serializes them, so a second `send()` call never starts before the
|
|
1785
|
+
* previous one's promise has settled. If that ever changed, these fields would need to become
|
|
1786
|
+
* per-call state instead of per-adapter-instance state.
|
|
1787
|
+
*/
|
|
1788
|
+
private async send(process: ManagedProcess, text: string, inputObserver?: ProviderInputObserver): Promise<OpenCodeSendOutcome> {
|
|
1789
|
+
await this.apiBudgets.get(process)?.prepare();
|
|
1790
|
+
// #2539 Phase B — retain the live process for the soft-steer wake hook (see its doc).
|
|
1791
|
+
this.softSteerProcess = process;
|
|
1792
|
+
let attempt = 0;
|
|
1793
|
+
let rerolled = false;
|
|
1794
|
+
let lastFailureDetail: string | undefined;
|
|
1795
|
+
let lastFailureErrorType: string | undefined;
|
|
1796
|
+
// #2397 round 4 review (F1/F2) — round 3 only raised this flag on a failed, retryable attempt,
|
|
1797
|
+
// so it stayed false while ATTEMPT 1's own POST was in flight. On a fast (e.g. loopback) server
|
|
1798
|
+
// the FAILED-attempt idle SSE frame routinely beats that attempt's own POST response — the
|
|
1799
|
+
// issue's own captured incident shape, a 429 on the very first attempt — so an unsuppressed
|
|
1800
|
+
// attempt-1 idle still closed the turn early and, via runner-core's #2389 seam, marked the
|
|
1801
|
+
// linked Task done while the retry loop was still running. Raised for the WHOLE call (below,
|
|
1802
|
+
// past the readiness guard — round 5 LOW-2) so there is no "attempt 1 is unguarded" window.
|
|
1803
|
+
// Every return path must therefore close the turn itself — `closeSuppressedTurn` does that,
|
|
1804
|
+
// idempotently per call (`turnClosed`) — see the `finally`.
|
|
1805
|
+
// #2459 — close a still-open event-driven terminal turn BEFORE the reset below: a chat
|
|
1806
|
+
// delivery taking over the boundary must end the terminal turn first (its own SSE idle, if
|
|
1807
|
+
// it even arrived, would be swallowed under this call's suppression), and closeEventTurn
|
|
1808
|
+
// sets `turnClosed` — resetting it after the close keeps THIS call's deterministic close
|
|
1809
|
+
// live. Order: terminal idle clears the terminal turn id, then this call's busy mints its own.
|
|
1810
|
+
if (this.eventTurnId) this.closeEventTurn();
|
|
1811
|
+
// #2459 round 1 (review D) — a chat delivery also consumes any armed terminal prompt: a
|
|
1812
|
+
// stale arm surviving this call would wrongly admit this call's own buffered busy echo
|
|
1813
|
+
// after the close. (This call's POST user echo is recorded in seenUserMessageIds live, so
|
|
1814
|
+
// its late duplicate can never re-arm — see the message.updated door.)
|
|
1815
|
+
this.pendingTerminalPrompt = false;
|
|
1816
|
+
// #2459 round 2 (review D) — a buffered terminal prompt flushed under the session
|
|
1817
|
+
// pseudo-turn BEFORE the delivery takes over: the operator typed first, and the buffer
|
|
1818
|
+
// must never leak across the headless turn boundary. Fallback id is the session
|
|
1819
|
+
// pseudo-turn — never this call's (or any prior headless) sendTurnId.
|
|
1820
|
+
this.flushBufferedPrompt(stringValue(process.meta?.sessionId) || undefined);
|
|
1821
|
+
this.turnClosed = false;
|
|
1822
|
+
// #2425 — reset at the top of every call: a stray leftover event from a truly-abandoned
|
|
1823
|
+
// PREVIOUS turn must not be allowed to affect THIS new call's turn boundary (see the field
|
|
1824
|
+
// comment).
|
|
1825
|
+
this.turnAbandoned = false;
|
|
1826
|
+
this.serverRetryWall = undefined;
|
|
1827
|
+
// #2413 review (HIGH-1) — one fresh turn id per send() (stable across this call's retries/
|
|
1828
|
+
// rerolls, which are ONE logical turn), so every send is distinct and the busy edge below hands
|
|
1829
|
+
// runner-core this exact id as its currentTurnId.
|
|
1830
|
+
this.sendTurnId = `opencode-turn-${crypto.randomUUID()}`;
|
|
1831
|
+
try {
|
|
1832
|
+
for (const relayContextInput = prepareOpenCodeRelayContext(process, text);;) {
|
|
1833
|
+
const client = process.meta?.client as OpenCodeClient | undefined;
|
|
1834
|
+
const sessionId = stringValue(process.meta?.sessionId);
|
|
1835
|
+
const model = stringValue(process.meta?.model) ?? openCodeQualifiedModel(undefined, this.provider);
|
|
1836
|
+
if (!client || !sessionId) throw new Error("OpenCode session is not ready");
|
|
1837
|
+
// #2397 round 5 review (LOW-2) — raised only once we're actually about to run an attempt
|
|
1838
|
+
// (past the guard above), so a "session not ready" throw never emits a close for a turn
|
|
1839
|
+
// that never started (never emitted its own busy edge below).
|
|
1840
|
+
this.turnInFlight = true;
|
|
1841
|
+
this.suppressProviderIdle = true;
|
|
1842
|
+
// #2459 round 1 (review D) — a headless turn now exists on this process: from here on,
|
|
1843
|
+
// terminal-origin busies are admitted only through the armed fresh-prompt gate.
|
|
1844
|
+
this.headlessTurnSeen = true;
|
|
1845
|
+
const providerState = attempt > 0 || rerolled
|
|
1846
|
+
? openCodeUpstreamRetryState({ attempt, rerolled, message: lastFailureDetail ?? "retrying", errorType: lastFailureErrorType })
|
|
1847
|
+
: undefined;
|
|
1848
|
+
this.statusCb({ status: "busy", reason: "provider-turn", ...(this.sendTurnId ? { id: this.sendTurnId } : {}), ...(providerState ? { providerState } : {}) });
|
|
1849
|
+
// #2389 — this endpoint is synchronous: it returns the COMPLETED assistant message. That
|
|
1850
|
+
// body is ground truth about whether the model produced anything, so it feeds the same
|
|
1851
|
+
// evidence funnel the cross-provider ledger reads, rather than being discarded as it was
|
|
1852
|
+
// during the incident. Only a positively-parsed non-empty completion counts; an
|
|
1853
|
+
// unrecognised body says nothing.
|
|
1854
|
+
// #2418/#2532 — compose once outside this retry loop, but the durable first-turn markers
|
|
1855
|
+
// are consumed by PROOF OF ACCEPTANCE, not by this await's return: `sendOpenCodeRelayTurn`
|
|
1856
|
+
// arms the SSE acceptance door before dispatch and reconciles against persisted session
|
|
1857
|
+
// messages if the await itself fails, so an accepted-but-interrupted turn (e.g. the #2425
|
|
1858
|
+
// whole-turn abort) still retains the consumed flags instead of re-prepending the briefing
|
|
1859
|
+
// on every queued message. A readiness guard or rejected transport request has not reached
|
|
1860
|
+
// the provider, so RunnerCore's retry must still include the briefing. Retryable resolved
|
|
1861
|
+
// completions and the same-turn session re-roll reuse this exact immutable local input, so
|
|
1862
|
+
// prefixes neither stack nor disappear mid-turn.
|
|
1863
|
+
inputObserver?.offered(Date.now(), false);
|
|
1864
|
+
let completion: OpenCodeCompletion;
|
|
1865
|
+
try {
|
|
1866
|
+
completion = await sendOpenCodeRelayTurn(client, process, sessionId, model, relayContextInput, (body) => this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body }));
|
|
1867
|
+
} catch (error) {
|
|
1868
|
+
if (this.holdOnServerRetryWall()) return "held";
|
|
1869
|
+
throw error;
|
|
1870
|
+
}
|
|
1871
|
+
if (this.holdOnServerRetryWall()) return "held";
|
|
1872
|
+
// #2355 — every completion (success, a produced-output error, or a still-retrying error)
|
|
1873
|
+
// carries this turn's fresh token usage; report it to Relay's normal context telemetry path
|
|
1874
|
+
// the same way Codex's `codexTokenUsageContext` does, rather than only on the happy path.
|
|
1875
|
+
this.reportContextUsage(process, model, completion.tokens);
|
|
1876
|
+
// MessageAbortedError is the intended outcome of an interrupt() (session.abort) WE issued
|
|
1877
|
+
// (or the operator issued) — not a provider failure. Fall through to the ordinary
|
|
1878
|
+
// empty/non-empty handling below rather than retrying or failing an intentional cancel.
|
|
1879
|
+
if (completion.error && completion.error.name !== "MessageAbortedError") {
|
|
1880
|
+
const error = completion.error;
|
|
1881
|
+
const detail = `${error.name}${error.statusCode !== undefined ? ` ${error.statusCode}` : ""}: ${error.message}`;
|
|
1882
|
+
// #2397 round 2 (F1/F2) — `completion.error` used to be checked independently of any
|
|
1883
|
+
// output the turn already produced. A turn that ran tool steps (a commit, say) and THEN
|
|
1884
|
+
// hit a retryable error was resent into a brand-new session with no memory of that work
|
|
1885
|
+
// — re-executing it. And a non-retryable error on a turn that DID produce a reply (e.g.
|
|
1886
|
+
// MessageOutputLengthError after a long, otherwise-successful answer) took a healthy
|
|
1887
|
+
// agent offline. Retrying or failing must only ever happen when the turn produced
|
|
1888
|
+
// NOTHING; with output present, report the error and let the turn end normally.
|
|
1889
|
+
// #2397 round 3 (HIGH-1) — `completionProducedOutput` excludes bare step-start/finish
|
|
1890
|
+
// bookkeeping parts. The issue's own captured incident (one bare step-start part, zero
|
|
1891
|
+
// tokens, a 429) used to satisfy the old parts>0 check and take THIS branch — no retry,
|
|
1892
|
+
// no error status, reported as a clean success. It must fall through to the retry path.
|
|
1893
|
+
const producedOutput = completionProducedOutput(completion);
|
|
1894
|
+
if (producedOutput) {
|
|
1895
|
+
this.sessionEventCb({ type: "narration", origin: "provider", body: `OpenCode reported ${detail} after the turn had already produced output; not retrying — reporting the output instead.` });
|
|
1896
|
+
this.clearUpstreamRetryState(attempt, rerolled);
|
|
1897
|
+
this.emitTurnFinalResponse(process, completion);
|
|
1898
|
+
this.closeSuppressedTurn();
|
|
1899
|
+
this.turnActivityCb();
|
|
1900
|
+
this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
|
|
1901
|
+
return "ok";
|
|
1902
|
+
}
|
|
1903
|
+
// #2619 — an explicit account usage/quota wall: in-session retries and a re-roll cannot
|
|
1904
|
+
// clear it (the re-roll would only drop the conversation). Hold now on the same session;
|
|
1905
|
+
// the relay resumes us. Checked BEFORE the non-retryable exit: a provider may send a
|
|
1906
|
+
// quota wall as a non-retryable 403 ("Quota exceeded; limit will reset tomorrow").
|
|
1907
|
+
if (isProviderUsageLimit(error)) {
|
|
1908
|
+
this.holdTurnOnProviderLimit(error, detail);
|
|
1909
|
+
return "held";
|
|
1910
|
+
}
|
|
1911
|
+
// #2619 — throttle wording is retryable whatever the provider flagged (zai sends a
|
|
1912
|
+
// non-retryable 403 "Rate limit exceeded"): retry, then hold like any throttle. A real
|
|
1913
|
+
// auth/model refusal still fails here.
|
|
1914
|
+
if (!error.isRetryable && !isProviderThrottle(error)) {
|
|
1915
|
+
this.failTurn(detail, openCodeTerminalFailureReason(error));
|
|
1916
|
+
return "failed";
|
|
1917
|
+
}
|
|
1918
|
+
if (attempt < OPENCODE_ERROR_RETRY_MAX_ATTEMPTS) {
|
|
1919
|
+
attempt += 1;
|
|
1920
|
+
const delayMs = openCodeErrorRetryDelayMs(attempt);
|
|
1921
|
+
lastFailureDetail = detail;
|
|
1922
|
+
lastFailureErrorType = error.errorType;
|
|
1923
|
+
// #2397 round 3 (MED-5) — `diagnostic: true`: this narrates the ADAPTER's own retry
|
|
1924
|
+
// mechanics, not anything the model produced. Left uncredited it would let
|
|
1925
|
+
// emptyTurns.noteOutput() (runner-core's onSessionEvent wiring) read a retry storm
|
|
1926
|
+
// that ultimately fails as forward progress, cancelling #2389's input-armed escalation
|
|
1927
|
+
// that is the only thing left to wake the spawn parent once failTurn takes the agent
|
|
1928
|
+
// offline below.
|
|
1929
|
+
this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed (${detail}); retrying in ${Math.round(delayMs / 1000)}s (attempt ${attempt}/${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS})…` });
|
|
1930
|
+
// suppressProviderIdle is already raised for the whole call (see entry) — nothing to
|
|
1931
|
+
// set here; this attempt's own leaked idle is suppressed same as attempt 1's.
|
|
1932
|
+
await this.sleepImpl(delayMs);
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
// #2619 — a provider overload (5xx) is provider-wide, not a broken session: a re-roll
|
|
1936
|
+
// would only drop the history before the same wall. Hold on the SAME session.
|
|
1937
|
+
if (isProviderOverload(error)) {
|
|
1938
|
+
this.holdTurnOnProviderLimit(error, `${detail} (after ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} in-session retries)`);
|
|
1939
|
+
return "held";
|
|
1940
|
+
}
|
|
1941
|
+
// Retry exhausted for the CURRENT session. Bimodal-per-session evidence (#2397 issue
|
|
1942
|
+
// comment) says an ambiguous 429 can be a per-session failure — re-roll to a fresh one
|
|
1943
|
+
// once. #2619 — at most ONCE PER HOLD EPISODE (until a turn goes through again), so a
|
|
1944
|
+
// long wall re-rolls once and then holds: history loss stays bounded to one attempt.
|
|
1945
|
+
if (!rerolled && !this.rerolledThisHoldEpisode) {
|
|
1946
|
+
this.rerolledThisHoldEpisode = true;
|
|
1947
|
+
// #2397 round 2 (F5) — say plainly that this drops the conversation: re-rolling
|
|
1948
|
+
// mutates process.meta.sessionId to a brand-new, history-less session, which for a
|
|
1949
|
+
// long-lived agent throws away everything said so far.
|
|
1950
|
+
this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} times in a row in this session (${detail}); re-rolling to a new OpenCode session (this drops the current conversation history) and retrying…` });
|
|
1951
|
+
let newSessionId: string;
|
|
1952
|
+
try {
|
|
1953
|
+
newSessionId = await client.createSession(stringValue(process.meta?.sessionTitle) ?? "agent-relay");
|
|
1954
|
+
} catch (createError) {
|
|
1955
|
+
// #2619 — the original session is still intact; a failed re-roll during a
|
|
1956
|
+
// provider outage is no reason to end the agent. Hold like the exhausted path.
|
|
1957
|
+
this.holdTurnOnProviderLimit(error, `${detail} (session re-roll failed: ${errMessage(createError)})`);
|
|
1958
|
+
return "held";
|
|
1959
|
+
}
|
|
1960
|
+
// Every other reader of the session id (send/interrupt/shutdown) goes through
|
|
1961
|
+
// process.meta.sessionId, so mutating it in place re-routes the whole adapter to the
|
|
1962
|
+
// new session — no separate "which session is active" bookkeeping needed.
|
|
1963
|
+
if (process.meta) process.meta.sessionId = newSessionId;
|
|
1964
|
+
// #2539 Phase B — soft-steer fencing on re-roll: same contract as the clear swap —
|
|
1965
|
+
// accepted-but-unconsumed batches on the dead session reoffer to the normal queue
|
|
1966
|
+
// (the settlement callback fires inside sessionReplaced).
|
|
1967
|
+
const softSteerOrphans = await this.softSteer.sessionReplaced(newSessionId);
|
|
1968
|
+
if (softSteerOrphans.length > 0) {
|
|
1969
|
+
logger.info("opencode", `soft-steer: reoffering ${softSteerOrphans.length} relay id(s) after the retry budget re-rolled the session`);
|
|
1970
|
+
}
|
|
1971
|
+
rerolled = true;
|
|
1972
|
+
attempt = 0;
|
|
1973
|
+
lastFailureDetail = detail;
|
|
1974
|
+
lastFailureErrorType = error.errorType;
|
|
1975
|
+
// suppressProviderIdle is already raised for the whole call — see entry.
|
|
1976
|
+
continue;
|
|
1977
|
+
}
|
|
1978
|
+
// #2619 — retries and the re-roll are exhausted on a RETRYABLE error: the provider is
|
|
1979
|
+
// down or throttling, not the agent. This used to failTurn → terminal `error` → runner
|
|
1980
|
+
// exit, killing every zai session ~10 min into a quota wall. Hold and let the relay's
|
|
1981
|
+
// resume sweep back off and wake the agent, as it does for Claude.
|
|
1982
|
+
this.holdTurnOnProviderLimit(error, `${detail} (after ${OPENCODE_ERROR_RETRY_MAX_ATTEMPTS} retries and a session re-roll)`);
|
|
1983
|
+
return "held";
|
|
1984
|
+
}
|
|
1985
|
+
if (!completion.empty && completionProducedOutput(completion)) {
|
|
1986
|
+
this.clearUpstreamRetryState(attempt, rerolled);
|
|
1987
|
+
this.emitTurnFinalResponse(process, completion);
|
|
1988
|
+
this.closeSuppressedTurn();
|
|
1989
|
+
this.turnActivityCb();
|
|
1990
|
+
this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
|
|
1991
|
+
return "ok";
|
|
1992
|
+
}
|
|
1993
|
+
// Either a non-empty completion with no positively-parsable output (an unrecognised body —
|
|
1994
|
+
// says nothing either way), or a POSITIVELY parsed empty completion (#2389's normal
|
|
1995
|
+
// empty-turn path, closed deterministically here rather than depending on a possibly late
|
|
1996
|
+
// or lost SSE idle frame). Either way this POST returning is itself proof the turn is over
|
|
1997
|
+
// — the endpoint is synchronous — so let the shared `finally` below perform the close
|
|
1998
|
+
// instead of duplicating it at two near-identical return sites. #2397 round 4 review (F3):
|
|
1999
|
+
// that exact duplication was unadjudicated — deleting either explicit call left every
|
|
2000
|
+
// opencode test green, since `finally` already produces the identical close with no
|
|
2001
|
+
// observable ordering difference from here (nothing else runs between this `return` and
|
|
2002
|
+
// the `finally`).
|
|
2003
|
+
this.rerolledThisHoldEpisode = false; // #2619 — the provider took a turn: the hold episode is over
|
|
2004
|
+
return "ok";
|
|
2005
|
+
}
|
|
2006
|
+
} finally {
|
|
2007
|
+
// #2397 round 4/5 review (F2) — the single deterministic close for this whole call. Some
|
|
2008
|
+
// return paths above call `closeSuppressedTurn()` explicitly first (when something else in
|
|
2009
|
+
// that branch — `turnActivityCb`, most notably — must run AFTER the close, so ordering
|
|
2010
|
+
// matters); the rest fall straight through to here, since nothing runs between their
|
|
2011
|
+
// `return` and this `finally` and an explicit call there would be a pure no-op duplicate
|
|
2012
|
+
// (see F3 above). `failTurn`'s own unconditional close also marks `turnClosed`, so by the
|
|
2013
|
+
// time ANY return path reaches here this is a no-op — EXCEPT when NONE of those ran: a throw
|
|
2014
|
+
// from `client.sendMessage` (transport reset, socket drop) or the "session not ready" guard.
|
|
2015
|
+
// #2397 round 6 review (MED-2r) — that guard throw must NOT fall through to
|
|
2016
|
+
// `closeSuppressedTurn()`. The old comment here claimed `suppressProviderIdle` "was also
|
|
2017
|
+
// never raised this call, so closeSuppressedTurn is a no-op there too" — true only on the
|
|
2018
|
+
// FIRST call. `suppressProviderIdle` is STICKY (round 5, MED-1): on any later call, if the
|
|
2019
|
+
// prior call's own trailing idle has not yet arrived to lower it back to false, it is still
|
|
2020
|
+
// true when this guard throws — so the old code emitted a phantom `idle + clear` for a turn
|
|
2021
|
+
// that never started, never emitted a busy, never reached the provider (verified: cold = 0
|
|
2022
|
+
// closes, warm = 1). Gate on whether THIS call ever actually started an attempt
|
|
2023
|
+
// (`turnInFlight` was raised past the guard), captured before resetting it below, instead of
|
|
2024
|
+
// on the sticky flag. Before F2 (unrelated to this gate) that left `suppressProviderIdle`
|
|
2025
|
+
// raised forever with no further idle to lower it — `currentTurnId` never cleared, the
|
|
2026
|
+
// provider-turn claim stuck busy, queued mail never draining.
|
|
2027
|
+
const turnStartedThisCall = this.turnInFlight;
|
|
2028
|
+
this.turnInFlight = false;
|
|
2029
|
+
// Closing here (when `turnStartedThisCall` is true — a genuine mid-attempt throw) trades a
|
|
2030
|
+
// turn the server might still be running for a turn that reliably ends; the adapter has no
|
|
2031
|
+
// other signal on a throw, and `failTurn` already makes that same trade unconditionally today.
|
|
2032
|
+
// #2425 — `turnClosed` still being false at this point means NEITHER a success branch nor
|
|
2033
|
+
// `failTurn` ran before we got here — the only way in is an uncaught throw (e.g. `sendMessage`
|
|
2034
|
+
// aborting on the #2425 turn-timeout, or any other transport failure) whose actual server-side
|
|
2035
|
+
// outcome is unknown. Mark the turn abandoned before force-closing it, so a genuine same-
|
|
2036
|
+
// session busy/idle that arrives later can still self-heal the status instead of being
|
|
2037
|
+
// swallowed forever by the otherwise-permanent `suppressProviderIdle` (see its field doc).
|
|
2038
|
+
if (turnStartedThisCall) {
|
|
2039
|
+
if (!this.turnClosed) this.turnAbandoned = true;
|
|
2040
|
+
this.closeSuppressedTurn();
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
/**
|
|
2046
|
+
* #2355 — the runner's `latestProcessContext()` (runner-core.ts) reads `process.meta.context`
|
|
2047
|
+
* directly (the same fallback path Codex's `meta.context` sets from `thread/tokenUsage/updated`),
|
|
2048
|
+
* feeding Relay's normal telemetry (dashboard/API/timeline) and the context-pressure warning /
|
|
2049
|
+
* self-compaction advisory. A no-op whenever the turn carried no parsable token count or the
|
|
2050
|
+
* model's window is unknown — silence here, not a fabricated 0/0 reading.
|
|
2051
|
+
*/
|
|
2052
|
+
private reportContextUsage(process: ManagedProcess, model: string | undefined, tokensUsed: number | undefined): void {
|
|
2053
|
+
// #2599 — precedence payload-record > legacy #2595 window > manifest. The record's qualified
|
|
2054
|
+
// name is ALSO threaded through: meta.model is re-pinned to the server-computed qualified name
|
|
2055
|
+
// when the record is present, so later reportContextUsage calls (which read meta.model) keep
|
|
2056
|
+
// resolving against the payload instead of re-deriving from a possibly-ambiguous alias.
|
|
2057
|
+
const config = process.meta?.config as RunnerSpawnConfig | undefined;
|
|
2058
|
+
const record = openCodeModelRecord(config?.modelRecord, this.provider);
|
|
2059
|
+
// #2659 — the configured window belongs to the LAUNCH model, not a terminal-switched one.
|
|
2060
|
+
const configWindow = openCodeOnLaunchModel(process.meta, model) ? record?.contextWindowTokens ?? config?.contextWindowTokens : undefined;
|
|
2061
|
+
const windowTokens = typeof configWindow === "number" && Number.isFinite(configWindow) && configWindow > 0
|
|
2062
|
+
? configWindow
|
|
2063
|
+
: openCodeContextWindowTokens(model, this.provider);
|
|
2064
|
+
const context = openCodeContextState({ tokensUsed, windowTokens });
|
|
2065
|
+
if (context && process.meta) process.meta.context = context;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/**
|
|
2069
|
+
* #2397 round 2 (F6) — once a retry (or the post-reroll retry) actually produces output, the
|
|
2070
|
+
* operator-visible "retrying…"/blocked hold must not linger on screen until whatever SSE idle
|
|
2071
|
+
* happens to arrive next — which `suppressProviderIdle` above can delay well past this turn.
|
|
2072
|
+
* Clear it explicitly and immediately; a no-op when no retry hold was ever shown.
|
|
2073
|
+
*/
|
|
2074
|
+
private clearUpstreamRetryState(attempt: number, rerolled: boolean): void {
|
|
2075
|
+
if (attempt === 0 && !rerolled) return;
|
|
2076
|
+
this.statusCb({
|
|
2077
|
+
status: "busy",
|
|
2078
|
+
reason: "provider-turn",
|
|
2079
|
+
providerState: { state: "active", reason: "provider-error-recovered", label: "OpenCode recovered after a provider error", source: "opencode", updatedAt: Date.now() },
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
/**
|
|
2084
|
+
* #2397 round 4/5 review (F1/F2/MED-1) — `suppressProviderIdle` is raised for the WHOLE `send()`
|
|
2085
|
+
* call, from just past the readiness guard (see the loop entry), so every path that ends the
|
|
2086
|
+
* turn — success, either failure, or the `finally`'s throw fallback — must close it
|
|
2087
|
+
* deterministically itself; the synchronous POST returning (or the throw itself) is ground truth
|
|
2088
|
+
* either way. `turnClosed` (NOT `suppressProviderIdle`) is the idempotency guard: it stops the
|
|
2089
|
+
* SAME call's multiple close sites — an explicit branch call plus the `finally` fallback — from
|
|
2090
|
+
* emitting a second idle for one call. `suppressProviderIdle` itself deliberately stays raised
|
|
2091
|
+
* once this fires (round 5, MED-1): a healthy turn's own trailing real SSE idle can still arrive
|
|
2092
|
+
* AFTER this close, and stays swallowed unless a genuine terminal turn or an abandoned turn
|
|
2093
|
+
* admits its own events — see the field comments.
|
|
2094
|
+
*/
|
|
2095
|
+
private closeSuppressedTurn(hold?: ProviderState): void {
|
|
2096
|
+
if (this.turnClosed) return;
|
|
2097
|
+
this.turnClosed = true;
|
|
2098
|
+
// #2484 round 1 review (F2/F3) — turn close: flush the debounced token-usage report now
|
|
2099
|
+
// rather than wait out its timer. Not awaited — a live turn boundary, not teardown; flush()
|
|
2100
|
+
// already routes failures to reporter.reportFailure internally, so the `.catch(() => {})`
|
|
2101
|
+
// here only satisfies the unguarded-async ratchet (#1665) — it never actually fires.
|
|
2102
|
+
void this.tokenUsagePoster.flushAll().catch(() => {});
|
|
2103
|
+
this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"], ...(hold ? { providerState: hold } : {}) });
|
|
2104
|
+
// #2539 Phase B — idle edge with unconsumed soft batches: arm the one bounded wake. Never
|
|
2105
|
+
// into a provider-limit hold: the wake would only hit the same wall; the relay's resume turn
|
|
2106
|
+
// ends in a normal idle that arms it then.
|
|
2107
|
+
if (!hold) this.maybeArmSoftSteerWake();
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/**
|
|
2111
|
+
* #2619 — end the turn in the shared rate-limit hold instead of a terminal failure: the turn
|
|
2112
|
+
* closes (idle + provider-turn) carrying the `blocked/rate_limit` providerState, which
|
|
2113
|
+
* runner-core keeps as its rateLimitHold until the relay's resume turn goes busy. The session
|
|
2114
|
+
* (and its history, including the refused user message) stays alive.
|
|
2115
|
+
*/
|
|
2116
|
+
private holdTurnOnProviderLimit(error: OpenCodeAssistantError, detail: string, resetAt?: number): void {
|
|
2117
|
+
const hold = buildProviderRefusalHold({ ...error, detail, source: "opencode", ...(resetAt ? { resetAt } : {}) });
|
|
2118
|
+
this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode provider refused the turn (${detail}); holding the session — Humwick resumes it ${hold.kind === "usage_limit" ? "when the usage window resets" : "with bounded backoff"}.` });
|
|
2119
|
+
this.closeSuppressedTurn(hold);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
/**
|
|
2123
|
+
* #2775 — OpenCode's own retry of an in-flight send(): a transient one is shown as "retrying"; a wall
|
|
2124
|
+
* (usage/free-tier limit, far-off `next`, or an endless throttle) aborts the session so the POST
|
|
2125
|
+
* returns and send() ends the turn in the shared rate-limit hold instead of waiting silently.
|
|
2126
|
+
*/
|
|
2127
|
+
private onServerRetry(retry: OpenCodeServerRetry | undefined, process?: ManagedProcess): void {
|
|
2128
|
+
if (!retry || !this.turnInFlight || this.serverRetryWall) return;
|
|
2129
|
+
if (!isOpenCodeServerRetryWall(retry)) {
|
|
2130
|
+
this.statusCb({ status: "busy", reason: "provider-turn", ...(this.sendTurnId ? { id: this.sendTurnId } : {}), providerState: openCodeServerRetryState(retry) });
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
this.serverRetryWall = retry;
|
|
2134
|
+
const client = process?.meta?.client as OpenCodeClient | undefined;
|
|
2135
|
+
const sessionId = stringValue(process?.meta?.sessionId);
|
|
2136
|
+
if (client && sessionId) void fireAndForget(`opencode abort on provider wall (session ${sessionId})`, () => client.abort(sessionId), (message) => logger.error("opencode", message));
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
private holdOnServerRetryWall(): boolean {
|
|
2140
|
+
const wall = this.serverRetryWall;
|
|
2141
|
+
if (!wall) return false;
|
|
2142
|
+
this.serverRetryWall = undefined;
|
|
2143
|
+
const error: OpenCodeAssistantError = { name: "OpenCodeRetry", message: wall.message, ...(wall.reason ? { errorType: wall.reason } : {}), isRetryable: true };
|
|
2144
|
+
this.holdTurnOnProviderLimit(error, `${wall.message} (OpenCode retry attempt ${wall.attempt}${wall.reason ? `, ${wall.reason}` : ""})`, wall.next);
|
|
2145
|
+
return true;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
/**
|
|
2149
|
+
* #2459 — close the event-driven (terminal-origin) turn on its matching SSE idle: emit the
|
|
2150
|
+
* turn-final response accumulated from the turn's finalized assistant text parts (the same
|
|
2151
|
+
* narration→final-response shape send()'s POST path produces, and the same turnId so the
|
|
2152
|
+
* dashboard's per-turn narration/response dedup applies), then the idle edge that closes
|
|
2153
|
+
* runner-core's turn. Completes the #2425 abandonment self-heal (the server's own truth for
|
|
2154
|
+
* the abandoned turn has now arrived). Deliberately does NOT re-raise the sticky idle
|
|
2155
|
+
* suppression or stamp the echo window: an event turn's busy+idle arrive LIVE — they DRIVE
|
|
2156
|
+
* this state machine; there is no send()-POST await whose buffered chunk could replay them —
|
|
2157
|
+
* so there is no trailing-echo shape to guard against, and a genuine follow-up terminal turn
|
|
2158
|
+
* must be able to open immediately.
|
|
2159
|
+
*/
|
|
2160
|
+
private closeEventTurn(): void {
|
|
2161
|
+
const turnId = this.eventTurnId;
|
|
2162
|
+
const text = this.eventTurnText.join("\n\n").trim();
|
|
2163
|
+
// #2605 — reconcile the turn's reasoning parts against what the live door already emitted,
|
|
2164
|
+
// per part id: a part whose latest snapshot EQUALS its last-emitted body needs no row (it is
|
|
2165
|
+
// already traced); a part that GREW after its last live emit — or was never live-emitted
|
|
2166
|
+
// (frames racing the close, a part observed only at close time) — gets one row under its OWN
|
|
2167
|
+
// part id, so the server upserts rather than duplicates. Comparison is scoped to THIS turn's
|
|
2168
|
+
// part ids (eventTurnReasoning) against their own ledger entries — never to a global body
|
|
2169
|
+
// pool — so an identical body in a later turn is never misread as already-seen. Each entry
|
|
2170
|
+
// is already the newest full snapshot for its part, so there is nothing to prefix-merge.
|
|
2171
|
+
const unseen: OpenCodeReasoningStep[] = [];
|
|
2172
|
+
for (const [stepId, body] of this.eventTurnReasoning) {
|
|
2173
|
+
const trimmed = body.trim();
|
|
2174
|
+
if (!trimmed) continue;
|
|
2175
|
+
if (this.emittedReasoningParts.get(stepId) === trimmed) continue;
|
|
2176
|
+
unseen.push({ stepId, body: trimmed });
|
|
2177
|
+
}
|
|
2178
|
+
this.eventTurnId = undefined;
|
|
2179
|
+
this.eventTurnText = [];
|
|
2180
|
+
this.eventTurnReasoning = new Map();
|
|
2181
|
+
// Stray idles with no open event turn stay swallowed via the existing `turnClosed` guard.
|
|
2182
|
+
this.turnClosed = true;
|
|
2183
|
+
// #2425 self-heal completion: this idle close was the server's own truth for the abandoned
|
|
2184
|
+
// turn. Round 1 (review D) — re-latch the sticky suppression it had bypassed (the #2425
|
|
2185
|
+
// escape hatch exists only while the abandonment is unresolved), so post-heal strays stay
|
|
2186
|
+
// swallowed under the same permanent suppression as every other send()-closed turn.
|
|
2187
|
+
if (this.turnAbandoned) {
|
|
2188
|
+
this.turnAbandoned = false;
|
|
2189
|
+
this.suppressProviderIdle = true;
|
|
2190
|
+
}
|
|
2191
|
+
if (turnId) {
|
|
2192
|
+
for (const step of unseen) this.emitReasoningFinal(openCodeReasoningSessionEvent(step, turnId, false));
|
|
2193
|
+
for (const event of openCodeTurnFinalEvents({ text }, turnId)) this.sessionEventCb(event);
|
|
2194
|
+
}
|
|
2195
|
+
// #2484 round 1 review (F2/F3) — same flush as closeSuppressedTurn's turn close; the
|
|
2196
|
+
// `.catch(() => {})` is the same ratchet-only no-op (flush() already self-catches).
|
|
2197
|
+
void this.tokenUsagePoster.flushAll().catch(() => {});
|
|
2198
|
+
this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
|
|
2199
|
+
// #2539 Phase B — a terminal-origin turn's close is an idle edge too: unconsumed soft
|
|
2200
|
+
// batches get the same single bounded wake.
|
|
2201
|
+
this.maybeArmSoftSteerWake();
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
/**
|
|
2205
|
+
* #2459 round 2 (review D) — emit a buffered terminal prompt under `turnId` and clear the
|
|
2206
|
+
* slot. `turnId` is the freshly minted terminal turn id (primary path — busy admission) or
|
|
2207
|
+
* the session pseudo-turn (bounded no-busy fallbacks); it is NEVER a stale headless
|
|
2208
|
+
* sendTurnId, which only exists while send() is in flight and is never passed here.
|
|
2209
|
+
*/
|
|
2210
|
+
private flushBufferedPrompt(turnId: string | undefined): void {
|
|
2211
|
+
const buffered = this.bufferedTerminalPrompt;
|
|
2212
|
+
if (!buffered) return;
|
|
2213
|
+
this.bufferedTerminalPrompt = undefined;
|
|
2214
|
+
this.sessionEventCb({ type: "prompt", origin: "terminal", body: buffered.body, occurredAt: buffered.occurredAt, ...(turnId ? { turnId } : {}) });
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
/**
|
|
2218
|
+
* #2459 — the turn id live session events should attach to: send()'s per-call id while a
|
|
2219
|
+
* headless delivery is in flight, otherwise the event-driven terminal turn minted on the
|
|
2220
|
+
* server's own busy edge. Deliberately NO fallback to a stale `sendTurnId` outside send() —
|
|
2221
|
+
* that inheritance is exactly the contamination this replaces; callers fall back to the
|
|
2222
|
+
* session pseudo-turn instead.
|
|
2223
|
+
*/
|
|
2224
|
+
private activeTurnId(): string | undefined {
|
|
2225
|
+
return this.turnInFlight ? this.sendTurnId : this.eventTurnId;
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
private failTurn(detail: string, terminalFailureReason: string): void {
|
|
2229
|
+
// #2397 round 3 (MED-5) — diagnostic:true; see the retry-loop narration above for why.
|
|
2230
|
+
this.sessionEventCb({ type: "narration", origin: "provider", diagnostic: true, body: `OpenCode turn failed: ${detail}` });
|
|
2231
|
+
// #2397 round 2 (F3) — an "error" status alone is never treated as a turn boundary by
|
|
2232
|
+
// #2389's seam in runner-core (only `idle && provider-turn` clears `currentTurnId` and notes
|
|
2233
|
+
// the turn ended); without this, the next `busy` reuses the stale turn id and the empty-turn
|
|
2234
|
+
// detector is blinded for the rest of the agent's life. Close the turn through that same seam
|
|
2235
|
+
// first — exactly as the parsed-empty-completion path above already does — before marking the
|
|
2236
|
+
// terminal failure. This close is unconditional (fires whether or not suppressProviderIdle was
|
|
2237
|
+
// raised), so mark `turnClosed` here too — otherwise `send()`'s `finally` fallback would see it
|
|
2238
|
+
// still unset and emit a second, out-of-order idle after the error status below.
|
|
2239
|
+
this.turnClosed = true;
|
|
2240
|
+
// #2484 round 1 review (F2/F3) — same flush as the other turn-close sites: a failed turn can
|
|
2241
|
+
// still carry real (partial) usage worth persisting; the `.catch(() => {})` is the same
|
|
2242
|
+
// ratchet-only no-op as the other two sites.
|
|
2243
|
+
void this.tokenUsagePoster.flushAll().catch(() => {});
|
|
2244
|
+
this.statusCb({ status: "idle", reason: "provider-turn", clear: ["provider-turn"] });
|
|
2245
|
+
this.statusCb({
|
|
2246
|
+
status: "error",
|
|
2247
|
+
reason: "provider-turn",
|
|
2248
|
+
clear: ["provider-turn"],
|
|
2249
|
+
// #2443 — mirrors the Claude adapter's own "model-unavailable" stop-failure shape
|
|
2250
|
+
// (providerState + an embedded timeline event) instead of leaving this error silent beyond
|
|
2251
|
+
// the mirror log: a terminal failure used to reach runner-core with no timeline event and no
|
|
2252
|
+
// classification, so an exit driven by this status surfaced nothing to the operator beyond
|
|
2253
|
+
// "exited after Ns". `terminal: true` marks it as needing operator attention, same as Claude's.
|
|
2254
|
+
providerState: { state: "failed", reason: terminalFailureReason, message: detail, source: "opencode-turn-failure", terminal: true },
|
|
2255
|
+
metadata: { terminalFailureReason, terminalFailureMessage: detail },
|
|
2256
|
+
timeline: {
|
|
2257
|
+
status: "provider.restart_decision",
|
|
2258
|
+
title: terminalFailureReason === "model-unavailable" ? "OpenCode model unavailable" : "OpenCode turn failed",
|
|
2259
|
+
body: detail,
|
|
2260
|
+
metadata: { eventType: "provider.restart_decision", decision: "stop-surface", reason: terminalFailureReason },
|
|
2261
|
+
},
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|