@humwick/server 0.130.1 → 0.130.3
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/README.md +5 -0
- package/bundled/prompts/manifest.json +1 -0
- package/bundled/prompts/task.hydration.md +1 -1
- package/bundled/roles/builder.md +1 -1
- package/docs/guides/sub-coordinators.md +12 -5
- package/docs/guides/tools-administration.md +4 -0
- package/docs/guides/tools-coordination.md +2 -0
- package/docs/guides/tools-tasks.md +8 -0
- package/docs/guides/workflows.md +74 -0
- package/docs/openapi.json +486 -4
- package/package.json +10 -18
- package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-Bq9kr21N.js} +2 -2
- package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-Dqiroy06.js} +2 -2
- package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-DYU962-H.js} +2 -2
- package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-DhPfzioE.js} +2 -2
- package/public/assets/{activity-yLztXEBt.js → activity-DzCcQTee.js} +2 -2
- package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-SgDt9i3T.js} +2 -2
- package/public/assets/agent-liveness-Bq-s73jW.js +2 -0
- package/public/assets/{agent-name-CTZBrSD9.js → agent-name-CcY3Aijy.js} +2 -2
- package/public/assets/agent-profiles-cxtRBn6O.js +2 -0
- package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-DGYbIhaI.js} +2 -2
- package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-DHbftV0N.js} +2 -2
- package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-CY113sp-.js} +2 -2
- package/public/assets/{agents-BYDslkJc.js → agents-BPzY7IlQ.js} +2 -2
- package/public/assets/{analytics-B-XSbEMC.js → analytics-DENFPv_a.js} +2 -2
- package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-KRvSc7sl.js} +2 -2
- package/public/assets/{artifacts-DPN1q4pA.js → artifacts-C9gSmL3s.js} +2 -2
- package/public/assets/{artifacts-oBOqks-2.js → artifacts-DT-HkI5O.js} +2 -2
- package/public/assets/{automation-4KcRFXEu.js → automation-BsmjQhKD.js} +2 -2
- package/public/assets/{backlog-Cw9xPTCF.js → backlog-8NYH_62t.js} +2 -2
- package/public/assets/{busy-health-CTu32MZt.js → busy-health-D3hwvYb4.js} +2 -2
- package/public/assets/{channels-BiXzg1G9.js → channels-DCJ5WL7O.js} +2 -2
- package/public/assets/{chat-Ctk2qwvA.js → chat-DAIl357U.js} +3 -3
- package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-CzrZfr9U.js} +2 -2
- package/public/assets/{connectors-C36ax4Vw.js → connectors-xTxtlyou.js} +2 -2
- package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BpebFLK2.js} +2 -2
- package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-BHvI5wXC.js} +2 -2
- package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-DGT0o3wJ.js} +2 -2
- package/public/assets/{deploys-Bwfq146q.js → deploys-hqWOHSHE.js} +2 -2
- package/public/assets/{display-CfO7rTuF.js → display-CDeH4I8T.js} +2 -2
- package/public/assets/{files-DhhZI2Ih.js → files-BTeGVmTg.js} +2 -2
- package/public/assets/{fleet-B_rFEbnL.js → fleet-DgXtNgCa.js} +2 -2
- package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BHEt5yc_.js} +2 -2
- package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-MLtodyPp.js} +3 -3
- package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-CAe664Z8.js} +2 -2
- package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-mapF4Fj9.js} +2 -2
- package/public/assets/index-CqqgOI5R.js +15 -0
- package/public/assets/index-MYAIwYTX.css +2 -0
- package/public/assets/{insights-fH1EL-qZ.js → insights-DNyf7IzF.js} +2 -2
- package/public/assets/{integrations-C5Oix6ng.js → integrations-DlYOHq7T.js} +2 -2
- package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-CeWBNWn7.js} +2 -2
- package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-Cib8nqdE.js} +2 -2
- package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-DSn1cPm7.js} +2 -2
- package/public/assets/{managed-agents-egllbpij.js → managed-agents-C3CkM81m.js} +2 -2
- package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-W4VQQPAv.js} +3 -3
- package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-CMsTdhjq.js} +2 -2
- package/public/assets/{memory-C8WmXsyr.js → memory-BV17qtO4.js} +2 -2
- package/public/assets/{messages-BzQdqlO6.js → messages-Bm_1AqSA.js} +2 -2
- package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Dg-vfP09.js} +2 -2
- package/public/assets/{overview-DCUnI4Z7.js → overview-DcYCrvlr.js} +2 -2
- package/public/assets/{pairs-ftCd2XcD.js → pairs-8o9nuG3V.js} +2 -2
- package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-BsRyBMea.js} +2 -2
- package/public/assets/project-profile-instructions-DETKFn_E.js +2 -0
- package/public/assets/projects-M_m6AHws.js +2 -0
- package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-C_mNCgeL.js} +2 -2
- package/public/assets/provider-display-BEeLXC_D.js +2 -0
- package/public/assets/{registry-DWCU5c8q.js → registry-DDHHHE5N.js} +2 -2
- package/public/assets/run-card-Dt4I4jaP.js +2 -0
- package/public/assets/scroll-text-BKXS1ahr.js +2 -0
- package/public/assets/{secrets-C7GIUATz.js → secrets-BkVwxI5O.js} +2 -2
- package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-Crq-6a-B.js} +2 -2
- package/public/assets/{settings-C6sZ9eBa.js → settings-CYcavMFu.js} +2 -2
- package/public/assets/shipping-H6gzEeZB.js +2 -0
- package/public/assets/{stats-V9Z-GxUd.js → stats-BJVeWkpV.js} +2 -2
- package/public/assets/{status-dot-CVv0iGHL.js → status-dot-BcHyw1Vw.js} +2 -2
- package/public/assets/store-DKPDIZBe.js +9 -0
- package/public/assets/{tasks-CXU75HRF.js → tasks-qJjlQPpx.js} +2 -2
- package/public/assets/{teams-BPf976zb.js → teams-Cd5Sk1uX.js} +2 -2
- package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-CpY5_m7W.js} +3 -3
- package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-DI_2llFR.js} +2 -2
- package/public/assets/token-estimate-Co-Jdphh.js +2 -0
- package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-0yJA_buM.js} +2 -2
- package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-uJXHf34W.js} +2 -2
- package/public/assets/use-project-list-CpWl6eHQ.js +2 -0
- package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BdxBW8mL.js} +2 -2
- package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-3IndfpOc.js} +2 -2
- package/public/assets/versioned-prompt-editor-CIVg7XB1.js +8 -0
- package/public/assets/{work-queue-TCjJxeT-.js → work-queue-B9rnLwHZ.js} +2 -2
- package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-X8wxyhzC.js} +2 -2
- package/public/assets/{workflows-CXQX20Qj.js → workflows-o5YyMFW7.js} +2 -2
- package/public/assets/{workspaces-qSYzKYMA.js → workspaces-CzEGoAng.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +31 -29
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
- package/runner/src/adapters/claude-effort.ts +2 -4
- package/runner/src/adapters/claude-headless.ts +1 -1
- package/runner/src/adapters/claude-launch-exit.ts +26 -0
- package/runner/src/adapters/claude-session-probe.ts +1 -1
- package/runner/src/adapters/claude-tmux.ts +38 -10
- package/runner/src/adapters/claude.ts +28 -75
- package/runner/src/adapters/codex-launch-config.ts +4 -5
- package/runner/src/adapters/codex.ts +1 -1
- package/runner/src/adapters/opencode-provider.ts +7 -22
- package/runner/src/adapters/opencode.ts +4 -4
- package/runner/src/adapters/provider-quota-harvest.ts +20 -71
- package/runner/src/agent-runner/credentials.ts +42 -12
- package/runner/src/agent-runner/inbox.ts +27 -0
- package/runner/src/agent-runner/lifecycle-milestones.ts +2 -4
- package/runner/src/agent-runner/liveness-exchange.ts +104 -0
- package/runner/src/bin/claude.ts +4 -0
- package/runner/src/bin/codex.ts +4 -0
- package/runner/src/bin/opencode.ts +4 -0
- package/runner/src/claude-system-prompt.ts +49 -0
- package/runner/src/index.ts +5 -2
- package/runner/src/launch-assembly.ts +14 -10
- package/runner/src/profile-home.ts +1 -1
- package/runner/src/project-instructions.ts +2 -2
- package/runner/src/provider-command.ts +18 -0
- package/runner/src/provider-setup.ts +35 -0
- package/runner/src/providers.ts +2 -1
- package/runner/src/runner-core.ts +32 -38
- package/runner/src/runner-helpers.ts +0 -8
- package/runner/src/session-scratch.ts +6 -2
- package/runner/src/spawn-input.ts +2 -1
- package/runner/src/test-run-dedup.ts +59 -56
- package/runner/test-support/provisioning-context.ts +4 -2
- package/runner/test-support/runner-harness.ts +2 -1
- package/scripts/cd-config.ts +8 -0
- package/scripts/gate-run-lease.ts +10 -7
- package/scripts/gate.sh +5 -1
- package/scripts/steward-policy-cleanup.ts +2 -2
- package/src/agent-authz.ts +2 -2
- package/src/agent-drive-arbiter.ts +3 -8
- package/src/agent-exit-cleanup-async.ts +2 -2
- package/src/agent-exit-cleanup.ts +23 -19
- package/src/agent-lifecycle-events.ts +112 -85
- package/src/agent-ref.ts +3 -2
- package/src/agent-status-events.ts +43 -14
- package/src/auth/password.ts +4 -3
- package/src/auth/session-refresh.ts +1 -1
- package/src/auth/session.test-fixture.ts +2 -2
- package/src/auth/session.ts +7 -7
- package/src/automations/crud.ts +8 -81
- package/src/automations/dispatch.ts +33 -58
- package/src/automations/index.ts +1 -1
- package/src/automations/persistence.ts +65 -0
- package/src/automations/precheck.ts +18 -16
- package/src/automations/read-async.ts +11 -0
- package/src/automations/reconcile.ts +30 -39
- package/src/automations/storage.ts +33 -10
- package/src/automations/write-async.ts +33 -0
- package/src/branch-land-notice.ts +93 -0
- package/src/branch-landed.ts +31 -98
- package/src/bus.ts +40 -39
- package/src/cd-deploy-notify.ts +32 -4
- package/src/cd-host-lag-notify.ts +7 -6
- package/src/ci/incidents.ts +2 -2
- package/src/ci/monitor.ts +5 -8
- package/src/ci/notifications.ts +4 -4
- package/src/ci/root-missing.ts +4 -9
- package/src/ci/transition-notify.ts +12 -44
- package/src/claude-app/oauth-http.ts +3 -3
- package/src/cli/cd-pauses.ts +19 -0
- package/src/cli/orchestrator.ts +1 -1
- package/src/cli/state.ts +2 -19
- package/src/cli/task-edit.ts +22 -0
- package/src/cli/task.ts +3 -1
- package/src/cli/workflow.ts +19 -4
- package/src/commands-db.ts +7 -7
- package/src/commands-write.ts +8 -0
- package/src/compaction-watch.ts +10 -8
- package/src/config-store-write.ts +13 -1
- package/src/config-store.ts +25 -14
- package/src/config.ts +4 -11
- package/src/connectors.ts +10 -8
- package/src/context-advisory.ts +22 -21
- package/src/db/activity-async.ts +6 -1
- package/src/db/activity-write-ops.ts +12 -1
- package/src/db/activity.ts +1 -14
- package/src/db/agent-action-write.ts +36 -0
- package/src/db/agent-control-async.ts +9 -0
- package/src/db/agent-control-write.ts +49 -0
- package/src/db/agent-effort-observe.ts +22 -0
- package/src/db/agent-meta.ts +14 -3
- package/src/db/agent-search.ts +1 -15
- package/src/db/agents-async.ts +59 -9
- package/src/db/agents-write-ops.ts +12 -3
- package/src/db/agents.ts +95 -65
- package/src/db/auth-identities.ts +2 -2
- package/src/db/automation-read-ops.ts +11 -0
- package/src/db/automation-write-ops.ts +46 -0
- package/src/db/blackboard.ts +2 -2
- package/src/db/browser-session-write.ts +32 -0
- package/src/db/bus-outbox-write-ops.ts +3 -1
- package/src/db/checkout-refresh-schema.ts +47 -0
- package/src/db/checkout-refresh.ts +5 -5
- package/src/db/ci-incidents-async.ts +5 -5
- package/src/db/ci-incidents-write-ops.ts +2 -2
- package/src/db/command-agent-profile-backfill.ts +39 -0
- package/src/db/command-agent-profiles.ts +5 -1
- package/src/db/command-retention.ts +126 -0
- package/src/db/command-row.ts +10 -2
- package/src/db/commands-list-query.ts +2 -0
- package/src/db/commands-planner-stats.test-fixture.ts +8 -0
- package/src/db/commands-read-ops.ts +16 -0
- package/src/db/commands-spawn-read.ts +1 -1
- package/src/db/config-store-write-ops.ts +7 -2
- package/src/db/connection.ts +1 -182
- package/src/db/context-carry-write.ts +74 -0
- package/src/db/continuation-archives-async.ts +5 -0
- package/src/db/continuation-archives-write-ops.ts +7 -0
- package/src/db/continuations.ts +6 -6
- package/src/db/db-error-registry.ts +12 -0
- package/src/db/db-writer-worker.ts +8 -1
- package/src/db/deploy-events-async.ts +1 -0
- package/src/db/deploy-events-read-ops.ts +2 -1
- package/src/db/deploy-events.ts +35 -4
- package/src/db/gateway.ts +26 -133
- package/src/db/history-retention-ops.ts +13 -0
- package/src/db/hosts-registration.ts +10 -2
- package/src/db/hosts.ts +9 -6
- package/src/db/inbox-async.ts +2 -2
- package/src/db/inbox-read-ops.ts +1 -1
- package/src/db/inbox.ts +9 -17
- package/src/db/index.ts +2 -0
- package/src/db/integrations-async.ts +8 -0
- package/src/db/integrations-ops.ts +15 -0
- package/src/db/integrations.ts +15 -21
- package/src/db/issue-associations.ts +2 -2
- package/src/db/issue-lifecycle.ts +3 -4
- package/src/db/issue-refs-async.ts +23 -0
- package/src/db/issue-refs-read-ops.ts +6 -0
- package/src/db/issue-refs-write-ops.ts +29 -0
- package/src/db/issue-refs.ts +15 -16
- package/src/db/kernel-event-log-schema.ts +2 -0
- package/src/db/land-boundaries.ts +51 -5
- package/src/db/land-gate-attestations.ts +2 -2
- package/src/db/landing-receipts.ts +2 -2
- package/src/db/lifecycle-context-write.ts +5 -0
- package/src/db/lifecycle-tail-async.ts +76 -0
- package/src/db/lifecycle-tail-write-ops.ts +62 -0
- package/src/db/lifecycle-write-window.ts +2 -1
- package/src/db/lineage-pair-dedupe-sql.ts +3 -3
- package/src/db/long-running-work.ts +2 -2
- package/src/db/main-connection-mode.ts +11 -0
- package/src/db/main-connection-writes.ts +41 -2
- package/src/db/maintenance-job-reconcile.ts +61 -0
- package/src/db/maintenance-job-runs-async.ts +14 -0
- package/src/db/maintenance-job-write-ops.ts +9 -0
- package/src/db/maintenance-pauses.ts +2 -2
- package/src/db/mappers.ts +2 -14
- package/src/db/merge-lease.ts +1 -15
- package/src/db/message-send.ts +4 -0
- package/src/db/messages.ts +31 -25
- package/src/db/migrations.ts +25 -16
- package/src/db/notification-write-ops.ts +19 -0
- package/src/db/operator-answer-write.ts +19 -0
- package/src/db/orchestrators.ts +14 -76
- package/src/db/pairs-async.ts +22 -1
- package/src/db/pairs-write-ops.ts +8 -3
- package/src/db/pairs.ts +42 -61
- package/src/db/plan-dispatch-schema.ts +5 -1
- package/src/db/plan-ready-notices.ts +43 -0
- package/src/db/plan-tools-include-migration.ts +36 -0
- package/src/db/plans-write-ops.ts +2 -0
- package/src/db/presence-buffer.ts +5 -0
- package/src/db/presence-loop.ts +18 -23
- package/src/db/presence-write.ts +0 -6
- package/src/db/project-entries-async.ts +14 -0
- package/src/db/project-entries-read-ops.ts +5 -1
- package/src/db/project-entries-write-ops.ts +8 -0
- package/src/db/project-entries.ts +15 -12
- package/src/db/project-journal.ts +2 -2
- package/src/db/project-profile-instructions.ts +50 -0
- package/src/db/project-trust-approvals.ts +2 -2
- package/src/db/prompt-templates.ts +4 -4
- package/src/db/prompt-versions.ts +9 -2
- package/src/db/provider-config-history.ts +8 -3
- package/src/db/provider-model-seed.ts +34 -0
- package/src/db/read-op-marshaling-fixtures.ts +23 -0
- package/src/db/read-ops.ts +8 -0
- package/src/db/retention.ts +13 -2
- package/src/db/review-publish-write.ts +8 -0
- package/src/db/routing-policy.ts +2 -3
- package/src/db/runtime-admin-async.ts +19 -0
- package/src/db/runtime-admin-write-ops.ts +36 -0
- package/src/db/runtime-tail-async.ts +288 -0
- package/src/db/runtime-tail-write-ops.ts +97 -0
- package/src/db/schema.ts +13 -4
- package/src/db/spawn-budget.ts +5 -0
- package/src/db/spawn-delegation-state.ts +11 -4
- package/src/db/spawn-delegation.ts +1 -11
- package/src/db/sql-audit-indexes.ts +3 -0
- package/src/db/stats-async.ts +9 -6
- package/src/db/task-entity.ts +28 -6
- package/src/db/task-list.ts +3 -1
- package/src/db/tasks.ts +1 -17
- package/src/db/team-claim-policy.ts +60 -0
- package/src/db/teams-async.ts +4 -0
- package/src/db/teams-write-ops.ts +4 -0
- package/src/db/terminal-events.ts +15 -0
- package/src/db/timer-cutover-bodies.ts +292 -0
- package/src/db/timer-cutover-core.ts +37 -278
- package/src/db/timer-cutover-state.ts +40 -0
- package/src/db/timer-cutover-write-ops.ts +1 -40
- package/src/db/timer-ingress.ts +7 -1
- package/src/db/token-db-write-ops.ts +19 -0
- package/src/db/token-standing-async.ts +12 -0
- package/src/db/token-standing.ts +21 -20
- package/src/db/token-usage-retention.ts +33 -0
- package/src/db/token-usage-write-ops.ts +1 -1
- package/src/db/token-usage.ts +4 -1
- package/src/db/user-invites-async.ts +13 -0
- package/src/db/user-invites.ts +3 -3
- package/src/db/users.ts +14 -6
- package/src/db/workflow-contact.ts +1 -1
- package/src/db/workflow-task-read-ops.ts +30 -0
- package/src/db/workspaces.ts +8 -5
- package/src/db/write-ops.ts +37 -12
- package/src/db/writer-fault-seam.ts +2 -9
- package/src/db/writer-fault-test-ops.ts +18 -0
- package/src/db/writer-gateway.ts +58 -56
- package/src/dev.ts +8 -3
- package/src/dispatch-availability.ts +10 -6
- package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
- package/src/event-loop-lag.ts +4 -2
- package/src/event-loop-yield.ts +2 -2
- package/src/events.ts +10 -8
- package/src/host/disk-alert.ts +121 -0
- package/src/host-provider-store.ts +5 -4
- package/src/hub-metrics-report.ts +5 -1
- package/src/hub-metrics.ts +38 -18
- package/src/injection-sweep-pacing.ts +6 -8
- package/src/land-gate-notifications.ts +11 -10
- package/src/land-gate-queue-progress.ts +3 -3
- package/src/land-settle-dispatch.ts +52 -92
- package/src/lifecycle-context-pressure.ts +11 -10
- package/src/lifecycle-manager.ts +107 -100
- package/src/maintenance/breaker.ts +18 -16
- package/src/maintenance/cd-watcher.ts +3 -3
- package/src/maintenance/checkout-refresh.ts +18 -1
- package/src/maintenance/critical-job-watchdog.ts +19 -20
- package/src/maintenance/events.ts +3 -3
- package/src/maintenance/history-retention-job.ts +30 -0
- package/src/maintenance/index.ts +1 -0
- package/src/maintenance/jobs.ts +32 -69
- package/src/maintenance/long-running-work.ts +3 -1
- package/src/maintenance/offline-agent-prune-job.ts +28 -0
- package/src/maintenance/orphaned-sessions.ts +5 -4
- package/src/maintenance/pauses.ts +4 -10
- package/src/maintenance/plan-ready-notice-job.ts +71 -0
- package/src/maintenance/scheduler.ts +98 -137
- package/src/maintenance/stalled-delivery-pager.ts +10 -9
- package/src/maintenance/teams.ts +19 -30
- package/src/maintenance/workspaces.ts +18 -28
- package/src/managed-policy-escalation.ts +7 -7
- package/src/managed-policy.ts +32 -28
- package/src/managed-provider-gate.ts +5 -4
- package/src/managed-spawn-reservation.ts +5 -4
- package/src/mcp/index.ts +12 -9
- package/src/mcp/output-descriptions.ts +8 -8
- package/src/mcp/presentation.ts +6 -5
- package/src/mcp/project-profile-instructions.ts +32 -0
- package/src/mcp/runtime-output.ts +6 -1
- package/src/mcp/self-care-visibility.ts +2 -2
- package/src/mcp/spawn-targets-matrix.ts +10 -1
- package/src/mcp/tool-groups.ts +4 -3
- package/src/mcp/tool-roles.ts +81 -0
- package/src/mcp/tools-spawn.ts +4 -3
- package/src/mcp/tools-workspace.ts +1 -1
- package/src/mcp-agent-action.ts +1 -2
- package/src/mcp-blackboard-tools.ts +6 -5
- package/src/mcp-decision-tools.ts +7 -7
- package/src/mcp-egress-tools.ts +10 -9
- package/src/mcp-issue-activity-tools.ts +2 -2
- package/src/mcp-issue-association-tools.ts +4 -4
- package/src/mcp-issue-ref-tools.ts +13 -12
- package/src/mcp-maintenance-tools.ts +2 -2
- package/src/mcp-merge-tools.ts +5 -5
- package/src/mcp-notification-subscription-tools.ts +14 -15
- package/src/mcp-output-levels.test-fixture.ts +1 -1
- package/src/mcp-profile-trust-tools.ts +5 -5
- package/src/mcp-project-journal-tools.ts +4 -12
- package/src/mcp-project-tools.ts +17 -18
- package/src/mcp-provider-catalog.ts +2 -2
- package/src/mcp-routing-policy-tools.ts +3 -2
- package/src/mcp-task-tools.ts +11 -5
- package/src/mcp-team-template-tools.ts +3 -3
- package/src/mcp-team-tools.ts +6 -6
- package/src/mcp-workflow-tools.ts +13 -4
- package/src/memory-area-injection.ts +13 -12
- package/src/memory-boundary-capture.ts +6 -6
- package/src/memory-injection.ts +45 -43
- package/src/merge-pause-maintenance.ts +5 -4
- package/src/notification-router.ts +44 -17
- package/src/notification-types.ts +2 -1
- package/src/notify.ts +5 -5
- package/src/observed-reset-refresh.ts +4 -4
- package/src/operator-alarm.ts +23 -11
- package/src/project-ingest.ts +6 -6
- package/src/project-instructions.ts +12 -1
- package/src/project-profile-instruction-text.ts +6 -0
- package/src/project-profile-instructions.ts +20 -0
- package/src/project-trust.ts +12 -11
- package/src/prompt-resolver.ts +3 -3
- package/src/provider-catalog-store.ts +3 -49
- package/src/provider-config-store.ts +12 -11
- package/src/provider-model-seed.ts +1 -18
- package/src/provider-quota-async.ts +5 -7
- package/src/provider-quota-config-store.ts +5 -4
- package/src/provider-secrets.test-fixture.ts +3 -3
- package/src/provider-timeline-event.ts +4 -7
- package/src/quota-band-delivery.ts +2 -2
- package/src/recipe-runner.ts +6 -5
- package/src/repo-config-source.ts +10 -4
- package/src/retention-config-store.ts +4 -3
- package/src/reviewer-pipeline.ts +11 -10
- package/src/routes/_route.ts +4 -4
- package/src/routes/_shared.ts +13 -12
- package/src/routes/agent-effort.ts +5 -5
- package/src/routes/agent-heartbeat-quota.ts +23 -0
- package/src/routes/agent-keep.ts +1 -1
- package/src/routes/agent-profiles.ts +6 -6
- package/src/routes/agent-reporting.ts +1 -1
- package/src/routes/agent-self-effort.ts +5 -5
- package/src/routes/agent-sessions.ts +24 -24
- package/src/routes/agents-spawn.ts +2 -2
- package/src/routes/agents.ts +19 -121
- package/src/routes/auth.ts +14 -15
- package/src/routes/authority-audit.ts +2 -2
- package/src/routes/automations.ts +6 -5
- package/src/routes/channel-binding-delete.ts +1 -1
- package/src/routes/channel-output.ts +3 -3
- package/src/routes/claude-api-budget.ts +5 -5
- package/src/routes/commands-listing.ts +5 -1
- package/src/routes/commands.ts +31 -29
- package/src/routes/context-resets.ts +2 -2
- package/src/routes/continuation-archives.ts +2 -2
- package/src/routes/coordinator-teams.ts +1 -1
- package/src/routes/decisions.ts +2 -2
- package/src/routes/host-secret-files.ts +1 -1
- package/src/routes/hosts.ts +11 -11
- package/src/routes/inbox.ts +3 -2
- package/src/routes/index.ts +12 -2
- package/src/routes/integrations.ts +32 -56
- package/src/routes/issue-activity.ts +2 -2
- package/src/routes/issue-associations.ts +4 -4
- package/src/routes/issue-refs.ts +9 -9
- package/src/routes/kernel-timers-operator.ts +1 -1
- package/src/routes/memory.ts +1 -1
- package/src/routes/merges.ts +11 -11
- package/src/routes/messages.ts +23 -17
- package/src/routes/orchestrator-bootstrap.ts +2 -2
- package/src/routes/orchestrator-host-registration.ts +4 -2
- package/src/routes/orchestrator-host-utilization.ts +5 -3
- package/src/routes/orchestrator-proxy.ts +4 -4
- package/src/routes/orchestrator.ts +28 -19
- package/src/routes/pairs.ts +8 -8
- package/src/routes/project-profile-instructions.ts +27 -0
- package/src/routes/project-trust.ts +3 -3
- package/src/routes/projects.ts +16 -16
- package/src/routes/prompt-templates.ts +12 -12
- package/src/routes/provider-config.ts +6 -6
- package/src/routes/provider-quota-config.ts +2 -2
- package/src/routes/provider-quotas.ts +10 -10
- package/src/routes/run-command.ts +1 -1
- package/src/routes/secrets-backup.ts +2 -2
- package/src/routes/secrets-import.ts +1 -1
- package/src/routes/secrets-vault.ts +7 -7
- package/src/routes/settings.ts +6 -6
- package/src/routes/shipping.ts +9 -1
- package/src/routes/spawn-policy.ts +8 -7
- package/src/routes/sse.ts +2 -2
- package/src/routes/stats.ts +6 -6
- package/src/routes/task-http.ts +15 -1
- package/src/routes/task-issues.ts +11 -26
- package/src/routes/tasks.ts +22 -53
- package/src/routes/team-templates.ts +12 -12
- package/src/routes/teams-config.ts +2 -2
- package/src/routes/teams.ts +10 -10
- package/src/routes/tokens.ts +30 -22
- package/src/routes/users.ts +24 -24
- package/src/routes/viewer-read.ts +3 -3
- package/src/routes/web-tool-calls.ts +4 -4
- package/src/routes/workflows.ts +22 -7
- package/src/routes/workspaces.ts +17 -16
- package/src/routing-policy-normalize.ts +1 -2
- package/src/routing-policy-push.ts +6 -6
- package/src/routing-step-defaults.ts +0 -2
- package/src/runner-token-mint-limit.ts +63 -0
- package/src/runtime-grant.ts +21 -13
- package/src/runtime-tokens.ts +59 -35
- package/src/secrets-broker/config.ts +1 -1
- package/src/secrets-broker/service.ts +2 -2
- package/src/security.ts +16 -21
- package/src/self-resume-carry.ts +21 -110
- package/src/self-resume-supersede.ts +4 -3
- package/src/self-resume.ts +54 -66
- package/src/server-background.ts +2 -2
- package/src/server.ts +26 -24
- package/src/services/agent-action.ts +12 -31
- package/src/services/agent-effort.ts +7 -32
- package/src/services/agent-hang-watchdog-job.ts +2 -2
- package/src/services/agent-heartbeat.ts +166 -0
- package/src/services/agent-reporting.ts +1 -1
- package/src/services/browser-inventory.ts +3 -3
- package/src/services/browser-session-state.ts +3 -2
- package/src/services/browser-session.ts +21 -27
- package/src/services/bus-command-update.ts +9 -9
- package/src/services/checkout-refresh-state.ts +1 -0
- package/src/services/checkout-refresh.test-fixture.ts +3 -2
- package/src/services/command-host.ts +7 -2
- package/src/services/coordinator-digest.ts +3 -2
- package/src/services/dispatch-command.ts +12 -13
- package/src/services/effective-launch-driver.ts +14 -9
- package/src/services/host-changed-paths.test-fixture.ts +5 -6
- package/src/services/host-project-readiness.ts +2 -2
- package/src/services/host-providers.ts +1 -1
- package/src/services/host-utilization.ts +27 -4
- package/src/services/idle-agent-digest.ts +17 -12
- package/src/services/idle-epics.ts +9 -10
- package/src/services/interrupt-agent.ts +12 -9
- package/src/services/issue-activity.ts +3 -3
- package/src/services/issue-associations.ts +15 -18
- package/src/services/issue-lifecycle-comments.ts +3 -2
- package/src/services/issue-lifecycle.ts +69 -72
- package/src/services/issue-refs.ts +10 -15
- package/src/services/kernel-timers-cutover-chaos-worker.ts +11 -8
- package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
- package/src/services/kernel-timers-ingress.ts +4 -4
- package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
- package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
- package/src/services/kernel-timers-outbox.ts +1 -1
- package/src/services/kernel-timers-stage10-operator.ts +1 -1
- package/src/services/kernel-timers.ts +4 -3
- package/src/services/land-gate-placement.ts +11 -2
- package/src/services/land-issue-relations.ts +5 -4
- package/src/services/land-restart-retry.ts +7 -6
- package/src/services/landed-ancestry.ts +25 -8
- package/src/services/legacy-landing-host.ts +4 -1
- package/src/services/legacy-landing-recovery.ts +8 -6
- package/src/services/managed-running.ts +17 -17
- package/src/services/memory-areas.ts +17 -53
- package/src/services/merge-pause.ts +9 -15
- package/src/services/operator-decisions.ts +37 -41
- package/src/services/orchestrator-registration-recovery.ts +6 -2
- package/src/services/orchestrator-upgrade.ts +14 -12
- package/src/services/permission-decision.ts +4 -3
- package/src/services/plan-ci-notice.ts +57 -0
- package/src/services/plan-ci-recovery.ts +2 -55
- package/src/services/plan-conformance.ts +4 -4
- package/src/services/plan-dispatch-policy.ts +10 -3
- package/src/services/plan-dispatch.ts +35 -14
- package/src/services/plan-graph.ts +39 -261
- package/src/services/plan-import.ts +1 -1
- package/src/services/plan-live-bindings.ts +8 -37
- package/src/services/plan-task-create.ts +1 -1
- package/src/services/plan-terminal-refs.ts +2 -12
- package/src/services/plan-wake-digest.ts +9 -3
- package/src/services/profile-trust.ts +6 -5
- package/src/services/project-config-read.ts +70 -0
- package/src/services/project-profile-instructions.ts +54 -0
- package/src/services/project.ts +11 -10
- package/src/services/prompt-templates.ts +6 -5
- package/src/services/provider-hold-nudge.ts +9 -9
- package/src/services/provider-hold-record.ts +13 -12
- package/src/services/provider-state-audit.ts +19 -18
- package/src/services/provisioning-harness/budget-baseline.json +4 -4
- package/src/services/provisioning-harness/driver.ts +1 -1
- package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
- package/src/services/provisioning-harness/lifecycle.ts +24 -22
- package/src/services/provisioning-harness/prepare.ts +16 -17
- package/src/services/rate-limit-resume.ts +10 -9
- package/src/services/red-main-auto-revert.ts +7 -5
- package/src/services/red-main-controller-deps.ts +20 -3
- package/src/services/red-main-incident.ts +3 -1
- package/src/services/red-main-quarantine.ts +2 -3
- package/src/services/register-agent.ts +22 -22
- package/src/services/restart-project-role.ts +1 -1
- package/src/services/restart-spawn.ts +74 -61
- package/src/services/review-publish.ts +7 -24
- package/src/services/review-ref-reconcile.ts +29 -38
- package/src/services/runtime-dependency-alert.ts +27 -0
- package/src/services/scheduler-command.ts +11 -9
- package/src/services/scheduler.ts +5 -5
- package/src/services/self-care-state.ts +2 -1
- package/src/services/self-effort-grant.ts +5 -25
- package/src/services/self-effort.ts +5 -5
- package/src/services/send-message.ts +21 -34
- package/src/services/shutdown-agent.ts +17 -14
- package/src/services/spawn-agent.ts +25 -43
- package/src/services/spawn-chain-inspector.ts +7 -14
- package/src/services/spawn-cwd-placement.ts +21 -5
- package/src/services/spawn-issue-activity.ts +8 -6
- package/src/services/spawn-launch.ts +17 -1
- package/src/services/spawn-loss-escalation.ts +1 -1
- package/src/services/spawn-policy.ts +68 -0
- package/src/services/spawn-registration-watchdog.ts +2 -2
- package/src/services/spawn-repo-probe.ts +7 -5
- package/src/services/spawn-tree-cleanup.ts +8 -9
- package/src/services/steward-reaper.ts +4 -4
- package/src/services/task-context-hydration.ts +25 -1
- package/src/services/task-delivery.ts +20 -34
- package/src/services/task-dispatch.ts +3 -0
- package/src/services/task-entity.ts +40 -203
- package/src/services/task-issue-link.ts +13 -20
- package/src/services/task-issue-weave.ts +12 -11
- package/src/services/task-lifecycle.ts +27 -31
- package/src/services/task-repo-cwd.ts +37 -0
- package/src/services/task-review-hold.ts +4 -17
- package/src/services/task-semantic-events.ts +13 -11
- package/src/services/task-state-reconcile.ts +8 -17
- package/src/services/task-validation.ts +2 -2
- package/src/services/team-templates.ts +12 -11
- package/src/services/team.ts +31 -98
- package/src/services/terminal-provider-exit.ts +6 -5
- package/src/services/timer-message-fire-chaos-worker.ts +7 -3
- package/src/services/timer-message-fire-outbox.ts +1 -1
- package/src/services/transient-agent-reaper.ts +25 -20
- package/src/services/turn-reconcile-audit.ts +9 -9
- package/src/services/user-onboarding.ts +8 -6
- package/src/services/user-profile.ts +6 -9
- package/src/services/work-finality.ts +16 -4
- package/src/services/workflow-auto-start.ts +26 -13
- package/src/services/workflow-caller.ts +61 -4
- package/src/services/workflow-child-proof-worker.ts +1 -1
- package/src/services/workflow-ci-main.ts +1 -1
- package/src/services/workflow-control-proof-worker.ts +1 -1
- package/src/services/workflow-coordinator.ts +8 -2
- package/src/services/workflow-decisions.ts +5 -5
- package/src/services/workflow-deferred-cleanup.ts +5 -3
- package/src/services/workflow-effect-driver.ts +42 -17
- package/src/services/workflow-effects.ts +58 -37
- package/src/services/workflow-land-notice.ts +10 -0
- package/src/services/workflow-pause-text.ts +43 -0
- package/src/services/workflow-pins.ts +7 -4
- package/src/services/workflow-reconcile.ts +14 -11
- package/src/services/workflow-resume-pins.ts +3 -3
- package/src/services/workflow-routing-defaults.ts +18 -0
- package/src/services/workflow-run-view.ts +6 -3
- package/src/services/workflow-run.test-fixture.ts +1 -1
- package/src/services/workflow-runs.ts +61 -33
- package/src/services/workflow-supervisor-state.ts +26 -1
- package/src/services/workflow-supervisor.ts +11 -6
- package/src/services/workspace-recovery.ts +4 -3
- package/src/settings/registry.ts +1 -2
- package/src/spawn-admission.ts +10 -2
- package/src/spawn-command.ts +4 -4
- package/src/spawn-models.ts +10 -12
- package/src/spawn-placement.ts +28 -10
- package/src/spawn-targets.ts +9 -9
- package/src/sse-agent-status.ts +15 -6
- package/src/sse.ts +49 -24
- package/src/startup-context-injection.ts +62 -57
- package/src/state-adoption/cd-cutover.ts +53 -7
- package/src/state-adoption/fleet-cutover.ts +35 -0
- package/src/state-adoption/fleet-plan.ts +36 -0
- package/src/state-adoption/index.ts +2 -2
- package/src/state-adoption/linux-plan.ts +2 -2
- package/src/state-adoption/linux.ts +2 -2
- package/src/state-adoption/macos-plan.ts +2 -2
- package/src/state-adoption/macos.ts +1 -1
- package/src/steward-handoff.ts +4 -3
- package/src/steward-policy-cleanup.ts +9 -8
- package/src/steward.ts +9 -8
- package/src/task-dispatch-live.ts +6 -3
- package/src/task-dispatcher.ts +3 -3
- package/src/teams-config-store.ts +4 -3
- package/src/token-db-write.ts +10 -0
- package/src/token-db.ts +2 -2
- package/src/token-usage-async.ts +2 -4
- package/src/user-awaiting-reply.ts +5 -5
- package/src/workspace-actions.ts +24 -29
- package/src/workspace-auto-merge.ts +14 -12
- package/src/workspace-changed-paths-client.ts +16 -5
- package/src/workspace-cleanup-stale.ts +30 -7
- package/src/workspace-command-settle.ts +26 -17
- package/src/workspace-config.ts +1 -1
- package/src/workspace-conflict-resolver.ts +9 -7
- package/src/workspace-dead-owner-recovery.ts +5 -4
- package/src/workspace-escalation.ts +12 -11
- package/src/workspace-human-escalation.ts +3 -3
- package/src/workspace-land-gate-watchdog.ts +3 -3
- package/src/workspace-land-refusal.ts +20 -0
- package/src/workspace-land-stopped.ts +1 -0
- package/src/workspace-merge.ts +10 -26
- package/src/workspace-nothing-to-land-notice.ts +2 -2
- package/src/workspace-orphans.ts +18 -37
- package/src/workspace-pr-completion.ts +10 -8
- package/src/workspace-rekey.ts +3 -3
- package/src/workspace-steward-failure.ts +2 -2
- package/src/workspace-steward-wake.ts +4 -4
- package/node_modules/@humwick/kernel/LICENSE +0 -167
- package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
- package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
- package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
- package/node_modules/@humwick/kernel/config.ts +0 -111
- package/node_modules/@humwick/kernel/contract.ts +0 -572
- package/node_modules/@humwick/kernel/control-authz.ts +0 -281
- package/node_modules/@humwick/kernel/control.ts +0 -645
- package/node_modules/@humwick/kernel/effects.ts +0 -493
- package/node_modules/@humwick/kernel/event-log.ts +0 -319
- package/node_modules/@humwick/kernel/http-transport.ts +0 -384
- package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
- package/node_modules/@humwick/kernel/identity.ts +0 -248
- package/node_modules/@humwick/kernel/index.ts +0 -28
- package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
- package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
- package/node_modules/@humwick/kernel/landing.ts +0 -953
- package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
- package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
- package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
- package/node_modules/@humwick/kernel/message-authority.ts +0 -281
- package/node_modules/@humwick/kernel/module-host.ts +0 -722
- package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
- package/node_modules/@humwick/kernel/package.json +0 -39
- package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
- package/node_modules/@humwick/kernel/persistence.ts +0 -510
- package/node_modules/@humwick/kernel/registry.ts +0 -60
- package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
- package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
- package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
- package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
- package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
- package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
- package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
- package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
- package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
- package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
- package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
- package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
- package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
- package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
- package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
- package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
- package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
- package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
- package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
- package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
- package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
- package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
- package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
- package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
- package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
- package/node_modules/@humwick/offload/LICENSE +0 -167
- package/node_modules/@humwick/offload/README.md +0 -57
- package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
- package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
- package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
- package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.js +0 -80
- package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
- package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/index.js +0 -5
- package/node_modules/@humwick/offload/dist/index.js.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
- package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
- package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/types.js +0 -176
- package/node_modules/@humwick/offload/dist/types.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
- package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
- package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.js +0 -1
- package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
- package/node_modules/@humwick/offload/package.json +0 -42
- package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
- package/node_modules/@humwick/offload/src/envelope.ts +0 -92
- package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
- package/node_modules/@humwick/offload/src/index.ts +0 -4
- package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
- package/node_modules/@humwick/offload/src/types.ts +0 -235
- package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
- package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
- package/node_modules/@humwick/offload/src/worker.ts +0 -5
- package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
- package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
- package/public/assets/index-B7ZerHLY.css +0 -2
- package/public/assets/index-BU3uWo2t.js +0 -15
- package/public/assets/projects-B_lmcqfO.js +0 -2
- package/public/assets/provider-display-DbLWpP3j.js +0 -2
- package/public/assets/run-card-CO_bguQf.js +0 -2
- package/public/assets/shipping-Bu8p1gEw.js +0 -2
- package/public/assets/store-BvtFdlGX.js +0 -9
- package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
- package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
- package/runner/src/agent-runner/http-liveness.ts +0 -85
- package/runner/src/gate-run-lease-probe.ts +0 -38
- package/src/db/claude-catalog-reconcile.ts +0 -27
- package/src/db/on-loop-write-transaction.ts +0 -34
- package/src/routing-model-aliases.ts +0 -21
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { getDb } from "./connection";
|
|
2
|
+
|
|
3
|
+
type Database = ReturnType<typeof getDb>;
|
|
4
|
+
|
|
5
|
+
// Keep one referenced command per checkout; generic command retention can expire its history (#3797).
|
|
6
|
+
export const CHECKOUT_REFRESH_SCHEMA = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS checkout_refresh_heads (
|
|
8
|
+
project_id TEXT NOT NULL,
|
|
9
|
+
orchestrator_id TEXT NOT NULL,
|
|
10
|
+
root_path TEXT NOT NULL,
|
|
11
|
+
command_id TEXT NOT NULL REFERENCES commands(id) ON DELETE CASCADE,
|
|
12
|
+
created_at INTEGER NOT NULL,
|
|
13
|
+
PRIMARY KEY (project_id, orchestrator_id, root_path)
|
|
14
|
+
);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_checkout_refresh_heads_command ON checkout_refresh_heads(command_id);
|
|
16
|
+
CREATE TRIGGER IF NOT EXISTS checkout_refresh_head_insert AFTER INSERT ON commands
|
|
17
|
+
WHEN NEW.type = 'project.checkout.refresh' AND json_valid(NEW.params)
|
|
18
|
+
AND json_type(NEW.params, '$.projectId') = 'text'
|
|
19
|
+
AND json_type(NEW.params, '$.orchestratorId') = 'text'
|
|
20
|
+
AND json_type(NEW.params, '$.rootPath') = 'text'
|
|
21
|
+
BEGIN
|
|
22
|
+
INSERT INTO checkout_refresh_heads VALUES (
|
|
23
|
+
json_extract(NEW.params, '$.projectId'), json_extract(NEW.params, '$.orchestratorId'),
|
|
24
|
+
json_extract(NEW.params, '$.rootPath'), NEW.id, NEW.created_at)
|
|
25
|
+
ON CONFLICT(project_id, orchestrator_id, root_path) DO UPDATE SET
|
|
26
|
+
command_id = excluded.command_id, created_at = excluded.created_at
|
|
27
|
+
WHERE excluded.created_at >= checkout_refresh_heads.created_at;
|
|
28
|
+
END;
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
export function migrateCheckoutRefreshHeads(db: Database): void {
|
|
32
|
+
db.transaction(() => {
|
|
33
|
+
const installed = db.query("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = 'checkout_refresh_heads'").get();
|
|
34
|
+
db.run(CHECKOUT_REFRESH_SCHEMA);
|
|
35
|
+
if (installed) return;
|
|
36
|
+
db.run(`INSERT INTO checkout_refresh_heads
|
|
37
|
+
SELECT project_id, orchestrator_id, root_path, id, created_at FROM (
|
|
38
|
+
SELECT id, created_at, json_extract(params, '$.projectId') AS project_id,
|
|
39
|
+
json_extract(params, '$.orchestratorId') AS orchestrator_id, json_extract(params, '$.rootPath') AS root_path,
|
|
40
|
+
row_number() OVER (PARTITION BY json_extract(params, '$.projectId'), json_extract(params, '$.orchestratorId'),
|
|
41
|
+
json_extract(params, '$.rootPath') ORDER BY created_at DESC, rowid DESC) AS ordinal
|
|
42
|
+
FROM commands WHERE type = 'project.checkout.refresh' AND json_valid(params)
|
|
43
|
+
AND json_type(params, '$.projectId') = 'text' AND json_type(params, '$.orchestratorId') = 'text'
|
|
44
|
+
AND json_type(params, '$.rootPath') = 'text'
|
|
45
|
+
) WHERE ordinal = 1`);
|
|
46
|
+
})();
|
|
47
|
+
}
|
|
@@ -4,12 +4,12 @@ import { getOrchestrator } from "./orchestrators";
|
|
|
4
4
|
import { stringValue } from "@humwick/sdk";
|
|
5
5
|
import { getDb } from "./connection";
|
|
6
6
|
|
|
7
|
+
export const CHECKOUT_REFRESH_COMMANDS_SQL = `SELECT c.id, c.status, c.params, c.result, c.error, c.updated_at
|
|
8
|
+
FROM checkout_refresh_heads h CROSS JOIN commands c ON c.id = h.command_id
|
|
9
|
+
WHERE h.project_id = ?`;
|
|
10
|
+
|
|
7
11
|
export function checkoutRefreshCommands(projectId: string): CheckoutRefreshStatus[] {
|
|
8
|
-
const rows = getDb().query(
|
|
9
|
-
SELECT id, status, params, result, error, updated_at,
|
|
10
|
-
row_number() OVER (PARTITION BY json_extract(params, '$.orchestratorId'), json_extract(params, '$.rootPath') ORDER BY created_at DESC, rowid DESC) AS ordinal
|
|
11
|
-
FROM commands WHERE type = 'project.checkout.refresh' AND json_extract(params, '$.projectId') = ?
|
|
12
|
-
) WHERE ordinal = 1`).all(projectId) as Array<{ id: string; status: Command["status"]; params: string; result: string | null; error: string | null; updated_at: number }>;
|
|
12
|
+
const rows = getDb().query(CHECKOUT_REFRESH_COMMANDS_SQL).all(projectId) as Array<{ id: string; status: Command["status"]; params: string; result: string | null; error: string | null; updated_at: number }>;
|
|
13
13
|
return rows.map((row) => {
|
|
14
14
|
const params = JSON.parse(row.params);
|
|
15
15
|
return { orchestratorId: params.orchestratorId, rootPath: params.rootPath, commandId: row.id, status: row.status, updatedAt: row.updated_at,
|
|
@@ -35,15 +35,15 @@ const write = <T>(op: string, args: unknown, idempotencyKey?: string): Promise<T
|
|
|
35
35
|
export function upsertWatchStateAsync(input: Parameters<typeof upsertWatchState>[0]): Promise<ReturnType<typeof upsertWatchState>> {
|
|
36
36
|
return write("ciIncidents.watch.upsert", input);
|
|
37
37
|
}
|
|
38
|
-
/** One gateway writer transaction for the complete watch + incident lifecycle decision. */
|
|
39
|
-
export function ingestCiObservationTransitionAsync(input: CiObservationTransitionInput): Promise<CiObservationTransitionResult> {
|
|
40
|
-
return write("ciIncidents.observation.ingest", input);
|
|
38
|
+
/** One gateway writer transaction for the complete watch + incident lifecycle decision. `now` pins a test clock. */
|
|
39
|
+
export function ingestCiObservationTransitionAsync(input: CiObservationTransitionInput, now?: number): Promise<CiObservationTransitionResult> {
|
|
40
|
+
return write("ciIncidents.observation.ingest", now === undefined ? input : { ...input, now });
|
|
41
41
|
}
|
|
42
42
|
export function markCiWatchNeverMatchedNotifiedAsync(watchKey: string): Promise<ReturnType<typeof markCiWatchNeverMatchedNotified>> {
|
|
43
43
|
return write("ciIncidents.watch.markNeverMatched", { watchKey });
|
|
44
44
|
}
|
|
45
|
-
export function markCiRootMissingNotifiedAsync(repository: string): Promise<ReturnType<typeof markCiRootMissingNotified>> {
|
|
46
|
-
return write("ciIncidents.root.markMissing", { repository });
|
|
45
|
+
export function markCiRootMissingNotifiedAsync(repository: string, now?: number): Promise<ReturnType<typeof markCiRootMissingNotified>> {
|
|
46
|
+
return write("ciIncidents.root.markMissing", now === undefined ? { repository } : { repository, now });
|
|
47
47
|
}
|
|
48
48
|
export function openOrDedupeIncidentAsync(input: Parameters<typeof openOrDedupeIncident>[0]): Promise<ReturnType<typeof openOrDedupeIncident>> {
|
|
49
49
|
return write("ciIncidents.openOrDedupe", input);
|
|
@@ -27,7 +27,7 @@ export const CI_INCIDENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
|
|
|
27
27
|
"ciIncidents.observation.ingest": {
|
|
28
28
|
offLoop: true,
|
|
29
29
|
idempotent: true,
|
|
30
|
-
fn: (a: Parameters<typeof applyCiObservationTransitionInternal>[0], ctx) => applyCiObservationTransitionInternal(a, ctx.now),
|
|
30
|
+
fn: (a: Parameters<typeof applyCiObservationTransitionInternal>[0] & { now?: number }, ctx) => applyCiObservationTransitionInternal(a, a.now ?? ctx.now),
|
|
31
31
|
},
|
|
32
32
|
"ciIncidents.gateRun.record": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof recordCiGateRuns>[0]) => recordCiGateRuns(a) },
|
|
33
33
|
"ciIncidents.gateBackfill.start": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof startCiGateBackfill>[0]) => startCiGateBackfill(a) },
|
|
@@ -36,7 +36,7 @@ export const CI_INCIDENTS_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
|
|
|
36
36
|
"ciIncidents.sha.prune": { offLoop: true, idempotent: true, fn: (_a: { now: number }, ctx) => pruneCiShaObservations(ctx.now) },
|
|
37
37
|
"ciIncidents.watch.upsert": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof upsertWatchState>[0], ctx) => upsertWatchState(a, ctx.now) },
|
|
38
38
|
"ciIncidents.watch.markNeverMatched": { offLoop: true, idempotent: true, fn: (a: { watchKey: string }, ctx) => markCiWatchNeverMatchedNotified(a.watchKey, ctx.now) },
|
|
39
|
-
"ciIncidents.root.markMissing": { offLoop: true, idempotent: true, fn: (a: { repository: string }, ctx) => markCiRootMissingNotified(a.repository, ctx.now) },
|
|
39
|
+
"ciIncidents.root.markMissing": { offLoop: true, idempotent: true, fn: (a: { repository: string; now?: number }, ctx) => markCiRootMissingNotified(a.repository, a.now ?? ctx.now) },
|
|
40
40
|
"ciIncidents.openOrDedupe": { offLoop: true, idempotent: true, fn: (a: Parameters<typeof openOrDedupeIncidentInternal>[0], ctx) => openOrDedupeIncidentInternal(a, ctx.now, false) },
|
|
41
41
|
// Guarded transitions throw after they have advanced; use the durable retry ledger rather than
|
|
42
42
|
// misclassifying that as safe replay. Distinct steward attempts carry distinct request ids.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// #3606/#3610 — spawn commands written before the content-hash store (command-agent-profiles.ts)
|
|
2
|
+
// still carry `params.agentProfile` inline (~0.4 MB each). Retention keeps some of them past the
|
|
3
|
+
// terminal window (managed agents, unconfirmed spawns), so move those profiles into the store in
|
|
4
|
+
// bounded chunks from the retention pass. `updated_at` is untouched: retention windows key off it.
|
|
5
|
+
|
|
6
|
+
import { storeCommandAgentProfile } from "./command-agent-profiles";
|
|
7
|
+
import { TERMINAL_COMMAND_STATUS_SQL_LIST } from "./command-terminal-status";
|
|
8
|
+
import type { getDb } from "./connection";
|
|
9
|
+
|
|
10
|
+
type Database = ReturnType<typeof getDb>;
|
|
11
|
+
const WRITE_TRANSACTION_BUDGET_MS = 25;
|
|
12
|
+
|
|
13
|
+
/** One bounded chunk: returns the same shape as a retention prune chunk (`pruned` = rows slimmed). */
|
|
14
|
+
export function slimSpawnProfileChunk(db: Database, limit: number): { candidates: number; attempted: number; pruned: number } {
|
|
15
|
+
// Only terminal rows: a live spawn is still being claimed. A profile carrying vault ciphertext stays
|
|
16
|
+
// inline (the #3488 settlement trigger prunes it there), so those rows are not candidates.
|
|
17
|
+
const ids = db.query(`
|
|
18
|
+
SELECT id FROM commands
|
|
19
|
+
WHERE type = 'agent.spawn' AND status IN (${TERMINAL_COMMAND_STATUS_SQL_LIST}) AND agent_profile_hash IS NULL
|
|
20
|
+
AND instr(params, '"agentProfile"') > 0 AND json_valid(params) AND json_type(params, '$.agentProfile') = 'object'
|
|
21
|
+
AND instr(params, 'sealed:v2:') = 0
|
|
22
|
+
LIMIT ?
|
|
23
|
+
`).all(limit) as Array<{ id: string }>;
|
|
24
|
+
if (ids.length === 0) return { candidates: 0, attempted: 0, pruned: 0 };
|
|
25
|
+
const read = db.query("SELECT params FROM commands WHERE id = ?");
|
|
26
|
+
const write = db.query("UPDATE commands SET params = ?, agent_profile_hash = ? WHERE id = ? AND agent_profile_hash IS NULL");
|
|
27
|
+
return db.transaction(() => {
|
|
28
|
+
const startedAt = performance.now(); let attempted = 0; let pruned = 0;
|
|
29
|
+
for (const { id } of ids) {
|
|
30
|
+
if (attempted > 0 && performance.now() - startedAt >= WRITE_TRANSACTION_BUDGET_MS) break;
|
|
31
|
+
attempted += 1;
|
|
32
|
+
const row = read.get(id) as { params: string } | null;
|
|
33
|
+
if (!row) continue;
|
|
34
|
+
const stored = storeCommandAgentProfile(JSON.parse(row.params) as Record<string, unknown>, Date.now(), db);
|
|
35
|
+
if (stored.agentProfileHash) pruned += write.run(JSON.stringify(stored.params), stored.agentProfileHash, id).changes;
|
|
36
|
+
}
|
|
37
|
+
return { candidates: ids.length, attempted, pruned };
|
|
38
|
+
})();
|
|
39
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { isRecord } from "@humwick/sdk";
|
|
9
9
|
import { getDb } from "./connection";
|
|
10
|
+
import { COMMAND_PROFILE_WARN_BYTES, warnIfOversized } from "./agent-meta";
|
|
10
11
|
|
|
11
12
|
type Database = ReturnType<typeof getDb>;
|
|
12
13
|
|
|
@@ -41,7 +42,10 @@ export function storeCommandAgentProfile(sealed: Record<string, unknown>, now: n
|
|
|
41
42
|
const body = JSON.stringify(profile);
|
|
42
43
|
if (body.includes("sealed:v2:")) return { params: sealed, agentProfileHash: null };
|
|
43
44
|
const hash = createHash("sha256").update(body).digest("hex");
|
|
44
|
-
db.query("INSERT OR IGNORE INTO command_agent_profiles (hash, profile, created_at) VALUES (?, ?, ?)").run(hash, body, now);
|
|
45
|
+
const inserted = db.query("INSERT OR IGNORE INTO command_agent_profiles (hash, profile, created_at) VALUES (?, ?, ?)").run(hash, body, now);
|
|
46
|
+
if (inserted.changes && Buffer.byteLength(body) > COMMAND_PROFILE_WARN_BYTES) {
|
|
47
|
+
warnIfOversized("command-profile", body, COMMAND_PROFILE_WARN_BYTES, "[commands] stored agentProfile");
|
|
48
|
+
}
|
|
45
49
|
const { agentProfile: _stored, ...params } = sealed;
|
|
46
50
|
return { params, agentProfileHash: hash };
|
|
47
51
|
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { quoteIdentifier } from "@humwick/kernel/persistence-schema";
|
|
2
|
+
import { getDb } from "./connection";
|
|
3
|
+
import { TERMINAL_COMMAND_STATUS_SQL_LIST } from "./command-terminal-status";
|
|
4
|
+
|
|
5
|
+
type Database = ReturnType<typeof getDb>;
|
|
6
|
+
|
|
7
|
+
const ELIGIBLE = `type != 'agent.spawn' AND status IN (${TERMINAL_COMMAND_STATUS_SQL_LIST})`;
|
|
8
|
+
export const COMMAND_RETENTION_SCHEMA = `
|
|
9
|
+
CREATE INDEX IF NOT EXISTS idx_commands_terminal_retention ON commands(updated_at, id) WHERE ${ELIGIBLE};
|
|
10
|
+
CREATE INDEX IF NOT EXISTS idx_commands_refresh_retention ON commands(updated_at, id)
|
|
11
|
+
WHERE type = 'project.checkout.refresh' AND status IN (${TERMINAL_COMMAND_STATUS_SQL_LIST});
|
|
12
|
+
CREATE TABLE IF NOT EXISTS command_retention_cursor (
|
|
13
|
+
id INTEGER PRIMARY KEY CHECK (id = 1), updated_at INTEGER NOT NULL, command_id TEXT NOT NULL);
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
// Sampled status stats favor a broad status scan plus sort; retain the bounded age seek (#3619).
|
|
17
|
+
export const TERMINAL_COMMAND_CANDIDATES_SQL = `SELECT id, updated_at FROM commands INDEXED BY idx_commands_terminal_retention
|
|
18
|
+
WHERE ${ELIGIBLE} AND updated_at < ? AND (updated_at, id) > (?, ?)
|
|
19
|
+
ORDER BY updated_at, id LIMIT ?`;
|
|
20
|
+
|
|
21
|
+
export const REFRESH_COMMAND_CANDIDATES_SQL = `SELECT id, updated_at FROM commands INDEXED BY idx_commands_refresh_retention
|
|
22
|
+
WHERE type = 'project.checkout.refresh' AND status IN (${TERMINAL_COMMAND_STATUS_SQL_LIST})
|
|
23
|
+
AND updated_at >= ? AND updated_at < ? AND (updated_at, id) > (?, ?)
|
|
24
|
+
ORDER BY updated_at, id LIMIT ?`;
|
|
25
|
+
|
|
26
|
+
const JSON_REFERENCES = [
|
|
27
|
+
["activity_events", "metadata", ["$.commandId", "$.mergeCommandId"]],
|
|
28
|
+
["task_events", "metadata", ["$.commandId", "$.mergeCommandId"]],
|
|
29
|
+
["workspaces", "metadata", ["$.mergeCommandId", "$.rekeyCommandId", "$.lastStewardRekey.commandId"]],
|
|
30
|
+
["scheduled_timers", "command", ["$.commandId"]],
|
|
31
|
+
["workflow_instances", "wait_for", ["$.commandId"]],
|
|
32
|
+
["workflow_instances", "initial_state", ["$.commandId"]],
|
|
33
|
+
["workflow_step_journal", "wait_for", ["$.commandId"]],
|
|
34
|
+
["workflow_step_journal", "state_snapshot", ["$.commandId"]],
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
function jsonReference(column: string, path: string): string {
|
|
38
|
+
return `json_extract(CASE WHEN json_valid(${column}) THEN ${column} ELSE '{}' END, '${path}')`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function commandReferences(db: Database): { tbl: string; col: string }[] {
|
|
42
|
+
return db.query(`SELECT s.name AS tbl, c.name AS col FROM sqlite_schema s, pragma_table_info(s.name) c
|
|
43
|
+
WHERE s.type = 'table' AND s.name NOT LIKE 'sqlite_%' AND s.name != 'command_retention_cursor'
|
|
44
|
+
AND (lower(replace(c.name, '_', '')) LIKE '%commandid' OR lower(replace(c.name, '_', '')) LIKE '%commandids')
|
|
45
|
+
UNION SELECT s.name, f."from" FROM sqlite_schema s, pragma_foreign_key_list(s.name) f
|
|
46
|
+
WHERE s.type = 'table' AND f."table" = 'commands'`).all() as { tbl: string; col: string }[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Equality checks in a DELETE must seek references, never repeatedly scan their history (#3619). */
|
|
50
|
+
export function ensureCommandRetentionReferenceIndexes(db: Database): void {
|
|
51
|
+
for (const { tbl, col } of commandReferences(db)) {
|
|
52
|
+
if (col.replaceAll("_", "").toLowerCase().endsWith("commandids")) continue;
|
|
53
|
+
const indexes = db.query(`PRAGMA index_list(${quoteIdentifier(tbl)})`).all() as { name: string; partial: number }[];
|
|
54
|
+
const covered = indexes.some(index => !index.partial &&
|
|
55
|
+
(db.query(`PRAGMA index_info(${quoteIdentifier(index.name)})`).get() as { name: string } | null)?.name === col);
|
|
56
|
+
if (!covered) db.run(`CREATE INDEX IF NOT EXISTS ${quoteIdentifier(`idx_retention_${tbl}_${col}`)} ON ${quoteIdentifier(tbl)}(${quoteIdentifier(col)})`);
|
|
57
|
+
}
|
|
58
|
+
for (const [table, column, paths] of JSON_REFERENCES) {
|
|
59
|
+
if (table === "scheduled_timers") continue; // idx_scheduled_timers_command_created already owns this expression.
|
|
60
|
+
for (const [i, path] of paths.entries()) db.run(`CREATE INDEX IF NOT EXISTS ${quoteIdentifier(`idx_retention_${table}_${column}_${i}`)}
|
|
61
|
+
ON ${table}(${jsonReference(column, path)})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const predicates = new WeakMap<Database, { version: number; sql: string }>();
|
|
66
|
+
|
|
67
|
+
/** Resolve each reference index for the whole candidate set, including CASCADE/SET NULL FKs. */
|
|
68
|
+
function referencedCommandIds(db: Database): string {
|
|
69
|
+
const version = (db.query("PRAGMA schema_version").get() as { schema_version: number }).schema_version;
|
|
70
|
+
const cached = predicates.get(db);
|
|
71
|
+
if (cached?.version === version) return cached.sql;
|
|
72
|
+
const refs = commandReferences(db);
|
|
73
|
+
const selections = refs.map(({ tbl, col }) => {
|
|
74
|
+
const table = quoteIdentifier(tbl), column = quoteIdentifier(col);
|
|
75
|
+
return col.replaceAll("_", "").toLowerCase().endsWith("commandids")
|
|
76
|
+
? `SELECT candidate.id FROM ${table} ref, json_each(CASE WHEN json_valid(ref.${column}) THEN ref.${column} ELSE '[]' END) item
|
|
77
|
+
JOIN candidates candidate ON CAST(item.value AS TEXT) = candidate.id`
|
|
78
|
+
: `SELECT candidate.id FROM candidates candidate CROSS JOIN ${table} ref ON ref.${column} = candidate.id`;
|
|
79
|
+
});
|
|
80
|
+
// Strip candidate TEXT affinity so JSON expression indexes can seek (#3619).
|
|
81
|
+
for (const [table, column, paths] of JSON_REFERENCES) {
|
|
82
|
+
for (const path of paths) {
|
|
83
|
+
const expression = table === "scheduled_timers" ? `json_extract(ref.${column}, '${path}')` : jsonReference(`ref.${column}`, path);
|
|
84
|
+
selections.push(`SELECT candidate.id FROM candidates candidate CROSS JOIN ${table} ref ON ${expression} = +candidate.id`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const sql = selections.join(" UNION ");
|
|
88
|
+
predicates.set(db, { version, sql });
|
|
89
|
+
return sql;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** #3619 — advance past referenced rows so they cannot starve unrelated expired commands. */
|
|
93
|
+
export function pruneTerminalCommands(cutoff: number, limit: number, refreshCutoff = cutoff): { pruned: number; scanned: number; moreWork: boolean } {
|
|
94
|
+
const db = getDb();
|
|
95
|
+
const start = performance.now();
|
|
96
|
+
const cursor = db.query("SELECT updated_at, command_id FROM command_retention_cursor WHERE id = 1").get() as
|
|
97
|
+
{ updated_at: number; command_id: string } | null;
|
|
98
|
+
const candidates = db.query(TERMINAL_COMMAND_CANDIDATES_SQL)
|
|
99
|
+
.all(cutoff, cursor?.updated_at ?? -1, cursor?.command_id ?? "", limit) as { id: string; updated_at: number }[];
|
|
100
|
+
// Merge two bounded age seeks; one shared cursor also advances past referenced refresh receipts (#3797).
|
|
101
|
+
if (refreshCutoff > cutoff) candidates.push(...db.query(REFRESH_COMMAND_CANDIDATES_SQL)
|
|
102
|
+
.all(cutoff, refreshCutoff, cursor?.updated_at ?? -1, cursor?.command_id ?? "", limit) as typeof candidates);
|
|
103
|
+
const rows = candidates.sort((a, b) => a.updated_at - b.updated_at || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)).slice(0, limit);
|
|
104
|
+
if (!rows.length) {
|
|
105
|
+
db.run("DELETE FROM command_retention_cursor WHERE id = 1");
|
|
106
|
+
return { pruned: 0, scanned: 0, moreWork: false };
|
|
107
|
+
}
|
|
108
|
+
const references = referencedCommandIds(db);
|
|
109
|
+
let pruned = 0, scanned = 0;
|
|
110
|
+
// Visit each reference index once per chunk, rather than switch indexes per command (#3807).
|
|
111
|
+
for (let offset = 0; offset < rows.length; offset += 25) {
|
|
112
|
+
if (scanned > 0 && performance.now() - start >= 25) break;
|
|
113
|
+
const chunk = rows.slice(offset, offset + 25);
|
|
114
|
+
pruned += db.query(`WITH candidates AS MATERIALIZED (
|
|
115
|
+
SELECT id FROM commands WHERE id IN (${chunk.map(() => "?").join(",")}) AND ${ELIGIBLE}
|
|
116
|
+
AND (updated_at < ? OR (type = 'project.checkout.refresh' AND updated_at < ?))),
|
|
117
|
+
referenced(id) AS MATERIALIZED (${references})
|
|
118
|
+
DELETE FROM commands WHERE id IN (SELECT id FROM candidates EXCEPT SELECT id FROM referenced)`)
|
|
119
|
+
.run(...chunk.map(row => row.id), cutoff, refreshCutoff).changes;
|
|
120
|
+
scanned += chunk.length;
|
|
121
|
+
}
|
|
122
|
+
const last = rows[scanned - 1]!;
|
|
123
|
+
db.query(`INSERT INTO command_retention_cursor VALUES (1, ?, ?)
|
|
124
|
+
ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at, command_id = excluded.command_id`).run(last.updated_at, last.id);
|
|
125
|
+
return { pruned, scanned, moreWork: true };
|
|
126
|
+
}
|
package/src/db/command-row.ts
CHANGED
|
@@ -41,13 +41,15 @@ export interface CommandRow {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/** `slim` (#3391): listings omit `params.agentProfile` (~0.5 MB per spawn row) instead of hydrating it;
|
|
45
|
+
* `GET /api/commands/:id` returns the full row. */
|
|
46
|
+
export function rowToCommand(row: CommandRow, options: { slim?: boolean } = {}): Command {
|
|
45
47
|
return {
|
|
46
48
|
id: row.id,
|
|
47
49
|
type: row.type,
|
|
48
50
|
source: row.source,
|
|
49
51
|
target: row.target,
|
|
50
|
-
params: hydrateCommandAgentProfile(parseObject(row.params), row.agent_profile_hash),
|
|
52
|
+
params: options.slim ? withoutAgentProfile(parseObject(row.params)) : hydrateCommandAgentProfile(parseObject(row.params), row.agent_profile_hash),
|
|
51
53
|
status: row.status,
|
|
52
54
|
result: row.result ? parseObject(row.result) : undefined,
|
|
53
55
|
error: row.error ?? undefined,
|
|
@@ -78,6 +80,12 @@ export function rowToCommand(row: CommandRow): Command {
|
|
|
78
80
|
};
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
function withoutAgentProfile(params: Record<string, unknown>): Record<string, unknown> {
|
|
84
|
+
if (!("agentProfile" in params)) return params;
|
|
85
|
+
const { agentProfile: _omitted, ...rest } = params;
|
|
86
|
+
return rest;
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
function parseObject(raw: string): Record<string, unknown> {
|
|
82
90
|
try {
|
|
83
91
|
const parsed = JSON.parse(raw);
|
|
@@ -9,6 +9,8 @@ export interface CommandFilters {
|
|
|
9
9
|
dispatchableOnly?: boolean;
|
|
10
10
|
/** #2663 — oldest-first for the executor DISPATCH feed, whose window is capped. See `getCommands`. */
|
|
11
11
|
oldestFirst?: boolean;
|
|
12
|
+
/** #3391 — omit `params.agentProfile` from rows (listing views; fetch the row by id for the profile). */
|
|
13
|
+
slim?: boolean;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export function buildListCommandsQuery(filters: CommandFilters = {}): { sql: string; params: unknown[] } {
|
|
@@ -39,6 +39,10 @@ export const PROD_SAMPLED_COMMANDS_STATS: CommandsStatsProfile = {
|
|
|
39
39
|
idx_commands_target_updated: "499038 6 1 1",
|
|
40
40
|
idx_commands_correlation: "499038 5",
|
|
41
41
|
idx_commands_type_status_created: "499038 18063 6168 1 1",
|
|
42
|
+
// Undeployed (#3619): conservative full-history upper bound for terminal non-spawn rows.
|
|
43
|
+
idx_commands_terminal_retention: "499038 2 1",
|
|
44
|
+
// Undeployed (#3797): conservative refresh-history census, before the one-hour pruning drains it.
|
|
45
|
+
idx_commands_refresh_retention: "65773 2 1",
|
|
42
46
|
idx_commands_created: "499038 1",
|
|
43
47
|
idx_commands_status: "499038 134",
|
|
44
48
|
idx_commands_target: "499038 6",
|
|
@@ -78,6 +82,10 @@ export const PROD_EXACT_COMMANDS_STATS: CommandsStatsProfile = {
|
|
|
78
82
|
idx_commands_target_updated: "505748 157 1 1",
|
|
79
83
|
idx_commands_correlation: "505748 12",
|
|
80
84
|
idx_commands_type_status_created: "505748 18063 6168 1 1",
|
|
85
|
+
// Undeployed (#3619): conservative full-history upper bound for terminal non-spawn rows.
|
|
86
|
+
idx_commands_terminal_retention: "505748 2 1",
|
|
87
|
+
// Undeployed (#3797): conservative refresh-history census, before the one-hour pruning drains it.
|
|
88
|
+
idx_commands_refresh_retention: "65773 2 1",
|
|
81
89
|
idx_commands_created: "505748 2",
|
|
82
90
|
idx_commands_status: "505748 72250",
|
|
83
91
|
idx_commands_target: "505748 157",
|
|
@@ -27,6 +27,21 @@ function hostLegacyLandingRecoveryCommands(args: { target: string; afterId?: str
|
|
|
27
27
|
return rows.map((row) => getCommand(row.id)).filter((command): command is Command => command !== null);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** The newest attempt of a retried command: `baseId` is attempt 1, `baseId:<n>` attempt n (#3827). One PK range
|
|
31
|
+
* scan instead of a point read per past attempt. */
|
|
32
|
+
function latestCommandAttempt(args: { baseId: string }): { attempt: number; command: Command } | null {
|
|
33
|
+
const rows = getDb().query("SELECT id FROM commands WHERE id = ?1 OR (id > ?2 AND id < ?3)")
|
|
34
|
+
.all(args.baseId, `${args.baseId}:`, `${args.baseId};`) as Array<{ id: string }>;
|
|
35
|
+
let latest: { attempt: number; id: string } | undefined;
|
|
36
|
+
for (const { id } of rows) {
|
|
37
|
+
const suffix = id.slice(args.baseId.length + 1);
|
|
38
|
+
const attempt = id === args.baseId ? 1 : /^[1-9]\d*$/.test(suffix) ? Number(suffix) : 0;
|
|
39
|
+
if (attempt > (latest?.attempt ?? 0)) latest = { attempt, id };
|
|
40
|
+
}
|
|
41
|
+
const command = latest ? getCommand(latest.id) : null;
|
|
42
|
+
return latest && command ? { attempt: latest.attempt, command } : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
export const COMMANDS_READ_OPS = Object.freeze({
|
|
31
46
|
"commands.latestByCorrelation": (args: { type: string; correlationId: string }) => findLatestCommandByCorrelation(args.type, args.correlationId),
|
|
32
47
|
"commands.list": (args: Parameters<typeof listCommands>[0]) => listCommands(args),
|
|
@@ -39,6 +54,7 @@ export const COMMANDS_READ_OPS = Object.freeze({
|
|
|
39
54
|
return command ? { status: command.status, error: command.error, registered: command.params.registrationConfirmed === true, admission: command.admission, orchestratorId: typeof command.params.orchestratorId === "string" ? command.params.orchestratorId : undefined } : null;
|
|
40
55
|
},
|
|
41
56
|
"commands.get": (args: { id: string }): Command | null => getCommand(args.id),
|
|
57
|
+
"commands.latestAttempt": latestCommandAttempt,
|
|
42
58
|
"commands.effortChanges": (args: { agentId: string; from: number; to: number; after?: EffortChangeCursor }) => listEffortChanges(args.agentId, args.from, args.to, args.after),
|
|
43
59
|
"commands.listLegacyLandingBackfillCandidates": listLegacyLandingBackfillCandidatesBody,
|
|
44
60
|
// #2102 — the merge⟺deploy interlock drain signal, read off the loop so polling it can never add
|
|
@@ -29,5 +29,5 @@ export const IN_FLIGHT_SPAWNS_BY_PARENT_SQL = `SELECT * FROM commands INDEXED BY
|
|
|
29
29
|
/** One parent's agent.spawn commands still in flight, newest first. */
|
|
30
30
|
export function listInFlightSpawnCommands(parentId: string, limit = 500): Command[] {
|
|
31
31
|
const rows = getDb().query(IN_FLIGHT_SPAWNS_BY_PARENT_SQL).all(...SPAWN_IN_FLIGHT_STATUSES, parentId, limit) as CommandRow[];
|
|
32
|
-
return rows.map(rowToCommand);
|
|
32
|
+
return rows.map((row) => rowToCommand(row));
|
|
33
33
|
}
|
|
@@ -13,14 +13,15 @@
|
|
|
13
13
|
// `_writer_idempotency`. That result is a `ConfigEntry` whose `value` is the SAME operator data already
|
|
14
14
|
// persisted verbatim in the `config` table of the SAME database file — so this introduces NO new
|
|
15
15
|
// at-rest exposure class (unlike a signed token, which is never otherwise stored). We therefore keep
|
|
16
|
-
// the real result in the ledger
|
|
17
|
-
//
|
|
16
|
+
// the real result in the ledger, except provider credentials: the ledger is a copy outside the live row,
|
|
17
|
+
// so it masks them like config_history does (#3540, #1870 S7).
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
deleteConfigInternal,
|
|
21
21
|
setConfigInternal,
|
|
22
22
|
} from "../config-store.ts";
|
|
23
23
|
import type { ConfigEntry } from "../types.ts";
|
|
24
|
+
import { credentialFreeConfigValue } from "./provider-config-history.ts";
|
|
24
25
|
import type { WriterOpContext, WriterOpSpec } from "./write-ops.ts";
|
|
25
26
|
|
|
26
27
|
/** Structured-clone-safe args for `config.set` (H6: plain data only). `value` is arbitrary JSON. */
|
|
@@ -46,6 +47,10 @@ export const CONFIG_STORE_WRITE_OPS: Readonly<Record<string, WriterOpSpec>> = Ob
|
|
|
46
47
|
offLoop: true,
|
|
47
48
|
idempotent: false,
|
|
48
49
|
idempotencySnapshotKey: (args: SetConfigOpArgs) => JSON.stringify([args.namespace, args.key]),
|
|
50
|
+
serializeIdempotencyResult: (result: unknown) => {
|
|
51
|
+
const entry = result as ConfigEntry;
|
|
52
|
+
return { ...entry, value: credentialFreeConfigValue(entry) };
|
|
53
|
+
},
|
|
49
54
|
fn: (args: SetConfigOpArgs, ctx: WriterOpContext): ConfigEntry =>
|
|
50
55
|
setConfigInternal(args.namespace, args.key, args.value, args.updatedBy, ctx.now),
|
|
51
56
|
},
|
package/src/db/connection.ts
CHANGED
|
@@ -66,7 +66,6 @@ export function closeDb(): void {
|
|
|
66
66
|
}
|
|
67
67
|
db = undefined as unknown as Database;
|
|
68
68
|
dbPath = undefined;
|
|
69
|
-
closeFastFailConnection();
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
/**
|
|
@@ -175,156 +174,6 @@ export const openExistingDb = (path: string): Database => {
|
|
|
175
174
|
/** A read-only connection to `path` for a reader that must not share the main RW handle (#3012). */
|
|
176
175
|
export const openReadOnlyDb = (path: string): Database => new Database(path, { readonly: true });
|
|
177
176
|
|
|
178
|
-
// #1835/#1840 — a SEPARATE connection to the same database file, opened with a much shorter
|
|
179
|
-
// busy_timeout than the shared app connection, lazily created and cached by (path, timeout).
|
|
180
|
-
let fastFailConn: Database | undefined;
|
|
181
|
-
let fastFailConnPath: string | undefined;
|
|
182
|
-
let fastFailConnBusyTimeoutMs: number | undefined;
|
|
183
|
-
|
|
184
|
-
function closeFastFailConnection(): void {
|
|
185
|
-
if (fastFailConn) {
|
|
186
|
-
try { fastFailConn.close(); } catch { /* a half-open handle must not wedge the caller */ }
|
|
187
|
-
}
|
|
188
|
-
fastFailConn = undefined;
|
|
189
|
-
fastFailConnPath = undefined;
|
|
190
|
-
fastFailConnBusyTimeoutMs = undefined;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/** Forbids `fn` from returning a `PromiseLike` — see `withFastFailConnection`'s own doc for why an
|
|
194
|
-
* async callback would silently defeat the whole point of this helper. Resolves to `never` for any
|
|
195
|
-
* `T` that is (or could be) a promise, which TypeScript then rejects at the CALL SITE (the argument
|
|
196
|
-
* no longer matches `() => never`) rather than merely in a comment. */
|
|
197
|
-
type NotPromiseLike<T> = T extends PromiseLike<unknown> ? never : T;
|
|
198
|
-
|
|
199
|
-
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
200
|
-
return typeof value === "object" && value !== null && typeof (value as { then?: unknown }).then === "function";
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Run `fn` with `getDb()` temporarily pointing at a SEPARATE connection to the same database file,
|
|
205
|
-
* using `busyTimeoutMs` instead of the shared app connection's own (much longer) busy_timeout — so a
|
|
206
|
-
* write-lock conflict during `fn` fails FAST (a `SQLITE_BUSY`/`SQLITE_LOCKED` throw after
|
|
207
|
-
* `busyTimeoutMs`) instead of blocking the event loop for up to the shared connection's own budget.
|
|
208
|
-
* Built for `memory-store-health-probe.ts`'s per-scope DB cycle (#1840): a health probe under real
|
|
209
|
-
* write-lock contention should report `cannot-tell` fast, not become a multi-second freeze.
|
|
210
|
-
*
|
|
211
|
-
* LOAD-BEARING INVARIANT — `fn` MUST be fully synchronous: no `await`, no callback deferred to a
|
|
212
|
-
* later microtask/macrotask. The swap works by reassigning the SAME module-level `db` binding
|
|
213
|
-
* `getDb()` reads, for the exact duration of `fn`'s synchronous call; nothing else can observe the
|
|
214
|
-
* swapped connection only because nothing else CAN run while `fn` occupies the (single-threaded)
|
|
215
|
-
* call stack, and the shared connection is restored in `finally` before control ever returns to the
|
|
216
|
-
* event loop. This is exactly why the shared app connection's OWN busy_timeout is never touched by
|
|
217
|
-
* this helper. A caller that needs fast-fail behavior spanning a real await needs a genuinely
|
|
218
|
-
* different design (e.g. #1809/#1822's offload primitive), not this one.
|
|
219
|
-
*
|
|
220
|
-
* ENFORCED AT COMPILE TIME, backstopped (not fixed) at runtime (#1840 re-review MED-1/MED-2) — an
|
|
221
|
-
* `async fn` would still return its Promise SYNCHRONOUSLY (the function body only runs to its first
|
|
222
|
-
* `await` before returning), so a naive version of this helper would restore the shared connection in
|
|
223
|
-
* `finally` and only THEN let the callback's post-`await` continuation run — silently against the
|
|
224
|
-
* ordinary shared connection, with no error, defeating the fast-fail guarantee for exactly the call it
|
|
225
|
-
* was supposed to protect. `NotPromiseLike<T>` is the REAL protection: it rejects an `async fn` at the
|
|
226
|
-
* CALL SITE, so no caller reachable by the type checker can ever produce this shape.
|
|
227
|
-
*
|
|
228
|
-
* The runtime `isThenable` check below is NOT a second way to prevent the same outcome — it CANNOT be:
|
|
229
|
-
* by the time `fn()` has returned a thenable, that thenable's continuation is already scheduled
|
|
230
|
-
* independent of anything this function does next, so throwing here does not (and cannot) stop it
|
|
231
|
-
* from eventually running against whatever connection is active when it resumes. What the runtime
|
|
232
|
-
* check buys is FAIL-LOUD, not fail-safe: a caller that routes an async callback around the type
|
|
233
|
-
* system (a cast through `any`, for instance) gets an immediate, unmissable `TypeError` at the call
|
|
234
|
-
* site instead of a silent, delayed connection mix-up nobody notices until a later bug report. Treat a
|
|
235
|
-
* caller ever seeing this error as a real bug in that call site, not a recoverable runtime condition.
|
|
236
|
-
*
|
|
237
|
-
* Falls back to running `fn` against the ordinary shared connection for `:memory:` databases (this
|
|
238
|
-
* repo's whole test suite) or before any database is open: `bun:sqlite` gives every `:memory:` handle
|
|
239
|
-
* a DISTINCT, empty database (no shared-cache URI in use here), so a second `:memory:` connection
|
|
240
|
-
* would silently see no data at all rather than fail fast on contention — and there is no meaningful
|
|
241
|
-
* lock contention to guard against in a single-process test run in the first place. The thenable
|
|
242
|
-
* guard still applies on this path, so misuse is caught the same way regardless of environment.
|
|
243
|
-
*/
|
|
244
|
-
export function withFastFailConnection<T>(busyTimeoutMs: number, fn: () => NotPromiseLike<T>): T {
|
|
245
|
-
if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0) {
|
|
246
|
-
throw new RangeError("busyTimeoutMs must be a non-negative integer");
|
|
247
|
-
}
|
|
248
|
-
const path = getDbPath();
|
|
249
|
-
const useFastFail = Boolean(path) && path !== ":memory:";
|
|
250
|
-
let original: Database | undefined;
|
|
251
|
-
if (useFastFail) {
|
|
252
|
-
if (!fastFailConn || fastFailConnPath !== path || fastFailConnBusyTimeoutMs !== busyTimeoutMs) {
|
|
253
|
-
closeFastFailConnection();
|
|
254
|
-
const conn = new Database(path!, { readwrite: true, create: false });
|
|
255
|
-
// The live connection already established WAL/foreign_keys/etc.; only busy_timeout needs to
|
|
256
|
-
// differ, applied AFTER the shared pragmas so it overrides the ordinary default.
|
|
257
|
-
applyConnectionPragmas(conn, { ensureWal: false });
|
|
258
|
-
conn.run(`PRAGMA busy_timeout = ${busyTimeoutMs}`);
|
|
259
|
-
fastFailConn = conn;
|
|
260
|
-
fastFailConnPath = path;
|
|
261
|
-
fastFailConnBusyTimeoutMs = busyTimeoutMs;
|
|
262
|
-
}
|
|
263
|
-
original = db;
|
|
264
|
-
db = fastFailConn;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
const result = fn();
|
|
268
|
-
if (isThenable(result)) {
|
|
269
|
-
// Fail LOUD, not fail-safe — see this function's doc for why this can only make the misuse
|
|
270
|
-
// visible, not undo it: the thenable's continuation is already scheduled and will still run
|
|
271
|
-
// against whatever connection is active when it resumes, regardless of this throw.
|
|
272
|
-
throw new TypeError(
|
|
273
|
-
"withFastFailConnection: fn returned a thenable — this is a caller bug, not a recoverable " +
|
|
274
|
-
"condition. fn must be fully synchronous; an async callback's post-await continuation runs " +
|
|
275
|
-
"AFTER this call restores the shared connection, so it silently escapes the fast-fail " +
|
|
276
|
-
"guarantee even though this check caught it here. Fix the caller — do not catch this and " +
|
|
277
|
-
"continue.",
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
return result as T;
|
|
281
|
-
} finally {
|
|
282
|
-
if (useFastFail) db = original!;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/** #2712 — is the write lock free right now? One empty `BEGIN IMMEDIATE` on the fast-fail connection,
|
|
287
|
-
* so a paced sweep can step aside for another writer instead of waiting out `busy_timeout` per write.
|
|
288
|
-
* Always true for `:memory:` (no second writer can exist), like {@link withFastFailConnection}. */
|
|
289
|
-
export function writerLockAvailable(busyTimeoutMs: number): boolean {
|
|
290
|
-
try {
|
|
291
|
-
return withFastFailConnection(busyTimeoutMs, () => {
|
|
292
|
-
const conn = getDb();
|
|
293
|
-
if (conn.inTransaction) return true;
|
|
294
|
-
conn.run("BEGIN IMMEDIATE");
|
|
295
|
-
conn.run("ROLLBACK");
|
|
296
|
-
return true;
|
|
297
|
-
});
|
|
298
|
-
} catch (err) {
|
|
299
|
-
if (isSqliteBusyError(err)) return false;
|
|
300
|
-
throw err;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// SQLITE_BUSY resilience for WRITE transactions (#1428).
|
|
305
|
-
//
|
|
306
|
-
// bun:sqlite's `db.transaction(fn)()` begins DEFERRED: the write lock is only taken when the first
|
|
307
|
-
// write statement runs. A read-then-write transaction (the hot message-send path reads getAgent /
|
|
308
|
-
// idempotency / duplicate checks, THEN inserts) therefore starts as a reader and UPGRADES to a
|
|
309
|
-
// writer. If another connection holds the write lock at that instant — e.g. the maintenance-worker
|
|
310
|
-
// subprocess mid-checkpoint/prune — SQLite returns SQLITE_BUSY *immediately* and IGNORES
|
|
311
|
-
// busy_timeout, because waiting for the upgrade could deadlock two readers. That is exactly the
|
|
312
|
-
// production `database is locked` on `POST /api/messages`.
|
|
313
|
-
//
|
|
314
|
-
// `BEGIN IMMEDIATE` takes the write lock up front, so busy_timeout governs contention: a concurrent
|
|
315
|
-
// writer WAITS for the lock instead of erroring on the upgrade. Route every read-then-write
|
|
316
|
-
// transaction through `writeTransaction`; keep pure read-only transactions on the deferred default
|
|
317
|
-
// (they never upgrade, and IMMEDIATE would needlessly serialize reads).
|
|
318
|
-
//
|
|
319
|
-
// SHORT sync-block bias (#1428): `writeTransaction` makes a SINGLE immediate attempt — it never
|
|
320
|
-
// synchronously sleeps-and-retries, because that would only lengthen the main-thread stall. With
|
|
321
|
-
// busy_timeout kept low, that one attempt blocks the loop only briefly. Resilience for the rare BUSY
|
|
322
|
-
// that survives busy_timeout lives in `runWriteWithBusyRetry`, which awaits between attempts so the
|
|
323
|
-
// event loop breathes (other requests/timers run) instead of the thread spinning.
|
|
324
|
-
const SQLITE_BUSY_RETRY_ATTEMPTS = 4;
|
|
325
|
-
const SQLITE_BUSY_RETRY_BASE_MS = 15;
|
|
326
|
-
const SQLITE_BUSY_RETRY_MAX_MS = 120;
|
|
327
|
-
|
|
328
177
|
/** True for the transient lock-contention codes SQLite raises under concurrency. */
|
|
329
178
|
export function isSqliteBusyError(err: unknown): boolean {
|
|
330
179
|
if (!err || typeof err !== "object") return false;
|
|
@@ -337,13 +186,7 @@ export function isSqliteBusyError(err: unknown): boolean {
|
|
|
337
186
|
return typeof message === "string" && /database (is|table is) locked|SQLITE_BUSY|SQLITE_LOCKED/i.test(message);
|
|
338
187
|
}
|
|
339
188
|
|
|
340
|
-
|
|
341
|
-
* Run `fn` inside a single `BEGIN IMMEDIATE` transaction. The write lock is taken at BEGIN, so
|
|
342
|
-
* busy_timeout (short, by design) governs contention and the loop stalls only briefly. On a BUSY
|
|
343
|
-
* that survives busy_timeout this THROWS — the caller on the hot path wraps it in
|
|
344
|
-
* `runWriteWithBusyRetry` for an async, loop-yielding retry; other callers surface the error as
|
|
345
|
-
* before. Keep the transaction body small: it runs synchronously with the write lock held.
|
|
346
|
-
*/
|
|
189
|
+
// Writer-thread transaction context; boot and in-memory fixtures use the same bodies.
|
|
347
190
|
declare const WriteTransactionContextBrand: unique symbol;
|
|
348
191
|
|
|
349
192
|
/** Unforgeable caller capability proving code is executing synchronously inside `writeTransaction`.
|
|
@@ -413,30 +256,6 @@ export function inWriteTransaction<T>(fn: (tx: WriteTransactionContext, db: Data
|
|
|
413
256
|
return active ? fn(active, getDb()) : writeTransaction((tx) => fn(tx, getDb()));
|
|
414
257
|
}
|
|
415
258
|
|
|
416
|
-
/**
|
|
417
|
-
* Hot-path write with a bounded, ASYNC retry around SQLITE_BUSY (#1428). Each attempt is one short
|
|
418
|
-
* synchronous `writeTransaction`; between attempts we `await` a jittered backoff via setTimeout,
|
|
419
|
-
* which YIELDS the event loop so other work runs and the contending writer (another process, a
|
|
420
|
-
* checkpoint) can release the lock — the opposite of a long synchronous busy_timeout spin. A rare
|
|
421
|
-
* contention spike thus degrades to a slightly slower request instead of a 500 or a multi-second
|
|
422
|
-
* main-thread stall.
|
|
423
|
-
*/
|
|
424
|
-
export async function runWriteWithBusyRetry<T>(fn: () => T, attempts: number = SQLITE_BUSY_RETRY_ATTEMPTS): Promise<T> {
|
|
425
|
-
for (let attempt = 1; ; attempt++) {
|
|
426
|
-
try {
|
|
427
|
-
return writeTransaction(fn);
|
|
428
|
-
} catch (err) {
|
|
429
|
-
if (attempt < attempts && isSqliteBusyError(err)) {
|
|
430
|
-
const cap = Math.min(SQLITE_BUSY_RETRY_MAX_MS, SQLITE_BUSY_RETRY_BASE_MS * 2 ** (attempt - 1));
|
|
431
|
-
const delay = cap / 2 + Math.random() * (cap / 2);
|
|
432
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
throw err;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
259
|
// Slow-query log. Off unless AGENT_RELAY_DB_SLOW_MS is set (>0). Wraps the
|
|
441
260
|
// execution of the heavy dynamic queries so the next slow query surfaces before
|
|
442
261
|
// it wedges something (see issue #197 — the 8.6s reply-obligation query).
|